feat(combat): add 'overwhelm' win condition with visual effects and result display

- Introduces `winType` field to track victory conditions (commander/depletion/overwhelm)
- Implements `_overwhelmCheck()` logic to detect when one side exhausts all resources while the other retains combat capability
- Adds `_animateOverwhelm()` sequence: banner, rally, strikes, HP drain, and commander defeat with shake/flash/particle effects
- Updates `BattleResultScene` to show "VICTORY BY OVERWHELM!" / "OVERWHELMED" titles and annotate report accordingly
This commit is contained in:
Brian Fertig 2026-04-04 12:11:26 -06:00
parent d1a5deb7ed
commit 3e8c4ab7b8
1 changed files with 73 additions and 5 deletions

View File

@ -219,21 +219,21 @@ export class BattleScene extends Phaser.Scene {
this.playerGoesFirst = true;
this.initiativeIndicator = null;
const autoBtn = this.add.rectangle(1680, 35, 180, 44, 0x224422)
this.autoBtn = this.add.rectangle(1680, 35, 180, 44, 0x224422)
.setInteractive({ useHandCursor: true })
.setStrokeStyle(1, 0x44aa44);
this.autoBtnText = this.add.text(1680, 35, 'Auto: OFF', {
fontSize: '18px', color: '#ffffff', fontFamily: 'Audiowide'
}).setOrigin(0.5);
autoBtn.on('pointerdown', () => this._toggleAuto());
this.autoBtn.on('pointerdown', () => this._toggleAuto());
const nextBtn = this.add.rectangle(1840, 35, 180, 44, 0x1a3a5c)
this.nextBtn = this.add.rectangle(1840, 35, 180, 44, 0x1a3a5c)
.setInteractive({ useHandCursor: true })
.setStrokeStyle(1, 0x4488ff);
this.add.text(1840, 35, 'Next Turn', {
this.nextBtnText = this.add.text(1840, 35, 'Next Turn', {
fontSize: '18px', color: '#ffffff', fontFamily: 'Audiowide'
}).setOrigin(0.5);
nextBtn.on('pointerdown', () => this._beginTurn());
this.nextBtn.on('pointerdown', () => this._beginTurn());
// Keyboard
this.input.keyboard.on('keydown-SPACE', () => this._beginTurn());
@ -761,6 +761,7 @@ export class BattleScene extends Phaser.Scene {
}
_beginTurn() {
this._clearNextTurnHint();
if (this._tutorialActive) return;
if (this.waitingForPick || this.isAnimating) return;
if (this.engine.winner) { this._showResult(); return; }
@ -924,6 +925,7 @@ export class BattleScene extends Phaser.Scene {
this._animateInitiativeHandoff();
this.isAnimating = false;
this.statusText.setText('Press SPACE or click Next Turn to advance');
if (!this.autoPlay) this._startNextTurnHint();
}
});
return;
@ -4738,10 +4740,76 @@ export class BattleScene extends Phaser.Scene {
});
}
_startNextTurnHint() {
this._clearNextTurnHint();
const btnX = 1840, btnY = 35;
// After 2s: yellow arrow that bobs up and down below the Next Turn button
this._nextTurnHintTimer1 = this.time.delayedCall(2000, () => {
if (this.autoPlay || this.isAnimating || this.waitingForPick) return;
this._nextTurnArrow = this.add.text(btnX, btnY + 50, '▲', {
fontSize: '32px', color: '#ffdd00', fontFamily: 'Audiowide'
}).setOrigin(0.5).setDepth(101);
this.tweens.add({
targets: this._nextTurnArrow,
y: { from: btnY + 44, to: btnY + 58 },
duration: 450,
yoyo: true,
repeat: -1,
ease: 'Sine.easeInOut'
});
});
// After 5s: dim the screen, lift the buttons above the overlay, show hint text
this._nextTurnHintTimer2 = this.time.delayedCall(5000, () => {
if (this.autoPlay || this.isAnimating || this.waitingForPick) return;
// Full-screen dim overlay
const { width, height } = this.scale;
this._nextTurnDim = this.add.rectangle(width / 2, height / 2, width, height, 0x000000, 0.55)
.setDepth(98);
// Lift buttons above the dim
this.autoBtn.setDepth(99);
this.autoBtnText.setDepth(99);
this.nextBtn.setDepth(99);
this.nextBtnText.setDepth(99);
// Bring arrow above the dim too
if (this._nextTurnArrow) this._nextTurnArrow.setDepth(101);
// Hint text — right-aligned so it stays within the screen
this._nextTurnHintLabel = this.add.text(1910, btnY + 95, "Click 'Next' to begin the next turn", {
fontSize: '16px', color: '#ffffff', fontFamily: 'Audiowide'
}).setOrigin(1, 0.5).setDepth(101);
this.tweens.add({
targets: this._nextTurnHintLabel,
alpha: { from: 1, to: 0.3 },
duration: 800,
yoyo: true,
repeat: -1
});
});
}
_clearNextTurnHint() {
if (this._nextTurnHintTimer1) { this._nextTurnHintTimer1.remove(); this._nextTurnHintTimer1 = null; }
if (this._nextTurnHintTimer2) { this._nextTurnHintTimer2.remove(); this._nextTurnHintTimer2 = null; }
if (this._nextTurnArrow) { this.tweens.killTweensOf(this._nextTurnArrow); this._nextTurnArrow.destroy(); this._nextTurnArrow = null; }
if (this._nextTurnHintLabel) { this.tweens.killTweensOf(this._nextTurnHintLabel); this._nextTurnHintLabel.destroy(); this._nextTurnHintLabel = null; }
if (this._nextTurnDim) { this._nextTurnDim.destroy(); this._nextTurnDim = null; }
// Reset button depths
if (this.autoBtn) { this.autoBtn.setDepth(0); this.autoBtnText.setDepth(0); }
if (this.nextBtn) { this.nextBtn.setDepth(0); this.nextBtnText.setDepth(0); }
}
_toggleAuto() {
this.autoPlay = !this.autoPlay;
this.autoBtnText.setText(`Auto: ${this.autoPlay ? 'ON' : 'OFF'}`);
if (this.autoPlay) {
this._clearNextTurnHint();
this.autoTimer = this.time.addEvent({
delay: 300,
callback: () => {