@shipload/sdk 1.0.0-next.5 → 1.0.0-next.51

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 (145) 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 +3167 -1353
  7. package/lib/shipload.js +13319 -5640
  8. package/lib/shipload.js.map +1 -1
  9. package/lib/shipload.m.js +13091 -5589
  10. package/lib/shipload.m.js.map +1 -1
  11. package/lib/testing.d.ts +1037 -0
  12. package/lib/testing.js +4363 -0
  13. package/lib/testing.js.map +1 -0
  14. package/lib/testing.m.js +4357 -0
  15. package/lib/testing.m.js.map +1 -0
  16. package/package.json +20 -2
  17. package/src/capabilities/craftable.ts +51 -0
  18. package/src/capabilities/crafting.test.ts +7 -0
  19. package/src/capabilities/crafting.ts +6 -7
  20. package/src/capabilities/gathering.test.ts +29 -0
  21. package/src/capabilities/gathering.ts +37 -19
  22. package/src/capabilities/index.ts +0 -1
  23. package/src/capabilities/modules.ts +22 -0
  24. package/src/capabilities/storage.ts +16 -1
  25. package/src/contracts/platform.ts +231 -3
  26. package/src/contracts/server.ts +1178 -511
  27. package/src/coordinates/address.ts +88 -0
  28. package/src/coordinates/constants.test.ts +15 -0
  29. package/src/coordinates/constants.ts +23 -0
  30. package/src/coordinates/index.ts +15 -0
  31. package/src/coordinates/memo.test.ts +47 -0
  32. package/src/coordinates/memo.ts +20 -0
  33. package/src/coordinates/permutation.ts +77 -0
  34. package/src/coordinates/regions.ts +48 -0
  35. package/src/coordinates/sectors.ts +115 -0
  36. package/src/data/capabilities.ts +31 -16
  37. package/src/data/capability-formulas.ts +21 -14
  38. package/src/data/catalog.ts +0 -5
  39. package/src/data/colors.ts +13 -47
  40. package/src/data/entities.json +154 -10
  41. package/src/data/item-ids.ts +31 -12
  42. package/src/data/items.json +192 -74
  43. package/src/data/kind-registry.json +131 -0
  44. package/src/data/kind-registry.ts +172 -0
  45. package/src/data/metadata.ts +166 -48
  46. package/src/data/recipes-runtime.ts +5 -23
  47. package/src/data/recipes.json +1003 -163
  48. package/src/derivation/build-methods.test.ts +13 -0
  49. package/src/derivation/build-methods.ts +48 -0
  50. package/src/derivation/capabilities.test.ts +151 -0
  51. package/src/derivation/capabilities.ts +527 -0
  52. package/src/derivation/capability-mappings.ts +58 -16
  53. package/src/derivation/crafting.ts +36 -33
  54. package/src/derivation/index.ts +28 -2
  55. package/src/derivation/recipe-usage.test.ts +81 -0
  56. package/src/derivation/recipe-usage.ts +141 -0
  57. package/src/derivation/reserve-regen.ts +34 -0
  58. package/src/derivation/resources.ts +98 -19
  59. package/src/derivation/rollups.test.ts +55 -0
  60. package/src/derivation/rollups.ts +56 -0
  61. package/src/derivation/stars.test.ts +67 -0
  62. package/src/derivation/stars.ts +25 -0
  63. package/src/derivation/stats.ts +6 -6
  64. package/src/derivation/stratum.ts +26 -22
  65. package/src/derivation/tiers.ts +40 -7
  66. package/src/derivation/upgrades.ts +14 -0
  67. package/src/derivation/wormhole.ts +141 -0
  68. package/src/entities/entity.ts +98 -0
  69. package/src/entities/gamestate.ts +3 -28
  70. package/src/entities/makers.ts +131 -134
  71. package/src/entities/slot-multiplier.ts +43 -0
  72. package/src/errors.ts +12 -16
  73. package/src/format.ts +26 -4
  74. package/src/index-module.ts +294 -34
  75. package/src/managers/actions.ts +616 -105
  76. package/src/managers/base.ts +6 -2
  77. package/src/managers/cluster.test.ts +39 -0
  78. package/src/managers/cluster.ts +53 -0
  79. package/src/managers/construction-types.ts +80 -0
  80. package/src/managers/construction.ts +412 -0
  81. package/src/managers/context.ts +29 -1
  82. package/src/managers/coordinates.ts +14 -0
  83. package/src/managers/entities.ts +27 -66
  84. package/src/managers/epochs.ts +40 -0
  85. package/src/managers/flatten-gather-plan.test.ts +80 -0
  86. package/src/managers/index.ts +20 -1
  87. package/src/managers/locations.ts +25 -29
  88. package/src/managers/nft.test.ts +14 -0
  89. package/src/managers/nft.ts +70 -0
  90. package/src/managers/players.ts +25 -0
  91. package/src/managers/plot.ts +122 -0
  92. package/src/nft/atomicassets.abi.json +1342 -0
  93. package/src/nft/atomicassets.ts +237 -0
  94. package/src/nft/atomicdata.ts +130 -0
  95. package/src/nft/buildImmutableData.ts +336 -0
  96. package/src/nft/description.ts +104 -36
  97. package/src/nft/index.ts +3 -0
  98. package/src/planner/index.ts +242 -0
  99. package/src/planner/planner.test.ts +325 -0
  100. package/src/resolution/describe-module.ts +43 -25
  101. package/src/resolution/display-name.ts +38 -10
  102. package/src/resolution/resolve-item.test.ts +48 -0
  103. package/src/resolution/resolve-item.ts +74 -47
  104. package/src/scan/index.ts +244 -0
  105. package/src/scan/scan-wasm.base64.ts +2 -0
  106. package/src/scheduling/accessor.ts +65 -23
  107. package/src/scheduling/availability.ts +108 -0
  108. package/src/scheduling/cancel.test.ts +348 -0
  109. package/src/scheduling/cancel.ts +209 -0
  110. package/src/scheduling/energy.ts +47 -0
  111. package/src/scheduling/idle-resolve.ts +45 -0
  112. package/src/scheduling/lane-core.ts +128 -0
  113. package/src/scheduling/lanes.test.ts +249 -0
  114. package/src/scheduling/lanes.ts +198 -0
  115. package/src/scheduling/projection.ts +210 -107
  116. package/src/scheduling/schedule.ts +240 -117
  117. package/src/scheduling/task-cargo.ts +46 -0
  118. package/src/scheduling/unwrap.test.ts +86 -0
  119. package/src/scheduling/unwrap.ts +190 -0
  120. package/src/shipload.ts +26 -1
  121. package/src/subscriptions/manager.cluster.test.ts +51 -0
  122. package/src/subscriptions/manager.ts +245 -172
  123. package/src/subscriptions/mappers.ts +5 -8
  124. package/src/subscriptions/types.ts +28 -3
  125. package/src/testing/catalog-hash.ts +19 -0
  126. package/src/testing/index.ts +2 -0
  127. package/src/testing/projection-parity.ts +167 -0
  128. package/src/travel/reach.ts +23 -0
  129. package/src/travel/route-planner.ts +277 -0
  130. package/src/travel/route-simulator.ts +174 -0
  131. package/src/travel/travel.ts +175 -118
  132. package/src/types/capabilities.ts +29 -6
  133. package/src/types/entity.ts +3 -3
  134. package/src/types/index.ts +0 -1
  135. package/src/types.ts +39 -14
  136. package/src/utils/cargo.test.ts +14 -0
  137. package/src/utils/cargo.ts +29 -0
  138. package/src/utils/display-name.ts +70 -0
  139. package/src/utils/system.ts +36 -24
  140. package/src/capabilities/loading.ts +0 -8
  141. package/src/entities/container.ts +0 -123
  142. package/src/entities/ship-deploy.ts +0 -295
  143. package/src/entities/ship.ts +0 -221
  144. package/src/entities/warehouse.ts +0 -127
  145. 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 = 'crafter'
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
+ crafterSpeed: 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,412 @@
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_craft_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 crafterLanes = entity.crafter_lanes ?? []
71
+ if (crafterLanes.length === 0) continue
72
+ const speed = crafterLanes.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: 'crafter',
78
+ crafterSpeed: 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 nowMs = now.getTime()
99
+ for (const entity of entities) {
100
+ const entityIdStr = entity.id.toString()
101
+ const sourceName = entity.entity_name || entityIdStr
102
+ for (const lane of getLanes(entity)) {
103
+ const startedMs = lane.schedule.started.toDate().getTime()
104
+ let cumulativeSec = 0
105
+ for (const task of lane.schedule.tasks) {
106
+ cumulativeSec += task.duration.toNumber()
107
+ if (!isPushTask(task)) continue
108
+ if (!task.entitytarget) continue
109
+ const projectedEndMs = startedMs + cumulativeSec * 1000
110
+ if (projectedEndMs < nowMs) continue
111
+ const targetIdStr = task.entitytarget.entity_id.toString()
112
+ const etaSeconds = Math.max(0, Math.round((projectedEndMs - nowMs) / 1000))
113
+ let perTarget = buckets.get(targetIdStr)
114
+ if (!perTarget) {
115
+ perTarget = new Map()
116
+ buckets.set(targetIdStr, perTarget)
117
+ }
118
+ for (const c of task.cargo) {
119
+ const itemId = c.item_id.toNumber()
120
+ const quantity = c.quantity.toNumber()
121
+ if (quantity === 0) continue
122
+ const key = `${entityIdStr}#${itemId}`
123
+ const existing = perTarget.get(key)
124
+ if (existing) {
125
+ existing.quantity += quantity
126
+ existing.etaSeconds = Math.min(existing.etaSeconds, etaSeconds)
127
+ } else {
128
+ perTarget.set(key, {
129
+ sourceEntityId: entity.id,
130
+ sourceEntityType: entity.type,
131
+ sourceName,
132
+ itemId,
133
+ quantity,
134
+ etaSeconds,
135
+ })
136
+ }
137
+ }
138
+ }
139
+ }
140
+ }
141
+ const out = new Map<string, InboundTransfer[]>()
142
+ for (const [targetId, perTarget] of buckets) {
143
+ out.set(targetId, Array.from(perTarget.values()))
144
+ }
145
+ return out
146
+ }
147
+
148
+ private plotReservation(
149
+ plot: ServerContract.Types.entity_info,
150
+ builder: ServerContract.Types.entity_info | undefined,
151
+ now: Date
152
+ ): {
153
+ builderId: UInt64
154
+ startsAt: number
155
+ completesAt: number
156
+ hasStarted: boolean
157
+ } | null {
158
+ const hold = plot.holds.find((h) => h.kind.toNumber() === HoldKind.BUILD)
159
+ if (!hold) return null
160
+ const builderId = hold.counterpart.entity_id
161
+ const completesAt = hold.until.toDate().getTime()
162
+ const startsAt = this.builderBuildStart(builder, plot.id) ?? completesAt
163
+ return {
164
+ builderId,
165
+ startsAt,
166
+ completesAt,
167
+ hasStarted: startsAt <= now.getTime(),
168
+ }
169
+ }
170
+
171
+ private builderBuildStart(
172
+ builder: ServerContract.Types.entity_info | undefined,
173
+ plotId: UInt64
174
+ ): number | undefined {
175
+ if (!builder) return undefined
176
+ for (const lane of getLanes(builder)) {
177
+ const startedMs = lane.schedule.started.toDate().getTime()
178
+ let startSec = 0
179
+ for (const task of lane.schedule.tasks) {
180
+ if (isBuildOfPlot(task, plotId)) return startedMs + startSec * 1000
181
+ startSec += task.duration.toNumber()
182
+ }
183
+ }
184
+ return undefined
185
+ }
186
+
187
+ private builderCancelability(
188
+ builder: ServerContract.Types.entity_info | undefined,
189
+ plotId: UInt64
190
+ ): {cancelable: boolean; blockingTaskCount: number} {
191
+ if (!builder) {
192
+ return {cancelable: false, blockingTaskCount: 0}
193
+ }
194
+ for (const lane of getLanes(builder)) {
195
+ const tasks = lane.schedule.tasks
196
+ const buildIdx = tasks.findIndex((t) => isBuildOfPlot(t, plotId))
197
+ if (buildIdx < 0) continue
198
+ const trailing = tasks.length - 1 - buildIdx
199
+ return {cancelable: trailing === 0, blockingTaskCount: trailing}
200
+ }
201
+ return {cancelable: false, blockingTaskCount: 0}
202
+ }
203
+
204
+ private buildFromReservation(
205
+ res: {
206
+ builderId: UInt64
207
+ startsAt: number
208
+ completesAt: number
209
+ hasStarted: boolean
210
+ },
211
+ plotId: UInt64,
212
+ builder: ServerContract.Types.entity_info | undefined
213
+ ): ScheduledBuild {
214
+ const {cancelable, blockingTaskCount} = this.builderCancelability(builder, plotId)
215
+ return {
216
+ shipId: res.builderId,
217
+ shipName: builder?.entity_name || res.builderId.toString(),
218
+ hasStarted: res.hasStarted,
219
+ startsAt: res.startsAt,
220
+ completesAt: res.completesAt,
221
+ cancelable,
222
+ blockingTaskCount,
223
+ }
224
+ }
225
+
226
+ scheduledBuildFor(
227
+ plot: ServerContract.Types.entity_info,
228
+ entities: ServerContract.Types.entity_info[],
229
+ now: Date
230
+ ): ScheduledBuild | null {
231
+ const hold = plot.holds.find((h) => h.kind.toNumber() === HoldKind.BUILD)
232
+ if (!hold) return null
233
+ const builder = entities.find((e) => e.id.equals(hold.counterpart.entity_id))
234
+ const res = this.plotReservation(plot, builder, now)
235
+ if (!res) return null
236
+ return this.buildFromReservation(res, plot.id, builder)
237
+ }
238
+
239
+ scheduledBuildsByTarget(
240
+ entities: ServerContract.Types.entity_info[],
241
+ now: Date
242
+ ): Map<string, ScheduledBuild> {
243
+ const byId = new Map(entities.map((e) => [e.id.toString(), e]))
244
+ const out = new Map<string, ScheduledBuild>()
245
+ for (const entity of entities) {
246
+ if (entity.type.toString() !== 'plot') continue
247
+ const hold = entity.holds.find((h) => h.kind.toNumber() === HoldKind.BUILD)
248
+ if (!hold) continue
249
+ const builder = byId.get(hold.counterpart.entity_id.toString())
250
+ const res = this.plotReservation(entity, builder, now)
251
+ if (!res) continue
252
+ out.set(entity.id.toString(), this.buildFromReservation(res, entity.id, builder))
253
+ }
254
+ return out
255
+ }
256
+
257
+ reservationsFrom(
258
+ sourceEntityId: UInt64,
259
+ entities: ServerContract.Types.entity_info[]
260
+ ): Reservation[] {
261
+ const source = entities.find((e) => e.id.equals(sourceEntityId))
262
+ if (!source) return []
263
+ return reservationsOf(source)
264
+ }
265
+
266
+ estimateFinalizeDuration(target: BuildableTarget, crafterSpeed: number): UInt32 {
267
+ return calc_craft_duration(crafterSpeed, target.progress.massRequired)
268
+ }
269
+
270
+ static isConstructionKind(kind: string): boolean {
271
+ return CONSTRUCTION_KINDS.has(kind)
272
+ }
273
+ }
274
+
275
+ function coordsEqual(
276
+ a: ServerContract.Types.coordinates,
277
+ b: ServerContract.Types.coordinates
278
+ ): boolean {
279
+ return a.x.equals(b.x) && a.y.equals(b.y)
280
+ }
281
+
282
+ function moduleKey(module: ServerContract.Types.module_entry): string {
283
+ const installed = module.installed
284
+ if (!installed) return `${module.type.toNumber()}:empty`
285
+
286
+ return `${module.type.toNumber()}:${installed.item_id.toNumber()}:${installed.stats.toString()}`
287
+ }
288
+
289
+ function sourceStackKey(cargo: ServerContract.Types.cargo_row): string {
290
+ return `${cargo.item_id.toNumber()}#${cargo.stats.toString()}#${(cargo.modules ?? [])
291
+ .map(moduleKey)
292
+ .join(',')}`
293
+ }
294
+
295
+ function matchRelevantCargo(
296
+ entity: ServerContract.Types.entity_info,
297
+ target: BuildableTarget,
298
+ cargo: ServerContract.Types.cargo_row[],
299
+ reservedByItem: Map<number, number>
300
+ ): SourceCargoStack[] {
301
+ const needsByItemId = new Map(
302
+ target.progress.rows.filter((row) => row.missing > 0).map((row) => [row.itemId, row])
303
+ )
304
+ const remainingReserved = new Map(reservedByItem)
305
+ const out: SourceCargoStack[] = []
306
+ for (const c of cargo) {
307
+ if (!c.entity_id.equals(entity.id)) continue
308
+ const itemId = c.item_id.toNumber()
309
+ const need = needsByItemId.get(itemId)
310
+ if (!need) continue
311
+ const gross = c.quantity.toNumber()
312
+ if (gross === 0) continue
313
+ const reservedRemaining = remainingReserved.get(itemId) ?? 0
314
+ const reserved = Math.min(gross, reservedRemaining)
315
+ const available = gross - reserved
316
+ if (reserved > 0) {
317
+ remainingReserved.set(itemId, reservedRemaining - reserved)
318
+ }
319
+ if (available === 0) continue
320
+ out.push({
321
+ key: sourceStackKey(c),
322
+ rowId: c.id,
323
+ itemId,
324
+ item: getItem(itemId),
325
+ stats: c.stats,
326
+ modules: c.modules ?? [],
327
+ available,
328
+ plotNeeds: need.missing,
329
+ reserved,
330
+ })
331
+ }
332
+ return out
333
+ }
334
+
335
+ function partitionSources(
336
+ target: BuildableTarget,
337
+ entities: ServerContract.Types.entity_info[],
338
+ cargo: ServerContract.Types.cargo_row[]
339
+ ): {eligible: SourceEntityRef[]; unreachable: SourceEntityRef[]} {
340
+ const eligible: SourceEntityRef[] = []
341
+ const unreachable: SourceEntityRef[] = []
342
+ for (const entity of entities) {
343
+ if (!entity.owner.equals(target.ownerName)) continue
344
+ if (entity.id.equals(target.entityId)) continue
345
+ if (!coordsEqual(entity.coordinates, target.coordinates)) continue
346
+ const reserved = reservedByItemFor(entity)
347
+ const relevant = matchRelevantCargo(entity, target, cargo, reserved)
348
+ if (relevant.length === 0) continue
349
+ const loaderLanes = entity.loader_lanes ?? []
350
+ const loaderCount = loaderLanes.length
351
+ const loaderTotalMass = loaderLanes.reduce((s, l) => s + Number(l.mass), 0)
352
+ const ref: SourceEntityRef = {
353
+ entityId: entity.id,
354
+ name: entity.id.toString(),
355
+ hasLoaders: loaderCount > 0,
356
+ loaderCount,
357
+ loaderTotalMass,
358
+ relevantCargo: relevant,
359
+ }
360
+ if (ref.hasLoaders) eligible.push(ref)
361
+ else unreachable.push(ref)
362
+ }
363
+ return {eligible, unreachable}
364
+ }
365
+
366
+ function isPushTask(task: ServerContract.Types.task): boolean {
367
+ return task.type.toNumber() === TaskType.UNLOAD
368
+ }
369
+
370
+ function isBuildOfPlot(task: ServerContract.Types.task, plotId: UInt64): boolean {
371
+ return (
372
+ task.type.toNumber() === TaskType.BUILDPLOT &&
373
+ task.entitytarget !== undefined &&
374
+ task.entitytarget.entity_id.equals(plotId)
375
+ )
376
+ }
377
+
378
+ function reservationsOf(source: ServerContract.Types.entity_info): Reservation[] {
379
+ const out = new Map<string, Reservation>()
380
+ for (const task of getTasks(source)) {
381
+ if (!isPushTask(task)) continue
382
+ if (!task.entitytarget) continue
383
+ const targetType = task.entitytarget.entity_type
384
+ const targetId = task.entitytarget.entity_id
385
+ for (const c of task.cargo) {
386
+ const itemId = c.item_id.toNumber()
387
+ const quantity = c.quantity.toNumber()
388
+ if (quantity === 0) continue
389
+ const key = `${targetId.toString()}#${itemId}`
390
+ const existing = out.get(key)
391
+ if (existing) {
392
+ existing.quantity += quantity
393
+ } else {
394
+ out.set(key, {
395
+ targetEntityId: targetId,
396
+ targetEntityType: targetType,
397
+ itemId,
398
+ quantity,
399
+ })
400
+ }
401
+ }
402
+ }
403
+ return Array.from(out.values())
404
+ }
405
+
406
+ function reservedByItemFor(source: ServerContract.Types.entity_info): Map<number, number> {
407
+ const out = new Map<number, number>()
408
+ for (const r of reservationsOf(source)) {
409
+ out.set(r.itemId, (out.get(r.itemId) ?? 0) + r.quantity)
410
+ }
411
+ return out
412
+ }
@@ -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
  }
@@ -0,0 +1,14 @@
1
+ import {BaseManager} from './base'
2
+ import {type CoordinateAddress, decodeAddress, encodeAddressMemo} from '../coordinates'
3
+
4
+ export class CoordinatesManager extends BaseManager {
5
+ async encode(x: number, y: number): Promise<CoordinateAddress> {
6
+ const game = await this.getGame()
7
+ return encodeAddressMemo(game.config.seed, x, y)
8
+ }
9
+
10
+ async decode(addr: CoordinateAddress): Promise<{x: number; y: number}> {
11
+ const game = await this.getGame()
12
+ return decodeAddress(game.config.seed, addr)
13
+ }
14
+ }