@shipload/sdk 0.7.1 → 1.0.0-beta1

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 (94) hide show
  1. package/lib/shipload.d.ts +2730 -287
  2. package/lib/shipload.js +10862 -2229
  3. package/lib/shipload.js.map +1 -1
  4. package/lib/shipload.m.js +10434 -2171
  5. package/lib/shipload.m.js.map +1 -1
  6. package/package.json +11 -20
  7. package/src/capabilities/crafting.ts +22 -0
  8. package/src/capabilities/gathering.ts +36 -0
  9. package/src/capabilities/guards.ts +38 -0
  10. package/src/capabilities/hauling.ts +22 -0
  11. package/src/capabilities/index.ts +8 -0
  12. package/src/capabilities/loading.ts +8 -0
  13. package/src/capabilities/modules.ts +86 -0
  14. package/src/capabilities/movement.ts +29 -0
  15. package/src/capabilities/storage.ts +159 -0
  16. package/src/contracts/server.ts +1389 -285
  17. package/src/data/capabilities.ts +408 -0
  18. package/src/data/catalog.ts +135 -0
  19. package/src/data/categories.ts +55 -0
  20. package/src/data/colors.ts +84 -0
  21. package/src/data/entities.json +50 -0
  22. package/src/data/item-ids.ts +75 -0
  23. package/src/data/items.json +252 -0
  24. package/src/data/locations.ts +53 -0
  25. package/src/data/metadata.ts +208 -0
  26. package/src/data/nebula-adjectives.json +211 -0
  27. package/src/data/nebula-nouns.json +151 -0
  28. package/src/data/recipes-runtime.ts +65 -0
  29. package/src/data/recipes.json +878 -0
  30. package/src/data/syllables.json +1790 -0
  31. package/src/data/tiers.ts +45 -0
  32. package/src/derivation/crafting.ts +350 -0
  33. package/src/derivation/index.ts +32 -0
  34. package/src/derivation/location-size.ts +15 -0
  35. package/src/derivation/resources.ts +112 -0
  36. package/src/derivation/stats.ts +146 -0
  37. package/src/derivation/strata.ts +43 -0
  38. package/src/derivation/stratum.ts +134 -0
  39. package/src/derivation/tiers.ts +54 -0
  40. package/src/entities/cargo-utils.ts +84 -0
  41. package/src/entities/container.ts +108 -0
  42. package/src/entities/entity-inventory.ts +39 -0
  43. package/src/entities/gamestate.ts +152 -0
  44. package/src/entities/inventory-accessor.ts +42 -0
  45. package/src/entities/location.ts +60 -0
  46. package/src/entities/makers.ts +196 -0
  47. package/src/entities/player.ts +15 -0
  48. package/src/entities/ship-deploy.ts +258 -0
  49. package/src/entities/ship.ts +204 -0
  50. package/src/entities/warehouse.ts +119 -0
  51. package/src/errors.ts +100 -9
  52. package/src/format.ts +12 -0
  53. package/src/index-module.ts +317 -7
  54. package/src/managers/actions.ts +250 -0
  55. package/src/managers/base.ts +25 -0
  56. package/src/managers/context.ts +114 -0
  57. package/src/managers/entities.ts +103 -0
  58. package/src/managers/epochs.ts +47 -0
  59. package/src/managers/index.ts +9 -0
  60. package/src/managers/locations.ts +68 -0
  61. package/src/managers/players.ts +13 -0
  62. package/src/nft/description.ts +176 -0
  63. package/src/nft/deserializers.ts +83 -0
  64. package/src/nft/index.ts +2 -0
  65. package/src/resolution/describe-module.ts +166 -0
  66. package/src/resolution/display-name.ts +39 -0
  67. package/src/resolution/resolve-item.ts +358 -0
  68. package/src/scheduling/accessor.ts +82 -0
  69. package/src/{epoch.ts → scheduling/epoch.ts} +1 -1
  70. package/src/scheduling/projection.ts +463 -0
  71. package/src/scheduling/schedule.ts +179 -0
  72. package/src/shipload.ts +47 -160
  73. package/src/subscriptions/connection.ts +154 -0
  74. package/src/subscriptions/debug.ts +17 -0
  75. package/src/subscriptions/index.ts +5 -0
  76. package/src/subscriptions/manager.ts +240 -0
  77. package/src/subscriptions/mappers.ts +28 -0
  78. package/src/subscriptions/types.ts +143 -0
  79. package/src/travel/travel.ts +500 -0
  80. package/src/types/capabilities.ts +76 -0
  81. package/src/types/entity-traits.ts +69 -0
  82. package/src/types/entity.ts +39 -0
  83. package/src/types/index.ts +3 -0
  84. package/src/types.ts +140 -35
  85. package/src/{hash.ts → utils/hash.ts} +2 -2
  86. package/src/utils/system.ts +168 -0
  87. package/src/goods.ts +0 -124
  88. package/src/market.ts +0 -214
  89. package/src/rolls.ts +0 -8
  90. package/src/ship.ts +0 -36
  91. package/src/state.ts +0 -0
  92. package/src/syllables.ts +0 -1184
  93. package/src/system.ts +0 -37
  94. package/src/travel.ts +0 -259
@@ -0,0 +1,463 @@
1
+ import {Name, TimePoint, UInt16, UInt32, UInt64} from '@wharfkit/antelope'
2
+ import {ServerContract} from '../contracts'
3
+ import {Coordinates, PRECISION, TaskType} from '../types'
4
+ import {
5
+ capsHasLoaders,
6
+ capsHasMovement,
7
+ capsHasStorage,
8
+ type EntityCapabilities,
9
+ type EntityState,
10
+ } from '../types/capabilities'
11
+ import {
12
+ ENTITY_CAPACITY_EXCEEDED,
13
+ RECIPE_INPUTS_EXCESS,
14
+ RECIPE_INPUTS_INSUFFICIENT,
15
+ RECIPE_INPUTS_INVALID,
16
+ RECIPE_NOT_FOUND,
17
+ SHIP_CARGO_NOT_LOADED,
18
+ } from '../errors'
19
+ import {getRecipe, type RecipeInput} from '../data/recipes-runtime'
20
+ import {getItem} from '../data/catalog'
21
+ import {distanceBetweenCoordinates, lerp} from '../travel/travel'
22
+ import {
23
+ calcStacksMass,
24
+ cargoItemToStack,
25
+ type CargoStack,
26
+ mergeStacks,
27
+ removeFromStacks,
28
+ stackToCargoItem,
29
+ } from '../capabilities/storage'
30
+ import * as schedule from './schedule'
31
+ import type {ScheduleData} from './schedule'
32
+
33
+ export interface ProjectedEntity {
34
+ location: Coordinates
35
+ energy: UInt16
36
+ cargo: CargoStack[]
37
+ shipMass: UInt32
38
+ capacity?: UInt64
39
+ engines?: ServerContract.Types.movement_stats
40
+ loaders?: ServerContract.Types.loader_stats
41
+ generator?: ServerContract.Types.energy_stats
42
+ hauler?: ServerContract.Types.hauler_stats
43
+ readonly cargoMass: UInt64
44
+ readonly totalMass: UInt64
45
+
46
+ hasMovement(): boolean
47
+ hasStorage(): boolean
48
+ hasLoaders(): boolean
49
+
50
+ capabilities(): EntityCapabilities
51
+ state(): EntityState
52
+ }
53
+
54
+ export interface Projectable extends ScheduleData {
55
+ coordinates: Coordinates | ServerContract.Types.coordinates
56
+ energy?: UInt16
57
+ hullmass?: UInt32
58
+ generator?: ServerContract.Types.energy_stats
59
+ engines?: ServerContract.Types.movement_stats
60
+ loaders?: ServerContract.Types.loader_stats
61
+ hauler?: ServerContract.Types.hauler_stats
62
+ capacity?: UInt32
63
+ cargo: ServerContract.Types.cargo_item[]
64
+ cargomass: UInt32
65
+ owner?: Name
66
+ }
67
+
68
+ function getHullMass(entity: Projectable): UInt32 {
69
+ return UInt32.from(entity.hullmass ?? 0)
70
+ }
71
+
72
+ 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
79
+
80
+ const cargo: CargoStack[] = entity.cargo.map(cargoItemToStack)
81
+
82
+ const projected: ProjectedEntity = {
83
+ location: Coordinates.from(entity.coordinates),
84
+ energy: UInt16.from(entity.energy ?? 0),
85
+ cargo,
86
+ shipMass,
87
+ capacity: capacity ? UInt64.from(capacity) : undefined,
88
+ engines,
89
+ generator,
90
+ hauler,
91
+ loaders,
92
+
93
+ get cargoMass() {
94
+ return calcStacksMass(this.cargo)
95
+ },
96
+
97
+ 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
103
+ },
104
+
105
+ hasMovement() {
106
+ return capsHasMovement(this.capabilities())
107
+ },
108
+
109
+ hasStorage() {
110
+ return capsHasStorage(this.capabilities())
111
+ },
112
+
113
+ hasLoaders() {
114
+ return capsHasLoaders(this.capabilities())
115
+ },
116
+
117
+ capabilities(): EntityCapabilities {
118
+ return {
119
+ hullmass: this.shipMass,
120
+ capacity: this.capacity ? UInt32.from(this.capacity) : undefined,
121
+ engines: this.engines,
122
+ generator: this.generator,
123
+ loaders: this.loaders,
124
+ }
125
+ },
126
+
127
+ state(): EntityState {
128
+ return {
129
+ owner: entity.owner ?? Name.from(''),
130
+ location: ServerContract.Types.coordinates.from(this.location),
131
+ energy: this.energy,
132
+ cargomass: UInt32.from(this.cargoMass),
133
+ cargo: this.cargo.map(stackToCargoItem),
134
+ }
135
+ },
136
+ }
137
+
138
+ return projected
139
+ }
140
+
141
+ function applyRechargeTask(
142
+ projected: ProjectedEntity,
143
+ _task: ServerContract.Types.task,
144
+ options: {complete: boolean; progress?: number}
145
+ ): void {
146
+ if (!projected.generator) return
147
+
148
+ if (options.complete) {
149
+ projected.energy = UInt16.from(projected.generator.capacity)
150
+ } else if (options.progress !== undefined) {
151
+ const capacity = Number(projected.generator.capacity)
152
+ const currentEnergy = Number(projected.energy)
153
+ const rechargeAmount = (capacity - currentEnergy) * options.progress
154
+ projected.energy = UInt16.from(Math.min(capacity, currentEnergy + rechargeAmount))
155
+ }
156
+ }
157
+
158
+ function applyFlightTask(
159
+ projected: ProjectedEntity,
160
+ task: ServerContract.Types.task,
161
+ options: {complete: boolean; progress?: number}
162
+ ): void {
163
+ if (!task.coordinates || !projected.engines) return
164
+
165
+ const origin = projected.location
166
+ const destination = Coordinates.from(task.coordinates)
167
+ const distance = distanceBetweenCoordinates(origin, task.coordinates)
168
+ const energyUsage = distance.dividing(PRECISION).multiplying(projected.engines.drain)
169
+
170
+ if (options.complete) {
171
+ projected.energy = projected.energy.gt(energyUsage)
172
+ ? UInt16.from(projected.energy.subtracting(energyUsage))
173
+ : UInt16.from(0)
174
+ projected.location = destination
175
+ } else if (options.progress !== undefined) {
176
+ const interpolated = lerp(origin, destination, options.progress)
177
+ projected.location = Coordinates.from({
178
+ x: Math.round(interpolated.x),
179
+ y: Math.round(interpolated.y),
180
+ })
181
+ const partialEnergy = UInt64.from(Math.floor(Number(energyUsage) * options.progress))
182
+ projected.energy = projected.energy.gt(partialEnergy)
183
+ ? UInt16.from(projected.energy.subtracting(partialEnergy))
184
+ : UInt16.from(0)
185
+ }
186
+ }
187
+
188
+ function addCargoItem(projected: ProjectedEntity, item: ServerContract.Types.cargo_item): void {
189
+ projected.cargo = mergeStacks(projected.cargo, cargoItemToStack(item))
190
+ }
191
+
192
+ function removeCargoItem(projected: ProjectedEntity, item: ServerContract.Types.cargo_item): void {
193
+ projected.cargo = removeFromStacks(projected.cargo, cargoItemToStack(item))
194
+ }
195
+
196
+ function applyAddCargoTask(projected: ProjectedEntity, task: ServerContract.Types.task): void {
197
+ for (const item of task.cargo) {
198
+ addCargoItem(projected, item)
199
+ }
200
+ }
201
+
202
+ function applyRemoveCargoTask(projected: ProjectedEntity, task: ServerContract.Types.task): void {
203
+ for (const item of task.cargo) {
204
+ removeCargoItem(projected, item)
205
+ }
206
+ }
207
+
208
+ function applyEnergyCost(projected: ProjectedEntity, task: ServerContract.Types.task): void {
209
+ if (!task.energy_cost) return
210
+ const energyCost = UInt16.from(task.energy_cost)
211
+ projected.energy = projected.energy.gt(energyCost)
212
+ ? UInt16.from(projected.energy.subtracting(energyCost))
213
+ : UInt16.from(0)
214
+ }
215
+
216
+ function applyGatherTask(
217
+ projected: ProjectedEntity,
218
+ task: ServerContract.Types.task,
219
+ options: {complete: boolean}
220
+ ): void {
221
+ if (!options.complete) return
222
+ applyEnergyCost(projected, task)
223
+ if (!task.entitytarget) {
224
+ applyAddCargoTask(projected, task)
225
+ }
226
+ }
227
+
228
+ function applyCraftTask(projected: ProjectedEntity, task: ServerContract.Types.task): void {
229
+ applyEnergyCost(projected, task)
230
+ if (task.cargo.length === 0) return
231
+
232
+ for (let i = 0; i < task.cargo.length - 1; i++) {
233
+ removeCargoItem(projected, task.cargo[i])
234
+ }
235
+ addCargoItem(projected, task.cargo[task.cargo.length - 1])
236
+ }
237
+
238
+ function applyDeployTask(projected: ProjectedEntity, task: ServerContract.Types.task): void {
239
+ applyEnergyCost(projected, task)
240
+ if (task.cargo.length > 0) {
241
+ removeCargoItem(projected, task.cargo[0])
242
+ }
243
+ }
244
+
245
+ function applyTask(projected: ProjectedEntity, task: ServerContract.Types.task): void {
246
+ switch (task.type.toNumber()) {
247
+ case TaskType.RECHARGE:
248
+ applyRechargeTask(projected, task, {complete: true})
249
+ break
250
+ case TaskType.TRAVEL:
251
+ applyFlightTask(projected, task, {complete: true})
252
+ break
253
+ case TaskType.LOAD:
254
+ case TaskType.UNWRAP:
255
+ applyAddCargoTask(projected, task)
256
+ break
257
+ case TaskType.UNLOAD:
258
+ case TaskType.WRAP:
259
+ applyRemoveCargoTask(projected, task)
260
+ break
261
+ case TaskType.GATHER:
262
+ applyGatherTask(projected, task, {complete: true})
263
+ break
264
+ case TaskType.CRAFT:
265
+ applyCraftTask(projected, task)
266
+ break
267
+ case TaskType.DEPLOY:
268
+ applyDeployTask(projected, task)
269
+ break
270
+ }
271
+ }
272
+
273
+ export interface ProjectionOptions {
274
+ upToTaskIndex?: number
275
+ }
276
+
277
+ export function projectEntity(entity: Projectable, options?: ProjectionOptions): ProjectedEntity {
278
+ const projected = createProjectedEntity(entity)
279
+ if (!entity.schedule || entity.schedule.tasks.length === 0) return projected
280
+
281
+ const tasks = entity.schedule.tasks
282
+ const taskCount =
283
+ options?.upToTaskIndex !== undefined
284
+ ? Math.max(0, Math.min(options.upToTaskIndex, tasks.length))
285
+ : tasks.length
286
+
287
+ for (let i = 0; i < taskCount; i++) {
288
+ applyTask(projected, tasks[i])
289
+ }
290
+ return projected
291
+ }
292
+
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
+ }),
317
+ }
318
+ }
319
+
320
+ export function projectFromCurrentState(snapshot: ProjectableSnapshot): ProjectedEntity {
321
+ const projectable = buildRemainingProjectable(snapshot)
322
+ return projectable ? projectEntity(projectable) : createProjectedEntity(snapshot)
323
+ }
324
+
325
+ function getRecipeInputsForOutput(outputItemId: number): RecipeInput[] | undefined {
326
+ const recipe = getRecipe(outputItemId)
327
+ return recipe?.inputs
328
+ }
329
+
330
+ function validateCraftTask(task: ServerContract.Types.task, projected: ProjectedEntity): void {
331
+ if (task.cargo.length === 0) return
332
+
333
+ const output = task.cargo[task.cargo.length - 1]
334
+ const inputs = task.cargo.slice(0, -1)
335
+ const craftQuantity = output.quantity.toNumber()
336
+
337
+ const recipe = getRecipeInputsForOutput(output.item_id.toNumber())
338
+ if (!recipe) throw new Error(RECIPE_NOT_FOUND)
339
+
340
+ const groupedInputs: ServerContract.Types.cargo_item[][] = recipe.map(() => [])
341
+ for (const input of inputs) {
342
+ let matched = false
343
+ for (let ri = 0; ri < recipe.length; ri++) {
344
+ 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
+ }
358
+ }
359
+ }
360
+ if (!matched) throw new Error(RECIPE_INPUTS_INVALID)
361
+ }
362
+
363
+ for (let ri = 0; ri < recipe.length; ri++) {
364
+ const stacks = groupedInputs[ri]
365
+ let provided = 0
366
+ for (const stack of stacks) {
367
+ provided += stack.quantity.toNumber()
368
+ }
369
+ const required = recipe[ri].quantity * craftQuantity
370
+ if (provided < required) throw new Error(RECIPE_INPUTS_INSUFFICIENT)
371
+ if (provided !== required) throw new Error(RECIPE_INPUTS_EXCESS)
372
+ }
373
+
374
+ for (const input of inputs) {
375
+ let found = false
376
+ for (const pc of projected.cargo) {
377
+ if (
378
+ pc.item_id.toNumber() === input.item_id.toNumber() &&
379
+ pc.stats.toString() === input.stats.toString()
380
+ ) {
381
+ if (pc.quantity.toNumber() < input.quantity.toNumber()) {
382
+ throw new Error(RECIPE_INPUTS_INSUFFICIENT)
383
+ }
384
+ found = true
385
+ break
386
+ }
387
+ }
388
+ if (!found) throw new Error(SHIP_CARGO_NOT_LOADED)
389
+ }
390
+ }
391
+
392
+ export function validateSchedule(entity: Projectable): void {
393
+ if (!entity.schedule || entity.schedule.tasks.length === 0) return
394
+
395
+ const projected = createProjectedEntity(entity)
396
+ for (const task of entity.schedule.tasks) {
397
+ if (task.type.toNumber() === TaskType.CRAFT) {
398
+ validateCraftTask(task, projected)
399
+ }
400
+ applyTask(projected, task)
401
+ if (projected.capacity && projected.cargoMass.gt(projected.capacity)) {
402
+ throw new Error(ENTITY_CAPACITY_EXCEEDED)
403
+ }
404
+ }
405
+ }
406
+
407
+ export function projectEntityAt(entity: Projectable, now: Date): ProjectedEntity {
408
+ const projected = createProjectedEntity(entity)
409
+
410
+ if (!entity.schedule || entity.schedule.tasks.length === 0) {
411
+ return projected
412
+ }
413
+
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)
418
+
419
+ if (!taskComplete && !taskInProgress) {
420
+ break
421
+ }
422
+
423
+ const progress = taskInProgress
424
+ ? schedule.getTaskElapsed(entity, i, now) / task.duration.toNumber()
425
+ : undefined
426
+
427
+ switch (task.type.toNumber()) {
428
+ case TaskType.RECHARGE:
429
+ applyRechargeTask(projected, task, {complete: taskComplete, progress})
430
+ break
431
+ case TaskType.TRAVEL:
432
+ applyFlightTask(projected, task, {complete: taskComplete, progress})
433
+ break
434
+ case TaskType.LOAD:
435
+ case TaskType.UNWRAP:
436
+ if (taskComplete) applyAddCargoTask(projected, task)
437
+ break
438
+ case TaskType.UNLOAD:
439
+ case TaskType.WRAP:
440
+ if (taskComplete) applyRemoveCargoTask(projected, task)
441
+ break
442
+ case TaskType.GATHER:
443
+ if (taskComplete) applyGatherTask(projected, task, {complete: true})
444
+ break
445
+ case TaskType.CRAFT:
446
+ if (taskComplete) applyCraftTask(projected, task)
447
+ break
448
+ case TaskType.DEPLOY:
449
+ if (taskComplete) applyDeployTask(projected, task)
450
+ break
451
+ }
452
+ }
453
+
454
+ return projected
455
+ }
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
+ }
@@ -0,0 +1,179 @@
1
+ import type {ServerContract} from '../contracts'
2
+ import {TaskType} from '../types'
3
+
4
+ type Schedule = ServerContract.Types.schedule
5
+ type Task = ServerContract.Types.task
6
+
7
+ 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
29
+ }
30
+
31
+ export function hasSchedule(entity: ScheduleData): boolean {
32
+ return !!entity.schedule && entity.schedule.tasks.length > 0
33
+ }
34
+
35
+ export function isIdle(entity: ScheduleData): boolean {
36
+ return !hasSchedule(entity)
37
+ }
38
+
39
+ export function getTasks(entity: ScheduleData): Task[] {
40
+ return entity.schedule?.tasks || []
41
+ }
42
+
43
+ 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)
46
+ }
47
+
48
+ 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)
53
+ }
54
+
55
+ 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)
60
+ }
61
+
62
+ export function scheduleComplete(entity: ScheduleData, now: Date): boolean {
63
+ return hasSchedule(entity) && scheduleRemaining(entity, now) === 0
64
+ }
65
+
66
+ export function currentTaskIndex(entity: ScheduleData, now: Date): number {
67
+ if (!entity.schedule || entity.schedule.tasks.length === 0) return -1
68
+
69
+ const elapsed = scheduleElapsed(entity, now)
70
+ let timeAccum = 0
71
+
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
+ }
79
+
80
+ return entity.schedule.tasks.length - 1
81
+ }
82
+
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]
87
+ }
88
+
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
93
+ }
94
+
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()
100
+ }
101
+ return timeAccum
102
+ }
103
+
104
+ export function getTaskElapsed(entity: ScheduleData, index: number, now: Date): number {
105
+ if (!entity.schedule || index < 0 || index >= entity.schedule.tasks.length) return 0
106
+
107
+ const elapsed = scheduleElapsed(entity, now)
108
+ const taskStart = getTaskStartTime(entity, index)
109
+ const taskDuration = entity.schedule.tasks[index].duration.toNumber()
110
+
111
+ if (elapsed <= taskStart) return 0
112
+ const elapsedInTask = elapsed - taskStart
113
+ return Math.min(elapsedInTask, taskDuration)
114
+ }
115
+
116
+ export function getTaskRemaining(entity: ScheduleData, index: number, now: Date): number {
117
+ if (!entity.schedule || index < 0 || index >= entity.schedule.tasks.length) return 0
118
+
119
+ const taskDuration = entity.schedule.tasks[index].duration.toNumber()
120
+ const taskElapsed = getTaskElapsed(entity, index, now)
121
+ return Math.max(0, taskDuration - taskElapsed)
122
+ }
123
+
124
+ export function isTaskComplete(entity: ScheduleData, index: number, now: Date): boolean {
125
+ if (!entity.schedule || index < 0 || index >= entity.schedule.tasks.length) return false
126
+
127
+ const taskDuration = entity.schedule.tasks[index].duration.toNumber()
128
+ const taskElapsed = getTaskElapsed(entity, index, now)
129
+ return taskElapsed >= taskDuration
130
+ }
131
+
132
+ export function isTaskInProgress(entity: ScheduleData, index: number, now: Date): boolean {
133
+ if (!entity.schedule || index < 0 || index >= entity.schedule.tasks.length) return false
134
+
135
+ const taskElapsed = getTaskElapsed(entity, index, now)
136
+ const taskDuration = entity.schedule.tasks[index].duration.toNumber()
137
+ return taskElapsed > 0 && taskElapsed < taskDuration
138
+ }
139
+
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)
148
+ }
149
+
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)
155
+ }
156
+
157
+ export function isTaskType(entity: ScheduleData, taskType: TaskType, now: Date): boolean {
158
+ return currentTaskType(entity, now) === taskType
159
+ }
160
+
161
+ export function isInFlight(entity: ScheduleData, now: Date): boolean {
162
+ return isTaskType(entity, TaskType.TRAVEL, now)
163
+ }
164
+
165
+ export function isRecharging(entity: ScheduleData, now: Date): boolean {
166
+ return isTaskType(entity, TaskType.RECHARGE, now)
167
+ }
168
+
169
+ export function isLoading(entity: ScheduleData, now: Date): boolean {
170
+ return isTaskType(entity, TaskType.LOAD, now)
171
+ }
172
+
173
+ export function isUnloading(entity: ScheduleData, now: Date): boolean {
174
+ return isTaskType(entity, TaskType.UNLOAD, now)
175
+ }
176
+
177
+ export function isGathering(entity: ScheduleData, now: Date): boolean {
178
+ return isTaskType(entity, TaskType.GATHER, now)
179
+ }