@shipload/sdk 1.0.0-next.3 → 1.0.0-next.30

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 (99) hide show
  1. package/lib/shipload.d.ts +1847 -962
  2. package/lib/shipload.js +9088 -4854
  3. package/lib/shipload.js.map +1 -1
  4. package/lib/shipload.m.js +8957 -4805
  5. package/lib/shipload.m.js.map +1 -1
  6. package/lib/testing.d.ts +856 -0
  7. package/lib/testing.js +3739 -0
  8. package/lib/testing.js.map +1 -0
  9. package/lib/testing.m.js +3733 -0
  10. package/lib/testing.m.js.map +1 -0
  11. package/package.json +15 -2
  12. package/src/capabilities/craftable.ts +51 -0
  13. package/src/capabilities/crafting.test.ts +7 -0
  14. package/src/capabilities/crafting.ts +3 -3
  15. package/src/capabilities/gathering.ts +17 -7
  16. package/src/capabilities/index.ts +0 -1
  17. package/src/capabilities/modules.ts +6 -0
  18. package/src/capabilities/storage.ts +16 -1
  19. package/src/contracts/platform.ts +231 -3
  20. package/src/contracts/server.ts +816 -471
  21. package/src/data/capabilities.ts +14 -329
  22. package/src/data/capability-formulas.ts +76 -0
  23. package/src/data/catalog.ts +0 -5
  24. package/src/data/colors.ts +14 -47
  25. package/src/data/entities.json +46 -10
  26. package/src/data/item-ids.ts +15 -12
  27. package/src/data/items.json +302 -38
  28. package/src/data/kind-registry.json +85 -0
  29. package/src/data/kind-registry.ts +150 -0
  30. package/src/data/metadata.ts +100 -31
  31. package/src/data/recipes-runtime.ts +3 -23
  32. package/src/data/recipes.json +250 -113
  33. package/src/derivation/build-methods.ts +45 -0
  34. package/src/derivation/capabilities.ts +415 -0
  35. package/src/derivation/capability-mappings.ts +117 -0
  36. package/src/derivation/crafting.ts +23 -24
  37. package/src/derivation/index.ts +17 -2
  38. package/src/derivation/reserve-regen.ts +34 -0
  39. package/src/derivation/resources.ts +125 -38
  40. package/src/derivation/stars.test.ts +51 -0
  41. package/src/derivation/stars.ts +15 -0
  42. package/src/derivation/stats.ts +6 -6
  43. package/src/derivation/stratum.ts +15 -19
  44. package/src/derivation/tiers.ts +28 -7
  45. package/src/entities/entity.ts +98 -0
  46. package/src/entities/gamestate.ts +3 -28
  47. package/src/entities/makers.ts +91 -136
  48. package/src/entities/slot-multiplier.ts +39 -0
  49. package/src/errors.ts +10 -15
  50. package/src/format.ts +26 -4
  51. package/src/index-module.ts +189 -47
  52. package/src/managers/actions.ts +252 -83
  53. package/src/managers/base.ts +6 -2
  54. package/src/managers/construction-types.ts +79 -0
  55. package/src/managers/construction.ts +396 -0
  56. package/src/managers/context.ts +11 -1
  57. package/src/managers/entities.ts +18 -66
  58. package/src/managers/epochs.ts +40 -0
  59. package/src/managers/index.ts +17 -1
  60. package/src/managers/locations.ts +25 -29
  61. package/src/managers/nft.ts +28 -0
  62. package/src/managers/plot.ts +127 -0
  63. package/src/nft/atomicassets.abi.json +1342 -0
  64. package/src/nft/atomicassets.ts +237 -0
  65. package/src/nft/atomicdata.ts +130 -0
  66. package/src/nft/buildImmutableData.ts +321 -0
  67. package/src/nft/description.ts +37 -15
  68. package/src/nft/index.ts +3 -0
  69. package/src/resolution/describe-module.ts +5 -8
  70. package/src/resolution/display-name.ts +38 -10
  71. package/src/resolution/resolve-item.ts +22 -20
  72. package/src/scheduling/accessor.ts +68 -22
  73. package/src/scheduling/availability.ts +108 -0
  74. package/src/scheduling/energy.ts +48 -0
  75. package/src/scheduling/lane-core.ts +130 -0
  76. package/src/scheduling/lanes.ts +60 -0
  77. package/src/scheduling/projection.ts +121 -94
  78. package/src/scheduling/schedule.ts +237 -103
  79. package/src/scheduling/task-cargo.ts +46 -0
  80. package/src/shipload.ts +16 -1
  81. package/src/subscriptions/manager.ts +40 -6
  82. package/src/subscriptions/mappers.ts +3 -8
  83. package/src/subscriptions/types.ts +3 -2
  84. package/src/testing/catalog-hash.ts +19 -0
  85. package/src/testing/index.ts +2 -0
  86. package/src/testing/projection-parity.ts +143 -0
  87. package/src/travel/travel.ts +90 -13
  88. package/src/types/capabilities.ts +1 -0
  89. package/src/types/index.ts +0 -1
  90. package/src/types.ts +19 -12
  91. package/src/utils/cargo.ts +27 -0
  92. package/src/utils/display-name.ts +61 -0
  93. package/src/utils/system.ts +25 -24
  94. package/src/capabilities/loading.ts +0 -8
  95. package/src/entities/container.ts +0 -108
  96. package/src/entities/ship-deploy.ts +0 -258
  97. package/src/entities/ship.ts +0 -204
  98. package/src/entities/warehouse.ts +0 -119
  99. package/src/types/entity-traits.ts +0 -69
@@ -1,179 +1,313 @@
1
1
  import type {ServerContract} from '../contracts'
2
2
  import {TaskType} from '../types'
3
+ import * as core from './lane-core'
3
4
 
4
5
  type Schedule = ServerContract.Types.schedule
5
6
  type Task = ServerContract.Types.task
7
+ type Lane = ServerContract.Types.lane
8
+
9
+ export const LANE_MOBILITY = 0
10
+ export const LANE_BARRIER = 255
6
11
 
7
12
  export interface ScheduleData {
8
- schedule?: Schedule
9
- }
10
-
11
- export interface Scheduleable extends ScheduleData {
12
- hasSchedule: boolean
13
- isIdle: boolean
14
- tasks: Task[]
15
- scheduleDuration(): number
16
- scheduleElapsed(now: Date): number
17
- scheduleRemaining(now: Date): number
18
- scheduleComplete(now: Date): boolean
19
- currentTaskIndex(now: Date): number
20
- currentTask(now: Date): Task | undefined
21
- currentTaskType(now: Date): TaskType | undefined
22
- getTaskStartTime(index: number): number
23
- getTaskElapsed(index: number, now: Date): number
24
- getTaskRemaining(index: number, now: Date): number
25
- isTaskComplete(index: number, now: Date): boolean
26
- isTaskInProgress(index: number, now: Date): boolean
27
- currentTaskProgress(now: Date): number
28
- scheduleProgress(now: Date): number
13
+ lanes?: Lane[]
14
+ }
15
+
16
+ export interface LaneView {
17
+ laneKey: number
18
+ schedule: Schedule
19
+ }
20
+
21
+ export {
22
+ laneStartsIn,
23
+ currentTaskIndexForLane,
24
+ laneTaskComplete,
25
+ laneTaskInProgress,
26
+ laneCompletesAt,
27
+ currentTaskProgressFloatForLane,
28
+ } from './lane-core'
29
+
30
+ export function getLanes(entity: ScheduleData): LaneView[] {
31
+ const lanes = entity.lanes
32
+ if (!lanes || lanes.length === 0) return []
33
+ return lanes.map((l) => ({laneKey: l.lane_key.toNumber(), schedule: l.schedule}))
34
+ }
35
+
36
+ export function getLane(entity: ScheduleData, laneKey: number): LaneView | undefined {
37
+ const lanes = entity.lanes
38
+ if (!lanes) return undefined
39
+ for (const l of lanes) {
40
+ if (l.lane_key.toNumber() === laneKey) return {laneKey, schedule: l.schedule}
41
+ }
42
+ return undefined
43
+ }
44
+
45
+ export function mobilityLane(entity: ScheduleData): LaneView | undefined {
46
+ return getLane(entity, LANE_MOBILITY)
29
47
  }
30
48
 
31
49
  export function hasSchedule(entity: ScheduleData): boolean {
32
- return !!entity.schedule && entity.schedule.tasks.length > 0
50
+ const lanes = entity.lanes
51
+ if (!lanes) return false
52
+ return lanes.some((l) => l.schedule.tasks.length > 0)
33
53
  }
34
54
 
35
55
  export function isIdle(entity: ScheduleData): boolean {
36
56
  return !hasSchedule(entity)
37
57
  }
38
58
 
59
+ export function isEntityIdle(entity: ScheduleData, now: Date): boolean {
60
+ const lanes = entity.lanes
61
+ if (!lanes) return true
62
+ return lanes.every((l) => core.currentTaskIndexForLane(l.schedule, now) < 0)
63
+ }
64
+
65
+ export function entityIdleAt(entity: ScheduleData, _now: Date): Date | undefined {
66
+ const lanes = entity.lanes
67
+ if (!lanes) return undefined
68
+ let maxMs: number | undefined
69
+ for (const l of lanes) {
70
+ if (l.schedule.tasks.length === 0) continue
71
+ const endMs = l.schedule.started.toDate().getTime() + core.laneDuration(l.schedule) * 1000
72
+ if (maxMs === undefined || endMs > maxMs) maxMs = endMs
73
+ }
74
+ return maxMs === undefined ? undefined : new Date(maxMs)
75
+ }
76
+
39
77
  export function getTasks(entity: ScheduleData): Task[] {
40
- return entity.schedule?.tasks || []
78
+ const lanes = entity.lanes
79
+ if (!lanes) return []
80
+ return lanes.flatMap((l) => l.schedule.tasks)
41
81
  }
42
82
 
43
83
  export function scheduleDuration(entity: ScheduleData): number {
44
- if (!entity.schedule) return 0
45
- return entity.schedule.tasks.reduce((sum, task) => sum + task.duration.toNumber(), 0)
84
+ let max = 0
85
+ for (const l of entity.lanes ?? []) max = Math.max(max, core.laneDuration(l.schedule))
86
+ return max
46
87
  }
47
88
 
48
89
  export function scheduleElapsed(entity: ScheduleData, now: Date): number {
49
- if (!entity.schedule) return 0
50
- const started = entity.schedule.started.toDate()
51
- const elapsed = Math.floor((now.getTime() - started.getTime()) / 1000)
52
- return Math.max(0, elapsed)
90
+ let max = 0
91
+ for (const l of entity.lanes ?? []) max = Math.max(max, core.laneElapsed(l.schedule, now))
92
+ return max
53
93
  }
54
94
 
55
95
  export function scheduleRemaining(entity: ScheduleData, now: Date): number {
56
- if (!entity.schedule) return 0
57
- const duration = scheduleDuration(entity)
58
- const elapsed = scheduleElapsed(entity, now)
59
- return Math.max(0, duration - elapsed)
96
+ let remaining = 0
97
+ for (const l of entity.lanes ?? []) {
98
+ remaining = Math.max(remaining, core.laneRemaining(l.schedule, now))
99
+ }
100
+ return remaining
60
101
  }
61
102
 
62
103
  export function scheduleComplete(entity: ScheduleData, now: Date): boolean {
63
- return hasSchedule(entity) && scheduleRemaining(entity, now) === 0
104
+ const lanes = entity.lanes
105
+ if (!lanes) return false
106
+ let hasAnyTask = false
107
+ let remaining = 0
108
+ for (const l of lanes) {
109
+ const tasks = l.schedule.tasks
110
+ if (tasks.length > 0) {
111
+ hasAnyTask = true
112
+ for (const t of tasks) {
113
+ if (t.type.toNumber() === TaskType.RESERVED) return false
114
+ }
115
+ }
116
+ remaining = Math.max(remaining, core.laneRemaining(l.schedule, now))
117
+ }
118
+ if (!hasAnyTask) return false
119
+ return remaining === 0
64
120
  }
65
121
 
66
- export function currentTaskIndex(entity: ScheduleData, now: Date): number {
67
- if (!entity.schedule || entity.schedule.tasks.length === 0) return -1
122
+ // Mirrors contract lane_front_complete: any lane whose front task is complete and non-reserved.
123
+ export function hasResolvable(entity: ScheduleData, now: Date): boolean {
124
+ for (const l of entity.lanes ?? []) {
125
+ if (core.laneTaskComplete(l.schedule, 0, now)) return true
126
+ }
127
+ return false
128
+ }
68
129
 
69
- const elapsed = scheduleElapsed(entity, now)
70
- let timeAccum = 0
130
+ export function currentTaskForLane(
131
+ entity: ScheduleData,
132
+ laneKey: number,
133
+ now: Date
134
+ ): Task | undefined {
135
+ const lane = getLane(entity, laneKey)
136
+ return lane ? core.currentTask(lane.schedule, now) : undefined
137
+ }
71
138
 
72
- for (let i = 0; i < entity.schedule.tasks.length; i++) {
73
- const taskDuration = entity.schedule.tasks[i].duration.toNumber()
74
- if (elapsed < timeAccum + taskDuration) {
75
- return i
76
- }
77
- timeAccum += taskDuration
78
- }
139
+ export function currentTaskTypeForLane(
140
+ entity: ScheduleData,
141
+ laneKey: number,
142
+ now: Date
143
+ ): TaskType | undefined {
144
+ const lane = getLane(entity, laneKey)
145
+ return lane ? core.currentTaskType(lane.schedule, now) : undefined
146
+ }
79
147
 
80
- return entity.schedule.tasks.length - 1
148
+ export function activeTasks(entity: ScheduleData, now: Date): Task[] {
149
+ const out: Task[] = []
150
+ for (const l of entity.lanes ?? []) {
151
+ const idx = core.currentTaskIndexForLane(l.schedule, now)
152
+ if (idx >= 0) out.push(l.schedule.tasks[idx])
153
+ }
154
+ return out
81
155
  }
82
156
 
83
- export function currentTask(entity: ScheduleData, now: Date): Task | undefined {
84
- const index = currentTaskIndex(entity, now)
85
- if (index < 0 || !entity.schedule) return undefined
86
- return entity.schedule.tasks[index]
157
+ export interface ResolvedEvent {
158
+ laneKey: number
159
+ taskIndex: number
160
+ task: Task
161
+ completesAt: Date
87
162
  }
88
163
 
89
- export function currentTaskType(entity: ScheduleData, now: Date): TaskType | undefined {
90
- const task = currentTask(entity, now)
91
- if (!task) return undefined
92
- return task.type.toNumber() as TaskType
164
+ // Canonical lane-front order (mirrors contract front_precedes): completion, then RECHARGE-last, then lane key.
165
+ function frontPrecedes(
166
+ a: {completesAt: Date; task: Task; laneKey: number},
167
+ b: {completesAt: Date; task: Task; laneKey: number}
168
+ ): number {
169
+ if (a.completesAt.getTime() !== b.completesAt.getTime()) {
170
+ return a.completesAt.getTime() - b.completesAt.getTime()
171
+ }
172
+ const aRecharge = a.task.type.toNumber() === TaskType.RECHARGE
173
+ const bRecharge = b.task.type.toNumber() === TaskType.RECHARGE
174
+ if (aRecharge !== bRecharge) return aRecharge ? 1 : -1
175
+ return a.laneKey - b.laneKey
93
176
  }
94
177
 
95
- export function getTaskStartTime(entity: ScheduleData, index: number): number {
96
- if (!entity.schedule || index < 0 || index >= entity.schedule.tasks.length) return 0
97
- let timeAccum = 0
98
- for (let i = 0; i < index; i++) {
99
- timeAccum += entity.schedule.tasks[i].duration.toNumber()
178
+ // Completed lane-fronts in canonical order (mirrors contract front_precedes).
179
+ export function resolveOrder(entity: ScheduleData, now: Date): ResolvedEvent[] {
180
+ const events: ResolvedEvent[] = []
181
+ for (const l of entity.lanes ?? []) {
182
+ const laneKey = l.lane_key.toNumber()
183
+ const startedMs = l.schedule.started.toDate().getTime()
184
+ let endSec = 0
185
+ for (let i = 0; i < l.schedule.tasks.length; i++) {
186
+ const task = l.schedule.tasks[i]
187
+ endSec += task.duration.toNumber()
188
+ const completesAt = new Date(startedMs + endSec * 1000)
189
+ if (task.type.toNumber() === TaskType.RESERVED) break
190
+ if (completesAt.getTime() > now.getTime()) break
191
+ events.push({laneKey, taskIndex: i, task, completesAt})
192
+ }
100
193
  }
101
- return timeAccum
194
+ events.sort(frontPrecedes)
195
+ return events
102
196
  }
103
197
 
104
- export function getTaskElapsed(entity: ScheduleData, index: number, now: Date): number {
105
- if (!entity.schedule || index < 0 || index >= entity.schedule.tasks.length) return 0
198
+ export interface OrderedTask {
199
+ laneKey: number
200
+ taskIndex: number
201
+ task: Task
202
+ startsAt: Date
203
+ completesAt: Date
204
+ }
106
205
 
107
- const elapsed = scheduleElapsed(entity, now)
108
- const taskStart = getTaskStartTime(entity, index)
109
- const taskDuration = entity.schedule.tasks[index].duration.toNumber()
206
+ // Every task across all lanes in canonical order (mirrors contract front_precedes).
207
+ export function orderedTasks(entity: ScheduleData): OrderedTask[] {
208
+ const out: OrderedTask[] = []
209
+ for (const l of entity.lanes ?? []) {
210
+ const laneKey = l.lane_key.toNumber()
211
+ const startedMs = l.schedule.started.toDate().getTime()
212
+ let endSec = 0
213
+ for (let i = 0; i < l.schedule.tasks.length; i++) {
214
+ const task = l.schedule.tasks[i]
215
+ const startsAt = new Date(startedMs + endSec * 1000)
216
+ endSec += task.duration.toNumber()
217
+ const completesAt = new Date(startedMs + endSec * 1000)
218
+ out.push({laneKey, taskIndex: i, task, startsAt, completesAt})
219
+ }
220
+ }
221
+ out.sort(frontPrecedes)
222
+ return out
223
+ }
110
224
 
111
- if (elapsed <= taskStart) return 0
112
- const elapsedInTask = elapsed - taskStart
113
- return Math.min(elapsedInTask, taskDuration)
225
+ export function laneRemainingOf(entity: ScheduleData, laneKey: number, now: Date): number {
226
+ const lane = getLane(entity, laneKey)
227
+ return lane ? core.laneRemaining(lane.schedule, now) : 0
114
228
  }
115
229
 
116
- export function getTaskRemaining(entity: ScheduleData, index: number, now: Date): number {
117
- if (!entity.schedule || index < 0 || index >= entity.schedule.tasks.length) return 0
230
+ export function laneStartsInOf(entity: ScheduleData, laneKey: number, now: Date): number {
231
+ const lane = getLane(entity, laneKey)
232
+ return lane ? core.laneStartsIn(lane.schedule, now) : 0
233
+ }
118
234
 
119
- const taskDuration = entity.schedule.tasks[index].duration.toNumber()
120
- const taskElapsed = getTaskElapsed(entity, index, now)
121
- return Math.max(0, taskDuration - taskElapsed)
235
+ export function laneCompleteOf(entity: ScheduleData, laneKey: number, now: Date): boolean {
236
+ const lane = getLane(entity, laneKey)
237
+ return lane ? core.laneComplete(lane.schedule, now) : false
122
238
  }
123
239
 
124
- export function isTaskComplete(entity: ScheduleData, index: number, now: Date): boolean {
125
- if (!entity.schedule || index < 0 || index >= entity.schedule.tasks.length) return false
240
+ export function laneProgressOf(entity: ScheduleData, laneKey: number, now: Date): number {
241
+ const lane = getLane(entity, laneKey)
242
+ return lane ? core.laneProgress(lane.schedule, now) : 0
243
+ }
126
244
 
127
- const taskDuration = entity.schedule.tasks[index].duration.toNumber()
128
- const taskElapsed = getTaskElapsed(entity, index, now)
129
- return taskElapsed >= taskDuration
245
+ export function laneTaskElapsedOf(
246
+ entity: ScheduleData,
247
+ laneKey: number,
248
+ index: number,
249
+ now: Date
250
+ ): number {
251
+ const lane = getLane(entity, laneKey)
252
+ return lane ? core.laneTaskElapsed(lane.schedule, index, now) : 0
130
253
  }
131
254
 
132
- export function isTaskInProgress(entity: ScheduleData, index: number, now: Date): boolean {
133
- if (!entity.schedule || index < 0 || index >= entity.schedule.tasks.length) return false
255
+ export function laneTaskRemainingOf(
256
+ entity: ScheduleData,
257
+ laneKey: number,
258
+ index: number,
259
+ now: Date
260
+ ): number {
261
+ const lane = getLane(entity, laneKey)
262
+ return lane ? core.laneTaskRemaining(lane.schedule, index, now) : 0
263
+ }
134
264
 
135
- const taskElapsed = getTaskElapsed(entity, index, now)
136
- const taskDuration = entity.schedule.tasks[index].duration.toNumber()
137
- return taskElapsed > 0 && taskElapsed < taskDuration
265
+ export function laneTaskCompleteOf(
266
+ entity: ScheduleData,
267
+ laneKey: number,
268
+ index: number,
269
+ now: Date
270
+ ): boolean {
271
+ const lane = getLane(entity, laneKey)
272
+ return lane ? core.laneTaskComplete(lane.schedule, index, now) : false
138
273
  }
139
274
 
140
- export function currentTaskProgress(entity: ScheduleData, now: Date): number {
141
- const task = currentTask(entity, now)
142
- if (!task) return 0
143
- const index = currentTaskIndex(entity, now)
144
- const elapsed = getTaskElapsed(entity, index, now)
145
- const duration = task.duration.toNumber()
146
- if (duration === 0) return 1
147
- return Math.min(1, elapsed / duration)
275
+ export function laneTaskInProgressOf(
276
+ entity: ScheduleData,
277
+ laneKey: number,
278
+ index: number,
279
+ now: Date
280
+ ): boolean {
281
+ const lane = getLane(entity, laneKey)
282
+ return lane ? core.laneTaskInProgress(lane.schedule, index, now) : false
148
283
  }
149
284
 
150
- export function scheduleProgress(entity: ScheduleData, now: Date): number {
151
- const duration = scheduleDuration(entity)
152
- if (duration === 0) return hasSchedule(entity) ? 1 : 0
153
- const elapsed = scheduleElapsed(entity, now)
154
- return Math.min(1, elapsed / duration)
285
+ export function currentTaskIndexOf(entity: ScheduleData, laneKey: number, now: Date): number {
286
+ const lane = getLane(entity, laneKey)
287
+ return lane ? core.currentTaskIndexForLane(lane.schedule, now) : -1
155
288
  }
156
289
 
157
- export function isTaskType(entity: ScheduleData, taskType: TaskType, now: Date): boolean {
158
- return currentTaskType(entity, now) === taskType
290
+ function entityDoesTaskType(entity: ScheduleData, taskType: TaskType, now: Date): boolean {
291
+ return activeTasks(entity, now).some((t) => t.type.toNumber() === taskType)
159
292
  }
160
293
 
161
294
  export function isInFlight(entity: ScheduleData, now: Date): boolean {
162
- return isTaskType(entity, TaskType.TRAVEL, now)
295
+ const lane = mobilityLane(entity)
296
+ return lane ? core.currentTaskType(lane.schedule, now) === TaskType.TRAVEL : false
163
297
  }
164
298
 
165
299
  export function isRecharging(entity: ScheduleData, now: Date): boolean {
166
- return isTaskType(entity, TaskType.RECHARGE, now)
300
+ return entityDoesTaskType(entity, TaskType.RECHARGE, now)
167
301
  }
168
302
 
169
303
  export function isLoading(entity: ScheduleData, now: Date): boolean {
170
- return isTaskType(entity, TaskType.LOAD, now)
304
+ return entityDoesTaskType(entity, TaskType.LOAD, now)
171
305
  }
172
306
 
173
307
  export function isUnloading(entity: ScheduleData, now: Date): boolean {
174
- return isTaskType(entity, TaskType.UNLOAD, now)
308
+ return entityDoesTaskType(entity, TaskType.UNLOAD, now)
175
309
  }
176
310
 
177
311
  export function isGathering(entity: ScheduleData, now: Date): boolean {
178
- return isTaskType(entity, TaskType.GATHER, now)
312
+ return entityDoesTaskType(entity, TaskType.GATHER, now)
179
313
  }
@@ -0,0 +1,46 @@
1
+ import type {ServerContract} from '../contracts'
2
+ import {TaskType} from '../types'
3
+
4
+ export type TaskCargoDirection = 'in' | 'out'
5
+
6
+ export interface TaskCargoChange {
7
+ direction: TaskCargoDirection
8
+ item_id: number
9
+ stats: bigint
10
+ modules: ServerContract.Types.module_entry[]
11
+ quantity: number
12
+ }
13
+
14
+ function toChange(
15
+ item: ServerContract.Types.cargo_item,
16
+ direction: TaskCargoDirection
17
+ ): TaskCargoChange {
18
+ return {
19
+ direction,
20
+ item_id: Number(item.item_id),
21
+ stats: BigInt(item.stats.toString()),
22
+ modules: item.modules ?? [],
23
+ quantity: Number(item.quantity),
24
+ }
25
+ }
26
+
27
+ export function taskCargoChanges(task: ServerContract.Types.task): TaskCargoChange[] {
28
+ const items = task.cargo ?? []
29
+ if (items.length === 0) return []
30
+ switch (Number(task.type)) {
31
+ case TaskType.LOAD:
32
+ case TaskType.UNWRAP:
33
+ return items.map((i) => toChange(i, 'in'))
34
+ case TaskType.GATHER:
35
+ return task.entitytarget ? [] : items.map((i) => toChange(i, 'in'))
36
+ case TaskType.UNLOAD:
37
+ return items.map((i) => toChange(i, 'out'))
38
+ case TaskType.CRAFT:
39
+ return [
40
+ ...items.slice(0, -1).map((i) => toChange(i, 'out')),
41
+ toChange(items[items.length - 1], 'in'),
42
+ ]
43
+ default:
44
+ return []
45
+ }
46
+ }
package/src/shipload.ts CHANGED
@@ -9,6 +9,7 @@ import type {PlayersManager} from './managers/players'
9
9
  import type {LocationsManager} from './managers/locations'
10
10
  import type {EpochsManager} from './managers/epochs'
11
11
  import type {ActionsManager} from './managers/actions'
12
+ import type {NftManager} from './managers/nft'
12
13
  import type {SubscriptionsManager} from './subscriptions/manager'
13
14
  import type {GameState} from './entities/gamestate'
14
15
 
@@ -17,6 +18,7 @@ interface ShiploadOptions {
17
18
  serverContractName?: string
18
19
  client?: APIClient
19
20
  subscriptionsUrl?: string
21
+ atomicAssetsAccount?: string
20
22
  }
21
23
 
22
24
  interface ShiploadConstructorOptions extends ShiploadOptions {
@@ -39,7 +41,12 @@ export class Shipload {
39
41
  ? serverContract
40
42
  : new ServerContract.Contract({client: apiClient})
41
43
 
42
- this._context = new GameContext(apiClient, server, platform)
44
+ this._context = new GameContext(
45
+ apiClient,
46
+ server,
47
+ platform,
48
+ constructorOptions?.atomicAssetsAccount ?? 'atomicassets'
49
+ )
43
50
 
44
51
  if (constructorOptions?.subscriptionsUrl) {
45
52
  this._context.setSubscriptionsUrl(constructorOptions.subscriptionsUrl)
@@ -79,6 +86,10 @@ export class Shipload {
79
86
  return this._context.client
80
87
  }
81
88
 
89
+ get atomicAssetsAccount(): string {
90
+ return this._context.atomicAssetsAccount
91
+ }
92
+
82
93
  get server(): Contract {
83
94
  return this._context.server
84
95
  }
@@ -107,6 +118,10 @@ export class Shipload {
107
118
  return this._context.actions
108
119
  }
109
120
 
121
+ get nft(): NftManager {
122
+ return this._context.nft
123
+ }
124
+
110
125
  get subscriptions(): SubscriptionsManager {
111
126
  return this._context.subscriptions
112
127
  }
@@ -13,12 +13,10 @@ import type {
13
13
  WireEntity,
14
14
  } from './types'
15
15
  import {mapEntity, parseWireEntity} from './mappers'
16
- import type {Ship} from '../entities/ship'
17
- import type {Warehouse} from '../entities/warehouse'
18
- import type {Container} from '../entities/container'
16
+ import type {Entity} from '../entities/entity'
19
17
 
20
- export type SubscriptionEntityType = 'ship' | 'warehouse' | 'container'
21
- export type EntityInstance = Ship | Warehouse | Container
18
+ export type SubscriptionEntityType = 'ship' | 'warehouse' | 'container' | 'nexus'
19
+ export type EntityInstance = Entity
22
20
 
23
21
  export interface SubscriptionsOptions {
24
22
  url: string
@@ -34,6 +32,12 @@ export interface BoundsSubscriptionHandle {
34
32
  current: Map<number, EntityInstance>
35
33
  }
36
34
 
35
+ export interface OwnerSubscriptionHandle {
36
+ readonly subId: string
37
+ unsubscribe(): void
38
+ current: Map<number, EntityInstance>
39
+ }
40
+
37
41
  export interface EntitySubscriptionHandle {
38
42
  readonly subId: string
39
43
  readonly entityType: SubscriptionEntityType
@@ -62,7 +66,7 @@ export class SubscriptionsManager {
62
66
  onSnapshot?: (entities: EntityInstance[]) => void
63
67
  onUpdate?: (entity: EntityInstance) => void
64
68
  onBoundsDelta?: (entered: EntityInstance[], exited: number[]) => void
65
- handle: BoundsSubscriptionHandle
69
+ handle: BoundsSubscriptionHandle | OwnerSubscriptionHandle
66
70
  }
67
71
  >()
68
72
  private subCounter = 0
@@ -162,6 +166,36 @@ export class SubscriptionsManager {
162
166
  return handle
163
167
  }
164
168
 
169
+ subscribeOwner(
170
+ owner: string,
171
+ handlers: {
172
+ onSnapshot?: (entities: EntityInstance[]) => void
173
+ onUpdate?: (entity: EntityInstance) => void
174
+ } = {}
175
+ ): OwnerSubscriptionHandle {
176
+ const subId = this.generateSubID('own')
177
+ const msg: SubscribeMessage = {
178
+ type: 'subscribe',
179
+ sub_id: subId,
180
+ owner,
181
+ }
182
+ const handle: OwnerSubscriptionHandle = {
183
+ subId,
184
+ unsubscribe: () => this.unsubscribeBounds(subId),
185
+ current: new Map(),
186
+ }
187
+ this.boundsSubs.set(subId, {
188
+ bounds: undefined,
189
+ owner,
190
+ prioritizeOwner: undefined,
191
+ onSnapshot: handlers.onSnapshot,
192
+ onUpdate: handlers.onUpdate,
193
+ handle,
194
+ })
195
+ this.sendMessage(msg)
196
+ return handle
197
+ }
198
+
165
199
  private unsubscribeBounds(subId: string) {
166
200
  this.boundsSubs.delete(subId)
167
201
  this.sendMessage({type: 'unsubscribe', sub_id: subId})
@@ -1,14 +1,9 @@
1
1
  import {ServerContract} from '../contracts'
2
- import {Ship} from '../entities/ship'
3
- import {Warehouse} from '../entities/warehouse'
4
- import {Container} from '../entities/container'
2
+ import {Entity} from '../entities/entity'
5
3
  import type {WireEntity} from './types'
6
4
 
7
- export function mapEntity(ei: ServerContract.Types.entity_info): Ship | Warehouse | Container {
8
- if (ei.type.equals('ship')) return new Ship(ei)
9
- if (ei.type.equals('warehouse')) return new Warehouse(ei)
10
- if (ei.type.equals('container')) return new Container(ei)
11
- throw new Error(`mapEntity: unknown entity type ${ei.type.toString()}`)
5
+ export function mapEntity(ei: ServerContract.Types.entity_info): Entity {
6
+ return new Entity(ei)
12
7
  }
13
8
 
14
9
  export function parseWireEntity(raw: WireEntity): ServerContract.Types.entity_info {
@@ -39,7 +39,7 @@ export type UnsubscribeMessage = {
39
39
  export type SubscribeEntityMessage = {
40
40
  type: 'subscribe_entity'
41
41
  sub_id: string
42
- entity_type: 'ship' | 'warehouse' | 'container'
42
+ entity_type: 'ship' | 'warehouse' | 'container' | 'nexus'
43
43
  entity_id: string
44
44
  }
45
45
 
@@ -80,10 +80,11 @@ export type AckMessage = {
80
80
 
81
81
  export type WireEntity = Record<string, unknown> & {
82
82
  type: number
83
- type_name: 'ship' | 'warehouse' | 'container'
83
+ type_name: 'ship' | 'warehouse' | 'container' | 'nexus'
84
84
  id: string | number
85
85
  owner: string
86
86
  coordinates: WireCoordinates
87
+ item_id: number
87
88
  }
88
89
 
89
90
  export type SnapshotMessage = {
@@ -0,0 +1,19 @@
1
+ import {createHash} from 'node:crypto'
2
+ import {readFileSync} from 'node:fs'
3
+
4
+ export const CATALOG_FILES_REL = [
5
+ 'items.json',
6
+ 'recipes.json',
7
+ 'entities.json',
8
+ 'kind-registry.json',
9
+ 'item-ids.ts',
10
+ ] as const
11
+
12
+ export function computeCatalogHash(filePaths: ReadonlyArray<string>): string {
13
+ const hash = createHash('sha256')
14
+ for (const p of filePaths) {
15
+ hash.update(readFileSync(p))
16
+ hash.update('\0')
17
+ }
18
+ return hash.digest('hex')
19
+ }
@@ -0,0 +1,2 @@
1
+ export * from './catalog-hash'
2
+ export * from './projection-parity'