feat(duel): allow swapping main view to opponent's board

- Add a toggle on the picture-in-picture window to swap the big board
  with the mini view of the opponent's field
- Color-coded frame and badge identify whose board is currently shown
  (blue = player 1, orange = player 2)
- Block building and rushing while spectating the opponent's board;
  the PiP then shows your own field instead
- Clarify duel-mode descriptions in lobby and start screen, add hints
  in the multiplayer bar
- Replace previous announcement banners so only one shows at a time
This commit is contained in:
Tronax 2026-08-16 13:35:46 +02:00
parent 4ec0e483aa
commit ef75203db7
Signed by: Tronax
SSH key fingerprint: SHA256:2pKKXDZucWvaF/GzXNz0FY53EAO1YDLN80bqS+TTz/o
12 changed files with 130 additions and 15 deletions

View file

@ -172,7 +172,11 @@ class MpGameController {
remote.mpMode = 'duel'
remote.localPlayerId = 1 - info.you
remote.ghost = true
// CRITICAL: index boards by PLAYER ID so engines[from] is always the
// board owned by the acting player on every client
this.engines = [engine, remote]
this.engines[info.you] = engine
this.engines[1 - info.you] = remote
this.remoteEngine = remote
}
@ -190,13 +194,33 @@ class MpGameController {
this.net.remoteTick = -1
if (this.heartbeat) clearInterval(this.heartbeat)
this.heartbeat = setInterval(() => this.net.sendProgress(this.myTick), 100)
store.mp.viewOpponent = false
engine.announce(
info.mode === 'coop' ? 'Coop-Modus!' : '1v1-Duell!',
info.mode === 'coop' ? 'Gemeinsames Gold Teamwork!' : 'Verteidige deine Basis!',
info.mode === 'coop'
? 'Gemeinsames Gold Teamwork!'
: 'Dies ist DEIN Feld Gegner live im Mini-Fenster',
)
}
/** duel: is the opponent's board currently shown on the main canvas? */
get viewSwap(): boolean {
return store.mp.viewOpponent && this.mode === 'duel' && this.remoteEngine !== null
}
/** engine whose board is rendered on the big canvas */
mainView(): GameEngine {
return this.viewSwap && this.remoteEngine ? this.remoteEngine : engine
}
toggleView(): void {
if (this.mode === 'duel' && this.remoteEngine) {
store.mp.viewOpponent = !store.mp.viewOpponent
engine.cancelMode()
}
}
private disposeGame(): void {
if (this.heartbeat) clearInterval(this.heartbeat)
this.heartbeat = null