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();
});