@shipload/sdk 0.7.1 → 2.0.0-rc1

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 (47) hide show
  1. package/lib/shipload.d.ts +1651 -226
  2. package/lib/shipload.js +4958 -1971
  3. package/lib/shipload.js.map +1 -1
  4. package/lib/shipload.m.js +4787 -1940
  5. package/lib/shipload.m.js.map +1 -1
  6. package/package.json +5 -4
  7. package/src/contracts/server.ts +585 -203
  8. package/src/data/goods.json +23 -0
  9. package/src/data/syllables.json +1184 -0
  10. package/src/entities/cargo-utils.ts +47 -0
  11. package/src/entities/entity-inventory.ts +39 -0
  12. package/src/entities/gamestate.ts +152 -0
  13. package/src/entities/location.ts +241 -0
  14. package/src/entities/player.ts +287 -0
  15. package/src/entities/ship.ts +559 -0
  16. package/src/entities/warehouse.ts +205 -0
  17. package/src/errors.ts +46 -9
  18. package/src/index-module.ts +119 -7
  19. package/src/managers/actions.ts +168 -0
  20. package/src/managers/base.ts +25 -0
  21. package/src/managers/context.ts +104 -0
  22. package/src/managers/entities.ts +86 -0
  23. package/src/managers/epochs.ts +47 -0
  24. package/src/managers/index.ts +9 -0
  25. package/src/managers/locations.ts +103 -0
  26. package/src/managers/players.ts +13 -0
  27. package/src/managers/trades.ts +119 -0
  28. package/src/market/goods.ts +31 -0
  29. package/src/{market.ts → market/market.ts} +32 -37
  30. package/src/{rolls.ts → market/rolls.ts} +3 -3
  31. package/src/{epoch.ts → scheduling/epoch.ts} +1 -1
  32. package/src/scheduling/projection.ts +218 -0
  33. package/src/scheduling/schedule.ts +155 -0
  34. package/src/shipload.ts +39 -157
  35. package/src/trading/collect.ts +939 -0
  36. package/src/trading/deal.ts +208 -0
  37. package/src/trading/trade.ts +203 -0
  38. package/src/travel/travel.ts +425 -0
  39. package/src/types.ts +60 -25
  40. package/src/utils/system.ts +27 -0
  41. package/src/goods.ts +0 -124
  42. package/src/ship.ts +0 -36
  43. package/src/state.ts +0 -0
  44. package/src/syllables.ts +0 -1184
  45. package/src/system.ts +0 -37
  46. package/src/travel.ts +0 -259
  47. /package/src/{hash.ts → utils/hash.ts} +0 -0
@@ -0,0 +1,559 @@
1
+ import {Name, NameType, UInt16, UInt32, UInt64, UInt64Type} from '@wharfkit/antelope'
2
+ import {ServerContract} from '../contracts'
3
+ import {Coordinates, CoordinatesType, PRECISION, TaskType} from '../types'
4
+ import {distanceBetweenCoordinates, lerp} from '../travel/travel'
5
+ import {Location} from './location'
6
+ import {getGood} from '../market/goods'
7
+ import * as schedule from '../scheduling/schedule'
8
+ import {Scheduleable} from '../scheduling/schedule'
9
+ import {EntityInventory} from './entity-inventory'
10
+ import {ProjectedEntity} from '../scheduling/projection'
11
+
12
+ export interface ShipStateInput {
13
+ id: UInt64Type
14
+ owner: NameType
15
+ name: string
16
+ location: CoordinatesType | {x: number; y: number; z?: number}
17
+ mass: number
18
+ capacity: number
19
+ energy: number
20
+ engines: ServerContract.Types.movement_stats
21
+ generator: ServerContract.Types.energy_stats
22
+ loaders: ServerContract.Types.loader_stats
23
+ schedule?: ServerContract.Types.schedule
24
+ cargo?: ServerContract.Types.cargo_item[]
25
+ }
26
+
27
+ export class Ship extends ServerContract.Types.entity_info implements Scheduleable {
28
+ static fromState(state: ShipStateInput): Ship {
29
+ const entityInfo = ServerContract.Types.entity_info.from({
30
+ type: Name.from('ship'),
31
+ id: UInt64.from(state.id),
32
+ owner: Name.from(state.owner),
33
+ entity_name: state.name,
34
+ location: ServerContract.Types.coordinates.from(state.location),
35
+ mass: UInt32.from(state.mass),
36
+ capacity: UInt32.from(state.capacity),
37
+ energy: UInt16.from(state.energy),
38
+ cargomass: UInt32.from(0),
39
+ cargo: state.cargo || [],
40
+ is_idle: !state.schedule,
41
+ current_task_elapsed: UInt32.from(0),
42
+ current_task_remaining: UInt32.from(0),
43
+ pending_tasks: [],
44
+ engines: state.engines,
45
+ generator: state.generator,
46
+ loaders: state.loaders,
47
+ schedule: state.schedule,
48
+ })
49
+ return new Ship(entityInfo)
50
+ }
51
+
52
+ private _location?: Location
53
+ private _inventory?: EntityInventory[]
54
+
55
+ get name(): string {
56
+ return this.entity_name
57
+ }
58
+
59
+ get inventory(): EntityInventory[] {
60
+ if (!this._inventory) {
61
+ this._inventory = this.cargo.map((item) => new EntityInventory(item))
62
+ }
63
+ return this._inventory
64
+ }
65
+
66
+ get maxDistance(): UInt32 {
67
+ if (!this.generator || !this.engines) return UInt32.from(0)
68
+ return UInt32.from(this.generator.capacity)
69
+ .dividing(this.engines.drain)
70
+ .multiplying(PRECISION)
71
+ }
72
+
73
+ get hasSchedule(): boolean {
74
+ return schedule.hasSchedule(this)
75
+ }
76
+
77
+ get isIdle(): boolean {
78
+ return this.is_idle
79
+ }
80
+
81
+ get tasks(): ServerContract.Types.task[] {
82
+ return schedule.getTasks(this)
83
+ }
84
+
85
+ scheduleDuration(): number {
86
+ return schedule.scheduleDuration(this)
87
+ }
88
+
89
+ scheduleElapsed(now: Date): number {
90
+ return schedule.scheduleElapsed(this, now)
91
+ }
92
+
93
+ scheduleRemaining(now: Date): number {
94
+ return schedule.scheduleRemaining(this, now)
95
+ }
96
+
97
+ scheduleComplete(now: Date): boolean {
98
+ return schedule.scheduleComplete(this, now)
99
+ }
100
+
101
+ currentTaskIndex(now: Date): number {
102
+ return schedule.currentTaskIndex(this, now)
103
+ }
104
+
105
+ currentTask(now: Date): ServerContract.Types.task | undefined {
106
+ return schedule.currentTask(this, now)
107
+ }
108
+
109
+ currentTaskType(now: Date): TaskType | undefined {
110
+ return schedule.currentTaskType(this, now)
111
+ }
112
+
113
+ getTaskStartTime(index: number): number {
114
+ return schedule.getTaskStartTime(this, index)
115
+ }
116
+
117
+ getTaskElapsed(index: number, now: Date): number {
118
+ return schedule.getTaskElapsed(this, index, now)
119
+ }
120
+
121
+ getTaskRemaining(index: number, now: Date): number {
122
+ return schedule.getTaskRemaining(this, index, now)
123
+ }
124
+
125
+ isTaskComplete(index: number, now: Date): boolean {
126
+ return schedule.isTaskComplete(this, index, now)
127
+ }
128
+
129
+ isTaskInProgress(index: number, now: Date): boolean {
130
+ return schedule.isTaskInProgress(this, index, now)
131
+ }
132
+
133
+ currentTaskProgress(now: Date): number {
134
+ return schedule.currentTaskProgress(this, now)
135
+ }
136
+
137
+ scheduleProgress(now: Date): number {
138
+ return schedule.scheduleProgress(this, now)
139
+ }
140
+
141
+ getFlightOrigin(flightTaskIndex: number): Coordinates {
142
+ if (!this.schedule) return this.location
143
+
144
+ let origin: Coordinates = this.location
145
+ for (let i = 0; i < flightTaskIndex && i < this.schedule.tasks.length; i++) {
146
+ const task = this.schedule.tasks[i]
147
+ if (task.type.equals(TaskType.FLIGHT) && task.location) {
148
+ origin = task.location
149
+ }
150
+ }
151
+ return origin
152
+ }
153
+
154
+ destinationLocation(): Coordinates | undefined {
155
+ if (!this.schedule) return undefined
156
+
157
+ for (let i = this.schedule.tasks.length - 1; i >= 0; i--) {
158
+ const task = this.schedule.tasks[i]
159
+ if (task.type.equals(TaskType.FLIGHT) && task.location) {
160
+ return task.location
161
+ }
162
+ }
163
+ return undefined
164
+ }
165
+
166
+ positionAt(now: Date): Coordinates {
167
+ if (!this.schedule || this.schedule.tasks.length === 0) {
168
+ return this.location
169
+ }
170
+
171
+ const taskIndex = this.currentTaskIndex(now)
172
+ if (taskIndex < 0) return this.location
173
+
174
+ const task = this.schedule.tasks[taskIndex]
175
+
176
+ if (!task.type.equals(TaskType.FLIGHT) || !task.location) {
177
+ return this.getFlightOrigin(taskIndex)
178
+ }
179
+
180
+ const origin = this.getFlightOrigin(taskIndex)
181
+ const destination = task.location
182
+ const progress = this.currentTaskProgress(now)
183
+
184
+ const interpolated = lerp(origin, destination, progress)
185
+ return Coordinates.from({
186
+ x: Math.round(interpolated.x),
187
+ y: Math.round(interpolated.y),
188
+ })
189
+ }
190
+
191
+ isInFlight(now: Date): boolean {
192
+ const taskType = this.currentTaskType(now)
193
+ return taskType === TaskType.FLIGHT
194
+ }
195
+
196
+ isRecharging(now: Date): boolean {
197
+ const taskType = this.currentTaskType(now)
198
+ return taskType === TaskType.RECHARGE
199
+ }
200
+
201
+ isLoading(now: Date): boolean {
202
+ const taskType = this.currentTaskType(now)
203
+ return taskType === TaskType.LOAD
204
+ }
205
+
206
+ isUnloading(now: Date): boolean {
207
+ const taskType = this.currentTaskType(now)
208
+ return taskType === TaskType.UNLOAD
209
+ }
210
+
211
+ calcCargoMass(): UInt64 {
212
+ let mass = UInt64.from(0)
213
+ for (const item of this.cargo) {
214
+ const good = getGood(item.good_id)
215
+ mass = mass.adding(good.mass.multiplying(item.quantity))
216
+ }
217
+ return mass
218
+ }
219
+
220
+ private createProjectedEntity(cargoMass: UInt64): ProjectedEntity {
221
+ const shipMass = UInt32.from(this.mass)
222
+ const loaders = this.loaders
223
+ return {
224
+ location: Coordinates.from(this.location),
225
+ energy: UInt16.from(this.energy),
226
+ cargoMass,
227
+ shipMass,
228
+ engines: this.engines,
229
+ generator: this.generator,
230
+ loaders,
231
+ get totalMass() {
232
+ let mass = UInt64.from(this.shipMass).adding(this.cargoMass)
233
+ if (this.loaders) {
234
+ mass = mass.adding(this.loaders.mass.multiplying(this.loaders.quantity))
235
+ }
236
+ return mass
237
+ },
238
+ }
239
+ }
240
+
241
+ project(): ProjectedEntity {
242
+ const cargoMass = this.calcCargoMass()
243
+ const projected = this.createProjectedEntity(cargoMass)
244
+
245
+ if (!this.schedule) {
246
+ return projected
247
+ }
248
+
249
+ for (const task of this.schedule.tasks) {
250
+ switch (task.type.toNumber()) {
251
+ case TaskType.RECHARGE:
252
+ if (projected.generator) {
253
+ projected.energy = UInt16.from(projected.generator.capacity)
254
+ }
255
+ break
256
+ case TaskType.FLIGHT: {
257
+ if (task.location) {
258
+ const distance = distanceBetweenCoordinates(
259
+ projected.location,
260
+ task.location
261
+ )
262
+ if (projected.engines) {
263
+ const energyUsage = distance
264
+ .dividing(PRECISION)
265
+ .multiplying(projected.engines.drain)
266
+ projected.energy = projected.energy.gt(energyUsage)
267
+ ? UInt16.from(projected.energy.subtracting(energyUsage))
268
+ : UInt16.from(0)
269
+ }
270
+ projected.location = Coordinates.from(task.location)
271
+ }
272
+ break
273
+ }
274
+ case TaskType.LOAD:
275
+ for (const item of task.cargo) {
276
+ const good = getGood(item.good_id)
277
+ projected.cargoMass = projected.cargoMass.adding(
278
+ good.mass.multiplying(item.quantity)
279
+ )
280
+ }
281
+ break
282
+ case TaskType.UNLOAD:
283
+ for (const item of task.cargo) {
284
+ const good = getGood(item.good_id)
285
+ const cargoMass = good.mass.multiplying(item.quantity)
286
+ projected.cargoMass = projected.cargoMass.gt(cargoMass)
287
+ ? projected.cargoMass.subtracting(cargoMass)
288
+ : UInt64.from(0)
289
+ }
290
+ break
291
+ }
292
+ }
293
+
294
+ return projected
295
+ }
296
+
297
+ projectAt(now: Date): ProjectedEntity {
298
+ const cargoMass = this.calcCargoMass()
299
+ const projected = this.createProjectedEntity(cargoMass)
300
+
301
+ if (!this.schedule || this.schedule.tasks.length === 0) {
302
+ return projected
303
+ }
304
+
305
+ for (let i = 0; i < this.schedule.tasks.length; i++) {
306
+ const task = this.schedule.tasks[i]
307
+ const taskComplete = this.isTaskComplete(i, now)
308
+ const taskInProgress = this.isTaskInProgress(i, now)
309
+
310
+ if (!taskComplete && !taskInProgress) {
311
+ break
312
+ }
313
+
314
+ switch (task.type.toNumber()) {
315
+ case TaskType.RECHARGE:
316
+ if (projected.generator) {
317
+ if (taskComplete) {
318
+ projected.energy = UInt16.from(projected.generator.capacity)
319
+ } else if (taskInProgress) {
320
+ const progress = this.getTaskElapsed(i, now) / task.duration.toNumber()
321
+ const capacity = Number(projected.generator.capacity)
322
+ const currentEnergy = Number(projected.energy)
323
+ const rechargeAmount = (capacity - currentEnergy) * progress
324
+ projected.energy = UInt16.from(
325
+ Math.min(capacity, currentEnergy + rechargeAmount)
326
+ )
327
+ }
328
+ }
329
+ break
330
+ case TaskType.FLIGHT: {
331
+ if (task.location && projected.engines) {
332
+ const origin = projected.location
333
+ const destination = Coordinates.from(task.location)
334
+ const distance = distanceBetweenCoordinates(origin, task.location)
335
+ const energyUsage = distance
336
+ .dividing(PRECISION)
337
+ .multiplying(projected.engines.drain)
338
+
339
+ if (taskComplete) {
340
+ projected.energy = projected.energy.gt(energyUsage)
341
+ ? UInt16.from(projected.energy.subtracting(energyUsage))
342
+ : UInt16.from(0)
343
+ projected.location = destination
344
+ } else if (taskInProgress) {
345
+ const progress = this.getTaskElapsed(i, now) / task.duration.toNumber()
346
+ const interpolated = lerp(origin, destination, progress)
347
+ projected.location = Coordinates.from({
348
+ x: Math.round(interpolated.x),
349
+ y: Math.round(interpolated.y),
350
+ })
351
+ const partialEnergy = UInt64.from(
352
+ Math.floor(Number(energyUsage) * progress)
353
+ )
354
+ projected.energy = projected.energy.gt(partialEnergy)
355
+ ? UInt16.from(projected.energy.subtracting(partialEnergy))
356
+ : UInt16.from(0)
357
+ }
358
+ }
359
+ break
360
+ }
361
+ case TaskType.LOAD:
362
+ if (taskComplete) {
363
+ for (const item of task.cargo) {
364
+ const good = getGood(item.good_id)
365
+ projected.cargoMass = projected.cargoMass.adding(
366
+ good.mass.multiplying(item.quantity)
367
+ )
368
+ }
369
+ }
370
+ break
371
+ case TaskType.UNLOAD:
372
+ if (taskComplete) {
373
+ for (const item of task.cargo) {
374
+ const good = getGood(item.good_id)
375
+ const cargoMass = good.mass.multiplying(item.quantity)
376
+ projected.cargoMass = projected.cargoMass.gt(cargoMass)
377
+ ? projected.cargoMass.subtracting(cargoMass)
378
+ : UInt64.from(0)
379
+ }
380
+ }
381
+ break
382
+ }
383
+ }
384
+
385
+ return projected
386
+ }
387
+
388
+ get currentLocation(): Coordinates {
389
+ return this.location
390
+ }
391
+
392
+ get totalCargoMass(): UInt64 {
393
+ return this.inventory.reduce((sum, c) => sum.adding(c.totalMass), UInt64.from(0))
394
+ }
395
+
396
+ get cargoValue(): UInt64 {
397
+ return this.inventory.reduce((sum, c) => sum.adding(c.totalCost), UInt64.from(0))
398
+ }
399
+
400
+ get totalMass(): UInt64 {
401
+ const cargoMass = this.totalCargoMass
402
+ let mass = UInt64.from(this.mass ?? 0).adding(cargoMass)
403
+ if (this.loaders) {
404
+ mass = mass.adding(UInt64.from(this.loaders.mass).multiplying(this.loaders.quantity))
405
+ }
406
+ return mass
407
+ }
408
+
409
+ get maxCapacity(): UInt64 {
410
+ return UInt64.from(this.capacity)
411
+ }
412
+
413
+ hasSpace(goodMass: UInt64, quantity: number): boolean {
414
+ const additionalMass = goodMass.multiplying(quantity)
415
+ const newTotal = this.totalMass.adding(additionalMass)
416
+ return newTotal.lte(this.maxCapacity)
417
+ }
418
+
419
+ get availableCapacity(): UInt64 {
420
+ if (this.totalMass.gte(this.maxCapacity)) {
421
+ return UInt64.from(0)
422
+ }
423
+ return this.maxCapacity.subtracting(this.totalMass)
424
+ }
425
+
426
+ get locationObject(): Location {
427
+ if (!this._location) {
428
+ this._location = Location.from(this.location)
429
+ }
430
+ return this._location
431
+ }
432
+
433
+ setLocation(location: Location): void {
434
+ this._location = location
435
+ }
436
+
437
+ getCargoForGood(goodId: UInt64Type): EntityInventory | undefined {
438
+ return this.inventory.find((c) => c.good_id.equals(goodId))
439
+ }
440
+
441
+ get sellableCargo(): EntityInventory[] {
442
+ return this.inventory.filter((c) => c.hasCargo)
443
+ }
444
+
445
+ get hasSellableCargo(): boolean {
446
+ return this.inventory.some((c) => c.hasCargo)
447
+ }
448
+
449
+ get sellableGoodsCount(): number {
450
+ return this.inventory.filter((c) => c.hasCargo).length
451
+ }
452
+
453
+ get isFull(): boolean {
454
+ return this.totalMass.gte(this.maxCapacity)
455
+ }
456
+
457
+ get energyPercent(): number {
458
+ if (!this.generator) return 0
459
+ return (Number(this.energy ?? 0) / Number(this.generator.capacity)) * 100
460
+ }
461
+
462
+ get needsRecharge(): boolean {
463
+ if (!this.generator) return false
464
+ return UInt64.from(this.energy ?? 0).lt(this.generator.capacity)
465
+ }
466
+
467
+ hasEnergyFor(distance: UInt64): boolean {
468
+ if (!this.engines) return false
469
+ const energyNeeded = distance.dividing(PRECISION).multiplying(this.engines.drain)
470
+ return UInt64.from(this.energy ?? 0).gte(energyNeeded)
471
+ }
472
+
473
+ calculateSaleValue(prices: Map<number, UInt64>): {
474
+ revenue: UInt64
475
+ profit: UInt64
476
+ cost: UInt64
477
+ } {
478
+ if (this.cargo.length === 0) {
479
+ return {revenue: UInt64.from(0), profit: UInt64.from(0), cost: UInt64.from(0)}
480
+ }
481
+
482
+ let revenue = UInt64.from(0)
483
+ let cost = UInt64.from(0)
484
+
485
+ for (const item of this.cargo) {
486
+ if (UInt32.from(item.quantity).equals(UInt32.from(0))) continue
487
+
488
+ const goodId = Number(item.good_id)
489
+ const salePrice = prices.get(goodId)
490
+
491
+ if (salePrice) {
492
+ revenue = revenue.adding(salePrice.multiplying(item.quantity))
493
+ }
494
+
495
+ cost = cost.adding(item.unit_cost.multiplying(item.quantity))
496
+ }
497
+
498
+ const profit = revenue.gte(cost) ? revenue.subtracting(cost) : UInt64.from(0)
499
+
500
+ return {
501
+ revenue,
502
+ profit,
503
+ cost,
504
+ }
505
+ }
506
+
507
+ calculateSaleValueFromArray(prices: UInt64[]): {
508
+ revenue: UInt64
509
+ profit: UInt64
510
+ cost: UInt64
511
+ } {
512
+ const priceMap = new Map<number, UInt64>()
513
+ prices.forEach((price, index) => {
514
+ priceMap.set(index, price)
515
+ })
516
+ return this.calculateSaleValue(priceMap)
517
+ }
518
+
519
+ afterSellGoods(goodsToSell: Array<{goodId: number; quantity: number}>): EntityInventory[] {
520
+ if (this.cargo.length === 0) {
521
+ return []
522
+ }
523
+
524
+ return this.cargo.map((item) => {
525
+ const saleItem = goodsToSell.find((s) => Number(item.good_id) === s.goodId)
526
+ if (!saleItem) {
527
+ return new EntityInventory(item)
528
+ }
529
+
530
+ const currentQty = Number(item.quantity)
531
+ const newQty = Math.max(0, currentQty - saleItem.quantity)
532
+
533
+ return new EntityInventory(
534
+ ServerContract.Types.cargo_item.from({
535
+ good_id: item.good_id,
536
+ quantity: UInt32.from(newQty),
537
+ unit_cost: item.unit_cost,
538
+ })
539
+ )
540
+ })
541
+ }
542
+
543
+ afterSellAllGoods(): EntityInventory[] {
544
+ if (this.cargo.length === 0) {
545
+ return []
546
+ }
547
+
548
+ return this.cargo.map(
549
+ (item) =>
550
+ new EntityInventory(
551
+ ServerContract.Types.cargo_item.from({
552
+ good_id: item.good_id,
553
+ quantity: UInt32.from(0),
554
+ unit_cost: item.unit_cost,
555
+ })
556
+ )
557
+ )
558
+ }
559
+ }