@shipload/sdk 1.0.0-next.4 → 1.0.0-next.5

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.
@@ -11,7 +11,7 @@ export interface EntityMetadata {
11
11
  }
12
12
 
13
13
  export const itemMetadata: Record<number, ItemMetadata> = {
14
- // === Resources (raw) ===
14
+ // === Resources / Ore ===
15
15
  101: {name: 'Ore', description: 'Crude metallic ore.', color: '#C26D3F'},
16
16
  102: {name: 'Ore', description: 'Refined metallic ore with improved purity.', color: '#C26D3F'},
17
17
  103: {
@@ -19,6 +19,15 @@ export const itemMetadata: Record<number, ItemMetadata> = {
19
19
  description: 'High-grade metallic ore with exceptional density.',
20
20
  color: '#C26D3F',
21
21
  },
22
+ 104: {name: 'Ore', description: '', color: '#C26D3F'},
23
+ 105: {name: 'Ore', description: '', color: '#C26D3F'},
24
+ 106: {name: 'Ore', description: '', color: '#C26D3F'},
25
+ 107: {name: 'Ore', description: '', color: '#C26D3F'},
26
+ 108: {name: 'Ore', description: '', color: '#C26D3F'},
27
+ 109: {name: 'Ore', description: '', color: '#C26D3F'},
28
+ 110: {name: 'Ore', description: '', color: '#C26D3F'},
29
+
30
+ // === Resources / Crystal ===
22
31
  201: {name: 'Crystal', description: 'Raw resonant crystal.', color: '#4ADBFF'},
23
32
  202: {
24
33
  name: 'Crystal',
@@ -30,6 +39,15 @@ export const itemMetadata: Record<number, ItemMetadata> = {
30
39
  description: 'High-grade resonant crystal with exceptional purity.',
31
40
  color: '#4ADBFF',
32
41
  },
42
+ 204: {name: 'Crystal', description: '', color: '#4ADBFF'},
43
+ 205: {name: 'Crystal', description: '', color: '#4ADBFF'},
44
+ 206: {name: 'Crystal', description: '', color: '#4ADBFF'},
45
+ 207: {name: 'Crystal', description: '', color: '#4ADBFF'},
46
+ 208: {name: 'Crystal', description: '', color: '#4ADBFF'},
47
+ 209: {name: 'Crystal', description: '', color: '#4ADBFF'},
48
+ 210: {name: 'Crystal', description: '', color: '#4ADBFF'},
49
+
50
+ // === Resources / Gas ===
33
51
  301: {name: 'Gas', description: 'Raw volatile gas.', color: '#B8E4A0'},
34
52
  302: {
35
53
  name: 'Gas',
@@ -41,6 +59,15 @@ export const itemMetadata: Record<number, ItemMetadata> = {
41
59
  description: 'High-grade volatile gas with exceptional energy density.',
42
60
  color: '#B8E4A0',
43
61
  },
62
+ 304: {name: 'Gas', description: '', color: '#B8E4A0'},
63
+ 305: {name: 'Gas', description: '', color: '#B8E4A0'},
64
+ 306: {name: 'Gas', description: '', color: '#B8E4A0'},
65
+ 307: {name: 'Gas', description: '', color: '#B8E4A0'},
66
+ 308: {name: 'Gas', description: '', color: '#B8E4A0'},
67
+ 309: {name: 'Gas', description: '', color: '#B8E4A0'},
68
+ 310: {name: 'Gas', description: '', color: '#B8E4A0'},
69
+
70
+ // === Resources / Regolith ===
44
71
  401: {name: 'Regolith', description: 'Crude regolith dust.', color: '#C4A57B'},
45
72
  402: {
46
73
  name: 'Regolith',
@@ -52,6 +79,15 @@ export const itemMetadata: Record<number, ItemMetadata> = {
52
79
  description: 'High-grade regolith with exceptional uniformity.',
53
80
  color: '#C4A57B',
54
81
  },
82
+ 404: {name: 'Regolith', description: '', color: '#C4A57B'},
83
+ 405: {name: 'Regolith', description: '', color: '#C4A57B'},
84
+ 406: {name: 'Regolith', description: '', color: '#C4A57B'},
85
+ 407: {name: 'Regolith', description: '', color: '#C4A57B'},
86
+ 408: {name: 'Regolith', description: '', color: '#C4A57B'},
87
+ 409: {name: 'Regolith', description: '', color: '#C4A57B'},
88
+ 410: {name: 'Regolith', description: '', color: '#C4A57B'},
89
+
90
+ // === Resources / Biomass ===
55
91
  501: {name: 'Biomass', description: 'Crude organic biomass.', color: '#5A8B3E'},
56
92
  502: {
57
93
  name: 'Biomass',
@@ -63,6 +99,13 @@ export const itemMetadata: Record<number, ItemMetadata> = {
63
99
  description: 'High-grade biomass with exceptional saturation.',
64
100
  color: '#5A8B3E',
65
101
  },
102
+ 504: {name: 'Biomass', description: '', color: '#5A8B3E'},
103
+ 505: {name: 'Biomass', description: '', color: '#5A8B3E'},
104
+ 506: {name: 'Biomass', description: '', color: '#5A8B3E'},
105
+ 507: {name: 'Biomass', description: '', color: '#5A8B3E'},
106
+ 508: {name: 'Biomass', description: '', color: '#5A8B3E'},
107
+ 509: {name: 'Biomass', description: '', color: '#5A8B3E'},
108
+ 510: {name: 'Biomass', description: '', color: '#5A8B3E'},
66
109
 
67
110
  // === Components (T1) ===
68
111
  10001: {
@@ -1,10 +1,15 @@
1
1
  import {getItem} from '../data/catalog'
2
2
 
3
3
  export const DEPTH_THRESHOLD_T1 = 0
4
- export const DEPTH_THRESHOLD_T2 = 2000
5
- export const DEPTH_THRESHOLD_T3 = 10000
6
- export const DEPTH_THRESHOLD_T4 = 30000
7
- export const DEPTH_THRESHOLD_T5 = 55000
4
+ export const DEPTH_THRESHOLD_T2 = 1500
5
+ export const DEPTH_THRESHOLD_T3 = 5000
6
+ export const DEPTH_THRESHOLD_T4 = 12000
7
+ export const DEPTH_THRESHOLD_T5 = 22000
8
+ export const DEPTH_THRESHOLD_T6 = 32000
9
+ export const DEPTH_THRESHOLD_T7 = 42000
10
+ export const DEPTH_THRESHOLD_T8 = 50000
11
+ export const DEPTH_THRESHOLD_T9 = 57000
12
+ export const DEPTH_THRESHOLD_T10 = 63000
8
13
 
9
14
  export const LOCATION_MIN_DEPTH = 500
10
15
  export const LOCATION_MAX_DEPTH = 65535
@@ -18,19 +23,22 @@ export const PLANET_SUBTYPE_ICY = 3
18
23
  export const PLANET_SUBTYPE_OCEAN = 4
19
24
  export const PLANET_SUBTYPE_INDUSTRIAL = 5
20
25
 
26
+ const DEPTH_THRESHOLD_TABLE = [
27
+ DEPTH_THRESHOLD_T1,
28
+ DEPTH_THRESHOLD_T2,
29
+ DEPTH_THRESHOLD_T3,
30
+ DEPTH_THRESHOLD_T4,
31
+ DEPTH_THRESHOLD_T5,
32
+ DEPTH_THRESHOLD_T6,
33
+ DEPTH_THRESHOLD_T7,
34
+ DEPTH_THRESHOLD_T8,
35
+ DEPTH_THRESHOLD_T9,
36
+ DEPTH_THRESHOLD_T10,
37
+ ]
38
+
21
39
  export function getDepthThreshold(tier: number): number {
22
- switch (tier) {
23
- case 1:
24
- return DEPTH_THRESHOLD_T1
25
- case 2:
26
- return DEPTH_THRESHOLD_T2
27
- case 3:
28
- return DEPTH_THRESHOLD_T3
29
- case 4:
30
- return DEPTH_THRESHOLD_T4
31
- default:
32
- return DEPTH_THRESHOLD_T5
33
- }
40
+ if (tier < 1 || tier > 10) return 65535
41
+ return DEPTH_THRESHOLD_TABLE[tier - 1]
34
42
  }
35
43
 
36
44
  export function getResourceTier(itemId: number): number {
@@ -46,9 +54,9 @@ export function getResourceWeight(itemId: number, stratum: number): number {
46
54
 
47
55
  switch (tier) {
48
56
  case 1:
49
- if (stratum < 2000) return 100
50
- if (stratum < 10000) return 80
51
- if (stratum < 30000) return 50
57
+ if (stratum < DEPTH_THRESHOLD_T2) return 100
58
+ if (stratum < DEPTH_THRESHOLD_T3) return 80
59
+ if (stratum < DEPTH_THRESHOLD_T4) return 50
52
60
  return 30
53
61
  case 2:
54
62
  if (depthAbove < 3000) return 40
@@ -1,6 +1,7 @@
1
1
  import {UInt64, type UInt64Type} from '@wharfkit/antelope'
2
2
  import {ServerContract} from '../contracts'
3
3
  import type {CoordinatesType} from '../types'
4
+ import {type FloatPosition, getInterpolatedPosition} from '../travel/travel'
4
5
  import {Location} from './location'
5
6
  import {ScheduleAccessor} from '../scheduling/accessor'
6
7
  import * as schedule from '../scheduling/schedule'
@@ -24,6 +25,14 @@ export class Container extends ServerContract.Types.entity_info {
24
25
  return this.entity_name
25
26
  }
26
27
 
28
+ get entityClass(): 'mobile' {
29
+ return 'mobile'
30
+ }
31
+
32
+ get canUndeploy(): boolean {
33
+ return true
34
+ }
35
+
27
36
  get sched(): ScheduleAccessor {
28
37
  this._sched ??= new ScheduleAccessor(this)
29
38
  return this._sched
@@ -33,6 +42,12 @@ export class Container extends ServerContract.Types.entity_info {
33
42
  return this.is_idle
34
43
  }
35
44
 
45
+ interpolatedPositionAt(now: Date): FloatPosition {
46
+ const taskIndex = this.sched.currentTaskIndex(now)
47
+ const progress = this.sched.currentTaskProgressFloat(now)
48
+ return getInterpolatedPosition(this, taskIndex, progress)
49
+ }
50
+
36
51
  isLoading(now: Date): boolean {
37
52
  return schedule.isLoading(this, now)
38
53
  }
@@ -8,6 +8,7 @@ import {
8
8
  MODULE_HAULER,
9
9
  MODULE_LOADER,
10
10
  } from '../capabilities/modules'
11
+ import {getItem} from '../data/catalog'
11
12
 
12
13
  export function computeShipHullCapabilities(stats: Record<string, number>): {
13
14
  hullmass: number
@@ -52,7 +53,38 @@ export function computeGeneratorCapabilities(stats: Record<string, number>): {
52
53
  }
53
54
  }
54
55
 
55
- export function computeGathererCapabilities(stats: Record<string, number>): {
56
+ export interface GathererDepthParams {
57
+ readonly floor: number
58
+ readonly slope: number
59
+ }
60
+
61
+ export const GATHERER_DEPTH_TABLE: readonly GathererDepthParams[] = [
62
+ {floor: 500, slope: 5},
63
+ {floor: 2000, slope: 11},
64
+ {floor: 7000, slope: 16},
65
+ {floor: 15000, slope: 18},
66
+ {floor: 25000, slope: 19},
67
+ {floor: 35000, slope: 16},
68
+ {floor: 46000, slope: 12},
69
+ {floor: 53500, slope: 10},
70
+ {floor: 60000, slope: 5},
71
+ {floor: 63500, slope: 2},
72
+ ]
73
+
74
+ export const GATHERER_DEPTH_MAX_TIER = 10
75
+
76
+ export function gathererDepthForTier(tol: number, tier: number): number {
77
+ if (tier < 1 || tier > GATHERER_DEPTH_MAX_TIER) {
78
+ throw new Error(`gatherer tier out of range: ${tier}`)
79
+ }
80
+ const p = GATHERER_DEPTH_TABLE[tier - 1]
81
+ return p.floor + tol * p.slope
82
+ }
83
+
84
+ export function computeGathererCapabilities(
85
+ stats: Record<string, number>,
86
+ tier: number
87
+ ): {
56
88
  yield: number
57
89
  drain: number
58
90
  depth: number
@@ -66,7 +98,7 @@ export function computeGathererCapabilities(stats: Record<string, number>): {
66
98
  return {
67
99
  yield: 200 + str,
68
100
  drain: Math.max(250, 1250 - Math.floor((con * 25) / 20)),
69
- depth: 200 + Math.floor((tol * 3) / 2),
101
+ depth: gathererDepthForTier(tol, tier),
70
102
  speed: 100 + Math.floor((ref * 4) / 5),
71
103
  }
72
104
  }
@@ -197,16 +229,20 @@ export function computeShipCapabilities(
197
229
  if (gathererModules.length > 0) {
198
230
  let totalYield = 0
199
231
  let totalDrain = 0
200
- let totalDepth = 0
232
+ let maxDepth = 0
201
233
  let totalSpeed = 0
202
234
  for (const m of gathererModules) {
203
- const caps = computeGathererCapabilities(decodeCraftedItemStats(m.itemId, m.stats))
235
+ const tier = getItem(m.itemId).tier
236
+ const caps = computeGathererCapabilities(
237
+ decodeCraftedItemStats(m.itemId, m.stats),
238
+ tier
239
+ )
204
240
  totalYield += caps.yield
205
241
  totalDrain += caps.drain
206
- totalDepth += caps.depth
242
+ if (caps.depth > maxDepth) maxDepth = caps.depth
207
243
  totalSpeed += caps.speed
208
244
  }
209
- ship.gatherer = {yield: totalYield, drain: totalDrain, depth: totalDepth, speed: totalSpeed}
245
+ ship.gatherer = {yield: totalYield, drain: totalDrain, depth: maxDepth, speed: totalSpeed}
210
246
  }
211
247
 
212
248
  const haulerModules = modules.filter((m) => getModuleCapabilityType(m.itemId) === MODULE_HAULER)
@@ -2,7 +2,9 @@ import {type UInt16, type UInt16Type, UInt32, UInt64, type UInt64Type} from '@wh
2
2
  import {ServerContract} from '../contracts'
3
3
  import {Coordinates, type CoordinatesType} from '../types'
4
4
  import {
5
+ type FloatPosition,
5
6
  getDestinationLocation,
7
+ getInterpolatedPosition,
6
8
  getPositionAt,
7
9
  getFlightOrigin as travelGetFlightOrigin,
8
10
  } from '../travel/travel'
@@ -55,6 +57,14 @@ export class Ship extends ServerContract.Types.entity_info {
55
57
  return this.entity_name
56
58
  }
57
59
 
60
+ get entityClass(): 'mobile' {
61
+ return 'mobile'
62
+ }
63
+
64
+ get canUndeploy(): boolean {
65
+ return true
66
+ }
67
+
58
68
  get inv(): InventoryAccessor {
59
69
  this._inv ??= new InventoryAccessor(this)
60
70
  return this._inv
@@ -87,12 +97,19 @@ export class Ship extends ServerContract.Types.entity_info {
87
97
  return dest ? Coordinates.from(dest) : undefined
88
98
  }
89
99
 
100
+ /** Chain-tile coordinates at `now`. For smooth visual position use interpolatedPositionAt. */
90
101
  positionAt(now: Date): Coordinates {
91
102
  const taskIndex = this.sched.currentTaskIndex(now)
92
103
  const progress = this.sched.currentTaskProgress(now)
93
104
  return Coordinates.from(getPositionAt(this, taskIndex, progress))
94
105
  }
95
106
 
107
+ interpolatedPositionAt(now: Date): FloatPosition {
108
+ const taskIndex = this.sched.currentTaskIndex(now)
109
+ const progress = this.sched.currentTaskProgressFloat(now)
110
+ return getInterpolatedPosition(this, taskIndex, progress)
111
+ }
112
+
96
113
  isInFlight(now: Date): boolean {
97
114
  return schedule.isInFlight(this, now)
98
115
  }
@@ -31,6 +31,14 @@ export class Warehouse extends ServerContract.Types.entity_info {
31
31
  return this.entity_name
32
32
  }
33
33
 
34
+ get entityClass(): 'building' {
35
+ return 'building'
36
+ }
37
+
38
+ get canDemolish(): boolean {
39
+ return true
40
+ }
41
+
34
42
  get inv(): InventoryAccessor {
35
43
  this._inv ??= new InventoryAccessor(this)
36
44
  return this._inv
@@ -117,30 +117,35 @@ export {
117
117
  distanceBetweenCoordinates,
118
118
  distanceBetweenPoints,
119
119
  findNearbyPlanets,
120
- lerp,
121
- rotation,
122
- calc_ship_mass,
123
120
  calc_acceleration,
121
+ calc_energyusage,
124
122
  calc_flighttime,
125
- calc_ship_flighttime,
126
- calc_ship_acceleration,
127
- calc_rechargetime,
128
- calc_ship_rechargetime,
129
- calc_loader_flighttime,
130
123
  calc_loader_acceleration,
131
- calc_energyusage,
124
+ calc_loader_flighttime,
132
125
  calc_orbital_altitude,
126
+ calc_rechargetime,
127
+ calc_ship_acceleration,
128
+ calc_ship_flighttime,
129
+ calc_ship_mass,
130
+ calc_ship_rechargetime,
133
131
  calc_transfer_duration,
134
- calculateTransferTime,
132
+ calculateFlightTime,
135
133
  calculateLoadTimeBreakdown,
136
134
  calculateRefuelingTime,
137
- calculateFlightTime,
138
- estimateTravelTime,
135
+ calculateTransferTime,
136
+ easeFlightProgress,
139
137
  estimateDealTravelTime,
140
- hasEnergyForDistance,
141
- getFlightOrigin,
138
+ estimateTravelTime,
139
+ flightSpeedFactor,
140
+ type FloatPosition,
142
141
  getDestinationLocation,
142
+ getFlightOrigin,
143
+ getInterpolatedPosition,
143
144
  getPositionAt,
145
+ hasEnergyForDistance,
146
+ interpolateFlightPosition,
147
+ lerp,
148
+ rotation,
144
149
  } from './travel/travel'
145
150
  export type {
146
151
  LoadTimeBreakdown,
@@ -246,8 +251,11 @@ export {
246
251
  computeWarehouseHullCapabilities,
247
252
  computeStorageCapabilities,
248
253
  computeShipCapabilities,
254
+ GATHERER_DEPTH_TABLE,
255
+ GATHERER_DEPTH_MAX_TIER,
256
+ gathererDepthForTier,
249
257
  } from './entities/ship-deploy'
250
- export type {ShipCapabilities} from './entities/ship-deploy'
258
+ export type {ShipCapabilities, GathererDepthParams} from './entities/ship-deploy'
251
259
 
252
260
  export {resolveItem} from './resolution/resolve-item'
253
261
  export type {
@@ -15,7 +15,7 @@ import {type CoordinatesType, EntityType, type EntityTypeName} from '../types'
15
15
  import {ServerContract} from '../contracts'
16
16
 
17
17
  export type EntityRefInput = {
18
- entityType: EntityTypeName
18
+ entityType: NameType
19
19
  entityId: UInt64Type
20
20
  }
21
21
 
@@ -244,6 +244,29 @@ export class ActionsManager extends BaseManager {
244
244
  })
245
245
  }
246
246
 
247
+ undeploy(host: EntityRefInput, target: EntityRefInput): Action {
248
+ return this.server.action('undeploy', {
249
+ host_type: Name.from(host.entityType),
250
+ host_id: UInt64.from(host.entityId),
251
+ target_type: Name.from(target.entityType),
252
+ target_id: UInt64.from(target.entityId),
253
+ })
254
+ }
255
+
256
+ wrapEntity(entity: EntityRefInput): Action {
257
+ return this.server.action('wrapentity', {
258
+ entity_type: Name.from(entity.entityType),
259
+ entity_id: UInt64.from(entity.entityId),
260
+ })
261
+ }
262
+
263
+ demolish(entity: EntityRefInput): Action {
264
+ return this.server.action('demolish', {
265
+ entity_type: Name.from(entity.entityType),
266
+ entity_id: UInt64.from(entity.entityId),
267
+ })
268
+ }
269
+
247
270
  joinGame(account: NameType, companyName: string): Action[] {
248
271
  return [this.foundCompany(account, companyName), this.join(account)]
249
272
  }
@@ -4,6 +4,7 @@ import {
4
4
  MODULE_ENGINE,
5
5
  MODULE_GATHERER,
6
6
  MODULE_GENERATOR,
7
+ MODULE_HAULER,
7
8
  MODULE_LOADER,
8
9
  MODULE_STORAGE,
9
10
  MODULE_WARP,
@@ -15,6 +16,7 @@ import {
15
16
  ITEM_ENGINE_T1,
16
17
  ITEM_GATHERER_T1,
17
18
  ITEM_GENERATOR_T1,
19
+ ITEM_HAULER_T1,
18
20
  ITEM_LOADER_T1,
19
21
  ITEM_SHIP_T1_PACKED,
20
22
  ITEM_STORAGE_T1,
@@ -22,6 +24,8 @@ import {
22
24
  ITEM_WARP_T1,
23
25
  } from '../data/item-ids'
24
26
  import {decodeStat} from '../derivation/crafting'
27
+ import {gathererDepthForTier} from '../entities/ship-deploy'
28
+ import {getItem} from '../data/catalog'
25
29
 
26
30
  function idiv(a: number, b: number): number {
27
31
  return Math.floor(a / b)
@@ -49,12 +53,16 @@ export const computeGeneratorRech = (fin: number): number => 1 + idiv(fin * 3, 1
49
53
  export const computeGathererYield = (str: number): number => 200 + str
50
54
  export const computeGathererDrain = (con: number): number =>
51
55
  Math.max(250, 1250 - idiv(con * 25, 20))
52
- export const computeGathererDepth = (tol: number): number => 200 + idiv(tol * 3, 2)
56
+ export const computeGathererDepth = (tol: number, tier: number): number =>
57
+ gathererDepthForTier(tol, tier)
53
58
  export const computeGathererSpeed = (ref: number): number => 100 + idiv(ref * 4, 5)
54
59
  export const computeLoaderMass = (ins: number): number => Math.max(200, 2000 - ins * 2)
55
60
  export const computeLoaderThrust = (pla: number): number => 1 + idiv(pla, 500)
56
61
  export const computeCrafterSpeed = (rea: number): number => 100 + idiv(rea * 4, 5)
57
62
  export const computeCrafterDrain = (fin: number): number => Math.max(5, 30 - idiv(fin, 33))
63
+ export const computeHaulerCapacity = (fin: number): number => Math.max(1, 1 + idiv(fin, 400))
64
+ export const computeHaulerEfficiency = (con: number): number => 2000 + con * 6
65
+ export const computeHaulerDrain = (com: number): number => Math.max(3, 15 - idiv(com, 80))
58
66
  export const computeWarpRange = (stat: number): number => 100 + stat * 3
59
67
 
60
68
  export function entityDisplayName(itemId: number): string {
@@ -86,6 +94,8 @@ export function moduleDisplayName(itemId: number): string {
86
94
  return 'Crafter'
87
95
  case ITEM_STORAGE_T1:
88
96
  return 'Storage'
97
+ case ITEM_HAULER_T1:
98
+ return 'Hauler'
89
99
  case ITEM_WARP_T1:
90
100
  return 'Warp'
91
101
  default:
@@ -121,8 +131,10 @@ export function formatModuleLine(slot: number, itemId: number, stats: bigint): s
121
131
  const tol = decodeStat(stats, 1)
122
132
  const con = decodeStat(stats, 3)
123
133
  const ref = decodeStat(stats, 4)
134
+ const tier = getItem(itemId).tier
124
135
  out += ` Yield ${computeGathererYield(str)} Depth ${computeGathererDepth(
125
- tol
136
+ tol,
137
+ tier
126
138
  )} Speed ${computeGathererSpeed(ref)} Drain ${computeGathererDrain(con)}`
127
139
  break
128
140
  }
@@ -147,6 +159,13 @@ export function formatModuleLine(slot: number, itemId: number, stats: bigint): s
147
159
  out += ` +${pct}% capacity`
148
160
  break
149
161
  }
162
+ case MODULE_HAULER: {
163
+ const fin = decodeStat(stats, 0)
164
+ const con = decodeStat(stats, 1)
165
+ const com = decodeStat(stats, 2)
166
+ out += ` Capacity ${computeHaulerCapacity(fin)} Efficiency ${computeHaulerEfficiency(con)} Drain ${computeHaulerDrain(com)}`
167
+ break
168
+ }
150
169
  case MODULE_WARP: {
151
170
  const stat = decodeStat(stats, 0)
152
171
  out += ` Range ${computeWarpRange(stat)}`
@@ -155,7 +155,8 @@ function resolveComponent(id: number, stats?: UInt64Type): ResolvedItem {
155
155
 
156
156
  function computeCapabilityGroup(
157
157
  moduleType: number,
158
- stats: Record<string, number>
158
+ stats: Record<string, number>,
159
+ tier: number
159
160
  ): ResolvedAttributeGroup | undefined {
160
161
  switch (moduleType) {
161
162
  case MODULE_ENGINE: {
@@ -179,7 +180,7 @@ function computeCapabilityGroup(
179
180
  }
180
181
  }
181
182
  case MODULE_GATHERER: {
182
- const caps = computeGathererCapabilities(stats)
183
+ const caps = computeGathererCapabilities(stats, tier)
183
184
  return {
184
185
  capability: 'Gatherer',
185
186
  attributes: [
@@ -242,7 +243,7 @@ function resolveModule(id: number, stats?: UInt64Type): ResolvedItem {
242
243
  if (stats !== undefined) {
243
244
  const decoded = decodeCraftedItemStats(id, toBigStats(stats))
244
245
  const modType = getModuleCapabilityType(id)
245
- const group = computeCapabilityGroup(modType, decoded)
246
+ const group = computeCapabilityGroup(modType, decoded, item.tier)
246
247
  if (group) attributes = [group]
247
248
  }
248
249
  return {
@@ -311,13 +312,16 @@ function resolveEntity(
311
312
  const modStats = BigInt(mod.installed.stats.toString())
312
313
  const decodedStats = decodeCraftedItemStats(modItemId, modStats)
313
314
  const modType = getModuleCapabilityType(modItemId)
314
- const group = computeCapabilityGroup(modType, decodedStats)
315
315
  let modName = 'Module'
316
+ let modTier = 1
316
317
  try {
317
- modName = getItem(modItemId).name
318
+ const modItem = getItem(modItemId)
319
+ modName = modItem.name
320
+ modTier = modItem.tier
318
321
  } catch {
319
322
  modName = itemMetadata[modItemId]?.name ?? 'Module'
320
323
  }
324
+ const group = computeCapabilityGroup(modType, decodedStats, modTier)
321
325
  return {
322
326
  name: modName,
323
327
  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 entity.schedule.tasks.length - 1
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
@@ -34,6 +34,12 @@ export interface BoundsSubscriptionHandle {
34
34
  current: Map<number, EntityInstance>
35
35
  }
36
36
 
37
+ export interface OwnerSubscriptionHandle {
38
+ readonly subId: string
39
+ unsubscribe(): void
40
+ current: Map<number, EntityInstance>
41
+ }
42
+
37
43
  export interface EntitySubscriptionHandle {
38
44
  readonly subId: string
39
45
  readonly entityType: SubscriptionEntityType
@@ -62,7 +68,7 @@ export class SubscriptionsManager {
62
68
  onSnapshot?: (entities: EntityInstance[]) => void
63
69
  onUpdate?: (entity: EntityInstance) => void
64
70
  onBoundsDelta?: (entered: EntityInstance[], exited: number[]) => void
65
- handle: BoundsSubscriptionHandle
71
+ handle: BoundsSubscriptionHandle | OwnerSubscriptionHandle
66
72
  }
67
73
  >()
68
74
  private subCounter = 0
@@ -162,6 +168,36 @@ export class SubscriptionsManager {
162
168
  return handle
163
169
  }
164
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
+
165
201
  private unsubscribeBounds(subId: string) {
166
202
  this.boundsSubs.delete(subId)
167
203
  this.sendMessage({type: 'unsubscribe', sub_id: subId})