@shipload/sdk 1.0.0-next.3 → 1.0.0-next.30
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 +1847 -962
- package/lib/shipload.js +9088 -4854
- package/lib/shipload.js.map +1 -1
- package/lib/shipload.m.js +8957 -4805
- package/lib/shipload.m.js.map +1 -1
- package/lib/testing.d.ts +856 -0
- package/lib/testing.js +3739 -0
- package/lib/testing.js.map +1 -0
- package/lib/testing.m.js +3733 -0
- package/lib/testing.m.js.map +1 -0
- package/package.json +15 -2
- package/src/capabilities/craftable.ts +51 -0
- package/src/capabilities/crafting.test.ts +7 -0
- package/src/capabilities/crafting.ts +3 -3
- package/src/capabilities/gathering.ts +17 -7
- package/src/capabilities/index.ts +0 -1
- package/src/capabilities/modules.ts +6 -0
- package/src/capabilities/storage.ts +16 -1
- package/src/contracts/platform.ts +231 -3
- package/src/contracts/server.ts +816 -471
- package/src/data/capabilities.ts +14 -329
- package/src/data/capability-formulas.ts +76 -0
- package/src/data/catalog.ts +0 -5
- package/src/data/colors.ts +14 -47
- package/src/data/entities.json +46 -10
- package/src/data/item-ids.ts +15 -12
- package/src/data/items.json +302 -38
- package/src/data/kind-registry.json +85 -0
- package/src/data/kind-registry.ts +150 -0
- package/src/data/metadata.ts +100 -31
- package/src/data/recipes-runtime.ts +3 -23
- package/src/data/recipes.json +250 -113
- package/src/derivation/build-methods.ts +45 -0
- package/src/derivation/capabilities.ts +415 -0
- package/src/derivation/capability-mappings.ts +117 -0
- package/src/derivation/crafting.ts +23 -24
- package/src/derivation/index.ts +17 -2
- package/src/derivation/reserve-regen.ts +34 -0
- package/src/derivation/resources.ts +125 -38
- package/src/derivation/stars.test.ts +51 -0
- package/src/derivation/stars.ts +15 -0
- package/src/derivation/stats.ts +6 -6
- package/src/derivation/stratum.ts +15 -19
- package/src/derivation/tiers.ts +28 -7
- package/src/entities/entity.ts +98 -0
- package/src/entities/gamestate.ts +3 -28
- package/src/entities/makers.ts +91 -136
- package/src/entities/slot-multiplier.ts +39 -0
- package/src/errors.ts +10 -15
- package/src/format.ts +26 -4
- package/src/index-module.ts +189 -47
- package/src/managers/actions.ts +252 -83
- package/src/managers/base.ts +6 -2
- package/src/managers/construction-types.ts +79 -0
- package/src/managers/construction.ts +396 -0
- package/src/managers/context.ts +11 -1
- package/src/managers/entities.ts +18 -66
- package/src/managers/epochs.ts +40 -0
- package/src/managers/index.ts +17 -1
- package/src/managers/locations.ts +25 -29
- package/src/managers/nft.ts +28 -0
- package/src/managers/plot.ts +127 -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 +321 -0
- package/src/nft/description.ts +37 -15
- package/src/nft/index.ts +3 -0
- package/src/resolution/describe-module.ts +5 -8
- package/src/resolution/display-name.ts +38 -10
- package/src/resolution/resolve-item.ts +22 -20
- package/src/scheduling/accessor.ts +68 -22
- package/src/scheduling/availability.ts +108 -0
- package/src/scheduling/energy.ts +48 -0
- package/src/scheduling/lane-core.ts +130 -0
- package/src/scheduling/lanes.ts +60 -0
- package/src/scheduling/projection.ts +121 -94
- package/src/scheduling/schedule.ts +237 -103
- package/src/scheduling/task-cargo.ts +46 -0
- package/src/shipload.ts +16 -1
- package/src/subscriptions/manager.ts +40 -6
- package/src/subscriptions/mappers.ts +3 -8
- package/src/subscriptions/types.ts +3 -2
- package/src/testing/catalog-hash.ts +19 -0
- package/src/testing/index.ts +2 -0
- package/src/testing/projection-parity.ts +143 -0
- package/src/travel/travel.ts +90 -13
- package/src/types/capabilities.ts +1 -0
- package/src/types/index.ts +0 -1
- package/src/types.ts +19 -12
- package/src/utils/cargo.ts +27 -0
- package/src/utils/display-name.ts +61 -0
- package/src/utils/system.ts +25 -24
- package/src/capabilities/loading.ts +0 -8
- package/src/entities/container.ts +0 -108
- package/src/entities/ship-deploy.ts +0 -258
- package/src/entities/ship.ts +0 -204
- package/src/entities/warehouse.ts +0 -119
- package/src/types/entity-traits.ts +0 -69
package/src/nft/description.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
MODULE_ENGINE,
|
|
5
5
|
MODULE_GATHERER,
|
|
6
6
|
MODULE_GENERATOR,
|
|
7
|
+
MODULE_HAULER,
|
|
7
8
|
MODULE_LOADER,
|
|
8
9
|
MODULE_STORAGE,
|
|
9
10
|
MODULE_WARP,
|
|
@@ -13,8 +14,10 @@ import {
|
|
|
13
14
|
ITEM_CONTAINER_T2_PACKED,
|
|
14
15
|
ITEM_CRAFTER_T1,
|
|
15
16
|
ITEM_ENGINE_T1,
|
|
17
|
+
ITEM_EXTRACTOR_T1_PACKED,
|
|
16
18
|
ITEM_GATHERER_T1,
|
|
17
19
|
ITEM_GENERATOR_T1,
|
|
20
|
+
ITEM_HAULER_T1,
|
|
18
21
|
ITEM_LOADER_T1,
|
|
19
22
|
ITEM_SHIP_T1_PACKED,
|
|
20
23
|
ITEM_STORAGE_T1,
|
|
@@ -22,6 +25,8 @@ import {
|
|
|
22
25
|
ITEM_WARP_T1,
|
|
23
26
|
} from '../data/item-ids'
|
|
24
27
|
import {decodeStat} from '../derivation/crafting'
|
|
28
|
+
import {gathererDepthForTier} from '../derivation/capabilities'
|
|
29
|
+
import {getItem} from '../data/catalog'
|
|
25
30
|
|
|
26
31
|
function idiv(a: number, b: number): number {
|
|
27
32
|
return Math.floor(a / b)
|
|
@@ -29,32 +34,35 @@ function idiv(a: number, b: number): number {
|
|
|
29
34
|
|
|
30
35
|
export function computeBaseHullmass(stats: bigint): number {
|
|
31
36
|
const density = decodeStat(stats, 1)
|
|
32
|
-
return
|
|
37
|
+
return 100000 - 75 * density
|
|
33
38
|
}
|
|
34
39
|
|
|
35
40
|
export function computeBaseCapacityShip(stats: bigint): number {
|
|
36
41
|
const s = decodeStat(stats, 0) + decodeStat(stats, 2) + decodeStat(stats, 3)
|
|
37
|
-
return Math.floor(
|
|
42
|
+
return Math.floor(5_000_000 * 6 ** (s / 2997))
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
export function computeBaseCapacityWarehouse(stats: bigint): number {
|
|
41
46
|
const s = decodeStat(stats, 0) + decodeStat(stats, 2) + decodeStat(stats, 3)
|
|
42
|
-
return Math.floor(
|
|
47
|
+
return Math.floor(100_000_000 * 6 ** (s / 2997))
|
|
43
48
|
}
|
|
44
49
|
|
|
45
50
|
export const computeEngineThrust = (vol: number): number => 400 + idiv(vol * 3, 4)
|
|
46
|
-
export const computeEngineDrain = (thm: number): number => Math.max(30, 50 - idiv(thm, 70))
|
|
47
|
-
export const computeGeneratorCap = (
|
|
48
|
-
export const computeGeneratorRech = (
|
|
51
|
+
export const computeEngineDrain = (thm: number): number => 2 * Math.max(30, 50 - idiv(thm, 70))
|
|
52
|
+
export const computeGeneratorCap = (com: number): number => 950 + idiv(com, 2)
|
|
53
|
+
export const computeGeneratorRech = (fin: number): number => 2 * (1 + idiv(fin * 3, 1000))
|
|
49
54
|
export const computeGathererYield = (str: number): number => 200 + str
|
|
50
55
|
export const computeGathererDrain = (con: number): number =>
|
|
51
|
-
Math.max(250, 1250 - idiv(con * 25, 20))
|
|
52
|
-
export const computeGathererDepth = (tol: number): number =>
|
|
53
|
-
|
|
54
|
-
export const computeLoaderMass = (
|
|
56
|
+
2 * Math.max(250, 1250 - idiv(con * 25, 20))
|
|
57
|
+
export const computeGathererDepth = (tol: number, tier: number): number =>
|
|
58
|
+
gathererDepthForTier(tol, tier)
|
|
59
|
+
export const computeLoaderMass = (ins: number): number => Math.max(200, 2000 - ins * 2)
|
|
55
60
|
export const computeLoaderThrust = (pla: number): number => 1 + idiv(pla, 500)
|
|
56
61
|
export const computeCrafterSpeed = (rea: number): number => 100 + idiv(rea * 4, 5)
|
|
57
|
-
export const computeCrafterDrain = (
|
|
62
|
+
export const computeCrafterDrain = (fin: number): number => Math.max(5, 30 - idiv(fin, 33))
|
|
63
|
+
export const computeHaulerCapacity = (fin: number): number => Math.max(1, 1 + idiv(fin, 400))
|
|
64
|
+
export const computeHaulerEfficiency = (con: number): number => 2000 + con * 6
|
|
65
|
+
export const computeHaulerDrain = (com: number): number => Math.max(3, 15 - idiv(com, 80))
|
|
58
66
|
export const computeWarpRange = (stat: number): number => 100 + stat * 3
|
|
59
67
|
|
|
60
68
|
export function entityDisplayName(itemId: number): string {
|
|
@@ -63,6 +71,8 @@ export function entityDisplayName(itemId: number): string {
|
|
|
63
71
|
return 'Ship'
|
|
64
72
|
case ITEM_WAREHOUSE_T1_PACKED:
|
|
65
73
|
return 'Warehouse'
|
|
74
|
+
case ITEM_EXTRACTOR_T1_PACKED:
|
|
75
|
+
return 'Extractor'
|
|
66
76
|
case ITEM_CONTAINER_T1_PACKED:
|
|
67
77
|
return 'Container'
|
|
68
78
|
case ITEM_CONTAINER_T2_PACKED:
|
|
@@ -86,6 +96,8 @@ export function moduleDisplayName(itemId: number): string {
|
|
|
86
96
|
return 'Crafter'
|
|
87
97
|
case ITEM_STORAGE_T1:
|
|
88
98
|
return 'Storage'
|
|
99
|
+
case ITEM_HAULER_T1:
|
|
100
|
+
return 'Hauler'
|
|
89
101
|
case ITEM_WARP_T1:
|
|
90
102
|
return 'Warp'
|
|
91
103
|
default:
|
|
@@ -119,11 +131,12 @@ export function formatModuleLine(slot: number, itemId: number, stats: bigint): s
|
|
|
119
131
|
case MODULE_GATHERER: {
|
|
120
132
|
const str = decodeStat(stats, 0)
|
|
121
133
|
const tol = decodeStat(stats, 1)
|
|
122
|
-
const con = decodeStat(stats,
|
|
123
|
-
const
|
|
134
|
+
const con = decodeStat(stats, 2)
|
|
135
|
+
const tier = getItem(itemId).tier
|
|
124
136
|
out += ` Yield ${computeGathererYield(str)} Depth ${computeGathererDepth(
|
|
125
|
-
tol
|
|
126
|
-
|
|
137
|
+
tol,
|
|
138
|
+
tier
|
|
139
|
+
)} Drain ${computeGathererDrain(con)}`
|
|
127
140
|
break
|
|
128
141
|
}
|
|
129
142
|
case MODULE_LOADER: {
|
|
@@ -147,6 +160,13 @@ export function formatModuleLine(slot: number, itemId: number, stats: bigint): s
|
|
|
147
160
|
out += ` +${pct}% capacity`
|
|
148
161
|
break
|
|
149
162
|
}
|
|
163
|
+
case MODULE_HAULER: {
|
|
164
|
+
const res = decodeStat(stats, 0)
|
|
165
|
+
const pla = decodeStat(stats, 1)
|
|
166
|
+
const ref = decodeStat(stats, 2)
|
|
167
|
+
out += ` Capacity ${computeHaulerCapacity(res)} Efficiency ${computeHaulerEfficiency(pla)} Drain ${computeHaulerDrain(ref)}`
|
|
168
|
+
break
|
|
169
|
+
}
|
|
150
170
|
case MODULE_WARP: {
|
|
151
171
|
const stat = decodeStat(stats, 0)
|
|
152
172
|
out += ` Range ${computeWarpRange(stat)}`
|
|
@@ -168,6 +188,8 @@ export function buildEntityDescription(
|
|
|
168
188
|
baseCapacity = computeBaseCapacityShip(hullStats)
|
|
169
189
|
} else if (itemId === ITEM_WAREHOUSE_T1_PACKED) {
|
|
170
190
|
baseCapacity = computeBaseCapacityWarehouse(hullStats)
|
|
191
|
+
} else if (itemId === ITEM_EXTRACTOR_T1_PACKED) {
|
|
192
|
+
baseCapacity = computeBaseCapacityShip(hullStats)
|
|
171
193
|
}
|
|
172
194
|
|
|
173
195
|
let out = entityDisplayName(itemId)
|
package/src/nft/index.ts
CHANGED
|
@@ -53,25 +53,22 @@ const TEMPLATES: Record<string, TemplateSpec> = {
|
|
|
53
53
|
gatherer: {
|
|
54
54
|
id: 'module.gatherer.description',
|
|
55
55
|
template:
|
|
56
|
-
'mines resources at {yield}
|
|
56
|
+
'mines resources at {yield} yield to a max depth of {depth} while draining {drain} energy per second',
|
|
57
57
|
params: [
|
|
58
58
|
['yield', 'Yield'],
|
|
59
|
-
['drain', 'Drain'],
|
|
60
59
|
['depth', 'Depth'],
|
|
61
|
-
['
|
|
60
|
+
['drain', 'Drain'],
|
|
62
61
|
],
|
|
63
|
-
highlightKeys: ['yield', 'depth', '
|
|
62
|
+
highlightKeys: ['yield', 'depth', 'drain'],
|
|
64
63
|
},
|
|
65
64
|
loader: {
|
|
66
65
|
id: 'module.loader.description',
|
|
67
|
-
template:
|
|
68
|
-
'{quantity} loader that generates {thrust} thrust with a weight of {mass} per unit',
|
|
66
|
+
template: 'generates {thrust} thrust with a weight of {mass} per unit',
|
|
69
67
|
params: [
|
|
70
|
-
['quantity', 'Quantity'],
|
|
71
68
|
['thrust', 'Thrust'],
|
|
72
69
|
['mass', 'Mass'],
|
|
73
70
|
],
|
|
74
|
-
highlightKeys: ['
|
|
71
|
+
highlightKeys: ['thrust', 'mass'],
|
|
75
72
|
},
|
|
76
73
|
crafter: {
|
|
77
74
|
id: 'module.crafter.description',
|
|
@@ -1,22 +1,50 @@
|
|
|
1
1
|
import type {ResolvedItem} from './resolve-item'
|
|
2
2
|
import type {ResourceCategory} from '../types'
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
CATEGORY_LABELS,
|
|
5
|
+
RESOURCE_TIER_ADJECTIVES,
|
|
6
|
+
COMPONENT_TIER_PREFIXES,
|
|
7
|
+
MODULE_TIER_PREFIXES,
|
|
8
|
+
} from '../types'
|
|
4
9
|
import {formatMass as defaultFormatMass} from '../format'
|
|
5
10
|
|
|
6
|
-
|
|
7
|
-
itemType: 'resource' | 'component' | 'module' | 'entity' | string
|
|
11
|
+
interface DisplayNameInputCommon {
|
|
8
12
|
tier: number
|
|
9
13
|
category?: ResourceCategory
|
|
10
14
|
name: string
|
|
11
15
|
}
|
|
12
16
|
|
|
13
|
-
export
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
export type DisplayNameInput =
|
|
18
|
+
| (DisplayNameInputCommon & {itemType: 'resource' | 'component' | 'module' | 'entity' | string})
|
|
19
|
+
| (DisplayNameInputCommon & {type: string})
|
|
20
|
+
|
|
21
|
+
function itemTypeOf(item: DisplayNameInput): string {
|
|
22
|
+
return 'itemType' in item ? item.itemType : item.type
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function tierPrefix(item: DisplayNameInput): string | null {
|
|
26
|
+
const t = itemTypeOf(item)
|
|
27
|
+
if (t === 'resource') return RESOURCE_TIER_ADJECTIVES[item.tier] ?? null
|
|
28
|
+
if (t === 'component') return COMPONENT_TIER_PREFIXES[item.tier] ?? null
|
|
29
|
+
if (t === 'module') return MODULE_TIER_PREFIXES[item.tier] ?? null
|
|
30
|
+
return null
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function rootName(item: DisplayNameInput): string {
|
|
34
|
+
if (itemTypeOf(item) !== 'resource') return item.name
|
|
35
|
+
return item.category ? CATEGORY_LABELS[item.category] : 'Resource'
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Tier-free display name: includes the resource tier adjective / component-module
|
|
39
|
+
// prefix, but no "(T#)" suffix. Use this when the tier is shown separately.
|
|
40
|
+
export function baseName(item: DisplayNameInput): string {
|
|
41
|
+
const prefix = tierPrefix(item)
|
|
42
|
+
const root = rootName(item)
|
|
43
|
+
return prefix ? `${prefix} ${root}` : root
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function displayName(item: DisplayNameInput): string {
|
|
47
|
+
return `${baseName(item)} (T${item.tier})`
|
|
20
48
|
}
|
|
21
49
|
|
|
22
50
|
export interface DescribeOptions {
|
|
@@ -26,19 +26,15 @@ import {
|
|
|
26
26
|
computeLoaderCapabilities,
|
|
27
27
|
computeShipHullCapabilities,
|
|
28
28
|
computeWarehouseHullCapabilities,
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
categoryIcons,
|
|
34
|
-
componentIcon,
|
|
35
|
-
itemAbbreviations,
|
|
36
|
-
moduleIcon,
|
|
37
|
-
} from '../data/colors'
|
|
29
|
+
computeContainerCapabilities,
|
|
30
|
+
computeContainerT2Capabilities,
|
|
31
|
+
} from '../derivation/capabilities'
|
|
32
|
+
import {categoryColors, componentIcon, itemAbbreviations, moduleIcon} from '../data/colors'
|
|
38
33
|
import type {ServerContract} from '../contracts'
|
|
39
34
|
import {
|
|
40
35
|
ITEM_CONTAINER_T1_PACKED,
|
|
41
36
|
ITEM_CONTAINER_T2_PACKED,
|
|
37
|
+
ITEM_EXTRACTOR_T1_PACKED,
|
|
42
38
|
ITEM_SHIP_T1_PACKED,
|
|
43
39
|
ITEM_WAREHOUSE_T1_PACKED,
|
|
44
40
|
} from '../data/item-ids'
|
|
@@ -109,7 +105,7 @@ function resolveResource(id: number, stats?: UInt64Type): ResolvedItem {
|
|
|
109
105
|
return {
|
|
110
106
|
itemId: id,
|
|
111
107
|
name: item.name,
|
|
112
|
-
icon:
|
|
108
|
+
icon: '',
|
|
113
109
|
abbreviation: null,
|
|
114
110
|
category: cat,
|
|
115
111
|
tier: item.tier,
|
|
@@ -155,7 +151,8 @@ function resolveComponent(id: number, stats?: UInt64Type): ResolvedItem {
|
|
|
155
151
|
|
|
156
152
|
function computeCapabilityGroup(
|
|
157
153
|
moduleType: number,
|
|
158
|
-
stats: Record<string, number
|
|
154
|
+
stats: Record<string, number>,
|
|
155
|
+
tier: number
|
|
159
156
|
): ResolvedAttributeGroup | undefined {
|
|
160
157
|
switch (moduleType) {
|
|
161
158
|
case MODULE_ENGINE: {
|
|
@@ -179,14 +176,13 @@ function computeCapabilityGroup(
|
|
|
179
176
|
}
|
|
180
177
|
}
|
|
181
178
|
case MODULE_GATHERER: {
|
|
182
|
-
const caps = computeGathererCapabilities(stats)
|
|
179
|
+
const caps = computeGathererCapabilities(stats, tier)
|
|
183
180
|
return {
|
|
184
181
|
capability: 'Gatherer',
|
|
185
182
|
attributes: [
|
|
186
183
|
{label: 'Yield', value: caps.yield},
|
|
187
184
|
{label: 'Drain', value: caps.drain},
|
|
188
185
|
{label: 'Depth', value: caps.depth},
|
|
189
|
-
{label: 'Speed', value: caps.speed},
|
|
190
186
|
],
|
|
191
187
|
}
|
|
192
188
|
}
|
|
@@ -223,10 +219,11 @@ function computeCapabilityGroup(
|
|
|
223
219
|
}
|
|
224
220
|
}
|
|
225
221
|
case MODULE_STORAGE: {
|
|
226
|
-
const str = stats.strength
|
|
227
|
-
const
|
|
228
|
-
const
|
|
229
|
-
const
|
|
222
|
+
const str = stats.strength
|
|
223
|
+
const den = stats.density
|
|
224
|
+
const hrd = stats.hardness
|
|
225
|
+
const com = stats.cohesion
|
|
226
|
+
const statSum = str + den + hrd + com
|
|
230
227
|
const pct = 10 + Math.floor((statSum * 10) / 2997)
|
|
231
228
|
return {capability: 'Storage', attributes: [{label: 'Capacity Bonus', value: pct}]}
|
|
232
229
|
}
|
|
@@ -241,7 +238,7 @@ function resolveModule(id: number, stats?: UInt64Type): ResolvedItem {
|
|
|
241
238
|
if (stats !== undefined) {
|
|
242
239
|
const decoded = decodeCraftedItemStats(id, toBigStats(stats))
|
|
243
240
|
const modType = getModuleCapabilityType(id)
|
|
244
|
-
const group = computeCapabilityGroup(modType, decoded)
|
|
241
|
+
const group = computeCapabilityGroup(modType, decoded, item.tier)
|
|
245
242
|
if (group) attributes = [group]
|
|
246
243
|
}
|
|
247
244
|
return {
|
|
@@ -268,6 +265,8 @@ function hullCapsForEntity(
|
|
|
268
265
|
return computeShipHullCapabilities(decoded)
|
|
269
266
|
case ITEM_WAREHOUSE_T1_PACKED:
|
|
270
267
|
return computeWarehouseHullCapabilities(decoded)
|
|
268
|
+
case ITEM_EXTRACTOR_T1_PACKED:
|
|
269
|
+
return computeShipHullCapabilities(decoded)
|
|
271
270
|
case ITEM_CONTAINER_T1_PACKED:
|
|
272
271
|
return computeContainerCapabilities(decoded)
|
|
273
272
|
case ITEM_CONTAINER_T2_PACKED:
|
|
@@ -310,13 +309,16 @@ function resolveEntity(
|
|
|
310
309
|
const modStats = BigInt(mod.installed.stats.toString())
|
|
311
310
|
const decodedStats = decodeCraftedItemStats(modItemId, modStats)
|
|
312
311
|
const modType = getModuleCapabilityType(modItemId)
|
|
313
|
-
const group = computeCapabilityGroup(modType, decodedStats)
|
|
314
312
|
let modName = 'Module'
|
|
313
|
+
let modTier = 1
|
|
315
314
|
try {
|
|
316
|
-
|
|
315
|
+
const modItem = getItem(modItemId)
|
|
316
|
+
modName = modItem.name
|
|
317
|
+
modTier = modItem.tier
|
|
317
318
|
} catch {
|
|
318
319
|
modName = itemMetadata[modItemId]?.name ?? 'Module'
|
|
319
320
|
}
|
|
321
|
+
const group = computeCapabilityGroup(modType, decodedStats, modTier)
|
|
320
322
|
return {
|
|
321
323
|
name: modName,
|
|
322
324
|
installed: true,
|
|
@@ -1,82 +1,128 @@
|
|
|
1
1
|
import type {ServerContract} from '../contracts'
|
|
2
2
|
import type {TaskType} from '../types'
|
|
3
|
-
import
|
|
4
|
-
import
|
|
3
|
+
import * as core from './lane-core'
|
|
4
|
+
import {
|
|
5
|
+
activeTasks,
|
|
6
|
+
getLane,
|
|
7
|
+
getLanes,
|
|
8
|
+
hasSchedule,
|
|
9
|
+
isIdle,
|
|
10
|
+
LANE_MOBILITY,
|
|
11
|
+
type LaneView,
|
|
12
|
+
type ScheduleData,
|
|
13
|
+
} from './schedule'
|
|
5
14
|
|
|
6
15
|
type Task = ServerContract.Types.task
|
|
7
16
|
|
|
8
17
|
export class ScheduleAccessor {
|
|
9
|
-
|
|
18
|
+
private _laneResolved = false
|
|
19
|
+
private _lane: LaneView | undefined
|
|
20
|
+
|
|
21
|
+
constructor(
|
|
22
|
+
private entity: ScheduleData,
|
|
23
|
+
private laneKey: number = LANE_MOBILITY
|
|
24
|
+
) {}
|
|
25
|
+
|
|
26
|
+
private get lane(): LaneView | undefined {
|
|
27
|
+
if (!this._laneResolved) {
|
|
28
|
+
this._lane = getLane(this.entity, this.laneKey)
|
|
29
|
+
this._laneResolved = true
|
|
30
|
+
}
|
|
31
|
+
return this._lane
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
forLane(laneKey: number): ScheduleAccessor {
|
|
35
|
+
return new ScheduleAccessor(this.entity, laneKey)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
get lanes(): LaneView[] {
|
|
39
|
+
return getLanes(this.entity)
|
|
40
|
+
}
|
|
10
41
|
|
|
11
42
|
get hasSchedule(): boolean {
|
|
12
|
-
return
|
|
43
|
+
return hasSchedule(this.entity)
|
|
13
44
|
}
|
|
14
45
|
|
|
15
46
|
get isIdle(): boolean {
|
|
16
|
-
return
|
|
47
|
+
return isIdle(this.entity)
|
|
17
48
|
}
|
|
18
49
|
|
|
19
50
|
get tasks(): Task[] {
|
|
20
|
-
return schedule.
|
|
51
|
+
return this.lane?.schedule.tasks ?? []
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
activeTasks(now: Date): Task[] {
|
|
55
|
+
return activeTasks(this.entity, now)
|
|
21
56
|
}
|
|
22
57
|
|
|
23
58
|
duration(): number {
|
|
24
|
-
return
|
|
59
|
+
return this.lane ? core.laneDuration(this.lane.schedule) : 0
|
|
25
60
|
}
|
|
26
61
|
|
|
27
62
|
elapsed(now: Date): number {
|
|
28
|
-
return
|
|
63
|
+
return this.lane ? core.laneElapsed(this.lane.schedule, now) : 0
|
|
29
64
|
}
|
|
30
65
|
|
|
31
66
|
remaining(now: Date): number {
|
|
32
|
-
return
|
|
67
|
+
return this.lane ? core.laneRemaining(this.lane.schedule, now) : 0
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
startsIn(now: Date): number {
|
|
71
|
+
return this.lane ? core.laneStartsIn(this.lane.schedule, now) : 0
|
|
33
72
|
}
|
|
34
73
|
|
|
35
74
|
complete(now: Date): boolean {
|
|
36
|
-
return
|
|
75
|
+
return this.lane ? core.laneComplete(this.lane.schedule, now) : false
|
|
37
76
|
}
|
|
38
77
|
|
|
39
78
|
currentTaskIndex(now: Date): number {
|
|
40
|
-
return
|
|
79
|
+
return this.lane ? core.currentTaskIndexForLane(this.lane.schedule, now) : -1
|
|
41
80
|
}
|
|
42
81
|
|
|
43
82
|
currentTask(now: Date): Task | undefined {
|
|
44
|
-
return
|
|
83
|
+
return this.lane ? core.currentTask(this.lane.schedule, now) : undefined
|
|
45
84
|
}
|
|
46
85
|
|
|
47
86
|
currentTaskType(now: Date): TaskType | undefined {
|
|
48
|
-
return
|
|
87
|
+
return this.lane ? core.currentTaskType(this.lane.schedule, now) : undefined
|
|
49
88
|
}
|
|
50
89
|
|
|
51
90
|
taskStartTime(index: number): number {
|
|
52
|
-
return
|
|
91
|
+
return this.lane ? core.laneTaskStartTime(this.lane.schedule, index) : 0
|
|
53
92
|
}
|
|
54
93
|
|
|
55
94
|
taskElapsed(index: number, now: Date): number {
|
|
56
|
-
return
|
|
95
|
+
return this.lane ? core.laneTaskElapsed(this.lane.schedule, index, now) : 0
|
|
57
96
|
}
|
|
58
97
|
|
|
59
98
|
taskRemaining(index: number, now: Date): number {
|
|
60
|
-
return
|
|
99
|
+
return this.lane ? core.laneTaskRemaining(this.lane.schedule, index, now) : 0
|
|
61
100
|
}
|
|
62
101
|
|
|
63
102
|
taskComplete(index: number, now: Date): boolean {
|
|
64
|
-
return
|
|
103
|
+
return this.lane ? core.laneTaskComplete(this.lane.schedule, index, now) : false
|
|
65
104
|
}
|
|
66
105
|
|
|
67
106
|
taskInProgress(index: number, now: Date): boolean {
|
|
68
|
-
return
|
|
107
|
+
return this.lane ? core.laneTaskInProgress(this.lane.schedule, index, now) : false
|
|
69
108
|
}
|
|
70
109
|
|
|
71
110
|
currentTaskProgress(now: Date): number {
|
|
72
|
-
return
|
|
111
|
+
return this.lane ? core.currentTaskProgress(this.lane.schedule, now) : 0
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
currentTaskProgressFloat(now: Date): number {
|
|
115
|
+
return this.lane ? core.currentTaskProgressFloatForLane(this.lane.schedule, now) : 0
|
|
73
116
|
}
|
|
74
117
|
|
|
75
118
|
progress(now: Date): number {
|
|
76
|
-
return
|
|
119
|
+
return this.lane ? core.laneProgress(this.lane.schedule, now) : 0
|
|
77
120
|
}
|
|
78
121
|
}
|
|
79
122
|
|
|
80
|
-
export function createScheduleAccessor(
|
|
81
|
-
|
|
123
|
+
export function createScheduleAccessor(
|
|
124
|
+
entity: ScheduleData,
|
|
125
|
+
laneKey: number = LANE_MOBILITY
|
|
126
|
+
): ScheduleAccessor {
|
|
127
|
+
return new ScheduleAccessor(entity, laneKey)
|
|
82
128
|
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type {ServerContract} from '../contracts'
|
|
2
|
+
import {TaskType} from '../types'
|
|
3
|
+
import * as schedule from './schedule'
|
|
4
|
+
|
|
5
|
+
type Task = ServerContract.Types.task
|
|
6
|
+
type CargoItem = ServerContract.Types.cargo_item
|
|
7
|
+
|
|
8
|
+
export interface CargoEffect {
|
|
9
|
+
added: CargoItem[]
|
|
10
|
+
removed: CargoItem[]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface AvailabilityInput extends schedule.ScheduleData {
|
|
14
|
+
cargo: CargoItem[]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function taskCargoEffect(task: Task): CargoEffect {
|
|
18
|
+
switch (task.type.toNumber()) {
|
|
19
|
+
case TaskType.LOAD:
|
|
20
|
+
case TaskType.UNWRAP:
|
|
21
|
+
case TaskType.UNDEPLOY:
|
|
22
|
+
return {added: task.cargo, removed: []}
|
|
23
|
+
case TaskType.UNLOAD:
|
|
24
|
+
return {added: [], removed: task.cargo}
|
|
25
|
+
case TaskType.GATHER:
|
|
26
|
+
return task.entitytarget ? {added: [], removed: []} : {added: task.cargo, removed: []}
|
|
27
|
+
case TaskType.CRAFT:
|
|
28
|
+
if (task.cargo.length === 0) return {added: [], removed: []}
|
|
29
|
+
return {added: [task.cargo[task.cargo.length - 1]], removed: task.cargo.slice(0, -1)}
|
|
30
|
+
case TaskType.DEPLOY:
|
|
31
|
+
return task.cargo.length > 0
|
|
32
|
+
? {added: [], removed: [task.cargo[0]]}
|
|
33
|
+
: {added: [], removed: []}
|
|
34
|
+
default:
|
|
35
|
+
return {added: [], removed: []}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function cargoKey(item: CargoItem): string {
|
|
40
|
+
const base = `${item.item_id.toNumber()}:${item.stats.toString()}`
|
|
41
|
+
const modules = item.modules ?? []
|
|
42
|
+
const entityId = item.entity_id?.toString()
|
|
43
|
+
const normalizedEntityId = entityId && entityId !== '0' ? entityId : ''
|
|
44
|
+
if (modules.length === 0 && normalizedEntityId === '') return base
|
|
45
|
+
return `${base}:modules=${JSON.stringify(modules)}:entity=${normalizedEntityId}`
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function cargoQuantity(item: CargoItem): bigint {
|
|
49
|
+
return BigInt(item.quantity.toString())
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function projectedCargoAvailableAt(
|
|
53
|
+
entity: AvailabilityInput,
|
|
54
|
+
at: Date
|
|
55
|
+
): Map<string, bigint> {
|
|
56
|
+
const avail = new Map<string, bigint>()
|
|
57
|
+
|
|
58
|
+
for (const item of entity.cargo) {
|
|
59
|
+
const key = cargoKey(item)
|
|
60
|
+
avail.set(key, (avail.get(key) ?? 0n) + cargoQuantity(item))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Every scheduled task reserves inputs against the unsettled cargo base, even already-elapsed ones.
|
|
64
|
+
const tasks = schedule.orderedTasks(entity)
|
|
65
|
+
|
|
66
|
+
for (const ordered of tasks) {
|
|
67
|
+
if (ordered.completesAt.getTime() >= at.getTime()) continue
|
|
68
|
+
|
|
69
|
+
for (const item of taskCargoEffect(ordered.task).added) {
|
|
70
|
+
const key = cargoKey(item)
|
|
71
|
+
avail.set(key, (avail.get(key) ?? 0n) + cargoQuantity(item))
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
for (const ordered of tasks) {
|
|
76
|
+
for (const item of taskCargoEffect(ordered.task).removed) {
|
|
77
|
+
const key = cargoKey(item)
|
|
78
|
+
const current = avail.get(key) ?? 0n
|
|
79
|
+
const quantity = cargoQuantity(item)
|
|
80
|
+
avail.set(key, current > quantity ? current - quantity : 0n)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return avail
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Latest completion among scheduled tasks producing any of the given inputs (a craft starts no earlier).
|
|
88
|
+
export function cargoReadyAt(entity: AvailabilityInput, inputItemIds: readonly number[]): Date {
|
|
89
|
+
let readyMs = 0
|
|
90
|
+
for (const ordered of schedule.orderedTasks(entity)) {
|
|
91
|
+
for (const item of taskCargoEffect(ordered.task).added) {
|
|
92
|
+
if (inputItemIds.includes(item.item_id.toNumber())) {
|
|
93
|
+
readyMs = Math.max(readyMs, ordered.completesAt.getTime())
|
|
94
|
+
break
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return new Date(readyMs)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function availableForItem(avail: Map<string, bigint>, itemId: number): bigint {
|
|
102
|
+
const prefix = `${itemId}:`
|
|
103
|
+
let total = 0n
|
|
104
|
+
for (const [key, quantity] of avail) {
|
|
105
|
+
if (key.startsWith(prefix)) total += quantity
|
|
106
|
+
}
|
|
107
|
+
return total
|
|
108
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import {TaskType} from '../types'
|
|
2
|
+
import {createProjectedEntity, type Projectable} from './projection'
|
|
3
|
+
import {orderedTasks} 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 ordered = orderedTasks(entity)
|
|
17
|
+
if (ordered.length === 0) return clamp(running)
|
|
18
|
+
|
|
19
|
+
const nowMs = now.getTime()
|
|
20
|
+
|
|
21
|
+
for (const {task, startsAt} of ordered) {
|
|
22
|
+
const duration = task.duration.toNumber()
|
|
23
|
+
const isReserved = task.type.toNumber() === TaskType.RESERVED
|
|
24
|
+
const elapsed = Math.min(
|
|
25
|
+
Math.max(0, Math.floor((nowMs - startsAt.getTime()) / 1000)),
|
|
26
|
+
duration
|
|
27
|
+
)
|
|
28
|
+
const complete = !isReserved && elapsed >= duration
|
|
29
|
+
const inProgress = !complete && elapsed > 0 && elapsed < duration
|
|
30
|
+
|
|
31
|
+
if (!complete && !inProgress) continue
|
|
32
|
+
|
|
33
|
+
const fraction = complete ? 1 : duration === 0 ? 1 : elapsed / duration
|
|
34
|
+
|
|
35
|
+
if (task.type.toNumber() === TaskType.RECHARGE) {
|
|
36
|
+
if (capacity !== undefined) {
|
|
37
|
+
running = complete ? capacity : running + (capacity - running) * fraction
|
|
38
|
+
}
|
|
39
|
+
} else {
|
|
40
|
+
const cost = Number(task.energy_cost ?? 0)
|
|
41
|
+
running -= cost * fraction
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
running = clamp(running)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return clamp(running)
|
|
48
|
+
}
|