@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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@shipload/sdk",
3
3
  "description": "SDKs for Shipload",
4
- "version": "1.0.0-next.20",
4
+ "version": "1.0.0-next.22",
5
5
  "homepage": "https://github.com/shipload/toolkit/tree/master/packages/sdk",
6
6
  "repository": {
7
7
  "type": "git",
@@ -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 quantityByItemId = new Map<number, number>()
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 id = c.item_id.toNumber()
102
- quantityByItemId.set(id, (quantityByItemId.get(id) ?? 0) + c.quantity.toNumber())
103
- }
104
-
105
- const out: SourceCargoStack[] = []
106
- for (const row of target.progress.rows) {
107
- if (row.missing === 0) continue
108
- const available = quantityByItemId.get(row.itemId) ?? 0
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
- itemId: row.itemId,
112
- item: getItem(row.itemId),
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: row.missing,
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 relevant = matchRelevantCargo(entity, target, cargo)
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
+ }
@@ -19,4 +19,6 @@ export type {
19
19
  SourceCargoStack,
20
20
  FinalizerEntityRef,
21
21
  FinalizerCapability,
22
+ InboundTransfer,
23
+ Reservation,
22
24
  } from './construction-types'
@@ -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, PRECISION, TaskType} from '../types'
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 {distanceBetweenCoordinates, lerp} from '../travel/travel'
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 || !projected.engines) return
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.energy = projected.energy.gt(energyUsage)
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(origin, destination, options.progress)
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(Math.floor(Number(energyUsage) * options.progress))
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: