@shipload/sdk 1.0.0-next.7 → 1.0.0-next.70
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/scan.d.ts +52 -0
- package/lib/scan.js +162 -0
- package/lib/scan.js.map +1 -0
- package/lib/scan.m.js +152 -0
- package/lib/scan.m.js.map +1 -0
- package/lib/shipload.d.ts +5696 -1503
- package/lib/shipload.js +20294 -5200
- package/lib/shipload.js.map +1 -1
- package/lib/shipload.m.js +20371 -5683
- package/lib/shipload.m.js.map +1 -1
- package/lib/testing.d.ts +1648 -0
- package/lib/testing.js +6980 -0
- package/lib/testing.js.map +1 -0
- package/lib/testing.m.js +6974 -0
- package/lib/testing.m.js.map +1 -0
- package/package.json +20 -2
- package/src/capabilities/craftable.test.ts +82 -0
- package/src/capabilities/craftable.ts +82 -0
- package/src/capabilities/crafting.test.ts +80 -0
- package/src/capabilities/crafting.ts +50 -8
- package/src/capabilities/gathering.test.ts +29 -0
- package/src/capabilities/gathering.ts +38 -20
- package/src/capabilities/hauling.ts +0 -5
- package/src/capabilities/index.ts +0 -1
- package/src/capabilities/modules.ts +27 -30
- package/src/capabilities/movement.ts +1 -1
- package/src/capabilities/storage.test.ts +26 -0
- package/src/capabilities/storage.ts +22 -7
- package/src/contracts/fund.ts +306 -0
- package/src/contracts/platform.ts +312 -3
- package/src/contracts/server.ts +2661 -466
- package/src/contracts/token.ts +170 -0
- package/src/contracts.ts +2 -0
- package/src/coordinates/address.ts +88 -0
- package/src/coordinates/constants.test.ts +15 -0
- package/src/coordinates/constants.ts +23 -0
- package/src/coordinates/index.ts +15 -0
- package/src/coordinates/memo.test.ts +47 -0
- package/src/coordinates/memo.ts +20 -0
- package/src/coordinates/permutation.ts +77 -0
- package/src/coordinates/regions.ts +48 -0
- package/src/coordinates/sectors.ts +115 -0
- package/src/data/capabilities.ts +49 -18
- package/src/data/capability-formulas.ts +26 -14
- package/src/data/catalog.test.ts +19 -0
- package/src/data/catalog.ts +13 -3
- package/src/data/charters.json +396 -0
- package/src/data/citizenry-adjectives.json +42 -0
- package/src/data/citizenry-nouns.json +50 -0
- package/src/data/colors.ts +14 -32
- package/src/data/entities.json +421 -15
- package/src/data/item-ids.ts +53 -13
- package/src/data/items.json +330 -77
- package/src/data/kind-registry.json +212 -0
- package/src/data/kind-registry.ts +192 -0
- package/src/data/metadata.ts +312 -51
- package/src/data/recipes-runtime.ts +6 -23
- package/src/data/recipes.json +2173 -173
- package/src/data/tiers.ts +9 -6
- package/src/derivation/build-methods.test.ts +71 -0
- package/src/derivation/build-methods.ts +50 -0
- package/src/derivation/capabilities.test.ts +348 -0
- package/src/derivation/capabilities.ts +707 -0
- package/src/derivation/capability-mappings.ts +60 -16
- package/src/derivation/crafting.test.ts +17 -0
- package/src/derivation/crafting.ts +57 -34
- package/src/derivation/index.ts +27 -2
- package/src/derivation/recipe-usage.test.ts +88 -0
- package/src/derivation/recipe-usage.ts +141 -0
- package/src/derivation/reserve-regen.ts +34 -0
- package/src/derivation/resources.ts +9 -1
- package/src/derivation/rollups.test.ts +55 -0
- package/src/derivation/rollups.ts +72 -0
- package/src/derivation/stars.test.ts +67 -0
- package/src/derivation/stars.ts +25 -0
- package/src/derivation/stat-scaling.ts +12 -0
- package/src/derivation/stats.ts +6 -6
- package/src/derivation/strata.ts +5 -3
- package/src/derivation/stratum.ts +26 -22
- package/src/derivation/tiers.ts +40 -7
- package/src/derivation/upgrades.ts +14 -0
- package/src/derivation/wormhole.ts +154 -0
- package/src/entities/entity.ts +102 -0
- package/src/entities/gamestate.ts +3 -28
- package/src/entities/makers.ts +144 -136
- package/src/entities/slot-multiplier.ts +43 -0
- package/src/entity/hydrate.test.ts +156 -0
- package/src/entity/hydrate.ts +86 -0
- package/src/errors.ts +21 -16
- package/src/format.ts +42 -9
- package/src/index-module.ts +411 -32
- package/src/influence/ballot.test.ts +103 -0
- package/src/influence/ballot.ts +98 -0
- package/src/influence/categories.ts +32 -0
- package/src/influence/charter-metadata.test.ts +136 -0
- package/src/influence/charter-metadata.ts +236 -0
- package/src/influence/charters.test.ts +112 -0
- package/src/influence/charters.ts +150 -0
- package/src/influence/citizenry.ts +38 -0
- package/src/influence/civic-shuttle.test.ts +202 -0
- package/src/influence/civic-shuttle.ts +142 -0
- package/src/influence/civic.test.ts +287 -0
- package/src/influence/civic.ts +269 -0
- package/src/influence/constants.ts +84 -0
- package/src/influence/decay.ts +20 -0
- package/src/influence/decomp.ts +95 -0
- package/src/influence/demand.ts +106 -0
- package/src/influence/duration.ts +101 -0
- package/src/influence/index.ts +118 -0
- package/src/influence/influence.test.ts +450 -0
- package/src/influence/quality.ts +25 -0
- package/src/influence/valuation.ts +154 -0
- package/src/managers/actions.ts +940 -93
- package/src/managers/balances.test.ts +161 -0
- package/src/managers/balances.ts +93 -0
- package/src/managers/base.ts +10 -2
- package/src/managers/cluster.test.ts +39 -0
- package/src/managers/cluster.ts +53 -0
- package/src/managers/construction-types.ts +80 -0
- package/src/managers/construction.ts +453 -0
- package/src/managers/context.ts +58 -1
- package/src/managers/coordinates.ts +14 -0
- package/src/managers/entities.ts +35 -63
- package/src/managers/epochs.ts +72 -1
- package/src/managers/index.ts +38 -1
- package/src/managers/influence.test.ts +56 -0
- package/src/managers/influence.ts +425 -0
- package/src/managers/jobs.test.ts +80 -0
- package/src/managers/jobs.ts +54 -0
- package/src/managers/locations.ts +25 -29
- package/src/managers/nft.test.ts +21 -0
- package/src/managers/nft.ts +110 -0
- package/src/managers/players.ts +25 -0
- package/src/managers/plot.ts +122 -0
- package/src/nft/atomicassets.abi.json +1342 -0
- package/src/nft/atomicassets.ts +237 -0
- package/src/nft/atomicdata.ts +130 -0
- package/src/nft/buildImmutableData.ts +388 -0
- package/src/nft/description.ts +222 -100
- package/src/nft/index.ts +3 -0
- package/src/planner/index.ts +296 -0
- package/src/planner/planner.test.ts +459 -0
- package/src/receipts.ts +22 -0
- package/src/resolution/describe-module.ts +43 -25
- package/src/resolution/display-name.ts +39 -11
- package/src/resolution/resolve-item.test.ts +48 -0
- package/src/resolution/resolve-item.ts +112 -58
- package/src/scan/index.ts +244 -0
- package/src/scan/scan-wasm.base64.ts +2 -0
- package/src/scheduling/accessor.ts +65 -23
- package/src/scheduling/availability.test.ts +320 -0
- package/src/scheduling/availability.ts +251 -0
- package/src/scheduling/cancel.test.ts +550 -0
- package/src/scheduling/cancel.ts +254 -0
- package/src/scheduling/cluster-stock.test.ts +31 -0
- package/src/scheduling/cluster-stock.ts +15 -0
- package/src/scheduling/energy.ts +47 -0
- package/src/scheduling/epoch.ts +1 -0
- package/src/scheduling/idle-resolve.ts +45 -0
- package/src/scheduling/jobs.test.ts +49 -0
- package/src/scheduling/jobs.ts +137 -0
- package/src/scheduling/lane-core.ts +128 -0
- package/src/scheduling/lanes.test.ts +250 -0
- package/src/scheduling/lanes.ts +227 -0
- package/src/scheduling/projection.ts +265 -149
- package/src/scheduling/schedule.test.ts +163 -0
- package/src/scheduling/schedule.ts +282 -116
- package/src/scheduling/task-cargo.test.ts +110 -0
- package/src/scheduling/task-cargo.ts +62 -0
- package/src/scheduling/task-effects.test.ts +35 -0
- package/src/scheduling/task-effects.ts +34 -0
- package/src/scheduling/unwrap.test.ts +86 -0
- package/src/scheduling/unwrap.ts +190 -0
- package/src/shipload.ts +63 -4
- package/src/subscriptions/manager.cluster.test.ts +51 -0
- package/src/subscriptions/manager.ts +245 -172
- package/src/subscriptions/mappers.ts +5 -8
- package/src/subscriptions/types.ts +28 -3
- package/src/testing/catalog-hash.ts +19 -0
- package/src/testing/index.ts +2 -0
- package/src/testing/projection-parity.ts +167 -0
- package/src/travel/reach.test.ts +86 -0
- package/src/travel/reach.ts +34 -0
- package/src/travel/route-build.test.ts +63 -0
- package/src/travel/route-build.ts +60 -0
- package/src/travel/route-planner.ts +288 -0
- package/src/travel/route-simulator.ts +170 -0
- package/src/travel/travel.ts +190 -123
- package/src/types/capabilities.ts +37 -8
- package/src/types/entity.ts +3 -3
- package/src/types/index.ts +0 -1
- package/src/types.ts +81 -23
- package/src/utils/cargo.test.ts +14 -0
- package/src/utils/cargo.ts +2 -0
- package/src/utils/display-name.ts +70 -0
- package/src/utils/hash.ts +22 -2
- package/src/utils/system.ts +24 -5
- package/src/capabilities/loading.ts +0 -8
- package/src/entities/container.ts +0 -123
- package/src/entities/ship-deploy.ts +0 -295
- package/src/entities/ship.ts +0 -221
- package/src/entities/warehouse.ts +0 -127
- package/src/types/entity-traits.ts +0 -69
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import {describe, expect, test} from 'bun:test'
|
|
2
|
+
import {
|
|
3
|
+
CHARTER_NONE,
|
|
4
|
+
CIVIC_DEPOT,
|
|
5
|
+
CIVIC_DOCK,
|
|
6
|
+
CIVIC_STAT_CRAFT_SPEED,
|
|
7
|
+
CIVIC_STAT_TRANSFER_SPEED,
|
|
8
|
+
CIVIC_WORKSHOP,
|
|
9
|
+
} from './constants'
|
|
10
|
+
import {
|
|
11
|
+
charterEligible,
|
|
12
|
+
charterIneligible,
|
|
13
|
+
charterNode,
|
|
14
|
+
charterBuildingEntity,
|
|
15
|
+
charterGateNodeFor,
|
|
16
|
+
charterRungValue,
|
|
17
|
+
charterSingletonMandate,
|
|
18
|
+
charterModuleCount,
|
|
19
|
+
eligibleCharters,
|
|
20
|
+
type BuiltCharter,
|
|
21
|
+
type CharterWorld,
|
|
22
|
+
type WorldBuilding,
|
|
23
|
+
} from './charters'
|
|
24
|
+
|
|
25
|
+
const WORKSHOP = 10001
|
|
26
|
+
const NEXUS = 20001
|
|
27
|
+
const DOCK = 30100001
|
|
28
|
+
const WORKSHOP_TUNEUP = 10100201
|
|
29
|
+
const DOCK_TUNEUP = 30100301
|
|
30
|
+
const DEPOT = 40100001
|
|
31
|
+
|
|
32
|
+
const WORKSHOP_ENTITY = 100n
|
|
33
|
+
const DOCK_ENTITY = 300n
|
|
34
|
+
|
|
35
|
+
function world(built: BuiltCharter[]): CharterWorld {
|
|
36
|
+
return {built}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function node(nodeId: number) {
|
|
40
|
+
const found = charterNode(nodeId)
|
|
41
|
+
if (!found) throw new Error(`registry is missing node ${nodeId}`)
|
|
42
|
+
return found
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function eligibleIds(w: CharterWorld): number[] {
|
|
46
|
+
return eligibleCharters(w).map((n) => n.nodeId)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
describe('charter eligibility mirror', () => {
|
|
50
|
+
test('an unbuilt world offers only the root', () => {
|
|
51
|
+
expect(eligibleIds(world([]))).toEqual([WORKSHOP])
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test('the root is the mandate by the singleton rule with no ballot', () => {
|
|
55
|
+
expect(charterSingletonMandate(world([]))).toBe(WORKSHOP)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test('completing the root opens the fork and closes the singleton path', () => {
|
|
59
|
+
const built = world([{nodeId: WORKSHOP, repeats: 0}])
|
|
60
|
+
expect(eligibleIds(built)).toEqual([WORKSHOP_TUNEUP, NEXUS, DOCK, DEPOT])
|
|
61
|
+
expect(charterSingletonMandate(built)).toBe(CHARTER_NONE)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
test('a completed charter reports already-taken', () => {
|
|
65
|
+
const built = world([{nodeId: WORKSHOP, repeats: 0}])
|
|
66
|
+
expect(charterIneligible(built, node(WORKSHOP))).toBe('already-taken')
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test('an unmet prereq reports prereq-missing', () => {
|
|
70
|
+
expect(charterIneligible(world([]), node(NEXUS))).toBe('prereq-missing')
|
|
71
|
+
expect(charterIneligible(world([]), node(DOCK_TUNEUP))).toBe('prereq-missing')
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test('a rung is eligible as soon as its prereq is complete', () => {
|
|
75
|
+
const built = world([{nodeId: WORKSHOP, repeats: 0}])
|
|
76
|
+
expect(charterEligible(built, node(WORKSHOP_TUNEUP))).toBe(true)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
test('a level gate is the node that creates its building', () => {
|
|
80
|
+
expect(charterGateNodeFor(CIVIC_WORKSHOP)?.nodeId).toBe(WORKSHOP)
|
|
81
|
+
expect(charterGateNodeFor(CIVIC_DOCK)?.nodeId).toBe(DOCK)
|
|
82
|
+
expect(charterGateNodeFor(CIVIC_DEPOT)?.nodeId).toBe(DEPOT)
|
|
83
|
+
expect(charterGateNodeFor(99)).toBeUndefined()
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
test('a building resolves to the entity recorded for its kind', () => {
|
|
87
|
+
const buildings: WorldBuilding[] = [
|
|
88
|
+
{entityId: WORKSHOP_ENTITY, building: CIVIC_WORKSHOP},
|
|
89
|
+
{entityId: DOCK_ENTITY, building: CIVIC_DOCK},
|
|
90
|
+
]
|
|
91
|
+
expect(charterBuildingEntity(buildings, CIVIC_WORKSHOP)).toBe(WORKSHOP_ENTITY)
|
|
92
|
+
expect(charterBuildingEntity(buildings, CIVIC_DEPOT)).toBe(0n)
|
|
93
|
+
expect(charterBuildingEntity([], CIVIC_DEPOT)).toBe(0n)
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
test('rung values sum over the completed nodes only', () => {
|
|
97
|
+
const gateOnly = world([{nodeId: WORKSHOP, repeats: 0}])
|
|
98
|
+
expect(charterRungValue(gateOnly, CIVIC_WORKSHOP, CIVIC_STAT_CRAFT_SPEED)).toBe(200)
|
|
99
|
+
|
|
100
|
+
const tuned = world([
|
|
101
|
+
{nodeId: WORKSHOP, repeats: 0},
|
|
102
|
+
{nodeId: WORKSHOP_TUNEUP, repeats: 0},
|
|
103
|
+
])
|
|
104
|
+
expect(charterRungValue(tuned, CIVIC_WORKSHOP, CIVIC_STAT_CRAFT_SPEED)).toBe(250)
|
|
105
|
+
expect(charterRungValue(tuned, CIVIC_DEPOT, CIVIC_STAT_TRANSFER_SPEED)).toBe(0)
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
test('modules sum over the completed nodes only', () => {
|
|
109
|
+
expect(charterModuleCount(world([]), CIVIC_WORKSHOP)).toBe(0)
|
|
110
|
+
expect(charterModuleCount(world([{nodeId: WORKSHOP, repeats: 0}]), CIVIC_WORKSHOP)).toBe(5)
|
|
111
|
+
})
|
|
112
|
+
})
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import chartersJson from '../data/charters.json'
|
|
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 {
|
|
10
|
+
kind: number
|
|
11
|
+
building: number
|
|
12
|
+
stat: number
|
|
13
|
+
value: number
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface CharterNode {
|
|
17
|
+
nodeId: number
|
|
18
|
+
cost: bigint
|
|
19
|
+
prereqs: number[]
|
|
20
|
+
grants: CharterGrant[]
|
|
21
|
+
exclusionGroup: number
|
|
22
|
+
repeatCap: number
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const CHARTER_REGISTRY: CharterNode[] = chartersJson.nodes.map((node) => ({
|
|
26
|
+
nodeId: node.nodeId,
|
|
27
|
+
cost: BigInt(node.cost),
|
|
28
|
+
prereqs: [...node.prereqs],
|
|
29
|
+
grants: node.grants.map((grant) => ({...grant})),
|
|
30
|
+
exclusionGroup: node.exclusionGroup,
|
|
31
|
+
repeatCap: node.repeatCap,
|
|
32
|
+
}))
|
|
33
|
+
|
|
34
|
+
export function charterNode(nodeId: number): CharterNode | undefined {
|
|
35
|
+
if (nodeId === CHARTER_NONE) return undefined
|
|
36
|
+
return CHARTER_REGISTRY.find((n) => n.nodeId === nodeId)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type CharterIneligibility = 'already-taken' | 'prereq-missing'
|
|
40
|
+
|
|
41
|
+
export const CHARTER_INELIGIBILITY_MESSAGES: Record<CharterIneligibility, string> = {
|
|
42
|
+
'already-taken': 'charter is already completed at this world',
|
|
43
|
+
'prereq-missing': 'charter prerequisites are not completed',
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface BuiltCharter {
|
|
47
|
+
nodeId: number
|
|
48
|
+
repeats: number
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface CharterWorld {
|
|
52
|
+
built: BuiltCharter[]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface WorldBuilding {
|
|
56
|
+
entityId: bigint
|
|
57
|
+
building: number
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function builtCharter(world: CharterWorld, nodeId: number): BuiltCharter | undefined {
|
|
61
|
+
return world.built.find((row) => row.nodeId === nodeId)
|
|
62
|
+
}
|
|
63
|
+
|
|
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
|
+
)
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
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
|
|
91
|
+
}
|
|
92
|
+
|
|
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
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function charterPrereqsMet(world: CharterWorld, node: CharterNode): boolean {
|
|
107
|
+
return node.prereqs.every(
|
|
108
|
+
(prereq) => prereq === CHARTER_NONE || builtCharter(world, prereq) !== undefined
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function charterIneligible(
|
|
113
|
+
world: CharterWorld,
|
|
114
|
+
node: CharterNode
|
|
115
|
+
): CharterIneligibility | undefined {
|
|
116
|
+
if (builtCharter(world, node.nodeId) !== undefined) return 'already-taken'
|
|
117
|
+
if (!charterPrereqsMet(world, node)) return 'prereq-missing'
|
|
118
|
+
return undefined
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function charterEligible(world: CharterWorld, node: CharterNode): boolean {
|
|
122
|
+
return charterIneligible(world, node) === undefined
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function eligibleCharters(world: CharterWorld): CharterNode[] {
|
|
126
|
+
return CHARTER_REGISTRY.filter((node) => charterEligible(world, node))
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function charterSingletonMandate(world: CharterWorld): number {
|
|
130
|
+
let only = CHARTER_NONE
|
|
131
|
+
for (const node of CHARTER_REGISTRY) {
|
|
132
|
+
if (!charterEligible(world, node)) continue
|
|
133
|
+
if (only !== CHARTER_NONE) return CHARTER_NONE
|
|
134
|
+
only = node.nodeId
|
|
135
|
+
}
|
|
136
|
+
return only
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function charterEligibleChained(
|
|
140
|
+
world: CharterWorld,
|
|
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
|
|
150
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import {Checksum256, type Checksum256Type} from '@wharfkit/antelope'
|
|
2
|
+
import {Coordinates, type CoordinatesType, LocationType} from '../types'
|
|
3
|
+
import {hash512, uint16} from '../utils/hash'
|
|
4
|
+
import {deriveLocationStatic, getSystemName} from '../utils/system'
|
|
5
|
+
import citizenryAdjectives from '../data/citizenry-adjectives.json'
|
|
6
|
+
import citizenryNouns from '../data/citizenry-nouns.json'
|
|
7
|
+
|
|
8
|
+
const CITIZENRY_FORMS: ((world: string, noun: string, adjective: string) => string)[] = [
|
|
9
|
+
(w, n) => `${w} ${n}`,
|
|
10
|
+
(w, n, a) => `${a} ${w} ${n}`,
|
|
11
|
+
(w, n, a) => `${a} ${n} of ${w}`,
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
const FORMS_WITH_ADJECTIVE = 2
|
|
15
|
+
|
|
16
|
+
export function citizenryName(
|
|
17
|
+
gameSeed: Checksum256Type,
|
|
18
|
+
coordinates: CoordinatesType
|
|
19
|
+
): string | undefined {
|
|
20
|
+
const coords = Coordinates.from(coordinates)
|
|
21
|
+
const location = deriveLocationStatic(gameSeed, coords)
|
|
22
|
+
if (Number(location.type) !== LocationType.PLANET) return undefined
|
|
23
|
+
|
|
24
|
+
const world = getSystemName(gameSeed, coords)
|
|
25
|
+
const roll = hash512(
|
|
26
|
+
Checksum256.from(gameSeed),
|
|
27
|
+
`citizenry-${coords.x.toString()}-${coords.y.toString()}`
|
|
28
|
+
)
|
|
29
|
+
const form = CITIZENRY_FORMS[roll.array[0] % CITIZENRY_FORMS.length]
|
|
30
|
+
const noun = citizenryNouns[uint16(roll, 1) % citizenryNouns.length]
|
|
31
|
+
const adjective = citizenryAdjectives[uint16(roll, 3) % citizenryAdjectives.length]
|
|
32
|
+
return form(world, noun, adjective)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function citizenryPatternCount(): number {
|
|
36
|
+
const plainForms = CITIZENRY_FORMS.length - FORMS_WITH_ADJECTIVE
|
|
37
|
+
return citizenryNouns.length * (plainForms + FORMS_WITH_ADJECTIVE * citizenryAdjectives.length)
|
|
38
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
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, T0)
|
|
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)], T0)
|
|
87
|
+
expect(bays[0].full).toBe(true)
|
|
88
|
+
expect(bays[1].full).toBe(false)
|
|
89
|
+
})
|
|
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
|
+
|
|
134
|
+
test('selectCivicShuttleBay takes the first free bay, then the lowest occupied one', () => {
|
|
135
|
+
const modules = depotModules()
|
|
136
|
+
expect(selectCivicShuttleBay(modules, ITEM_DEPOT_T1_PACKED, [], T0)?.laneKey).toBe(1)
|
|
137
|
+
|
|
138
|
+
const firstBusy = [makeLane(1, [shuttleTask(20, 21)])]
|
|
139
|
+
expect(selectCivicShuttleBay(modules, ITEM_DEPOT_T1_PACKED, firstBusy, T0)?.laneKey).toBe(2)
|
|
140
|
+
|
|
141
|
+
const bothBusy = [makeLane(1, [shuttleTask(20, 21)]), makeLane(2, [shuttleTask(22, 23)])]
|
|
142
|
+
expect(selectCivicShuttleBay(modules, ITEM_DEPOT_T1_PACKED, bothBusy, T0)?.laneKey).toBe(1)
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
test('selectCivicShuttleBay returns nothing when the building has no loaders', () => {
|
|
146
|
+
const modules = [makeModuleEntry(ITEM_STORAGE_T1, storageStats)]
|
|
147
|
+
expect(selectCivicShuttleBay(modules, ITEM_DEPOT_T1_PACKED, [], T0)).toBeUndefined()
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
test('pendingCivicTransfers reports the bay, both counterparties, and the tail', () => {
|
|
151
|
+
const lanes = [
|
|
152
|
+
makeLane(1, [shuttleTask(20, 21), shuttleTask(22, 23)]),
|
|
153
|
+
makeLane(2, [shuttleTask(24, 25)]),
|
|
154
|
+
]
|
|
155
|
+
const transfers = pendingCivicTransfers({lanes} as never, T0)
|
|
156
|
+
expect(transfers.length).toBe(3)
|
|
157
|
+
|
|
158
|
+
const first = transfers.find((t) => String(t.senderId) === '20')!
|
|
159
|
+
expect(first.laneKey).toBe(1)
|
|
160
|
+
expect(String(first.receiverId)).toBe('21')
|
|
161
|
+
expect(first.isTail).toBe(false)
|
|
162
|
+
expect(first.callable).toBe(false)
|
|
163
|
+
expect(first.startsAt).toEqual(T0)
|
|
164
|
+
expect(first.completesAt).toEqual(new Date(T0.getTime() + 60_000))
|
|
165
|
+
|
|
166
|
+
const second = transfers.find((t) => String(t.senderId) === '22')!
|
|
167
|
+
expect(second.isTail).toBe(true)
|
|
168
|
+
expect(second.callable).toBe(true)
|
|
169
|
+
|
|
170
|
+
const onBayTwo = transfers.find((t) => String(t.senderId) === '24')!
|
|
171
|
+
expect(onBayTwo.laneKey).toBe(2)
|
|
172
|
+
expect(onBayTwo.callable).toBe(true)
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
test('a grouped tail transfer is not callable', () => {
|
|
176
|
+
const lanes = [makeLane(1, [shuttleTask(20, 21, 7)])]
|
|
177
|
+
const [transfer] = pendingCivicTransfers({lanes} as never, T0)
|
|
178
|
+
expect(transfer.isTail).toBe(true)
|
|
179
|
+
expect(transfer.callable).toBe(false)
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
test('civicTransfersFrom counts only the ships a player sends from', () => {
|
|
183
|
+
const lanes = [
|
|
184
|
+
makeLane(1, [shuttleTask(20, 21), shuttleTask(30, 31)]),
|
|
185
|
+
makeLane(2, [shuttleTask(22, 23)]),
|
|
186
|
+
]
|
|
187
|
+
const entity = {lanes} as never
|
|
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)
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
test('civicTransfersAtCap trips at the per-player cap', () => {
|
|
194
|
+
const mine = Array.from({length: CIVIC_TRANSFER_PER_PLAYER_CAP}, (_, i) =>
|
|
195
|
+
shuttleTask(20 + i, 50)
|
|
196
|
+
)
|
|
197
|
+
const senderIds = mine.map((_, i) => 20 + i)
|
|
198
|
+
const under = {lanes: [makeLane(1, mine.slice(0, -1))]} as never
|
|
199
|
+
const at = {lanes: [makeLane(1, mine)]} as never
|
|
200
|
+
expect(civicTransfersAtCap(under, senderIds, T0)).toBe(false)
|
|
201
|
+
expect(civicTransfersAtCap(at, senderIds, T0)).toBe(true)
|
|
202
|
+
})
|
|
@@ -0,0 +1,142 @@
|
|
|
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
|
+
// 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
|
+
|
|
39
|
+
export function civicShuttleBays(
|
|
40
|
+
modules: ModuleEntry[],
|
|
41
|
+
entityItemId: number,
|
|
42
|
+
lanes: Lane[],
|
|
43
|
+
now: Date = new Date()
|
|
44
|
+
): CivicShuttleBay[] {
|
|
45
|
+
const bays: CivicShuttleBay[] = []
|
|
46
|
+
for (let slotIndex = 0; slotIndex < modules.length; slotIndex++) {
|
|
47
|
+
const installed = modules[slotIndex].installed
|
|
48
|
+
if (!installed) continue
|
|
49
|
+
if (getItem(installed.item_id).moduleType !== 'loader') continue
|
|
50
|
+
const laneKey = laneKeyForModule(slotIndex)
|
|
51
|
+
const loader = resolveLaneLoader(modules, entityItemId, laneKey)
|
|
52
|
+
const lane = lanes.find((l) => l.lane_key.toNumber() === laneKey)
|
|
53
|
+
const queued = unfinishedTasks(lane, now)
|
|
54
|
+
bays.push({
|
|
55
|
+
laneKey,
|
|
56
|
+
slotIndex,
|
|
57
|
+
thrust: loader.thrust,
|
|
58
|
+
mass: loader.mass,
|
|
59
|
+
outputPct: loader.outputPct,
|
|
60
|
+
queued,
|
|
61
|
+
full: queued >= JOB_QUEUE_CAP,
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
return bays
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Mirrors worker_lane_key_or_mobility for loaders: first free bay, else the lowest bay.
|
|
68
|
+
export function selectCivicShuttleBay(
|
|
69
|
+
modules: ModuleEntry[],
|
|
70
|
+
entityItemId: number,
|
|
71
|
+
lanes: Lane[],
|
|
72
|
+
now: Date = new Date()
|
|
73
|
+
): CivicShuttleBay | undefined {
|
|
74
|
+
const bays = civicShuttleBays(modules, entityItemId, lanes, now)
|
|
75
|
+
return bays.find((bay) => bay.queued === 0) ?? bays[0]
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface PendingCivicTransfer {
|
|
79
|
+
laneKey: number
|
|
80
|
+
taskIndex: number
|
|
81
|
+
senderId: UInt64
|
|
82
|
+
receiverId: UInt64
|
|
83
|
+
cargo: CargoItem[]
|
|
84
|
+
startsAt: Date
|
|
85
|
+
completesAt: Date
|
|
86
|
+
isTail: boolean
|
|
87
|
+
complete: boolean
|
|
88
|
+
callable: boolean
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function pendingCivicTransfers(
|
|
92
|
+
entity: ScheduleData,
|
|
93
|
+
now: Date = new Date()
|
|
94
|
+
): PendingCivicTransfer[] {
|
|
95
|
+
const tailIndex = new Map<number, number>()
|
|
96
|
+
for (const l of entity.lanes ?? []) {
|
|
97
|
+
tailIndex.set(l.lane_key.toNumber(), l.schedule.tasks.length - 1)
|
|
98
|
+
}
|
|
99
|
+
const out: PendingCivicTransfer[] = []
|
|
100
|
+
for (const entry of orderedTasks(entity)) {
|
|
101
|
+
if (entry.task.type.toNumber() !== TaskType.SHUTTLE) continue
|
|
102
|
+
const pull = entry.task.couplings.find((c) => c.kind.toNumber() === HoldKind.PULL)
|
|
103
|
+
const push = entry.task.couplings.find((c) => c.kind.toNumber() === HoldKind.PUSH)
|
|
104
|
+
if (!pull || !push) continue
|
|
105
|
+
const isTail = tailIndex.get(entry.laneKey) === entry.taskIndex
|
|
106
|
+
const complete = entry.completesAt.getTime() <= now.getTime()
|
|
107
|
+
out.push({
|
|
108
|
+
laneKey: entry.laneKey,
|
|
109
|
+
taskIndex: entry.taskIndex,
|
|
110
|
+
senderId: pull.counterpart.entity_id,
|
|
111
|
+
receiverId: push.counterpart.entity_id,
|
|
112
|
+
cargo: entry.task.cargo,
|
|
113
|
+
startsAt: entry.startsAt,
|
|
114
|
+
completesAt: entry.completesAt,
|
|
115
|
+
isTail,
|
|
116
|
+
complete,
|
|
117
|
+
callable: isTail && !complete && !entry.task.entitygroup,
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
return out
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Mirrors civic_transfers_for: the sender side of every shuttle the building holds, by owner.
|
|
124
|
+
export function civicTransfersFrom(
|
|
125
|
+
entity: ScheduleData,
|
|
126
|
+
senderIds: Array<UInt64 | string | number>,
|
|
127
|
+
now: Date = new Date()
|
|
128
|
+
): PendingCivicTransfer[] {
|
|
129
|
+
const owned = new Set(senderIds.map((id) => String(id)))
|
|
130
|
+
return pendingCivicTransfers(entity, now).filter((t) => owned.has(String(t.senderId)))
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function civicTransfersAtCap(
|
|
134
|
+
entity: ScheduleData,
|
|
135
|
+
senderIds: Array<UInt64 | string | number>,
|
|
136
|
+
now: Date = new Date()
|
|
137
|
+
): boolean {
|
|
138
|
+
return (
|
|
139
|
+
civicTransfersFrom(entity, senderIds, now).filter((t) => !t.complete).length >=
|
|
140
|
+
CIVIC_TRANSFER_PER_PLAYER_CAP
|
|
141
|
+
)
|
|
142
|
+
}
|