get gems when finishing rounds

This commit is contained in:
koneko 2025-01-26 22:26:29 +01:00
parent 2f3bb621c7
commit 5ea6017547
12 changed files with 182 additions and 50 deletions

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

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

View File

@ -1,11 +1,12 @@
[
{
"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.",
"type": "Fire",
"totalLevels": 2,
"textures": [],
"cantCombineWith": [],
"specialCombine": ["Yeti"],
"specialCombine": [],
"genericImprovements": [
{
"damageUp": 2,
@ -21,6 +22,22 @@
"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

@ -125,7 +125,7 @@ export default class GameAssets {
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] = texture;
gem.textures[i - 1] = texture;
}
}
for (let i = 0; i < 7; i++) {

View File

@ -72,12 +72,14 @@ export type TowerStatsDefinition = {
export type GemDefinition = {
name: string;
description: string;
type: GemType;
totalLevels: number;
textures: PIXI.Texture[];
cantCombineWith: GemType[];
specialCombine: GemType[];
genericImprovements: GenericGemImprovement[];
gemResistanceModifications: CreepResistancesDefinition[];
};
export type GenericGemImprovement = {

View File

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

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 = 0xffff00;
}
this.ticksToFadeAway = ticksToFadeAway;
this.textObj = new PIXI.Text({

View File

@ -42,7 +42,7 @@ export class Tower extends GameObject {
texture: texture,
height: Engine.GridCellSize,
width: Engine.GridCellSize,
zIndex: 10,
zIndex: 130,
});
this.container.addChild(this.sprite);
this.parent.container.addChild(this.container);
@ -53,7 +53,8 @@ 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) => {

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,
@ -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.gemDefinition.name;
this.gemDescriptionText.text = gem.gemDefinition.description;
}
public Show(x, y) {
this.container.alpha = 1;
if (x + this.container.width > Engine.app.canvas.width) {

View File

@ -7,33 +7,43 @@ import Button, { ButtonTexture } from './Button';
import { Tower } from '../game/Tower';
import Gem from '../game/Gem';
class VisualGemSlot extends GuiObject {
export class VisualGemSlot extends GuiObject {
public iconSprite: PIXI.Sprite;
private background: PIXI.Sprite;
private gem: Gem;
private i: number = 0;
constructor(index: number, parent: PIXI.Container, gem: Gem | null) {
super(true);
let gtexture;
if (gem == null) {
gtexture = GameAssets.PlusIconTexture;
} else {
gtexture = gem.texture;
}
this.container.x = 10;
this.container.y = index * Engine.GridCellSize + 300;
this.background = new PIXI.Sprite({
texture: GameAssets.FrameInventory,
});
this.gem = gem;
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.container.addChild(this.background);
this.container.addChild(this.iconSprite);
parent.addChild(this.container);

View File

@ -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,6 +73,7 @@ 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();

View File

@ -13,11 +13,13 @@ import TowerManager from '../classes/game/TowerManager';
import { MissionPickerScene } from './MissionPicker';
import GameUIConstants from '../classes/GameUIConstants';
import Tooltip from '../classes/gui/Tooltip';
import TowerPanel from '../classes/gui/TowerPanel';
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 {
@ -31,6 +33,13 @@ export class GameScene extends Scene {
public sidebar: Sidebar;
public tooltip: Tooltip;
public towerPanel: TowerPanel;
public dimGraphics: PIXI.Graphics = new PIXI.Graphics({
x: 0,
y: 0,
zIndex: 120,
});
private offerGemsSprite: PIXI.NineSliceSprite;
private visualGems: VisualGemSlot[] = [];
private currentRound: number = 0;
private isWaveManagerFinished: boolean = false;
private playerWon: boolean = false;
@ -47,13 +56,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);
@ -76,10 +78,11 @@ export class GameScene extends Scene {
});
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 = () => {
@ -98,12 +101,19 @@ 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.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();
}
public update(elapsedMS) {
if (this.isGameOver) {
@ -116,6 +126,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);
@ -124,16 +135,13 @@ export class GameScene extends Scene {
`Round ${this.currentRound + 1}/${this.mission.rounds.length} completed.`,
'info'
);
if (this.currentRound + 2 == 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++;
this.OfferPlayerGems();
this.currentRound++;
}
}
@ -145,14 +153,59 @@ 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 });
}
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: 400,
height: 200,
texture: GameAssets.Frame01Texture,
leftWidth: 100,
topHeight: 100,
rightWidth: 100,
bottomHeight: 100,
zIndex: this.dimGraphics.zIndex + 1,
x: Engine.app.canvas.width / 2 - 200,
y: Engine.app.canvas.height / 2 - 100,
});
Engine.GameMaster.currentScene.stage.addChildAt(this.offerGemsSprite, 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 + 69 * (index + 1);
vGem.container.y = this.offerGemsSprite.y + 50;
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();
this.PlayerPickedGem(_Gem);
};
});
}
private PlayerPickedGem(gem: Gem) {
this.offerGemsSprite.destroy();
this.dimGraphics.clear();
this.visualGems.forEach((item) => item.destroy());
Engine.Grid.gridInteractionEnabled = true;
Engine.NotificationManager.Notify(gem.gemDefinition.name + ' added to your inventory.', 'gemaward');
}
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 {