screeps-connectivity 0.4.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 +12 -0
- package/package.json +1 -1
- package/src/ScreepsClient.ts +4 -1
- package/src/http/HttpClient.ts +2 -2
- package/src/http/endpoints/game.ts +13 -1
- package/src/index.ts +3 -0
- package/src/mocks/roomDecorations.ts +140 -0
- package/src/stores/UserStore.ts +35 -0
- package/src/types/api.ts +75 -0
- package/src/types/events.ts +1 -0
- package/src/types/game.ts +11 -0
- package/tests/http/endpoints/game.test.ts +28 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
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
|
+
|
|
9
|
+
## 0.5.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 1e7161f: Add `http.game.roomHistory(room, time, shard?)` to `GameEndpoints` — handles both official server (path-based URL) and private server (query-param URL) automatically. `HistoryPlayer` in `screeps-client` is refactored to use this endpoint instead of a raw `fetch()` with manual token injection.
|
|
14
|
+
|
|
3
15
|
## 0.4.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/package.json
CHANGED
package/src/ScreepsClient.ts
CHANGED
|
@@ -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,
|
package/src/http/HttpClient.ts
CHANGED
|
@@ -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)
|
|
@@ -13,6 +13,8 @@ import type {
|
|
|
13
13
|
ApiGenUniqueObjectNameResponse,
|
|
14
14
|
ApiCheckUniqueObjectNameResponse,
|
|
15
15
|
ApiGameTickResponse,
|
|
16
|
+
RoomHistoryChunk,
|
|
17
|
+
ApiRoomDecorationsResponse,
|
|
16
18
|
} from '../../types/api.js'
|
|
17
19
|
import { createPowerCreepsEndpoints, type PowerCreepsEndpoints } from './power-creeps.js'
|
|
18
20
|
|
|
@@ -20,6 +22,7 @@ export interface GameEndpoints {
|
|
|
20
22
|
roomTerrain(room: string, shard?: string | null): Promise<ApiRoomTerrainResponse>
|
|
21
23
|
/** @deprecated Not available on private servers (backend-local). Room objects are delivered via the `room:<name>` WebSocket channel. */
|
|
22
24
|
roomObjects(room: string, shard?: string | null): Promise<ApiRoomObjectsResponse>
|
|
25
|
+
roomDecorations(room: string, shard?: string | null): Promise<ApiRoomDecorationsResponse>
|
|
23
26
|
roomStatus(room: string, shard?: string | null): Promise<{ ok: number; status: string; novice?: string }>
|
|
24
27
|
roomOverview(room: string, interval?: number, shard?: string | null): Promise<unknown>
|
|
25
28
|
time(shard?: string | null): Promise<{ ok: number; time: number }>
|
|
@@ -39,6 +42,8 @@ export interface GameEndpoints {
|
|
|
39
42
|
removeConstructionSite(room: string, ids: string[], shard?: string | null): Promise<{ ok: number }>
|
|
40
43
|
addObjectIntent(id: string, room: string, name: string, intent: unknown, shard?: string | null): Promise<{ ok: number }>
|
|
41
44
|
addGlobalIntent(name: string, intent: unknown): Promise<{ ok: number }>
|
|
45
|
+
/** Fetch a room history chunk. Pass shard for official multi-shard servers; omit for private servers. */
|
|
46
|
+
roomHistory(room: string, time: number, shard?: string | null): Promise<RoomHistoryChunk>
|
|
42
47
|
setNotifyWhenAttacked(id: string, enabled: boolean): Promise<{ ok: number }>
|
|
43
48
|
createInvader(room: string, x: number, y: number, size: number, type: string, boosted?: boolean): Promise<{ ok: number }>
|
|
44
49
|
removeInvader(id: string): Promise<{ ok: number }>
|
|
@@ -59,10 +64,13 @@ function withShard(params: Record<string, unknown>, shard?: string | null): Reco
|
|
|
59
64
|
return params
|
|
60
65
|
}
|
|
61
66
|
|
|
62
|
-
export function createGameEndpoints(http: HttpClient): GameEndpoints {
|
|
67
|
+
export function createGameEndpoints(http: HttpClient, decorationsMock?: ApiRoomDecorationsResponse): GameEndpoints {
|
|
63
68
|
return {
|
|
64
69
|
roomTerrain: (room, shard) => http.request('GET', '/api/game/room-terrain', withShard({ room, encoded: 1 }, shard)),
|
|
65
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)),
|
|
66
74
|
roomStatus: (room, shard) => http.request('GET', '/api/game/room-status', withShard({ room }, shard)),
|
|
67
75
|
roomOverview: (room, interval = 8, shard) => http.request('GET', '/api/game/room-overview', withShard({ room, interval }, shard)),
|
|
68
76
|
time: (shard) => http.request('GET', '/api/game/time', withShard({}, shard)),
|
|
@@ -85,6 +93,10 @@ export function createGameEndpoints(http: HttpClient): GameEndpoints {
|
|
|
85
93
|
removeConstructionSite: (room, ids, shard) => http.request('POST', '/api/game/add-object-intent', withShard({ _id: 'room', room, name: 'removeConstructionSite', intent: ids.map(id => ({ id, roomName: room })) }, shard)),
|
|
86
94
|
addObjectIntent: (id, room, name, intent, shard) => http.request('POST', '/api/game/add-object-intent', withShard({ _id: id, room, name, intent }, shard)),
|
|
87
95
|
addGlobalIntent: (name, intent) => http.request('POST', '/api/game/add-global-intent', { name, intent }),
|
|
96
|
+
roomHistory: (room, time, shard) =>
|
|
97
|
+
shard
|
|
98
|
+
? http.request('GET', `/room-history/${encodeURIComponent(shard)}/${encodeURIComponent(room)}/${time}.json`)
|
|
99
|
+
: http.request('GET', '/room-history', { room, time }),
|
|
88
100
|
setNotifyWhenAttacked: (id, enabled) => http.request('POST', '/api/game/set-notify-when-attacked', { _id: id, enabled }),
|
|
89
101
|
createInvader: (room, x, y, size, type, boosted) => http.request('POST', '/api/game/create-invader', { room, x, y, size, type, ...(boosted != null ? { boosted } : {}) }),
|
|
90
102
|
removeInvader: (id) => http.request('POST', '/api/game/remove-invader', { _id: id }),
|
package/src/index.ts
CHANGED
|
@@ -35,10 +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'
|
|
43
|
+
export type { RoomHistoryChunk, ApiRoomDecorationsResponse, ApiRoomDecorationItem, ApiRoomDecorationDef, ApiRoomDecorationGraphic, ApiRoomDecorationActive } from './types/api.js'
|
|
44
|
+
export { ROOM_DECORATIONS_MOCK } from './mocks/roomDecorations.js'
|
|
42
45
|
|
|
43
46
|
export { badgeToSvg } from './badge/index.js'
|
|
44
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
|
+
}
|
package/src/stores/UserStore.ts
CHANGED
|
@@ -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
|
@@ -2,6 +2,13 @@ export interface ApiOkResponse {
|
|
|
2
2
|
ok: number
|
|
3
3
|
}
|
|
4
4
|
|
|
5
|
+
export interface RoomHistoryChunk {
|
|
6
|
+
timestamp: number
|
|
7
|
+
room: string
|
|
8
|
+
base: number
|
|
9
|
+
ticks: Record<string, import('./game.js').RoomObjectDiff>
|
|
10
|
+
}
|
|
11
|
+
|
|
5
12
|
export interface ApiAuthSigninResponse {
|
|
6
13
|
ok: number
|
|
7
14
|
token: string
|
|
@@ -250,3 +257,71 @@ export interface ApiUserMessagesUnreadCountResponse {
|
|
|
250
257
|
ok: number
|
|
251
258
|
count: number
|
|
252
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
|
+
}
|
package/src/types/events.ts
CHANGED
|
@@ -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 }
|
|
@@ -207,4 +207,32 @@ describe('game endpoints', () => {
|
|
|
207
207
|
expect(url).toContain('/api/game/tick')
|
|
208
208
|
expect(res.tick).toBe(500)
|
|
209
209
|
})
|
|
210
|
+
|
|
211
|
+
it('roomHistory uses path URL for official server (shard provided)', async () => {
|
|
212
|
+
const chunk = { timestamp: 1000, room: 'W1N1', base: 1000, ticks: {} }
|
|
213
|
+
fetchMock.mockResolvedValue(mockResponse(chunk))
|
|
214
|
+
const http = new HttpClient({ url: 'http://test.local', auth: new TokenAuth({ token: 't' }) })
|
|
215
|
+
|
|
216
|
+
const res = await http.game.roomHistory('W1N1', 1000, 'shard0')
|
|
217
|
+
|
|
218
|
+
const [url, init] = fetchMock.mock.calls[0] as [string, RequestInit]
|
|
219
|
+
expect(init.method).toBe('GET')
|
|
220
|
+
expect(url).toBe('http://test.local/room-history/shard0/W1N1/1000.json')
|
|
221
|
+
expect(res.base).toBe(1000)
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
it('roomHistory uses query params for private server (no shard)', async () => {
|
|
225
|
+
const chunk = { timestamp: 1000, room: 'W1N1', base: 1000, ticks: {} }
|
|
226
|
+
fetchMock.mockResolvedValue(mockResponse(chunk))
|
|
227
|
+
const http = new HttpClient({ url: 'http://test.local', auth: new TokenAuth({ token: 't' }) })
|
|
228
|
+
|
|
229
|
+
const res = await http.game.roomHistory('W1N1', 1000)
|
|
230
|
+
|
|
231
|
+
const [url, init] = fetchMock.mock.calls[0] as [string, RequestInit]
|
|
232
|
+
expect(init.method).toBe('GET')
|
|
233
|
+
expect(url).toMatch(/\/room-history/)
|
|
234
|
+
expect(url).toContain('room=W1N1')
|
|
235
|
+
expect(url).toContain('time=1000')
|
|
236
|
+
expect(res.base).toBe(1000)
|
|
237
|
+
})
|
|
210
238
|
})
|