From 2fcca0f21c7c2a26b33a4af7d1440878152930fd Mon Sep 17 00:00:00 2001 From: koneko <67551503+koneko@users.noreply.github.com> Date: Sat, 8 Feb 2025 18:28:15 +0100 Subject: [PATCH] add resistances, needs more balance --- docs/todos.md | 4 +- public/assets/json/Gems.json | 6 +-- public/maps.tiled-session | 30 +++++++---- src/classes/game/Creep.ts | 31 ++++++++--- src/classes/game/Projectile.ts | 24 ++++++--- src/classes/game/Tower.ts | 32 +++++++---- src/classes/game/TowerBehaviours.ts | 19 ++++++- src/classes/gui/TowerPanel.ts | 84 +++++++++++++++++++++++++++++ 8 files changed, 190 insertions(+), 40 deletions(-) diff --git a/docs/todos.md b/docs/todos.md index 3590a01..f1bcd2d 100644 --- a/docs/todos.md +++ b/docs/todos.md @@ -4,7 +4,7 @@ List of things to implement following the "release" of the minimum viable produc ## Creeps -- [ ] Elemental resistances/attunement +- [x] Elemental resistances/attunement - [x] Proper animation via PNG sequence - [x] More variety in Creeps - [x] Health bar @@ -29,4 +29,4 @@ List of things to implement following the "release" of the minimum viable produc - [ ] Add sound effects - [ ] Tutorial image/mission - [ ] Pause menu -- [ ] Score screen when winning/losing map +- [x] Score screen when winning/losing map diff --git a/public/assets/json/Gems.json b/public/assets/json/Gems.json index d41a211..23ef124 100644 --- a/public/assets/json/Gems.json +++ b/public/assets/json/Gems.json @@ -31,14 +31,14 @@ { "physical": 0, "divine": 0, - "fire": 0.5, + "fire": 0.25, "ice": 0, "frostfire": 0 }, { "physical": 0, "divine": 0, - "fire": 0.5, + "fire": 0.25, "ice": 0, "frostfire": 0 } @@ -47,7 +47,7 @@ { "name": "Yeti Gem", "description": "Yeti gem description. Something something, write this while drunk or something.", - "color": "dodgerblue", + "color": "#32e4fc", "type": "Yeti", "totalLevels": 2, "textures": [], diff --git a/public/maps.tiled-session b/public/maps.tiled-session index 404fdbb..01632f8 100644 --- a/public/maps.tiled-session +++ b/public/maps.tiled-session @@ -3,11 +3,11 @@ "height": 4300, "width": 2 }, - "activeFile": "tiled/Mission01.tmx", + "activeFile": "tiled/07_final_stretch.tmx", "expandedProjectPaths": [ + ".", "tiled", - "assets/missions", - "." + "assets/missions" ], "file.lastUsedOpenFilter": "All Files (*)", "fileStates": { @@ -102,6 +102,14 @@ "y": 474.6666666666666 } }, + "tiled/07_final_stretch.tmx": { + "scale": 0.5, + "selectedLayer": 0, + "viewCenter": { + "x": 971, + "y": 270 + } + }, "tiled/Mission01.tmx": { "scale": 1, "selectedLayer": 0, @@ -122,26 +130,26 @@ "openFiles": [ "tiled/04_crossroads.tmx", "tiled/05_the_maze.tmx", - "tiled/Mission01.tmx", "tiled/01_first_steps.tmx", "tiled/02_the_turn.tmx", "tiled/03_fork_in_the_road.tmx", - "tiled/06_multiple_fronts.tmx" + "tiled/06_multiple_fronts.tmx", + "tiled/07_final_stretch.tmx" ], "project": "maps.tiled-project", "recentFiles": [ - "tiled/Mission01.tmx", "tiled/04_crossroads.tmx", "tiled/05_the_maze.tmx", - "tiled/06_multiple_fronts.tmx", - "tiled/03_fork_in_the_road.tmx", - "tiled/02_the_turn.tmx", "tiled/01_first_steps.tmx", + "tiled/02_the_turn.tmx", + "tiled/03_fork_in_the_road.tmx", + "tiled/06_multiple_fronts.tmx", + "tiled/07_final_stretch.tmx", + "tiled/Mission01.tmx", "tiled/01_first_steps..tmx", "Mission011.tmx", "Tileset.tsx", - "Mission01.tmx", - "C:/home/koneko/dumping/tiles/TiledTDThree64.tmx" + "Mission01.tmx" ], "tileset.lastUsedFilter": "Tiled tileset files (*.tsx *.xml)" } diff --git a/src/classes/game/Creep.ts b/src/classes/game/Creep.ts index fa17654..cb79b65 100644 --- a/src/classes/game/Creep.ts +++ b/src/classes/game/Creep.ts @@ -1,7 +1,7 @@ import GameAssets from '../Assets'; import Assets from '../Assets'; import { Engine } from '../Bastion'; -import { CreepStatsDefinition, CreepType, PathDefinition } from '../Definitions'; +import { CreepResistancesDefinition, CreepStatsDefinition, CreepType, PathDefinition } from '../Definitions'; import GameObject from '../GameObject'; import * as PIXI from 'pixi.js'; import { CreepEvents } from '../Events'; @@ -52,11 +52,30 @@ export default class Creep extends GameObject { this.x = path[0][0] * Engine.GridCellSize + Engine.GridCellSize / 2; this.y = path[0][1] * Engine.GridCellSize + Engine.GridCellSize / 2; // TODO: Unsubscribe from events once the scene is destroyed - Engine.GameScene.events.on(CreepEvents.TakenDamage, (creepID, damage) => { - if (creepID != this.id) return; - this.health -= damage; - this.UpdateHealthbar(); - }); + Engine.GameScene.events.on( + CreepEvents.TakenDamage, + (creepID, damage, gemResistanceModifications: CreepResistancesDefinition) => { + if (creepID != this.id) return; + + // Apply resistances. + this.health -= damage + damage * (gemResistanceModifications.physical - this.stats.resistance.physical); + if (gemResistanceModifications.fire != 0) + this.health -= Math.max(damage * (gemResistanceModifications.fire - this.stats.resistance.fire), 0); + if (gemResistanceModifications.ice != 0) + this.health -= Math.max(damage * (gemResistanceModifications.ice - this.stats.resistance.ice), 0); + if (gemResistanceModifications.frostfire != 0) + this.health -= Math.max( + damage * (gemResistanceModifications.frostfire - this.stats.resistance.frostfire), + 0 + ); + if (gemResistanceModifications.divine != 0) + this.health -= Math.max( + damage * (gemResistanceModifications.divine - this.stats.resistance.divine), + 0 + ); + this.UpdateHealthbar(); + } + ); Engine.Grid.container.addChild(this.container); this.container.addChild(this.healthBarGraphics); this.container.addChild(this.sprite); diff --git a/src/classes/game/Projectile.ts b/src/classes/game/Projectile.ts index 8be5852..c9caf6d 100644 --- a/src/classes/game/Projectile.ts +++ b/src/classes/game/Projectile.ts @@ -4,6 +4,7 @@ import { Engine } from '../Bastion'; import Creep from './Creep'; import { CreepEvents } from '../Events'; import { Tower } from './Tower'; +import { CreepResistancesDefinition } from '../Definitions'; export function calculateAngleToPoint(x, y, targetX, targetY) { const dx = targetX - x; @@ -22,15 +23,26 @@ export default class Projectile extends GameObject { public pierce: number = 1; public timeToLive: number; public parent: Tower; - private collidedCreepIDs = []; - constructor(x, y, textures, angle, damage, tint, tower: Tower) { + public gemResistanceModifications: CreepResistancesDefinition; + public collidedCreepIDs = []; + constructor( + x, + y, + textures, + angle, + damage, + tint, + timeToLive, + pierce, + gemResistanceModifications: CreepResistancesDefinition + ) { super(); this.x = x; this.y = y; - this.timeToLive = tower.computedTimeToLive; - this.pierce = tower.computedPierce; - this.parent = tower; + this.timeToLive = timeToLive; + this.pierce = pierce; this.damage = damage; + this.gemResistanceModifications = gemResistanceModifications; this.sprite = new PIXI.AnimatedSprite({ textures: textures, scale: 0.25, rotation: angle }); this.sprite.anchor.set(0.5, 0.5); this.sprite.play(); @@ -73,7 +85,7 @@ export default class Projectile extends GameObject { } public onCollide(creep) { - Engine.GameScene.events.emit(CreepEvents.TakenDamage, creep.id, this.damage); + Engine.GameScene.events.emit(CreepEvents.TakenDamage, creep.id, this.damage, this.gemResistanceModifications); } public checkCollision(creep: Creep) { diff --git a/src/classes/game/Tower.ts b/src/classes/game/Tower.ts index a253d1e..9930b46 100644 --- a/src/classes/game/Tower.ts +++ b/src/classes/game/Tower.ts @@ -1,7 +1,7 @@ import { Engine } from '../Bastion'; import * as PIXI from 'pixi.js'; import GameObject from '../GameObject'; -import { GemType, TowerDefinition } from '../Definitions'; +import { CreepResistancesDefinition, GemType, TowerDefinition } from '../Definitions'; import { Cell } from './Grid'; import { TowerBehaviours } from './TowerManager'; import Projectile, { calculateAngleToPoint } from './Projectile'; @@ -29,6 +29,7 @@ export class Tower extends GameObject { public computedRange: number; public computedTimeToLive: number; public computedPierce: number; + public totalGemResistanceModifications: CreepResistancesDefinition; public parent: Cell; constructor(row, column, texture, definition, behaviour) { @@ -103,14 +104,23 @@ export class Tower extends GameObject { public Shoot(angle) { let x = this.column * Engine.GridCellSize + Engine.GridCellSize / 2; let y = this.row * Engine.GridCellSize + Engine.GridCellSize / 2; - let combinedTint = 0xffffff; - this.slottedGems.forEach((gem) => { - let rgb = new PIXI.Color(gem.definition.color).toRgb(); - combinedTint = - ((combinedTint & 0xff0000) + (rgb.r << 16)) | - ((combinedTint & 0x00ff00) + (rgb.g << 8)) | - ((combinedTint & 0x0000ff) + rgb.b); - }); + let combinedTint = new PIXI.Color('white'); + if (this.slottedGems.length > 0) { + let color = new PIXI.Color(this.slottedGems[0].definition.color); + for (let i = 1; i < this.slottedGems.length; i++) { + const element = this.slottedGems[i]; + color.multiply(element.definition.color); + } + combinedTint = color; + } + // this.slottedGems.forEach((gem) => { + // let rgb = new PIXI.Color(gem.definition.color).toRgb(); + // combinedTint = + // ((combinedTint & 0xff0000) + (rgb.r << 16)) | + // ((combinedTint & 0x00ff00) + (rgb.g << 8)) | + // ((combinedTint & 0x0000ff) + rgb.b); + // }); + // combinedTint = new PIXI.Color(this.slottedGems[0].definition.color). let proj = new Projectile( x, y, @@ -118,7 +128,9 @@ export class Tower extends GameObject { angle, this.computedDamageToDeal, combinedTint, - this + this.computedTimeToLive, + this.computedPierce, + this.totalGemResistanceModifications ); this.projectiles.push(proj); return proj; diff --git a/src/classes/game/TowerBehaviours.ts b/src/classes/game/TowerBehaviours.ts index fbc321c..b203ac4 100644 --- a/src/classes/game/TowerBehaviours.ts +++ b/src/classes/game/TowerBehaviours.ts @@ -13,7 +13,9 @@ import { Tower } from './Tower'; function projectileCheck(tower: Tower, elapsedMS: number) { tower.projectiles.forEach((proj) => { if (proj.deleteMe) { - tower.damageDealt += tower.computedDamageToDeal; + proj.collidedCreepIDs.forEach(() => { + tower.damageDealt += tower.computedDamageToDeal; + }); tower.projectiles.splice(tower.projectiles.indexOf(proj), 1); proj = null; } else proj.update(elapsedMS); @@ -32,7 +34,13 @@ export function computeGemImprovements(tower: Tower) { let gemRangeUp = 0; let gemTimeToLiveUp = 0; let gemPierceUp = 0; - + tower.totalGemResistanceModifications = { + fire: 0, + frostfire: 0, + divine: 0, + ice: 0, + physical: 0, + }; tower.slottedGems.forEach((gem) => { let ccurrentGemImprovements = gem.currentGemImprovement(); gemDamage += ccurrentGemImprovements.damageUp; @@ -40,6 +48,13 @@ export function computeGemImprovements(tower: Tower) { gemRangeUp += ccurrentGemImprovements.rangeUp; gemTimeToLiveUp += ccurrentGemImprovements.timeToLiveUp; gemPierceUp += ccurrentGemImprovements.pierceUp; + + let gemResMod = gem.currentGemResistanceModifications(); + tower.totalGemResistanceModifications.physical += gemResMod.physical; + tower.totalGemResistanceModifications.ice += gemResMod.ice; + tower.totalGemResistanceModifications.fire += gemResMod.fire; + tower.totalGemResistanceModifications.divine += gemResMod.divine; + tower.totalGemResistanceModifications.frostfire += gemResMod.frostfire; }); tower.computedDamageToDeal = tower.definition.stats.damage + gemDamage; tower.computedAttackSpeed = tower.definition.stats.cooldown - gemAttackSpeedUp; diff --git a/src/classes/gui/TowerPanel.ts b/src/classes/gui/TowerPanel.ts index ddfde88..2eefd2b 100644 --- a/src/classes/gui/TowerPanel.ts +++ b/src/classes/gui/TowerPanel.ts @@ -109,6 +109,11 @@ export default class TowerPanel extends GuiObject { public damageText: PIXI.Text; public totalDamage: PIXI.Text; public attackSpeedText: PIXI.Text; + public fireResDamage: PIXI.Text; + public iceResDamage: PIXI.Text; + public frostFireResDamage: PIXI.Text; + public divineResDamage: PIXI.Text; + public physicalResDamage: PIXI.Text; constructor(bounds: PIXI.Rectangle) { super(false); @@ -202,6 +207,79 @@ export default class TowerPanel extends GuiObject { }), }); this.container.addChild(this.totalDamage); + + this.fireResDamage = new PIXI.Text({ + x: 10, + y: 170, + zIndex: 5, + style: new PIXI.TextStyle({ + fill: 0xfc5353, + fontSize: 18, + stroke: { + color: 0x000000, + width: 2, + }, + }), + }); + this.container.addChild(this.fireResDamage); + + this.iceResDamage = new PIXI.Text({ + x: 10, + y: 190, + zIndex: 5, + style: new PIXI.TextStyle({ + fill: 0x32e4fc, + fontSize: 18, + stroke: { + color: 0x000000, + width: 2, + }, + }), + }); + this.container.addChild(this.iceResDamage); + + this.frostFireResDamage = new PIXI.Text({ + x: 10, + y: 210, + zIndex: 5, + style: new PIXI.TextStyle({ + fill: 0xd753fc, + fontSize: 18, + stroke: { + color: 0x000000, + width: 2, + }, + }), + }); + this.container.addChild(this.frostFireResDamage); + this.divineResDamage = new PIXI.Text({ + x: 10, + y: 230, + zIndex: 5, + style: new PIXI.TextStyle({ + fill: 0xfcee53, + fontSize: 18, + stroke: { + color: 0x000000, + width: 2, + }, + }), + }); + this.container.addChild(this.divineResDamage); + this.physicalResDamage = new PIXI.Text({ + x: 10, + y: 250, + zIndex: 5, + style: new PIXI.TextStyle({ + fill: 0xffffff, + fontSize: 18, + stroke: { + color: 0x000000, + width: 2, + }, + }), + }); + this.container.addChild(this.physicalResDamage); } private MakeSlots(tower: Tower) { this.vGems.forEach((vGem) => { @@ -254,6 +332,12 @@ export default class TowerPanel extends GuiObject { this.totalDamage.text = 'Damage dealt: ' + tower.damageDealt + ' damage'; this.attackSpeedText.x = this.damageText.width + 10; this.attackSpeedText.text = ` every ${Math.floor((tower.computedAttackSpeed / 60) * 100) / 100}s`; + + this.fireResDamage.text = `+${tower.totalGemResistanceModifications.fire * 100}% Fire damage`; + this.iceResDamage.text = `+${tower.totalGemResistanceModifications.ice * 100}% Ice damage`; + this.frostFireResDamage.text = `+${tower.totalGemResistanceModifications.frostfire * 100}% FrostFire damage`; + this.divineResDamage.text = `+${tower.totalGemResistanceModifications.divine * 100}% Divine damage`; + this.physicalResDamage.text = `+${tower.totalGemResistanceModifications.physical * 100}% Physical damage`; } private ShowLeft() { this.towerPanel.x = -100;