@shipload/sdk 1.0.0-next.65 → 1.0.0-next.67
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 +786 -321
- package/lib/shipload.js +2731 -1305
- package/lib/shipload.js.map +1 -1
- package/lib/shipload.m.js +2644 -1286
- package/lib/shipload.m.js.map +1 -1
- package/lib/testing.d.ts +223 -146
- package/lib/testing.js +907 -563
- package/lib/testing.js.map +1 -1
- package/lib/testing.m.js +907 -563
- package/lib/testing.m.js.map +1 -1
- package/package.json +1 -1
- package/src/capabilities/crafting.test.ts +51 -6
- package/src/capabilities/crafting.ts +19 -4
- package/src/capabilities/gathering.ts +2 -2
- package/src/capabilities/storage.test.ts +26 -0
- package/src/capabilities/storage.ts +6 -6
- package/src/contracts/fund.ts +79 -2
- package/src/contracts/platform.ts +10 -1
- package/src/contracts/server.ts +521 -330
- package/src/data/charters.json +325 -127
- package/src/data/entities.json +29 -24
- package/src/data/items.json +114 -114
- package/src/data/kind-registry.json +3 -3
- package/src/data/kind-registry.ts +5 -0
- package/src/data/metadata.ts +9 -2
- package/src/data/recipes.json +56 -95
- package/src/derivation/build-methods.test.ts +59 -1
- package/src/derivation/build-methods.ts +2 -0
- package/src/derivation/capabilities.ts +7 -7
- package/src/derivation/strata.ts +5 -3
- package/src/derivation/tiers.ts +5 -5
- package/src/entity/hydrate.test.ts +156 -0
- package/src/entity/hydrate.ts +86 -0
- package/src/errors.ts +9 -0
- package/src/format.ts +15 -13
- package/src/index-module.ts +32 -5
- package/src/influence/ballot.test.ts +103 -0
- package/src/influence/ballot.ts +98 -0
- package/src/influence/charter-metadata.test.ts +136 -0
- package/src/influence/charter-metadata.ts +236 -0
- package/src/influence/charters.test.ts +51 -65
- package/src/influence/charters.ts +70 -40
- package/src/influence/civic-shuttle.test.ts +159 -0
- package/src/influence/civic-shuttle.ts +115 -0
- package/src/influence/civic.test.ts +287 -0
- package/src/influence/civic.ts +269 -0
- package/src/influence/constants.ts +30 -9
- package/src/influence/decomp.ts +15 -15
- package/src/influence/duration.ts +42 -6
- package/src/influence/index.ts +59 -6
- package/src/influence/influence.test.ts +76 -9
- package/src/influence/valuation.ts +17 -17
- package/src/managers/actions.ts +99 -37
- package/src/managers/base.ts +4 -0
- package/src/managers/context.ts +3 -1
- package/src/managers/epochs.ts +32 -1
- package/src/managers/index.ts +4 -2
- package/src/managers/influence.test.ts +18 -20
- package/src/managers/influence.ts +137 -84
- package/src/managers/jobs.test.ts +2 -2
- package/src/managers/jobs.ts +4 -4
- package/src/nft/description.ts +10 -11
- package/src/planner/index.ts +54 -0
- package/src/planner/planner.test.ts +127 -2
- package/src/resolution/display-name.ts +1 -1
- package/src/scheduling/availability.test.ts +113 -0
- package/src/scheduling/availability.ts +48 -8
- package/src/scheduling/epoch.ts +1 -0
- package/src/scheduling/jobs.ts +1 -1
- package/src/scheduling/projection.ts +2 -16
- package/src/scheduling/task-cargo.test.ts +62 -1
- package/src/scheduling/task-cargo.ts +25 -10
- package/src/scheduling/task-effects.test.ts +35 -0
- package/src/scheduling/task-effects.ts +34 -0
- package/src/scheduling/unwrap.test.ts +3 -3
- package/src/scheduling/unwrap.ts +3 -3
- package/src/shipload.ts +22 -3
- package/src/travel/reach.test.ts +86 -0
- package/src/travel/reach.ts +23 -10
- package/src/travel/route-build.test.ts +63 -0
- package/src/travel/route-build.ts +60 -0
- package/src/travel/travel.ts +2 -1
- package/src/types.ts +6 -2
- package/src/utils/hash.ts +18 -2
- package/src/utils/system.ts +6 -1
|
@@ -1,26 +1,34 @@
|
|
|
1
1
|
import chartersJson from '../data/charters.json'
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import {
|
|
3
|
+
CHARTER_NONE,
|
|
4
|
+
CIVIC_GRANT_LEVEL_GATE,
|
|
5
|
+
CIVIC_GRANT_MODULE,
|
|
6
|
+
CIVIC_GRANT_RUNG,
|
|
7
|
+
} from './constants'
|
|
8
|
+
|
|
9
|
+
export interface CharterGrant {
|
|
5
10
|
kind: number
|
|
6
|
-
|
|
7
|
-
targetItemId: number
|
|
8
|
-
slotMask: number
|
|
11
|
+
building: number
|
|
9
12
|
stat: number
|
|
13
|
+
value: number
|
|
10
14
|
}
|
|
11
15
|
|
|
12
16
|
export interface CharterNode {
|
|
13
17
|
nodeId: number
|
|
14
18
|
cost: bigint
|
|
15
19
|
prereqs: number[]
|
|
16
|
-
|
|
20
|
+
grants: CharterGrant[]
|
|
21
|
+
exclusionGroup: number
|
|
22
|
+
repeatCap: number
|
|
17
23
|
}
|
|
18
24
|
|
|
19
25
|
export const CHARTER_REGISTRY: CharterNode[] = chartersJson.nodes.map((node) => ({
|
|
20
26
|
nodeId: node.nodeId,
|
|
21
27
|
cost: BigInt(node.cost),
|
|
22
28
|
prereqs: [...node.prereqs],
|
|
23
|
-
|
|
29
|
+
grants: node.grants.map((grant) => ({...grant})),
|
|
30
|
+
exclusionGroup: node.exclusionGroup,
|
|
31
|
+
repeatCap: node.repeatCap,
|
|
24
32
|
}))
|
|
25
33
|
|
|
26
34
|
export function charterNode(nodeId: number): CharterNode | undefined {
|
|
@@ -28,58 +36,77 @@ export function charterNode(nodeId: number): CharterNode | undefined {
|
|
|
28
36
|
return CHARTER_REGISTRY.find((n) => n.nodeId === nodeId)
|
|
29
37
|
}
|
|
30
38
|
|
|
31
|
-
export type CharterIneligibility = 'already-taken' | 'prereq-missing'
|
|
39
|
+
export type CharterIneligibility = 'already-taken' | 'prereq-missing'
|
|
32
40
|
|
|
33
41
|
export const CHARTER_INELIGIBILITY_MESSAGES: Record<CharterIneligibility, string> = {
|
|
34
42
|
'already-taken': 'charter is already completed at this world',
|
|
35
43
|
'prereq-missing': 'charter prerequisites are not completed',
|
|
36
|
-
'refit-target-missing': 'charter refit target not found at this world',
|
|
37
44
|
}
|
|
38
45
|
|
|
39
46
|
export interface BuiltCharter {
|
|
40
47
|
nodeId: number
|
|
41
|
-
|
|
48
|
+
repeats: number
|
|
42
49
|
}
|
|
43
50
|
|
|
44
51
|
export interface CharterWorld {
|
|
45
52
|
built: BuiltCharter[]
|
|
46
|
-
entityExists?: (entityId: bigint) => boolean
|
|
47
53
|
}
|
|
48
54
|
|
|
49
|
-
export interface
|
|
50
|
-
|
|
51
|
-
|
|
55
|
+
export interface WorldBuilding {
|
|
56
|
+
entityId: bigint
|
|
57
|
+
building: number
|
|
52
58
|
}
|
|
53
59
|
|
|
54
60
|
function builtCharter(world: CharterWorld, nodeId: number): BuiltCharter | undefined {
|
|
55
61
|
return world.built.find((row) => row.nodeId === nodeId)
|
|
56
62
|
}
|
|
57
63
|
|
|
58
|
-
export function
|
|
59
|
-
return CHARTER_REGISTRY.find(
|
|
60
|
-
(
|
|
61
|
-
|
|
64
|
+
export function charterGateNodeFor(building: number, level: number = 1): CharterNode | undefined {
|
|
65
|
+
return CHARTER_REGISTRY.find((node) =>
|
|
66
|
+
node.grants.some(
|
|
67
|
+
(grant) =>
|
|
68
|
+
grant.kind === CIVIC_GRANT_LEVEL_GATE &&
|
|
69
|
+
grant.building === building &&
|
|
70
|
+
grant.value === level
|
|
71
|
+
)
|
|
62
72
|
)
|
|
63
73
|
}
|
|
64
74
|
|
|
65
|
-
export function
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
75
|
+
export function charterBuildingEntity(buildings: WorldBuilding[], building: number): bigint {
|
|
76
|
+
const record = buildings.find((row) => row.building === building)
|
|
77
|
+
return record ? record.entityId : 0n
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function charterRungValue(world: CharterWorld, building: number, stat: number): number {
|
|
81
|
+
let total = 0
|
|
82
|
+
for (const node of CHARTER_REGISTRY) {
|
|
83
|
+
if (builtCharter(world, node.nodeId) === undefined) continue
|
|
84
|
+
for (const grant of node.grants) {
|
|
85
|
+
if (grant.kind !== CIVIC_GRANT_RUNG) continue
|
|
86
|
+
if (grant.building !== building || grant.stat !== stat) continue
|
|
87
|
+
total += grant.value
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return total
|
|
69
91
|
}
|
|
70
92
|
|
|
71
|
-
export function
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
93
|
+
export function charterModuleCount(world: CharterWorld, building: number): number {
|
|
94
|
+
let total = 0
|
|
95
|
+
for (const node of CHARTER_REGISTRY) {
|
|
96
|
+
if (builtCharter(world, node.nodeId) === undefined) continue
|
|
97
|
+
for (const grant of node.grants) {
|
|
98
|
+
if (grant.kind !== CIVIC_GRANT_MODULE) continue
|
|
99
|
+
if (grant.building !== building) continue
|
|
100
|
+
total += grant.value
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return total
|
|
78
104
|
}
|
|
79
105
|
|
|
80
|
-
export function
|
|
81
|
-
|
|
82
|
-
|
|
106
|
+
export function charterPrereqsMet(world: CharterWorld, node: CharterNode): boolean {
|
|
107
|
+
return node.prereqs.every(
|
|
108
|
+
(prereq) => prereq === CHARTER_NONE || builtCharter(world, prereq) !== undefined
|
|
109
|
+
)
|
|
83
110
|
}
|
|
84
111
|
|
|
85
112
|
export function charterIneligible(
|
|
@@ -88,7 +115,6 @@ export function charterIneligible(
|
|
|
88
115
|
): CharterIneligibility | undefined {
|
|
89
116
|
if (builtCharter(world, node.nodeId) !== undefined) return 'already-taken'
|
|
90
117
|
if (!charterPrereqsMet(world, node)) return 'prereq-missing'
|
|
91
|
-
if (!charterEffectTargetPresent(world, node)) return 'refit-target-missing'
|
|
92
118
|
return undefined
|
|
93
119
|
}
|
|
94
120
|
|
|
@@ -110,11 +136,15 @@ export function charterSingletonMandate(world: CharterWorld): number {
|
|
|
110
136
|
return only
|
|
111
137
|
}
|
|
112
138
|
|
|
113
|
-
export function
|
|
114
|
-
stored: ChosenCharter,
|
|
139
|
+
export function charterEligibleChained(
|
|
115
140
|
world: CharterWorld,
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
141
|
+
node: CharterNode,
|
|
142
|
+
seated: number[]
|
|
143
|
+
): boolean {
|
|
144
|
+
if (builtCharter(world, node.nodeId) !== undefined || seated.includes(node.nodeId)) return false
|
|
145
|
+
for (const prereq of node.prereqs) {
|
|
146
|
+
if (prereq === CHARTER_NONE) continue
|
|
147
|
+
if (builtCharter(world, prereq) === undefined && !seated.includes(prereq)) return false
|
|
148
|
+
}
|
|
149
|
+
return true
|
|
120
150
|
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import {UInt8, UInt16, UInt32, UInt64} from '@wharfkit/antelope'
|
|
2
|
+
import {expect, test} from 'bun:test'
|
|
3
|
+
import type {ServerContract} from '../contracts'
|
|
4
|
+
import {encodeStats} from '../derivation/crafting'
|
|
5
|
+
import {ITEM_DEPOT_T1_PACKED, ITEM_LOADER_T1, ITEM_STORAGE_T1} from '../data/item-ids'
|
|
6
|
+
import {JOB_QUEUE_CAP} from '../scheduling/jobs'
|
|
7
|
+
import {HoldKind, TaskType} from '../types'
|
|
8
|
+
import {
|
|
9
|
+
civicShuttleBays,
|
|
10
|
+
civicTransfersAtCap,
|
|
11
|
+
civicTransfersFrom,
|
|
12
|
+
pendingCivicTransfers,
|
|
13
|
+
selectCivicShuttleBay,
|
|
14
|
+
CIVIC_TRANSFER_PER_PLAYER_CAP,
|
|
15
|
+
} from './civic-shuttle'
|
|
16
|
+
|
|
17
|
+
type Lane = ServerContract.Types.lane
|
|
18
|
+
type ModuleEntry = ServerContract.Types.module_entry
|
|
19
|
+
type Task = ServerContract.Types.task
|
|
20
|
+
|
|
21
|
+
const T0 = new Date('2026-01-01T00:00:00.000Z')
|
|
22
|
+
const loaderStats = encodeStats([300, 500])
|
|
23
|
+
const storageStats = encodeStats([300, 300])
|
|
24
|
+
|
|
25
|
+
function makeModuleEntry(itemId: number, stats: bigint): ModuleEntry {
|
|
26
|
+
return {
|
|
27
|
+
type: UInt8.from(0),
|
|
28
|
+
installed: {item_id: UInt16.from(itemId), stats: UInt64.from(stats)},
|
|
29
|
+
} as unknown as ModuleEntry
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// The live Depot layout: two loader bays then four storage.
|
|
33
|
+
function depotModules(): ModuleEntry[] {
|
|
34
|
+
return [
|
|
35
|
+
makeModuleEntry(ITEM_LOADER_T1, loaderStats),
|
|
36
|
+
makeModuleEntry(ITEM_LOADER_T1, loaderStats),
|
|
37
|
+
makeModuleEntry(ITEM_STORAGE_T1, storageStats),
|
|
38
|
+
makeModuleEntry(ITEM_STORAGE_T1, storageStats),
|
|
39
|
+
makeModuleEntry(ITEM_STORAGE_T1, storageStats),
|
|
40
|
+
makeModuleEntry(ITEM_STORAGE_T1, storageStats),
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function shuttleTask(senderId: number, receiverId: number, group?: number): Task {
|
|
45
|
+
return {
|
|
46
|
+
type: UInt8.from(TaskType.SHUTTLE),
|
|
47
|
+
duration: UInt32.from(60),
|
|
48
|
+
cancelable: UInt8.from(2),
|
|
49
|
+
cargo: [],
|
|
50
|
+
couplings: [
|
|
51
|
+
{
|
|
52
|
+
counterpart: {entity_id: UInt64.from(senderId), entity_type: UInt8.from(1)},
|
|
53
|
+
hold: UInt64.from(1),
|
|
54
|
+
kind: UInt8.from(HoldKind.PULL),
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
counterpart: {entity_id: UInt64.from(receiverId), entity_type: UInt8.from(1)},
|
|
58
|
+
hold: UInt64.from(1),
|
|
59
|
+
kind: UInt8.from(HoldKind.PUSH),
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
entitygroup: group === undefined ? undefined : UInt64.from(group),
|
|
63
|
+
} as unknown as Task
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function makeLane(laneKey: number, tasks: Task[]): Lane {
|
|
67
|
+
return {
|
|
68
|
+
lane_key: UInt8.from(laneKey),
|
|
69
|
+
schedule: {started: {toDate: () => T0}, tasks},
|
|
70
|
+
} as unknown as Lane
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
test('civicShuttleBays lists both Depot bays with their queue depth', () => {
|
|
74
|
+
const lanes = [makeLane(1, [shuttleTask(20, 21)])]
|
|
75
|
+
const bays = civicShuttleBays(depotModules(), ITEM_DEPOT_T1_PACKED, lanes)
|
|
76
|
+
expect(bays.map((b) => b.laneKey)).toEqual([1, 2])
|
|
77
|
+
expect(bays.map((b) => b.slotIndex)).toEqual([0, 1])
|
|
78
|
+
expect(bays[0].queued).toBe(1)
|
|
79
|
+
expect(bays[1].queued).toBe(0)
|
|
80
|
+
expect(bays.every((b) => !b.full)).toBe(true)
|
|
81
|
+
expect(bays.every((b) => b.thrust > 0 && b.mass > 0)).toBe(true)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
test('a bay is full at the job queue cap', () => {
|
|
85
|
+
const filled = Array.from({length: JOB_QUEUE_CAP}, () => shuttleTask(20, 21))
|
|
86
|
+
const bays = civicShuttleBays(depotModules(), ITEM_DEPOT_T1_PACKED, [makeLane(1, filled)])
|
|
87
|
+
expect(bays[0].full).toBe(true)
|
|
88
|
+
expect(bays[1].full).toBe(false)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
test('selectCivicShuttleBay takes the first free bay, then the lowest occupied one', () => {
|
|
92
|
+
const modules = depotModules()
|
|
93
|
+
expect(selectCivicShuttleBay(modules, ITEM_DEPOT_T1_PACKED, [])?.laneKey).toBe(1)
|
|
94
|
+
|
|
95
|
+
const firstBusy = [makeLane(1, [shuttleTask(20, 21)])]
|
|
96
|
+
expect(selectCivicShuttleBay(modules, ITEM_DEPOT_T1_PACKED, firstBusy)?.laneKey).toBe(2)
|
|
97
|
+
|
|
98
|
+
const bothBusy = [makeLane(1, [shuttleTask(20, 21)]), makeLane(2, [shuttleTask(22, 23)])]
|
|
99
|
+
expect(selectCivicShuttleBay(modules, ITEM_DEPOT_T1_PACKED, bothBusy)?.laneKey).toBe(1)
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
test('selectCivicShuttleBay returns nothing when the building has no loaders', () => {
|
|
103
|
+
const modules = [makeModuleEntry(ITEM_STORAGE_T1, storageStats)]
|
|
104
|
+
expect(selectCivicShuttleBay(modules, ITEM_DEPOT_T1_PACKED, [])).toBeUndefined()
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
test('pendingCivicTransfers reports the bay, both counterparties, and the tail', () => {
|
|
108
|
+
const lanes = [
|
|
109
|
+
makeLane(1, [shuttleTask(20, 21), shuttleTask(22, 23)]),
|
|
110
|
+
makeLane(2, [shuttleTask(24, 25)]),
|
|
111
|
+
]
|
|
112
|
+
const transfers = pendingCivicTransfers({lanes} as never)
|
|
113
|
+
expect(transfers.length).toBe(3)
|
|
114
|
+
|
|
115
|
+
const first = transfers.find((t) => String(t.senderId) === '20')!
|
|
116
|
+
expect(first.laneKey).toBe(1)
|
|
117
|
+
expect(String(first.receiverId)).toBe('21')
|
|
118
|
+
expect(first.isTail).toBe(false)
|
|
119
|
+
expect(first.callable).toBe(false)
|
|
120
|
+
expect(first.startsAt).toEqual(T0)
|
|
121
|
+
expect(first.completesAt).toEqual(new Date(T0.getTime() + 60_000))
|
|
122
|
+
|
|
123
|
+
const second = transfers.find((t) => String(t.senderId) === '22')!
|
|
124
|
+
expect(second.isTail).toBe(true)
|
|
125
|
+
expect(second.callable).toBe(true)
|
|
126
|
+
|
|
127
|
+
const onBayTwo = transfers.find((t) => String(t.senderId) === '24')!
|
|
128
|
+
expect(onBayTwo.laneKey).toBe(2)
|
|
129
|
+
expect(onBayTwo.callable).toBe(true)
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
test('a grouped tail transfer is not callable', () => {
|
|
133
|
+
const lanes = [makeLane(1, [shuttleTask(20, 21, 7)])]
|
|
134
|
+
const [transfer] = pendingCivicTransfers({lanes} as never)
|
|
135
|
+
expect(transfer.isTail).toBe(true)
|
|
136
|
+
expect(transfer.callable).toBe(false)
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
test('civicTransfersFrom counts only the ships a player sends from', () => {
|
|
140
|
+
const lanes = [
|
|
141
|
+
makeLane(1, [shuttleTask(20, 21), shuttleTask(30, 31)]),
|
|
142
|
+
makeLane(2, [shuttleTask(22, 23)]),
|
|
143
|
+
]
|
|
144
|
+
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)
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
test('civicTransfersAtCap trips at the per-player cap', () => {
|
|
151
|
+
const mine = Array.from({length: CIVIC_TRANSFER_PER_PLAYER_CAP}, (_, i) =>
|
|
152
|
+
shuttleTask(20 + i, 50)
|
|
153
|
+
)
|
|
154
|
+
const senderIds = mine.map((_, i) => 20 + i)
|
|
155
|
+
const under = {lanes: [makeLane(1, mine.slice(0, -1))]} as never
|
|
156
|
+
const at = {lanes: [makeLane(1, mine)]} as never
|
|
157
|
+
expect(civicTransfersAtCap(under, senderIds)).toBe(false)
|
|
158
|
+
expect(civicTransfersAtCap(at, senderIds)).toBe(true)
|
|
159
|
+
})
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type {UInt64} from '@wharfkit/antelope'
|
|
2
|
+
import type {ServerContract} from '../contracts'
|
|
3
|
+
import {getItem} from '../data/catalog'
|
|
4
|
+
import {JOB_QUEUE_CAP} from '../scheduling/jobs'
|
|
5
|
+
import {laneKeyForModule, resolveLaneLoader} from '../scheduling/lanes'
|
|
6
|
+
import {orderedTasks, type ScheduleData} from '../scheduling/schedule'
|
|
7
|
+
import {HoldKind, TaskType} from '../types'
|
|
8
|
+
|
|
9
|
+
type CargoItem = ServerContract.Types.cargo_item
|
|
10
|
+
type Lane = ServerContract.Types.lane
|
|
11
|
+
type ModuleEntry = ServerContract.Types.module_entry
|
|
12
|
+
|
|
13
|
+
export const CIVIC_TRANSFER_PER_PLAYER_CAP = 4
|
|
14
|
+
|
|
15
|
+
export interface CivicShuttleBay {
|
|
16
|
+
laneKey: number
|
|
17
|
+
slotIndex: number
|
|
18
|
+
thrust: number
|
|
19
|
+
mass: number
|
|
20
|
+
outputPct: number
|
|
21
|
+
queued: number
|
|
22
|
+
full: boolean
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function civicShuttleBays(
|
|
26
|
+
modules: ModuleEntry[],
|
|
27
|
+
entityItemId: number,
|
|
28
|
+
lanes: Lane[]
|
|
29
|
+
): CivicShuttleBay[] {
|
|
30
|
+
const bays: CivicShuttleBay[] = []
|
|
31
|
+
for (let slotIndex = 0; slotIndex < modules.length; slotIndex++) {
|
|
32
|
+
const installed = modules[slotIndex].installed
|
|
33
|
+
if (!installed) continue
|
|
34
|
+
if (getItem(installed.item_id).moduleType !== 'loader') continue
|
|
35
|
+
const laneKey = laneKeyForModule(slotIndex)
|
|
36
|
+
const loader = resolveLaneLoader(modules, entityItemId, laneKey)
|
|
37
|
+
const lane = lanes.find((l) => l.lane_key.toNumber() === laneKey)
|
|
38
|
+
const queued = lane ? lane.schedule.tasks.length : 0
|
|
39
|
+
bays.push({
|
|
40
|
+
laneKey,
|
|
41
|
+
slotIndex,
|
|
42
|
+
thrust: loader.thrust,
|
|
43
|
+
mass: loader.mass,
|
|
44
|
+
outputPct: loader.outputPct,
|
|
45
|
+
queued,
|
|
46
|
+
full: queued >= JOB_QUEUE_CAP,
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
return bays
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Mirrors worker_lane_key_or_mobility for loaders: first free bay, else the lowest bay.
|
|
53
|
+
export function selectCivicShuttleBay(
|
|
54
|
+
modules: ModuleEntry[],
|
|
55
|
+
entityItemId: number,
|
|
56
|
+
lanes: Lane[]
|
|
57
|
+
): CivicShuttleBay | undefined {
|
|
58
|
+
const bays = civicShuttleBays(modules, entityItemId, lanes)
|
|
59
|
+
return bays.find((bay) => bay.queued === 0) ?? bays[0]
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface PendingCivicTransfer {
|
|
63
|
+
laneKey: number
|
|
64
|
+
taskIndex: number
|
|
65
|
+
senderId: UInt64
|
|
66
|
+
receiverId: UInt64
|
|
67
|
+
cargo: CargoItem[]
|
|
68
|
+
startsAt: Date
|
|
69
|
+
completesAt: Date
|
|
70
|
+
isTail: boolean
|
|
71
|
+
callable: boolean
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function pendingCivicTransfers(entity: ScheduleData): PendingCivicTransfer[] {
|
|
75
|
+
const tailIndex = new Map<number, number>()
|
|
76
|
+
for (const l of entity.lanes ?? []) {
|
|
77
|
+
tailIndex.set(l.lane_key.toNumber(), l.schedule.tasks.length - 1)
|
|
78
|
+
}
|
|
79
|
+
const out: PendingCivicTransfer[] = []
|
|
80
|
+
for (const entry of orderedTasks(entity)) {
|
|
81
|
+
if (entry.task.type.toNumber() !== TaskType.SHUTTLE) continue
|
|
82
|
+
const pull = entry.task.couplings.find((c) => c.kind.toNumber() === HoldKind.PULL)
|
|
83
|
+
const push = entry.task.couplings.find((c) => c.kind.toNumber() === HoldKind.PUSH)
|
|
84
|
+
if (!pull || !push) continue
|
|
85
|
+
const isTail = tailIndex.get(entry.laneKey) === entry.taskIndex
|
|
86
|
+
out.push({
|
|
87
|
+
laneKey: entry.laneKey,
|
|
88
|
+
taskIndex: entry.taskIndex,
|
|
89
|
+
senderId: pull.counterpart.entity_id,
|
|
90
|
+
receiverId: push.counterpart.entity_id,
|
|
91
|
+
cargo: entry.task.cargo,
|
|
92
|
+
startsAt: entry.startsAt,
|
|
93
|
+
completesAt: entry.completesAt,
|
|
94
|
+
isTail,
|
|
95
|
+
callable: isTail && !entry.task.entitygroup,
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
return out
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Mirrors civic_transfers_for: the sender side of every shuttle the building holds, by owner.
|
|
102
|
+
export function civicTransfersFrom(
|
|
103
|
+
entity: ScheduleData,
|
|
104
|
+
senderIds: Array<UInt64 | string | number>
|
|
105
|
+
): PendingCivicTransfer[] {
|
|
106
|
+
const owned = new Set(senderIds.map((id) => String(id)))
|
|
107
|
+
return pendingCivicTransfers(entity).filter((t) => owned.has(String(t.senderId)))
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function civicTransfersAtCap(
|
|
111
|
+
entity: ScheduleData,
|
|
112
|
+
senderIds: Array<UInt64 | string | number>
|
|
113
|
+
): boolean {
|
|
114
|
+
return civicTransfersFrom(entity, senderIds).length >= CIVIC_TRANSFER_PER_PLAYER_CAP
|
|
115
|
+
}
|