improvement

This commit is contained in:
Koneko 2025-02-10 20:06:18 +01:00 committed by GitHub
commit 5b18dd601a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
38 changed files with 491 additions and 141 deletions

View File

@ -12,3 +12,7 @@ The [design document](/docs/design.md) contains the starting idea which was init
## Todos ## Todos
Todos are available [here](/docs/todos.md) which contain self-made tasks which should get done or are already done. Todos are available [here](/docs/todos.md) which contain self-made tasks which should get done or are already done.
## Extra documentation
Extra documentation is available [here](/docs)

43
docs/linecount.md Normal file
View File

@ -0,0 +1,43 @@
Generated by: `find ./src -name '*.ts' | xargs wc -l`
92 ./src/main.ts
1 ./src/vite-env.d.ts
17 ./src/classes/GameUIConstants.ts
145 ./src/classes/gui/TowerTab.ts
375 ./src/classes/gui/TowerPanel.ts
52 ./src/classes/gui/TextInput.ts
211 ./src/classes/gui/ModalDialog.ts
51 ./src/classes/gui/Sidebar.ts
122 ./src/classes/gui/HighScoreDialog.ts
70 ./src/classes/gui/Button.ts
60 ./src/classes/gui/Gemsmith.ts
154 ./src/classes/gui/EndGameDialog.ts
220 ./src/classes/gui/GemTab.ts
59 ./src/classes/gui/GamePausedDialog.ts
36 ./src/classes/gui/MessageBox.ts
205 ./src/classes/gui/Tooltip.ts
126 ./src/classes/Definitions.ts
77 ./src/classes/game/WaveManager.ts
260 ./src/classes/game/Grid.ts
51 ./src/classes/game/Gem.ts
114 ./src/classes/game/TowerBehaviours.ts
154 ./src/classes/game/Tower.ts
180 ./src/classes/game/MissionStats.ts
100 ./src/classes/game/AnimationManager.ts
177 ./src/classes/game/Creep.ts
47 ./src/classes/game/KeyboardManager.ts
104 ./src/classes/game/Projectile.ts
86 ./src/classes/game/NotificationManager.ts
130 ./src/classes/game/TowerManager.ts
71 ./src/classes/game/HighScoreManager.ts
76 ./src/classes/GuiObject.ts
203 ./src/classes/Assets.ts
52 ./src/classes/GameObject.ts
68 ./src/classes/Bastion.ts
30 ./src/classes/Events.ts
37 ./src/scenes/Scene.ts
17 ./src/scenes/Settings.ts
67 ./src/scenes/HowToPlay.ts
59 ./src/scenes/Main.ts
353 ./src/scenes/Game.ts
27 ./src/scenes/MissionPicker.ts
`4536 total`

View File

@ -17,6 +17,7 @@ List of things to implement following the "release" of the minimum viable produc
- [x] Tower info on click - [x] Tower info on click
- [x] Animate projectiles - [x] Animate projectiles
- [x] Better mouseover tracking when placing tower and showing radius - [x] Better mouseover tracking when placing tower and showing radius
- [x] Sell tower button
## Gems ## Gems
@ -25,8 +26,8 @@ List of things to implement following the "release" of the minimum viable produc
## Other ## Other
- [ ] Create mission authoring tool - [ ] Disable player action during combat phase.
- [ ] Add sound effects - [ ] Add sound effects
- [ ] Tutorial image/mission - [x] Tutorial image/mission
- [ ] Pause menu - [ ] Pause menu
- [x] Score screen when winning/losing map - [x] Score screen when winning/losing map

View File

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.11.0" orientation="orthogonal" renderorder="right-down" width="30" height="17" tilewidth="64" tileheight="64" infinite="0" nextlayerid="2" nextobjectid="1">
<tileset firstgid="1" source="Tileset.tsx"/>
<layer id="1" name="Tile Layer 1" width="30" height="17">
<data encoding="csv">
15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,
15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,
15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,
4,4,4,4,4,4,4,4,4,4,4,4,4,5,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,
18,18,18,18,18,18,18,18,18,18,18,18,18,19,15,15,15,15,15,3,4,4,4,4,4,4,4,4,4,4,
32,32,32,32,32,32,32,32,32,32,32,7,18,19,15,15,15,15,15,17,18,18,18,18,18,18,18,18,18,18,
15,15,15,15,15,15,15,15,15,15,15,17,18,19,15,15,15,15,15,17,18,6,32,32,32,32,32,32,32,32,
15,15,15,15,15,15,15,15,15,15,15,17,18,19,15,15,15,15,15,17,18,19,15,15,15,15,15,15,15,15,
15,15,15,15,15,15,15,15,15,15,15,17,18,19,15,15,15,15,15,17,18,19,15,15,15,15,15,15,15,15,
15,15,3,4,4,4,4,4,4,4,4,21,18,19,15,15,15,15,15,17,18,19,15,15,15,15,15,15,15,15,
15,15,17,18,18,18,18,18,18,18,18,18,18,19,15,15,15,15,15,17,18,19,15,15,15,15,15,15,15,15,
15,15,17,18,6,32,32,32,32,32,32,32,32,33,15,15,15,15,15,17,18,19,15,15,15,15,15,15,15,15,
15,15,17,18,19,15,15,15,15,15,15,15,15,15,15,15,15,15,15,17,18,19,15,15,15,15,15,15,15,15,
15,15,17,18,20,4,4,4,4,4,4,4,4,4,4,4,4,4,4,21,18,19,15,15,15,15,15,15,15,15,
15,15,17,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,19,15,15,15,15,15,15,15,15,
15,15,31,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,33,15,15,15,15,15,15,15,15,
15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15
</data>
</layer>
</map>

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

View File

@ -49,5 +49,56 @@
"frostfire": 0 "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
}
}
} }
] ]

View File

@ -1,7 +1,7 @@
[ [
{ {
"name": "Fire Gem", "name": "Fire Gem",
"description": "Forged from molten lava, the Fire Gem imbues your tower's attacks and adds 50% extra fire damage. It can be merged with any gem and is common. This text shouldn't be long.", "description": "Forged from molten lava, the Fire Gem imbues your tower's attacks and adds 50% extra fire damage. It can be merged with any gem and is common.",
"color": "red", "color": "red",
"type": "Fire", "type": "Fire",
"totalLevels": 2, "totalLevels": 2,
@ -12,16 +12,16 @@
"genericImprovements": [ "genericImprovements": [
{ {
"damageUp": 2, "damageUp": 2,
"attackSpeedUp": 10, "attackSpeedUp": 0,
"rangeUp": 0.5, "rangeUp": 0,
"timeToLiveUp": 0, "timeToLiveUp": 0,
"pierceUp": 1, "pierceUp": 1,
"gemValueUp": 0 "gemValueUp": 0
}, },
{ {
"damageUp": 2, "damageUp": 2,
"attackSpeedUp": 10, "attackSpeedUp": 0,
"rangeUp": 0.5, "rangeUp": 0,
"timeToLiveUp": 0, "timeToLiveUp": 0,
"pierceUp": 1, "pierceUp": 1,
"gemValueUp": 10 "gemValueUp": 10
@ -58,7 +58,7 @@
{ {
"damageUp": 2, "damageUp": 2,
"attackSpeedUp": 10, "attackSpeedUp": 10,
"rangeUp": 0.5, "rangeUp": 0,
"timeToLiveUp": 0, "timeToLiveUp": 0,
"pierceUp": 1, "pierceUp": 1,
"gemValueUp": 0 "gemValueUp": 0
@ -66,7 +66,7 @@
{ {
"damageUp": 2, "damageUp": 2,
"attackSpeedUp": 10, "attackSpeedUp": 10,
"rangeUp": 0.5, "rangeUp": 0,
"timeToLiveUp": 0, "timeToLiveUp": 0,
"pierceUp": 1, "pierceUp": 1,
"gemValueUp": 10 "gemValueUp": 10
@ -103,7 +103,7 @@
{ {
"damageUp": 2, "damageUp": 2,
"attackSpeedUp": 10, "attackSpeedUp": 10,
"rangeUp": 0.5, "rangeUp": 0,
"timeToLiveUp": 0, "timeToLiveUp": 0,
"pierceUp": 1, "pierceUp": 1,
"gemValueUp": 0 "gemValueUp": 0
@ -163,7 +163,7 @@
{ {
"damageUp": 2, "damageUp": 2,
"attackSpeedUp": 10, "attackSpeedUp": 10,
"rangeUp": 0.5, "rangeUp": 0,
"timeToLiveUp": 0, "timeToLiveUp": 0,
"pierceUp": 1, "pierceUp": 1,
"gemValueUp": 0 "gemValueUp": 0
@ -171,7 +171,7 @@
{ {
"damageUp": 2, "damageUp": 2,
"attackSpeedUp": 10, "attackSpeedUp": 10,
"rangeUp": 0.5, "rangeUp": 0,
"timeToLiveUp": 0, "timeToLiveUp": 0,
"pierceUp": 1, "pierceUp": 1,
"gemValueUp": 10 "gemValueUp": 10

View File

@ -9,11 +9,11 @@
"description": "The building block of society, nothing more basic exists.", "description": "The building block of society, nothing more basic exists.",
"stats": { "stats": {
"damage": 2, "damage": 2,
"cooldown": 120, "cooldown": 2000,
"gemSlotsAmount": 2, "gemSlotsAmount": 2,
"cost": 100, "cost": 100,
"range": 3, "range": 4,
"timeToLive": 120, "timeToLive": 20,
"pierce": 1 "pierce": 1
} }
}, },
@ -27,11 +27,11 @@
"description": "If you feel a little circular.", "description": "If you feel a little circular.",
"stats": { "stats": {
"damage": 2, "damage": 2,
"cooldown": 120, "cooldown": 2000,
"gemSlotsAmount": 3, "gemSlotsAmount": 3,
"cost": 125, "cost": 125,
"range": 2, "range": 2.5,
"timeToLive": 8, "timeToLive": 12,
"pierce": 30 "pierce": 30
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -3,6 +3,8 @@ import { CreepDefinition, GemDefinition, MissionDefinition, TowerDefinition } fr
import { Engine } from './Bastion'; import { Engine } from './Bastion';
export default class GameAssets { export default class GameAssets {
public static MainBackground: PIXI.Texture;
public static Frame01Texture: PIXI.Texture; public static Frame01Texture: PIXI.Texture;
public static Frame02Texture: PIXI.Texture; public static Frame02Texture: PIXI.Texture;
public static Frame03Texture: PIXI.Texture; public static Frame03Texture: PIXI.Texture;
@ -26,9 +28,16 @@ export default class GameAssets {
public static BannerGemsmith: PIXI.Texture; public static BannerGemsmith: PIXI.Texture;
public static EndScreenDialog: PIXI.Texture; public static EndScreenDialog: PIXI.Texture;
public static Tutorial01: PIXI.Texture;
public static Tutorial02: PIXI.Texture;
public static Tutorial03: PIXI.Texture;
public static Tutorial04: PIXI.Texture;
public static Tutorial05: PIXI.Texture;
public static PlayIconTexture: PIXI.Texture; public static PlayIconTexture: PIXI.Texture;
public static PauseIconTexture: PIXI.Texture; public static PauseIconTexture: PIXI.Texture;
public static ExclamationIconTexture: PIXI.Texture; public static ExclamationIconTexture: PIXI.Texture;
public static FastForwardIconTexture: PIXI.Texture;
public static HomeIconTexture: PIXI.Texture; public static HomeIconTexture: PIXI.Texture;
public static HammerIconTexture: PIXI.Texture; public static HammerIconTexture: PIXI.Texture;
public static XIconTexture: PIXI.Texture; public static XIconTexture: PIXI.Texture;
@ -98,6 +107,14 @@ export default class GameAssets {
this.Load('./assets/gui/frame_blue.png').then((texture) => (this.BlueBackground = texture)), this.Load('./assets/gui/frame_blue.png').then((texture) => (this.BlueBackground = texture)),
this.Load('./assets/gui/banner_01.png').then((texture) => (this.BannerGemsmith = texture)), this.Load('./assets/gui/banner_01.png').then((texture) => (this.BannerGemsmith = texture)),
this.Load('./assets/gui/note.png').then((texture) => (this.EndScreenDialog = texture)), this.Load('./assets/gui/note.png').then((texture) => (this.EndScreenDialog = texture)),
this.Load('./assets/gui/main_background.jpg').then((texture) => (this.MainBackground = texture)),
this.Load('./assets/tutorial/tutorial01.jpg').then((texture) => (this.Tutorial01 = texture)),
this.Load('./assets/tutorial/tutorial02.jpg').then((texture) => (this.Tutorial02 = texture)),
this.Load('./assets/tutorial/tutorial03.jpg').then((texture) => (this.Tutorial03 = texture)),
this.Load('./assets/tutorial/tutorial04.jpg').then((texture) => (this.Tutorial04 = texture)),
this.Load('./assets/tutorial/tutorial05.jpg').then((texture) => (this.Tutorial05 = texture)),
this.Load('./assets/gui/heart.png').then((texture) => (this.HealthTexture = texture)), this.Load('./assets/gui/heart.png').then((texture) => (this.HealthTexture = texture)),
this.Load('./assets/gui/money.png').then((texture) => (this.GoldTexture = texture)), this.Load('./assets/gui/money.png').then((texture) => (this.GoldTexture = texture)),
this.Load('./assets/gui/wave.png').then((texture) => (this.WaveTexture = texture)), this.Load('./assets/gui/wave.png').then((texture) => (this.WaveTexture = texture)),
@ -105,7 +122,8 @@ export default class GameAssets {
this.Load('./assets/gui/title01.png').then((texture) => (this.TitleTexture = texture)), this.Load('./assets/gui/title01.png').then((texture) => (this.TitleTexture = texture)),
this.Load('./assets/gui/icons/play.png').then((texture) => (this.PlayIconTexture = texture)), this.Load('./assets/gui/icons/play.png').then((texture) => (this.PlayIconTexture = texture)),
this.Load('./assets/gui/icons/pause.png').then((texture) => (this.PauseIconTexture = texture)), this.Load('./assets/gui/icons/pause.png').then((texture) => (this.PauseIconTexture = texture)),
this.Load('./assets/gui/icons/exclamation.png').then((texture) => (this.ExclamationIconTexture = texture)), this.Load('./assets/gui/icons/pause.png').then((texture) => (this.PauseIconTexture = texture)),
this.Load('./assets/gui/icons/fastforward.png').then((texture) => (this.FastForwardIconTexture = texture)),
this.Load('./assets/gui/icons/home.png').then((texture) => (this.HomeIconTexture = texture)), this.Load('./assets/gui/icons/home.png').then((texture) => (this.HomeIconTexture = texture)),
this.Load('./assets/gui/icons/hammer.png').then((texture) => (this.HammerIconTexture = texture)), this.Load('./assets/gui/icons/hammer.png').then((texture) => (this.HammerIconTexture = texture)),
this.Load('./assets/gui/icons/cross.png').then((texture) => (this.XIconTexture = texture)), this.Load('./assets/gui/icons/cross.png').then((texture) => (this.XIconTexture = texture)),

View File

@ -1,5 +1,4 @@
import * as PIXI from 'pixi.js'; import * as PIXI from 'pixi.js';
import GameObject from './GameObject';
import GuiObject from './GuiObject'; import GuiObject from './GuiObject';
import Scene from '../scenes/Scene'; import Scene from '../scenes/Scene';
import { Grid } from './game/Grid'; import { Grid } from './game/Grid';
@ -10,7 +9,6 @@ import { AnimationManager } from './game/AnimationManager';
import NotificationManager from './game/NotificationManager'; import NotificationManager from './game/NotificationManager';
import Gem from './game/Gem'; import Gem from './game/Gem';
import GameAssets from './Assets'; import GameAssets from './Assets';
import { GemType } from './Definitions';
export class Engine { export class Engine {
public static app: PIXI.Application; public static app: PIXI.Application;
@ -33,10 +31,10 @@ export class Engine {
public static TestSuite() { public static TestSuite() {
let params = new URLSearchParams(location.href); let params = new URLSearchParams(location.href);
if (params.entries().next().value[1] != 'game') return; if (params.entries().next().value[1] != 'game') return;
Engine.NotificationManager.Notify('Loaded testing suite.', 'danger'); Engine.NotificationManager.Notify('Loaded testing suite.', 'danger');
let tower = GameAssets.Towers[0];
Engine.TowerManager.ToggleChoosingTowerLocation('RESET'); Engine.TowerManager.ToggleChoosingTowerLocation('RESET');
Engine.TowerManager.PlaceTower(GameAssets.Towers[1], 6, 10, GameAssets.Towers[1].behaviour, true); Engine.TowerManager.PlaceTower(tower, 6, 10, tower.behaviour, true);
for (let i = 0; i < 29; i++) { for (let i = 0; i < 29; i++) {
this.GameScene.MissionStats.giveGem(new Gem(i % 4), true); this.GameScene.MissionStats.giveGem(new Gem(i % 4), true);
} }

View File

@ -108,6 +108,9 @@ export enum CreepType {
Basic = 0, Basic = 0,
Quick = 1, Quick = 1,
Tank = 2, Tank = 2,
Cloaker = 3,
Demon = 4,
Maker = 5,
} }
export enum GemType { export enum GemType {

View File

@ -131,8 +131,8 @@ export default class Creep extends GameObject {
this.sprite.scale.x *= -1; this.sprite.scale.x *= -1;
} }
} }
let deltaX = this.speed * elapsedMS * directionX; let deltaX = this.speed * elapsedMS * directionX * Engine.GameScene.gameSpeedMultiplier;
let deltaY = this.speed * elapsedMS * directionY; let deltaY = this.speed * elapsedMS * directionY * Engine.GameScene.gameSpeedMultiplier;
let increaseIndex = false; let increaseIndex = false;
if (deltaX > 0 && this.x + deltaX > targetX) { if (deltaX > 0 && this.x + deltaX > targetX) {

View File

@ -67,6 +67,7 @@ export class Cell extends GameObject {
Engine.GameScene.events.on(TowerEvents.TowerSoldEvent, (_, row, col) => { Engine.GameScene.events.on(TowerEvents.TowerSoldEvent, (_, row, col) => {
if (row == this.row && col == this.column) { if (row == this.row && col == this.column) {
this.hasTowerPlaced = false; this.hasTowerPlaced = false;
Engine.Grid.rangePreview.clear();
} }
}); });

View File

@ -1,7 +1,7 @@
/** /**
* Handles keyboard events. * Handles keyboard events.
*/ */
class KeyboardManager { export default class KeyboardManager {
private static listeners: ((event: KeyboardEvent) => void)[] = []; private static listeners: ((event: KeyboardEvent) => void)[] = [];
public static init() { public static init() {
@ -35,7 +35,7 @@ class KeyboardManager {
private static handleKeyDown(event: KeyboardEvent) { private static handleKeyDown(event: KeyboardEvent) {
if (KeyboardManager.listeners.length > 0) { 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--) { for (let i = KeyboardManager.listeners.length - 1; i >= 0; i--) {
KeyboardManager.listeners[i](event); KeyboardManager.listeners[i](event);
if (event.defaultPrevented) { if (event.defaultPrevented) {
@ -45,5 +45,3 @@ class KeyboardManager {
} }
} }
} }
export default KeyboardManager;

View File

@ -64,7 +64,7 @@ export default class Projectile extends GameObject {
if (this.deleteMe) return; if (this.deleteMe) return;
if (this.x > 2000 || this.x < 0 || this.y > 2000 || this.y < 0 || this.pierce <= 0 || this.timeToLive <= 0) if (this.x > 2000 || this.x < 0 || this.y > 2000 || this.y < 0 || this.pierce <= 0 || this.timeToLive <= 0)
return this.destroy(); return this.destroy();
this.timeToLive--; this.timeToLive -= Engine.GameScene.gameSpeedMultiplier;
Engine.Grid.creeps.forEach((creep) => { Engine.Grid.creeps.forEach((creep) => {
if (this.pierce <= 0) return; if (this.pierce <= 0) return;
if (creep && creep.container && this.checkCollision(creep)) { if (creep && creep.container && this.checkCollision(creep)) {
@ -77,19 +77,25 @@ export default class Projectile extends GameObject {
} }
} }
}); });
this.x += Math.cos(this.angle) * this.speed * elapsedMS; this.x += Math.cos(this.angle) * this.speed * elapsedMS * Engine.GameScene.gameSpeedMultiplier;
this.y += Math.sin(this.angle) * this.speed * elapsedMS; this.y += Math.sin(this.angle) * this.speed * elapsedMS * Engine.GameScene.gameSpeedMultiplier;
this.container.x = this.x; this.container.x = this.x;
this.container.y = this.y; this.container.y = this.y;
} }
public onCollide(creep) { public onCollide(creep) {
/*
Note:
Right now it is possible for the bullet to 'overshoot' the creep if the bullet speed is too fast and the position is updated so that the
new position is beyond the creep (i.e. the bullet is never 'in the creep').
This should be fixed so that we calculate the hit if the creep is in a line from the previous position to the new position.
*/
Engine.GameScene.events.emit(CreepEvents.TakenDamage, creep.id, this.damage, this.gemResistanceModifications); Engine.GameScene.events.emit(CreepEvents.TakenDamage, creep.id, this.damage, this.gemResistanceModifications);
} }
public checkCollision(creep: Creep) { public checkCollision(creep: Creep) {
console.debug(creep); //console.debug(creep);
if (creep == null || creep.container == null || creep.container._position == null) return; if (creep == null || creep.container == null || creep.container._position == null) return;
let mybb = this.copyContainerToBB(); let mybb = this.copyContainerToBB();
let otherbb = creep.copyContainerToBB(); let otherbb = creep.copyContainerToBB();

View File

@ -16,16 +16,18 @@ export function distance(x1, y1, x2, y2) {
export class Tower extends GameObject { export class Tower extends GameObject {
public row: number; public row: number;
public column: number; public column: number;
public setAsSold: boolean = false;
public sold: boolean = false;
public definition: TowerDefinition; public definition: TowerDefinition;
public slottedGems: Array<Gem> = []; public slottedGems: Array<Gem> = [];
public damageDealt: number = 0; public damageDealt: number = 0;
public projectiles: Projectile[] = []; public projectiles: Projectile[] = [];
public behaviour: string; public behaviour: string;
public sprite: PIXI.Sprite; public sprite: PIXI.Sprite;
public ticksUntilNextShot: number; public millisecondsUntilNextShot: number;
public graphics: PIXI.Graphics = new PIXI.Graphics(); public graphics: PIXI.Graphics = new PIXI.Graphics();
public computedDamageToDeal: number; public computedDamageToDeal: number;
public computedAttackSpeed: number; public computedCooldown: number;
public computedRange: number; public computedRange: number;
public computedTimeToLive: number; public computedTimeToLive: number;
public computedPierce: number; public computedPierce: number;
@ -38,7 +40,7 @@ export class Tower extends GameObject {
this.column = column; this.column = column;
this.behaviour = behaviour; this.behaviour = behaviour;
this.definition = definition; this.definition = definition;
this.ticksUntilNextShot = 0; this.millisecondsUntilNextShot = 0;
this.parent = Engine.Grid.getCellByRowAndCol(row, column); this.parent = Engine.Grid.getCellByRowAndCol(row, column);
this.sprite = new PIXI.Sprite({ this.sprite = new PIXI.Sprite({
texture: texture, texture: texture,
@ -74,6 +76,7 @@ export class Tower extends GameObject {
} }
public UnslotGem(index) { public UnslotGem(index) {
const gem = this.slottedGems.splice(index, 1)[0]; 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); Engine.GameScene.MissionStats.giveGem(gem, true);
for (let i = index; i < this.slottedGems.length - 1; i++) { for (let i = index; i < this.slottedGems.length - 1; i++) {
if (this.slottedGems[i] == null) { if (this.slottedGems[i] == null) {
@ -113,14 +116,6 @@ export class Tower extends GameObject {
} }
combinedTint = color; 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( let proj = new Projectile(
x, x,
y, y,
@ -132,10 +127,20 @@ export class Tower extends GameObject {
this.computedPierce, this.computedPierce,
this.totalGemResistanceModifications this.totalGemResistanceModifications
); );
const time = new Date().toISOString();
console.log(`${time} ${this.definition.name} shot at ${angle} degrees`);
this.projectiles.push(proj); this.projectiles.push(proj);
return proj; return proj;
} }
public Sell() {
this.setAsSold = true;
// Selling logic is handled in TowerManager.update()
}
public update(elapsedMS: any): void { 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.BasicTowerBehaviour) BasicTowerBehaviour(this, elapsedMS);
if (this.behaviour == TowerBehaviours.CircleTowerBehaviour) CircleTowerBehaviour(this, elapsedMS); if (this.behaviour == TowerBehaviours.CircleTowerBehaviour) CircleTowerBehaviour(this, elapsedMS);
} }

View File

@ -12,11 +12,13 @@ import { Tower } from './Tower';
*/ */
function projectileCheck(tower: Tower, elapsedMS: number) { function projectileCheck(tower: Tower, elapsedMS: number) {
tower.projectiles.forEach((proj) => { tower.projectiles.forEach((proj) => {
if (proj.deleteMe) { if (proj.deleteMe || tower.sold) {
proj.collidedCreepIDs.forEach(() => { proj.collidedCreepIDs.forEach(() => {
tower.damageDealt += tower.computedDamageToDeal; tower.damageDealt += tower.computedDamageToDeal;
}); });
proj.collidedCreepIDs = [];
tower.projectiles.splice(tower.projectiles.indexOf(proj), 1); tower.projectiles.splice(tower.projectiles.indexOf(proj), 1);
proj.destroy();
proj = null; proj = null;
} else proj.update(elapsedMS); } else proj.update(elapsedMS);
}); });
@ -57,7 +59,7 @@ export function computeGemImprovements(tower: Tower) {
tower.totalGemResistanceModifications.frostfire += gemResMod.frostfire; tower.totalGemResistanceModifications.frostfire += gemResMod.frostfire;
}); });
tower.computedDamageToDeal = tower.definition.stats.damage + gemDamage; tower.computedDamageToDeal = tower.definition.stats.damage + gemDamage;
tower.computedAttackSpeed = tower.definition.stats.cooldown - gemAttackSpeedUp; tower.computedCooldown = tower.definition.stats.cooldown - gemAttackSpeedUp;
tower.computedRange = tower.definition.stats.range + gemRangeUp; tower.computedRange = tower.definition.stats.range + gemRangeUp;
tower.computedTimeToLive = tower.definition.stats.timeToLive + gemTimeToLiveUp; tower.computedTimeToLive = tower.definition.stats.timeToLive + gemTimeToLiveUp;
tower.computedPierce = tower.definition.stats.pierce + gemPierceUp; tower.computedPierce = tower.definition.stats.pierce + gemPierceUp;
@ -71,30 +73,32 @@ export function computeGemImprovements(tower: Tower) {
* @param elapsedMS - The elapsed time in milliseconds since the last update. * @param elapsedMS - The elapsed time in milliseconds since the last update.
*/ */
export function BasicTowerBehaviour(tower: Tower, elapsedMS: number) { export function BasicTowerBehaviour(tower: Tower, elapsedMS: number) {
if (tower.ticksUntilNextShot % 2 == 0) computeGemImprovements(tower); computeGemImprovements(tower);
projectileCheck(tower, elapsedMS); projectileCheck(tower, elapsedMS);
if (tower.ticksUntilNextShot > 0) tower.ticksUntilNextShot--; if (tower.millisecondsUntilNextShot > 0)
tower.millisecondsUntilNextShot -= elapsedMS * Engine.GameScene.gameSpeedMultiplier;
let creepsInRange = tower.GetCreepsInRange(); let creepsInRange = tower.GetCreepsInRange();
if (creepsInRange.length > 0) { if (creepsInRange.length > 0) {
let focus = creepsInRange[0]; let focus = creepsInRange[0];
if (tower.ticksUntilNextShot <= 0) { if (tower.millisecondsUntilNextShot <= 0) {
let x = tower.column * Engine.GridCellSize + Engine.GridCellSize / 2; let x = tower.column * Engine.GridCellSize + Engine.GridCellSize / 2;
let y = tower.row * Engine.GridCellSize + Engine.GridCellSize / 2; let y = tower.row * Engine.GridCellSize + Engine.GridCellSize / 2;
tower.ticksUntilNextShot = tower.computedAttackSpeed; tower.millisecondsUntilNextShot = tower.computedCooldown;
tower.Shoot(calculateAngleToPoint(x, y, focus.x, focus.y)); tower.Shoot(calculateAngleToPoint(x, y, focus.x, focus.y));
} }
} }
} }
export function CircleTowerBehaviour(tower: Tower, elapsedMS: number) { export function CircleTowerBehaviour(tower: Tower, elapsedMS: number) {
if (tower.ticksUntilNextShot % 2 == 0) computeGemImprovements(tower); computeGemImprovements(tower);
projectileCheck(tower, elapsedMS); projectileCheck(tower, elapsedMS);
if (tower.ticksUntilNextShot > 0) tower.ticksUntilNextShot--; if (tower.millisecondsUntilNextShot > 0)
tower.millisecondsUntilNextShot -= elapsedMS * Engine.GameScene.gameSpeedMultiplier;
let creepsInRange = tower.GetCreepsInRange(); let creepsInRange = tower.GetCreepsInRange();
if (creepsInRange.length > 0) { if (creepsInRange.length > 0) {
let focus = creepsInRange[0]; let focus = creepsInRange[0];
if (tower.ticksUntilNextShot <= 0) { if (tower.millisecondsUntilNextShot <= 0) {
tower.ticksUntilNextShot = tower.computedAttackSpeed; tower.millisecondsUntilNextShot = tower.computedCooldown;
let x = tower.column * Engine.GridCellSize + Engine.GridCellSize / 2; let x = tower.column * Engine.GridCellSize + Engine.GridCellSize / 2;
let y = tower.row * Engine.GridCellSize + Engine.GridCellSize / 2; let y = tower.row * Engine.GridCellSize + Engine.GridCellSize / 2;
tower.Shoot(calculateAngleToPoint(x, y, x, y + 10)); // Up tower.Shoot(calculateAngleToPoint(x, y, x, y + 10)); // Up

View File

@ -114,8 +114,17 @@ export default class TowerManager {
} }
} }
public update(elapsedMS) { public update(elapsedMS) {
this.towers.forEach((twr) => { this.towers.forEach((twr, idx) => {
twr.update(elapsedMS); 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);
}); });
} }
} }

View File

@ -60,7 +60,7 @@ export default class WaveManager extends GameObject {
} }
public update(elapsedMS: number): void { public update(elapsedMS: number): void {
if (this.started == false) return; if (this.started == false) return;
this.ticks += elapsedMS; this.ticks += elapsedMS * Engine.GameScene.gameSpeedMultiplier;
this.creeps.forEach((creep) => { this.creeps.forEach((creep) => {
if (!creep.spawned && creep.tickToSpawnAt <= this.ticks) { if (!creep.spawned && creep.tickToSpawnAt <= this.ticks) {
creep.spawned = true; creep.spawned = true;

View File

@ -0,0 +1,59 @@
import * as PIXI from 'pixi.js';
import ModalDialogBase from './ModalDialog';
import Button, { ButtonTexture } from './Button';
import { Engine } from '../Bastion';
import { MissionPickerScene } from '../../scenes/MissionPicker';
import { GameScene } from '../../scenes/Game';
import KeyboardManager from '../game/KeyboardManager';
export default class GamePausedDialog extends ModalDialogBase {
private btnMainMenu: Button;
private btnRetry: Button;
private btnContinue: Button;
private _unsubKeypress: () => void;
constructor() {
super([]);
this._unsubKeypress = KeyboardManager.onKeyPressed(this.onContinueClick.bind(this));
}
protected override createContent(): PIXI.Container {
const container = new PIXI.Container();
this.btnMainMenu = new Button(new PIXI.Rectangle(0, 0, 300, 60), 'Main Menu', ButtonTexture.Button01);
this.btnMainMenu.onClick = this.onMainMenuClick.bind(this);
container.addChild(this.btnMainMenu.container);
this.btnRetry = new Button(new PIXI.Rectangle(0, 70, 300, 60), 'Retry', ButtonTexture.Button01);
this.btnRetry.onClick = this.onRetryClick.bind(this);
container.addChild(this.btnRetry.container);
this.btnContinue = new Button(new PIXI.Rectangle(0, 140, 300, 60), 'Continue', ButtonTexture.Button01);
this.btnContinue.onClick = this.onContinueClick.bind(this);
container.addChild(this.btnContinue.container);
return container;
}
private onMainMenuClick(): void {
this.close();
this._unsubKeypress();
Engine.GameScene.UnpauseGame();
Engine.GameScene.destroy();
Engine.GameMaster.changeScene(new MissionPickerScene());
}
private onRetryClick(): void {
const missionName = Engine.GameScene.mission.name;
this.close();
this._unsubKeypress();
Engine.GameScene.UnpauseGame();
Engine.GameScene.destroy();
Engine.GameMaster.changeScene(new MissionPickerScene());
Engine.GameMaster.changeScene(new GameScene(missionName));
Engine.NotificationManager.Notify('Retrying mission.', 'green');
}
private onContinueClick(): void {
this.close();
Engine.GameScene.UnpauseGame();
}
}

View File

@ -1,6 +1,5 @@
import * as PIXI from 'pixi.js'; import * as PIXI from 'pixi.js';
import ModalDialogBase from './ModalDialog'; import ModalDialogBase from './ModalDialog';
import GuiObject from '../GuiObject';
export default class MessageBox extends ModalDialogBase { export default class MessageBox extends ModalDialogBase {
private caption: string; private caption: string;

View File

@ -1,6 +1,5 @@
import * as PIXI from 'pixi.js'; import * as PIXI from 'pixi.js';
import GuiObject from '../GuiObject'; import GuiObject from '../GuiObject';
import Assets from '../Assets';
import { Engine } from '../Bastion'; import { Engine } from '../Bastion';
import GameAssets from '../Assets'; import GameAssets from '../Assets';
import Button, { ButtonTexture } from './Button'; 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( const contentBounds = `x: ${Math.round(this.dialogContent.x)}, y: ${Math.round(
this.dialogContent.y this.dialogContent.y
)}, width: ${Math.round(this.dialogContent.width)}, height: ${Math.round(this.dialogContent.height)}`; )}, width: ${Math.round(this.dialogContent.width)}, height: ${Math.round(this.dialogContent.height)}`;
console.debug( // console.debug(
`ModalDialogBase.show(dialog: ${dialogBounds}, content: ${contentBounds}, buttons: ${this.buttonCaptions})` // `ModalDialogBase.show(dialog: ${dialogBounds}, content: ${contentBounds}, buttons: ${this.buttonCaptions})`
); // );
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
Engine.app.stage.addChild(this.container); Engine.app.stage.addChild(this.container);
this.onClosed = (button) => { this.onClosed = (button) => {

View File

@ -179,9 +179,14 @@ export default class Tooltip extends GuiObject {
this.gemDescriptionText.alpha = 1; this.gemDescriptionText.alpha = 1;
this.titleText.text = `Lv. ${gem.level} ` + gem.definition.name; this.titleText.text = `Lv. ${gem.level} ` + gem.definition.name;
let costToLevelUp;
if (!gem.isMaxLevel())
costToLevelUp = `Costs ${gem.definition.genericImprovements[gem.level].gemValueUp} gold to level up.`;
else costToLevelUp = 'Max level.';
this.gemDescriptionText.text = this.gemDescriptionText.text =
`Valued at ${gem.definition.initialGemValue + gem.currentGemImprovement().gemValueUp} gold. ` + `${costToLevelUp} Valued at ${
gem.definition.description; gem.definition.initialGemValue + gem.currentGemImprovement().gemValueUp
} gold. ` + gem.definition.description;
} }
public Show(x, y) { public Show(x, y) {
this.container.alpha = 1; this.container.alpha = 1;

View File

@ -73,7 +73,7 @@ export class VisualGemSlot extends GuiObject {
this.container.addChild(this.background); this.container.addChild(this.background);
this.container.addChild(this.iconSprite); this.container.addChild(this.iconSprite);
this.container.addChild(this.frame); this.container.addChild(this.frame);
let txt = gem ? gem.id : ''; let txt = gem ? gem.level : '';
let dbgText = new PIXI.Text({ let dbgText = new PIXI.Text({
text: txt, text: txt,
zIndex: 11, zIndex: 11,
@ -114,6 +114,7 @@ export default class TowerPanel extends GuiObject {
public frostFireResDamage: PIXI.Text; public frostFireResDamage: PIXI.Text;
public divineResDamage: PIXI.Text; public divineResDamage: PIXI.Text;
public physicalResDamage: PIXI.Text; public physicalResDamage: PIXI.Text;
private sellButton: Button;
constructor(bounds: PIXI.Rectangle) { constructor(bounds: PIXI.Rectangle) {
super(false); super(false);
@ -156,6 +157,7 @@ export default class TowerPanel extends GuiObject {
zIndex: 5, zIndex: 5,
style: new PIXI.TextStyle({ style: new PIXI.TextStyle({
fill: 0xffffff, fill: 0xffffff,
fontSize: 25,
stroke: { stroke: {
color: 0x000000, color: 0x000000,
width: 2, width: 2,
@ -280,6 +282,14 @@ export default class TowerPanel extends GuiObject {
}), }),
}); });
this.container.addChild(this.physicalResDamage); 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) { private MakeSlots(tower: Tower) {
this.vGems.forEach((vGem) => { this.vGems.forEach((vGem) => {
@ -319,7 +329,7 @@ export default class TowerPanel extends GuiObject {
this.MakeSlots(tower); this.MakeSlots(tower);
this.showingTower = tower; this.showingTower = tower;
Engine.GameScene.sidebar.gemTab.selectingGemTowerObject = tower; Engine.GameScene.sidebar.gemTab.selectingGemTowerObject = tower;
if (tower.container.parent.x < 900) { if (tower.container.parent.x < 1270) {
this.ShowRight(); this.ShowRight();
} else { } else {
this.ShowLeft(); this.ShowLeft();
@ -331,13 +341,18 @@ export default class TowerPanel extends GuiObject {
this.damageText.text = 'Deals ' + tower.computedDamageToDeal + ' damage'; this.damageText.text = 'Deals ' + tower.computedDamageToDeal + ' damage';
this.totalDamage.text = 'Damage dealt: ' + tower.damageDealt + ' damage'; this.totalDamage.text = 'Damage dealt: ' + tower.damageDealt + ' damage';
this.attackSpeedText.x = this.damageText.width + 10; this.attackSpeedText.x = this.damageText.width + 10;
this.attackSpeedText.text = ` every ${Math.floor((tower.computedAttackSpeed / 60) * 100) / 100}s`; this.attackSpeedText.text = ` every ${Math.floor((tower.computedCooldown / 60) * 100) / 100}s`;
this.fireResDamage.text = `+${tower.totalGemResistanceModifications.fire * 100}% Fire damage`; this.fireResDamage.text = `+${tower.totalGemResistanceModifications.fire * 100}% Fire damage`;
this.iceResDamage.text = `+${tower.totalGemResistanceModifications.ice * 100}% Ice damage`; this.iceResDamage.text = `+${tower.totalGemResistanceModifications.ice * 100}% Ice damage`;
this.frostFireResDamage.text = `+${tower.totalGemResistanceModifications.frostfire * 100}% FrostFire damage`; this.frostFireResDamage.text = `+${tower.totalGemResistanceModifications.frostfire * 100}% FrostFire damage`;
this.divineResDamage.text = `+${tower.totalGemResistanceModifications.divine * 100}% Divine damage`; this.divineResDamage.text = `+${tower.totalGemResistanceModifications.divine * 100}% Divine damage`;
this.physicalResDamage.text = `+${tower.totalGemResistanceModifications.physical * 100}% Physical 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() { private ShowLeft() {
this.towerPanel.x = -100; this.towerPanel.x = -100;

View File

@ -46,6 +46,7 @@ import { GemType } from './classes/Definitions';
} }
Engine.latestCommit = await fetch('/latest_commit').then((res) => res.text()); Engine.latestCommit = await fetch('/latest_commit').then((res) => res.text());
window.addEventListener('resize', resize); window.addEventListener('resize', resize);
resize(); resize();
await Assets.LoadAssets(); await Assets.LoadAssets();
GameUIConstants.init(); GameUIConstants.init();
@ -71,4 +72,21 @@ import { GemType } from './classes/Definitions';
return 'You are about to leave.'; return 'You are about to leave.';
}; };
else Engine.TestSuite(); else Engine.TestSuite();
let gamePausedDueToBlur = false;
window.addEventListener('blur', () => {
console.log('blur');
if (Engine.GameScene && !Engine.GameScene.isPaused) {
Engine.GameScene.PauseGame();
gamePausedDueToBlur = true;
}
});
window.addEventListener('focus', () => {
console.log('focus');
if (Engine.GameScene && gamePausedDueToBlur && Engine.GameScene.isPaused) {
gamePausedDueToBlur = false;
Engine.GameScene.UnpauseGame();
}
});
})(); })();

View File

@ -18,11 +18,12 @@ import TowerPanel, { VisualGemSlot } from '../classes/gui/TowerPanel';
import Gem from '../classes/game/Gem'; import Gem from '../classes/game/Gem';
import EndGameDialog from '../classes/gui/EndGameDialog'; import EndGameDialog from '../classes/gui/EndGameDialog';
import HighScoreDialog, { HighScoreDialogButtons } from '../classes/gui/HighScoreDialog'; import HighScoreDialog, { HighScoreDialogButtons } from '../classes/gui/HighScoreDialog';
import GamePausedDialog from '../classes/gui/GamePausedDialog';
enum RoundMode { enum RoundMode {
Purchase = 0, Purchase = 0,
Combat = 1, Combat = 1,
OfferingGems = 2, Misc = 2,
} }
export class GameScene extends Scene { export class GameScene extends Scene {
@ -36,6 +37,10 @@ export class GameScene extends Scene {
public sidebar: Sidebar; public sidebar: Sidebar;
public tooltip: Tooltip; public tooltip: Tooltip;
public towerPanel: TowerPanel; public towerPanel: TowerPanel;
public isPaused: boolean = false;
public gameSpeedMultiplier: number = 1;
private pauseButton: Button;
private visualGems: VisualGemSlot[] = []; private visualGems: VisualGemSlot[] = [];
private currentRound: number = 0; private currentRound: number = 0;
private isWaveManagerFinished: boolean = false; private isWaveManagerFinished: boolean = false;
@ -47,10 +52,12 @@ export class GameScene extends Scene {
y: 0, y: 0,
zIndex: 120, zIndex: 120,
}); });
private windowTitle: string;
constructor(name: string) { constructor(name: string) {
super(); super();
Engine.GameScene = this; Engine.GameScene = this;
this.windowTitle = document.title;
GameAssets.Missions.forEach((mission, index) => { GameAssets.Missions.forEach((mission, index) => {
if (mission.name == name) { if (mission.name == name) {
this.mission = mission; this.mission = mission;
@ -101,15 +108,21 @@ export class GameScene extends Scene {
this.changeRoundButton.CustomButtonLogic(); this.changeRoundButton.CustomButtonLogic();
this.changeRoundButton.onClick = () => { this.changeRoundButton.onClick = () => {
if (this.playerWon) return this.ReturnToMain(); if (this.playerWon) return this.ReturnToMain();
if (this.roundMode == RoundMode.Combat) if (this.roundMode == RoundMode.Combat) {
return Engine.NotificationManager.Notify('Wave is already in progress.', 'warn'); if (this.gameSpeedMultiplier !== 1) {
this.UpdateGameSpeedMultiplier(1);
} else {
this.UpdateGameSpeedMultiplier(2);
}
return;
}
if (this.isGameOver) return Engine.NotificationManager.Notify('No more waves.', 'danger'); 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.setRoundMode(RoundMode.Combat);
this.changeRoundButton.buttonIcon.texture = GameAssets.ExclamationIconTexture; this.changeRoundButton.buttonIcon.texture = GameAssets.FastForwardIconTexture;
this.events.emit(WaveManagerEvents.NewWave, `${this.currentRound + 1}`); 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) => { this.events.on(GemEvents.TowerPanelSelectGem, (gem, index, tower) => {
if (gem == null) { if (gem == null) {
if (!this.MissionStats.checkIfPlayerHasAnyGems()) if (!this.MissionStats.checkIfPlayerHasAnyGems())
@ -120,6 +133,30 @@ export class GameScene extends Scene {
} }
this.sidebar.gemTab.TowerPanelSelectingGem(gem, index, tower); 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 = new PIXI.Ticker();
this.ticker.maxFPS = 60; this.ticker.maxFPS = 60;
this.ticker.minFPS = 30; this.ticker.minFPS = 30;
@ -130,10 +167,10 @@ export class GameScene extends Scene {
this.ticker.add(() => { this.ticker.add(() => {
if (this.update) this.update(this.ticker.elapsedMS); if (this.update) this.update(this.ticker.elapsedMS);
// if (this.isFastForwarded) this.update(this.ticker.elapsedMS);
}); });
this.ticker.start(); this.ticker.start();
} }
public update(elapsedMS) { public update(elapsedMS) {
if (this.isGameOver) { if (this.isGameOver) {
if (this.destroyTicker) { if (this.destroyTicker) {
@ -183,7 +220,7 @@ export class GameScene extends Scene {
Engine.Grid.gridInteractionEnabled = false; Engine.Grid.gridInteractionEnabled = false;
Engine.GameScene.sidebar.towerTab.resetTint(); Engine.GameScene.sidebar.towerTab.resetTint();
Engine.TowerManager.ResetChooseTower(); Engine.TowerManager.ResetChooseTower();
this.setRoundMode(RoundMode.OfferingGems); this.setRoundMode(RoundMode.Misc);
let gemsToOffer = this.mission.rounds[this.currentRound].offeredGems; let gemsToOffer = this.mission.rounds[this.currentRound].offeredGems;
this.DarkenScreen(); this.DarkenScreen();
this.offerGemsSprite = new PIXI.NineSliceSprite({ this.offerGemsSprite = new PIXI.NineSliceSprite({
@ -245,6 +282,21 @@ export class GameScene extends Scene {
this.setRoundMode(RoundMode.Purchase); this.setRoundMode(RoundMode.Purchase);
} }
public PauseGame() {
this.isPaused = true;
this.ticker.stop();
document.title = '[PAUSED] ' + this.windowTitle;
}
public UnpauseGame() {
this.isPaused = false;
this.ticker.start();
document.title = this.windowTitle;
}
public ShowPauseDialog() {
const gamePausedDialog = new GamePausedDialog();
gamePausedDialog.show();
}
private async ShowEndgameDialog(lost) { private async ShowEndgameDialog(lost) {
const endGameDialog = new EndGameDialog(this.mission.name, this.MissionStats, lost); const endGameDialog = new EndGameDialog(this.mission.name, this.MissionStats, lost);
await endGameDialog.show(); await endGameDialog.show();
@ -292,4 +344,10 @@ export class GameScene extends Scene {
private ReturnToMain() { private ReturnToMain() {
Engine.GameMaster.changeScene(new MissionPickerScene()); Engine.GameMaster.changeScene(new MissionPickerScene());
} }
private UpdateGameSpeedMultiplier(newMultiplier: number) {
this.gameSpeedMultiplier = newMultiplier;
if (newMultiplier === 1) Engine.NotificationManager.Notify('Regular speed.', 'info');
else Engine.NotificationManager.Notify('Fast forward activated.', 'info');
}
} }

67
src/scenes/HowToPlay.ts Normal file
View File

@ -0,0 +1,67 @@
import GameAssets from '../classes/Assets';
import Assets from '../classes/Assets';
import { Engine } from '../classes/Bastion';
import Button, { ButtonTexture } from '../classes/gui/Button';
import { GameScene } from './Game';
import { MainScene } from './Main';
import Scene from './Scene';
import * as PIXI from 'pixi.js';
export class HowToPlay extends Scene {
public currentImg = 1;
public sprite;
public init() {
let sprites = [
null,
GameAssets.Tutorial01,
GameAssets.Tutorial02,
GameAssets.Tutorial03,
GameAssets.Tutorial04,
GameAssets.Tutorial05,
];
this.sprite = new PIXI.Sprite({
texture: GameAssets.Tutorial01,
scale: 0.6,
x: 250,
y: 150,
});
this.stage.addChild(this.sprite);
let leftButton = new Button(
new PIXI.Rectangle(250, this.sprite.height + 160, 120, 60),
'Back',
ButtonTexture.Button01
);
leftButton.container.alpha = 0;
leftButton.onClick = () => {
if (leftButton.container.alpha == 0 || this.currentImg == 1) return;
this.currentImg--;
if (this.currentImg == 3) this.sprite.scale = 1.1;
else this.sprite.scale = 0.6;
this.sprite.texture = sprites[this.currentImg];
if (this.currentImg == 1) leftButton.container.alpha = 0;
};
let right = new Button(
new PIXI.Rectangle(this.sprite.width + 130, this.sprite.height + 160, 120, 60),
'Next',
ButtonTexture.Button01
);
right.onClick = () => {
if (right.container.alpha == 0) return;
this.currentImg++;
if (this.currentImg == 3) this.sprite.scale = 1.1;
else this.sprite.scale = 0.6;
if (this.currentImg != 1) leftButton.container.alpha = 1;
this.sprite.texture = sprites[this.currentImg];
if (this.currentImg == 5) right.container.alpha = 0;
};
const button = new Button(
new PIXI.Rectangle(this.sprite.width - 540, this.sprite.height + 160, 200, 60),
'Main menu',
ButtonTexture.Button01
);
button.onClick = (e) => {
Engine.GameMaster.changeScene(new MainScene());
};
}
}

View File

@ -1,66 +1,59 @@
import GameAssets from '../classes/Assets';
import { Engine } from '../classes/Bastion'; import { Engine } from '../classes/Bastion';
import { FadeInOut, Tween } from '../classes/game/AnimationManager'; import { FadeInOut, Tween } from '../classes/game/AnimationManager';
import Button, { ButtonTexture } from '../classes/gui/Button'; import Button, { ButtonTexture } from '../classes/gui/Button';
import { HowToPlay } from './HowToPlay';
import { MissionPickerScene } from './MissionPicker'; import { MissionPickerScene } from './MissionPicker';
import Scene from './Scene'; import Scene from './Scene';
import * as PIXI from 'pixi.js'; import * as PIXI from 'pixi.js';
import { SettingsScene } from './Settings';
export class MainScene extends Scene { export class MainScene extends Scene {
public init() { public init() {
// Background
this.addMainBackground();
const NewGameButton = { const NewGameButton = {
caption: 'New Game', caption: 'New Game',
rect: new PIXI.Rectangle( rect: new PIXI.Rectangle(Engine.app.canvas.width / 2 - 300 / 2, 400 + 0 * 70, 300, 60),
Engine.app.canvas.width / 2 - 300 / 2,
Engine.app.canvas.height / 5 + 3 * 80, texture: ButtonTexture.Button01,
300, };
60 const TutorialButton = {
), caption: 'How to play',
texture: ButtonTexture.Button02, rect: new PIXI.Rectangle(Engine.app.canvas.width / 2 - 300 / 2, 400 + 1 * 70, 300, 60),
texture: ButtonTexture.Button01,
}; };
const SettingsButton = { const SettingsButton = {
caption: 'Settings', caption: 'Settings',
rect: new PIXI.Rectangle( rect: new PIXI.Rectangle(Engine.app.canvas.width / 2 - 300 / 2, 400 + 2 * 70, 300, 60),
Engine.app.canvas.width / 2 - 300 / 2, texture: ButtonTexture.Button01,
Engine.app.canvas.height / 5 + 4 * 80,
300,
60
),
texture: ButtonTexture.Button02,
}; };
let text = new PIXI.Text({
x: Engine.app.canvas.width / 2 - 300 / 2,
y: Engine.app.canvas.height / 5 + 1 * 80,
text: 'BASTION',
style: {
fill: 0xffaa00,
fontFamily: 'Aclonica',
fontSize: 100,
},
});
text.x = text.x - text.width / 5;
Engine.GameMaster.currentScene.stage.addChild(text);
let text2 = new PIXI.Text({ let text2 = new PIXI.Text({
x: 0, x: 0,
y: 0, y: 0,
text: 'Latest commit: ' + Engine.latestCommit, text: 'Latest commit: ' + Engine.latestCommit,
style: { style: {
fill: 0x000000, fill: 0xffffff,
fontSize: 10, fontSize: 10,
fontWeight: 'bold', fontWeight: 'bold',
}, },
}); });
Engine.GameMaster.currentScene.stage.addChild(text2); this.stage.addChild(text2);
const button01 = new Button(NewGameButton.rect, NewGameButton.caption, NewGameButton.texture, true); const button01 = new Button(NewGameButton.rect, NewGameButton.caption, NewGameButton.texture, true);
button01.onClick = (e) => { button01.onClick = (e) => {
Engine.GameMaster.currentScene.stage.removeChild(text);
Engine.GameMaster.currentScene.stage.removeChild(text2);
Engine.GameMaster.changeScene(new MissionPickerScene()); Engine.GameMaster.changeScene(new MissionPickerScene());
}; };
let b2 = new Button(SettingsButton.rect, SettingsButton.caption, SettingsButton.texture, true); // let b2 = new Button(SettingsButton.rect, SettingsButton.caption, SettingsButton.texture, true);
b2.onClick = (e) => { // b2.onClick = (e) => {
Engine.NotificationManager.Notify('Not finished.', 'info'); // Engine.GameMaster.changeScene(new SettingsScene());
// };
let b3 = new Button(TutorialButton.rect, TutorialButton.caption, TutorialButton.texture, true);
b3.onClick = (e) => {
Engine.GameMaster.changeScene(new HowToPlay());
}; };
} }
} }

View File

@ -8,18 +8,14 @@ import * as PIXI from 'pixi.js';
export class MissionPickerScene extends Scene { export class MissionPickerScene extends Scene {
public init() { public init() {
const button = new Button(new PIXI.Rectangle(0, 0, 300, 60), 'Back to main', ButtonTexture.Button01); this.addMainBackground();
const button = new Button(new PIXI.Rectangle(10, 10, 300, 60), 'Back to main', ButtonTexture.Button01);
button.onClick = (e) => { button.onClick = (e) => {
Engine.GameMaster.changeScene(new MainScene()); Engine.GameMaster.changeScene(new MainScene());
}; };
Assets.Missions.forEach((mission, index) => { Assets.Missions.forEach((mission, index) => {
const button = new Button( const button = new Button(
new PIXI.Rectangle( new PIXI.Rectangle(Engine.app.canvas.width / 2 - 300 / 2, 400 + index * 70, 300, 60),
Engine.app.canvas.width / 2 - 300 / 2,
Engine.app.canvas.height / 5 + index * 80,
300,
60
),
mission.name, mission.name,
ButtonTexture.Button01 ButtonTexture.Button01
); );

View File

@ -1,3 +1,4 @@
import GameAssets from '../classes/Assets';
import { Engine } from '../classes/Bastion'; import { Engine } from '../classes/Bastion';
import GuiObject from '../classes/GuiObject'; import GuiObject from '../classes/GuiObject';
import * as PIXI from 'pixi.js'; import * as PIXI from 'pixi.js';
@ -18,6 +19,14 @@ export default class Scene {
}); });
} }
public addMainBackground() {
// Background
const sprite = new PIXI.Sprite(GameAssets.MainBackground);
sprite.width = Engine.app.canvas.width;
sprite.height = Engine.app.canvas.height;
this.stage.addChild(sprite);
}
public get events(): PIXI.EventEmitter { public get events(): PIXI.EventEmitter {
return this._events; return this._events;
} }

17
src/scenes/Settings.ts Normal file
View File

@ -0,0 +1,17 @@
import Assets from '../classes/Assets';
import { Engine } from '../classes/Bastion';
import Button, { ButtonTexture } from '../classes/gui/Button';
import { GameScene } from './Game';
import { MainScene } from './Main';
import Scene from './Scene';
import * as PIXI from 'pixi.js';
export class SettingsScene extends Scene {
public init() {
this.addMainBackground();
const button = new Button(new PIXI.Rectangle(10, 10, 300, 60), 'Back to main', ButtonTexture.Button01);
button.onClick = (e) => {
Engine.GameMaster.changeScene(new MainScene());
};
}
}