@shipload/sdk 0.7.1 → 1.0.0-beta1

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 (94) hide show
  1. package/lib/shipload.d.ts +2730 -287
  2. package/lib/shipload.js +10862 -2229
  3. package/lib/shipload.js.map +1 -1
  4. package/lib/shipload.m.js +10434 -2171
  5. package/lib/shipload.m.js.map +1 -1
  6. package/package.json +11 -20
  7. package/src/capabilities/crafting.ts +22 -0
  8. package/src/capabilities/gathering.ts +36 -0
  9. package/src/capabilities/guards.ts +38 -0
  10. package/src/capabilities/hauling.ts +22 -0
  11. package/src/capabilities/index.ts +8 -0
  12. package/src/capabilities/loading.ts +8 -0
  13. package/src/capabilities/modules.ts +86 -0
  14. package/src/capabilities/movement.ts +29 -0
  15. package/src/capabilities/storage.ts +159 -0
  16. package/src/contracts/server.ts +1389 -285
  17. package/src/data/capabilities.ts +408 -0
  18. package/src/data/catalog.ts +135 -0
  19. package/src/data/categories.ts +55 -0
  20. package/src/data/colors.ts +84 -0
  21. package/src/data/entities.json +50 -0
  22. package/src/data/item-ids.ts +75 -0
  23. package/src/data/items.json +252 -0
  24. package/src/data/locations.ts +53 -0
  25. package/src/data/metadata.ts +208 -0
  26. package/src/data/nebula-adjectives.json +211 -0
  27. package/src/data/nebula-nouns.json +151 -0
  28. package/src/data/recipes-runtime.ts +65 -0
  29. package/src/data/recipes.json +878 -0
  30. package/src/data/syllables.json +1790 -0
  31. package/src/data/tiers.ts +45 -0
  32. package/src/derivation/crafting.ts +350 -0
  33. package/src/derivation/index.ts +32 -0
  34. package/src/derivation/location-size.ts +15 -0
  35. package/src/derivation/resources.ts +112 -0
  36. package/src/derivation/stats.ts +146 -0
  37. package/src/derivation/strata.ts +43 -0
  38. package/src/derivation/stratum.ts +134 -0
  39. package/src/derivation/tiers.ts +54 -0
  40. package/src/entities/cargo-utils.ts +84 -0
  41. package/src/entities/container.ts +108 -0
  42. package/src/entities/entity-inventory.ts +39 -0
  43. package/src/entities/gamestate.ts +152 -0
  44. package/src/entities/inventory-accessor.ts +42 -0
  45. package/src/entities/location.ts +60 -0
  46. package/src/entities/makers.ts +196 -0
  47. package/src/entities/player.ts +15 -0
  48. package/src/entities/ship-deploy.ts +258 -0
  49. package/src/entities/ship.ts +204 -0
  50. package/src/entities/warehouse.ts +119 -0
  51. package/src/errors.ts +100 -9
  52. package/src/format.ts +12 -0
  53. package/src/index-module.ts +317 -7
  54. package/src/managers/actions.ts +250 -0
  55. package/src/managers/base.ts +25 -0
  56. package/src/managers/context.ts +114 -0
  57. package/src/managers/entities.ts +103 -0
  58. package/src/managers/epochs.ts +47 -0
  59. package/src/managers/index.ts +9 -0
  60. package/src/managers/locations.ts +68 -0
  61. package/src/managers/players.ts +13 -0
  62. package/src/nft/description.ts +176 -0
  63. package/src/nft/deserializers.ts +83 -0
  64. package/src/nft/index.ts +2 -0
  65. package/src/resolution/describe-module.ts +166 -0
  66. package/src/resolution/display-name.ts +39 -0
  67. package/src/resolution/resolve-item.ts +358 -0
  68. package/src/scheduling/accessor.ts +82 -0
  69. package/src/{epoch.ts → scheduling/epoch.ts} +1 -1
  70. package/src/scheduling/projection.ts +463 -0
  71. package/src/scheduling/schedule.ts +179 -0
  72. package/src/shipload.ts +47 -160
  73. package/src/subscriptions/connection.ts +154 -0
  74. package/src/subscriptions/debug.ts +17 -0
  75. package/src/subscriptions/index.ts +5 -0
  76. package/src/subscriptions/manager.ts +240 -0
  77. package/src/subscriptions/mappers.ts +28 -0
  78. package/src/subscriptions/types.ts +143 -0
  79. package/src/travel/travel.ts +500 -0
  80. package/src/types/capabilities.ts +76 -0
  81. package/src/types/entity-traits.ts +69 -0
  82. package/src/types/entity.ts +39 -0
  83. package/src/types/index.ts +3 -0
  84. package/src/types.ts +140 -35
  85. package/src/{hash.ts → utils/hash.ts} +2 -2
  86. package/src/utils/system.ts +168 -0
  87. package/src/goods.ts +0 -124
  88. package/src/market.ts +0 -214
  89. package/src/rolls.ts +0 -8
  90. package/src/ship.ts +0 -36
  91. package/src/state.ts +0 -0
  92. package/src/syllables.ts +0 -1184
  93. package/src/system.ts +0 -37
  94. package/src/travel.ts +0 -259
@@ -0,0 +1,28 @@
1
+ import {ServerContract} from '../contracts'
2
+ import {Ship} from '../entities/ship'
3
+ import {Warehouse} from '../entities/warehouse'
4
+ import {Container} from '../entities/container'
5
+ import type {WireEntity} from './types'
6
+
7
+ export function mapEntity(ei: ServerContract.Types.entity_info): Ship | Warehouse | Container {
8
+ if (ei.type.equals('ship')) return new Ship(ei)
9
+ if (ei.type.equals('warehouse')) return new Warehouse(ei)
10
+ if (ei.type.equals('container')) return new Container(ei)
11
+ throw new Error(`mapEntity: unknown entity type ${ei.type.toString()}`)
12
+ }
13
+
14
+ export function parseWireEntity(raw: WireEntity): ServerContract.Types.entity_info {
15
+ const shaped: Record<string, unknown> = {...raw}
16
+
17
+ if (typeof shaped.type === 'number' && typeof shaped.type_name === 'string') {
18
+ shaped.type = shaped.type_name
19
+ }
20
+ delete shaped.type_name
21
+
22
+ if (shaped.entity_name === undefined && typeof shaped.name === 'string') {
23
+ shaped.entity_name = shaped.name
24
+ }
25
+ delete shaped.name
26
+
27
+ return ServerContract.Types.entity_info.from(shaped)
28
+ }
@@ -0,0 +1,143 @@
1
+ import type {ServerContract} from '../contracts'
2
+
3
+ export type EntityInfo = ServerContract.Types.entity_info
4
+
5
+ export interface BoundingBox {
6
+ min_x: number
7
+ min_y: number
8
+ max_x: number
9
+ max_y: number
10
+ }
11
+
12
+ export interface WireCoordinates {
13
+ x: number
14
+ y: number
15
+ z?: number
16
+ }
17
+
18
+ // --- Client → Server ---
19
+
20
+ export type SubscribeMessage = {
21
+ type: 'subscribe'
22
+ sub_id: string
23
+ bounds?: BoundingBox
24
+ owner?: string
25
+ prioritize_owner?: string
26
+ }
27
+
28
+ export type UpdateBoundsMessage = {
29
+ type: 'update_bounds'
30
+ sub_id: string
31
+ bounds: BoundingBox
32
+ }
33
+
34
+ export type UnsubscribeMessage = {
35
+ type: 'unsubscribe'
36
+ sub_id: string
37
+ }
38
+
39
+ export type SubscribeEntityMessage = {
40
+ type: 'subscribe_entity'
41
+ sub_id: string
42
+ entity_type: 'ship' | 'warehouse' | 'container'
43
+ entity_id: string
44
+ }
45
+
46
+ export type UnsubscribeEntityMessage = {
47
+ type: 'unsubscribe_entity'
48
+ sub_id: string
49
+ }
50
+
51
+ export type SubscribeEventsMessage = {
52
+ type: 'subscribe_events'
53
+ sub_id: string
54
+ event_filter?: Record<string, unknown>
55
+ }
56
+
57
+ export type UnsubscribeEventsMessage = {
58
+ type: 'unsubscribe_events'
59
+ sub_id: string
60
+ }
61
+
62
+ export type PingMessage = {type: 'ping'}
63
+
64
+ export type ClientMessage =
65
+ | SubscribeMessage
66
+ | UpdateBoundsMessage
67
+ | UnsubscribeMessage
68
+ | SubscribeEntityMessage
69
+ | UnsubscribeEntityMessage
70
+ | SubscribeEventsMessage
71
+ | UnsubscribeEventsMessage
72
+ | PingMessage
73
+
74
+ // --- Server → Client ---
75
+
76
+ export type AckMessage = {
77
+ type: 'subscribed' | 'unsubscribed' | 'bounds_updated'
78
+ sub_id: string
79
+ }
80
+
81
+ export type WireEntity = Record<string, unknown> & {
82
+ type: number
83
+ type_name: 'ship' | 'warehouse' | 'container'
84
+ id: string | number
85
+ owner: string
86
+ coordinates: WireCoordinates
87
+ }
88
+
89
+ export type SnapshotMessage = {
90
+ type: 'snapshot'
91
+ sub_id: string
92
+ seq: number
93
+ entities: WireEntity[]
94
+ truncated?: boolean
95
+ }
96
+
97
+ export type UpdateMessage = {
98
+ type: 'update'
99
+ sub_ids: string[]
100
+ entity_id: number
101
+ entity: WireEntity
102
+ seq: number
103
+ }
104
+
105
+ export type BoundsDeltaMessage = {
106
+ type: 'bounds_delta'
107
+ sub_id: string
108
+ entered: WireEntity[]
109
+ exited: number[]
110
+ seq: number
111
+ truncated?: boolean
112
+ }
113
+
114
+ export type EventMessage = {
115
+ type: 'event'
116
+ sub_id: string
117
+ catchup: boolean
118
+ events: Array<Record<string, unknown>>
119
+ seq?: number
120
+ }
121
+
122
+ export type EventCatchupCompleteMessage = {
123
+ type: 'event_catchup_complete'
124
+ sub_id: string
125
+ }
126
+
127
+ export type PongMessage = {type: 'pong'}
128
+
129
+ export type ErrorMessage = {
130
+ type: 'error'
131
+ error: string
132
+ sub_id?: string
133
+ }
134
+
135
+ export type ServerMessage =
136
+ | AckMessage
137
+ | SnapshotMessage
138
+ | UpdateMessage
139
+ | BoundsDeltaMessage
140
+ | EventMessage
141
+ | EventCatchupCompleteMessage
142
+ | PongMessage
143
+ | ErrorMessage
@@ -0,0 +1,500 @@
1
+ /**
2
+ * Travel calculations for ship movement, energy usage, and flight times.
3
+ *
4
+ * Functions prefixed with `calc_` are contract-parity functions that mirror
5
+ * the C++ implementation in the server contract (schedule.cpp, ship.cpp).
6
+ * These use snake_case intentionally to match the contract naming convention
7
+ * and signal that they must produce identical results to the on-chain code.
8
+ *
9
+ * Functions prefixed with `calculate` are higher-level SDK helpers that may
10
+ * combine multiple contract calculations for convenience.
11
+ */
12
+
13
+ import {
14
+ type Checksum256,
15
+ Int64,
16
+ type Int64Type,
17
+ UInt16,
18
+ UInt32,
19
+ type UInt32Type,
20
+ UInt64,
21
+ type UInt64Type,
22
+ } from '@wharfkit/antelope'
23
+
24
+ import type {ServerContract} from '../contracts'
25
+ import {
26
+ BASE_ORBITAL_MASS,
27
+ type CargoMassInfo,
28
+ type Distance,
29
+ MAX_ORBITAL_ALTITUDE,
30
+ MIN_ORBITAL_ALTITUDE,
31
+ PRECISION,
32
+ type ShipLike,
33
+ TaskType,
34
+ } from '../types'
35
+ import {getItem} from '../data/catalog'
36
+ import {hasSystem} from '../utils/system'
37
+
38
+ export function calc_orbital_altitude(mass: number): number {
39
+ if (mass <= BASE_ORBITAL_MASS) {
40
+ return MIN_ORBITAL_ALTITUDE
41
+ }
42
+
43
+ const ratio = mass / BASE_ORBITAL_MASS
44
+ const capRatio = 10.0
45
+ let scale = Math.log(ratio) / Math.log(capRatio)
46
+ scale = Math.min(scale, 1.0)
47
+
48
+ return MIN_ORBITAL_ALTITUDE + Math.floor((MAX_ORBITAL_ALTITUDE - MIN_ORBITAL_ALTITUDE) * scale)
49
+ }
50
+
51
+ export function distanceBetweenCoordinates(
52
+ origin: ServerContract.ActionParams.Type.coordinates,
53
+ destination: ServerContract.ActionParams.Type.coordinates
54
+ ): UInt64 {
55
+ return distanceBetweenPoints(origin.x, origin.y, destination.x, destination.y)
56
+ }
57
+
58
+ export function distanceBetweenPoints(
59
+ x1: Int64Type,
60
+ y1: Int64Type,
61
+ x2: Int64Type,
62
+ y2: Int64Type
63
+ ): UInt64 {
64
+ const x = (x1 - x2) ** 2
65
+ const y = (y1 - y2) ** 2
66
+ return UInt64.from(Math.sqrt(x + y) * PRECISION)
67
+ }
68
+
69
+ export function lerp(
70
+ origin: ServerContract.ActionParams.Type.coordinates,
71
+ destination: ServerContract.ActionParams.Type.coordinates,
72
+ time: number
73
+ ): ServerContract.ActionParams.Type.coordinates {
74
+ return {
75
+ x: (1 - time) * Number(origin.x) + time * Number(destination.x),
76
+ y: (1 - time) * Number(origin.y) + time * Number(destination.y),
77
+ }
78
+ }
79
+
80
+ export function rotation(
81
+ origin: ServerContract.ActionParams.Type.coordinates,
82
+ destination: ServerContract.ActionParams.Type.coordinates
83
+ ) {
84
+ return Math.atan2(destination.y - origin.y, destination.x - origin.x) * (180 / Math.PI) + 90
85
+ }
86
+
87
+ export function findNearbyPlanets(
88
+ seed: Checksum256,
89
+ origin: ServerContract.ActionParams.Type.coordinates,
90
+ maxDistance: UInt64Type = 20 * PRECISION
91
+ ): Distance[] {
92
+ const nearbySystems: Distance[] = []
93
+
94
+ const max = UInt64.from(maxDistance / PRECISION)
95
+ const xMin = Int64.from(origin.x).subtracting(max)
96
+ const xMax = Int64.from(origin.x).adding(max)
97
+ const yMin = Int64.from(origin.y).subtracting(max)
98
+ const yMax = Int64.from(origin.y).adding(max)
99
+
100
+ for (let x = Number(xMin); x <= Number(xMax); x++) {
101
+ for (let y = Number(yMin); y <= Number(yMax); y++) {
102
+ const samePlace = x === Number(origin.x) && y === Number(origin.y)
103
+ if (!samePlace) {
104
+ const distance = distanceBetweenPoints(origin.x, origin.y, x, y)
105
+ if (Number(distance) <= Number(maxDistance)) {
106
+ const system = hasSystem(seed, {x, y})
107
+ if (system) {
108
+ nearbySystems.push({origin, destination: {x, y}, distance})
109
+ }
110
+ }
111
+ }
112
+ }
113
+ }
114
+
115
+ return nearbySystems
116
+ }
117
+
118
+ export function calc_rechargetime(
119
+ capacity: UInt32Type,
120
+ energy: UInt32Type,
121
+ recharge: UInt32Type
122
+ ): UInt32 {
123
+ const cap = UInt32.from(capacity)
124
+ const eng = UInt32.from(energy)
125
+ if (eng.gte(cap)) return UInt32.zero
126
+ return cap.subtracting(eng).dividing(recharge)
127
+ }
128
+
129
+ export function calc_ship_rechargetime(ship: ShipLike): UInt32 {
130
+ if (!ship.generator) return UInt32.from(0)
131
+ return calc_rechargetime(
132
+ ship.generator.capacity,
133
+ ship.energy ?? UInt16.from(0),
134
+ ship.generator.recharge
135
+ )
136
+ }
137
+
138
+ export function calc_flighttime(distance: UInt64Type, acceleration: number): UInt32 {
139
+ return UInt32.from(2 * Math.sqrt(Number(distance) / acceleration))
140
+ }
141
+
142
+ export function calc_loader_flighttime(ship: ShipLike, mass: UInt64, altitude?: number): UInt32 {
143
+ const z = altitude ?? ship.coordinates.z?.toNumber() ?? calc_orbital_altitude(Number(mass))
144
+ return calc_flighttime(z, calc_loader_acceleration(ship, mass))
145
+ }
146
+
147
+ export function calc_loader_acceleration(ship: ShipLike, mass: UInt64): number {
148
+ const thrust = ship.loaders ? Number(ship.loaders.thrust) : 0
149
+ const loaderMass = ship.loaders ? Number(ship.loaders.mass) : 0
150
+ return calc_acceleration(thrust, Number(mass) + loaderMass)
151
+ }
152
+
153
+ export function calc_ship_flighttime(ship: ShipLike, mass: UInt64, distance: UInt64): UInt32 {
154
+ const acceleration = calc_ship_acceleration(ship, mass)
155
+ return calc_flighttime(distance, acceleration)
156
+ }
157
+
158
+ export function calc_ship_acceleration(ship: ShipLike, mass: UInt64): number {
159
+ const thrust = ship.engines ? Number(ship.engines.thrust) : 0
160
+ return calc_acceleration(thrust, Number(mass))
161
+ }
162
+
163
+ export function calc_acceleration(thrust: number, mass: number): number {
164
+ return (thrust / mass) * PRECISION
165
+ }
166
+
167
+ export function calc_ship_mass(ship: ShipLike, cargos: CargoMassInfo[]): UInt64 {
168
+ const mass = UInt64.from(0)
169
+
170
+ mass.add(ship.hullmass)
171
+
172
+ if (ship.loaders && ship.loaders.quantity.gt(UInt32.zero)) {
173
+ mass.add(ship.loaders.mass.multiplying(ship.loaders.quantity))
174
+ }
175
+
176
+ for (const cargo of cargos) {
177
+ const cargoMass = getItem(cargo.item_id).mass * Number(UInt32.from(cargo.quantity))
178
+ mass.add(UInt64.from(cargoMass))
179
+ }
180
+
181
+ return mass
182
+ }
183
+
184
+ export function calc_energyusage(distance: UInt64Type, drain: UInt32Type): UInt32 {
185
+ return UInt64.from(distance).dividing(PRECISION).multiplying(drain)
186
+ }
187
+
188
+ export function calculateTransferTime(
189
+ ship: ShipLike,
190
+ cargos: CargoMassInfo[],
191
+ quantities?: Map<number, number>
192
+ ): UInt32 {
193
+ let mass = UInt64.from(0)
194
+
195
+ for (const cargo of cargos) {
196
+ const qty = quantities?.get(Number(cargo.item_id)) ?? 0
197
+ if (qty > 0) {
198
+ const good_mass = getItem(cargo.item_id).mass
199
+ const cargo_mass = good_mass * qty
200
+ mass = UInt64.from(mass).adding(UInt64.from(cargo_mass))
201
+ }
202
+ }
203
+
204
+ if (mass.equals(UInt64.zero)) {
205
+ return UInt32.from(0)
206
+ }
207
+
208
+ if (!ship.loaders) return UInt32.from(0)
209
+ mass = UInt64.from(mass).adding(ship.loaders.mass)
210
+ const transfer_time = calc_loader_flighttime(ship, mass)
211
+ return transfer_time.dividing(ship.loaders.quantity)
212
+ }
213
+
214
+ export function calculateRefuelingTime(ship: ShipLike): UInt32 {
215
+ return calc_ship_rechargetime(ship)
216
+ }
217
+
218
+ export function calculateFlightTime(
219
+ ship: ShipLike,
220
+ cargos: CargoMassInfo[],
221
+ distance: UInt64Type
222
+ ): UInt32 {
223
+ const mass = calc_ship_mass(ship, cargos)
224
+ return calc_ship_flighttime(ship, mass, distance)
225
+ }
226
+
227
+ export interface LoadTimeBreakdown {
228
+ unloadTime: number
229
+ loadTime: number
230
+ totalTime: number
231
+ unloadMass: number
232
+ loadMass: number
233
+ }
234
+
235
+ export function calculateLoadTimeBreakdown(
236
+ ship: ShipLike,
237
+ cargos: CargoMassInfo[],
238
+ loadQuantities?: Map<number, number>,
239
+ unloadQuantities?: Map<number, number>
240
+ ): LoadTimeBreakdown {
241
+ let mass_unload = UInt64.from(0)
242
+ let mass_load = UInt64.from(0)
243
+
244
+ for (const cargo of cargos) {
245
+ const goodId = Number(cargo.item_id)
246
+ const loadQty = loadQuantities?.get(goodId) ?? 0
247
+ const unloadQty = unloadQuantities?.get(goodId) ?? 0
248
+
249
+ if (loadQty > 0 || unloadQty > 0) {
250
+ const good = getItem(cargo.item_id)
251
+
252
+ if (loadQty > 0) {
253
+ const cargo_mass = good.mass * loadQty
254
+ mass_load = UInt64.from(mass_load).adding(UInt64.from(cargo_mass))
255
+ }
256
+ if (unloadQty > 0) {
257
+ const cargo_mass = good.mass * unloadQty
258
+ mass_unload = UInt64.from(mass_unload).adding(UInt64.from(cargo_mass))
259
+ }
260
+ }
261
+ }
262
+
263
+ let unloadTime = 0
264
+ let loadTime = 0
265
+
266
+ if (mass_unload.gt(UInt64.zero) && ship.loaders) {
267
+ const totalMass = UInt64.from(mass_unload).adding(ship.loaders.mass)
268
+ unloadTime = Number(calc_loader_flighttime(ship, totalMass))
269
+ }
270
+
271
+ if (mass_load.gt(UInt64.zero) && ship.loaders) {
272
+ const totalMass = UInt64.from(mass_load).adding(ship.loaders.mass)
273
+ loadTime = Number(calc_loader_flighttime(ship, totalMass))
274
+ }
275
+
276
+ const numLoaders = ship.loaders ? Number(ship.loaders.quantity) : 0
277
+ const totalTime = numLoaders > 0 ? (unloadTime + loadTime) / numLoaders : 0
278
+ const unloadTimePerLoader = numLoaders > 0 ? unloadTime / numLoaders : 0
279
+ const loadTimePerLoader = numLoaders > 0 ? loadTime / numLoaders : 0
280
+
281
+ return {
282
+ unloadTime: unloadTimePerLoader,
283
+ loadTime: loadTimePerLoader,
284
+ totalTime,
285
+ unloadMass: Number(mass_unload),
286
+ loadMass: Number(mass_load),
287
+ }
288
+ }
289
+
290
+ export interface EstimatedTravelTime {
291
+ flightTime: UInt32
292
+ rechargeTime: UInt32
293
+ loadTime: UInt32
294
+ unloadTime: UInt32
295
+ total: UInt32
296
+ }
297
+
298
+ export interface EstimateTravelTimeOptions {
299
+ needsRecharge?: boolean
300
+ loadMass?: UInt32Type
301
+ unloadMass?: UInt32Type
302
+ }
303
+
304
+ export function estimateTravelTime(
305
+ ship: ShipLike,
306
+ travelMass: UInt64Type,
307
+ distance: UInt64Type,
308
+ options: EstimateTravelTimeOptions = {}
309
+ ): EstimatedTravelTime {
310
+ const {needsRecharge = false, loadMass, unloadMass} = options
311
+
312
+ const flightTime = calc_ship_flighttime(ship, UInt64.from(travelMass), UInt64.from(distance))
313
+ const rechargeTime = needsRecharge ? calc_ship_rechargetime(ship) : UInt32.zero
314
+
315
+ let loadTime = UInt32.zero
316
+ let unloadTime = UInt32.zero
317
+
318
+ if (
319
+ loadMass &&
320
+ UInt32.from(loadMass).gt(UInt32.zero) &&
321
+ ship.loaders &&
322
+ ship.loaders.quantity.gt(UInt32.zero)
323
+ ) {
324
+ const totalMass = UInt64.from(loadMass).adding(ship.loaders.mass)
325
+ loadTime = calc_loader_flighttime(ship, totalMass).dividing(ship.loaders.quantity)
326
+ }
327
+
328
+ if (
329
+ unloadMass &&
330
+ UInt32.from(unloadMass).gt(UInt32.zero) &&
331
+ ship.loaders &&
332
+ ship.loaders.quantity.gt(UInt32.zero)
333
+ ) {
334
+ const totalMass = UInt64.from(unloadMass).adding(ship.loaders.mass)
335
+ unloadTime = calc_loader_flighttime(ship, totalMass).dividing(ship.loaders.quantity)
336
+ }
337
+
338
+ return {
339
+ flightTime,
340
+ rechargeTime,
341
+ loadTime,
342
+ unloadTime,
343
+ total: flightTime.adding(rechargeTime).adding(loadTime).adding(unloadTime),
344
+ }
345
+ }
346
+
347
+ export function estimateDealTravelTime(
348
+ ship: ShipLike,
349
+ shipMass: UInt64Type,
350
+ distance: UInt64Type,
351
+ loadMass: UInt32Type
352
+ ): UInt32 {
353
+ const needsRecharge = !hasEnergyForDistance(ship, distance)
354
+ const estimate = estimateTravelTime(ship, shipMass, distance, {
355
+ needsRecharge,
356
+ loadMass,
357
+ })
358
+ return estimate.total
359
+ }
360
+
361
+ export function hasEnergyForDistance(ship: ShipLike, distance: UInt64Type): boolean {
362
+ if (!ship.engines) return false
363
+ const energyNeeded = UInt64.from(distance).dividing(PRECISION).multiplying(ship.engines.drain)
364
+ return UInt64.from(ship.energy ?? 0).gte(energyNeeded)
365
+ }
366
+
367
+ export interface TransferEntity {
368
+ location: {z?: {toNumber(): number} | number}
369
+ loaders?: {
370
+ thrust: {toNumber(): number} | number
371
+ mass: {toNumber(): number} | number
372
+ quantity: {toNumber(): number} | number
373
+ }
374
+ }
375
+
376
+ export interface HasScheduleAndLocation {
377
+ coordinates: ServerContract.ActionParams.Type.coordinates
378
+ schedule?: ServerContract.Types.schedule
379
+ }
380
+
381
+ export function getFlightOrigin(
382
+ entity: HasScheduleAndLocation,
383
+ flightTaskIndex: number
384
+ ): ServerContract.ActionParams.Type.coordinates {
385
+ if (!entity.schedule) return entity.coordinates
386
+
387
+ let origin = entity.coordinates
388
+ for (let i = 0; i < flightTaskIndex && i < entity.schedule.tasks.length; i++) {
389
+ const task = entity.schedule.tasks[i]
390
+ if (task.type.equals(TaskType.TRAVEL) && task.coordinates) {
391
+ origin = task.coordinates
392
+ }
393
+ }
394
+ return origin
395
+ }
396
+
397
+ export function getDestinationLocation(
398
+ entity: HasScheduleAndLocation
399
+ ): 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]
404
+ if (task.type.equals(TaskType.TRAVEL) && task.coordinates) {
405
+ return task.coordinates
406
+ }
407
+ }
408
+ return undefined
409
+ }
410
+
411
+ export function getPositionAt(
412
+ entity: HasScheduleAndLocation,
413
+ taskIndex: number,
414
+ taskProgress: number
415
+ ): ServerContract.ActionParams.Type.coordinates {
416
+ if (!entity.schedule || entity.schedule.tasks.length === 0 || taskIndex < 0) {
417
+ return entity.coordinates
418
+ }
419
+
420
+ const task = entity.schedule.tasks[taskIndex]
421
+
422
+ if (!task.type.equals(TaskType.TRAVEL) || !task.coordinates) {
423
+ return getFlightOrigin(entity, taskIndex)
424
+ }
425
+
426
+ const origin = getFlightOrigin(entity, taskIndex)
427
+ const destination = task.coordinates
428
+
429
+ const interpolated = lerp(origin, destination, taskProgress)
430
+ return {
431
+ x: Math.round(interpolated.x),
432
+ y: Math.round(interpolated.y),
433
+ }
434
+ }
435
+
436
+ export function calc_transfer_duration(
437
+ source: TransferEntity,
438
+ dest: TransferEntity,
439
+ cargoMass: number
440
+ ): number {
441
+ if (cargoMass === 0) {
442
+ return 0
443
+ }
444
+
445
+ let totalThrust = 0
446
+ let totalLoaderMass = 0
447
+ let totalQuantity = 0
448
+
449
+ if (source.loaders) {
450
+ const thrust =
451
+ typeof source.loaders.thrust === 'number'
452
+ ? source.loaders.thrust
453
+ : source.loaders.thrust.toNumber()
454
+ const mass =
455
+ typeof source.loaders.mass === 'number'
456
+ ? source.loaders.mass
457
+ : source.loaders.mass.toNumber()
458
+ const qty =
459
+ typeof source.loaders.quantity === 'number'
460
+ ? source.loaders.quantity
461
+ : source.loaders.quantity.toNumber()
462
+ totalThrust += thrust * qty
463
+ totalLoaderMass += mass * qty
464
+ totalQuantity += qty
465
+ }
466
+
467
+ if (dest.loaders) {
468
+ const thrust =
469
+ typeof dest.loaders.thrust === 'number'
470
+ ? dest.loaders.thrust
471
+ : dest.loaders.thrust.toNumber()
472
+ const mass =
473
+ typeof dest.loaders.mass === 'number' ? dest.loaders.mass : dest.loaders.mass.toNumber()
474
+ const qty =
475
+ typeof dest.loaders.quantity === 'number'
476
+ ? dest.loaders.quantity
477
+ : dest.loaders.quantity.toNumber()
478
+ totalThrust += thrust * qty
479
+ totalLoaderMass += mass * qty
480
+ totalQuantity += qty
481
+ }
482
+
483
+ if (totalThrust === 0 || totalQuantity === 0) {
484
+ return 0
485
+ }
486
+
487
+ const sourceZ =
488
+ typeof source.location.z === 'number'
489
+ ? source.location.z
490
+ : (source.location.z?.toNumber() ?? 0)
491
+ const destZ =
492
+ typeof dest.location.z === 'number' ? dest.location.z : (dest.location.z?.toNumber() ?? 0)
493
+ const distance = Math.abs(sourceZ - destZ)
494
+
495
+ const totalMass = cargoMass + totalLoaderMass
496
+ const acceleration = calc_acceleration(totalThrust, totalMass)
497
+ const flightTime = 2 * Math.sqrt(distance / acceleration)
498
+
499
+ return Math.floor(flightTime / totalQuantity)
500
+ }