diff --git a/TableturfBattleClient/src/Board.ts b/TableturfBattleClient/src/Board.ts index 803ef8d..7426f36 100644 --- a/TableturfBattleClient/src/Board.ts +++ b/TableturfBattleClient/src/Board.ts @@ -6,17 +6,19 @@ class Board { playerIndex: number | null = 0; cardPlaying: Card | null = null; cardRotation = 0; - specialAttack = false; + _specialAttack = false; autoHighlight = true; flip = false; highlightX = NaN; highlightY = NaN; - private highlightedCells: [x: number, y: number][] = [ ]; + private highlightedCells: Point[] = [ ]; + private animatedCells: Point[] = [ ]; + private specialAnimatedCells: [point: Point, elements: HTMLElement[]][] = [ ]; private touch: [index: number, x: number, y: number, highlightX: number, highlightY: number] | null = null; - startSpaces: { x: number, y: number }[] = [ ]; + startSpaces: Point[] = [ ]; onsubmit: ((x: number, y: number) => void) | null = null; oncancel: (() => void) | null = null; @@ -100,6 +102,15 @@ class Board { }); } + get specialAttack() { return this._specialAttack; } + set specialAttack(value: boolean) { + this._specialAttack = value; + if (value) + this.table.classList.add('specialAttackVisual'); + else + this.table.classList.remove('specialAttackVisual'); + } + moveHighlight(move: (x: number, y: number, r: number) => [number, number, number], clamp: boolean) { if (this.playerIndex == null) return; if (this.highlightedCells.length == 0 || isNaN(this.highlightX) || isNaN(this.highlightY) || isNaN(this.cardRotation)) { @@ -185,7 +196,7 @@ class Board { cell.classList.add('hoverillegal'); if (space == Space.SpecialInactive1) cell.classList.add('hoverspecial'); - this.highlightedCells.push([x2, y2]); + this.highlightedCells.push({ x: x2, y: y2 }); } } } @@ -201,18 +212,82 @@ class Board { } private internalClearHighlight() { - for (const [x, y] of this.highlightedCells) { - this.cells[x][y].setAttribute('class', Space[this.grid[x][y]] ); + for (const s of this.highlightedCells) { + this.cells[s.x][s.y].setAttribute('class', Space[this.grid[s.x][s.y]] ); } this.highlightedCells.splice(0); } + enableInkAnimations() { + this.table.classList.add('enableInkAnimation'); + } + + showInkAnimation(x: number, y: number) { + this.animatedCells.push({ x, y }); + this.cells[x][y].classList.add('inkAnimation'); + } + + clearInkAnimations() { + for (const s of this.animatedCells) + this.cells[s.x][s.y].classList.remove('inkAnimation'); + this.animatedCells.splice(0); + this.table.classList.remove('enableInkAnimation'); + } + + showSubmitAnimation() { + for (const s of this.highlightedCells) + this.cells[s.x][s.y].classList.add('submitted'); + } + + private showSpecialAnimation(x: number, y: number) { + if ((this.grid[x][y] & Space.SpecialActive1) != Space.SpecialActive1 || this.specialAnimatedCells.find(el => el[0].x == x && el[0].y == y)) + return; + + const parent = this.cells[x][y]; + const els = [ ]; + els.push(this.createSpecialAnimationElement(parent)); + els.push(this.createSpecialAnimationElement(parent)); + els.push(this.createSpecialAnimationElement(parent)); + els.push(this.createSpecialAnimationElement(parent)); + els.push(this.createSpecialAnimationElement(parent)); + els.push(this.createSpecialAnimationElement(parent)); + els.push(this.createSpecialAnimationElement(parent)); + els.push(this.createSpecialAnimationElement(parent)); + els.push(this.createSpecialAnimationElement(parent)); + els.push(this.createSpecialAnimationElement(parent)); + this.specialAnimatedCells.push([{ x, y }, els]); + } + + private createSpecialAnimationElement(parent: Element) { + const el = document.createElement('div'); + parent.appendChild(el); + return el; + } + + private clearSpecialAnimation(x: number, y: number) { + const i = this.specialAnimatedCells.findIndex(el => el[0].x == x && el[0].y == y); + if (i < 0) return; + + for (const el of this.specialAnimatedCells[i][1]) + el.parentElement!.removeChild(el); + this.specialAnimatedCells.splice(i, 1); + } + + clearAllSpecialAnimations() { + for (const entry of this.specialAnimatedCells) { + for (const el of entry[1]) + el.parentElement!.removeChild(el); + } + this.specialAnimatedCells.splice(0); + } + resize(grid?: Space[][]) { if (grid) this.grid = grid; clearChildren(this.table); this.cells.splice(0); this.highlightedCells.splice(0); + this.specialAnimatedCells.splice(0); const boardWidth = this.grid.length; const boardHeight = this.grid[0].length; @@ -229,7 +304,8 @@ class Board { col.push(td); td.addEventListener('click', () => { if (this.autoHighlight && this.cardPlaying != null && this.onsubmit && !isNaN(this.highlightX) && !isNaN(this.highlightY)) { - this.onsubmit(this.highlightX, this.highlightY); + if (this.onsubmit) + this.onsubmit(this.highlightX, this.highlightY); } }); td.addEventListener('contextmenu', e => e.preventDefault()); @@ -291,15 +367,21 @@ class Board { refresh() { this.clearHighlight(); + this.clearInkAnimations(); for (let x = 0; x < this.grid.length; x++) { for (let y = 0; y < this.grid[x].length; y++) { - this.cells[x][y].setAttribute('class', Space[this.grid[x][y]] ); + this.setDisplayedSpace(x, y, this.grid[x][y]); } } } setDisplayedSpace(x: number, y: number, newState: Space) { this.cells[x][y].setAttribute('class', Space[newState]); + if (this.cells[x][y].childNodes.length > 0) { + if ((newState & Space.SpecialActive1) != Space.SpecialActive1) + this.clearSpecialAnimation(x, y); + } else + this.showSpecialAnimation(x, y); } getScores() { diff --git a/TableturfBattleClient/src/Pages/GamePage.ts b/TableturfBattleClient/src/Pages/GamePage.ts index 020704c..5ef03e4 100644 --- a/TableturfBattleClient/src/Pages/GamePage.ts +++ b/TableturfBattleClient/src/Pages/GamePage.ts @@ -229,7 +229,7 @@ replayNextButton.addEventListener('click', _ => { replayAnimationAbortController = new AbortController(); (async () => { - await playInkAnimations({ game: { state: GameState.Ongoing, board: null, turnNumber: currentGame.turnNumber, players: currentGame.players }, placements: result.placements, specialSpacesActivated: result.specialSpacesActivated }, anySpecialAttacks, replayAnimationAbortController.signal); + await playInkAnimations({ game: { state: GameState.Ongoing, board: null, turnNumber: currentGame.turnNumber, players: currentGame.players }, moves, placements: result.placements, specialSpacesActivated: result.specialSpacesActivated }, anySpecialAttacks, replayAnimationAbortController.signal); turnNumberLabel.setTurnNumber(currentGame.turnNumber); clearPlayContainers(); if (currentGame.turnNumber > 12) { @@ -527,6 +527,7 @@ function lockGamePage() { async function playInkAnimations(data: { game: { state: GameState, board: Space[][] | null, turnNumber: number, players: Player[] }, + moves: Move[], placements: Placement[], specialSpacesActivated: Point[] }, anySpecialAttacks: boolean, abortSignal?: AbortSignal) { @@ -535,9 +536,16 @@ async function playInkAnimations(data: { board.clearHighlight(); board.cardPlaying = null; board.autoHighlight = false; + board.specialAttack = false; canPlay = false; timeLabel.faded = true; await delay(anySpecialAttacks ? 3000 : 1000, abortSignal); + for (let i = 0; i < data.game.players.length; i++) { + if ((data.moves[i] as PlayMove).isSpecialAttack) + playerBars[i].specialPoints -= data.moves[i].card.specialCost; + playerBars[i].highlightSpecialPoints = 0; + } + board.enableInkAnimations(); for (const placement of placements) { // Skip the delay when cards don't overlap. if (placement.spacesAffected.find(p => inkPlaced.has(p.space.y * 37 + p.space.x))) { @@ -548,9 +556,12 @@ async function playInkAnimations(data: { for (const p of placement.spacesAffected) { inkPlaced.add(p.space.y * 37 + p.space.x); board.setDisplayedSpace(p.space.x, p.space.y, p.newState); + board.showInkAnimation(p.space.x, p.space.y); } } - await delay(1000, abortSignal); + await delay(500, abortSignal); + board.clearInkAnimations(); + await delay(500, abortSignal); // Show special spaces. if (data.game.board) @@ -679,6 +690,8 @@ function updateHandAndDeck(playerData: PlayerData) { } } else { board.cardPlaying = card; + if (specialButton.checked) + playerBars[currentGame!.me!.playerIndex].highlightSpecialPoints = card.specialCost; if (isNaN(board.highlightX) || isNaN(board.highlightY)) { board.highlightX = board.startSpaces[board.playerIndex!].x - (board.flip ? 4 : 3); board.highlightY = board.startSpaces[board.playerIndex!].y - (board.flip ? 4 : 3); @@ -784,6 +797,7 @@ function passButton_click(e: Event) { clearPlayHint(); specialButton.checked = false; board.cardPlaying = null; + playerBars[currentGame!.me!.playerIndex].highlightSpecialPoints = 0; board.specialAttack = false; board.clearHighlight(); handButtons.deselect(); @@ -809,6 +823,7 @@ function specialButton_click(e: Event) { specialButton.checked = !specialButton.checked; board.specialAttack = specialButton.checked; handButtons.deselect(); + playerBars[currentGame!.me!.playerIndex].highlightSpecialPoints = 0; if (specialButton.checked) { passHint.hidden = true; showPlayHint('Unleash it next to