@shipload/sdk 1.0.0-next.67 → 1.0.0-next.68
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 +6 -5
- package/lib/shipload.js +27 -10
- package/lib/shipload.js.map +1 -1
- package/lib/shipload.m.js +27 -10
- package/lib/shipload.m.js.map +1 -1
- package/package.json +1 -1
- package/src/influence/civic-shuttle.test.ts +56 -13
- package/src/influence/civic-shuttle.ts +37 -10
package/package.json
CHANGED
|
@@ -72,7 +72,7 @@ function makeLane(laneKey: number, tasks: Task[]): Lane {
|
|
|
72
72
|
|
|
73
73
|
test('civicShuttleBays lists both Depot bays with their queue depth', () => {
|
|
74
74
|
const lanes = [makeLane(1, [shuttleTask(20, 21)])]
|
|
75
|
-
const bays = civicShuttleBays(depotModules(), ITEM_DEPOT_T1_PACKED, lanes)
|
|
75
|
+
const bays = civicShuttleBays(depotModules(), ITEM_DEPOT_T1_PACKED, lanes, T0)
|
|
76
76
|
expect(bays.map((b) => b.laneKey)).toEqual([1, 2])
|
|
77
77
|
expect(bays.map((b) => b.slotIndex)).toEqual([0, 1])
|
|
78
78
|
expect(bays[0].queued).toBe(1)
|
|
@@ -83,25 +83,68 @@ test('civicShuttleBays lists both Depot bays with their queue depth', () => {
|
|
|
83
83
|
|
|
84
84
|
test('a bay is full at the job queue cap', () => {
|
|
85
85
|
const filled = Array.from({length: JOB_QUEUE_CAP}, () => shuttleTask(20, 21))
|
|
86
|
-
const bays = civicShuttleBays(depotModules(), ITEM_DEPOT_T1_PACKED, [makeLane(1, filled)])
|
|
86
|
+
const bays = civicShuttleBays(depotModules(), ITEM_DEPOT_T1_PACKED, [makeLane(1, filled)], T0)
|
|
87
87
|
expect(bays[0].full).toBe(true)
|
|
88
88
|
expect(bays[1].full).toBe(false)
|
|
89
89
|
})
|
|
90
90
|
|
|
91
|
+
const AFTER = new Date(T0.getTime() + 3_600_000)
|
|
92
|
+
|
|
93
|
+
test('a landed booking leaves the bay free, since booking clears it', () => {
|
|
94
|
+
const filled = Array.from({length: JOB_QUEUE_CAP}, () => shuttleTask(20, 21))
|
|
95
|
+
const lanes = [makeLane(1, filled)]
|
|
96
|
+
const bays = civicShuttleBays(depotModules(), ITEM_DEPOT_T1_PACKED, lanes, AFTER)
|
|
97
|
+
expect(bays[0].queued).toBe(0)
|
|
98
|
+
expect(bays[0].full).toBe(false)
|
|
99
|
+
expect(selectCivicShuttleBay(depotModules(), ITEM_DEPOT_T1_PACKED, lanes, AFTER)?.laneKey).toBe(
|
|
100
|
+
1
|
|
101
|
+
)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
test('a bay part-way through its queue counts only what is still unfinished', () => {
|
|
105
|
+
const lanes = [makeLane(1, [shuttleTask(20, 21), shuttleTask(22, 23), shuttleTask(24, 25)])]
|
|
106
|
+
const bays = civicShuttleBays(
|
|
107
|
+
depotModules(),
|
|
108
|
+
ITEM_DEPOT_T1_PACKED,
|
|
109
|
+
lanes,
|
|
110
|
+
new Date(T0.getTime() + 90_000)
|
|
111
|
+
)
|
|
112
|
+
expect(bays[0].queued).toBe(2)
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
test('a landed transfer is listed for settling but is not callable', () => {
|
|
116
|
+
const lanes = [makeLane(1, [shuttleTask(20, 21)])]
|
|
117
|
+
const [transfer] = pendingCivicTransfers({lanes} as never, AFTER)
|
|
118
|
+
expect(transfer.isTail).toBe(true)
|
|
119
|
+
expect(transfer.complete).toBe(true)
|
|
120
|
+
expect(transfer.callable).toBe(false)
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
test('landed transfers do not count against the per-player cap', () => {
|
|
124
|
+
const mine = Array.from({length: CIVIC_TRANSFER_PER_PLAYER_CAP}, (_, i) =>
|
|
125
|
+
shuttleTask(20 + i, 50)
|
|
126
|
+
)
|
|
127
|
+
const senderIds = mine.map((_, i) => 20 + i)
|
|
128
|
+
const at = {lanes: [makeLane(1, mine)]} as never
|
|
129
|
+
expect(civicTransfersAtCap(at, senderIds, T0)).toBe(true)
|
|
130
|
+
expect(civicTransfersAtCap(at, senderIds, AFTER)).toBe(false)
|
|
131
|
+
expect(civicTransfersFrom(at, senderIds, AFTER).length).toBe(CIVIC_TRANSFER_PER_PLAYER_CAP)
|
|
132
|
+
})
|
|
133
|
+
|
|
91
134
|
test('selectCivicShuttleBay takes the first free bay, then the lowest occupied one', () => {
|
|
92
135
|
const modules = depotModules()
|
|
93
|
-
expect(selectCivicShuttleBay(modules, ITEM_DEPOT_T1_PACKED, [])?.laneKey).toBe(1)
|
|
136
|
+
expect(selectCivicShuttleBay(modules, ITEM_DEPOT_T1_PACKED, [], T0)?.laneKey).toBe(1)
|
|
94
137
|
|
|
95
138
|
const firstBusy = [makeLane(1, [shuttleTask(20, 21)])]
|
|
96
|
-
expect(selectCivicShuttleBay(modules, ITEM_DEPOT_T1_PACKED, firstBusy)?.laneKey).toBe(2)
|
|
139
|
+
expect(selectCivicShuttleBay(modules, ITEM_DEPOT_T1_PACKED, firstBusy, T0)?.laneKey).toBe(2)
|
|
97
140
|
|
|
98
141
|
const bothBusy = [makeLane(1, [shuttleTask(20, 21)]), makeLane(2, [shuttleTask(22, 23)])]
|
|
99
|
-
expect(selectCivicShuttleBay(modules, ITEM_DEPOT_T1_PACKED, bothBusy)?.laneKey).toBe(1)
|
|
142
|
+
expect(selectCivicShuttleBay(modules, ITEM_DEPOT_T1_PACKED, bothBusy, T0)?.laneKey).toBe(1)
|
|
100
143
|
})
|
|
101
144
|
|
|
102
145
|
test('selectCivicShuttleBay returns nothing when the building has no loaders', () => {
|
|
103
146
|
const modules = [makeModuleEntry(ITEM_STORAGE_T1, storageStats)]
|
|
104
|
-
expect(selectCivicShuttleBay(modules, ITEM_DEPOT_T1_PACKED, [])).toBeUndefined()
|
|
147
|
+
expect(selectCivicShuttleBay(modules, ITEM_DEPOT_T1_PACKED, [], T0)).toBeUndefined()
|
|
105
148
|
})
|
|
106
149
|
|
|
107
150
|
test('pendingCivicTransfers reports the bay, both counterparties, and the tail', () => {
|
|
@@ -109,7 +152,7 @@ test('pendingCivicTransfers reports the bay, both counterparties, and the tail',
|
|
|
109
152
|
makeLane(1, [shuttleTask(20, 21), shuttleTask(22, 23)]),
|
|
110
153
|
makeLane(2, [shuttleTask(24, 25)]),
|
|
111
154
|
]
|
|
112
|
-
const transfers = pendingCivicTransfers({lanes} as never)
|
|
155
|
+
const transfers = pendingCivicTransfers({lanes} as never, T0)
|
|
113
156
|
expect(transfers.length).toBe(3)
|
|
114
157
|
|
|
115
158
|
const first = transfers.find((t) => String(t.senderId) === '20')!
|
|
@@ -131,7 +174,7 @@ test('pendingCivicTransfers reports the bay, both counterparties, and the tail',
|
|
|
131
174
|
|
|
132
175
|
test('a grouped tail transfer is not callable', () => {
|
|
133
176
|
const lanes = [makeLane(1, [shuttleTask(20, 21, 7)])]
|
|
134
|
-
const [transfer] = pendingCivicTransfers({lanes} as never)
|
|
177
|
+
const [transfer] = pendingCivicTransfers({lanes} as never, T0)
|
|
135
178
|
expect(transfer.isTail).toBe(true)
|
|
136
179
|
expect(transfer.callable).toBe(false)
|
|
137
180
|
})
|
|
@@ -142,9 +185,9 @@ test('civicTransfersFrom counts only the ships a player sends from', () => {
|
|
|
142
185
|
makeLane(2, [shuttleTask(22, 23)]),
|
|
143
186
|
]
|
|
144
187
|
const entity = {lanes} as never
|
|
145
|
-
expect(civicTransfersFrom(entity, [20, 22]).length).toBe(2)
|
|
146
|
-
expect(civicTransfersFrom(entity, [30]).length).toBe(1)
|
|
147
|
-
expect(civicTransfersFrom(entity, [99]).length).toBe(0)
|
|
188
|
+
expect(civicTransfersFrom(entity, [20, 22], T0).length).toBe(2)
|
|
189
|
+
expect(civicTransfersFrom(entity, [30], T0).length).toBe(1)
|
|
190
|
+
expect(civicTransfersFrom(entity, [99], T0).length).toBe(0)
|
|
148
191
|
})
|
|
149
192
|
|
|
150
193
|
test('civicTransfersAtCap trips at the per-player cap', () => {
|
|
@@ -154,6 +197,6 @@ test('civicTransfersAtCap trips at the per-player cap', () => {
|
|
|
154
197
|
const senderIds = mine.map((_, i) => 20 + i)
|
|
155
198
|
const under = {lanes: [makeLane(1, mine.slice(0, -1))]} as never
|
|
156
199
|
const at = {lanes: [makeLane(1, mine)]} as never
|
|
157
|
-
expect(civicTransfersAtCap(under, senderIds)).toBe(false)
|
|
158
|
-
expect(civicTransfersAtCap(at, senderIds)).toBe(true)
|
|
200
|
+
expect(civicTransfersAtCap(under, senderIds, T0)).toBe(false)
|
|
201
|
+
expect(civicTransfersAtCap(at, senderIds, T0)).toBe(true)
|
|
159
202
|
})
|
|
@@ -22,10 +22,25 @@ export interface CivicShuttleBay {
|
|
|
22
22
|
full: boolean
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
// a landed booking clears during the next civicshuttle, so only unfinished tasks occupy a bay
|
|
26
|
+
function unfinishedTasks(lane: Lane | undefined, now: Date): number {
|
|
27
|
+
if (!lane) return 0
|
|
28
|
+
const startedMs = lane.schedule.started.toDate().getTime()
|
|
29
|
+
const nowMs = now.getTime()
|
|
30
|
+
let endSec = 0
|
|
31
|
+
let count = 0
|
|
32
|
+
for (const task of lane.schedule.tasks) {
|
|
33
|
+
endSec += task.duration.toNumber()
|
|
34
|
+
if (startedMs + endSec * 1000 > nowMs) count++
|
|
35
|
+
}
|
|
36
|
+
return count
|
|
37
|
+
}
|
|
38
|
+
|
|
25
39
|
export function civicShuttleBays(
|
|
26
40
|
modules: ModuleEntry[],
|
|
27
41
|
entityItemId: number,
|
|
28
|
-
lanes: Lane[]
|
|
42
|
+
lanes: Lane[],
|
|
43
|
+
now: Date = new Date()
|
|
29
44
|
): CivicShuttleBay[] {
|
|
30
45
|
const bays: CivicShuttleBay[] = []
|
|
31
46
|
for (let slotIndex = 0; slotIndex < modules.length; slotIndex++) {
|
|
@@ -35,7 +50,7 @@ export function civicShuttleBays(
|
|
|
35
50
|
const laneKey = laneKeyForModule(slotIndex)
|
|
36
51
|
const loader = resolveLaneLoader(modules, entityItemId, laneKey)
|
|
37
52
|
const lane = lanes.find((l) => l.lane_key.toNumber() === laneKey)
|
|
38
|
-
const queued = lane
|
|
53
|
+
const queued = unfinishedTasks(lane, now)
|
|
39
54
|
bays.push({
|
|
40
55
|
laneKey,
|
|
41
56
|
slotIndex,
|
|
@@ -53,9 +68,10 @@ export function civicShuttleBays(
|
|
|
53
68
|
export function selectCivicShuttleBay(
|
|
54
69
|
modules: ModuleEntry[],
|
|
55
70
|
entityItemId: number,
|
|
56
|
-
lanes: Lane[]
|
|
71
|
+
lanes: Lane[],
|
|
72
|
+
now: Date = new Date()
|
|
57
73
|
): CivicShuttleBay | undefined {
|
|
58
|
-
const bays = civicShuttleBays(modules, entityItemId, lanes)
|
|
74
|
+
const bays = civicShuttleBays(modules, entityItemId, lanes, now)
|
|
59
75
|
return bays.find((bay) => bay.queued === 0) ?? bays[0]
|
|
60
76
|
}
|
|
61
77
|
|
|
@@ -68,10 +84,14 @@ export interface PendingCivicTransfer {
|
|
|
68
84
|
startsAt: Date
|
|
69
85
|
completesAt: Date
|
|
70
86
|
isTail: boolean
|
|
87
|
+
complete: boolean
|
|
71
88
|
callable: boolean
|
|
72
89
|
}
|
|
73
90
|
|
|
74
|
-
export function pendingCivicTransfers(
|
|
91
|
+
export function pendingCivicTransfers(
|
|
92
|
+
entity: ScheduleData,
|
|
93
|
+
now: Date = new Date()
|
|
94
|
+
): PendingCivicTransfer[] {
|
|
75
95
|
const tailIndex = new Map<number, number>()
|
|
76
96
|
for (const l of entity.lanes ?? []) {
|
|
77
97
|
tailIndex.set(l.lane_key.toNumber(), l.schedule.tasks.length - 1)
|
|
@@ -83,6 +103,7 @@ export function pendingCivicTransfers(entity: ScheduleData): PendingCivicTransfe
|
|
|
83
103
|
const push = entry.task.couplings.find((c) => c.kind.toNumber() === HoldKind.PUSH)
|
|
84
104
|
if (!pull || !push) continue
|
|
85
105
|
const isTail = tailIndex.get(entry.laneKey) === entry.taskIndex
|
|
106
|
+
const complete = entry.completesAt.getTime() <= now.getTime()
|
|
86
107
|
out.push({
|
|
87
108
|
laneKey: entry.laneKey,
|
|
88
109
|
taskIndex: entry.taskIndex,
|
|
@@ -92,7 +113,8 @@ export function pendingCivicTransfers(entity: ScheduleData): PendingCivicTransfe
|
|
|
92
113
|
startsAt: entry.startsAt,
|
|
93
114
|
completesAt: entry.completesAt,
|
|
94
115
|
isTail,
|
|
95
|
-
|
|
116
|
+
complete,
|
|
117
|
+
callable: isTail && !complete && !entry.task.entitygroup,
|
|
96
118
|
})
|
|
97
119
|
}
|
|
98
120
|
return out
|
|
@@ -101,15 +123,20 @@ export function pendingCivicTransfers(entity: ScheduleData): PendingCivicTransfe
|
|
|
101
123
|
// Mirrors civic_transfers_for: the sender side of every shuttle the building holds, by owner.
|
|
102
124
|
export function civicTransfersFrom(
|
|
103
125
|
entity: ScheduleData,
|
|
104
|
-
senderIds: Array<UInt64 | string | number
|
|
126
|
+
senderIds: Array<UInt64 | string | number>,
|
|
127
|
+
now: Date = new Date()
|
|
105
128
|
): PendingCivicTransfer[] {
|
|
106
129
|
const owned = new Set(senderIds.map((id) => String(id)))
|
|
107
|
-
return pendingCivicTransfers(entity).filter((t) => owned.has(String(t.senderId)))
|
|
130
|
+
return pendingCivicTransfers(entity, now).filter((t) => owned.has(String(t.senderId)))
|
|
108
131
|
}
|
|
109
132
|
|
|
110
133
|
export function civicTransfersAtCap(
|
|
111
134
|
entity: ScheduleData,
|
|
112
|
-
senderIds: Array<UInt64 | string | number
|
|
135
|
+
senderIds: Array<UInt64 | string | number>,
|
|
136
|
+
now: Date = new Date()
|
|
113
137
|
): boolean {
|
|
114
|
-
return
|
|
138
|
+
return (
|
|
139
|
+
civicTransfersFrom(entity, senderIds, now).filter((t) => !t.complete).length >=
|
|
140
|
+
CIVIC_TRANSFER_PER_PLAYER_CAP
|
|
141
|
+
)
|
|
115
142
|
}
|