@shipload/sdk 1.0.0-next.62 → 1.0.0-next.63

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.
Files changed (51) hide show
  1. package/lib/shipload.d.ts +1172 -29
  2. package/lib/shipload.js +3877 -1294
  3. package/lib/shipload.js.map +1 -1
  4. package/lib/shipload.m.js +3781 -1298
  5. package/lib/shipload.m.js.map +1 -1
  6. package/lib/testing.d.ts +391 -13
  7. package/lib/testing.js +1554 -114
  8. package/lib/testing.js.map +1 -1
  9. package/lib/testing.m.js +1555 -115
  10. package/lib/testing.m.js.map +1 -1
  11. package/package.json +2 -2
  12. package/src/contracts/fund.ts +201 -0
  13. package/src/contracts/platform.ts +73 -1
  14. package/src/contracts/server.ts +946 -26
  15. package/src/contracts.ts +1 -0
  16. package/src/data/charters.json +198 -0
  17. package/src/data/entities.json +36 -0
  18. package/src/data/item-ids.ts +2 -0
  19. package/src/data/items.json +12 -0
  20. package/src/data/kind-registry.json +20 -3
  21. package/src/data/kind-registry.ts +5 -0
  22. package/src/data/metadata.ts +14 -0
  23. package/src/data/recipes.json +0 -48
  24. package/src/derivation/capabilities.ts +14 -0
  25. package/src/derivation/recipe-usage.test.ts +0 -2
  26. package/src/format.ts +9 -0
  27. package/src/index-module.ts +24 -1
  28. package/src/influence/categories.ts +32 -0
  29. package/src/influence/charters.test.ts +126 -0
  30. package/src/influence/charters.ts +120 -0
  31. package/src/influence/citizenry.ts +31 -0
  32. package/src/influence/constants.ts +63 -0
  33. package/src/influence/decay.ts +20 -0
  34. package/src/influence/decomp.ts +95 -0
  35. package/src/influence/demand.ts +106 -0
  36. package/src/influence/duration.ts +36 -0
  37. package/src/influence/index.ts +63 -0
  38. package/src/influence/influence.test.ts +291 -0
  39. package/src/influence/quality.ts +25 -0
  40. package/src/influence/valuation.ts +154 -0
  41. package/src/managers/actions.ts +135 -0
  42. package/src/managers/construction.ts +11 -1
  43. package/src/managers/context.ts +9 -0
  44. package/src/managers/entities.ts +11 -0
  45. package/src/managers/index.ts +12 -0
  46. package/src/managers/influence.test.ts +58 -0
  47. package/src/managers/influence.ts +347 -0
  48. package/src/nft/description.ts +7 -0
  49. package/src/scheduling/task-cargo.ts +1 -0
  50. package/src/shipload.ts +5 -0
  51. package/src/types.ts +23 -4
@@ -13,6 +13,18 @@ export type {GridCell, ClusterCell, Cluster} from './cluster'
13
13
  export {NftManager} from './nft'
14
14
  export type {NftConfigForItem} from './nft'
15
15
  export {JobsManager} from './jobs'
16
+ export {InfluenceManager} from './influence'
17
+ export type {
18
+ CharterProgress,
19
+ ContributePreview,
20
+ ContributePreviewRow,
21
+ FoundedWorld,
22
+ InfluenceStanding,
23
+ PendingBallot,
24
+ VoteCast,
25
+ VoteOption,
26
+ VoteStandings,
27
+ } from './influence'
16
28
  export {PlotManager} from './plot'
17
29
  export type {PlotProgress, PlotProgressInputRow} from './plot'
18
30
  export {ConstructionManager} from './construction'
@@ -0,0 +1,58 @@
1
+ import {describe, expect, it} from 'bun:test'
2
+ import {Name, UInt64} from '@wharfkit/antelope'
3
+ import {InfluenceManager} from './influence'
4
+ import {coordsToLocationId} from '../types'
5
+
6
+ const LOCATION = {x: 12, y: 34}
7
+
8
+ function managerWith(rowsByTable: Record<string, unknown[]>) {
9
+ const calls: {table: string; scope: unknown}[] = []
10
+ const table = (name: string, scope: unknown) => {
11
+ calls.push({table: name, scope})
12
+ return {all: async () => rowsByTable[name] ?? []}
13
+ }
14
+ const ctx = {server: {table}} as never
15
+ return {manager: new InfluenceManager(ctx), calls}
16
+ }
17
+
18
+ describe('InfluenceManager scoped table reads', () => {
19
+ it('getVoteCasts scopes infvote by location and maps fields', async () => {
20
+ const {manager, calls} = managerWith({
21
+ infvote: [{account: Name.from('eggmaple.gm'), epoch: 5, node_id: 2, weight: '100'}],
22
+ })
23
+ const rows = await manager.getVoteCasts(LOCATION)
24
+ expect(calls).toHaveLength(1)
25
+ expect(calls[0].table).toBe('infvote')
26
+ expect(UInt64.from(calls[0].scope as never).equals(coordsToLocationId(LOCATION))).toBe(true)
27
+ expect(rows).toEqual([
28
+ {account: Name.from('eggmaple.gm'), epoch: 5, nodeId: 2, weight: 100n},
29
+ ])
30
+ })
31
+
32
+ it('getVoteTallies scopes inftally by location and maps fields', async () => {
33
+ const {manager, calls} = managerWith({
34
+ inftally: [{node_id: 3, total: '250'}],
35
+ })
36
+ const rows = await manager.getVoteTallies(LOCATION)
37
+ expect(calls[0].table).toBe('inftally')
38
+ expect(UInt64.from(calls[0].scope as never).equals(coordsToLocationId(LOCATION))).toBe(true)
39
+ expect(rows).toEqual([{nodeId: 3, total: 250n}])
40
+ })
41
+
42
+ it('getBuiltCharters scopes charters by location and maps fields', async () => {
43
+ const {manager, calls} = managerWith({
44
+ charters: [{node_id: 1, entity_id: '9001'}],
45
+ })
46
+ const rows = await manager.getBuiltCharters(LOCATION)
47
+ expect(calls[0].table).toBe('charters')
48
+ expect(UInt64.from(calls[0].scope as never).equals(coordsToLocationId(LOCATION))).toBe(true)
49
+ expect(rows).toEqual([{nodeId: 1, entityId: 9001n}])
50
+ })
51
+
52
+ it('returns an empty array when the scope has no rows', async () => {
53
+ const {manager} = managerWith({})
54
+ expect(await manager.getVoteCasts(LOCATION)).toEqual([])
55
+ expect(await manager.getVoteTallies(LOCATION)).toEqual([])
56
+ expect(await manager.getBuiltCharters(LOCATION)).toEqual([])
57
+ })
58
+ })
@@ -0,0 +1,347 @@
1
+ import {Int64, Name, type NameType, UInt64} from '@wharfkit/antelope'
2
+ import {BaseManager} from './base'
3
+ import type {ServerContract} from '../contracts'
4
+ import {coordsToLocationId, locationIdToCoords, type CoordinatesType} from '../types'
5
+ import {
6
+ type BuiltCharter,
7
+ citizenryName,
8
+ contributeDuration,
9
+ decayActive,
10
+ deriveDemand,
11
+ pricingFromWeights,
12
+ valueCargoItem,
13
+ type DemandTriple,
14
+ type DemandView,
15
+ type ValuedItem,
16
+ } from '../influence'
17
+ import {getItem} from '../data/catalog'
18
+
19
+ export interface InfluenceStanding {
20
+ epoch: number
21
+ playerLifetime: bigint
22
+ playerActive: bigint
23
+ standingLifetime: bigint
24
+ standingActive: bigint
25
+ locationLifetime: bigint
26
+ locationActive: bigint
27
+ watermark: bigint
28
+ mandate: number
29
+ founder: Name
30
+ founded: number
31
+ }
32
+
33
+ export interface CharterProgress {
34
+ locationId: bigint
35
+ lifetime: bigint
36
+ watermark: bigint
37
+ surplus: bigint
38
+ mandate: number
39
+ nextCost: bigint
40
+ prereqsMet: boolean
41
+ buildable: boolean
42
+ epoch: number
43
+ built: {nodeId: number; completedEpoch: number; entityId: bigint}[]
44
+ }
45
+
46
+ export interface ContributePreviewRow {
47
+ itemId: number
48
+ quantity: number
49
+ stats: bigint
50
+ valueAtomic: bigint
51
+ massKg: number
52
+ }
53
+
54
+ export interface ContributePreview {
55
+ rows: ContributePreviewRow[]
56
+ totalAtomic: bigint
57
+ totalMassKg: number
58
+ durationSeconds: number
59
+ demand: DemandView
60
+ }
61
+
62
+ export interface FoundedWorld {
63
+ locationId: bigint
64
+ x: number
65
+ y: number
66
+ lifetime: bigint
67
+ active: bigint
68
+ watermark: bigint
69
+ chosen: number
70
+ chosenEpoch: number
71
+ mandate: number | undefined
72
+ founder: Name
73
+ founded: number
74
+ }
75
+
76
+ export interface VoteOption {
77
+ nodeId: number
78
+ cost: bigint
79
+ eligible: boolean
80
+ total: bigint | undefined
81
+ }
82
+
83
+ export interface VoteStandings {
84
+ locationId: bigint
85
+ epoch: number
86
+ chosen: number
87
+ chosenEpoch: number
88
+ mandate: number
89
+ ballotEpoch: number | undefined
90
+ talliesSuppressed: boolean
91
+ options: VoteOption[]
92
+ }
93
+
94
+ export interface VoteCast {
95
+ account: Name
96
+ epoch: number
97
+ nodeId: number
98
+ weight: bigint
99
+ }
100
+
101
+ export interface PendingBallot {
102
+ locationId: bigint
103
+ x: number
104
+ y: number
105
+ epoch: number
106
+ }
107
+
108
+ function big(value: unknown): bigint {
109
+ return BigInt(String(value))
110
+ }
111
+
112
+ export class InfluenceManager extends BaseManager {
113
+ async getStanding(owner: NameType, location: CoordinatesType): Promise<InfluenceStanding> {
114
+ const result = (await this.server.readonly('getinfluence', {
115
+ owner: Name.from(owner),
116
+ x: Int64.from(location.x),
117
+ y: Int64.from(location.y),
118
+ })) as ServerContract.Types.influence_totals
119
+
120
+ return {
121
+ epoch: Number(result.epoch),
122
+ playerLifetime: big(result.player_lifetime),
123
+ playerActive: big(result.player_active),
124
+ standingLifetime: big(result.standing_lifetime),
125
+ standingActive: big(result.standing_active),
126
+ locationLifetime: big(result.location_lifetime),
127
+ locationActive: big(result.location_active),
128
+ watermark: big(result.watermark),
129
+ mandate: Number(result.mandate),
130
+ founder: Name.from(result.founder),
131
+ founded: Number(result.founded),
132
+ }
133
+ }
134
+
135
+ async getDemandConfig(): Promise<DemandTriple> {
136
+ const row = (await this.server.table('infdemand').get()) as
137
+ | ServerContract.Types.infdemand_row
138
+ | undefined
139
+ if (!row) throw new Error('influence demand config is unset')
140
+ return {peak: big(row.peak), base: big(row.base), floor: big(row.floor)}
141
+ }
142
+
143
+ async getQualityDivisor(): Promise<number> {
144
+ const row = (await this.server.table('infquality').get()) as
145
+ | ServerContract.Types.infquality_row
146
+ | undefined
147
+ if (!row) throw new Error('influence quality config is unset')
148
+ return Number(row.d1)
149
+ }
150
+
151
+ async getWeights(): Promise<ServerContract.Types.infweight_row[]> {
152
+ return (await this.server.table('infweight').all()) as ServerContract.Types.infweight_row[]
153
+ }
154
+
155
+ async getDemand(location: CoordinatesType): Promise<DemandView> {
156
+ const game = await this.getGame()
157
+ const state = await this.getState()
158
+ const triple = await this.getDemandConfig()
159
+ return deriveDemand({
160
+ gameSeed: game.config.seed,
161
+ epochSeed: state.epochSeed,
162
+ coordinates: location,
163
+ triple,
164
+ })
165
+ }
166
+
167
+ async getCitizenryName(location: CoordinatesType): Promise<string | undefined> {
168
+ const game = await this.getGame()
169
+ return citizenryName(game.config.seed, location)
170
+ }
171
+
172
+ async previewContribution(
173
+ location: CoordinatesType,
174
+ bundle: ValuedItem[],
175
+ opts: {altitudeZ?: number} = {}
176
+ ): Promise<ContributePreview> {
177
+ const [demand, d1, weights] = await Promise.all([
178
+ this.getDemand(location),
179
+ this.getQualityDivisor(),
180
+ this.getWeights(),
181
+ ])
182
+ const pricing = pricingFromWeights(
183
+ d1,
184
+ weights.map((w) => ({
185
+ category: Number(w.category),
186
+ tier: Number(w.tier),
187
+ weightFp: big(w.weight_fp),
188
+ }))
189
+ )
190
+
191
+ let totalAtomic = 0n
192
+ let totalMassKg = 0
193
+ const rows = bundle.map((item) => {
194
+ const valueAtomic = valueCargoItem(item, demand, pricing)
195
+ const massKg = getItem(item.itemId).mass * item.quantity
196
+ totalAtomic += valueAtomic
197
+ totalMassKg += massKg
198
+ return {...item, valueAtomic, massKg}
199
+ })
200
+
201
+ return {
202
+ rows,
203
+ totalAtomic,
204
+ totalMassKg,
205
+ durationSeconds: contributeDuration(totalMassKg, opts.altitudeZ ?? 0),
206
+ demand,
207
+ }
208
+ }
209
+
210
+ async getCharter(location: CoordinatesType): Promise<CharterProgress> {
211
+ const result = (await this.server.readonly('getcharter', {
212
+ x: Int64.from(location.x),
213
+ y: Int64.from(location.y),
214
+ })) as ServerContract.Types.charter_result
215
+
216
+ return {
217
+ locationId: big(result.location_id),
218
+ lifetime: big(result.lifetime),
219
+ watermark: big(result.watermark),
220
+ surplus: big(result.surplus),
221
+ mandate: Number(result.mandate),
222
+ nextCost: big(result.next_cost),
223
+ prereqsMet: Boolean(result.prereqs_met),
224
+ buildable: Boolean(result.buildable),
225
+ epoch: Number(result.epoch),
226
+ built: result.built.map((b) => ({
227
+ nodeId: Number(b.node_id),
228
+ completedEpoch: Number(b.completed_epoch),
229
+ entityId: big(b.entity_id),
230
+ })),
231
+ }
232
+ }
233
+
234
+ async getPools(): Promise<ServerContract.Types.pool_view[]> {
235
+ const result = (await this.server.readonly('getpools')) as ServerContract.Types.pools_result
236
+ return result.pools
237
+ }
238
+
239
+ async getPool(category: number, tier: number): Promise<ServerContract.Types.pool_pair_result> {
240
+ return (await this.server.readonly('getpool', {
241
+ category,
242
+ tier,
243
+ })) as ServerContract.Types.pool_pair_result
244
+ }
245
+
246
+ async getMintConfig(): Promise<ServerContract.Types.mintcfg_result> {
247
+ return (await this.server.readonly('getmintcfg')) as ServerContract.Types.mintcfg_result
248
+ }
249
+
250
+ async getVotes(location: CoordinatesType): Promise<VoteStandings> {
251
+ const result = (await this.server.readonly('getvotes', {
252
+ x: Int64.from(location.x),
253
+ y: Int64.from(location.y),
254
+ })) as ServerContract.Types.vote_result
255
+
256
+ const epoch = Number(result.epoch)
257
+ const rawBallotEpoch = Number(result.ballot_epoch)
258
+ const ballotEpoch = rawBallotEpoch === 0 ? undefined : rawBallotEpoch
259
+ const talliesSuppressed = ballotEpoch !== undefined && ballotEpoch < epoch
260
+
261
+ return {
262
+ locationId: big(result.location_id),
263
+ epoch,
264
+ chosen: Number(result.chosen),
265
+ chosenEpoch: Number(result.chosen_epoch),
266
+ mandate: Number(result.effective),
267
+ ballotEpoch,
268
+ talliesSuppressed,
269
+ options: result.options.map((option) => ({
270
+ nodeId: Number(option.node_id),
271
+ cost: big(option.cost),
272
+ eligible: Boolean(option.eligible),
273
+ total: talliesSuppressed ? undefined : big(option.total),
274
+ })),
275
+ }
276
+ }
277
+
278
+ async getVoteCasts(location: CoordinatesType): Promise<VoteCast[]> {
279
+ const rows = (await this.server
280
+ .table('infvote', coordsToLocationId(location))
281
+ .all()) as ServerContract.Types.infvote_row[]
282
+ return rows.map((row) => ({
283
+ account: Name.from(row.account),
284
+ epoch: Number(row.epoch),
285
+ nodeId: Number(row.node_id),
286
+ weight: big(row.weight),
287
+ }))
288
+ }
289
+
290
+ async getVoteTallies(location: CoordinatesType): Promise<{nodeId: number; total: bigint}[]> {
291
+ const rows = (await this.server
292
+ .table('inftally', coordsToLocationId(location))
293
+ .all()) as ServerContract.Types.inftally_row[]
294
+ return rows.map((row) => ({nodeId: Number(row.node_id), total: big(row.total)}))
295
+ }
296
+
297
+ async getBuiltCharters(location: CoordinatesType): Promise<BuiltCharter[]> {
298
+ const rows = (await this.server
299
+ .table('charters', coordsToLocationId(location))
300
+ .all()) as ServerContract.Types.charters_row[]
301
+ return rows.map((row) => ({nodeId: Number(row.node_id), entityId: big(row.entity_id)}))
302
+ }
303
+
304
+ async getBallotQueue(): Promise<PendingBallot[]> {
305
+ const rows = (await this.server
306
+ .table('infballot')
307
+ .all()) as ServerContract.Types.infballot_row[]
308
+ return rows.map((row) => ({
309
+ locationId: big(row.location_id),
310
+ ...locationIdToCoords(row.location_id),
311
+ epoch: Number(row.epoch),
312
+ }))
313
+ }
314
+
315
+ async getFoundedWorlds(opts: {withMandate?: boolean} = {}): Promise<FoundedWorld[]> {
316
+ const rows = (await this.server.table('infloc').all()) as ServerContract.Types.infloc_row[]
317
+ const epoch = (await this.getState()).epoch
318
+ const worlds = rows.map((row) => ({
319
+ locationId: big(row.location_id),
320
+ ...locationIdToCoords(row.location_id),
321
+ lifetime: big(row.lifetime),
322
+ active: decayActive(
323
+ big(row.active),
324
+ Math.max(0, Number(epoch) - Number(row.last_update_epoch))
325
+ ),
326
+ watermark: big(row.watermark),
327
+ chosen: Number(row.chosen),
328
+ chosenEpoch: Number(row.chosen_epoch),
329
+ mandate: undefined as number | undefined,
330
+ founder: Name.from(row.founder),
331
+ founded: Number(row.founded),
332
+ }))
333
+
334
+ if (!opts.withMandate) return worlds
335
+
336
+ for (const world of worlds) {
337
+ world.mandate = (await this.getCharter({x: world.x, y: world.y})).mandate
338
+ }
339
+ return worlds
340
+ }
341
+
342
+ async isFounded(location: CoordinatesType): Promise<boolean> {
343
+ const id = coordsToLocationId(location)
344
+ const row = await this.server.table('infloc').get(UInt64.from(id))
345
+ return row !== undefined
346
+ }
347
+ }
@@ -85,6 +85,11 @@ export function computeBaseCapacityWorkshop(stats: bigint): number {
85
85
  return Math.floor(5_000_000 * 6 ** (s / 1998))
86
86
  }
87
87
 
88
+ export function computeBaseCapacityDepot(stats: bigint): number {
89
+ const s = decodeStat(stats, 0) + decodeStat(stats, 2)
90
+ return Math.floor(50_000_000 * 6 ** (s / 1998))
91
+ }
92
+
88
93
  const CAPACITY_FN_BY_KIND: Record<string, (stats: bigint) => number> = {
89
94
  ship: computeBaseCapacityShip,
90
95
  warehouse: computeBaseCapacityWarehouse,
@@ -95,6 +100,8 @@ const CAPACITY_FN_BY_KIND: Record<string, (stats: bigint) => number> = {
95
100
  mdriver: computeBaseCapacityContainer,
96
101
  mcatcher: computeBaseCapacityContainer,
97
102
  container: computeBaseCapacityContainer,
103
+ nexus: computeBaseCapacityContainer,
104
+ depot: computeBaseCapacityDepot,
98
105
  }
99
106
 
100
107
  export function computeBaseCapacityForEntity(itemId: number, stats: bigint): number {
@@ -34,6 +34,7 @@ export function taskCargoChanges(task: ServerContract.Types.task): TaskCargoChan
34
34
  case TaskType.GATHER:
35
35
  return task.couplings.length > 0 ? [] : items.map((i) => toChange(i, 'in'))
36
36
  case TaskType.UNLOAD:
37
+ case TaskType.CONTRIBUTE:
37
38
  return items.map((i) => toChange(i, 'out'))
38
39
  case TaskType.CRAFT:
39
40
  return [
package/src/shipload.ts CHANGED
@@ -13,6 +13,7 @@ import type {ActionsManager} from './managers/actions'
13
13
  import type {ClusterManager} from './managers/cluster'
14
14
  import type {NftManager} from './managers/nft'
15
15
  import type {JobsManager} from './managers/jobs'
16
+ import type {InfluenceManager} from './managers/influence'
16
17
  import type {SubscriptionsManager} from './subscriptions/manager'
17
18
  import type {GameState} from './entities/gamestate'
18
19
 
@@ -137,6 +138,10 @@ export class Shipload {
137
138
  return this._context.jobs
138
139
  }
139
140
 
141
+ get influence(): InfluenceManager {
142
+ return this._context.influence
143
+ }
144
+
140
145
  get subscriptions(): SubscriptionsManager {
141
146
  return this._context.subscriptions
142
147
  }
package/src/types.ts CHANGED
@@ -8,6 +8,7 @@ import {
8
8
  type UInt64Type,
9
9
  } from '@wharfkit/antelope'
10
10
  import {ServerContract} from './contracts'
11
+ import {COORD_MAX, COORD_MIN} from './coordinates/constants'
11
12
 
12
13
  export const PRECISION = 10000
13
14
  export const CRAFT_ENERGY_DIVISOR = 150000
@@ -70,6 +71,7 @@ export enum TaskType {
70
71
  UPGRADE = 17,
71
72
  SHUTTLE = 18,
72
73
  REFIT = 19,
74
+ CONTRIBUTE = 20,
73
75
  }
74
76
 
75
77
  export enum RefitOp {
@@ -124,13 +126,30 @@ export class Coordinates extends ServerContract.Types.coordinates {
124
126
 
125
127
  export function coordsToLocationId(coords: CoordinatesType): UInt64 {
126
128
  const c = Coordinates.from(coords)
127
- const mask = BigInt(0xffffffff)
128
- const x = BigInt(c.x.toNumber()) & mask
129
- const y = BigInt(c.y.toNumber()) & mask
130
- const id = (x << BigInt(32)) | y
129
+ const x = BigInt(c.x.toString())
130
+ const y = BigInt(c.y.toString())
131
+ if (
132
+ x < BigInt(COORD_MIN) ||
133
+ x > BigInt(COORD_MAX) ||
134
+ y < BigInt(COORD_MIN) ||
135
+ y > BigInt(COORD_MAX)
136
+ ) {
137
+ throw new Error('coordinate out of int32 bounds')
138
+ }
139
+ const offset = BigInt(2147483648)
140
+ const id = ((x + offset) << BigInt(32)) | (y + offset)
131
141
  return UInt64.from(id)
132
142
  }
133
143
 
144
+ export function locationIdToCoords(id: UInt64Type): {x: number; y: number} {
145
+ const value = BigInt(UInt64.from(id).toString())
146
+ const offset = BigInt(2147483648)
147
+ return {
148
+ x: Number((value >> BigInt(32)) - offset),
149
+ y: Number((value & BigInt('0xffffffff')) - offset),
150
+ }
151
+ }
152
+
134
153
  export interface Distance {
135
154
  origin: ServerContract.ActionParams.Type.coordinates
136
155
  destination: ServerContract.ActionParams.Type.coordinates