@watchtower-sdk/core 0.2.0 → 0.2.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/README.md +137 -245
- package/dist/index.d.mts +148 -1
- package/dist/index.d.ts +148 -1
- package/dist/index.js +327 -0
- package/dist/index.mjs +326 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
# @watchtower/
|
|
1
|
+
# @watchtower-sdk/core
|
|
2
2
|
|
|
3
|
-
Simple game backend SDK - cloud saves
|
|
3
|
+
Simple game backend SDK - cloud saves and multiplayer.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install @watchtower/
|
|
8
|
+
npm install @watchtower-sdk/core
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
## Quick Start
|
|
12
12
|
|
|
13
13
|
```typescript
|
|
14
|
-
import { Watchtower } from '@watchtower/
|
|
14
|
+
import { Watchtower } from '@watchtower-sdk/core'
|
|
15
15
|
|
|
16
16
|
const wt = new Watchtower({
|
|
17
17
|
gameId: 'my-game',
|
|
@@ -19,265 +19,168 @@ const wt = new Watchtower({
|
|
|
19
19
|
})
|
|
20
20
|
|
|
21
21
|
// Cloud saves
|
|
22
|
-
await wt.save('progress', { level: 5
|
|
22
|
+
await wt.save('progress', { level: 5 })
|
|
23
23
|
const data = await wt.load('progress')
|
|
24
24
|
|
|
25
25
|
// Multiplayer
|
|
26
|
-
const
|
|
27
|
-
|
|
26
|
+
const state = { players: {} }
|
|
27
|
+
const sync = wt.sync(state)
|
|
28
|
+
await sync.join('room-code')
|
|
29
|
+
state.players[sync.myId] = { x: 0, y: 0 }
|
|
30
|
+
// Others appear in state.players automatically!
|
|
28
31
|
```
|
|
29
32
|
|
|
30
33
|
## Cloud Saves
|
|
31
34
|
|
|
32
|
-
|
|
35
|
+
Key-value storage per player. JSON in, JSON out.
|
|
33
36
|
|
|
34
37
|
```typescript
|
|
35
|
-
// Save
|
|
38
|
+
// Save
|
|
36
39
|
await wt.save('progress', { level: 5, coins: 100 })
|
|
37
40
|
await wt.save('settings', { music: true, sfx: true })
|
|
38
|
-
await wt.save('inventory', ['sword', 'shield', 'potion'])
|
|
39
41
|
|
|
40
|
-
// Load
|
|
42
|
+
// Load
|
|
41
43
|
const progress = await wt.load('progress')
|
|
42
44
|
const settings = await wt.load('settings')
|
|
43
45
|
|
|
44
|
-
// List all
|
|
45
|
-
const keys = await wt.listSaves() // ['progress', 'settings'
|
|
46
|
+
// List all saves
|
|
47
|
+
const keys = await wt.listSaves() // ['progress', 'settings']
|
|
46
48
|
|
|
47
|
-
// Delete
|
|
48
|
-
await wt.deleteSave('
|
|
49
|
+
// Delete
|
|
50
|
+
await wt.deleteSave('progress')
|
|
49
51
|
```
|
|
50
52
|
|
|
51
|
-
## Multiplayer
|
|
53
|
+
## Multiplayer
|
|
52
54
|
|
|
53
|
-
|
|
55
|
+
Point at your game state. Join a room. State syncs automatically.
|
|
54
56
|
|
|
55
57
|
```typescript
|
|
56
|
-
//
|
|
57
|
-
const
|
|
58
|
-
console.log('Room code:', room.code)
|
|
59
|
-
|
|
60
|
-
// Join an existing room
|
|
61
|
-
const room = await wt.joinRoom('ABCD')
|
|
62
|
-
|
|
63
|
-
// Check room properties
|
|
64
|
-
room.isHost // true if you're the host
|
|
65
|
-
room.hostId // current host's player ID
|
|
66
|
-
room.playerId // your player ID
|
|
67
|
-
room.playerCount // number of players
|
|
68
|
-
room.players // all players' states
|
|
69
|
-
```
|
|
58
|
+
// Your game state
|
|
59
|
+
const state = { players: {} }
|
|
70
60
|
|
|
71
|
-
|
|
61
|
+
// Connect to Watchtower
|
|
62
|
+
const sync = wt.sync(state)
|
|
72
63
|
|
|
73
|
-
|
|
64
|
+
// Join a room
|
|
65
|
+
await sync.join('my-room')
|
|
74
66
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
health: 100
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
// State is merged, so you can update individual fields
|
|
85
|
-
room.player.set({ x: 150 }) // keeps y, sprite, health
|
|
67
|
+
// Add yourself
|
|
68
|
+
state.players[sync.myId] = {
|
|
69
|
+
x: 0,
|
|
70
|
+
y: 0,
|
|
71
|
+
name: 'Player1'
|
|
72
|
+
}
|
|
86
73
|
|
|
87
|
-
//
|
|
88
|
-
|
|
74
|
+
// Move (automatically syncs to others!)
|
|
75
|
+
state.players[sync.myId].x += 5
|
|
89
76
|
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
// Update other player's sprite
|
|
95
|
-
updateOtherPlayer(playerId, state.x, state.y, state.sprite)
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
})
|
|
77
|
+
// Draw everyone (others appear automatically!)
|
|
78
|
+
for (const [id, player] of Object.entries(state.players)) {
|
|
79
|
+
drawPlayer(player.x, player.y)
|
|
80
|
+
}
|
|
99
81
|
```
|
|
100
82
|
|
|
101
|
-
|
|
83
|
+
No events. No message handlers. Just read and write your state.
|
|
102
84
|
|
|
103
|
-
|
|
85
|
+
### Creating & Joining Rooms
|
|
104
86
|
|
|
105
87
|
```typescript
|
|
106
|
-
//
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
phase: 'lobby',
|
|
110
|
-
round: 0,
|
|
111
|
-
scores: {}
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
// Start the game
|
|
115
|
-
room.state.set({ phase: 'playing', round: 1 })
|
|
116
|
-
}
|
|
88
|
+
// Create a new room
|
|
89
|
+
const code = await sync.create({ maxPlayers: 4 })
|
|
90
|
+
console.log('Share this code:', code) // e.g., "A3B7X2"
|
|
117
91
|
|
|
118
|
-
//
|
|
119
|
-
|
|
120
|
-
if (state.phase === 'playing') {
|
|
121
|
-
startGame()
|
|
122
|
-
}
|
|
123
|
-
if (state.phase === 'gameover') {
|
|
124
|
-
showWinner(state.winner)
|
|
125
|
-
}
|
|
126
|
-
})
|
|
92
|
+
// Join existing room
|
|
93
|
+
await sync.join('A3B7X2')
|
|
127
94
|
|
|
128
|
-
//
|
|
129
|
-
|
|
130
|
-
```
|
|
95
|
+
// Leave room
|
|
96
|
+
await sync.leave()
|
|
131
97
|
|
|
132
|
-
|
|
98
|
+
// List public rooms
|
|
99
|
+
const rooms = await sync.listRooms()
|
|
100
|
+
```
|
|
133
101
|
|
|
134
|
-
|
|
102
|
+
### Options
|
|
135
103
|
|
|
136
104
|
```typescript
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
// Send to specific player
|
|
142
|
-
room.sendTo(playerId, { type: 'private_message', text: 'hey' })
|
|
143
|
-
|
|
144
|
-
// Receive messages
|
|
145
|
-
room.on('message', (from, data) => {
|
|
146
|
-
if (data.type === 'explosion') {
|
|
147
|
-
createExplosion(data.x, data.y)
|
|
148
|
-
}
|
|
149
|
-
if (data.type === 'chat') {
|
|
150
|
-
showChat(from, data.message)
|
|
151
|
-
}
|
|
105
|
+
const sync = wt.sync(state, {
|
|
106
|
+
tickRate: 20, // Updates per second (default: 20)
|
|
107
|
+
interpolate: true // Smooth remote movement (default: true)
|
|
152
108
|
})
|
|
153
109
|
```
|
|
154
110
|
|
|
155
|
-
|
|
111
|
+
### Properties
|
|
156
112
|
|
|
157
113
|
```typescript
|
|
158
|
-
//
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
})
|
|
114
|
+
sync.myId // Your player ID
|
|
115
|
+
sync.roomId // Current room, or null
|
|
116
|
+
sync.connected // WebSocket connected?
|
|
117
|
+
```
|
|
163
118
|
|
|
164
|
-
|
|
165
|
-
room.on('playerJoined', (playerId, playerCount) => {
|
|
166
|
-
console.log(`${playerId} joined (${playerCount} players)`)
|
|
167
|
-
spawnPlayer(playerId)
|
|
168
|
-
})
|
|
119
|
+
### Events (Optional)
|
|
169
120
|
|
|
170
|
-
|
|
171
|
-
room.on('playerLeft', (playerId, playerCount) => {
|
|
172
|
-
console.log(`${playerId} left (${playerCount} players)`)
|
|
173
|
-
removePlayer(playerId)
|
|
174
|
-
})
|
|
121
|
+
You don't need events—just read your state. But if you want notifications:
|
|
175
122
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
})
|
|
183
|
-
|
|
184
|
-
// Disconnected
|
|
185
|
-
room.on('disconnected', () => {
|
|
186
|
-
console.log('Lost connection')
|
|
187
|
-
})
|
|
188
|
-
|
|
189
|
-
// Error
|
|
190
|
-
room.on('error', (error) => {
|
|
191
|
-
console.error('Room error:', error)
|
|
192
|
-
})
|
|
123
|
+
```typescript
|
|
124
|
+
sync.on('join', (playerId) => console.log(playerId, 'joined'))
|
|
125
|
+
sync.on('leave', (playerId) => console.log(playerId, 'left'))
|
|
126
|
+
sync.on('connected', () => console.log('Connected'))
|
|
127
|
+
sync.on('disconnected', () => console.log('Disconnected'))
|
|
193
128
|
```
|
|
194
129
|
|
|
195
|
-
|
|
130
|
+
### Chat & Messages
|
|
131
|
+
|
|
132
|
+
Messages are just state:
|
|
196
133
|
|
|
197
134
|
```typescript
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
135
|
+
state.chat = [
|
|
136
|
+
...state.chat.slice(-50), // Keep last 50
|
|
137
|
+
{ from: sync.myId, text: 'Hello!', ts: Date.now() }
|
|
138
|
+
]
|
|
202
139
|
```
|
|
203
140
|
|
|
204
|
-
## Full Example
|
|
141
|
+
## Full Example
|
|
205
142
|
|
|
206
143
|
```typescript
|
|
207
|
-
import { Watchtower } from '@watchtower/
|
|
144
|
+
import { Watchtower } from '@watchtower-sdk/core'
|
|
208
145
|
|
|
209
146
|
const wt = new Watchtower({ gameId: 'my-game', apiKey: 'wt_...' })
|
|
147
|
+
const state = { players: {} }
|
|
148
|
+
const sync = wt.sync(state)
|
|
210
149
|
|
|
211
150
|
// Join or create room
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
151
|
+
const code = prompt('Room code? (blank to create)')
|
|
152
|
+
if (code) {
|
|
153
|
+
await sync.join(code)
|
|
154
|
+
} else {
|
|
155
|
+
const newCode = await sync.create()
|
|
156
|
+
alert('Share: ' + newCode)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Add yourself
|
|
160
|
+
state.players[sync.myId] = {
|
|
161
|
+
x: Math.random() * 800,
|
|
162
|
+
y: Math.random() * 600,
|
|
163
|
+
color: '#' + Math.floor(Math.random()*16777215).toString(16)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Game loop
|
|
167
|
+
function loop() {
|
|
168
|
+
// Move
|
|
169
|
+
if (keys.left) state.players[sync.myId].x -= 5
|
|
170
|
+
if (keys.right) state.players[sync.myId].x += 5
|
|
171
|
+
if (keys.up) state.players[sync.myId].y -= 5
|
|
172
|
+
if (keys.down) state.players[sync.myId].y += 5
|
|
218
173
|
|
|
219
|
-
//
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
animation: myPlayer.currentAnim
|
|
225
|
-
})
|
|
226
|
-
requestAnimationFrame(gameLoop)
|
|
174
|
+
// Draw everyone
|
|
175
|
+
ctx.clearRect(0, 0, 800, 600)
|
|
176
|
+
for (const [id, p] of Object.entries(state.players)) {
|
|
177
|
+
ctx.fillStyle = p.color
|
|
178
|
+
ctx.fillRect(p.x - 10, p.y - 10, 20, 20)
|
|
227
179
|
}
|
|
228
|
-
gameLoop()
|
|
229
|
-
|
|
230
|
-
// Render other players
|
|
231
|
-
const otherPlayers: Record<string, Sprite> = {}
|
|
232
|
-
|
|
233
|
-
room.on('players', (players) => {
|
|
234
|
-
for (const [id, state] of Object.entries(players)) {
|
|
235
|
-
if (id === room.playerId) continue
|
|
236
|
-
|
|
237
|
-
// Create sprite if new player
|
|
238
|
-
if (!otherPlayers[id]) {
|
|
239
|
-
otherPlayers[id] = createSprite()
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// Update position
|
|
243
|
-
otherPlayers[id].x = state.x as number
|
|
244
|
-
otherPlayers[id].y = state.y as number
|
|
245
|
-
otherPlayers[id].play(state.animation as string)
|
|
246
|
-
}
|
|
247
|
-
})
|
|
248
|
-
|
|
249
|
-
// Clean up when players leave
|
|
250
|
-
room.on('playerLeft', (id) => {
|
|
251
|
-
otherPlayers[id]?.destroy()
|
|
252
|
-
delete otherPlayers[id]
|
|
253
|
-
})
|
|
254
|
-
|
|
255
|
-
// Handle game events
|
|
256
|
-
room.on('message', (from, data: any) => {
|
|
257
|
-
if (data.type === 'shoot') {
|
|
258
|
-
createBullet(data.x, data.y, data.dir)
|
|
259
|
-
}
|
|
260
|
-
})
|
|
261
180
|
|
|
262
|
-
|
|
263
|
-
room.on('state', (state: any) => {
|
|
264
|
-
if (state.phase === 'playing') {
|
|
265
|
-
showRound(state.round)
|
|
266
|
-
}
|
|
267
|
-
if (state.phase === 'gameover') {
|
|
268
|
-
showWinner(state.winner)
|
|
269
|
-
}
|
|
270
|
-
})
|
|
271
|
-
|
|
272
|
-
// If we're host, start game when 2 players join
|
|
273
|
-
room.on('playerJoined', (_, count) => {
|
|
274
|
-
if (room.isHost && count >= 2) {
|
|
275
|
-
room.state.set({ phase: 'playing', round: 1 })
|
|
276
|
-
}
|
|
277
|
-
})
|
|
278
|
-
|
|
279
|
-
return room
|
|
181
|
+
requestAnimationFrame(loop)
|
|
280
182
|
}
|
|
183
|
+
loop()
|
|
281
184
|
```
|
|
282
185
|
|
|
283
186
|
## API Reference
|
|
@@ -285,51 +188,40 @@ async function joinGame(code?: string) {
|
|
|
285
188
|
### Watchtower
|
|
286
189
|
|
|
287
190
|
```typescript
|
|
288
|
-
const wt = new Watchtower(
|
|
191
|
+
const wt = new Watchtower({
|
|
192
|
+
gameId: string, // From dashboard
|
|
193
|
+
apiKey?: string, // From dashboard
|
|
194
|
+
playerId?: string // Auto-generated if not provided
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
wt.playerId // Current player ID
|
|
198
|
+
wt.gameId // Game ID
|
|
199
|
+
|
|
200
|
+
// Saves
|
|
201
|
+
await wt.save(key: string, data: any): Promise<void>
|
|
202
|
+
await wt.load<T>(key: string): Promise<T | null>
|
|
203
|
+
await wt.listSaves(): Promise<string[]>
|
|
204
|
+
await wt.deleteSave(key: string): Promise<void>
|
|
205
|
+
|
|
206
|
+
// Multiplayer
|
|
207
|
+
wt.sync(state: object, options?: SyncOptions): Sync
|
|
289
208
|
```
|
|
290
209
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
| `playerCount` | `number` | Number of players |
|
|
307
|
-
| `players` | `PlayersState` | All players' states |
|
|
308
|
-
| `connected` | `boolean` | Connection status |
|
|
309
|
-
|
|
310
|
-
| Method | Description |
|
|
311
|
-
|--------|-------------|
|
|
312
|
-
| `player.set(state)` | Set your player state (auto-synced) |
|
|
313
|
-
| `player.get()` | Get your current player state |
|
|
314
|
-
| `player.sync()` | Force immediate sync |
|
|
315
|
-
| `state.set(state)` | Set game state (host only) |
|
|
316
|
-
| `state.get()` | Get current game state |
|
|
317
|
-
| `broadcast(data)` | Send to all players |
|
|
318
|
-
| `sendTo(id, data)` | Send to specific player |
|
|
319
|
-
| `transferHost(id)` | Transfer host (host only) |
|
|
320
|
-
| `disconnect()` | Leave the room |
|
|
321
|
-
|
|
322
|
-
| Event | Callback | Description |
|
|
323
|
-
|-------|----------|-------------|
|
|
324
|
-
| `connected` | `({playerId, room}) => void` | Connected to room |
|
|
325
|
-
| `players` | `(players) => void` | Player states updated |
|
|
326
|
-
| `state` | `(state) => void` | Game state updated |
|
|
327
|
-
| `playerJoined` | `(id, count) => void` | Player joined |
|
|
328
|
-
| `playerLeft` | `(id, count) => void` | Player left |
|
|
329
|
-
| `hostChanged` | `(newHostId) => void` | Host changed |
|
|
330
|
-
| `message` | `(from, data) => void` | Received broadcast |
|
|
331
|
-
| `disconnected` | `() => void` | Lost connection |
|
|
332
|
-
| `error` | `(error) => void` | Error occurred |
|
|
210
|
+
### Sync
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
sync.myId: string
|
|
214
|
+
sync.roomId: string | null
|
|
215
|
+
sync.connected: boolean
|
|
216
|
+
|
|
217
|
+
await sync.join(roomId: string, options?: JoinOptions): Promise<void>
|
|
218
|
+
await sync.leave(): Promise<void>
|
|
219
|
+
await sync.create(options?: CreateOptions): Promise<string>
|
|
220
|
+
await sync.listRooms(): Promise<RoomListing[]>
|
|
221
|
+
|
|
222
|
+
sync.on(event: string, callback: Function): void
|
|
223
|
+
sync.off(event: string, callback: Function): void
|
|
224
|
+
```
|
|
333
225
|
|
|
334
226
|
## License
|
|
335
227
|
|
package/dist/index.d.mts
CHANGED
|
@@ -206,6 +206,123 @@ declare class Room {
|
|
|
206
206
|
/** Check if connected */
|
|
207
207
|
get connected(): boolean;
|
|
208
208
|
}
|
|
209
|
+
interface SyncOptions {
|
|
210
|
+
/** Updates per second (default: 20) */
|
|
211
|
+
tickRate?: number;
|
|
212
|
+
/** Enable interpolation for remote entities (default: true) */
|
|
213
|
+
interpolate?: boolean;
|
|
214
|
+
}
|
|
215
|
+
interface JoinOptions {
|
|
216
|
+
/** Create room if it doesn't exist */
|
|
217
|
+
create?: boolean;
|
|
218
|
+
/** Max players (only on create) */
|
|
219
|
+
maxPlayers?: number;
|
|
220
|
+
/** Make room public/discoverable (only on create) */
|
|
221
|
+
public?: boolean;
|
|
222
|
+
/** Room metadata (only on create) */
|
|
223
|
+
metadata?: Record<string, unknown>;
|
|
224
|
+
}
|
|
225
|
+
interface RoomListing {
|
|
226
|
+
id: string;
|
|
227
|
+
players: number;
|
|
228
|
+
maxPlayers?: number;
|
|
229
|
+
metadata?: Record<string, unknown>;
|
|
230
|
+
createdAt: number;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Sync - Automatic state synchronization
|
|
234
|
+
*
|
|
235
|
+
* Point this at your game state object and it becomes multiplayer.
|
|
236
|
+
* No events, no callbacks - just read and write your state.
|
|
237
|
+
*
|
|
238
|
+
* @example
|
|
239
|
+
* ```ts
|
|
240
|
+
* const state = { players: {} }
|
|
241
|
+
* const sync = wt.sync(state)
|
|
242
|
+
*
|
|
243
|
+
* await sync.join('my-room')
|
|
244
|
+
*
|
|
245
|
+
* // Add yourself
|
|
246
|
+
* state.players[sync.myId] = { x: 0, y: 0, name: 'Player1' }
|
|
247
|
+
*
|
|
248
|
+
* // Move (automatically syncs to others)
|
|
249
|
+
* state.players[sync.myId].x = 100
|
|
250
|
+
*
|
|
251
|
+
* // Others appear automatically in state.players!
|
|
252
|
+
* for (const [id, player] of Object.entries(state.players)) {
|
|
253
|
+
* draw(player.x, player.y)
|
|
254
|
+
* }
|
|
255
|
+
* ```
|
|
256
|
+
*/
|
|
257
|
+
declare class Sync<T extends Record<string, unknown>> {
|
|
258
|
+
/** The synchronized state object */
|
|
259
|
+
readonly state: T;
|
|
260
|
+
/** Your player ID */
|
|
261
|
+
readonly myId: string;
|
|
262
|
+
/** Current room ID (null if not in a room) */
|
|
263
|
+
get roomId(): string | null;
|
|
264
|
+
/** Whether currently connected to a room */
|
|
265
|
+
get connected(): boolean;
|
|
266
|
+
private config;
|
|
267
|
+
private options;
|
|
268
|
+
private _roomId;
|
|
269
|
+
private ws;
|
|
270
|
+
private syncInterval;
|
|
271
|
+
private lastSentState;
|
|
272
|
+
private interpolationTargets;
|
|
273
|
+
private listeners;
|
|
274
|
+
constructor(state: T, config: Required<WatchtowerConfig>, options?: SyncOptions);
|
|
275
|
+
/**
|
|
276
|
+
* Join a room - your state will sync with everyone in this room
|
|
277
|
+
*
|
|
278
|
+
* @param roomId - Room identifier (any string)
|
|
279
|
+
* @param options - Join options
|
|
280
|
+
*/
|
|
281
|
+
join(roomId: string, options?: JoinOptions): Promise<void>;
|
|
282
|
+
/**
|
|
283
|
+
* Leave the current room
|
|
284
|
+
*/
|
|
285
|
+
leave(): Promise<void>;
|
|
286
|
+
/**
|
|
287
|
+
* Send a one-off message to all players in the room
|
|
288
|
+
*
|
|
289
|
+
* @param data - Any JSON-serializable data
|
|
290
|
+
*/
|
|
291
|
+
broadcast(data: unknown): void;
|
|
292
|
+
/**
|
|
293
|
+
* Create a new room and join it
|
|
294
|
+
*
|
|
295
|
+
* @param options - Room creation options
|
|
296
|
+
* @returns The room code/ID
|
|
297
|
+
*/
|
|
298
|
+
create(options?: Omit<JoinOptions, 'create'>): Promise<string>;
|
|
299
|
+
/**
|
|
300
|
+
* List public rooms
|
|
301
|
+
*/
|
|
302
|
+
listRooms(): Promise<RoomListing[]>;
|
|
303
|
+
/**
|
|
304
|
+
* Subscribe to sync events
|
|
305
|
+
*/
|
|
306
|
+
on(event: 'join' | 'leave' | 'error' | 'connected' | 'disconnected' | 'message', callback: Function): void;
|
|
307
|
+
/**
|
|
308
|
+
* Unsubscribe from sync events
|
|
309
|
+
*/
|
|
310
|
+
off(event: string, callback: Function): void;
|
|
311
|
+
private emit;
|
|
312
|
+
private connectWebSocket;
|
|
313
|
+
private handleMessage;
|
|
314
|
+
private applyFullState;
|
|
315
|
+
private applyPlayerState;
|
|
316
|
+
private removePlayer;
|
|
317
|
+
private clearRemotePlayers;
|
|
318
|
+
private findPlayersKey;
|
|
319
|
+
private startSyncLoop;
|
|
320
|
+
private stopSyncLoop;
|
|
321
|
+
private syncMyState;
|
|
322
|
+
private updateInterpolation;
|
|
323
|
+
private generateRoomCode;
|
|
324
|
+
private getHeaders;
|
|
325
|
+
}
|
|
209
326
|
declare class Watchtower {
|
|
210
327
|
/** @internal - Config is non-enumerable to prevent accidental API key exposure */
|
|
211
328
|
private readonly config;
|
|
@@ -291,6 +408,36 @@ declare class Watchtower {
|
|
|
291
408
|
* Note: This returns a promise, use `await wt.stats` or `wt.getStats()`
|
|
292
409
|
*/
|
|
293
410
|
get stats(): Promise<GameStats>;
|
|
411
|
+
/**
|
|
412
|
+
* Create a synchronized state object
|
|
413
|
+
*
|
|
414
|
+
* Point this at your game state and it becomes multiplayer.
|
|
415
|
+
* No events, no callbacks - just read and write your state.
|
|
416
|
+
*
|
|
417
|
+
* @param state - Your game state object (e.g., { players: {} })
|
|
418
|
+
* @param options - Sync options (tickRate, interpolation)
|
|
419
|
+
* @returns A Sync instance
|
|
420
|
+
*
|
|
421
|
+
* @example
|
|
422
|
+
* ```ts
|
|
423
|
+
* const state = { players: {} }
|
|
424
|
+
* const sync = wt.sync(state)
|
|
425
|
+
*
|
|
426
|
+
* await sync.join('my-room')
|
|
427
|
+
*
|
|
428
|
+
* // Add yourself
|
|
429
|
+
* state.players[sync.myId] = { x: 0, y: 0 }
|
|
430
|
+
*
|
|
431
|
+
* // Move (automatically syncs to others)
|
|
432
|
+
* state.players[sync.myId].x = 100
|
|
433
|
+
*
|
|
434
|
+
* // Others appear automatically in state.players!
|
|
435
|
+
* for (const [id, player] of Object.entries(state.players)) {
|
|
436
|
+
* draw(player.x, player.y)
|
|
437
|
+
* }
|
|
438
|
+
* ```
|
|
439
|
+
*/
|
|
440
|
+
sync<T extends Record<string, unknown>>(state: T, options?: SyncOptions): Sync<T>;
|
|
294
441
|
}
|
|
295
442
|
|
|
296
|
-
export { type GameState, type GameStats, type PlayerInfo, type PlayerState, type PlayerStats, type PlayersState, Room, type RoomEventMap, type RoomInfo, type SaveData, Watchtower, type WatchtowerConfig, Watchtower as default };
|
|
443
|
+
export { type GameState, type GameStats, type JoinOptions, type PlayerInfo, type PlayerState, type PlayerStats, type PlayersState, Room, type RoomEventMap, type RoomInfo, type RoomListing, type SaveData, Sync, type SyncOptions, Watchtower, type WatchtowerConfig, Watchtower as default };
|