add selling and upgrading gems

This commit is contained in:
Koneko 2025-02-04 23:43:46 +01:00 committed by GitHub
commit 92f196310d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 205 additions and 23 deletions

View File

@ -12,8 +12,8 @@ List of things to implement following the "release" of the minimum viable produc
## Towers ## Towers
- [x] Extend projectile into seperate defintion + json file - [x] Extend projectile into seperate defintion + json file
- [ ] Make tower react with slotted gems - [x] Make tower react with slotted gems
- [ ] Alter damage based on attunement from slotted gems - [x] Alter damage based on attunement from slotted gems
- [x] Tower info on click - [x] Tower info on click
- [x] Animate projectiles - [x] Animate projectiles
- [x] Better mouseover tracking when placing tower and showing radius - [x] Better mouseover tracking when placing tower and showing radius
@ -21,7 +21,7 @@ List of things to implement following the "release" of the minimum viable produc
## Gems ## Gems
- [x] Create Gem definitions - [x] Create Gem definitions
- [ ] Make gems affect towers - [x] Make gems affect towers
## Other ## Other

Binary file not shown.

Before

Width:  |  Height:  |  Size: 244 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

View File

@ -7,20 +7,23 @@
"textures": [], "textures": [],
"cantCombineWith": [], "cantCombineWith": [],
"specialCombine": [], "specialCombine": [],
"initialGemValue": 10,
"genericImprovements": [ "genericImprovements": [
{ {
"damageUp": 2, "damageUp": 2,
"attackSpeedUp": 100, "attackSpeedUp": 100,
"rangeUp": 0.5, "rangeUp": 0.5,
"timeToLiveUp": 0, "timeToLiveUp": 0,
"pierceUp": 1 "pierceUp": 1,
"gemValueUp": 0
}, },
{ {
"damageUp": 2, "damageUp": 2,
"attackSpeedUp": 100, "attackSpeedUp": 100,
"rangeUp": 0.5, "rangeUp": 0.5,
"timeToLiveUp": 0, "timeToLiveUp": 0,
"pierceUp": 1 "pierceUp": 1,
"gemValueUp": 10
} }
], ],
"gemResistanceModifications": [ "gemResistanceModifications": [
@ -48,20 +51,23 @@
"textures": [], "textures": [],
"cantCombineWith": [], "cantCombineWith": [],
"specialCombine": [], "specialCombine": [],
"initialGemValue": 10,
"genericImprovements": [ "genericImprovements": [
{ {
"damageUp": 2, "damageUp": 2,
"attackSpeedUp": 100, "attackSpeedUp": 100,
"rangeUp": 0.5, "rangeUp": 0.5,
"timeToLiveUp": 0, "timeToLiveUp": 0,
"pierceUp": 1 "pierceUp": 1,
"gemValueUp": 0
}, },
{ {
"damageUp": 2, "damageUp": 2,
"attackSpeedUp": 100, "attackSpeedUp": 100,
"rangeUp": 0.5, "rangeUp": 0.5,
"timeToLiveUp": 0, "timeToLiveUp": 0,
"pierceUp": 1 "pierceUp": 1,
"gemValueUp": 10
} }
], ],
"gemResistanceModifications": [ "gemResistanceModifications": [
@ -89,27 +95,31 @@
"textures": [], "textures": [],
"cantCombineWith": [], "cantCombineWith": [],
"specialCombine": [], "specialCombine": [],
"initialGemValue": 10,
"genericImprovements": [ "genericImprovements": [
{ {
"damageUp": 2, "damageUp": 2,
"attackSpeedUp": 100, "attackSpeedUp": 100,
"rangeUp": 0.5, "rangeUp": 0.5,
"timeToLiveUp": 0, "timeToLiveUp": 0,
"pierceUp": 1 "pierceUp": 1,
"gemValueUp": 0
}, },
{ {
"damageUp": 2, "damageUp": 2,
"attackSpeedUp": 100, "attackSpeedUp": 100,
"rangeUp": 0, "rangeUp": 0,
"timeToLiveUp": 0, "timeToLiveUp": 0,
"pierceUp": 1 "pierceUp": 1,
"gemValueUp": 10
}, },
{ {
"damageUp": 2, "damageUp": 2,
"attackSpeedUp": 100, "attackSpeedUp": 100,
"rangeUp": 0, "rangeUp": 0,
"timeToLiveUp": 0, "timeToLiveUp": 0,
"pierceUp": 1 "pierceUp": 1,
"gemValueUp": 10
} }
], ],
"gemResistanceModifications": [ "gemResistanceModifications": [
@ -144,20 +154,23 @@
"textures": [], "textures": [],
"cantCombineWith": [], "cantCombineWith": [],
"specialCombine": [], "specialCombine": [],
"initialGemValue": 10,
"genericImprovements": [ "genericImprovements": [
{ {
"damageUp": 2, "damageUp": 2,
"attackSpeedUp": 100, "attackSpeedUp": 100,
"rangeUp": 0.5, "rangeUp": 0.5,
"timeToLiveUp": 0, "timeToLiveUp": 0,
"pierceUp": 1 "pierceUp": 1,
"gemValueUp": 0
}, },
{ {
"damageUp": 2, "damageUp": 2,
"attackSpeedUp": 100, "attackSpeedUp": 100,
"rangeUp": 0.5, "rangeUp": 0.5,
"timeToLiveUp": 0, "timeToLiveUp": 0,
"pierceUp": 1 "pierceUp": 1,
"gemValueUp": 10
} }
], ],
"gemResistanceModifications": [ "gemResistanceModifications": [

View File

@ -23,7 +23,7 @@ export default class GameAssets {
public static WaveTexture: PIXI.Texture; public static WaveTexture: PIXI.Texture;
public static SwordsTexture: PIXI.Texture; public static SwordsTexture: PIXI.Texture;
public static TitleTexture: PIXI.Texture; public static TitleTexture: PIXI.Texture;
public static GemFrame: PIXI.Texture; public static BannerGemsmith: PIXI.Texture;
public static PlayIconTexture: PIXI.Texture; public static PlayIconTexture: PIXI.Texture;
public static PauseIconTexture: PIXI.Texture; public static PauseIconTexture: PIXI.Texture;
@ -95,7 +95,7 @@ export default class GameAssets {
this.Load('./assets/gui/frame_red.png').then((texture) => (this.RedBackground = 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_green.png').then((texture) => (this.GreenBackground = texture)),
this.Load('./assets/gui/frame_blue.png').then((texture) => (this.BlueBackground = 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/banner_01.png').then((texture) => (this.BannerGemsmith = texture)),
this.Load('./assets/gui/heart.png').then((texture) => (this.HealthTexture = 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/money.png').then((texture) => (this.GoldTexture = texture)),
this.Load('./assets/gui/wave.png').then((texture) => (this.WaveTexture = texture)), this.Load('./assets/gui/wave.png').then((texture) => (this.WaveTexture = texture)),

View File

@ -78,6 +78,7 @@ export type GemDefinition = {
textures: PIXI.Texture[]; textures: PIXI.Texture[];
cantCombineWith: GemType[]; cantCombineWith: GemType[];
specialCombine: GemType[]; specialCombine: GemType[];
initialGemValue: number;
genericImprovements: GenericGemImprovement[]; genericImprovements: GenericGemImprovement[];
gemResistanceModifications: CreepResistancesDefinition[]; gemResistanceModifications: CreepResistancesDefinition[];
}; };
@ -88,6 +89,7 @@ export type GenericGemImprovement = {
rangeUp: number; rangeUp: number;
timeToLiveUp: number; timeToLiveUp: number;
pierceUp: number; pierceUp: number;
gemValueUp: number;
}; };
export type PathDefinition = [[row: number, column: number]]; export type PathDefinition = [[row: number, column: number]];

View File

@ -25,6 +25,7 @@ export default class Gem {
rangeUp: 0, rangeUp: 0,
timeToLiveUp: 0, timeToLiveUp: 0,
pierceUp: 0, pierceUp: 0,
gemValueUp: 0,
}; };
for (let i = 0; i < this.level; i++) { for (let i = 0; i < this.level; i++) {
const item = this.definition.genericImprovements[i]; const item = this.definition.genericImprovements[i];
@ -33,10 +34,19 @@ export default class Gem {
totalGemImprovement.rangeUp += item.rangeUp; totalGemImprovement.rangeUp += item.rangeUp;
totalGemImprovement.timeToLiveUp += item.timeToLiveUp; totalGemImprovement.timeToLiveUp += item.timeToLiveUp;
totalGemImprovement.pierceUp += item.pierceUp; totalGemImprovement.pierceUp += item.pierceUp;
totalGemImprovement.gemValueUp += item.gemValueUp;
} }
return totalGemImprovement; return totalGemImprovement;
} }
public currentGemResistanceModifications() { public currentGemResistanceModifications() {
return this.definition.gemResistanceModifications[this.level - 1]; return this.definition.gemResistanceModifications[this.level - 1];
} }
public isMaxLevel() {
return this.level == this.definition.totalLevels;
}
public levelUp(howMuch) {
if (!howMuch) howMuch = 1;
this.level += howMuch;
this.texture = this.definition.textures[this.level - 1];
}
} }

View File

@ -32,7 +32,7 @@ export default class GemTab extends GuiObject {
this.gemTabSprite.x = 0; this.gemTabSprite.x = 0;
this.gemTabSprite.y = 0; this.gemTabSprite.y = 0;
this.gemTabSprite.width = this.bounds.width; this.gemTabSprite.width = this.bounds.width;
this.gemTabSprite.height = this.bounds.height - 255; this.gemTabSprite.height = this.bounds.height;
this.container.addChild(this.gemTabSprite); this.container.addChild(this.gemTabSprite);
Engine.app.canvas.addEventListener('pointermove', () => { Engine.app.canvas.addEventListener('pointermove', () => {
this.pointerMoveEvent(); this.pointerMoveEvent();
@ -76,10 +76,22 @@ export default class GemTab extends GuiObject {
} }
} }
public pointerMoveEvent() { public pointerMoveEvent() {
if (!this.isDragAndDroppingGem || !Engine.GameScene.towerPanel.isShown || !this.dragAndDroppingGem) return; if (!this.isDragAndDroppingGem || !this.dragAndDroppingGem) return;
this.dragAndDroppingGem.container.x = Engine.MouseX - 32; this.dragAndDroppingGem.container.x = Engine.MouseX - 32;
this.dragAndDroppingGem.container.y = Engine.MouseY - 32; this.dragAndDroppingGem.container.y = Engine.MouseY - 32;
} }
private isOverlappingGemsmith(me: VisualGemSlot, other: VisualGemSlot, otherParent: PIXI.Container) {
let ddbb = me.copyContainerToBB();
let vb = other.copyContainerToBB();
let x = otherParent.x + vb.x + Engine.GameScene.sidebar.container.x;
let y = otherParent.y + vb.y + Engine.GameScene.sidebar.container.y;
let vgbb = new PIXI.Rectangle(x, y, vb.width, vb.height);
// console.log(ddbb, vgbb, ddbb.getBounds().intersects(vgbb));
if (ddbb.getBounds().intersects(vgbb)) {
if (other && other.gem == null) return true;
}
}
public RebuildInventoryVisual() { public RebuildInventoryVisual() {
this.vGems.forEach((vGem) => vGem.destroy()); this.vGems.forEach((vGem) => vGem.destroy());
this.vGems = []; this.vGems = [];
@ -112,7 +124,6 @@ export default class GemTab extends GuiObject {
this.selectingGemTowerObject.SlotGem(takenGem, this.selectingGemSlotIndex); this.selectingGemTowerObject.SlotGem(takenGem, this.selectingGemSlotIndex);
this.RebuildInventoryVisual(); this.RebuildInventoryVisual();
} else { } else {
if (!Engine.GameScene.towerPanel.isShown) return;
// initialise drag and drop // initialise drag and drop
this.isDragAndDroppingGem = true; this.isDragAndDroppingGem = true;
this.dragAndDroppingGem = vGem; this.dragAndDroppingGem = vGem;
@ -124,6 +135,63 @@ export default class GemTab extends GuiObject {
vGem.container.onpointerup = () => { vGem.container.onpointerup = () => {
if (this.isSelectingGem) return; if (this.isSelectingGem) return;
let overlapping = null; let overlapping = null;
let wantToSell = this.isOverlappingGemsmith(
this.dragAndDroppingGem,
Engine.GameScene.sidebar.gemsmith.sellVGem,
Engine.GameScene.sidebar.gemsmith.container
);
if (wantToSell) {
let sellFor =
this.dragAndDroppingGem.gem.definition.initialGemValue +
this.dragAndDroppingGem.gem.currentGemImprovement().gemValueUp;
Engine.GameScene.MissionStats.earnGold(Math.ceil(sellFor * 0.8));
Engine.NotificationManager.Notify(
`Sold Lv. ${this.dragAndDroppingGem.gem.level} ${
this.dragAndDroppingGem.gem.definition.name
} for ${Math.ceil(sellFor * 0.8)} gold.`,
'info'
);
Engine.GameScene.MissionStats.takeGem(this.dragAndDroppingGem.gem);
this.isDragAndDroppingGem = false;
this.dragAndDroppingGem = null;
this.RebuildInventoryVisual();
return;
}
let wantToUpgrade = this.isOverlappingGemsmith(
this.dragAndDroppingGem,
Engine.GameScene.sidebar.gemsmith.upgradeVGem,
Engine.GameScene.sidebar.gemsmith.container
);
if (wantToUpgrade) {
if (this.dragAndDroppingGem.gem.isMaxLevel())
Engine.NotificationManager.Notify('Gem is max level.', 'warn');
else {
let cost =
this.dragAndDroppingGem.gem.definition.genericImprovements[
this.dragAndDroppingGem.gem.level
].gemValueUp;
if (Engine.GameScene.MissionStats.hasEnoughGold(cost)) {
Engine.GameScene.MissionStats.spendGold(cost);
this.dragAndDroppingGem.gem.levelUp(1);
Engine.NotificationManager.Notify(
`Spent ${cost} gold to upgrade ${this.dragAndDroppingGem.gem.definition.name} Lv. ${
this.dragAndDroppingGem.gem.level - 1
} -> Lv. ${this.dragAndDroppingGem.gem.level}!`,
'warn'
);
} else {
Engine.NotificationManager.Notify(
"You don't have enough, you need " + cost + ' gold to upgrade this gem.',
'warn'
);
}
}
this.isDragAndDroppingGem = false;
this.dragAndDroppingGem = null;
this.RebuildInventoryVisual();
return;
}
Engine.GameScene.towerPanel.vGems.forEach((internVG) => { Engine.GameScene.towerPanel.vGems.forEach((internVG) => {
if (overlapping || !this.dragAndDroppingGem) return; if (overlapping || !this.dragAndDroppingGem) return;
let ddbb = this.dragAndDroppingGem.copyContainerToBB(); let ddbb = this.dragAndDroppingGem.copyContainerToBB();
@ -132,7 +200,7 @@ export default class GemTab extends GuiObject {
let y = Engine.GameScene.towerPanel.container.y + vb.y; let y = Engine.GameScene.towerPanel.container.y + vb.y;
let vgbb = new PIXI.Rectangle(x, y, vb.width, vb.height); let vgbb = new PIXI.Rectangle(x, y, vb.width, vb.height);
console.log(ddbb, vgbb, ddbb.getBounds().intersects(vgbb)); // console.log(ddbb, vgbb, ddbb.getBounds().intersects(vgbb));
if (ddbb.getBounds().intersects(vgbb)) { if (ddbb.getBounds().intersects(vgbb)) {
if (internVG && internVG.gem == null) overlapping = internVG; if (internVG && internVG.gem == null) overlapping = internVG;
} }

View File

@ -0,0 +1,60 @@
import * as PIXI from 'pixi.js';
import GuiObject from '../GuiObject';
import GameAssets from '../Assets';
import { VisualGemSlot } from './TowerPanel';
export default class Gemsmith extends GuiObject {
private bounds: PIXI.Rectangle;
public sellVGem: VisualGemSlot;
public upgradeVGem: VisualGemSlot;
// ! NOTE: Gemsmith logic is contained within GemTab.ts
constructor(bounds: PIXI.Rectangle) {
super(false);
this.bounds = bounds;
this.container.x = this.bounds.x;
this.container.y = this.bounds.y;
let background = new PIXI.Sprite({
x: 0,
y: 0,
width: this.bounds.width,
height: this.bounds.height,
texture: GameAssets.BannerGemsmith,
});
this.container.addChild(background);
let sellLabel = new PIXI.Text({
x: 40,
y: this.bounds.height / 6.5,
text: 'Sell gem',
style: new PIXI.TextStyle({
fill: 0xffdb00, // orange color
fontSize: 18,
stroke: {
color: 0x000000,
width: 2,
},
}),
});
this.container.addChild(sellLabel);
let upgradeLabel = new PIXI.Text({
x: 155,
y: this.bounds.height / 6.5,
text: 'Upgrade gem',
style: new PIXI.TextStyle({
fill: 0x2df937,
fontSize: 18,
stroke: {
color: 0x000000,
width: 2,
},
}),
});
this.container.addChild(upgradeLabel);
this.sellVGem = new VisualGemSlot(0, this.container, null, 'SELL');
this.sellVGem.container.x = 45;
this.sellVGem.container.y = this.bounds.height / 4;
this.upgradeVGem = new VisualGemSlot(0, this.container, null, 'UPGRADE');
this.upgradeVGem.container.x = 180;
this.upgradeVGem.container.y = this.bounds.height / 4;
}
}

View File

@ -3,10 +3,12 @@ import GuiObject from '../GuiObject';
import GameAssets from '../Assets'; import GameAssets from '../Assets';
import TowerTab from './TowerTab'; import TowerTab from './TowerTab';
import GemTab from './GemTab'; import GemTab from './GemTab';
import Gemsmith from './Gemsmith';
export default class Sidebar extends GuiObject { export default class Sidebar extends GuiObject {
public towerTab: TowerTab; public towerTab: TowerTab;
public gemTab: GemTab; public gemTab: GemTab;
public gemsmith: Gemsmith;
private bounds: PIXI.Rectangle; private bounds: PIXI.Rectangle;
private sidebarSprite: PIXI.NineSliceSprite; private sidebarSprite: PIXI.NineSliceSprite;
@ -33,8 +35,17 @@ export default class Sidebar extends GuiObject {
this.towerTab = new TowerTab(towerTabRect); this.towerTab = new TowerTab(towerTabRect);
this.container.addChild(this.towerTab.container); this.container.addChild(this.towerTab.container);
const gemTabRect = new PIXI.Rectangle(60, 180, this.bounds.width - 65, this.bounds.height - 280); const gemTabRect = new PIXI.Rectangle(60, 180, this.bounds.width - 65, this.bounds.height - 280 - 255);
this.gemTab = new GemTab(gemTabRect); this.gemTab = new GemTab(gemTabRect);
this.container.addChild(this.gemTab.container); this.container.addChild(this.gemTab.container);
const gemSmithRect = new PIXI.Rectangle(
60,
185 + gemTabRect.height,
this.bounds.width - 65,
this.bounds.height - 840
);
this.gemsmith = new Gemsmith(gemSmithRect);
this.container.addChild(this.gemsmith.container);
} }
} }

View File

@ -179,7 +179,9 @@ export default class Tooltip extends GuiObject {
this.gemDescriptionText.alpha = 1; this.gemDescriptionText.alpha = 1;
this.titleText.text = `Lv. ${gem.level} ` + gem.definition.name; this.titleText.text = `Lv. ${gem.level} ` + gem.definition.name;
this.gemDescriptionText.text = gem.definition.description; this.gemDescriptionText.text =
`Valued at ${gem.definition.initialGemValue + gem.currentGemImprovement().gemValueUp} gold. ` +
gem.definition.description;
} }
public Show(x, y) { public Show(x, y) {
this.container.alpha = 1; this.container.alpha = 1;

View File

@ -15,7 +15,7 @@ export class VisualGemSlot extends GuiObject {
private frame: PIXI.Sprite; private frame: PIXI.Sprite;
public i: number = 0; public i: number = 0;
public gem: Gem = null; public gem: Gem = null;
constructor(index: number, parent: PIXI.Container, gem: Gem | null) { constructor(index: number, parent: PIXI.Container, gem: Gem | null, extra?) {
super(true); super(true);
let gtexture; let gtexture;
this.i = index; this.i = index;
@ -24,7 +24,11 @@ export class VisualGemSlot extends GuiObject {
this.background = new PIXI.Sprite({ this.background = new PIXI.Sprite({
texture: GameAssets.Frame01Texture, texture: GameAssets.Frame01Texture,
}); });
if (gem == null) { if (gem == null && !extra) {
gtexture = GameAssets.PlusIconTexture;
} else if (extra == 'SELL') {
gtexture = GameAssets.GoldTexture;
} else if (extra == 'UPGRADE') {
gtexture = GameAssets.PlusIconTexture; gtexture = GameAssets.PlusIconTexture;
} else { } else {
gtexture = gem.texture; gtexture = gem.texture;
@ -36,12 +40,24 @@ export class VisualGemSlot extends GuiObject {
}); });
this.background.width = Engine.GridCellSize; this.background.width = Engine.GridCellSize;
this.background.height = Engine.GridCellSize; this.background.height = Engine.GridCellSize;
if (gem == null) { if (gem == null && !extra) {
this.iconSprite.x = Engine.GridCellSize / 2; this.iconSprite.x = Engine.GridCellSize / 2;
this.iconSprite.y = Engine.GridCellSize / 2; this.iconSprite.y = Engine.GridCellSize / 2;
this.iconSprite.width = Engine.GridCellSize / 2; this.iconSprite.width = Engine.GridCellSize / 2;
this.iconSprite.height = Engine.GridCellSize / 2; this.iconSprite.height = Engine.GridCellSize / 2;
this.iconSprite.anchor.set(0.5, 0.5); this.iconSprite.anchor.set(0.5, 0.5);
} else if (extra == 'SELL') {
this.iconSprite.x = 4;
this.iconSprite.y = 4;
this.iconSprite.width = Engine.GridCellSize - 8;
this.iconSprite.height = Engine.GridCellSize - 8;
} else if (extra == 'UPGRADE') {
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.tint = 0x2df937;
this.iconSprite.anchor.set(0.5, 0.5);
} else { } else {
this.iconSprite.x = 4; this.iconSprite.x = 4;
this.iconSprite.y = 4; this.iconSprite.y = 4;