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
- [ ] Extend projectile into seperate defintion + json file
- [x] Extend projectile into seperate defintion + json file
- [ ] Make tower react with slotted gems
- [ ] Alter damage based on attunement from slotted gems
- [ ] Tower info on click
- [x] Tower info on click
- [x] Animate projectiles
- [x] Better mouseover tracking when placing tower and showing radius
## Gems
- [ ] Create Gem definitions
- [x] Create Gem definitions
- [ ] Make gems affect towers
## Other

View File

@ -4,7 +4,7 @@
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"dev": "vite --host",
"build": "tsc && vite build",
"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,
"stats": {
"health": 5,
"speed": 2.4,
"speed": 6,
"special": null,
"resistance": {
"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]
}
],
"offeredGems": [0, 1, 2, 3]
"offeredGems": [0, 0, 0, 0]
},
{
"waves": [

View File

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

View File

@ -1,5 +1,5 @@
import * as PIXI from 'pixi.js';
import { CreepDefinition, MissionDefinition, TowerDefinition } from './Definitions';
import { CreepDefinition, GemDefinition, MissionDefinition, TowerDefinition } from './Definitions';
import { Engine } from './Bastion';
export default class GameAssets {
@ -7,6 +7,7 @@ export default class GameAssets {
public static Frame02Texture: PIXI.Texture;
public static Frame03Texture: PIXI.Texture;
public static Frame04Texture: PIXI.Texture;
public static Frame05Texture: PIXI.Texture;
public static FrameInventory: PIXI.Texture;
public static FrameBackground: PIXI.Texture;
public static FrameTowerTab: PIXI.Texture;
@ -22,18 +23,22 @@ export default class GameAssets {
public static WaveTexture: PIXI.Texture;
public static SwordsTexture: PIXI.Texture;
public static TitleTexture: PIXI.Texture;
public static GemFrame: PIXI.Texture;
public static PlayIconTexture: PIXI.Texture;
public static PauseIconTexture: PIXI.Texture;
public static ExclamationIconTexture: PIXI.Texture;
public static HomeIconTexture: PIXI.Texture;
public static HammerIconTexture: PIXI.Texture;
public static XIconTexture: PIXI.Texture;
public static PlusIconTexture: PIXI.Texture;
public static GemAmountIcons: PIXI.Texture[] = [];
public static Missions: MissionDefinition[];
public static MissionBackgrounds: PIXI.Texture[] = [];
public static Towers: TowerDefinition[];
public static Creeps: CreepDefinition[];
public static Gems: GemDefinition[];
private static text;
private static async Load(src) {
@ -74,55 +79,70 @@ export default class GameAssets {
Engine.app.stage.addChild(this.text);
await Promise.all([
this.Load('/aclonica.woff2'),
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_small.png').then((texture) => (this.ButtonSmallTexture = 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_03.png').then((texture) => (this.Frame03Texture = 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/background_01.png').then((texture) => (this.FrameBackground = texture)),
this.Load('/assets/gui/background_02.png').then((texture) => (this.FrameTowerTab = texture)),
this.Load('/assets/gui/frame_violet.png').then((texture) => (this.VioletBackground = texture)),
this.Load('/assets/gui/frame_red.png').then((texture) => (this.RedBackground = texture)),
this.Load('/assets/gui/frame_green.png').then((texture) => (this.GreenBackground = texture)),
this.Load('/assets/gui/frame_blue.png').then((texture) => (this.BlueBackground = 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/wave.png').then((texture) => (this.WaveTexture = texture)),
this.Load('/assets/gui/sword_02.png').then((texture) => (this.SwordsTexture = 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/pause.png').then((texture) => (this.PauseIconTexture = 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('./aclonica.woff2'),
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_small.png').then((texture) => (this.ButtonSmallTexture = 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_03.png').then((texture) => (this.Frame03Texture = texture)),
this.Load('./assets/gui/frame_04.png').then((texture) => (this.Frame04Texture = texture)),
this.Load('./assets/gui/frame_05.png').then((texture) => (this.Frame05Texture = texture)),
this.Load('./assets/gui/frame_inv.png').then((texture) => (this.FrameInventory = texture)),
this.Load('./assets/gui/background_01.png').then((texture) => (this.FrameBackground = texture)),
this.Load('./assets/gui/background_02.png').then((texture) => (this.FrameTowerTab = texture)),
this.Load('./assets/gui/frame_violet.png').then((texture) => (this.VioletBackground = texture)),
this.Load('./assets/gui/frame_red.png').then((texture) => (this.RedBackground = texture)),
this.Load('./assets/gui/frame_green.png').then((texture) => (this.GreenBackground = texture)),
this.Load('./assets/gui/frame_blue.png').then((texture) => (this.BlueBackground = texture)),
this.Load('./assets/gui/gem_frame.png').then((texture) => (this.GemFrame = 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/wave.png').then((texture) => (this.WaveTexture = texture)),
this.Load('./assets/gui/sword_02.png').then((texture) => (this.SwordsTexture = 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/pause.png').then((texture) => (this.PauseIconTexture = 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.LoadTowers(),
this.LoadCreeps(),
this.LoadGemIcons(),
this.LoadGems(),
]);
t.destroy();
this.text.destroy();
// Set this.text = true to disallow calling GameAssets.LoadAssets() again
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++) {
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() {
const res = await fetch('/assets/json/Creeps.json');
const res = await fetch('./assets/json/Creeps.json');
const creeps = await res.json();
this.Creeps = creeps;
for (let idx = 0; idx < this.Creeps.length; idx++) {
const creep = this.Creeps[idx];
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;
}
}
@ -130,20 +150,20 @@ export default class GameAssets {
private static async LoadMissions() {
// 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() {
const res = await fetch('/assets/json/Towers.json');
const res = await fetch('./assets/json/Towers.json');
const towers = await res.json();
this.Towers = towers;
for (let idx = 0; idx < this.Towers.length; idx++) {
const tower = this.Towers[idx];
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.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 { AnimationManager } from './game/AnimationManager';
import NotificationManager from './game/NotificationManager';
import Gem from './game/Gem';
import GameAssets from './Assets';
import { GemType } from './Definitions';
export class Engine {
public static app: PIXI.Application;
@ -25,6 +28,15 @@ export class Engine {
public static GridRows: number = 17;
public static MouseX: 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 {

View File

@ -70,13 +70,27 @@ export type TowerStatsDefinition = {
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 {
Basic = 0,
Quick = 1,
Tank = 2,
}
export type GenericGemImprovement = {
damageUp: number;
attackSpeedUp: number;
rangeUp: number;
timeToLiveUp: number;
pierceUp: number;
};
export type PathDefinition = [[row: number, column: number]];
export enum TerrainType {
Restricted = 0,
@ -84,6 +98,13 @@ export enum TerrainType {
Path = 9,
}
// Make sure to sync these with the respective JSON files.
export enum CreepType {
Basic = 0,
Quick = 1,
Tank = 2,
}
export enum GemType {
Fire = 0,
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 GameObject from '../GameObject';
import * as PIXI from 'pixi.js';
export enum CreepEvents {
Died = 'died',
TakenDamage = 'takenDamage',
Escaped = 'escaped',
Moved = 'moved',
}
import { CreepEvents } from '../Events';
export default class Creep extends GameObject {
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 GameAssets from '../Assets';
import { Engine } from '../Bastion';
import Creep, { CreepEvents } from './Creep';
import { TowerEvents } from './Tower';
export enum GridEvents {
CellMouseOver = 'cellmouseover',
CellMouseLeave = 'cellmouseleave',
}
import Creep from './Creep';
import { CreepEvents, TowerEvents, GridEvents } from '../Events';
export class Cell extends GameObject {
public type: TerrainType;
@ -38,7 +33,6 @@ export class Cell extends GameObject {
zIndex: 99,
interactive: true,
});
// ? TODO: make range preview 1 global graphics obj, child. fix
this.g = new PIXI.Graphics({
zIndex: 5,
@ -48,13 +42,16 @@ export class Cell extends GameObject {
this.container.addChild(this.clickDetector);
this.container.addChild(this.g);
this.clickDetector.on('pointerup', (e) => {
if (!Engine.Grid.gridInteractionEnabled) return;
if (Engine.TowerManager.isPlacingTower) Engine.Grid.onGridCellClicked(row, column);
else this.OpenSelectedTowerPanel();
});
this.clickDetector.on('pointerenter', (e) => {
if (!Engine.Grid.gridInteractionEnabled) return;
Engine.GameScene.events.emit(GridEvents.CellMouseOver, this);
});
this.clickDetector.on('pointerleave', (e) => {
if (!Engine.Grid.gridInteractionEnabled) return;
Engine.GameScene.events.emit(GridEvents.CellMouseLeave, this);
Engine.Grid.rangePreview.clear();
});
@ -83,6 +80,8 @@ export class Cell extends GameObject {
}
public OpenSelectedTowerPanel() {
if (this.hasTowerPlaced) {
const tower = Engine.TowerManager.GetTowerByRowAndCol(this.row, this.column);
Engine.GameScene.towerPanel.Show(tower);
}
}
public checkIfCantPlace() {
@ -114,6 +113,7 @@ export class Grid extends GameObject {
public rangePreview: PIXI.Graphics;
public creeps: Creep[] = [];
public gridShown: boolean = false;
public gridInteractionEnabled = true;
constructor(map: GameMapDefinition, missionIndex) {
super();

View File

@ -2,7 +2,8 @@ import Assets from '../Assets';
import { Engine } from '../Bastion';
import GameObject from '../GameObject';
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 {
private hp: number = 100;
@ -10,6 +11,10 @@ export default class MissionStats extends GameObject {
private goldText: PIXI.Text;
private healthText: PIXI.Text;
private waveText: PIXI.Text;
private inventory: Gem[] = [];
// TODO: implement score keeping for leaderboards.
private score: number = 0;
public getHP() {
return this.hp;
@ -44,6 +49,33 @@ export default class MissionStats extends GameObject {
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) {
super();
this.hp = initialHP;

View File

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

View File

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

View File

@ -5,39 +5,21 @@ import { TowerDefinition } from '../Definitions';
import { Cell } from './Grid';
import { TowerBehaviours } from './TowerManager';
import Projectile, { calculateAngleToPoint } from './Projectile';
import GameAssets from '../Assets';
import Creep from './Creep';
import Gem from './Gem';
function distance(x1, y1, x2, y2) {
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 {
public row: number;
public column: number;
public definition: TowerDefinition;
public slottedGems: Array<Gem> = [];
public damageDealt: number = 0;
private projectiles: Projectile[] = [];
private behaviour: string;
private definition: TowerDefinition;
private sprite: PIXI.Sprite;
private ticksUntilNextShot: number;
private graphics: PIXI.Graphics = new PIXI.Graphics();
@ -51,12 +33,11 @@ export class Tower extends GameObject {
this.definition = definition;
this.ticksUntilNextShot = 0;
this.parent = Engine.Grid.getCellByRowAndCol(row, column);
console.log(texture);
this.sprite = new PIXI.Sprite({
texture: texture,
height: Engine.GridCellSize,
width: Engine.GridCellSize,
zIndex: 10,
zIndex: 130,
});
this.container.addChild(this.sprite);
this.parent.container.addChild(this.container);
@ -67,12 +48,34 @@ export class Tower extends GameObject {
}
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) => {
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() {
let creeps = Engine.Grid.creeps;
@ -97,6 +100,7 @@ export class Tower extends GameObject {
public update(elapsedMS: any): void {
this.projectiles.forEach((proj) => {
if (proj.deleteMe) {
this.damageDealt += this.definition.stats.damage;
this.projectiles.splice(this.projectiles.indexOf(proj), 1);
proj = null;
} else proj.update(elapsedMS);
@ -114,7 +118,7 @@ export class Tower extends GameObject {
}
}
override destroy(): void {
public destroy(): void {
super.destroy();
this.parent.clickDetector.off('pointerenter', this.onParentCellEnter);
this.parent.clickDetector.off('pointerleave', this.onParentCellLeave);

View File

@ -2,8 +2,9 @@ import * as PIXI from 'pixi.js';
import { Engine } from '../Bastion';
import { TerrainType, TowerDefinition } from '../Definitions';
import GameAssets from '../Assets';
import { Tower, TowerEvents } from './Tower';
import { Cell, GridEvents } from './Grid';
import { Tower } from './Tower';
import { Cell } from './Grid';
import { GridEvents, TowerEvents } from '../Events';
export enum TowerBehaviours {
BasicTowerBehaviour = 'BasicTowerBehaviour',
@ -71,7 +72,7 @@ export default class TowerManager {
'TowerManager.selectedTower is null when trying to place tower.',
'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);
}
@ -87,7 +88,7 @@ export default class TowerManager {
return returnTower;
}
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)
return Engine.NotificationManager.Notify('Not enough gold.', 'warn');
if (
@ -95,7 +96,7 @@ export default class TowerManager {
Engine.Grid.getCellByRowAndCol(row, column).type != TerrainType.Path &&
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);
this.towers.push(tower);
this.ToggleChoosingTowerLocation('RESET');

View File

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

View File

@ -1,10 +1,19 @@
import * as PIXI from 'pixi.js';
import GuiObject from '../GuiObject';
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 {
private bounds: PIXI.Rectangle;
private gemTabSprite: PIXI.NineSliceSprite;
private vGems: VisualGemSlot[] = [];
public isSelectingGem: boolean = false;
public selectingGemSlotIndex: number = -1;
public selectingGemTowerObject: Tower = null;
constructor(bounds: PIXI.Rectangle) {
super(false);
@ -22,7 +31,79 @@ export default class GemTab extends GuiObject {
this.gemTabSprite.y = 0;
this.gemTabSprite.width = this.bounds.width;
this.gemTabSprite.height = this.bounds.height;
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 {
public towerTab: TowerTab;
public gemTab: GemTab;
private bounds: PIXI.Rectangle;
private sidebarSprite: PIXI.NineSliceSprite;
private gemTab: GemTab;
constructor(bounds: PIXI.Rectangle) {
super(false);

View File

@ -2,6 +2,7 @@ import * as PIXI from 'pixi.js';
import GuiObject from '../GuiObject';
import GameAssets from '../Assets';
import { Engine } from '../Bastion';
import Gem from '../game/Gem';
// ! TODO NEXT!
@ -14,12 +15,17 @@ export default class Tooltip extends GuiObject {
private damageText: PIXI.Text;
private gemAmount: PIXI.Text;
private gemAmountSprite: PIXI.Sprite;
private title: PIXI.Sprite;
private costSprite: PIXI.Sprite;
private damageSprite: PIXI.Sprite;
private gemDescriptionText: PIXI.Text;
constructor(bounds: PIXI.Rectangle) {
super(false);
this.bounds = bounds;
this.container.x = -500;
this.container.y = -500;
this.container.zIndex = 150;
this.tooltipSprite = new PIXI.NineSliceSprite({
texture: GameAssets.Frame04Texture,
leftWidth: 200,
@ -43,16 +49,16 @@ export default class Tooltip extends GuiObject {
}),
});
this.titleText.anchor.set(0.5, 0);
let title = new PIXI.Sprite({
this.title = new PIXI.Sprite({
x: this.tooltipSprite.width / 2,
y: -20,
width: 250,
height: 40,
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,
x: 10,
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,
x: 22,
y: 70,
@ -105,7 +111,7 @@ export default class Tooltip extends GuiObject {
});
this.gemAmount = new PIXI.Text({
x: 54,
y: 108,
y: 105,
zIndex: 5,
text: 'Something went wrong if you see this.',
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(title);
this.container.addChild(costSprite);
this.container.addChild(damageSprite);
this.container.addChild(this.title);
this.container.addChild(this.costSprite);
this.container.addChild(this.damageSprite);
this.container.addChild(this.gemAmountSprite);
this.container.addChild(this.costText);
this.container.addChild(this.titleText);
this.container.addChild(this.damageText);
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.gemAmount.text = `Has ${gemSlotsAmount} Gem slots.`;
this.gemAmountSprite.texture = GameAssets.GemAmountIcons[gemSlotsAmount];
this.costText.text = `Costs ${cost} gold.`;
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) {
this.container.alpha = 1;
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 GameAssets from '../Assets';
import { Engine } from '../Bastion';
import { TowerEvents } from '../game/Tower';
import { TowerEvents } from '../Events';
import { TowerDefinition } from '../Definitions';
class TowerButton extends GuiObject {
@ -47,7 +47,8 @@ class TowerButton extends GuiObject {
Engine.GameScene.events.on(TowerEvents.TowerPlacedEvent, (name) => {
this.resetTint();
});
this.container.onmousemove = (e) => {
this.container.onpointermove = (e) => {
if (Engine.Grid.gridInteractionEnabled == false) return;
if (Engine.TowerManager.isPlacingTower) return;
this.ShowTooltip();
};
@ -63,7 +64,7 @@ class TowerButton extends GuiObject {
definition = item;
}
});
Engine.GameScene.tooltip.SetContent(
Engine.GameScene.tooltip.SetContentTower(
this.towerName,
definition.stats.damage,
definition.stats.cost,
@ -72,10 +73,12 @@ class TowerButton extends GuiObject {
Engine.GameScene.tooltip.Show(Engine.MouseX, Engine.MouseY);
}
public onClick(e: PIXI.FederatedPointerEvent): void {
if (Engine.Grid.gridInteractionEnabled == false) return;
if (Engine.TowerManager.isPlacingTower && Engine.TowerManager.selectedTower.name != this.towerName) {
Engine.GameScene.sidebar.towerTab.resetTint();
Engine.TowerManager.ResetChooseTower();
}
Engine.GameScene.towerPanel.Hide();
Engine.GameScene.tooltip.Hide();
if (this.frameSprite.tint == 0x00ff00) {
this.frameSprite.tint = 0xffffff;

View File

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

View File

@ -1,9 +1,10 @@
import GameAssets from '../classes/Assets';
import { Engine } from '../classes/Bastion';
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 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 Button, { ButtonTexture } from '../classes/gui/Button';
import Scene from './Scene';
@ -13,10 +14,13 @@ import TowerManager from '../classes/game/TowerManager';
import { MissionPickerScene } from './MissionPicker';
import GameUIConstants from '../classes/GameUIConstants';
import Tooltip from '../classes/gui/Tooltip';
import TowerPanel, { VisualGemSlot } from '../classes/gui/TowerPanel';
import Gem from '../classes/game/Gem';
enum RoundMode {
Purchase = 0,
Combat = 1,
OfferingGems = 2,
}
export class GameScene extends Scene {
@ -29,10 +33,18 @@ export class GameScene extends Scene {
public changeRoundButton: Button;
public sidebar: Sidebar;
public tooltip: Tooltip;
public towerPanel: TowerPanel;
private visualGems: VisualGemSlot[] = [];
private currentRound: number = 0;
private isWaveManagerFinished: boolean = false;
private playerWon: 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) {
super();
@ -45,13 +57,6 @@ export class GameScene extends Scene {
});
}
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 TowerManager();
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.MissionStats.earnGold(playerAward);
});
this.towerPanel = new TowerPanel(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.container.removeFromParent();
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.
// TODO: maybe make this better? add like a seperate class for icon buttons or smth
this.changeRoundButton.CustomButtonLogic = () => {
this.changeRoundButton.buttonIcon = new PIXI.Sprite({
texture: GameAssets.PlayIconTexture,
@ -95,12 +101,34 @@ export class GameScene extends Scene {
if (this.roundMode == RoundMode.Combat)
return Engine.NotificationManager.Notify('Wave is already in progress.', 'warn');
if (this.isGameOver) return Engine.NotificationManager.Notify('No more waves.', 'danger');
if (this.roundMode == RoundMode.OfferingGems) return;
this.setRoundMode(RoundMode.Combat);
this.changeRoundButton.buttonIcon.texture = GameAssets.ExclamationIconTexture;
this.events.emit(WaveManagerEvents.NewWave, `${this.currentRound + 1}`);
};
this.MissionStats = new MissionStats(100, 200);
this.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) {
if (this.isGameOver) {
@ -113,6 +141,7 @@ export class GameScene extends Scene {
Engine.WaveManager.update(elapsedMS);
Engine.Grid.update(elapsedMS);
Engine.TowerManager.update(elapsedMS);
// Means the round is finished.
if (this.isWaveManagerFinished && Engine.Grid.creeps.length == 0) {
this.isWaveManagerFinished = false;
this.setRoundMode(RoundMode.Purchase);
@ -121,14 +150,14 @@ export class GameScene extends Scene {
`Round ${this.currentRound + 1}/${this.mission.rounds.length} completed.`,
'info'
);
if (this.currentRound == this.mission.rounds.length) {
Engine.NotificationManager.Notify(`Final round.`, 'danger');
}
if (this.currentRound + 1 == this.mission.rounds.length) {
Engine.NotificationManager.Notify(`Mission victory!!`, 'reward');
this.changeRoundButton.buttonIcon.texture = GameAssets.HomeIconTexture;
this.playerWon = true;
} else this.currentRound++;
} else {
this.OfferPlayerGems();
this.currentRound++;
}
}
if (this.MissionStats.getHP() <= 0) {
@ -139,9 +168,80 @@ export class GameScene extends Scene {
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) {
// TODO: show to player for real
// TODO: show to player for real (see how this.OfferPlayerGems() does it)
if (lost) {
console.log('LOSE!');
} else {
@ -174,5 +274,4 @@ export class GameScene extends Scene {
Engine.GameMaster.currentScene.stage.removeChildren();
Engine.GameMaster.changeScene(new MissionPickerScene());
}
public onTowerPlaced() {}
}