feat: add multi-map system with 4 distinct biomes, layouts, and multiplayer sync
- Introduce 4 playable map environments: * Meadow (Grüne Lichtung): Classic balanced S-curve path with lush grasslands, flowers, and standard obstacles * Desert (Sonnendünen): Winding dune trail with sandstone rocks, oasis palms, and scrub * Frostland (Frostgipfel): Icy serpentine path through snowfields with frost-covered pines and crystal rocks * Volcano (Lavabruch): Tight aggressive layout through dark obsidian/basalt with glowing magma streams and floating ember sparks - Add comprehensive MapDef & MapTheme configuration for distinct waypoint routes, flying trajectories, biome color palettes, portal/keep styles, and obstacle densities - Extend Canvas 2D renderer to dynamically adapt backgrounds, path texturing, portal effects, and procedurally drawn biome foliage/decorations per map - Upgrade GameEngine with dynamic map loading, obstacle clearing logic, and per-map highscore persistence - Implement multiplayer map synchronization in WebSocket server (set-map event), room state, and lobby UI allowing host map selection - Update StartScreen and LobbyScreen components with interactive map selection cards and per-map highscore tracking - Add automated test suites for map path integrity, determinism, playability, and room map protocol synchronization
This commit is contained in:
parent
a7fb3efa61
commit
ce2484bb8d
13 changed files with 911 additions and 132 deletions
|
|
@ -235,12 +235,18 @@ function makeCode() {
|
|||
return code
|
||||
}
|
||||
|
||||
const VALID_MAPS = ['meadow', 'desert', 'frostland', 'volcano']
|
||||
|
||||
function sanitizeName(raw) {
|
||||
if (typeof raw !== 'string') return 'Spieler'
|
||||
const clean = raw.trim().replace(/[^\p{L}\p{N}_\- ]/gu, '').slice(0, 12)
|
||||
return clean || 'Spieler'
|
||||
}
|
||||
|
||||
function sanitizeMapId(raw) {
|
||||
return VALID_MAPS.includes(raw) ? raw : 'meadow'
|
||||
}
|
||||
|
||||
function send(ws, obj) {
|
||||
if (ws && ws.readyState === 1) {
|
||||
try {
|
||||
|
|
@ -384,10 +390,21 @@ wss.on('connection', (ws) => {
|
|||
return send(ws, { t: 'error', msg: 'Zu viele aktive Räume. Bitte später erneut versuchen.' })
|
||||
}
|
||||
const code = makeCode()
|
||||
const room = { code, mode: m.mode, created: Date.now(), seq: 0, players: [{ ws, id: 0, name: sanitizeName(m.name) }] }
|
||||
const mapId = sanitizeMapId(m.mapId)
|
||||
const room = { code, mode: m.mode, mapId, created: Date.now(), seq: 0, players: [{ ws, id: 0, name: sanitizeName(m.name) }] }
|
||||
rooms.set(code, room)
|
||||
ws._room = code
|
||||
send(ws, { t: 'room', code, mode: room.mode, players: playerInfo(room), you: 0 })
|
||||
send(ws, { t: 'room', code, mode: room.mode, mapId: room.mapId, players: playerInfo(room), you: 0 })
|
||||
break
|
||||
}
|
||||
case 'set-map': {
|
||||
const room = roomOf(ws)
|
||||
if (!room || room.players.length === 0) return
|
||||
if (room.players[0].ws !== ws) return // only host may change map
|
||||
room.mapId = sanitizeMapId(m.mapId)
|
||||
for (const p of room.players) {
|
||||
send(p.ws, { t: 'room', code: room.code, mode: room.mode, mapId: room.mapId, players: playerInfo(room), you: p.id })
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'join': {
|
||||
|
|
@ -399,7 +416,7 @@ wss.on('connection', (ws) => {
|
|||
room.players.push({ ws, id: 1, name: sanitizeName(m.name) })
|
||||
ws._room = code
|
||||
for (const p of room.players) {
|
||||
send(p.ws, { t: 'room', code, mode: room.mode, players: playerInfo(room), you: p.id })
|
||||
send(p.ws, { t: 'room', code: room.code, mode: room.mode, mapId: room.mapId, players: playerInfo(room), you: p.id })
|
||||
}
|
||||
break
|
||||
}
|
||||
|
|
@ -409,7 +426,7 @@ wss.on('connection', (ws) => {
|
|||
if (room.players[0].ws !== ws) return // only the host may start
|
||||
const seed = (Math.random() * 1e9) | 0
|
||||
for (const p of room.players) {
|
||||
send(p.ws, { t: 'start', seed, mode: room.mode, players: playerInfo(room), you: p.id })
|
||||
send(p.ws, { t: 'start', seed, mode: room.mode, mapId: room.mapId, players: playerInfo(room), you: p.id })
|
||||
}
|
||||
break
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue