@shipload/sdk 1.0.0-next.4 → 1.0.0-next.40

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 (127) hide show
  1. package/lib/shipload.d.ts +2473 -973
  2. package/lib/shipload.js +11529 -5211
  3. package/lib/shipload.js.map +1 -1
  4. package/lib/shipload.m.js +11338 -5162
  5. package/lib/shipload.m.js.map +1 -1
  6. package/lib/testing.d.ts +970 -0
  7. package/lib/testing.js +4013 -0
  8. package/lib/testing.js.map +1 -0
  9. package/lib/testing.m.js +4007 -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 +5 -6
  15. package/src/capabilities/gathering.test.ts +16 -0
  16. package/src/capabilities/gathering.ts +35 -18
  17. package/src/capabilities/index.ts +0 -1
  18. package/src/capabilities/modules.ts +9 -0
  19. package/src/capabilities/storage.ts +16 -1
  20. package/src/contracts/platform.ts +231 -3
  21. package/src/contracts/server.ts +1021 -481
  22. package/src/coordinates/address.ts +88 -0
  23. package/src/coordinates/constants.test.ts +15 -0
  24. package/src/coordinates/constants.ts +23 -0
  25. package/src/coordinates/index.ts +15 -0
  26. package/src/coordinates/memo.test.ts +47 -0
  27. package/src/coordinates/memo.ts +20 -0
  28. package/src/coordinates/permutation.ts +77 -0
  29. package/src/coordinates/regions.ts +48 -0
  30. package/src/coordinates/sectors.ts +115 -0
  31. package/src/data/capabilities.ts +12 -5
  32. package/src/data/capability-formulas.ts +14 -7
  33. package/src/data/catalog.ts +0 -5
  34. package/src/data/colors.ts +14 -47
  35. package/src/data/entities.json +76 -10
  36. package/src/data/item-ids.ts +18 -12
  37. package/src/data/items.json +321 -38
  38. package/src/data/kind-registry.json +109 -0
  39. package/src/data/kind-registry.ts +165 -0
  40. package/src/data/metadata.ts +119 -33
  41. package/src/data/recipes-runtime.ts +3 -23
  42. package/src/data/recipes.json +238 -117
  43. package/src/derivation/build-methods.ts +45 -0
  44. package/src/derivation/capabilities.test.ts +151 -0
  45. package/src/derivation/capabilities.ts +512 -0
  46. package/src/derivation/capability-mappings.ts +9 -12
  47. package/src/derivation/crafting.ts +23 -24
  48. package/src/derivation/index.ts +25 -2
  49. package/src/derivation/recipe-usage.test.ts +78 -0
  50. package/src/derivation/recipe-usage.ts +141 -0
  51. package/src/derivation/reserve-regen.ts +34 -0
  52. package/src/derivation/resources.ts +125 -38
  53. package/src/derivation/rollups.test.ts +55 -0
  54. package/src/derivation/rollups.ts +56 -0
  55. package/src/derivation/stars.test.ts +51 -0
  56. package/src/derivation/stars.ts +15 -0
  57. package/src/derivation/stats.ts +6 -6
  58. package/src/derivation/stratum.ts +17 -20
  59. package/src/derivation/tiers.ts +40 -7
  60. package/src/derivation/wormhole.ts +136 -0
  61. package/src/entities/entity.ts +98 -0
  62. package/src/entities/gamestate.ts +3 -28
  63. package/src/entities/makers.ts +124 -134
  64. package/src/entities/slot-multiplier.ts +43 -0
  65. package/src/errors.ts +12 -16
  66. package/src/format.ts +26 -4
  67. package/src/index-module.ts +267 -47
  68. package/src/managers/actions.ts +528 -95
  69. package/src/managers/base.ts +6 -2
  70. package/src/managers/construction-types.ts +80 -0
  71. package/src/managers/construction.ts +412 -0
  72. package/src/managers/context.ts +20 -1
  73. package/src/managers/coordinates.ts +14 -0
  74. package/src/managers/entities.ts +18 -66
  75. package/src/managers/epochs.ts +40 -0
  76. package/src/managers/index.ts +17 -1
  77. package/src/managers/locations.ts +25 -29
  78. package/src/managers/nft.test.ts +14 -0
  79. package/src/managers/nft.ts +70 -0
  80. package/src/managers/plot.ts +122 -0
  81. package/src/nft/atomicassets.abi.json +1342 -0
  82. package/src/nft/atomicassets.ts +237 -0
  83. package/src/nft/atomicdata.ts +130 -0
  84. package/src/nft/buildImmutableData.ts +338 -0
  85. package/src/nft/description.ts +98 -24
  86. package/src/nft/index.ts +3 -0
  87. package/src/planner/index.ts +127 -0
  88. package/src/planner/planner.test.ts +319 -0
  89. package/src/resolution/describe-module.ts +18 -13
  90. package/src/resolution/display-name.ts +38 -10
  91. package/src/resolution/resolve-item.test.ts +37 -0
  92. package/src/resolution/resolve-item.ts +55 -24
  93. package/src/scheduling/accessor.ts +68 -22
  94. package/src/scheduling/availability.ts +108 -0
  95. package/src/scheduling/cancel.test.ts +348 -0
  96. package/src/scheduling/cancel.ts +209 -0
  97. package/src/scheduling/energy.ts +47 -0
  98. package/src/scheduling/idle-resolve.ts +45 -0
  99. package/src/scheduling/lane-core.ts +128 -0
  100. package/src/scheduling/lanes.test.ts +249 -0
  101. package/src/scheduling/lanes.ts +198 -0
  102. package/src/scheduling/projection.ts +209 -105
  103. package/src/scheduling/schedule.ts +241 -104
  104. package/src/scheduling/task-cargo.ts +46 -0
  105. package/src/shipload.ts +21 -1
  106. package/src/subscriptions/manager.ts +229 -142
  107. package/src/subscriptions/mappers.ts +5 -8
  108. package/src/subscriptions/types.ts +11 -3
  109. package/src/testing/catalog-hash.ts +19 -0
  110. package/src/testing/index.ts +2 -0
  111. package/src/testing/projection-parity.ts +167 -0
  112. package/src/travel/reach.ts +23 -0
  113. package/src/travel/route-planner.ts +196 -0
  114. package/src/travel/travel.ts +200 -112
  115. package/src/types/capabilities.ts +29 -6
  116. package/src/types/entity.ts +3 -3
  117. package/src/types/index.ts +0 -1
  118. package/src/types.ts +28 -13
  119. package/src/utils/cargo.ts +27 -0
  120. package/src/utils/display-name.ts +70 -0
  121. package/src/utils/system.ts +36 -24
  122. package/src/capabilities/loading.ts +0 -8
  123. package/src/entities/container.ts +0 -108
  124. package/src/entities/ship-deploy.ts +0 -259
  125. package/src/entities/ship.ts +0 -204
  126. package/src/entities/warehouse.ts +0 -119
  127. package/src/types/entity-traits.ts +0 -69
@@ -1,8 +1,7 @@
1
- import {Name, TimePoint, UInt16, UInt32, UInt64} from '@wharfkit/antelope'
1
+ import {Name, UInt16, UInt32, UInt64} from '@wharfkit/antelope'
2
2
  import {ServerContract} from '../contracts'
3
- import {Coordinates, PRECISION, TaskType} from '../types'
3
+ import {Coordinates, TaskType} from '../types'
4
4
  import {
5
- capsHasLoaders,
6
5
  capsHasMovement,
7
6
  capsHasStorage,
8
7
  type EntityCapabilities,
@@ -14,17 +13,19 @@ import {
14
13
  RECIPE_INPUTS_INSUFFICIENT,
15
14
  RECIPE_INPUTS_INVALID,
16
15
  RECIPE_NOT_FOUND,
17
- SHIP_CARGO_NOT_LOADED,
16
+ ENTITY_CARGO_NOT_LOADED,
18
17
  } from '../errors'
19
- import {getRecipe, type RecipeInput} from '../data/recipes-runtime'
20
- import {getItem} from '../data/catalog'
21
- import {distanceBetweenCoordinates, lerp} from '../travel/travel'
18
+ import {getEntityLayout, getRecipe, type RecipeInput} from '../data/recipes-runtime'
19
+ import {computeEntityCapabilities} from '../derivation/capabilities'
20
+ import {decodeCraftedItemStats, decodeStat} from '../derivation/crafting'
21
+ import {packedModulesToInstalled, type InstalledModule} from '../entities/slot-multiplier'
22
+ import {lerp} from '../travel/travel'
22
23
  import {
23
24
  calcStacksMass,
24
25
  cargoItemToStack,
25
26
  type CargoStack,
26
27
  mergeStacks,
27
- removeFromStacks,
28
+ subtractFromStacks,
28
29
  stackToCargoItem,
29
30
  } from '../capabilities/storage'
30
31
  import * as schedule from './schedule'
@@ -37,15 +38,19 @@ export interface ProjectedEntity {
37
38
  shipMass: UInt32
38
39
  capacity?: UInt64
39
40
  engines?: ServerContract.Types.movement_stats
40
- loaders?: ServerContract.Types.loader_stats
41
+ loaderLanes: ServerContract.Types.loader_lane[]
41
42
  generator?: ServerContract.Types.energy_stats
42
43
  hauler?: ServerContract.Types.hauler_stats
44
+ launcher?: ServerContract.Types.launcher_stats
43
45
  readonly cargoMass: UInt64
44
46
  readonly totalMass: UInt64
47
+ readonly gathererLanes: ServerContract.Types.gatherer_lane[]
48
+ readonly crafterLanes: ServerContract.Types.crafter_lane[]
45
49
 
46
50
  hasMovement(): boolean
47
51
  hasStorage(): boolean
48
52
  hasLoaders(): boolean
53
+ hasLauncher(): boolean
49
54
 
50
55
  capabilities(): EntityCapabilities
51
56
  state(): EntityState
@@ -57,25 +62,148 @@ export interface Projectable extends ScheduleData {
57
62
  hullmass?: UInt32
58
63
  generator?: ServerContract.Types.energy_stats
59
64
  engines?: ServerContract.Types.movement_stats
60
- loaders?: ServerContract.Types.loader_stats
65
+ loader_lanes?: ServerContract.Types.loader_lane[]
66
+ gatherer_lanes?: ServerContract.Types.gatherer_lane[]
67
+ crafter_lanes?: ServerContract.Types.crafter_lane[]
61
68
  hauler?: ServerContract.Types.hauler_stats
69
+ launcher?: ServerContract.Types.launcher_stats
62
70
  capacity?: UInt32
63
71
  cargo: ServerContract.Types.cargo_item[]
64
72
  cargomass: UInt32
65
73
  owner?: Name
74
+ stats?: bigint
75
+ item_id?: number | UInt16
76
+ modules?: ServerContract.Types.module_entry[] | InstalledModule[]
66
77
  }
67
78
 
68
- function getHullMass(entity: Projectable): UInt32 {
69
- return UInt32.from(entity.hullmass ?? 0)
79
+ function toInstalledModules(
80
+ modules: ServerContract.Types.module_entry[] | InstalledModule[]
81
+ ): InstalledModule[] {
82
+ if (modules.length > 0 && 'itemId' in modules[0]) {
83
+ return modules as InstalledModule[]
84
+ }
85
+ return packedModulesToInstalled(modules as ServerContract.Types.module_entry[])
86
+ }
87
+
88
+ interface ProjectedCaps {
89
+ hullmass?: UInt32
90
+ capacity?: UInt32
91
+ engines?: ServerContract.Types.movement_stats
92
+ generator?: ServerContract.Types.energy_stats
93
+ loaderLanes: ServerContract.Types.loader_lane[]
94
+ gathererLanes: ServerContract.Types.gatherer_lane[]
95
+ crafterLanes: ServerContract.Types.crafter_lane[]
96
+ hauler?: ServerContract.Types.hauler_stats
97
+ launcher?: ServerContract.Types.launcher_stats
98
+ }
99
+
100
+ function recomputeCaps(entity: Projectable): ProjectedCaps | undefined {
101
+ if (
102
+ entity.item_id === undefined ||
103
+ entity.modules === undefined ||
104
+ entity.stats === undefined
105
+ ) {
106
+ return undefined
107
+ }
108
+
109
+ const itemId = Number(
110
+ typeof entity.item_id === 'number' ? entity.item_id : entity.item_id.value
111
+ )
112
+ const hullStats = decodeCraftedItemStats(itemId, entity.stats)
113
+ if (hullStats.strength === undefined) hullStats.strength = decodeStat(entity.stats, 0)
114
+ if (hullStats.hardness === undefined) hullStats.hardness = decodeStat(entity.stats, 2)
115
+ const layout = getEntityLayout(itemId)?.slots ?? []
116
+ const installed = toInstalledModules(entity.modules)
117
+ const caps = computeEntityCapabilities(hullStats, itemId, installed, layout)
118
+
119
+ const toLoaderLane = (l: {
120
+ slotIndex: number
121
+ mass: number
122
+ thrust: number
123
+ outputPct: number
124
+ }): ServerContract.Types.loader_lane =>
125
+ ServerContract.Types.loader_lane.from({
126
+ slot_index: l.slotIndex,
127
+ mass: l.mass,
128
+ thrust: l.thrust,
129
+ output_pct: l.outputPct,
130
+ })
131
+
132
+ const toGathererLane = (l: {
133
+ slotIndex: number
134
+ yield: number
135
+ drain: number
136
+ depth: number
137
+ outputPct: number
138
+ }): ServerContract.Types.gatherer_lane =>
139
+ ServerContract.Types.gatherer_lane.from({
140
+ slot_index: l.slotIndex,
141
+ yield: l.yield,
142
+ drain: l.drain,
143
+ depth: l.depth,
144
+ output_pct: l.outputPct,
145
+ })
146
+
147
+ const toCrafterLane = (l: {
148
+ slotIndex: number
149
+ speed: number
150
+ drain: number
151
+ outputPct: number
152
+ }): ServerContract.Types.crafter_lane =>
153
+ ServerContract.Types.crafter_lane.from({
154
+ slot_index: l.slotIndex,
155
+ speed: l.speed,
156
+ drain: l.drain,
157
+ output_pct: l.outputPct,
158
+ })
159
+
160
+ return {
161
+ hullmass: UInt32.from(caps.hullmass),
162
+ capacity: UInt32.from(caps.capacity),
163
+ engines: caps.engines ? ServerContract.Types.movement_stats.from(caps.engines) : undefined,
164
+ generator: caps.generator
165
+ ? ServerContract.Types.energy_stats.from(caps.generator)
166
+ : undefined,
167
+ loaderLanes: (caps.loaderLanes ?? []).map(toLoaderLane),
168
+ gathererLanes: (caps.gathererLanes ?? []).map(toGathererLane),
169
+ crafterLanes: (caps.crafterLanes ?? []).map(toCrafterLane),
170
+ hauler: caps.hauler ? ServerContract.Types.hauler_stats.from(caps.hauler) : undefined,
171
+ launcher: caps.launcher
172
+ ? ServerContract.Types.launcher_stats.from({
173
+ charge_rate: caps.launcher.chargeRate,
174
+ velocity: caps.launcher.velocity,
175
+ drain: caps.launcher.drain,
176
+ })
177
+ : undefined,
178
+ }
179
+ }
180
+
181
+ function loaderLanesTotalMass(lanes: ServerContract.Types.loader_lane[]): UInt64 {
182
+ let total = 0
183
+ for (const l of lanes) total += Number(l.mass)
184
+ return UInt64.from(total)
70
185
  }
71
186
 
72
187
  export function createProjectedEntity(entity: Projectable): ProjectedEntity {
73
- const shipMass = getHullMass(entity)
74
- const loaders = entity.loaders
75
- const engines = entity.engines
76
- const generator = entity.generator
77
- const hauler = entity.hauler
78
- const capacity = entity.capacity
188
+ const needsRecompute =
189
+ entity.hullmass === undefined ||
190
+ entity.loader_lanes === undefined ||
191
+ entity.engines === undefined ||
192
+ entity.generator === undefined ||
193
+ entity.hauler === undefined ||
194
+ entity.launcher === undefined ||
195
+ entity.capacity === undefined
196
+ const caps = needsRecompute ? recomputeCaps(entity) : undefined
197
+
198
+ const shipMass = UInt32.from(entity.hullmass ?? caps?.hullmass ?? 0)
199
+ const loaderLanes = entity.loader_lanes ?? caps?.loaderLanes ?? []
200
+ const gathererLanes = entity.gatherer_lanes ?? caps?.gathererLanes ?? []
201
+ const crafterLanes = entity.crafter_lanes ?? caps?.crafterLanes ?? []
202
+ const engines = entity.engines ?? caps?.engines
203
+ const generator = entity.generator ?? caps?.generator
204
+ const hauler = entity.hauler ?? caps?.hauler
205
+ const launcher = entity.launcher ?? caps?.launcher
206
+ const capacity = entity.capacity ?? caps?.capacity
79
207
 
80
208
  const cargo: CargoStack[] = entity.cargo.map(cargoItemToStack)
81
209
 
@@ -88,18 +216,19 @@ export function createProjectedEntity(entity: Projectable): ProjectedEntity {
88
216
  engines,
89
217
  generator,
90
218
  hauler,
91
- loaders,
219
+ launcher,
220
+ loaderLanes,
221
+ gathererLanes,
222
+ crafterLanes,
92
223
 
93
224
  get cargoMass() {
94
225
  return calcStacksMass(this.cargo)
95
226
  },
96
227
 
97
228
  get totalMass() {
98
- let mass = UInt64.from(this.shipMass).adding(this.cargoMass)
99
- if (this.loaders) {
100
- mass = mass.adding(this.loaders.mass.multiplying(this.loaders.quantity))
101
- }
102
- return mass
229
+ return UInt64.from(this.shipMass)
230
+ .adding(this.cargoMass)
231
+ .adding(loaderLanesTotalMass(this.loaderLanes))
103
232
  },
104
233
 
105
234
  hasMovement() {
@@ -111,7 +240,11 @@ export function createProjectedEntity(entity: Projectable): ProjectedEntity {
111
240
  },
112
241
 
113
242
  hasLoaders() {
114
- return capsHasLoaders(this.capabilities())
243
+ return this.loaderLanes.length > 0
244
+ },
245
+
246
+ hasLauncher() {
247
+ return this.launcher !== undefined
115
248
  },
116
249
 
117
250
  capabilities(): EntityCapabilities {
@@ -120,7 +253,7 @@ export function createProjectedEntity(entity: Projectable): ProjectedEntity {
120
253
  capacity: this.capacity ? UInt32.from(this.capacity) : undefined,
121
254
  engines: this.engines,
122
255
  generator: this.generator,
123
- loaders: this.loaders,
256
+ launcher: this.launcher,
124
257
  }
125
258
  },
126
259
 
@@ -160,25 +293,22 @@ function applyFlightTask(
160
293
  task: ServerContract.Types.task,
161
294
  options: {complete: boolean; progress?: number}
162
295
  ): void {
163
- if (!task.coordinates || !projected.engines) return
296
+ if (!task.coordinates) return
164
297
 
165
- const origin = projected.location
166
298
  const destination = Coordinates.from(task.coordinates)
167
- const distance = distanceBetweenCoordinates(origin, task.coordinates)
168
- const energyUsage = distance.dividing(PRECISION).multiplying(projected.engines.drain)
169
299
 
170
300
  if (options.complete) {
171
- projected.energy = projected.energy.gt(energyUsage)
172
- ? UInt16.from(projected.energy.subtracting(energyUsage))
173
- : UInt16.from(0)
301
+ applyEnergyCost(projected, task)
174
302
  projected.location = destination
175
303
  } else if (options.progress !== undefined) {
176
- const interpolated = lerp(origin, destination, options.progress)
304
+ const interpolated = lerp(projected.location, destination, options.progress)
177
305
  projected.location = Coordinates.from({
178
306
  x: Math.round(interpolated.x),
179
307
  y: Math.round(interpolated.y),
180
308
  })
181
- const partialEnergy = UInt64.from(Math.floor(Number(energyUsage) * options.progress))
309
+ const partialEnergy = UInt64.from(
310
+ Math.floor(Number(task.energy_cost ?? 0) * options.progress)
311
+ )
182
312
  projected.energy = projected.energy.gt(partialEnergy)
183
313
  ? UInt16.from(projected.energy.subtracting(partialEnergy))
184
314
  : UInt16.from(0)
@@ -190,7 +320,7 @@ function addCargoItem(projected: ProjectedEntity, item: ServerContract.Types.car
190
320
  }
191
321
 
192
322
  function removeCargoItem(projected: ProjectedEntity, item: ServerContract.Types.cargo_item): void {
193
- projected.cargo = removeFromStacks(projected.cargo, cargoItemToStack(item))
323
+ projected.cargo = subtractFromStacks(projected.cargo, cargoItemToStack(item))
194
324
  }
195
325
 
196
326
  function applyAddCargoTask(projected: ProjectedEntity, task: ServerContract.Types.task): void {
@@ -248,6 +378,8 @@ function applyTask(projected: ProjectedEntity, task: ServerContract.Types.task):
248
378
  applyRechargeTask(projected, task, {complete: true})
249
379
  break
250
380
  case TaskType.TRAVEL:
381
+ case TaskType.WARP:
382
+ case TaskType.TRANSIT:
251
383
  applyFlightTask(projected, task, {complete: true})
252
384
  break
253
385
  case TaskType.LOAD:
@@ -255,7 +387,6 @@ function applyTask(projected: ProjectedEntity, task: ServerContract.Types.task):
255
387
  applyAddCargoTask(projected, task)
256
388
  break
257
389
  case TaskType.UNLOAD:
258
- case TaskType.WRAP:
259
390
  applyRemoveCargoTask(projected, task)
260
391
  break
261
392
  case TaskType.GATHER:
@@ -267,6 +398,9 @@ function applyTask(projected: ProjectedEntity, task: ServerContract.Types.task):
267
398
  case TaskType.DEPLOY:
268
399
  applyDeployTask(projected, task)
269
400
  break
401
+ case TaskType.UNDEPLOY:
402
+ case TaskType.DEMOLISH:
403
+ break
270
404
  }
271
405
  }
272
406
 
@@ -276,50 +410,27 @@ export interface ProjectionOptions {
276
410
 
277
411
  export function projectEntity(entity: Projectable, options?: ProjectionOptions): ProjectedEntity {
278
412
  const projected = createProjectedEntity(entity)
279
- if (!entity.schedule || entity.schedule.tasks.length === 0) return projected
413
+ const ordered = schedule.orderedTasks(entity)
414
+ if (ordered.length === 0) return projected
280
415
 
281
- const tasks = entity.schedule.tasks
282
416
  const taskCount =
283
417
  options?.upToTaskIndex !== undefined
284
- ? Math.max(0, Math.min(options.upToTaskIndex, tasks.length))
285
- : tasks.length
418
+ ? Math.max(0, Math.min(options.upToTaskIndex, ordered.length))
419
+ : ordered.length
286
420
 
287
421
  for (let i = 0; i < taskCount; i++) {
288
- applyTask(projected, tasks[i])
422
+ applyTask(projected, ordered[i].task)
289
423
  }
290
424
  return projected
291
425
  }
292
426
 
293
- export interface ProjectableSnapshot extends Projectable {
294
- current_task?: ServerContract.Types.task
295
- pending_tasks?: ServerContract.Types.task[]
296
- }
297
-
298
- function buildRemainingProjectable(snapshot: ProjectableSnapshot): Projectable | null {
299
- if (!snapshot.schedule) return null
300
- const remainingTasks: ServerContract.Types.task[] = []
301
- if (snapshot.current_task) remainingTasks.push(snapshot.current_task)
302
- if (snapshot.pending_tasks?.length) remainingTasks.push(...snapshot.pending_tasks)
303
- if (remainingTasks.length === 0) return null
304
-
305
- const completedCount = snapshot.schedule.tasks.length - remainingTasks.length
306
- let startedMs = snapshot.schedule.started.toMilliseconds()
307
- for (let i = 0; i < completedCount; i++) {
308
- startedMs += snapshot.schedule.tasks[i].duration.toNumber() * 1000
309
- }
310
-
311
- return {
312
- ...snapshot,
313
- schedule: ServerContract.Types.schedule.from({
314
- started: TimePoint.fromMilliseconds(startedMs),
315
- tasks: remainingTasks,
316
- }),
427
+ export function projectRemainingAt(entity: Projectable, _now: Date): ProjectedEntity {
428
+ // Resolve is lazy/entity-global: completed tasks are unsettled until resolve, so replay all.
429
+ const projected = createProjectedEntity(entity)
430
+ for (const {task} of schedule.orderedTasks(entity)) {
431
+ applyTask(projected, task)
317
432
  }
318
- }
319
-
320
- export function projectFromCurrentState(snapshot: ProjectableSnapshot): ProjectedEntity {
321
- const projectable = buildRemainingProjectable(snapshot)
322
- return projectable ? projectEntity(projectable) : createProjectedEntity(snapshot)
433
+ return projected
323
434
  }
324
435
 
325
436
  function getRecipeInputsForOutput(outputItemId: number): RecipeInput[] | undefined {
@@ -342,19 +453,10 @@ function validateCraftTask(task: ServerContract.Types.task, projected: Projected
342
453
  let matched = false
343
454
  for (let ri = 0; ri < recipe.length; ri++) {
344
455
  const req = recipe[ri]
345
- if ('itemId' in req) {
346
- if (input.item_id.toNumber() === req.itemId) {
347
- groupedInputs[ri].push(input)
348
- matched = true
349
- break
350
- }
351
- } else {
352
- const item = getItem(input.item_id)
353
- if (item.category === req.category && item.tier === req.tier) {
354
- groupedInputs[ri].push(input)
355
- matched = true
356
- break
357
- }
456
+ if (input.item_id.toNumber() === req.itemId) {
457
+ groupedInputs[ri].push(input)
458
+ matched = true
459
+ break
358
460
  }
359
461
  }
360
462
  if (!matched) throw new Error(RECIPE_INPUTS_INVALID)
@@ -385,15 +487,16 @@ function validateCraftTask(task: ServerContract.Types.task, projected: Projected
385
487
  break
386
488
  }
387
489
  }
388
- if (!found) throw new Error(SHIP_CARGO_NOT_LOADED)
490
+ if (!found) throw new Error(ENTITY_CARGO_NOT_LOADED)
389
491
  }
390
492
  }
391
493
 
392
494
  export function validateSchedule(entity: Projectable): void {
393
- if (!entity.schedule || entity.schedule.tasks.length === 0) return
495
+ const ordered = schedule.orderedTasks(entity)
496
+ if (ordered.length === 0) return
394
497
 
395
498
  const projected = createProjectedEntity(entity)
396
- for (const task of entity.schedule.tasks) {
499
+ for (const {task} of ordered) {
397
500
  if (task.type.toNumber() === TaskType.CRAFT) {
398
501
  validateCraftTask(task, projected)
399
502
  }
@@ -407,28 +510,35 @@ export function validateSchedule(entity: Projectable): void {
407
510
  export function projectEntityAt(entity: Projectable, now: Date): ProjectedEntity {
408
511
  const projected = createProjectedEntity(entity)
409
512
 
410
- if (!entity.schedule || entity.schedule.tasks.length === 0) {
513
+ const ordered = schedule.orderedTasks(entity)
514
+ if (ordered.length === 0) {
411
515
  return projected
412
516
  }
413
517
 
414
- for (let i = 0; i < entity.schedule.tasks.length; i++) {
415
- const task = entity.schedule.tasks[i]
416
- const taskComplete = schedule.isTaskComplete(entity, i, now)
417
- const taskInProgress = schedule.isTaskInProgress(entity, i, now)
518
+ const nowMs = now.getTime()
519
+
520
+ for (const {task, startsAt} of ordered) {
521
+ const duration = task.duration.toNumber()
522
+ const elapsed = Math.min(
523
+ Math.max(0, Math.floor((nowMs - startsAt.getTime()) / 1000)),
524
+ duration
525
+ )
526
+ const taskComplete = elapsed >= duration
527
+ const taskInProgress = elapsed > 0 && elapsed < duration
418
528
 
419
529
  if (!taskComplete && !taskInProgress) {
420
- break
530
+ continue
421
531
  }
422
532
 
423
- const progress = taskInProgress
424
- ? schedule.getTaskElapsed(entity, i, now) / task.duration.toNumber()
425
- : undefined
533
+ const progress = taskInProgress ? elapsed / duration : undefined
426
534
 
427
535
  switch (task.type.toNumber()) {
428
536
  case TaskType.RECHARGE:
429
537
  applyRechargeTask(projected, task, {complete: taskComplete, progress})
430
538
  break
431
539
  case TaskType.TRAVEL:
540
+ case TaskType.WARP:
541
+ case TaskType.TRANSIT:
432
542
  applyFlightTask(projected, task, {complete: taskComplete, progress})
433
543
  break
434
544
  case TaskType.LOAD:
@@ -436,7 +546,6 @@ export function projectEntityAt(entity: Projectable, now: Date): ProjectedEntity
436
546
  if (taskComplete) applyAddCargoTask(projected, task)
437
547
  break
438
548
  case TaskType.UNLOAD:
439
- case TaskType.WRAP:
440
549
  if (taskComplete) applyRemoveCargoTask(projected, task)
441
550
  break
442
551
  case TaskType.GATHER:
@@ -448,16 +557,11 @@ export function projectEntityAt(entity: Projectable, now: Date): ProjectedEntity
448
557
  case TaskType.DEPLOY:
449
558
  if (taskComplete) applyDeployTask(projected, task)
450
559
  break
560
+ case TaskType.UNDEPLOY:
561
+ case TaskType.DEMOLISH:
562
+ break
451
563
  }
452
564
  }
453
565
 
454
566
  return projected
455
567
  }
456
-
457
- export function projectFromCurrentStateAt(
458
- snapshot: ProjectableSnapshot,
459
- now: Date
460
- ): ProjectedEntity {
461
- const projectable = buildRemainingProjectable(snapshot)
462
- return projectable ? projectEntityAt(projectable, now) : createProjectedEntity(snapshot)
463
- }