@shipload/sdk 1.0.0-next.1 → 1.0.0-next.11

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 (56) hide show
  1. package/lib/shipload.d.ts +618 -353
  2. package/lib/shipload.js +2250 -1059
  3. package/lib/shipload.js.map +1 -1
  4. package/lib/shipload.m.js +2193 -1044
  5. package/lib/shipload.m.js.map +1 -1
  6. package/package.json +2 -2
  7. package/src/capabilities/modules.ts +3 -0
  8. package/src/capabilities/storage.ts +1 -1
  9. package/src/contracts/platform.ts +13 -1
  10. package/src/contracts/server.ts +227 -282
  11. package/src/data/capabilities.ts +5 -330
  12. package/src/data/capability-formulas.ts +70 -0
  13. package/src/data/catalog.ts +0 -5
  14. package/src/data/colors.ts +2 -16
  15. package/src/data/entities.json +46 -10
  16. package/src/data/item-ids.ts +4 -1
  17. package/src/data/items.json +264 -0
  18. package/src/data/metadata.ts +63 -1
  19. package/src/data/recipes-runtime.ts +1 -0
  20. package/src/data/recipes.json +139 -11
  21. package/src/derivation/capability-mappings.ts +122 -0
  22. package/src/derivation/index.ts +1 -0
  23. package/src/derivation/resources.ts +116 -37
  24. package/src/derivation/stats.ts +1 -2
  25. package/src/entities/container.ts +25 -10
  26. package/src/entities/extractor.ts +144 -0
  27. package/src/entities/factory.ts +135 -0
  28. package/src/entities/gamestate.ts +0 -9
  29. package/src/entities/makers.ts +130 -20
  30. package/src/entities/nexus.ts +29 -0
  31. package/src/entities/ship-deploy.ts +114 -56
  32. package/src/entities/ship.ts +17 -0
  33. package/src/entities/slot-multiplier.ts +21 -0
  34. package/src/entities/warehouse.ts +20 -3
  35. package/src/errors.ts +10 -13
  36. package/src/index-module.ts +81 -26
  37. package/src/managers/actions.ts +53 -80
  38. package/src/managers/entities.ts +65 -17
  39. package/src/managers/locations.ts +2 -20
  40. package/src/nft/atomicdata.ts +128 -0
  41. package/src/nft/description.ts +41 -7
  42. package/src/nft/index.ts +1 -0
  43. package/src/resolution/resolve-item.ts +17 -9
  44. package/src/scheduling/accessor.ts +4 -0
  45. package/src/scheduling/projection.ts +10 -2
  46. package/src/scheduling/schedule.ts +15 -1
  47. package/src/scheduling/task-cargo.ts +47 -0
  48. package/src/subscriptions/connection.ts +50 -2
  49. package/src/subscriptions/manager.ts +84 -4
  50. package/src/subscriptions/mappers.ts +5 -1
  51. package/src/subscriptions/types.ts +2 -2
  52. package/src/travel/travel.ts +61 -2
  53. package/src/types/entity-traits.ts +103 -1
  54. package/src/types.ts +11 -1
  55. package/src/utils/cargo.ts +27 -0
  56. package/src/utils/system.ts +25 -24
@@ -3,14 +3,18 @@ import {BaseManager} from './base'
3
3
  import {Ship} from '../entities/ship'
4
4
  import {Warehouse} from '../entities/warehouse'
5
5
  import {Container} from '../entities/container'
6
+ import {Extractor} from '../entities/extractor'
7
+ import {Factory} from '../entities/factory'
8
+ import {Nexus} from '../entities/nexus'
6
9
  import type {ServerContract} from '../contracts'
7
10
 
8
- export type EntityType = 'ship' | 'warehouse' | 'container' | 'location'
11
+ export type EntityType = 'ship' | 'warehouse' | 'extractor' | 'factory' | 'container' | 'nexus'
9
12
 
10
13
  export class EntitiesManager extends BaseManager {
11
- async getEntity(type: EntityType, id: UInt64Type): Promise<Ship | Warehouse | Container> {
14
+ async getEntity(
15
+ id: UInt64Type
16
+ ): Promise<Ship | Warehouse | Extractor | Factory | Container | Nexus> {
12
17
  const result = await this.server.readonly('getentity', {
13
- entity_type: Name.from(type),
14
18
  entity_id: id,
15
19
  })
16
20
  const entityInfo = result as ServerContract.Types.entity_info
@@ -20,11 +24,11 @@ export class EntitiesManager extends BaseManager {
20
24
  async getEntities(
21
25
  owner: NameType | ServerContract.Types.player_row,
22
26
  type?: EntityType
23
- ): Promise<(Ship | Warehouse | Container)[]> {
27
+ ): Promise<(Ship | Warehouse | Extractor | Factory | Container | Nexus)[]> {
24
28
  const ownerName = this.resolveOwner(owner)
25
29
  const result = await this.server.readonly('getentities', {
26
30
  owner: ownerName,
27
- entity_type: type ? Name.from(type) : null,
31
+ entity_type: type,
28
32
  })
29
33
  const entities = result as ServerContract.Types.entity_info[]
30
34
  return entities.map((entity) => this.wrapEntity(entity))
@@ -37,21 +41,29 @@ export class EntitiesManager extends BaseManager {
37
41
  const ownerName = this.resolveOwner(owner)
38
42
  const result = await this.server.readonly('getsummaries', {
39
43
  owner: ownerName,
40
- entity_type: type ? Name.from(type) : null,
44
+ entity_type: type,
41
45
  })
42
46
  return result as ServerContract.Types.entity_summary[]
43
47
  }
44
48
 
45
49
  async getShip(id: UInt64Type): Promise<Ship> {
46
- return (await this.getEntity('ship', id)) as Ship
50
+ return (await this.getEntity(id)) as Ship
47
51
  }
48
52
 
49
53
  async getWarehouse(id: UInt64Type): Promise<Warehouse> {
50
- return (await this.getEntity('warehouse', id)) as Warehouse
54
+ return (await this.getEntity(id)) as Warehouse
51
55
  }
52
56
 
53
57
  async getContainer(id: UInt64Type): Promise<Container> {
54
- return (await this.getEntity('container', id)) as Container
58
+ return (await this.getEntity(id)) as Container
59
+ }
60
+
61
+ async getExtractor(id: UInt64Type): Promise<Extractor> {
62
+ return (await this.getEntity(id)) as Extractor
63
+ }
64
+
65
+ async getFactory(id: UInt64Type): Promise<Factory> {
66
+ return (await this.getEntity(id)) as Factory
55
67
  }
56
68
 
57
69
  async getShips(owner: NameType | ServerContract.Types.player_row): Promise<Ship[]> {
@@ -66,6 +78,14 @@ export class EntitiesManager extends BaseManager {
66
78
  return (await this.getEntities(owner, 'container')) as Container[]
67
79
  }
68
80
 
81
+ async getExtractors(owner: NameType | ServerContract.Types.player_row): Promise<Extractor[]> {
82
+ return (await this.getEntities(owner, 'extractor')) as Extractor[]
83
+ }
84
+
85
+ async getFactories(owner: NameType | ServerContract.Types.player_row): Promise<Factory[]> {
86
+ return (await this.getEntities(owner, 'factory')) as Factory[]
87
+ }
88
+
69
89
  async getShipSummaries(
70
90
  owner: NameType | ServerContract.Types.player_row
71
91
  ): Promise<ServerContract.Types.entity_summary[]> {
@@ -84,14 +104,42 @@ export class EntitiesManager extends BaseManager {
84
104
  return this.getSummaries(owner, 'container')
85
105
  }
86
106
 
87
- private wrapEntity(entity: ServerContract.Types.entity_info): Ship | Warehouse | Container {
88
- if (entity.type.equals('ship')) {
89
- return new Ship(entity)
90
- } else if (entity.type.equals('warehouse')) {
91
- return new Warehouse(entity)
92
- } else {
93
- return new Container(entity)
94
- }
107
+ async getExtractorSummaries(
108
+ owner: NameType | ServerContract.Types.player_row
109
+ ): Promise<ServerContract.Types.entity_summary[]> {
110
+ return this.getSummaries(owner, 'extractor')
111
+ }
112
+
113
+ async getFactorySummaries(
114
+ owner: NameType | ServerContract.Types.player_row
115
+ ): Promise<ServerContract.Types.entity_summary[]> {
116
+ return this.getSummaries(owner, 'factory')
117
+ }
118
+
119
+ async getNexus(id: UInt64Type): Promise<Nexus> {
120
+ return (await this.getEntity(id)) as Nexus
121
+ }
122
+
123
+ async getNexuses(owner: NameType | ServerContract.Types.player_row): Promise<Nexus[]> {
124
+ return (await this.getEntities(owner, 'nexus')) as Nexus[]
125
+ }
126
+
127
+ async getNexusSummaries(
128
+ owner: NameType | ServerContract.Types.player_row
129
+ ): Promise<ServerContract.Types.entity_summary[]> {
130
+ return this.getSummaries(owner, 'nexus')
131
+ }
132
+
133
+ private wrapEntity(
134
+ entity: ServerContract.Types.entity_info
135
+ ): Ship | Warehouse | Extractor | Factory | Container | Nexus {
136
+ if (entity.type.equals('ship')) return new Ship(entity)
137
+ if (entity.type.equals('warehouse')) return new Warehouse(entity)
138
+ if (entity.type.equals('extractor')) return new Extractor(entity)
139
+ if (entity.type.equals('factory')) return new Factory(entity)
140
+ if (entity.type.equals('container')) return new Container(entity)
141
+ if (entity.type.equals('nexus')) return new Nexus(entity)
142
+ throw new Error(`unknown entity type: ${entity.type}`)
95
143
  }
96
144
 
97
145
  private resolveOwner(owner: NameType | ServerContract.Types.player_row): Name {
@@ -1,6 +1,6 @@
1
- import {type UInt16Type, UInt64, type UInt64Type} from '@wharfkit/antelope'
1
+ import type {UInt16Type} from '@wharfkit/antelope'
2
2
  import {BaseManager} from './base'
3
- import {type CoordinatesType, coordsToLocationId, type Distance} from '../types'
3
+ import type {CoordinatesType, Distance} from '../types'
4
4
  import {hasSystem} from '../utils/system'
5
5
  import {findNearbyPlanets} from '../travel/travel'
6
6
  import type {ServerContract} from '../contracts'
@@ -47,22 +47,4 @@ export class LocationsManager extends BaseManager {
47
47
  reserve: overrideMap.get(s.index) ?? s.reserve,
48
48
  }))
49
49
  }
50
-
51
- async getLocationEntity(
52
- id: UInt64Type
53
- ): Promise<ServerContract.Types.location_row | undefined> {
54
- const row = await this.server.table('location').get(UInt64.from(id))
55
- return row ?? undefined
56
- }
57
-
58
- async getLocationEntityAt(
59
- coords: CoordinatesType
60
- ): Promise<ServerContract.Types.location_row | undefined> {
61
- const id = coordsToLocationId(coords)
62
- return this.getLocationEntity(id)
63
- }
64
-
65
- async getAllLocationEntities(): Promise<ServerContract.Types.location_row[]> {
66
- return this.server.table('location').all()
67
- }
68
50
  }
@@ -0,0 +1,128 @@
1
+ export interface SchemaField {
2
+ name: string
3
+ type: string
4
+ }
5
+
6
+ export type RawData =
7
+ | Uint8Array
8
+ | string
9
+ | number[]
10
+ | {immutable_serialized_data: Uint8Array | string | number[]}
11
+
12
+ export function deserializeAtomicData(
13
+ data: RawData,
14
+ schema: SchemaField[]
15
+ ): Record<string, unknown> {
16
+ let rawData: Uint8Array | string | number[]
17
+ if (data && typeof data === 'object' && 'immutable_serialized_data' in (data as object)) {
18
+ rawData = (data as {immutable_serialized_data: Uint8Array | string | number[]})
19
+ .immutable_serialized_data
20
+ } else {
21
+ rawData = data as Uint8Array | string | number[]
22
+ }
23
+
24
+ let bytes: Uint8Array
25
+ if (typeof rawData === 'string') {
26
+ const hex = rawData
27
+ bytes = new Uint8Array(hex.length / 2)
28
+ for (let i = 0; i < hex.length; i += 2) {
29
+ bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16)
30
+ }
31
+ } else if (Array.isArray(rawData)) {
32
+ bytes = new Uint8Array(rawData)
33
+ } else {
34
+ bytes = rawData
35
+ }
36
+
37
+ let offset = 0
38
+
39
+ function readVarint(): number {
40
+ let result = 0
41
+ let multiplier = 1
42
+ while (bytes[offset] >= 128) {
43
+ result += (bytes[offset] - 128) * multiplier
44
+ offset++
45
+ multiplier *= 128
46
+ }
47
+ result += bytes[offset] * multiplier
48
+ offset++
49
+ return result
50
+ }
51
+
52
+ function readVarint64(): bigint {
53
+ let result = 0n
54
+ let multiplier = 1n
55
+ while (bytes[offset] >= 128) {
56
+ result += BigInt(bytes[offset] - 128) * multiplier
57
+ offset++
58
+ multiplier *= 128n
59
+ }
60
+ result += BigInt(bytes[offset]) * multiplier
61
+ offset++
62
+ return result
63
+ }
64
+
65
+ function readZigzagInt64(): bigint {
66
+ const unsigned = readVarint64()
67
+ if (unsigned % 2n === 0n) {
68
+ return unsigned / 2n
69
+ } else {
70
+ return -(unsigned / 2n) - 1n
71
+ }
72
+ }
73
+
74
+ function readString(): string {
75
+ const length = readVarint()
76
+ const str = new TextDecoder().decode(bytes.slice(offset, offset + length))
77
+ offset += length
78
+ return str
79
+ }
80
+
81
+ const RESERVED = 4
82
+ const result: Record<string, unknown> = {}
83
+
84
+ while (offset < bytes.length) {
85
+ const fieldIndex = readVarint() - RESERVED
86
+ const field = schema[fieldIndex]
87
+ if (!field) break
88
+
89
+ switch (field.type) {
90
+ case 'uint16':
91
+ result[field.name] = readVarint()
92
+ break
93
+ case 'uint32':
94
+ result[field.name] = readVarint()
95
+ break
96
+ case 'uint64':
97
+ result[field.name] = readVarint64()
98
+ break
99
+ case 'int32':
100
+ result[field.name] = readZigzagInt64()
101
+ break
102
+ case 'int64':
103
+ result[field.name] = readZigzagInt64()
104
+ break
105
+ case 'string':
106
+ result[field.name] = readString()
107
+ break
108
+ case 'uint16[]': {
109
+ const len = readVarint()
110
+ const arr: number[] = []
111
+ for (let i = 0; i < len; i++) arr.push(readVarint())
112
+ result[field.name] = arr
113
+ break
114
+ }
115
+ case 'uint64[]': {
116
+ const len = readVarint()
117
+ const arr: bigint[] = []
118
+ for (let i = 0; i < len; i++) arr.push(readVarint64())
119
+ result[field.name] = arr
120
+ break
121
+ }
122
+ default:
123
+ throw new Error(`Unknown type: ${field.type}`)
124
+ }
125
+ }
126
+
127
+ return result
128
+ }
@@ -4,22 +4,29 @@ import {
4
4
  MODULE_ENGINE,
5
5
  MODULE_GATHERER,
6
6
  MODULE_GENERATOR,
7
+ MODULE_HAULER,
7
8
  MODULE_LOADER,
8
9
  MODULE_STORAGE,
10
+ MODULE_WARP,
9
11
  } from '../capabilities/modules'
10
12
  import {
11
13
  ITEM_CONTAINER_T1_PACKED,
12
14
  ITEM_CONTAINER_T2_PACKED,
13
15
  ITEM_CRAFTER_T1,
14
16
  ITEM_ENGINE_T1,
17
+ ITEM_EXTRACTOR_T1_PACKED,
15
18
  ITEM_GATHERER_T1,
16
19
  ITEM_GENERATOR_T1,
20
+ ITEM_HAULER_T1,
17
21
  ITEM_LOADER_T1,
18
22
  ITEM_SHIP_T1_PACKED,
19
23
  ITEM_STORAGE_T1,
20
24
  ITEM_WAREHOUSE_T1_PACKED,
25
+ ITEM_WARP_T1,
21
26
  } from '../data/item-ids'
22
27
  import {decodeStat} from '../derivation/crafting'
28
+ import {gathererDepthForTier} from '../entities/ship-deploy'
29
+ import {getItem} from '../data/catalog'
23
30
 
24
31
  function idiv(a: number, b: number): number {
25
32
  return Math.floor(a / b)
@@ -27,7 +34,7 @@ function idiv(a: number, b: number): number {
27
34
 
28
35
  export function computeBaseHullmass(stats: bigint): number {
29
36
  const density = decodeStat(stats, 1)
30
- return 25000 + 75 * density
37
+ return 100000 - 75 * density
31
38
  }
32
39
 
33
40
  export function computeBaseCapacityShip(stats: bigint): number {
@@ -42,17 +49,22 @@ export function computeBaseCapacityWarehouse(stats: bigint): number {
42
49
 
43
50
  export const computeEngineThrust = (vol: number): number => 400 + idiv(vol * 3, 4)
44
51
  export const computeEngineDrain = (thm: number): number => Math.max(30, 50 - idiv(thm, 70))
45
- export const computeGeneratorCap = (res: number): number => 300 + idiv(res, 6)
46
- export const computeGeneratorRech = (ref: number): number => 1 + idiv(ref * 3, 1000)
52
+ export const computeGeneratorCap = (com: number): number => 300 + idiv(com, 6)
53
+ export const computeGeneratorRech = (fin: number): number => 1 + idiv(fin * 3, 1000)
47
54
  export const computeGathererYield = (str: number): number => 200 + str
48
55
  export const computeGathererDrain = (con: number): number =>
49
56
  Math.max(250, 1250 - idiv(con * 25, 20))
50
- export const computeGathererDepth = (tol: number): number => 200 + idiv(tol * 3, 2)
57
+ export const computeGathererDepth = (tol: number, tier: number): number =>
58
+ gathererDepthForTier(tol, tier)
51
59
  export const computeGathererSpeed = (ref: number): number => 100 + idiv(ref * 4, 5)
52
- export const computeLoaderMass = (fin: number): number => Math.max(200, 2000 - fin * 2)
60
+ export const computeLoaderMass = (ins: number): number => Math.max(200, 2000 - ins * 2)
53
61
  export const computeLoaderThrust = (pla: number): number => 1 + idiv(pla, 500)
54
62
  export const computeCrafterSpeed = (rea: number): number => 100 + idiv(rea * 4, 5)
55
- export const computeCrafterDrain = (com: number): number => Math.max(5, 30 - idiv(com, 33))
63
+ export const computeCrafterDrain = (fin: number): number => Math.max(5, 30 - idiv(fin, 33))
64
+ export const computeHaulerCapacity = (fin: number): number => Math.max(1, 1 + idiv(fin, 400))
65
+ export const computeHaulerEfficiency = (con: number): number => 2000 + con * 6
66
+ export const computeHaulerDrain = (com: number): number => Math.max(3, 15 - idiv(com, 80))
67
+ export const computeWarpRange = (stat: number): number => 100 + stat * 3
56
68
 
57
69
  export function entityDisplayName(itemId: number): string {
58
70
  switch (itemId) {
@@ -60,6 +72,8 @@ export function entityDisplayName(itemId: number): string {
60
72
  return 'Ship'
61
73
  case ITEM_WAREHOUSE_T1_PACKED:
62
74
  return 'Warehouse'
75
+ case ITEM_EXTRACTOR_T1_PACKED:
76
+ return 'Extractor'
63
77
  case ITEM_CONTAINER_T1_PACKED:
64
78
  return 'Container'
65
79
  case ITEM_CONTAINER_T2_PACKED:
@@ -83,6 +97,10 @@ export function moduleDisplayName(itemId: number): string {
83
97
  return 'Crafter'
84
98
  case ITEM_STORAGE_T1:
85
99
  return 'Storage'
100
+ case ITEM_HAULER_T1:
101
+ return 'Hauler'
102
+ case ITEM_WARP_T1:
103
+ return 'Warp'
86
104
  default:
87
105
  return 'Module'
88
106
  }
@@ -116,8 +134,10 @@ export function formatModuleLine(slot: number, itemId: number, stats: bigint): s
116
134
  const tol = decodeStat(stats, 1)
117
135
  const con = decodeStat(stats, 3)
118
136
  const ref = decodeStat(stats, 4)
137
+ const tier = getItem(itemId).tier
119
138
  out += ` Yield ${computeGathererYield(str)} Depth ${computeGathererDepth(
120
- tol
139
+ tol,
140
+ tier
121
141
  )} Speed ${computeGathererSpeed(ref)} Drain ${computeGathererDrain(con)}`
122
142
  break
123
143
  }
@@ -142,6 +162,18 @@ export function formatModuleLine(slot: number, itemId: number, stats: bigint): s
142
162
  out += ` +${pct}% capacity`
143
163
  break
144
164
  }
165
+ case MODULE_HAULER: {
166
+ const fin = decodeStat(stats, 0)
167
+ const con = decodeStat(stats, 1)
168
+ const com = decodeStat(stats, 2)
169
+ out += ` Capacity ${computeHaulerCapacity(fin)} Efficiency ${computeHaulerEfficiency(con)} Drain ${computeHaulerDrain(com)}`
170
+ break
171
+ }
172
+ case MODULE_WARP: {
173
+ const stat = decodeStat(stats, 0)
174
+ out += ` Range ${computeWarpRange(stat)}`
175
+ break
176
+ }
145
177
  }
146
178
  return out
147
179
  }
@@ -158,6 +190,8 @@ export function buildEntityDescription(
158
190
  baseCapacity = computeBaseCapacityShip(hullStats)
159
191
  } else if (itemId === ITEM_WAREHOUSE_T1_PACKED) {
160
192
  baseCapacity = computeBaseCapacityWarehouse(hullStats)
193
+ } else if (itemId === ITEM_EXTRACTOR_T1_PACKED) {
194
+ baseCapacity = computeBaseCapacityShip(hullStats)
161
195
  }
162
196
 
163
197
  let out = entityDisplayName(itemId)
package/src/nft/index.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from './deserializers'
2
2
  export * from './description'
3
+ export * from './atomicdata'
@@ -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 ?? 500
227
- const hrd = stats.hardness ?? 500
228
- const sat = stats.saturation ?? 500
229
- const statSum = str + hrd + sat
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
- modName = getItem(modItemId).name
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
  }
@@ -14,7 +14,7 @@ import {
14
14
  RECIPE_INPUTS_INSUFFICIENT,
15
15
  RECIPE_INPUTS_INVALID,
16
16
  RECIPE_NOT_FOUND,
17
- SHIP_CARGO_NOT_LOADED,
17
+ ENTITY_CARGO_NOT_LOADED,
18
18
  } from '../errors'
19
19
  import {getRecipe, type RecipeInput} from '../data/recipes-runtime'
20
20
  import {getItem} from '../data/catalog'
@@ -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
 
@@ -385,7 +389,7 @@ function validateCraftTask(task: ServerContract.Types.task, projected: Projected
385
389
  break
386
390
  }
387
391
  }
388
- if (!found) throw new Error(SHIP_CARGO_NOT_LOADED)
392
+ if (!found) throw new Error(ENTITY_CARGO_NOT_LOADED)
389
393
  }
390
394
  }
391
395
 
@@ -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
@@ -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
+ }