diff --git a/public/assets/gems/Soulforge/1.png b/public/assets/gems/Soulforge/1.png new file mode 100644 index 0000000..add5555 Binary files /dev/null and b/public/assets/gems/Soulforge/1.png differ diff --git a/public/assets/gems/Soulforge/2.png b/public/assets/gems/Soulforge/2.png new file mode 100644 index 0000000..8da6be4 Binary files /dev/null and b/public/assets/gems/Soulforge/2.png differ diff --git a/public/assets/gems/Titalium/1.png b/public/assets/gems/Titalium/1.png new file mode 100644 index 0000000..d9a9b42 Binary files /dev/null and b/public/assets/gems/Titalium/1.png differ diff --git a/public/assets/gems/Titalium/2.png b/public/assets/gems/Titalium/2.png new file mode 100644 index 0000000..e8df315 Binary files /dev/null and b/public/assets/gems/Titalium/2.png differ diff --git a/public/assets/gems/Titalium/3.png b/public/assets/gems/Titalium/3.png new file mode 100644 index 0000000..c014b53 Binary files /dev/null and b/public/assets/gems/Titalium/3.png differ diff --git a/public/assets/gems/Yeti/1.png b/public/assets/gems/Yeti/1.png new file mode 100644 index 0000000..4dbf7c1 Binary files /dev/null and b/public/assets/gems/Yeti/1.png differ diff --git a/public/assets/gems/Yeti/2.png b/public/assets/gems/Yeti/2.png new file mode 100644 index 0000000..e6b3b07 Binary files /dev/null and b/public/assets/gems/Yeti/2.png differ diff --git a/public/assets/json/Gems.json b/public/assets/json/Gems.json index 61d55e3..880ee75 100644 --- a/public/assets/json/Gems.json +++ b/public/assets/json/Gems.json @@ -39,5 +39,142 @@ "frostfire": 0 } ] + }, + { + "name": "Yeti Gem", + "description": "Yeti gem description. Something something, write this while drunk or something.", + "type": "Yeti", + "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, + "ice": 0, + "frostfire": 0 + }, + { + "physical": 0, + "divine": 0, + "fire": 0, + "ice": 0, + "frostfire": 0 + } + ] + }, + { + "name": "Titalium Gem", + "description": "Titalium gem description. Something something zombie creeps working for you something something.", + "type": "Titalium", + "totalLevels": 3, + "textures": [], + "cantCombineWith": [], + "specialCombine": [], + "genericImprovements": [ + { + "damageUp": 2, + "attackSpeedUp": 100, + "rangeUp": 0.5, + "timeToLiveUp": 0, + "pierceUp": 1 + }, + { + "damageUp": 2, + "attackSpeedUp": 100, + "rangeUp": 0, + "timeToLiveUp": 0, + "pierceUp": 1 + }, + { + "damageUp": 2, + "attackSpeedUp": 100, + "rangeUp": 0, + "timeToLiveUp": 0, + "pierceUp": 1 + } + ], + "gemResistanceModifications": [ + { + "physical": 0, + "divine": 0, + "fire": 0, + "ice": 0, + "frostfire": 0 + }, + { + "physical": 0, + "divine": 0, + "fire": 0, + "ice": 0, + "frostfire": 0 + }, + { + "physical": 0, + "divine": 0, + "fire": 0, + "ice": 0, + "frostfire": 0 + } + ] + }, + { + "name": "Soulforge Gem", + "description": "Soulforge gem description, have to write later.", + "type": "Soulforge", + "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, + "ice": 0, + "frostfire": 0 + }, + { + "physical": 0, + "divine": 0, + "fire": 0, + "ice": 0, + "frostfire": 0 + } + ] } ] diff --git a/src/classes/Bastion.ts b/src/classes/Bastion.ts index 8821991..36c9f56 100644 --- a/src/classes/Bastion.ts +++ b/src/classes/Bastion.ts @@ -32,9 +32,9 @@ export class Engine { 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); + Engine.TowerManager.PlaceTower(GameAssets.Towers[1], 8, 10, GameAssets.Towers[0].behaviour, true); + for (let i = 0; i < 29; i++) { + this.GameScene.MissionStats.giveGem(new Gem(i % 4), true); } } } diff --git a/src/classes/game/Gem.ts b/src/classes/game/Gem.ts index 15c2614..f3742c1 100644 --- a/src/classes/game/Gem.ts +++ b/src/classes/game/Gem.ts @@ -1,5 +1,5 @@ import * as PIXI from 'pixi.js'; -import { GemType, GemDefinition } from '../Definitions'; +import { GemType, GemDefinition, GenericGemImprovement } from '../Definitions'; import GameAssets from '../Assets'; let latestGemId = 0; @@ -8,11 +8,35 @@ export default class Gem { public texture: PIXI.Texture; public level: number = 1; public definition: GemDefinition; - public id; - constructor(gemType: GemType) { + public id: number | string; + constructor(gemType: GemType, doNotIncrement?: boolean) { this.definition = GameAssets.Gems[gemType]; this.texture = this.definition.textures[0]; - this.id = latestGemId + 1; - latestGemId++; + + if (!doNotIncrement) { + this.id = latestGemId + 1; + latestGemId++; + } else this.id = ''; + } + public currentGemImprovement() { + let totalGemImprovement: GenericGemImprovement = { + damageUp: 0, + attackSpeedUp: 0, + rangeUp: 0, + timeToLiveUp: 0, + pierceUp: 0, + }; + for (let i = 0; i < this.level; i++) { + const item = this.definition.genericImprovements[i]; + totalGemImprovement.damageUp += item.damageUp; + totalGemImprovement.attackSpeedUp += item.attackSpeedUp; + totalGemImprovement.rangeUp += item.rangeUp; + totalGemImprovement.timeToLiveUp += item.timeToLiveUp; + totalGemImprovement.pierceUp += item.pierceUp; + } + return totalGemImprovement; + } + public currentGemResistanceModifications() { + return this.definition.gemResistanceModifications[this.level - 1]; } } diff --git a/src/classes/game/Grid.ts b/src/classes/game/Grid.ts index 8479512..f0246e8 100644 --- a/src/classes/game/Grid.ts +++ b/src/classes/game/Grid.ts @@ -47,11 +47,11 @@ export class Cell extends GameObject { else this.OpenSelectedTowerPanel(); }); this.clickDetector.on('pointerenter', (e) => { - if (!Engine.Grid.gridInteractionEnabled) return; + if (!Engine.Grid.gridInteractionEnabled || Engine.GameScene.towerPanel.isShown) return; Engine.GameScene.events.emit(GridEvents.CellMouseOver, this); }); this.clickDetector.on('pointerleave', (e) => { - if (!Engine.Grid.gridInteractionEnabled) return; + if (!Engine.Grid.gridInteractionEnabled || Engine.GameScene.towerPanel.isShown) return; Engine.GameScene.events.emit(GridEvents.CellMouseLeave, this); Engine.Grid.rangePreview.clear(); }); diff --git a/src/classes/game/MissionStats.ts b/src/classes/game/MissionStats.ts index c1c74e2..f7b894c 100644 --- a/src/classes/game/MissionStats.ts +++ b/src/classes/game/MissionStats.ts @@ -50,9 +50,9 @@ export default class MissionStats extends GameObject { } public giveGem(gem: Gem, noNotify?) { - if (this.inventory.length >= 48) + if (this.inventory.length >= 32) return Engine.NotificationManager.Notify( - "Can't hold more than 48 Gems. Extra Gem was thrown away.", + "Can't hold more than 32 Gems. Extra Gem was thrown away.", 'danger' ); this.inventory.push(gem); diff --git a/src/classes/game/Projectile.ts b/src/classes/game/Projectile.ts index 9ce0327..62300e5 100644 --- a/src/classes/game/Projectile.ts +++ b/src/classes/game/Projectile.ts @@ -3,6 +3,7 @@ import GameObject from '../GameObject'; import { Engine } from '../Bastion'; import Creep from './Creep'; import { CreepEvents } from '../Events'; +import { Tower } from './Tower'; export function calculateAngleToPoint(x, y, targetX, targetY) { const dx = targetX - x; @@ -19,16 +20,19 @@ export default class Projectile extends GameObject { public speed: number; public damage: number; public timeToLive: number = 1; - constructor(x, y, textures, angle, damage) { + public parent: Tower; + constructor(x, y, textures, angle, damage, tint, tower) { super(); this.x = x; this.y = y; + this.parent = tower; this.damage = damage; this.sprite = new PIXI.AnimatedSprite({ textures: textures, scale: 0.25, rotation: angle }); this.sprite.anchor.set(0.5, 0.5); this.sprite.play(); this.container.x = this.x; this.container.y = this.y; + this.sprite.tint = tint; this.container.addChild(this.sprite); Engine.GameMaster.currentScene.stage.addChild(this.container); diff --git a/src/classes/game/Tower.ts b/src/classes/game/Tower.ts index d81d5a8..620543f 100644 --- a/src/classes/game/Tower.ts +++ b/src/classes/game/Tower.ts @@ -7,6 +7,7 @@ import { TowerBehaviours } from './TowerManager'; import Projectile, { calculateAngleToPoint } from './Projectile'; import Creep from './Creep'; import Gem from './Gem'; +import { BasicTowerBehaviour } from './TowerBehaviours'; export function distance(x1, y1, x2, y2) { return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); @@ -18,12 +19,13 @@ export class Tower extends GameObject { public definition: TowerDefinition; public slottedGems: Array = []; public damageDealt: number = 0; - private projectiles: Projectile[] = []; - private behaviour: string; - private sprite: PIXI.Sprite; - private ticksUntilNextShot: number; - private graphics: PIXI.Graphics = new PIXI.Graphics(); - private parent: Cell; + public projectiles: Projectile[] = []; + public behaviour: string; + public sprite: PIXI.Sprite; + public ticksUntilNextShot: number; + public graphics: PIXI.Graphics = new PIXI.Graphics(); + public computedDamageToDeal: number; + public parent: Cell; constructor(row, column, texture, definition, behaviour) { super(); @@ -40,6 +42,7 @@ export class Tower extends GameObject { zIndex: 130, }); this.container.addChild(this.sprite); + this.computedDamageToDeal = this.definition.stats.damage; this.parent.container.addChild(this.container); this.container.interactiveChildren = true; this.parent.clickDetector.on('pointerenter', this.onParentCellEnter); @@ -48,7 +51,11 @@ export class Tower extends GameObject { } private onParentCellEnter = (e) => { - if (!Engine.TowerManager.isPlacingTower && Engine.Grid.gridInteractionEnabled) + if ( + !Engine.TowerManager.isPlacingTower && + Engine.Grid.gridInteractionEnabled && + !Engine.GameScene.towerPanel.isShown + ) this.parent.showRangePreview(false, this.definition.stats.range); }; @@ -94,29 +101,16 @@ export class Tower extends GameObject { let x = this.column * Engine.GridCellSize + Engine.GridCellSize / 2; let y = this.row * Engine.GridCellSize + Engine.GridCellSize / 2; let angle = calculateAngleToPoint(x, y, creep.x, creep.y); + let tint = 0xffffff; + this.slottedGems.forEach((gem) => { + if (gem.definition.type.toString() == 'Fire') tint = 0xff0000; + }); this.projectiles.push( - new Projectile(x, y, this.definition.projectileTextures, angle, this.definition.stats.damage) + new Projectile(x, y, this.definition.projectileTextures, angle, this.computedDamageToDeal, tint, this) ); } 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); - }); - if (this.behaviour == TowerBehaviours.BasicTowerBehaviour) { - if (this.ticksUntilNextShot > 0) this.ticksUntilNextShot--; - let creepsInRange = this.GetCreepsInRange(); - if (creepsInRange.length > 0) { - let focus = creepsInRange[0]; - if (this.ticksUntilNextShot == 0) { - this.ticksUntilNextShot = this.definition.stats.cooldown; - this.Shoot(focus); - } - } - } + if (this.behaviour == TowerBehaviours.BasicTowerBehaviour) BasicTowerBehaviour(this, elapsedMS); } public destroy(): void { diff --git a/src/classes/game/TowerBehaviours.ts b/src/classes/game/TowerBehaviours.ts new file mode 100644 index 0000000..7f7d8d4 --- /dev/null +++ b/src/classes/game/TowerBehaviours.ts @@ -0,0 +1,54 @@ +import { Tower } from './Tower'; + +/** + * Checks the projectiles of the tower and updates or removes them based on their state. + * If a projectile is marked for deletion, it is removed from the tower's projectiles array + * and the tower's damage dealt is incremented. + * + * @param tower - The tower whose projectiles are being checked. + * @param elapsedMS - The elapsed time in milliseconds since the last update. + */ +function projectileCheck(tower: Tower, elapsedMS: number) { + tower.projectiles.forEach((proj) => { + if (proj.deleteMe) { + tower.damageDealt += tower.computedDamageToDeal; + tower.projectiles.splice(tower.projectiles.indexOf(proj), 1); + proj = null; + } else proj.update(elapsedMS); + }); +} + +/** + * Computes the total damage the tower can deal by summing its base damage and the damage + * improvements from its slotted gems. Mutates passed tower. + * + * @param tower - The tower whose damage is being computed. + */ +export function computeGemImprovements(tower: Tower) { + let gemDamage = 0; + tower.slottedGems.forEach((gem) => { + gemDamage += gem.currentGemImprovement().damageUp; + }); + tower.computedDamageToDeal = tower.definition.stats.damage + gemDamage; +} + +/** + * Defines the basic behavior of a tower, including computing damage, checking projectiles, + * and handling shooting at creeps within range. + * + * @param tower - The tower whose behavior is being defined. + * @param elapsedMS - The elapsed time in milliseconds since the last update. + */ +export function BasicTowerBehaviour(tower: Tower, elapsedMS: number) { + if (tower.ticksUntilNextShot % 2 == 0) computeGemImprovements(tower); + projectileCheck(tower, elapsedMS); + if (tower.ticksUntilNextShot > 0) tower.ticksUntilNextShot--; + let creepsInRange = tower.GetCreepsInRange(); + if (creepsInRange.length > 0) { + let focus = creepsInRange[0]; + if (tower.ticksUntilNextShot == 0) { + tower.ticksUntilNextShot = tower.definition.stats.cooldown; + tower.Shoot(focus); + } + } +} diff --git a/src/classes/gui/GemTab.ts b/src/classes/gui/GemTab.ts index f7da1d5..04b0833 100644 --- a/src/classes/gui/GemTab.ts +++ b/src/classes/gui/GemTab.ts @@ -32,7 +32,7 @@ export default class GemTab extends GuiObject { this.gemTabSprite.x = 0; this.gemTabSprite.y = 0; this.gemTabSprite.width = this.bounds.width; - this.gemTabSprite.height = this.bounds.height; + this.gemTabSprite.height = this.bounds.height - 255; this.container.addChild(this.gemTabSprite); Engine.app.canvas.addEventListener('pointermove', () => { this.pointerMoveEvent(); diff --git a/src/classes/gui/TowerPanel.ts b/src/classes/gui/TowerPanel.ts index 5493cdb..f75b2e7 100644 --- a/src/classes/gui/TowerPanel.ts +++ b/src/classes/gui/TowerPanel.ts @@ -7,6 +7,7 @@ import Button, { ButtonTexture } from './Button'; import { Tower } from '../game/Tower'; import Gem from '../game/Gem'; import { GemEvents } from '../Events'; +import { computeGemImprovements } from '../game/TowerBehaviours'; export class VisualGemSlot extends GuiObject { public iconSprite: PIXI.Sprite; @@ -89,6 +90,9 @@ export default class TowerPanel extends GuiObject { public showingTower: Tower = null; public isShown: boolean = false; public titleText: PIXI.Text; + public damageText: PIXI.Text; + public totalDamage: PIXI.Text; + public attackSpeedText: PIXI.Text; constructor(bounds: PIXI.Rectangle) { super(false); @@ -139,6 +143,49 @@ export default class TowerPanel extends GuiObject { }); this.titleText.anchor.set(0.5, 0); this.container.addChild(this.titleText); + + this.damageText = new PIXI.Text({ + x: 10, + y: 100, + zIndex: 5, + style: new PIXI.TextStyle({ + fill: 0xffa500, // orange color + fontSize: 18, + stroke: { + color: 0x000000, + width: 2, + }, + }), + }); + this.container.addChild(this.damageText); + this.attackSpeedText = new PIXI.Text({ + x: 100, + y: 100, + zIndex: 5, + style: new PIXI.TextStyle({ + fill: 0xffffff, + fontSize: 18, + stroke: { + color: 0x000000, + width: 2, + }, + }), + }); + this.container.addChild(this.attackSpeedText); + this.totalDamage = new PIXI.Text({ + x: 10, + y: 130, + zIndex: 5, + style: new PIXI.TextStyle({ + fill: 0xff0000, + fontSize: 18, + stroke: { + color: 0x000000, + width: 2, + }, + }), + }); + this.container.addChild(this.totalDamage); } private MakeSlots(tower: Tower) { this.vGems.forEach((vGem) => { @@ -173,18 +220,24 @@ export default class TowerPanel extends GuiObject { } public Show(tower: Tower) { this.isShown = true; + computeGemImprovements(tower); this.SetContent(tower); this.MakeSlots(tower); this.showingTower = tower; Engine.GameScene.sidebar.gemTab.selectingGemTowerObject = tower; - if (tower.container.x < 900) { + if (tower.container.parent.x < 900) { this.ShowRight(); } else { this.ShowLeft(); } + tower.parent.showRangePreview(false, tower.definition.stats.range); } private SetContent(tower: Tower) { this.titleText.text = tower.definition.name; + this.damageText.text = 'Deals ' + tower.computedDamageToDeal + ' damage'; + this.totalDamage.text = 'Damage dealt: ' + tower.damageDealt + ' damage'; + this.attackSpeedText.x = this.damageText.width + 10; + this.attackSpeedText.text = ` every ${Math.floor(tower.definition.stats.cooldown / 60)}s`; } private ShowLeft() { this.towerPanel.x = -100; @@ -202,5 +255,6 @@ export default class TowerPanel extends GuiObject { this.isShown = false; this.container.alpha = 0; this.container.x = GameUIConstants.SidebarRect.x + 10; + Engine.Grid.rangePreview.clear(); } } diff --git a/src/scenes/Game.ts b/src/scenes/Game.ts index b6a58d8..fb01b7c 100644 --- a/src/scenes/Game.ts +++ b/src/scenes/Game.ts @@ -213,7 +213,7 @@ export class GameScene extends Scene { // offerText.x -= offerText.width; Engine.GameMaster.currentScene.stage.addChildAt(offerText, 0); gemsToOffer.forEach((gType, index) => { - let _Gem = new Gem(gType); + let _Gem = new Gem(gType, true); let vGem = new VisualGemSlot(0, Engine.app.stage, _Gem); this.visualGems.push(vGem); vGem.container.x = this.offerGemsSprite.x - 15 + 69 * (index + 1); @@ -228,7 +228,7 @@ export class GameScene extends Scene { vGem.onClick = () => { Engine.GameScene.tooltip.Hide(); offerText.destroy(); - this.PlayerPickedGem(_Gem); + this.PlayerPickedGem(new Gem(gType)); }; }); } @@ -238,6 +238,7 @@ export class GameScene extends Scene { this.visualGems.forEach((item) => item.destroy()); Engine.Grid.gridInteractionEnabled = true; this.MissionStats.giveGem(gem); + this.setRoundMode(RoundMode.Purchase); } private ShowScoreScreen(lost) {