@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.
Files changed (99) hide show
  1. package/lib/shipload.d.ts +1847 -962
  2. package/lib/shipload.js +9088 -4854
  3. package/lib/shipload.js.map +1 -1
  4. package/lib/shipload.m.js +8957 -4805
  5. package/lib/shipload.m.js.map +1 -1
  6. package/lib/testing.d.ts +856 -0
  7. package/lib/testing.js +3739 -0
  8. package/lib/testing.js.map +1 -0
  9. package/lib/testing.m.js +3733 -0
  10. package/lib/testing.m.js.map +1 -0
  11. package/package.json +15 -2
  12. package/src/capabilities/craftable.ts +51 -0
  13. package/src/capabilities/crafting.test.ts +7 -0
  14. package/src/capabilities/crafting.ts +3 -3
  15. package/src/capabilities/gathering.ts +17 -7
  16. package/src/capabilities/index.ts +0 -1
  17. package/src/capabilities/modules.ts +6 -0
  18. package/src/capabilities/storage.ts +16 -1
  19. package/src/contracts/platform.ts +231 -3
  20. package/src/contracts/server.ts +816 -471
  21. package/src/data/capabilities.ts +14 -329
  22. package/src/data/capability-formulas.ts +76 -0
  23. package/src/data/catalog.ts +0 -5
  24. package/src/data/colors.ts +14 -47
  25. package/src/data/entities.json +46 -10
  26. package/src/data/item-ids.ts +15 -12
  27. package/src/data/items.json +302 -38
  28. package/src/data/kind-registry.json +85 -0
  29. package/src/data/kind-registry.ts +150 -0
  30. package/src/data/metadata.ts +100 -31
  31. package/src/data/recipes-runtime.ts +3 -23
  32. package/src/data/recipes.json +250 -113
  33. package/src/derivation/build-methods.ts +45 -0
  34. package/src/derivation/capabilities.ts +415 -0
  35. package/src/derivation/capability-mappings.ts +117 -0
  36. package/src/derivation/crafting.ts +23 -24
  37. package/src/derivation/index.ts +17 -2
  38. package/src/derivation/reserve-regen.ts +34 -0
  39. package/src/derivation/resources.ts +125 -38
  40. package/src/derivation/stars.test.ts +51 -0
  41. package/src/derivation/stars.ts +15 -0
  42. package/src/derivation/stats.ts +6 -6
  43. package/src/derivation/stratum.ts +15 -19
  44. package/src/derivation/tiers.ts +28 -7
  45. package/src/entities/entity.ts +98 -0
  46. package/src/entities/gamestate.ts +3 -28
  47. package/src/entities/makers.ts +91 -136
  48. package/src/entities/slot-multiplier.ts +39 -0
  49. package/src/errors.ts +10 -15
  50. package/src/format.ts +26 -4
  51. package/src/index-module.ts +189 -47
  52. package/src/managers/actions.ts +252 -83
  53. package/src/managers/base.ts +6 -2
  54. package/src/managers/construction-types.ts +79 -0
  55. package/src/managers/construction.ts +396 -0
  56. package/src/managers/context.ts +11 -1
  57. package/src/managers/entities.ts +18 -66
  58. package/src/managers/epochs.ts +40 -0
  59. package/src/managers/index.ts +17 -1
  60. package/src/managers/locations.ts +25 -29
  61. package/src/managers/nft.ts +28 -0
  62. package/src/managers/plot.ts +127 -0
  63. package/src/nft/atomicassets.abi.json +1342 -0
  64. package/src/nft/atomicassets.ts +237 -0
  65. package/src/nft/atomicdata.ts +130 -0
  66. package/src/nft/buildImmutableData.ts +321 -0
  67. package/src/nft/description.ts +37 -15
  68. package/src/nft/index.ts +3 -0
  69. package/src/resolution/describe-module.ts +5 -8
  70. package/src/resolution/display-name.ts +38 -10
  71. package/src/resolution/resolve-item.ts +22 -20
  72. package/src/scheduling/accessor.ts +68 -22
  73. package/src/scheduling/availability.ts +108 -0
  74. package/src/scheduling/energy.ts +48 -0
  75. package/src/scheduling/lane-core.ts +130 -0
  76. package/src/scheduling/lanes.ts +60 -0
  77. package/src/scheduling/projection.ts +121 -94
  78. package/src/scheduling/schedule.ts +237 -103
  79. package/src/scheduling/task-cargo.ts +46 -0
  80. package/src/shipload.ts +16 -1
  81. package/src/subscriptions/manager.ts +40 -6
  82. package/src/subscriptions/mappers.ts +3 -8
  83. package/src/subscriptions/types.ts +3 -2
  84. package/src/testing/catalog-hash.ts +19 -0
  85. package/src/testing/index.ts +2 -0
  86. package/src/testing/projection-parity.ts +143 -0
  87. package/src/travel/travel.ts +90 -13
  88. package/src/types/capabilities.ts +1 -0
  89. package/src/types/index.ts +0 -1
  90. package/src/types.ts +19 -12
  91. package/src/utils/cargo.ts +27 -0
  92. package/src/utils/display-name.ts +61 -0
  93. package/src/utils/system.ts +25 -24
  94. package/src/capabilities/loading.ts +0 -8
  95. package/src/entities/container.ts +0 -108
  96. package/src/entities/ship-deploy.ts +0 -258
  97. package/src/entities/ship.ts +0 -204
  98. package/src/entities/warehouse.ts +0 -119
  99. package/src/types/entity-traits.ts +0 -69
@@ -0,0 +1,143 @@
1
+ import type {UInt16, UInt32} from '@wharfkit/antelope'
2
+ import type {ServerContract} from '../contracts'
3
+ import type {ProjectedEntity} from '../scheduling/projection'
4
+ import {type CargoStack, cargoItemToStack, mergeStacks} from '../capabilities/storage'
5
+
6
+ export interface ContractProjectedState {
7
+ owner: {toString(): string}
8
+ coordinates: ServerContract.Types.coordinates
9
+ energy?: UInt16
10
+ cargomass: UInt32
11
+ cargo: ServerContract.Types.cargo_view[]
12
+ hullmass?: UInt32
13
+ capacity?: UInt32
14
+ engines?: ServerContract.Types.movement_stats
15
+ loaders?: ServerContract.Types.loader_stats
16
+ generator?: ServerContract.Types.energy_stats
17
+ hauler?: ServerContract.Types.hauler_stats
18
+ }
19
+
20
+ export interface ProjectionComparisonOptions {
21
+ step?: number
22
+ }
23
+
24
+ export function assertProjectionEquals(
25
+ contract: ContractProjectedState,
26
+ sdk: ProjectedEntity,
27
+ options: ProjectionComparisonOptions = {}
28
+ ): void {
29
+ const mismatches: string[] = []
30
+
31
+ const record = (name: string, c: unknown, s: unknown) => {
32
+ if (c !== s) mismatches.push(` ${name}: contract=${fmt(c)} sdk=${fmt(s)}`)
33
+ }
34
+
35
+ const recordStatBlock = (name: string, c: unknown, s: unknown) => {
36
+ const cPresent = c !== undefined && c !== null
37
+ const sPresent = s !== undefined && s !== null
38
+ if (cPresent !== sPresent) {
39
+ mismatches.push(
40
+ ` ${name}: contract=${cPresent ? 'present' : 'absent'} sdk=${sPresent ? 'present' : 'absent'}`
41
+ )
42
+ return
43
+ }
44
+ if (!cPresent) return
45
+ const cn = JSON.stringify(normaliseStatBlock(c))
46
+ const sn = JSON.stringify(normaliseStatBlock(s))
47
+ if (cn !== sn) mismatches.push(` ${name}: contract=${cn} sdk=${sn}`)
48
+ }
49
+
50
+ record('coordinates.x', toNum(contract.coordinates.x), Number(sdk.location.x))
51
+ record('coordinates.y', toNum(contract.coordinates.y), Number(sdk.location.y))
52
+ record('energy', toNum(contract.energy), Number(sdk.energy))
53
+ record('cargomass', toNum(contract.cargomass), Number(sdk.cargoMass))
54
+ record('hullmass', toNum(contract.hullmass), Number(sdk.shipMass))
55
+ record('capacity', toNum(contract.capacity), sdk.capacity ? Number(sdk.capacity) : undefined)
56
+
57
+ recordStatBlock('engines', contract.engines, sdk.engines)
58
+ recordStatBlock('loaders', contract.loaders, sdk.loaders)
59
+ recordStatBlock('generator', contract.generator, sdk.generator)
60
+ recordStatBlock('hauler', contract.hauler, sdk.hauler)
61
+
62
+ if (contract.cargo.length > 0 || sdk.cargo.length > 0) {
63
+ const contractCargo = normaliseCargo(mergeContractCargo(contract.cargo))
64
+ const sdkCargo = normaliseCargo(sdk.cargo)
65
+ if (contractCargo.length !== sdkCargo.length) {
66
+ mismatches.push(
67
+ ` cargo.length: contract=${contractCargo.length} sdk=${sdkCargo.length}`
68
+ )
69
+ } else {
70
+ for (let i = 0; i < contractCargo.length; i++) {
71
+ const c = contractCargo[i]
72
+ const s = sdkCargo[i]
73
+ if (c.itemId !== s.itemId || c.stats !== s.stats || c.quantity !== s.quantity) {
74
+ mismatches.push(
75
+ ` cargo[${i}]: contract={item:${c.itemId},stats:${c.stats},qty:${c.quantity}} sdk={item:${s.itemId},stats:${s.stats},qty:${s.quantity}}`
76
+ )
77
+ }
78
+ }
79
+ }
80
+ }
81
+
82
+ if (mismatches.length > 0) {
83
+ const header =
84
+ options.step !== undefined
85
+ ? `projection divergence at step ${options.step}:`
86
+ : 'projection divergence:'
87
+ throw new Error([header, ...mismatches].join('\n'))
88
+ }
89
+ }
90
+
91
+ interface NormalisedStack {
92
+ itemId: number
93
+ stats: string
94
+ quantity: string
95
+ }
96
+
97
+ function mergeContractCargo(cargo: ServerContract.Types.cargo_view[]): CargoStack[] {
98
+ return cargo.reduce<CargoStack[]>(
99
+ (acc, row) =>
100
+ mergeStacks(acc, cargoItemToStack(row as unknown as ServerContract.Types.cargo_item)),
101
+ []
102
+ )
103
+ }
104
+
105
+ function normaliseCargo(cargo: CargoStack[]): NormalisedStack[] {
106
+ return cargo
107
+ .map((s) => ({
108
+ itemId: Number(s.item_id),
109
+ stats: BigInt(s.stats.toString()).toString(),
110
+ quantity: BigInt(s.quantity.toString()).toString(),
111
+ }))
112
+ .sort(stackSort)
113
+ }
114
+
115
+ function stackSort(a: NormalisedStack, b: NormalisedStack): number {
116
+ if (a.itemId !== b.itemId) return a.itemId - b.itemId
117
+ return a.stats < b.stats ? -1 : a.stats > b.stats ? 1 : 0
118
+ }
119
+
120
+ function toNum(v: unknown): number | undefined {
121
+ if (v === undefined || v === null) return undefined
122
+ if (typeof v === 'number') return v
123
+ if (typeof v === 'bigint') return Number(v)
124
+ if (typeof (v as {toNumber?: unknown}).toNumber === 'function') {
125
+ return (v as {toNumber(): number}).toNumber()
126
+ }
127
+ return Number(v as number)
128
+ }
129
+
130
+ function fmt(v: unknown): string {
131
+ if (v === undefined) return 'undefined'
132
+ if (v === null) return 'null'
133
+ return String(v)
134
+ }
135
+
136
+ function normaliseStatBlock(block: unknown): Record<string, number> {
137
+ const out: Record<string, number> = {}
138
+ const obj = block as Record<string, unknown>
139
+ for (const k of Object.keys(obj).sort()) {
140
+ out[k] = toNum(obj[k]) ?? 0
141
+ }
142
+ return out
143
+ }
@@ -28,12 +28,17 @@ import {
28
28
  type Distance,
29
29
  MAX_ORBITAL_ALTITUDE,
30
30
  MIN_ORBITAL_ALTITUDE,
31
+ MIN_TRANSFER_DISTANCE_ORBITAL_VESSEL,
32
+ MIN_TRANSFER_DISTANCE_PLANETARY_STRUCTURE,
31
33
  PRECISION,
32
34
  type ShipLike,
33
35
  TaskType,
34
36
  } from '../types'
37
+ import {EntityClass} from '../data/kind-registry'
35
38
  import {getItem} from '../data/catalog'
36
39
  import {hasSystem} from '../utils/system'
40
+ import * as scheduleModel from '../scheduling/schedule'
41
+ import type {ScheduleData} from '../scheduling/schedule'
37
42
 
38
43
  export function calc_orbital_altitude(mass: number): number {
39
44
  if (mass <= BASE_ORBITAL_MASS) {
@@ -77,6 +82,60 @@ export function lerp(
77
82
  }
78
83
  }
79
84
 
85
+ export interface FloatPosition {
86
+ x: number
87
+ y: number
88
+ }
89
+
90
+ export function easeFlightProgress(t: number): number {
91
+ if (t <= 0) return 0
92
+ if (t >= 1) return 1
93
+ return t < 0.5 ? 2 * t * t : 1 - 2 * (1 - t) * (1 - t)
94
+ }
95
+
96
+ export function flightSpeedFactor(t: number): number {
97
+ if (t <= 0 || t >= 1) return 0
98
+ return t < 0.5 ? 4 * t : 4 * (1 - t)
99
+ }
100
+
101
+ export function interpolateFlightPosition(
102
+ origin: {x: Int64Type | number; y: Int64Type | number},
103
+ destination: {x: Int64Type | number; y: Int64Type | number},
104
+ taskProgress: number,
105
+ options?: {easing?: 'physics' | 'linear'}
106
+ ): FloatPosition {
107
+ const t = options?.easing === 'linear' ? taskProgress : easeFlightProgress(taskProgress)
108
+ return {
109
+ x: (1 - t) * Number(origin.x) + t * Number(destination.x),
110
+ y: (1 - t) * Number(origin.y) + t * Number(destination.y),
111
+ }
112
+ }
113
+
114
+ export function getInterpolatedPosition(
115
+ entity: HasScheduleAndLocation,
116
+ taskIndex: number,
117
+ taskProgress: number
118
+ ): FloatPosition {
119
+ const tasks = mobilityTasks(entity)
120
+ if (tasks.length === 0) {
121
+ return {x: Number(entity.coordinates.x), y: Number(entity.coordinates.y)}
122
+ }
123
+ if (taskIndex < 0) {
124
+ const settled = getFlightOrigin(entity, tasks.length)
125
+ return {x: Number(settled.x), y: Number(settled.y)}
126
+ }
127
+ const task = tasks[taskIndex]
128
+ if (!task.type.equals(TaskType.TRAVEL) || !task.coordinates) {
129
+ const origin = getFlightOrigin(entity, taskIndex)
130
+ return {x: Number(origin.x), y: Number(origin.y)}
131
+ }
132
+ return interpolateFlightPosition(
133
+ getFlightOrigin(entity, taskIndex),
134
+ task.coordinates,
135
+ taskProgress
136
+ )
137
+ }
138
+
80
139
  export function rotation(
81
140
  origin: ServerContract.ActionParams.Type.coordinates,
82
141
  destination: ServerContract.ActionParams.Type.coordinates
@@ -366,6 +425,7 @@ export function hasEnergyForDistance(ship: ShipLike, distance: UInt64Type): bool
366
425
 
367
426
  export interface TransferEntity {
368
427
  location: {z?: {toNumber(): number} | number}
428
+ entityClass: EntityClass
369
429
  loaders?: {
370
430
  thrust: {toNumber(): number} | number
371
431
  mass: {toNumber(): number} | number
@@ -373,20 +433,22 @@ export interface TransferEntity {
373
433
  }
374
434
  }
375
435
 
376
- export interface HasScheduleAndLocation {
436
+ export interface HasScheduleAndLocation extends ScheduleData {
377
437
  coordinates: ServerContract.ActionParams.Type.coordinates
378
- schedule?: ServerContract.Types.schedule
438
+ }
439
+
440
+ function mobilityTasks(entity: HasScheduleAndLocation): ServerContract.Types.task[] {
441
+ return scheduleModel.mobilityLane(entity)?.schedule.tasks ?? []
379
442
  }
380
443
 
381
444
  export function getFlightOrigin(
382
445
  entity: HasScheduleAndLocation,
383
446
  flightTaskIndex: number
384
447
  ): ServerContract.ActionParams.Type.coordinates {
385
- if (!entity.schedule) return entity.coordinates
386
-
448
+ const tasks = mobilityTasks(entity)
387
449
  let origin = entity.coordinates
388
- for (let i = 0; i < flightTaskIndex && i < entity.schedule.tasks.length; i++) {
389
- const task = entity.schedule.tasks[i]
450
+ for (let i = 0; i < flightTaskIndex && i < tasks.length; i++) {
451
+ const task = tasks[i]
390
452
  if (task.type.equals(TaskType.TRAVEL) && task.coordinates) {
391
453
  origin = task.coordinates
392
454
  }
@@ -397,10 +459,9 @@ export function getFlightOrigin(
397
459
  export function getDestinationLocation(
398
460
  entity: HasScheduleAndLocation
399
461
  ): ServerContract.ActionParams.Type.coordinates | undefined {
400
- if (!entity.schedule) return undefined
401
-
402
- for (let i = entity.schedule.tasks.length - 1; i >= 0; i--) {
403
- const task = entity.schedule.tasks[i]
462
+ const tasks = mobilityTasks(entity)
463
+ for (let i = tasks.length - 1; i >= 0; i--) {
464
+ const task = tasks[i]
404
465
  if (task.type.equals(TaskType.TRAVEL) && task.coordinates) {
405
466
  return task.coordinates
406
467
  }
@@ -408,16 +469,21 @@ export function getDestinationLocation(
408
469
  return undefined
409
470
  }
410
471
 
472
+ /** Returns chain-tile coordinates (rounded). For visual position use getInterpolatedPosition. */
411
473
  export function getPositionAt(
412
474
  entity: HasScheduleAndLocation,
413
475
  taskIndex: number,
414
476
  taskProgress: number
415
477
  ): ServerContract.ActionParams.Type.coordinates {
416
- if (!entity.schedule || entity.schedule.tasks.length === 0 || taskIndex < 0) {
478
+ const tasks = mobilityTasks(entity)
479
+ if (tasks.length === 0) {
417
480
  return entity.coordinates
418
481
  }
482
+ if (taskIndex < 0) {
483
+ return getFlightOrigin(entity, tasks.length)
484
+ }
419
485
 
420
- const task = entity.schedule.tasks[taskIndex]
486
+ const task = tasks[taskIndex]
421
487
 
422
488
  if (!task.type.equals(TaskType.TRAVEL) || !task.coordinates) {
423
489
  return getFlightOrigin(entity, taskIndex)
@@ -433,6 +499,12 @@ export function getPositionAt(
433
499
  }
434
500
  }
435
501
 
502
+ export function minTransferDistance(entityClass: EntityClass): number {
503
+ return entityClass === EntityClass.OrbitalVessel
504
+ ? MIN_TRANSFER_DISTANCE_ORBITAL_VESSEL
505
+ : MIN_TRANSFER_DISTANCE_PLANETARY_STRUCTURE
506
+ }
507
+
436
508
  export function calc_transfer_duration(
437
509
  source: TransferEntity,
438
510
  dest: TransferEntity,
@@ -490,7 +562,12 @@ export function calc_transfer_duration(
490
562
  : (source.location.z?.toNumber() ?? 0)
491
563
  const destZ =
492
564
  typeof dest.location.z === 'number' ? dest.location.z : (dest.location.z?.toNumber() ?? 0)
493
- const distance = Math.abs(sourceZ - destZ)
565
+ const rawDistance = Math.abs(sourceZ - destZ)
566
+ const minDistance = Math.max(
567
+ minTransferDistance(source.entityClass),
568
+ minTransferDistance(dest.entityClass)
569
+ )
570
+ const distance = rawDistance < minDistance ? minDistance : rawDistance
494
571
 
495
572
  const totalMass = cargoMass + totalLoaderMass
496
573
  const acceleration = calc_acceleration(totalThrust, totalMass)
@@ -29,6 +29,7 @@ export interface MassCapability {
29
29
  }
30
30
 
31
31
  export interface ScheduleCapability {
32
+ lanes?: ServerContract.Types.lane[]
32
33
  schedule?: ServerContract.Types.schedule
33
34
  }
34
35
 
@@ -1,3 +1,2 @@
1
1
  export * from './capabilities'
2
2
  export * from './entity'
3
- export * from './entity-traits'
package/src/types.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import {
2
2
  type Int64Type,
3
- Name,
4
3
  type UInt16,
5
4
  type UInt16Type,
6
5
  type UInt32,
@@ -12,7 +11,7 @@ import {ServerContract} from './contracts'
12
11
  export const PRECISION = 10000
13
12
  export const CRAFT_ENERGY_DIVISOR = 150000
14
13
 
15
- export const WAREHOUSE_Z = 500
14
+ export const PLANETARY_STRUCTURE_Z = 0
16
15
 
17
16
  export const CONTAINER_Z = 300
18
17
 
@@ -23,6 +22,9 @@ export const MAX_ORBITAL_ALTITUDE = 3000
23
22
 
24
23
  export const BASE_ORBITAL_MASS = 100000
25
24
 
25
+ export const MIN_TRANSFER_DISTANCE_PLANETARY_STRUCTURE = 100
26
+ export const MIN_TRANSFER_DISTANCE_ORBITAL_VESSEL = 300
27
+
26
28
  export interface ShipLike {
27
29
  coordinates: ServerContract.Types.coordinates
28
30
  hullmass?: UInt32
@@ -49,8 +51,12 @@ export enum TaskType {
49
51
  WARP = 6,
50
52
  CRAFT = 7,
51
53
  DEPLOY = 8,
52
- WRAP = 9,
53
54
  UNWRAP = 10,
55
+ UNDEPLOY = 11,
56
+ DEMOLISH = 13,
57
+ CLAIMPLOT = 14,
58
+ BUILDPLOT = 15,
59
+ RESERVED = 16,
54
60
  }
55
61
 
56
62
  export enum LocationType {
@@ -58,6 +64,7 @@ export enum LocationType {
58
64
  PLANET = 1,
59
65
  ASTEROID = 2,
60
66
  NEBULA = 3,
67
+ ICE_FIELD = 4,
61
68
  }
62
69
 
63
70
  export enum TaskCancelable {
@@ -66,14 +73,6 @@ export enum TaskCancelable {
66
73
  ALWAYS = 2,
67
74
  }
68
75
 
69
- export const EntityType = {
70
- SHIP: Name.from('ship'),
71
- WAREHOUSE: Name.from('warehouse'),
72
- CONTAINER: Name.from('container'),
73
- } as const
74
-
75
- export type EntityTypeName = (typeof EntityType)[keyof typeof EntityType]
76
-
77
76
  export type CoordinatesType =
78
77
  | Coordinates
79
78
  | ServerContract.Types.coordinates
@@ -122,8 +121,9 @@ export type ModuleType =
122
121
  | 'launcher'
123
122
  | 'storage'
124
123
  | 'hauler'
124
+ | 'battery'
125
125
 
126
- export const TIER_ADJECTIVES: Record<number, string> = {
126
+ export const RESOURCE_TIER_ADJECTIVES: Record<number, string> = {
127
127
  1: 'Crude',
128
128
  2: 'Dense',
129
129
  3: 'Pure',
@@ -136,6 +136,9 @@ export const TIER_ADJECTIVES: Record<number, string> = {
136
136
  10: 'Ascendant',
137
137
  }
138
138
 
139
+ export const COMPONENT_TIER_PREFIXES: Record<number, string> = {}
140
+ export const MODULE_TIER_PREFIXES: Record<number, string> = {}
141
+
139
142
  export const CATEGORY_LABELS: Record<ResourceCategory, string> = {
140
143
  ore: 'Ore',
141
144
  crystal: 'Crystal',
@@ -159,3 +162,7 @@ export interface Item {
159
162
  export function formatTier(tier: number): string {
160
163
  return 'T' + tier
161
164
  }
165
+
166
+ export function tierAdjective(tier: number): string {
167
+ return RESOURCE_TIER_ADJECTIVES[tier] ?? `T${tier}`
168
+ }
@@ -0,0 +1,27 @@
1
+ import type {ServerContract} from '../contracts'
2
+
3
+ export function cargoRef(src: {
4
+ item_id: number
5
+ stats: bigint | number
6
+ modules?: ServerContract.Types.module_entry[]
7
+ }): ServerContract.ActionParams.Type.cargo_ref {
8
+ return {
9
+ item_id: src.item_id,
10
+ stats: src.stats,
11
+ modules: src.modules ?? [],
12
+ }
13
+ }
14
+
15
+ export function cargoItem(
16
+ src: {
17
+ item_id: number
18
+ stats: bigint | number
19
+ modules?: ServerContract.Types.module_entry[]
20
+ },
21
+ quantity: bigint | number
22
+ ): ServerContract.ActionParams.Type.cargo_item {
23
+ return {
24
+ ...cargoRef(src),
25
+ quantity,
26
+ }
27
+ }
@@ -0,0 +1,61 @@
1
+ export interface DisplayNameResult {
2
+ valid: boolean
3
+ reason?: string
4
+ name: string
5
+ }
6
+
7
+ const MAX_DISPLAY_NAME_BYTES = 32
8
+ const ASCII_SPACE = 0x20
9
+ const ZERO_WIDTH_CODE_POINTS = new Set([0x200b, 0x200c, 0x200d, 0x2060, 0xfeff])
10
+ const textEncoder = new TextEncoder()
11
+
12
+ function isControlCharacter(codePoint: number): boolean {
13
+ return codePoint <= 0x1f || codePoint === 0x7f || (codePoint >= 0x80 && codePoint <= 0x9f)
14
+ }
15
+
16
+ function isLoneSurrogate(codePoint: number): boolean {
17
+ return codePoint >= 0xd800 && codePoint <= 0xdfff
18
+ }
19
+
20
+ function isBidiControl(codePoint: number): boolean {
21
+ return (
22
+ (codePoint >= 0x202a && codePoint <= 0x202e) || (codePoint >= 0x2066 && codePoint <= 0x2069)
23
+ )
24
+ }
25
+
26
+ function isInvalidDisplayNameCodePoint(codePoint: number): boolean {
27
+ return (
28
+ isControlCharacter(codePoint) ||
29
+ isLoneSurrogate(codePoint) ||
30
+ ZERO_WIDTH_CODE_POINTS.has(codePoint) ||
31
+ isBidiControl(codePoint)
32
+ )
33
+ }
34
+
35
+ export function normalizeDisplayName(input: string): string {
36
+ let start = 0
37
+ let end = input.length
38
+
39
+ while (start < end && input.charCodeAt(start) === ASCII_SPACE) start++
40
+ while (end > start && input.charCodeAt(end - 1) === ASCII_SPACE) end--
41
+
42
+ return input.slice(start, end)
43
+ }
44
+
45
+ export function validateDisplayName(input: string): DisplayNameResult {
46
+ const name = normalizeDisplayName(input)
47
+
48
+ if (name.length === 0) return {valid: false, reason: 'empty', name}
49
+ if (textEncoder.encode(name).length > MAX_DISPLAY_NAME_BYTES) {
50
+ return {valid: false, reason: 'too_long', name}
51
+ }
52
+
53
+ for (const character of name) {
54
+ const codePoint = character.codePointAt(0)!
55
+ if (isInvalidDisplayNameCodePoint(codePoint)) {
56
+ return {valid: false, reason: 'invalid_char', name}
57
+ }
58
+ }
59
+
60
+ return {valid: true, name}
61
+ }
@@ -8,7 +8,6 @@ import nebulaAdjectives from '../data/nebula-adjectives.json'
8
8
  import nebulaNouns from '../data/nebula-nouns.json'
9
9
 
10
10
  const LOCATION_EXISTS_THRESHOLD = 0x10
11
- const LOCATION_ACTIVE_THRESHOLD = 0x80
12
11
 
13
12
  export function getLocationType(
14
13
  gameSeed: Checksum256Type,
@@ -22,12 +21,10 @@ export function getLocationType(
22
21
  return LocationType.EMPTY
23
22
  }
24
23
 
25
- if (hashResult.array[1] < 96) {
26
- return LocationType.PLANET
27
- } else if (hashResult.array[1] < 176) {
28
- return LocationType.ASTEROID
29
- }
30
- return LocationType.NEBULA
24
+ if (hashResult.array[1] < 96) return LocationType.PLANET
25
+ if (hashResult.array[1] < 149) return LocationType.ASTEROID
26
+ if (hashResult.array[1] < 202) return LocationType.NEBULA
27
+ return LocationType.ICE_FIELD
31
28
  }
32
29
 
33
30
  export function isGatherableLocation(locationType: LocationType): boolean {
@@ -44,6 +41,8 @@ export function getLocationTypeName(type: LocationType): string {
44
41
  return 'Asteroid'
45
42
  case LocationType.NEBULA:
46
43
  return 'Nebula'
44
+ case LocationType.ICE_FIELD:
45
+ return 'Ice Field'
47
46
  }
48
47
  }
49
48
 
@@ -76,6 +75,15 @@ function generateNebulaName(hashResult: Checksum512): string {
76
75
  return `${nebulaAdjectives[adjIdx]} ${nebulaNouns[nounIdx]}`
77
76
  }
78
77
 
78
+ function generateIceFieldName(hashResult: Checksum512): string {
79
+ const A = 65
80
+ const letter1 = String.fromCharCode(A + (hashResult.array[0] % 26))
81
+ const letter2 = String.fromCharCode(A + (hashResult.array[1] % 26))
82
+ const subId = (hashResult.array[2] % 9) + 1
83
+ const num = (uint16(hashResult, 3) % 9000) + 1000
84
+ return `${letter1}${letter2}-${subId}/${num}`
85
+ }
86
+
79
87
  export function getSystemName(gameSeed: Checksum256Type, location: CoordinatesType): string {
80
88
  const seed = Checksum256.from(gameSeed)
81
89
  const locationType = getLocationType(seed, location)
@@ -91,6 +99,8 @@ export function getSystemName(gameSeed: Checksum256Type, location: CoordinatesTy
91
99
  return generateAsteroidName(hashResult)
92
100
  case LocationType.NEBULA:
93
101
  return generateNebulaName(hashResult)
102
+ case LocationType.ICE_FIELD:
103
+ return generateIceFieldName(hashResult)
94
104
  default:
95
105
  return generatePlanetName(hashResult)
96
106
  }
@@ -123,10 +133,12 @@ export function deriveLocationStatic(
123
133
 
124
134
  if (hashResult.array[1] < 96) {
125
135
  loc.type = UInt8.from(LocationType.PLANET)
126
- } else if (hashResult.array[1] < 176) {
136
+ } else if (hashResult.array[1] < 149) {
127
137
  loc.type = UInt8.from(LocationType.ASTEROID)
128
- } else {
138
+ } else if (hashResult.array[1] < 202) {
129
139
  loc.type = UInt8.from(LocationType.NEBULA)
140
+ } else {
141
+ loc.type = UInt8.from(LocationType.ICE_FIELD)
130
142
  }
131
143
 
132
144
  loc.subtype = UInt8.from(
@@ -138,31 +150,20 @@ export function deriveLocationStatic(
138
150
  return loc
139
151
  }
140
152
 
141
- export function deriveLocationEpoch(
142
- epochSeed: Checksum256Type,
153
+ export function isLocationBuildable(
154
+ gameSeed: Checksum256Type,
143
155
  coordinates: CoordinatesType
144
- ): ServerContract.Types.location_epoch {
145
- const seed = Checksum256.from(epochSeed)
146
- const coords = Coordinates.from(coordinates)
147
- const str = `system-epoch-${coords.x}-${coords.y}`
148
- const hashResult = hash512(seed, str)
149
-
150
- return ServerContract.Types.location_epoch.from({
151
- active: hashResult.array[0] < LOCATION_ACTIVE_THRESHOLD,
152
- seed0: hashResult.array[1],
153
- seed1: hashResult.array[2],
154
- })
156
+ ): boolean {
157
+ return getLocationType(gameSeed, coordinates) === LocationType.PLANET
155
158
  }
156
159
 
157
160
  export function deriveLocation(
158
161
  gameSeed: Checksum256Type,
159
- epochSeed: Checksum256Type,
160
162
  coordinates: CoordinatesType
161
163
  ): ServerContract.Types.location_derived {
162
164
  const staticProps = deriveLocationStatic(gameSeed, coordinates)
163
165
  return ServerContract.Types.location_derived.from({
164
166
  static_props: staticProps,
165
- epoch_props: deriveLocationEpoch(epochSeed, coordinates),
166
167
  size: deriveLocationSize(staticProps),
167
168
  })
168
169
  }
@@ -1,8 +0,0 @@
1
- import {UInt32, type UInt64} from '@wharfkit/antelope'
2
- import type {LoaderCapability} from '../types/capabilities'
3
-
4
- export function calcLoadDuration(entity: LoaderCapability, cargoMass: UInt64): UInt32 {
5
- const totalThrust = entity.loaders.thrust.toNumber() * entity.loaders.quantity.toNumber()
6
- if (totalThrust === 0) return UInt32.from(0)
7
- return UInt32.from(Math.ceil(Number(cargoMass) / totalThrust))
8
- }