@shipload/sdk 1.0.0-next.6 → 1.0.0-next.60

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 (155) hide show
  1. package/lib/scan.d.ts +52 -0
  2. package/lib/scan.js +162 -0
  3. package/lib/scan.js.map +1 -0
  4. package/lib/scan.m.js +152 -0
  5. package/lib/scan.m.js.map +1 -0
  6. package/lib/shipload.d.ts +3589 -1363
  7. package/lib/shipload.js +15700 -5454
  8. package/lib/shipload.js.map +1 -1
  9. package/lib/shipload.m.js +15423 -5402
  10. package/lib/shipload.m.js.map +1 -1
  11. package/lib/testing.d.ts +1173 -0
  12. package/lib/testing.js +4947 -0
  13. package/lib/testing.js.map +1 -0
  14. package/lib/testing.m.js +4941 -0
  15. package/lib/testing.m.js.map +1 -0
  16. package/package.json +20 -2
  17. package/src/capabilities/craftable.test.ts +82 -0
  18. package/src/capabilities/craftable.ts +82 -0
  19. package/src/capabilities/crafting.test.ts +35 -0
  20. package/src/capabilities/crafting.ts +34 -7
  21. package/src/capabilities/gathering.test.ts +29 -0
  22. package/src/capabilities/gathering.ts +37 -19
  23. package/src/capabilities/hauling.ts +0 -5
  24. package/src/capabilities/index.ts +0 -1
  25. package/src/capabilities/modules.ts +42 -0
  26. package/src/capabilities/movement.ts +1 -1
  27. package/src/capabilities/storage.ts +16 -1
  28. package/src/contracts/platform.ts +231 -3
  29. package/src/contracts/server.ts +1518 -501
  30. package/src/coordinates/address.ts +88 -0
  31. package/src/coordinates/constants.test.ts +15 -0
  32. package/src/coordinates/constants.ts +23 -0
  33. package/src/coordinates/index.ts +15 -0
  34. package/src/coordinates/memo.test.ts +47 -0
  35. package/src/coordinates/memo.ts +20 -0
  36. package/src/coordinates/permutation.ts +77 -0
  37. package/src/coordinates/regions.ts +48 -0
  38. package/src/coordinates/sectors.ts +115 -0
  39. package/src/data/capabilities.ts +49 -18
  40. package/src/data/capability-formulas.ts +26 -14
  41. package/src/data/catalog.ts +0 -5
  42. package/src/data/colors.ts +13 -47
  43. package/src/data/entities.json +379 -14
  44. package/src/data/item-ids.ts +48 -12
  45. package/src/data/items.json +301 -74
  46. package/src/data/kind-registry.json +195 -0
  47. package/src/data/kind-registry.ts +182 -0
  48. package/src/data/metadata.ts +277 -48
  49. package/src/data/recipes-runtime.ts +6 -23
  50. package/src/data/recipes.json +2074 -173
  51. package/src/derivation/build-methods.test.ts +13 -0
  52. package/src/derivation/build-methods.ts +48 -0
  53. package/src/derivation/capabilities.test.ts +348 -0
  54. package/src/derivation/capabilities.ts +692 -0
  55. package/src/derivation/capability-mappings.ts +60 -16
  56. package/src/derivation/crafting.test.ts +17 -0
  57. package/src/derivation/crafting.ts +56 -33
  58. package/src/derivation/index.ts +27 -2
  59. package/src/derivation/recipe-usage.test.ts +88 -0
  60. package/src/derivation/recipe-usage.ts +141 -0
  61. package/src/derivation/reserve-regen.ts +34 -0
  62. package/src/derivation/resources.ts +9 -1
  63. package/src/derivation/rollups.test.ts +55 -0
  64. package/src/derivation/rollups.ts +72 -0
  65. package/src/derivation/stars.test.ts +67 -0
  66. package/src/derivation/stars.ts +25 -0
  67. package/src/derivation/stat-scaling.ts +12 -0
  68. package/src/derivation/stats.ts +6 -6
  69. package/src/derivation/stratum.ts +26 -22
  70. package/src/derivation/tiers.ts +40 -7
  71. package/src/derivation/upgrades.ts +14 -0
  72. package/src/derivation/wormhole.ts +154 -0
  73. package/src/entities/entity.ts +102 -0
  74. package/src/entities/gamestate.ts +3 -28
  75. package/src/entities/makers.ts +141 -135
  76. package/src/entities/slot-multiplier.ts +43 -0
  77. package/src/errors.ts +12 -16
  78. package/src/format.ts +26 -4
  79. package/src/index-module.ts +333 -35
  80. package/src/managers/actions.ts +668 -103
  81. package/src/managers/base.ts +6 -2
  82. package/src/managers/cluster.test.ts +39 -0
  83. package/src/managers/cluster.ts +53 -0
  84. package/src/managers/construction-types.ts +80 -0
  85. package/src/managers/construction.ts +443 -0
  86. package/src/managers/context.ts +29 -1
  87. package/src/managers/coordinates.ts +14 -0
  88. package/src/managers/entities.ts +27 -66
  89. package/src/managers/epochs.ts +40 -0
  90. package/src/managers/index.ts +20 -1
  91. package/src/managers/locations.ts +25 -29
  92. package/src/managers/nft.test.ts +14 -0
  93. package/src/managers/nft.ts +70 -0
  94. package/src/managers/players.ts +25 -0
  95. package/src/managers/plot.ts +122 -0
  96. package/src/nft/atomicassets.abi.json +1342 -0
  97. package/src/nft/atomicassets.ts +237 -0
  98. package/src/nft/atomicdata.ts +130 -0
  99. package/src/nft/buildImmutableData.ts +385 -0
  100. package/src/nft/description.ts +225 -52
  101. package/src/nft/index.ts +3 -0
  102. package/src/planner/index.ts +242 -0
  103. package/src/planner/planner.test.ts +334 -0
  104. package/src/resolution/describe-module.ts +43 -25
  105. package/src/resolution/display-name.ts +38 -10
  106. package/src/resolution/resolve-item.test.ts +48 -0
  107. package/src/resolution/resolve-item.ts +112 -58
  108. package/src/scan/index.ts +244 -0
  109. package/src/scan/scan-wasm.base64.ts +2 -0
  110. package/src/scheduling/accessor.ts +65 -23
  111. package/src/scheduling/availability.test.ts +204 -0
  112. package/src/scheduling/availability.ts +211 -0
  113. package/src/scheduling/cancel.test.ts +542 -0
  114. package/src/scheduling/cancel.ts +254 -0
  115. package/src/scheduling/cluster-stock.test.ts +31 -0
  116. package/src/scheduling/cluster-stock.ts +15 -0
  117. package/src/scheduling/energy.ts +47 -0
  118. package/src/scheduling/idle-resolve.ts +45 -0
  119. package/src/scheduling/jobs.ts +71 -0
  120. package/src/scheduling/lane-core.ts +128 -0
  121. package/src/scheduling/lanes.test.ts +250 -0
  122. package/src/scheduling/lanes.ts +227 -0
  123. package/src/scheduling/projection.ts +257 -133
  124. package/src/scheduling/schedule.test.ts +119 -0
  125. package/src/scheduling/schedule.ts +257 -117
  126. package/src/scheduling/task-cargo.test.ts +44 -0
  127. package/src/scheduling/task-cargo.ts +46 -0
  128. package/src/scheduling/unwrap.test.ts +86 -0
  129. package/src/scheduling/unwrap.ts +190 -0
  130. package/src/shipload.ts +26 -1
  131. package/src/subscriptions/manager.cluster.test.ts +51 -0
  132. package/src/subscriptions/manager.ts +245 -172
  133. package/src/subscriptions/mappers.ts +5 -8
  134. package/src/subscriptions/types.ts +28 -3
  135. package/src/testing/catalog-hash.ts +19 -0
  136. package/src/testing/index.ts +2 -0
  137. package/src/testing/projection-parity.ts +167 -0
  138. package/src/travel/reach.ts +21 -0
  139. package/src/travel/route-planner.ts +288 -0
  140. package/src/travel/route-simulator.ts +170 -0
  141. package/src/travel/travel.ts +188 -122
  142. package/src/types/capabilities.ts +37 -8
  143. package/src/types/entity.ts +3 -3
  144. package/src/types/index.ts +0 -1
  145. package/src/types.ts +51 -16
  146. package/src/utils/cargo.test.ts +14 -0
  147. package/src/utils/cargo.ts +29 -0
  148. package/src/utils/display-name.ts +70 -0
  149. package/src/utils/system.ts +15 -16
  150. package/src/capabilities/loading.ts +0 -8
  151. package/src/entities/container.ts +0 -123
  152. package/src/entities/ship-deploy.ts +0 -295
  153. package/src/entities/ship.ts +0 -221
  154. package/src/entities/warehouse.ts +0 -127
  155. package/src/types/entity-traits.ts +0 -69
@@ -15,11 +15,15 @@ export abstract class BaseManager {
15
15
  return this.context.platform
16
16
  }
17
17
 
18
+ protected get atomicAssetsAccount() {
19
+ return this.context.atomicAssetsAccount
20
+ }
21
+
18
22
  protected async getGame() {
19
23
  return this.context.getGame()
20
24
  }
21
25
 
22
- protected async getState() {
23
- return this.context.getState()
26
+ protected async getState(reload = false) {
27
+ return this.context.getState(reload)
24
28
  }
25
29
  }
@@ -0,0 +1,39 @@
1
+ import {describe, expect, test} from 'bun:test'
2
+ import {computeFreeCells} from './cluster'
3
+
4
+ describe('computeFreeCells', () => {
5
+ test('returns footprint cells that are not occupied', () => {
6
+ const footprint = [
7
+ {gx: 2, gy: 0},
8
+ {gx: 0, gy: 2},
9
+ {gx: 1, gy: 1},
10
+ ]
11
+ const occupied = [{gx: 2, gy: 0, entity: 7}]
12
+ expect(computeFreeCells(footprint, occupied)).toEqual([
13
+ {gx: 0, gy: 2},
14
+ {gx: 1, gy: 1},
15
+ ])
16
+ })
17
+
18
+ test('matches occupancy on signed coordinates', () => {
19
+ const footprint = [
20
+ {gx: -2, gy: 0},
21
+ {gx: 0, gy: -2},
22
+ {gx: -1, gy: 1},
23
+ ]
24
+ const occupied = [{gx: 0, gy: -2, entity: 9}]
25
+ expect(computeFreeCells(footprint, occupied)).toEqual([
26
+ {gx: -2, gy: 0},
27
+ {gx: -1, gy: 1},
28
+ ])
29
+ })
30
+
31
+ test('empty footprint yields no free cells', () => {
32
+ expect(computeFreeCells([], [])).toEqual([])
33
+ })
34
+
35
+ test('fully occupied footprint yields no free cells', () => {
36
+ const footprint = [{gx: 1, gy: 1}]
37
+ expect(computeFreeCells(footprint, [{gx: 1, gy: 1, entity: 3}])).toEqual([])
38
+ })
39
+ })
@@ -0,0 +1,53 @@
1
+ import {UInt16, type UInt16Type, UInt64, type UInt64Type} from '@wharfkit/antelope'
2
+ import {BaseManager} from './base'
3
+ import type {ServerContract} from '../contracts'
4
+
5
+ export interface GridCell {
6
+ gx: number
7
+ gy: number
8
+ }
9
+
10
+ export interface ClusterCell extends GridCell {
11
+ entity: number
12
+ }
13
+
14
+ export interface Cluster {
15
+ root: number
16
+ cells: ClusterCell[]
17
+ }
18
+
19
+ export function computeFreeCells(footprint: GridCell[], occupied: ClusterCell[]): GridCell[] {
20
+ const taken = new Set(occupied.map((c) => `${c.gx},${c.gy}`))
21
+ return footprint.filter((c) => !taken.has(`${c.gx},${c.gy}`))
22
+ }
23
+
24
+ export class ClusterManager extends BaseManager {
25
+ async getFootprint(itemId: UInt16Type): Promise<GridCell[]> {
26
+ const res = (await this.server.readonly('getfootprint', {
27
+ item_id: UInt16.from(itemId),
28
+ })) as ServerContract.Types.footprint_result
29
+ return res.cells.map((c) => ({gx: Number(c.gx), gy: Number(c.gy)}))
30
+ }
31
+
32
+ async getCluster(hubId: UInt64Type): Promise<Cluster> {
33
+ const res = (await this.server.readonly('getcluster', {
34
+ hub_id: UInt64.from(hubId),
35
+ })) as ServerContract.Types.cluster_row
36
+ return {
37
+ root: Number(res.root),
38
+ cells: res.cells.map((c) => ({
39
+ gx: Number(c.gx),
40
+ gy: Number(c.gy),
41
+ entity: Number(c.entity),
42
+ })),
43
+ }
44
+ }
45
+
46
+ async freeCells(hubId: UInt64Type, hubItemId: UInt16Type): Promise<GridCell[]> {
47
+ const [footprint, cluster] = await Promise.all([
48
+ this.getFootprint(hubItemId),
49
+ this.getCluster(hubId),
50
+ ])
51
+ return computeFreeCells(footprint, cluster.cells)
52
+ }
53
+ }
@@ -0,0 +1,80 @@
1
+ import type {Name, UInt32, UInt64} from '@wharfkit/antelope'
2
+ import type {ServerContract} from '../contracts'
3
+ import type {Item} from '../types'
4
+ import type {Recipe} from '../data/recipes-runtime'
5
+ import type {PlotProgress} from './plot'
6
+
7
+ export type BuildState = 'initializing' | 'accepting' | 'ready' | 'scheduled' | 'finalizing'
8
+
9
+ export type FinalizerCapability = 'builder'
10
+
11
+ export interface BuildableTarget {
12
+ entityId: UInt64
13
+ ownerName: Name
14
+ coordinates: ServerContract.Types.coordinates
15
+ targetItemId: number
16
+ targetItem: Item
17
+ state: BuildState
18
+ recipe: Recipe
19
+ progress: PlotProgress
20
+ finalizeAction: Name
21
+ finalizerCapability: FinalizerCapability
22
+ activeTask?: ServerContract.Types.task
23
+ scheduledBuild?: ScheduledBuild
24
+ }
25
+
26
+ export interface SourceEntityRef {
27
+ entityId: UInt64
28
+ name: string
29
+ hasLoaders: boolean
30
+ loaderCount: number
31
+ loaderTotalMass: number
32
+ relevantCargo: SourceCargoStack[]
33
+ }
34
+
35
+ export interface SourceCargoStack {
36
+ key: string
37
+ rowId: UInt64
38
+ itemId: number
39
+ item: Item
40
+ stats: UInt64
41
+ modules: ServerContract.Types.module_entry[]
42
+ available: number
43
+ plotNeeds: number
44
+ reserved: number
45
+ }
46
+
47
+ export interface FinalizerEntityRef {
48
+ entityId: UInt64
49
+ entityType: Name
50
+ name: string
51
+ capability: FinalizerCapability
52
+ builderSpeed: number
53
+ estimatedDuration: UInt32
54
+ }
55
+
56
+ export interface InboundTransfer {
57
+ sourceEntityId: UInt64
58
+ sourceEntityType: Name
59
+ sourceName: string
60
+ itemId: number
61
+ quantity: number
62
+ etaSeconds: number
63
+ }
64
+
65
+ export interface ScheduledBuild {
66
+ shipId: UInt64
67
+ shipName: string
68
+ hasStarted: boolean
69
+ startsAt: number
70
+ completesAt: number
71
+ cancelable: boolean
72
+ blockingTaskCount: number
73
+ }
74
+
75
+ export interface Reservation {
76
+ targetEntityId: UInt64
77
+ targetEntityType: Name
78
+ itemId: number
79
+ quantity: number
80
+ }
@@ -0,0 +1,443 @@
1
+ import type {UInt64, UInt32} from '@wharfkit/antelope'
2
+ import {BaseManager} from './base'
3
+ import type {ServerContract} from '../contracts'
4
+ import {PlotManager} from './plot'
5
+ import {getItem} from '../data/catalog'
6
+ import {calc_build_duration} from '../capabilities/crafting'
7
+ import {getLanes, getTasks} from '../scheduling/schedule'
8
+ import {HoldKind, TaskType} from '../types'
9
+ import type {
10
+ BuildableTarget,
11
+ FinalizerEntityRef,
12
+ InboundTransfer,
13
+ Reservation,
14
+ ScheduledBuild,
15
+ SourceCargoStack,
16
+ SourceEntityRef,
17
+ } from './construction-types'
18
+
19
+ const CONSTRUCTION_KINDS = new Set<string>(['plot'])
20
+
21
+ export class ConstructionManager extends BaseManager {
22
+ private readonly plot = new PlotManager(this.context)
23
+
24
+ getTarget(
25
+ entity: ServerContract.Types.entity_row,
26
+ cargo: ServerContract.Types.cargo_row[],
27
+ activeTask?: ServerContract.Types.task,
28
+ scheduledBuild?: ScheduledBuild
29
+ ): BuildableTarget | null {
30
+ const kind = entity.kind.toString()
31
+ if (kind === 'plot') {
32
+ return this.plot.buildableTarget(entity, cargo, activeTask, scheduledBuild)
33
+ }
34
+ return null
35
+ }
36
+
37
+ eligibleSources(
38
+ target: BuildableTarget,
39
+ entities: ServerContract.Types.entity_info[],
40
+ cargo: ServerContract.Types.cargo_row[]
41
+ ): SourceEntityRef[] {
42
+ return partitionSources(target, entities, cargo).eligible
43
+ }
44
+
45
+ unreachableSources(
46
+ target: BuildableTarget,
47
+ entities: ServerContract.Types.entity_info[],
48
+ cargo: ServerContract.Types.cargo_row[]
49
+ ): SourceEntityRef[] {
50
+ return partitionSources(target, entities, cargo).unreachable
51
+ }
52
+
53
+ partitionSources(
54
+ target: BuildableTarget,
55
+ entities: ServerContract.Types.entity_info[],
56
+ cargo: ServerContract.Types.cargo_row[]
57
+ ): {eligible: SourceEntityRef[]; unreachable: SourceEntityRef[]} {
58
+ return partitionSources(target, entities, cargo)
59
+ }
60
+
61
+ eligibleFinalizers(
62
+ target: BuildableTarget,
63
+ entities: ServerContract.Types.entity_info[]
64
+ ): FinalizerEntityRef[] {
65
+ const out: FinalizerEntityRef[] = []
66
+ for (const entity of entities) {
67
+ if (!entity.owner.equals(target.ownerName)) continue
68
+ if (entity.id.equals(target.entityId)) continue
69
+ if (!coordsEqual(entity.coordinates, target.coordinates)) continue
70
+ const builderLanes = entity.builder_lanes ?? []
71
+ if (builderLanes.length === 0) continue
72
+ const speed = builderLanes.reduce((s, l) => s + Number(l.speed), 0)
73
+ out.push({
74
+ entityId: entity.id,
75
+ entityType: entity.type,
76
+ name: entity.entity_name,
77
+ capability: 'builder',
78
+ builderSpeed: speed,
79
+ estimatedDuration: this.estimateFinalizeDuration(target, speed),
80
+ })
81
+ }
82
+ return out.sort((a, b) => a.estimatedDuration.value - b.estimatedDuration.value)
83
+ }
84
+
85
+ inboundTransfersTo(
86
+ plotId: UInt64,
87
+ entities: ServerContract.Types.entity_info[],
88
+ now: Date
89
+ ): InboundTransfer[] {
90
+ return this.inboundTransfersByTarget(entities, now).get(plotId.toString()) ?? []
91
+ }
92
+
93
+ inboundTransfersByTarget(
94
+ entities: ServerContract.Types.entity_info[],
95
+ now: Date
96
+ ): Map<string, InboundTransfer[]> {
97
+ const buckets = new Map<string, Map<string, InboundTransfer>>()
98
+ const entitiesById = new Map(entities.map((entity) => [entity.id.toString(), entity]))
99
+ const nowMs = now.getTime()
100
+ for (const entity of entities) {
101
+ for (const lane of getLanes(entity)) {
102
+ const startedMs = lane.schedule.started.toDate().getTime()
103
+ let cumulativeSec = 0
104
+ for (const task of lane.schedule.tasks) {
105
+ cumulativeSec += task.duration.toNumber()
106
+ if (!isDeliveryTask(task)) continue
107
+ const target = deliveryTarget(task)
108
+ if (!target) continue
109
+ const source = deliverySource(task, entity, entitiesById)
110
+ if (!source) continue
111
+ const projectedEndMs = startedMs + cumulativeSec * 1000
112
+ if (projectedEndMs < nowMs) continue
113
+ const targetIdStr = target.entity_id.toString()
114
+ const sourceIdStr = source.id.toString()
115
+ const sourceName = source.entity_name || sourceIdStr
116
+ const etaSeconds = Math.max(0, Math.round((projectedEndMs - nowMs) / 1000))
117
+ let perTarget = buckets.get(targetIdStr)
118
+ if (!perTarget) {
119
+ perTarget = new Map()
120
+ buckets.set(targetIdStr, perTarget)
121
+ }
122
+ for (const c of task.cargo) {
123
+ const itemId = c.item_id.toNumber()
124
+ const quantity = c.quantity.toNumber()
125
+ if (quantity === 0) continue
126
+ const key = `${sourceIdStr}#${itemId}`
127
+ const existing = perTarget.get(key)
128
+ if (existing) {
129
+ existing.quantity += quantity
130
+ existing.etaSeconds = Math.min(existing.etaSeconds, etaSeconds)
131
+ } else {
132
+ perTarget.set(key, {
133
+ sourceEntityId: source.id,
134
+ sourceEntityType: source.type,
135
+ sourceName,
136
+ itemId,
137
+ quantity,
138
+ etaSeconds,
139
+ })
140
+ }
141
+ }
142
+ }
143
+ }
144
+ }
145
+ const out = new Map<string, InboundTransfer[]>()
146
+ for (const [targetId, perTarget] of buckets) {
147
+ out.set(targetId, Array.from(perTarget.values()))
148
+ }
149
+ return out
150
+ }
151
+
152
+ private plotReservation(
153
+ plot: ServerContract.Types.entity_info,
154
+ builder: ServerContract.Types.entity_info | undefined,
155
+ now: Date
156
+ ): {
157
+ builderId: UInt64
158
+ startsAt: number
159
+ completesAt: number
160
+ hasStarted: boolean
161
+ } | null {
162
+ const hold = plot.holds.find((h) => h.kind.toNumber() === HoldKind.BUILD)
163
+ if (!hold) return null
164
+ const builderId = hold.counterpart.entity_id
165
+ const completesAt = hold.until.toDate().getTime()
166
+ const startsAt = this.builderBuildStart(builder, plot.id) ?? completesAt
167
+ return {
168
+ builderId,
169
+ startsAt,
170
+ completesAt,
171
+ hasStarted: startsAt <= now.getTime(),
172
+ }
173
+ }
174
+
175
+ private builderBuildStart(
176
+ builder: ServerContract.Types.entity_info | undefined,
177
+ plotId: UInt64
178
+ ): number | undefined {
179
+ if (!builder) return undefined
180
+ for (const lane of getLanes(builder)) {
181
+ const startedMs = lane.schedule.started.toDate().getTime()
182
+ let startSec = 0
183
+ for (const task of lane.schedule.tasks) {
184
+ if (isBuildOfPlot(task, plotId)) return startedMs + startSec * 1000
185
+ startSec += task.duration.toNumber()
186
+ }
187
+ }
188
+ return undefined
189
+ }
190
+
191
+ private builderCancelability(
192
+ builder: ServerContract.Types.entity_info | undefined,
193
+ plotId: UInt64
194
+ ): {cancelable: boolean; blockingTaskCount: number} {
195
+ if (!builder) {
196
+ return {cancelable: false, blockingTaskCount: 0}
197
+ }
198
+ for (const lane of getLanes(builder)) {
199
+ const tasks = lane.schedule.tasks
200
+ const buildIdx = tasks.findIndex((t) => isBuildOfPlot(t, plotId))
201
+ if (buildIdx < 0) continue
202
+ const trailing = tasks.length - 1 - buildIdx
203
+ return {cancelable: trailing === 0, blockingTaskCount: trailing}
204
+ }
205
+ return {cancelable: false, blockingTaskCount: 0}
206
+ }
207
+
208
+ private buildFromReservation(
209
+ res: {
210
+ builderId: UInt64
211
+ startsAt: number
212
+ completesAt: number
213
+ hasStarted: boolean
214
+ },
215
+ plotId: UInt64,
216
+ builder: ServerContract.Types.entity_info | undefined
217
+ ): ScheduledBuild {
218
+ const {cancelable, blockingTaskCount} = this.builderCancelability(builder, plotId)
219
+ return {
220
+ shipId: res.builderId,
221
+ shipName: builder?.entity_name || res.builderId.toString(),
222
+ hasStarted: res.hasStarted,
223
+ startsAt: res.startsAt,
224
+ completesAt: res.completesAt,
225
+ cancelable,
226
+ blockingTaskCount,
227
+ }
228
+ }
229
+
230
+ scheduledBuildFor(
231
+ plot: ServerContract.Types.entity_info,
232
+ entities: ServerContract.Types.entity_info[],
233
+ now: Date
234
+ ): ScheduledBuild | null {
235
+ const hold = plot.holds.find((h) => h.kind.toNumber() === HoldKind.BUILD)
236
+ if (!hold) return null
237
+ const builder = entities.find((e) => e.id.equals(hold.counterpart.entity_id))
238
+ const res = this.plotReservation(plot, builder, now)
239
+ if (!res) return null
240
+ return this.buildFromReservation(res, plot.id, builder)
241
+ }
242
+
243
+ scheduledBuildsByTarget(
244
+ entities: ServerContract.Types.entity_info[],
245
+ now: Date
246
+ ): Map<string, ScheduledBuild> {
247
+ const byId = new Map(entities.map((e) => [e.id.toString(), e]))
248
+ const out = new Map<string, ScheduledBuild>()
249
+ for (const entity of entities) {
250
+ if (entity.type.toString() !== 'plot') continue
251
+ const hold = entity.holds.find((h) => h.kind.toNumber() === HoldKind.BUILD)
252
+ if (!hold) continue
253
+ const builder = byId.get(hold.counterpart.entity_id.toString())
254
+ const res = this.plotReservation(entity, builder, now)
255
+ if (!res) continue
256
+ out.set(entity.id.toString(), this.buildFromReservation(res, entity.id, builder))
257
+ }
258
+ return out
259
+ }
260
+
261
+ reservationsFrom(
262
+ sourceEntityId: UInt64,
263
+ entities: ServerContract.Types.entity_info[]
264
+ ): Reservation[] {
265
+ const source = entities.find((e) => e.id.equals(sourceEntityId))
266
+ if (!source) return []
267
+ return reservationsOf(source)
268
+ }
269
+
270
+ estimateFinalizeDuration(target: BuildableTarget, builderSpeed: number): UInt32 {
271
+ return calc_build_duration(builderSpeed, target.progress.massRequired)
272
+ }
273
+
274
+ static isConstructionKind(kind: string): boolean {
275
+ return CONSTRUCTION_KINDS.has(kind)
276
+ }
277
+ }
278
+
279
+ function coordsEqual(
280
+ a: ServerContract.Types.coordinates,
281
+ b: ServerContract.Types.coordinates
282
+ ): boolean {
283
+ return a.x.equals(b.x) && a.y.equals(b.y)
284
+ }
285
+
286
+ function moduleKey(module: ServerContract.Types.module_entry): string {
287
+ const installed = module.installed
288
+ if (!installed) return `${module.type.toNumber()}:empty`
289
+
290
+ return `${module.type.toNumber()}:${installed.item_id.toNumber()}:${installed.stats.toString()}`
291
+ }
292
+
293
+ function sourceStackKey(cargo: ServerContract.Types.cargo_row): string {
294
+ return `${cargo.item_id.toNumber()}#${cargo.stats.toString()}#${(cargo.modules ?? [])
295
+ .map(moduleKey)
296
+ .join(',')}`
297
+ }
298
+
299
+ function matchRelevantCargo(
300
+ entity: ServerContract.Types.entity_info,
301
+ target: BuildableTarget,
302
+ cargo: ServerContract.Types.cargo_row[],
303
+ reservedByItem: Map<number, number>
304
+ ): SourceCargoStack[] {
305
+ const needsByItemId = new Map(
306
+ target.progress.rows.filter((row) => row.missing > 0).map((row) => [row.itemId, row])
307
+ )
308
+ const remainingReserved = new Map(reservedByItem)
309
+ const out: SourceCargoStack[] = []
310
+ for (const c of cargo) {
311
+ if (!c.entity_id.equals(entity.id)) continue
312
+ const itemId = c.item_id.toNumber()
313
+ const need = needsByItemId.get(itemId)
314
+ if (!need) continue
315
+ const gross = c.quantity.toNumber()
316
+ if (gross === 0) continue
317
+ const reservedRemaining = remainingReserved.get(itemId) ?? 0
318
+ const reserved = Math.min(gross, reservedRemaining)
319
+ const available = gross - reserved
320
+ if (reserved > 0) {
321
+ remainingReserved.set(itemId, reservedRemaining - reserved)
322
+ }
323
+ if (available === 0) continue
324
+ out.push({
325
+ key: sourceStackKey(c),
326
+ rowId: c.id,
327
+ itemId,
328
+ item: getItem(itemId),
329
+ stats: c.stats,
330
+ modules: c.modules ?? [],
331
+ available,
332
+ plotNeeds: need.missing,
333
+ reserved,
334
+ })
335
+ }
336
+ return out
337
+ }
338
+
339
+ function partitionSources(
340
+ target: BuildableTarget,
341
+ entities: ServerContract.Types.entity_info[],
342
+ cargo: ServerContract.Types.cargo_row[]
343
+ ): {eligible: SourceEntityRef[]; unreachable: SourceEntityRef[]} {
344
+ const eligible: SourceEntityRef[] = []
345
+ const unreachable: SourceEntityRef[] = []
346
+ for (const entity of entities) {
347
+ if (!entity.owner.equals(target.ownerName)) continue
348
+ if (entity.id.equals(target.entityId)) continue
349
+ if (!coordsEqual(entity.coordinates, target.coordinates)) continue
350
+ const reserved = reservedByItemFor(entity)
351
+ const relevant = matchRelevantCargo(entity, target, cargo, reserved)
352
+ if (relevant.length === 0) continue
353
+ const loaderLanes = entity.loader_lanes ?? []
354
+ const loaderCount = loaderLanes.length
355
+ const loaderTotalMass = loaderLanes.reduce((s, l) => s + Number(l.mass), 0)
356
+ const ref: SourceEntityRef = {
357
+ entityId: entity.id,
358
+ name: entity.id.toString(),
359
+ hasLoaders: loaderCount > 0,
360
+ loaderCount,
361
+ loaderTotalMass,
362
+ relevantCargo: relevant,
363
+ }
364
+ if (ref.hasLoaders) eligible.push(ref)
365
+ else unreachable.push(ref)
366
+ }
367
+ return {eligible, unreachable}
368
+ }
369
+
370
+ function isDeliveryTask(task: ServerContract.Types.task): boolean {
371
+ const type = task.type.toNumber()
372
+ return type === TaskType.UNLOAD || type === TaskType.SHUTTLE
373
+ }
374
+
375
+ function deliveryTarget(
376
+ task: ServerContract.Types.task
377
+ ): ServerContract.Types.entity_ref | undefined {
378
+ if (task.type.toNumber() === TaskType.SHUTTLE) {
379
+ return task.couplings.find((coupling) => coupling.kind.toNumber() === HoldKind.PUSH)
380
+ ?.counterpart
381
+ }
382
+ return task.couplings[0]?.counterpart
383
+ }
384
+
385
+ function deliverySource(
386
+ task: ServerContract.Types.task,
387
+ transporter: ServerContract.Types.entity_info,
388
+ entitiesById: ReadonlyMap<string, ServerContract.Types.entity_info>
389
+ ): ServerContract.Types.entity_info | undefined {
390
+ if (task.type.toNumber() !== TaskType.SHUTTLE) return transporter
391
+ const source = task.couplings.find(
392
+ (coupling) => coupling.kind.toNumber() === HoldKind.PULL
393
+ )?.counterpart
394
+ return source ? entitiesById.get(source.entity_id.toString()) : undefined
395
+ }
396
+
397
+ function isReservingPushTask(task: ServerContract.Types.task): boolean {
398
+ return task.type.toNumber() === TaskType.UNLOAD
399
+ }
400
+
401
+ function isBuildOfPlot(task: ServerContract.Types.task, plotId: UInt64): boolean {
402
+ return (
403
+ task.type.toNumber() === TaskType.BUILDPLOT &&
404
+ task.couplings.length > 0 &&
405
+ task.couplings[0].counterpart.entity_id.equals(plotId)
406
+ )
407
+ }
408
+
409
+ function reservationsOf(source: ServerContract.Types.entity_info): Reservation[] {
410
+ const out = new Map<string, Reservation>()
411
+ for (const task of getTasks(source)) {
412
+ if (!isReservingPushTask(task)) continue
413
+ if (task.couplings.length === 0) continue
414
+ const targetType = task.couplings[0].counterpart.entity_type
415
+ const targetId = task.couplings[0].counterpart.entity_id
416
+ for (const c of task.cargo) {
417
+ const itemId = c.item_id.toNumber()
418
+ const quantity = c.quantity.toNumber()
419
+ if (quantity === 0) continue
420
+ const key = `${targetId.toString()}#${itemId}`
421
+ const existing = out.get(key)
422
+ if (existing) {
423
+ existing.quantity += quantity
424
+ } else {
425
+ out.set(key, {
426
+ targetEntityId: targetId,
427
+ targetEntityType: targetType,
428
+ itemId,
429
+ quantity,
430
+ })
431
+ }
432
+ }
433
+ }
434
+ return Array.from(out.values())
435
+ }
436
+
437
+ function reservedByItemFor(source: ServerContract.Types.entity_info): Map<number, number> {
438
+ const out = new Map<number, number>()
439
+ for (const r of reservationsOf(source)) {
440
+ out.set(r.itemId, (out.get(r.itemId) ?? 0) + r.quantity)
441
+ }
442
+ return out
443
+ }
@@ -6,16 +6,22 @@ import {GameState} from '../entities/gamestate'
6
6
  import {EntitiesManager} from './entities'
7
7
  import {PlayersManager} from './players'
8
8
  import {LocationsManager} from './locations'
9
+ import {CoordinatesManager} from './coordinates'
9
10
  import {EpochsManager} from './epochs'
10
11
  import {ActionsManager} from './actions'
12
+ import {ClusterManager} from './cluster'
13
+ import {NftManager} from './nft'
11
14
  import {SubscriptionsManager} from '../subscriptions/manager'
12
15
 
13
16
  export class GameContext {
14
17
  private _entities?: EntitiesManager
15
18
  private _players?: PlayersManager
16
19
  private _locations?: LocationsManager
20
+ private _coordinates?: CoordinatesManager
17
21
  private _epochs?: EpochsManager
18
22
  private _actions?: ActionsManager
23
+ private _clusters?: ClusterManager
24
+ private _nft?: NftManager
19
25
  private _subscriptions?: SubscriptionsManager
20
26
  private _subscriptionsUrl?: string
21
27
 
@@ -25,7 +31,8 @@ export class GameContext {
25
31
  constructor(
26
32
  public readonly client: APIClient,
27
33
  public readonly server: Contract,
28
- public readonly platform: Contract
34
+ public readonly platform: Contract,
35
+ public readonly atomicAssetsAccount: string = 'atomicassets'
29
36
  ) {}
30
37
 
31
38
  get entities(): EntitiesManager {
@@ -49,6 +56,13 @@ export class GameContext {
49
56
  return this._locations
50
57
  }
51
58
 
59
+ get coordinates(): CoordinatesManager {
60
+ if (!this._coordinates) {
61
+ this._coordinates = new CoordinatesManager(this)
62
+ }
63
+ return this._coordinates
64
+ }
65
+
52
66
  get epochs(): EpochsManager {
53
67
  if (!this._epochs) {
54
68
  this._epochs = new EpochsManager(this)
@@ -63,6 +77,20 @@ export class GameContext {
63
77
  return this._actions
64
78
  }
65
79
 
80
+ get clusters(): ClusterManager {
81
+ if (!this._clusters) {
82
+ this._clusters = new ClusterManager(this)
83
+ }
84
+ return this._clusters
85
+ }
86
+
87
+ get nft(): NftManager {
88
+ if (!this._nft) {
89
+ this._nft = new NftManager(this)
90
+ }
91
+ return this._nft
92
+ }
93
+
66
94
  setSubscriptionsUrl(url: string) {
67
95
  this._subscriptionsUrl = url
68
96
  }