@shipload/sdk 1.0.0-next.20 → 1.0.0-next.21

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.21",
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
 
@@ -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,79 @@ 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 targetIdStr = task.entitytarget.entity_id.toString()
106
+ const etaSeconds = Math.max(
107
+ 0,
108
+ Math.round((startedMs + cumulativeSec * 1000 - nowMs) / 1000)
109
+ )
110
+ let perTarget = buckets.get(targetIdStr)
111
+ if (!perTarget) {
112
+ perTarget = new Map()
113
+ buckets.set(targetIdStr, perTarget)
114
+ }
115
+ for (const c of task.cargo) {
116
+ const itemId = c.item_id.toNumber()
117
+ const quantity = c.quantity.toNumber()
118
+ if (quantity === 0) continue
119
+ const key = `${entityIdStr}#${itemId}`
120
+ const existing = perTarget.get(key)
121
+ if (existing) {
122
+ existing.quantity += quantity
123
+ existing.etaSeconds = Math.min(existing.etaSeconds, etaSeconds)
124
+ } else {
125
+ perTarget.set(key, {
126
+ sourceEntityId: entity.id,
127
+ sourceEntityType: entity.type,
128
+ sourceName,
129
+ itemId,
130
+ quantity,
131
+ etaSeconds,
132
+ })
133
+ }
134
+ }
135
+ }
136
+ }
137
+ const out = new Map<string, InboundTransfer[]>()
138
+ for (const [targetId, perTarget] of buckets) {
139
+ out.set(targetId, Array.from(perTarget.values()))
140
+ }
141
+ return out
142
+ }
143
+
144
+ reservationsFrom(
145
+ sourceEntityId: UInt64,
146
+ entities: ServerContract.Types.entity_info[]
147
+ ): Reservation[] {
148
+ const source = entities.find((e) => e.id.equals(sourceEntityId))
149
+ if (!source) return []
150
+ return reservationsOf(source)
151
+ }
152
+
77
153
  estimateFinalizeDuration(target: BuildableTarget, crafterSpeed: number): UInt32 {
78
154
  return calc_craft_duration(crafterSpeed, target.progress.massRequired)
79
155
  }
@@ -90,28 +166,54 @@ function coordsEqual(
90
166
  return a.x.equals(b.x) && a.y.equals(b.y)
91
167
  }
92
168
 
169
+ function moduleKey(module: ServerContract.Types.module_entry): string {
170
+ const installed = module.installed
171
+ if (!installed) return `${module.type.toNumber()}:empty`
172
+
173
+ return `${module.type.toNumber()}:${installed.item_id.toNumber()}:${installed.stats.toString()}`
174
+ }
175
+
176
+ function sourceStackKey(cargo: ServerContract.Types.cargo_row): string {
177
+ return `${cargo.item_id.toNumber()}#${cargo.stats.toString()}#${(cargo.modules ?? [])
178
+ .map(moduleKey)
179
+ .join(',')}`
180
+ }
181
+
93
182
  function matchRelevantCargo(
94
183
  entity: ServerContract.Types.entity_info,
95
184
  target: BuildableTarget,
96
- cargo: ServerContract.Types.cargo_row[]
185
+ cargo: ServerContract.Types.cargo_row[],
186
+ reservedByItem: Map<number, number>
97
187
  ): SourceCargoStack[] {
98
- const quantityByItemId = new Map<number, number>()
188
+ const needsByItemId = new Map(
189
+ target.progress.rows.filter((row) => row.missing > 0).map((row) => [row.itemId, row])
190
+ )
191
+ const remainingReserved = new Map(reservedByItem)
192
+ const out: SourceCargoStack[] = []
99
193
  for (const c of cargo) {
100
194
  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
195
+ const itemId = c.item_id.toNumber()
196
+ const need = needsByItemId.get(itemId)
197
+ if (!need) continue
198
+ const gross = c.quantity.toNumber()
199
+ if (gross === 0) continue
200
+ const reservedRemaining = remainingReserved.get(itemId) ?? 0
201
+ const reserved = Math.min(gross, reservedRemaining)
202
+ const available = gross - reserved
203
+ if (reserved > 0) {
204
+ remainingReserved.set(itemId, reservedRemaining - reserved)
205
+ }
109
206
  if (available === 0) continue
110
207
  out.push({
111
- itemId: row.itemId,
112
- item: getItem(row.itemId),
208
+ key: sourceStackKey(c),
209
+ rowId: c.id,
210
+ itemId,
211
+ item: getItem(itemId),
212
+ stats: c.stats,
213
+ modules: c.modules ?? [],
113
214
  available,
114
- plotNeeds: row.missing,
215
+ plotNeeds: need.missing,
216
+ reserved,
115
217
  })
116
218
  }
117
219
  return out
@@ -128,7 +230,8 @@ function partitionSources(
128
230
  if (!entity.owner.equals(target.ownerName)) continue
129
231
  if (entity.id.equals(target.entityId)) continue
130
232
  if (!coordsEqual(entity.coordinates, target.coordinates)) continue
131
- const relevant = matchRelevantCargo(entity, target, cargo)
233
+ const reserved = reservedByItemFor(entity)
234
+ const relevant = matchRelevantCargo(entity, target, cargo, reserved)
132
235
  if (relevant.length === 0) continue
133
236
  const loaderCount = entity.loaders?.quantity.toNumber() ?? 0
134
237
  const loaderTotalMass = entity.loaders?.mass.toNumber() ?? 0
@@ -145,3 +248,45 @@ function partitionSources(
145
248
  }
146
249
  return {eligible, unreachable}
147
250
  }
251
+
252
+ function isTransferTask(task: ServerContract.Types.task): boolean {
253
+ const type = task.type.toNumber()
254
+ return type === TaskType.LOAD || type === TaskType.UNLOAD
255
+ }
256
+
257
+ function reservationsOf(source: ServerContract.Types.entity_info): Reservation[] {
258
+ if (!source.schedule) return []
259
+ const out = new Map<string, Reservation>()
260
+ for (const task of source.schedule.tasks) {
261
+ if (!isTransferTask(task)) continue
262
+ if (!task.entitytarget) continue
263
+ const targetType = task.entitytarget.entity_type
264
+ const targetId = task.entitytarget.entity_id
265
+ for (const c of task.cargo) {
266
+ const itemId = c.item_id.toNumber()
267
+ const quantity = c.quantity.toNumber()
268
+ if (quantity === 0) continue
269
+ const key = `${targetId.toString()}#${itemId}`
270
+ const existing = out.get(key)
271
+ if (existing) {
272
+ existing.quantity += quantity
273
+ } else {
274
+ out.set(key, {
275
+ targetEntityId: targetId,
276
+ targetEntityType: targetType,
277
+ itemId,
278
+ quantity,
279
+ })
280
+ }
281
+ }
282
+ }
283
+ return Array.from(out.values())
284
+ }
285
+
286
+ function reservedByItemFor(source: ServerContract.Types.entity_info): Map<number, number> {
287
+ const out = new Map<number, number>()
288
+ for (const r of reservationsOf(source)) {
289
+ out.set(r.itemId, (out.get(r.itemId) ?? 0) + r.quantity)
290
+ }
291
+ return out
292
+ }
@@ -19,4 +19,6 @@ export type {
19
19
  SourceCargoStack,
20
20
  FinalizerEntityRef,
21
21
  FinalizerCapability,
22
+ InboundTransfer,
23
+ Reservation,
22
24
  } from './construction-types'