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/index.html b/index.html index 707302d..4528b2a 100644 --- a/index.html +++ b/index.html @@ -2,11 +2,10 @@ - - Bastion App + Bastion: The Watcher's Lament diff --git a/public/TiledTDThree64.png b/public/TiledTDThree64.png new file mode 100644 index 0000000..2abadf3 Binary files /dev/null and b/public/TiledTDThree64.png differ diff --git a/public/Tileset.tsx b/public/Tileset.tsx index 7f536c1..f109f67 100644 --- a/public/Tileset.tsx +++ b/public/Tileset.tsx @@ -1,4 +1,4 @@ - + diff --git a/public/assets/creeps/basic/0.png b/public/assets/creeps/basic/0.png index aca768d..05b90b7 100644 Binary files a/public/assets/creeps/basic/0.png and b/public/assets/creeps/basic/0.png differ diff --git a/public/assets/creeps/basic/1.png b/public/assets/creeps/basic/1.png index b060a5a..d978a9a 100644 Binary files a/public/assets/creeps/basic/1.png and b/public/assets/creeps/basic/1.png differ diff --git a/public/assets/creeps/basic/10.png b/public/assets/creeps/basic/10.png index 84e98db..48b4f7d 100644 Binary files a/public/assets/creeps/basic/10.png and b/public/assets/creeps/basic/10.png differ diff --git a/public/assets/creeps/basic/11.png b/public/assets/creeps/basic/11.png index b060a5a..d978a9a 100644 Binary files a/public/assets/creeps/basic/11.png and b/public/assets/creeps/basic/11.png differ diff --git a/public/assets/creeps/basic/2.png b/public/assets/creeps/basic/2.png index 84e98db..48b4f7d 100644 Binary files a/public/assets/creeps/basic/2.png and b/public/assets/creeps/basic/2.png differ diff --git a/public/assets/creeps/basic/3.png b/public/assets/creeps/basic/3.png index 80bb092..06a46df 100644 Binary files a/public/assets/creeps/basic/3.png and b/public/assets/creeps/basic/3.png differ diff --git a/public/assets/creeps/basic/4.png b/public/assets/creeps/basic/4.png index 4c7cd40..139737c 100644 Binary files a/public/assets/creeps/basic/4.png and b/public/assets/creeps/basic/4.png differ diff --git a/public/assets/creeps/basic/5.png b/public/assets/creeps/basic/5.png index 4f35905..fac6dd7 100644 Binary files a/public/assets/creeps/basic/5.png and b/public/assets/creeps/basic/5.png differ diff --git a/public/assets/creeps/basic/6.png b/public/assets/creeps/basic/6.png index 65a8abd..db9bc87 100644 Binary files a/public/assets/creeps/basic/6.png and b/public/assets/creeps/basic/6.png differ diff --git a/public/assets/creeps/basic/7.png b/public/assets/creeps/basic/7.png index 4f35905..fac6dd7 100644 Binary files a/public/assets/creeps/basic/7.png and b/public/assets/creeps/basic/7.png differ diff --git a/public/assets/creeps/basic/8.png b/public/assets/creeps/basic/8.png index 4c7cd40..139737c 100644 Binary files a/public/assets/creeps/basic/8.png and b/public/assets/creeps/basic/8.png differ diff --git a/public/assets/creeps/basic/9.png b/public/assets/creeps/basic/9.png index 80bb092..06a46df 100644 Binary files a/public/assets/creeps/basic/9.png and b/public/assets/creeps/basic/9.png differ diff --git a/public/assets/creeps/cloaker/0.png b/public/assets/creeps/cloaker/0.png new file mode 100644 index 0000000..bcbc3af Binary files /dev/null and b/public/assets/creeps/cloaker/0.png differ diff --git a/public/assets/creeps/cloaker/1.png b/public/assets/creeps/cloaker/1.png new file mode 100644 index 0000000..200bc9e Binary files /dev/null and b/public/assets/creeps/cloaker/1.png differ diff --git a/public/assets/creeps/cloaker/10.png b/public/assets/creeps/cloaker/10.png new file mode 100644 index 0000000..3df93f8 Binary files /dev/null and b/public/assets/creeps/cloaker/10.png differ diff --git a/public/assets/creeps/cloaker/11.png b/public/assets/creeps/cloaker/11.png new file mode 100644 index 0000000..2512c1c Binary files /dev/null and b/public/assets/creeps/cloaker/11.png differ diff --git a/public/assets/creeps/cloaker/2.png b/public/assets/creeps/cloaker/2.png new file mode 100644 index 0000000..5e0d41c Binary files /dev/null and b/public/assets/creeps/cloaker/2.png differ diff --git a/public/assets/creeps/cloaker/3.png b/public/assets/creeps/cloaker/3.png new file mode 100644 index 0000000..779efea Binary files /dev/null and b/public/assets/creeps/cloaker/3.png differ diff --git a/public/assets/creeps/cloaker/4.png b/public/assets/creeps/cloaker/4.png new file mode 100644 index 0000000..95924e0 Binary files /dev/null and b/public/assets/creeps/cloaker/4.png differ diff --git a/public/assets/creeps/cloaker/5.png b/public/assets/creeps/cloaker/5.png new file mode 100644 index 0000000..56ce34e Binary files /dev/null and b/public/assets/creeps/cloaker/5.png differ diff --git a/public/assets/creeps/cloaker/6.png b/public/assets/creeps/cloaker/6.png new file mode 100644 index 0000000..da52a20 Binary files /dev/null and b/public/assets/creeps/cloaker/6.png differ diff --git a/public/assets/creeps/cloaker/7.png b/public/assets/creeps/cloaker/7.png new file mode 100644 index 0000000..4baad15 Binary files /dev/null and b/public/assets/creeps/cloaker/7.png differ diff --git a/public/assets/creeps/cloaker/8.png b/public/assets/creeps/cloaker/8.png new file mode 100644 index 0000000..5030dca Binary files /dev/null and b/public/assets/creeps/cloaker/8.png differ diff --git a/public/assets/creeps/cloaker/9.png b/public/assets/creeps/cloaker/9.png new file mode 100644 index 0000000..4ac8690 Binary files /dev/null and b/public/assets/creeps/cloaker/9.png differ diff --git a/public/assets/creeps/demon/0.png b/public/assets/creeps/demon/0.png new file mode 100644 index 0000000..4981186 Binary files /dev/null and b/public/assets/creeps/demon/0.png differ diff --git a/public/assets/creeps/demon/1.png b/public/assets/creeps/demon/1.png new file mode 100644 index 0000000..c082f4d Binary files /dev/null and b/public/assets/creeps/demon/1.png differ diff --git a/public/assets/creeps/demon/2.png b/public/assets/creeps/demon/2.png new file mode 100644 index 0000000..284d170 Binary files /dev/null and b/public/assets/creeps/demon/2.png differ diff --git a/public/assets/creeps/demon/3.png b/public/assets/creeps/demon/3.png new file mode 100644 index 0000000..22e9cc8 Binary files /dev/null and b/public/assets/creeps/demon/3.png differ diff --git a/public/assets/creeps/demon/4.png b/public/assets/creeps/demon/4.png new file mode 100644 index 0000000..14e592a Binary files /dev/null and b/public/assets/creeps/demon/4.png differ diff --git a/public/assets/creeps/demon/5.png b/public/assets/creeps/demon/5.png new file mode 100644 index 0000000..0590e06 Binary files /dev/null and b/public/assets/creeps/demon/5.png differ diff --git a/public/assets/creeps/demon/6.png b/public/assets/creeps/demon/6.png new file mode 100644 index 0000000..d59ee3e Binary files /dev/null and b/public/assets/creeps/demon/6.png differ diff --git a/public/assets/creeps/demon/7.png b/public/assets/creeps/demon/7.png new file mode 100644 index 0000000..f40d8ab Binary files /dev/null and b/public/assets/creeps/demon/7.png differ diff --git a/public/assets/creeps/maker/1.png b/public/assets/creeps/maker/1.png new file mode 100644 index 0000000..042a582 Binary files /dev/null and b/public/assets/creeps/maker/1.png differ diff --git a/public/assets/creeps/quick/10.png b/public/assets/creeps/maker/10.png similarity index 98% rename from public/assets/creeps/quick/10.png rename to public/assets/creeps/maker/10.png index aa2c1c5..55fa573 100644 Binary files a/public/assets/creeps/quick/10.png and b/public/assets/creeps/maker/10.png differ diff --git a/public/assets/creeps/quick/11.png b/public/assets/creeps/maker/11.png similarity index 98% rename from public/assets/creeps/quick/11.png rename to public/assets/creeps/maker/11.png index ebf3fbf..042a582 100644 Binary files a/public/assets/creeps/quick/11.png and b/public/assets/creeps/maker/11.png differ diff --git a/public/assets/creeps/maker/2.png b/public/assets/creeps/maker/2.png new file mode 100644 index 0000000..55fa573 Binary files /dev/null and b/public/assets/creeps/maker/2.png differ diff --git a/public/assets/creeps/maker/3.png b/public/assets/creeps/maker/3.png new file mode 100644 index 0000000..59275bc Binary files /dev/null and b/public/assets/creeps/maker/3.png differ diff --git a/public/assets/creeps/maker/4.png b/public/assets/creeps/maker/4.png new file mode 100644 index 0000000..e120c5c Binary files /dev/null and b/public/assets/creeps/maker/4.png differ diff --git a/public/assets/creeps/maker/5.png b/public/assets/creeps/maker/5.png new file mode 100644 index 0000000..510cbc1 Binary files /dev/null and b/public/assets/creeps/maker/5.png differ diff --git a/public/assets/creeps/maker/6.png b/public/assets/creeps/maker/6.png new file mode 100644 index 0000000..3ded504 Binary files /dev/null and b/public/assets/creeps/maker/6.png differ diff --git a/public/assets/creeps/maker/7.png b/public/assets/creeps/maker/7.png new file mode 100644 index 0000000..510cbc1 Binary files /dev/null and b/public/assets/creeps/maker/7.png differ diff --git a/public/assets/creeps/quick/8.png b/public/assets/creeps/maker/8.png similarity index 98% rename from public/assets/creeps/quick/8.png rename to public/assets/creeps/maker/8.png index fa89fb3..e120c5c 100644 Binary files a/public/assets/creeps/quick/8.png and b/public/assets/creeps/maker/8.png differ diff --git a/public/assets/creeps/quick/9.png b/public/assets/creeps/maker/9.png similarity index 98% rename from public/assets/creeps/quick/9.png rename to public/assets/creeps/maker/9.png index d5778d8..59275bc 100644 Binary files a/public/assets/creeps/quick/9.png and b/public/assets/creeps/maker/9.png differ diff --git a/public/assets/creeps/quick/0.png b/public/assets/creeps/quick/0.png index 946b183..516c3c1 100644 Binary files a/public/assets/creeps/quick/0.png and b/public/assets/creeps/quick/0.png differ diff --git a/public/assets/creeps/quick/1.png b/public/assets/creeps/quick/1.png index ebf3fbf..409cd58 100644 Binary files a/public/assets/creeps/quick/1.png and b/public/assets/creeps/quick/1.png differ diff --git a/public/assets/creeps/quick/2.png b/public/assets/creeps/quick/2.png index aa2c1c5..90d2e95 100644 Binary files a/public/assets/creeps/quick/2.png and b/public/assets/creeps/quick/2.png differ diff --git a/public/assets/creeps/quick/3.png b/public/assets/creeps/quick/3.png index d5778d8..83250d4 100644 Binary files a/public/assets/creeps/quick/3.png and b/public/assets/creeps/quick/3.png differ diff --git a/public/assets/creeps/quick/4.png b/public/assets/creeps/quick/4.png index fa89fb3..f3f6d65 100644 Binary files a/public/assets/creeps/quick/4.png and b/public/assets/creeps/quick/4.png differ diff --git a/public/assets/creeps/quick/5.png b/public/assets/creeps/quick/5.png index 5851161..046d3a4 100644 Binary files a/public/assets/creeps/quick/5.png and b/public/assets/creeps/quick/5.png differ diff --git a/public/assets/creeps/quick/6.png b/public/assets/creeps/quick/6.png index 195f03a..8038456 100644 Binary files a/public/assets/creeps/quick/6.png and b/public/assets/creeps/quick/6.png differ diff --git a/public/assets/creeps/quick/7.png b/public/assets/creeps/quick/7.png index 5851161..65f4bbe 100644 Binary files a/public/assets/creeps/quick/7.png and b/public/assets/creeps/quick/7.png differ diff --git a/public/assets/creeps/tank/0.png b/public/assets/creeps/tank/0.png index a8aed52..f409430 100644 Binary files a/public/assets/creeps/tank/0.png and b/public/assets/creeps/tank/0.png differ diff --git a/public/assets/creeps/tank/1.png b/public/assets/creeps/tank/1.png index fbb5014..469e13d 100644 Binary files a/public/assets/creeps/tank/1.png and b/public/assets/creeps/tank/1.png differ diff --git a/public/assets/creeps/tank/10.png b/public/assets/creeps/tank/10.png index a652962..c67f03f 100644 Binary files a/public/assets/creeps/tank/10.png and b/public/assets/creeps/tank/10.png differ diff --git a/public/assets/creeps/tank/11.png b/public/assets/creeps/tank/11.png index fbb5014..469e13d 100644 Binary files a/public/assets/creeps/tank/11.png and b/public/assets/creeps/tank/11.png differ diff --git a/public/assets/creeps/tank/2.png b/public/assets/creeps/tank/2.png index a652962..c67f03f 100644 Binary files a/public/assets/creeps/tank/2.png and b/public/assets/creeps/tank/2.png differ diff --git a/public/assets/creeps/tank/3.png b/public/assets/creeps/tank/3.png index f6da5a0..ea50556 100644 Binary files a/public/assets/creeps/tank/3.png and b/public/assets/creeps/tank/3.png differ diff --git a/public/assets/creeps/tank/4.png b/public/assets/creeps/tank/4.png index 7df3630..ee42532 100644 Binary files a/public/assets/creeps/tank/4.png and b/public/assets/creeps/tank/4.png differ diff --git a/public/assets/creeps/tank/5.png b/public/assets/creeps/tank/5.png index 027af78..88404b0 100644 Binary files a/public/assets/creeps/tank/5.png and b/public/assets/creeps/tank/5.png differ diff --git a/public/assets/creeps/tank/6.png b/public/assets/creeps/tank/6.png index edac5a5..1e04a28 100644 Binary files a/public/assets/creeps/tank/6.png and b/public/assets/creeps/tank/6.png differ diff --git a/public/assets/creeps/tank/7.png b/public/assets/creeps/tank/7.png index 537912d..88404b0 100644 Binary files a/public/assets/creeps/tank/7.png and b/public/assets/creeps/tank/7.png differ diff --git a/public/assets/creeps/tank/8.png b/public/assets/creeps/tank/8.png index 3ba4085..ee42532 100644 Binary files a/public/assets/creeps/tank/8.png and b/public/assets/creeps/tank/8.png differ diff --git a/public/assets/creeps/tank/9.png b/public/assets/creeps/tank/9.png index 9e6e707..6531367 100644 Binary files a/public/assets/creeps/tank/9.png and b/public/assets/creeps/tank/9.png differ diff --git a/public/assets/json/Creeps.json b/public/assets/json/Creeps.json index 0679b07..7eab6fa 100644 --- a/public/assets/json/Creeps.json +++ b/public/assets/json/Creeps.json @@ -5,7 +5,7 @@ "textureArrayLength": 12, "stats": { "health": 5, - "speed": 6, + "speed": 4, "special": null, "resistance": { "physical": 0, @@ -19,10 +19,10 @@ { "name": "quick", "textures": [], - "textureArrayLength": 12, + "textureArrayLength": 8, "stats": { - "health": 7, - "speed": 2.4, + "health": 2, + "speed": 6, "special": null, "resistance": { "physical": 0, @@ -38,8 +38,8 @@ "textures": [], "textureArrayLength": 12, "stats": { - "health": 7, - "speed": 2.4, + "health": 12, + "speed": 2, "special": null, "resistance": { "physical": 0, diff --git a/public/assets/json/Gems.json b/public/assets/json/Gems.json index aab44ba..23ef124 100644 --- a/public/assets/json/Gems.json +++ b/public/assets/json/Gems.json @@ -2,6 +2,7 @@ { "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.", + "color": "red", "type": "Fire", "totalLevels": 2, "textures": [], @@ -11,7 +12,7 @@ "genericImprovements": [ { "damageUp": 2, - "attackSpeedUp": 100, + "attackSpeedUp": 10, "rangeUp": 0.5, "timeToLiveUp": 0, "pierceUp": 1, @@ -19,7 +20,7 @@ }, { "damageUp": 2, - "attackSpeedUp": 100, + "attackSpeedUp": 10, "rangeUp": 0.5, "timeToLiveUp": 0, "pierceUp": 1, @@ -30,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 } @@ -46,6 +47,7 @@ { "name": "Yeti Gem", "description": "Yeti gem description. Something something, write this while drunk or something.", + "color": "#32e4fc", "type": "Yeti", "totalLevels": 2, "textures": [], @@ -55,7 +57,7 @@ "genericImprovements": [ { "damageUp": 2, - "attackSpeedUp": 100, + "attackSpeedUp": 10, "rangeUp": 0.5, "timeToLiveUp": 0, "pierceUp": 1, @@ -63,7 +65,7 @@ }, { "damageUp": 2, - "attackSpeedUp": 100, + "attackSpeedUp": 10, "rangeUp": 0.5, "timeToLiveUp": 0, "pierceUp": 1, @@ -90,6 +92,7 @@ { "name": "Titalium Gem", "description": "Titalium gem description. Something something zombie creeps working for you something something.", + "color": "pink", "type": "Titalium", "totalLevels": 3, "textures": [], @@ -99,7 +102,7 @@ "genericImprovements": [ { "damageUp": 2, - "attackSpeedUp": 100, + "attackSpeedUp": 10, "rangeUp": 0.5, "timeToLiveUp": 0, "pierceUp": 1, @@ -107,7 +110,7 @@ }, { "damageUp": 2, - "attackSpeedUp": 100, + "attackSpeedUp": 10, "rangeUp": 0, "timeToLiveUp": 0, "pierceUp": 1, @@ -115,7 +118,7 @@ }, { "damageUp": 2, - "attackSpeedUp": 100, + "attackSpeedUp": 10, "rangeUp": 0, "timeToLiveUp": 0, "pierceUp": 1, @@ -149,6 +152,7 @@ { "name": "Soulforge Gem", "description": "Soulforge gem description, have to write later.", + "color": "gray", "type": "Soulforge", "totalLevels": 2, "textures": [], @@ -158,7 +162,7 @@ "genericImprovements": [ { "damageUp": 2, - "attackSpeedUp": 100, + "attackSpeedUp": 10, "rangeUp": 0.5, "timeToLiveUp": 0, "pierceUp": 1, @@ -166,7 +170,7 @@ }, { "damageUp": 2, - "attackSpeedUp": 100, + "attackSpeedUp": 10, "rangeUp": 0.5, "timeToLiveUp": 0, "pierceUp": 1, diff --git a/public/assets/json/Towers.json b/public/assets/json/Towers.json index 1a609f7..34a088b 100644 --- a/public/assets/json/Towers.json +++ b/public/assets/json/Towers.json @@ -9,26 +9,30 @@ "description": "The building block of society, nothing more basic exists.", "stats": { "damage": 2, - "cooldown": 60, + "cooldown": 120, "gemSlotsAmount": 2, "cost": 100, - "range": 3 + "range": 3, + "timeToLive": 120, + "pierce": 1 } }, { "name": "Circle Tower", - "behaviour": "BasicTowerBehaviour", + "behaviour": "CircleTowerBehaviour", "sprite": "circle_tower", "texture": null, "projectileTextures": [], "projectileTexturesArrayLength": 4, "description": "If you feel a little circular.", "stats": { - "damage": 6, - "cooldown": 180, + "damage": 2, + "cooldown": 120, "gemSlotsAmount": 3, "cost": 125, - "range": 3 + "range": 2, + "timeToLive": 8, + "pierce": 30 } } ] diff --git a/public/assets/maps/01_first_steps.png b/public/assets/maps/01_first_steps.png new file mode 100644 index 0000000..0d50143 Binary files /dev/null and b/public/assets/maps/01_first_steps.png differ diff --git a/public/assets/maps/02_the_turn.png b/public/assets/maps/02_the_turn.png new file mode 100644 index 0000000..cd31cf8 Binary files /dev/null and b/public/assets/maps/02_the_turn.png differ diff --git a/public/assets/maps/03_fork_in_the_road.png b/public/assets/maps/03_fork_in_the_road.png new file mode 100644 index 0000000..591e55f Binary files /dev/null and b/public/assets/maps/03_fork_in_the_road.png differ diff --git a/public/assets/maps/04_crossroads.png b/public/assets/maps/04_crossroads.png new file mode 100644 index 0000000..9b05fe7 Binary files /dev/null and b/public/assets/maps/04_crossroads.png differ diff --git a/public/assets/maps/05_the_maze.png b/public/assets/maps/05_the_maze.png new file mode 100644 index 0000000..bbbc554 Binary files /dev/null and b/public/assets/maps/05_the_maze.png differ diff --git a/public/assets/maps/06_multiple_fronts.png b/public/assets/maps/06_multiple_fronts.png new file mode 100644 index 0000000..851333f Binary files /dev/null and b/public/assets/maps/06_multiple_fronts.png differ diff --git a/public/assets/maps/07_final_stretch.png b/public/assets/maps/07_final_stretch.png new file mode 100644 index 0000000..0bd3bc9 Binary files /dev/null and b/public/assets/maps/07_final_stretch.png differ diff --git a/public/assets/maps/mission_01.png b/public/assets/maps/mission_01.png deleted file mode 100644 index 6d14258..0000000 Binary files a/public/assets/maps/mission_01.png and /dev/null differ diff --git a/public/assets/missions/01_first_steps.json b/public/assets/missions/01_first_steps.json new file mode 100644 index 0000000..313c3ea --- /dev/null +++ b/public/assets/missions/01_first_steps.json @@ -0,0 +1,76 @@ +{ + "name": "First Steps", + "description": "Welcome to your first defense mission. Protect the exit from basic invaders.", + "mapImage": { + "url": "/assets/maps/01_first_steps.png" + }, + "gameMap": { + "rows": 17, + "columns": 25, + "cells": [ + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + ], + "paths": [ + [ + [0, 8], + [1, 8], + [2, 8], + [3, 8], + [4, 8], + [5, 8], + [6, 8], + [7, 8], + [8, 8], + [9, 8], + [10, 8], + [11, 8], + [12, 8], + [13, 8], + [14, 8], + [15, 8], + [16, 8], + [17, 8], + [18, 8], + [19, 8], + [20, 8], + [21, 8], + [22, 8], + [23, 8], + [24, 8] + ] + ] + }, + "rounds": [ + { + "waves": [ + { + "firstCreepSpawnTick": 500, + "spawnIntervalTicks": 1000, + "creeps": [0, 0, 0] + }, + { + "firstCreepSpawnTick": 4000, + "spawnIntervalTicks": 1000, + "creeps": [0, 0, 0, 0] + } + ], + "offeredGems": [0, 1] + } + ] +} diff --git a/public/assets/missions/02_the_turn.json b/public/assets/missions/02_the_turn.json new file mode 100644 index 0000000..1cb273f --- /dev/null +++ b/public/assets/missions/02_the_turn.json @@ -0,0 +1,128 @@ +{ + "name": "The Turn", + "description": "Start using some better strategies for this one.", + "mapImage": { + "url": "/assets/maps/02_the_turn.png" + }, + "gameMap": { + "rows": 17, + "columns": 25, + "cells": [ + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 1, 1, 1, 1, 1, 0, 9, 9, 9, 9, 9], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 9, 0, 1, 1, 1, 1, 1, 0, 9, 0, 0, 0, 0], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 9, 0, 1, 1, 1, 1, 1, 0, 9, 0, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 9, 0, 1, 1, 1, 1, 1, 0, 9, 0, 1, 1, 1], + [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 1, 1, 1, 1, 1, 0, 9, 0, 1, 1, 1], + [1, 1, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 1, 1, 1, 1, 1, 0, 9, 0, 1, 1, 1], + [1, 1, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 9, 0, 1, 1, 1], + [1, 1, 0, 9, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 9, 0, 1, 1, 1], + [1, 1, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 1, 1, 1], + [1, 1, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 1, 1, 1], + [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + ], + "paths": [ + [ + [0, 4], + [1, 4], + [2, 4], + [3, 4], + [4, 4], + [5, 4], + [6, 4], + [7, 4], + [8, 4], + [9, 4], + [10, 4], + [11, 4], + [12, 4], + [12, 5], + [12, 6], + [12, 7], + [12, 8], + [12, 9], + [12, 10], + [11, 10], + [10, 10], + [9, 10], + [8, 10], + [7, 10], + [6, 10], + [5, 10], + [4, 10], + [3, 10], + [3, 11], + [3, 12], + [3, 13], + [3, 14], + [4, 14], + [5, 14], + [6, 14], + [7, 14], + [8, 14], + [9, 14], + [10, 14], + [11, 14], + [12, 14], + [13, 14], + [14, 14], + [15, 14], + [16, 14], + [17, 14], + [18, 14], + [19, 14], + [20, 14], + [20, 13], + [20, 12], + [20, 11], + [20, 10], + [20, 9], + [20, 8], + [20, 7], + [20, 6], + [20, 5], + [21, 5], + [22, 5], + [23, 5], + [24, 5] + ] + ] + }, + "rounds": [ + { + "waves": [ + { + "firstCreepSpawnTick": 500, + "spawnIntervalTicks": 1000, + "creeps": [0, 0, 0, 0, 0] + } + ], + "offeredGems": [0, 0, 0, 0] + }, + { + "waves": [ + { + "firstCreepSpawnTick": 500, + "spawnIntervalTicks": 1000, + "creeps": [1, 1, 1, 1, 1] + } + ], + "offeredGems": [0, 1, 2, 3] + }, + { + "waves": [ + { + "firstCreepSpawnTick": 500, + "spawnIntervalTicks": 1000, + "creeps": [2, 2, 2, 2, 2] + } + ], + "offeredGems": [0, 1, 2, 3] + } + ] +} diff --git a/public/assets/missions/03_fork_in_the_road.json b/public/assets/missions/03_fork_in_the_road.json new file mode 100644 index 0000000..a28f74a --- /dev/null +++ b/public/assets/missions/03_fork_in_the_road.json @@ -0,0 +1,141 @@ +{ + "name": "Fork in the Road", + "description": "Multiple paths and heavily armored tank units test your defensive strategy.", + "mapImage": { + "url": "/assets/maps/03_fork_in_the_road.png" + }, + "gameMap": { + "rows": 17, + "columns": 25, + "cells": [ + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 1, 1, 1, 1, 1, 1, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9], + [1, 1, 1, 1, 1, 1, 1, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 1, 1, 1, 1, 1, 1, 0, 9, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 0, 9, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 1, 1, 1, 1, 1, 1, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9], + [1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + ], + "paths": [ + [ + [0, 8], + [1, 8], + [2, 8], + [3, 8], + [4, 8], + [5, 8], + [6, 8], + [7, 8], + [8, 8], + [8, 7], + [8, 6], + [8, 5], + [8, 4], + [9, 4], + [10, 4], + [11, 4], + [12, 4], + [13, 4], + [14, 4], + [15, 4], + [16, 4], + [17, 4], + [18, 4], + [19, 4], + [20, 4], + [21, 4], + [22, 4], + [23, 4], + [24, 4] + ], + [ + [0, 8], + [1, 8], + [2, 8], + [3, 8], + [4, 8], + [5, 8], + [6, 8], + [7, 8], + [8, 8], + [8, 9], + [8, 10], + [8, 11], + [8, 12], + [9, 12], + [10, 12], + [11, 12], + [12, 12], + [13, 12], + [14, 12], + [15, 12], + [16, 12], + [17, 12], + [18, 12], + [19, 12], + [20, 12], + [21, 12], + [22, 12], + [23, 12], + [24, 12] + ] + ] + }, + "rounds": [ + { + "waves": [ + { + "firstCreepSpawnTick": 500, + "spawnIntervalTicks": 1000, + "creeps": [0, 0, 0, 0, 0] + }, + { + "firstCreepSpawnTick": 6000, + "spawnIntervalTicks": 800, + "creeps": [1, 1, 1, 1] + } + ], + "offeredGems": [0, 1, 2] + }, + { + "waves": [ + { + "firstCreepSpawnTick": 500, + "spawnIntervalTicks": 900, + "creeps": [2, 0, 0, 2] + }, + { + "firstCreepSpawnTick": 5000, + "spawnIntervalTicks": 700, + "creeps": [1, 1, 2, 1, 1] + } + ], + "offeredGems": [0, 1, 2, 3] + }, + { + "waves": [ + { + "firstCreepSpawnTick": 500, + "spawnIntervalTicks": 800, + "creeps": [2, 1, 0, 2, 1, 0] + }, + { + "firstCreepSpawnTick": 6000, + "spawnIntervalTicks": 600, + "creeps": [2, 2, 1, 1, 2, 2] + } + ], + "offeredGems": [0, 1, 2, 3] + } + ] +} diff --git a/public/assets/missions/04_crossroads.json b/public/assets/missions/04_crossroads.json new file mode 100644 index 0000000..2fa69ef --- /dev/null +++ b/public/assets/missions/04_crossroads.json @@ -0,0 +1,169 @@ +{ + "name": "Crossroads", + "description": "Multiple entry points and intersecting paths require careful tower placement and strategy.", + "mapImage": { + "url": "/assets/maps/04_crossroads.png" + }, + "gameMap": { + "rows": 17, + "columns": 25, + "cells": [ + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 9, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 0, 9, 9, 9, 9, 9, 9, 9, 9, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 0, 9, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 0, 9, 9, 9, 9, 9, 9, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0], + [1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 9, 0, 1, 1, 1, 1, 1, 0, 9, 9, 9, 9, 9], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 9, 0, 1, 1, 1, 1, 1, 0, 9, 0, 0, 0, 0], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 9, 0, 1, 1, 1, 1, 1, 0, 9, 0, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 9, 0, 0, 0, 0, 0, 0, 0, 9, 0, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 1, 1, 1], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 1, 1, 1, 1, 1, 1, 1, 1], + [9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 1, 1, 1, 1, 1, 1, 1, 1], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + ], + "paths": [ + [ + [0, 13], + [1, 13], + [2, 13], + [3, 13], + [4, 13], + [5, 13], + [6, 13], + [7, 13], + [8, 13], + [9, 13], + [10, 13], + [11, 13], + [12, 13], + [13, 13], + [14, 13], + [15, 13], + [15, 12], + [15, 11], + [15, 10], + [16, 10], + [17, 10], + [18, 10], + [19, 10], + [20, 10], + [20, 9], + [20, 8], + [20, 7], + [20, 6], + [21, 6], + [22, 6], + [23, 6], + [24, 6] + ], + [ + [14, 0], + [14, 1], + [14, 2], + [13, 2], + [12, 2], + [11, 2], + [10, 2], + [9, 2], + [8, 2], + [7, 2], + [7, 3], + [7, 4], + [7, 5], + [8, 5], + [9, 5], + [10, 5], + [11, 5], + [12, 5], + [12, 6], + [12, 7], + [12, 8], + [12, 9], + [12, 10], + [13, 10], + [14, 10], + [15, 10], + [16, 10], + [17, 10], + [18, 10], + [19, 10], + [20, 10], + [20, 9], + [20, 8], + [20, 7], + [20, 6], + [21, 6], + [22, 6], + [23, 6], + [24, 6] + ] + ] + }, + "rounds": [ + { + "waves": [ + { + "firstCreepSpawnTick": 500, + "spawnIntervalTicks": 1000, + "creeps": [0, 0, 1, 0, 0] + }, + { + "firstCreepSpawnTick": 6000, + "spawnIntervalTicks": 800, + "creeps": [1, 1, 2, 1, 1] + } + ], + "offeredGems": [0, 1, 2, 0] + }, + { + "waves": [ + { + "firstCreepSpawnTick": 500, + "spawnIntervalTicks": 800, + "creeps": [2, 0, 2, 0, 2, 0] + }, + { + "firstCreepSpawnTick": 5000, + "spawnIntervalTicks": 600, + "creeps": [1, 2, 1, 2, 1, 2] + } + ], + "offeredGems": [0, 1, 2, 3] + }, + { + "waves": [ + { + "firstCreepSpawnTick": 500, + "spawnIntervalTicks": 700, + "creeps": [2, 1, 0, 2, 1, 0, 2] + }, + { + "firstCreepSpawnTick": 6000, + "spawnIntervalTicks": 500, + "creeps": [2, 2, 1, 1, 2, 2, 1] + } + ], + "offeredGems": [0, 1, 2, 3] + }, + { + "waves": [ + { + "firstCreepSpawnTick": 500, + "spawnIntervalTicks": 600, + "creeps": [2, 2, 2, 1, 1, 1, 0, 0] + }, + { + "firstCreepSpawnTick": 7000, + "spawnIntervalTicks": 400, + "creeps": [2, 2, 2, 2, 1, 1, 1, 1] + } + ], + "offeredGems": [0, 1, 2, 3] + } + ] +} diff --git a/public/assets/missions/05_the_maze.json b/public/assets/missions/05_the_maze.json new file mode 100644 index 0000000..4d52af2 --- /dev/null +++ b/public/assets/missions/05_the_maze.json @@ -0,0 +1,263 @@ +{ + "name": "The Maze", + "description": "A complex network of paths requires masterful tower placement and timing.", + "mapImage": { + "url": "/assets/maps/05_the_maze.png" + }, + "gameMap": { + "rows": 17, + "columns": 25, + "cells": [ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1], + [9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 1, 1, 1, 1, 1, 1], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 1, 1, 1, 1, 1, 1], + [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 1, 1, 1, 1, 1, 1], + [1, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 9, 0, 1, 1, 1, 1, 1, 1], + [1, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 9, 0, 1, 1, 1, 1, 1, 1], + [1, 0, 9, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 9, 0, 0, 9, 0, 0, 0, 0, 0, 0, 1], + [1, 0, 9, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0], + [1, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 9, 0, 0, 9, 0, 0, 0, 0, 9, 9, 9], + [1, 0, 9, 0, 0, 9, 9, 9, 9, 9, 9, 0, 1, 0, 9, 0, 0, 9, 0, 1, 1, 0, 0, 0, 0], + [1, 0, 9, 0, 0, 9, 0, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 9, 0, 1, 1, 1, 1, 1, 1], + [1, 0, 9, 0, 0, 9, 0, 1, 1, 0, 9, 9, 9, 9, 9, 0, 0, 9, 0, 1, 1, 1, 1, 1, 1], + [1, 0, 9, 0, 0, 9, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 1, 1, 1, 1, 1, 1], + [1, 0, 9, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 1, 1, 1, 1, 1, 1], + [0, 0, 9, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 1, 1, 1, 1, 1, 1], + [9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1], + [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + ], + "paths": [ + [ + [0, 1], + [1, 1], + [2, 1], + [3, 1], + [4, 1], + [5, 1], + [6, 1], + [7, 1], + [8, 1], + [9, 1], + [10, 1], + [11, 1], + [12, 1], + [13, 1], + [14, 1], + [15, 1], + [16, 1], + [17, 1], + [17, 2], + [17, 3], + [17, 4], + [17, 5], + [17, 6], + [17, 7], + [17, 8], + [17, 9], + [17, 10], + [17, 11], + [17, 12], + [17, 13], + [17, 14], + [16, 14], + [15, 14], + [14, 14], + [13, 14], + [12, 14], + [11, 14], + [10, 14], + [9, 14], + [8, 14], + [7, 14], + [6, 14], + [5, 14], + [5, 13], + [5, 12], + [5, 11], + [5, 10], + [5, 9], + [6, 9], + [7, 9], + [8, 9], + [9, 9], + [10, 9], + [10, 10], + [10, 11], + [11, 11], + [12, 11], + [13, 11], + [14, 11], + [14, 10], + [14, 9], + [14, 8], + [14, 7], + [15, 7], + [16, 7], + [17, 7], + [18, 7], + [19, 7], + [20, 7], + [21, 7], + [22, 7], + [22, 8], + [23, 8], + [24, 8] + ], + [ + [0, 15], + [1, 15], + [2, 15], + [2, 14], + [2, 13], + [2, 12], + [2, 11], + [2, 10], + [2, 9], + [2, 8], + [2, 7], + [2, 6], + [2, 5], + [2, 4], + [3, 4], + [4, 4], + [5, 4], + [6, 4], + [7, 4], + [8, 4], + [9, 4], + [10, 4], + [11, 4], + [12, 4], + [13, 4], + [14, 4], + [14, 5], + [14, 6], + [14, 7], + [14, 8], + [14, 9], + [14, 10], + [14, 11], + [13, 11], + [12, 11], + [11, 11], + [10, 11], + [10, 10], + [10, 9], + [9, 9], + [8, 9], + [7, 9], + [6, 9], + [5, 9], + [5, 10], + [5, 11], + [5, 12], + [5, 13], + [5, 14], + [6, 14], + [7, 14], + [8, 14], + [9, 14], + [10, 14], + [11, 14], + [12, 14], + [13, 14], + [14, 14], + [15, 14], + [16, 14], + [17, 14], + [17, 13], + [17, 12], + [17, 11], + [17, 10], + [17, 9], + [17, 8], + [17, 7], + [18, 7], + [19, 7], + [20, 7], + [21, 7], + [22, 7], + [22, 8], + [23, 8], + [24, 8] + ] + ] + }, + "rounds": [ + { + "waves": [ + { + "firstCreepSpawnTick": 500, + "spawnIntervalTicks": 800, + "creeps": [0, 1, 0, 1, 0, 1] + }, + { + "firstCreepSpawnTick": 5000, + "spawnIntervalTicks": 600, + "creeps": [1, 1, 2, 1, 1, 2] + } + ], + "offeredGems": [0, 1, 2, 3] + }, + { + "waves": [ + { + "firstCreepSpawnTick": 500, + "spawnIntervalTicks": 700, + "creeps": [2, 0, 2, 1, 2, 0, 2] + }, + { + "firstCreepSpawnTick": 6000, + "spawnIntervalTicks": 500, + "creeps": [2, 1, 2, 1, 2, 1, 2] + } + ], + "offeredGems": [0, 1, 2, 3] + }, + { + "waves": [ + { + "firstCreepSpawnTick": 500, + "spawnIntervalTicks": 600, + "creeps": [2, 2, 1, 1, 0, 2, 2, 1] + }, + { + "firstCreepSpawnTick": 6000, + "spawnIntervalTicks": 400, + "creeps": [2, 2, 2, 1, 1, 1, 2, 2] + } + ], + "offeredGems": [0, 1, 2, 3] + }, + { + "waves": [ + { + "firstCreepSpawnTick": 500, + "spawnIntervalTicks": 500, + "creeps": [2, 2, 1, 1, 2, 2, 1, 1, 2] + }, + { + "firstCreepSpawnTick": 7000, + "spawnIntervalTicks": 300, + "creeps": [2, 2, 2, 1, 1, 1, 2, 2, 2] + } + ], + "offeredGems": [0, 1, 2, 3] + }, + { + "waves": [ + { + "firstCreepSpawnTick": 500, + "spawnIntervalTicks": 400, + "creeps": [2, 2, 2, 2, 1, 1, 1, 1, 2, 2] + }, + { + "firstCreepSpawnTick": 8000, + "spawnIntervalTicks": 250, + "creeps": [2, 2, 2, 2, 2, 1, 1, 1, 1, 1] + } + ], + "offeredGems": [0, 1, 2, 3] + } + ] +} diff --git a/public/assets/missions/06_multiple_fronts.json b/public/assets/missions/06_multiple_fronts.json new file mode 100644 index 0000000..9094ddf --- /dev/null +++ b/public/assets/missions/06_multiple_fronts.json @@ -0,0 +1,199 @@ +{ + "name": "Multi-Defense", + "description": "Multiple exits must be defended simultaneously against increasingly difficult waves.", + "mapImage": { + "url": "/assets/maps/06_multiple_fronts.png" + }, + "gameMap": { + "rows": 17, + "columns": 25, + "cells": [ + [1, 1, 1, 1, 0, 9, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 0, 0, 0, 0, 9, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1], + [1, 0, 9, 9, 9, 9, 0, 1, 1, 1, 1, 1, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 1, 1], + [1, 0, 9, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 9, 0, 0, 0, 0, 0, 0, 0, 9, 0, 1, 1], + [1, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 1, 1, 1, 1, 1, 0, 9, 0, 0, 0], + [1, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 1, 1, 1, 1, 1, 0, 9, 9, 9, 9], + [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [9, 9, 9, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [0, 0, 9, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 0, 9, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 0, 9, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1], + [1, 0, 9, 0, 1, 1, 1, 1, 1, 1, 0, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0], + [1, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9], + [1, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + ], + "paths": [ + [ + [5, 0], + [5, 1], + [5, 2], + [4, 2], + [3, 2], + [2, 2], + [2, 3], + [2, 4], + [2, 5], + [3, 5], + [4, 5], + [5, 5], + [6, 5], + [7, 5], + [8, 5], + [9, 5], + [10, 5], + [11, 5], + [12, 5], + [13, 5], + [13, 4], + [13, 3], + [13, 2], + [14, 2], + [15, 2], + [16, 2], + [17, 2], + [18, 2], + [19, 2], + [20, 2], + [21, 2], + [21, 3], + [21, 4], + [21, 5], + [22, 5], + [23, 5], + [24, 5] + ], + [ + [0, 9], + [1, 9], + [2, 9], + [2, 10], + [2, 11], + [2, 12], + [2, 13], + [2, 14], + [2, 15], + [3, 15], + [4, 15], + [5, 15], + [6, 15], + [7, 15], + [8, 15], + [9, 15], + [10, 15], + [11, 15], + [11, 14], + [11, 13], + [12, 13], + [13, 13], + [14, 13], + [15, 13], + [16, 13], + [17, 13], + [17, 14], + [18, 14], + [19, 14], + [20, 14], + [21, 14], + [22, 14], + [23, 14], + [24, 14] + ] + ] + }, + "rounds": [ + { + "waves": [ + { + "firstCreepSpawnTick": 500, + "spawnIntervalTicks": 700, + "creeps": [0, 1, 0, 1, 0, 1, 0] + }, + { + "firstCreepSpawnTick": 5000, + "spawnIntervalTicks": 500, + "creeps": [1, 1, 2, 1, 1, 2, 1] + } + ], + "offeredGems": [0, 1, 2, 3] + }, + { + "waves": [ + { + "firstCreepSpawnTick": 500, + "spawnIntervalTicks": 600, + "creeps": [2, 1, 2, 1, 2, 1, 2, 1] + }, + { + "firstCreepSpawnTick": 6000, + "spawnIntervalTicks": 400, + "creeps": [2, 2, 1, 1, 2, 2, 1, 1] + } + ], + "offeredGems": [0, 1, 2, 3] + }, + { + "waves": [ + { + "firstCreepSpawnTick": 500, + "spawnIntervalTicks": 500, + "creeps": [2, 2, 1, 1, 2, 2, 1, 1, 2] + }, + { + "firstCreepSpawnTick": 6000, + "spawnIntervalTicks": 300, + "creeps": [2, 2, 2, 1, 1, 1, 2, 2, 2] + } + ], + "offeredGems": [0, 1, 2, 3] + }, + { + "waves": [ + { + "firstCreepSpawnTick": 500, + "spawnIntervalTicks": 400, + "creeps": [2, 2, 2, 1, 1, 1, 2, 2, 2, 1] + }, + { + "firstCreepSpawnTick": 7000, + "spawnIntervalTicks": 250, + "creeps": [2, 2, 2, 2, 1, 1, 1, 1, 2, 2] + } + ], + "offeredGems": [0, 1, 2, 3] + }, + { + "waves": [ + { + "firstCreepSpawnTick": 500, + "spawnIntervalTicks": 300, + "creeps": [2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2] + }, + { + "firstCreepSpawnTick": 7000, + "spawnIntervalTicks": 200, + "creeps": [2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 2] + } + ], + "offeredGems": [0, 1, 2, 3] + }, + { + "waves": [ + { + "firstCreepSpawnTick": 500, + "spawnIntervalTicks": 250, + "creeps": [2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 2, 2] + }, + { + "firstCreepSpawnTick": 8000, + "spawnIntervalTicks": 150, + "creeps": [2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1] + } + ], + "offeredGems": [0, 1, 2, 3] + } + ] +} diff --git a/public/assets/missions/07_final_stretch.json b/public/assets/missions/07_final_stretch.json new file mode 100644 index 0000000..1f207bf --- /dev/null +++ b/public/assets/missions/07_final_stretch.json @@ -0,0 +1,223 @@ +{ + "name": "Final Stretch", + "description": "The final map, the longest map, the 7th map.", + "mapImage": { + "url": "/assets/maps/07_final_stretch.png" + }, + "gameMap": { + "rows": 17, + "columns": 25, + "cells": [ + [1, 0, 9, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 9, 0, 1], + [1, 0, 9, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 9, 0, 1], + [1, 0, 9, 0, 1, 0, 9, 9, 9, 9, 9, 0, 1, 0, 9, 9, 9, 9, 9, 0, 1, 0, 9, 0, 1], + [1, 0, 9, 0, 1, 0, 9, 0, 0, 0, 9, 0, 1, 0, 9, 0, 0, 0, 9, 0, 1, 0, 9, 0, 1], + [1, 0, 9, 0, 1, 0, 9, 0, 1, 0, 9, 0, 1, 0, 9, 0, 1, 0, 9, 0, 1, 0, 9, 0, 1], + [1, 0, 9, 0, 1, 0, 9, 0, 1, 0, 9, 0, 1, 0, 9, 0, 1, 0, 9, 0, 1, 0, 9, 0, 1], + [1, 0, 9, 0, 1, 0, 9, 0, 1, 0, 9, 0, 1, 0, 9, 0, 1, 0, 9, 0, 1, 0, 9, 0, 1], + [1, 0, 9, 0, 1, 0, 9, 0, 1, 0, 9, 0, 1, 0, 9, 0, 1, 0, 9, 0, 1, 0, 9, 0, 1], + [1, 0, 9, 0, 1, 0, 9, 0, 1, 0, 9, 0, 1, 0, 9, 0, 1, 0, 9, 0, 1, 0, 9, 0, 1], + [1, 0, 9, 0, 1, 0, 9, 0, 1, 0, 9, 0, 1, 0, 9, 0, 1, 0, 9, 0, 1, 0, 9, 0, 1], + [1, 0, 9, 0, 1, 0, 9, 0, 1, 0, 9, 0, 1, 0, 9, 0, 1, 0, 9, 0, 1, 0, 9, 0, 1], + [1, 0, 9, 0, 1, 0, 9, 0, 1, 0, 9, 0, 1, 0, 9, 0, 1, 0, 9, 0, 1, 0, 9, 0, 1], + [1, 0, 9, 0, 1, 0, 9, 0, 1, 0, 9, 0, 1, 0, 9, 0, 1, 0, 9, 0, 1, 0, 9, 0, 1], + [1, 0, 9, 0, 0, 0, 9, 0, 1, 0, 9, 0, 0, 0, 9, 0, 1, 0, 9, 0, 0, 0, 9, 0, 1], + [1, 0, 9, 9, 9, 9, 9, 0, 1, 0, 9, 9, 9, 9, 9, 0, 1, 0, 9, 9, 9, 9, 9, 0, 1], + [1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + ], + "paths": [ + [ + [2, 0], + [2, 1], + [2, 2], + [2, 3], + [2, 4], + [2, 5], + [2, 6], + [2, 7], + [2, 8], + [2, 9], + [2, 10], + [2, 11], + [2, 12], + [2, 13], + [2, 14], + [3, 14], + [4, 14], + [5, 14], + [6, 14], + [6, 13], + [6, 12], + [6, 11], + [6, 10], + [6, 9], + [6, 8], + [6, 7], + [6, 6], + [6, 5], + [6, 4], + [6, 3], + [6, 2], + [7, 2], + [8, 2], + [9, 2], + [10, 2], + [10, 3], + [10, 4], + [10, 5], + [10, 6], + [10, 7], + [10, 8], + [10, 9], + [10, 10], + [10, 11], + [10, 12], + [10, 13], + [10, 14], + [11, 14], + [12, 14], + [13, 14], + [14, 14], + [14, 13], + [14, 12], + [14, 11], + [14, 10], + [14, 9], + [14, 8], + [14, 7], + [14, 6], + [14, 5], + [14, 4], + [14, 3], + [14, 2], + [15, 2], + [16, 2], + [17, 2], + [18, 2], + [18, 3], + [18, 4], + [18, 5], + [18, 6], + [18, 7], + [18, 8], + [18, 9], + [18, 10], + [18, 11], + [18, 12], + [18, 13], + [18, 14], + [19, 14], + [20, 14], + [21, 14], + [22, 14], + [22, 13], + [22, 12], + [22, 11], + [22, 10], + [22, 9], + [22, 8], + [22, 7], + [22, 6], + [22, 5], + [22, 4], + [22, 3], + [22, 2], + [22, 1], + [22, 0] + ] + ] + }, + "rounds": [ + { + "waves": [ + { + "firstCreepSpawnTick": 500, + "spawnIntervalTicks": 700, + "creeps": [0, 1, 0, 1, 0, 1, 0] + }, + { + "firstCreepSpawnTick": 5000, + "spawnIntervalTicks": 500, + "creeps": [1, 1, 2, 1, 1, 2, 1] + } + ], + "offeredGems": [0, 1, 2, 3] + }, + { + "waves": [ + { + "firstCreepSpawnTick": 500, + "spawnIntervalTicks": 600, + "creeps": [2, 1, 2, 1, 2, 1, 2, 1] + }, + { + "firstCreepSpawnTick": 6000, + "spawnIntervalTicks": 400, + "creeps": [2, 2, 1, 1, 2, 2, 1, 1] + } + ], + "offeredGems": [0, 1, 2, 3] + }, + { + "waves": [ + { + "firstCreepSpawnTick": 500, + "spawnIntervalTicks": 500, + "creeps": [2, 2, 1, 1, 2, 2, 1, 1, 2] + }, + { + "firstCreepSpawnTick": 6000, + "spawnIntervalTicks": 300, + "creeps": [2, 2, 2, 1, 1, 1, 2, 2, 2] + } + ], + "offeredGems": [0, 1, 2, 3] + }, + { + "waves": [ + { + "firstCreepSpawnTick": 500, + "spawnIntervalTicks": 400, + "creeps": [2, 2, 2, 1, 1, 1, 2, 2, 2, 1] + }, + { + "firstCreepSpawnTick": 7000, + "spawnIntervalTicks": 250, + "creeps": [2, 2, 2, 2, 1, 1, 1, 1, 2, 2] + } + ], + "offeredGems": [0, 1, 2, 3] + }, + { + "waves": [ + { + "firstCreepSpawnTick": 500, + "spawnIntervalTicks": 300, + "creeps": [2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2] + }, + { + "firstCreepSpawnTick": 7000, + "spawnIntervalTicks": 200, + "creeps": [2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 2] + } + ], + "offeredGems": [0, 1, 2, 3] + }, + { + "waves": [ + { + "firstCreepSpawnTick": 500, + "spawnIntervalTicks": 250, + "creeps": [2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 2, 2] + }, + { + "firstCreepSpawnTick": 8000, + "spawnIntervalTicks": 150, + "creeps": [2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1] + } + ], + "offeredGems": [0, 1, 2, 3] + } + ] +} diff --git a/public/assets/missions/mission_01.json b/public/assets/missions/mission_01.json deleted file mode 100644 index 13567fd..0000000 --- a/public/assets/missions/mission_01.json +++ /dev/null @@ -1,128 +0,0 @@ -{ - "name": "Mission 1", - "description": "This is the first mission", - "mapImage": { - "url": "/assets/maps/mission_01.png" - }, - "gameMap": { - "rows": 17, - "columns": 25, - "cells": [ - [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], - [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], - [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], - [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], - [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], - [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], - [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], - [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], - [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], - [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], - [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], - [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], - [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], - [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], - [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], - [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], - [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - ], - "paths": [ - [ - [4, 0], - [4, 1], - [4, 2], - [4, 3], - [4, 4], - [4, 5], - [4, 6], - [4, 7], - [4, 8], - [4, 9], - [4, 10], - [4, 11], - [4, 12], - [5, 12], - [6, 12], - [7, 12], - [8, 12], - [9, 12], - [10, 12], - [10, 11], - [10, 10], - [10, 9], - [10, 8], - [10, 7], - [10, 6], - [10, 5], - [10, 4], - [10, 3], - [11, 3], - [12, 3], - [13, 3], - [14, 3], - [14, 4], - [14, 5], - [14, 6], - [14, 7], - [14, 8], - [14, 9], - [14, 10], - [14, 11], - [14, 12], - [14, 13], - [14, 14], - [14, 15], - [14, 16], - [14, 17], - [14, 18], - [14, 19], - [14, 20], - [13, 20], - [12, 20], - [11, 20], - [10, 20], - [9, 20], - [8, 20], - [7, 20], - [6, 20], - [5, 20], - [5, 21], - [5, 22], - [5, 23], - [5, 24] - ] - ] - }, - "rounds": [ - { - "waves": [ - { - "firstCreepSpawnTick": 500, - "spawnIntervalTicks": 1000, - "creeps": [0, 0, 0, 0, 0] - } - ], - "offeredGems": [0, 0, 0, 0] - }, - { - "waves": [ - { - "firstCreepSpawnTick": 500, - "spawnIntervalTicks": 1000, - "creeps": [1, 1, 1, 1, 1] - } - ], - "offeredGems": [0, 1, 2, 3] - }, - { - "waves": [ - { - "firstCreepSpawnTick": 500, - "spawnIntervalTicks": 1000, - "creeps": [2, 2, 2, 2, 2] - } - ], - "offeredGems": [0, 1, 2, 3] - } - ] -} diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..c4cfea9 Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/favicon.png b/public/favicon.png new file mode 100644 index 0000000..8f8ebc9 Binary files /dev/null and b/public/favicon.png differ diff --git a/public/maps.tiled-session b/public/maps.tiled-session index abcd439..01632f8 100644 --- a/public/maps.tiled-session +++ b/public/maps.tiled-session @@ -3,9 +3,11 @@ "height": 4300, "width": 2 }, - "activeFile": "Mission011.tmx", + "activeFile": "tiled/07_final_stretch.tmx", "expandedProjectPaths": [ - "." + ".", + "tiled", + "assets/missions" ], "file.lastUsedOpenFilter": "All Files (*)", "fileStates": { @@ -16,7 +18,7 @@ "scaleInDock": 0.33, "scaleInEditor": 1 }, - "/home/koneko/dumping/tiles/TiledTDThree64.tmx": { + "C:/home/koneko/dumping/tiles/TiledTDThree64.tmx": { "scale": 0.187625, "selectedLayer": 0, "viewCenter": { @@ -36,31 +38,118 @@ "scale": 0.5, "selectedLayer": 0, "viewCenter": { - "x": 570, - "y": 448 + "x": 959, + "y": 543 } }, "Tileset.tsx": { - "scaleInDock": 0.5, + "scaleInDock": 0.75, "scaleInEditor": 1 + }, + "tiled/01_first_steps..tmx": { + "scale": 1, + "selectedLayer": 0, + "viewCenter": { + "x": 975, + "y": 640.5 + } + }, + "tiled/01_first_steps.tmx": { + "scale": 0.33, + "selectedLayer": 0, + "viewCenter": { + "x": 639.3939393939394, + "y": 433.33333333333326 + } + }, + "tiled/02_the_turn.tmx": { + "scale": 0.33, + "selectedLayer": 0, + "viewCenter": { + "x": 869.6969696969697, + "y": 203.03030303030323 + } + }, + "tiled/03_fork_in_the_road.tmx": { + "scale": 0.33, + "selectedLayer": 0, + "viewCenter": { + "x": 2693.9393939393935, + "y": 239.3939393939395 + } + }, + "tiled/04_crossroads.tmx": { + "scale": 1, + "selectedLayer": 0, + "viewCenter": { + "x": 646.5, + "y": 543 + } + }, + "tiled/05_the_maze.tmx": { + "scale": 0.5, + "selectedLayer": 0, + "viewCenter": { + "x": 896, + "y": 406 + } + }, + "tiled/06_multiple_fronts.tmx": { + "scale": 0.75, + "selectedLayer": 0, + "viewCenter": { + "x": 873.3333333333333, + "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, + "viewCenter": { + "x": 833, + "y": 629 + } } }, + "last.exportedFilePath": "C:/Work/Projects/Matej/towerdefense/public/tiled", "last.externalTilesetPath": "/home/koneko/Programing/js/towerdefense/public", "map.height": 17, + "map.lastUsedExportFilter": "All Files (*)", "map.lastUsedFormat": "tmx", "map.tileHeight": 64, "map.tileWidth": 64, - "map.width": 30, + "map.width": 25, "openFiles": [ - "Tileset.tsx", - "Mission011.tmx" + "tiled/04_crossroads.tmx", + "tiled/05_the_maze.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" ], "project": "maps.tiled-project", "recentFiles": [ - "Tileset.tsx", + "tiled/04_crossroads.tmx", + "tiled/05_the_maze.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", - "Mission01.tmx", - "/home/koneko/dumping/tiles/TiledTDThree64.tmx" + "Tileset.tsx", + "Mission01.tmx" ], "tileset.lastUsedFilter": "Tiled tileset files (*.tsx *.xml)" } diff --git a/public/tiled/01_first_steps.tmx b/public/tiled/01_first_steps.tmx new file mode 100644 index 0000000..be53f3c --- /dev/null +++ b/public/tiled/01_first_steps.tmx @@ -0,0 +1,25 @@ + + + + + +15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, +15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, +15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, +15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, +15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, +15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, +15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, +4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4, +18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18, +32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32, +15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, +15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, +15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, +15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, +15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, +15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, +15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15 + + + diff --git a/public/tiled/02_the_turn.tmx b/public/tiled/02_the_turn.tmx new file mode 100644 index 0000000..17914fa --- /dev/null +++ b/public/tiled/02_the_turn.tmx @@ -0,0 +1,25 @@ + + + + + +15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, +15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, +15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, +4,4,4,4,4,4,4,4,4,4,4,4,4,5,15,15,15,15,15,15,15,15,15,15,15, +18,18,18,18,18,18,18,18,18,18,18,18,18,19,15,15,15,15,15,3,4,4,4,4,4, +32,32,32,32,32,32,32,32,32,32,32,7,18,19,15,15,15,15,15,17,18,18,18,18,18, +15,15,15,15,15,15,15,15,15,15,15,17,18,19,15,15,15,15,15,17,18,6,32,32,32, +15,15,15,15,15,15,15,15,15,15,15,17,18,19,15,15,15,15,15,17,18,19,15,15,15, +15,15,15,15,15,15,15,15,15,15,15,17,18,19,15,15,15,15,15,17,18,19,15,15,15, +15,15,3,4,4,4,4,4,4,4,4,21,18,19,15,15,15,15,15,17,18,19,15,15,15, +15,15,17,18,18,18,18,18,18,18,18,18,18,19,15,15,15,15,15,17,18,19,15,15,15, +15,15,17,18,6,32,32,32,32,32,32,32,32,33,15,15,15,15,15,17,18,19,15,15,15, +15,15,17,18,19,15,15,15,15,15,15,15,15,15,15,15,15,15,15,17,18,19,15,15,15, +15,15,17,18,20,4,4,4,4,4,4,4,4,4,4,4,4,4,4,21,18,19,15,15,15, +15,15,17,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,19,15,15,15, +15,15,31,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,33,15,15,15, +15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15 + + + diff --git a/public/tiled/03_fork_in_the_road.tmx b/public/tiled/03_fork_in_the_road.tmx new file mode 100644 index 0000000..b328de8 --- /dev/null +++ b/public/tiled/03_fork_in_the_road.tmx @@ -0,0 +1,25 @@ + + + + + +15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, +15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, +15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, +15,15,15,15,15,15,15,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4, +15,15,15,15,15,15,15,17,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18, +15,15,15,15,15,15,15,17,18,6,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32, +15,15,15,15,15,15,15,17,18,19,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, +4,4,4,4,4,4,4,21,18,19,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, +18,18,18,18,18,18,18,18,18,19,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, +32,32,32,32,32,32,32,7,18,19,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, +15,15,15,15,15,15,15,17,18,19,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, +15,15,15,15,15,15,15,17,18,20,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4, +15,15,15,15,15,15,15,17,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18, +15,15,15,15,15,15,15,31,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32, +15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, +15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, +15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15 + + + diff --git a/public/tiled/04_crossroads.tmx b/public/tiled/04_crossroads.tmx new file mode 100644 index 0000000..6269a07 --- /dev/null +++ b/public/tiled/04_crossroads.tmx @@ -0,0 +1,25 @@ + + + + + +15,15,15,15,15,15,15,15,15,15,15,15,15,17,18,19,15,15,15,15,15,15,15,15,15, +15,15,15,15,15,15,3,4,4,4,4,4,4,21,18,19,15,15,15,15,15,15,15,15,15, +15,15,15,15,15,15,17,18,18,18,18,18,18,18,18,19,15,15,15,15,15,15,15,15,15, +15,15,15,15,15,15,17,18,6,32,32,32,32,32,32,33,15,15,15,15,15,15,15,15,15, +15,15,15,15,15,15,17,18,20,4,4,4,4,5,15,15,15,15,15,15,15,15,15,15,15, +15,15,15,15,15,15,17,18,18,18,18,18,18,19,15,15,15,15,15,3,4,4,4,4,4, +15,15,15,15,15,15,31,32,32,32,32,7,18,19,15,15,15,15,15,17,18,18,18,18,18, +15,15,15,15,15,15,15,15,15,15,15,17,18,19,15,15,15,15,15,17,18,6,32,32,32, +15,15,15,15,15,15,15,15,15,15,15,17,18,19,15,15,15,15,15,17,18,19,15,15,15, +15,15,15,15,15,15,15,15,15,15,15,17,18,20,4,4,4,4,4,21,18,19,15,15,15, +15,15,15,15,15,15,15,15,15,15,15,17,18,18,18,18,18,18,18,18,18,19,15,15,15, +15,15,15,15,15,15,15,15,15,15,15,31,32,32,7,18,6,32,32,32,32,33,15,15,15, +4,4,4,4,4,4,4,4,4,4,4,4,4,4,21,18,19,15,15,15,15,15,15,15,15, +18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,19,15,15,15,15,15,15,15,15, +32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,33,15,15,15,15,15,15,15,15, +15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, +15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15 + + + diff --git a/public/tiled/05_the_maze.tmx b/public/tiled/05_the_maze.tmx new file mode 100644 index 0000000..b13c7a8 --- /dev/null +++ b/public/tiled/05_the_maze.tmx @@ -0,0 +1,25 @@ + + + + + +4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,5,15,15,15,15,15,15, +18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,19,15,15,15,15,15,15, +32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,7,18,19,15,15,15,15,15,15, +15,3,4,4,4,4,4,4,4,4,4,4,4,4,4,5,17,18,19,15,15,15,15,15,15, +15,17,18,18,18,18,18,18,18,18,18,18,18,18,18,19,17,18,19,15,15,15,15,15,15, +15,17,18,6,32,32,32,32,32,32,32,32,32,7,18,19,17,18,19,15,15,15,15,15,15, +15,17,18,19,15,15,15,15,15,15,15,15,15,17,18,20,21,18,20,4,4,4,4,5,15, +15,17,18,19,15,15,15,15,15,15,15,15,15,17,18,18,18,18,18,18,18,18,18,20,4, +15,17,18,19,3,4,4,4,4,4,4,5,15,17,18,6,7,18,6,32,32,7,18,18,18, +15,17,18,19,17,18,18,18,18,18,18,19,15,17,18,19,17,18,19,15,15,31,32,32,32, +15,17,18,19,17,18,6,32,32,7,18,20,4,21,18,19,17,18,19,15,15,15,15,15,15, +15,17,18,19,17,18,19,15,15,17,18,18,18,18,18,19,17,18,19,15,15,15,15,15,15, +15,17,18,19,17,18,19,15,15,31,32,32,32,32,32,33,17,18,19,15,15,15,15,15,15, +15,17,18,19,17,18,20,4,4,4,4,4,4,4,4,4,21,18,19,15,15,15,15,15,15, +4,21,18,19,17,18,18,18,18,18,18,18,18,18,18,18,18,18,19,15,15,15,15,15,15, +18,18,18,19,31,32,32,32,32,32,32,32,32,32,32,32,32,32,33,15,15,15,15,15,15, +32,32,32,33,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15 + + + diff --git a/public/tiled/06_multiple_fronts.tmx b/public/tiled/06_multiple_fronts.tmx new file mode 100644 index 0000000..11fee87 --- /dev/null +++ b/public/tiled/06_multiple_fronts.tmx @@ -0,0 +1,25 @@ + + + + + +15,15,15,15,17,18,19,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, +15,3,4,4,21,18,19,15,15,15,15,15,3,4,4,4,4,4,4,4,4,4,5,15,15, +15,17,18,18,18,18,19,15,15,15,15,15,17,18,18,18,18,18,18,18,18,18,19,15,15, +15,17,18,6,32,32,33,15,15,15,15,15,17,18,6,32,32,32,32,32,7,18,19,15,15, +15,17,18,19,15,15,15,15,15,15,15,15,17,18,19,15,15,15,15,15,17,18,20,4,4, +15,17,18,20,4,4,4,4,4,4,4,4,21,18,19,15,15,15,15,15,17,18,18,18,18, +15,17,18,18,18,18,18,18,18,18,18,18,18,18,19,15,15,15,15,15,31,32,32,32,32, +15,31,32,32,32,32,32,32,32,32,32,32,32,32,33,15,15,15,15,15,15,15,15,15,15, +4,4,4,5,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, +18,18,18,19,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, +32,7,18,19,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, +15,17,18,19,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, +15,17,18,19,15,15,15,15,15,15,3,4,4,4,4,4,4,4,5,15,15,15,15,15,15, +15,17,18,19,15,15,15,15,15,15,17,18,18,18,18,18,18,18,20,4,4,4,4,4,4, +15,17,18,20,4,4,4,4,4,4,21,18,6,32,32,32,7,18,18,18,18,18,18,18,18, +15,17,18,18,18,18,18,18,18,18,18,18,19,15,15,15,31,32,32,32,32,32,32,32,32, +15,31,32,32,32,32,32,32,32,32,32,32,33,15,15,15,15,15,15,15,15,15,15,15,15 + + + diff --git a/public/tiled/07_final_stretch.tmx b/public/tiled/07_final_stretch.tmx new file mode 100644 index 0000000..d61ba2e --- /dev/null +++ b/public/tiled/07_final_stretch.tmx @@ -0,0 +1,25 @@ + + + + + +15,17,18,19,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,17,18,19,15, +15,17,18,19,15,3,4,4,4,4,4,5,15,3,4,4,4,4,4,5,15,17,18,19,15, +15,17,18,19,15,17,18,18,18,18,18,19,15,17,18,18,18,18,18,19,15,17,18,19,15, +15,17,18,19,15,17,18,6,32,7,18,19,15,17,18,6,32,7,18,19,15,17,18,19,15, +15,17,18,19,15,17,18,19,15,17,18,19,15,17,18,19,15,17,18,19,15,17,18,19,15, +15,17,18,19,15,17,18,19,15,17,18,19,15,17,18,19,15,17,18,19,15,17,18,19,15, +15,17,18,19,15,17,18,19,15,17,18,19,15,17,18,19,15,17,18,19,15,17,18,19,15, +15,17,18,19,15,17,18,19,15,17,18,19,15,17,18,19,15,17,18,19,15,17,18,19,15, +15,17,18,19,15,17,18,19,15,17,18,19,15,17,18,19,15,17,18,19,15,17,18,19,15, +15,17,18,19,15,17,18,19,15,17,18,19,15,17,18,19,15,17,18,19,15,17,18,19,15, +15,17,18,19,15,17,18,19,15,17,18,19,15,17,18,19,15,17,18,19,15,17,18,19,15, +15,17,18,19,15,17,18,19,15,17,18,19,15,17,18,19,15,17,18,19,15,17,18,19,15, +15,17,18,19,15,17,18,19,15,17,18,19,15,17,18,19,15,17,18,19,15,17,18,19,15, +15,17,18,20,4,21,18,19,15,17,18,20,4,21,18,19,15,17,18,20,4,21,18,19,15, +15,17,18,18,18,18,18,19,15,17,18,18,18,18,18,19,15,17,18,18,18,18,18,19,15, +15,31,32,32,32,32,32,33,15,31,32,32,32,32,32,33,15,31,32,32,32,32,32,33,15, +15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15 + + + diff --git a/src/classes/Assets.ts b/src/classes/Assets.ts index ee2edbe..33ada65 100644 --- a/src/classes/Assets.ts +++ b/src/classes/Assets.ts @@ -24,6 +24,7 @@ export default class GameAssets { public static SwordsTexture: PIXI.Texture; public static TitleTexture: PIXI.Texture; public static BannerGemsmith: PIXI.Texture; + public static EndScreenDialog: PIXI.Texture; public static PlayIconTexture: PIXI.Texture; public static PauseIconTexture: PIXI.Texture; @@ -34,7 +35,7 @@ export default class GameAssets { public static PlusIconTexture: PIXI.Texture; public static GemAmountIcons: PIXI.Texture[] = []; - public static Missions: MissionDefinition[]; + public static Missions: MissionDefinition[] = []; public static MissionBackgrounds: PIXI.Texture[] = []; public static Towers: TowerDefinition[]; public static Creeps: CreepDefinition[]; @@ -96,6 +97,7 @@ export default class GameAssets { 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/banner_01.png').then((texture) => (this.BannerGemsmith = texture)), + this.Load('./assets/gui/note.png').then((texture) => (this.EndScreenDialog = 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)), @@ -150,7 +152,13 @@ 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')]; + await this.LoadMission('./assets/missions/01_first_steps.json'); + await this.LoadMission('./assets/missions/02_the_turn.json'); + await this.LoadMission('./assets/missions/03_fork_in_the_road.json'); + await this.LoadMission('./assets/missions/04_crossroads.json'); + await this.LoadMission('./assets/missions/05_the_maze.json'); + await this.LoadMission('./assets/missions/06_multiple_fronts.json'); + await this.LoadMission('./assets/missions/07_final_stretch.json'); } private static async LoadTowers() { @@ -170,13 +178,8 @@ export default class GameAssets { private static async LoadMission(missionUrl: string) { const res = await fetch(missionUrl); const mission = await res.json(); - await this.LoadBackground(mission.mapImage.url); - return mission; - } - - private static async LoadBackground(backgroundUrl: string) { - let index = this.MissionBackgrounds.length - 1; - if (index == -1) index = 0; - this.MissionBackgrounds[index] = await this.Load(backgroundUrl); + console.log(`Loading mission: ${missionUrl} [${mission.name} / ${mission.mapImage.url}]`); + GameAssets.Missions.push(mission); + GameAssets.MissionBackgrounds.push(await this.Load(mission.mapImage.url)); } } diff --git a/src/classes/Bastion.ts b/src/classes/Bastion.ts index 36c9f56..b277cf9 100644 --- a/src/classes/Bastion.ts +++ b/src/classes/Bastion.ts @@ -22,6 +22,7 @@ export class Engine { public static NotificationManager: NotificationManager; public static GameScene: GameScene; public static latestCommit: string; + public static latestGemId = 0; public static GridCellSize: number = 64; public static GridColumns: number = 25; @@ -30,9 +31,12 @@ export class Engine { public static MouseY: number = 0; public static TestSuite() { + let params = new URLSearchParams(location.href); + if (params.entries().next().value[1] != 'game') return; + Engine.NotificationManager.Notify('Loaded testing suite.', 'danger'); Engine.TowerManager.ToggleChoosingTowerLocation('RESET'); - Engine.TowerManager.PlaceTower(GameAssets.Towers[1], 8, 10, GameAssets.Towers[0].behaviour, true); + Engine.TowerManager.PlaceTower(GameAssets.Towers[1], 6, 10, GameAssets.Towers[1].behaviour, true); for (let i = 0; i < 29; i++) { this.GameScene.MissionStats.giveGem(new Gem(i % 4), true); } @@ -41,7 +45,6 @@ export class Engine { export default class GameMaster { public currentScene: Scene; - private GameObjects: GameObject[] = []; constructor() { Engine.GameMaster = this; @@ -61,9 +64,6 @@ export default class GameMaster { if (this.currentScene) { this.currentScene.destroy(); } - this.GameObjects.forEach((element) => { - element.destroy(); - }); this.currentScene = newScene; this.currentScene.init(); } diff --git a/src/classes/Definitions.ts b/src/classes/Definitions.ts index 3026d7e..b33ec4f 100644 --- a/src/classes/Definitions.ts +++ b/src/classes/Definitions.ts @@ -68,11 +68,14 @@ export type TowerStatsDefinition = { gemSlotsAmount: number; cost: number; range: number; + timeToLive: number; + pierce: number; }; export type GemDefinition = { name: string; description: string; + color: PIXI.ColorSource; type: GemType; totalLevels: number; textures: PIXI.Texture[]; @@ -92,7 +95,7 @@ export type GenericGemImprovement = { gemValueUp: number; }; -export type PathDefinition = [[row: number, column: number]]; +export type PathDefinition = [[column: number, row: number]]; export enum TerrainType { Restricted = 0, diff --git a/src/classes/GameObject.ts b/src/classes/GameObject.ts index 7b7963b..11a576b 100644 --- a/src/classes/GameObject.ts +++ b/src/classes/GameObject.ts @@ -24,6 +24,7 @@ export default abstract class GameObject { } public copyContainerToBB() { + if (this.container == null) return null; this.bb.x = this.container.x; this.bb.y = this.container.y; this.bb.width = this.container.width; diff --git a/src/classes/GameUIConstants.ts b/src/classes/GameUIConstants.ts index a875166..060dff4 100644 --- a/src/classes/GameUIConstants.ts +++ b/src/classes/GameUIConstants.ts @@ -1,8 +1,10 @@ import * as PIXI from 'pixi.js'; import { Engine } from './Bastion'; export default class GameUIConstants { - public static SidebarRect; - public static ChangeRoundButtonRect; + public static SidebarRect: PIXI.Rectangle; + public static ChangeRoundButtonRect: PIXI.Rectangle; + public static MaximumPlayerNameLength = 20; + public static init() { GameUIConstants.SidebarRect = new PIXI.Rectangle( Engine.app.canvas.width - 360, diff --git a/src/classes/game/Creep.ts b/src/classes/game/Creep.ts index af4f4e1..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'; @@ -49,13 +49,33 @@ export default class Creep extends GameObject { this.maxHealth = this.stats.health; this.path = path; // Added + 32 to center them. - this.x = path[0][1] * Engine.GridCellSize + Engine.GridCellSize / 2; - this.y = path[0][0] * Engine.GridCellSize + Engine.GridCellSize / 2; - Engine.GameScene.events.on(CreepEvents.TakenDamage, (creepID, damage) => { - if (creepID != this.id) return; - this.health -= damage; - this.UpdateHealthbar(); - }); + 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, 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); @@ -94,10 +114,10 @@ export default class Creep extends GameObject { const targetCell = this.path[this.pathIndex + 1]; // Added + 32 for centering. - const targetX = targetCell[1] * Engine.GridCellSize + Engine.GridCellSize / 2; - const targetY = targetCell[0] * Engine.GridCellSize + Engine.GridCellSize / 2; - const directionX = targetCell[1] - currentCell[1]; - const directionY = targetCell[0] - currentCell[0]; + const targetX = targetCell[0] * Engine.GridCellSize + Engine.GridCellSize / 2; + const targetY = targetCell[1] * Engine.GridCellSize + Engine.GridCellSize / 2; + const directionX = targetCell[0] - currentCell[0]; + const directionY = targetCell[1] - currentCell[1]; if (directionX > 0) { // Going right if (this.direction != 1) { diff --git a/src/classes/game/Gem.ts b/src/classes/game/Gem.ts index b699dbc..8f0bc07 100644 --- a/src/classes/game/Gem.ts +++ b/src/classes/game/Gem.ts @@ -1,8 +1,7 @@ import * as PIXI from 'pixi.js'; import { GemType, GemDefinition, GenericGemImprovement } from '../Definitions'; import GameAssets from '../Assets'; - -let latestGemId = 0; +import { Engine } from '../Bastion'; export default class Gem { public texture: PIXI.Texture; @@ -14,8 +13,8 @@ export default class Gem { this.texture = this.definition.textures[0]; if (!doNotIncrement) { - this.id = latestGemId + 1; - latestGemId++; + this.id = Engine.latestGemId + 1; + Engine.latestGemId++; } else this.id = ''; } public currentGemImprovement() { diff --git a/src/classes/game/Grid.ts b/src/classes/game/Grid.ts index f0246e8..d736c63 100644 --- a/src/classes/game/Grid.ts +++ b/src/classes/game/Grid.ts @@ -6,6 +6,8 @@ import { Engine } from '../Bastion'; import Creep from './Creep'; import { CreepEvents, TowerEvents, GridEvents } from '../Events'; +let genPath = []; + export class Cell extends GameObject { public type: TerrainType; public row: number; @@ -55,6 +57,7 @@ export class Cell extends GameObject { Engine.GameScene.events.emit(GridEvents.CellMouseLeave, this); Engine.Grid.rangePreview.clear(); }); + Engine.GameScene.events.on(TowerEvents.TowerPlacedEvent, (_, row, col) => { if (row == this.row && col == this.column) { this.hasTowerPlaced = true; @@ -66,6 +69,44 @@ export class Cell extends GameObject { this.hasTowerPlaced = false; } }); + + // Disable this if you want to add new maps. + if (true) return; + + const text = new PIXI.Text({ + text: `${this.column}|${this.row}`, + style: new PIXI.TextStyle({ + fill: 0xffffff, + fontSize: 16, + stroke: { + color: 0x000000, + width: 2, + }, + }), + }); + this.container.addChild(text); + text.anchor.set(0.5, 0.5); + text.x = this.bb.width / 2; + text.y = this.bb.height / 2; + if (this.type == TerrainType.Path) { + text.style.fill = 'green'; + text.style.fontWeight = 'bold'; + } + if (this.type == TerrainType.Restricted) { + text.style.fill = 'gold'; + } + this.clickDetector.on('pointerup', () => { + const cellIndex = genPath.findIndex(([col, row]) => col === this.column && row === this.row); + if (cellIndex !== -1) { + text.style.fill = 0xffffff; + genPath.splice(cellIndex, 1); + } else { + text.style.fill = 0xff0000; + genPath.push([this.column, this.row]); + } + console.log('updated gen path'); + console.log(JSON.stringify(genPath)); + }); } public showRangePreview(invalid, range) { let color = 0xffffff; @@ -132,8 +173,7 @@ export class Grid extends GameObject { for (let y = 0; y < this.gameMap.columns; y++) { for (let x = 0; x < this.gameMap.rows; x++) { let type = this.gameMap.cells[x][y]; - if (!type) type = 1; - const isPath = this.gameMap.paths.some((path) => path.some((p) => p[0] === x && p[1] === y)); + const isPath = this.gameMap.paths.some((path) => path.some((p) => p[1] === x && p[0] === y)); if (isPath) type = TerrainType.Path; let cell = new Cell(type, x, y, isPath); this.cells.push(cell); @@ -144,6 +184,32 @@ export class Grid extends GameObject { }); this.container.addChild(this.rangePreview); } + public generateCells() { + const newGrid = Array.from({ length: this.gameMap.rows }, () => Array(this.gameMap.columns).fill(1)); + + this.cells.forEach((cell) => { + if (cell.isPath) { + newGrid[cell.row][cell.column] = 9; + for (let i = -1; i <= 1; i++) { + for (let j = -1; j <= 1; j++) { + const newRow = cell.row + i; + const newCol = cell.column + j; + if ( + newRow >= 0 && + newRow < this.gameMap.rows && + newCol >= 0 && + newCol < this.gameMap.columns && + newGrid[newRow][newCol] !== 9 + ) { + newGrid[newRow][newCol] = 0; + } + } + } + } + }); + + console.log(JSON.stringify(newGrid)); + } public toggleGrid(force?: 'hide' | 'show') { this.cells.forEach((cell) => { if (force) { diff --git a/src/classes/game/HighScoreManager.ts b/src/classes/game/HighScoreManager.ts new file mode 100644 index 0000000..3015554 --- /dev/null +++ b/src/classes/game/HighScoreManager.ts @@ -0,0 +1,71 @@ +/** + * Handles the high score system. + */ +export class HighScoreManager { + private static readonly STORAGE_KEY_PREFIX = 'highscore_'; + private static readonly MAX_SCORES = 10; + + public readonly missionName: string; + private scores: PlayerScore[]; + + constructor(missionName: string) { + this.missionName = missionName; + this.scores = this.loadScores(); + this.scores.sort((a, b) => b.score - a.score || a.timestamp - b.timestamp); + } + + private loadScores(): PlayerScore[] { + const storedScores = localStorage.getItem(HighScoreManager.STORAGE_KEY_PREFIX + this.missionName); + return HighScoreManager.parseStoredScores(storedScores); + } + + private saveScores(): void { + localStorage.setItem(HighScoreManager.STORAGE_KEY_PREFIX + this.missionName, JSON.stringify(this.scores)); + } + + public addScore(playerScore: PlayerScore): void { + this.scores.push(playerScore); + this.scores.sort((a, b) => b.score - a.score); + if (this.scores.length > HighScoreManager.MAX_SCORES) { + this.scores.length = HighScoreManager.MAX_SCORES; + } + this.saveScores(); + } + + public getScores(): PlayerScore[] { + return this.scores; + } + + private static parseStoredScores(storedScores: string | null): PlayerScore[] { + if (!storedScores) { + return []; + } + try { + const parsedScores = JSON.parse(storedScores); + if ( + Array.isArray(parsedScores) && + parsedScores.every( + (score) => + typeof score.playerName === 'string' && + typeof score.score === 'number' && + typeof score.timestamp === 'number' + ) + ) { + return parsedScores.map((score) => ({ + playerName: score.playerName, + score: score.score, + timestamp: score.timestamp, + })); + } + } catch (e) { + console.error('Failed to parse stored scores:', e); + } + return []; + } +} + +export type PlayerScore = { + playerName: string; + score: number; + timestamp: number; +}; diff --git a/src/classes/game/KeyboardManager.ts b/src/classes/game/KeyboardManager.ts new file mode 100644 index 0000000..bffe515 --- /dev/null +++ b/src/classes/game/KeyboardManager.ts @@ -0,0 +1,49 @@ +/** + * Handles keyboard events. + */ +class KeyboardManager { + private static listeners: ((event: KeyboardEvent) => void)[] = []; + + public static init() { + window.addEventListener('keydown', KeyboardManager.handleKeyDown); + } + + /** + * Add a callback to be called when a key is pressed. + * Note: Calling preventDefault() on the event will prevent other callbacks from being called. + * @param key The key to listen for. + * @param callback The callback to call when the key is pressed. + * @returns A function that can be called to remove the callback. + */ + public static onKeyPressed(callback: (event: KeyboardEvent) => void) { + KeyboardManager.listeners = [...KeyboardManager.listeners, callback]; + return () => KeyboardManager.offKey(callback); + } + + /** + * Remove a callback. + */ + public static offKey(callback: (event: KeyboardEvent) => void) { + const index = KeyboardManager.listeners.indexOf(callback); + if (index >= 0) { + KeyboardManager.listeners = [ + ...KeyboardManager.listeners.slice(0, index), + ...KeyboardManager.listeners.slice(index + 1), + ]; + } + } + + private static handleKeyDown(event: KeyboardEvent) { + if (KeyboardManager.listeners.length > 0) { + console.log(`Key down: ${event.key}`); + for (let i = KeyboardManager.listeners.length - 1; i >= 0; i--) { + KeyboardManager.listeners[i](event); + if (event.defaultPrevented) { + break; + } + } + } + } +} + +export default KeyboardManager; diff --git a/src/classes/game/MissionStats.ts b/src/classes/game/MissionStats.ts index f7b894c..d9074f2 100644 --- a/src/classes/game/MissionStats.ts +++ b/src/classes/game/MissionStats.ts @@ -8,14 +8,16 @@ import Gem from './Gem'; export default class MissionStats extends GameObject { private hp: number = 100; private gold: number = 0; + private goldEarned: number = 0; + private goldSpent: number = 0; + private wavesSurvived: number = 0; + private damageDealt: number = 0; + private creepsKilled: number = 0; 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; } @@ -147,5 +149,32 @@ export default class MissionStats extends GameObject { }); } + public getStats() { + return { + hp: this.hp, + gold: this.gold, + wavesSurvived: this.wavesSurvived, + goldEarned: this.goldEarned, + goldSpent: this.goldSpent, + score: this.calculateScore(), + }; + } + + private calculateScore() { + const uniqueGems = []; + for (const gem of this.inventory) { + if (!uniqueGems.includes(gem.definition.name)) { + uniqueGems.push(gem.definition.name); + } + } + return ( + this.damageDealt * 2 + + this.hp * 10 + + (this.goldEarned - this.goldSpent) * 3 + + this.wavesSurvived * 100 + + uniqueGems.length * 100 + ); + } + public update() {} } diff --git a/src/classes/game/NotificationManager.ts b/src/classes/game/NotificationManager.ts index 737be4e..547155b 100644 --- a/src/classes/game/NotificationManager.ts +++ b/src/classes/game/NotificationManager.ts @@ -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' | 'gemaward'; +export type NotificationType = 'info' | 'warn' | 'danger' | 'reward' | 'gemaward' | 'green'; class Notification { public textObj: PIXI.Text; @@ -22,6 +22,8 @@ class Notification { fill = 0xd65afc; } else if (type == 'gemaward') { fill = 0xffffff; + } else if (type == 'green') { + fill = 0x00ff00; } this.ticksToFadeAway = ticksToFadeAway; this.textObj = new PIXI.Text({ diff --git a/src/classes/game/Projectile.ts b/src/classes/game/Projectile.ts index 62300e5..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; @@ -19,14 +20,29 @@ export default class Projectile extends GameObject { public angle: number; public speed: number; public damage: number; - public timeToLive: number = 1; + public pierce: number = 1; + public timeToLive: number; public parent: Tower; - constructor(x, y, textures, angle, damage, tint, 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.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(); @@ -46,13 +62,19 @@ export default class Projectile extends GameObject { public update(elapsedMS) { if (this.deleteMe) return; - if (this.x > 2000 || this.x < 0 || this.y > 2000 || this.y < 0 || this.timeToLive <= 0) return this.destroy(); + if (this.x > 2000 || this.x < 0 || this.y > 2000 || this.y < 0 || this.pierce <= 0 || this.timeToLive <= 0) + return this.destroy(); + this.timeToLive--; Engine.Grid.creeps.forEach((creep) => { - if (this.timeToLive <= 0) return; - if (creep.container && this.checkCollision(creep)) { - this.timeToLive--; - this.onCollide(creep); - return; + if (this.pierce <= 0) return; + if (creep && creep.container && this.checkCollision(creep)) { + let exists = this.collidedCreepIDs.find((c) => creep.id == c.id); + if (!exists) { + this.collidedCreepIDs.push(creep); + this.pierce--; + this.onCollide(creep); + return; + } } }); this.x += Math.cos(this.angle) * this.speed * elapsedMS; @@ -63,11 +85,12 @@ 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) { - if (creep == null) return; + console.debug(creep); + if (creep == null || creep.container == null || creep.container._position == null) return; let mybb = this.copyContainerToBB(); let otherbb = creep.copyContainerToBB(); return mybb.getBounds().intersects(otherbb.getBounds()); diff --git a/src/classes/game/Tower.ts b/src/classes/game/Tower.ts index 620543f..9930b46 100644 --- a/src/classes/game/Tower.ts +++ b/src/classes/game/Tower.ts @@ -1,13 +1,13 @@ import { Engine } from '../Bastion'; import * as PIXI from 'pixi.js'; import GameObject from '../GameObject'; -import { TowerDefinition } from '../Definitions'; +import { CreepResistancesDefinition, GemType, TowerDefinition } from '../Definitions'; import { Cell } from './Grid'; import { TowerBehaviours } from './TowerManager'; import Projectile, { calculateAngleToPoint } from './Projectile'; import Creep from './Creep'; import Gem from './Gem'; -import { BasicTowerBehaviour } from './TowerBehaviours'; +import { BasicTowerBehaviour, CircleTowerBehaviour } from './TowerBehaviours'; export function distance(x1, y1, x2, y2) { return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); @@ -25,6 +25,11 @@ export class Tower extends GameObject { public ticksUntilNextShot: number; public graphics: PIXI.Graphics = new PIXI.Graphics(); public computedDamageToDeal: number; + public computedAttackSpeed: number; + public computedRange: number; + public computedTimeToLive: number; + public computedPierce: number; + public totalGemResistanceModifications: CreepResistancesDefinition; public parent: Cell; constructor(row, column, texture, definition, behaviour) { @@ -56,14 +61,13 @@ export class Tower extends GameObject { Engine.Grid.gridInteractionEnabled && !Engine.GameScene.towerPanel.isShown ) - this.parent.showRangePreview(false, this.definition.stats.range); + this.parent.showRangePreview(false, this.computedRange); }; 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); @@ -92,25 +96,48 @@ export class Tower extends GameObject { const y = creep.y; const towerX = this.column * Engine.GridCellSize + Engine.GridCellSize / 2; const towerY = this.row * Engine.GridCellSize + Engine.GridCellSize / 2; - const radius = this.definition.stats.range * Engine.GridCellSize; + const radius = this.computedRange * Engine.GridCellSize; const d = distance(towerX, towerY, x, y); return d < radius + (Engine.GridCellSize * 2) / 3; }); } - public Shoot(creep: Creep) { + public Shoot(angle) { 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.computedDamageToDeal, tint, this) + 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, + this.definition.projectileTextures, + angle, + this.computedDamageToDeal, + combinedTint, + this.computedTimeToLive, + this.computedPierce, + this.totalGemResistanceModifications ); + this.projectiles.push(proj); + return proj; } public update(elapsedMS: any): void { if (this.behaviour == TowerBehaviours.BasicTowerBehaviour) BasicTowerBehaviour(this, elapsedMS); + if (this.behaviour == TowerBehaviours.CircleTowerBehaviour) CircleTowerBehaviour(this, elapsedMS); } public destroy(): void { diff --git a/src/classes/game/TowerBehaviours.ts b/src/classes/game/TowerBehaviours.ts index 7f7d8d4..b203ac4 100644 --- a/src/classes/game/TowerBehaviours.ts +++ b/src/classes/game/TowerBehaviours.ts @@ -1,3 +1,5 @@ +import { Engine } from '../Bastion'; +import { calculateAngleToPoint } from './Projectile'; import { Tower } from './Tower'; /** @@ -11,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); @@ -26,10 +30,37 @@ function projectileCheck(tower: Tower, elapsedMS: number) { */ export function computeGemImprovements(tower: Tower) { let gemDamage = 0; + let gemAttackSpeedUp = 0; + 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) => { - gemDamage += gem.currentGemImprovement().damageUp; + let ccurrentGemImprovements = gem.currentGemImprovement(); + gemDamage += ccurrentGemImprovements.damageUp; + gemAttackSpeedUp += ccurrentGemImprovements.attackSpeedUp; + 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; + tower.computedRange = tower.definition.stats.range + gemRangeUp; + tower.computedTimeToLive = tower.definition.stats.timeToLive + gemTimeToLiveUp; + tower.computedPierce = tower.definition.stats.pierce + gemPierceUp; } /** @@ -46,9 +77,34 @@ export function BasicTowerBehaviour(tower: Tower, elapsedMS: number) { 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); + if (tower.ticksUntilNextShot <= 0) { + let x = tower.column * Engine.GridCellSize + Engine.GridCellSize / 2; + let y = tower.row * Engine.GridCellSize + Engine.GridCellSize / 2; + tower.ticksUntilNextShot = tower.computedAttackSpeed; + tower.Shoot(calculateAngleToPoint(x, y, focus.x, focus.y)); + } + } +} + +export function CircleTowerBehaviour(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.computedAttackSpeed; + let x = tower.column * Engine.GridCellSize + Engine.GridCellSize / 2; + let y = tower.row * Engine.GridCellSize + Engine.GridCellSize / 2; + tower.Shoot(calculateAngleToPoint(x, y, x, y + 10)); // Up + tower.Shoot(calculateAngleToPoint(x, y, x + 10, y)); // Right + tower.Shoot(calculateAngleToPoint(x, y, x - 10, y)); // Left + tower.Shoot(calculateAngleToPoint(x, y, x, y - 10)); // Down + tower.Shoot(calculateAngleToPoint(x, y, x + 10, y + 10)); // Up right + tower.Shoot(calculateAngleToPoint(x, y, x - 10, y + 10)); // Up left + tower.Shoot(calculateAngleToPoint(x, y, x - 10, y - 10)); // Down left + tower.Shoot(calculateAngleToPoint(x, y, x + 10, y - 10)); // Down right } } } diff --git a/src/classes/game/TowerManager.ts b/src/classes/game/TowerManager.ts index d63d0e6..967f547 100644 --- a/src/classes/game/TowerManager.ts +++ b/src/classes/game/TowerManager.ts @@ -8,6 +8,7 @@ import { GridEvents, TowerEvents } from '../Events'; export enum TowerBehaviours { BasicTowerBehaviour = 'BasicTowerBehaviour', + CircleTowerBehaviour = 'CircleTowerBehaviour', } export default class TowerManager { @@ -23,6 +24,7 @@ export default class TowerManager { }); private towers: Tower[] = []; constructor() { + // TODO: Unsubscribe from events once the scene is destroyed Engine.TowerManager = this; Engine.GameScene.events.on(GridEvents.CellMouseOver, (cell: Cell) => { if (this.isPlacingTower) { diff --git a/src/classes/game/WaveManager.ts b/src/classes/game/WaveManager.ts index 6bc94b6..4c73a1e 100644 --- a/src/classes/game/WaveManager.ts +++ b/src/classes/game/WaveManager.ts @@ -17,31 +17,38 @@ export default class WaveManager extends GameObject { private paths: PathDefinition[]; private ticks: number = 0; private started: boolean = false; - public finished: boolean = false; private internalCreepId: number = 0; + private pathsIndex: number; + public finished: boolean = false; + constructor(rounds: MissionRoundDefinition[], paths: PathDefinition[]) { super(); Engine.WaveManager = this; this.rounds = rounds; this.paths = paths; + this.pathsIndex = 0; } public start(roundIndex) { this.started = true; this.ticks = 0; this.creeps = []; this.finished = false; + if (this.pathsIndex + 1 == this.paths.length) { + this.pathsIndex = 0; + } else { + this.pathsIndex++; + } let tickToSpawnAt = 0; this.rounds[roundIndex].waves.forEach((wave) => { tickToSpawnAt += wave.firstCreepSpawnTick; wave.creeps.forEach((creep) => { - const creepObj = new Creep(creep, this.paths[0], this.internalCreepId); + const creepObj = new Creep(creep, this.paths[this.pathsIndex], this.internalCreepId); this.internalCreepId++; const creepInstance = { creep: creepObj, tickToSpawnAt, spawned: false, }; - console.log('CREAWTASEDASD'); tickToSpawnAt += wave.spawnIntervalTicks; this.creeps.push(creepInstance); }); @@ -58,10 +65,8 @@ export default class WaveManager extends GameObject { if (!creep.spawned && creep.tickToSpawnAt <= this.ticks) { creep.spawned = true; this.events.emit(WaveManagerEvents.CreepSpawned, creep.creep); - console.log('Wave manager creep spawned, ', creep, this.ticks); if (!this.finished && this.creeps.every((creep) => creep.spawned)) { this.finished = true; - console.log('wave maanger finisehd'); this.events.emit(WaveManagerEvents.Finished); } } else if (creep.spawned) { diff --git a/src/classes/gui/Button.ts b/src/classes/gui/Button.ts index 05fb20a..fcf6dd5 100644 --- a/src/classes/gui/Button.ts +++ b/src/classes/gui/Button.ts @@ -22,6 +22,10 @@ export default class Button extends GuiObject { this.buttonText.text = caption; } + getCaption(): string { + return this.caption; + } + constructor(bounds: PIXI.Rectangle, caption: string, buttonTexture: ButtonTexture, enabled: boolean = true) { super(true); if (buttonTexture == ButtonTexture.Button01) this.buttonTexture = Assets.Button01Texture; diff --git a/src/classes/gui/EndGameDialog.ts b/src/classes/gui/EndGameDialog.ts new file mode 100644 index 0000000..b0d0c52 --- /dev/null +++ b/src/classes/gui/EndGameDialog.ts @@ -0,0 +1,154 @@ +import * as PIXI from 'pixi.js'; +import GameAssets from '../Assets'; +import GameUIConstants from '../GameUIConstants'; +import ModalDialogBase from './ModalDialog'; +import TextInput from './TextInput'; +import MessageBox from './MessageBox'; +import { HighScoreManager } from '../game/HighScoreManager'; +import MissionStats from '../game/MissionStats'; + +export const EndGameDialogButtons = { + Confirm: 'OK', + Skip: 'Skip', +}; + +export default class EndGameDialog extends ModalDialogBase { + private dialogCaption: PIXI.Text; + private playerNameTextInput: TextInput; + private lost: boolean; + private highScore: HighScoreManager; + private missionStats: MissionStats; + + constructor(missionName: string, missionStats: MissionStats, lost: boolean) { + super( + [EndGameDialogButtons.Confirm, EndGameDialogButtons.Skip], + EndGameDialogButtons.Confirm, + EndGameDialogButtons.Skip + ); + this.lost = lost; + this.highScore = new HighScoreManager(missionName); + this.missionStats = missionStats; + } + + protected override generate(): void { + super.generate(); + this.dialogCaption = new PIXI.Text({ + text: this.lost ? 'You lost!' : 'You won!', + style: new PIXI.TextStyle({ + fill: 0xffffff, + fontSize: 36, + stroke: { color: 0x000000, width: 2 }, + dropShadow: { + color: 0x000000, + blur: 8, + distance: 0, + }, + }), + }); + this.dialogCaption.anchor.set(0.5, 0.5); + this.dialogCaption.x = this.dialogContainer.width / 2; + this.dialogCaption.y = 50; + this.dialogContainer.addChild(this.dialogCaption); + } + + protected override createDialogBackground(): PIXI.NineSliceSprite { + return new PIXI.NineSliceSprite({ + texture: GameAssets.EndScreenDialog, + leftWidth: 50, + topHeight: 100, + rightWidth: 50, + bottomHeight: 50, + }); + } + + protected override createContent(): PIXI.Container { + const container = new PIXI.Container(); + const lineHeight = 35; + const lblScore = this.createText('Mission details:', '#fee', true); + container.addChild(lblScore); + const stats = this.missionStats.getStats(); + const width = this.getWidth() - this.background.leftWidth - this.background.rightWidth - 20; + const labels = [ + this.createText('HP:'), + this.createText('Gold:'), + this.createText('Waves Survived:'), + this.createText('Gold Earned:'), + this.createText('Gold Spent:'), + this.createText('----'), + this.createText('Score:'), + ]; + const values = [ + this.createText(stats.hp.toString(), 'yellow'), + this.createText(stats.gold.toString(), 'yellow'), + this.createText(stats.wavesSurvived.toString(), 'yellow'), + this.createText(stats.goldEarned.toString(), 'yellow'), + this.createText(stats.goldSpent.toString(), 'yellow'), + this.createText('----', 'yellow'), + this.createText(stats.score.toString(), 'yellow'), + ]; + const valueX = 300; + for (let i = 0; i < labels.length; i++) { + if (labels[i].text === '----') { + const line = new PIXI.Graphics(); + const y = lblScore.y + lblScore.height + 10 + i * lineHeight + lineHeight / 2; + line.moveTo(10, y); + line.lineTo(width, y); + line.stroke({ color: 'yellow', width: 2 }); + container.addChild(line); + } else { + labels[i].x = 10; + labels[i].y = lblScore.y + lblScore.height + 10 + i * lineHeight; + container.addChild(labels[i]); + + values[i].x = valueX; + values[i].y = lblScore.y + lblScore.height + 10 + i * lineHeight; + container.addChild(values[i]); + } + } + const offsetY = values[values.length - 1].y + lineHeight + 80; + + const lblName = this.createText('Enter your name:'); + lblName.y = offsetY; + container.addChild(lblName); + this.playerNameTextInput = new TextInput(width, GameUIConstants.MaximumPlayerNameLength); + this.playerNameTextInput.container.y = lblName.y + lblName.height + 10; + container.addChild(this.playerNameTextInput.container); + return container; + } + + override close(button?: string): void { + if (button === EndGameDialogButtons.Confirm) { + if (this.playerNameTextInput.getText().length == 0) { + MessageBox.show('Please enter your name.', ['OK']); + } else { + this.highScore.addScore({ + playerName: this.playerNameTextInput.getText(), + score: this.missionStats.getStats().score, + timestamp: Date.now(), + }); + super.close(button); + } + } else { + super.close(button); + } + } + + private createText(caption: string, color: string = '#fff', bold = false): PIXI.Text { + return new PIXI.Text({ + text: caption, + style: new PIXI.TextStyle({ + fill: color, + fontSize: 24, + fontWeight: bold ? 'bold' : 'normal', + }), + }); + } + + protected override getWidth(): number | undefined { + return 600; + } + + protected override getHeight(): number | undefined { + return 800; + } +} diff --git a/src/classes/gui/HighScoreDialog.ts b/src/classes/gui/HighScoreDialog.ts new file mode 100644 index 0000000..6a3220b --- /dev/null +++ b/src/classes/gui/HighScoreDialog.ts @@ -0,0 +1,122 @@ +import * as PIXI from 'pixi.js'; +import GameAssets from '../Assets'; +import ModalDialogBase from './ModalDialog'; +import { HighScoreManager } from '../game/HighScoreManager'; + +export const HighScoreDialogButtons = { + Retry: 'Retry', + MainMenu: 'Main Menu', + NextMission: 'Next Mission', +}; + +export default class HighScoreDialog extends ModalDialogBase { + private dialogCaption: PIXI.Text; + private highScore: HighScoreManager; + + constructor(missionName: string, retryPossible: boolean, nextMissionAvailable: boolean) { + super( + [ + ...(retryPossible ? [HighScoreDialogButtons.Retry] : []), + ...(nextMissionAvailable ? [HighScoreDialogButtons.NextMission] : []), + HighScoreDialogButtons.MainMenu, + ], + retryPossible + ? HighScoreDialogButtons.Retry + : nextMissionAvailable + ? HighScoreDialogButtons.NextMission + : HighScoreDialogButtons.MainMenu, + HighScoreDialogButtons.MainMenu + ); + this.highScore = new HighScoreManager(missionName); + } + + protected override generate(): void { + super.generate(); + this.dialogCaption = new PIXI.Text({ + text: 'Highscore', + style: new PIXI.TextStyle({ + fill: 0xffffff, + fontSize: 36, + stroke: { color: 0x000000, width: 2 }, + dropShadow: { + color: 0x000000, + blur: 8, + distance: 0, + }, + }), + }); + this.dialogCaption.anchor.set(0.5, 0.5); + this.dialogCaption.x = this.dialogContainer.width / 2; + this.dialogCaption.y = 50; + this.dialogContainer.addChild(this.dialogCaption); + } + + protected override createDialogBackground(): PIXI.NineSliceSprite { + return new PIXI.NineSliceSprite({ + texture: GameAssets.EndScreenDialog, + leftWidth: 50, + topHeight: 100, + rightWidth: 50, + bottomHeight: 50, + }); + } + + protected override createContent(): PIXI.Container { + const container = new PIXI.Container(); + const caption = this.createText('Mission: ' + this.highScore.missionName, '#fee', true); + container.addChild(caption); + const lineHeight = 35; + const scores = this.highScore.getScores(); + while (scores.length < 10) { + scores.push({ playerName: '---', score: 0, timestamp: 0 }); + } + + const numberTexts = [ + this.createText('#', '#fee'), + ...scores.map((_, i) => this.createText((i + 1).toString())), + ]; + const playerTexts = [ + this.createText('Player', '#fee', true), + ...scores.map((score) => this.createText(score.playerName)), + ]; + const scoreTexts = [ + this.createText('Score', '#fee', true), + ...scores.map((score) => this.createText(score.score.toString())), + ]; + const playerX = numberTexts.reduce((maxX, text) => Math.max(maxX, text.width), 0) + 20; + const scoreX = playerX + playerTexts.reduce((maxX, text) => Math.max(maxX, text.width), 0) + 20; + for (let i = 0; i < playerTexts.length; i++) { + numberTexts[i].x = 10; + numberTexts[i].y = lineHeight + 10 + i * lineHeight; + container.addChild(numberTexts[i]); + + playerTexts[i].x = playerX; + playerTexts[i].y = lineHeight + 10 + i * lineHeight; + container.addChild(playerTexts[i]); + + scoreTexts[i].x = scoreX; + scoreTexts[i].y = lineHeight + 10 + i * lineHeight; + container.addChild(scoreTexts[i]); + } + return container; + } + + private createText(caption: string, color: string = '#fff', bold = false): PIXI.Text { + return new PIXI.Text({ + text: caption, + style: new PIXI.TextStyle({ + fill: color, + fontSize: 24, + fontWeight: bold ? 'bold' : 'normal', + }), + }); + } + + protected override getWidth(): number | undefined { + return 600; + } + + protected override getHeight(): number | undefined { + return 800; + } +} diff --git a/src/classes/gui/MessageBox.ts b/src/classes/gui/MessageBox.ts new file mode 100644 index 0000000..7febad1 --- /dev/null +++ b/src/classes/gui/MessageBox.ts @@ -0,0 +1,37 @@ +import * as PIXI from 'pixi.js'; +import ModalDialogBase from './ModalDialog'; +import GuiObject from '../GuiObject'; + +export default class MessageBox extends ModalDialogBase { + private caption: string; + + constructor(caption: string, buttons: string[], escapeKeyButton?: string | null, enterKeyButton?: string | null) { + if (!enterKeyButton && buttons.length > 0) enterKeyButton = buttons[0]; + if (!escapeKeyButton && buttons.length > 0) escapeKeyButton = buttons[buttons.length - 1]; + + super(buttons, escapeKeyButton, enterKeyButton); + this.caption = caption; + } + + protected override createContent(): PIXI.Container { + const text = new PIXI.Text({ + text: this.caption, + style: new PIXI.TextStyle({ + fill: 0xffffff, + fontSize: 24, + }), + }); + return text; + } + + /** + * Shows a message box with the specified caption and buttons. + * @param caption The caption of the message box. + * @param buttons The buttons to show. + * @returns A promise that resolves with the button that was clicked. + */ + public static show(caption: string, buttons: string[], escapeKeyButtonIndex: number = 0): Promise { + const messageBox = new MessageBox(caption, buttons); + return messageBox.show(); + } +} diff --git a/src/classes/gui/ModalDialog.ts b/src/classes/gui/ModalDialog.ts new file mode 100644 index 0000000..8f682b4 --- /dev/null +++ b/src/classes/gui/ModalDialog.ts @@ -0,0 +1,212 @@ +import * as PIXI from 'pixi.js'; +import GuiObject from '../GuiObject'; +import Assets from '../Assets'; +import { Engine } from '../Bastion'; +import GameAssets from '../Assets'; +import Button, { ButtonTexture } from './Button'; +import KeyboardManager from '../game/KeyboardManager'; + +export default abstract class ModalDialogBase extends GuiObject { + protected overlay: PIXI.Graphics; + protected buttonHeight = 65; + protected buttonCaptions: string[]; + protected buttons: Button[] = []; + protected dialogContent: PIXI.Container; + protected dialogContainer: PIXI.Container; + protected background: PIXI.NineSliceSprite; + + private generated = false; + private escapeKeyButton?: string | null; + private enterKeyButton?: string | null; + private keyboardManagerUnsubscribe: () => void; + + protected constructor(buttons: string[], enterKeyButton?: string | null, escapeKeyButton?: string | null) { + super(); + this.buttonCaptions = buttons; + if (escapeKeyButton && !buttons.includes(escapeKeyButton)) + throw new Error(`Escape key button "${escapeKeyButton}" not found in buttons: ${buttons}`); + this.escapeKeyButton = escapeKeyButton; + if (enterKeyButton && !buttons.includes(enterKeyButton)) + throw new Error(`Enter key button "${enterKeyButton}" not found in buttons: ${buttons}`); + this.enterKeyButton = enterKeyButton; + this.keyboardManagerUnsubscribe = KeyboardManager.onKeyPressed(this.onKeyPress.bind(this)); + } + + /** + * Show the dialog and await the user's response. + * @param width The width of the dialog (optional, if not provided then it is automatically calculated). + * @param height The height of the dialog (optional, if not provided then it is automatically calculated). + * @returns Button that was clicked or null if dialog was closed without clicking a button (potentially no buttons defined). + */ + public show(): Promise { + this.generate(); + const dialogBounds = `x: ${Math.round(this.dialogContainer.x)}, y: ${Math.round( + this.dialogContainer.y + )}, width: ${Math.round(this.dialogContainer.width)}, height: ${Math.round(this.dialogContainer.height)}`; + const contentBounds = `x: ${Math.round(this.dialogContent.x)}, y: ${Math.round( + this.dialogContent.y + )}, width: ${Math.round(this.dialogContent.width)}, height: ${Math.round(this.dialogContent.height)}`; + console.debug( + `ModalDialogBase.show(dialog: ${dialogBounds}, content: ${contentBounds}, buttons: ${this.buttonCaptions})` + ); + return new Promise((resolve, reject) => { + Engine.app.stage.addChild(this.container); + this.onClosed = (button) => { + this.destroy(); + resolve(button); + }; + }); + } + + /** + * Event that is triggered when the dialog is closed. + * @param button The button that was clicked or null if the dialog was closed without clicking a button. + */ + public onClosed: (button?: string) => void; + + /** + * Create the content of the dialog. + */ + protected abstract createContent(): PIXI.Container; + + /** + * Creates dialog background. + */ + protected createDialogBackground(): PIXI.NineSliceSprite { + return new PIXI.NineSliceSprite({ + texture: GameAssets.Frame04Texture, + leftWidth: 60, + topHeight: 60, + rightWidth: 60, + bottomHeight: 60, + }); + } + + /** + * Gets the width of the dialog. + * If not overridden, the width is automatically calculated. + */ + protected getWidth(): number | undefined { + return undefined; + } + + /** + * Gets the height of the dialog. + * If not overridden, the height is automatically calculated. + */ + protected getHeight(): number | undefined { + return undefined; + } + + protected generate() { + if (this.generated) return; + this.generated = true; + // Show overlay to prevent user from interacting with the game + this.overlay = new PIXI.Graphics(); + this.overlay.rect(0, 0, Engine.app.canvas.width, Engine.app.canvas.height); + this.overlay.fill({ color: 0x000000, alpha: 0.5 }); + // Prevent interaction with the underlying scene + this.overlay.interactive = true; + this.container.addChild(this.overlay); + const buttonDefs = this.buttonCaptions.map((btnCaption) => ({ + caption: btnCaption, + width: btnCaption.length * 14 + 60, + height: this.buttonHeight, + click: () => this.close(btnCaption), + })); + + this.background = this.createDialogBackground(); + this.dialogContent = this.createContent(); + + let buttonTotalWidth = 0; + for (const buttonDef of buttonDefs) { + if (buttonTotalWidth > 0) buttonTotalWidth += 10; + buttonTotalWidth += buttonDef.width; + } + const buttonAreaHeight = this.buttonCaptions.length > 0 ? this.buttonHeight + 10 : 0; + + let width = + this.getWidth() || + Math.max(buttonTotalWidth, this.dialogContent.width) + + this.background.leftWidth + + this.background.rightWidth; + let height = + this.getHeight() || + this.dialogContent.height + buttonAreaHeight + this.background.topHeight + this.background.bottomHeight; + const modalBounds = new PIXI.Rectangle( + Engine.app.canvas.width / 2 - width / 2, + Engine.app.canvas.height / 2 - height / 2, + width, + height + ); + + this.dialogContainer = new PIXI.Container(); + this.dialogContainer.x = modalBounds.x; + this.dialogContainer.y = modalBounds.y; + this.background.width = width; + this.background.height = height; + this.dialogContainer.addChild(this.background); + + if (this.dialogContent.width < modalBounds.width) { + this.dialogContent.x = modalBounds.width / 2 - this.dialogContent.width / 2; + } + if ( + this.dialogContent.height < + modalBounds.height - buttonAreaHeight - this.background.topHeight - this.background.bottomHeight + ) { + this.dialogContent.y = + this.background.topHeight + + (modalBounds.height - buttonAreaHeight - this.background.topHeight - this.background.bottomHeight) / 2 - + this.dialogContent.height / 2; + } else { + this.dialogContent.y = this.background.topHeight; + } + this.dialogContainer.addChild(this.dialogContent); + + let buttonXPos = modalBounds.width / 2 - buttonTotalWidth / 2; + for (const buttonDef of buttonDefs) { + const button = new Button( + new PIXI.Rectangle( + buttonXPos, + modalBounds.height - this.buttonHeight - this.background.bottomHeight, + buttonDef.width, + buttonDef.height + ), + buttonDef.caption, + ButtonTexture.Button01 + ); + button.onClick = buttonDef.click; + this.buttons.push(button); + this.dialogContainer.addChild(button.container); + buttonXPos += buttonDef.width + 10; + } + this.container.addChild(this.dialogContainer); + } + + /** + * Close the dialog. + * Only use this method if you want to close the dialog without clicking a button. + */ + public close(button?: string) { + this.destroy(); + if (this.onClosed) this.onClosed(button); + } + + override destroy(): void { + this.keyboardManagerUnsubscribe(); + super.destroy(); + } + + private onKeyPress(event: KeyboardEvent) { + if (this.buttons.length === 0) return; + if (this.escapeKeyButton && event.key === 'Escape') { + // Message box is modal, so we can safely prevent the default behavior + event.preventDefault(); + this.close(this.escapeKeyButton); + } else if (this.enterKeyButton && event.key === 'Enter') { + // Message box is modal, so we can safely prevent the default behavior + event.preventDefault(); + this.close(this.enterKeyButton); + } + } +} diff --git a/src/classes/gui/TextInput.ts b/src/classes/gui/TextInput.ts new file mode 100644 index 0000000..8c95640 --- /dev/null +++ b/src/classes/gui/TextInput.ts @@ -0,0 +1,52 @@ +import * as PIXI from 'pixi.js'; +import GuiObject from '../GuiObject'; +import Assets from '../Assets'; +import GameAssets from '../Assets'; +import KeyboardManager from '../game/KeyboardManager'; + +export default class TextInput extends GuiObject { + private backgroundSprite: PIXI.NineSliceSprite; + private text: PIXI.Text; + private maxLength: number; + private keyboardManagerUnsubscribe: () => void; + + public getText(): string { + return this.text.text; + } + + constructor(width: number, maxLength: number) { + super(); + this.maxLength = maxLength; + this.backgroundSprite = new PIXI.NineSliceSprite({ + texture: GameAssets.Frame01Texture, + leftWidth: 20, + topHeight: 20, + rightWidth: 20, + bottomHeight: 20, + }); + this.backgroundSprite.x = 0; + this.backgroundSprite.y = 0; + this.backgroundSprite.width = width; + this.backgroundSprite.height = 80; + this.container.addChild(this.backgroundSprite); + this.text = new PIXI.Text({ + text: '', + style: new PIXI.TextStyle({ + fill: 0xffffff, + fontSize: 24, + }), + }); + this.text.x = 30; + this.text.y = 25; + this.container.addChild(this.text); + this.keyboardManagerUnsubscribe = KeyboardManager.onKeyPressed(this.onKeyPress.bind(this)); + } + + private onKeyPress(event: KeyboardEvent) { + if (event.key == 'Backspace') { + this.text.text = this.text.text.slice(0, -1); + } else if (event.key.length == 1 && this.text.text.length < this.maxLength) { + this.text.text += event.key; + } + } +} diff --git a/src/classes/gui/TowerPanel.ts b/src/classes/gui/TowerPanel.ts index c090ebf..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) => { @@ -246,14 +324,20 @@ export default class TowerPanel extends GuiObject { } else { this.ShowLeft(); } - tower.parent.showRangePreview(false, tower.definition.stats.range); + tower.parent.showRangePreview(false, tower.computedRange); } 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`; + 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; @@ -270,7 +354,7 @@ export default class TowerPanel extends GuiObject { public Hide() { this.isShown = false; this.container.alpha = 0; - this.container.x = GameUIConstants.SidebarRect.x + 10; + this.container.x = -1000; Engine.Grid.rangePreview.clear(); } } diff --git a/src/main.ts b/src/main.ts index 39659c1..6311fad 100644 --- a/src/main.ts +++ b/src/main.ts @@ -6,6 +6,8 @@ import { GameScene } from './scenes/Game'; import { AnimationManager } from './classes/game/AnimationManager'; import NotificationManager from './classes/game/NotificationManager'; import GameUIConstants from './classes/GameUIConstants'; +import KeyboardManager from './classes/game/KeyboardManager'; +import { GemType } from './classes/Definitions'; (async () => { const app = new PIXI.Application(); @@ -47,6 +49,7 @@ import GameUIConstants from './classes/GameUIConstants'; resize(); await Assets.LoadAssets(); GameUIConstants.init(); + KeyboardManager.init(); new GameMaster(); Engine.AnimationManager = new AnimationManager(); Engine.NotificationManager = new NotificationManager(); @@ -61,7 +64,7 @@ import GameUIConstants from './classes/GameUIConstants'; }); Engine.GameMaster.changeScene(new MainScene()); let params = new URLSearchParams(location.href); - if (params.entries().next().value[1] == 'game') Engine.GameMaster.changeScene(new GameScene('Mission 1')); + if (params.entries().next().value[1] == 'game') Engine.GameMaster.changeScene(new GameScene('The Turn')); if (Engine.latestCommit != 'DEVELOPMENT') window.onbeforeunload = () => { diff --git a/src/scenes/Game.ts b/src/scenes/Game.ts index fb01b7c..8ed5ebf 100644 --- a/src/scenes/Game.ts +++ b/src/scenes/Game.ts @@ -16,6 +16,8 @@ import GameUIConstants from '../classes/GameUIConstants'; import Tooltip from '../classes/gui/Tooltip'; import TowerPanel, { VisualGemSlot } from '../classes/gui/TowerPanel'; import Gem from '../classes/game/Gem'; +import EndGameDialog from '../classes/gui/EndGameDialog'; +import HighScoreDialog, { HighScoreDialogButtons } from '../classes/gui/HighScoreDialog'; enum RoundMode { Purchase = 0, @@ -57,6 +59,7 @@ export class GameScene extends Scene { }); } public init() { + Engine.latestGemId = 0; new Grid(this.mission.gameMap, this.missionIndex); new TowerManager(); new WaveManager(this.mission.rounds, this.mission.gameMap.paths); @@ -130,6 +133,7 @@ export class GameScene extends Scene { }); this.ticker.start(); } + public update(elapsedMS) { if (this.isGameOver) { if (this.destroyTicker) { @@ -162,10 +166,10 @@ export class GameScene extends Scene { if (this.MissionStats.getHP() <= 0) { this.isGameOver = true; - this.ShowScoreScreen(true); + this.ShowEndgameDialog(true); } else if (this.playerWon) { this.isGameOver = true; - this.ShowScoreScreen(false); + this.ShowEndgameDialog(false); } } public DarkenScreen() { @@ -241,12 +245,27 @@ export class GameScene extends Scene { this.setRoundMode(RoundMode.Purchase); } - private ShowScoreScreen(lost) { - // TODO: show to player for real (see how this.OfferPlayerGems() does it) - if (lost) { - console.log('LOSE!'); - } else { - console.log('WIN!'); + private async ShowEndgameDialog(lost) { + const endGameDialog = new EndGameDialog(this.mission.name, this.MissionStats, lost); + await endGameDialog.show(); + const highScore = new HighScoreDialog( + this.mission.name, + lost, + !lost && this.missionIndex + 1 < GameAssets.Missions.length + ); + const result = await highScore.show(); + if (result === HighScoreDialogButtons.MainMenu) { + this.ReturnToMain(); + } else if (result === HighScoreDialogButtons.NextMission) { + this.destroy(); + Engine.GameMaster.changeScene(new MissionPickerScene()); + Engine.GameMaster.changeScene(new GameScene(GameAssets.Missions[this.missionIndex + 1].name)); + Engine.NotificationManager.Notify('Loading next mission. Good luck.', 'green'); + } else if (result === HighScoreDialogButtons.Retry) { + this.destroy(); + Engine.GameMaster.changeScene(new MissionPickerScene()); + Engine.GameMaster.changeScene(new GameScene(this.mission.name)); + Engine.NotificationManager.Notify('Retrying mission.', 'green'); } } @@ -271,8 +290,6 @@ export class GameScene extends Scene { } private ReturnToMain() { - this.destroy(); - Engine.GameMaster.currentScene.stage.removeChildren(); Engine.GameMaster.changeScene(new MissionPickerScene()); } } diff --git a/src/scenes/Scene.ts b/src/scenes/Scene.ts index 0e2d9d6..0686908 100644 --- a/src/scenes/Scene.ts +++ b/src/scenes/Scene.ts @@ -12,6 +12,7 @@ export default class Scene { } public destroy() { this.stage.destroy(); + this._events.removeAllListeners(); this.gui.forEach((element) => { element.destroy(); });