screeps-connectivity 0.5.0 → 0.5.1

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,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 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.
8
+
3
9
  ## 0.5.0
4
10
 
5
11
  ### 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.1",
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
@@ -150,6 +150,10 @@ export interface VisualStyle {
150
150
  width?: number
151
151
  radius?: number
152
152
  font?: string | number
153
+ fontSize?: number
154
+ fontFamily?: string
155
+ fontStyle?: string
156
+ fontVariant?: string
153
157
  }
154
158
 
155
159
  export type RoomVisualEntry =
@@ -158,3 +162,10 @@ export type RoomVisualEntry =
158
162
  | { t: 'r'; x: number; y: number; w: number; h: number; s: VisualStyle }
159
163
  | { t: 'p'; points: [number, number][]; s: VisualStyle }
160
164
  | { t: 'l'; x1: number; y1: number; x2: number; y2: number; s: VisualStyle }
165
+
166
+ export type MapVisualEntry =
167
+ | { t: 't'; n: string; x: number; y: number; text: string; s: VisualStyle }
168
+ | { t: 'c'; n: string; x: number; y: number; s: VisualStyle }
169
+ | { t: 'r'; n: string; x: number; y: number; w: number; h: number; s: VisualStyle }
170
+ | { t: 'p'; points: Array<{ n: string; x: number; y: number }>; s: VisualStyle }
171
+ | { t: 'l'; n1: string; x1: number; y1: number; n2: string; x2: number; y2: number; s: VisualStyle }