@shipload/sdk 2.0.0-rc1 → 2.0.0-rc10
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 +1591 -1101
- package/lib/shipload.js +6735 -3491
- package/lib/shipload.js.map +1 -1
- package/lib/shipload.m.js +6421 -3275
- package/lib/shipload.m.js.map +1 -1
- package/package.json +6 -6
- package/src/capabilities/crafting.ts +26 -0
- package/src/capabilities/extraction.ts +36 -0
- package/src/capabilities/guards.ts +38 -0
- package/src/capabilities/hauling.ts +22 -0
- package/src/capabilities/index.ts +8 -0
- package/src/capabilities/loading.ts +8 -0
- package/src/capabilities/modules.ts +57 -0
- package/src/capabilities/movement.ts +29 -0
- package/src/capabilities/storage.ts +72 -0
- package/src/contracts/server.ts +890 -309
- package/src/data/capabilities.ts +408 -0
- package/src/data/categories.ts +58 -0
- package/src/data/colors.ts +52 -0
- package/src/data/items.json +17 -0
- package/src/data/locations.ts +53 -0
- package/src/data/nebula-adjectives.json +211 -0
- package/src/data/nebula-nouns.json +151 -0
- package/src/data/recipes.ts +571 -0
- package/src/data/syllables.json +1386 -780
- package/src/data/tiers.ts +45 -0
- package/src/derivation/crafting.ts +197 -0
- package/src/derivation/index.ts +28 -0
- package/src/derivation/location-size.ts +15 -0
- package/src/derivation/resources.ts +142 -0
- package/src/derivation/stats.ts +146 -0
- package/src/derivation/stratum.ts +118 -0
- package/src/entities/cargo-utils.ts +46 -9
- package/src/entities/container.ts +106 -0
- package/src/entities/entity-inventory.ts +13 -13
- package/src/entities/inventory-accessor.ts +42 -0
- package/src/entities/location.ts +7 -188
- package/src/entities/makers.ts +72 -0
- package/src/entities/player.ts +1 -273
- package/src/entities/ship-deploy.ts +263 -0
- package/src/entities/ship.ts +93 -453
- package/src/entities/warehouse.ts +34 -148
- package/src/errors.ts +4 -4
- package/src/index-module.ts +225 -42
- package/src/managers/actions.ts +107 -78
- package/src/managers/context.ts +0 -9
- package/src/managers/entities.ts +22 -5
- package/src/managers/index.ts +0 -1
- package/src/managers/locations.ts +15 -79
- package/src/market/items.ts +30 -0
- package/src/nft/description.ts +175 -0
- package/src/nft/deserializers.ts +81 -0
- package/src/nft/index.ts +2 -0
- package/src/resolution/resolve-item.ts +313 -0
- package/src/scheduling/accessor.ts +82 -0
- package/src/scheduling/projection.ts +158 -54
- package/src/scheduling/schedule.ts +24 -0
- package/src/shipload.ts +0 -5
- package/src/travel/travel.ts +93 -19
- package/src/types/capabilities.ts +71 -0
- package/src/types/entity-traits.ts +69 -0
- package/src/types/entity.ts +39 -0
- package/src/types/index.ts +3 -0
- package/src/types.ts +76 -33
- package/src/utils/hash.ts +1 -1
- package/src/utils/system.ts +139 -11
- package/src/data/goods.json +0 -23
- package/src/managers/trades.ts +0 -119
- package/src/market/goods.ts +0 -31
- package/src/market/market.ts +0 -209
- package/src/market/rolls.ts +0 -8
- package/src/trading/collect.ts +0 -939
- package/src/trading/deal.ts +0 -208
- package/src/trading/trade.ts +0 -203
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export type CraftedItemCategory = 'component' | 'module' | 'entity' | 'resource'
|
|
2
|
+
|
|
3
|
+
export const ITEM_TYPE_RESOURCE = 0
|
|
4
|
+
export const ITEM_TYPE_COMPONENT = 1
|
|
5
|
+
export const ITEM_TYPE_MODULE = 2
|
|
6
|
+
export const ITEM_TYPE_ENTITY = 3
|
|
7
|
+
|
|
8
|
+
export function itemTypeCode(id: number): number {
|
|
9
|
+
switch (itemCategory(id)) {
|
|
10
|
+
case 'resource':
|
|
11
|
+
return ITEM_TYPE_RESOURCE
|
|
12
|
+
case 'component':
|
|
13
|
+
return ITEM_TYPE_COMPONENT
|
|
14
|
+
case 'module':
|
|
15
|
+
return ITEM_TYPE_MODULE
|
|
16
|
+
case 'entity':
|
|
17
|
+
return ITEM_TYPE_ENTITY
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function itemTier(id: number): number {
|
|
22
|
+
if (id < 10000) return 0
|
|
23
|
+
return Math.floor(id / 10000)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function itemOffset(id: number): number {
|
|
27
|
+
return id % 10000
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function itemCategory(id: number): CraftedItemCategory {
|
|
31
|
+
if (id < 10000) return 'resource'
|
|
32
|
+
const offset = itemOffset(id)
|
|
33
|
+
if (offset >= 200) return 'entity'
|
|
34
|
+
if (offset >= 100) return 'module'
|
|
35
|
+
return 'component'
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function isRelatedItem(a: number, b: number): boolean {
|
|
39
|
+
if (a < 10000 || b < 10000) return false
|
|
40
|
+
return itemOffset(a) === itemOffset(b)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function isCraftedItem(id: number): boolean {
|
|
44
|
+
return id >= 10000
|
|
45
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import {UInt64} from '@wharfkit/antelope'
|
|
2
|
+
import type {ResourceCategory} from '../types'
|
|
3
|
+
import {
|
|
4
|
+
entityRecipes,
|
|
5
|
+
getComponentById,
|
|
6
|
+
getEntityRecipe,
|
|
7
|
+
getModuleRecipe,
|
|
8
|
+
moduleRecipes,
|
|
9
|
+
} from '../data/recipes'
|
|
10
|
+
import {deriveResourceStats} from './stratum'
|
|
11
|
+
|
|
12
|
+
export interface StackInput {
|
|
13
|
+
quantity: number
|
|
14
|
+
stats: Record<string, number>
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface CategoryStacks {
|
|
18
|
+
category: ResourceCategory
|
|
19
|
+
stacks: StackInput[]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function encodeStats(values: number[]): bigint {
|
|
23
|
+
let seed = 0n
|
|
24
|
+
for (let i = 0; i < values.length && i < 6; i++) {
|
|
25
|
+
seed |= BigInt(values[i] & 0x3ff) << BigInt(i * 10)
|
|
26
|
+
}
|
|
27
|
+
return seed
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function decodeStat(seed: bigint, index: number): number {
|
|
31
|
+
return Number((seed >> BigInt(index * 10)) & 0x3ffn)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function decodeStats(seed: bigint, count: number): number[] {
|
|
35
|
+
const stats: number[] = []
|
|
36
|
+
for (let i = 0; i < count; i++) {
|
|
37
|
+
stats.push(decodeStat(seed, i))
|
|
38
|
+
}
|
|
39
|
+
return stats
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function mapStatsToKeys(seed: bigint, statDefs: {key: string}[]): Record<string, number> {
|
|
43
|
+
const values = decodeStats(seed, statDefs.length)
|
|
44
|
+
const result: Record<string, number> = {}
|
|
45
|
+
for (let i = 0; i < statDefs.length; i++) {
|
|
46
|
+
result[statDefs[i].key] = values[i]
|
|
47
|
+
}
|
|
48
|
+
return result
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function decodeCraftedItemStats(itemId: number, seed: bigint): Record<string, number> {
|
|
52
|
+
const comp = getComponentById(itemId)
|
|
53
|
+
if (comp) return mapStatsToKeys(seed, comp.stats)
|
|
54
|
+
|
|
55
|
+
const entityRecipe = entityRecipes.find((r) => r.packedItemId === itemId)
|
|
56
|
+
if (entityRecipe) return mapStatsToKeys(seed, entityRecipe.stats)
|
|
57
|
+
|
|
58
|
+
const moduleRecipe = moduleRecipes.find((r) => r.itemId === itemId)
|
|
59
|
+
if (moduleRecipe) return mapStatsToKeys(seed, moduleRecipe.stats)
|
|
60
|
+
|
|
61
|
+
return {}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function blendStacks(stacks: StackInput[], statKey: string): number {
|
|
65
|
+
let totalQty = 0
|
|
66
|
+
let weightedSum = 0
|
|
67
|
+
for (const stack of stacks) {
|
|
68
|
+
const val = stack.stats[statKey] ?? 0
|
|
69
|
+
weightedSum += val * stack.quantity
|
|
70
|
+
totalQty += stack.quantity
|
|
71
|
+
}
|
|
72
|
+
if (totalQty === 0) return 0
|
|
73
|
+
return Math.floor(weightedSum / totalQty)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function computeComponentStats(
|
|
77
|
+
componentId: number,
|
|
78
|
+
categoryStacks: CategoryStacks[]
|
|
79
|
+
): {key: string; value: number}[] {
|
|
80
|
+
const comp = getComponentById(componentId)
|
|
81
|
+
if (!comp) return []
|
|
82
|
+
|
|
83
|
+
return comp.stats.map((statDef) => {
|
|
84
|
+
const matching = categoryStacks.find((cs) => cs.category === statDef.source)
|
|
85
|
+
const value = matching ? blendStacks(matching.stacks, statDef.key) : 0
|
|
86
|
+
return {key: statDef.key, value: Math.max(1, Math.min(999, value))}
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function blendComponentStacks(
|
|
91
|
+
stacks: {quantity: number; stats: Record<string, number>}[]
|
|
92
|
+
): Record<string, number> {
|
|
93
|
+
if (stacks.length === 0) return {}
|
|
94
|
+
const allKeys = new Set<string>()
|
|
95
|
+
for (const s of stacks) {
|
|
96
|
+
for (const k of Object.keys(s.stats)) allKeys.add(k)
|
|
97
|
+
}
|
|
98
|
+
const result: Record<string, number> = {}
|
|
99
|
+
for (const key of allKeys) {
|
|
100
|
+
result[key] = blendStacks(
|
|
101
|
+
stacks.map((s) => ({quantity: s.quantity, stats: s.stats})),
|
|
102
|
+
key
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
return result
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function computeEntityStats(
|
|
109
|
+
entityRecipeId: string,
|
|
110
|
+
componentStacks: Record<number, {quantity: number; stats: Record<string, number>}[]>
|
|
111
|
+
): {key: string; value: number}[] {
|
|
112
|
+
const recipe = getEntityRecipe(entityRecipeId) ?? getModuleRecipe(entityRecipeId)
|
|
113
|
+
if (!recipe) return []
|
|
114
|
+
|
|
115
|
+
const blendedByComponent: Record<number, Record<string, number>> = {}
|
|
116
|
+
for (const [compId, stacks] of Object.entries(componentStacks)) {
|
|
117
|
+
blendedByComponent[Number(compId)] = blendComponentStacks(stacks)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return recipe.stats.map((stat) => {
|
|
121
|
+
const blended = blendedByComponent[stat.sourceComponentId] ?? {}
|
|
122
|
+
const value = blended[stat.sourceStatKey] ?? 0
|
|
123
|
+
return {key: stat.key, value: Math.max(1, Math.min(999, value))}
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function decodeStackStats(itemId: number, seed: UInt64): Record<string, number> {
|
|
128
|
+
if (itemId >= 10000) {
|
|
129
|
+
return decodeCraftedItemStats(itemId, BigInt(seed.toString()))
|
|
130
|
+
}
|
|
131
|
+
const raw = deriveResourceStats(BigInt(seed.toString()))
|
|
132
|
+
return {stat1: raw.stat1, stat2: raw.stat2, stat3: raw.stat3}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export const categoryItemMass: Record<string, number> = {
|
|
136
|
+
metal: 30000,
|
|
137
|
+
precious: 40000,
|
|
138
|
+
gas: 15000,
|
|
139
|
+
mineral: 22000,
|
|
140
|
+
organic: 15000,
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function computeInputMass(
|
|
144
|
+
itemId: string | number,
|
|
145
|
+
itemType: 'component' | 'module' | 'entity'
|
|
146
|
+
): number {
|
|
147
|
+
if (itemType === 'component') {
|
|
148
|
+
const comp = getComponentById(itemId as number)
|
|
149
|
+
if (!comp) return 0
|
|
150
|
+
return comp.recipe.reduce((sum, input) => {
|
|
151
|
+
const mass = input.category ? categoryItemMass[input.category] ?? 0 : 0
|
|
152
|
+
return sum + mass * input.quantity
|
|
153
|
+
}, 0)
|
|
154
|
+
}
|
|
155
|
+
if (itemType === 'module') {
|
|
156
|
+
const mod = getModuleRecipe(itemId as string)
|
|
157
|
+
if (!mod) return 0
|
|
158
|
+
return mod.recipe.reduce((sum, input) => {
|
|
159
|
+
const comp = input.itemId ? getComponentById(input.itemId) : undefined
|
|
160
|
+
return sum + (comp?.mass ?? 0) * input.quantity
|
|
161
|
+
}, 0)
|
|
162
|
+
}
|
|
163
|
+
if (itemType === 'entity') {
|
|
164
|
+
const ent = getEntityRecipe(itemId as string)
|
|
165
|
+
if (!ent) return 0
|
|
166
|
+
return ent.recipe.reduce((sum, input) => {
|
|
167
|
+
const comp = input.itemId ? getComponentById(input.itemId) : undefined
|
|
168
|
+
return sum + (comp?.mass ?? 0) * input.quantity
|
|
169
|
+
}, 0)
|
|
170
|
+
}
|
|
171
|
+
return 0
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function blendCrossGroup(sources: {value: number; weight: number}[]): number {
|
|
175
|
+
let weightedSum = 0
|
|
176
|
+
let totalWeight = 0
|
|
177
|
+
for (const src of sources) {
|
|
178
|
+
weightedSum += src.value * src.weight
|
|
179
|
+
totalWeight += src.weight
|
|
180
|
+
}
|
|
181
|
+
if (totalWeight === 0) return 1
|
|
182
|
+
const result = Math.floor(weightedSum / totalWeight)
|
|
183
|
+
return Math.max(1, Math.min(999, result))
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function blendCargoStacks(
|
|
187
|
+
itemId: number,
|
|
188
|
+
stacks: {quantity: number; seed: UInt64}[]
|
|
189
|
+
): UInt64 {
|
|
190
|
+
const decoded = stacks.map((s) => ({
|
|
191
|
+
quantity: s.quantity,
|
|
192
|
+
stats: decodeStackStats(itemId, s.seed),
|
|
193
|
+
}))
|
|
194
|
+
const allKeys = Object.keys(decoded[0]?.stats ?? {})
|
|
195
|
+
const blended = allKeys.map((key) => Math.max(1, Math.min(999, blendStacks(decoded, key))))
|
|
196
|
+
return UInt64.from(encodeStats(blended))
|
|
197
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export {deriveStratum, deriveResourceStats} from './stratum'
|
|
2
|
+
export type {StratumInfo, ResourceStats} from './stratum'
|
|
3
|
+
export {deriveLocationSize} from './location-size'
|
|
4
|
+
export {
|
|
5
|
+
getEligibleResources,
|
|
6
|
+
getResourceWeight,
|
|
7
|
+
getLocationCandidates,
|
|
8
|
+
getDepthThreshold,
|
|
9
|
+
getResourceTier,
|
|
10
|
+
depthScaleFactor,
|
|
11
|
+
DEPTH_THRESHOLD_T1,
|
|
12
|
+
DEPTH_THRESHOLD_T2,
|
|
13
|
+
DEPTH_THRESHOLD_T3,
|
|
14
|
+
DEPTH_THRESHOLD_T4,
|
|
15
|
+
DEPTH_THRESHOLD_T5,
|
|
16
|
+
LOCATION_MIN_DEPTH,
|
|
17
|
+
LOCATION_MAX_DEPTH,
|
|
18
|
+
YIELD_THRESHOLD,
|
|
19
|
+
PLANET_SUBTYPE_GAS_GIANT,
|
|
20
|
+
PLANET_SUBTYPE_ROCKY,
|
|
21
|
+
PLANET_SUBTYPE_TERRESTRIAL,
|
|
22
|
+
PLANET_SUBTYPE_ICY,
|
|
23
|
+
PLANET_SUBTYPE_OCEAN,
|
|
24
|
+
PLANET_SUBTYPE_INDUSTRIAL,
|
|
25
|
+
} from './resources'
|
|
26
|
+
|
|
27
|
+
export * from './stats'
|
|
28
|
+
export * from './crafting'
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {ServerContract} from '../contracts'
|
|
2
|
+
import {LocationType} from '../types'
|
|
3
|
+
import {LOCATION_MAX_DEPTH, LOCATION_MIN_DEPTH} from './resources'
|
|
4
|
+
|
|
5
|
+
export function deriveLocationSize(loc: ServerContract.Types.location_static): number {
|
|
6
|
+
if (loc.type.toNumber() === LocationType.EMPTY) return 0
|
|
7
|
+
|
|
8
|
+
const raw = (loc.seed0.toNumber() << 8) | loc.seed1.toNumber()
|
|
9
|
+
const normalized = raw / 65535
|
|
10
|
+
|
|
11
|
+
const curved = Math.pow(normalized, 3.0)
|
|
12
|
+
|
|
13
|
+
const range = LOCATION_MAX_DEPTH - LOCATION_MIN_DEPTH
|
|
14
|
+
return Math.floor(LOCATION_MIN_DEPTH + curved * range)
|
|
15
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import {ResourceTier} from '../types'
|
|
2
|
+
|
|
3
|
+
export const DEPTH_THRESHOLD_T1 = 0
|
|
4
|
+
export const DEPTH_THRESHOLD_T2 = 2000
|
|
5
|
+
export const DEPTH_THRESHOLD_T3 = 10000
|
|
6
|
+
export const DEPTH_THRESHOLD_T4 = 30000
|
|
7
|
+
export const DEPTH_THRESHOLD_T5 = 55000
|
|
8
|
+
|
|
9
|
+
export const LOCATION_MIN_DEPTH = 500
|
|
10
|
+
export const LOCATION_MAX_DEPTH = 65535
|
|
11
|
+
|
|
12
|
+
export const YIELD_THRESHOLD = Math.floor(0.003 * 0xffffffff)
|
|
13
|
+
|
|
14
|
+
export const PLANET_SUBTYPE_GAS_GIANT = 0
|
|
15
|
+
export const PLANET_SUBTYPE_ROCKY = 1
|
|
16
|
+
export const PLANET_SUBTYPE_TERRESTRIAL = 2
|
|
17
|
+
export const PLANET_SUBTYPE_ICY = 3
|
|
18
|
+
export const PLANET_SUBTYPE_OCEAN = 4
|
|
19
|
+
export const PLANET_SUBTYPE_INDUSTRIAL = 5
|
|
20
|
+
|
|
21
|
+
interface ResourceEntry {
|
|
22
|
+
id: number
|
|
23
|
+
tier: ResourceTier
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const RESOURCE_CATALOG: ResourceEntry[] = [
|
|
27
|
+
{id: 26, tier: 't1'},
|
|
28
|
+
{id: 13, tier: 't2'},
|
|
29
|
+
{id: 24, tier: 't3'},
|
|
30
|
+
{id: 29, tier: 't1'},
|
|
31
|
+
{id: 47, tier: 't2'},
|
|
32
|
+
{id: 79, tier: 't3'},
|
|
33
|
+
{id: 1, tier: 't1'},
|
|
34
|
+
{id: 2, tier: 't2'},
|
|
35
|
+
{id: 18, tier: 't3'},
|
|
36
|
+
{id: 14, tier: 't1'},
|
|
37
|
+
{id: 1000, tier: 't2'},
|
|
38
|
+
{id: 1001, tier: 't3'},
|
|
39
|
+
{id: 6, tier: 't1'},
|
|
40
|
+
{id: 1003, tier: 't2'},
|
|
41
|
+
{id: 1002, tier: 't3'},
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
export function getDepthThreshold(tier: ResourceTier): number {
|
|
45
|
+
switch (tier) {
|
|
46
|
+
case 't1':
|
|
47
|
+
return DEPTH_THRESHOLD_T1
|
|
48
|
+
case 't2':
|
|
49
|
+
return DEPTH_THRESHOLD_T2
|
|
50
|
+
case 't3':
|
|
51
|
+
return DEPTH_THRESHOLD_T3
|
|
52
|
+
case 't4':
|
|
53
|
+
return DEPTH_THRESHOLD_T4
|
|
54
|
+
case 't5':
|
|
55
|
+
return DEPTH_THRESHOLD_T5
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function getResourceTier(itemId: number): ResourceTier {
|
|
60
|
+
const entry = RESOURCE_CATALOG.find((r) => r.id === itemId)
|
|
61
|
+
return entry ? entry.tier : 't5'
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function getResourceWeight(itemId: number, stratum: number): number {
|
|
65
|
+
const tier = getResourceTier(itemId)
|
|
66
|
+
const threshold = getDepthThreshold(tier)
|
|
67
|
+
if (stratum < threshold) return 0
|
|
68
|
+
|
|
69
|
+
const depthAbove = stratum - threshold
|
|
70
|
+
|
|
71
|
+
switch (tier) {
|
|
72
|
+
case 't1':
|
|
73
|
+
if (stratum < 2000) return 100
|
|
74
|
+
if (stratum < 10000) return 80
|
|
75
|
+
if (stratum < 30000) return 50
|
|
76
|
+
return 30
|
|
77
|
+
case 't2':
|
|
78
|
+
if (depthAbove < 3000) return 40
|
|
79
|
+
if (depthAbove < 8000) return 60
|
|
80
|
+
return 50
|
|
81
|
+
case 't3':
|
|
82
|
+
if (depthAbove < 5000) return 20
|
|
83
|
+
if (depthAbove < 15000) return 35
|
|
84
|
+
return 40
|
|
85
|
+
case 't4':
|
|
86
|
+
if (depthAbove < 10000) return 10
|
|
87
|
+
if (depthAbove < 25000) return 20
|
|
88
|
+
return 30
|
|
89
|
+
case 't5':
|
|
90
|
+
return 10
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const ASTEROID_RESOURCES = [26, 13, 24, 29, 47]
|
|
95
|
+
const NEBULA_RESOURCES = [47, 79, 1, 2, 18]
|
|
96
|
+
const GAS_GIANT_RESOURCES = [1, 2, 18, 14, 6]
|
|
97
|
+
const ROCKY_RESOURCES = [26, 13, 24, 14, 1000, 1001, 1002]
|
|
98
|
+
const TERRESTRIAL_RESOURCES = [29, 47, 14, 1000, 6, 1003, 1002]
|
|
99
|
+
const ICY_RESOURCES = [26, 1, 2, 14, 1001, 6, 1003]
|
|
100
|
+
const OCEAN_RESOURCES = [29, 79, 1, 18, 6, 1003, 1002]
|
|
101
|
+
const INDUSTRIAL_RESOURCES = [26, 13, 24, 29, 79, 1000, 1001]
|
|
102
|
+
|
|
103
|
+
export function getLocationCandidates(locationType: number, subtype: number): number[] {
|
|
104
|
+
if (locationType === 2) return ASTEROID_RESOURCES
|
|
105
|
+
if (locationType === 3) return NEBULA_RESOURCES
|
|
106
|
+
if (locationType === 1) {
|
|
107
|
+
switch (subtype) {
|
|
108
|
+
case PLANET_SUBTYPE_GAS_GIANT:
|
|
109
|
+
return GAS_GIANT_RESOURCES
|
|
110
|
+
case PLANET_SUBTYPE_ROCKY:
|
|
111
|
+
return ROCKY_RESOURCES
|
|
112
|
+
case PLANET_SUBTYPE_TERRESTRIAL:
|
|
113
|
+
return TERRESTRIAL_RESOURCES
|
|
114
|
+
case PLANET_SUBTYPE_ICY:
|
|
115
|
+
return ICY_RESOURCES
|
|
116
|
+
case PLANET_SUBTYPE_OCEAN:
|
|
117
|
+
return OCEAN_RESOURCES
|
|
118
|
+
case PLANET_SUBTYPE_INDUSTRIAL:
|
|
119
|
+
return INDUSTRIAL_RESOURCES
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return []
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function getEligibleResources(
|
|
126
|
+
locationType: number,
|
|
127
|
+
subtype: number,
|
|
128
|
+
stratum: number
|
|
129
|
+
): number[] {
|
|
130
|
+
const candidates = getLocationCandidates(locationType, subtype)
|
|
131
|
+
return candidates.filter((itemId) => {
|
|
132
|
+
const tier = getResourceTier(itemId)
|
|
133
|
+
const threshold = getDepthThreshold(tier)
|
|
134
|
+
return stratum >= threshold
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function depthScaleFactor(stratum: number): number {
|
|
139
|
+
if (stratum <= 1) return 1.0
|
|
140
|
+
const logScale = Math.log(stratum) / Math.log(65535)
|
|
141
|
+
return 1.0 + logScale * 2.0
|
|
142
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import type {ResourceCategory} from '../types'
|
|
2
|
+
|
|
3
|
+
export interface StatDefinition {
|
|
4
|
+
key: string
|
|
5
|
+
label: string
|
|
6
|
+
abbreviation: string
|
|
7
|
+
purpose: string
|
|
8
|
+
inverted?: boolean
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const METAL_STATS: StatDefinition[] = [
|
|
12
|
+
{
|
|
13
|
+
key: 'strength',
|
|
14
|
+
label: 'Strength',
|
|
15
|
+
abbreviation: 'STR',
|
|
16
|
+
purpose: 'Raw structural/mechanical force',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
key: 'tolerance',
|
|
20
|
+
label: 'Tolerance',
|
|
21
|
+
abbreviation: 'TOL',
|
|
22
|
+
purpose: 'Ability to withstand heat, pressure, and stress extremes',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
key: 'density',
|
|
26
|
+
label: 'Density',
|
|
27
|
+
abbreviation: 'DEN',
|
|
28
|
+
purpose: 'Mass per unit',
|
|
29
|
+
inverted: true,
|
|
30
|
+
},
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
const PRECIOUS_STATS: StatDefinition[] = [
|
|
34
|
+
{
|
|
35
|
+
key: 'conductivity',
|
|
36
|
+
label: 'Conductivity',
|
|
37
|
+
abbreviation: 'CON',
|
|
38
|
+
purpose: 'Efficiency of energy/signal transfer',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
key: 'ductility',
|
|
42
|
+
label: 'Ductility',
|
|
43
|
+
abbreviation: 'DUC',
|
|
44
|
+
purpose: 'Ability to be worked into fine, precise shapes',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
key: 'reflectivity',
|
|
48
|
+
label: 'Reflectivity',
|
|
49
|
+
abbreviation: 'REF',
|
|
50
|
+
purpose: 'Surface quality for heat management and precision optics',
|
|
51
|
+
},
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
const GAS_STATS: StatDefinition[] = [
|
|
55
|
+
{
|
|
56
|
+
key: 'volatility',
|
|
57
|
+
label: 'Volatility',
|
|
58
|
+
abbreviation: 'VOL',
|
|
59
|
+
purpose: 'Energy release potential for propulsion and force',
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
key: 'reactivity',
|
|
63
|
+
label: 'Reactivity',
|
|
64
|
+
abbreviation: 'REA',
|
|
65
|
+
purpose: 'Chemical interaction speed for processing and penetration',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
key: 'thermal',
|
|
69
|
+
label: 'Thermal',
|
|
70
|
+
abbreviation: 'THM',
|
|
71
|
+
purpose: 'Heat capacity for thermal management',
|
|
72
|
+
},
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
const MINERAL_STATS: StatDefinition[] = [
|
|
76
|
+
{
|
|
77
|
+
key: 'resonance',
|
|
78
|
+
label: 'Resonance',
|
|
79
|
+
abbreviation: 'RES',
|
|
80
|
+
purpose: 'Energy field interaction — storage, focusing, projection',
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
key: 'hardness',
|
|
84
|
+
label: 'Hardness',
|
|
85
|
+
abbreviation: 'HRD',
|
|
86
|
+
purpose: 'Resistance to wear — cutting surfaces, penetration',
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
key: 'clarity',
|
|
90
|
+
label: 'Clarity',
|
|
91
|
+
abbreviation: 'CLR',
|
|
92
|
+
purpose: 'Crystalline perfection — precision optics',
|
|
93
|
+
},
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
const ORGANIC_STATS: StatDefinition[] = [
|
|
97
|
+
{
|
|
98
|
+
key: 'plasticity',
|
|
99
|
+
label: 'Plasticity',
|
|
100
|
+
abbreviation: 'PLA',
|
|
101
|
+
purpose: 'Ease of reshaping — speeds processing',
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
key: 'insulation',
|
|
105
|
+
label: 'Insulation',
|
|
106
|
+
abbreviation: 'INS',
|
|
107
|
+
purpose: 'Energy containment — reduces energy loss',
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
key: 'purity',
|
|
111
|
+
label: 'Purity',
|
|
112
|
+
abbreviation: 'PUR',
|
|
113
|
+
purpose: 'Biological cleanliness — better composites and lubricants',
|
|
114
|
+
},
|
|
115
|
+
]
|
|
116
|
+
|
|
117
|
+
const STAT_MAP: Record<ResourceCategory, StatDefinition[]> = {
|
|
118
|
+
metal: METAL_STATS,
|
|
119
|
+
precious: PRECIOUS_STATS,
|
|
120
|
+
gas: GAS_STATS,
|
|
121
|
+
mineral: MINERAL_STATS,
|
|
122
|
+
organic: ORGANIC_STATS,
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function getStatDefinitions(category: ResourceCategory): StatDefinition[] {
|
|
126
|
+
return STAT_MAP[category]
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function getStatName(category: ResourceCategory, index: 0 | 1 | 2): StatDefinition {
|
|
130
|
+
return STAT_MAP[category][index]
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface NamedStats {
|
|
134
|
+
definitions: StatDefinition[]
|
|
135
|
+
values: [number, number, number]
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function resolveStats(
|
|
139
|
+
category: ResourceCategory,
|
|
140
|
+
stats: {stat1: number; stat2: number; stat3: number}
|
|
141
|
+
): NamedStats {
|
|
142
|
+
return {
|
|
143
|
+
definitions: STAT_MAP[category],
|
|
144
|
+
values: [stats.stat1, stats.stat2, stats.stat3],
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import {Bytes, Checksum256, Checksum256Type} from '@wharfkit/antelope'
|
|
2
|
+
import {hash512} from '../utils/hash'
|
|
3
|
+
import {Coordinates, CoordinatesType} from '../types'
|
|
4
|
+
import {
|
|
5
|
+
depthScaleFactor,
|
|
6
|
+
getEligibleResources,
|
|
7
|
+
getResourceWeight,
|
|
8
|
+
YIELD_THRESHOLD,
|
|
9
|
+
} from './resources'
|
|
10
|
+
|
|
11
|
+
export interface StratumInfo {
|
|
12
|
+
itemId: number
|
|
13
|
+
seed: bigint
|
|
14
|
+
richness: number
|
|
15
|
+
reserve: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ResourceStats {
|
|
19
|
+
stat1: number
|
|
20
|
+
stat2: number
|
|
21
|
+
stat3: number
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function deriveStratum(
|
|
25
|
+
epochSeed: Checksum256Type,
|
|
26
|
+
coords: CoordinatesType,
|
|
27
|
+
stratum: number,
|
|
28
|
+
locationType: number,
|
|
29
|
+
subtype: number,
|
|
30
|
+
_maxDepth: number
|
|
31
|
+
): StratumInfo {
|
|
32
|
+
const seed = Checksum256.from(epochSeed)
|
|
33
|
+
const c = Coordinates.from(coords)
|
|
34
|
+
const input = `stratum-${c.x}-${c.y}-${stratum}`
|
|
35
|
+
const hashResult = hash512(seed, input)
|
|
36
|
+
const bytes = hashResult.array
|
|
37
|
+
|
|
38
|
+
const rawReserve = ((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]) >>> 0
|
|
39
|
+
|
|
40
|
+
let reserve = 0
|
|
41
|
+
if (rawReserve <= YIELD_THRESHOLD) {
|
|
42
|
+
const baseReserve = (rawReserve % 333) + 1
|
|
43
|
+
const scale = depthScaleFactor(stratum)
|
|
44
|
+
reserve = Math.floor(baseReserve * scale)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (reserve === 0) return {itemId: 0, seed: 0n, richness: 0, reserve: 0}
|
|
48
|
+
|
|
49
|
+
const eligible = getEligibleResources(locationType, subtype, stratum)
|
|
50
|
+
if (eligible.length === 0) return {itemId: 0, seed: 0n, richness: 0, reserve: 0}
|
|
51
|
+
|
|
52
|
+
const resourceRoll = ((bytes[4] << 24) | (bytes[5] << 16) | (bytes[6] << 8) | bytes[7]) >>> 0
|
|
53
|
+
|
|
54
|
+
let totalWeight = 0
|
|
55
|
+
for (const id of eligible) {
|
|
56
|
+
totalWeight += getResourceWeight(id, stratum)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let selectedItemId = eligible[0]
|
|
60
|
+
if (totalWeight > 0) {
|
|
61
|
+
const roll = resourceRoll % totalWeight
|
|
62
|
+
let cumulative = 0
|
|
63
|
+
for (const id of eligible) {
|
|
64
|
+
cumulative += getResourceWeight(id, stratum)
|
|
65
|
+
if (roll < cumulative) {
|
|
66
|
+
selectedItemId = id
|
|
67
|
+
break
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const seedBigInt =
|
|
73
|
+
(BigInt(bytes[8]) << 56n) |
|
|
74
|
+
(BigInt(bytes[9]) << 48n) |
|
|
75
|
+
(BigInt(bytes[10]) << 40n) |
|
|
76
|
+
(BigInt(bytes[11]) << 32n) |
|
|
77
|
+
(BigInt(bytes[12]) << 24n) |
|
|
78
|
+
(BigInt(bytes[13]) << 16n) |
|
|
79
|
+
(BigInt(bytes[14]) << 8n) |
|
|
80
|
+
BigInt(bytes[15])
|
|
81
|
+
|
|
82
|
+
const rawRichness = (bytes[16] << 8) | bytes[17]
|
|
83
|
+
const normalized = rawRichness / 65535
|
|
84
|
+
const baseRichness = Math.floor(normalized * normalized * 999) + 1
|
|
85
|
+
|
|
86
|
+
let depthBonus = 0
|
|
87
|
+
if (stratum > 1) {
|
|
88
|
+
depthBonus = (50 * Math.log(stratum)) / Math.log(65535)
|
|
89
|
+
}
|
|
90
|
+
const richness = Math.min(Math.floor(baseRichness + depthBonus), 1000)
|
|
91
|
+
|
|
92
|
+
return {itemId: selectedItemId, seed: seedBigInt, richness, reserve}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function deriveResourceStats(seed: bigint): ResourceStats {
|
|
96
|
+
const seedBytes = new Uint8Array(8)
|
|
97
|
+
for (let i = 7; i >= 0; i--) {
|
|
98
|
+
seedBytes[i] = Number(seed & 0xffn)
|
|
99
|
+
seed >>= 8n
|
|
100
|
+
}
|
|
101
|
+
const hashResult = Checksum256.hash(Bytes.from(seedBytes))
|
|
102
|
+
const hashBytes = hashResult.array
|
|
103
|
+
|
|
104
|
+
const extractU16 = (offset: number): number => (hashBytes[offset] << 8) | hashBytes[offset + 1]
|
|
105
|
+
|
|
106
|
+
const weibull = (raw: number): number => {
|
|
107
|
+
const u = raw / 65536
|
|
108
|
+
let x = 0.27 * Math.sqrt(-Math.log(1 - u))
|
|
109
|
+
if (x > 1) x = 1
|
|
110
|
+
return Math.floor(x * 999) + 1
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
stat1: weibull(extractU16(0)),
|
|
115
|
+
stat2: weibull(extractU16(2)),
|
|
116
|
+
stat3: weibull(extractU16(4)),
|
|
117
|
+
}
|
|
118
|
+
}
|