@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.
- package/lib/shipload.d.ts +1847 -962
- package/lib/shipload.js +9088 -4854
- package/lib/shipload.js.map +1 -1
- package/lib/shipload.m.js +8957 -4805
- package/lib/shipload.m.js.map +1 -1
- package/lib/testing.d.ts +856 -0
- package/lib/testing.js +3739 -0
- package/lib/testing.js.map +1 -0
- package/lib/testing.m.js +3733 -0
- package/lib/testing.m.js.map +1 -0
- package/package.json +15 -2
- package/src/capabilities/craftable.ts +51 -0
- package/src/capabilities/crafting.test.ts +7 -0
- package/src/capabilities/crafting.ts +3 -3
- package/src/capabilities/gathering.ts +17 -7
- package/src/capabilities/index.ts +0 -1
- package/src/capabilities/modules.ts +6 -0
- package/src/capabilities/storage.ts +16 -1
- package/src/contracts/platform.ts +231 -3
- package/src/contracts/server.ts +816 -471
- package/src/data/capabilities.ts +14 -329
- package/src/data/capability-formulas.ts +76 -0
- package/src/data/catalog.ts +0 -5
- package/src/data/colors.ts +14 -47
- package/src/data/entities.json +46 -10
- package/src/data/item-ids.ts +15 -12
- package/src/data/items.json +302 -38
- package/src/data/kind-registry.json +85 -0
- package/src/data/kind-registry.ts +150 -0
- package/src/data/metadata.ts +100 -31
- package/src/data/recipes-runtime.ts +3 -23
- package/src/data/recipes.json +250 -113
- package/src/derivation/build-methods.ts +45 -0
- package/src/derivation/capabilities.ts +415 -0
- package/src/derivation/capability-mappings.ts +117 -0
- package/src/derivation/crafting.ts +23 -24
- package/src/derivation/index.ts +17 -2
- package/src/derivation/reserve-regen.ts +34 -0
- package/src/derivation/resources.ts +125 -38
- package/src/derivation/stars.test.ts +51 -0
- package/src/derivation/stars.ts +15 -0
- package/src/derivation/stats.ts +6 -6
- package/src/derivation/stratum.ts +15 -19
- package/src/derivation/tiers.ts +28 -7
- package/src/entities/entity.ts +98 -0
- package/src/entities/gamestate.ts +3 -28
- package/src/entities/makers.ts +91 -136
- package/src/entities/slot-multiplier.ts +39 -0
- package/src/errors.ts +10 -15
- package/src/format.ts +26 -4
- package/src/index-module.ts +189 -47
- package/src/managers/actions.ts +252 -83
- package/src/managers/base.ts +6 -2
- package/src/managers/construction-types.ts +79 -0
- package/src/managers/construction.ts +396 -0
- package/src/managers/context.ts +11 -1
- package/src/managers/entities.ts +18 -66
- package/src/managers/epochs.ts +40 -0
- package/src/managers/index.ts +17 -1
- package/src/managers/locations.ts +25 -29
- package/src/managers/nft.ts +28 -0
- package/src/managers/plot.ts +127 -0
- package/src/nft/atomicassets.abi.json +1342 -0
- package/src/nft/atomicassets.ts +237 -0
- package/src/nft/atomicdata.ts +130 -0
- package/src/nft/buildImmutableData.ts +321 -0
- package/src/nft/description.ts +37 -15
- package/src/nft/index.ts +3 -0
- package/src/resolution/describe-module.ts +5 -8
- package/src/resolution/display-name.ts +38 -10
- package/src/resolution/resolve-item.ts +22 -20
- package/src/scheduling/accessor.ts +68 -22
- package/src/scheduling/availability.ts +108 -0
- package/src/scheduling/energy.ts +48 -0
- package/src/scheduling/lane-core.ts +130 -0
- package/src/scheduling/lanes.ts +60 -0
- package/src/scheduling/projection.ts +121 -94
- package/src/scheduling/schedule.ts +237 -103
- package/src/scheduling/task-cargo.ts +46 -0
- package/src/shipload.ts +16 -1
- package/src/subscriptions/manager.ts +40 -6
- package/src/subscriptions/mappers.ts +3 -8
- package/src/subscriptions/types.ts +3 -2
- package/src/testing/catalog-hash.ts +19 -0
- package/src/testing/index.ts +2 -0
- package/src/testing/projection-parity.ts +143 -0
- package/src/travel/travel.ts +90 -13
- package/src/types/capabilities.ts +1 -0
- package/src/types/index.ts +0 -1
- package/src/types.ts +19 -12
- package/src/utils/cargo.ts +27 -0
- package/src/utils/display-name.ts +61 -0
- package/src/utils/system.ts +25 -24
- package/src/capabilities/loading.ts +0 -8
- package/src/entities/container.ts +0 -108
- package/src/entities/ship-deploy.ts +0 -258
- package/src/entities/ship.ts +0 -204
- package/src/entities/warehouse.ts +0 -119
- package/src/types/entity-traits.ts +0 -69
|
@@ -0,0 +1,130 @@
|
|
|
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 function laneDuration(schedule: Schedule): number {
|
|
8
|
+
return schedule.tasks.reduce((sum, task) => sum + task.duration.toNumber(), 0)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function laneRawElapsed(schedule: Schedule, now: Date): number {
|
|
12
|
+
const started = schedule.started.toDate()
|
|
13
|
+
return Math.floor((now.getTime() - started.getTime()) / 1000)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function laneElapsed(schedule: Schedule, now: Date): number {
|
|
17
|
+
return Math.max(0, laneRawElapsed(schedule, now))
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function laneStartsIn(schedule: Schedule, now: Date): number {
|
|
21
|
+
return Math.max(0, -laneRawElapsed(schedule, now))
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function laneRemaining(schedule: Schedule, now: Date): number {
|
|
25
|
+
return Math.max(0, laneDuration(schedule) - laneRawElapsed(schedule, now))
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function laneComplete(schedule: Schedule, now: Date): boolean {
|
|
29
|
+
if (schedule.tasks.length === 0) return false
|
|
30
|
+
if (schedule.tasks.some((t) => t.type.toNumber() === TaskType.RESERVED)) return false
|
|
31
|
+
return laneRemaining(schedule, now) === 0
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function laneProgress(schedule: Schedule, now: Date): number {
|
|
35
|
+
const duration = laneDuration(schedule)
|
|
36
|
+
if (duration === 0) return schedule.tasks.length > 0 ? 1 : 0
|
|
37
|
+
return Math.min(1, laneElapsed(schedule, now) / duration)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function currentTaskIndexForLane(schedule: Schedule, now: Date): number {
|
|
41
|
+
if (schedule.tasks.length === 0) return -1
|
|
42
|
+
if (laneRawElapsed(schedule, now) < 0) return -1
|
|
43
|
+
const elapsed = laneElapsed(schedule, now)
|
|
44
|
+
let timeAccum = 0
|
|
45
|
+
for (let i = 0; i < schedule.tasks.length; i++) {
|
|
46
|
+
const taskDuration = schedule.tasks[i].duration.toNumber()
|
|
47
|
+
if (elapsed < timeAccum + taskDuration) return i
|
|
48
|
+
timeAccum += taskDuration
|
|
49
|
+
}
|
|
50
|
+
return -1
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function currentTask(schedule: Schedule, now: Date): Task | undefined {
|
|
54
|
+
const index = currentTaskIndexForLane(schedule, now)
|
|
55
|
+
if (index < 0) return undefined
|
|
56
|
+
return schedule.tasks[index]
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function currentTaskType(schedule: Schedule, now: Date): TaskType | undefined {
|
|
60
|
+
const task = currentTask(schedule, now)
|
|
61
|
+
return task ? (task.type.toNumber() as TaskType) : undefined
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function laneTaskStartTime(schedule: Schedule, index: number): number {
|
|
65
|
+
if (index < 0 || index >= schedule.tasks.length) return 0
|
|
66
|
+
let timeAccum = 0
|
|
67
|
+
for (let i = 0; i < index; i++) {
|
|
68
|
+
timeAccum += schedule.tasks[i].duration.toNumber()
|
|
69
|
+
}
|
|
70
|
+
return timeAccum
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function laneTaskElapsed(schedule: Schedule, index: number, now: Date): number {
|
|
74
|
+
if (index < 0 || index >= schedule.tasks.length) return 0
|
|
75
|
+
const elapsed = laneElapsed(schedule, now)
|
|
76
|
+
const taskStart = laneTaskStartTime(schedule, index)
|
|
77
|
+
const taskDuration = schedule.tasks[index].duration.toNumber()
|
|
78
|
+
if (elapsed <= taskStart) return 0
|
|
79
|
+
return Math.min(elapsed - taskStart, taskDuration)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function laneTaskRemaining(schedule: Schedule, index: number, now: Date): number {
|
|
83
|
+
if (index < 0 || index >= schedule.tasks.length) return 0
|
|
84
|
+
const taskDuration = schedule.tasks[index].duration.toNumber()
|
|
85
|
+
return Math.max(0, taskDuration - laneTaskElapsed(schedule, index, now))
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function laneTaskComplete(schedule: Schedule, index: number, now: Date): boolean {
|
|
89
|
+
if (index < 0 || index >= schedule.tasks.length) return false
|
|
90
|
+
if (schedule.tasks[index].type.toNumber() === TaskType.RESERVED) return false
|
|
91
|
+
const taskDuration = schedule.tasks[index].duration.toNumber()
|
|
92
|
+
return laneTaskElapsed(schedule, index, now) >= taskDuration
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function laneTaskInProgress(schedule: Schedule, index: number, now: Date): boolean {
|
|
96
|
+
if (index < 0 || index >= schedule.tasks.length) return false
|
|
97
|
+
const taskElapsed = laneTaskElapsed(schedule, index, now)
|
|
98
|
+
const taskDuration = schedule.tasks[index].duration.toNumber()
|
|
99
|
+
return taskElapsed > 0 && taskElapsed < taskDuration
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function laneCompletesAt(schedule: Schedule, index: number): Date {
|
|
103
|
+
const startedMs = schedule.started.toDate().getTime()
|
|
104
|
+
const endSec =
|
|
105
|
+
laneTaskStartTime(schedule, index) + (schedule.tasks[index]?.duration.toNumber() ?? 0)
|
|
106
|
+
return new Date(startedMs + endSec * 1000)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function currentTaskProgress(schedule: Schedule, now: Date): number {
|
|
110
|
+
const index = currentTaskIndexForLane(schedule, now)
|
|
111
|
+
if (index < 0) return 0
|
|
112
|
+
const elapsed = laneTaskElapsed(schedule, index, now)
|
|
113
|
+
const duration = schedule.tasks[index].duration.toNumber()
|
|
114
|
+
if (duration === 0) return 1
|
|
115
|
+
return Math.min(1, elapsed / duration)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function currentTaskProgressFloatForLane(schedule: Schedule, now: Date): number {
|
|
119
|
+
if (schedule.tasks.length === 0) return 0
|
|
120
|
+
const index = currentTaskIndexForLane(schedule, now)
|
|
121
|
+
if (index < 0) return 0
|
|
122
|
+
const task = schedule.tasks[index]
|
|
123
|
+
const durationMs = task.duration.toNumber() * 1000
|
|
124
|
+
if (durationMs === 0) return 1
|
|
125
|
+
const startedMs = schedule.started.toDate().getTime()
|
|
126
|
+
const taskStartMs = startedMs + laneTaskStartTime(schedule, index) * 1000
|
|
127
|
+
const elapsedMs = now.getTime() - taskStartMs
|
|
128
|
+
if (elapsedMs <= 0) return 0
|
|
129
|
+
return Math.min(1, elapsedMs / durationMs)
|
|
130
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type {ServerContract} from '../contracts'
|
|
2
|
+
import {getItem} from '../data/catalog'
|
|
3
|
+
import type {ModuleType} from '../types'
|
|
4
|
+
import {getLane, type ScheduleData} from './schedule'
|
|
5
|
+
|
|
6
|
+
type ModuleEntry = ServerContract.Types.module_entry
|
|
7
|
+
type Lane = ServerContract.Types.lane
|
|
8
|
+
type Schedule = ServerContract.Types.schedule
|
|
9
|
+
|
|
10
|
+
export function laneKeyForModule(slotIndex: number): number {
|
|
11
|
+
return slotIndex + 1
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function laneIsFree(lanes: Lane[], laneKey: number): boolean {
|
|
15
|
+
const lane = lanes.find((entry) => entry.lane_key.toNumber() === laneKey)
|
|
16
|
+
return lane ? lane.schedule.tasks.length === 0 : true
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function workerLaneKey(
|
|
20
|
+
modules: ModuleEntry[],
|
|
21
|
+
moduleSubtype: ModuleType,
|
|
22
|
+
lanes: Lane[]
|
|
23
|
+
): number {
|
|
24
|
+
const occupiedMatchingLaneKeys: number[] = []
|
|
25
|
+
|
|
26
|
+
for (let slotIndex = 0; slotIndex < modules.length; slotIndex++) {
|
|
27
|
+
const installed = modules[slotIndex].installed
|
|
28
|
+
if (!installed) continue
|
|
29
|
+
if (getItem(installed.item_id).moduleType !== moduleSubtype) continue
|
|
30
|
+
|
|
31
|
+
const laneKey = laneKeyForModule(slotIndex)
|
|
32
|
+
if (laneIsFree(lanes, laneKey)) return laneKey
|
|
33
|
+
occupiedMatchingLaneKeys.push(laneKey)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (occupiedMatchingLaneKeys.length > 0) {
|
|
37
|
+
return Math.min(...occupiedMatchingLaneKeys)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
throw new Error(`No installed ${moduleSubtype} worker module`)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function rawScheduleEnd(schedule: Schedule): Date {
|
|
44
|
+
const durationSec = schedule.tasks.reduce((sum, task) => sum + task.duration.toNumber(), 0)
|
|
45
|
+
return new Date(schedule.started.toDate().getTime() + durationSec * 1000)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function candidateLaneCompletesAt(
|
|
49
|
+
entity: ScheduleData,
|
|
50
|
+
laneKey: number,
|
|
51
|
+
durationSec: number,
|
|
52
|
+
now: Date
|
|
53
|
+
): Date {
|
|
54
|
+
const lane = getLane(entity, laneKey)
|
|
55
|
+
const startMs = lane
|
|
56
|
+
? Math.max(rawScheduleEnd(lane.schedule).getTime(), now.getTime())
|
|
57
|
+
: now.getTime()
|
|
58
|
+
|
|
59
|
+
return new Date(startMs + durationSec * 1000)
|
|
60
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {Name,
|
|
1
|
+
import {Name, UInt16, UInt32, UInt64} from '@wharfkit/antelope'
|
|
2
2
|
import {ServerContract} from '../contracts'
|
|
3
|
-
import {Coordinates,
|
|
3
|
+
import {Coordinates, TaskType} from '../types'
|
|
4
4
|
import {
|
|
5
5
|
capsHasLoaders,
|
|
6
6
|
capsHasMovement,
|
|
@@ -14,17 +14,19 @@ import {
|
|
|
14
14
|
RECIPE_INPUTS_INSUFFICIENT,
|
|
15
15
|
RECIPE_INPUTS_INVALID,
|
|
16
16
|
RECIPE_NOT_FOUND,
|
|
17
|
-
|
|
17
|
+
ENTITY_CARGO_NOT_LOADED,
|
|
18
18
|
} from '../errors'
|
|
19
|
-
import {getRecipe, type RecipeInput} from '../data/recipes-runtime'
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
19
|
+
import {getEntityLayout, getRecipe, type RecipeInput} from '../data/recipes-runtime'
|
|
20
|
+
import {computeEntityCapabilities} from '../derivation/capabilities'
|
|
21
|
+
import {decodeCraftedItemStats} from '../derivation/crafting'
|
|
22
|
+
import {packedModulesToInstalled, type InstalledModule} from '../entities/slot-multiplier'
|
|
23
|
+
import {lerp} from '../travel/travel'
|
|
22
24
|
import {
|
|
23
25
|
calcStacksMass,
|
|
24
26
|
cargoItemToStack,
|
|
25
27
|
type CargoStack,
|
|
26
28
|
mergeStacks,
|
|
27
|
-
|
|
29
|
+
subtractFromStacks,
|
|
28
30
|
stackToCargoItem,
|
|
29
31
|
} from '../capabilities/storage'
|
|
30
32
|
import * as schedule from './schedule'
|
|
@@ -63,19 +65,74 @@ export interface Projectable extends ScheduleData {
|
|
|
63
65
|
cargo: ServerContract.Types.cargo_item[]
|
|
64
66
|
cargomass: UInt32
|
|
65
67
|
owner?: Name
|
|
68
|
+
stats?: bigint
|
|
69
|
+
item_id?: number | UInt16
|
|
70
|
+
modules?: ServerContract.Types.module_entry[] | InstalledModule[]
|
|
66
71
|
}
|
|
67
72
|
|
|
68
|
-
function
|
|
69
|
-
|
|
73
|
+
function toInstalledModules(
|
|
74
|
+
modules: ServerContract.Types.module_entry[] | InstalledModule[]
|
|
75
|
+
): InstalledModule[] {
|
|
76
|
+
if (modules.length > 0 && 'itemId' in modules[0]) {
|
|
77
|
+
return modules as InstalledModule[]
|
|
78
|
+
}
|
|
79
|
+
return packedModulesToInstalled(modules as ServerContract.Types.module_entry[])
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
interface ProjectedCaps {
|
|
83
|
+
hullmass?: UInt32
|
|
84
|
+
capacity?: UInt32
|
|
85
|
+
engines?: ServerContract.Types.movement_stats
|
|
86
|
+
generator?: ServerContract.Types.energy_stats
|
|
87
|
+
loaders?: ServerContract.Types.loader_stats
|
|
88
|
+
hauler?: ServerContract.Types.hauler_stats
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function recomputeCaps(entity: Projectable): ProjectedCaps | undefined {
|
|
92
|
+
if (
|
|
93
|
+
entity.item_id === undefined ||
|
|
94
|
+
entity.modules === undefined ||
|
|
95
|
+
entity.stats === undefined
|
|
96
|
+
) {
|
|
97
|
+
return undefined
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const itemId = Number(
|
|
101
|
+
typeof entity.item_id === 'number' ? entity.item_id : entity.item_id.value
|
|
102
|
+
)
|
|
103
|
+
const hullStats = decodeCraftedItemStats(itemId, entity.stats)
|
|
104
|
+
const layout = getEntityLayout(itemId)?.slots ?? []
|
|
105
|
+
const installed = toInstalledModules(entity.modules)
|
|
106
|
+
const caps = computeEntityCapabilities(hullStats, itemId, installed, layout)
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
hullmass: UInt32.from(caps.hullmass),
|
|
110
|
+
capacity: UInt32.from(caps.capacity),
|
|
111
|
+
engines: caps.engines ? ServerContract.Types.movement_stats.from(caps.engines) : undefined,
|
|
112
|
+
generator: caps.generator
|
|
113
|
+
? ServerContract.Types.energy_stats.from(caps.generator)
|
|
114
|
+
: undefined,
|
|
115
|
+
loaders: caps.loaders ? ServerContract.Types.loader_stats.from(caps.loaders) : undefined,
|
|
116
|
+
hauler: caps.hauler ? ServerContract.Types.hauler_stats.from(caps.hauler) : undefined,
|
|
117
|
+
}
|
|
70
118
|
}
|
|
71
119
|
|
|
72
120
|
export function createProjectedEntity(entity: Projectable): ProjectedEntity {
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
121
|
+
const needsRecompute =
|
|
122
|
+
entity.hullmass === undefined ||
|
|
123
|
+
entity.loaders === undefined ||
|
|
124
|
+
entity.engines === undefined ||
|
|
125
|
+
entity.generator === undefined ||
|
|
126
|
+
entity.hauler === undefined ||
|
|
127
|
+
entity.capacity === undefined
|
|
128
|
+
const caps = needsRecompute ? recomputeCaps(entity) : undefined
|
|
129
|
+
|
|
130
|
+
const shipMass = UInt32.from(entity.hullmass ?? caps?.hullmass ?? 0)
|
|
131
|
+
const loaders = entity.loaders ?? caps?.loaders
|
|
132
|
+
const engines = entity.engines ?? caps?.engines
|
|
133
|
+
const generator = entity.generator ?? caps?.generator
|
|
134
|
+
const hauler = entity.hauler ?? caps?.hauler
|
|
135
|
+
const capacity = entity.capacity ?? caps?.capacity
|
|
79
136
|
|
|
80
137
|
const cargo: CargoStack[] = entity.cargo.map(cargoItemToStack)
|
|
81
138
|
|
|
@@ -160,25 +217,22 @@ function applyFlightTask(
|
|
|
160
217
|
task: ServerContract.Types.task,
|
|
161
218
|
options: {complete: boolean; progress?: number}
|
|
162
219
|
): void {
|
|
163
|
-
if (!task.coordinates
|
|
220
|
+
if (!task.coordinates) return
|
|
164
221
|
|
|
165
|
-
const origin = projected.location
|
|
166
222
|
const destination = Coordinates.from(task.coordinates)
|
|
167
|
-
const distance = distanceBetweenCoordinates(origin, task.coordinates)
|
|
168
|
-
const energyUsage = distance.dividing(PRECISION).multiplying(projected.engines.drain)
|
|
169
223
|
|
|
170
224
|
if (options.complete) {
|
|
171
|
-
projected
|
|
172
|
-
? UInt16.from(projected.energy.subtracting(energyUsage))
|
|
173
|
-
: UInt16.from(0)
|
|
225
|
+
applyEnergyCost(projected, task)
|
|
174
226
|
projected.location = destination
|
|
175
227
|
} else if (options.progress !== undefined) {
|
|
176
|
-
const interpolated = lerp(
|
|
228
|
+
const interpolated = lerp(projected.location, destination, options.progress)
|
|
177
229
|
projected.location = Coordinates.from({
|
|
178
230
|
x: Math.round(interpolated.x),
|
|
179
231
|
y: Math.round(interpolated.y),
|
|
180
232
|
})
|
|
181
|
-
const partialEnergy = UInt64.from(
|
|
233
|
+
const partialEnergy = UInt64.from(
|
|
234
|
+
Math.floor(Number(task.energy_cost ?? 0) * options.progress)
|
|
235
|
+
)
|
|
182
236
|
projected.energy = projected.energy.gt(partialEnergy)
|
|
183
237
|
? UInt16.from(projected.energy.subtracting(partialEnergy))
|
|
184
238
|
: UInt16.from(0)
|
|
@@ -190,7 +244,7 @@ function addCargoItem(projected: ProjectedEntity, item: ServerContract.Types.car
|
|
|
190
244
|
}
|
|
191
245
|
|
|
192
246
|
function removeCargoItem(projected: ProjectedEntity, item: ServerContract.Types.cargo_item): void {
|
|
193
|
-
projected.cargo =
|
|
247
|
+
projected.cargo = subtractFromStacks(projected.cargo, cargoItemToStack(item))
|
|
194
248
|
}
|
|
195
249
|
|
|
196
250
|
function applyAddCargoTask(projected: ProjectedEntity, task: ServerContract.Types.task): void {
|
|
@@ -248,6 +302,7 @@ function applyTask(projected: ProjectedEntity, task: ServerContract.Types.task):
|
|
|
248
302
|
applyRechargeTask(projected, task, {complete: true})
|
|
249
303
|
break
|
|
250
304
|
case TaskType.TRAVEL:
|
|
305
|
+
case TaskType.WARP:
|
|
251
306
|
applyFlightTask(projected, task, {complete: true})
|
|
252
307
|
break
|
|
253
308
|
case TaskType.LOAD:
|
|
@@ -255,7 +310,6 @@ function applyTask(projected: ProjectedEntity, task: ServerContract.Types.task):
|
|
|
255
310
|
applyAddCargoTask(projected, task)
|
|
256
311
|
break
|
|
257
312
|
case TaskType.UNLOAD:
|
|
258
|
-
case TaskType.WRAP:
|
|
259
313
|
applyRemoveCargoTask(projected, task)
|
|
260
314
|
break
|
|
261
315
|
case TaskType.GATHER:
|
|
@@ -267,6 +321,9 @@ function applyTask(projected: ProjectedEntity, task: ServerContract.Types.task):
|
|
|
267
321
|
case TaskType.DEPLOY:
|
|
268
322
|
applyDeployTask(projected, task)
|
|
269
323
|
break
|
|
324
|
+
case TaskType.UNDEPLOY:
|
|
325
|
+
case TaskType.DEMOLISH:
|
|
326
|
+
break
|
|
270
327
|
}
|
|
271
328
|
}
|
|
272
329
|
|
|
@@ -276,50 +333,27 @@ export interface ProjectionOptions {
|
|
|
276
333
|
|
|
277
334
|
export function projectEntity(entity: Projectable, options?: ProjectionOptions): ProjectedEntity {
|
|
278
335
|
const projected = createProjectedEntity(entity)
|
|
279
|
-
|
|
336
|
+
const ordered = schedule.orderedTasks(entity)
|
|
337
|
+
if (ordered.length === 0) return projected
|
|
280
338
|
|
|
281
|
-
const tasks = entity.schedule.tasks
|
|
282
339
|
const taskCount =
|
|
283
340
|
options?.upToTaskIndex !== undefined
|
|
284
|
-
? Math.max(0, Math.min(options.upToTaskIndex,
|
|
285
|
-
:
|
|
341
|
+
? Math.max(0, Math.min(options.upToTaskIndex, ordered.length))
|
|
342
|
+
: ordered.length
|
|
286
343
|
|
|
287
344
|
for (let i = 0; i < taskCount; i++) {
|
|
288
|
-
applyTask(projected,
|
|
345
|
+
applyTask(projected, ordered[i].task)
|
|
289
346
|
}
|
|
290
347
|
return projected
|
|
291
348
|
}
|
|
292
349
|
|
|
293
|
-
export
|
|
294
|
-
|
|
295
|
-
|
|
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
|
-
}),
|
|
350
|
+
export function projectRemainingAt(entity: Projectable, _now: Date): ProjectedEntity {
|
|
351
|
+
// Resolve is lazy/entity-global: completed tasks are unsettled until resolve, so replay all.
|
|
352
|
+
const projected = createProjectedEntity(entity)
|
|
353
|
+
for (const {task} of schedule.orderedTasks(entity)) {
|
|
354
|
+
applyTask(projected, task)
|
|
317
355
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
export function projectFromCurrentState(snapshot: ProjectableSnapshot): ProjectedEntity {
|
|
321
|
-
const projectable = buildRemainingProjectable(snapshot)
|
|
322
|
-
return projectable ? projectEntity(projectable) : createProjectedEntity(snapshot)
|
|
356
|
+
return projected
|
|
323
357
|
}
|
|
324
358
|
|
|
325
359
|
function getRecipeInputsForOutput(outputItemId: number): RecipeInput[] | undefined {
|
|
@@ -342,19 +376,10 @@ function validateCraftTask(task: ServerContract.Types.task, projected: Projected
|
|
|
342
376
|
let matched = false
|
|
343
377
|
for (let ri = 0; ri < recipe.length; ri++) {
|
|
344
378
|
const req = recipe[ri]
|
|
345
|
-
if (
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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
|
-
}
|
|
379
|
+
if (input.item_id.toNumber() === req.itemId) {
|
|
380
|
+
groupedInputs[ri].push(input)
|
|
381
|
+
matched = true
|
|
382
|
+
break
|
|
358
383
|
}
|
|
359
384
|
}
|
|
360
385
|
if (!matched) throw new Error(RECIPE_INPUTS_INVALID)
|
|
@@ -385,15 +410,16 @@ function validateCraftTask(task: ServerContract.Types.task, projected: Projected
|
|
|
385
410
|
break
|
|
386
411
|
}
|
|
387
412
|
}
|
|
388
|
-
if (!found) throw new Error(
|
|
413
|
+
if (!found) throw new Error(ENTITY_CARGO_NOT_LOADED)
|
|
389
414
|
}
|
|
390
415
|
}
|
|
391
416
|
|
|
392
417
|
export function validateSchedule(entity: Projectable): void {
|
|
393
|
-
|
|
418
|
+
const ordered = schedule.orderedTasks(entity)
|
|
419
|
+
if (ordered.length === 0) return
|
|
394
420
|
|
|
395
421
|
const projected = createProjectedEntity(entity)
|
|
396
|
-
for (const task of
|
|
422
|
+
for (const {task} of ordered) {
|
|
397
423
|
if (task.type.toNumber() === TaskType.CRAFT) {
|
|
398
424
|
validateCraftTask(task, projected)
|
|
399
425
|
}
|
|
@@ -407,28 +433,35 @@ export function validateSchedule(entity: Projectable): void {
|
|
|
407
433
|
export function projectEntityAt(entity: Projectable, now: Date): ProjectedEntity {
|
|
408
434
|
const projected = createProjectedEntity(entity)
|
|
409
435
|
|
|
410
|
-
|
|
436
|
+
const ordered = schedule.orderedTasks(entity)
|
|
437
|
+
if (ordered.length === 0) {
|
|
411
438
|
return projected
|
|
412
439
|
}
|
|
413
440
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
const
|
|
441
|
+
const nowMs = now.getTime()
|
|
442
|
+
|
|
443
|
+
for (const {task, startsAt} of ordered) {
|
|
444
|
+
const duration = task.duration.toNumber()
|
|
445
|
+
const isReserved = task.type.toNumber() === TaskType.RESERVED
|
|
446
|
+
const elapsed = Math.min(
|
|
447
|
+
Math.max(0, Math.floor((nowMs - startsAt.getTime()) / 1000)),
|
|
448
|
+
duration
|
|
449
|
+
)
|
|
450
|
+
const taskComplete = !isReserved && elapsed >= duration
|
|
451
|
+
const taskInProgress = elapsed > 0 && elapsed < duration
|
|
418
452
|
|
|
419
453
|
if (!taskComplete && !taskInProgress) {
|
|
420
|
-
|
|
454
|
+
continue
|
|
421
455
|
}
|
|
422
456
|
|
|
423
|
-
const progress = taskInProgress
|
|
424
|
-
? schedule.getTaskElapsed(entity, i, now) / task.duration.toNumber()
|
|
425
|
-
: undefined
|
|
457
|
+
const progress = taskInProgress ? elapsed / duration : undefined
|
|
426
458
|
|
|
427
459
|
switch (task.type.toNumber()) {
|
|
428
460
|
case TaskType.RECHARGE:
|
|
429
461
|
applyRechargeTask(projected, task, {complete: taskComplete, progress})
|
|
430
462
|
break
|
|
431
463
|
case TaskType.TRAVEL:
|
|
464
|
+
case TaskType.WARP:
|
|
432
465
|
applyFlightTask(projected, task, {complete: taskComplete, progress})
|
|
433
466
|
break
|
|
434
467
|
case TaskType.LOAD:
|
|
@@ -436,7 +469,6 @@ export function projectEntityAt(entity: Projectable, now: Date): ProjectedEntity
|
|
|
436
469
|
if (taskComplete) applyAddCargoTask(projected, task)
|
|
437
470
|
break
|
|
438
471
|
case TaskType.UNLOAD:
|
|
439
|
-
case TaskType.WRAP:
|
|
440
472
|
if (taskComplete) applyRemoveCargoTask(projected, task)
|
|
441
473
|
break
|
|
442
474
|
case TaskType.GATHER:
|
|
@@ -448,16 +480,11 @@ export function projectEntityAt(entity: Projectable, now: Date): ProjectedEntity
|
|
|
448
480
|
case TaskType.DEPLOY:
|
|
449
481
|
if (taskComplete) applyDeployTask(projected, task)
|
|
450
482
|
break
|
|
483
|
+
case TaskType.UNDEPLOY:
|
|
484
|
+
case TaskType.DEMOLISH:
|
|
485
|
+
break
|
|
451
486
|
}
|
|
452
487
|
}
|
|
453
488
|
|
|
454
489
|
return projected
|
|
455
490
|
}
|
|
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
|
-
}
|