@shipload/sdk 2.0.0-rc1 → 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.
- package/README.md +349 -1
- package/lib/shipload.d.ts +609 -248
- package/lib/shipload.js +1683 -1031
- package/lib/shipload.js.map +1 -1
- package/lib/shipload.m.js +1613 -1047
- package/lib/shipload.m.js.map +1 -1
- package/package.json +1 -2
- package/src/capabilities/extraction.ts +37 -0
- package/src/capabilities/guards.ts +43 -0
- package/src/capabilities/index.ts +5 -0
- package/src/capabilities/loading.ts +8 -0
- package/src/capabilities/movement.ts +29 -0
- package/src/capabilities/storage.ts +67 -0
- package/src/contracts/server.ts +340 -136
- package/src/data/goods.json +2 -2
- package/src/entities/cargo-utils.ts +96 -1
- package/src/entities/container.ts +70 -0
- package/src/entities/inventory-accessor.ts +46 -0
- package/src/entities/location.ts +22 -8
- package/src/entities/makers.ts +69 -0
- package/src/entities/player.ts +2 -1
- package/src/entities/ship.ts +86 -437
- package/src/entities/warehouse.ts +28 -144
- package/src/index-module.ts +34 -1
- package/src/managers/actions.ts +60 -28
- package/src/managers/entities.ts +22 -5
- package/src/managers/locations.ts +28 -9
- package/src/managers/trades.ts +2 -2
- package/src/market/market.ts +3 -4
- package/src/scheduling/accessor.ts +82 -0
- package/src/scheduling/projection.ts +125 -53
- package/src/scheduling/schedule.ts +24 -0
- package/src/trading/collect.ts +0 -1
- package/src/trading/deal.ts +0 -1
- package/src/travel/travel.ts +63 -2
- package/src/types/capabilities.ts +79 -0
- package/src/types/entity-traits.ts +70 -0
- package/src/types/entity.ts +36 -0
- package/src/types/index.ts +3 -0
- package/src/types.ts +75 -8
- package/src/utils/hash.ts +1 -1
- package/src/utils/system.ts +132 -4
package/src/data/goods.json
CHANGED
|
@@ -7,14 +7,14 @@
|
|
|
7
7
|
"mass": 15000
|
|
8
8
|
},
|
|
9
9
|
{
|
|
10
|
-
"id":
|
|
10
|
+
"id": 26,
|
|
11
11
|
"name": "Iron",
|
|
12
12
|
"description": "A versatile metal used in construction and manufacturing.",
|
|
13
13
|
"base_price": 125,
|
|
14
14
|
"mass": 40000
|
|
15
15
|
},
|
|
16
16
|
{
|
|
17
|
-
"id":
|
|
17
|
+
"id": 29,
|
|
18
18
|
"name": "Copper",
|
|
19
19
|
"description": "A conductive metal vital for electronics and wiring.",
|
|
20
20
|
"base_price": 200,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {UInt64, UInt64Type} from '@wharfkit/antelope'
|
|
1
|
+
import {UInt32, UInt64, UInt64Type} from '@wharfkit/antelope'
|
|
2
2
|
import {EntityInventory} from './entity-inventory'
|
|
3
|
+
import {ServerContract} from '../contracts'
|
|
3
4
|
|
|
4
5
|
export interface CargoData {
|
|
5
6
|
cargo: EntityInventory[]
|
|
@@ -45,3 +46,97 @@ export function availableCapacity(currentMass: UInt64, maxCapacity: UInt64): UIn
|
|
|
45
46
|
export function isFull(currentMass: UInt64, maxCapacity: UInt64): boolean {
|
|
46
47
|
return currentMass.gte(maxCapacity)
|
|
47
48
|
}
|
|
49
|
+
|
|
50
|
+
export interface SaleValue {
|
|
51
|
+
revenue: UInt64
|
|
52
|
+
profit: UInt64
|
|
53
|
+
cost: UInt64
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function calculateSaleValue(
|
|
57
|
+
cargo: ServerContract.Types.cargo_item[],
|
|
58
|
+
prices: Map<number, UInt64>
|
|
59
|
+
): SaleValue {
|
|
60
|
+
if (cargo.length === 0) {
|
|
61
|
+
return {revenue: UInt64.from(0), profit: UInt64.from(0), cost: UInt64.from(0)}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let revenue = UInt64.from(0)
|
|
65
|
+
let cost = UInt64.from(0)
|
|
66
|
+
|
|
67
|
+
for (const item of cargo) {
|
|
68
|
+
if (UInt32.from(item.quantity).equals(UInt32.from(0))) continue
|
|
69
|
+
|
|
70
|
+
const goodId = Number(item.good_id)
|
|
71
|
+
const salePrice = prices.get(goodId)
|
|
72
|
+
|
|
73
|
+
if (salePrice) {
|
|
74
|
+
revenue = revenue.adding(salePrice.multiplying(item.quantity))
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
cost = cost.adding(item.unit_cost.multiplying(item.quantity))
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const profit = revenue.gte(cost) ? revenue.subtracting(cost) : UInt64.from(0)
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
revenue,
|
|
84
|
+
profit,
|
|
85
|
+
cost,
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function calculateSaleValueFromArray(
|
|
90
|
+
cargo: ServerContract.Types.cargo_item[],
|
|
91
|
+
prices: UInt64[]
|
|
92
|
+
): SaleValue {
|
|
93
|
+
const priceMap = new Map<number, UInt64>()
|
|
94
|
+
prices.forEach((price, index) => {
|
|
95
|
+
priceMap.set(index, price)
|
|
96
|
+
})
|
|
97
|
+
return calculateSaleValue(cargo, priceMap)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function afterSellGoods(
|
|
101
|
+
cargo: ServerContract.Types.cargo_item[],
|
|
102
|
+
goodsToSell: Array<{goodId: number; quantity: number}>
|
|
103
|
+
): EntityInventory[] {
|
|
104
|
+
if (cargo.length === 0) {
|
|
105
|
+
return []
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return cargo.map((item) => {
|
|
109
|
+
const saleItem = goodsToSell.find((s) => Number(item.good_id) === s.goodId)
|
|
110
|
+
if (!saleItem) {
|
|
111
|
+
return new EntityInventory(item)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const currentQty = Number(item.quantity)
|
|
115
|
+
const newQty = Math.max(0, currentQty - saleItem.quantity)
|
|
116
|
+
|
|
117
|
+
return new EntityInventory(
|
|
118
|
+
ServerContract.Types.cargo_item.from({
|
|
119
|
+
good_id: item.good_id,
|
|
120
|
+
quantity: UInt32.from(newQty),
|
|
121
|
+
unit_cost: item.unit_cost,
|
|
122
|
+
})
|
|
123
|
+
)
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function afterSellAllGoods(cargo: ServerContract.Types.cargo_item[]): EntityInventory[] {
|
|
128
|
+
if (cargo.length === 0) {
|
|
129
|
+
return []
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return cargo.map(
|
|
133
|
+
(item) =>
|
|
134
|
+
new EntityInventory(
|
|
135
|
+
ServerContract.Types.cargo_item.from({
|
|
136
|
+
good_id: item.good_id,
|
|
137
|
+
quantity: UInt32.from(0),
|
|
138
|
+
unit_cost: item.unit_cost,
|
|
139
|
+
})
|
|
140
|
+
)
|
|
141
|
+
)
|
|
142
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import {UInt64, UInt64Type} from '@wharfkit/antelope'
|
|
2
|
+
import {ServerContract} from '../contracts'
|
|
3
|
+
import {CoordinatesType} from '../types'
|
|
4
|
+
import {Location} from './location'
|
|
5
|
+
import {ScheduleAccessor} from '../scheduling/accessor'
|
|
6
|
+
import * as schedule from '../scheduling/schedule'
|
|
7
|
+
|
|
8
|
+
export interface ContainerStateInput {
|
|
9
|
+
id: UInt64Type
|
|
10
|
+
owner: string
|
|
11
|
+
name: string
|
|
12
|
+
coordinates: CoordinatesType | {x: number; y: number; z?: number}
|
|
13
|
+
hullmass: number
|
|
14
|
+
capacity: number
|
|
15
|
+
cargomass?: number
|
|
16
|
+
schedule?: ServerContract.Types.schedule
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class Container extends ServerContract.Types.entity_info {
|
|
20
|
+
private _sched?: ScheduleAccessor
|
|
21
|
+
|
|
22
|
+
get name(): string {
|
|
23
|
+
return this.entity_name
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get sched(): ScheduleAccessor {
|
|
27
|
+
return (this._sched ??= new ScheduleAccessor(this))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get isIdle(): boolean {
|
|
31
|
+
return this.is_idle
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
isLoading(now: Date): boolean {
|
|
35
|
+
return schedule.isLoading(this, now)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
isUnloading(now: Date): boolean {
|
|
39
|
+
return schedule.isUnloading(this, now)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
get location(): Location {
|
|
43
|
+
return Location.from(this.coordinates)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
get totalMass(): UInt64 {
|
|
47
|
+
return UInt64.from(this.hullmass ?? 0).adding(this.cargomass)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
get maxCapacity(): UInt64 {
|
|
51
|
+
return UInt64.from(this.capacity)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
get availableCapacity(): UInt64 {
|
|
55
|
+
const cargo = UInt64.from(this.cargomass)
|
|
56
|
+
return cargo.gte(this.maxCapacity) ? UInt64.from(0) : this.maxCapacity.subtracting(cargo)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
hasSpace(additionalMass: UInt64): boolean {
|
|
60
|
+
return UInt64.from(this.cargomass).adding(additionalMass).lte(this.maxCapacity)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
get isFull(): boolean {
|
|
64
|
+
return UInt64.from(this.cargomass).gte(this.maxCapacity)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
get orbitalAltitude(): number {
|
|
68
|
+
return this.coordinates.z?.toNumber() || 0
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import {UInt64, UInt64Type} from '@wharfkit/antelope'
|
|
2
|
+
import {EntityInventory} from './entity-inventory'
|
|
3
|
+
import {HasCargo} from '../capabilities/storage'
|
|
4
|
+
|
|
5
|
+
export type {HasCargo}
|
|
6
|
+
|
|
7
|
+
export class InventoryAccessor {
|
|
8
|
+
private _items?: EntityInventory[]
|
|
9
|
+
|
|
10
|
+
constructor(private readonly entity: HasCargo) {}
|
|
11
|
+
|
|
12
|
+
get items(): EntityInventory[] {
|
|
13
|
+
if (!this._items) {
|
|
14
|
+
this._items = this.entity.cargo.map((item) => new EntityInventory(item))
|
|
15
|
+
}
|
|
16
|
+
return this._items
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
get totalMass(): UInt64 {
|
|
20
|
+
return this.items.reduce((sum, c) => sum.adding(c.totalMass), UInt64.from(0))
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get totalValue(): UInt64 {
|
|
24
|
+
return this.items.reduce((sum, c) => sum.adding(c.totalCost), UInt64.from(0))
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
forGood(goodId: UInt64Type): EntityInventory | undefined {
|
|
28
|
+
return this.items.find((c) => c.good_id.equals(goodId))
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get sellable(): EntityInventory[] {
|
|
32
|
+
return this.items.filter((c) => c.hasCargo)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
get hasSellable(): boolean {
|
|
36
|
+
return this.items.some((c) => c.hasCargo)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
get sellableCount(): number {
|
|
40
|
+
return this.items.filter((c) => c.hasCargo).length
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function createInventoryAccessor(entity: HasCargo): InventoryAccessor {
|
|
45
|
+
return new InventoryAccessor(entity)
|
|
46
|
+
}
|
package/src/entities/location.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {Checksum256, Checksum256Type, UInt16, UInt16Type, UInt64} from '@wharfkit/antelope'
|
|
2
2
|
import {ServerContract} from '../contracts'
|
|
3
|
-
import {Coordinates, CoordinatesType, Distance, GoodPrice} from '../types'
|
|
4
|
-
import {hasSystem} from '../utils/system'
|
|
3
|
+
import {Coordinates, CoordinatesType, Distance, GoodPrice, LocationType} from '../types'
|
|
4
|
+
import {getLocationType, hasSystem, isExtractableLocation} from '../utils/system'
|
|
5
5
|
import {findNearbyPlanets} from '../travel/travel'
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -13,7 +13,7 @@ export class Location {
|
|
|
13
13
|
private _marketPrices?: GoodPrice[]
|
|
14
14
|
private _gameSeed?: Checksum256
|
|
15
15
|
private _hasSystem?: boolean
|
|
16
|
-
private _locationRows?: ServerContract.Types.
|
|
16
|
+
private _locationRows?: ServerContract.Types.supply_row[]
|
|
17
17
|
private _epoch?: UInt64
|
|
18
18
|
|
|
19
19
|
constructor(coordinates: CoordinatesType) {
|
|
@@ -28,7 +28,7 @@ export class Location {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
|
-
* Check if this location has a system (planet)
|
|
31
|
+
* Check if this location has a system (planet, asteroid, or nebula)
|
|
32
32
|
*/
|
|
33
33
|
hasSystemAt(gameSeed: Checksum256Type): boolean {
|
|
34
34
|
const seed = Checksum256.from(gameSeed)
|
|
@@ -39,6 +39,20 @@ export class Location {
|
|
|
39
39
|
return this._hasSystem
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Get the location type (EMPTY, PLANET, ASTEROID, or NEBULA)
|
|
44
|
+
*/
|
|
45
|
+
getLocationTypeAt(gameSeed: Checksum256Type): LocationType {
|
|
46
|
+
return getLocationType(gameSeed, this.coordinates)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Check if this location is extractable (asteroid or nebula)
|
|
51
|
+
*/
|
|
52
|
+
isExtractableAt(gameSeed: Checksum256Type): boolean {
|
|
53
|
+
return isExtractableLocation(this.getLocationTypeAt(gameSeed))
|
|
54
|
+
}
|
|
55
|
+
|
|
42
56
|
/**
|
|
43
57
|
* Set cached market prices for this location
|
|
44
58
|
*/
|
|
@@ -79,7 +93,7 @@ export class Location {
|
|
|
79
93
|
/**
|
|
80
94
|
* Set location rows (supply data) for this location
|
|
81
95
|
*/
|
|
82
|
-
setLocationRows(rows: ServerContract.Types.
|
|
96
|
+
setLocationRows(rows: ServerContract.Types.supply_row[], epoch: UInt64): void {
|
|
83
97
|
this._locationRows = rows
|
|
84
98
|
this._epoch = epoch
|
|
85
99
|
}
|
|
@@ -87,7 +101,7 @@ export class Location {
|
|
|
87
101
|
/**
|
|
88
102
|
* Get cached location rows (supply data)
|
|
89
103
|
*/
|
|
90
|
-
get locationRows(): ServerContract.Types.
|
|
104
|
+
get locationRows(): ServerContract.Types.supply_row[] | undefined {
|
|
91
105
|
return this._locationRows
|
|
92
106
|
}
|
|
93
107
|
|
|
@@ -107,7 +121,7 @@ export class Location {
|
|
|
107
121
|
* Get all available goods at this location (goods with supply > 0)
|
|
108
122
|
* Returns undefined if location rows not cached
|
|
109
123
|
*/
|
|
110
|
-
get availableGoods(): ServerContract.Types.
|
|
124
|
+
get availableGoods(): ServerContract.Types.supply_row[] | undefined {
|
|
111
125
|
if (!this._locationRows) return undefined
|
|
112
126
|
return this._locationRows.filter(
|
|
113
127
|
(r) => this._epoch && r.epoch.equals(this._epoch) && r.supply.gt(UInt16.from(0))
|
|
@@ -209,7 +223,7 @@ export class Location {
|
|
|
209
223
|
: UInt16.from(0)
|
|
210
224
|
: currentSupply.adding(quantityDelta)
|
|
211
225
|
|
|
212
|
-
return ServerContract.Types.
|
|
226
|
+
return ServerContract.Types.supply_row.from({
|
|
213
227
|
id: row.id,
|
|
214
228
|
coordinates: row.coordinates,
|
|
215
229
|
epoch: row.epoch,
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import {Name, UInt16, UInt32, UInt64} from '@wharfkit/antelope'
|
|
2
|
+
import {ServerContract} from '../contracts'
|
|
3
|
+
import {Ship, ShipStateInput} from './ship'
|
|
4
|
+
import {Warehouse, WarehouseStateInput} from './warehouse'
|
|
5
|
+
import {Container, ContainerStateInput} from './container'
|
|
6
|
+
|
|
7
|
+
export function makeShip(state: ShipStateInput): Ship {
|
|
8
|
+
const entityInfo = ServerContract.Types.entity_info.from({
|
|
9
|
+
type: Name.from('ship'),
|
|
10
|
+
id: UInt64.from(state.id),
|
|
11
|
+
owner: Name.from(state.owner),
|
|
12
|
+
entity_name: state.name,
|
|
13
|
+
coordinates: ServerContract.Types.coordinates.from(state.coordinates),
|
|
14
|
+
hullmass: UInt32.from(state.hullmass),
|
|
15
|
+
capacity: UInt32.from(state.capacity),
|
|
16
|
+
energy: UInt16.from(state.energy),
|
|
17
|
+
cargomass: UInt32.from(0),
|
|
18
|
+
cargo: state.cargo || [],
|
|
19
|
+
is_idle: !state.schedule,
|
|
20
|
+
current_task_elapsed: UInt32.from(0),
|
|
21
|
+
current_task_remaining: UInt32.from(0),
|
|
22
|
+
pending_tasks: [],
|
|
23
|
+
engines: state.engines,
|
|
24
|
+
generator: state.generator,
|
|
25
|
+
loaders: state.loaders,
|
|
26
|
+
schedule: state.schedule,
|
|
27
|
+
})
|
|
28
|
+
return new Ship(entityInfo)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function makeWarehouse(state: WarehouseStateInput): Warehouse {
|
|
32
|
+
const entityInfo = ServerContract.Types.entity_info.from({
|
|
33
|
+
type: Name.from('warehouse'),
|
|
34
|
+
id: UInt64.from(state.id),
|
|
35
|
+
owner: Name.from(state.owner),
|
|
36
|
+
entity_name: state.name,
|
|
37
|
+
coordinates: ServerContract.Types.coordinates.from(state.coordinates),
|
|
38
|
+
capacity: UInt32.from(state.capacity),
|
|
39
|
+
cargomass: UInt32.from(0),
|
|
40
|
+
cargo: state.cargo || [],
|
|
41
|
+
loaders: state.loaders,
|
|
42
|
+
is_idle: !state.schedule,
|
|
43
|
+
current_task_elapsed: UInt32.from(0),
|
|
44
|
+
current_task_remaining: UInt32.from(0),
|
|
45
|
+
pending_tasks: [],
|
|
46
|
+
schedule: state.schedule,
|
|
47
|
+
})
|
|
48
|
+
return new Warehouse(entityInfo)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function makeContainer(state: ContainerStateInput): Container {
|
|
52
|
+
const entityInfo = ServerContract.Types.entity_info.from({
|
|
53
|
+
type: Name.from('container'),
|
|
54
|
+
id: UInt64.from(state.id),
|
|
55
|
+
owner: Name.from(state.owner),
|
|
56
|
+
entity_name: state.name,
|
|
57
|
+
coordinates: ServerContract.Types.coordinates.from(state.coordinates),
|
|
58
|
+
hullmass: UInt32.from(state.hullmass),
|
|
59
|
+
capacity: UInt32.from(state.capacity),
|
|
60
|
+
cargomass: UInt32.from(state.cargomass || 0),
|
|
61
|
+
cargo: [],
|
|
62
|
+
is_idle: !state.schedule,
|
|
63
|
+
current_task_elapsed: UInt32.from(0),
|
|
64
|
+
current_task_remaining: UInt32.from(0),
|
|
65
|
+
pending_tasks: [],
|
|
66
|
+
schedule: state.schedule,
|
|
67
|
+
})
|
|
68
|
+
return new Container(entityInfo)
|
|
69
|
+
}
|
package/src/entities/player.ts
CHANGED
|
@@ -37,7 +37,8 @@ export class Player extends ServerContract.Types.player_row {
|
|
|
37
37
|
}
|
|
38
38
|
// Constants for game rules (match smart contract)
|
|
39
39
|
private static readonly MAX_LOAN = 1000000
|
|
40
|
-
|
|
40
|
+
// Contract formula: 2500 * pow(5, sequence - 1) = 500 * pow(5, sequence)
|
|
41
|
+
private static readonly BASE_SHIP_COST = 500
|
|
41
42
|
private static readonly SHIP_COST_MULTIPLIER = 5
|
|
42
43
|
|
|
43
44
|
// Optional ship count for nextShipCost calculation
|