@shipload/sdk 1.0.0-next.20 → 1.0.0-next.22
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 +25 -1
- package/lib/shipload.js +186 -22
- package/lib/shipload.js.map +1 -1
- package/lib/shipload.m.js +186 -23
- package/lib/shipload.m.js.map +1 -1
- package/package.json +1 -1
- package/src/index-module.ts +4 -0
- package/src/managers/construction-types.ts +21 -0
- package/src/managers/construction.ts +159 -15
- package/src/managers/index.ts +2 -0
- package/src/scheduling/energy.ts +41 -0
- package/src/scheduling/projection.ts +10 -11
package/package.json
CHANGED
package/src/index-module.ts
CHANGED
|
@@ -59,6 +59,8 @@ export type {
|
|
|
59
59
|
SourceCargoStack,
|
|
60
60
|
FinalizerEntityRef,
|
|
61
61
|
FinalizerCapability,
|
|
62
|
+
InboundTransfer,
|
|
63
|
+
Reservation,
|
|
62
64
|
} from './managers'
|
|
63
65
|
export type {EntityRefInput} from './managers/actions'
|
|
64
66
|
|
|
@@ -202,6 +204,8 @@ export type {
|
|
|
202
204
|
export {taskCargoChanges} from './scheduling/task-cargo'
|
|
203
205
|
export type {TaskCargoChange, TaskCargoDirection} from './scheduling/task-cargo'
|
|
204
206
|
|
|
207
|
+
export {energyAtTime} from './scheduling/energy'
|
|
208
|
+
|
|
205
209
|
export * from './types/capabilities'
|
|
206
210
|
export * from './types/entity'
|
|
207
211
|
export {
|
|
@@ -32,10 +32,15 @@ export interface SourceEntityRef {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
export interface SourceCargoStack {
|
|
35
|
+
key: string
|
|
36
|
+
rowId: UInt64
|
|
35
37
|
itemId: number
|
|
36
38
|
item: Item
|
|
39
|
+
stats: UInt64
|
|
40
|
+
modules: ServerContract.Types.module_entry[]
|
|
37
41
|
available: number
|
|
38
42
|
plotNeeds: number
|
|
43
|
+
reserved: number
|
|
39
44
|
}
|
|
40
45
|
|
|
41
46
|
export interface FinalizerEntityRef {
|
|
@@ -45,3 +50,19 @@ export interface FinalizerEntityRef {
|
|
|
45
50
|
crafterSpeed: number
|
|
46
51
|
estimatedDuration: UInt32
|
|
47
52
|
}
|
|
53
|
+
|
|
54
|
+
export interface InboundTransfer {
|
|
55
|
+
sourceEntityId: UInt64
|
|
56
|
+
sourceEntityType: Name
|
|
57
|
+
sourceName: string
|
|
58
|
+
itemId: number
|
|
59
|
+
quantity: number
|
|
60
|
+
etaSeconds: number
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface Reservation {
|
|
64
|
+
targetEntityId: UInt64
|
|
65
|
+
targetEntityType: Name
|
|
66
|
+
itemId: number
|
|
67
|
+
quantity: number
|
|
68
|
+
}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
import type {UInt32} from '@wharfkit/antelope'
|
|
1
|
+
import type {UInt64, UInt32} from '@wharfkit/antelope'
|
|
2
2
|
import {BaseManager} from './base'
|
|
3
3
|
import type {ServerContract} from '../contracts'
|
|
4
4
|
import {PlotManager} from './plot'
|
|
5
5
|
import {getItem} from '../data/catalog'
|
|
6
6
|
import {calc_craft_duration} from '../capabilities/crafting'
|
|
7
|
+
import {TaskType} from '../types'
|
|
7
8
|
import type {
|
|
8
9
|
BuildableTarget,
|
|
9
10
|
FinalizerEntityRef,
|
|
11
|
+
InboundTransfer,
|
|
12
|
+
Reservation,
|
|
10
13
|
SourceCargoStack,
|
|
11
14
|
SourceEntityRef,
|
|
12
15
|
} from './construction-types'
|
|
@@ -74,6 +77,78 @@ export class ConstructionManager extends BaseManager {
|
|
|
74
77
|
return out.sort((a, b) => a.estimatedDuration.value - b.estimatedDuration.value)
|
|
75
78
|
}
|
|
76
79
|
|
|
80
|
+
inboundTransfersTo(
|
|
81
|
+
plotId: UInt64,
|
|
82
|
+
entities: ServerContract.Types.entity_info[],
|
|
83
|
+
now: Date
|
|
84
|
+
): InboundTransfer[] {
|
|
85
|
+
return this.inboundTransfersByTarget(entities, now).get(plotId.toString()) ?? []
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
inboundTransfersByTarget(
|
|
89
|
+
entities: ServerContract.Types.entity_info[],
|
|
90
|
+
now: Date
|
|
91
|
+
): Map<string, InboundTransfer[]> {
|
|
92
|
+
const buckets = new Map<string, Map<string, InboundTransfer>>()
|
|
93
|
+
const nowMs = now.getTime()
|
|
94
|
+
for (const entity of entities) {
|
|
95
|
+
const schedule = entity.schedule
|
|
96
|
+
if (!schedule) continue
|
|
97
|
+
const entityIdStr = entity.id.toString()
|
|
98
|
+
const sourceName = entity.entity_name || entityIdStr
|
|
99
|
+
const startedMs = schedule.started.toDate().getTime()
|
|
100
|
+
let cumulativeSec = 0
|
|
101
|
+
for (const task of schedule.tasks) {
|
|
102
|
+
cumulativeSec += task.duration.toNumber()
|
|
103
|
+
if (!isTransferTask(task)) continue
|
|
104
|
+
if (!task.entitytarget) continue
|
|
105
|
+
const projectedEndMs = startedMs + cumulativeSec * 1000
|
|
106
|
+
if (projectedEndMs < nowMs) continue
|
|
107
|
+
const targetIdStr = task.entitytarget.entity_id.toString()
|
|
108
|
+
const etaSeconds = Math.max(0, Math.round((projectedEndMs - nowMs) / 1000))
|
|
109
|
+
let perTarget = buckets.get(targetIdStr)
|
|
110
|
+
if (!perTarget) {
|
|
111
|
+
perTarget = new Map()
|
|
112
|
+
buckets.set(targetIdStr, perTarget)
|
|
113
|
+
}
|
|
114
|
+
for (const c of task.cargo) {
|
|
115
|
+
const itemId = c.item_id.toNumber()
|
|
116
|
+
const quantity = c.quantity.toNumber()
|
|
117
|
+
if (quantity === 0) continue
|
|
118
|
+
const key = `${entityIdStr}#${itemId}`
|
|
119
|
+
const existing = perTarget.get(key)
|
|
120
|
+
if (existing) {
|
|
121
|
+
existing.quantity += quantity
|
|
122
|
+
existing.etaSeconds = Math.min(existing.etaSeconds, etaSeconds)
|
|
123
|
+
} else {
|
|
124
|
+
perTarget.set(key, {
|
|
125
|
+
sourceEntityId: entity.id,
|
|
126
|
+
sourceEntityType: entity.type,
|
|
127
|
+
sourceName,
|
|
128
|
+
itemId,
|
|
129
|
+
quantity,
|
|
130
|
+
etaSeconds,
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const out = new Map<string, InboundTransfer[]>()
|
|
137
|
+
for (const [targetId, perTarget] of buckets) {
|
|
138
|
+
out.set(targetId, Array.from(perTarget.values()))
|
|
139
|
+
}
|
|
140
|
+
return out
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
reservationsFrom(
|
|
144
|
+
sourceEntityId: UInt64,
|
|
145
|
+
entities: ServerContract.Types.entity_info[]
|
|
146
|
+
): Reservation[] {
|
|
147
|
+
const source = entities.find((e) => e.id.equals(sourceEntityId))
|
|
148
|
+
if (!source) return []
|
|
149
|
+
return reservationsOf(source)
|
|
150
|
+
}
|
|
151
|
+
|
|
77
152
|
estimateFinalizeDuration(target: BuildableTarget, crafterSpeed: number): UInt32 {
|
|
78
153
|
return calc_craft_duration(crafterSpeed, target.progress.massRequired)
|
|
79
154
|
}
|
|
@@ -90,28 +165,54 @@ function coordsEqual(
|
|
|
90
165
|
return a.x.equals(b.x) && a.y.equals(b.y)
|
|
91
166
|
}
|
|
92
167
|
|
|
168
|
+
function moduleKey(module: ServerContract.Types.module_entry): string {
|
|
169
|
+
const installed = module.installed
|
|
170
|
+
if (!installed) return `${module.type.toNumber()}:empty`
|
|
171
|
+
|
|
172
|
+
return `${module.type.toNumber()}:${installed.item_id.toNumber()}:${installed.stats.toString()}`
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function sourceStackKey(cargo: ServerContract.Types.cargo_row): string {
|
|
176
|
+
return `${cargo.item_id.toNumber()}#${cargo.stats.toString()}#${(cargo.modules ?? [])
|
|
177
|
+
.map(moduleKey)
|
|
178
|
+
.join(',')}`
|
|
179
|
+
}
|
|
180
|
+
|
|
93
181
|
function matchRelevantCargo(
|
|
94
182
|
entity: ServerContract.Types.entity_info,
|
|
95
183
|
target: BuildableTarget,
|
|
96
|
-
cargo: ServerContract.Types.cargo_row[]
|
|
184
|
+
cargo: ServerContract.Types.cargo_row[],
|
|
185
|
+
reservedByItem: Map<number, number>
|
|
97
186
|
): SourceCargoStack[] {
|
|
98
|
-
const
|
|
187
|
+
const needsByItemId = new Map(
|
|
188
|
+
target.progress.rows.filter((row) => row.missing > 0).map((row) => [row.itemId, row])
|
|
189
|
+
)
|
|
190
|
+
const remainingReserved = new Map(reservedByItem)
|
|
191
|
+
const out: SourceCargoStack[] = []
|
|
99
192
|
for (const c of cargo) {
|
|
100
193
|
if (!c.entity_id.equals(entity.id)) continue
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const available =
|
|
194
|
+
const itemId = c.item_id.toNumber()
|
|
195
|
+
const need = needsByItemId.get(itemId)
|
|
196
|
+
if (!need) continue
|
|
197
|
+
const gross = c.quantity.toNumber()
|
|
198
|
+
if (gross === 0) continue
|
|
199
|
+
const reservedRemaining = remainingReserved.get(itemId) ?? 0
|
|
200
|
+
const reserved = Math.min(gross, reservedRemaining)
|
|
201
|
+
const available = gross - reserved
|
|
202
|
+
if (reserved > 0) {
|
|
203
|
+
remainingReserved.set(itemId, reservedRemaining - reserved)
|
|
204
|
+
}
|
|
109
205
|
if (available === 0) continue
|
|
110
206
|
out.push({
|
|
111
|
-
|
|
112
|
-
|
|
207
|
+
key: sourceStackKey(c),
|
|
208
|
+
rowId: c.id,
|
|
209
|
+
itemId,
|
|
210
|
+
item: getItem(itemId),
|
|
211
|
+
stats: c.stats,
|
|
212
|
+
modules: c.modules ?? [],
|
|
113
213
|
available,
|
|
114
|
-
plotNeeds:
|
|
214
|
+
plotNeeds: need.missing,
|
|
215
|
+
reserved,
|
|
115
216
|
})
|
|
116
217
|
}
|
|
117
218
|
return out
|
|
@@ -128,7 +229,8 @@ function partitionSources(
|
|
|
128
229
|
if (!entity.owner.equals(target.ownerName)) continue
|
|
129
230
|
if (entity.id.equals(target.entityId)) continue
|
|
130
231
|
if (!coordsEqual(entity.coordinates, target.coordinates)) continue
|
|
131
|
-
const
|
|
232
|
+
const reserved = reservedByItemFor(entity)
|
|
233
|
+
const relevant = matchRelevantCargo(entity, target, cargo, reserved)
|
|
132
234
|
if (relevant.length === 0) continue
|
|
133
235
|
const loaderCount = entity.loaders?.quantity.toNumber() ?? 0
|
|
134
236
|
const loaderTotalMass = entity.loaders?.mass.toNumber() ?? 0
|
|
@@ -145,3 +247,45 @@ function partitionSources(
|
|
|
145
247
|
}
|
|
146
248
|
return {eligible, unreachable}
|
|
147
249
|
}
|
|
250
|
+
|
|
251
|
+
function isTransferTask(task: ServerContract.Types.task): boolean {
|
|
252
|
+
const type = task.type.toNumber()
|
|
253
|
+
return type === TaskType.LOAD || type === TaskType.UNLOAD
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function reservationsOf(source: ServerContract.Types.entity_info): Reservation[] {
|
|
257
|
+
if (!source.schedule) return []
|
|
258
|
+
const out = new Map<string, Reservation>()
|
|
259
|
+
for (const task of source.schedule.tasks) {
|
|
260
|
+
if (!isTransferTask(task)) continue
|
|
261
|
+
if (!task.entitytarget) continue
|
|
262
|
+
const targetType = task.entitytarget.entity_type
|
|
263
|
+
const targetId = task.entitytarget.entity_id
|
|
264
|
+
for (const c of task.cargo) {
|
|
265
|
+
const itemId = c.item_id.toNumber()
|
|
266
|
+
const quantity = c.quantity.toNumber()
|
|
267
|
+
if (quantity === 0) continue
|
|
268
|
+
const key = `${targetId.toString()}#${itemId}`
|
|
269
|
+
const existing = out.get(key)
|
|
270
|
+
if (existing) {
|
|
271
|
+
existing.quantity += quantity
|
|
272
|
+
} else {
|
|
273
|
+
out.set(key, {
|
|
274
|
+
targetEntityId: targetId,
|
|
275
|
+
targetEntityType: targetType,
|
|
276
|
+
itemId,
|
|
277
|
+
quantity,
|
|
278
|
+
})
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return Array.from(out.values())
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function reservedByItemFor(source: ServerContract.Types.entity_info): Map<number, number> {
|
|
286
|
+
const out = new Map<number, number>()
|
|
287
|
+
for (const r of reservationsOf(source)) {
|
|
288
|
+
out.set(r.itemId, (out.get(r.itemId) ?? 0) + r.quantity)
|
|
289
|
+
}
|
|
290
|
+
return out
|
|
291
|
+
}
|
package/src/managers/index.ts
CHANGED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import {TaskType} from '../types'
|
|
2
|
+
import {createProjectedEntity, type Projectable} from './projection'
|
|
3
|
+
import {currentTaskIndex, currentTaskProgressFloat, isTaskComplete} from './schedule'
|
|
4
|
+
|
|
5
|
+
export function energyAtTime(entity: Projectable, now: Date): number {
|
|
6
|
+
const projected = createProjectedEntity(entity)
|
|
7
|
+
const capacity = projected.generator ? Number(projected.generator.capacity) : undefined
|
|
8
|
+
|
|
9
|
+
const clamp = (value: number): number => {
|
|
10
|
+
const floored = Math.max(0, value)
|
|
11
|
+
return capacity !== undefined ? Math.min(capacity, floored) : floored
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let running = Number(projected.energy)
|
|
15
|
+
|
|
16
|
+
const tasks = entity.schedule?.tasks
|
|
17
|
+
if (!tasks || tasks.length === 0) return clamp(running)
|
|
18
|
+
|
|
19
|
+
const activeIndex = currentTaskIndex(entity, now)
|
|
20
|
+
const activeProgress = currentTaskProgressFloat(entity, now)
|
|
21
|
+
|
|
22
|
+
for (let i = 0; i < tasks.length; i++) {
|
|
23
|
+
const complete = isTaskComplete(entity, i, now)
|
|
24
|
+
if (!complete && i !== activeIndex) break
|
|
25
|
+
|
|
26
|
+
const fraction = complete ? 1 : activeProgress
|
|
27
|
+
|
|
28
|
+
if (tasks[i].type.toNumber() === TaskType.RECHARGE) {
|
|
29
|
+
if (capacity !== undefined) {
|
|
30
|
+
running = complete ? capacity : running + (capacity - running) * fraction
|
|
31
|
+
}
|
|
32
|
+
} else {
|
|
33
|
+
const cost = Number(tasks[i].energy_cost ?? 0)
|
|
34
|
+
running -= cost * fraction
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
running = clamp(running)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return clamp(running)
|
|
41
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {Name, TimePoint, 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,
|
|
@@ -20,7 +20,7 @@ import {getEntityLayout, getRecipe, type RecipeInput} from '../data/recipes-runt
|
|
|
20
20
|
import {computeEntityCapabilities} from '../derivation/capabilities'
|
|
21
21
|
import {decodeCraftedItemStats} from '../derivation/crafting'
|
|
22
22
|
import {packedModulesToInstalled, type InstalledModule} from '../entities/slot-multiplier'
|
|
23
|
-
import {
|
|
23
|
+
import {lerp} from '../travel/travel'
|
|
24
24
|
import {
|
|
25
25
|
calcStacksMass,
|
|
26
26
|
cargoItemToStack,
|
|
@@ -217,25 +217,22 @@ function applyFlightTask(
|
|
|
217
217
|
task: ServerContract.Types.task,
|
|
218
218
|
options: {complete: boolean; progress?: number}
|
|
219
219
|
): void {
|
|
220
|
-
if (!task.coordinates
|
|
220
|
+
if (!task.coordinates) return
|
|
221
221
|
|
|
222
|
-
const origin = projected.location
|
|
223
222
|
const destination = Coordinates.from(task.coordinates)
|
|
224
|
-
const distance = distanceBetweenCoordinates(origin, task.coordinates)
|
|
225
|
-
const energyUsage = distance.dividing(PRECISION).multiplying(projected.engines.drain)
|
|
226
223
|
|
|
227
224
|
if (options.complete) {
|
|
228
|
-
projected
|
|
229
|
-
? UInt16.from(projected.energy.subtracting(energyUsage))
|
|
230
|
-
: UInt16.from(0)
|
|
225
|
+
applyEnergyCost(projected, task)
|
|
231
226
|
projected.location = destination
|
|
232
227
|
} else if (options.progress !== undefined) {
|
|
233
|
-
const interpolated = lerp(
|
|
228
|
+
const interpolated = lerp(projected.location, destination, options.progress)
|
|
234
229
|
projected.location = Coordinates.from({
|
|
235
230
|
x: Math.round(interpolated.x),
|
|
236
231
|
y: Math.round(interpolated.y),
|
|
237
232
|
})
|
|
238
|
-
const partialEnergy = UInt64.from(
|
|
233
|
+
const partialEnergy = UInt64.from(
|
|
234
|
+
Math.floor(Number(task.energy_cost ?? 0) * options.progress)
|
|
235
|
+
)
|
|
239
236
|
projected.energy = projected.energy.gt(partialEnergy)
|
|
240
237
|
? UInt16.from(projected.energy.subtracting(partialEnergy))
|
|
241
238
|
: UInt16.from(0)
|
|
@@ -305,6 +302,7 @@ function applyTask(projected: ProjectedEntity, task: ServerContract.Types.task):
|
|
|
305
302
|
applyRechargeTask(projected, task, {complete: true})
|
|
306
303
|
break
|
|
307
304
|
case TaskType.TRAVEL:
|
|
305
|
+
case TaskType.WARP:
|
|
308
306
|
applyFlightTask(projected, task, {complete: true})
|
|
309
307
|
break
|
|
310
308
|
case TaskType.LOAD:
|
|
@@ -479,6 +477,7 @@ export function projectEntityAt(entity: Projectable, now: Date): ProjectedEntity
|
|
|
479
477
|
applyRechargeTask(projected, task, {complete: taskComplete, progress})
|
|
480
478
|
break
|
|
481
479
|
case TaskType.TRAVEL:
|
|
480
|
+
case TaskType.WARP:
|
|
482
481
|
applyFlightTask(projected, task, {complete: taskComplete, progress})
|
|
483
482
|
break
|
|
484
483
|
case TaskType.LOAD:
|