@shipload/sdk 1.0.0-next.26 → 1.0.0-next.28

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 (40) hide show
  1. package/lib/shipload.d.ts +201 -86
  2. package/lib/shipload.js +843 -425
  3. package/lib/shipload.js.map +1 -1
  4. package/lib/shipload.m.js +825 -423
  5. package/lib/shipload.m.js.map +1 -1
  6. package/lib/testing.d.ts +13 -8
  7. package/lib/testing.js +41 -26
  8. package/lib/testing.js.map +1 -1
  9. package/lib/testing.m.js +41 -26
  10. package/lib/testing.m.js.map +1 -1
  11. package/package.json +1 -1
  12. package/src/capabilities/craftable.ts +51 -0
  13. package/src/contracts/server.ts +39 -18
  14. package/src/data/capabilities.ts +5 -0
  15. package/src/data/colors.ts +1 -1
  16. package/src/data/item-ids.ts +1 -1
  17. package/src/data/metadata.ts +3 -3
  18. package/src/data/recipes.json +10 -10
  19. package/src/derivation/capabilities.ts +11 -11
  20. package/src/derivation/index.ts +9 -0
  21. package/src/derivation/stars.test.ts +51 -0
  22. package/src/derivation/stars.ts +15 -0
  23. package/src/derivation/stats.ts +5 -4
  24. package/src/entities/entity.ts +1 -1
  25. package/src/entities/makers.ts +15 -6
  26. package/src/index-module.ts +33 -4
  27. package/src/managers/actions.ts +10 -1
  28. package/src/managers/construction.ts +67 -65
  29. package/src/nft/buildImmutableData.ts +13 -11
  30. package/src/nft/description.ts +2 -2
  31. package/src/resolution/resolve-item.ts +2 -2
  32. package/src/scheduling/accessor.ts +65 -23
  33. package/src/scheduling/availability.ts +108 -0
  34. package/src/scheduling/energy.ts +18 -11
  35. package/src/scheduling/lane-core.ts +130 -0
  36. package/src/scheduling/lanes.ts +60 -0
  37. package/src/scheduling/projection.ts +30 -54
  38. package/src/scheduling/schedule.ts +236 -121
  39. package/src/travel/travel.ts +21 -16
  40. package/src/types/capabilities.ts +1 -0
@@ -1,198 +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
- if (!hasSchedule(entity)) return false
64
- if ((entity.schedule?.tasks ?? []).some((t) => t.type.toNumber() === TaskType.RESERVED))
65
- return false
66
- return scheduleRemaining(entity, now) === 0
67
- }
68
-
69
- export function currentTaskIndex(entity: ScheduleData, now: Date): number {
70
- if (!entity.schedule || entity.schedule.tasks.length === 0) return -1
71
-
72
- const elapsed = scheduleElapsed(entity, now)
73
- let timeAccum = 0
74
-
75
- for (let i = 0; i < entity.schedule.tasks.length; i++) {
76
- const taskDuration = entity.schedule.tasks[i].duration.toNumber()
77
- if (elapsed < timeAccum + taskDuration) {
78
- return i
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
+ }
79
115
  }
80
- timeAccum += taskDuration
116
+ remaining = Math.max(remaining, core.laneRemaining(l.schedule, now))
81
117
  }
118
+ if (!hasAnyTask) return false
119
+ return remaining === 0
120
+ }
82
121
 
83
- 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
84
128
  }
85
129
 
86
- export function currentTask(entity: ScheduleData, now: Date): Task | undefined {
87
- const index = currentTaskIndex(entity, now)
88
- if (index < 0 || !entity.schedule) return undefined
89
- return entity.schedule.tasks[index]
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
90
137
  }
91
138
 
92
- export function currentTaskType(entity: ScheduleData, now: Date): TaskType | undefined {
93
- const task = currentTask(entity, now)
94
- if (!task) return undefined
95
- return task.type.toNumber() as TaskType
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
96
146
  }
97
147
 
98
- export function getTaskStartTime(entity: ScheduleData, index: number): number {
99
- if (!entity.schedule || index < 0 || index >= entity.schedule.tasks.length) return 0
100
- let timeAccum = 0
101
- for (let i = 0; i < index; i++) {
102
- timeAccum += entity.schedule.tasks[i].duration.toNumber()
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])
103
153
  }
104
- return timeAccum
154
+ return out
105
155
  }
106
156
 
107
- export function getTaskElapsed(entity: ScheduleData, index: number, now: Date): number {
108
- if (!entity.schedule || index < 0 || index >= entity.schedule.tasks.length) return 0
157
+ export interface ResolvedEvent {
158
+ laneKey: number
159
+ taskIndex: number
160
+ task: Task
161
+ completesAt: Date
162
+ }
109
163
 
110
- const elapsed = scheduleElapsed(entity, now)
111
- const taskStart = getTaskStartTime(entity, index)
112
- const taskDuration = entity.schedule.tasks[index].duration.toNumber()
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
176
+ }
113
177
 
114
- if (elapsed <= taskStart) return 0
115
- const elapsedInTask = elapsed - taskStart
116
- return Math.min(elapsedInTask, taskDuration)
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
+ }
193
+ }
194
+ events.sort(frontPrecedes)
195
+ return events
117
196
  }
118
197
 
119
- export function getTaskRemaining(entity: ScheduleData, index: number, now: Date): number {
120
- 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
+ }
205
+
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
+ }
121
224
 
122
- const taskDuration = entity.schedule.tasks[index].duration.toNumber()
123
- const taskElapsed = getTaskElapsed(entity, index, now)
124
- return Math.max(0, taskDuration - taskElapsed)
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
125
228
  }
126
229
 
127
- export function isTaskComplete(entity: ScheduleData, index: number, now: Date): boolean {
128
- if (!entity.schedule || index < 0 || index >= entity.schedule.tasks.length) return false
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
+ }
129
234
 
130
- if (entity.schedule.tasks[index].type.toNumber() === TaskType.RESERVED) return false
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
238
+ }
131
239
 
132
- const taskDuration = entity.schedule.tasks[index].duration.toNumber()
133
- const taskElapsed = getTaskElapsed(entity, index, now)
134
- return taskElapsed >= taskDuration
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
135
243
  }
136
244
 
137
- export function isTaskInProgress(entity: ScheduleData, index: number, now: Date): boolean {
138
- if (!entity.schedule || index < 0 || index >= entity.schedule.tasks.length) return false
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
253
+ }
139
254
 
140
- const taskElapsed = getTaskElapsed(entity, index, now)
141
- const taskDuration = entity.schedule.tasks[index].duration.toNumber()
142
- return taskElapsed > 0 && taskElapsed < taskDuration
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
143
263
  }
144
264
 
145
- export function currentTaskProgress(entity: ScheduleData, now: Date): number {
146
- const task = currentTask(entity, now)
147
- if (!task) return 0
148
- const index = currentTaskIndex(entity, now)
149
- const elapsed = getTaskElapsed(entity, index, now)
150
- const duration = task.duration.toNumber()
151
- if (duration === 0) return 1
152
- return Math.min(1, elapsed / duration)
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
153
273
  }
154
274
 
155
- export function currentTaskProgressFloat(entity: ScheduleData, now: Date): number {
156
- if (!entity.schedule || entity.schedule.tasks.length === 0) return 0
157
- const index = currentTaskIndex(entity, now)
158
- if (index < 0) return 0
159
- const task = entity.schedule.tasks[index]
160
- const durationMs = task.duration.toNumber() * 1000
161
- if (durationMs === 0) return 1
162
- const startedMs = entity.schedule.started.toDate().getTime()
163
- const taskStartMs = startedMs + getTaskStartTime(entity, index) * 1000
164
- const elapsedMs = now.getTime() - taskStartMs
165
- if (elapsedMs <= 0) return 0
166
- return Math.min(1, elapsedMs / durationMs)
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
167
283
  }
168
284
 
169
- export function scheduleProgress(entity: ScheduleData, now: Date): number {
170
- const duration = scheduleDuration(entity)
171
- if (duration === 0) return hasSchedule(entity) ? 1 : 0
172
- const elapsed = scheduleElapsed(entity, now)
173
- 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
174
288
  }
175
289
 
176
- export function isTaskType(entity: ScheduleData, taskType: TaskType, now: Date): boolean {
177
- 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)
178
292
  }
179
293
 
180
294
  export function isInFlight(entity: ScheduleData, now: Date): boolean {
181
- return isTaskType(entity, TaskType.TRAVEL, now)
295
+ const lane = mobilityLane(entity)
296
+ return lane ? core.currentTaskType(lane.schedule, now) === TaskType.TRAVEL : false
182
297
  }
183
298
 
184
299
  export function isRecharging(entity: ScheduleData, now: Date): boolean {
185
- return isTaskType(entity, TaskType.RECHARGE, now)
300
+ return entityDoesTaskType(entity, TaskType.RECHARGE, now)
186
301
  }
187
302
 
188
303
  export function isLoading(entity: ScheduleData, now: Date): boolean {
189
- return isTaskType(entity, TaskType.LOAD, now)
304
+ return entityDoesTaskType(entity, TaskType.LOAD, now)
190
305
  }
191
306
 
192
307
  export function isUnloading(entity: ScheduleData, now: Date): boolean {
193
- return isTaskType(entity, TaskType.UNLOAD, now)
308
+ return entityDoesTaskType(entity, TaskType.UNLOAD, now)
194
309
  }
195
310
 
196
311
  export function isGathering(entity: ScheduleData, now: Date): boolean {
197
- return isTaskType(entity, TaskType.GATHER, now)
312
+ return entityDoesTaskType(entity, TaskType.GATHER, now)
198
313
  }
@@ -35,6 +35,8 @@ import {
35
35
  } from '../types'
36
36
  import {getItem} from '../data/catalog'
37
37
  import {hasSystem} from '../utils/system'
38
+ import * as scheduleModel from '../scheduling/schedule'
39
+ import type {ScheduleData} from '../scheduling/schedule'
38
40
 
39
41
  export function calc_orbital_altitude(mass: number): number {
40
42
  if (mass <= BASE_ORBITAL_MASS) {
@@ -112,14 +114,15 @@ export function getInterpolatedPosition(
112
114
  taskIndex: number,
113
115
  taskProgress: number
114
116
  ): FloatPosition {
115
- if (!entity.schedule || entity.schedule.tasks.length === 0) {
117
+ const tasks = mobilityTasks(entity)
118
+ if (tasks.length === 0) {
116
119
  return {x: Number(entity.coordinates.x), y: Number(entity.coordinates.y)}
117
120
  }
118
121
  if (taskIndex < 0) {
119
- const settled = getFlightOrigin(entity, entity.schedule.tasks.length)
122
+ const settled = getFlightOrigin(entity, tasks.length)
120
123
  return {x: Number(settled.x), y: Number(settled.y)}
121
124
  }
122
- const task = entity.schedule.tasks[taskIndex]
125
+ const task = tasks[taskIndex]
123
126
  if (!task.type.equals(TaskType.TRAVEL) || !task.coordinates) {
124
127
  const origin = getFlightOrigin(entity, taskIndex)
125
128
  return {x: Number(origin.x), y: Number(origin.y)}
@@ -427,20 +430,22 @@ export interface TransferEntity {
427
430
  }
428
431
  }
429
432
 
430
- export interface HasScheduleAndLocation {
433
+ export interface HasScheduleAndLocation extends ScheduleData {
431
434
  coordinates: ServerContract.ActionParams.Type.coordinates
432
- schedule?: ServerContract.Types.schedule
435
+ }
436
+
437
+ function mobilityTasks(entity: HasScheduleAndLocation): ServerContract.Types.task[] {
438
+ return scheduleModel.mobilityLane(entity)?.schedule.tasks ?? []
433
439
  }
434
440
 
435
441
  export function getFlightOrigin(
436
442
  entity: HasScheduleAndLocation,
437
443
  flightTaskIndex: number
438
444
  ): ServerContract.ActionParams.Type.coordinates {
439
- if (!entity.schedule) return entity.coordinates
440
-
445
+ const tasks = mobilityTasks(entity)
441
446
  let origin = entity.coordinates
442
- for (let i = 0; i < flightTaskIndex && i < entity.schedule.tasks.length; i++) {
443
- const task = entity.schedule.tasks[i]
447
+ for (let i = 0; i < flightTaskIndex && i < tasks.length; i++) {
448
+ const task = tasks[i]
444
449
  if (task.type.equals(TaskType.TRAVEL) && task.coordinates) {
445
450
  origin = task.coordinates
446
451
  }
@@ -451,10 +456,9 @@ export function getFlightOrigin(
451
456
  export function getDestinationLocation(
452
457
  entity: HasScheduleAndLocation
453
458
  ): ServerContract.ActionParams.Type.coordinates | undefined {
454
- if (!entity.schedule) return undefined
455
-
456
- for (let i = entity.schedule.tasks.length - 1; i >= 0; i--) {
457
- const task = entity.schedule.tasks[i]
459
+ const tasks = mobilityTasks(entity)
460
+ for (let i = tasks.length - 1; i >= 0; i--) {
461
+ const task = tasks[i]
458
462
  if (task.type.equals(TaskType.TRAVEL) && task.coordinates) {
459
463
  return task.coordinates
460
464
  }
@@ -468,14 +472,15 @@ export function getPositionAt(
468
472
  taskIndex: number,
469
473
  taskProgress: number
470
474
  ): ServerContract.ActionParams.Type.coordinates {
471
- if (!entity.schedule || entity.schedule.tasks.length === 0) {
475
+ const tasks = mobilityTasks(entity)
476
+ if (tasks.length === 0) {
472
477
  return entity.coordinates
473
478
  }
474
479
  if (taskIndex < 0) {
475
- return getFlightOrigin(entity, entity.schedule.tasks.length)
480
+ return getFlightOrigin(entity, tasks.length)
476
481
  }
477
482
 
478
- const task = entity.schedule.tasks[taskIndex]
483
+ const task = tasks[taskIndex]
479
484
 
480
485
  if (!task.type.equals(TaskType.TRAVEL) || !task.coordinates) {
481
486
  return getFlightOrigin(entity, taskIndex)
@@ -29,6 +29,7 @@ export interface MassCapability {
29
29
  }
30
30
 
31
31
  export interface ScheduleCapability {
32
+ lanes?: ServerContract.Types.lane[]
32
33
  schedule?: ServerContract.Types.schedule
33
34
  }
34
35