add gem slots for testing and user feedback

This commit is contained in:
Koneko 2025-02-02 22:41:27 +01:00 committed by GitHub
commit b3a36b1f35
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 769 additions and 172 deletions

24
docs/content.md Normal file
View File

@ -0,0 +1,24 @@
# How to add new content to the game
A small guide so other people also understand how to add content.
## Tower
1. Update Towers.json by adding to the end of the array.
2. Update TowerType in Defintions.ts
3. Based of the Tower.sprite value, add projectile folder with appropriate projectiles as .png.
4. Based of the Tower.sprite value, add the tower sprite into towers folder as a .png.
5. Add appropriate behaviour in Tower.ts (if statement in update).
6. Add way to spawn via TowerTab.ts button.
## Creep
1. Update Creeps.json by adding to the end of the array.
2. Update CreepType in Defintions.ts
3. Based of the Creep.name value, add creep's walking animations to the same named subfolder in creeps folder.
4. When using creeps in waves, reference them by their index in the CreepType enum.
## Gem
1. Update Gems.json by adding to the end of the array.
2. Update GemType in Defintions.ts and make sure Gem.type is CaSe sensitively the same.

View File

@ -11,16 +11,16 @@ List of things to implement following the "release" of the minimum viable produc
## Towers ## Towers
- [ ] Extend projectile into seperate defintion + json file - [x] Extend projectile into seperate defintion + json file
- [ ] Make tower react with slotted gems - [ ] Make tower react with slotted gems
- [ ] Alter damage based on attunement from slotted gems - [ ] Alter damage based on attunement from slotted gems
- [ ] 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
## Gems ## Gems
- [ ] Create Gem definitions - [x] Create Gem definitions
- [ ] Make gems affect towers - [ ] Make gems affect towers
## Other ## Other

View File

@ -4,7 +4,7 @@
"version": "0.0.0", "version": "0.0.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite --host",
"build": "tsc && vite build", "build": "tsc && vite build",
"preview": "vite preview" "preview": "vite preview"
}, },

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

BIN
public/assets/gui/frame_05.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

Before

Width:  |  Height:  |  Size: 3.3 MiB

After

Width:  |  Height:  |  Size: 3.3 MiB

View File

@ -5,7 +5,7 @@
"textureArrayLength": 12, "textureArrayLength": 12,
"stats": { "stats": {
"health": 5, "health": 5,
"speed": 2.4, "speed": 6,
"special": null, "special": null,
"resistance": { "resistance": {
"physical": 0, "physical": 0,

View File

@ -0,0 +1,43 @@
[
{
"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.",
"type": "Fire",
"totalLevels": 2,
"textures": [],
"cantCombineWith": [],
"specialCombine": [],
"genericImprovements": [
{
"damageUp": 2,
"attackSpeedUp": 100,
"rangeUp": 0.5,
"timeToLiveUp": 0,
"pierceUp": 1
},
{
"damageUp": 2,
"attackSpeedUp": 100,
"rangeUp": 0.5,
"timeToLiveUp": 0,
"pierceUp": 1
}
],
"gemResistanceModifications": [
{
"physical": 0,
"divine": 0,
"fire": 0.5,
"ice": 0,
"frostfire": 0
},
{
"physical": 0,
"divine": 0,
"fire": 0.5,
"ice": 0,
"frostfire": 0
}
]
}
]

View File

@ -102,7 +102,7 @@
"creeps": [0, 0, 0, 0, 0] "creeps": [0, 0, 0, 0, 0]
} }
], ],
"offeredGems": [0, 1, 2, 3] "offeredGems": [0, 0, 0, 0]
}, },
{ {
"waves": [ "waves": [

View File

@ -36,7 +36,7 @@
"scale": 0.5, "scale": 0.5,
"selectedLayer": 0, "selectedLayer": 0,
"viewCenter": { "viewCenter": {
"x": 1070, "x": 570,
"y": 448 "y": 448
} }
}, },

View File

@ -1,5 +1,5 @@
import * as PIXI from 'pixi.js'; import * as PIXI from 'pixi.js';
import { CreepDefinition, MissionDefinition, TowerDefinition } from './Definitions'; import { CreepDefinition, GemDefinition, MissionDefinition, TowerDefinition } from './Definitions';
import { Engine } from './Bastion'; import { Engine } from './Bastion';
export default class GameAssets { export default class GameAssets {
@ -7,6 +7,7 @@ export default class GameAssets {
public static Frame02Texture: PIXI.Texture; public static Frame02Texture: PIXI.Texture;
public static Frame03Texture: PIXI.Texture; public static Frame03Texture: PIXI.Texture;
public static Frame04Texture: PIXI.Texture; public static Frame04Texture: PIXI.Texture;
public static Frame05Texture: PIXI.Texture;
public static FrameInventory: PIXI.Texture; public static FrameInventory: PIXI.Texture;
public static FrameBackground: PIXI.Texture; public static FrameBackground: PIXI.Texture;
public static FrameTowerTab: PIXI.Texture; public static FrameTowerTab: PIXI.Texture;
@ -22,18 +23,22 @@ export default class GameAssets {
public static WaveTexture: PIXI.Texture; public static WaveTexture: PIXI.Texture;
public static SwordsTexture: PIXI.Texture; public static SwordsTexture: PIXI.Texture;
public static TitleTexture: PIXI.Texture; public static TitleTexture: PIXI.Texture;
public static GemFrame: 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 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 PlusIconTexture: PIXI.Texture;
public static GemAmountIcons: PIXI.Texture[] = []; public static GemAmountIcons: PIXI.Texture[] = [];
public static Missions: MissionDefinition[]; public static Missions: MissionDefinition[];
public static MissionBackgrounds: PIXI.Texture[] = []; public static MissionBackgrounds: PIXI.Texture[] = [];
public static Towers: TowerDefinition[]; public static Towers: TowerDefinition[];
public static Creeps: CreepDefinition[]; public static Creeps: CreepDefinition[];
public static Gems: GemDefinition[];
private static text; private static text;
private static async Load(src) { private static async Load(src) {
@ -74,55 +79,70 @@ export default class GameAssets {
Engine.app.stage.addChild(this.text); Engine.app.stage.addChild(this.text);
await Promise.all([ await Promise.all([
this.Load('/aclonica.woff2'), this.Load('./aclonica.woff2'),
this.Load('/assets/gui/button_01.png').then((texture) => (this.Button01Texture = texture)), this.Load('./assets/gui/button_01.png').then((texture) => (this.Button01Texture = texture)),
this.Load('/assets/gui/button_02.png').then((texture) => (this.Button02Texture = texture)), this.Load('./assets/gui/button_02.png').then((texture) => (this.Button02Texture = texture)),
this.Load('/assets/gui/button_small.png').then((texture) => (this.ButtonSmallTexture = texture)), this.Load('./assets/gui/button_small.png').then((texture) => (this.ButtonSmallTexture = texture)),
this.Load('/assets/gui/frame_01.png').then((texture) => (this.Frame01Texture = texture)), this.Load('./assets/gui/frame_01.png').then((texture) => (this.Frame01Texture = texture)),
this.Load('/assets/gui/frame_02.png').then((texture) => (this.Frame02Texture = texture)), this.Load('./assets/gui/frame_02.png').then((texture) => (this.Frame02Texture = texture)),
this.Load('/assets/gui/frame_03.png').then((texture) => (this.Frame03Texture = texture)), this.Load('./assets/gui/frame_03.png').then((texture) => (this.Frame03Texture = texture)),
this.Load('/assets/gui/frame_04.png').then((texture) => (this.Frame04Texture = texture)), this.Load('./assets/gui/frame_04.png').then((texture) => (this.Frame04Texture = texture)),
this.Load('/assets/gui/frame_inv.png').then((texture) => (this.FrameInventory = texture)), this.Load('./assets/gui/frame_05.png').then((texture) => (this.Frame05Texture = texture)),
this.Load('/assets/gui/background_01.png').then((texture) => (this.FrameBackground = texture)), this.Load('./assets/gui/frame_inv.png').then((texture) => (this.FrameInventory = texture)),
this.Load('/assets/gui/background_02.png').then((texture) => (this.FrameTowerTab = texture)), this.Load('./assets/gui/background_01.png').then((texture) => (this.FrameBackground = texture)),
this.Load('/assets/gui/frame_violet.png').then((texture) => (this.VioletBackground = texture)), this.Load('./assets/gui/background_02.png').then((texture) => (this.FrameTowerTab = texture)),
this.Load('/assets/gui/frame_red.png').then((texture) => (this.RedBackground = texture)), this.Load('./assets/gui/frame_violet.png').then((texture) => (this.VioletBackground = texture)),
this.Load('/assets/gui/frame_green.png').then((texture) => (this.GreenBackground = texture)), this.Load('./assets/gui/frame_red.png').then((texture) => (this.RedBackground = texture)),
this.Load('/assets/gui/frame_blue.png').then((texture) => (this.BlueBackground = texture)), this.Load('./assets/gui/frame_green.png').then((texture) => (this.GreenBackground = texture)),
this.Load('/assets/gui/heart.png').then((texture) => (this.HealthTexture = texture)), this.Load('./assets/gui/frame_blue.png').then((texture) => (this.BlueBackground = texture)),
this.Load('/assets/gui/money.png').then((texture) => (this.GoldTexture = texture)), this.Load('./assets/gui/gem_frame.png').then((texture) => (this.GemFrame = texture)),
this.Load('/assets/gui/wave.png').then((texture) => (this.WaveTexture = texture)), this.Load('./assets/gui/heart.png').then((texture) => (this.HealthTexture = texture)),
this.Load('/assets/gui/sword_02.png').then((texture) => (this.SwordsTexture = texture)), this.Load('./assets/gui/money.png').then((texture) => (this.GoldTexture = texture)),
this.Load('/assets/gui/title01.png').then((texture) => (this.TitleTexture = texture)), this.Load('./assets/gui/wave.png').then((texture) => (this.WaveTexture = texture)),
this.Load('/assets/gui/icons/play.png').then((texture) => (this.PlayIconTexture = texture)), this.Load('./assets/gui/sword_02.png').then((texture) => (this.SwordsTexture = texture)),
this.Load('/assets/gui/icons/pause.png').then((texture) => (this.PauseIconTexture = texture)), this.Load('./assets/gui/title01.png').then((texture) => (this.TitleTexture = texture)),
this.Load('/assets/gui/icons/exclamation.png').then((texture) => (this.ExclamationIconTexture = texture)), this.Load('./assets/gui/icons/play.png').then((texture) => (this.PlayIconTexture = texture)),
this.Load('/assets/gui/icons/home.png').then((texture) => (this.HomeIconTexture = texture)), this.Load('./assets/gui/icons/pause.png').then((texture) => (this.PauseIconTexture = texture)),
this.Load('/assets/gui/icons/hammer.png').then((texture) => (this.HammerIconTexture = texture)), this.Load('./assets/gui/icons/exclamation.png').then((texture) => (this.ExclamationIconTexture = 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/cross.png').then((texture) => (this.XIconTexture = texture)),
this.Load('./assets/gui/icons/plus.png').then((texture) => (this.PlusIconTexture = texture)),
this.LoadMissions(), this.LoadMissions(),
this.LoadTowers(), this.LoadTowers(),
this.LoadCreeps(), this.LoadCreeps(),
this.LoadGemIcons(), this.LoadGems(),
]); ]);
t.destroy(); t.destroy();
this.text.destroy(); this.text.destroy();
// Set this.text = true to disallow calling GameAssets.LoadAssets() again // Set this.text = true to disallow calling GameAssets.LoadAssets() again
this.text = true; this.text = true;
} }
private static async LoadGemIcons() { private static async LoadGems() {
const res = await fetch('./assets/json/Gems.json');
const gems = await res.json();
this.Gems = gems;
for (let idx = 0; idx < gems.length; idx++) {
const gem = this.Gems[idx];
for (let i = 1; i <= gem.totalLevels; i++) {
const texture = await this.Load(`./assets/gems/${gem.type}/${i}.png`);
gem.textures[i - 1] = texture;
}
}
for (let i = 0; i < 7; i++) { for (let i = 0; i < 7; i++) {
this.GemAmountIcons[i] = await this.Load(`/assets/gui/gem_amount_${i}.png`); this.GemAmountIcons[i] = await this.Load(`./assets/gui/gem_amount_${i}.png`);
} }
} }
private static async LoadCreeps() { private static async LoadCreeps() {
const res = await fetch('/assets/json/Creeps.json'); const res = await fetch('./assets/json/Creeps.json');
const creeps = await res.json(); const creeps = await res.json();
this.Creeps = creeps; this.Creeps = creeps;
for (let idx = 0; idx < this.Creeps.length; idx++) { for (let idx = 0; idx < this.Creeps.length; idx++) {
const creep = this.Creeps[idx]; const creep = this.Creeps[idx];
for (let i = 0; i < creep.textureArrayLength; i++) { for (let i = 0; i < creep.textureArrayLength; i++) {
const texture = await this.Load(`/assets/creeps/${creep.name}/${i}.png`); const texture = await this.Load(`./assets/creeps/${creep.name}/${i}.png`);
creep.textures[i] = texture; creep.textures[i] = texture;
} }
} }
@ -130,20 +150,20 @@ export default class GameAssets {
private static async LoadMissions() { private static async LoadMissions() {
// When adding missions, make sure to keep order. // When adding missions, make sure to keep order.
GameAssets.Missions = [await this.LoadMission('/assets/missions/mission_01.json')]; GameAssets.Missions = [await this.LoadMission('./assets/missions/mission_01.json')];
} }
private static async LoadTowers() { private static async LoadTowers() {
const res = await fetch('/assets/json/Towers.json'); const res = await fetch('./assets/json/Towers.json');
const towers = await res.json(); const towers = await res.json();
this.Towers = towers; this.Towers = towers;
for (let idx = 0; idx < this.Towers.length; idx++) { for (let idx = 0; idx < this.Towers.length; idx++) {
const tower = this.Towers[idx]; const tower = this.Towers[idx];
for (let i = 0; i < tower.projectileTexturesArrayLength; i++) { for (let i = 0; i < tower.projectileTexturesArrayLength; i++) {
const projTexture = await this.Load(`/assets/projectiles/${tower.sprite}/${i}.png`); const projTexture = await this.Load(`./assets/projectiles/${tower.sprite}/${i}.png`);
tower.projectileTextures[i] = projTexture; tower.projectileTextures[i] = projTexture;
} }
tower.texture = await this.Load(`/assets/towers/${tower.sprite}.png`); tower.texture = await this.Load(`./assets/towers/${tower.sprite}.png`);
} }
} }

View File

@ -8,6 +8,9 @@ import TowerManager from './game/TowerManager';
import { GameScene } from '../scenes/Game'; import { GameScene } from '../scenes/Game';
import { AnimationManager } from './game/AnimationManager'; import { AnimationManager } from './game/AnimationManager';
import NotificationManager from './game/NotificationManager'; import NotificationManager from './game/NotificationManager';
import Gem from './game/Gem';
import GameAssets from './Assets';
import { GemType } from './Definitions';
export class Engine { export class Engine {
public static app: PIXI.Application; public static app: PIXI.Application;
@ -25,6 +28,15 @@ export class Engine {
public static GridRows: number = 17; public static GridRows: number = 17;
public static MouseX: number = 0; public static MouseX: number = 0;
public static MouseY: number = 0; public static MouseY: number = 0;
public static TestSuite() {
Engine.NotificationManager.Notify('Loaded testing suite.', 'danger');
Engine.TowerManager.ToggleChoosingTowerLocation('RESET');
Engine.TowerManager.PlaceTower(GameAssets.Towers[1], 10, 15, GameAssets.Towers[0].behaviour, true);
for (let i = 0; i < 16; i++) {
this.GameScene.MissionStats.giveGem(new Gem(GemType.Fire), true);
}
}
} }
export default class GameMaster { export default class GameMaster {

View File

@ -70,13 +70,27 @@ export type TowerStatsDefinition = {
range: number; range: number;
}; };
export type PathDefinition = [[row: number, column: number]]; export type GemDefinition = {
name: string;
description: string;
type: GemType;
totalLevels: number;
textures: PIXI.Texture[];
cantCombineWith: GemType[];
specialCombine: GemType[];
genericImprovements: GenericGemImprovement[];
gemResistanceModifications: CreepResistancesDefinition[];
};
export enum CreepType { export type GenericGemImprovement = {
Basic = 0, damageUp: number;
Quick = 1, attackSpeedUp: number;
Tank = 2, rangeUp: number;
} timeToLiveUp: number;
pierceUp: number;
};
export type PathDefinition = [[row: number, column: number]];
export enum TerrainType { export enum TerrainType {
Restricted = 0, Restricted = 0,
@ -84,6 +98,13 @@ export enum TerrainType {
Path = 9, Path = 9,
} }
// Make sure to sync these with the respective JSON files.
export enum CreepType {
Basic = 0,
Quick = 1,
Tank = 2,
}
export enum GemType { export enum GemType {
Fire = 0, Fire = 0,
Yeti = 1, Yeti = 1,

30
src/classes/Events.ts Normal file
View File

@ -0,0 +1,30 @@
export enum WaveManagerEvents {
CreepSpawned = 'creepSpawned',
Finished = 'finished',
NewWave = 'newwave',
}
export enum CreepEvents {
Died = 'died',
TakenDamage = 'takenDamage',
Escaped = 'escaped',
Moved = 'moved',
}
export enum GridEvents {
CellMouseOver = 'cellmouseover',
CellMouseLeave = 'cellmouseleave',
}
export enum TowerEvents {
TowerPlacedEvent = 'towerPlacedEvent',
TowerSoldEvent = 'towerSoldEvent',
}
export enum StatsEvents {
GemGivenEvent = 'gemGivenEvent',
}
export enum GemEvents {
TowerPanelSelectGem = 'towerTabSelectGem',
}

View File

@ -4,13 +4,7 @@ import { Engine } from '../Bastion';
import { CreepStatsDefinition, CreepType, PathDefinition } from '../Definitions'; import { CreepStatsDefinition, CreepType, PathDefinition } from '../Definitions';
import GameObject from '../GameObject'; import GameObject from '../GameObject';
import * as PIXI from 'pixi.js'; import * as PIXI from 'pixi.js';
import { CreepEvents } from '../Events';
export enum CreepEvents {
Died = 'died',
TakenDamage = 'takenDamage',
Escaped = 'escaped',
Moved = 'moved',
}
export default class Creep extends GameObject { export default class Creep extends GameObject {
public id: number; public id: number;

18
src/classes/game/Gem.ts Normal file
View File

@ -0,0 +1,18 @@
import * as PIXI from 'pixi.js';
import { GemType, GemDefinition } from '../Definitions';
import GameAssets from '../Assets';
let latestGemId = 0;
export default class Gem {
public texture: PIXI.Texture;
public level: number = 1;
public definition: GemDefinition;
public id;
constructor(gemType: GemType) {
this.definition = GameAssets.Gems[gemType];
this.texture = this.definition.textures[0];
this.id = latestGemId + 1;
latestGemId++;
}
}

View File

@ -3,13 +3,8 @@ import GameObject from '../GameObject';
import { GameMapDefinition, TerrainType } from '../Definitions'; import { GameMapDefinition, TerrainType } from '../Definitions';
import GameAssets from '../Assets'; import GameAssets from '../Assets';
import { Engine } from '../Bastion'; import { Engine } from '../Bastion';
import Creep, { CreepEvents } from './Creep'; import Creep from './Creep';
import { TowerEvents } from './Tower'; import { CreepEvents, TowerEvents, GridEvents } from '../Events';
export enum GridEvents {
CellMouseOver = 'cellmouseover',
CellMouseLeave = 'cellmouseleave',
}
export class Cell extends GameObject { export class Cell extends GameObject {
public type: TerrainType; public type: TerrainType;
@ -38,7 +33,6 @@ export class Cell extends GameObject {
zIndex: 99, zIndex: 99,
interactive: true, interactive: true,
}); });
// ? TODO: make range preview 1 global graphics obj, child. fix
this.g = new PIXI.Graphics({ this.g = new PIXI.Graphics({
zIndex: 5, zIndex: 5,
@ -48,13 +42,16 @@ export class Cell extends GameObject {
this.container.addChild(this.clickDetector); this.container.addChild(this.clickDetector);
this.container.addChild(this.g); this.container.addChild(this.g);
this.clickDetector.on('pointerup', (e) => { this.clickDetector.on('pointerup', (e) => {
if (!Engine.Grid.gridInteractionEnabled) return;
if (Engine.TowerManager.isPlacingTower) Engine.Grid.onGridCellClicked(row, column); if (Engine.TowerManager.isPlacingTower) Engine.Grid.onGridCellClicked(row, column);
else this.OpenSelectedTowerPanel(); else this.OpenSelectedTowerPanel();
}); });
this.clickDetector.on('pointerenter', (e) => { this.clickDetector.on('pointerenter', (e) => {
if (!Engine.Grid.gridInteractionEnabled) return;
Engine.GameScene.events.emit(GridEvents.CellMouseOver, this); Engine.GameScene.events.emit(GridEvents.CellMouseOver, this);
}); });
this.clickDetector.on('pointerleave', (e) => { this.clickDetector.on('pointerleave', (e) => {
if (!Engine.Grid.gridInteractionEnabled) return;
Engine.GameScene.events.emit(GridEvents.CellMouseLeave, this); Engine.GameScene.events.emit(GridEvents.CellMouseLeave, this);
Engine.Grid.rangePreview.clear(); Engine.Grid.rangePreview.clear();
}); });
@ -83,6 +80,8 @@ export class Cell extends GameObject {
} }
public OpenSelectedTowerPanel() { public OpenSelectedTowerPanel() {
if (this.hasTowerPlaced) { if (this.hasTowerPlaced) {
const tower = Engine.TowerManager.GetTowerByRowAndCol(this.row, this.column);
Engine.GameScene.towerPanel.Show(tower);
} }
} }
public checkIfCantPlace() { public checkIfCantPlace() {
@ -114,6 +113,7 @@ export class Grid extends GameObject {
public rangePreview: PIXI.Graphics; public rangePreview: PIXI.Graphics;
public creeps: Creep[] = []; public creeps: Creep[] = [];
public gridShown: boolean = false; public gridShown: boolean = false;
public gridInteractionEnabled = true;
constructor(map: GameMapDefinition, missionIndex) { constructor(map: GameMapDefinition, missionIndex) {
super(); super();

View File

@ -2,7 +2,8 @@ import Assets from '../Assets';
import { Engine } from '../Bastion'; import { Engine } from '../Bastion';
import GameObject from '../GameObject'; import GameObject from '../GameObject';
import * as PIXI from 'pixi.js'; import * as PIXI from 'pixi.js';
import { WaveManagerEvents } from './WaveManager'; import { WaveManagerEvents, StatsEvents } from '../Events';
import Gem from './Gem';
export default class MissionStats extends GameObject { export default class MissionStats extends GameObject {
private hp: number = 100; private hp: number = 100;
@ -10,6 +11,10 @@ export default class MissionStats extends GameObject {
private goldText: PIXI.Text; private goldText: PIXI.Text;
private healthText: PIXI.Text; private healthText: PIXI.Text;
private waveText: PIXI.Text; private waveText: PIXI.Text;
private inventory: Gem[] = [];
// TODO: implement score keeping for leaderboards.
private score: number = 0;
public getHP() { public getHP() {
return this.hp; return this.hp;
@ -44,6 +49,33 @@ export default class MissionStats extends GameObject {
this.goldText.text = this.gold; this.goldText.text = this.gold;
} }
public giveGem(gem: Gem, noNotify?) {
if (this.inventory.length >= 48)
return Engine.NotificationManager.Notify(
"Can't hold more than 48 Gems. Extra Gem was thrown away.",
'danger'
);
this.inventory.push(gem);
if (!noNotify)
Engine.NotificationManager.Notify(
`Lv. ${gem.level} ${gem.definition.name}` + ' added to your inventory.',
'gemaward'
);
Engine.GameScene.events.emit(StatsEvents.GemGivenEvent, gem);
}
public takeGem(gem) {
return this.inventory.splice(this.inventory.indexOf(gem), 1)[0];
}
public getInventory() {
return this.inventory;
}
public checkIfPlayerHasAnyGems() {
return this.inventory.length > 0;
}
constructor(initialHP: number, initialGold: number) { constructor(initialHP: number, initialGold: number) {
super(); super();
this.hp = initialHP; this.hp = initialHP;

View File

@ -3,7 +3,7 @@ import GameObject from '../GameObject';
import * as PIXI from 'pixi.js'; import * as PIXI from 'pixi.js';
import { FadeInOut } from './AnimationManager'; import { FadeInOut } from './AnimationManager';
export type NotificationType = 'info' | 'warn' | 'danger' | 'reward'; export type NotificationType = 'info' | 'warn' | 'danger' | 'reward' | 'gemaward';
class Notification { class Notification {
public textObj: PIXI.Text; public textObj: PIXI.Text;
@ -20,6 +20,8 @@ class Notification {
fill = 0xfc0a0a; fill = 0xfc0a0a;
} else if (type == 'reward') { } else if (type == 'reward') {
fill = 0xd65afc; fill = 0xd65afc;
} else if (type == 'gemaward') {
fill = 0xffffff;
} }
this.ticksToFadeAway = ticksToFadeAway; this.ticksToFadeAway = ticksToFadeAway;
this.textObj = new PIXI.Text({ this.textObj = new PIXI.Text({
@ -53,7 +55,7 @@ export default class NotificationManager extends GameObject {
this.bb.x = Engine.app.canvas.width / 2; this.bb.x = Engine.app.canvas.width / 2;
this.bb.y = 40; this.bb.y = 40;
this.copyBBToContainer(); this.copyBBToContainer();
this.container.zIndex = 100; this.container.zIndex = 200;
Engine.app.stage.addChild(this.container); Engine.app.stage.addChild(this.container);
} }
public Notify(text, type: NotificationType) { public Notify(text, type: NotificationType) {
@ -72,7 +74,7 @@ export default class NotificationManager extends GameObject {
if (this.ticks >= notif.ticksToFadeAway && !notif.animating) { if (this.ticks >= notif.ticksToFadeAway && !notif.animating) {
notif.animating = true; notif.animating = true;
Engine.AnimationManager.Animate( Engine.AnimationManager.Animate(
new FadeInOut('out', 240, notif.textObj, () => { new FadeInOut('out', 300, notif.textObj, () => {
notif.destroy(); notif.destroy();
}) })
); );

View File

@ -1,7 +1,8 @@
import * as PIXI from 'pixi.js'; import * as PIXI from 'pixi.js';
import GameObject from '../GameObject'; import GameObject from '../GameObject';
import { Engine } from '../Bastion'; import { Engine } from '../Bastion';
import Creep, { CreepEvents } from './Creep'; import Creep from './Creep';
import { CreepEvents } from '../Events';
export function calculateAngleToPoint(x, y, targetX, targetY) { export function calculateAngleToPoint(x, y, targetX, targetY) {
const dx = targetX - x; const dx = targetX - x;

View File

@ -5,39 +5,21 @@ import { TowerDefinition } from '../Definitions';
import { Cell } from './Grid'; import { Cell } from './Grid';
import { TowerBehaviours } from './TowerManager'; import { TowerBehaviours } from './TowerManager';
import Projectile, { calculateAngleToPoint } from './Projectile'; import Projectile, { calculateAngleToPoint } from './Projectile';
import GameAssets from '../Assets';
import Creep from './Creep'; import Creep from './Creep';
import Gem from './Gem';
function distance(x1, y1, x2, y2) { function distance(x1, y1, x2, y2) {
return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
} }
export type TowerInstance = {
row: number;
column: number;
sprite: PIXI.Sprite;
projectiles: Array<any>;
baseDamage: number;
damage: number;
cooldown: number;
ticksToFireAt: number;
slottedGems: Array<any>;
cost: number;
baseRange: number;
range: number;
};
export enum TowerEvents {
TowerPlacedEvent = 'towerPlacedEvent',
TowerSoldEvent = 'towerSoldEvent',
}
export class Tower extends GameObject { export class Tower extends GameObject {
public row: number; public row: number;
public column: number; public column: number;
public definition: TowerDefinition;
public slottedGems: Array<Gem> = [];
public damageDealt: number = 0;
private projectiles: Projectile[] = []; private projectiles: Projectile[] = [];
private behaviour: string; private behaviour: string;
private definition: TowerDefinition;
private sprite: PIXI.Sprite; private sprite: PIXI.Sprite;
private ticksUntilNextShot: number; private ticksUntilNextShot: number;
private graphics: PIXI.Graphics = new PIXI.Graphics(); private graphics: PIXI.Graphics = new PIXI.Graphics();
@ -51,12 +33,11 @@ export class Tower extends GameObject {
this.definition = definition; this.definition = definition;
this.ticksUntilNextShot = 0; this.ticksUntilNextShot = 0;
this.parent = Engine.Grid.getCellByRowAndCol(row, column); this.parent = Engine.Grid.getCellByRowAndCol(row, column);
console.log(texture);
this.sprite = new PIXI.Sprite({ this.sprite = new PIXI.Sprite({
texture: texture, texture: texture,
height: Engine.GridCellSize, height: Engine.GridCellSize,
width: Engine.GridCellSize, width: Engine.GridCellSize,
zIndex: 10, zIndex: 130,
}); });
this.container.addChild(this.sprite); this.container.addChild(this.sprite);
this.parent.container.addChild(this.container); this.parent.container.addChild(this.container);
@ -67,12 +48,34 @@ export class Tower extends GameObject {
} }
private onParentCellEnter = (e) => { private onParentCellEnter = (e) => {
if (!Engine.TowerManager.isPlacingTower) this.parent.showRangePreview(false, this.definition.stats.range); if (!Engine.TowerManager.isPlacingTower && Engine.Grid.gridInteractionEnabled)
this.parent.showRangePreview(false, this.definition.stats.range);
}; };
private onParentCellLeave = (e) => { private onParentCellLeave = (e) => {
this.graphics.clear(); this.graphics.clear();
}; };
public SlotGem(gem: Gem, index: number) {
console.log('ATTEMPTING TO SLOT ', gem, index);
this.slottedGems[index] = gem;
Engine.GameScene.towerPanel.Hide();
Engine.GameScene.towerPanel.Show(this);
}
public UnslotGem(index) {
const gem = this.slottedGems.splice(index, 1)[0];
Engine.GameScene.MissionStats.giveGem(gem, true);
for (let i = index; i < this.slottedGems.length - 1; i++) {
if (this.slottedGems[i] == null) {
this.slottedGems[i] = this.slottedGems[i + 1];
this.slottedGems[i + 1] = null;
}
}
this.slottedGems = this.slottedGems.filter((gem) => gem != null);
Engine.NotificationManager.Notify(
`Lv. ${gem.level} ${gem.definition.name} unslotted and placed back in your inventory.`,
'info'
);
}
public GetCreepsInRange() { public GetCreepsInRange() {
let creeps = Engine.Grid.creeps; let creeps = Engine.Grid.creeps;
@ -97,6 +100,7 @@ export class Tower extends GameObject {
public update(elapsedMS: any): void { public update(elapsedMS: any): void {
this.projectiles.forEach((proj) => { this.projectiles.forEach((proj) => {
if (proj.deleteMe) { if (proj.deleteMe) {
this.damageDealt += this.definition.stats.damage;
this.projectiles.splice(this.projectiles.indexOf(proj), 1); this.projectiles.splice(this.projectiles.indexOf(proj), 1);
proj = null; proj = null;
} else proj.update(elapsedMS); } else proj.update(elapsedMS);
@ -114,7 +118,7 @@ export class Tower extends GameObject {
} }
} }
override destroy(): void { public destroy(): void {
super.destroy(); super.destroy();
this.parent.clickDetector.off('pointerenter', this.onParentCellEnter); this.parent.clickDetector.off('pointerenter', this.onParentCellEnter);
this.parent.clickDetector.off('pointerleave', this.onParentCellLeave); this.parent.clickDetector.off('pointerleave', this.onParentCellLeave);

View File

@ -2,8 +2,9 @@ import * as PIXI from 'pixi.js';
import { Engine } from '../Bastion'; import { Engine } from '../Bastion';
import { TerrainType, TowerDefinition } from '../Definitions'; import { TerrainType, TowerDefinition } from '../Definitions';
import GameAssets from '../Assets'; import GameAssets from '../Assets';
import { Tower, TowerEvents } from './Tower'; import { Tower } from './Tower';
import { Cell, GridEvents } from './Grid'; import { Cell } from './Grid';
import { GridEvents, TowerEvents } from '../Events';
export enum TowerBehaviours { export enum TowerBehaviours {
BasicTowerBehaviour = 'BasicTowerBehaviour', BasicTowerBehaviour = 'BasicTowerBehaviour',
@ -71,7 +72,7 @@ export default class TowerManager {
'TowerManager.selectedTower is null when trying to place tower.', 'TowerManager.selectedTower is null when trying to place tower.',
'danger' 'danger'
); );
throw console.warn('TowerManager.selectedTower is null when trying to place tower.'); return console.warn('TowerManager.selectedTower is null when trying to place tower.');
} }
this.PlaceTower(this.selectedTower, row, column, this.selectedTower.behaviour); this.PlaceTower(this.selectedTower, row, column, this.selectedTower.behaviour);
} }
@ -87,7 +88,7 @@ export default class TowerManager {
return returnTower; return returnTower;
} }
public PlaceTower(definition: TowerDefinition, row, column, behaviour: string, ignoreCost?) { public PlaceTower(definition: TowerDefinition, row, column, behaviour: string, ignoreCost?) {
const sprite = this.selectedTower.texture; const sprite = definition.texture;
if (!Engine.GameScene.MissionStats.hasEnoughGold(definition.stats.cost) && !ignoreCost) if (!Engine.GameScene.MissionStats.hasEnoughGold(definition.stats.cost) && !ignoreCost)
return Engine.NotificationManager.Notify('Not enough gold.', 'warn'); return Engine.NotificationManager.Notify('Not enough gold.', 'warn');
if ( if (
@ -95,7 +96,7 @@ export default class TowerManager {
Engine.Grid.getCellByRowAndCol(row, column).type != TerrainType.Path && Engine.Grid.getCellByRowAndCol(row, column).type != TerrainType.Path &&
Engine.Grid.getCellByRowAndCol(row, column).type != TerrainType.Restricted Engine.Grid.getCellByRowAndCol(row, column).type != TerrainType.Restricted
) { ) {
Engine.GameScene.MissionStats.spendGold(definition.stats.cost); if (!ignoreCost) Engine.GameScene.MissionStats.spendGold(definition.stats.cost);
let tower = new Tower(row, column, sprite, definition, behaviour); let tower = new Tower(row, column, sprite, definition, behaviour);
this.towers.push(tower); this.towers.push(tower);
this.ToggleChoosingTowerLocation('RESET'); this.ToggleChoosingTowerLocation('RESET');

View File

@ -1,15 +1,9 @@
import { CreepType, MissionRoundDefinition, PathDefinition } from '../Definitions'; import { CreepType, MissionRoundDefinition, PathDefinition } from '../Definitions';
import * as PIXI from 'pixi.js'; import Creep from './Creep';
import Creep, { CreepEvents } from './Creep';
import { Engine } from '../Bastion'; import { Engine } from '../Bastion';
import { WaveManagerEvents } from '../Events';
import GameObject from '../GameObject'; import GameObject from '../GameObject';
export enum WaveManagerEvents {
CreepSpawned = 'creepSpawned',
Finished = 'finished',
NewWave = 'newwave',
}
type CreepInstance = { type CreepInstance = {
creep: Creep; creep: Creep;
tickToSpawnAt: number; tickToSpawnAt: number;

View File

@ -1,10 +1,19 @@
import * as PIXI from 'pixi.js'; import * as PIXI from 'pixi.js';
import GuiObject from '../GuiObject'; import GuiObject from '../GuiObject';
import GameAssets from '../Assets'; import GameAssets from '../Assets';
import { Engine } from '../Bastion';
import { StatsEvents } from '../Events';
import Gem from '../game/Gem';
import { VisualGemSlot } from './TowerPanel';
import { Tower } from '../game/Tower';
export default class GemTab extends GuiObject { export default class GemTab extends GuiObject {
private bounds: PIXI.Rectangle; private bounds: PIXI.Rectangle;
private gemTabSprite: PIXI.NineSliceSprite; private gemTabSprite: PIXI.NineSliceSprite;
private vGems: VisualGemSlot[] = [];
public isSelectingGem: boolean = false;
public selectingGemSlotIndex: number = -1;
public selectingGemTowerObject: Tower = null;
constructor(bounds: PIXI.Rectangle) { constructor(bounds: PIXI.Rectangle) {
super(false); super(false);
@ -22,7 +31,79 @@ export default class GemTab extends GuiObject {
this.gemTabSprite.y = 0; this.gemTabSprite.y = 0;
this.gemTabSprite.width = this.bounds.width; this.gemTabSprite.width = this.bounds.width;
this.gemTabSprite.height = this.bounds.height; this.gemTabSprite.height = this.bounds.height;
this.container.addChild(this.gemTabSprite); this.container.addChild(this.gemTabSprite);
Engine.GameScene.events.on(StatsEvents.GemGivenEvent, () => {
this.RebuildInventoryVisual();
});
}
// TODO: add more visual clarity
public TowerPanelSelectingGem(gem: Gem, index: number, tower: Tower) {
console.log('TOWER PANEL SELECTING GEM ' + index);
if (index < 0) console.error('TOWER PANEL SELECTING GEM INDEX IS LESS THAN 0, ', index);
// index = Engine.GameScene.towerPanel.vGems.indexOf(gem);
if (!this.isSelectingGem) {
this.isSelectingGem = true;
if (gem == null) {
// Want to select gem to slot in, already checked if player has a Gem.
Engine.NotificationManager.Notify(
'Click on any Gem in your inventory to slot it into this Gem slot.',
'info'
);
this.selectingGemSlotIndex = index;
this.selectingGemTowerObject = tower;
} else {
// Already have a gem selected
tower.UnslotGem(index);
this.RebuildInventoryVisual();
Engine.GameScene.towerPanel.Hide();
Engine.GameScene.towerPanel.Show(tower);
this.isSelectingGem = false;
this.selectingGemSlotIndex = -1;
this.selectingGemTowerObject = null;
}
} else {
if (gem == null) {
this.isSelectingGem = false;
this.selectingGemSlotIndex = -1;
this.selectingGemTowerObject = null;
}
}
}
public RebuildInventoryVisual() {
this.vGems.forEach((vGem) => vGem.destroy());
this.vGems = [];
Engine.GameScene.MissionStats.getInventory().forEach((gem, index) => {
let vGem = new VisualGemSlot(0, this.container, gem);
let vGemYCoord = 10;
let vGemXCoord = (index % 4) * 70 + 10;
let vGemYIdx = index;
while (true) {
if (vGemYIdx <= 3) break;
vGemYCoord += 66;
vGemYIdx -= 4;
}
vGem.container.x = vGemXCoord;
vGem.container.y = vGemYCoord;
vGem.container.onpointermove = () => {
if (gem == null) return;
Engine.GameScene.tooltip.SetContentGem(gem);
Engine.GameScene.tooltip.Show(Engine.MouseX, Engine.MouseY);
};
vGem.container.onpointerleave = () => {
Engine.GameScene.tooltip.Hide();
};
vGem.onClick = () => {
Engine.GameScene.tooltip.Hide();
if (this.isSelectingGem) {
this.isSelectingGem = false;
let takenGem = Engine.GameScene.MissionStats.takeGem(gem);
this.selectingGemTowerObject.SlotGem(takenGem, this.selectingGemSlotIndex);
this.RebuildInventoryVisual();
}
};
this.vGems.push(vGem);
});
} }
} }

View File

@ -1,32 +0,0 @@
import * as PIXI from 'pixi.js';
import GuiObject from '../GuiObject';
import GameAssets from '../Assets';
import { Engine } from '../Bastion';
// ! TODO NEXT!
export default class SelectedTowerPanel extends GuiObject {
private bounds: PIXI.Rectangle;
private towerPanel: PIXI.NineSliceSprite;
constructor(bounds: PIXI.Rectangle) {
super(false);
this.bounds = bounds;
this.container.x = this.bounds.x;
this.container.y = this.bounds.y;
this.towerPanel = new PIXI.NineSliceSprite({
texture: GameAssets.FrameTowerTab,
leftWidth: 1000,
topHeight: 1000,
rightWidth: 1000,
bottomHeight: 1000,
});
this.towerPanel.x = -300;
this.towerPanel.y = -300;
this.towerPanel.width = this.bounds.width;
this.towerPanel.height = this.bounds.height;
this.container.addChild(this.towerPanel);
Engine.GameMaster.currentScene.stage.addChild(this.container);
}
}

View File

@ -6,9 +6,9 @@ import GemTab from './GemTab';
export default class Sidebar extends GuiObject { export default class Sidebar extends GuiObject {
public towerTab: TowerTab; public towerTab: TowerTab;
public gemTab: GemTab;
private bounds: PIXI.Rectangle; private bounds: PIXI.Rectangle;
private sidebarSprite: PIXI.NineSliceSprite; private sidebarSprite: PIXI.NineSliceSprite;
private gemTab: GemTab;
constructor(bounds: PIXI.Rectangle) { constructor(bounds: PIXI.Rectangle) {
super(false); super(false);

View File

@ -2,6 +2,7 @@ import * as PIXI from 'pixi.js';
import GuiObject from '../GuiObject'; import GuiObject from '../GuiObject';
import GameAssets from '../Assets'; import GameAssets from '../Assets';
import { Engine } from '../Bastion'; import { Engine } from '../Bastion';
import Gem from '../game/Gem';
// ! TODO NEXT! // ! TODO NEXT!
@ -14,12 +15,17 @@ export default class Tooltip extends GuiObject {
private damageText: PIXI.Text; private damageText: PIXI.Text;
private gemAmount: PIXI.Text; private gemAmount: PIXI.Text;
private gemAmountSprite: PIXI.Sprite; private gemAmountSprite: PIXI.Sprite;
private title: PIXI.Sprite;
private costSprite: PIXI.Sprite;
private damageSprite: PIXI.Sprite;
private gemDescriptionText: PIXI.Text;
constructor(bounds: PIXI.Rectangle) { constructor(bounds: PIXI.Rectangle) {
super(false); super(false);
this.bounds = bounds; this.bounds = bounds;
this.container.x = -500; this.container.x = -500;
this.container.y = -500; this.container.y = -500;
this.container.zIndex = 150;
this.tooltipSprite = new PIXI.NineSliceSprite({ this.tooltipSprite = new PIXI.NineSliceSprite({
texture: GameAssets.Frame04Texture, texture: GameAssets.Frame04Texture,
leftWidth: 200, leftWidth: 200,
@ -43,16 +49,16 @@ export default class Tooltip extends GuiObject {
}), }),
}); });
this.titleText.anchor.set(0.5, 0); this.titleText.anchor.set(0.5, 0);
let title = new PIXI.Sprite({ this.title = new PIXI.Sprite({
x: this.tooltipSprite.width / 2, x: this.tooltipSprite.width / 2,
y: -20, y: -20,
width: 250, width: 250,
height: 40, height: 40,
texture: GameAssets.TitleTexture, texture: GameAssets.TitleTexture,
}); });
title.anchor.set(0.5, 0); this.title.anchor.set(0.5, 0);
const costSprite = new PIXI.Sprite({ this.costSprite = new PIXI.Sprite({
texture: GameAssets.GoldTexture, texture: GameAssets.GoldTexture,
x: 10, x: 10,
y: 20, y: 20,
@ -88,7 +94,7 @@ export default class Tooltip extends GuiObject {
}, },
}, },
}); });
const damageSprite = new PIXI.Sprite({ this.damageSprite = new PIXI.Sprite({
texture: GameAssets.SwordsTexture, texture: GameAssets.SwordsTexture,
x: 22, x: 22,
y: 70, y: 70,
@ -105,7 +111,7 @@ export default class Tooltip extends GuiObject {
}); });
this.gemAmount = new PIXI.Text({ this.gemAmount = new PIXI.Text({
x: 54, x: 54,
y: 108, y: 105,
zIndex: 5, zIndex: 5,
text: 'Something went wrong if you see this.', text: 'Something went wrong if you see this.',
style: { style: {
@ -117,26 +123,64 @@ export default class Tooltip extends GuiObject {
}, },
}, },
}); });
this.gemDescriptionText = new PIXI.Text({
x: 10,
y: 20,
text: '',
style: {
fontSize: 18,
wordWrap: true,
wordWrapWidth: this.tooltipSprite.width - 30,
fill: 'white',
fontWeight: 'bold',
fontStyle: 'italic',
stroke: {
color: 0x000000,
width: 5,
},
},
});
this.container.addChild(this.tooltipSprite); this.container.addChild(this.tooltipSprite);
this.container.addChild(title); this.container.addChild(this.title);
this.container.addChild(costSprite); this.container.addChild(this.costSprite);
this.container.addChild(damageSprite); this.container.addChild(this.damageSprite);
this.container.addChild(this.gemAmountSprite); this.container.addChild(this.gemAmountSprite);
this.container.addChild(this.costText); this.container.addChild(this.costText);
this.container.addChild(this.titleText); this.container.addChild(this.titleText);
this.container.addChild(this.damageText); this.container.addChild(this.damageText);
this.container.addChild(this.gemAmount); this.container.addChild(this.gemAmount);
Engine.GameMaster.currentScene.stage.addChild(this.container); this.container.addChild(this.gemDescriptionText);
Engine.app.stage.addChildAt(this.container, 0);
} }
public SetContent(title, damage: number, cost: number, gemSlotsAmount: number) { public SetContentTower(title, damage: number, cost: number, gemSlotsAmount: number) {
this.costSprite.alpha = 1;
this.damageSprite.alpha = 1;
this.gemAmountSprite.alpha = 1;
this.costText.alpha = 1;
this.damageText.alpha = 1;
this.gemAmount.alpha = 1;
this.gemDescriptionText.alpha = 0;
this.titleText.text = title; this.titleText.text = title;
this.gemAmount.text = `Has ${gemSlotsAmount} Gem slots.`; this.gemAmount.text = `Has ${gemSlotsAmount} Gem slots.`;
this.gemAmountSprite.texture = GameAssets.GemAmountIcons[gemSlotsAmount]; this.gemAmountSprite.texture = GameAssets.GemAmountIcons[gemSlotsAmount];
this.costText.text = `Costs ${cost} gold.`; this.costText.text = `Costs ${cost} gold.`;
this.damageText.text = `Deals ${damage} base damage.`; this.damageText.text = `Deals ${damage} base damage.`;
} }
public SetContentGem(gem: Gem) {
this.costSprite.alpha = 0;
this.damageSprite.alpha = 0;
this.gemAmountSprite.alpha = 0;
this.costText.alpha = 0;
this.damageText.alpha = 0;
this.gemAmount.alpha = 0;
this.gemDescriptionText.alpha = 1;
this.titleText.text = `Lv. ${gem.level} ` + gem.definition.name;
this.gemDescriptionText.text = gem.definition.description;
}
public Show(x, y) { public Show(x, y) {
this.container.alpha = 1; this.container.alpha = 1;
if (x + this.container.width > Engine.app.canvas.width) { if (x + this.container.width > Engine.app.canvas.width) {

View File

@ -0,0 +1,205 @@
import * as PIXI from 'pixi.js';
import GuiObject from '../GuiObject';
import GameAssets from '../Assets';
import { Engine } from '../Bastion';
import GameUIConstants from '../GameUIConstants';
import Button, { ButtonTexture } from './Button';
import { Tower } from '../game/Tower';
import Gem from '../game/Gem';
import { GemEvents } from '../Events';
export class VisualGemSlot extends GuiObject {
public iconSprite: PIXI.Sprite;
private background: PIXI.Sprite;
private frame: PIXI.Sprite;
public i: number = 0;
constructor(index: number, parent: PIXI.Container, gem: Gem | null) {
super(true);
let gtexture;
this.i = index;
this.container.x = 10;
this.container.y = index * (Engine.GridCellSize + 6) + 300;
this.background = new PIXI.Sprite({
texture: GameAssets.Frame01Texture,
});
if (gem == null) {
gtexture = GameAssets.PlusIconTexture;
} else {
gtexture = gem.texture;
}
this.iconSprite = new PIXI.Sprite({
texture: gtexture,
zIndex: 10,
});
this.background.width = Engine.GridCellSize;
this.background.height = Engine.GridCellSize;
if (gem == null) {
this.iconSprite.x = Engine.GridCellSize / 2;
this.iconSprite.y = Engine.GridCellSize / 2;
this.iconSprite.width = Engine.GridCellSize / 2;
this.iconSprite.height = Engine.GridCellSize / 2;
this.iconSprite.anchor.set(0.5, 0.5);
} else {
this.iconSprite.x = 4;
this.iconSprite.y = 4;
this.iconSprite.width = Engine.GridCellSize - 8;
this.iconSprite.height = Engine.GridCellSize - 8;
}
this.frame = new PIXI.Sprite({
texture: GameAssets.Frame05Texture,
width: 64,
height: 64,
});
this.container.addChild(this.background);
this.container.addChild(this.iconSprite);
this.container.addChild(this.frame);
if (Engine.latestCommit == 'DEVELOPMENT') {
let txt = gem ? gem.id : '';
let dbgText = new PIXI.Text({
text: txt,
zIndex: 11,
style: {
fill: 'white',
stroke: {
color: 0x000000,
width: 5,
},
},
});
this.container.addChild(dbgText);
}
parent.addChild(this.container);
}
public setTint(color) {
this.frame.tint = color;
}
public resetTint() {
this.frame.tint = 0xffffff;
}
}
export default class TowerPanel extends GuiObject {
private bounds: PIXI.Rectangle;
private towerPanel: PIXI.NineSliceSprite;
private closeBtn: Button;
public vGems: VisualGemSlot[] = [];
public isShown: boolean = false;
public titleText: PIXI.Text;
constructor(bounds: PIXI.Rectangle) {
super(false);
this.bounds = bounds;
this.towerPanel = new PIXI.NineSliceSprite({
texture: GameAssets.Frame03Texture,
leftWidth: 100,
topHeight: 100,
rightWidth: 100,
bottomHeight: 100,
});
this.towerPanel.width = this.bounds.width;
this.towerPanel.height = this.bounds.height - this.bounds.height / 3.5;
this.closeBtn = new Button(new PIXI.Rectangle(-20, -20, 60, 60), '', ButtonTexture.Button01, true);
this.closeBtn.container.removeFromParent();
// Added custom button logic to still keep all the regular events for the button, just have an icon instead of text.
// TODO: maybe make this better? add like a seperate class for icon buttons or smth
this.closeBtn.CustomButtonLogic = () => {
this.closeBtn.buttonIcon = new PIXI.Sprite({
texture: GameAssets.XIconTexture,
x: this.closeBtn.container.width / 2,
y: this.closeBtn.container.height / 2,
scale: 0.2,
});
this.closeBtn.buttonIcon.anchor.set(0.5, 0.5);
this.closeBtn.container.addChild(this.closeBtn.buttonIcon);
};
this.closeBtn.onClick = () => {
this.Hide();
};
this.Hide();
this.closeBtn.CustomButtonLogic();
this.container.y = Engine.app.canvas.height / 2 - Engine.app.canvas.height / 2.7;
this.container.addChild(this.towerPanel);
this.container.addChild(this.closeBtn.container);
Engine.GameMaster.currentScene.stage.addChild(this.container);
this.titleText = new PIXI.Text({
x: this.bounds.width / 3,
y: 50,
zIndex: 5,
style: new PIXI.TextStyle({
fill: 0xffffff,
stroke: {
color: 0x000000,
width: 2,
},
}),
});
this.titleText.anchor.set(0.5, 0);
this.container.addChild(this.titleText);
}
private MakeSlots(tower: Tower) {
this.vGems.forEach((vGem) => {
vGem.destroy();
});
this.vGems = [];
let amount = tower.definition.stats.gemSlotsAmount;
// amount = 6;
for (let i = 0; i < amount; i++) {
console.log('BUILDING TOWER PANEL ' + i);
let gem = tower.slottedGems[i];
if (!gem) gem = null;
const vGem = new VisualGemSlot(i, this.container, gem);
vGem.resetTint();
this.vGems.push(vGem);
vGem.container.onpointermove = () => {
if (!gem) return;
Engine.GameScene.tooltip.SetContentGem(gem);
Engine.GameScene.tooltip.Show(Engine.MouseX, Engine.MouseY);
};
vGem.container.onpointerleave = () => {
Engine.GameScene.tooltip.Hide();
};
vGem.onClick = () => {
Engine.GameScene.tooltip.Hide();
console.warn('EMITTING TOWER PANEL SELECT GEM', gem, vGem.i, i, tower);
Engine.GameScene.events.emit(GemEvents.TowerPanelSelectGem, gem, vGem.i, tower);
if (!gem && Engine.GameScene.sidebar.gemTab.isSelectingGem) vGem.setTint(0x00ffff);
else vGem.resetTint();
};
}
}
public Show(tower: Tower) {
let mouseX = Engine.MouseX;
this.isShown = true;
this.SetContent(tower);
this.MakeSlots(tower);
if (tower.container.x < 900) {
this.ShowRight();
} else {
this.ShowLeft();
}
}
private SetContent(tower: Tower) {
this.titleText.text = tower.definition.name;
}
private ShowLeft() {
this.towerPanel.x = -100;
this.container.x = 0;
this.container.alpha = 1;
this.closeBtn.container.x = this.bounds.width - 150;
}
private ShowRight() {
this.towerPanel.x = -10;
this.container.x = GameUIConstants.SidebarRect.x - 210;
this.closeBtn.container.x = -20;
this.container.alpha = 1;
}
public Hide() {
this.isShown = false;
this.container.alpha = 0;
this.container.x = GameUIConstants.SidebarRect.x + 10;
}
}

View File

@ -2,7 +2,7 @@ import * as PIXI from 'pixi.js';
import GuiObject from '../GuiObject'; import GuiObject from '../GuiObject';
import GameAssets from '../Assets'; import GameAssets from '../Assets';
import { Engine } from '../Bastion'; import { Engine } from '../Bastion';
import { TowerEvents } from '../game/Tower'; import { TowerEvents } from '../Events';
import { TowerDefinition } from '../Definitions'; import { TowerDefinition } from '../Definitions';
class TowerButton extends GuiObject { class TowerButton extends GuiObject {
@ -47,7 +47,8 @@ class TowerButton extends GuiObject {
Engine.GameScene.events.on(TowerEvents.TowerPlacedEvent, (name) => { Engine.GameScene.events.on(TowerEvents.TowerPlacedEvent, (name) => {
this.resetTint(); this.resetTint();
}); });
this.container.onmousemove = (e) => { this.container.onpointermove = (e) => {
if (Engine.Grid.gridInteractionEnabled == false) return;
if (Engine.TowerManager.isPlacingTower) return; if (Engine.TowerManager.isPlacingTower) return;
this.ShowTooltip(); this.ShowTooltip();
}; };
@ -63,7 +64,7 @@ class TowerButton extends GuiObject {
definition = item; definition = item;
} }
}); });
Engine.GameScene.tooltip.SetContent( Engine.GameScene.tooltip.SetContentTower(
this.towerName, this.towerName,
definition.stats.damage, definition.stats.damage,
definition.stats.cost, definition.stats.cost,
@ -72,10 +73,12 @@ class TowerButton extends GuiObject {
Engine.GameScene.tooltip.Show(Engine.MouseX, Engine.MouseY); Engine.GameScene.tooltip.Show(Engine.MouseX, Engine.MouseY);
} }
public onClick(e: PIXI.FederatedPointerEvent): void { public onClick(e: PIXI.FederatedPointerEvent): void {
if (Engine.Grid.gridInteractionEnabled == false) return;
if (Engine.TowerManager.isPlacingTower && Engine.TowerManager.selectedTower.name != this.towerName) { if (Engine.TowerManager.isPlacingTower && Engine.TowerManager.selectedTower.name != this.towerName) {
Engine.GameScene.sidebar.towerTab.resetTint(); Engine.GameScene.sidebar.towerTab.resetTint();
Engine.TowerManager.ResetChooseTower(); Engine.TowerManager.ResetChooseTower();
} }
Engine.GameScene.towerPanel.Hide();
Engine.GameScene.tooltip.Hide(); Engine.GameScene.tooltip.Hide();
if (this.frameSprite.tint == 0x00ff00) { if (this.frameSprite.tint == 0x00ff00) {
this.frameSprite.tint = 0xffffff; this.frameSprite.tint = 0xffffff;

View File

@ -67,4 +67,5 @@ import GameUIConstants from './classes/GameUIConstants';
window.onbeforeunload = () => { window.onbeforeunload = () => {
return 'You are about to leave.'; return 'You are about to leave.';
}; };
else Engine.TestSuite();
})(); })();

View File

@ -1,9 +1,10 @@
import GameAssets from '../classes/Assets'; import GameAssets from '../classes/Assets';
import { Engine } from '../classes/Bastion'; import { Engine } from '../classes/Bastion';
import { MissionDefinition } from '../classes/Definitions'; import { MissionDefinition } from '../classes/Definitions';
import Creep, { CreepEvents } from '../classes/game/Creep'; import Creep from '../classes/game/Creep';
import { Grid } from '../classes/game/Grid'; import { Grid } from '../classes/game/Grid';
import WaveManager, { WaveManagerEvents } from '../classes/game/WaveManager'; import WaveManager from '../classes/game/WaveManager';
import { WaveManagerEvents, CreepEvents, GemEvents } from '../classes/Events';
import Sidebar from '../classes/gui/Sidebar'; import Sidebar from '../classes/gui/Sidebar';
import Button, { ButtonTexture } from '../classes/gui/Button'; import Button, { ButtonTexture } from '../classes/gui/Button';
import Scene from './Scene'; import Scene from './Scene';
@ -13,10 +14,13 @@ import TowerManager from '../classes/game/TowerManager';
import { MissionPickerScene } from './MissionPicker'; import { MissionPickerScene } from './MissionPicker';
import GameUIConstants from '../classes/GameUIConstants'; import GameUIConstants from '../classes/GameUIConstants';
import Tooltip from '../classes/gui/Tooltip'; import Tooltip from '../classes/gui/Tooltip';
import TowerPanel, { VisualGemSlot } from '../classes/gui/TowerPanel';
import Gem from '../classes/game/Gem';
enum RoundMode { enum RoundMode {
Purchase = 0, Purchase = 0,
Combat = 1, Combat = 1,
OfferingGems = 2,
} }
export class GameScene extends Scene { export class GameScene extends Scene {
@ -29,10 +33,18 @@ export class GameScene extends Scene {
public changeRoundButton: Button; public changeRoundButton: Button;
public sidebar: Sidebar; public sidebar: Sidebar;
public tooltip: Tooltip; public tooltip: Tooltip;
public towerPanel: TowerPanel;
private visualGems: VisualGemSlot[] = [];
private currentRound: number = 0; private currentRound: number = 0;
private isWaveManagerFinished: boolean = false; private isWaveManagerFinished: boolean = false;
private playerWon: boolean = false; private playerWon: boolean = false;
private destroyTicker: boolean = false; private destroyTicker: boolean = false;
private offerGemsSprite: PIXI.NineSliceSprite;
private dimGraphics: PIXI.Graphics = new PIXI.Graphics({
x: 0,
y: 0,
zIndex: 120,
});
constructor(name: string) { constructor(name: string) {
super(); super();
@ -45,13 +57,6 @@ export class GameScene extends Scene {
}); });
} }
public init() { public init() {
this.ticker = new PIXI.Ticker();
this.ticker.maxFPS = 60;
this.ticker.minFPS = 30;
this.ticker.add(() => {
if (this.update) this.update(this.ticker.elapsedMS);
});
this.ticker.start();
new Grid(this.mission.gameMap, this.missionIndex); new Grid(this.mission.gameMap, this.missionIndex);
new TowerManager(); new TowerManager();
new WaveManager(this.mission.rounds, this.mission.gameMap.paths); new WaveManager(this.mission.rounds, this.mission.gameMap.paths);
@ -72,13 +77,14 @@ export class GameScene extends Scene {
this.events.on(CreepEvents.Died, (playerAward, creepThatDied) => { this.events.on(CreepEvents.Died, (playerAward, creepThatDied) => {
this.MissionStats.earnGold(playerAward); this.MissionStats.earnGold(playerAward);
}); });
this.towerPanel = new TowerPanel(GameUIConstants.SidebarRect);
this.sidebar = new Sidebar(GameUIConstants.SidebarRect); this.sidebar = new Sidebar(GameUIConstants.SidebarRect);
this.tooltip = new Tooltip(new PIXI.Rectangle(0, 0, 350, 160));
this.changeRoundButton = new Button(GameUIConstants.ChangeRoundButtonRect, '', ButtonTexture.Button01, true); this.changeRoundButton = new Button(GameUIConstants.ChangeRoundButtonRect, '', ButtonTexture.Button01, true);
this.changeRoundButton.container.removeFromParent(); this.changeRoundButton.container.removeFromParent();
this.sidebar.container.addChild(this.changeRoundButton.container); this.sidebar.container.addChild(this.changeRoundButton.container);
Engine.GameMaster.currentScene.stage.addChildAt(this.dimGraphics, 0);
this.tooltip = new Tooltip(new PIXI.Rectangle(0, 0, 350, 160));
// Added custom button logic to still keep all the regular events for the button, just have an icon instead of text. // Added custom button logic to still keep all the regular events for the button, just have an icon instead of text.
// TODO: maybe make this better? add like a seperate class for icon buttons or smth
this.changeRoundButton.CustomButtonLogic = () => { this.changeRoundButton.CustomButtonLogic = () => {
this.changeRoundButton.buttonIcon = new PIXI.Sprite({ this.changeRoundButton.buttonIcon = new PIXI.Sprite({
texture: GameAssets.PlayIconTexture, texture: GameAssets.PlayIconTexture,
@ -95,12 +101,34 @@ export class GameScene extends Scene {
if (this.roundMode == RoundMode.Combat) if (this.roundMode == RoundMode.Combat)
return Engine.NotificationManager.Notify('Wave is already in progress.', 'warn'); return Engine.NotificationManager.Notify('Wave is already in progress.', 'warn');
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;
this.setRoundMode(RoundMode.Combat); this.setRoundMode(RoundMode.Combat);
this.changeRoundButton.buttonIcon.texture = GameAssets.ExclamationIconTexture; this.changeRoundButton.buttonIcon.texture = GameAssets.ExclamationIconTexture;
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(100, 200);
this.events.on(GemEvents.TowerPanelSelectGem, (gem, index, tower) => {
if (gem == null) {
if (!this.MissionStats.checkIfPlayerHasAnyGems())
return Engine.NotificationManager.Notify(
'You require atleast 1 Gem in your inventory to slot it in a Gem slot.',
'warn'
);
}
this.sidebar.gemTab.TowerPanelSelectingGem(gem, index, tower);
});
this.ticker = new PIXI.Ticker();
this.ticker.maxFPS = 60;
this.ticker.minFPS = 30;
// fix tooltip behaving weirdly for some reason
this.tooltip.SetContentTower(0, 0, 0, 0);
this.tooltip.Show(Engine.MouseX, Engine.MouseY);
this.tooltip.Hide();
this.ticker.add(() => {
if (this.update) this.update(this.ticker.elapsedMS);
});
this.ticker.start();
} }
public update(elapsedMS) { public update(elapsedMS) {
if (this.isGameOver) { if (this.isGameOver) {
@ -113,6 +141,7 @@ export class GameScene extends Scene {
Engine.WaveManager.update(elapsedMS); Engine.WaveManager.update(elapsedMS);
Engine.Grid.update(elapsedMS); Engine.Grid.update(elapsedMS);
Engine.TowerManager.update(elapsedMS); Engine.TowerManager.update(elapsedMS);
// Means the round is finished.
if (this.isWaveManagerFinished && Engine.Grid.creeps.length == 0) { if (this.isWaveManagerFinished && Engine.Grid.creeps.length == 0) {
this.isWaveManagerFinished = false; this.isWaveManagerFinished = false;
this.setRoundMode(RoundMode.Purchase); this.setRoundMode(RoundMode.Purchase);
@ -121,14 +150,14 @@ export class GameScene extends Scene {
`Round ${this.currentRound + 1}/${this.mission.rounds.length} completed.`, `Round ${this.currentRound + 1}/${this.mission.rounds.length} completed.`,
'info' 'info'
); );
if (this.currentRound == this.mission.rounds.length) {
Engine.NotificationManager.Notify(`Final round.`, 'danger');
}
if (this.currentRound + 1 == this.mission.rounds.length) { if (this.currentRound + 1 == this.mission.rounds.length) {
Engine.NotificationManager.Notify(`Mission victory!!`, 'reward'); Engine.NotificationManager.Notify(`Mission victory!!`, 'reward');
this.changeRoundButton.buttonIcon.texture = GameAssets.HomeIconTexture; this.changeRoundButton.buttonIcon.texture = GameAssets.HomeIconTexture;
this.playerWon = true; this.playerWon = true;
} else this.currentRound++; } else {
this.OfferPlayerGems();
this.currentRound++;
}
} }
if (this.MissionStats.getHP() <= 0) { if (this.MissionStats.getHP() <= 0) {
@ -139,9 +168,80 @@ export class GameScene extends Scene {
this.ShowScoreScreen(false); this.ShowScoreScreen(false);
} }
} }
public DarkenScreen() {
this.dimGraphics.rect(0, 0, Engine.app.canvas.width, Engine.app.canvas.height);
this.dimGraphics.fill({ color: 0x000000, alpha: 0.5 });
}
public UndarkenScreen() {
this.dimGraphics.clear();
}
private OfferPlayerGems() {
Engine.Grid.gridInteractionEnabled = false;
Engine.GameScene.sidebar.towerTab.resetTint();
Engine.TowerManager.ResetChooseTower();
this.setRoundMode(RoundMode.OfferingGems);
let gemsToOffer = this.mission.rounds[this.currentRound].offeredGems;
this.DarkenScreen();
this.offerGemsSprite = new PIXI.NineSliceSprite({
width: 380,
height: 150,
texture: GameAssets.Frame01Texture,
leftWidth: 100,
topHeight: 100,
rightWidth: 100,
bottomHeight: 100,
zIndex: this.dimGraphics.zIndex + 1,
x: Engine.app.canvas.width / 2 - 190,
y: Engine.app.canvas.height / 2 - 75,
});
Engine.GameMaster.currentScene.stage.addChildAt(this.offerGemsSprite, 0);
let offerText = new PIXI.Text({
x: Engine.app.canvas.width / 4,
y: Engine.app.canvas.height / 4,
zIndex: this.dimGraphics.zIndex + 1,
text: 'Choose a Gem as your reward for beating this round!',
style: {
fontSize: 40,
fill: 'orange',
fontWeight: 'bold',
stroke: {
color: 0x000000,
width: 5,
},
},
});
// offerText.x -= offerText.width;
Engine.GameMaster.currentScene.stage.addChildAt(offerText, 0);
gemsToOffer.forEach((gType, index) => {
let _Gem = new Gem(gType);
let vGem = new VisualGemSlot(0, Engine.app.stage, _Gem);
this.visualGems.push(vGem);
vGem.container.x = this.offerGemsSprite.x - 15 + 69 * (index + 1);
vGem.container.y = this.offerGemsSprite.y + 40;
vGem.container.onpointermove = () => {
Engine.GameScene.tooltip.SetContentGem(_Gem);
Engine.GameScene.tooltip.Show(Engine.MouseX, Engine.MouseY);
};
vGem.container.onpointerleave = () => {
Engine.GameScene.tooltip.Hide();
};
vGem.onClick = () => {
Engine.GameScene.tooltip.Hide();
offerText.destroy();
this.PlayerPickedGem(_Gem);
};
});
}
private PlayerPickedGem(gem: Gem) {
this.offerGemsSprite.destroy();
this.UndarkenScreen();
this.visualGems.forEach((item) => item.destroy());
Engine.Grid.gridInteractionEnabled = true;
this.MissionStats.giveGem(gem);
}
private ShowScoreScreen(lost) { private ShowScoreScreen(lost) {
// TODO: show to player for real // TODO: show to player for real (see how this.OfferPlayerGems() does it)
if (lost) { if (lost) {
console.log('LOSE!'); console.log('LOSE!');
} else { } else {
@ -174,5 +274,4 @@ export class GameScene extends Scene {
Engine.GameMaster.currentScene.stage.removeChildren(); Engine.GameMaster.currentScene.stage.removeChildren();
Engine.GameMaster.changeScene(new MissionPickerScene()); Engine.GameMaster.changeScene(new MissionPickerScene());
} }
public onTowerPlaced() {}
} }