From 07db180b5dc1eaca5376c38aed2c0e72a7fc4660 Mon Sep 17 00:00:00 2001 From: koneko <67551503+koneko@users.noreply.github.com> Date: Sun, 9 Feb 2025 22:52:10 +0100 Subject: [PATCH] basic pausing --- docs/todos.md | 2 +- public/assets/creeps/maker/{11.png => 0.png} | Bin public/assets/json/Creeps.json | 51 +++++++++++++++++++ src/classes/Definitions.ts | 3 ++ src/classes/game/Grid.ts | 1 + src/classes/game/KeyboardManager.ts | 6 +-- src/classes/game/Tower.ts | 19 ++++--- src/classes/game/TowerBehaviours.ts | 3 +- src/classes/game/TowerManager.ts | 13 ++++- src/classes/gui/MessageBox.ts | 1 - src/classes/gui/ModalDialog.ts | 7 ++- src/classes/gui/TowerPanel.ts | 16 +++++- src/scenes/Game.ts | 46 +++++++++++++++-- 13 files changed, 142 insertions(+), 26 deletions(-) rename public/assets/creeps/maker/{11.png => 0.png} (100%) diff --git a/docs/todos.md b/docs/todos.md index d1af3fb..808b08a 100644 --- a/docs/todos.md +++ b/docs/todos.md @@ -17,7 +17,7 @@ List of things to implement following the "release" of the minimum viable produc - [x] Tower info on click - [x] Animate projectiles - [x] Better mouseover tracking when placing tower and showing radius -- [ ] Sell tower button +- [x] Sell tower button ## Gems diff --git a/public/assets/creeps/maker/11.png b/public/assets/creeps/maker/0.png similarity index 100% rename from public/assets/creeps/maker/11.png rename to public/assets/creeps/maker/0.png diff --git a/public/assets/json/Creeps.json b/public/assets/json/Creeps.json index 7eab6fa..90b9153 100644 --- a/public/assets/json/Creeps.json +++ b/public/assets/json/Creeps.json @@ -49,5 +49,56 @@ "frostfire": 0 } } + }, + { + "name": "cloaker", + "textures": [], + "textureArrayLength": 12, + "stats": { + "health": 12, + "speed": 2, + "special": null, + "resistance": { + "physical": 0, + "divine": 0, + "fire": 0, + "ice": 0, + "frostfire": 0 + } + } + }, + { + "name": "demon", + "textures": [], + "textureArrayLength": 8, + "stats": { + "health": 12, + "speed": 2, + "special": null, + "resistance": { + "physical": 0, + "divine": 0, + "fire": 0, + "ice": 0, + "frostfire": 0 + } + } + }, + { + "name": "maker", + "textures": [], + "textureArrayLength": 11, + "stats": { + "health": 11, + "speed": 2, + "special": null, + "resistance": { + "physical": 0, + "divine": 0, + "fire": 0, + "ice": 0, + "frostfire": 0 + } + } } ] diff --git a/src/classes/Definitions.ts b/src/classes/Definitions.ts index b33ec4f..4af3c63 100644 --- a/src/classes/Definitions.ts +++ b/src/classes/Definitions.ts @@ -108,6 +108,9 @@ export enum CreepType { Basic = 0, Quick = 1, Tank = 2, + Cloaker = 3, + Demon = 4, + Maker = 5, } export enum GemType { diff --git a/src/classes/game/Grid.ts b/src/classes/game/Grid.ts index d736c63..3a30768 100644 --- a/src/classes/game/Grid.ts +++ b/src/classes/game/Grid.ts @@ -67,6 +67,7 @@ export class Cell extends GameObject { Engine.GameScene.events.on(TowerEvents.TowerSoldEvent, (_, row, col) => { if (row == this.row && col == this.column) { this.hasTowerPlaced = false; + Engine.Grid.rangePreview.clear(); } }); diff --git a/src/classes/game/KeyboardManager.ts b/src/classes/game/KeyboardManager.ts index bffe515..9bac58b 100644 --- a/src/classes/game/KeyboardManager.ts +++ b/src/classes/game/KeyboardManager.ts @@ -1,7 +1,7 @@ /** * Handles keyboard events. */ -class KeyboardManager { +export default class KeyboardManager { private static listeners: ((event: KeyboardEvent) => void)[] = []; public static init() { @@ -35,7 +35,7 @@ class KeyboardManager { private static handleKeyDown(event: KeyboardEvent) { if (KeyboardManager.listeners.length > 0) { - console.log(`Key down: ${event.key}`); + // console.log(`Key down: ${event.key}`); for (let i = KeyboardManager.listeners.length - 1; i >= 0; i--) { KeyboardManager.listeners[i](event); if (event.defaultPrevented) { @@ -45,5 +45,3 @@ class KeyboardManager { } } } - -export default KeyboardManager; diff --git a/src/classes/game/Tower.ts b/src/classes/game/Tower.ts index 9930b46..bf7d74e 100644 --- a/src/classes/game/Tower.ts +++ b/src/classes/game/Tower.ts @@ -16,6 +16,8 @@ export function distance(x1, y1, x2, y2) { export class Tower extends GameObject { public row: number; public column: number; + public setAsSold: boolean = false; + public sold: boolean = false; public definition: TowerDefinition; public slottedGems: Array = []; public damageDealt: number = 0; @@ -74,6 +76,7 @@ export class Tower extends GameObject { } public UnslotGem(index) { const gem = this.slottedGems.splice(index, 1)[0]; + if (gem == null || !gem) return console.warn('UnslotGem: Gem is null.'); Engine.GameScene.MissionStats.giveGem(gem, true); for (let i = index; i < this.slottedGems.length - 1; i++) { if (this.slottedGems[i] == null) { @@ -113,14 +116,6 @@ export class Tower extends GameObject { } combinedTint = color; } - // this.slottedGems.forEach((gem) => { - // let rgb = new PIXI.Color(gem.definition.color).toRgb(); - // combinedTint = - // ((combinedTint & 0xff0000) + (rgb.r << 16)) | - // ((combinedTint & 0x00ff00) + (rgb.g << 8)) | - // ((combinedTint & 0x0000ff) + rgb.b); - // }); - // combinedTint = new PIXI.Color(this.slottedGems[0].definition.color). let proj = new Projectile( x, y, @@ -135,7 +130,15 @@ export class Tower extends GameObject { this.projectiles.push(proj); return proj; } + public Sell() { + this.setAsSold = true; + // Selling logic is handled in TowerManager.update() + } public update(elapsedMS: any): void { + if (this.sold) return; + if (this.setAsSold) { + this.sold = true; + } if (this.behaviour == TowerBehaviours.BasicTowerBehaviour) BasicTowerBehaviour(this, elapsedMS); if (this.behaviour == TowerBehaviours.CircleTowerBehaviour) CircleTowerBehaviour(this, elapsedMS); } diff --git a/src/classes/game/TowerBehaviours.ts b/src/classes/game/TowerBehaviours.ts index 719b8d2..21a8071 100644 --- a/src/classes/game/TowerBehaviours.ts +++ b/src/classes/game/TowerBehaviours.ts @@ -12,12 +12,13 @@ import { Tower } from './Tower'; */ function projectileCheck(tower: Tower, elapsedMS: number) { tower.projectiles.forEach((proj) => { - if (proj.deleteMe) { + if (proj.deleteMe || tower.sold) { proj.collidedCreepIDs.forEach(() => { tower.damageDealt += tower.computedDamageToDeal; }); proj.collidedCreepIDs = []; tower.projectiles.splice(tower.projectiles.indexOf(proj), 1); + proj.destroy(); proj = null; } else proj.update(elapsedMS); }); diff --git a/src/classes/game/TowerManager.ts b/src/classes/game/TowerManager.ts index 967f547..1a31cd5 100644 --- a/src/classes/game/TowerManager.ts +++ b/src/classes/game/TowerManager.ts @@ -114,8 +114,17 @@ export default class TowerManager { } } public update(elapsedMS) { - this.towers.forEach((twr) => { - twr.update(elapsedMS); + this.towers.forEach((twr, idx) => { + if (twr.sold) { + twr.slottedGems = twr.slottedGems.filter((gem) => gem != null); + while (twr.slottedGems.length > 0) { + twr.UnslotGem(0); + } + Engine.GameScene.MissionStats.earnGold(twr.definition.stats.cost); + twr.destroy(); + this.towers.splice(idx, 1); + Engine.GameScene.events.emit(TowerEvents.TowerSoldEvent, twr.name, twr.row, twr.column); + } else twr.update(elapsedMS); }); } } diff --git a/src/classes/gui/MessageBox.ts b/src/classes/gui/MessageBox.ts index 7febad1..ef9dd23 100644 --- a/src/classes/gui/MessageBox.ts +++ b/src/classes/gui/MessageBox.ts @@ -1,6 +1,5 @@ import * as PIXI from 'pixi.js'; import ModalDialogBase from './ModalDialog'; -import GuiObject from '../GuiObject'; export default class MessageBox extends ModalDialogBase { private caption: string; diff --git a/src/classes/gui/ModalDialog.ts b/src/classes/gui/ModalDialog.ts index 8f682b4..c1501cd 100644 --- a/src/classes/gui/ModalDialog.ts +++ b/src/classes/gui/ModalDialog.ts @@ -1,6 +1,5 @@ import * as PIXI from 'pixi.js'; import GuiObject from '../GuiObject'; -import Assets from '../Assets'; import { Engine } from '../Bastion'; import GameAssets from '../Assets'; import Button, { ButtonTexture } from './Button'; @@ -46,9 +45,9 @@ export default abstract class ModalDialogBase extends GuiObject { const contentBounds = `x: ${Math.round(this.dialogContent.x)}, y: ${Math.round( this.dialogContent.y )}, width: ${Math.round(this.dialogContent.width)}, height: ${Math.round(this.dialogContent.height)}`; - console.debug( - `ModalDialogBase.show(dialog: ${dialogBounds}, content: ${contentBounds}, buttons: ${this.buttonCaptions})` - ); + // console.debug( + // `ModalDialogBase.show(dialog: ${dialogBounds}, content: ${contentBounds}, buttons: ${this.buttonCaptions})` + // ); return new Promise((resolve, reject) => { Engine.app.stage.addChild(this.container); this.onClosed = (button) => { diff --git a/src/classes/gui/TowerPanel.ts b/src/classes/gui/TowerPanel.ts index f104c6d..cb6287d 100644 --- a/src/classes/gui/TowerPanel.ts +++ b/src/classes/gui/TowerPanel.ts @@ -114,6 +114,7 @@ export default class TowerPanel extends GuiObject { public frostFireResDamage: PIXI.Text; public divineResDamage: PIXI.Text; public physicalResDamage: PIXI.Text; + private sellButton: Button; constructor(bounds: PIXI.Rectangle) { super(false); @@ -281,6 +282,14 @@ export default class TowerPanel extends GuiObject { }), }); this.container.addChild(this.physicalResDamage); + this.sellButton = new Button( + new PIXI.Rectangle(5, this.towerPanel.height - 70, this.towerPanel.width - 115, 60), + 'Sell', + ButtonTexture.Button02, + true + ); + this.sellButton.container.removeFromParent(); + this.container.addChild(this.sellButton.container); } private MakeSlots(tower: Tower) { this.vGems.forEach((vGem) => { @@ -320,7 +329,7 @@ export default class TowerPanel extends GuiObject { this.MakeSlots(tower); this.showingTower = tower; Engine.GameScene.sidebar.gemTab.selectingGemTowerObject = tower; - if (tower.container.parent.x < 900) { + if (tower.container.parent.x < 1270) { this.ShowRight(); } else { this.ShowLeft(); @@ -339,6 +348,11 @@ export default class TowerPanel extends GuiObject { this.frostFireResDamage.text = `+${tower.totalGemResistanceModifications.frostfire * 100}% FrostFire damage`; this.divineResDamage.text = `+${tower.totalGemResistanceModifications.divine * 100}% Divine damage`; this.physicalResDamage.text = `+${tower.totalGemResistanceModifications.physical * 100}% Physical damage`; + this.sellButton.setCaption('Sell for ' + tower.definition.stats.cost + ' gold'); + this.sellButton.onClick = () => { + tower.Sell(); + this.Hide(); + }; } private ShowLeft() { this.towerPanel.x = -100; diff --git a/src/scenes/Game.ts b/src/scenes/Game.ts index 8ed5ebf..6a80ca7 100644 --- a/src/scenes/Game.ts +++ b/src/scenes/Game.ts @@ -22,7 +22,7 @@ import HighScoreDialog, { HighScoreDialogButtons } from '../classes/gui/HighScor enum RoundMode { Purchase = 0, Combat = 1, - OfferingGems = 2, + Misc = 2, } export class GameScene extends Scene { @@ -36,6 +36,8 @@ export class GameScene extends Scene { public sidebar: Sidebar; public tooltip: Tooltip; public towerPanel: TowerPanel; + public isPaused: boolean = false; + private pauseButton: Button; private visualGems: VisualGemSlot[] = []; private currentRound: number = 0; private isWaveManagerFinished: boolean = false; @@ -104,12 +106,12 @@ export class GameScene extends Scene { if (this.roundMode == RoundMode.Combat) return Engine.NotificationManager.Notify('Wave is already in progress.', 'warn'); if (this.isGameOver) return Engine.NotificationManager.Notify('No more waves.', 'danger'); - if (this.roundMode == RoundMode.OfferingGems) return; + if (this.roundMode == RoundMode.Misc) return; this.setRoundMode(RoundMode.Combat); this.changeRoundButton.buttonIcon.texture = GameAssets.ExclamationIconTexture; this.events.emit(WaveManagerEvents.NewWave, `${this.currentRound + 1}`); }; - this.MissionStats = new MissionStats(100, 200); + this.MissionStats = new MissionStats(125, 450); this.events.on(GemEvents.TowerPanelSelectGem, (gem, index, tower) => { if (gem == null) { if (!this.MissionStats.checkIfPlayerHasAnyGems()) @@ -120,6 +122,30 @@ export class GameScene extends Scene { } this.sidebar.gemTab.TowerPanelSelectingGem(gem, index, tower); }); + + this.pauseButton = new Button(new PIXI.Rectangle(5, 5, 120, 80), '', ButtonTexture.Button01, true); + this.pauseButton.container.removeFromParent(); + this.stage.addChild(this.pauseButton.container); + this.pauseButton.CustomButtonLogic = () => { + this.pauseButton.buttonIcon = new PIXI.Sprite({ + texture: GameAssets.PauseIconTexture, + x: this.pauseButton.container.width / 2, + y: this.pauseButton.container.height / 2, + scale: 0.2, + }); + this.pauseButton.buttonIcon.anchor.set(0.5, 0.5); + this.pauseButton.container.addChild(this.pauseButton.buttonIcon); + }; + this.pauseButton.CustomButtonLogic(); + this.pauseButton.onClick = () => { + if (this.isPaused) { + this.UnpauseGame(); + } else { + this.ShowPauseDialog(); + this.PauseGame(); + } + }; + this.ticker = new PIXI.Ticker(); this.ticker.maxFPS = 60; this.ticker.minFPS = 30; @@ -183,7 +209,7 @@ export class GameScene extends Scene { Engine.Grid.gridInteractionEnabled = false; Engine.GameScene.sidebar.towerTab.resetTint(); Engine.TowerManager.ResetChooseTower(); - this.setRoundMode(RoundMode.OfferingGems); + this.setRoundMode(RoundMode.Misc); let gemsToOffer = this.mission.rounds[this.currentRound].offeredGems; this.DarkenScreen(); this.offerGemsSprite = new PIXI.NineSliceSprite({ @@ -245,6 +271,18 @@ export class GameScene extends Scene { this.setRoundMode(RoundMode.Purchase); } + public PauseGame() { + this.isPaused = true; + this.ticker.stop(); + } + public UnpauseGame() { + this.isPaused = false; + this.ticker.start(); + } + public ShowPauseDialog() { + console.warn("Pause dialog doesn't exist."); + } + private async ShowEndgameDialog(lost) { const endGameDialog = new EndGameDialog(this.mission.name, this.MissionStats, lost); await endGameDialog.show();