@shipload/sdk 1.0.0-next.0 → 1.0.0-next.10
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/lib/shipload.d.ts +512 -320
- package/lib/shipload.js +1960 -1060
- package/lib/shipload.js.map +1 -1
- package/lib/shipload.m.js +1920 -1056
- package/lib/shipload.m.js.map +1 -1
- package/package.json +8 -3
- package/src/capabilities/modules.ts +3 -0
- package/src/capabilities/storage.ts +1 -1
- package/src/contracts/platform.ts +13 -1
- package/src/contracts/server.ts +227 -282
- package/src/data/capabilities.ts +5 -330
- package/src/data/capability-formulas.ts +70 -0
- package/src/data/catalog.ts +0 -5
- package/src/data/colors.ts +2 -16
- package/src/data/entities.json +33 -10
- package/src/data/item-ids.ts +3 -1
- package/src/data/items.json +258 -0
- package/src/data/metadata.ts +57 -1
- package/src/data/recipes-runtime.ts +1 -0
- package/src/data/recipes.json +82 -11
- package/src/derivation/capability-mappings.ts +122 -0
- package/src/derivation/index.ts +1 -0
- package/src/derivation/resources.ts +116 -37
- package/src/derivation/stats.ts +1 -2
- package/src/entities/container.ts +25 -10
- package/src/entities/extractor.ts +144 -0
- package/src/entities/gamestate.ts +0 -9
- package/src/entities/makers.ts +71 -20
- package/src/entities/ship-deploy.ts +114 -56
- package/src/entities/ship.ts +17 -0
- package/src/entities/slot-multiplier.ts +21 -0
- package/src/entities/warehouse.ts +20 -3
- package/src/index-module.ts +67 -26
- package/src/managers/actions.ts +53 -80
- package/src/managers/entities.ts +31 -17
- package/src/managers/locations.ts +2 -20
- package/src/nft/atomicdata.ts +125 -0
- package/src/nft/description.ts +41 -7
- package/src/nft/index.ts +1 -0
- package/src/resolution/resolve-item.ts +17 -9
- package/src/scheduling/accessor.ts +4 -0
- package/src/scheduling/projection.ts +8 -0
- package/src/scheduling/schedule.ts +15 -1
- package/src/scheduling/task-cargo.ts +47 -0
- package/src/subscriptions/connection.ts +50 -2
- package/src/subscriptions/manager.ts +81 -2
- package/src/travel/travel.ts +61 -2
- package/src/types/entity-traits.ts +64 -1
- package/src/types.ts +11 -1
- package/src/utils/cargo.ts +27 -0
- package/src/utils/system.ts +25 -24
|
@@ -39,6 +39,7 @@ import type {ServerContract} from '../contracts'
|
|
|
39
39
|
import {
|
|
40
40
|
ITEM_CONTAINER_T1_PACKED,
|
|
41
41
|
ITEM_CONTAINER_T2_PACKED,
|
|
42
|
+
ITEM_EXTRACTOR_T1_PACKED,
|
|
42
43
|
ITEM_SHIP_T1_PACKED,
|
|
43
44
|
ITEM_WAREHOUSE_T1_PACKED,
|
|
44
45
|
} from '../data/item-ids'
|
|
@@ -155,7 +156,8 @@ function resolveComponent(id: number, stats?: UInt64Type): ResolvedItem {
|
|
|
155
156
|
|
|
156
157
|
function computeCapabilityGroup(
|
|
157
158
|
moduleType: number,
|
|
158
|
-
stats: Record<string, number
|
|
159
|
+
stats: Record<string, number>,
|
|
160
|
+
tier: number
|
|
159
161
|
): ResolvedAttributeGroup | undefined {
|
|
160
162
|
switch (moduleType) {
|
|
161
163
|
case MODULE_ENGINE: {
|
|
@@ -179,7 +181,7 @@ function computeCapabilityGroup(
|
|
|
179
181
|
}
|
|
180
182
|
}
|
|
181
183
|
case MODULE_GATHERER: {
|
|
182
|
-
const caps = computeGathererCapabilities(stats)
|
|
184
|
+
const caps = computeGathererCapabilities(stats, tier)
|
|
183
185
|
return {
|
|
184
186
|
capability: 'Gatherer',
|
|
185
187
|
attributes: [
|
|
@@ -223,10 +225,11 @@ function computeCapabilityGroup(
|
|
|
223
225
|
}
|
|
224
226
|
}
|
|
225
227
|
case MODULE_STORAGE: {
|
|
226
|
-
const str = stats.strength
|
|
227
|
-
const
|
|
228
|
-
const
|
|
229
|
-
const
|
|
228
|
+
const str = stats.strength
|
|
229
|
+
const den = stats.density
|
|
230
|
+
const hrd = stats.hardness
|
|
231
|
+
const sat = stats.saturation
|
|
232
|
+
const statSum = str + den + hrd + sat
|
|
230
233
|
const pct = 10 + Math.floor((statSum * 10) / 2997)
|
|
231
234
|
return {capability: 'Storage', attributes: [{label: 'Capacity Bonus', value: pct}]}
|
|
232
235
|
}
|
|
@@ -241,7 +244,7 @@ function resolveModule(id: number, stats?: UInt64Type): ResolvedItem {
|
|
|
241
244
|
if (stats !== undefined) {
|
|
242
245
|
const decoded = decodeCraftedItemStats(id, toBigStats(stats))
|
|
243
246
|
const modType = getModuleCapabilityType(id)
|
|
244
|
-
const group = computeCapabilityGroup(modType, decoded)
|
|
247
|
+
const group = computeCapabilityGroup(modType, decoded, item.tier)
|
|
245
248
|
if (group) attributes = [group]
|
|
246
249
|
}
|
|
247
250
|
return {
|
|
@@ -268,6 +271,8 @@ function hullCapsForEntity(
|
|
|
268
271
|
return computeShipHullCapabilities(decoded)
|
|
269
272
|
case ITEM_WAREHOUSE_T1_PACKED:
|
|
270
273
|
return computeWarehouseHullCapabilities(decoded)
|
|
274
|
+
case ITEM_EXTRACTOR_T1_PACKED:
|
|
275
|
+
return computeShipHullCapabilities(decoded)
|
|
271
276
|
case ITEM_CONTAINER_T1_PACKED:
|
|
272
277
|
return computeContainerCapabilities(decoded)
|
|
273
278
|
case ITEM_CONTAINER_T2_PACKED:
|
|
@@ -310,13 +315,16 @@ function resolveEntity(
|
|
|
310
315
|
const modStats = BigInt(mod.installed.stats.toString())
|
|
311
316
|
const decodedStats = decodeCraftedItemStats(modItemId, modStats)
|
|
312
317
|
const modType = getModuleCapabilityType(modItemId)
|
|
313
|
-
const group = computeCapabilityGroup(modType, decodedStats)
|
|
314
318
|
let modName = 'Module'
|
|
319
|
+
let modTier = 1
|
|
315
320
|
try {
|
|
316
|
-
|
|
321
|
+
const modItem = getItem(modItemId)
|
|
322
|
+
modName = modItem.name
|
|
323
|
+
modTier = modItem.tier
|
|
317
324
|
} catch {
|
|
318
325
|
modName = itemMetadata[modItemId]?.name ?? 'Module'
|
|
319
326
|
}
|
|
327
|
+
const group = computeCapabilityGroup(modType, decodedStats, modTier)
|
|
320
328
|
return {
|
|
321
329
|
name: modName,
|
|
322
330
|
installed: true,
|
|
@@ -72,6 +72,10 @@ export class ScheduleAccessor {
|
|
|
72
72
|
return schedule.currentTaskProgress(this.entity, now)
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
currentTaskProgressFloat(now: Date): number {
|
|
76
|
+
return schedule.currentTaskProgressFloat(this.entity, now)
|
|
77
|
+
}
|
|
78
|
+
|
|
75
79
|
progress(now: Date): number {
|
|
76
80
|
return schedule.scheduleProgress(this.entity, now)
|
|
77
81
|
}
|
|
@@ -267,6 +267,10 @@ function applyTask(projected: ProjectedEntity, task: ServerContract.Types.task):
|
|
|
267
267
|
case TaskType.DEPLOY:
|
|
268
268
|
applyDeployTask(projected, task)
|
|
269
269
|
break
|
|
270
|
+
case TaskType.UNDEPLOY:
|
|
271
|
+
case TaskType.WRAP_ENTITY:
|
|
272
|
+
case TaskType.DEMOLISH:
|
|
273
|
+
break
|
|
270
274
|
}
|
|
271
275
|
}
|
|
272
276
|
|
|
@@ -448,6 +452,10 @@ export function projectEntityAt(entity: Projectable, now: Date): ProjectedEntity
|
|
|
448
452
|
case TaskType.DEPLOY:
|
|
449
453
|
if (taskComplete) applyDeployTask(projected, task)
|
|
450
454
|
break
|
|
455
|
+
case TaskType.UNDEPLOY:
|
|
456
|
+
case TaskType.WRAP_ENTITY:
|
|
457
|
+
case TaskType.DEMOLISH:
|
|
458
|
+
break
|
|
451
459
|
}
|
|
452
460
|
}
|
|
453
461
|
|
|
@@ -77,7 +77,7 @@ export function currentTaskIndex(entity: ScheduleData, now: Date): number {
|
|
|
77
77
|
timeAccum += taskDuration
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
return
|
|
80
|
+
return -1
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
export function currentTask(entity: ScheduleData, now: Date): Task | undefined {
|
|
@@ -147,6 +147,20 @@ export function currentTaskProgress(entity: ScheduleData, now: Date): number {
|
|
|
147
147
|
return Math.min(1, elapsed / duration)
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
+
export function currentTaskProgressFloat(entity: ScheduleData, now: Date): number {
|
|
151
|
+
if (!entity.schedule || entity.schedule.tasks.length === 0) return 0
|
|
152
|
+
const index = currentTaskIndex(entity, now)
|
|
153
|
+
if (index < 0) return 0
|
|
154
|
+
const task = entity.schedule.tasks[index]
|
|
155
|
+
const durationMs = task.duration.toNumber() * 1000
|
|
156
|
+
if (durationMs === 0) return 1
|
|
157
|
+
const startedMs = entity.schedule.started.toDate().getTime()
|
|
158
|
+
const taskStartMs = startedMs + getTaskStartTime(entity, index) * 1000
|
|
159
|
+
const elapsedMs = now.getTime() - taskStartMs
|
|
160
|
+
if (elapsedMs <= 0) return 0
|
|
161
|
+
return Math.min(1, elapsedMs / durationMs)
|
|
162
|
+
}
|
|
163
|
+
|
|
150
164
|
export function scheduleProgress(entity: ScheduleData, now: Date): number {
|
|
151
165
|
const duration = scheduleDuration(entity)
|
|
152
166
|
if (duration === 0) return hasSchedule(entity) ? 1 : 0
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type {ServerContract} from '../contracts'
|
|
2
|
+
import {TaskType} from '../types'
|
|
3
|
+
|
|
4
|
+
export type TaskCargoDirection = 'in' | 'out'
|
|
5
|
+
|
|
6
|
+
export interface TaskCargoChange {
|
|
7
|
+
direction: TaskCargoDirection
|
|
8
|
+
item_id: number
|
|
9
|
+
stats: bigint
|
|
10
|
+
modules: ServerContract.Types.module_entry[]
|
|
11
|
+
quantity: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function toChange(
|
|
15
|
+
item: ServerContract.Types.cargo_item,
|
|
16
|
+
direction: TaskCargoDirection
|
|
17
|
+
): TaskCargoChange {
|
|
18
|
+
return {
|
|
19
|
+
direction,
|
|
20
|
+
item_id: Number(item.item_id),
|
|
21
|
+
stats: BigInt(item.stats.toString()),
|
|
22
|
+
modules: item.modules ?? [],
|
|
23
|
+
quantity: Number(item.quantity),
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function taskCargoChanges(task: ServerContract.Types.task): TaskCargoChange[] {
|
|
28
|
+
const items = task.cargo ?? []
|
|
29
|
+
if (items.length === 0) return []
|
|
30
|
+
switch (Number(task.type)) {
|
|
31
|
+
case TaskType.LOAD:
|
|
32
|
+
case TaskType.UNWRAP:
|
|
33
|
+
return items.map((i) => toChange(i, 'in'))
|
|
34
|
+
case TaskType.GATHER:
|
|
35
|
+
return task.entitytarget ? [] : items.map((i) => toChange(i, 'in'))
|
|
36
|
+
case TaskType.UNLOAD:
|
|
37
|
+
case TaskType.WRAP:
|
|
38
|
+
return items.map((i) => toChange(i, 'out'))
|
|
39
|
+
case TaskType.CRAFT:
|
|
40
|
+
return [
|
|
41
|
+
...items.slice(0, -1).map((i) => toChange(i, 'out')),
|
|
42
|
+
toChange(items[items.length - 1], 'in'),
|
|
43
|
+
]
|
|
44
|
+
default:
|
|
45
|
+
return []
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -7,6 +7,9 @@ export interface WebSocketConnectionOptions {
|
|
|
7
7
|
url: string
|
|
8
8
|
onMessage: (message: ServerMessage) => void
|
|
9
9
|
onStateChange?: (state: ConnectionState) => void
|
|
10
|
+
minReconnectDelay?: number
|
|
11
|
+
pingIntervalMs?: number
|
|
12
|
+
pongTimeoutMs?: number
|
|
10
13
|
}
|
|
11
14
|
|
|
12
15
|
export class WebSocketConnection {
|
|
@@ -19,15 +22,26 @@ export class WebSocketConnection {
|
|
|
19
22
|
private _state: ConnectionState = 'disconnected'
|
|
20
23
|
private shouldReconnect = true
|
|
21
24
|
private sendQueue: string[] = []
|
|
25
|
+
private minReconnectDelay: number
|
|
26
|
+
private pingIntervalMs: number
|
|
27
|
+
private pongTimeoutMs: number
|
|
28
|
+
private pingTimer: ReturnType<typeof setInterval> | null = null
|
|
29
|
+
private staleTimer: ReturnType<typeof setTimeout> | null = null
|
|
22
30
|
|
|
23
|
-
private static readonly
|
|
31
|
+
private static readonly DEFAULT_MIN_RECONNECT_DELAY = 1000
|
|
24
32
|
private static readonly MAX_RECONNECT_DELAY = 30000
|
|
25
33
|
private static readonly RECONNECT_MULTIPLIER = 2
|
|
34
|
+
private static readonly DEFAULT_PING_INTERVAL_MS = 25000
|
|
35
|
+
private static readonly DEFAULT_PONG_TIMEOUT_MS = 10000
|
|
26
36
|
|
|
27
37
|
constructor(options: WebSocketConnectionOptions) {
|
|
28
38
|
this.url = options.url
|
|
29
39
|
this.onMessage = options.onMessage
|
|
30
40
|
this.onStateChange = options.onStateChange
|
|
41
|
+
this.minReconnectDelay =
|
|
42
|
+
options.minReconnectDelay ?? WebSocketConnection.DEFAULT_MIN_RECONNECT_DELAY
|
|
43
|
+
this.pingIntervalMs = options.pingIntervalMs ?? WebSocketConnection.DEFAULT_PING_INTERVAL_MS
|
|
44
|
+
this.pongTimeoutMs = options.pongTimeoutMs ?? WebSocketConnection.DEFAULT_PONG_TIMEOUT_MS
|
|
31
45
|
}
|
|
32
46
|
|
|
33
47
|
get state(): ConnectionState {
|
|
@@ -64,9 +78,11 @@ export class WebSocketConnection {
|
|
|
64
78
|
) {
|
|
65
79
|
this.ws.send(this.sendQueue.shift()!)
|
|
66
80
|
}
|
|
81
|
+
this.startHeartbeat()
|
|
67
82
|
}
|
|
68
83
|
|
|
69
84
|
this.ws.onmessage = (event) => {
|
|
85
|
+
this.resetStaleTimer()
|
|
70
86
|
try {
|
|
71
87
|
const message = JSON.parse(event.data) as ServerMessage
|
|
72
88
|
this.onMessage(message)
|
|
@@ -77,6 +93,7 @@ export class WebSocketConnection {
|
|
|
77
93
|
}
|
|
78
94
|
|
|
79
95
|
this.ws.onclose = () => {
|
|
96
|
+
this.stopHeartbeat()
|
|
80
97
|
this.ws = null
|
|
81
98
|
this.sendQueue.length = 0
|
|
82
99
|
|
|
@@ -104,7 +121,7 @@ export class WebSocketConnection {
|
|
|
104
121
|
}
|
|
105
122
|
|
|
106
123
|
const delay = Math.min(
|
|
107
|
-
|
|
124
|
+
this.minReconnectDelay *
|
|
108
125
|
WebSocketConnection.RECONNECT_MULTIPLIER ** this.reconnectAttempts,
|
|
109
126
|
WebSocketConnection.MAX_RECONNECT_DELAY
|
|
110
127
|
)
|
|
@@ -126,6 +143,8 @@ export class WebSocketConnection {
|
|
|
126
143
|
this.reconnectTimeout = null
|
|
127
144
|
}
|
|
128
145
|
|
|
146
|
+
this.stopHeartbeat()
|
|
147
|
+
|
|
129
148
|
if (this.ws) {
|
|
130
149
|
this.ws.close()
|
|
131
150
|
this.ws = null
|
|
@@ -135,6 +154,35 @@ export class WebSocketConnection {
|
|
|
135
154
|
this.setState('disconnected')
|
|
136
155
|
}
|
|
137
156
|
|
|
157
|
+
private startHeartbeat() {
|
|
158
|
+
this.stopHeartbeat()
|
|
159
|
+
this.resetStaleTimer()
|
|
160
|
+
this.pingTimer = setInterval(() => {
|
|
161
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
162
|
+
this.ws.send(JSON.stringify({type: 'ping'}))
|
|
163
|
+
}
|
|
164
|
+
}, this.pingIntervalMs)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private stopHeartbeat() {
|
|
168
|
+
if (this.pingTimer) {
|
|
169
|
+
clearInterval(this.pingTimer)
|
|
170
|
+
this.pingTimer = null
|
|
171
|
+
}
|
|
172
|
+
if (this.staleTimer) {
|
|
173
|
+
clearTimeout(this.staleTimer)
|
|
174
|
+
this.staleTimer = null
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private resetStaleTimer() {
|
|
179
|
+
if (this.staleTimer) clearTimeout(this.staleTimer)
|
|
180
|
+
this.staleTimer = setTimeout(() => {
|
|
181
|
+
debug('No frames within ping interval + pong timeout — forcing reconnect')
|
|
182
|
+
if (this.ws) this.ws.close()
|
|
183
|
+
}, this.pingIntervalMs + this.pongTimeoutMs)
|
|
184
|
+
}
|
|
185
|
+
|
|
138
186
|
close() {
|
|
139
187
|
this.disconnect()
|
|
140
188
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {WebSocketConnection} from './connection'
|
|
1
|
+
import {WebSocketConnection, type ConnectionState} from './connection'
|
|
2
2
|
import type {
|
|
3
3
|
BoundingBox,
|
|
4
4
|
BoundsDeltaMessage,
|
|
@@ -22,6 +22,9 @@ export type EntityInstance = Ship | Warehouse | Container
|
|
|
22
22
|
|
|
23
23
|
export interface SubscriptionsOptions {
|
|
24
24
|
url: string
|
|
25
|
+
minReconnectDelay?: number
|
|
26
|
+
pingIntervalMs?: number
|
|
27
|
+
pongTimeoutMs?: number
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
export interface BoundsSubscriptionHandle {
|
|
@@ -31,6 +34,12 @@ export interface BoundsSubscriptionHandle {
|
|
|
31
34
|
current: Map<number, EntityInstance>
|
|
32
35
|
}
|
|
33
36
|
|
|
37
|
+
export interface OwnerSubscriptionHandle {
|
|
38
|
+
readonly subId: string
|
|
39
|
+
unsubscribe(): void
|
|
40
|
+
current: Map<number, EntityInstance>
|
|
41
|
+
}
|
|
42
|
+
|
|
34
43
|
export interface EntitySubscriptionHandle {
|
|
35
44
|
readonly subId: string
|
|
36
45
|
readonly entityType: SubscriptionEntityType
|
|
@@ -53,18 +62,26 @@ export class SubscriptionsManager {
|
|
|
53
62
|
private readonly boundsSubs = new Map<
|
|
54
63
|
string,
|
|
55
64
|
{
|
|
65
|
+
bounds?: BoundingBox
|
|
66
|
+
owner?: string
|
|
67
|
+
prioritizeOwner?: string
|
|
56
68
|
onSnapshot?: (entities: EntityInstance[]) => void
|
|
57
69
|
onUpdate?: (entity: EntityInstance) => void
|
|
58
70
|
onBoundsDelta?: (entered: EntityInstance[], exited: number[]) => void
|
|
59
|
-
handle: BoundsSubscriptionHandle
|
|
71
|
+
handle: BoundsSubscriptionHandle | OwnerSubscriptionHandle
|
|
60
72
|
}
|
|
61
73
|
>()
|
|
62
74
|
private subCounter = 0
|
|
75
|
+
private hasConnected = false
|
|
63
76
|
|
|
64
77
|
constructor(opts: SubscriptionsOptions) {
|
|
65
78
|
this.conn = new WebSocketConnection({
|
|
66
79
|
url: opts.url,
|
|
67
80
|
onMessage: (m) => this.onMessage(m),
|
|
81
|
+
onStateChange: (s) => this.onStateChange(s),
|
|
82
|
+
minReconnectDelay: opts.minReconnectDelay,
|
|
83
|
+
pingIntervalMs: opts.pingIntervalMs,
|
|
84
|
+
pongTimeoutMs: opts.pongTimeoutMs,
|
|
68
85
|
})
|
|
69
86
|
this.conn.connect()
|
|
70
87
|
}
|
|
@@ -139,6 +156,9 @@ export class SubscriptionsManager {
|
|
|
139
156
|
current: new Map(),
|
|
140
157
|
}
|
|
141
158
|
this.boundsSubs.set(subId, {
|
|
159
|
+
bounds,
|
|
160
|
+
owner: handlers.owner,
|
|
161
|
+
prioritizeOwner: handlers.prioritizeOwner,
|
|
142
162
|
onSnapshot: handlers.onSnapshot,
|
|
143
163
|
onUpdate: handlers.onUpdate,
|
|
144
164
|
onBoundsDelta: handlers.onBoundsDelta,
|
|
@@ -148,16 +168,75 @@ export class SubscriptionsManager {
|
|
|
148
168
|
return handle
|
|
149
169
|
}
|
|
150
170
|
|
|
171
|
+
subscribeOwner(
|
|
172
|
+
owner: string,
|
|
173
|
+
handlers: {
|
|
174
|
+
onSnapshot?: (entities: EntityInstance[]) => void
|
|
175
|
+
onUpdate?: (entity: EntityInstance) => void
|
|
176
|
+
} = {}
|
|
177
|
+
): OwnerSubscriptionHandle {
|
|
178
|
+
const subId = this.generateSubID('own')
|
|
179
|
+
const msg: SubscribeMessage = {
|
|
180
|
+
type: 'subscribe',
|
|
181
|
+
sub_id: subId,
|
|
182
|
+
owner,
|
|
183
|
+
}
|
|
184
|
+
const handle: OwnerSubscriptionHandle = {
|
|
185
|
+
subId,
|
|
186
|
+
unsubscribe: () => this.unsubscribeBounds(subId),
|
|
187
|
+
current: new Map(),
|
|
188
|
+
}
|
|
189
|
+
this.boundsSubs.set(subId, {
|
|
190
|
+
bounds: undefined,
|
|
191
|
+
owner,
|
|
192
|
+
prioritizeOwner: undefined,
|
|
193
|
+
onSnapshot: handlers.onSnapshot,
|
|
194
|
+
onUpdate: handlers.onUpdate,
|
|
195
|
+
handle,
|
|
196
|
+
})
|
|
197
|
+
this.sendMessage(msg)
|
|
198
|
+
return handle
|
|
199
|
+
}
|
|
200
|
+
|
|
151
201
|
private unsubscribeBounds(subId: string) {
|
|
152
202
|
this.boundsSubs.delete(subId)
|
|
153
203
|
this.sendMessage({type: 'unsubscribe', sub_id: subId})
|
|
154
204
|
}
|
|
155
205
|
|
|
156
206
|
private updateBounds(subId: string, bounds: BoundingBox) {
|
|
207
|
+
const entry = this.boundsSubs.get(subId)
|
|
208
|
+
if (entry) entry.bounds = bounds
|
|
157
209
|
const msg: UpdateBoundsMessage = {type: 'update_bounds', sub_id: subId, bounds}
|
|
158
210
|
this.sendMessage(msg)
|
|
159
211
|
}
|
|
160
212
|
|
|
213
|
+
private onStateChange(state: ConnectionState) {
|
|
214
|
+
if (state !== 'connected') return
|
|
215
|
+
if (!this.hasConnected) {
|
|
216
|
+
this.hasConnected = true
|
|
217
|
+
return
|
|
218
|
+
}
|
|
219
|
+
for (const [subId, entry] of this.entitySubs) {
|
|
220
|
+
const msg: SubscribeEntityMessage = {
|
|
221
|
+
type: 'subscribe_entity',
|
|
222
|
+
sub_id: subId,
|
|
223
|
+
entity_type: entry.type,
|
|
224
|
+
entity_id: entry.id,
|
|
225
|
+
}
|
|
226
|
+
this.sendMessage(msg)
|
|
227
|
+
}
|
|
228
|
+
for (const [subId, entry] of this.boundsSubs) {
|
|
229
|
+
const msg: SubscribeMessage = {
|
|
230
|
+
type: 'subscribe',
|
|
231
|
+
sub_id: subId,
|
|
232
|
+
bounds: entry.bounds,
|
|
233
|
+
owner: entry.owner,
|
|
234
|
+
prioritize_owner: entry.prioritizeOwner,
|
|
235
|
+
}
|
|
236
|
+
this.sendMessage(msg)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
161
240
|
private onMessage(msg: ServerMessage) {
|
|
162
241
|
switch (msg.type) {
|
|
163
242
|
case 'snapshot':
|
package/src/travel/travel.ts
CHANGED
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
type Distance,
|
|
29
29
|
MAX_ORBITAL_ALTITUDE,
|
|
30
30
|
MIN_ORBITAL_ALTITUDE,
|
|
31
|
+
MIN_TRANSFER_DISTANCE,
|
|
31
32
|
PRECISION,
|
|
32
33
|
type ShipLike,
|
|
33
34
|
TaskType,
|
|
@@ -77,6 +78,59 @@ export function lerp(
|
|
|
77
78
|
}
|
|
78
79
|
}
|
|
79
80
|
|
|
81
|
+
export interface FloatPosition {
|
|
82
|
+
x: number
|
|
83
|
+
y: number
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function easeFlightProgress(t: number): number {
|
|
87
|
+
if (t <= 0) return 0
|
|
88
|
+
if (t >= 1) return 1
|
|
89
|
+
return t < 0.5 ? 2 * t * t : 1 - 2 * (1 - t) * (1 - t)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function flightSpeedFactor(t: number): number {
|
|
93
|
+
if (t <= 0 || t >= 1) return 0
|
|
94
|
+
return t < 0.5 ? 4 * t : 4 * (1 - t)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function interpolateFlightPosition(
|
|
98
|
+
origin: {x: Int64Type | number; y: Int64Type | number},
|
|
99
|
+
destination: {x: Int64Type | number; y: Int64Type | number},
|
|
100
|
+
taskProgress: number,
|
|
101
|
+
options?: {easing?: 'physics' | 'linear'}
|
|
102
|
+
): FloatPosition {
|
|
103
|
+
const t = options?.easing === 'linear' ? taskProgress : easeFlightProgress(taskProgress)
|
|
104
|
+
return {
|
|
105
|
+
x: (1 - t) * Number(origin.x) + t * Number(destination.x),
|
|
106
|
+
y: (1 - t) * Number(origin.y) + t * Number(destination.y),
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function getInterpolatedPosition(
|
|
111
|
+
entity: HasScheduleAndLocation,
|
|
112
|
+
taskIndex: number,
|
|
113
|
+
taskProgress: number
|
|
114
|
+
): FloatPosition {
|
|
115
|
+
if (!entity.schedule || entity.schedule.tasks.length === 0) {
|
|
116
|
+
return {x: Number(entity.coordinates.x), y: Number(entity.coordinates.y)}
|
|
117
|
+
}
|
|
118
|
+
if (taskIndex < 0) {
|
|
119
|
+
const settled = getFlightOrigin(entity, entity.schedule.tasks.length)
|
|
120
|
+
return {x: Number(settled.x), y: Number(settled.y)}
|
|
121
|
+
}
|
|
122
|
+
const task = entity.schedule.tasks[taskIndex]
|
|
123
|
+
if (!task.type.equals(TaskType.TRAVEL) || !task.coordinates) {
|
|
124
|
+
const origin = getFlightOrigin(entity, taskIndex)
|
|
125
|
+
return {x: Number(origin.x), y: Number(origin.y)}
|
|
126
|
+
}
|
|
127
|
+
return interpolateFlightPosition(
|
|
128
|
+
getFlightOrigin(entity, taskIndex),
|
|
129
|
+
task.coordinates,
|
|
130
|
+
taskProgress
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
|
|
80
134
|
export function rotation(
|
|
81
135
|
origin: ServerContract.ActionParams.Type.coordinates,
|
|
82
136
|
destination: ServerContract.ActionParams.Type.coordinates
|
|
@@ -408,14 +462,18 @@ export function getDestinationLocation(
|
|
|
408
462
|
return undefined
|
|
409
463
|
}
|
|
410
464
|
|
|
465
|
+
/** Returns chain-tile coordinates (rounded). For visual position use getInterpolatedPosition. */
|
|
411
466
|
export function getPositionAt(
|
|
412
467
|
entity: HasScheduleAndLocation,
|
|
413
468
|
taskIndex: number,
|
|
414
469
|
taskProgress: number
|
|
415
470
|
): ServerContract.ActionParams.Type.coordinates {
|
|
416
|
-
if (!entity.schedule || entity.schedule.tasks.length === 0
|
|
471
|
+
if (!entity.schedule || entity.schedule.tasks.length === 0) {
|
|
417
472
|
return entity.coordinates
|
|
418
473
|
}
|
|
474
|
+
if (taskIndex < 0) {
|
|
475
|
+
return getFlightOrigin(entity, entity.schedule.tasks.length)
|
|
476
|
+
}
|
|
419
477
|
|
|
420
478
|
const task = entity.schedule.tasks[taskIndex]
|
|
421
479
|
|
|
@@ -490,7 +548,8 @@ export function calc_transfer_duration(
|
|
|
490
548
|
: (source.location.z?.toNumber() ?? 0)
|
|
491
549
|
const destZ =
|
|
492
550
|
typeof dest.location.z === 'number' ? dest.location.z : (dest.location.z?.toNumber() ?? 0)
|
|
493
|
-
const
|
|
551
|
+
const rawDistance = Math.abs(sourceZ - destZ)
|
|
552
|
+
const distance = rawDistance < MIN_TRANSFER_DISTANCE ? MIN_TRANSFER_DISTANCE : rawDistance
|
|
494
553
|
|
|
495
554
|
const totalMass = cargoMass + totalLoaderMass
|
|
496
555
|
const acceleration = calc_acceleration(totalThrust, totalMass)
|
|
@@ -1,16 +1,60 @@
|
|
|
1
1
|
import {Name} from '@wharfkit/antelope'
|
|
2
|
+
import {
|
|
3
|
+
ITEM_CONTAINER_T1_PACKED,
|
|
4
|
+
ITEM_CONTAINER_T2_PACKED,
|
|
5
|
+
ITEM_EXTRACTOR_T1_PACKED,
|
|
6
|
+
ITEM_SHIP_T1_PACKED,
|
|
7
|
+
ITEM_WAREHOUSE_T1_PACKED,
|
|
8
|
+
} from '../data/item-ids'
|
|
2
9
|
|
|
3
10
|
export const ENTITY_SHIP = Name.from('ship')
|
|
4
11
|
export const ENTITY_WAREHOUSE = Name.from('warehouse')
|
|
12
|
+
export const ENTITY_EXTRACTOR = Name.from('extractor')
|
|
5
13
|
export const ENTITY_CONTAINER = Name.from('container')
|
|
6
14
|
|
|
7
|
-
export
|
|
15
|
+
export enum EntityClass {
|
|
16
|
+
OrbitalVessel = 0,
|
|
17
|
+
PlanetaryStructure = 1,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getEntityClass(entityType: Name | EntityTypeName): EntityClass {
|
|
21
|
+
const typeName = typeof entityType === 'string' ? entityType : entityType.toString()
|
|
22
|
+
switch (typeName) {
|
|
23
|
+
case 'ship':
|
|
24
|
+
case 'container':
|
|
25
|
+
return EntityClass.OrbitalVessel
|
|
26
|
+
case 'warehouse':
|
|
27
|
+
case 'extractor':
|
|
28
|
+
return EntityClass.PlanetaryStructure
|
|
29
|
+
default:
|
|
30
|
+
throw new Error(`Entity type has no class: ${typeName}`)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function getPackedEntityType(itemId: number): Name | null {
|
|
35
|
+
switch (itemId) {
|
|
36
|
+
case ITEM_SHIP_T1_PACKED:
|
|
37
|
+
return ENTITY_SHIP
|
|
38
|
+
case ITEM_CONTAINER_T1_PACKED:
|
|
39
|
+
case ITEM_CONTAINER_T2_PACKED:
|
|
40
|
+
return ENTITY_CONTAINER
|
|
41
|
+
case ITEM_WAREHOUSE_T1_PACKED:
|
|
42
|
+
return ENTITY_WAREHOUSE
|
|
43
|
+
case ITEM_EXTRACTOR_T1_PACKED:
|
|
44
|
+
return ENTITY_EXTRACTOR
|
|
45
|
+
default:
|
|
46
|
+
return null
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type EntityTypeName = 'ship' | 'warehouse' | 'extractor' | 'container'
|
|
8
51
|
|
|
9
52
|
export interface EntityTraits {
|
|
10
53
|
typeName: Name
|
|
11
54
|
isMovable: boolean
|
|
12
55
|
hasEnergy: boolean
|
|
13
56
|
hasLoaders: boolean
|
|
57
|
+
hasModules: boolean
|
|
14
58
|
notFoundError: string
|
|
15
59
|
}
|
|
16
60
|
|
|
@@ -19,6 +63,7 @@ export const shipTraits: EntityTraits = {
|
|
|
19
63
|
isMovable: true,
|
|
20
64
|
hasEnergy: true,
|
|
21
65
|
hasLoaders: true,
|
|
66
|
+
hasModules: true,
|
|
22
67
|
|
|
23
68
|
notFoundError: 'ship not found',
|
|
24
69
|
}
|
|
@@ -28,15 +73,27 @@ export const warehouseTraits: EntityTraits = {
|
|
|
28
73
|
isMovable: false,
|
|
29
74
|
hasEnergy: false,
|
|
30
75
|
hasLoaders: true,
|
|
76
|
+
hasModules: true,
|
|
31
77
|
|
|
32
78
|
notFoundError: 'warehouse not found',
|
|
33
79
|
}
|
|
34
80
|
|
|
81
|
+
export const extractorTraits: EntityTraits = {
|
|
82
|
+
typeName: ENTITY_EXTRACTOR,
|
|
83
|
+
isMovable: false,
|
|
84
|
+
hasEnergy: true,
|
|
85
|
+
hasLoaders: false,
|
|
86
|
+
hasModules: true,
|
|
87
|
+
|
|
88
|
+
notFoundError: 'extractor not found',
|
|
89
|
+
}
|
|
90
|
+
|
|
35
91
|
export const containerTraits: EntityTraits = {
|
|
36
92
|
typeName: ENTITY_CONTAINER,
|
|
37
93
|
isMovable: true,
|
|
38
94
|
hasEnergy: false,
|
|
39
95
|
hasLoaders: false,
|
|
96
|
+
hasModules: false,
|
|
40
97
|
|
|
41
98
|
notFoundError: 'container not found',
|
|
42
99
|
}
|
|
@@ -49,6 +106,8 @@ export function getEntityTraits(entityType: Name | EntityTypeName): EntityTraits
|
|
|
49
106
|
return shipTraits
|
|
50
107
|
case 'warehouse':
|
|
51
108
|
return warehouseTraits
|
|
109
|
+
case 'extractor':
|
|
110
|
+
return extractorTraits
|
|
52
111
|
case 'container':
|
|
53
112
|
return containerTraits
|
|
54
113
|
default:
|
|
@@ -64,6 +123,10 @@ export function isWarehouse(entity: {type?: Name}): boolean {
|
|
|
64
123
|
return entity.type?.equals(ENTITY_WAREHOUSE) ?? false
|
|
65
124
|
}
|
|
66
125
|
|
|
126
|
+
export function isExtractor(entity: {type?: Name}): boolean {
|
|
127
|
+
return entity.type?.equals(ENTITY_EXTRACTOR) ?? false
|
|
128
|
+
}
|
|
129
|
+
|
|
67
130
|
export function isContainer(entity: {type?: Name}): boolean {
|
|
68
131
|
return entity.type?.equals(ENTITY_CONTAINER) ?? false
|
|
69
132
|
}
|
package/src/types.ts
CHANGED
|
@@ -12,7 +12,7 @@ import {ServerContract} from './contracts'
|
|
|
12
12
|
export const PRECISION = 10000
|
|
13
13
|
export const CRAFT_ENERGY_DIVISOR = 150000
|
|
14
14
|
|
|
15
|
-
export const
|
|
15
|
+
export const PLANETARY_STRUCTURE_Z = 0
|
|
16
16
|
|
|
17
17
|
export const CONTAINER_Z = 300
|
|
18
18
|
|
|
@@ -23,6 +23,8 @@ export const MAX_ORBITAL_ALTITUDE = 3000
|
|
|
23
23
|
|
|
24
24
|
export const BASE_ORBITAL_MASS = 100000
|
|
25
25
|
|
|
26
|
+
export const MIN_TRANSFER_DISTANCE = 100
|
|
27
|
+
|
|
26
28
|
export interface ShipLike {
|
|
27
29
|
coordinates: ServerContract.Types.coordinates
|
|
28
30
|
hullmass?: UInt32
|
|
@@ -51,6 +53,9 @@ export enum TaskType {
|
|
|
51
53
|
DEPLOY = 8,
|
|
52
54
|
WRAP = 9,
|
|
53
55
|
UNWRAP = 10,
|
|
56
|
+
UNDEPLOY = 11,
|
|
57
|
+
WRAP_ENTITY = 12,
|
|
58
|
+
DEMOLISH = 13,
|
|
54
59
|
}
|
|
55
60
|
|
|
56
61
|
export enum LocationType {
|
|
@@ -58,6 +63,7 @@ export enum LocationType {
|
|
|
58
63
|
PLANET = 1,
|
|
59
64
|
ASTEROID = 2,
|
|
60
65
|
NEBULA = 3,
|
|
66
|
+
ICE_FIELD = 4,
|
|
61
67
|
}
|
|
62
68
|
|
|
63
69
|
export enum TaskCancelable {
|
|
@@ -159,3 +165,7 @@ export interface Item {
|
|
|
159
165
|
export function formatTier(tier: number): string {
|
|
160
166
|
return 'T' + tier
|
|
161
167
|
}
|
|
168
|
+
|
|
169
|
+
export function tierAdjective(tier: number): string {
|
|
170
|
+
return TIER_ADJECTIVES[tier] ?? `T${tier}`
|
|
171
|
+
}
|