@shipload/sdk 0.7.1 → 2.0.0-rc2

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 (62) hide show
  1. package/README.md +349 -1
  2. package/lib/shipload.d.ts +2016 -230
  3. package/lib/shipload.js +5733 -2094
  4. package/lib/shipload.js.map +1 -1
  5. package/lib/shipload.m.js +5425 -2012
  6. package/lib/shipload.m.js.map +1 -1
  7. package/package.json +5 -5
  8. package/src/capabilities/extraction.ts +37 -0
  9. package/src/capabilities/guards.ts +43 -0
  10. package/src/capabilities/index.ts +5 -0
  11. package/src/capabilities/loading.ts +8 -0
  12. package/src/capabilities/movement.ts +29 -0
  13. package/src/capabilities/storage.ts +67 -0
  14. package/src/contracts/server.ts +788 -202
  15. package/src/data/goods.json +23 -0
  16. package/src/data/syllables.json +1184 -0
  17. package/src/entities/cargo-utils.ts +142 -0
  18. package/src/entities/container.ts +70 -0
  19. package/src/entities/entity-inventory.ts +39 -0
  20. package/src/entities/gamestate.ts +152 -0
  21. package/src/entities/inventory-accessor.ts +46 -0
  22. package/src/entities/location.ts +255 -0
  23. package/src/entities/makers.ts +69 -0
  24. package/src/entities/player.ts +288 -0
  25. package/src/entities/ship.ts +208 -0
  26. package/src/entities/warehouse.ts +89 -0
  27. package/src/errors.ts +46 -9
  28. package/src/index-module.ts +152 -7
  29. package/src/managers/actions.ts +200 -0
  30. package/src/managers/base.ts +25 -0
  31. package/src/managers/context.ts +104 -0
  32. package/src/managers/entities.ts +103 -0
  33. package/src/managers/epochs.ts +47 -0
  34. package/src/managers/index.ts +9 -0
  35. package/src/managers/locations.ts +122 -0
  36. package/src/managers/players.ts +13 -0
  37. package/src/managers/trades.ts +119 -0
  38. package/src/market/goods.ts +31 -0
  39. package/src/{market.ts → market/market.ts} +31 -37
  40. package/src/{rolls.ts → market/rolls.ts} +3 -3
  41. package/src/scheduling/accessor.ts +82 -0
  42. package/src/{epoch.ts → scheduling/epoch.ts} +1 -1
  43. package/src/scheduling/projection.ts +290 -0
  44. package/src/scheduling/schedule.ts +179 -0
  45. package/src/shipload.ts +39 -157
  46. package/src/trading/collect.ts +938 -0
  47. package/src/trading/deal.ts +207 -0
  48. package/src/trading/trade.ts +203 -0
  49. package/src/travel/travel.ts +486 -0
  50. package/src/types/capabilities.ts +79 -0
  51. package/src/types/entity-traits.ts +70 -0
  52. package/src/types/entity.ts +36 -0
  53. package/src/types/index.ts +3 -0
  54. package/src/types.ts +127 -25
  55. package/src/{hash.ts → utils/hash.ts} +1 -1
  56. package/src/utils/system.ts +155 -0
  57. package/src/goods.ts +0 -124
  58. package/src/ship.ts +0 -36
  59. package/src/state.ts +0 -0
  60. package/src/syllables.ts +0 -1184
  61. package/src/system.ts +0 -37
  62. package/src/travel.ts +0 -259
@@ -0,0 +1,207 @@
1
+ import {Int64, UInt16, UInt32, UInt64, UInt64Type} from '@wharfkit/antelope'
2
+ import {Coordinates, GoodPrice, PRECISION, ShipLike} from '../types'
3
+ import {Location} from '../entities/location'
4
+ import {Ship} from '../entities/ship'
5
+ import {
6
+ distanceBetweenCoordinates,
7
+ type EstimatedTravelTime,
8
+ estimateTravelTime,
9
+ } from '../travel/travel'
10
+ import {calculateProfitPerSecond, calculateTradeProfit, isProfitable} from './trade'
11
+
12
+ /**
13
+ * Trading deal interface representing a profitable trade opportunity
14
+ */
15
+ export interface Deal {
16
+ /** Origin location */
17
+ origin: Location
18
+ /** Destination location */
19
+ destination: Location
20
+ /** Good to trade */
21
+ good: GoodPrice
22
+ /** Distance between origin and destination */
23
+ distance: UInt64
24
+ /** Available supply at origin */
25
+ supply: UInt16
26
+ /** Buy price at origin */
27
+ buyPrice: UInt32
28
+ /** Sell price at destination */
29
+ sellPrice: UInt32
30
+ /** Profit per unit */
31
+ profitPerUnit: UInt32
32
+ /** Maximum quantity that can be traded */
33
+ maxQuantity: UInt32
34
+ /** Total profit for max quantity */
35
+ totalProfit: Int64
36
+ /** Estimated travel time in seconds */
37
+ travelTime: UInt32
38
+ /** Detailed breakdown of travel time components */
39
+ travelTimeBreakdown: EstimatedTravelTime
40
+ /** Profit per second (floating point for UI display) */
41
+ profitPerSecond: number
42
+ /** Profit margin percentage (floating point for UI display) */
43
+ marginPercent: number
44
+ }
45
+
46
+ /**
47
+ * Options for finding deals
48
+ */
49
+ export interface FindDealsOptions {
50
+ /** Maximum number of deals to return */
51
+ maxDeals?: number
52
+ /** Maximum search distance */
53
+ maxDistance?: number
54
+ /** Player's current balance (for affordability filtering) */
55
+ playerBalance?: UInt64Type
56
+ /** Minimum profit per second threshold */
57
+ minProfitPerSecond?: number
58
+ /** Minimum profit margin percentage */
59
+ minMarginPercent?: number
60
+ /** Override available cargo space (in mass units). If provided, uses this instead of calculating from ship's current cargo. */
61
+ availableSpace?: number
62
+ }
63
+
64
+ /**
65
+ * Calculate deals for a ship from a specific origin location
66
+ */
67
+ export async function findDealsForShip(
68
+ ship: Ship,
69
+ originLocation: Coordinates,
70
+ getNearbyLocations: (origin: Coordinates, maxDistance: number) => Promise<Location[]>,
71
+ getMarketPrices: (location: Coordinates) => Promise<GoodPrice[]>,
72
+ options: FindDealsOptions = {}
73
+ ): Promise<Deal[]> {
74
+ const {
75
+ maxDeals = 10,
76
+ maxDistance = 20 * PRECISION,
77
+ playerBalance,
78
+ minProfitPerSecond = 0,
79
+ minMarginPercent = 0,
80
+ availableSpace,
81
+ } = options
82
+
83
+ const balance = playerBalance !== undefined ? UInt64.from(playerBalance) : undefined
84
+
85
+ const origin = Location.from(originLocation)
86
+ const originPrices = await getMarketPrices(originLocation)
87
+ origin.setMarketPrices(originPrices)
88
+
89
+ // Get nearby locations
90
+ const nearbyLocations = await getNearbyLocations(originLocation, maxDistance)
91
+
92
+ const deals: Deal[] = []
93
+ const currentMass = ship.totalMass
94
+ const shipCapacity = ship.maxCapacity
95
+ const effectiveAvailableMass =
96
+ availableSpace !== undefined
97
+ ? UInt64.from(availableSpace)
98
+ : currentMass.lt(shipCapacity)
99
+ ? shipCapacity.subtracting(currentMass)
100
+ : UInt64.zero
101
+
102
+ // Check each nearby location
103
+ for (const destLocation of nearbyLocations) {
104
+ const destinationPrices = await getMarketPrices(destLocation.coordinates)
105
+ destLocation.setMarketPrices(destinationPrices)
106
+
107
+ const distance = distanceBetweenCoordinates(originLocation, destLocation.coordinates)
108
+
109
+ // Compare prices for each good
110
+ for (const originGood of originPrices) {
111
+ const destGood = destinationPrices.find((g) => g.id.equals(originGood.id))
112
+ if (!destGood) continue
113
+
114
+ if (
115
+ !isProfitable(originGood.price, destGood.price) ||
116
+ originGood.supply.equals(UInt16.from(0))
117
+ ) {
118
+ continue
119
+ }
120
+
121
+ // Calculate max quantity based on balance, cargo space, and supply
122
+ const canAfford =
123
+ balance !== undefined
124
+ ? balance.dividing(originGood.price)
125
+ : UInt64.from(Number.MAX_SAFE_INTEGER)
126
+ const canHaul = effectiveAvailableMass.dividing(originGood.good.mass)
127
+ const supplyLimit = UInt64.from(originGood.supply)
128
+
129
+ // Find minimum of canAfford, canHaul, supplyLimit
130
+ let maxQuantity = canAfford
131
+ if (canHaul.lt(maxQuantity)) maxQuantity = canHaul
132
+ if (supplyLimit.lt(maxQuantity)) maxQuantity = supplyLimit
133
+
134
+ if (maxQuantity.equals(UInt64.zero)) continue
135
+
136
+ // Calculate travel time with cargo (includes recharge + load time)
137
+ const cargoMass = originGood.good.mass.multiplying(maxQuantity)
138
+ const availableSpaceUInt = UInt64.from(availableSpace)
139
+ const baseMass =
140
+ availableSpace !== undefined
141
+ ? shipCapacity.gte(availableSpaceUInt)
142
+ ? shipCapacity.subtracting(availableSpaceUInt)
143
+ : UInt64.zero
144
+ : currentMass
145
+ const totalMass = baseMass.adding(cargoMass)
146
+ const needsRecharge = !ship.hasEnergyFor(distance)
147
+ const travelEstimate = estimateTravelTime(ship as ShipLike, totalMass, distance, {
148
+ needsRecharge,
149
+ loadMass: Number(cargoMass),
150
+ })
151
+
152
+ // Calculate profitability metrics using trade.ts functions
153
+ const tradeCalc = calculateTradeProfit(maxQuantity, originGood.price, destGood.price)
154
+ const profitPerUnit = destGood.price.subtracting(originGood.price)
155
+ const profitPerSecond = calculateProfitPerSecond(tradeCalc.profit, travelEstimate.total)
156
+
157
+ // Apply filters
158
+ if (profitPerSecond < minProfitPerSecond) continue
159
+ if (tradeCalc.margin < minMarginPercent) continue
160
+
161
+ deals.push({
162
+ origin,
163
+ destination: destLocation,
164
+ good: originGood,
165
+ distance,
166
+ supply: originGood.supply,
167
+ buyPrice: originGood.price,
168
+ sellPrice: destGood.price,
169
+ profitPerUnit,
170
+ maxQuantity: UInt32.from(maxQuantity),
171
+ totalProfit: tradeCalc.profit,
172
+ travelTime: travelEstimate.total,
173
+ travelTimeBreakdown: travelEstimate,
174
+ profitPerSecond,
175
+ marginPercent: tradeCalc.margin,
176
+ })
177
+ }
178
+ }
179
+
180
+ // Sort by profit per second (descending)
181
+ deals.sort((a, b) => b.profitPerSecond - a.profitPerSecond)
182
+
183
+ return deals.slice(0, maxDeals)
184
+ }
185
+
186
+ /**
187
+ * Find the single best deal for a ship from a specific origin location
188
+ */
189
+ export async function findBestDeal(
190
+ ship: Ship,
191
+ originLocation: Coordinates,
192
+ getNearbyLocations: (origin: Coordinates, maxDistance: number) => Promise<Location[]>,
193
+ getMarketPrices: (location: Coordinates) => Promise<GoodPrice[]>,
194
+ options: FindDealsOptions = {}
195
+ ): Promise<Deal | undefined> {
196
+ const deals = await findDealsForShip(
197
+ ship,
198
+ originLocation,
199
+ getNearbyLocations,
200
+ getMarketPrices,
201
+ {
202
+ ...options,
203
+ maxDeals: 1,
204
+ }
205
+ )
206
+ return deals[0]
207
+ }
@@ -0,0 +1,203 @@
1
+ import {Int64, Int64Type, UInt32, UInt32Type, UInt64} from '@wharfkit/antelope'
2
+ import {Ship} from '../entities/ship'
3
+ import {Player} from '../entities/player'
4
+ import {GoodPrice} from '../types'
5
+
6
+ /**
7
+ * Trade calculation result
8
+ */
9
+ export interface TradeCalculation {
10
+ maxQuantity: number
11
+ totalCost: number
12
+ totalMass: UInt64
13
+ affordableQuantity: number
14
+ spaceForQuantity: number
15
+ }
16
+
17
+ /**
18
+ * Calculate updated weighted average cargo cost after purchase.
19
+ * Matches contract logic: (paid * owned + cost) / (owned + quantity)
20
+ *
21
+ * @param currentPaid - Current average cost per unit (from cargo.paid)
22
+ * @param currentOwned - Current owned quantity
23
+ * @param purchaseCost - Total cost of new purchase (price * quantity)
24
+ * @param purchaseQuantity - Quantity being purchased
25
+ * @returns New weighted average cost per unit
26
+ *
27
+ * @example
28
+ * // Owned 10 units at 100 each, buying 5 more at 120 each
29
+ * const newPaid = calculateUpdatedCargoCost(
30
+ * UInt64.from(100),
31
+ * UInt32.from(10),
32
+ * UInt64.from(600),
33
+ * UInt32.from(5)
34
+ * )
35
+ * // Result: (100*10 + 600) / (10+5) = 106.67 per unit
36
+ */
37
+ export function calculateUpdatedCargoCost(
38
+ currentPaid: UInt64,
39
+ currentOwned: UInt32,
40
+ purchaseCost: UInt64,
41
+ purchaseQuantity: UInt32
42
+ ): UInt64 {
43
+ // Match contract: (paid * owned + cost) / (owned + quantity)
44
+ const numerator = currentPaid.multiplying(currentOwned).adding(purchaseCost)
45
+ const denominator = UInt32.from(currentOwned).adding(purchaseQuantity)
46
+ return numerator.dividing(denominator)
47
+ }
48
+
49
+ /**
50
+ * Calculate the maximum quantity of a good a ship can buy
51
+ * considering both space and player balance
52
+ */
53
+ export function calculateMaxTradeQuantity(
54
+ ship: Ship,
55
+ player: Player,
56
+ goodPrice: GoodPrice
57
+ ): TradeCalculation {
58
+ const pricePerUnit = UInt32.from(goodPrice.price)
59
+ const massPerUnit = UInt32.from(goodPrice.good.mass)
60
+
61
+ const spaceForQuantity = ship.availableCapacity.dividing(massPerUnit)
62
+ const affordableQuantity = player.balance.dividing(pricePerUnit)
63
+ const maxQuantity = spaceForQuantity.lt(affordableQuantity)
64
+ ? spaceForQuantity
65
+ : affordableQuantity
66
+
67
+ return {
68
+ maxQuantity: Number(maxQuantity),
69
+ totalCost: Number(maxQuantity.multiplying(pricePerUnit)),
70
+ totalMass: UInt64.from(maxQuantity.multiplying(massPerUnit)),
71
+ affordableQuantity: Number(affordableQuantity),
72
+ spaceForQuantity: Number(spaceForQuantity),
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Trade profit calculation result
78
+ */
79
+ export interface TradeProfitResult {
80
+ revenue: UInt64
81
+ cost: UInt64
82
+ profit: Int64
83
+ margin: number
84
+ }
85
+
86
+ /**
87
+ * Calculate profit for a trade route
88
+ */
89
+ export function calculateTradeProfit(
90
+ quantity: UInt32Type,
91
+ buyPrice: UInt32Type,
92
+ sellPrice: UInt32Type
93
+ ): TradeProfitResult {
94
+ const qty = UInt32.from(quantity)
95
+ const buy = UInt32.from(buyPrice)
96
+ const sell = UInt32.from(sellPrice)
97
+
98
+ const cost = UInt64.from(qty).multiplying(buy)
99
+ const revenue = UInt64.from(qty).multiplying(sell)
100
+ const profit = Int64.from(revenue).subtracting(cost)
101
+ const margin = cost.gt(UInt64.zero) ? (Number(profit) / Number(cost)) * 100 : 0
102
+
103
+ return {revenue, cost, profit, margin}
104
+ }
105
+
106
+ /**
107
+ * Calculate profit per unit of mass
108
+ */
109
+ export function calculateProfitPerMass(
110
+ quantity: number,
111
+ buyPrice: number,
112
+ sellPrice: number,
113
+ massPerUnit: number
114
+ ): number {
115
+ const profit = (sellPrice - buyPrice) * quantity
116
+ const totalMass = quantity * massPerUnit
117
+ return totalMass > 0 ? profit / totalMass : 0
118
+ }
119
+
120
+ /**
121
+ * Calculate profit per second for a trade route
122
+ */
123
+ export function calculateProfitPerSecond(profit: Int64Type, travelTimeSeconds: UInt32Type): number {
124
+ const t = UInt32.from(travelTimeSeconds)
125
+ return t.gt(UInt32.zero) ? Number(profit) / Number(t) : 0
126
+ }
127
+
128
+ /**
129
+ * Find the best good to trade between two locations
130
+ */
131
+ export function findBestGoodToTrade(
132
+ ship: Ship,
133
+ player: Player,
134
+ originPrices: GoodPrice[],
135
+ destPrices: GoodPrice[],
136
+ travelTimeSeconds: UInt32Type
137
+ ): {
138
+ good: GoodPrice
139
+ quantity: number
140
+ profit: number
141
+ profitPerSecond: number
142
+ margin: number
143
+ } | null {
144
+ let bestTrade: {
145
+ good: GoodPrice
146
+ quantity: number
147
+ profit: number
148
+ profitPerSecond: number
149
+ margin: number
150
+ } | null = null
151
+ let bestProfitPerSecond = 0
152
+
153
+ for (const originPrice of originPrices) {
154
+ const destPrice = destPrices.find((p) => p.id.equals(originPrice.id))
155
+ if (!destPrice) continue
156
+
157
+ if (!isProfitable(originPrice.price, destPrice.price)) continue
158
+
159
+ const calc = calculateMaxTradeQuantity(ship, player, originPrice)
160
+ if (calc.maxQuantity === 0) continue
161
+
162
+ const tradeResult = calculateTradeProfit(
163
+ calc.maxQuantity,
164
+ originPrice.price,
165
+ destPrice.price
166
+ )
167
+ const profitPerSecond = calculateProfitPerSecond(tradeResult.profit, travelTimeSeconds)
168
+
169
+ if (profitPerSecond > bestProfitPerSecond) {
170
+ bestProfitPerSecond = profitPerSecond
171
+ bestTrade = {
172
+ good: originPrice,
173
+ quantity: calc.maxQuantity,
174
+ profit: Number(tradeResult.profit),
175
+ profitPerSecond,
176
+ margin: tradeResult.margin,
177
+ }
178
+ }
179
+ }
180
+
181
+ return bestTrade
182
+ }
183
+
184
+ /**
185
+ * Calculate break-even price for selling cargo
186
+ */
187
+ export function calculateBreakEvenPrice(costPaid: number, quantity: number): number {
188
+ return quantity > 0 ? costPaid / quantity : 0
189
+ }
190
+
191
+ /**
192
+ * Check if a trade is profitable
193
+ */
194
+ export function isProfitable(buyPrice: UInt32Type, sellPrice: UInt32Type): boolean {
195
+ return UInt32.from(sellPrice).gt(UInt32.from(buyPrice))
196
+ }
197
+
198
+ /**
199
+ * Calculate return on investment percentage
200
+ */
201
+ export function calculateROI(cost: number, profit: number): number {
202
+ return cost > 0 ? (profit / cost) * 100 : 0
203
+ }