@shipload/sdk 0.7.0 → 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.
- package/lib/shipload.d.ts +2730 -287
- package/lib/shipload.js +10862 -2228
- package/lib/shipload.js.map +1 -1
- package/lib/shipload.m.js +10434 -2170
- package/lib/shipload.m.js.map +1 -1
- package/package.json +11 -20
- package/src/capabilities/crafting.ts +22 -0
- package/src/capabilities/gathering.ts +36 -0
- package/src/capabilities/guards.ts +38 -0
- package/src/capabilities/hauling.ts +22 -0
- package/src/capabilities/index.ts +8 -0
- package/src/capabilities/loading.ts +8 -0
- package/src/capabilities/modules.ts +86 -0
- package/src/capabilities/movement.ts +29 -0
- package/src/capabilities/storage.ts +159 -0
- package/src/contracts/platform.ts +30 -30
- package/src/contracts/server.ts +1387 -283
- package/src/data/capabilities.ts +408 -0
- package/src/data/catalog.ts +135 -0
- package/src/data/categories.ts +55 -0
- package/src/data/colors.ts +84 -0
- package/src/data/entities.json +50 -0
- package/src/data/item-ids.ts +75 -0
- package/src/data/items.json +252 -0
- package/src/data/locations.ts +53 -0
- package/src/data/metadata.ts +208 -0
- package/src/data/nebula-adjectives.json +211 -0
- package/src/data/nebula-nouns.json +151 -0
- package/src/data/recipes-runtime.ts +65 -0
- package/src/data/recipes.json +878 -0
- package/src/data/syllables.json +1790 -0
- package/src/data/tiers.ts +45 -0
- package/src/derivation/crafting.ts +350 -0
- package/src/derivation/index.ts +32 -0
- package/src/derivation/location-size.ts +15 -0
- package/src/derivation/resources.ts +112 -0
- package/src/derivation/stats.ts +146 -0
- package/src/derivation/strata.ts +43 -0
- package/src/derivation/stratum.ts +134 -0
- package/src/derivation/tiers.ts +54 -0
- package/src/entities/cargo-utils.ts +84 -0
- package/src/entities/container.ts +108 -0
- package/src/entities/entity-inventory.ts +39 -0
- package/src/entities/gamestate.ts +152 -0
- package/src/entities/inventory-accessor.ts +42 -0
- package/src/entities/location.ts +60 -0
- package/src/entities/makers.ts +196 -0
- package/src/entities/player.ts +15 -0
- package/src/entities/ship-deploy.ts +258 -0
- package/src/entities/ship.ts +204 -0
- package/src/entities/warehouse.ts +119 -0
- package/src/errors.ts +100 -9
- package/src/format.ts +12 -0
- package/src/index-module.ts +317 -7
- package/src/managers/actions.ts +250 -0
- package/src/managers/base.ts +25 -0
- package/src/managers/context.ts +114 -0
- package/src/managers/entities.ts +103 -0
- package/src/managers/epochs.ts +47 -0
- package/src/managers/index.ts +9 -0
- package/src/managers/locations.ts +68 -0
- package/src/managers/players.ts +13 -0
- package/src/nft/description.ts +176 -0
- package/src/nft/deserializers.ts +83 -0
- package/src/nft/index.ts +2 -0
- package/src/resolution/describe-module.ts +166 -0
- package/src/resolution/display-name.ts +39 -0
- package/src/resolution/resolve-item.ts +358 -0
- package/src/scheduling/accessor.ts +82 -0
- package/src/{epoch.ts → scheduling/epoch.ts} +1 -1
- package/src/scheduling/projection.ts +463 -0
- package/src/scheduling/schedule.ts +179 -0
- package/src/shipload.ts +47 -160
- package/src/subscriptions/connection.ts +154 -0
- package/src/subscriptions/debug.ts +17 -0
- package/src/subscriptions/index.ts +5 -0
- package/src/subscriptions/manager.ts +240 -0
- package/src/subscriptions/mappers.ts +28 -0
- package/src/subscriptions/types.ts +143 -0
- package/src/travel/travel.ts +500 -0
- package/src/types/capabilities.ts +76 -0
- package/src/types/entity-traits.ts +69 -0
- package/src/types/entity.ts +39 -0
- package/src/types/index.ts +3 -0
- package/src/types.ts +140 -35
- package/src/{hash.ts → utils/hash.ts} +2 -2
- package/src/utils/system.ts +168 -0
- package/src/goods.ts +0 -124
- package/src/market.ts +0 -214
- package/src/rolls.ts +0 -8
- package/src/ship.ts +0 -36
- package/src/state.ts +0 -0
- package/src/syllables.ts +0 -1184
- package/src/system.ts +0 -36
- 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
|
+
}
|