screeps-connectivity 0.5.0 → 0.5.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 9523f3c: Make highway resources easier to spot on the world map. Power banks are now
8
+ drawn as larger bright-red dots (radius 1.5 → 2.5) instead of small orange ones,
9
+ and deposits — previously rendered as tiny muted-red "foreign" dots because
10
+ their `d` map2 key fell through to the generic user-object path — now show as
11
+ prominent white dots. The deposit key is documented on `RoomMap2Data`.
12
+
13
+ ## 0.5.1
14
+
15
+ ### Patch Changes
16
+
17
+ - 36673a3: Add `Game.map.visual` rendering support. The map view now subscribes to the `mapVisual` WebSocket channel and renders player-drawn map visuals (lines, circles, rects, polys, text) on the world map canvas using PixiJS.
18
+
3
19
  ## 0.5.0
4
20
 
5
21
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "screeps-connectivity",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "license": "ISC",
5
5
  "type": "module",
6
6
  "repository": {
@@ -13,6 +13,7 @@ import type { LogFn } from './logger.js'
13
13
  import type { AuthStrategy } from './http/auth/AuthStrategy.js'
14
14
  import type { StorageAdapter } from './storage/StorageAdapter.js'
15
15
  import type { Subscription } from './subscription/index.js'
16
+ import type { ApiRoomDecorationsResponse } from './types/api.js'
16
17
 
17
18
  type WsConstructor = typeof globalThis.WebSocket
18
19
 
@@ -39,6 +40,8 @@ export interface ScreepsClientOptions {
39
40
  * Pass `false` to disable.
40
41
  */
41
42
  tokenRefresh?: TokenRefreshOptions | false
43
+ /** Override the /api/game/room-decorations response with static data (useful for dev/testing when the server doesn't support the endpoint). */
44
+ decorationsMock?: ApiRoomDecorationsResponse
42
45
  }
43
46
 
44
47
  export class ScreepsClient {
@@ -70,7 +73,7 @@ export class ScreepsClient {
70
73
  this.logger = Logger.create(opts.debug)
71
74
  this.logger.log(`[screeps:client] init ${opts.url}`)
72
75
  this.cache = new Cache(namespace, opts.storage ?? null)
73
- this.http = new HttpClient({ url: opts.url, auth: opts.auth, logger: this.logger.child('http'), serverPassword: opts.serverPassword })
76
+ this.http = new HttpClient({ url: opts.url, auth: opts.auth, logger: this.logger.child('http'), serverPassword: opts.serverPassword, decorationsMock: opts.decorationsMock })
74
77
  this.socket = new SocketClient({ url: opts.url, WebSocket: opts.WebSocket, logger: this.logger.child('socket') })
75
78
  const map2Storage = new Map2Storage({
76
79
  adapter: opts.storage ?? null,
@@ -32,7 +32,7 @@ export class HttpClient extends EventTarget {
32
32
  readonly leaderboard: LeaderboardEndpoints
33
33
  readonly experimental: ExperimentalEndpoints
34
34
 
35
- constructor(opts: { url: string; auth: AuthStrategy; logger?: Logger; serverPassword?: string }) {
35
+ constructor(opts: { url: string; auth: AuthStrategy; logger?: Logger; serverPassword?: string; decorationsMock?: import('../types/api.js').ApiRoomDecorationsResponse }) {
36
36
  super()
37
37
  this.baseUrl = opts.url.endsWith('/') ? opts.url : `${opts.url}/`
38
38
  this.authStrategy = opts.auth
@@ -40,7 +40,7 @@ export class HttpClient extends EventTarget {
40
40
  this.serverPassword = opts.serverPassword ?? null
41
41
  this.auth = createAuthEndpoints(this)
42
42
  this.register = createRegisterEndpoints(this)
43
- this.game = createGameEndpoints(this)
43
+ this.game = createGameEndpoints(this, opts.decorationsMock)
44
44
  this.user = createUserEndpoints(this)
45
45
  this.leaderboard = createLeaderboardEndpoints(this)
46
46
  this.experimental = createExperimentalEndpoints(this)
@@ -14,6 +14,7 @@ import type {
14
14
  ApiCheckUniqueObjectNameResponse,
15
15
  ApiGameTickResponse,
16
16
  RoomHistoryChunk,
17
+ ApiRoomDecorationsResponse,
17
18
  } from '../../types/api.js'
18
19
  import { createPowerCreepsEndpoints, type PowerCreepsEndpoints } from './power-creeps.js'
19
20
 
@@ -21,6 +22,7 @@ export interface GameEndpoints {
21
22
  roomTerrain(room: string, shard?: string | null): Promise<ApiRoomTerrainResponse>
22
23
  /** @deprecated Not available on private servers (backend-local). Room objects are delivered via the `room:<name>` WebSocket channel. */
23
24
  roomObjects(room: string, shard?: string | null): Promise<ApiRoomObjectsResponse>
25
+ roomDecorations(room: string, shard?: string | null): Promise<ApiRoomDecorationsResponse>
24
26
  roomStatus(room: string, shard?: string | null): Promise<{ ok: number; status: string; novice?: string }>
25
27
  roomOverview(room: string, interval?: number, shard?: string | null): Promise<unknown>
26
28
  time(shard?: string | null): Promise<{ ok: number; time: number }>
@@ -62,10 +64,13 @@ function withShard(params: Record<string, unknown>, shard?: string | null): Reco
62
64
  return params
63
65
  }
64
66
 
65
- export function createGameEndpoints(http: HttpClient): GameEndpoints {
67
+ export function createGameEndpoints(http: HttpClient, decorationsMock?: ApiRoomDecorationsResponse): GameEndpoints {
66
68
  return {
67
69
  roomTerrain: (room, shard) => http.request('GET', '/api/game/room-terrain', withShard({ room, encoded: 1 }, shard)),
68
70
  roomObjects: (room, shard) => http.request('GET', '/api/game/room-objects', withShard({ room }, shard)),
71
+ roomDecorations: decorationsMock
72
+ ? () => Promise.resolve(decorationsMock)
73
+ : (room, shard) => http.request('GET', '/api/game/room-decorations', withShard({ room }, shard)),
69
74
  roomStatus: (room, shard) => http.request('GET', '/api/game/room-status', withShard({ room }, shard)),
70
75
  roomOverview: (room, interval = 8, shard) => http.request('GET', '/api/game/room-overview', withShard({ room, interval }, shard)),
71
76
  time: (shard) => http.request('GET', '/api/game/time', withShard({}, shard)),
package/src/index.ts CHANGED
@@ -35,11 +35,13 @@ export type {
35
35
  Badge,
36
36
  VisualStyle,
37
37
  RoomVisualEntry,
38
+ MapVisualEntry,
38
39
  } from './types/game.js'
39
40
 
40
41
  export { fetchServerVersion, fetchAuthModInfo, checkUsername, checkEmail, registerUser, getServerFeature, getScreepsmodAuth } from './http/fetchServerVersion.js'
41
42
  export type { ApiAuthModInfoResponse } from './http/fetchServerVersion.js'
42
- export type { RoomHistoryChunk } from './types/api.js'
43
+ export type { RoomHistoryChunk, ApiRoomDecorationsResponse, ApiRoomDecorationItem, ApiRoomDecorationDef, ApiRoomDecorationGraphic, ApiRoomDecorationActive } from './types/api.js'
44
+ export { ROOM_DECORATIONS_MOCK } from './mocks/roomDecorations.js'
43
45
 
44
46
  export { badgeToSvg } from './badge/index.js'
45
47
  export { BadgeColors } from './badge/colors.js'
@@ -0,0 +1,140 @@
1
+ import type { ApiRoomDecorationsResponse } from '../types/api.js'
2
+
3
+ /**
4
+ * Example /api/game/room-decorations response captured from the official Screeps server.
5
+ * Use as `decorationsMock` in ScreepsClientOptions to test decoration rendering locally.
6
+ *
7
+ * Contains: wallLandscape (W-00031), floorLandscape (F-00065), creep overlay (C-00071).
8
+ */
9
+ export const ROOM_DECORATIONS_MOCK: ApiRoomDecorationsResponse = {
10
+ ok: 1,
11
+ decorations: [
12
+ {
13
+ _id: '5fd719f496a97dbdd2f57bea',
14
+ user: '58356f6fc8012784246abb7e',
15
+ active: {
16
+ strokeWidth: '30',
17
+ foregroundColor: '#6A88FF',
18
+ foregroundAlpha: 1,
19
+ backgroundColor: '#5261A0',
20
+ backgroundBrightness: '1',
21
+ strokeColor: '#465EC4',
22
+ strokeBrightness: '1',
23
+ strokeLighting: '1',
24
+ foregroundBrightness: '1',
25
+ world: true,
26
+ tileScale: 1,
27
+ shard: 'shard1',
28
+ room: 'E2S49',
29
+ },
30
+ decoration: {
31
+ _id: '5eea71d952fdab816c74e50c',
32
+ graphics: [],
33
+ description: 'звездочки',
34
+ rarityMultiplier: 1,
35
+ type: 'wallLandscape',
36
+ foregroundUrl: 'https://s3.amazonaws.com/static.screeps.com/decorations/0e53bc662a2688275b9c6766cf946031b19bf0b.png',
37
+ theme: '5e908ece62339b0045ac2d0e',
38
+ rarity: 4,
39
+ name: 'W-00031:WI:X',
40
+ group: '5edf121e1f95b4047880e2ef',
41
+ steamItemDefId: 4415,
42
+ enabled: true,
43
+ groupDescription: 'Controllable brightness.',
44
+ preview: {
45
+ original: 'https://s3.amazonaws.com/static.screeps.com/decorations/6c5533fd1f83ecadb8cf105fdda95fa4687114b.png',
46
+ '128x128': 'https://s3.amazonaws.com/static.screeps.com/decorations/919410fbd3c74c697d7396ba0636b2d33ca3fa5.png',
47
+ '256x256': 'https://s3.amazonaws.com/static.screeps.com/decorations/16f3453e69387abfe110050532d9e7c67e8fc41.png',
48
+ },
49
+ },
50
+ },
51
+ {
52
+ _id: '60086a96a6ad8c744554e05c',
53
+ user: '58356f6fc8012784246abb7e',
54
+ active: {
55
+ floorBackgroundColor: '#F67BFF',
56
+ floorBackgroundBrightness: 0.98,
57
+ floorForegroundColor: '#F67BFF',
58
+ floorForegroundAlpha: 0.87,
59
+ swampColor: '#4073A3',
60
+ swampStrokeColor: '#CC7DD2',
61
+ swampStrokeWidth: 30,
62
+ roadsColor: '#E9A7EE',
63
+ roadsBrightness: 0.41,
64
+ tileScale: 1,
65
+ floorForegroundBrightness: 0.42,
66
+ world: true,
67
+ shard: 'shard1',
68
+ room: 'E2S49',
69
+ },
70
+ decoration: {
71
+ _id: '5eea668452fdab5ea674e1ca',
72
+ graphics: [],
73
+ description: 'калейдоскоп',
74
+ rarityMultiplier: 1,
75
+ type: 'floorLandscape',
76
+ floorForegroundUrl: 'https://s3.amazonaws.com/static.screeps.com/decorations/7e3d680d2f48b1ff244a95e564275536fe4ed61.png',
77
+ theme: '5e908ed162339b0045ac2d0f',
78
+ rarity: 4,
79
+ name: 'F-00065:AL:X',
80
+ group: '5ee9016d961c4e27734cf301',
81
+ steamItemDefId: 3606,
82
+ enabled: true,
83
+ groupDescription: 'Adjustable brightness.',
84
+ preview: {
85
+ original: 'https://s3.amazonaws.com/static.screeps.com/decorations/da7256631a2a2c8568221ad1f0bef0bb5525a7.png',
86
+ '128x128': 'https://s3.amazonaws.com/static.screeps.com/decorations/3c0f0da6b41e413bdc4f66a01eed62b5b252c29.png',
87
+ '256x256': 'https://s3.amazonaws.com/static.screeps.com/decorations/80c9978b58403e13b4a28f47ef1422e895c04fe.png',
88
+ },
89
+ },
90
+ },
91
+ {
92
+ _id: '5fbab429b2f60e0fae891bcb',
93
+ user: '58356f6fc8012784246abb7e',
94
+ active: {
95
+ lighting: true,
96
+ position: 'below',
97
+ width: 256,
98
+ height: 256,
99
+ syncRotate: true,
100
+ animation: 'blink',
101
+ brightness: '1',
102
+ nameFilter: 'ecarry*',
103
+ exclude: false,
104
+ firstColor: '#FFFFFF',
105
+ firstAlpha: 1,
106
+ secondColor: '#FFFFFF',
107
+ secondAlpha: '1',
108
+ flip: false,
109
+ color: '#1fe265',
110
+ alpha: 0.79,
111
+ },
112
+ decoration: {
113
+ _id: '5f2ab6c1ad10bf3222df1b4f',
114
+ graphics: [
115
+ {
116
+ url: 'https://s3.amazonaws.com/static.screeps.com/decorations/a23d96123d27bf163babe0a29a54de003aea568.svg',
117
+ color: 'color',
118
+ alpha: 'alpha',
119
+ visible: '',
120
+ },
121
+ ],
122
+ description: '38.svg',
123
+ rarityMultiplier: 1,
124
+ type: 'creep',
125
+ theme: '5ee3db64961c4e98dc4cf121',
126
+ rarity: 5,
127
+ name: 'C-00071:CC:X',
128
+ group: '5eec306d0042094b2845f7d6',
129
+ steamItemDefId: 57779,
130
+ enabled: true,
131
+ groupDescription: 'Choose your own color.',
132
+ preview: {
133
+ original: 'https://s3.amazonaws.com/static.screeps.com/decorations/3a7853ca055d7ae5738852f6f73f27389594e2e.png',
134
+ '128x128': 'https://s3.amazonaws.com/static.screeps.com/decorations/8557cffe9216525092427f90b9847ef5bdcd6c8.png',
135
+ '256x256': 'https://s3.amazonaws.com/static.screeps.com/decorations/e198e4f909cdb99f11a8cc7bc9f7e7a733faeca.png',
136
+ },
137
+ },
138
+ },
139
+ ],
140
+ }
@@ -200,6 +200,41 @@ export class UserStore extends TypedStore<UserStoreEvents> {
200
200
  }
201
201
  }
202
202
 
203
+ subscribeMapVisual(shard: string | null): Subscription {
204
+ this.logger.log('subscribe mapVisual', shard)
205
+ let socketSub: Subscription | null = null
206
+ let listenerSub: Subscription | null = null
207
+ let disposed = false
208
+
209
+ const setup = async () => {
210
+ try {
211
+ const uid = this._userId ?? (await this.me())._id
212
+ if (disposed) return
213
+ // Official multi-shard servers use mapVisual:${uid}/${shard}; unofficial use mapVisual:${uid}.
214
+ const fullChannel = shard ? `mapVisual:${uid}/${shard}` : `mapVisual:${uid}`
215
+ socketSub = this.socket.subscribe(fullChannel)
216
+ listenerSub = this.socket.on(fullChannel, (data) => {
217
+ this.emit('user:mapVisual', { shard, data: typeof data === 'string' ? data : '' })
218
+ })
219
+ } catch (err) {
220
+ if (!disposed) {
221
+ this.dispatchEvent(new ErrorEvent('error', { error: err instanceof Error ? err : new Error(String(err)) }))
222
+ }
223
+ }
224
+ }
225
+
226
+ void setup()
227
+
228
+ return {
229
+ dispose: () => {
230
+ this.logger.log('unsubscribe mapVisual', shard)
231
+ disposed = true
232
+ socketSub?.dispose()
233
+ listenerSub?.dispose()
234
+ },
235
+ }
236
+ }
237
+
203
238
  /** Subscribe to the general user stream to receive global data like flags. */
204
239
  subscribeUserStream(): Subscription {
205
240
  this.logger.log('subscribe user stream')
package/src/types/api.ts CHANGED
@@ -257,3 +257,71 @@ export interface ApiUserMessagesUnreadCountResponse {
257
257
  ok: number
258
258
  count: number
259
259
  }
260
+
261
+ export interface ApiRoomDecorationGraphic {
262
+ url: string
263
+ color?: string
264
+ alpha?: string
265
+ visible?: string
266
+ }
267
+
268
+ export interface ApiRoomDecorationDef {
269
+ _id: string
270
+ type: 'floorLandscape' | 'wallLandscape' | 'wallGraffiti' | 'creep' | 'object' | 'metadata'
271
+ graphics?: ApiRoomDecorationGraphic[]
272
+ foregroundUrl?: string
273
+ floorForegroundUrl?: string
274
+ tileScale?: number | string
275
+ [key: string]: unknown
276
+ }
277
+
278
+ /** User-configured properties for an activated decoration. Number fields may arrive as strings from the API. */
279
+ export interface ApiRoomDecorationActive {
280
+ // floorLandscape
281
+ floorBackgroundColor?: string
282
+ floorBackgroundBrightness?: number | string
283
+ floorForegroundColor?: string
284
+ floorForegroundBrightness?: number | string
285
+ floorForegroundAlpha?: number | string
286
+ swampColor?: string
287
+ swampStrokeColor?: string
288
+ swampStrokeWidth?: number | string
289
+ roadsColor?: string
290
+ roadsBrightness?: number | string
291
+ // wallLandscape
292
+ backgroundColor?: string
293
+ backgroundBrightness?: number | string
294
+ strokeColor?: string
295
+ strokeBrightness?: number | string
296
+ strokeWidth?: number | string
297
+ strokeLighting?: number | string
298
+ foregroundColor?: string
299
+ foregroundAlpha?: number | string
300
+ foregroundBrightness?: number | string
301
+ // creep / object
302
+ user?: string
303
+ nameFilter?: string
304
+ exclude?: boolean
305
+ width?: number | string
306
+ height?: number | string
307
+ brightness?: number | string
308
+ lighting?: boolean
309
+ animation?: string
310
+ position?: string
311
+ syncRotate?: boolean
312
+ flip?: boolean
313
+ [key: string]: unknown
314
+ }
315
+
316
+ /** Raw decoration item as returned by /api/game/room-decorations. */
317
+ export interface ApiRoomDecorationItem {
318
+ _id: string
319
+ user: string
320
+ active: ApiRoomDecorationActive
321
+ decoration: ApiRoomDecorationDef
322
+ }
323
+
324
+ export interface ApiRoomDecorationsResponse {
325
+ ok: number
326
+ decorations: ApiRoomDecorationItem[]
327
+ }
@@ -42,6 +42,7 @@ export interface UserStoreEvents {
42
42
  'user:code': { branch: string; modules: Record<string, string> }
43
43
  'user:stream': Record<string, unknown>
44
44
  'user:memory': { path: string; shard: string | null; value: unknown }
45
+ 'user:mapVisual': { shard: string | null; data: string }
45
46
  }
46
47
 
47
48
  export interface ServerStoreEvents {
package/src/types/game.ts CHANGED
@@ -7,6 +7,7 @@ export interface RoomMap2Data {
7
7
  c?: [number, number][] | null // controllers
8
8
  m?: [number, number][] | null // minerals
9
9
  k?: [number, number][] | null // source keeper lairs
10
+ d?: [number, number][] | null // deposits (highway commodity resource)
10
11
  [userId: string]: [number, number][] | null | undefined // structures + creeps for that user
11
12
  }
12
13
 
@@ -150,6 +151,10 @@ export interface VisualStyle {
150
151
  width?: number
151
152
  radius?: number
152
153
  font?: string | number
154
+ fontSize?: number
155
+ fontFamily?: string
156
+ fontStyle?: string
157
+ fontVariant?: string
153
158
  }
154
159
 
155
160
  export type RoomVisualEntry =
@@ -158,3 +163,10 @@ export type RoomVisualEntry =
158
163
  | { t: 'r'; x: number; y: number; w: number; h: number; s: VisualStyle }
159
164
  | { t: 'p'; points: [number, number][]; s: VisualStyle }
160
165
  | { t: 'l'; x1: number; y1: number; x2: number; y2: number; s: VisualStyle }
166
+
167
+ export type MapVisualEntry =
168
+ | { t: 't'; n: string; x: number; y: number; text: string; s: VisualStyle }
169
+ | { t: 'c'; n: string; x: number; y: number; s: VisualStyle }
170
+ | { t: 'r'; n: string; x: number; y: number; w: number; h: number; s: VisualStyle }
171
+ | { t: 'p'; points: Array<{ n: string; x: number; y: number }>; s: VisualStyle }
172
+ | { t: 'l'; n1: string; x1: number; y1: number; n2: string; x2: number; y2: number; s: VisualStyle }