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

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.
Files changed (56) hide show
  1. package/lib/shipload.d.ts +618 -353
  2. package/lib/shipload.js +2250 -1059
  3. package/lib/shipload.js.map +1 -1
  4. package/lib/shipload.m.js +2193 -1044
  5. package/lib/shipload.m.js.map +1 -1
  6. package/package.json +2 -2
  7. package/src/capabilities/modules.ts +3 -0
  8. package/src/capabilities/storage.ts +1 -1
  9. package/src/contracts/platform.ts +13 -1
  10. package/src/contracts/server.ts +227 -282
  11. package/src/data/capabilities.ts +5 -330
  12. package/src/data/capability-formulas.ts +70 -0
  13. package/src/data/catalog.ts +0 -5
  14. package/src/data/colors.ts +2 -16
  15. package/src/data/entities.json +46 -10
  16. package/src/data/item-ids.ts +4 -1
  17. package/src/data/items.json +264 -0
  18. package/src/data/metadata.ts +63 -1
  19. package/src/data/recipes-runtime.ts +1 -0
  20. package/src/data/recipes.json +139 -11
  21. package/src/derivation/capability-mappings.ts +122 -0
  22. package/src/derivation/index.ts +1 -0
  23. package/src/derivation/resources.ts +116 -37
  24. package/src/derivation/stats.ts +1 -2
  25. package/src/entities/container.ts +25 -10
  26. package/src/entities/extractor.ts +144 -0
  27. package/src/entities/factory.ts +135 -0
  28. package/src/entities/gamestate.ts +0 -9
  29. package/src/entities/makers.ts +130 -20
  30. package/src/entities/nexus.ts +29 -0
  31. package/src/entities/ship-deploy.ts +114 -56
  32. package/src/entities/ship.ts +17 -0
  33. package/src/entities/slot-multiplier.ts +21 -0
  34. package/src/entities/warehouse.ts +20 -3
  35. package/src/errors.ts +10 -13
  36. package/src/index-module.ts +81 -26
  37. package/src/managers/actions.ts +53 -80
  38. package/src/managers/entities.ts +65 -17
  39. package/src/managers/locations.ts +2 -20
  40. package/src/nft/atomicdata.ts +128 -0
  41. package/src/nft/description.ts +41 -7
  42. package/src/nft/index.ts +1 -0
  43. package/src/resolution/resolve-item.ts +17 -9
  44. package/src/scheduling/accessor.ts +4 -0
  45. package/src/scheduling/projection.ts +10 -2
  46. package/src/scheduling/schedule.ts +15 -1
  47. package/src/scheduling/task-cargo.ts +47 -0
  48. package/src/subscriptions/connection.ts +50 -2
  49. package/src/subscriptions/manager.ts +84 -4
  50. package/src/subscriptions/mappers.ts +5 -1
  51. package/src/subscriptions/types.ts +2 -2
  52. package/src/travel/travel.ts +61 -2
  53. package/src/types/entity-traits.ts +103 -1
  54. package/src/types.ts +11 -1
  55. package/src/utils/cargo.ts +27 -0
  56. package/src/utils/system.ts +25 -24
@@ -0,0 +1,122 @@
1
+ import {SLOT_FORMULAS, type SlotConsumerKind} from '../data/capability-formulas'
2
+ import {getStatDefinitions, type StatDefinition} from './stats'
3
+ import {
4
+ getRecipe,
5
+ type Recipe,
6
+ type RecipeInput,
7
+ type RecipeInputCategory,
8
+ } from '../data/recipes-runtime'
9
+ import {
10
+ ITEM_ENGINE_T1,
11
+ ITEM_EXTRACTOR_T1_PACKED,
12
+ ITEM_GENERATOR_T1,
13
+ ITEM_GATHERER_T1,
14
+ ITEM_LOADER_T1,
15
+ ITEM_CRAFTER_T1,
16
+ ITEM_STORAGE_T1,
17
+ ITEM_HAULER_T1,
18
+ ITEM_WARP_T1,
19
+ ITEM_SHIP_T1_PACKED,
20
+ ITEM_CONTAINER_T1_PACKED,
21
+ ITEM_WAREHOUSE_T1_PACKED,
22
+ ITEM_CONTAINER_T2_PACKED,
23
+ } from '../data/item-ids'
24
+ import type {StatMapping} from '../data/capabilities'
25
+
26
+ export const KIND_TO_ITEM_ID: Record<SlotConsumerKind, number> = {
27
+ engine: ITEM_ENGINE_T1,
28
+ generator: ITEM_GENERATOR_T1,
29
+ gatherer: ITEM_GATHERER_T1,
30
+ loader: ITEM_LOADER_T1,
31
+ crafter: ITEM_CRAFTER_T1,
32
+ storage: ITEM_STORAGE_T1,
33
+ hauler: ITEM_HAULER_T1,
34
+ warp: ITEM_WARP_T1,
35
+ 'ship-t1': ITEM_SHIP_T1_PACKED,
36
+ 'container-t1': ITEM_CONTAINER_T1_PACKED,
37
+ 'warehouse-t1': ITEM_WAREHOUSE_T1_PACKED,
38
+ 'extractor-t1': ITEM_EXTRACTOR_T1_PACKED,
39
+ 'container-t2': ITEM_CONTAINER_T2_PACKED,
40
+ }
41
+
42
+ function isCategoryInput(input: RecipeInput): input is RecipeInputCategory {
43
+ return 'category' in input
44
+ }
45
+
46
+ /**
47
+ * Walk a recipe's slot source down to the raw category stat that ultimately
48
+ * lands in that slot. Returns the StatDefinition or undefined if the trace
49
+ * dead-ends (unknown sub-component, missing slot, etc.).
50
+ *
51
+ * Multi-source sub-slots collapse to `sources[0]`; top-level multi-source slots
52
+ * are expanded by the caller (`deriveStatMappings`).
53
+ */
54
+ function traceToRawCategoryStat(
55
+ recipe: Recipe,
56
+ source: {inputIndex: number; statIndex: number},
57
+ visited: Set<number> = new Set()
58
+ ): StatDefinition | undefined {
59
+ const input = recipe.inputs[source.inputIndex]
60
+ if (!input) return undefined
61
+ if (isCategoryInput(input)) {
62
+ const defs = getStatDefinitions(input.category)
63
+ return defs[source.statIndex]
64
+ }
65
+ if (visited.has(input.itemId)) return undefined
66
+ const subRecipe = getRecipe(input.itemId)
67
+ if (!subRecipe) return undefined
68
+ const subSlot = subRecipe.statSlots[source.statIndex]
69
+ if (!subSlot) return undefined
70
+ const subSource = subSlot.sources[0]
71
+ if (!subSource) return undefined
72
+ const nextVisited = new Set(visited)
73
+ nextVisited.add(input.itemId)
74
+ return traceToRawCategoryStat(subRecipe, subSource, nextVisited)
75
+ }
76
+
77
+ let cached: StatMapping[] | undefined
78
+
79
+ export function deriveStatMappings(): StatMapping[] {
80
+ if (cached) return cached
81
+ const out: StatMapping[] = []
82
+ const seen = new Set<string>()
83
+ for (const [kind, slots] of Object.entries(SLOT_FORMULAS) as [
84
+ SlotConsumerKind,
85
+ Record<number, {capability: string; attribute: string}>,
86
+ ][]) {
87
+ const itemId = KIND_TO_ITEM_ID[kind]
88
+ const recipe = getRecipe(itemId)
89
+ if (!recipe) continue
90
+ for (const [slotIdxStr, consumer] of Object.entries(slots)) {
91
+ const slotIdx = Number(slotIdxStr)
92
+ const slot = recipe.statSlots[slotIdx]
93
+ if (!slot) continue
94
+ for (const source of slot.sources) {
95
+ const stat = traceToRawCategoryStat(recipe, source)
96
+ if (!stat) continue
97
+ const key = `${stat.label}|${consumer.capability}|${consumer.attribute}`
98
+ if (seen.has(key)) continue
99
+ seen.add(key)
100
+ out.push({
101
+ stat: stat.label,
102
+ capability: consumer.capability,
103
+ attribute: consumer.attribute,
104
+ })
105
+ }
106
+ }
107
+ }
108
+ cached = out
109
+ return out
110
+ }
111
+
112
+ export function getStatMappings(): StatMapping[] {
113
+ return deriveStatMappings()
114
+ }
115
+
116
+ export function getStatMappingsForStat(stat: string): StatMapping[] {
117
+ return deriveStatMappings().filter((m) => m.stat === stat)
118
+ }
119
+
120
+ export function getStatMappingsForCapability(capability: string): StatMapping[] {
121
+ return deriveStatMappings().filter((m) => m.capability === capability)
122
+ }
@@ -7,6 +7,7 @@ export {
7
7
  getEligibleResources,
8
8
  getResourceWeight,
9
9
  getLocationCandidates,
10
+ getLocationProfile,
10
11
  getDepthThreshold,
11
12
  getResourceTier,
12
13
  DEPTH_THRESHOLD_T1,
@@ -1,10 +1,16 @@
1
1
  import {getItem} from '../data/catalog'
2
+ import {LocationType} from '../types'
2
3
 
3
4
  export const DEPTH_THRESHOLD_T1 = 0
4
- export const DEPTH_THRESHOLD_T2 = 2000
5
- export const DEPTH_THRESHOLD_T3 = 10000
6
- export const DEPTH_THRESHOLD_T4 = 30000
7
- export const DEPTH_THRESHOLD_T5 = 55000
5
+ export const DEPTH_THRESHOLD_T2 = 1500
6
+ export const DEPTH_THRESHOLD_T3 = 5000
7
+ export const DEPTH_THRESHOLD_T4 = 12000
8
+ export const DEPTH_THRESHOLD_T5 = 22000
9
+ export const DEPTH_THRESHOLD_T6 = 32000
10
+ export const DEPTH_THRESHOLD_T7 = 42000
11
+ export const DEPTH_THRESHOLD_T8 = 50000
12
+ export const DEPTH_THRESHOLD_T9 = 57000
13
+ export const DEPTH_THRESHOLD_T10 = 63000
8
14
 
9
15
  export const LOCATION_MIN_DEPTH = 500
10
16
  export const LOCATION_MAX_DEPTH = 65535
@@ -18,19 +24,22 @@ export const PLANET_SUBTYPE_ICY = 3
18
24
  export const PLANET_SUBTYPE_OCEAN = 4
19
25
  export const PLANET_SUBTYPE_INDUSTRIAL = 5
20
26
 
27
+ const DEPTH_THRESHOLD_TABLE = [
28
+ DEPTH_THRESHOLD_T1,
29
+ DEPTH_THRESHOLD_T2,
30
+ DEPTH_THRESHOLD_T3,
31
+ DEPTH_THRESHOLD_T4,
32
+ DEPTH_THRESHOLD_T5,
33
+ DEPTH_THRESHOLD_T6,
34
+ DEPTH_THRESHOLD_T7,
35
+ DEPTH_THRESHOLD_T8,
36
+ DEPTH_THRESHOLD_T9,
37
+ DEPTH_THRESHOLD_T10,
38
+ ]
39
+
21
40
  export function getDepthThreshold(tier: number): number {
22
- switch (tier) {
23
- case 1:
24
- return DEPTH_THRESHOLD_T1
25
- case 2:
26
- return DEPTH_THRESHOLD_T2
27
- case 3:
28
- return DEPTH_THRESHOLD_T3
29
- case 4:
30
- return DEPTH_THRESHOLD_T4
31
- default:
32
- return DEPTH_THRESHOLD_T5
33
- }
41
+ if (tier < 1 || tier > 10) return 65535
42
+ return DEPTH_THRESHOLD_TABLE[tier - 1]
34
43
  }
35
44
 
36
45
  export function getResourceTier(itemId: number): number {
@@ -46,9 +55,9 @@ export function getResourceWeight(itemId: number, stratum: number): number {
46
55
 
47
56
  switch (tier) {
48
57
  case 1:
49
- if (stratum < 2000) return 100
50
- if (stratum < 10000) return 80
51
- if (stratum < 30000) return 50
58
+ if (stratum < DEPTH_THRESHOLD_T2) return 100
59
+ if (stratum < DEPTH_THRESHOLD_T3) return 80
60
+ if (stratum < DEPTH_THRESHOLD_T4) return 50
52
61
  return 30
53
62
  case 2:
54
63
  if (depthAbove < 3000) return 40
@@ -67,37 +76,107 @@ export function getResourceWeight(itemId: number, stratum: number): number {
67
76
  }
68
77
  }
69
78
 
70
- const ASTEROID_RESOURCES = [101, 102, 103, 201, 202]
71
- const NEBULA_RESOURCES = [202, 203, 301, 302, 303]
72
- const GAS_GIANT_RESOURCES = [301, 302, 303, 401, 501]
73
- const ROCKY_RESOURCES = [101, 102, 103, 401, 402, 403, 503]
74
- const TERRESTRIAL_RESOURCES = [201, 202, 401, 402, 501, 502, 503]
75
- const ICY_RESOURCES = [101, 301, 302, 401, 403, 501, 502]
76
- const OCEAN_RESOURCES = [201, 203, 301, 303, 501, 502, 503]
77
- const INDUSTRIAL_RESOURCES = [101, 102, 103, 201, 203, 402, 403]
79
+ const RESOURCE_ORE = 0
80
+ const RESOURCE_GAS = 1
81
+ const RESOURCE_REGOLITH = 2
82
+ const RESOURCE_BIOMASS = 3
83
+ const RESOURCE_CRYSTAL = 4
78
84
 
79
- export function getLocationCandidates(locationType: number, subtype: number): number[] {
80
- if (locationType === 2) return ASTEROID_RESOURCES
81
- if (locationType === 3) return NEBULA_RESOURCES
82
- if (locationType === 1) {
85
+ interface LocationProfileEntry {
86
+ category: number
87
+ maxTier: number
88
+ }
89
+
90
+ function categoryBaseId(category: number): number {
91
+ switch (category) {
92
+ case RESOURCE_ORE:
93
+ return 100
94
+ case RESOURCE_CRYSTAL:
95
+ return 200
96
+ case RESOURCE_GAS:
97
+ return 300
98
+ case RESOURCE_REGOLITH:
99
+ return 400
100
+ case RESOURCE_BIOMASS:
101
+ return 500
102
+ default:
103
+ return 0
104
+ }
105
+ }
106
+
107
+ function resourceId(category: number, tier: number): number {
108
+ return categoryBaseId(category) + tier
109
+ }
110
+
111
+ export function getLocationProfile(locationType: number, subtype: number): LocationProfileEntry[] {
112
+ if (locationType === LocationType.ASTEROID) {
113
+ return [
114
+ {category: RESOURCE_ORE, maxTier: 5},
115
+ {category: RESOURCE_CRYSTAL, maxTier: 5},
116
+ ]
117
+ }
118
+ if (locationType === LocationType.NEBULA) {
119
+ return [
120
+ {category: RESOURCE_GAS, maxTier: 5},
121
+ {category: RESOURCE_REGOLITH, maxTier: 5},
122
+ ]
123
+ }
124
+ if (locationType === LocationType.ICE_FIELD) {
125
+ return [
126
+ {category: RESOURCE_GAS, maxTier: 5},
127
+ {category: RESOURCE_BIOMASS, maxTier: 5},
128
+ ]
129
+ }
130
+ if (locationType === LocationType.PLANET) {
83
131
  switch (subtype) {
84
132
  case PLANET_SUBTYPE_GAS_GIANT:
85
- return GAS_GIANT_RESOURCES
133
+ return [
134
+ {category: RESOURCE_GAS, maxTier: 10},
135
+ {category: RESOURCE_CRYSTAL, maxTier: 3},
136
+ ]
86
137
  case PLANET_SUBTYPE_ROCKY:
87
- return ROCKY_RESOURCES
138
+ return [
139
+ {category: RESOURCE_REGOLITH, maxTier: 10},
140
+ {category: RESOURCE_ORE, maxTier: 3},
141
+ ]
88
142
  case PLANET_SUBTYPE_TERRESTRIAL:
89
- return TERRESTRIAL_RESOURCES
143
+ return [
144
+ {category: RESOURCE_ORE, maxTier: 10},
145
+ {category: RESOURCE_BIOMASS, maxTier: 3},
146
+ ]
90
147
  case PLANET_SUBTYPE_ICY:
91
- return ICY_RESOURCES
148
+ return [
149
+ {category: RESOURCE_CRYSTAL, maxTier: 10},
150
+ {category: RESOURCE_REGOLITH, maxTier: 3},
151
+ ]
92
152
  case PLANET_SUBTYPE_OCEAN:
93
- return OCEAN_RESOURCES
153
+ return [
154
+ {category: RESOURCE_BIOMASS, maxTier: 10},
155
+ {category: RESOURCE_GAS, maxTier: 3},
156
+ ]
94
157
  case PLANET_SUBTYPE_INDUSTRIAL:
95
- return INDUSTRIAL_RESOURCES
158
+ return [
159
+ {category: RESOURCE_ORE, maxTier: 3},
160
+ {category: RESOURCE_CRYSTAL, maxTier: 3},
161
+ {category: RESOURCE_REGOLITH, maxTier: 3},
162
+ {category: RESOURCE_BIOMASS, maxTier: 3},
163
+ ]
96
164
  }
97
165
  }
98
166
  return []
99
167
  }
100
168
 
169
+ export function getLocationCandidates(locationType: number, subtype: number): number[] {
170
+ const profile = getLocationProfile(locationType, subtype)
171
+ const ids: number[] = []
172
+ for (const {category, maxTier} of profile) {
173
+ for (let tier = 1; tier <= maxTier; tier++) {
174
+ ids.push(resourceId(category, tier))
175
+ }
176
+ }
177
+ return ids
178
+ }
179
+
101
180
  export function getEligibleResources(
102
181
  locationType: number,
103
182
  subtype: number,
@@ -25,8 +25,7 @@ const ORE_STATS: StatDefinition[] = [
25
25
  key: 'density',
26
26
  label: 'Density',
27
27
  abbreviation: 'DEN',
28
- purpose: 'Mass per unit',
29
- inverted: true,
28
+ purpose: 'Structural integrity — higher rolls produce lighter hulls',
30
29
  },
31
30
  ]
32
31
 
@@ -1,6 +1,7 @@
1
1
  import {UInt64, type UInt64Type} from '@wharfkit/antelope'
2
2
  import {ServerContract} from '../contracts'
3
3
  import type {CoordinatesType} from '../types'
4
+ import {type FloatPosition, getInterpolatedPosition} from '../travel/travel'
4
5
  import {Location} from './location'
5
6
  import {ScheduleAccessor} from '../scheduling/accessor'
6
7
  import * as schedule from '../scheduling/schedule'
@@ -24,6 +25,14 @@ export class Container extends ServerContract.Types.entity_info {
24
25
  return this.entity_name
25
26
  }
26
27
 
28
+ get entityClass(): 'orbital' {
29
+ return 'orbital'
30
+ }
31
+
32
+ get canUndeploy(): boolean {
33
+ return true
34
+ }
35
+
27
36
  get sched(): ScheduleAccessor {
28
37
  this._sched ??= new ScheduleAccessor(this)
29
38
  return this._sched
@@ -33,6 +42,12 @@ export class Container extends ServerContract.Types.entity_info {
33
42
  return this.is_idle
34
43
  }
35
44
 
45
+ interpolatedPositionAt(now: Date): FloatPosition {
46
+ const taskIndex = this.sched.currentTaskIndex(now)
47
+ const progress = this.sched.currentTaskProgressFloat(now)
48
+ return getInterpolatedPosition(this, taskIndex, progress)
49
+ }
50
+
36
51
  isLoading(now: Date): boolean {
37
52
  return schedule.isLoading(this, now)
38
53
  }
@@ -75,12 +90,12 @@ export function computeContainerCapabilities(stats: Record<string, number>): {
75
90
  hullmass: number
76
91
  capacity: number
77
92
  } {
78
- const density = stats['density'] ?? 500
79
- const strength = stats['strength'] ?? 500
80
- const hardness = stats['hardness'] ?? 500
81
- const saturation = stats['saturation'] ?? 500
93
+ const density = stats.density
94
+ const strength = stats.strength
95
+ const hardness = stats.hardness
96
+ const saturation = stats.saturation
82
97
 
83
- const hullmass = 25000 + 75 * density
98
+ const hullmass = 100000 - 75 * density
84
99
 
85
100
  const statSum = strength + hardness + saturation
86
101
  const exponent = statSum / 2997
@@ -93,12 +108,12 @@ export function computeContainerT2Capabilities(stats: Record<string, number>): {
93
108
  hullmass: number
94
109
  capacity: number
95
110
  } {
96
- const strength = stats['strength'] ?? 0
97
- const density = stats['density'] ?? 0
98
- const hardness = stats['hardness'] ?? 0
99
- const saturation = stats['saturation'] ?? 0
111
+ const strength = stats.strength
112
+ const density = stats.density
113
+ const hardness = stats.hardness
114
+ const saturation = stats.saturation
100
115
 
101
- const hullmass = 20000 + 50 * density
116
+ const hullmass = 70000 - 50 * density
102
117
 
103
118
  const statSum = strength + hardness + saturation
104
119
  const exponent = statSum / 2500
@@ -0,0 +1,144 @@
1
+ import {UInt64, type UInt64Type} from '@wharfkit/antelope'
2
+ import {ServerContract} from '../contracts'
3
+ import type {CoordinatesType} from '../types'
4
+ import {Location} from './location'
5
+ import {ScheduleAccessor} from '../scheduling/accessor'
6
+ import {InventoryAccessor} from './inventory-accessor'
7
+ import type {EntityInventory} from './entity-inventory'
8
+ import type {PackedModuleInput} from './ship'
9
+ import {decodeCraftedItemStats} from '../derivation/crafting'
10
+ import {getModuleCapabilityType, MODULE_GATHERER, MODULE_GENERATOR} from '../capabilities/modules'
11
+ import {computeGathererCapabilities, computeGeneratorCapabilities} from './ship-deploy'
12
+ import {applySlotMultiplier, clampUint16, getSlotAmp, type InstalledModule} from './slot-multiplier'
13
+ import type {EntitySlot} from '../data/recipes-runtime'
14
+ import {getItem} from '../data/catalog'
15
+
16
+ export interface ExtractorStateInput {
17
+ id: UInt64Type
18
+ owner: string
19
+ name: string
20
+ coordinates: CoordinatesType | {x: number; y: number; z?: number}
21
+ hullmass?: number
22
+ capacity?: number
23
+ energy?: number
24
+ modules?: PackedModuleInput[]
25
+ schedule?: ServerContract.Types.schedule
26
+ cargo?: ServerContract.Types.cargo_item[]
27
+ }
28
+
29
+ export class Extractor extends ServerContract.Types.entity_info {
30
+ private _sched?: ScheduleAccessor
31
+ private _inv?: InventoryAccessor
32
+
33
+ get name(): string {
34
+ return this.entity_name
35
+ }
36
+
37
+ get entityClass(): 'planetary' {
38
+ return 'planetary'
39
+ }
40
+
41
+ get canDemolish(): boolean {
42
+ return true
43
+ }
44
+
45
+ get inv(): InventoryAccessor {
46
+ this._inv ??= new InventoryAccessor(this)
47
+ return this._inv
48
+ }
49
+
50
+ get inventory(): EntityInventory[] {
51
+ return this.inv.items
52
+ }
53
+
54
+ get sched(): ScheduleAccessor {
55
+ this._sched ??= new ScheduleAccessor(this)
56
+ return this._sched
57
+ }
58
+
59
+ get isIdle(): boolean {
60
+ return this.is_idle
61
+ }
62
+
63
+ get location(): Location {
64
+ return Location.from(this.coordinates)
65
+ }
66
+
67
+ get totalCargoMass(): UInt64 {
68
+ return this.inv.totalMass
69
+ }
70
+
71
+ get maxCapacity(): UInt64 {
72
+ return UInt64.from(this.capacity)
73
+ }
74
+
75
+ get availableCapacity(): UInt64 {
76
+ const cargo = this.totalCargoMass
77
+ return cargo.gte(this.maxCapacity) ? UInt64.from(0) : this.maxCapacity.subtracting(cargo)
78
+ }
79
+
80
+ get isFull(): boolean {
81
+ return this.totalCargoMass.gte(this.maxCapacity)
82
+ }
83
+
84
+ get totalMass(): UInt64 {
85
+ const hull = this.hullmass ? UInt64.from(this.hullmass) : UInt64.from(0)
86
+ return hull.adding(this.totalCargoMass)
87
+ }
88
+ }
89
+
90
+ export interface ExtractorCapabilities {
91
+ generator?: {capacity: number; recharge: number}
92
+ gatherer?: {yield: number; drain: number; depth: number; speed: number}
93
+ }
94
+
95
+ export function computeExtractorCapabilities(
96
+ modules: InstalledModule[],
97
+ layout: EntitySlot[]
98
+ ): ExtractorCapabilities {
99
+ const out: ExtractorCapabilities = {}
100
+
101
+ const genModules = modules.filter((m) => getModuleCapabilityType(m.itemId) === MODULE_GENERATOR)
102
+ if (genModules.length > 0) {
103
+ let totalCapacity = 0
104
+ let totalRecharge = 0
105
+ for (const m of genModules) {
106
+ const caps = computeGeneratorCapabilities(decodeCraftedItemStats(m.itemId, m.stats))
107
+ const amp = getSlotAmp(layout, m.slotIndex)
108
+ totalCapacity += applySlotMultiplier(caps.capacity, amp)
109
+ totalRecharge += applySlotMultiplier(caps.recharge, amp)
110
+ }
111
+ out.generator = {
112
+ capacity: clampUint16(totalCapacity),
113
+ recharge: clampUint16(totalRecharge),
114
+ }
115
+ }
116
+
117
+ const gathModules = modules.filter((m) => getModuleCapabilityType(m.itemId) === MODULE_GATHERER)
118
+ if (gathModules.length > 0) {
119
+ let totalYield = 0
120
+ let totalDrain = 0
121
+ let maxDepth = 0
122
+ let totalSpeed = 0
123
+ for (const m of gathModules) {
124
+ const tier = getItem(m.itemId).tier
125
+ const caps = computeGathererCapabilities(
126
+ decodeCraftedItemStats(m.itemId, m.stats),
127
+ tier
128
+ )
129
+ const amp = getSlotAmp(layout, m.slotIndex)
130
+ totalYield += applySlotMultiplier(caps.yield, amp)
131
+ totalDrain += caps.drain
132
+ if (caps.depth > maxDepth) maxDepth = caps.depth
133
+ totalSpeed += applySlotMultiplier(caps.speed, amp)
134
+ }
135
+ out.gatherer = {
136
+ yield: clampUint16(totalYield),
137
+ drain: totalDrain,
138
+ depth: maxDepth,
139
+ speed: clampUint16(totalSpeed),
140
+ }
141
+ }
142
+
143
+ return out
144
+ }
@@ -0,0 +1,135 @@
1
+ import {UInt64, type UInt64Type} from '@wharfkit/antelope'
2
+ import {ServerContract} from '../contracts'
3
+ import type {CoordinatesType} from '../types'
4
+ import {Location} from './location'
5
+ import {ScheduleAccessor} from '../scheduling/accessor'
6
+ import {InventoryAccessor} from './inventory-accessor'
7
+ import type {EntityInventory} from './entity-inventory'
8
+ import type {PackedModuleInput} from './ship'
9
+ import {decodeCraftedItemStats} from '../derivation/crafting'
10
+ import {getModuleCapabilityType, MODULE_CRAFTER, MODULE_GENERATOR} from '../capabilities/modules'
11
+ import {computeCrafterCapabilities, computeGeneratorCapabilities} from './ship-deploy'
12
+ import {applySlotMultiplier, clampUint16, getSlotAmp, type InstalledModule} from './slot-multiplier'
13
+ import type {EntitySlot} from '../data/recipes-runtime'
14
+
15
+ export interface FactoryStateInput {
16
+ id: UInt64Type
17
+ owner: string
18
+ name: string
19
+ coordinates: CoordinatesType | {x: number; y: number; z?: number}
20
+ hullmass?: number
21
+ capacity?: number
22
+ energy?: number
23
+ modules?: PackedModuleInput[]
24
+ schedule?: ServerContract.Types.schedule
25
+ cargo?: ServerContract.Types.cargo_item[]
26
+ }
27
+
28
+ export class Factory extends ServerContract.Types.entity_info {
29
+ private _sched?: ScheduleAccessor
30
+ private _inv?: InventoryAccessor
31
+
32
+ get name(): string {
33
+ return this.entity_name
34
+ }
35
+
36
+ get entityClass(): 'planetary' {
37
+ return 'planetary'
38
+ }
39
+
40
+ get canDemolish(): boolean {
41
+ return true
42
+ }
43
+
44
+ get inv(): InventoryAccessor {
45
+ this._inv ??= new InventoryAccessor(this)
46
+ return this._inv
47
+ }
48
+
49
+ get inventory(): EntityInventory[] {
50
+ return this.inv.items
51
+ }
52
+
53
+ get sched(): ScheduleAccessor {
54
+ this._sched ??= new ScheduleAccessor(this)
55
+ return this._sched
56
+ }
57
+
58
+ get isIdle(): boolean {
59
+ return this.is_idle
60
+ }
61
+
62
+ get location(): Location {
63
+ return Location.from(this.coordinates)
64
+ }
65
+
66
+ get totalCargoMass(): UInt64 {
67
+ return this.inv.totalMass
68
+ }
69
+
70
+ get maxCapacity(): UInt64 {
71
+ return UInt64.from(this.capacity)
72
+ }
73
+
74
+ get availableCapacity(): UInt64 {
75
+ const cargo = this.totalCargoMass
76
+ return cargo.gte(this.maxCapacity) ? UInt64.from(0) : this.maxCapacity.subtracting(cargo)
77
+ }
78
+
79
+ get isFull(): boolean {
80
+ return this.totalCargoMass.gte(this.maxCapacity)
81
+ }
82
+
83
+ get totalMass(): UInt64 {
84
+ const hull = this.hullmass ? UInt64.from(this.hullmass) : UInt64.from(0)
85
+ return hull.adding(this.totalCargoMass)
86
+ }
87
+ }
88
+
89
+ export interface FactoryCapabilities {
90
+ generator?: {capacity: number; recharge: number}
91
+ crafter?: {speed: number; drain: number}
92
+ }
93
+
94
+ export function computeFactoryCapabilities(
95
+ modules: InstalledModule[],
96
+ layout: EntitySlot[]
97
+ ): FactoryCapabilities {
98
+ const out: FactoryCapabilities = {}
99
+
100
+ const genModules = modules.filter((m) => getModuleCapabilityType(m.itemId) === MODULE_GENERATOR)
101
+ if (genModules.length > 0) {
102
+ let totalCapacity = 0
103
+ let totalRecharge = 0
104
+ for (const m of genModules) {
105
+ const caps = computeGeneratorCapabilities(decodeCraftedItemStats(m.itemId, m.stats))
106
+ const amp = getSlotAmp(layout, m.slotIndex)
107
+ totalCapacity += applySlotMultiplier(caps.capacity, amp)
108
+ totalRecharge += applySlotMultiplier(caps.recharge, amp)
109
+ }
110
+ out.generator = {
111
+ capacity: clampUint16(totalCapacity),
112
+ recharge: clampUint16(totalRecharge),
113
+ }
114
+ }
115
+
116
+ const crafterModules = modules.filter(
117
+ (m) => getModuleCapabilityType(m.itemId) === MODULE_CRAFTER
118
+ )
119
+ if (crafterModules.length > 0) {
120
+ let totalSpeed = 0
121
+ let totalDrain = 0
122
+ for (const m of crafterModules) {
123
+ const caps = computeCrafterCapabilities(decodeCraftedItemStats(m.itemId, m.stats))
124
+ const amp = getSlotAmp(layout, m.slotIndex)
125
+ totalSpeed += applySlotMultiplier(caps.speed, amp)
126
+ totalDrain += caps.drain
127
+ }
128
+ out.crafter = {
129
+ speed: clampUint16(totalSpeed),
130
+ drain: totalDrain,
131
+ }
132
+ }
133
+
134
+ return out
135
+ }