├── static ├── coin.svg ├── sound │ ├── bgSound.mp3 │ ├── sfx │ │ ├── pickUp.m4a │ │ ├── clickEnemy.m4a │ │ ├── clickMenu.m4a │ │ ├── towerShoot.m4a │ │ ├── lowResourse.m4a │ │ ├── towerUpgrade.m4a │ │ └── index.ts │ └── index.ts ├── font │ ├── AstroSpace-0Wl3o.otf │ └── AstroSpace-eZ2Bg.ttf ├── projectiles │ ├── Thunderbolt │ │ ├── Follow │ │ │ ├── 0.svg │ │ │ ├── 2.svg │ │ │ ├── 1.svg │ │ │ └── 3.svg │ │ └── Die │ │ │ ├── 2.svg │ │ │ ├── 1.svg │ │ │ └── 0.svg │ ├── Fireball │ │ ├── Follow │ │ │ ├── 2.svg │ │ │ ├── 0.svg │ │ │ ├── 3.svg │ │ │ └── 1.svg │ │ └── Die │ │ │ ├── 0.svg │ │ │ ├── 2.svg │ │ │ └── 1.svg │ ├── Icebolt │ │ ├── Die │ │ │ ├── 2.svg │ │ │ ├── 1.svg │ │ │ └── 0.svg │ │ └── Follow │ │ │ ├── 1.svg │ │ │ └── 0.svg │ ├── Poisonball │ │ ├── Die │ │ │ ├── 2.svg │ │ │ ├── 0.svg │ │ │ └── 1.svg │ │ └── Follow │ │ │ ├── 3.svg │ │ │ └── 0.svg │ └── Click │ │ ├── 0.svg │ │ ├── 3.svg │ │ ├── 1.svg │ │ └── 2.svg ├── cursors │ ├── build0.svg │ ├── cursor-hammer.svg │ ├── cursor-arrow.svg │ ├── build3.svg │ ├── build4.svg │ ├── build1.svg │ └── build2.svg ├── towers │ ├── Base.svg │ ├── Upgrade0.svg │ ├── FireTower │ │ ├── Base0.svg │ │ ├── Upgrade1.svg │ │ └── Base1.svg │ ├── IceTower │ │ ├── Base0.svg │ │ ├── Upgrade1.svg │ │ └── Base1.svg │ ├── PoisonTower │ │ ├── Base0.svg │ │ ├── Upgrade1.svg │ │ └── Base1.svg │ └── ThunderTower │ │ ├── Base0.svg │ │ ├── Upgrade1.svg │ │ └── Base1.svg ├── enemies │ ├── PurpleCommon │ │ └── Die │ │ │ └── 0.svg │ └── RedBlobElite │ │ └── Die │ │ └── 2.svg └── loot.svg ├── .npmrc ├── src ├── lib │ ├── config │ │ ├── animations │ │ │ ├── cursors │ │ │ │ ├── index.ts │ │ │ │ └── Build.ts │ │ │ ├── Loot.ts │ │ │ ├── Throne.ts │ │ │ ├── towers │ │ │ │ ├── TowerBase.ts │ │ │ │ ├── IceTower │ │ │ │ │ ├── Base0.ts │ │ │ │ │ ├── Base1.ts │ │ │ │ │ ├── Base2.ts │ │ │ │ │ ├── Upgrade0.ts │ │ │ │ │ ├── Upgrade1.ts │ │ │ │ │ └── Upgrade2.ts │ │ │ │ ├── FireTower │ │ │ │ │ ├── Base0.ts │ │ │ │ │ ├── Base1.ts │ │ │ │ │ ├── Base2.ts │ │ │ │ │ ├── Upgrade0.ts │ │ │ │ │ ├── Upgrade1.ts │ │ │ │ │ └── Upgrade2.ts │ │ │ │ ├── PoisonTower │ │ │ │ │ ├── Base0.ts │ │ │ │ │ ├── Base1.ts │ │ │ │ │ ├── Base2.ts │ │ │ │ │ ├── Upgrade0.ts │ │ │ │ │ ├── Upgrade1.ts │ │ │ │ │ └── Upgrade2.ts │ │ │ │ ├── ThunderTower │ │ │ │ │ ├── Base0.ts │ │ │ │ │ ├── Base1.ts │ │ │ │ │ ├── Base2.ts │ │ │ │ │ ├── Upgrade0.ts │ │ │ │ │ ├── Upgrade1.ts │ │ │ │ │ └── Upgrade2.ts │ │ │ │ └── index.ts │ │ │ ├── projectiles │ │ │ │ ├── Icebolt │ │ │ │ │ ├── Follow.ts │ │ │ │ │ └── Die.ts │ │ │ │ ├── Click.ts │ │ │ │ ├── Fireball │ │ │ │ │ ├── Die.ts │ │ │ │ │ └── Follow.ts │ │ │ │ ├── Poisonball │ │ │ │ │ ├── Die.ts │ │ │ │ │ └── Follow.ts │ │ │ │ ├── Thunderbolt │ │ │ │ │ ├── Die.ts │ │ │ │ │ └── Follow.ts │ │ │ │ └── index.ts │ │ │ ├── enemies │ │ │ │ ├── BlueCommon │ │ │ │ │ ├── Die.ts │ │ │ │ │ └── Follow.ts │ │ │ │ ├── PurpleCommon │ │ │ │ │ ├── Die.ts │ │ │ │ │ └── Follow.ts │ │ │ │ ├── RedBlobElite │ │ │ │ │ ├── Die.ts │ │ │ │ │ └── Follow.ts │ │ │ │ ├── YellowCommon │ │ │ │ │ ├── Die.ts │ │ │ │ │ └── Follow.ts │ │ │ │ ├── BlueBlobElite │ │ │ │ │ ├── Die.ts │ │ │ │ │ └── Follow.ts │ │ │ │ ├── BlueCircleElite │ │ │ │ │ ├── Die.ts │ │ │ │ │ └── Follow.ts │ │ │ │ ├── GreenCircleElite │ │ │ │ │ ├── Die.ts │ │ │ │ │ └── Follow.ts │ │ │ │ └── index.ts │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── entities │ │ │ ├── index.ts │ │ │ ├── projectiles.ts │ │ │ ├── towers.ts │ │ │ └── enemies.ts │ │ ├── upgrades.ts │ │ ├── collisionHandlers.ts │ │ └── stages.ts │ ├── index.ts │ ├── components │ │ ├── Effects │ │ │ ├── index.ts │ │ │ └── TowerShoot.svelte │ │ ├── Effect.svelte │ │ ├── PauseIcon.svelte │ │ ├── Gui │ │ │ ├── MenuLayout.svelte │ │ │ ├── Button.svelte │ │ │ ├── StartScreen.svelte │ │ │ ├── PauseScreen.svelte │ │ │ ├── WinLoseScreen.svelte │ │ │ └── Pause.svelte │ │ ├── BackgroundContainer.svelte │ │ ├── GitHubIcon.svelte │ │ ├── Loot.svelte │ │ ├── Bg1.svelte │ │ ├── StaticEntity.svelte │ │ ├── Bg3.svelte │ │ ├── DynamicEntity.svelte │ │ ├── Bg2.svelte │ │ ├── Bg4.svelte │ │ ├── HealthBar.svelte │ │ └── GameArea.svelte │ ├── store │ │ ├── States │ │ │ ├── Throne │ │ │ │ ├── index.ts │ │ │ │ ├── Idle.svelte.ts │ │ │ │ └── Die.svelte.ts │ │ │ ├── Enemy │ │ │ │ ├── index.ts │ │ │ │ ├── Die.svelte.ts │ │ │ │ └── FollowThrone.svelte.ts │ │ │ ├── Projectiles │ │ │ │ ├── index.ts │ │ │ │ ├── FollowAngle.svelte.ts │ │ │ │ ├── Explode.svelte.ts │ │ │ │ └── FollowTarget.svelte.ts │ │ │ ├── Tower │ │ │ │ ├── index.ts │ │ │ │ ├── NotBuilt.svelte.ts │ │ │ │ ├── Guard.svelte.ts │ │ │ │ ├── Shoot.svelte.ts │ │ │ │ └── Upgrade.svelte.ts │ │ │ ├── BaseState.svelte.ts │ │ │ └── index.ts │ │ ├── entityFabric.ts │ │ ├── managers.svelte.ts │ │ ├── StateMachine.svelte.ts │ │ ├── Screen.svelte.ts │ │ ├── Cursor.svelte.ts │ │ ├── Vector2.svelte.ts │ │ ├── Animation.svelte.ts │ │ ├── Game.svelte.ts │ │ ├── LootTracker.svelte.ts │ │ ├── CollisionManager.svelte.ts │ │ ├── GameLoop.svelte.ts │ │ ├── EntityManager.svelte.ts │ │ └── SoundManager.svelte.ts │ └── utils │ │ └── preload.ts ├── app.d.ts ├── routes │ ├── +layout.svelte │ └── global.css └── app.html ├── .prettierignore ├── vite.config.ts ├── .prettierrc ├── .gitignore ├── tsconfig.json ├── eslint.config.js ├── README.md ├── svelte.config.js ├── LICENSE.txt └── package.json /static/coin.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /src/lib/config/animations/cursors/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Build'; 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Package Managers 2 | package-lock.json 3 | pnpm-lock.yaml 4 | yarn.lock 5 | -------------------------------------------------------------------------------- /src/lib/index.ts: -------------------------------------------------------------------------------- 1 | // place files you want to import through the `$lib` alias in this folder. 2 | -------------------------------------------------------------------------------- /src/lib/components/Effects/index.ts: -------------------------------------------------------------------------------- 1 | export { default as TowerShoot } from './TowerShoot.svelte'; 2 | -------------------------------------------------------------------------------- /src/lib/store/States/Throne/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Idle.svelte'; 2 | export * from './Die.svelte'; 3 | -------------------------------------------------------------------------------- /static/sound/bgSound.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baterson/svelte-tower-defence/HEAD/static/sound/bgSound.mp3 -------------------------------------------------------------------------------- /static/sound/sfx/pickUp.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baterson/svelte-tower-defence/HEAD/static/sound/sfx/pickUp.m4a -------------------------------------------------------------------------------- /static/sound/sfx/clickEnemy.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baterson/svelte-tower-defence/HEAD/static/sound/sfx/clickEnemy.m4a -------------------------------------------------------------------------------- /static/sound/sfx/clickMenu.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baterson/svelte-tower-defence/HEAD/static/sound/sfx/clickMenu.m4a -------------------------------------------------------------------------------- /static/sound/sfx/towerShoot.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baterson/svelte-tower-defence/HEAD/static/sound/sfx/towerShoot.m4a -------------------------------------------------------------------------------- /src/lib/store/States/Enemy/index.ts: -------------------------------------------------------------------------------- 1 | export { Die } from './Die.svelte'; 2 | export { FollowThrone } from './FollowThrone.svelte'; 3 | -------------------------------------------------------------------------------- /static/font/AstroSpace-0Wl3o.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baterson/svelte-tower-defence/HEAD/static/font/AstroSpace-0Wl3o.otf -------------------------------------------------------------------------------- /static/font/AstroSpace-eZ2Bg.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baterson/svelte-tower-defence/HEAD/static/font/AstroSpace-eZ2Bg.ttf -------------------------------------------------------------------------------- /static/sound/sfx/lowResourse.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baterson/svelte-tower-defence/HEAD/static/sound/sfx/lowResourse.m4a -------------------------------------------------------------------------------- /static/sound/sfx/towerUpgrade.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baterson/svelte-tower-defence/HEAD/static/sound/sfx/towerUpgrade.m4a -------------------------------------------------------------------------------- /static/sound/index.ts: -------------------------------------------------------------------------------- 1 | import bgSound from './bgSound.mp3'; 2 | import { sfx } from './sfx'; 3 | 4 | export const sounds = { 5 | bgSound: bgSound, 6 | effects: sfx 7 | }; 8 | -------------------------------------------------------------------------------- /src/lib/store/States/Throne/Idle.svelte.ts: -------------------------------------------------------------------------------- 1 | import { BaseState } from '$lib/store/States/BaseState.svelte'; 2 | 3 | export class Idle extends BaseState { 4 | update() {} 5 | } 6 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import { defineConfig } from 'vite'; 3 | 4 | export default defineConfig({ 5 | plugins: [sveltekit()] 6 | }); 7 | -------------------------------------------------------------------------------- /src/lib/config/animations/Loot.ts: -------------------------------------------------------------------------------- 1 | const frames = ['/loot.svg']; 2 | 3 | export const Loot = { 4 | name: 'Loot', 5 | frames, 6 | framesAmount: 1, 7 | frameRate: 0, 8 | loop: true 9 | } -------------------------------------------------------------------------------- /src/lib/store/States/Projectiles/index.ts: -------------------------------------------------------------------------------- 1 | export { FollowAngle } from './FollowAngle.svelte'; 2 | export { FollowTarget } from './FollowTarget.svelte'; 3 | export { Explode } from './Explode.svelte'; 4 | -------------------------------------------------------------------------------- /src/lib/config/animations/Throne.ts: -------------------------------------------------------------------------------- 1 | const frames = ['/throne.svg']; 2 | 3 | export const Throne = { 4 | name: 'Throne', 5 | frames, 6 | framesAmount: 1, 7 | frameRate: 0, 8 | loop: true 9 | }; 10 | -------------------------------------------------------------------------------- /src/lib/store/States/Tower/index.ts: -------------------------------------------------------------------------------- 1 | export { NotBuilt } from './NotBuilt.svelte'; 2 | export { Upgrade } from './Upgrade.svelte'; 3 | export { Guard } from './Guard.svelte'; 4 | export { Shoot } from './Shoot.svelte'; 5 | -------------------------------------------------------------------------------- /src/lib/config/animations/towers/TowerBase.ts: -------------------------------------------------------------------------------- 1 | const frames = ['/towers/Base.svg']; 2 | 3 | export const TowerBase = { 4 | name: 'TowerBase', 5 | frames, 6 | framesAmount: 1, 7 | frameRate: 0, 8 | loop: true 9 | }; 10 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100, 6 | "plugins": ["prettier-plugin-svelte"], 7 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] 8 | } 9 | -------------------------------------------------------------------------------- /src/lib/config/animations/towers/IceTower/Base0.ts: -------------------------------------------------------------------------------- 1 | const frames = ['/towers/IceTower/Base0.svg']; 2 | 3 | export const IceTowerBase0 = { 4 | name: 'IceTowerBase0', 5 | frames, 6 | framesAmount: 1, 7 | frameRate: 0, 8 | loop: true 9 | }; 10 | -------------------------------------------------------------------------------- /src/lib/config/animations/towers/IceTower/Base1.ts: -------------------------------------------------------------------------------- 1 | const frames = ['/towers/IceTower/Base1.svg']; 2 | 3 | export const IceTowerBase1 = { 4 | name: 'IceTowerBase1', 5 | frames, 6 | framesAmount: 1, 7 | frameRate: 0, 8 | loop: true 9 | }; 10 | -------------------------------------------------------------------------------- /src/lib/config/animations/towers/IceTower/Base2.ts: -------------------------------------------------------------------------------- 1 | const frames = ['/towers/IceTower/Base2.svg']; 2 | 3 | export const IceTowerBase2 = { 4 | name: 'IceTowerBase2', 5 | frames, 6 | framesAmount: 1, 7 | frameRate: 0, 8 | loop: true 9 | }; 10 | -------------------------------------------------------------------------------- /src/lib/config/animations/towers/FireTower/Base0.ts: -------------------------------------------------------------------------------- 1 | const frames = ['/towers/FireTower/Base0.svg']; 2 | 3 | export const FireTowerBase0 = { 4 | name: 'FireTowerBase0', 5 | frames, 6 | framesAmount: 1, 7 | frameRate: 0, 8 | loop: true 9 | }; 10 | -------------------------------------------------------------------------------- /src/lib/config/animations/towers/FireTower/Base1.ts: -------------------------------------------------------------------------------- 1 | const frames = ['/towers/FireTower/Base1.svg']; 2 | 3 | export const FireTowerBase1 = { 4 | name: 'FireTowerBase1', 5 | frames, 6 | framesAmount: 1, 7 | frameRate: 0, 8 | loop: true 9 | }; 10 | -------------------------------------------------------------------------------- /src/lib/config/animations/towers/FireTower/Base2.ts: -------------------------------------------------------------------------------- 1 | const frames = ['/towers/FireTower/Base2.svg']; 2 | 3 | export const FireTowerBase2 = { 4 | name: 'FireTowerBase2', 5 | frames, 6 | framesAmount: 1, 7 | frameRate: 0, 8 | loop: true 9 | }; 10 | -------------------------------------------------------------------------------- /src/lib/config/animations/towers/PoisonTower/Base0.ts: -------------------------------------------------------------------------------- 1 | const frames = ['/towers/PoisonTower/Base0.svg']; 2 | 3 | export const PoisonTowerBase0 = { 4 | name: 'PoisonTowerBase0', 5 | frames, 6 | framesAmount: 1, 7 | frameRate: 0, 8 | loop: true 9 | }; 10 | -------------------------------------------------------------------------------- /src/lib/config/animations/towers/PoisonTower/Base1.ts: -------------------------------------------------------------------------------- 1 | const frames = ['/towers/PoisonTower/Base1.svg']; 2 | 3 | export const PoisonTowerBase1 = { 4 | name: 'PoisonTowerBase1', 5 | frames, 6 | framesAmount: 1, 7 | frameRate: 0, 8 | loop: true 9 | }; 10 | -------------------------------------------------------------------------------- /src/lib/config/animations/towers/PoisonTower/Base2.ts: -------------------------------------------------------------------------------- 1 | const frames = ['/towers/PoisonTower/Base2.svg']; 2 | 3 | export const PoisonTowerBase2 = { 4 | name: 'PoisonTowerBase2', 5 | frames, 6 | framesAmount: 1, 7 | frameRate: 0, 8 | loop: true 9 | }; 10 | -------------------------------------------------------------------------------- /src/lib/config/animations/towers/ThunderTower/Base0.ts: -------------------------------------------------------------------------------- 1 | const frames = ['/towers/ThunderTower/Base0.svg']; 2 | 3 | export const ThunderTowerBase0 = { 4 | name: 'ThunderTowerBase0', 5 | frames, 6 | framesAmount: 1, 7 | frameRate: 0, 8 | loop: true 9 | }; 10 | -------------------------------------------------------------------------------- /src/lib/config/animations/towers/ThunderTower/Base1.ts: -------------------------------------------------------------------------------- 1 | const frames = ['/towers/ThunderTower/Base1.svg']; 2 | 3 | export const ThunderTowerBase1 = { 4 | name: 'ThunderTowerBase1', 5 | frames, 6 | framesAmount: 1, 7 | frameRate: 0, 8 | loop: true 9 | }; 10 | -------------------------------------------------------------------------------- /src/lib/config/animations/towers/ThunderTower/Base2.ts: -------------------------------------------------------------------------------- 1 | const frames = ['/towers/ThunderTower/Base2.svg']; 2 | 3 | export const ThunderTowerBase2 = { 4 | name: 'ThunderTowerBase2', 5 | frames, 6 | framesAmount: 1, 7 | frameRate: 0, 8 | loop: true 9 | }; 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | # Output 4 | .output 5 | .vercel 6 | /.svelte-kit 7 | /build 8 | 9 | # OS 10 | .DS_Store 11 | Thumbs.db 12 | 13 | # Env 14 | .env 15 | .env.* 16 | !.env.example 17 | !.env.test 18 | 19 | # Vite 20 | vite.config.js.timestamp-* 21 | vite.config.ts.timestamp-* 22 | -------------------------------------------------------------------------------- /src/lib/config/animations/cursors/Build.ts: -------------------------------------------------------------------------------- 1 | const frames = ['/cursors/build0.svg', '/cursors/build1.svg', '/cursors/build2.svg']; 2 | 3 | export const TowerBuildCursor = { 4 | name: 'TowerBuildCursor', 5 | frames, 6 | framesAmount: frames.length, 7 | frameRate: 7, 8 | loop: false 9 | }; 10 | -------------------------------------------------------------------------------- /src/lib/config/index.ts: -------------------------------------------------------------------------------- 1 | import type { EntityConfig } from './entities'; 2 | import { entities } from './entities'; 3 | 4 | export const getConfig = (name: string): EntityConfig => { 5 | if (!entities[name]) throw new Error(`Entity config not found for: ${name}`); 6 | return entities[name]; 7 | }; 8 | -------------------------------------------------------------------------------- /static/projectiles/Thunderbolt/Follow/0.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/lib/config/animations/projectiles/Icebolt/Follow.ts: -------------------------------------------------------------------------------- 1 | const frames = ['/projectiles/Icebolt/Follow/0.svg', '/projectiles/Icebolt/Follow/1.svg']; 2 | 3 | export const IceboltFollow = { 4 | name: 'IceboltFollow', 5 | frames, 6 | framesAmount: frames.length, 7 | frameRate: 10, 8 | loop: true 9 | }; 10 | -------------------------------------------------------------------------------- /src/lib/config/animations/towers/IceTower/Upgrade0.ts: -------------------------------------------------------------------------------- 1 | const frames = ['/towers/Base.svg', '/towers/Upgrade0.svg', '/towers/IceTower/Base0.svg']; 2 | 3 | export const IceTowerUpgrade0 = { 4 | name: 'IceTowerUpgrade0', 5 | frames, 6 | framesAmount: frames.length, 7 | frameRate: 10, 8 | loop: false 9 | }; 10 | -------------------------------------------------------------------------------- /src/lib/config/animations/towers/FireTower/Upgrade0.ts: -------------------------------------------------------------------------------- 1 | const frames = ['/towers/Base.svg', '/towers/Upgrade0.svg', '/towers/FireTower/Base0.svg']; 2 | 3 | export const FireTowerUpgrade0 = { 4 | name: 'FireTowerUpgrade0', 5 | frames, 6 | framesAmount: frames.length, 7 | frameRate: 10, 8 | loop: false 9 | }; 10 | -------------------------------------------------------------------------------- /static/projectiles/Thunderbolt/Follow/2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/lib/config/animations/towers/PoisonTower/Upgrade0.ts: -------------------------------------------------------------------------------- 1 | const frames = ['/towers/Base.svg', '/towers/Upgrade0.svg', '/towers/PoisonTower/Base0.svg']; 2 | 3 | export const PoisonTowerUpgrade0 = { 4 | name: 'PoisonTowerUpgrade0', 5 | frames, 6 | framesAmount: frames.length, 7 | frameRate: 10, 8 | loop: false 9 | }; 10 | -------------------------------------------------------------------------------- /static/projectiles/Thunderbolt/Follow/1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/lib/config/animations/towers/ThunderTower/Upgrade0.ts: -------------------------------------------------------------------------------- 1 | const frames = ['/towers/Base.svg', '/towers/Upgrade0.svg', '/towers/ThunderTower/Base0.svg']; 2 | 3 | export const ThunderTowerUpgrade0 = { 4 | name: 'ThunderTowerUpgrade0', 5 | frames, 6 | framesAmount: frames.length, 7 | frameRate: 10, 8 | loop: false 9 | }; 10 | -------------------------------------------------------------------------------- /src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://kit.svelte.dev/docs/types#app 2 | // for information about these interfaces 3 | declare global { 4 | namespace App { 5 | // interface Error {} 6 | // interface Locals {} 7 | // interface PageData {} 8 | // interface PageState {} 9 | // interface Platform {} 10 | } 11 | } 12 | 13 | export {}; 14 | -------------------------------------------------------------------------------- /src/routes/+layout.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/lib/store/States/Throne/Die.svelte.ts: -------------------------------------------------------------------------------- 1 | import { managers } from '$lib/store/managers.svelte'; 2 | import { BaseState } from '$lib/store/States/BaseState.svelte'; 3 | 4 | export class Die extends BaseState { 5 | constructor(stateMachine) { 6 | super(stateMachine); 7 | 8 | managers.get('stageManager').gameOver('lose'); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/lib/config/animations/projectiles/Click.ts: -------------------------------------------------------------------------------- 1 | const frames = [ 2 | '/projectiles/Click/0.svg', 3 | '/projectiles/Click/1.svg', 4 | '/projectiles/Click/2.svg', 5 | '/projectiles/Click/3.svg' 6 | ]; 7 | 8 | export const Click = { 9 | name: 'Click', 10 | frames, 11 | framesAmount: frames.length, 12 | frameRate: 10, 13 | loop: false 14 | }; 15 | -------------------------------------------------------------------------------- /src/lib/config/animations/projectiles/Icebolt/Die.ts: -------------------------------------------------------------------------------- 1 | const frames = [ 2 | '/projectiles/Icebolt/Die/0.svg', 3 | '/projectiles/Icebolt/Die/1.svg', 4 | '/projectiles/Icebolt/Die/2.svg' 5 | ]; 6 | 7 | export const IceboltDie = { 8 | name: 'IceboltDie', 9 | frames, 10 | framesAmount: frames.length, 11 | frameRate: 10, 12 | loop: false 13 | }; 14 | -------------------------------------------------------------------------------- /src/lib/config/animations/enemies/BlueCommon/Die.ts: -------------------------------------------------------------------------------- 1 | const frames = [ 2 | '/enemies/BlueCommon/Die/0.svg', 3 | '/enemies/BlueCommon/Die/1.svg', 4 | '/enemies/BlueCommon/Die/2.svg' 5 | ]; 6 | 7 | export const BlueCommonDie = { 8 | name: 'BlueCommonDie', 9 | frames, 10 | framesAmount: frames.length, 11 | frameRate: 10, 12 | loop: false 13 | }; 14 | -------------------------------------------------------------------------------- /src/lib/config/animations/towers/IceTower/Upgrade1.ts: -------------------------------------------------------------------------------- 1 | const frames = [ 2 | '/towers/IceTower/Base0.svg', 3 | '/towers/IceTower/Upgrade1.svg', 4 | '/towers/IceTower/Base1.svg' 5 | ]; 6 | 7 | export const IceTowerUpgrade1 = { 8 | name: 'IceTowerUpgrade1', 9 | frames, 10 | framesAmount: frames.length, 11 | frameRate: 10, 12 | loop: false 13 | }; 14 | -------------------------------------------------------------------------------- /src/lib/config/animations/towers/IceTower/Upgrade2.ts: -------------------------------------------------------------------------------- 1 | const frames = [ 2 | '/towers/IceTower/Base1.svg', 3 | '/towers/IceTower/Upgrade2.svg', 4 | '/towers/IceTower/Base2.svg' 5 | ]; 6 | 7 | export const IceTowerUpgrade2 = { 8 | name: 'IceTowerUpgrade2', 9 | frames, 10 | framesAmount: frames.length, 11 | frameRate: 10, 12 | loop: false 13 | }; 14 | -------------------------------------------------------------------------------- /src/lib/config/animations/projectiles/Fireball/Die.ts: -------------------------------------------------------------------------------- 1 | const frames = [ 2 | '/projectiles/Fireball/Die/0.svg', 3 | '/projectiles/Fireball/Die/1.svg', 4 | '/projectiles/Fireball/Die/2.svg' 5 | ]; 6 | 7 | export const FireballDie = { 8 | name: 'FireballDie', 9 | frames, 10 | framesAmount: frames.length, 11 | frameRate: 10, 12 | loop: false 13 | }; 14 | -------------------------------------------------------------------------------- /src/lib/config/animations/towers/FireTower/Upgrade1.ts: -------------------------------------------------------------------------------- 1 | const frames = [ 2 | '/towers/FireTower/Base0.svg', 3 | '/towers/FireTower/Upgrade1.svg', 4 | '/towers/FireTower/Base1.svg' 5 | ]; 6 | 7 | export const FireTowerUpgrade1 = { 8 | name: 'FireTowerUpgrade1', 9 | frames, 10 | framesAmount: frames.length, 11 | frameRate: 10, 12 | loop: false 13 | }; 14 | -------------------------------------------------------------------------------- /src/lib/config/animations/towers/FireTower/Upgrade2.ts: -------------------------------------------------------------------------------- 1 | const frames = [ 2 | '/towers/FireTower/Base1.svg', 3 | '/towers/FireTower/Upgrade2.svg', 4 | '/towers/FireTower/Base2.svg' 5 | ]; 6 | 7 | export const FireTowerUpgrade2 = { 8 | name: 'FireTowerUpgrade2', 9 | frames, 10 | framesAmount: frames.length, 11 | frameRate: 10, 12 | loop: false 13 | }; 14 | -------------------------------------------------------------------------------- /src/lib/components/Effect.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/lib/config/animations/enemies/PurpleCommon/Die.ts: -------------------------------------------------------------------------------- 1 | const frames = [ 2 | '/enemies/PurpleCommon/Die/0.svg', 3 | '/enemies/PurpleCommon/Die/1.svg', 4 | '/enemies/PurpleCommon/Die/2.svg' 5 | ]; 6 | 7 | export const PurpleCommonDie = { 8 | name: 'PurpleCommonDie', 9 | frames, 10 | framesAmount: frames.length, 11 | frameRate: 10, 12 | loop: false 13 | }; 14 | -------------------------------------------------------------------------------- /src/lib/config/animations/enemies/RedBlobElite/Die.ts: -------------------------------------------------------------------------------- 1 | const frames = [ 2 | '/enemies/RedBlobElite/Die/0.svg', 3 | '/enemies/RedBlobElite/Die/1.svg', 4 | '/enemies/RedBlobElite/Die/2.svg' 5 | ]; 6 | 7 | export const RedBlobEliteDie = { 8 | name: 'RedBlobEliteDie', 9 | frames, 10 | framesAmount: frames.length, 11 | frameRate: 10, 12 | loop: false 13 | }; 14 | -------------------------------------------------------------------------------- /src/lib/config/animations/enemies/YellowCommon/Die.ts: -------------------------------------------------------------------------------- 1 | const frames = [ 2 | '/enemies/YellowCommon/Die/0.svg', 3 | '/enemies/YellowCommon/Die/1.svg', 4 | '/enemies/YellowCommon/Die/2.svg' 5 | ]; 6 | 7 | export const YellowCommonDie = { 8 | name: 'YellowCommonDie', 9 | frames, 10 | framesAmount: frames.length, 11 | frameRate: 10, 12 | loop: false 13 | }; 14 | -------------------------------------------------------------------------------- /src/lib/config/animations/enemies/BlueBlobElite/Die.ts: -------------------------------------------------------------------------------- 1 | const frames = [ 2 | '/enemies/BlueBlobElite/Die/0.svg', 3 | '/enemies/BlueBlobElite/Die/1.svg', 4 | '/enemies/BlueBlobElite/Die/2.svg' 5 | ]; 6 | 7 | export const BlueBlobEliteDie = { 8 | name: 'BlueBlobEliteDie', 9 | frames, 10 | framesAmount: frames.length, 11 | frameRate: 10, 12 | loop: false 13 | }; 14 | -------------------------------------------------------------------------------- /src/lib/config/animations/enemies/BlueCommon/Follow.ts: -------------------------------------------------------------------------------- 1 | const frames = [ 2 | '/enemies/BlueCommon/Follow/0.svg', 3 | '/enemies/BlueCommon/Follow/1.svg', 4 | '/enemies/BlueCommon/Follow/2.svg' 5 | ]; 6 | 7 | export const BlueCommonFollow = { 8 | name: 'BlueCommonFollow', 9 | frames, 10 | framesAmount: frames.length, 11 | frameRate: 10, 12 | loop: true 13 | }; 14 | -------------------------------------------------------------------------------- /src/lib/config/animations/projectiles/Poisonball/Die.ts: -------------------------------------------------------------------------------- 1 | const frames = [ 2 | '/projectiles/Poisonball/Die/0.svg', 3 | '/projectiles/Poisonball/Die/1.svg', 4 | '/projectiles/Poisonball/Die/2.svg' 5 | ]; 6 | 7 | export const PoisonballDie = { 8 | name: 'PoisonballDie', 9 | frames, 10 | framesAmount: frames.length, 11 | frameRate: 10, 12 | loop: false 13 | }; 14 | -------------------------------------------------------------------------------- /src/lib/config/animations/projectiles/Thunderbolt/Die.ts: -------------------------------------------------------------------------------- 1 | const frames = [ 2 | '/projectiles/Thunderbolt/Die/0.svg', 3 | '/projectiles/Thunderbolt/Die/1.svg', 4 | '/projectiles/Thunderbolt/Die/2.svg' 5 | ]; 6 | 7 | export const ThunderboltDie = { 8 | name: 'ThunderboltDie', 9 | frames, 10 | framesAmount: frames.length, 11 | frameRate: 10, 12 | loop: false 13 | }; 14 | -------------------------------------------------------------------------------- /src/lib/config/animations/towers/PoisonTower/Upgrade1.ts: -------------------------------------------------------------------------------- 1 | const frames = [ 2 | '/towers/PoisonTower/Base0.svg', 3 | '/towers/PoisonTower/Upgrade1.svg', 4 | '/towers/PoisonTower/Base1.svg' 5 | ]; 6 | 7 | export const PoisonTowerUpgrade1 = { 8 | name: 'PoisonTowerUpgrade1', 9 | frames, 10 | framesAmount: frames.length, 11 | frameRate: 10, 12 | loop: false 13 | }; 14 | -------------------------------------------------------------------------------- /src/lib/config/animations/towers/PoisonTower/Upgrade2.ts: -------------------------------------------------------------------------------- 1 | const frames = [ 2 | '/towers/PoisonTower/Base1.svg', 3 | '/towers/PoisonTower/Upgrade2.svg', 4 | '/towers/PoisonTower/Base2.svg' 5 | ]; 6 | 7 | export const PoisonTowerUpgrade2 = { 8 | name: 'PoisonTowerUpgrade2', 9 | frames, 10 | framesAmount: frames.length, 11 | frameRate: 10, 12 | loop: false 13 | }; 14 | -------------------------------------------------------------------------------- /src/lib/config/animations/projectiles/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Fireball/Die'; 2 | export * from './Fireball/Follow'; 3 | export * from './Icebolt/Die'; 4 | export * from './Icebolt/Follow'; 5 | export * from './Poisonball/Die'; 6 | export * from './Poisonball/Follow'; 7 | export * from './Thunderbolt/Die'; 8 | export * from './Thunderbolt/Follow'; 9 | export * from './Click'; 10 | -------------------------------------------------------------------------------- /src/lib/config/animations/towers/ThunderTower/Upgrade1.ts: -------------------------------------------------------------------------------- 1 | const frames = [ 2 | '/towers/ThunderTower/Base0.svg', 3 | '/towers/ThunderTower/Upgrade1.svg', 4 | '/towers/ThunderTower/Base1.svg' 5 | ]; 6 | 7 | export const ThunderTowerUpgrade1 = { 8 | name: 'ThunderTowerUpgrade1', 9 | frames, 10 | framesAmount: frames.length, 11 | frameRate: 10, 12 | loop: false 13 | }; 14 | -------------------------------------------------------------------------------- /src/lib/config/animations/towers/ThunderTower/Upgrade2.ts: -------------------------------------------------------------------------------- 1 | const frames = [ 2 | '/towers/ThunderTower/Base1.svg', 3 | '/towers/ThunderTower/Upgrade2.svg', 4 | '/towers/ThunderTower/Base2.svg' 5 | ]; 6 | 7 | export const ThunderTowerUpgrade2 = { 8 | name: 'ThunderTowerUpgrade2', 9 | frames, 10 | framesAmount: frames.length, 11 | frameRate: 10, 12 | loop: false 13 | }; 14 | -------------------------------------------------------------------------------- /src/lib/store/States/BaseState.svelte.ts: -------------------------------------------------------------------------------- 1 | export class BaseState { 2 | stateMachine = $state(); 3 | 4 | constructor(stateMachine) { 5 | this.stateMachine = stateMachine; 6 | } 7 | 8 | update(deltaTime: number, entity) {} 9 | 10 | get name() { 11 | return this.constructor.name; 12 | } 13 | 14 | get entity() { 15 | return this.stateMachine.owner; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/lib/config/animations/enemies/BlueCircleElite/Die.ts: -------------------------------------------------------------------------------- 1 | const frames = [ 2 | '/enemies/BlueCircleElite/Die/0.svg', 3 | '/enemies/BlueCircleElite/Die/1.svg', 4 | '/enemies/BlueCircleElite/Die/2.svg' 5 | ]; 6 | 7 | export const BlueCircleEliteDie = { 8 | name: 'BlueCircleEliteDie', 9 | frames, 10 | framesAmount: frames.length, 11 | frameRate: 10, 12 | loop: false 13 | }; 14 | -------------------------------------------------------------------------------- /src/lib/config/animations/enemies/PurpleCommon/Follow.ts: -------------------------------------------------------------------------------- 1 | const frames = [ 2 | '/enemies/PurpleCommon/Follow/0.svg', 3 | '/enemies/PurpleCommon/Follow/1.svg', 4 | '/enemies/PurpleCommon/Follow/2.svg' 5 | ]; 6 | 7 | export const PurpleCommonFollow = { 8 | name: 'PurpleCommonFollow', 9 | frames, 10 | framesAmount: frames.length, 11 | frameRate: 10, 12 | loop: true 13 | }; 14 | -------------------------------------------------------------------------------- /src/lib/config/animations/enemies/GreenCircleElite/Die.ts: -------------------------------------------------------------------------------- 1 | const frames = [ 2 | '/enemies/GreenCircleElite/Die/0.svg', 3 | '/enemies/GreenCircleElite/Die/1.svg', 4 | '/enemies/GreenCircleElite/Die/2.svg' 5 | ]; 6 | 7 | export const GreenCircleEliteDie = { 8 | name: 'GreenCircleEliteDie', 9 | frames, 10 | framesAmount: frames.length, 11 | frameRate: 10, 12 | loop: false 13 | }; 14 | -------------------------------------------------------------------------------- /src/lib/config/animations/projectiles/Fireball/Follow.ts: -------------------------------------------------------------------------------- 1 | const frames = [ 2 | '/projectiles/Fireball/Follow/0.svg', 3 | '/projectiles/Fireball/Follow/1.svg', 4 | '/projectiles/Fireball/Follow/2.svg', 5 | '/projectiles/Fireball/Follow/3.svg' 6 | ]; 7 | 8 | export const FireballFollow = { 9 | name: 'FireballFollow', 10 | frames, 11 | framesAmount: frames.length, 12 | frameRate: 10, 13 | loop: true 14 | }; 15 | -------------------------------------------------------------------------------- /src/lib/config/animations/enemies/RedBlobElite/Follow.ts: -------------------------------------------------------------------------------- 1 | const frames = [ 2 | '/enemies/RedBlobElite/Follow/0.svg', 3 | '/enemies/RedBlobElite/Follow/1.svg', 4 | '/enemies/RedBlobElite/Follow/2.svg', 5 | '/enemies/RedBlobElite/Follow/3.svg' 6 | ]; 7 | 8 | export const RedBlobEliteFollow = { 9 | name: 'RedBlobEliteFollow', 10 | frames, 11 | framesAmount: frames.length, 12 | frameRate: 10, 13 | loop: true 14 | }; 15 | -------------------------------------------------------------------------------- /src/lib/config/animations/enemies/YellowCommon/Follow.ts: -------------------------------------------------------------------------------- 1 | const frames = [ 2 | '/enemies/YellowCommon/Follow/0.svg', 3 | '/enemies/YellowCommon/Follow/1.svg', 4 | '/enemies/YellowCommon/Follow/2.svg', 5 | '/enemies/YellowCommon/Follow/3.svg' 6 | ]; 7 | 8 | export const YellowCommonFollow = { 9 | name: 'YellowCommonFollow', 10 | frames, 11 | framesAmount: frames.length, 12 | frameRate: 10, 13 | loop: true 14 | }; 15 | -------------------------------------------------------------------------------- /src/lib/config/animations/enemies/BlueBlobElite/Follow.ts: -------------------------------------------------------------------------------- 1 | const frames = [ 2 | '/enemies/BlueBlobElite/Follow/0.svg', 3 | '/enemies/BlueBlobElite/Follow/1.svg', 4 | '/enemies/BlueBlobElite/Follow/2.svg', 5 | '/enemies/BlueBlobElite/Follow/3.svg' 6 | ]; 7 | 8 | export const BlueBlobEliteFollow = { 9 | name: 'BlueBlobEliteFollow', 10 | frames, 11 | framesAmount: frames.length, 12 | frameRate: 10, 13 | loop: true 14 | }; 15 | -------------------------------------------------------------------------------- /src/lib/config/animations/projectiles/Poisonball/Follow.ts: -------------------------------------------------------------------------------- 1 | const frames = [ 2 | '/projectiles/Poisonball/Follow/0.svg', 3 | '/projectiles/Poisonball/Follow/1.svg', 4 | '/projectiles/Poisonball/Follow/2.svg', 5 | '/projectiles/Poisonball/Follow/3.svg' 6 | ]; 7 | 8 | export const PoisonballFollow = { 9 | name: 'PoisonballFollow', 10 | frames, 11 | framesAmount: frames.length, 12 | frameRate: 10, 13 | loop: true 14 | }; 15 | -------------------------------------------------------------------------------- /src/routes/global.css: -------------------------------------------------------------------------------- 1 | body { 2 | -webkit-user-select: none; 3 | -ms-user-select: none; 4 | user-select: none; 5 | font-family: 'Astro Space', sans-serif; 6 | } 7 | 8 | @font-face { 9 | font-family: 'Astro Space'; 10 | src: 11 | url('/font/AstroSpace-0Wl3o.otf') format('opentype'), 12 | url('/font/AstroSpace-0Wl3o.ttf') format('truetype'); 13 | font-weight: 200; 14 | font-style: normal; 15 | font-display: swap; 16 | } 17 | -------------------------------------------------------------------------------- /src/lib/config/animations/projectiles/Thunderbolt/Follow.ts: -------------------------------------------------------------------------------- 1 | const frames = [ 2 | '/projectiles/Thunderbolt/Follow/0.svg', 3 | '/projectiles/Thunderbolt/Follow/1.svg', 4 | '/projectiles/Thunderbolt/Follow/2.svg', 5 | '/projectiles/Thunderbolt/Follow/3.svg' 6 | ]; 7 | 8 | export const ThunderboltFollow = { 9 | name: 'ThunderboltFollow', 10 | frames, 11 | framesAmount: frames.length, 12 | frameRate: 10, 13 | loop: true 14 | }; 15 | -------------------------------------------------------------------------------- /static/sound/sfx/index.ts: -------------------------------------------------------------------------------- 1 | import pickUp from './pickUp.m4a'; 2 | import towerUpgrade from './towerUpgrade.m4a'; 3 | import clickEnemy from './clickEnemy.m4a'; 4 | import towerShoot from './towerShoot.m4a'; 5 | import clickMenu from './clickMenu.m4a'; 6 | import lowResourse from './lowResourse.m4a'; 7 | 8 | export const sfx = { 9 | pickUp, 10 | towerUpgrade, 11 | clickEnemy, 12 | towerShoot, 13 | clickMenu, 14 | lowResourse 15 | }; 16 | -------------------------------------------------------------------------------- /src/lib/config/animations/enemies/BlueCircleElite/Follow.ts: -------------------------------------------------------------------------------- 1 | const frames = [ 2 | '/enemies/BlueCircleElite/Follow/0.svg', 3 | '/enemies/BlueCircleElite/Follow/1.svg', 4 | '/enemies/BlueCircleElite/Follow/2.svg', 5 | '/enemies/BlueCircleElite/Follow/3.svg' 6 | ]; 7 | 8 | export const BlueCircleEliteFollow = { 9 | name: 'BlueCircleEliteFollow', 10 | frames, 11 | framesAmount: frames.length, 12 | frameRate: 10, 13 | loop: true 14 | }; 15 | -------------------------------------------------------------------------------- /static/projectiles/Fireball/Follow/2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 10 |
%sveltekit.body%
11 | 12 | 13 | -------------------------------------------------------------------------------- /src/lib/config/animations/enemies/GreenCircleElite/Follow.ts: -------------------------------------------------------------------------------- 1 | const frames = [ 2 | '/enemies/GreenCircleElite/Follow/0.svg', 3 | '/enemies/GreenCircleElite/Follow/1.svg', 4 | '/enemies/GreenCircleElite/Follow/2.svg', 5 | '/enemies/GreenCircleElite/Follow/3.svg' 6 | ]; 7 | 8 | export const GreenCircleEliteFollow = { 9 | name: 'GreenCircleEliteFollow', 10 | frames, 11 | framesAmount: frames.length, 12 | frameRate: 10, 13 | loop: true 14 | }; 15 | -------------------------------------------------------------------------------- /static/cursors/build0.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /static/cursors/cursor-hammer.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /static/cursors/cursor-arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/lib/components/PauseIcon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/lib/store/States/Tower/NotBuilt.svelte.ts: -------------------------------------------------------------------------------- 1 | import { BaseState } from '$lib/store/States/BaseState.svelte'; 2 | import { screen } from '$lib/store/Screen.svelte'; 3 | 4 | export class NotBuilt extends BaseState { 5 | isSetup = false; 6 | 7 | update(deltaTime: number, entity: any): void { 8 | if (!this.isSetup) { 9 | if (screen.isMobile) { 10 | this.entity.position.y += 30; 11 | } else { 12 | this.entity.position.y += 35; 13 | } 14 | 15 | this.isSetup = true; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /static/projectiles/Fireball/Follow/0.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/lib/store/entityFabric.ts: -------------------------------------------------------------------------------- 1 | import { getConfig } from '$lib/config/'; 2 | import { Entity } from './Entity.svelte'; 3 | 4 | export const initEntity = (name, position, stateContext = {}) => { 5 | // State from config 6 | const { context = {}, ...config } = getConfig(name); 7 | // State Context overrides 8 | const { initialState, ...restContext } = stateContext; 9 | 10 | if (initialState) { 11 | config.initialState = initialState; 12 | } 13 | 14 | const entity = new Entity(name, position.clone(), config, { ...context, ...restContext }); 15 | return entity; 16 | }; 17 | -------------------------------------------------------------------------------- /static/cursors/build3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /static/cursors/build4.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /static/cursors/build1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /static/projectiles/Fireball/Follow/3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /static/cursors/build2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /static/projectiles/Fireball/Follow/1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/lib/store/States/Enemy/Die.svelte.ts: -------------------------------------------------------------------------------- 1 | import { BaseState } from '$lib/store/States/BaseState.svelte'; 2 | import { managers } from '$lib/store/managers.svelte'; 3 | 4 | export class Die extends BaseState { 5 | constructor(stateMachine) { 6 | super(stateMachine); 7 | 8 | stateMachine.owner.stopInteractions(); 9 | this.entity.removeCollider(); 10 | } 11 | 12 | update() { 13 | if (this.entity.animation.isComplete) { 14 | const { entityManager, stageManager } = managers.get(['entityManager', 'stageManager']); 15 | entityManager.destroy(this.entity.id); 16 | stageManager.spawnLoot(this.entity); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/lib/config/animations/index.ts: -------------------------------------------------------------------------------- 1 | import * as Enemies from './enemies'; 2 | import * as Towers from './towers'; 3 | import * as Projectiles from './projectiles'; 4 | import * as Cursors from './cursors'; 5 | import { Throne } from './Throne'; 6 | import { Loot } from './Loot'; 7 | 8 | export const animations = { 9 | Throne, 10 | Loot, 11 | ...Enemies, 12 | ...Towers, 13 | ...Projectiles, 14 | ...Cursors 15 | }; 16 | 17 | export const getAnimation = (name: string) => { 18 | if (!animations[name]) throw new Error(`Animation config not found for: ${name}`); 19 | const animation = animations[name]; 20 | 21 | return animation; 22 | }; 23 | -------------------------------------------------------------------------------- /src/lib/config/animations/enemies/index.ts: -------------------------------------------------------------------------------- 1 | export * from './BlueBlobElite/Die'; 2 | export * from './BlueBlobElite/Follow'; 3 | export * from './BlueCircleElite/Die'; 4 | export * from './BlueCircleElite/Follow'; 5 | export * from './GreenCircleElite/Die'; 6 | export * from './GreenCircleElite/Follow'; 7 | export * from './RedBlobElite/Die'; 8 | export * from './RedBlobElite/Follow'; 9 | 10 | // commons 11 | export * from './BlueCommon/Die'; 12 | export * from './BlueCommon/Follow'; 13 | export * from './PurpleCommon/Die'; 14 | export * from './PurpleCommon/Follow'; 15 | export * from './YellowCommon/Die'; 16 | export * from './YellowCommon/Follow'; 17 | -------------------------------------------------------------------------------- /src/lib/store/States/Projectiles/FollowAngle.svelte.ts: -------------------------------------------------------------------------------- 1 | import { BaseState } from '$lib/store/States/BaseState.svelte'; 2 | import { getDirectionFromAngle } from '$lib/utils/math'; 3 | 4 | export class FollowAngle extends BaseState { 5 | update(deltaTime: number) { 6 | this.follow(deltaTime); 7 | } 8 | 9 | follow(deltaTime: number) { 10 | const { angle } = this.stateMachine.context; 11 | const direction = getDirectionFromAngle(angle); 12 | 13 | const velocity = direction.multiply(this.entity.stats.speed * deltaTime); 14 | this.entity.position = this.entity.position.add(velocity); 15 | this.entity.rotation = (angle * 180) / Math.PI + 90; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/lib/config/entities/index.ts: -------------------------------------------------------------------------------- 1 | import { enemies } from './enemies'; 2 | import { towers } from './towers'; 3 | import { projectiles } from './projectiles'; 4 | 5 | export const entities = { ...enemies, ...towers, ...projectiles }; 6 | 7 | const entitiesDefined = 8 | Object.keys(enemies).length + Object.keys(towers).length + Object.keys(projectiles).length; 9 | 10 | const entitiesExported = Object.keys(entities).length; 11 | 12 | if (entitiesDefined !== entitiesExported) { 13 | throw new Error( 14 | `Entities defined and exported do not match. Check for duplicated Entity names : Total count: ${entitiesDefined} / Exported count ${entitiesExported}` 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/lib/store/managers.svelte.ts: -------------------------------------------------------------------------------- 1 | export class Managers { 2 | managers = $state({}); 3 | 4 | setup = (managers: any) => { 5 | this.managers = managers; 6 | }; 7 | 8 | update = (deltaTime: number) => { 9 | this.managers.stageManager.update(deltaTime); 10 | this.managers.entityManager.update(deltaTime); 11 | this.managers.collisionManager.update(); 12 | }; 13 | 14 | get = (name: string | string[]) => { 15 | if (typeof name === 'string') { 16 | return this.managers[name]; 17 | } 18 | return name.reduce((acc, n) => { 19 | acc[n] = this.managers[n]; 20 | return acc; 21 | }, {}); 22 | }; 23 | } 24 | 25 | export const managers = new Managers(); 26 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true, 12 | "moduleResolution": "bundler" 13 | } 14 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias 15 | // except $lib which is handled by https://kit.svelte.dev/docs/configuration#files 16 | // 17 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes 18 | // from the referenced tsconfig.json - TypeScript does not merge them in 19 | } 20 | -------------------------------------------------------------------------------- /src/lib/components/Gui/MenuLayout.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 16 | 17 | 30 | -------------------------------------------------------------------------------- /src/lib/components/BackgroundContainer.svelte: -------------------------------------------------------------------------------- 1 | 15 | 16 | {#key stageNumber} 17 |
18 | 19 |
20 | {/key} 21 | 22 | 29 | -------------------------------------------------------------------------------- /static/towers/Base.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import eslint from '@eslint/js'; 2 | import prettier from 'eslint-config-prettier'; 3 | import svelte from 'eslint-plugin-svelte'; 4 | import globals from 'globals'; 5 | import tseslint from 'typescript-eslint'; 6 | 7 | export default tseslint.config( 8 | eslint.configs.recommended, 9 | ...tseslint.configs.recommended, 10 | ...svelte.configs['flat/recommended'], 11 | prettier, 12 | ...svelte.configs['flat/prettier'], 13 | { 14 | languageOptions: { 15 | globals: { 16 | ...globals.browser, 17 | ...globals.node 18 | } 19 | } 20 | }, 21 | { 22 | files: ['**/*.svelte'], 23 | languageOptions: { 24 | parserOptions: { 25 | parser: tseslint.parser 26 | } 27 | } 28 | }, 29 | { 30 | ignores: ['build/', '.svelte-kit/', 'dist/'] 31 | } 32 | ); 33 | -------------------------------------------------------------------------------- /static/projectiles/Icebolt/Die/2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /static/projectiles/Poisonball/Die/2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /static/projectiles/Thunderbolt/Die/2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/lib/store/States/Projectiles/Explode.svelte.ts: -------------------------------------------------------------------------------- 1 | import { BaseState } from '$lib/store/States/BaseState.svelte'; 2 | import { managers } from '$lib/store/managers.svelte'; 3 | 4 | export class Explode extends BaseState { 5 | constructor(stateMachine) { 6 | super(stateMachine); 7 | 8 | this.entity.removeCollider(); 9 | this.entity.width = 30; 10 | this.entity.height = 30; 11 | this.entity.animation.onFrameChange = (frame) => { 12 | this.entity.scale += 0.5; 13 | }; 14 | 15 | stateMachine.owner.stopInteractions(); 16 | } 17 | 18 | update() { 19 | if (!this.entity.animation || (this.entity.animation && this.entity.animation.isComplete)) { 20 | const entityManager = managers.get('entityManager'); 21 | entityManager.destroy(this.entity.id); 22 | 23 | this.entity.animation.onFrameChange = () => {}; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/lib/store/States/index.ts: -------------------------------------------------------------------------------- 1 | import * as EnemyStates from './Enemy/'; 2 | import * as TowerStates from './Tower/'; 3 | import * as ProjectileStates from './Projectiles/'; 4 | import * as ThroneStates from './Throne/'; 5 | 6 | export const initState = (stateMachine, entityType, name, stateContext = {}) => { 7 | let States = {}; 8 | 9 | if (entityType === 'enemy') { 10 | States = EnemyStates; 11 | } else if (entityType === 'tower') { 12 | States = TowerStates; 13 | } else if (['projectile', 'loot'].includes(entityType)) { 14 | States = ProjectileStates; 15 | } else if (entityType === 'throne') { 16 | States = ThroneStates; 17 | } 18 | 19 | const State = States[name]; 20 | 21 | if (!State) { 22 | throw new Error(`Invalid entity type: ${entityType} with name ${name}`); 23 | } 24 | 25 | return new State(stateMachine, stateContext); 26 | }; 27 | -------------------------------------------------------------------------------- /src/lib/store/StateMachine.svelte.ts: -------------------------------------------------------------------------------- 1 | import { initState } from '$lib/store/States'; 2 | 3 | export class StateMachine { 4 | currentState = $state(); 5 | context = $state(); 6 | 7 | constructor({ owner, initialState, onEnter, context = {} }) { 8 | this.owner = owner; 9 | this.onEnter = onEnter; 10 | this.context = context; 11 | 12 | this.onEnter(initialState); 13 | this.currentState = initState(this, owner.type, initialState, context); 14 | } 15 | 16 | update(deltaTime, entityManager) { 17 | this.currentState.update(deltaTime, this.owner, entityManager); 18 | } 19 | 20 | setState(name, stateContext = {}) { 21 | if (name === this.currentState.name) return; 22 | 23 | this.onEnter(name); 24 | this.context = { ...this.context, ...stateContext }; 25 | this.currentState = initState(this, this.owner.type, name, stateContext); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/lib/store/States/Tower/Guard.svelte.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * TowerStates.ts 3 | * State classes for tower behavior 4 | */ 5 | 6 | import { BaseState } from '$lib/store/States/BaseState.svelte'; 7 | import { managers } from '$lib/store/managers.svelte'; 8 | export class Guard extends BaseState { 9 | constructor(stateMachine) { 10 | super(stateMachine); 11 | 12 | const gameLoop = managers.get('gameLoop'); 13 | 14 | const atackSpeed = this.entity.stats.attackSpeed; 15 | 16 | this.cdId = gameLoop.setCD(atackSpeed, true); 17 | } 18 | 19 | update() { 20 | const { entityManager, gameLoop } = managers.get(['entityManager', 'gameLoop']); 21 | 22 | const target = entityManager.findNearestEntity(this.entity, entityManager.enemies); 23 | 24 | if (gameLoop.isCDReady(this.cdId) && target) { 25 | this.stateMachine.setState('Shoot', { spawner: this.entity, target }); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/lib/components/GitHubIcon.svelte: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Svelte Space 2 | 3 | A TowerDefence clicker game made with Svelte 5, without canvas. Playable on different screens, Mobile or Desktop. 4 | [Live demo link](https://svelte-tower-defence.vercel.app/) 5 | 6 | ![svelte-tower-defence vercel app-10January2025-ezgif com-video-to-gif-converter](https://github.com/user-attachments/assets/3083899b-50a0-482d-9878-f2bb880ccb9a) 7 | 8 | 9 | Game was made during [Svelte Hack 2024](https://hack.sveltesociety.dev/2024) and got 4th place in the "Wizzbangery Wizard" category. 10 | 11 | [SvleteHack 2024 winners](https://hack.sveltesociety.dev/2024/winners) 12 | 13 | Project team: 14 | 15 | - [Ivan Semochkin (Developer)](https://github.com/baterson) 16 | - [Fedor Semochkin (Developer)](https://github.com/Fedorse) 17 | - [Albert Fedyunin (Art/Design)](https://artstation.com/kunglaohat) 18 | 19 | ```bash 20 | # Run locally 21 | npm install 22 | npm run dev 23 | ``` 24 | -------------------------------------------------------------------------------- /static/projectiles/Poisonball/Die/0.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /static/projectiles/Poisonball/Follow/3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-vercel'; 2 | // import path from 'path'; 3 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; 4 | 5 | /** @type {import('@sveltejs/kit').Config} */ 6 | const config = { 7 | // Consult https://kit.svelte.dev/docs/integrations#preprocessors 8 | // for more information about preprocessors 9 | preprocess: vitePreprocess(), 10 | 11 | kit: { 12 | // dev: true, 13 | // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. 14 | // If your environment is not supported, or you settled on a specific environment, switch out the adapter. 15 | // See https://kit.svelte.dev/docs/adapters for more information about adapters. 16 | adapter: adapter({ 17 | runtime: 'nodejs20.x' 18 | }) 19 | }, 20 | onwarn: (warning, handler) => { 21 | if (warning.code.startsWith('a11y-')) { 22 | return; 23 | } 24 | handler(warning); 25 | } 26 | }; 27 | 28 | export default config; 29 | -------------------------------------------------------------------------------- /src/lib/store/Screen.svelte.ts: -------------------------------------------------------------------------------- 1 | import type { Entity } from './Entity.svelte'; 2 | 3 | export class Screen { 4 | width = $state(0); 5 | height = $state(0); 6 | gameAreaWidth = $state(0); 7 | gameAreaHeight = $state(0); 8 | gameXOffset = $state(0); 9 | gameYOffset = $state(0); 10 | isMobile = $derived(this.width < 768); 11 | 12 | get screenBounds() { 13 | return { 14 | x1: 0, 15 | y1: 0, 16 | x2: this.width, 17 | y2: this.height 18 | }; 19 | } 20 | 21 | get gameBoundingBox() { 22 | return { 23 | x1: this.gameXOffset, 24 | y1: this.gameYOffset, 25 | x2: this.width, 26 | y2: this.height 27 | }; 28 | } 29 | 30 | isEntityInScreen(entity: Entity): boolean { 31 | const { x1, y1, x2, y2 } = this.screenBounds; 32 | const { x, y } = entity.position; 33 | 34 | return ( 35 | x + entity.width / 2 > x1 && 36 | x - entity.width / 2 < x2 && 37 | y + entity.height > y1 && 38 | y - entity.height < y2 39 | ); 40 | } 41 | } 42 | 43 | export const screen = new Screen(); 44 | -------------------------------------------------------------------------------- /src/lib/config/animations/towers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './TowerBase'; 2 | export * from './FireTower/Base0'; 3 | export * from './FireTower/Base1'; 4 | export * from './FireTower/Base2'; 5 | export * from './FireTower/Upgrade0'; 6 | export * from './FireTower/Upgrade1'; 7 | export * from './FireTower/Upgrade2'; 8 | export * from './IceTower/Base0'; 9 | export * from './IceTower/Base1'; 10 | export * from './IceTower/Base2'; 11 | export * from './IceTower/Upgrade0'; 12 | export * from './IceTower/Upgrade1'; 13 | export * from './IceTower/Upgrade2'; 14 | export * from './PoisonTower/Base0'; 15 | export * from './PoisonTower/Base1'; 16 | export * from './PoisonTower/Base2'; 17 | export * from './PoisonTower/Upgrade0'; 18 | export * from './PoisonTower/Upgrade1'; 19 | export * from './PoisonTower/Upgrade2'; 20 | export * from './ThunderTower/Base0'; 21 | export * from './ThunderTower/Base1'; 22 | export * from './ThunderTower/Base2'; 23 | export * from './ThunderTower/Upgrade0'; 24 | export * from './ThunderTower/Upgrade1'; 25 | export * from './ThunderTower/Upgrade2'; 26 | -------------------------------------------------------------------------------- /src/lib/config/upgrades.ts: -------------------------------------------------------------------------------- 1 | export const FireTower = [ 2 | (tower) => {}, 3 | (tower) => { 4 | tower.stats.attackSpeed -= 50; 5 | tower.stats.projectileNumber += 1; 6 | }, 7 | (tower) => { 8 | tower.stats.attackSpeed -= 50; 9 | tower.stats.projectileNumber += 1; 10 | } 11 | ]; 12 | 13 | export const PoisonTower = [ 14 | (tower) => {}, 15 | (tower) => { 16 | tower.stats.attackSpeed -= 50; 17 | tower.stats.projectileNumber += 1; 18 | }, 19 | (tower) => { 20 | tower.stats.attackSpeed -= 50; 21 | tower.stats.projectileNumber += 1; 22 | } 23 | ]; 24 | 25 | export const ThunderTower = [ 26 | (tower) => {}, 27 | (tower) => { 28 | tower.stats.attackSpeed -= 50; 29 | tower.stats.projectileNumber += 1; 30 | }, 31 | (tower) => { 32 | tower.stats.attackSpeed -= 50; 33 | tower.stats.projectileNumber += 1; 34 | } 35 | ]; 36 | 37 | export const IceTower = [ 38 | (tower) => {}, 39 | (tower) => { 40 | tower.stats.attackSpeed -= 50; 41 | tower.stats.projectileNumber += 1; 42 | }, 43 | (tower) => { 44 | tower.stats.attackSpeed -= 30; 45 | tower.stats.projectileNumber += 1; 46 | } 47 | ]; 48 | -------------------------------------------------------------------------------- /static/projectiles/Icebolt/Follow/1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Ivan Semochkin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /static/enemies/PurpleCommon/Die/0.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /static/projectiles/Icebolt/Follow/0.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svelte-tower-defence", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite dev", 7 | "build": "vite build", 8 | "preview": "vite preview", 9 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 10 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", 11 | "lint": "prettier --check . && eslint .", 12 | "format": "prettier --write ." 13 | }, 14 | "devDependencies": { 15 | "@sveltejs/adapter-auto": "^3.0.0", 16 | "@sveltejs/kit": "^2.0.0", 17 | "@sveltejs/vite-plugin-svelte": "^4.0.0-next.6", 18 | "@types/eslint": "^9.6.0", 19 | "eslint": "^9.0.0", 20 | "eslint-config-prettier": "^9.1.0", 21 | "eslint-plugin-svelte": "^2.36.0", 22 | "globals": "^15.0.0", 23 | "prettier": "^3.1.1", 24 | "prettier-plugin-svelte": "^3.1.2", 25 | "svelte": "^5.0.0-next.1", 26 | "svelte-check": "^4.0.0", 27 | "typescript": "^5.0.0", 28 | "typescript-eslint": "^8.0.0", 29 | "vite": "^5.0.3" 30 | }, 31 | "type": "module", 32 | "dependencies": { 33 | "@sveltejs/adapter-vercel": "^5.5.2", 34 | "@vercel/analytics": "^1.5.0", 35 | "@vercel/speed-insights": "^1.2.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /static/projectiles/Icebolt/Die/1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /static/projectiles/Poisonball/Die/1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /static/projectiles/Thunderbolt/Die/1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/lib/components/Loot.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |
{ 13 | lootTracker.unsetAnimation('LowLoot'); 14 | }} 15 | > 16 | 17 | {lootTracker.collectedLoot} 18 |
19 | 20 | 58 | -------------------------------------------------------------------------------- /src/lib/config/collisionHandlers.ts: -------------------------------------------------------------------------------- 1 | import type { Entity } from '$lib/store/Entity.svelte'; 2 | import { lootTracker } from '$lib/store/LootTracker.svelte'; 3 | import { managers } from '$lib/store/managers.svelte'; 4 | import { soundManager } from '$lib/store/SoundManager.svelte'; 5 | 6 | export const enemyCollider = (entity: Entity, other: Entity) => { 7 | entity.takeDamage(other.stats.damage); 8 | }; 9 | 10 | export const fireballCollider = (fireball, other) => { 11 | fireball.state.setState('Explode'); 12 | }; 13 | 14 | export const projectileCollider = (projectile, other) => { 15 | if (other === 'OUT_OF_BOUNDS') { 16 | projectile.stopInteractions(); 17 | managers.get('entityManager').destroy(entity.id); 18 | } else { 19 | projectile.state.setState('Explode'); 20 | } 21 | 22 | return; 23 | }; 24 | 25 | export const throneCollider = (entity, other) => { 26 | if (other.type === 'loot') { 27 | lootTracker.receiveLoot(1); 28 | soundManager.play('pickUp'); 29 | } else if (other.type === 'enemy') { 30 | entity.takeDamage(other.stats.damage); 31 | } 32 | 33 | return; 34 | }; 35 | 36 | export const lootCollider = (entity, other) => { 37 | entity.stopInteractions(); 38 | managers.get('entityManager').destroy(entity.id); 39 | }; 40 | -------------------------------------------------------------------------------- /static/projectiles/Fireball/Die/0.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /static/projectiles/Icebolt/Die/0.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /static/projectiles/Thunderbolt/Die/0.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /static/projectiles/Thunderbolt/Follow/3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/lib/components/Gui/Button.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 | 18 | 19 | 53 | -------------------------------------------------------------------------------- /src/lib/store/States/Tower/Shoot.svelte.ts: -------------------------------------------------------------------------------- 1 | import { BaseState } from '$lib/store/States/BaseState.svelte'; 2 | import { managers } from '$lib/store/managers.svelte'; 3 | import { Vector2 } from '$lib/store/Vector2.svelte'; 4 | import { boundingBoxFromPoint } from '$lib/utils/math'; 5 | import { soundManager } from '$lib/store/SoundManager.svelte'; 6 | 7 | export class Shoot extends BaseState { 8 | update() { 9 | this.shoot(); 10 | this.entity.addVFX('TowerShoot'); 11 | this.entity.state.setState('Guard'); 12 | } 13 | 14 | shoot() { 15 | const stageManager = managers.get('stageManager'); 16 | const { spawner } = this.stateMachine.context; 17 | const projectileType = spawner.stats.projectileType; 18 | const projectileNumber = this.entity.stats.projectileNumber; 19 | // use upgradeLvl 20 | 21 | const towerShootPoint = spawner.position.add(new Vector2(spawner.width / 2, 0)); 22 | 23 | const entityManager = managers.get('entityManager'); 24 | const targets = entityManager.findNearestEntities( 25 | spawner, 26 | entityManager.enemies, 27 | projectileNumber 28 | ); 29 | targets.forEach((target) => { 30 | stageManager.spawnEntity(projectileType, towerShootPoint, { 31 | spawner, 32 | target 33 | }); 34 | }); 35 | 36 | soundManager.play('towerShoot'); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/lib/store/Cursor.svelte.ts: -------------------------------------------------------------------------------- 1 | import { getAnimation } from '$lib/config/animations'; 2 | import { Animation } from '$lib/store/Animation.svelte'; 3 | 4 | export const cursors = { 5 | arrow: '/cursors/cursor-arrow.svg', 6 | hammer: '/cursors/cursor-hammer.svg' 7 | }; 8 | 9 | export class Cursor { 10 | animation = $state(); 11 | 12 | arrow = $derived(`url(${cursors.arrow}), auto`); 13 | hammer = $derived(`url(${cursors.hammer}), auto`); 14 | 15 | inAnimation = $derived(this.animation && !this.animation.isComplete); 16 | currentFrame = $derived(this.getCurrentFrame()); 17 | 18 | update(deltaTime: number) { 19 | if (this.inAnimation) { 20 | this.animation.update(deltaTime); 21 | } 22 | } 23 | 24 | get(name) { 25 | if (this.inAnimation) { 26 | return this.currentFrame; 27 | } else { 28 | return this[name]; 29 | } 30 | } 31 | 32 | setAnimation(animation: string) { 33 | if (!animation) { 34 | this.animation = null; 35 | return; 36 | } 37 | 38 | const animationConfig = getAnimation(animation); 39 | 40 | this.animation = new Animation({ ...animationConfig }); 41 | } 42 | 43 | getCurrentFrame() { 44 | const { frames } = getAnimation(this.animation.name); 45 | return `url(${frames[this.animation.currentFrame]}), auto`; 46 | } 47 | } 48 | 49 | export const cursor = new Cursor(); 50 | -------------------------------------------------------------------------------- /src/lib/config/stages.ts: -------------------------------------------------------------------------------- 1 | export const stages = [ 2 | { 3 | commonEnemies: ['PurpleCommon', 'YellowCommon'], 4 | time: 60000, 5 | 6 | eliteEnemies: ['RedBlobElite'], 7 | spawnDelays: { 8 | common: 900, 9 | elite: 1400 10 | }, 11 | statsAmplify: { 12 | health: 1, 13 | speed: 1, 14 | damage: 1 15 | } 16 | }, 17 | { 18 | commonEnemies: ['YellowCommon', 'BlueCommon'], 19 | eliteEnemies: ['GreenCircleElite', 'BlueCircleElite'], 20 | time: 120000, 21 | 22 | spawnDelays: { 23 | common: 800, 24 | elite: 1300 25 | }, 26 | statsAmplify: { 27 | health: 1.2, 28 | speed: 1.2, 29 | damage: 1.2 30 | } 31 | }, 32 | { 33 | commonEnemies: ['PurpleCommon', 'YellowCommon', 'BlueCommon'], 34 | eliteEnemies: ['BlueBlobElite', 'GreenCircleElite', 'BlueCircleElite'], 35 | time: 120000, 36 | 37 | spawnDelays: { 38 | common: 600, 39 | elite: 1100 40 | }, 41 | statsAmplify: { 42 | health: 1.7, 43 | speed: 1.4, 44 | damage: 1.5 45 | } 46 | }, 47 | { 48 | commonEnemies: ['PurpleCommon', 'YellowCommon', 'BlueCommon'], 49 | eliteEnemies: ['BlueCircleElite', 'GreenCircleElite', 'BlueBlobElite', 'RedBlobElite'], 50 | time: 120000, 51 | 52 | spawnDelays: { 53 | common: 400, 54 | elite: 800 55 | }, 56 | statsAmplify: { 57 | health: 2.2, 58 | speed: 1.6, 59 | damage: 1.8 60 | } 61 | } 62 | ]; 63 | -------------------------------------------------------------------------------- /src/lib/store/Vector2.svelte.ts: -------------------------------------------------------------------------------- 1 | export class Vector2 { 2 | x = $state(0); 3 | y = $state(0); 4 | 5 | constructor(x = 0, y = 0) { 6 | this.x = x; 7 | this.y = y; 8 | } 9 | 10 | clone(): Vector2 { 11 | return new Vector2(this.x, this.y); 12 | } 13 | 14 | isEqual(vector: Vector2): boolean { 15 | return this.x === vector.x && this.y === vector.y; 16 | } 17 | 18 | add(vector: Vector2): Vector2 { 19 | return new Vector2(this.x + vector.x, this.y + vector.y); 20 | } 21 | 22 | subtract(vector: Vector2): Vector2 { 23 | return new Vector2(this.x - vector.x, this.y - vector.y); 24 | } 25 | 26 | multiply(scalar: number): Vector2 { 27 | return new Vector2(this.x * scalar, this.y * scalar); 28 | } 29 | 30 | magnitude(): number { 31 | return Math.sqrt(this.x * this.x + this.y * this.y); 32 | } 33 | 34 | normalize(): Vector2 { 35 | const mag = this.magnitude(); 36 | return mag ? new Vector2(this.x / mag, this.y / mag) : new Vector2(); 37 | } 38 | 39 | distance(vector: Vector2): number { 40 | return this.subtract(vector).magnitude(); 41 | } 42 | 43 | dot(vector: Vector2): number { 44 | return this.x * vector.x + this.y * vector.y; 45 | } 46 | 47 | rotate(angle: number): Vector2 { 48 | const cos = Math.cos(angle); 49 | const sin = Math.sin(angle); 50 | return new Vector2(this.x * cos - this.y * sin, this.x * sin + this.y * cos); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/lib/store/Animation.svelte.ts: -------------------------------------------------------------------------------- 1 | interface AnimationConfig { 2 | name: string; 3 | framesAmount: number; 4 | frameRate: number; 5 | loop?: boolean; 6 | } 7 | 8 | export class Animation { 9 | name: string; 10 | framesAmount = $state(0); 11 | frameRate = $state(0); 12 | loop = $state(false); 13 | currentFrame = $state(0); 14 | currentFrameTime = $state(0); 15 | isComplete = $derived(this.getIsAnimationComplete()); 16 | onFrameChange = $state(() => {}); 17 | 18 | constructor({ name, framesAmount, frameRate, loop = false }: AnimationConfig) { 19 | this.name = name; 20 | this.framesAmount = framesAmount; 21 | this.frameRate = frameRate; 22 | this.loop = loop; 23 | } 24 | 25 | update(deltaTime: number) { 26 | if (this.frameRate === 0) return; 27 | 28 | this.currentFrameTime += deltaTime; 29 | 30 | if (this.currentFrameTime >= 1000 / this.frameRate) { 31 | this.currentFrameTime = 0; 32 | this.nextFrame(); 33 | } 34 | } 35 | 36 | nextFrame() { 37 | this.onFrameChange(this.currentFrame); 38 | 39 | if (this.currentFrame >= this.framesAmount - 1) { 40 | if (this.loop) { 41 | this.currentFrame = 0; 42 | } else { 43 | this.currentFrame = this.framesAmount - 1; 44 | } 45 | } else { 46 | this.currentFrame += 1; 47 | } 48 | } 49 | 50 | getIsAnimationComplete(): boolean { 51 | if (this.loop) { 52 | return false; 53 | } 54 | return this.currentFrame === this.framesAmount - 1; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /static/projectiles/Fireball/Die/2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/lib/store/States/Enemy/FollowThrone.svelte.ts: -------------------------------------------------------------------------------- 1 | import { BaseState } from '$lib/store/States/BaseState.svelte'; 2 | import { Vector2 } from '$lib/store/Vector2.svelte'; 3 | import { angleToTarget, getDirectionFromAngle } from '$lib/utils/math'; 4 | 5 | export class FollowThrone extends BaseState { 6 | lastAngle = 0; 7 | 8 | update(deltaTime: number) { 9 | this.follow(deltaTime); 10 | } 11 | 12 | follow(deltaTime: number) { 13 | const { throne } = this.stateMachine.context; 14 | if (!throne.width) { 15 | return; 16 | } 17 | 18 | // const targetCenter = throne.boundingBox.center.clone(); 19 | const targetCenter = throne.position; 20 | // // const targetCenter = throne.position.clone(); 21 | // const targetCenterX = new Vector2(throne.position.x + throne.width / 2, throne.position.y); 22 | 23 | // const p = this.entity.position; 24 | // const t = new Vector2( 25 | // throne.position.x + throne.width / 2, 26 | // throne.position.y + throne.height / 2 27 | // ); 28 | 29 | // console.log('this.entity.position, targetCenter', this.entity.position.x, targetCenter.x); 30 | 31 | this.entity.rotation = angleToTarget(this.entity.position, targetCenter); 32 | // console.log('this.entity.rotation', this.entity.rotation); 33 | 34 | const direction = getDirectionFromAngle(this.entity.rotation); 35 | 36 | const velocity = direction.multiply(this.entity.stats.speed * deltaTime); 37 | 38 | this.entity.position = this.entity.position.add(velocity); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /static/projectiles/Click/0.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/lib/store/States/Tower/Upgrade.svelte.ts: -------------------------------------------------------------------------------- 1 | import { soundManager } from '$lib/store/SoundManager.svelte'; 2 | import { BaseState } from '$lib/store/States/BaseState.svelte'; 3 | 4 | const getPrefix = (level) => { 5 | if (level === 1) { 6 | return '1'; 7 | } else if (level === 2) { 8 | return '2'; 9 | } 10 | return '0'; 11 | }; 12 | 13 | const getUpgradeAnimationName = (tower, level) => { 14 | return `${tower.name}Upgrade${getPrefix(level)}`; 15 | }; 16 | 17 | const getGuardAnimationName = (tower, level) => { 18 | return `${tower.name}Base${getPrefix(level)}`; 19 | }; 20 | 21 | export class Upgrade extends BaseState { 22 | constructor(stateMachine) { 23 | super(stateMachine); 24 | 25 | this.entity.upgradeLevel += 1; 26 | 27 | const upgradeFn = this.entity.upgrades[this.entity.upgradeLevel]; 28 | upgradeFn(this.entity); 29 | 30 | this.entity.animation.onFrameChange = (frame) => { 31 | // this.entity.scale += 0.1; 32 | }; 33 | 34 | soundManager.play('towerUpgrade'); 35 | } 36 | 37 | update(deltaTime) { 38 | if (this.entity.animation.isComplete) { 39 | this.entity.stateToAnimation = { 40 | ...this.entity.stateToAnimation, 41 | Guard: getGuardAnimationName(this.entity, this.entity.upgradeLevel), 42 | Shoot: getGuardAnimationName(this.entity, this.entity.upgradeLevel), 43 | Upgrade: getUpgradeAnimationName(this.entity, this.entity.upgradeLevel + 1) 44 | }; 45 | 46 | this.entity.state.setState('Guard'); 47 | this.entity.animation.onFrameChange = () => {}; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /static/projectiles/Click/3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/lib/components/Bg1.svelte: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 | 50 | -------------------------------------------------------------------------------- /src/lib/store/Game.svelte.ts: -------------------------------------------------------------------------------- 1 | import { CollisionManager } from './CollisionManager.svelte'; 2 | import { EntityManager } from './EntityManager.svelte'; 3 | import { StageManager } from './StageManager.svelte'; 4 | import { GameLoop } from './GameLoop.svelte'; 5 | import { managers } from './managers.svelte'; 6 | import { soundManager } from './SoundManager.svelte'; 7 | import { cursor } from './Cursor.svelte'; 8 | import { lootTracker } from './LootTracker.svelte'; 9 | 10 | export class Game { 11 | isStarted = $state(false); 12 | 13 | update = async (deltaTime) => { 14 | managers.update(deltaTime); 15 | cursor.update(deltaTime); 16 | }; 17 | 18 | start = async () => { 19 | managers.setup(setupManagers()); 20 | 21 | const { gameLoop, stageManager } = managers.get(['gameLoop', 'stageManager']); 22 | 23 | stageManager.init(); 24 | 25 | gameLoop.start(this.update); 26 | 27 | soundManager.play('bgSound'); 28 | 29 | this.isStarted = true; 30 | }; 31 | 32 | restart = () => { 33 | const { entityManager, gameLoop, stageManager } = managers.get([ 34 | 'entityManager', 35 | 'gameLoop', 36 | 'stageManager' 37 | ]); 38 | 39 | gameLoop.reset(); 40 | entityManager.reset(); 41 | stageManager.reset(); 42 | soundManager.reset(); 43 | lootTracker.reset(); 44 | 45 | gameLoop.resume(); 46 | }; 47 | } 48 | 49 | const setupManagers = () => { 50 | managers.collisionManager = new CollisionManager(); 51 | managers.entityManager = new EntityManager(); 52 | managers.gameLoop = new GameLoop(); 53 | managers.stageManager = new StageManager(); 54 | 55 | return managers; 56 | }; 57 | 58 | export const game = new Game(); 59 | -------------------------------------------------------------------------------- /src/lib/store/States/Projectiles/FollowTarget.svelte.ts: -------------------------------------------------------------------------------- 1 | import { BaseState } from '$lib/store/States/BaseState.svelte'; 2 | import { angleToTarget, boundingBoxFromPoint, getDirectionFromAngle } from '$lib/utils/math'; 3 | import { Vector2 } from '$lib/store/Vector2.svelte'; 4 | 5 | export class FollowTarget extends BaseState { 6 | private lastAngle = 0; 7 | private rotationOffset = 90; // Offset for sprite orientation 8 | 9 | update(deltaTime: number) { 10 | const { target } = this.stateMachine.context; 11 | 12 | if (target?.isInteractable) { 13 | this.followTarget(deltaTime, target); 14 | } else { 15 | this.stateMachine.setState('FollowAngle', { 16 | angle: this.lastAngle 17 | }); 18 | } 19 | } 20 | 21 | getTopMiddlePoint(entity): Vector2 { 22 | const boundingBox = boundingBoxFromPoint(entity.position, entity.width, entity.height); 23 | return boundingBox.topMiddle; 24 | } 25 | 26 | followTarget(deltaTime: number, target: any) { 27 | const entityCenter = this.entity.boundingBox.center; 28 | const targetTopMiddle = this.getTopMiddlePoint(target); 29 | 30 | // Calculate angle from entity center to target's top middle point 31 | this.lastAngle = angleToTarget(entityCenter, target.boundingBox.center); 32 | 33 | // Update entity rotation with offset 34 | this.entity.rotation = (this.lastAngle * 180) / Math.PI + this.rotationOffset; 35 | 36 | // Get movement direction from the angle 37 | const direction = getDirectionFromAngle(this.lastAngle); 38 | 39 | // Calculate velocity 40 | const velocity = direction.multiply(this.entity.stats.speed * deltaTime); 41 | 42 | // Update the entity position 43 | this.entity.position = this.entity.position.add(velocity); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/lib/components/Effects/TowerShoot.svelte: -------------------------------------------------------------------------------- 1 | 35 | 36 |
{ 39 | entity.removeVFX('TowerShoot'); 40 | }} 41 | class="shoot-effect" 42 | style:--effect-color={color} 43 | style:left={`${offset.x}px`} 44 | style:top={`${offset.y}px`} 45 | >
46 | 47 | 72 | -------------------------------------------------------------------------------- /src/lib/components/StaticEntity.svelte: -------------------------------------------------------------------------------- 1 | 34 | 35 | Enemy animation 43 | 44 | 64 | -------------------------------------------------------------------------------- /src/lib/components/Bg3.svelte: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 | 63 | -------------------------------------------------------------------------------- /src/lib/components/DynamicEntity.svelte: -------------------------------------------------------------------------------- 1 | 36 | 37 | Enemy animation 47 | 48 | 67 | -------------------------------------------------------------------------------- /src/lib/components/Gui/StartScreen.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 |
16 | 17 | <div> 18 | <h2>Hint:</h2> 19 | <p>- Click on an Enemy to kill it</p> 20 | <p>- Click on a gray spot to build a Tower</p> 21 | <p>- Click on a Tower to upgrade</p> 22 | <div class="additional-hint"> 23 | <div>Manage your resources carefully if you want to survive until the last wave!</div> 24 | <div class="cost"> 25 | <div class="cost-item"> 26 | <div>Upgrading towers costs 35</div> 27 | <div> 28 | <Loot width={13} height={13} /> 29 | </div> 30 | </div> 31 | <div class="cost-item"> 32 | <div>Enemy click costs 5</div> 33 | <div> 34 | <Loot width={13} height={13} /> 35 | </div> 36 | </div> 37 | </div> 38 | </div> 39 | </div> 40 | <div class="buttons-container"> 41 | <Button disabled={!soundManager.preloaded} {text} onclick={onStart} /> 42 | </div> 43 | </div> 44 | </MenuLayout> 45 | 46 | <style> 47 | .content { 48 | display: flex; 49 | flex-direction: column; 50 | align-items: center; 51 | justify-content: space-around; 52 | height: 100%; 53 | color: rgba(214, 133, 218); 54 | text-align: center; 55 | } 56 | .additional-hint { 57 | font-size: small; 58 | max-width: 80%; 59 | margin: auto; 60 | line-height: 1.6; 61 | 62 | color: hsl(270, 15%, 45%); 63 | 64 | flex-direction: column; 65 | padding-top: 1.5rem; 66 | } 67 | .cost { 68 | padding-top: 0.5rem; 69 | flex-direction: column; 70 | font-size: x-small; 71 | } 72 | .cost-item { 73 | display: flex; 74 | align-items: center; 75 | justify-content: center; 76 | gap: 0.5rem; 77 | } 78 | </style> 79 | -------------------------------------------------------------------------------- /src/lib/components/Gui/PauseScreen.svelte: -------------------------------------------------------------------------------- 1 | <script> 2 | import Button from './Button.svelte'; 3 | import MenuLayout from './MenuLayout.svelte'; 4 | import GitHubIcon from '../GitHubIcon.svelte'; 5 | import { soundManager } from '$lib/store/SoundManager.svelte'; 6 | import { onDestroy, onMount } from 'svelte'; 7 | 8 | const { onResume, onRestart } = $props(); 9 | 10 | const toggleMute = () => { 11 | soundManager.toggleMute(); 12 | }; 13 | 14 | onMount(() => { 15 | soundManager.reduceBgVolume(); 16 | }); 17 | 18 | onDestroy(() => { 19 | soundManager.restoreBgVolume(); 20 | }); 21 | </script> 22 | 23 | <div class="pause-overlay"> 24 | <MenuLayout> 25 | <div class="content"> 26 | <div class="header"> 27 | <a href="https://github.com/baterson/svelte-tower-defence" class="git-link" 28 | ><GitHubIcon /></a 29 | > 30 | </div> 31 | <div class="title"> 32 | <h1>Paused</h1> 33 | </div> 34 | <div class="buttons-container"> 35 | <Button text="Resume Game" onclick={onResume} /> 36 | <Button text="Restart Game" onclick={onRestart} /> 37 | <Button text={soundManager.isMuted ? 'Sound: OFF' : 'Sound: ON'} onclick={toggleMute} /> 38 | </div> 39 | </div> 40 | </MenuLayout> 41 | </div> 42 | 43 | <style> 44 | .content { 45 | width: 100%; 46 | height: 100%; 47 | display: flex; 48 | flex-direction: column; 49 | align-items: center; 50 | justify-content: space-around; 51 | } 52 | .pause-overlay { 53 | position: relative; 54 | inset: 0; 55 | width: 100%; 56 | height: 100%; 57 | z-index: 100; 58 | } 59 | .header { 60 | display: flex; 61 | justify-content: end; 62 | align-items: center; 63 | width: 90%; 64 | font-size: 1.5rem; 65 | color: #fff; 66 | } 67 | .git-link { 68 | color: #fff; 69 | text-decoration: none; 70 | display: flex; 71 | } 72 | 73 | .title { 74 | color: rgba(214, 133, 218); 75 | font-weight: bold; 76 | font-size: 2rem; 77 | } 78 | 79 | .buttons-container { 80 | display: flex; 81 | flex-direction: column; 82 | gap: 1.5rem; 83 | align-items: center; 84 | justify-content: center; 85 | } 86 | 87 | @media (max-width: 768px) { 88 | .title { 89 | font-size: 2rem; 90 | } 91 | } 92 | </style> 93 | -------------------------------------------------------------------------------- /src/lib/components/Bg2.svelte: -------------------------------------------------------------------------------- 1 | <div class="bg-container"> 2 | <div class="gradient-bg"></div> 3 | <div class="aurora"></div> 4 | <div class="stars"></div> 5 | </div> 6 | 7 | <style> 8 | .bg-container { 9 | position: absolute; 10 | overflow: hidden; 11 | inset: 0; 12 | background: radial-gradient(circle, rgba(0, 20, 40, 0.8), rgba(0, 5, 15, 1)); 13 | z-index: 0; 14 | } 15 | 16 | .gradient-bg { 17 | position: absolute; 18 | background: radial-gradient(circle at 50% 10%, #2a0066, #10013e 70%); 19 | inset: 0; 20 | z-index: 1; 21 | } 22 | 23 | .aurora { 24 | position: absolute; 25 | inset: 0; 26 | z-index: 2; 27 | opacity: 0.3; 28 | background: radial-gradient(circle at center, rgba(0, 255, 157, 0.3) 0%, transparent 50%), 29 | linear-gradient(90deg, transparent 0%, #00ff9d 30%, #00c3ff 70%, transparent 100%); 30 | filter: blur(60px); 31 | transform: translateY(40%); 32 | animation: aurora-waves 8s ease-in-out infinite; 33 | } 34 | .stars { 35 | position: absolute; 36 | inset: 0; 37 | z-index: 3; 38 | background-image: radial-gradient(2px 2px at 10% 20%, rgba(255, 255, 255, 1), rgba(0, 0, 0, 0)), 39 | radial-gradient(2px 2px at 40% 70%, rgba(255, 255, 255, 0.9), rgba(0, 0, 0, 0)), 40 | radial-gradient(1.5px 1.5px at 50% 40%, rgba(255, 255, 255, 0.8), rgba(0, 0, 0, 0)), 41 | radial-gradient(1.5px 1.5px at 80% 30%, rgba(255, 255, 255, 0.8), rgba(0, 0, 0, 0)), 42 | radial-gradient(1px 1px at 20% 80%, rgba(255, 255, 255, 0.7), rgba(0, 0, 0, 0)), 43 | radial-gradient(1px 1px at 70% 50%, rgba(255, 255, 255, 0.7), rgba(0, 0, 0, 0)), 44 | radial-gradient(1px 1px at 90% 90%, rgba(255, 255, 255, 0.7), rgba(0, 0, 0, 0)); 45 | background-repeat: repeat; 46 | background-size: 47 | 200px 200px, 48 | 250px 250px, 49 | 150px 150px; 50 | animation: twinkle 4s ease-in-out infinite alternate; 51 | } 52 | 53 | @keyframes aurora-waves { 54 | 0%, 55 | 100% { 56 | transform: translateY(40%) translateX(-10%); 57 | } 58 | 50% { 59 | transform: translateY(40%) translateX(10%); 60 | } 61 | } 62 | 63 | @keyframes twinkle { 64 | 0% { 65 | opacity: 0.6; 66 | } 67 | 50% { 68 | opacity: 1; 69 | } 70 | 100% { 71 | opacity: 0.8; 72 | } 73 | } 74 | </style> 75 | -------------------------------------------------------------------------------- /src/lib/store/LootTracker.svelte.ts: -------------------------------------------------------------------------------- 1 | import { boundingBoxFromPoint } from '$lib/utils/math'; 2 | import { soundManager } from './SoundManager.svelte'; 3 | import { cursor } from './Cursor.svelte'; 4 | import { managers } from './managers.svelte'; 5 | 6 | const LOOT_MAP = { 7 | click: 5, 8 | upgrade: 35 9 | }; 10 | 11 | export class LootTracker { 12 | collectedLoot = $state(100); 13 | playLowLootAnimation = $state(false); 14 | playTowerUpgradeAnimation = $state(false); 15 | playEnemyClickAnimation = $state(false); 16 | score = $state(100); 17 | killsEnemy = $state(0); 18 | 19 | spendLoot(action) { 20 | const toSpend = LOOT_MAP[action.type]; 21 | 22 | if (this.collectedLoot < toSpend) { 23 | this.playAnimation('LowLoot'); 24 | // soundManager.play('lowResourse'); 25 | return; 26 | } 27 | 28 | if (action.type === 'upgrade') { 29 | const { tower } = action.payload; 30 | this.collectedLoot -= toSpend; 31 | 32 | tower.state.setState('Upgrade'); 33 | this.playAnimation('TowerUpgrade'); 34 | 35 | cursor.setAnimation('TowerBuildCursor'); 36 | } else if (action.type === 'click') { 37 | const { collisionManager, stageManager } = managers.get(['collisionManager', 'stageManager']); 38 | const { offset } = action.payload; 39 | 40 | const boundingBox = boundingBoxFromPoint(offset, 60, 60); 41 | const enemies = collisionManager.filterEnemiesByBounds(boundingBox); 42 | 43 | stageManager.spawnEntity('ClickExplode', offset); 44 | soundManager.play('clickEnemy', true); 45 | 46 | enemies.forEach((enemy) => { 47 | enemy.state.setState('Die'); 48 | }); 49 | 50 | if (enemies.length > 0) { 51 | this.collectedLoot -= toSpend; 52 | } 53 | } 54 | } 55 | 56 | receiveLoot(loot) { 57 | this.collectedLoot += loot; 58 | this.score += loot; 59 | this.killsEnemy++; 60 | } 61 | reset() { 62 | this.collectedLoot = 100; 63 | this.killsEnemy = 0; 64 | } 65 | 66 | getAnimation(name) { 67 | return `play${name}Animation`; 68 | } 69 | 70 | playAnimation(name) { 71 | const animation = this.getAnimation(name); 72 | this[animation] = true; 73 | } 74 | 75 | unsetAnimation(name) { 76 | const animation = this.getAnimation(name); 77 | this[animation] = false; 78 | } 79 | } 80 | 81 | export const lootTracker = new LootTracker(); 82 | -------------------------------------------------------------------------------- /static/projectiles/Fireball/Die/1.svg: -------------------------------------------------------------------------------- 1 | <svg width="20" height="22" viewBox="0 0 20 22" fill="none" xmlns="http://www.w3.org/2000/svg"> 2 | <circle cx="2.5" cy="2.5" r="2.5" transform="matrix(1 0 0 -1 8 5)" fill="#E45F01"/> 3 | <circle cx="1.66667" cy="1.66667" r="1.66667" transform="matrix(1 0 0 -1 8.83984 5)" fill="#FFE627"/> 4 | <circle cx="10.5" cy="20.5" r="1.5" fill="#E45F01"/> 5 | <circle cx="10.5039" cy="20" r="1" fill="#FFE627"/> 6 | <circle cx="19.5" cy="11.5" r="0.5" transform="rotate(-90 19.5 11.5)" fill="#E45F01"/> 7 | <circle cx="19.3333" cy="11.4987" r="0.333333" transform="rotate(-90 19.3333 11.4987)" fill="#FFE627"/> 8 | <circle cx="0.5" cy="0.5" r="0.5" transform="matrix(0 -1 -1 0 2 13)" fill="#E45F01"/> 9 | <circle cx="0.333333" cy="0.333333" r="0.333333" transform="matrix(0 -1 -1 0 2 12.832)" fill="#FFE627"/> 10 | <circle cx="15.5" cy="19.5" r="0.5" fill="#E45F01"/> 11 | <circle cx="15.5013" cy="19.3333" r="0.333333" fill="#FFE627"/> 12 | <circle cx="0.5" cy="0.5" r="0.5" transform="matrix(0.848614 -0.529013 -0.529013 -0.848614 6.52734 4.37695)" fill="#E45F01"/> 13 | <circle cx="0.333333" cy="0.333333" r="0.333333" transform="matrix(0.848614 -0.529013 -0.529013 -0.848614 6.66797 4.28906)" fill="#FFE627"/> 14 | <circle cx="15.6879" cy="4.68814" r="0.5" transform="rotate(-148.061 15.6879 4.68814)" fill="#E45F01"/> 15 | <circle cx="15.6005" cy="4.82985" r="0.333333" transform="rotate(-148.061 15.6005 4.82985)" fill="#FFE627"/> 16 | <circle cx="2" cy="2" r="2" transform="matrix(0.626268 -0.779608 -0.779608 -0.626268 3.30859 9.8125)" fill="#E45F01"/> 17 | <circle cx="1.33333" cy="1.33333" r="1.33333" transform="matrix(0.626268 -0.779608 -0.779608 -0.626268 3.73047 9.28906)" fill="#FFE627"/> 18 | <circle cx="4.49957" cy="17.5006" r="2.5" transform="rotate(39.9996 4.49957 17.5006)" fill="#E45F01"/> 19 | <circle cx="5.04138" cy="16.8656" r="1.66667" transform="rotate(39.9996 5.04138 16.8656)" fill="#FFE627"/> 20 | <circle cx="17.507" cy="16.5053" r="1.50303" transform="rotate(-32.2745 17.507 16.5053)" fill="#E45F01"/> 21 | <circle cx="17.2416" cy="16.0797" r="1.00202" transform="rotate(-32.2745 17.2416 16.0797)" fill="#FFE627"/> 22 | <circle cx="1.5" cy="1.5" r="1.5" transform="matrix(0.632115 0.774875 0.774875 -0.632115 15.3906 6.28516)" fill="#E45F01"/> 23 | <circle cx="1" cy="1" r="1" transform="matrix(0.632115 0.774875 0.774875 -0.632115 15.7109 6.67578)" fill="#FFE627"/> 24 | </svg> 25 | -------------------------------------------------------------------------------- /static/projectiles/Click/1.svg: -------------------------------------------------------------------------------- 1 | <svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg"> 2 | <circle cx="13.398" cy="4.26325" r="4.15276" transform="rotate(180 13.398 4.26325)" fill="#FF8DF4"/> 3 | <circle cx="13.3838" cy="5.64751" r="2.76851" transform="rotate(180 13.3838 5.64751)" fill="#FFDFFC"/> 4 | <circle cx="2.64975" cy="2.64975" r="2.64975" transform="matrix(-1 0 0 1 16.1953 24.4844)" fill="#FF8DF4"/> 5 | <circle cx="1.7665" cy="1.7665" r="1.7665" transform="matrix(-1 0 0 1 15.3047 24.4844)" fill="#FFDFFC"/> 6 | <circle cx="1.17215" cy="1.17215" r="1.17215" transform="matrix(0 -1 -1 0 3.70703 16.793)" fill="#FF8DF4"/> 7 | <circle cx="0.781433" cy="0.781433" r="0.781433" transform="matrix(0 -1 -1 0 3.70703 16.3984)" fill="#FFDFFC"/> 8 | <circle cx="25.8542" cy="16.7357" r="1.11588" transform="rotate(-90 25.8542 16.7357)" fill="#FF8DF4"/> 9 | <circle cx="25.4822" cy="16.7307" r="0.743923" transform="rotate(-90 25.4822 16.7307)" fill="#FFDFFC"/> 10 | <circle cx="1.30091" cy="1.30091" r="1.30091" transform="matrix(-1 0 0 1 9.35547 24.4844)" fill="#FF8DF4"/> 11 | <circle cx="0.867272" cy="0.867272" r="0.867272" transform="matrix(-1 0 0 1 8.92188 24.4844)" fill="#FFDFFC"/> 12 | <circle cx="19.6743" cy="5.30571" r="1.09234" transform="rotate(-148.061 19.6743 5.30571)" fill="#FF8DF4"/> 13 | <circle cx="19.4821" cy="5.61396" r="0.728229" transform="rotate(-148.061 19.4821 5.61396)" fill="#FFDFFC"/> 14 | <circle cx="23.1114" cy="10.6122" r="4.31862" transform="rotate(-128.775 23.1114 10.6122)" fill="#FF8DF4"/> 15 | <circle cx="21.9805" cy="11.5012" r="2.87908" transform="rotate(-128.775 21.9805 11.5012)" fill="#FFDFFC"/> 16 | <circle cx="4.44351" cy="4.44351" r="4.44351" transform="matrix(-0.766049 0.642783 0.642783 0.766049 22.3633 16.7305)" fill="#FF8DF4"/> 17 | <circle cx="2.96234" cy="2.96234" r="2.96234" transform="matrix(-0.766049 0.642783 0.642783 0.766049 21.2227 17.6895)" fill="#FFDFFC"/> 18 | <circle cx="2.35843" cy="2.35843" r="2.35843" transform="matrix(-0.8455 -0.533976 -0.533976 0.8455 7.87109 21.1797)" fill="#FF8DF4"/> 19 | <circle cx="1.57229" cy="1.57229" r="1.57229" transform="matrix(-0.8455 -0.533976 -0.533976 0.8455 7.19922 20.7578)" fill="#FFDFFC"/> 20 | <circle cx="4.77516" cy="9.31452" r="3.14712" transform="rotate(129.206 4.77516 9.31452)" fill="#FF8DF4"/> 21 | <circle cx="5.57927" cy="9.98311" r="2.09808" transform="rotate(129.206 5.57927 9.98311)" fill="#FFDFFC"/> 22 | </svg> 23 | -------------------------------------------------------------------------------- /src/lib/store/CollisionManager.svelte.ts: -------------------------------------------------------------------------------- 1 | import { managers } from './managers.svelte'; 2 | import type { Entity } from './Entity.svelte'; 3 | import { screen } from '$lib/store/Screen.svelte'; 4 | import { checkRectCollision, type Rect2D } from '$lib/utils/math'; 5 | 6 | export class CollisionManager { 7 | update() { 8 | const entityManager = managers.get('entityManager'); 9 | 10 | this.handleEnemyCollisions( 11 | entityManager.livingEnemies, 12 | entityManager.livingProjectiles, 13 | entityManager.throne 14 | ); 15 | 16 | this.handleProjectileCollisions(entityManager.livingProjectiles); 17 | 18 | this.handleLootCollisions(entityManager.livingLoot, entityManager.throne); 19 | } 20 | 21 | handleEnemyCollisions(enemies: Entity[], projectiles: Entity[], throne: Entity): void { 22 | for (const enemy of enemies) { 23 | for (const projectile of projectiles) { 24 | if (this.checkCollision(projectile, enemy)) { 25 | projectile.onCollide(enemy); 26 | enemy.onCollide(projectile); 27 | } 28 | } 29 | 30 | if (this.checkCollision(enemy, throne)) { 31 | enemy.onCollide(throne); 32 | throne.onCollide(enemy); 33 | } 34 | } 35 | } 36 | 37 | handleProjectileCollisions(projectiles: Entity[]): void { 38 | for (const projectile of projectiles) { 39 | if (!this.checkScreenBounds(projectile)) { 40 | projectile.onCollide('OUT_OF_BOUNDS'); 41 | continue; 42 | } 43 | } 44 | } 45 | 46 | handleLootCollisions(lootEntities, throne): void { 47 | lootEntities.forEach((loot) => { 48 | if (this.checkCollision(loot, throne)) { 49 | loot.onCollide(throne); 50 | throne.onCollide(loot); 51 | } 52 | }); 53 | } 54 | 55 | checkCollision(entity1: Entity, entity2: Entity): boolean { 56 | return checkRectCollision(entity1.boundingBox, entity2.boundingBox); 57 | } 58 | 59 | filterEnemiesByBounds = (bounds) => { 60 | const entityManager = managers.get('entityManager'); 61 | 62 | return entityManager.livingEnemies.filter((entity) => 63 | checkRectCollision(entity.boundingBox, bounds) 64 | ); 65 | }; 66 | 67 | checkBounds(entity: Entity, boundsRect: Rect2D): boolean { 68 | return checkRectCollision(entity.boundingBox, boundsRect); 69 | } 70 | 71 | checkScreenBounds(entity: Entity): boolean { 72 | return checkRectCollision(entity.boundingBox, screen.screenBounds); 73 | } 74 | 75 | checkGameBounds(entity: Entity): boolean { 76 | return checkRectCollision(entity.boundingBox, screen.gameBoundingBox); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/lib/components/Bg4.svelte: -------------------------------------------------------------------------------- 1 | <div class="bg-container"> 2 | <div class="quantum-base"></div> 3 | <div class="energy-fields"></div> 4 | <div class="quantum-grid"></div> 5 | <div class="stars"></div> 6 | </div> 7 | 8 | <style> 9 | .bg-container { 10 | position: absolute; 11 | inset: 0; 12 | overflow: hidden; 13 | background: radial-gradient(circle, rgba(0, 20, 40, 0.8), rgba(0, 5, 15, 1)); 14 | z-index: 0; 15 | } 16 | 17 | .quantum-base { 18 | position: absolute; 19 | inset: 0; 20 | background: radial-gradient(circle at center, #200030 0%, #100020 50%, #0a0014 100%); 21 | z-index: 1; 22 | } 23 | 24 | .energy-fields { 25 | position: absolute; 26 | inset: 0; 27 | z-index: 2; 28 | background: repeating-radial-gradient( 29 | circle at 50% 50%, 30 | rgba(255, 0, 255, 0.1) 0%, 31 | transparent 10%, 32 | rgba(255, 0, 255, 0.1) 20% 33 | ); 34 | filter: blur(10px); 35 | } 36 | 37 | .quantum-grid { 38 | position: absolute; 39 | inset: 0; 40 | z-index: 3; 41 | background-image: linear-gradient(rgba(157, 0, 255, 0.1) 1px, transparent 1px), 42 | linear-gradient(90deg, rgba(157, 0, 255, 0.1) 1px, transparent 1px); 43 | background-size: 40px 40px; 44 | opacity: 0.3; 45 | animation: grid-drift 20s linear infinite; 46 | } 47 | 48 | .stars { 49 | position: absolute; 50 | inset: 0; 51 | z-index: 4; 52 | background-image: radial-gradient(2px 2px at 10% 20%, rgba(255, 255, 255, 1), rgba(0, 0, 0, 0)), 53 | radial-gradient(2px 2px at 40% 70%, rgba(255, 255, 255, 0.9), rgba(0, 0, 0, 0)), 54 | radial-gradient(1.5px 1.5px at 50% 40%, rgba(255, 255, 255, 0.8), rgba(0, 0, 0, 0)), 55 | radial-gradient(1.5px 1.5px at 80% 30%, rgba(255, 255, 255, 0.8), rgba(0, 0, 0, 0)), 56 | radial-gradient(1px 1px at 20% 80%, rgba(255, 255, 255, 0.7), rgba(0, 0, 0, 0)), 57 | radial-gradient(1px 1px at 70% 50%, rgba(255, 255, 255, 0.7), rgba(0, 0, 0, 0)), 58 | radial-gradient(1px 1px at 90% 90%, rgba(255, 255, 255, 0.7), rgba(0, 0, 0, 0)); 59 | background-repeat: repeat; 60 | background-size: 61 | 200px 200px, 62 | 250px 250px, 63 | 150px 150px; 64 | animation: twinkle 4s ease-in-out infinite alternate; 65 | } 66 | 67 | @keyframes grid-drift { 68 | 0% { 69 | background-position: 0 0; 70 | } 71 | 100% { 72 | background-position: 40px 40px; 73 | } 74 | } 75 | 76 | @keyframes twinkle { 77 | 0% { 78 | opacity: 0.6; 79 | } 80 | 50% { 81 | opacity: 1; 82 | } 83 | 100% { 84 | opacity: 0.8; 85 | } 86 | } 87 | </style> 88 | -------------------------------------------------------------------------------- /static/loot.svg: -------------------------------------------------------------------------------- 1 | <svg width="13" height="14" viewBox="0 0 13 14" fill="none" xmlns="http://www.w3.org/2000/svg"> 2 | <path d="M13 7.5C13 9.22391 12.3152 10.8772 11.0962 12.0962C9.87721 13.3152 8.22391 14 6.5 14C4.77609 14 3.12279 13.3152 1.90381 12.0962C0.68482 10.8772 2.60303e-07 9.22391 0 7.5L1.85791 7.5C1.85791 8.73116 2.34698 9.9119 3.21754 10.7825C4.08811 11.653 5.26884 12.1421 6.5 12.1421C7.73116 12.1421 8.91189 11.653 9.78246 10.7825C10.653 9.91189 11.1421 8.73116 11.1421 7.5H13Z" fill="#FFE627"/> 3 | <path d="M13 7.5C13 9.22391 12.3152 10.8772 11.0962 12.0962C9.87721 13.3152 8.22391 14 6.5 14C4.77609 14 3.12279 13.3152 1.90381 12.0962C0.68482 10.8772 2.60303e-07 9.22391 0 7.5H1.01962C1.01962 8.95349 1.59702 10.3474 2.62479 11.3752C3.65256 12.403 5.04651 12.9804 6.5 12.9804C7.95349 12.9804 9.34744 12.403 10.3752 11.3752C11.403 10.3474 11.9804 8.95349 11.9804 7.5H13Z" fill="#FFBB27"/> 4 | <circle cx="6.5" cy="7" r="3.5" fill="url(#paint0_linear_2360_1897)"/> 5 | <circle cx="6.50349" cy="7.0074" r="2.21053" fill="#350F42"/> 6 | <circle cx="6.50662" cy="7.00662" r="1.66287" fill="url(#paint1_linear_2360_1897)"/> 7 | <circle cx="6.50155" cy="6.99764" r="0.841394" fill="#350F42"/> 8 | <circle cx="5.99044" cy="6.15841" r="0.517781" fill="white"/> 9 | <circle cx="7.34009" cy="8.08032" r="0.238524" fill="white"/> 10 | <path d="M13 6.5C13 4.77609 12.3152 3.12279 11.0962 1.90381C9.87721 0.684819 8.22391 0 6.5 0C4.77609 0 3.12279 0.684819 1.90381 1.90381C0.68482 3.12279 2.60303e-07 4.77609 0 6.5L1.85791 6.5C1.85791 5.26884 2.34698 4.0881 3.21754 3.21754C4.08811 2.34698 5.26884 1.85791 6.5 1.85791C7.73116 1.85791 8.91189 2.34698 9.78246 3.21754C10.653 4.08811 11.1421 5.26884 11.1421 6.5H13Z" fill="#FFE627"/> 11 | <path d="M12 6.5C12 5.04131 11.4205 3.64236 10.3891 2.61091C9.35764 1.57946 7.95869 1 6.5 1C5.04131 1 3.64236 1.57946 2.61091 2.61091C1.57946 3.64236 1 5.04131 1 6.5L1.8736 6.5C1.8736 5.273 2.36102 4.09626 3.22864 3.22864C4.09626 2.36102 5.273 1.8736 6.5 1.8736C7.727 1.8736 8.90374 2.36102 9.77136 3.22864C10.639 4.09626 11.1264 5.273 11.1264 6.5H12Z" fill="#FFBB27"/> 12 | <defs> 13 | <linearGradient id="paint0_linear_2360_1897" x1="6.5" y1="3.5" x2="6.5" y2="10.5" gradientUnits="userSpaceOnUse"> 14 | <stop stop-color="#EBD6EB"/> 15 | <stop offset="0.9999" stop-color="white"/> 16 | </linearGradient> 17 | <linearGradient id="paint1_linear_2360_1897" x1="6.50662" y1="5.34375" x2="6.50662" y2="8.6695" gradientUnits="userSpaceOnUse"> 18 | <stop stop-color="#853085"/> 19 | <stop offset="1" stop-color="#9D4A9D"/> 20 | </linearGradient> 21 | </defs> 22 | </svg> 23 | -------------------------------------------------------------------------------- /src/lib/config/entities/projectiles.ts: -------------------------------------------------------------------------------- 1 | import { fireballCollider, lootCollider } from '../collisionHandlers'; 2 | 3 | export const projectiles = { 4 | Fireball: { 5 | type: 'projectile', 6 | width: 20, 7 | height: 22, 8 | vfx: [], 9 | initialState: 'FollowTarget', 10 | stateToAnimation: { 11 | FollowTarget: 'FireballFollow', 12 | FollowAngle: 'FireballFollow', 13 | Explode: 'FireballDie' 14 | }, 15 | scale: 2, 16 | stats: { 17 | health: 1, 18 | speed: 0.5, 19 | damage: 11 20 | }, 21 | onCollide: fireballCollider 22 | }, 23 | Icebolt: { 24 | type: 'projectile', 25 | width: 10, 26 | height: 27, 27 | vfx: [], 28 | initialState: 'FollowTarget', 29 | stateToAnimation: { 30 | FollowTarget: 'IceboltFollow', 31 | FollowAngle: 'IceboltFollow', 32 | Explode: 'IceboltDie' 33 | }, 34 | scale: 2, 35 | stats: { 36 | health: 1, 37 | speed: 0.3, 38 | damage: 11 39 | }, 40 | onCollide: fireballCollider 41 | }, 42 | Poisonball: { 43 | type: 'projectile', 44 | width: 17, 45 | height: 16, 46 | vfx: [], 47 | initialState: 'FollowTarget', 48 | stateToAnimation: { 49 | FollowTarget: 'PoisonballFollow', 50 | FollowAngle: 'PoisonballFollow', 51 | Explode: 'PoisonballDie' 52 | }, 53 | scale: 2, 54 | stats: { 55 | health: 1, 56 | speed: 0.4, 57 | damage: 11 58 | }, 59 | onCollide: fireballCollider 60 | }, 61 | Thunderbolt: { 62 | type: 'projectile', 63 | width: 17, 64 | height: 31, 65 | vfx: [], 66 | initialState: 'FollowTarget', 67 | stateToAnimation: { 68 | FollowTarget: 'ThunderboltFollow', 69 | FollowAngle: 'ThunderboltFollow', 70 | Explode: 'ThunderboltDie' 71 | }, 72 | scale: 2, 73 | stats: { 74 | health: 1, 75 | speed: 0.4, 76 | damage: 11 77 | }, 78 | onCollide: fireballCollider 79 | }, 80 | 81 | Loot: { 82 | type: 'loot', 83 | vfx: [], 84 | width: 14, 85 | height: 19, 86 | initialState: 'FollowTarget', 87 | stateToAnimation: { 88 | FollowTarget: 'Loot' 89 | }, 90 | scale: 1, 91 | stats: { 92 | health: 1, 93 | damage: 0, 94 | speed: 0.5 95 | }, 96 | onCollide: lootCollider 97 | }, 98 | 99 | ClickExplode: { 100 | type: 'projectile', 101 | vfx: [], 102 | width: 30, 103 | height: 30, 104 | initialState: 'Explode', 105 | stateToAnimation: { 106 | Explode: 'Click' 107 | }, 108 | scale: 1, 109 | stats: { 110 | health: 1, 111 | damage: 10, 112 | speed: 0.5 113 | } 114 | } 115 | }; 116 | -------------------------------------------------------------------------------- /src/lib/config/entities/towers.ts: -------------------------------------------------------------------------------- 1 | import { throneCollider } from '../collisionHandlers'; 2 | import * as upgrades from '../upgrades'; 3 | 4 | export const towers = { 5 | Throne: { 6 | type: 'throne', 7 | // width: 256, 8 | // height: 252, 9 | stats: { 10 | health: 500, 11 | damage: 999, 12 | speed: 0 13 | }, 14 | scale: 0.6, 15 | stateToAnimation: { 16 | Idle: 'Throne', 17 | Die: 'Throne' 18 | }, 19 | initialState: 'Idle', 20 | onCollide: throneCollider 21 | }, 22 | FireTower: { 23 | type: 'tower', 24 | // width: 68, 25 | // height: 72, 26 | initialState: 'NotBuilt', 27 | stateToAnimation: { 28 | Guard: 'FireTowerBase0', 29 | Shoot: 'FireTowerBase0', 30 | NotBuilt: 'TowerBase', 31 | Upgrade: 'FireTowerUpgrade0' 32 | }, 33 | effects: [], 34 | stats: { 35 | health: 1000, 36 | attackSpeed: 800, 37 | projectileNumber: 1, 38 | projectileType: 'Fireball' 39 | }, 40 | scale: 0.8, 41 | upgrades: upgrades.FireTower 42 | }, 43 | 44 | ThunderTower: { 45 | type: 'tower', 46 | // width: 68, 47 | // height: 72, 48 | initialState: 'NotBuilt', 49 | stateToAnimation: { 50 | Guard: 'ThunderTowerBase0', 51 | Shoot: 'ThunderTowerBase0', 52 | NotBuilt: 'TowerBase', 53 | Upgrade: 'ThunderTowerUpgrade0' 54 | }, 55 | effects: [], 56 | stats: { 57 | health: 1000, 58 | attackSpeed: 750, 59 | projectileNumber: 1, 60 | projectileType: 'Thunderbolt' 61 | }, 62 | scale: 0.8, 63 | upgrades: upgrades.ThunderTower 64 | }, 65 | 66 | PoisonTower: { 67 | type: 'tower', 68 | // width: 68, 69 | // height: 72, 70 | initialState: 'NotBuilt', 71 | stateToAnimation: { 72 | Guard: 'PoisonTowerBase0', 73 | Shoot: 'PoisonTowerBase0', 74 | NotBuilt: 'TowerBase', 75 | Upgrade: 'PoisonTowerUpgrade0' 76 | }, 77 | effects: [], 78 | stats: { 79 | health: 1000, 80 | attackSpeed: 800, 81 | projectileNumber: 1, 82 | projectileType: 'Poisonball' 83 | }, 84 | scale: 0.8, 85 | upgrades: upgrades.PoisonTower 86 | }, 87 | 88 | IceTower: { 89 | type: 'tower', 90 | // width: 68, 91 | // height: 72, 92 | initialState: 'NotBuilt', 93 | stateToAnimation: { 94 | Guard: 'IceTowerBase0', 95 | Shoot: 'IceTowerBase0', 96 | NotBuilt: 'TowerBase', 97 | Upgrade: 'IceTowerUpgrade0' 98 | }, 99 | effects: [], 100 | stats: { 101 | health: 1000, 102 | attackSpeed: 850, 103 | projectileNumber: 1, 104 | projectileType: 'Icebolt' 105 | }, 106 | scale: 0.8, 107 | upgrades: upgrades.IceTower 108 | } 109 | }; 110 | -------------------------------------------------------------------------------- /static/towers/Upgrade0.svg: -------------------------------------------------------------------------------- 1 | <svg width="68" height="86" viewBox="0 0 68 86" fill="none" xmlns="http://www.w3.org/2000/svg"> 2 | <path d="M1 35.3789H67V76.0017C67 80.9722 62.9706 85.0016 58 85.0016H10C5.02944 85.0016 1 80.9722 1 76.0016V35.3789Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 3 | <path d="M1 24C1 19.0294 5.02944 15 10 15H58C62.9706 15 67 19.0294 67 24V60.8146C67 65.7852 62.9706 69.8146 58 69.8146H10C5.02944 69.8146 1 65.7852 1 60.8146V24Z" fill="url(#paint0_linear_2240_1950)" stroke="#350F42" stroke-width="2"/> 4 | <path d="M9 11H31V37C31 39.2091 29.2091 41 27 41H13C10.7909 41 9 39.2091 9 37V11Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 5 | <rect x="9" y="1" width="22" height="22" rx="4" fill="url(#paint1_linear_2240_1950)" stroke="#350F42" stroke-width="2"/> 6 | <path d="M9 32H31V58C31 60.2091 29.2091 62 27 62H13C10.7909 62 9 60.2091 9 58V32Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 7 | <rect x="9" y="29" width="22" height="22" rx="4" fill="url(#paint2_linear_2240_1950)" stroke="#350F42" stroke-width="2"/> 8 | <path d="M37 11H59V37C59 39.2091 57.2091 41 55 41H41C38.7909 41 37 39.2091 37 37V11Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 9 | <rect x="37" y="1" width="22" height="22" rx="4" fill="url(#paint3_linear_2240_1950)" stroke="#350F42" stroke-width="2"/> 10 | <path d="M37 32H59V58C59 60.2091 57.2091 62 55 62H41C38.7909 62 37 60.2091 37 58V32Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 11 | <rect x="37" y="29" width="22" height="22" rx="4" fill="url(#paint4_linear_2240_1950)" stroke="#350F42" stroke-width="2"/> 12 | <defs> 13 | <linearGradient id="paint0_linear_2240_1950" x1="34" y1="14" x2="34" y2="70.8146" gradientUnits="userSpaceOnUse"> 14 | <stop stop-color="#8F90BD"/> 15 | <stop offset="1" stop-color="#A5A6CE"/> 16 | </linearGradient> 17 | <linearGradient id="paint1_linear_2240_1950" x1="20" y1="0" x2="20" y2="24" gradientUnits="userSpaceOnUse"> 18 | <stop stop-color="#8F90BD"/> 19 | <stop offset="1" stop-color="#A5A6CE"/> 20 | </linearGradient> 21 | <linearGradient id="paint2_linear_2240_1950" x1="20" y1="28" x2="20" y2="52" gradientUnits="userSpaceOnUse"> 22 | <stop stop-color="#8F90BD"/> 23 | <stop offset="1" stop-color="#A5A6CE"/> 24 | </linearGradient> 25 | <linearGradient id="paint3_linear_2240_1950" x1="48" y1="0" x2="48" y2="24" gradientUnits="userSpaceOnUse"> 26 | <stop stop-color="#8F90BD"/> 27 | <stop offset="1" stop-color="#A5A6CE"/> 28 | </linearGradient> 29 | <linearGradient id="paint4_linear_2240_1950" x1="48" y1="28" x2="48" y2="52" gradientUnits="userSpaceOnUse"> 30 | <stop stop-color="#8F90BD"/> 31 | <stop offset="1" stop-color="#A5A6CE"/> 32 | </linearGradient> 33 | </defs> 34 | </svg> 35 | -------------------------------------------------------------------------------- /src/lib/components/Gui/WinLoseScreen.svelte: -------------------------------------------------------------------------------- 1 | <script> 2 | const { onRestart } = $props(); 3 | import Button from './Button.svelte'; 4 | import MenuLayout from './MenuLayout.svelte'; 5 | import { lootTracker } from '$lib/store/LootTracker.svelte'; 6 | import { managers } from '$lib/store/managers.svelte'; 7 | import { fade, slide } from 'svelte/transition'; 8 | import { soundManager } from '$lib/store/SoundManager.svelte'; 9 | import { onDestroy, onMount } from 'svelte'; 10 | import Loot from './Loot.svelte'; 11 | 12 | const stageManager = $derived(managers.get('stageManager')); 13 | 14 | onMount(() => { 15 | soundManager.reduceBgVolume(); 16 | }); 17 | 18 | onDestroy(() => { 19 | soundManager.restoreBgVolume(); 20 | }); 21 | </script> 22 | 23 | <MenuLayout> 24 | <div class="content" in:slide={{ duration: 500 }} out:fade={{ duration: 300 }}> 25 | <h1>{stageManager.stageResult === 'win' ? 'You win!' : 'You lose!'}</h1> 26 | <div class="scores"> 27 | <h2>Enemies killed: {lootTracker.killsEnemy}</h2> 28 | <h2>Score: {lootTracker.score}</h2> 29 | </div> 30 | <div class="hint"> 31 | <h2>Hint:</h2> 32 | <p>- Click on an Enemy to kill it</p> 33 | <p>- Click on a gray spot to build a Tower</p> 34 | <p>- Click on a Tower to upgrade</p> 35 | <div class="additional-hint"> 36 | <div>Manage your resources carefully if you want to survive until the last wave!</div> 37 | <div class="cost"> 38 | <div class="cost-item"> 39 | <div>Upgrading towers costs 35</div> 40 | <div> 41 | <Loot width={13} height={13} /> 42 | </div> 43 | </div> 44 | <div class="cost-item"> 45 | <div>Enemy click costs 5</div> 46 | <div> 47 | <Loot width={13} height={13} /> 48 | </div> 49 | </div> 50 | </div> 51 | </div> 52 | </div> 53 | <Button onclick={onRestart} text="Restart" /> 54 | </div> 55 | </MenuLayout> 56 | 57 | <style> 58 | .content { 59 | display: flex; 60 | flex-direction: column; 61 | align-items: center; 62 | justify-content: space-around; 63 | height: 100%; 64 | color: rgba(214, 133, 218); 65 | } 66 | .hint { 67 | text-align: center; 68 | } 69 | h2 { 70 | font-size: 2rem; 71 | } 72 | h1 { 73 | font-size: 4rem; 74 | } 75 | .additional-hint { 76 | font-size: small; 77 | max-width: 80%; 78 | margin: auto; 79 | line-height: 1.6; 80 | color: hsl(270, 15%, 45%); 81 | 82 | flex-direction: column; 83 | padding-top: 1.5rem; 84 | } 85 | .cost { 86 | padding-top: 0.5rem; 87 | flex-direction: column; 88 | font-size: x-small; 89 | } 90 | .cost-item { 91 | display: flex; 92 | align-items: center; 93 | justify-content: center; 94 | gap: 0.5rem; 95 | } 96 | </style> 97 | -------------------------------------------------------------------------------- /static/projectiles/Click/2.svg: -------------------------------------------------------------------------------- 1 | <svg width="27" height="30" viewBox="0 0 27 30" fill="none" xmlns="http://www.w3.org/2000/svg"> 2 | <circle cx="12.9365" cy="3.37202" r="3.37212" transform="rotate(180 12.9365 3.37202)" fill="#FF8DF4"/> 3 | <circle cx="12.9277" cy="4.49606" r="2.24808" transform="rotate(180 12.9277 4.49606)" fill="#FFDFFC"/> 4 | <circle cx="2.02327" cy="2.02327" r="2.02327" transform="matrix(-1 0 0 1 14.957 25.6289)" fill="#FF8DF4"/> 5 | <circle cx="1.34885" cy="1.34885" r="1.34885" transform="matrix(-1 0 0 1 14.2773 25.6289)" fill="#FFDFFC"/> 6 | <circle cx="0.674423" cy="0.674423" r="0.674423" transform="matrix(0 -1 -1 0 1.46875 16.1855)" fill="#FF8DF4"/> 7 | <circle cx="0.449615" cy="0.449615" r="0.449615" transform="matrix(0 -1 -1 0 1.46875 15.959)" fill="#FFDFFC"/> 8 | <circle cx="25.0768" cy="16.8607" r="0.674423" transform="rotate(-90 25.0768 16.8607)" fill="#FF8DF4"/> 9 | <circle cx="24.852" cy="16.859" r="0.449615" transform="rotate(-90 24.852 16.859)" fill="#FFDFFC"/> 10 | <circle cx="0.674423" cy="0.674423" r="0.674423" transform="matrix(-1 0 0 1 6.86328 25.6289)" fill="#FF8DF4"/> 11 | <circle cx="0.449615" cy="0.449615" r="0.449615" transform="matrix(-1 0 0 1 6.63672 25.6289)" fill="#FFDFFC"/> 12 | <circle cx="18.0813" cy="4.97519" r="0.674423" transform="rotate(-148.061 18.0813 4.97519)" fill="#FF8DF4"/> 13 | <circle cx="17.9618" cy="5.16575" r="0.449615" transform="rotate(-148.061 17.9618 5.16575)" fill="#FFDFFC"/> 14 | <circle cx="0.674423" cy="0.674423" r="0.674423" transform="matrix(0.848614 -0.529013 -0.529013 -0.848614 5.71875 7.25195)" fill="#FF8DF4"/> 15 | <circle cx="0.449615" cy="0.449615" r="0.449615" transform="matrix(0.848614 -0.529013 -0.529013 -0.848614 5.91016 7.13281)" fill="#FFDFFC"/> 16 | <circle cx="23.0465" cy="9.44371" r="2.69769" transform="rotate(-128.775 23.0465 9.44371)" fill="#FF8DF4"/> 17 | <circle cx="22.3422" cy="10.0009" r="1.79846" transform="rotate(-128.775 22.3422 10.0009)" fill="#FFDFFC"/> 18 | <circle cx="3.37212" cy="3.37212" r="3.37212" transform="matrix(-0.766049 0.642783 0.642783 0.766049 21.4453 18.8574)" fill="#FF8DF4"/> 19 | <circle cx="2.24808" cy="2.24808" r="2.24808" transform="matrix(-0.766049 0.642783 0.642783 0.766049 20.5781 19.584)" fill="#FFDFFC"/> 20 | <circle cx="2.02736" cy="2.02736" r="2.02736" transform="matrix(-0.8455 -0.533976 -0.533976 0.8455 6.28125 21.6328)" fill="#FF8DF4"/> 21 | <circle cx="1.35157" cy="1.35157" r="1.35157" transform="matrix(-0.8455 -0.533976 -0.533976 0.8455 5.70703 21.2695)" fill="#FFDFFC"/> 22 | <circle cx="3.48922" cy="8.76736" r="2.02327" transform="rotate(129.206 3.48922 8.76736)" fill="#FF8DF4"/> 23 | <circle cx="4.00453" cy="9.19647" r="1.34885" transform="rotate(129.206 4.00453 9.19647)" fill="#FFDFFC"/> 24 | </svg> 25 | -------------------------------------------------------------------------------- /src/lib/utils/preload.ts: -------------------------------------------------------------------------------- 1 | export const preloadUrls = [ 2 | '/projectiles/Click/0.svg', 3 | '/projectiles/Click/1.svg', 4 | '/projectiles/Click/2.svg', 5 | '/projectiles/Click/3.svg', 6 | '/projectiles/Fireball/Die/0.svg', 7 | '/projectiles/Fireball/Die/1.svg', 8 | '/projectiles/Fireball/Die/2.svg', 9 | '/projectiles/Fireball/Follow/0.svg', 10 | '/projectiles/Fireball/Follow/1.svg', 11 | '/projectiles/Fireball/Follow/2.svg', 12 | '/projectiles/Fireball/Follow/3.svg', 13 | '/projectiles/Icebolt/Die/0.svg', 14 | '/projectiles/Icebolt/Die/1.svg', 15 | '/projectiles/Icebolt/Die/2.svg', 16 | '/projectiles/Icebolt/Follow/0.svg', 17 | '/projectiles/Icebolt/Follow/1.svg', 18 | '/projectiles/Poisonball/Die/0.svg', 19 | '/projectiles/Poisonball/Die/1.svg', 20 | '/projectiles/Poisonball/Die/2.svg', 21 | '/projectiles/Poisonball/Follow/0.svg', 22 | '/projectiles/Poisonball/Follow/1.svg', 23 | '/projectiles/Poisonball/Follow/2.svg', 24 | '/projectiles/Poisonball/Follow/3.svg', 25 | '/projectiles/Thunderbolt/Die/0.svg', 26 | '/projectiles/Thunderbolt/Die/1.svg', 27 | '/projectiles/Thunderbolt/Die/2.svg', 28 | '/projectiles/Thunderbolt/Follow/0.svg', 29 | '/projectiles/Thunderbolt/Follow/1.svg', 30 | '/projectiles/Thunderbolt/Follow/2.svg', 31 | '/projectiles/Thunderbolt/Follow/3.svg', 32 | 33 | '/enemies/BlueCommon/Follow/0.svg', 34 | '/enemies/BlueCommon/Follow/1.svg', 35 | '/enemies/BlueCommon/Follow/2.svg', 36 | '/enemies/BlueCommon/Die/0.svg', 37 | '/enemies/BlueCommon/Die/1.svg', 38 | '/enemies/BlueCommon/Die/2.svg', 39 | '/enemies/BlueCommon/Follow/0.svg', 40 | '/enemies/BlueCommon/Follow/1.svg', 41 | '/enemies/BlueCommon/Follow/2.svg', 42 | '/enemies/BlueCommon/Die/0.svg', 43 | '/enemies/BlueCommon/Die/1.svg', 44 | '/enemies/BlueCommon/Die/2.svg', 45 | 46 | '/towers/FireTower/Base0.svg', 47 | '/towers/FireTower/Base1.svg', 48 | '/towers/FireTower/Base2.svg', 49 | '/towers/FireTower/Upgrade1.svg', 50 | '/towers/FireTower/Upgrade2.svg', 51 | '/towers/IceTower/Base0.svg', 52 | '/towers/IceTower/Base1.svg', 53 | '/towers/IceTower/Base2.svg', 54 | '/towers/IceTower/Upgrade1.svg', 55 | '/towers/IceTower/Upgrade2.svg', 56 | '/towers/PoisonTower/Base0.svg', 57 | '/towers/PoisonTower/Base1.svg', 58 | '/towers/PoisonTower/Base2.svg', 59 | '/towers/PoisonTower/Upgrade1.svg', 60 | '/towers/PoisonTower/Upgrade2.svg', 61 | '/towers/ThunderTower/Base0.svg', 62 | '/towers/ThunderTower/Base1.svg', 63 | '/towers/ThunderTower/Base2.svg', 64 | '/towers/ThunderTower/Upgrade1.svg', 65 | '/towers/ThunderTower/Upgrade2.svg', 66 | '/towers/Base.svg', 67 | '/towers/Upgrade0.svg', 68 | '/throne.svg', 69 | 70 | '/cursors/cursor-hammer.svg', 71 | '/cursors/cursor-arrow.svg', 72 | '/cursors/build0.svg', 73 | '/cursors/build1.svg', 74 | '/cursors/build2.svg' 75 | ]; 76 | -------------------------------------------------------------------------------- /src/lib/store/GameLoop.svelte.ts: -------------------------------------------------------------------------------- 1 | const MS_PER_UPDATE = 16.666; 2 | 3 | export class GameLoop { 4 | static lastCDId = 0; 5 | 6 | previousTime = $state(performance.now()); 7 | accumulator = $state(0.0); 8 | elapsedTime = $state(0.0); 9 | pauseState = $state(null); 10 | cooldowns = $state< 11 | Record< 12 | string, 13 | { 14 | startTime: number; 15 | waitTime: number; 16 | isInfinite: boolean; 17 | } 18 | > 19 | >({}); 20 | 21 | constructor() { 22 | this.loop = this.loop.bind(this); 23 | this.previousTime = 0; 24 | this.elapsedTime = 0; 25 | this.accumulator = 0; 26 | } 27 | 28 | get elapsedInSeconds() { 29 | return this.elapsedTime / 1000; 30 | } 31 | 32 | start(update) { 33 | this.update = update; 34 | 35 | requestAnimationFrame(this.loop); 36 | } 37 | 38 | reset() { 39 | this.previousTime = 0; 40 | this.elapsedTime = 0; 41 | this.accumulator = 0; 42 | } 43 | 44 | loop(currentTime: number) { 45 | if (this.pauseState) { 46 | this.previousTime = currentTime; 47 | requestAnimationFrame(this.loop); 48 | return; 49 | } 50 | 51 | let frameTime = currentTime - this.previousTime; 52 | 53 | if (frameTime > 250) { 54 | frameTime = 250; 55 | } 56 | 57 | this.previousTime = currentTime; 58 | this.accumulator += frameTime; 59 | this.elapsedTime += frameTime; 60 | 61 | while (this.accumulator >= MS_PER_UPDATE) { 62 | this.update(MS_PER_UPDATE); 63 | this.accumulator -= MS_PER_UPDATE; 64 | } 65 | 66 | requestAnimationFrame(this.loop); 67 | } 68 | 69 | setCD(waitTime: number, isInfinite = false) { 70 | const id = GameLoop.lastCDId++; 71 | 72 | this.cooldowns[id] = { 73 | waitTime, 74 | isInfinite, 75 | startTime: this.elapsedTime 76 | }; 77 | 78 | return id; 79 | } 80 | 81 | isCDReady(cooldownId: string): boolean { 82 | const cd = this.cooldowns[cooldownId]; 83 | if (!cd || this.pauseState) return false; 84 | 85 | const elapsedCD = this.elapsedTime - cd.startTime; 86 | 87 | if (elapsedCD <= cd.waitTime) { 88 | return false; 89 | } 90 | 91 | if (cd.isInfinite) { 92 | this.cooldowns[cooldownId] = { 93 | ...cd, 94 | startTime: this.elapsedTime 95 | }; 96 | return true; 97 | } else { 98 | delete this.cooldowns[cooldownId]; 99 | return true; 100 | } 101 | } 102 | 103 | pause() { 104 | if (!this.pauseState) { 105 | this.pauseState = { pausedTime: this.elapsedTime }; 106 | } else { 107 | this.resume(); 108 | } 109 | } 110 | 111 | resume() { 112 | if (!this.pauseState) return; 113 | 114 | const currentTime = performance.now(); 115 | 116 | this.previousTime = currentTime; 117 | 118 | this.pauseState = null; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/lib/components/HealthBar.svelte: -------------------------------------------------------------------------------- 1 | <script> 2 | import { cursor } from '$lib/store/Cursor.svelte'; 3 | 4 | const { entity } = $props(); 5 | 6 | const maxHealth = 500; 7 | const segments = 5; // 5 segments of 100 each 8 | 9 | // Calculate health percentage for width 10 | const healthPercentage = $derived( 11 | Math.max(0, Math.min(100, (entity.stats.health / maxHealth) * 100)) 12 | ); 13 | 14 | // Generate array of segment markers 15 | const segmentMarkers = Array.from({ length: segments - 1 }, (_, i) => (i + 1) * (100 / segments)); 16 | </script> 17 | 18 | <div class="health-bar-container" style:cursor={cursor.get('arrow')}> 19 | <div class="health-bar-background"> 20 | {#each segmentMarkers as marker} 21 | <div class="segment-marker" style:left="{marker}%" /> 22 | {/each} 23 | <div 24 | class="health-bar-fill" 25 | style:width="{healthPercentage}%" 26 | class:low={healthPercentage <= 20} 27 | class:medium={healthPercentage > 20 && healthPercentage <= 60} 28 | class:high={healthPercentage > 60} 29 | /> 30 | </div> 31 | </div> 32 | 33 | <style> 34 | .health-bar-container { 35 | bottom: 12px; 36 | position: absolute; 37 | width: var(--width); 38 | height: 24px; 39 | display: flex; 40 | flex-direction: column; 41 | align-items: center; 42 | } 43 | 44 | .health-bar-background { 45 | position: relative; 46 | width: 100%; 47 | height: 40px; 48 | background-color: rgba(0, 0, 0, 0.5); 49 | border-radius: 10px; 50 | overflow: hidden; 51 | } 52 | 53 | .segment-marker { 54 | position: absolute; 55 | top: 0; 56 | width: 2px; 57 | height: 100%; 58 | background-color: rgba(255, 255, 255, 0.3); 59 | z-index: 1; 60 | } 61 | 62 | .health-bar-fill { 63 | position: absolute; 64 | top: 0; 65 | left: 0; 66 | height: 100%; 67 | transition: 68 | width 0.3s ease-out, 69 | background-color 0.3s ease-out; 70 | z-index: 0; 71 | } 72 | 73 | .health-bar-fill.low { 74 | background-color: #ff4444; 75 | box-shadow: 0 0 10px #ff4444; 76 | } 77 | 78 | .health-bar-fill.medium { 79 | background-color: #ffff44; 80 | box-shadow: 0 0 10px #ffff44; 81 | } 82 | 83 | .health-bar-fill.high { 84 | background-color: #44ff44; 85 | box-shadow: 0 0 10px #44ff44; 86 | } 87 | 88 | .first-segment-highlight { 89 | position: absolute; 90 | top: 0; 91 | left: 0; 92 | height: 100%; 93 | background-color: rgba(195, 116, 20, 0.8); 94 | z-index: 2; 95 | animation: flashOnce 1s ease-out forwards; 96 | } 97 | 98 | @media (max-width: 768px) { 99 | .health-bar-container { 100 | bottom: 40px; 101 | } 102 | } 103 | 104 | @keyframes flashOnce { 105 | 0% { 106 | transform: scale(0); 107 | opacity: 0; 108 | } 109 | 50% { 110 | transform: scale(1.2); 111 | opacity: 0.8; 112 | } 113 | 100% { 114 | transform: scale(0); 115 | opacity: 0; 116 | } 117 | } 118 | </style> 119 | -------------------------------------------------------------------------------- /static/towers/FireTower/Base0.svg: -------------------------------------------------------------------------------- 1 | <svg width="68" height="107" viewBox="0 0 68 107" fill="none" xmlns="http://www.w3.org/2000/svg"> 2 | <path d="M1 56.3789H67V97.0017C67 101.972 62.9706 106.002 58 106.002H10C5.02944 106.002 1 101.972 1 97.0016V56.3789Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 3 | <path d="M1 45C1 40.0294 5.02944 36 10 36H58C62.9706 36 67 40.0294 67 45V81.8146C67 86.7852 62.9706 90.8146 58 90.8146H10C5.02944 90.8146 1 86.7852 1 81.8146V45Z" fill="url(#paint0_linear_2294_2246)" stroke="#350F42" stroke-width="2"/> 4 | <path d="M9 15.416H31V51.0002C31 53.2093 29.2091 55.0002 27 55.0002H13C10.7909 55.0002 9 53.2093 9 51.0002V15.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 5 | <rect x="9" y="1" width="22" height="22" rx="4" fill="url(#paint1_linear_2294_2246)" stroke="#350F42" stroke-width="2"/> 6 | <path d="M9 43.416H31V79.0002C31 81.2093 29.2091 83.0002 27 83.0002H13C10.7909 83.0002 9 81.2093 9 79.0002V43.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 7 | <rect x="9" y="29" width="22" height="22" rx="4" fill="url(#paint2_linear_2294_2246)" stroke="#350F42" stroke-width="2"/> 8 | <path d="M37 15.416H59V51.0002C59 53.2093 57.2091 55.0002 55 55.0002H41C38.7909 55.0002 37 53.2093 37 51.0002V15.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 9 | <rect x="37" y="1" width="22" height="22" rx="4" fill="url(#paint3_linear_2294_2246)" stroke="#350F42" stroke-width="2"/> 10 | <path d="M37 43.416H59V79.0002C59 81.2093 57.2091 83.0002 55 83.0002H41C38.7909 83.0002 37 81.2093 37 79.0002V43.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 11 | <rect x="37" y="29" width="22" height="22" rx="4" fill="url(#paint4_linear_2294_2246)" stroke="#350F42" stroke-width="2"/> 12 | <circle cx="33.5" cy="23.5" r="15.5" fill="#F5492B" stroke="#1E0528" stroke-width="2"/> 13 | <circle cx="28.8471" cy="17.1537" r="2.53846" fill="white"/> 14 | <circle cx="40.2692" cy="32.2692" r="1.26923" fill="#FF8C78"/> 15 | <defs> 16 | <linearGradient id="paint0_linear_2294_2246" x1="34" y1="35" x2="34" y2="91.8146" gradientUnits="userSpaceOnUse"> 17 | <stop stop-color="#8F90BD"/> 18 | <stop offset="1" stop-color="#A5A6CE"/> 19 | </linearGradient> 20 | <linearGradient id="paint1_linear_2294_2246" x1="20" y1="0" x2="20" y2="24" gradientUnits="userSpaceOnUse"> 21 | <stop stop-color="#8F90BD"/> 22 | <stop offset="1" stop-color="#A5A6CE"/> 23 | </linearGradient> 24 | <linearGradient id="paint2_linear_2294_2246" x1="20" y1="28" x2="20" y2="52" gradientUnits="userSpaceOnUse"> 25 | <stop stop-color="#8F90BD"/> 26 | <stop offset="1" stop-color="#A5A6CE"/> 27 | </linearGradient> 28 | <linearGradient id="paint3_linear_2294_2246" x1="48" y1="0" x2="48" y2="24" gradientUnits="userSpaceOnUse"> 29 | <stop stop-color="#8F90BD"/> 30 | <stop offset="1" stop-color="#A5A6CE"/> 31 | </linearGradient> 32 | <linearGradient id="paint4_linear_2294_2246" x1="48" y1="28" x2="48" y2="52" gradientUnits="userSpaceOnUse"> 33 | <stop stop-color="#8F90BD"/> 34 | <stop offset="1" stop-color="#A5A6CE"/> 35 | </linearGradient> 36 | </defs> 37 | </svg> 38 | -------------------------------------------------------------------------------- /static/towers/IceTower/Base0.svg: -------------------------------------------------------------------------------- 1 | <svg width="68" height="107" viewBox="0 0 68 107" fill="none" xmlns="http://www.w3.org/2000/svg"> 2 | <path d="M1 56.3789H67V97.0017C67 101.972 62.9706 106.002 58 106.002H10C5.02944 106.002 1 101.972 1 97.0016V56.3789Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 3 | <path d="M1 45C1 40.0294 5.02944 36 10 36H58C62.9706 36 67 40.0294 67 45V81.8146C67 86.7852 62.9706 90.8146 58 90.8146H10C5.02944 90.8146 1 86.7852 1 81.8146V45Z" fill="url(#paint0_linear_2240_1995)" stroke="#350F42" stroke-width="2"/> 4 | <path d="M9 15.416H31V51.0002C31 53.2093 29.2091 55.0002 27 55.0002H13C10.7909 55.0002 9 53.2093 9 51.0002V15.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 5 | <rect x="9" y="1" width="22" height="22" rx="4" fill="url(#paint1_linear_2240_1995)" stroke="#350F42" stroke-width="2"/> 6 | <path d="M9 43.416H31V79.0002C31 81.2093 29.2091 83.0002 27 83.0002H13C10.7909 83.0002 9 81.2093 9 79.0002V43.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 7 | <rect x="9" y="29" width="22" height="22" rx="4" fill="url(#paint2_linear_2240_1995)" stroke="#350F42" stroke-width="2"/> 8 | <path d="M37 15.416H59V51.0002C59 53.2093 57.2091 55.0002 55 55.0002H41C38.7909 55.0002 37 53.2093 37 51.0002V15.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 9 | <rect x="37" y="1" width="22" height="22" rx="4" fill="url(#paint3_linear_2240_1995)" stroke="#350F42" stroke-width="2"/> 10 | <path d="M37 43.416H59V79.0002C59 81.2093 57.2091 83.0002 55 83.0002H41C38.7909 83.0002 37 81.2093 37 79.0002V43.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 11 | <rect x="37" y="29" width="22" height="22" rx="4" fill="url(#paint4_linear_2240_1995)" stroke="#350F42" stroke-width="2"/> 12 | <circle cx="33.5" cy="23.5" r="15.5" fill="#9AD5FF" stroke="#1E0528" stroke-width="2"/> 13 | <circle cx="28.8471" cy="17.1537" r="2.53846" fill="white"/> 14 | <circle cx="40.2692" cy="32.2692" r="1.26923" fill="#E7E7F8"/> 15 | <defs> 16 | <linearGradient id="paint0_linear_2240_1995" x1="34" y1="35" x2="34" y2="91.8146" gradientUnits="userSpaceOnUse"> 17 | <stop stop-color="#8F90BD"/> 18 | <stop offset="1" stop-color="#A5A6CE"/> 19 | </linearGradient> 20 | <linearGradient id="paint1_linear_2240_1995" x1="20" y1="0" x2="20" y2="24" gradientUnits="userSpaceOnUse"> 21 | <stop stop-color="#8F90BD"/> 22 | <stop offset="1" stop-color="#A5A6CE"/> 23 | </linearGradient> 24 | <linearGradient id="paint2_linear_2240_1995" x1="20" y1="28" x2="20" y2="52" gradientUnits="userSpaceOnUse"> 25 | <stop stop-color="#8F90BD"/> 26 | <stop offset="1" stop-color="#A5A6CE"/> 27 | </linearGradient> 28 | <linearGradient id="paint3_linear_2240_1995" x1="48" y1="0" x2="48" y2="24" gradientUnits="userSpaceOnUse"> 29 | <stop stop-color="#8F90BD"/> 30 | <stop offset="1" stop-color="#A5A6CE"/> 31 | </linearGradient> 32 | <linearGradient id="paint4_linear_2240_1995" x1="48" y1="28" x2="48" y2="52" gradientUnits="userSpaceOnUse"> 33 | <stop stop-color="#8F90BD"/> 34 | <stop offset="1" stop-color="#A5A6CE"/> 35 | </linearGradient> 36 | </defs> 37 | </svg> 38 | -------------------------------------------------------------------------------- /static/towers/PoisonTower/Base0.svg: -------------------------------------------------------------------------------- 1 | <svg width="68" height="107" viewBox="0 0 68 107" fill="none" xmlns="http://www.w3.org/2000/svg"> 2 | <path d="M1 56.3789H67V97.0017C67 101.972 62.9706 106.002 58 106.002H10C5.02944 106.002 1 101.972 1 97.0016V56.3789Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 3 | <path d="M1 45C1 40.0294 5.02944 36 10 36H58C62.9706 36 67 40.0294 67 45V81.8146C67 86.7852 62.9706 90.8146 58 90.8146H10C5.02944 90.8146 1 86.7852 1 81.8146V45Z" fill="url(#paint0_linear_2294_2087)" stroke="#350F42" stroke-width="2"/> 4 | <path d="M9 15.416H31V51.0002C31 53.2093 29.2091 55.0002 27 55.0002H13C10.7909 55.0002 9 53.2093 9 51.0002V15.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 5 | <rect x="9" y="1" width="22" height="22" rx="4" fill="url(#paint1_linear_2294_2087)" stroke="#350F42" stroke-width="2"/> 6 | <path d="M9 43.416H31V79.0002C31 81.2093 29.2091 83.0002 27 83.0002H13C10.7909 83.0002 9 81.2093 9 79.0002V43.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 7 | <rect x="9" y="29" width="22" height="22" rx="4" fill="url(#paint2_linear_2294_2087)" stroke="#350F42" stroke-width="2"/> 8 | <path d="M37 15.416H59V51.0002C59 53.2093 57.2091 55.0002 55 55.0002H41C38.7909 55.0002 37 53.2093 37 51.0002V15.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 9 | <rect x="37" y="1" width="22" height="22" rx="4" fill="url(#paint3_linear_2294_2087)" stroke="#350F42" stroke-width="2"/> 10 | <path d="M37 43.416H59V79.0002C59 81.2093 57.2091 83.0002 55 83.0002H41C38.7909 83.0002 37 81.2093 37 79.0002V43.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 11 | <rect x="37" y="29" width="22" height="22" rx="4" fill="url(#paint4_linear_2294_2087)" stroke="#350F42" stroke-width="2"/> 12 | <circle cx="33.5" cy="23.5" r="15.5" fill="#6CE966" stroke="#1E0528" stroke-width="2"/> 13 | <circle cx="28.8471" cy="17.1537" r="2.53846" fill="white"/> 14 | <circle cx="40.2692" cy="32.2692" r="1.26923" fill="#E7E7F8"/> 15 | <defs> 16 | <linearGradient id="paint0_linear_2294_2087" x1="34" y1="35" x2="34" y2="91.8146" gradientUnits="userSpaceOnUse"> 17 | <stop stop-color="#8F90BD"/> 18 | <stop offset="1" stop-color="#A5A6CE"/> 19 | </linearGradient> 20 | <linearGradient id="paint1_linear_2294_2087" x1="20" y1="0" x2="20" y2="24" gradientUnits="userSpaceOnUse"> 21 | <stop stop-color="#8F90BD"/> 22 | <stop offset="1" stop-color="#A5A6CE"/> 23 | </linearGradient> 24 | <linearGradient id="paint2_linear_2294_2087" x1="20" y1="28" x2="20" y2="52" gradientUnits="userSpaceOnUse"> 25 | <stop stop-color="#8F90BD"/> 26 | <stop offset="1" stop-color="#A5A6CE"/> 27 | </linearGradient> 28 | <linearGradient id="paint3_linear_2294_2087" x1="48" y1="0" x2="48" y2="24" gradientUnits="userSpaceOnUse"> 29 | <stop stop-color="#8F90BD"/> 30 | <stop offset="1" stop-color="#A5A6CE"/> 31 | </linearGradient> 32 | <linearGradient id="paint4_linear_2294_2087" x1="48" y1="28" x2="48" y2="52" gradientUnits="userSpaceOnUse"> 33 | <stop stop-color="#8F90BD"/> 34 | <stop offset="1" stop-color="#A5A6CE"/> 35 | </linearGradient> 36 | </defs> 37 | </svg> 38 | -------------------------------------------------------------------------------- /static/towers/ThunderTower/Base0.svg: -------------------------------------------------------------------------------- 1 | <svg width="68" height="107" viewBox="0 0 68 107" fill="none" xmlns="http://www.w3.org/2000/svg"> 2 | <path d="M1 56.3789H67V97.0017C67 101.972 62.9706 106.002 58 106.002H10C5.02944 106.002 1 101.972 1 97.0016V56.3789Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 3 | <path d="M1 45C1 40.0294 5.02944 36 10 36H58C62.9706 36 67 40.0294 67 45V81.8146C67 86.7852 62.9706 90.8146 58 90.8146H10C5.02944 90.8146 1 86.7852 1 81.8146V45Z" fill="url(#paint0_linear_2238_1937)" stroke="#350F42" stroke-width="2"/> 4 | <path d="M9 15.416H31V51.0002C31 53.2093 29.2091 55.0002 27 55.0002H13C10.7909 55.0002 9 53.2093 9 51.0002V15.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 5 | <rect x="9" y="1" width="22" height="22" rx="4" fill="url(#paint1_linear_2238_1937)" stroke="#350F42" stroke-width="2"/> 6 | <path d="M9 43.416H31V79.0002C31 81.2093 29.2091 83.0002 27 83.0002H13C10.7909 83.0002 9 81.2093 9 79.0002V43.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 7 | <rect x="9" y="29" width="22" height="22" rx="4" fill="url(#paint2_linear_2238_1937)" stroke="#350F42" stroke-width="2"/> 8 | <path d="M37 15.416H59V51.0002C59 53.2093 57.2091 55.0002 55 55.0002H41C38.7909 55.0002 37 53.2093 37 51.0002V15.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 9 | <rect x="37" y="1" width="22" height="22" rx="4" fill="url(#paint3_linear_2238_1937)" stroke="#350F42" stroke-width="2"/> 10 | <path d="M37 43.416H59V79.0002C59 81.2093 57.2091 83.0002 55 83.0002H41C38.7909 83.0002 37 81.2093 37 79.0002V43.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 11 | <rect x="37" y="29" width="22" height="22" rx="4" fill="url(#paint4_linear_2238_1937)" stroke="#350F42" stroke-width="2"/> 12 | <circle cx="33.5" cy="23.5" r="15.5" fill="#FFF263" stroke="#1E0528" stroke-width="2"/> 13 | <circle cx="28.8471" cy="17.1537" r="2.53846" fill="white"/> 14 | <circle cx="40.2692" cy="32.2692" r="1.26923" fill="#FFFABD"/> 15 | <defs> 16 | <linearGradient id="paint0_linear_2238_1937" x1="34" y1="35" x2="34" y2="91.8146" gradientUnits="userSpaceOnUse"> 17 | <stop stop-color="#8F90BD"/> 18 | <stop offset="1" stop-color="#A5A6CE"/> 19 | </linearGradient> 20 | <linearGradient id="paint1_linear_2238_1937" x1="20" y1="0" x2="20" y2="24" gradientUnits="userSpaceOnUse"> 21 | <stop stop-color="#8F90BD"/> 22 | <stop offset="1" stop-color="#A5A6CE"/> 23 | </linearGradient> 24 | <linearGradient id="paint2_linear_2238_1937" x1="20" y1="28" x2="20" y2="52" gradientUnits="userSpaceOnUse"> 25 | <stop stop-color="#8F90BD"/> 26 | <stop offset="1" stop-color="#A5A6CE"/> 27 | </linearGradient> 28 | <linearGradient id="paint3_linear_2238_1937" x1="48" y1="0" x2="48" y2="24" gradientUnits="userSpaceOnUse"> 29 | <stop stop-color="#8F90BD"/> 30 | <stop offset="1" stop-color="#A5A6CE"/> 31 | </linearGradient> 32 | <linearGradient id="paint4_linear_2238_1937" x1="48" y1="28" x2="48" y2="52" gradientUnits="userSpaceOnUse"> 33 | <stop stop-color="#8F90BD"/> 34 | <stop offset="1" stop-color="#A5A6CE"/> 35 | </linearGradient> 36 | </defs> 37 | </svg> 38 | -------------------------------------------------------------------------------- /static/enemies/RedBlobElite/Die/2.svg: -------------------------------------------------------------------------------- 1 | <svg width="117" height="40" viewBox="0 0 117 40" fill="none" xmlns="http://www.w3.org/2000/svg"> 2 | <mask id="path-1-inside-1_2247_3392" fill="white"> 3 | <path d="M117 25.9246C117 40.2424 90.8087 39.9494 58.5 39.9494C26.1913 39.9494 0 41.5173 0 27.1996C0 12.8818 26.1913 0 58.5 0C90.8087 0 117 11.6068 117 25.9246Z"/> 4 | </mask> 5 | <path d="M117 25.9246C117 40.2424 90.8087 39.9494 58.5 39.9494C26.1913 39.9494 0 41.5173 0 27.1996C0 12.8818 26.1913 0 58.5 0C90.8087 0 117 11.6068 117 25.9246Z" fill="#9A2B4E"/> 6 | <path d="M115 25.9246C115 28.7502 113.772 30.8295 111.289 32.4954C108.688 34.2399 104.746 35.4888 99.5456 36.3319C89.1583 38.0161 74.7326 37.9494 58.5 37.9494V41.9494C74.5761 41.9494 89.4003 42.0291 100.186 40.2804C105.572 39.4071 110.197 38.0446 113.517 35.8171C116.954 33.5111 119 30.2579 119 25.9246H115ZM58.5 37.9494C42.2264 37.9494 27.8111 38.336 17.396 36.9608C12.1845 36.2728 8.25106 35.1714 5.6639 33.5402C3.21405 31.9956 2 30.0128 2 27.1996H-2C-2 31.5452 0.0598726 34.7356 3.53054 36.9238C6.8639 39.0255 11.4976 40.2168 16.8725 40.9264C27.6302 42.3468 42.4649 41.9494 58.5 41.9494V37.9494ZM2 27.1996C2 24.2633 3.33862 21.2345 6.0698 18.2627C8.80498 15.2865 12.8508 12.4705 17.9898 10.0384C28.2632 5.17637 42.5866 2 58.5 2V-2C42.1047 -2 27.1782 1.26454 16.2787 6.42286C10.8312 9.00095 6.30998 12.09 3.12464 15.556C-0.0647036 19.0264 -2 22.977 -2 27.1996H2ZM58.5 2C74.4422 2 88.779 4.86759 99.0554 9.42164C104.196 11.6998 108.232 14.3608 110.956 17.2144C113.67 20.0579 115 22.9979 115 25.9246H119C119 21.6924 117.056 17.8124 113.85 14.4527C110.653 11.1032 106.122 8.17789 100.676 5.76464C89.7796 0.935832 74.8665 -2 58.5 -2V2Z" fill="black" mask="url(#path-1-inside-1_2247_3392)"/> 7 | <circle cx="29.0256" cy="22.0198" r="10.8069" fill="url(#paint0_linear_2247_3392)"/> 8 | <circle cx="29.0324" cy="22.0344" r="6.82539" fill="#350F42"/> 9 | <circle cx="29.041" cy="22.0332" r="5.1348" fill="url(#paint1_linear_2247_3392)"/> 10 | <circle cx="29.0196" cy="22.0216" r="2.59777" fill="#350F42"/> 11 | <path d="M40.6141 22.0205C41.4022 22.0205 42.0491 21.3797 41.9629 20.5964C41.6407 17.6669 40.3312 14.9174 38.2274 12.8136C35.7856 10.3718 32.4738 9 29.0205 9C25.5673 9 22.2554 10.3718 19.8136 12.8136C17.7098 14.9174 16.4003 17.6669 16.0781 20.5964C15.9919 21.3797 16.6388 22.0205 17.4269 22.0205C18.215 22.0205 18.8435 21.3785 18.9537 20.5982C19.2604 18.4275 20.2634 16.3997 21.8316 14.8316C23.7382 12.9249 26.3241 11.8538 29.0205 11.8538C31.7169 11.8538 34.3028 12.9249 36.2095 14.8316C37.7776 16.3997 38.7806 18.4275 39.0873 20.5982C39.1975 21.3785 39.8261 22.0205 40.6141 22.0205Z" fill="#350F42"/> 12 | <circle cx="27.43" cy="19.4183" r="1.59802" fill="white"/> 13 | <circle cx="31.6284" cy="25.353" r="0.737761" fill="white"/> 14 | <defs> 15 | <linearGradient id="paint0_linear_2247_3392" x1="29.0256" y1="11.2129" x2="29.0256" y2="32.8266" gradientUnits="userSpaceOnUse"> 16 | <stop stop-color="#EBD6EB"/> 17 | <stop offset="0.9999" stop-color="white"/> 18 | </linearGradient> 19 | <linearGradient id="paint1_linear_2247_3392" x1="29.041" y1="16.8984" x2="29.041" y2="27.168" gradientUnits="userSpaceOnUse"> 20 | <stop stop-color="#853085"/> 21 | <stop offset="1" stop-color="#9D4A9D"/> 22 | </linearGradient> 23 | </defs> 24 | </svg> 25 | -------------------------------------------------------------------------------- /src/lib/components/GameArea.svelte: -------------------------------------------------------------------------------- 1 | <script> 2 | import { managers } from '$lib/store/managers.svelte'; 3 | import { cursor } from '$lib/store/Cursor.svelte'; 4 | import DynamicEntity from './DynamicEntity.svelte'; 5 | import StaticEntity from './StaticEntity.svelte'; 6 | import { lootTracker } from '$lib/store/LootTracker.svelte'; 7 | import HealthBar from './HealthBar.svelte'; 8 | 9 | const entityManager = $derived(managers.get('entityManager')); 10 | 11 | const onclick = (tower) => (e) => { 12 | e.stopPropagation(); 13 | if (tower.upgradeLevel === 2 || !tower.isUpgradable || cursor.inAnimation) { 14 | return; 15 | } 16 | 17 | lootTracker.spendLoot({ type: 'upgrade', payload: { tower } }); 18 | }; 19 | </script> 20 | 21 | <section style:cursor={cursor.get('arrow')}> 22 | {#each entityManager.loot as loot (loot.id)} 23 | <DynamicEntity entity={loot} --z-index={11} /> 24 | {/each} 25 | 26 | {#each entityManager.projectiles as projectile (projectile.id)} 27 | <DynamicEntity entity={projectile} --z-index={12} /> 28 | {/each} 29 | 30 | {#each entityManager.enemies as enemy (enemy.id)} 31 | <DynamicEntity entity={enemy} /> 32 | {/each} 33 | 34 | <div class="towers"> 35 | {#each entityManager.topTowers as tower, index (tower.id)} 36 | <div class="tower-wrapper"> 37 | <StaticEntity 38 | entity={tower} 39 | onclick={onclick(tower)} 40 | --cursor={cursor.get('hammer')} 41 | --z-index={10} 42 | --pointer-events={'auto'} 43 | --margin-left={index === 0 ? '12px' : '0'} 44 | --margin-right={index === 1 ? '12px' : '0'} 45 | /> 46 | </div> 47 | {/each} 48 | </div> 49 | 50 | <div class="throne"> 51 | <StaticEntity entity={entityManager.throne} --cursor={cursor.get('arrow')} /> 52 | <HealthBar entity={entityManager.throne} --width={`${entityManager.throne.width}px`} /> 53 | </div> 54 | 55 | <div class="towers"> 56 | {#each entityManager.bottomTowers as tower, index (tower.id)} 57 | <div class="tower-wrapper"> 58 | <StaticEntity 59 | entity={tower} 60 | onclick={onclick(tower)} 61 | --cursor={cursor.get('hammer')} 62 | --z-index={10} 63 | --pointer-events={'auto'} 64 | --margin-left={index === 0 ? '12px' : '0'} 65 | --margin-right={index === 1 ? '12px' : '0'} 66 | /> 67 | </div> 68 | {/each} 69 | </div> 70 | </section> 71 | 72 | <style> 73 | section { 74 | position: relative; 75 | display: flex; 76 | flex-direction: column; 77 | justify-content: center; 78 | align-items: center; 79 | align-items: center; 80 | width: 100%; 81 | height: 100%; 82 | will-change: transform; 83 | z-index: 4; 84 | } 85 | 86 | .towers { 87 | width: 100%; 88 | height: 100%; 89 | display: flex; 90 | align-items: center; 91 | justify-content: center; 92 | } 93 | 94 | .tower-wrapper { 95 | width: 100%; 96 | height: 100%; 97 | display: flex; 98 | justify-content: center; 99 | align-items: center; 100 | } 101 | 102 | .throne { 103 | position: relative; 104 | width: 100%; 105 | display: flex; 106 | flex-direction: column; 107 | justify-content: center; 108 | align-items: center; 109 | 110 | gap: -10px; 111 | } 112 | 113 | @media (max-width: 768px) { 114 | section { 115 | margin-top: 0; 116 | justify-content: end; 117 | } 118 | 119 | .towers { 120 | gap: 60px; 121 | } 122 | } 123 | </style> 124 | -------------------------------------------------------------------------------- /src/lib/config/entities/enemies.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Entity configurations for all game entities 3 | * @module entityConfig 4 | */ 5 | 6 | import type { Entity } from '$lib/store/Entity.svelte'; 7 | 8 | import { enemyCollider } from '../collisionHandlers'; 9 | 10 | type Stats = { 11 | health?: number; 12 | speed?: number; 13 | damage?: number; 14 | attackRange?: number; 15 | attackSpeed?: number; 16 | scale?: number; 17 | projectileNumber?: number; 18 | projectileType?: string; 19 | }; 20 | 21 | export interface EntityConfig { 22 | type: 'enemy' | 'tower' | 'projectile' | 'throne' | 'loot'; 23 | width: number; 24 | height: number; 25 | initialState: string; 26 | effects?: string[]; 27 | spriteSheet?: string; 28 | sprites?: any[]; 29 | upgrades?: string[]; 30 | onCollide?: (entity: Entity, target: Entity) => void; 31 | stats: Stats; 32 | } 33 | 34 | export const enemies: Record<string, EntityConfig> = { 35 | // Commons 36 | 37 | PurpleCommon: { 38 | type: 'enemy', 39 | initialState: 'FollowThrone', 40 | vfx: [], 41 | scale: 0.6, 42 | stateToAnimation: { 43 | FollowThrone: 'PurpleCommonFollow', 44 | Die: 'PurpleCommonDie' 45 | }, 46 | stats: { 47 | health: 20, 48 | speed: 0.03, 49 | damage: 5 50 | }, 51 | onCollide: enemyCollider 52 | }, 53 | YellowCommon: { 54 | type: 'enemy', 55 | initialState: 'FollowThrone', 56 | vfx: [], 57 | scale: 0.8, 58 | stateToAnimation: { 59 | FollowThrone: 'YellowCommonFollow', 60 | Die: 'YellowCommonDie' 61 | }, 62 | stats: { 63 | health: 15, 64 | speed: 0.025, 65 | damage: 4 66 | }, 67 | onCollide: enemyCollider 68 | }, 69 | BlueCommon: { 70 | type: 'enemy', 71 | initialState: 'FollowThrone', 72 | vfx: [], 73 | scale: 0.7, 74 | stateToAnimation: { 75 | FollowThrone: 'BlueCommonFollow', 76 | Die: 'BlueCommonDie' 77 | }, 78 | stats: { 79 | health: 25, 80 | speed: 0.02, 81 | damage: 8 82 | }, 83 | onCollide: enemyCollider 84 | }, 85 | 86 | // Elites 87 | 88 | GreenCircleElite: { 89 | type: 'enemy', 90 | initialState: 'FollowThrone', 91 | vfx: [], 92 | scale: 1, 93 | stateToAnimation: { 94 | FollowThrone: 'GreenCircleEliteFollow', 95 | Die: 'GreenCircleEliteDie' 96 | }, 97 | stats: { 98 | health: 80, 99 | speed: 0.01, 100 | damage: 15 101 | }, 102 | onCollide: enemyCollider 103 | }, 104 | 105 | RedBlobElite: { 106 | type: 'enemy', 107 | initialState: 'FollowThrone', 108 | vfx: [], 109 | scale: 0.8, 110 | stateToAnimation: { 111 | FollowThrone: 'RedBlobEliteFollow', 112 | Die: 'RedBlobEliteDie' 113 | }, 114 | stats: { 115 | health: 30, 116 | speed: 0.015, 117 | damage: 10 118 | }, 119 | onCollide: enemyCollider 120 | }, 121 | BlueBlobElite: { 122 | type: 'enemy', 123 | initialState: 'FollowThrone', 124 | vfx: [], 125 | scale: 1, 126 | stateToAnimation: { 127 | FollowThrone: 'BlueBlobEliteFollow', 128 | Die: 'BlueBlobEliteDie' 129 | }, 130 | stats: { 131 | health: 60, 132 | speed: 0.04, 133 | damage: 8 134 | }, 135 | onCollide: enemyCollider 136 | }, 137 | BlueCircleElite: { 138 | type: 'enemy', 139 | initialState: 'FollowThrone', 140 | vfx: [], 141 | scale: 1, 142 | stateToAnimation: { 143 | FollowThrone: 'BlueCircleEliteFollow', 144 | Die: 'BlueCircleEliteDie' 145 | }, 146 | stats: { 147 | health: 70, 148 | speed: 0.02, 149 | damage: 10 150 | }, 151 | onCollide: enemyCollider 152 | } 153 | }; 154 | -------------------------------------------------------------------------------- /src/lib/store/EntityManager.svelte.ts: -------------------------------------------------------------------------------- 1 | import { boundingBoxFromPoint, checkRectCollision } from '$lib/utils/math'; 2 | import type { Entity } from './Entity.svelte'; 3 | import type { Vector2 } from './Vector2.svelte'; 4 | 5 | export class EntityManager { 6 | entities = $state.raw<Entity[]>([]); 7 | 8 | enemies = $derived(this.entities.filter((entity) => entity.type === 'enemy')); 9 | livingEnemies = $derived(this.enemies.filter((entity) => entity.isInteractable)); 10 | 11 | towers = $derived(this.entities.filter((entity) => entity.type === 'tower')); 12 | topTowers = $derived(this.towers.filter((tower, index) => index <= 1)); 13 | bottomTowers = $derived(this.towers.filter((tower, index) => index >= 2)); 14 | projectiles = $derived(this.entities.filter((entity) => entity.type === 'projectile')); 15 | 16 | livingProjectiles = $derived(this.projectiles.filter((entity) => entity.isInteractable)); 17 | throne = $derived(this.entities.find((entity) => entity.type === 'throne')); 18 | 19 | loot = $derived(this.entities.filter((entity) => entity.type === 'loot')); 20 | livingLoot = $derived(this.loot.filter((entity) => entity.isInteractable)); 21 | 22 | update = (deltaTime: number) => { 23 | this.entities.forEach((entity) => entity.update(deltaTime)); 24 | }; 25 | 26 | reset = () => { 27 | this.entities = []; 28 | }; 29 | 30 | filterByName(name: string | string[]): Entity[] { 31 | return this.entities.filter((entity) => { 32 | if (Array.isArray(name)) { 33 | return name.includes(entity.name); 34 | } 35 | return entity.name === name; 36 | }); 37 | } 38 | 39 | findNearestEntity(source: Entity, targets: Entity[]): Entity | undefined { 40 | const validTargets = targets.filter( 41 | (target) => target.isInteractable 42 | // (target) => target.isInteractable && screen.isEntityInScreen(target) 43 | ); 44 | 45 | if (validTargets.length === 0) return undefined; 46 | 47 | const distances = validTargets.map((target) => source.position.distance(target.position)); 48 | const minDistance = Math.min(...distances); 49 | const index = distances.indexOf(minDistance); 50 | 51 | return validTargets[index]; 52 | } 53 | findNearestEntities(source: Entity, targets: Entity[], count: number = 3): Entity[] | undefined { 54 | const validTargets = targets.filter((target) => target.isInteractable); 55 | 56 | if (validTargets.length === 0) return undefined; 57 | 58 | const targetWithDistances = validTargets.map((target) => ({ 59 | target, 60 | distance: source.position.distance(target.position) 61 | })); 62 | targetWithDistances.sort((a, b) => a.distance - b.distance); 63 | const neartstCount = Math.min(count, targetWithDistances.length); 64 | 65 | return targetWithDistances.slice(0, neartstCount).map((target) => target.target); 66 | } 67 | 68 | findByPosition(position: Vector2): Entity[] | [] { 69 | const boundingBox = boundingBoxFromPoint(position, 100, 100); 70 | 71 | return this.entities.filter((entity) => checkRectCollision(entity.boundingBox, boundingBox)); 72 | } 73 | 74 | add = (entity: Entity | Entity[]) => { 75 | this.entities = [...this.entities, ...(Array.isArray(entity) ? entity : [entity])]; 76 | }; 77 | 78 | destroy = (entityId: number) => { 79 | this.entities = this.entities.filter((entity) => entity.id !== entityId); 80 | }; 81 | 82 | getByName = (name: string): Entity | undefined => { 83 | return this.entities.find((entity) => entity.name === name); 84 | }; 85 | 86 | getById = (entityId: number): Entity | undefined => { 87 | return this.entities.find((entity) => entity.id === entityId); 88 | }; 89 | } 90 | -------------------------------------------------------------------------------- /static/towers/FireTower/Upgrade1.svg: -------------------------------------------------------------------------------- 1 | <svg width="84" height="126" viewBox="0 0 84 126" fill="none" xmlns="http://www.w3.org/2000/svg"> 2 | <path d="M1 58.6719H83V115.237C83 120.207 78.9706 124.237 74 124.237H10C5.02944 124.237 1 120.207 1 115.237V58.6719Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 3 | <path d="M1 52C1 47.0294 5.02944 43 10 43H74C78.9706 43 83 47.0294 83 52V106.36C83 111.331 78.9706 115.36 74 115.36H10C5.02944 115.36 1 111.331 1 106.36V52Z" fill="url(#paint0_linear_2294_2292)" stroke="#350F42" stroke-width="2"/> 4 | <path d="M9 56.3789H75V97.0017C75 101.972 70.9706 106.002 66 106.002H18C13.0294 106.002 9 101.972 9 97.0016V56.3789Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 5 | <path d="M9 45C9 40.0294 13.0294 36 18 36H66C70.9706 36 75 40.0294 75 45V81.8146C75 86.7852 70.9706 90.8146 66 90.8146H18C13.0294 90.8146 9 86.7852 9 81.8146V45Z" fill="url(#paint1_linear_2294_2292)" stroke="#350F42" stroke-width="2"/> 6 | <path d="M17 15.416H39V51.0002C39 53.2093 37.2091 55.0002 35 55.0002H21C18.7909 55.0002 17 53.2093 17 51.0002V15.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 7 | <rect x="17" y="1" width="22" height="22" rx="4" fill="url(#paint2_linear_2294_2292)" stroke="#350F42" stroke-width="2"/> 8 | <path d="M17 43.416H39V79.0002C39 81.2093 37.2091 83.0002 35 83.0002H21C18.7909 83.0002 17 81.2093 17 79.0002V43.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 9 | <rect x="17" y="29" width="22" height="22" rx="4" fill="url(#paint3_linear_2294_2292)" stroke="#350F42" stroke-width="2"/> 10 | <path d="M45 15.416H67V51.0002C67 53.2093 65.2091 55.0002 63 55.0002H49C46.7909 55.0002 45 53.2093 45 51.0002V15.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 11 | <rect x="45" y="1" width="22" height="22" rx="4" fill="url(#paint4_linear_2294_2292)" stroke="#350F42" stroke-width="2"/> 12 | <path d="M45 43.416H67V79.0002C67 81.2093 65.2091 83.0002 63 83.0002H49C46.7909 83.0002 45 81.2093 45 79.0002V43.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 13 | <rect x="45" y="29" width="22" height="22" rx="4" fill="url(#paint5_linear_2294_2292)" stroke="#350F42" stroke-width="2"/> 14 | <circle cx="41.5" cy="23.5" r="15.5" fill="#F5492B" stroke="#1E0528" stroke-width="2"/> 15 | <circle cx="36.8471" cy="17.1537" r="2.53846" fill="white"/> 16 | <circle cx="48.2692" cy="32.2692" r="1.26923" fill="#FF8C78"/> 17 | <defs> 18 | <linearGradient id="paint0_linear_2294_2292" x1="42" y1="42" x2="42" y2="116.36" gradientUnits="userSpaceOnUse"> 19 | <stop stop-color="#8F90BD"/> 20 | <stop offset="1" stop-color="#A5A6CE"/> 21 | </linearGradient> 22 | <linearGradient id="paint1_linear_2294_2292" x1="42" y1="35" x2="42" y2="91.8146" gradientUnits="userSpaceOnUse"> 23 | <stop stop-color="#8F90BD"/> 24 | <stop offset="1" stop-color="#A5A6CE"/> 25 | </linearGradient> 26 | <linearGradient id="paint2_linear_2294_2292" x1="28" y1="0" x2="28" y2="24" gradientUnits="userSpaceOnUse"> 27 | <stop stop-color="#8F90BD"/> 28 | <stop offset="1" stop-color="#A5A6CE"/> 29 | </linearGradient> 30 | <linearGradient id="paint3_linear_2294_2292" x1="28" y1="28" x2="28" y2="52" gradientUnits="userSpaceOnUse"> 31 | <stop stop-color="#8F90BD"/> 32 | <stop offset="1" stop-color="#A5A6CE"/> 33 | </linearGradient> 34 | <linearGradient id="paint4_linear_2294_2292" x1="56" y1="0" x2="56" y2="24" gradientUnits="userSpaceOnUse"> 35 | <stop stop-color="#8F90BD"/> 36 | <stop offset="1" stop-color="#A5A6CE"/> 37 | </linearGradient> 38 | <linearGradient id="paint5_linear_2294_2292" x1="56" y1="28" x2="56" y2="52" gradientUnits="userSpaceOnUse"> 39 | <stop stop-color="#8F90BD"/> 40 | <stop offset="1" stop-color="#A5A6CE"/> 41 | </linearGradient> 42 | </defs> 43 | </svg> 44 | -------------------------------------------------------------------------------- /static/towers/IceTower/Upgrade1.svg: -------------------------------------------------------------------------------- 1 | <svg width="84" height="126" viewBox="0 0 84 126" fill="none" xmlns="http://www.w3.org/2000/svg"> 2 | <path d="M1 58.6719H83V115.237C83 120.207 78.9706 124.237 74 124.237H10C5.02944 124.237 1 120.207 1 115.237V58.6719Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 3 | <path d="M1 52C1 47.0294 5.02944 43 10 43H74C78.9706 43 83 47.0294 83 52V106.36C83 111.331 78.9706 115.36 74 115.36H10C5.02944 115.36 1 111.331 1 106.36V52Z" fill="url(#paint0_linear_2294_1884)" stroke="#350F42" stroke-width="2"/> 4 | <path d="M9 56.3789H75V97.0017C75 101.972 70.9706 106.002 66 106.002H18C13.0294 106.002 9 101.972 9 97.0016V56.3789Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 5 | <path d="M9 45C9 40.0294 13.0294 36 18 36H66C70.9706 36 75 40.0294 75 45V81.8146C75 86.7852 70.9706 90.8146 66 90.8146H18C13.0294 90.8146 9 86.7852 9 81.8146V45Z" fill="url(#paint1_linear_2294_1884)" stroke="#350F42" stroke-width="2"/> 6 | <path d="M17 15.416H39V51.0002C39 53.2093 37.2091 55.0002 35 55.0002H21C18.7909 55.0002 17 53.2093 17 51.0002V15.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 7 | <rect x="17" y="1" width="22" height="22" rx="4" fill="url(#paint2_linear_2294_1884)" stroke="#350F42" stroke-width="2"/> 8 | <path d="M17 43.416H39V79.0002C39 81.2093 37.2091 83.0002 35 83.0002H21C18.7909 83.0002 17 81.2093 17 79.0002V43.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 9 | <rect x="17" y="29" width="22" height="22" rx="4" fill="url(#paint3_linear_2294_1884)" stroke="#350F42" stroke-width="2"/> 10 | <path d="M45 15.416H67V51.0002C67 53.2093 65.2091 55.0002 63 55.0002H49C46.7909 55.0002 45 53.2093 45 51.0002V15.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 11 | <rect x="45" y="1" width="22" height="22" rx="4" fill="url(#paint4_linear_2294_1884)" stroke="#350F42" stroke-width="2"/> 12 | <path d="M45 43.416H67V79.0002C67 81.2093 65.2091 83.0002 63 83.0002H49C46.7909 83.0002 45 81.2093 45 79.0002V43.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 13 | <rect x="45" y="29" width="22" height="22" rx="4" fill="url(#paint5_linear_2294_1884)" stroke="#350F42" stroke-width="2"/> 14 | <circle cx="41.5" cy="23.5" r="15.5" fill="#9AD5FF" stroke="#1E0528" stroke-width="2"/> 15 | <circle cx="36.8471" cy="17.1537" r="2.53846" fill="white"/> 16 | <circle cx="48.2692" cy="32.2692" r="1.26923" fill="#E7E7F8"/> 17 | <defs> 18 | <linearGradient id="paint0_linear_2294_1884" x1="42" y1="42" x2="42" y2="116.36" gradientUnits="userSpaceOnUse"> 19 | <stop stop-color="#8F90BD"/> 20 | <stop offset="1" stop-color="#A5A6CE"/> 21 | </linearGradient> 22 | <linearGradient id="paint1_linear_2294_1884" x1="42" y1="35" x2="42" y2="91.8146" gradientUnits="userSpaceOnUse"> 23 | <stop stop-color="#8F90BD"/> 24 | <stop offset="1" stop-color="#A5A6CE"/> 25 | </linearGradient> 26 | <linearGradient id="paint2_linear_2294_1884" x1="28" y1="0" x2="28" y2="24" gradientUnits="userSpaceOnUse"> 27 | <stop stop-color="#8F90BD"/> 28 | <stop offset="1" stop-color="#A5A6CE"/> 29 | </linearGradient> 30 | <linearGradient id="paint3_linear_2294_1884" x1="28" y1="28" x2="28" y2="52" gradientUnits="userSpaceOnUse"> 31 | <stop stop-color="#8F90BD"/> 32 | <stop offset="1" stop-color="#A5A6CE"/> 33 | </linearGradient> 34 | <linearGradient id="paint4_linear_2294_1884" x1="56" y1="0" x2="56" y2="24" gradientUnits="userSpaceOnUse"> 35 | <stop stop-color="#8F90BD"/> 36 | <stop offset="1" stop-color="#A5A6CE"/> 37 | </linearGradient> 38 | <linearGradient id="paint5_linear_2294_1884" x1="56" y1="28" x2="56" y2="52" gradientUnits="userSpaceOnUse"> 39 | <stop stop-color="#8F90BD"/> 40 | <stop offset="1" stop-color="#A5A6CE"/> 41 | </linearGradient> 42 | </defs> 43 | </svg> 44 | -------------------------------------------------------------------------------- /static/towers/PoisonTower/Upgrade1.svg: -------------------------------------------------------------------------------- 1 | <svg width="84" height="126" viewBox="0 0 84 126" fill="none" xmlns="http://www.w3.org/2000/svg"> 2 | <path d="M1 58.6719H83V115.237C83 120.207 78.9706 124.237 74 124.237H10C5.02944 124.237 1 120.207 1 115.237V58.6719Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 3 | <path d="M1 52C1 47.0294 5.02944 43 10 43H74C78.9706 43 83 47.0294 83 52V106.36C83 111.331 78.9706 115.36 74 115.36H10C5.02944 115.36 1 111.331 1 106.36V52Z" fill="url(#paint0_linear_2294_2133)" stroke="#350F42" stroke-width="2"/> 4 | <path d="M9 56.3789H75V97.0017C75 101.972 70.9706 106.002 66 106.002H18C13.0294 106.002 9 101.972 9 97.0016V56.3789Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 5 | <path d="M9 45C9 40.0294 13.0294 36 18 36H66C70.9706 36 75 40.0294 75 45V81.8146C75 86.7852 70.9706 90.8146 66 90.8146H18C13.0294 90.8146 9 86.7852 9 81.8146V45Z" fill="url(#paint1_linear_2294_2133)" stroke="#350F42" stroke-width="2"/> 6 | <path d="M17 15.416H39V51.0002C39 53.2093 37.2091 55.0002 35 55.0002H21C18.7909 55.0002 17 53.2093 17 51.0002V15.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 7 | <rect x="17" y="1" width="22" height="22" rx="4" fill="url(#paint2_linear_2294_2133)" stroke="#350F42" stroke-width="2"/> 8 | <path d="M17 43.416H39V79.0002C39 81.2093 37.2091 83.0002 35 83.0002H21C18.7909 83.0002 17 81.2093 17 79.0002V43.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 9 | <rect x="17" y="29" width="22" height="22" rx="4" fill="url(#paint3_linear_2294_2133)" stroke="#350F42" stroke-width="2"/> 10 | <path d="M45 15.416H67V51.0002C67 53.2093 65.2091 55.0002 63 55.0002H49C46.7909 55.0002 45 53.2093 45 51.0002V15.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 11 | <rect x="45" y="1" width="22" height="22" rx="4" fill="url(#paint4_linear_2294_2133)" stroke="#350F42" stroke-width="2"/> 12 | <path d="M45 43.416H67V79.0002C67 81.2093 65.2091 83.0002 63 83.0002H49C46.7909 83.0002 45 81.2093 45 79.0002V43.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 13 | <rect x="45" y="29" width="22" height="22" rx="4" fill="url(#paint5_linear_2294_2133)" stroke="#350F42" stroke-width="2"/> 14 | <circle cx="41.5" cy="23.5" r="15.5" fill="#6CE966" stroke="#1E0528" stroke-width="2"/> 15 | <circle cx="36.8471" cy="17.1537" r="2.53846" fill="white"/> 16 | <circle cx="48.2692" cy="32.2692" r="1.26923" fill="#E7E7F8"/> 17 | <defs> 18 | <linearGradient id="paint0_linear_2294_2133" x1="42" y1="42" x2="42" y2="116.36" gradientUnits="userSpaceOnUse"> 19 | <stop stop-color="#8F90BD"/> 20 | <stop offset="1" stop-color="#A5A6CE"/> 21 | </linearGradient> 22 | <linearGradient id="paint1_linear_2294_2133" x1="42" y1="35" x2="42" y2="91.8146" gradientUnits="userSpaceOnUse"> 23 | <stop stop-color="#8F90BD"/> 24 | <stop offset="1" stop-color="#A5A6CE"/> 25 | </linearGradient> 26 | <linearGradient id="paint2_linear_2294_2133" x1="28" y1="0" x2="28" y2="24" gradientUnits="userSpaceOnUse"> 27 | <stop stop-color="#8F90BD"/> 28 | <stop offset="1" stop-color="#A5A6CE"/> 29 | </linearGradient> 30 | <linearGradient id="paint3_linear_2294_2133" x1="28" y1="28" x2="28" y2="52" gradientUnits="userSpaceOnUse"> 31 | <stop stop-color="#8F90BD"/> 32 | <stop offset="1" stop-color="#A5A6CE"/> 33 | </linearGradient> 34 | <linearGradient id="paint4_linear_2294_2133" x1="56" y1="0" x2="56" y2="24" gradientUnits="userSpaceOnUse"> 35 | <stop stop-color="#8F90BD"/> 36 | <stop offset="1" stop-color="#A5A6CE"/> 37 | </linearGradient> 38 | <linearGradient id="paint5_linear_2294_2133" x1="56" y1="28" x2="56" y2="52" gradientUnits="userSpaceOnUse"> 39 | <stop stop-color="#8F90BD"/> 40 | <stop offset="1" stop-color="#A5A6CE"/> 41 | </linearGradient> 42 | </defs> 43 | </svg> 44 | -------------------------------------------------------------------------------- /static/towers/ThunderTower/Upgrade1.svg: -------------------------------------------------------------------------------- 1 | <svg width="84" height="126" viewBox="0 0 84 126" fill="none" xmlns="http://www.w3.org/2000/svg"> 2 | <path d="M1 58.6719H83V115.237C83 120.207 78.9706 124.237 74 124.237H10C5.02944 124.237 1 120.207 1 115.237V58.6719Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 3 | <path d="M1 52C1 47.0294 5.02944 43 10 43H74C78.9706 43 83 47.0294 83 52V106.36C83 111.331 78.9706 115.36 74 115.36H10C5.02944 115.36 1 111.331 1 106.36V52Z" fill="url(#paint0_linear_2294_2451)" stroke="#350F42" stroke-width="2"/> 4 | <path d="M9 56.3789H75V97.0017C75 101.972 70.9706 106.002 66 106.002H18C13.0294 106.002 9 101.972 9 97.0016V56.3789Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 5 | <path d="M9 45C9 40.0294 13.0294 36 18 36H66C70.9706 36 75 40.0294 75 45V81.8146C75 86.7852 70.9706 90.8146 66 90.8146H18C13.0294 90.8146 9 86.7852 9 81.8146V45Z" fill="url(#paint1_linear_2294_2451)" stroke="#350F42" stroke-width="2"/> 6 | <path d="M17 15.416H39V51.0002C39 53.2093 37.2091 55.0002 35 55.0002H21C18.7909 55.0002 17 53.2093 17 51.0002V15.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 7 | <rect x="17" y="1" width="22" height="22" rx="4" fill="url(#paint2_linear_2294_2451)" stroke="#350F42" stroke-width="2"/> 8 | <path d="M17 43.416H39V79.0002C39 81.2093 37.2091 83.0002 35 83.0002H21C18.7909 83.0002 17 81.2093 17 79.0002V43.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 9 | <rect x="17" y="29" width="22" height="22" rx="4" fill="url(#paint3_linear_2294_2451)" stroke="#350F42" stroke-width="2"/> 10 | <path d="M45 15.416H67V51.0002C67 53.2093 65.2091 55.0002 63 55.0002H49C46.7909 55.0002 45 53.2093 45 51.0002V15.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 11 | <rect x="45" y="1" width="22" height="22" rx="4" fill="url(#paint4_linear_2294_2451)" stroke="#350F42" stroke-width="2"/> 12 | <path d="M45 43.416H67V79.0002C67 81.2093 65.2091 83.0002 63 83.0002H49C46.7909 83.0002 45 81.2093 45 79.0002V43.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 13 | <rect x="45" y="29" width="22" height="22" rx="4" fill="url(#paint5_linear_2294_2451)" stroke="#350F42" stroke-width="2"/> 14 | <circle cx="41.5" cy="23.5" r="15.5" fill="#FFF263" stroke="#1E0528" stroke-width="2"/> 15 | <circle cx="36.8471" cy="17.1537" r="2.53846" fill="white"/> 16 | <circle cx="48.2692" cy="32.2692" r="1.26923" fill="#E7E7F8"/> 17 | <defs> 18 | <linearGradient id="paint0_linear_2294_2451" x1="42" y1="42" x2="42" y2="116.36" gradientUnits="userSpaceOnUse"> 19 | <stop stop-color="#8F90BD"/> 20 | <stop offset="1" stop-color="#A5A6CE"/> 21 | </linearGradient> 22 | <linearGradient id="paint1_linear_2294_2451" x1="42" y1="35" x2="42" y2="91.8146" gradientUnits="userSpaceOnUse"> 23 | <stop stop-color="#8F90BD"/> 24 | <stop offset="1" stop-color="#A5A6CE"/> 25 | </linearGradient> 26 | <linearGradient id="paint2_linear_2294_2451" x1="28" y1="0" x2="28" y2="24" gradientUnits="userSpaceOnUse"> 27 | <stop stop-color="#8F90BD"/> 28 | <stop offset="1" stop-color="#A5A6CE"/> 29 | </linearGradient> 30 | <linearGradient id="paint3_linear_2294_2451" x1="28" y1="28" x2="28" y2="52" gradientUnits="userSpaceOnUse"> 31 | <stop stop-color="#8F90BD"/> 32 | <stop offset="1" stop-color="#A5A6CE"/> 33 | </linearGradient> 34 | <linearGradient id="paint4_linear_2294_2451" x1="56" y1="0" x2="56" y2="24" gradientUnits="userSpaceOnUse"> 35 | <stop stop-color="#8F90BD"/> 36 | <stop offset="1" stop-color="#A5A6CE"/> 37 | </linearGradient> 38 | <linearGradient id="paint5_linear_2294_2451" x1="56" y1="28" x2="56" y2="52" gradientUnits="userSpaceOnUse"> 39 | <stop stop-color="#8F90BD"/> 40 | <stop offset="1" stop-color="#A5A6CE"/> 41 | </linearGradient> 42 | </defs> 43 | </svg> 44 | -------------------------------------------------------------------------------- /static/towers/FireTower/Base1.svg: -------------------------------------------------------------------------------- 1 | <svg width="93" height="140" viewBox="0 0 93 140" fill="none" xmlns="http://www.w3.org/2000/svg"> 2 | <path d="M1 72.6719H92V129.237C92 134.207 87.9706 138.237 83 138.237H10C5.02944 138.237 1 134.207 1 129.237V72.6719Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 3 | <path d="M1 55C1 50.0294 5.02944 46 10 46H83C87.9706 46 92 50.0294 92 55V109.36C92 114.331 87.9706 118.36 83 118.36H10C5.02944 118.36 1 114.331 1 109.36V55Z" fill="url(#paint0_linear_2294_2267)" stroke="#350F42" stroke-width="2"/> 4 | <path d="M14 56.3789H80V97.0017C80 101.972 75.9706 106.002 71 106.002H23C18.0294 106.002 14 101.972 14 97.0016V56.3789Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 5 | <path d="M14 45C14 40.0294 18.0294 36 23 36H71C75.9706 36 80 40.0294 80 45V81.8146C80 86.7852 75.9706 90.8146 71 90.8146H23C18.0294 90.8146 14 86.7852 14 81.8146V45Z" fill="url(#paint1_linear_2294_2267)" stroke="#350F42" stroke-width="2"/> 6 | <path d="M22 15.416H44V51.0002C44 53.2093 42.2091 55.0002 40 55.0002H26C23.7909 55.0002 22 53.2093 22 51.0002V15.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 7 | <rect x="22" y="1" width="22" height="22" rx="4" fill="url(#paint2_linear_2294_2267)" stroke="#350F42" stroke-width="2"/> 8 | <path d="M22 43.416H44V79.0002C44 81.2093 42.2091 83.0002 40 83.0002H26C23.7909 83.0002 22 81.2093 22 79.0002V43.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 9 | <rect x="22" y="29" width="22" height="22" rx="4" fill="url(#paint3_linear_2294_2267)" stroke="#350F42" stroke-width="2"/> 10 | <path d="M50 15.416H72V51.0002C72 53.2093 70.2091 55.0002 68 55.0002H54C51.7909 55.0002 50 53.2093 50 51.0002V15.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 11 | <rect x="50" y="1" width="22" height="22" rx="4" fill="url(#paint4_linear_2294_2267)" stroke="#350F42" stroke-width="2"/> 12 | <path d="M50 43.416H72V79.0002C72 81.2093 70.2091 83.0002 68 83.0002H54C51.7909 83.0002 50 81.2093 50 79.0002V43.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 13 | <rect x="50" y="29" width="22" height="22" rx="4" fill="url(#paint5_linear_2294_2267)" stroke="#350F42" stroke-width="2"/> 14 | <circle cx="46.5" cy="23.5" r="15.5" fill="#F5492B" stroke="#1E0528" stroke-width="2"/> 15 | <circle cx="41.8471" cy="17.1537" r="2.53846" fill="white"/> 16 | <circle cx="53.2692" cy="32.2692" r="1.26923" fill="#FF8C78"/> 17 | <defs> 18 | <linearGradient id="paint0_linear_2294_2267" x1="46.5" y1="45" x2="46.5" y2="119.36" gradientUnits="userSpaceOnUse"> 19 | <stop stop-color="#8F90BD"/> 20 | <stop offset="1" stop-color="#A5A6CE"/> 21 | </linearGradient> 22 | <linearGradient id="paint1_linear_2294_2267" x1="47" y1="35" x2="47" y2="91.8146" gradientUnits="userSpaceOnUse"> 23 | <stop stop-color="#8F90BD"/> 24 | <stop offset="1" stop-color="#A5A6CE"/> 25 | </linearGradient> 26 | <linearGradient id="paint2_linear_2294_2267" x1="33" y1="0" x2="33" y2="24" gradientUnits="userSpaceOnUse"> 27 | <stop stop-color="#8F90BD"/> 28 | <stop offset="1" stop-color="#A5A6CE"/> 29 | </linearGradient> 30 | <linearGradient id="paint3_linear_2294_2267" x1="33" y1="28" x2="33" y2="52" gradientUnits="userSpaceOnUse"> 31 | <stop stop-color="#8F90BD"/> 32 | <stop offset="1" stop-color="#A5A6CE"/> 33 | </linearGradient> 34 | <linearGradient id="paint4_linear_2294_2267" x1="61" y1="0" x2="61" y2="24" gradientUnits="userSpaceOnUse"> 35 | <stop stop-color="#8F90BD"/> 36 | <stop offset="1" stop-color="#A5A6CE"/> 37 | </linearGradient> 38 | <linearGradient id="paint5_linear_2294_2267" x1="61" y1="28" x2="61" y2="52" gradientUnits="userSpaceOnUse"> 39 | <stop stop-color="#8F90BD"/> 40 | <stop offset="1" stop-color="#A5A6CE"/> 41 | </linearGradient> 42 | </defs> 43 | </svg> 44 | -------------------------------------------------------------------------------- /static/towers/IceTower/Base1.svg: -------------------------------------------------------------------------------- 1 | <svg width="93" height="140" viewBox="0 0 93 140" fill="none" xmlns="http://www.w3.org/2000/svg"> 2 | <path d="M1 72.6719H92V129.237C92 134.207 87.9706 138.237 83 138.237H10C5.02944 138.237 1 134.207 1 129.237V72.6719Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 3 | <path d="M1 55C1 50.0294 5.02944 46 10 46H83C87.9706 46 92 50.0294 92 55V109.36C92 114.331 87.9706 118.36 83 118.36H10C5.02944 118.36 1 114.331 1 109.36V55Z" fill="url(#paint0_linear_2294_1837)" stroke="#350F42" stroke-width="2"/> 4 | <path d="M14 56.3789H80V97.0017C80 101.972 75.9706 106.002 71 106.002H23C18.0294 106.002 14 101.972 14 97.0016V56.3789Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 5 | <path d="M14 45C14 40.0294 18.0294 36 23 36H71C75.9706 36 80 40.0294 80 45V81.8146C80 86.7852 75.9706 90.8146 71 90.8146H23C18.0294 90.8146 14 86.7852 14 81.8146V45Z" fill="url(#paint1_linear_2294_1837)" stroke="#350F42" stroke-width="2"/> 6 | <path d="M22 15.416H44V51.0002C44 53.2093 42.2091 55.0002 40 55.0002H26C23.7909 55.0002 22 53.2093 22 51.0002V15.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 7 | <rect x="22" y="1" width="22" height="22" rx="4" fill="url(#paint2_linear_2294_1837)" stroke="#350F42" stroke-width="2"/> 8 | <path d="M22 43.416H44V79.0002C44 81.2093 42.2091 83.0002 40 83.0002H26C23.7909 83.0002 22 81.2093 22 79.0002V43.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 9 | <rect x="22" y="29" width="22" height="22" rx="4" fill="url(#paint3_linear_2294_1837)" stroke="#350F42" stroke-width="2"/> 10 | <path d="M50 15.416H72V51.0002C72 53.2093 70.2091 55.0002 68 55.0002H54C51.7909 55.0002 50 53.2093 50 51.0002V15.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 11 | <rect x="50" y="1" width="22" height="22" rx="4" fill="url(#paint4_linear_2294_1837)" stroke="#350F42" stroke-width="2"/> 12 | <path d="M50 43.416H72V79.0002C72 81.2093 70.2091 83.0002 68 83.0002H54C51.7909 83.0002 50 81.2093 50 79.0002V43.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 13 | <rect x="50" y="29" width="22" height="22" rx="4" fill="url(#paint5_linear_2294_1837)" stroke="#350F42" stroke-width="2"/> 14 | <circle cx="46.5" cy="23.5" r="15.5" fill="#9AD5FF" stroke="#1E0528" stroke-width="2"/> 15 | <circle cx="41.8471" cy="17.1537" r="2.53846" fill="white"/> 16 | <circle cx="53.2692" cy="32.2692" r="1.26923" fill="#E7E7F8"/> 17 | <defs> 18 | <linearGradient id="paint0_linear_2294_1837" x1="46.5" y1="45" x2="46.5" y2="119.36" gradientUnits="userSpaceOnUse"> 19 | <stop stop-color="#8F90BD"/> 20 | <stop offset="1" stop-color="#A5A6CE"/> 21 | </linearGradient> 22 | <linearGradient id="paint1_linear_2294_1837" x1="47" y1="35" x2="47" y2="91.8146" gradientUnits="userSpaceOnUse"> 23 | <stop stop-color="#8F90BD"/> 24 | <stop offset="1" stop-color="#A5A6CE"/> 25 | </linearGradient> 26 | <linearGradient id="paint2_linear_2294_1837" x1="33" y1="0" x2="33" y2="24" gradientUnits="userSpaceOnUse"> 27 | <stop stop-color="#8F90BD"/> 28 | <stop offset="1" stop-color="#A5A6CE"/> 29 | </linearGradient> 30 | <linearGradient id="paint3_linear_2294_1837" x1="33" y1="28" x2="33" y2="52" gradientUnits="userSpaceOnUse"> 31 | <stop stop-color="#8F90BD"/> 32 | <stop offset="1" stop-color="#A5A6CE"/> 33 | </linearGradient> 34 | <linearGradient id="paint4_linear_2294_1837" x1="61" y1="0" x2="61" y2="24" gradientUnits="userSpaceOnUse"> 35 | <stop stop-color="#8F90BD"/> 36 | <stop offset="1" stop-color="#A5A6CE"/> 37 | </linearGradient> 38 | <linearGradient id="paint5_linear_2294_1837" x1="61" y1="28" x2="61" y2="52" gradientUnits="userSpaceOnUse"> 39 | <stop stop-color="#8F90BD"/> 40 | <stop offset="1" stop-color="#A5A6CE"/> 41 | </linearGradient> 42 | </defs> 43 | </svg> 44 | -------------------------------------------------------------------------------- /static/towers/PoisonTower/Base1.svg: -------------------------------------------------------------------------------- 1 | <svg width="93" height="140" viewBox="0 0 93 140" fill="none" xmlns="http://www.w3.org/2000/svg"> 2 | <path d="M1 72.6719H92V129.237C92 134.207 87.9706 138.237 83 138.237H10C5.02944 138.237 1 134.207 1 129.237V72.6719Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 3 | <path d="M1 55C1 50.0294 5.02944 46 10 46H83C87.9706 46 92 50.0294 92 55V109.36C92 114.331 87.9706 118.36 83 118.36H10C5.02944 118.36 1 114.331 1 109.36V55Z" fill="url(#paint0_linear_2294_2108)" stroke="#350F42" stroke-width="2"/> 4 | <path d="M14 56.3789H80V97.0017C80 101.972 75.9706 106.002 71 106.002H23C18.0294 106.002 14 101.972 14 97.0016V56.3789Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 5 | <path d="M14 45C14 40.0294 18.0294 36 23 36H71C75.9706 36 80 40.0294 80 45V81.8146C80 86.7852 75.9706 90.8146 71 90.8146H23C18.0294 90.8146 14 86.7852 14 81.8146V45Z" fill="url(#paint1_linear_2294_2108)" stroke="#350F42" stroke-width="2"/> 6 | <path d="M22 15.416H44V51.0002C44 53.2093 42.2091 55.0002 40 55.0002H26C23.7909 55.0002 22 53.2093 22 51.0002V15.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 7 | <rect x="22" y="1" width="22" height="22" rx="4" fill="url(#paint2_linear_2294_2108)" stroke="#350F42" stroke-width="2"/> 8 | <path d="M22 43.416H44V79.0002C44 81.2093 42.2091 83.0002 40 83.0002H26C23.7909 83.0002 22 81.2093 22 79.0002V43.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 9 | <rect x="22" y="29" width="22" height="22" rx="4" fill="url(#paint3_linear_2294_2108)" stroke="#350F42" stroke-width="2"/> 10 | <path d="M50 15.416H72V51.0002C72 53.2093 70.2091 55.0002 68 55.0002H54C51.7909 55.0002 50 53.2093 50 51.0002V15.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 11 | <rect x="50" y="1" width="22" height="22" rx="4" fill="url(#paint4_linear_2294_2108)" stroke="#350F42" stroke-width="2"/> 12 | <path d="M50 43.416H72V79.0002C72 81.2093 70.2091 83.0002 68 83.0002H54C51.7909 83.0002 50 81.2093 50 79.0002V43.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 13 | <rect x="50" y="29" width="22" height="22" rx="4" fill="url(#paint5_linear_2294_2108)" stroke="#350F42" stroke-width="2"/> 14 | <circle cx="46.5" cy="23.5" r="15.5" fill="#6CE966" stroke="#1E0528" stroke-width="2"/> 15 | <circle cx="41.8471" cy="17.1537" r="2.53846" fill="white"/> 16 | <circle cx="53.2692" cy="32.2692" r="1.26923" fill="#E7E7F8"/> 17 | <defs> 18 | <linearGradient id="paint0_linear_2294_2108" x1="46.5" y1="45" x2="46.5" y2="119.36" gradientUnits="userSpaceOnUse"> 19 | <stop stop-color="#8F90BD"/> 20 | <stop offset="1" stop-color="#A5A6CE"/> 21 | </linearGradient> 22 | <linearGradient id="paint1_linear_2294_2108" x1="47" y1="35" x2="47" y2="91.8146" gradientUnits="userSpaceOnUse"> 23 | <stop stop-color="#8F90BD"/> 24 | <stop offset="1" stop-color="#A5A6CE"/> 25 | </linearGradient> 26 | <linearGradient id="paint2_linear_2294_2108" x1="33" y1="0" x2="33" y2="24" gradientUnits="userSpaceOnUse"> 27 | <stop stop-color="#8F90BD"/> 28 | <stop offset="1" stop-color="#A5A6CE"/> 29 | </linearGradient> 30 | <linearGradient id="paint3_linear_2294_2108" x1="33" y1="28" x2="33" y2="52" gradientUnits="userSpaceOnUse"> 31 | <stop stop-color="#8F90BD"/> 32 | <stop offset="1" stop-color="#A5A6CE"/> 33 | </linearGradient> 34 | <linearGradient id="paint4_linear_2294_2108" x1="61" y1="0" x2="61" y2="24" gradientUnits="userSpaceOnUse"> 35 | <stop stop-color="#8F90BD"/> 36 | <stop offset="1" stop-color="#A5A6CE"/> 37 | </linearGradient> 38 | <linearGradient id="paint5_linear_2294_2108" x1="61" y1="28" x2="61" y2="52" gradientUnits="userSpaceOnUse"> 39 | <stop stop-color="#8F90BD"/> 40 | <stop offset="1" stop-color="#A5A6CE"/> 41 | </linearGradient> 42 | </defs> 43 | </svg> 44 | -------------------------------------------------------------------------------- /static/towers/ThunderTower/Base1.svg: -------------------------------------------------------------------------------- 1 | <svg width="93" height="140" viewBox="0 0 93 140" fill="none" xmlns="http://www.w3.org/2000/svg"> 2 | <path d="M1 72.6719H92V129.237C92 134.207 87.9706 138.237 83 138.237H10C5.02944 138.237 1 134.207 1 129.237V72.6719Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 3 | <path d="M1 55C1 50.0294 5.02944 46 10 46H83C87.9706 46 92 50.0294 92 55V109.36C92 114.331 87.9706 118.36 83 118.36H10C5.02944 118.36 1 114.331 1 109.36V55Z" fill="url(#paint0_linear_2294_2426)" stroke="#350F42" stroke-width="2"/> 4 | <path d="M14 56.3789H80V97.0017C80 101.972 75.9706 106.002 71 106.002H23C18.0294 106.002 14 101.972 14 97.0016V56.3789Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 5 | <path d="M14 45C14 40.0294 18.0294 36 23 36H71C75.9706 36 80 40.0294 80 45V81.8146C80 86.7852 75.9706 90.8146 71 90.8146H23C18.0294 90.8146 14 86.7852 14 81.8146V45Z" fill="url(#paint1_linear_2294_2426)" stroke="#350F42" stroke-width="2"/> 6 | <path d="M22 15.416H44V51.0002C44 53.2093 42.2091 55.0002 40 55.0002H26C23.7909 55.0002 22 53.2093 22 51.0002V15.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 7 | <rect x="22" y="1" width="22" height="22" rx="4" fill="url(#paint2_linear_2294_2426)" stroke="#350F42" stroke-width="2"/> 8 | <path d="M22 43.416H44V79.0002C44 81.2093 42.2091 83.0002 40 83.0002H26C23.7909 83.0002 22 81.2093 22 79.0002V43.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 9 | <rect x="22" y="29" width="22" height="22" rx="4" fill="url(#paint3_linear_2294_2426)" stroke="#350F42" stroke-width="2"/> 10 | <path d="M50 15.416H72V51.0002C72 53.2093 70.2091 55.0002 68 55.0002H54C51.7909 55.0002 50 53.2093 50 51.0002V15.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 11 | <rect x="50" y="1" width="22" height="22" rx="4" fill="url(#paint4_linear_2294_2426)" stroke="#350F42" stroke-width="2"/> 12 | <path d="M50 43.416H72V79.0002C72 81.2093 70.2091 83.0002 68 83.0002H54C51.7909 83.0002 50 81.2093 50 79.0002V43.416Z" fill="#8384AA" stroke="#350F42" stroke-width="2"/> 13 | <rect x="50" y="29" width="22" height="22" rx="4" fill="url(#paint5_linear_2294_2426)" stroke="#350F42" stroke-width="2"/> 14 | <circle cx="46.5" cy="23.5" r="15.5" fill="#FFF263" stroke="#1E0528" stroke-width="2"/> 15 | <circle cx="41.8471" cy="17.1537" r="2.53846" fill="white"/> 16 | <circle cx="53.2692" cy="32.2692" r="1.26923" fill="#E7E7F8"/> 17 | <defs> 18 | <linearGradient id="paint0_linear_2294_2426" x1="46.5" y1="45" x2="46.5" y2="119.36" gradientUnits="userSpaceOnUse"> 19 | <stop stop-color="#8F90BD"/> 20 | <stop offset="1" stop-color="#A5A6CE"/> 21 | </linearGradient> 22 | <linearGradient id="paint1_linear_2294_2426" x1="47" y1="35" x2="47" y2="91.8146" gradientUnits="userSpaceOnUse"> 23 | <stop stop-color="#8F90BD"/> 24 | <stop offset="1" stop-color="#A5A6CE"/> 25 | </linearGradient> 26 | <linearGradient id="paint2_linear_2294_2426" x1="33" y1="0" x2="33" y2="24" gradientUnits="userSpaceOnUse"> 27 | <stop stop-color="#8F90BD"/> 28 | <stop offset="1" stop-color="#A5A6CE"/> 29 | </linearGradient> 30 | <linearGradient id="paint3_linear_2294_2426" x1="33" y1="28" x2="33" y2="52" gradientUnits="userSpaceOnUse"> 31 | <stop stop-color="#8F90BD"/> 32 | <stop offset="1" stop-color="#A5A6CE"/> 33 | </linearGradient> 34 | <linearGradient id="paint4_linear_2294_2426" x1="61" y1="0" x2="61" y2="24" gradientUnits="userSpaceOnUse"> 35 | <stop stop-color="#8F90BD"/> 36 | <stop offset="1" stop-color="#A5A6CE"/> 37 | </linearGradient> 38 | <linearGradient id="paint5_linear_2294_2426" x1="61" y1="28" x2="61" y2="52" gradientUnits="userSpaceOnUse"> 39 | <stop stop-color="#8F90BD"/> 40 | <stop offset="1" stop-color="#A5A6CE"/> 41 | </linearGradient> 42 | </defs> 43 | </svg> 44 | -------------------------------------------------------------------------------- /src/lib/components/Gui/Pause.svelte: -------------------------------------------------------------------------------- 1 | <svg width="54" height="54" viewBox="0 0 54 54" fill="none" xmlns="http://www.w3.org/2000/svg"> 2 | <mask 3 | id="path-1-outside-1_0_1" 4 | maskUnits="userSpaceOnUse" 5 | x="0" 6 | y="0" 7 | width="54" 8 | height="54" 9 | fill="black" 10 | > 11 | <rect fill="white" width="54" height="54" /> 12 | <path 13 | d="M50.0898 37.7747C50.35 37.8961 50.66 37.7837 50.7762 37.5211C53.2068 32.0286 53.6627 25.8578 52.0564 20.0591C50.4038 14.0933 46.6768 8.91281 41.5457 5.44952C36.4146 1.98623 30.2161 0.467392 24.0651 1.16617C17.9142 1.86495 12.2144 4.7355 7.99089 9.26151C3.76741 13.7875 1.29741 19.672 1.02516 25.8565C0.7529 32.0411 2.69625 38.1198 6.50568 42.9995C10.3151 47.8791 15.7406 51.2394 21.8064 52.476C27.7021 53.6779 33.8267 52.7968 39.1382 49.9927C39.3922 49.8586 39.4829 49.5416 39.3438 49.2904C39.2047 49.0392 38.8884 48.9488 38.6343 49.0827C33.5395 51.767 27.6672 52.6094 22.0141 51.457C16.191 50.2698 10.9825 47.0439 7.32545 42.3595C3.6684 37.675 1.80279 31.8394 2.06415 25.9023C2.32552 19.9651 4.69671 14.316 8.75126 9.97105C12.8058 5.62608 18.2776 2.87036 24.1825 2.19953C30.0875 1.5287 36.038 2.98679 40.9639 6.31154C45.8897 9.6363 49.4677 14.6095 51.0542 20.3368C52.5943 25.8968 52.1595 31.8132 49.8335 37.0813C49.7175 37.344 49.8295 37.6532 50.0898 37.7747Z" 14 | /> 15 | </mask> 16 | <path 17 | d="M50.0898 37.7747C50.35 37.8961 50.66 37.7837 50.7762 37.5211C53.2068 32.0286 53.6627 25.8578 52.0564 20.0591C50.4038 14.0933 46.6768 8.91281 41.5457 5.44952C36.4146 1.98623 30.2161 0.467392 24.0651 1.16617C17.9142 1.86495 12.2144 4.7355 7.99089 9.26151C3.76741 13.7875 1.29741 19.672 1.02516 25.8565C0.7529 32.0411 2.69625 38.1198 6.50568 42.9995C10.3151 47.8791 15.7406 51.2394 21.8064 52.476C27.7021 53.6779 33.8267 52.7968 39.1382 49.9927C39.3922 49.8586 39.4829 49.5416 39.3438 49.2904C39.2047 49.0392 38.8884 48.9488 38.6343 49.0827C33.5395 51.767 27.6672 52.6094 22.0141 51.457C16.191 50.2698 10.9825 47.0439 7.32545 42.3595C3.6684 37.675 1.80279 31.8394 2.06415 25.9023C2.32552 19.9651 4.69671 14.316 8.75126 9.97105C12.8058 5.62608 18.2776 2.87036 24.1825 2.19953C30.0875 1.5287 36.038 2.98679 40.9639 6.31154C45.8897 9.6363 49.4677 14.6095 51.0542 20.3368C52.5943 25.8968 52.1595 31.8132 49.8335 37.0813C49.7175 37.344 49.8295 37.6532 50.0898 37.7747Z" 18 | fill="#D685DA" 19 | /> 20 | <path 21 | d="M50.0898 37.7747C50.35 37.8961 50.66 37.7837 50.7762 37.5211C53.2068 32.0286 53.6627 25.8578 52.0564 20.0591C50.4038 14.0933 46.6768 8.91281 41.5457 5.44952C36.4146 1.98623 30.2161 0.467392 24.0651 1.16617C17.9142 1.86495 12.2144 4.7355 7.99089 9.26151C3.76741 13.7875 1.29741 19.672 1.02516 25.8565C0.7529 32.0411 2.69625 38.1198 6.50568 42.9995C10.3151 47.8791 15.7406 51.2394 21.8064 52.476C27.7021 53.6779 33.8267 52.7968 39.1382 49.9927C39.3922 49.8586 39.4829 49.5416 39.3438 49.2904C39.2047 49.0392 38.8884 48.9488 38.6343 49.0827C33.5395 51.767 27.6672 52.6094 22.0141 51.457C16.191 50.2698 10.9825 47.0439 7.32545 42.3595C3.6684 37.675 1.80279 31.8394 2.06415 25.9023C2.32552 19.9651 4.69671 14.316 8.75126 9.97105C12.8058 5.62608 18.2776 2.87036 24.1825 2.19953C30.0875 1.5287 36.038 2.98679 40.9639 6.31154C45.8897 9.6363 49.4677 14.6095 51.0542 20.3368C52.5943 25.8968 52.1595 31.8132 49.8335 37.0813C49.7175 37.344 49.8295 37.6532 50.0898 37.7747Z" 22 | stroke="white" 23 | stroke-width="2" 24 | mask="url(#path-1-outside-1_0_1)" 25 | /> 26 | <circle cx="27" cy="27" r="16.5" fill="#D685DA" stroke="white" /> 27 | <circle cx="45" cy="44" r="4.5" fill="#D685DA" stroke="white" /> 28 | <rect x="20.5" y="20.5" width="5" height="13" rx="2.5" fill="#FFDFFC" stroke="white" /> 29 | <rect x="28.5" y="20.5" width="5" height="13" rx="2.5" fill="#FFDFFC" stroke="white" /> 30 | </svg> 31 | -------------------------------------------------------------------------------- /src/lib/store/SoundManager.svelte.ts: -------------------------------------------------------------------------------- 1 | const sounds = { 2 | bgSound: '/sound/bgSound.mp3', 3 | effects: { 4 | pickUp: '/sound/sfx/pickUp.m4a', 5 | towerUpgrade: '/sound/sfx/towerUpgrade.m4a', 6 | clickEnemy: '/sound/sfx/clickEnemy.m4a', 7 | towerShoot: '/sound/sfx/towerShoot.m4a', 8 | clickMenu: '/sound/sfx/clickMenu.m4a', 9 | lowResourse: '/sound/sfx/lowResourse.m4a' 10 | } 11 | }; 12 | 13 | const MUSIC_VOLUME = 0.3; 14 | const SFX_VOLUME = 0.2; 15 | 16 | export class SoundManager { 17 | sounds = $state.raw({}); 18 | isMuted = $state(false); 19 | playingEffectsCount = $state(0); 20 | 21 | // Resource loading states 22 | preloaded = $state(false); 23 | loadedCount = $state(0); 24 | totalResources = $derived(Object.keys(sounds.effects).length + 1); 25 | preloadPercent = $derived( 26 | Math.min(Math.floor((this.loadedCount / this.totalResources) * 100), 100) 27 | ); 28 | 29 | preload = async () => { 30 | const loadSound = (name, url) => { 31 | return new Promise((resolve, reject) => { 32 | const audio = new Audio(url); 33 | 34 | const onLoad = () => { 35 | this.sounds[name] = audio; 36 | this.sounds[name].volume = name === 'bgSound' ? MUSIC_VOLUME : SFX_VOLUME; 37 | this.sounds[name].loop = name === 'bgSound'; 38 | this.loadedCount += 1; 39 | audio.removeEventListener('canplaythrough', onLoad); 40 | resolve(); 41 | }; 42 | 43 | const onError = (error) => { 44 | audio.removeEventListener('error', onError); 45 | reject(error); 46 | }; 47 | 48 | audio.addEventListener('canplaythrough', onLoad); 49 | audio.addEventListener('error', onError); 50 | audio.load(); 51 | }); 52 | }; 53 | 54 | try { 55 | await loadSound('bgSound', sounds.bgSound); 56 | const effectPromises = Object.entries(sounds.effects).map(([name, url]) => 57 | loadSound(name, url) 58 | ); 59 | await Promise.all(effectPromises); 60 | this.preloaded = true; 61 | console.log('Sounds preloaded successfully.'); 62 | } catch (error) { 63 | console.error('Error preloading sounds:', error); 64 | throw error; 65 | } 66 | }; 67 | 68 | reduceBgVolume = () => { 69 | this.sounds.bgSound.volume = MUSIC_VOLUME - 0.2; 70 | }; 71 | 72 | restoreBgVolume = () => { 73 | this.sounds.bgSound.volume = MUSIC_VOLUME; 74 | }; 75 | 76 | play = (name, isImportant = false) => { 77 | if (this.isMuted) return; 78 | 79 | const audio = this.sounds[name]; 80 | if (!audio) return; 81 | 82 | if (name === 'bgSound') { 83 | audio.currentTime = 0; 84 | audio.play(); 85 | return; 86 | } 87 | 88 | // Disable sfx on Safari due to performance issues 89 | if (navigator.userAgent.includes('Safari') && !navigator.userAgent.includes('Chrome')) { 90 | return; 91 | } 92 | 93 | if (isImportant || this.playingEffectsCount < 10) { 94 | audio.currentTime = 0; 95 | audio.play(); 96 | const onEnded = () => { 97 | this.playingEffectsCount -= 1; 98 | audio.removeEventListener('ended', onEnded); 99 | }; 100 | audio.addEventListener('ended', onEnded); 101 | this.playingEffectsCount += 1; 102 | } 103 | }; 104 | 105 | pause(name) { 106 | const audio = this.sounds[name]; 107 | if (audio) { 108 | audio.pause(); 109 | } 110 | } 111 | 112 | toggleMute() { 113 | this.isMuted = !this.isMuted; 114 | 115 | if (this.isMuted) { 116 | this.pause('bgSound'); 117 | } else { 118 | this.play('bgSound'); 119 | } 120 | } 121 | 122 | reset() { 123 | if (this.isMuted) return; 124 | const bgSound = this.sounds['bgSound']; 125 | 126 | if (bgSound) { 127 | bgSound.currentTime = 0; 128 | bgSound.volume = MUSIC_VOLUME; 129 | bgSound.play(); 130 | } 131 | } 132 | } 133 | 134 | export const soundManager = new SoundManager(); 135 | -------------------------------------------------------------------------------- /static/projectiles/Poisonball/Follow/0.svg: -------------------------------------------------------------------------------- 1 | <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> 2 | <mask id="path-1-inside-1_2231_1974" fill="white"> 3 | <path fill-rule="evenodd" clip-rule="evenodd" d="M13.9624 5.65412C15.1484 5.25594 15.9985 4.17728 15.9985 2.90909C15.9985 1.30244 14.634 0 12.9509 0C11.4582 0 10.2161 1.02436 9.95417 2.37682C9.45285 2.2495 8.92613 2.18164 8.38281 2.18164C6.73102 2.18164 5.23268 2.80882 4.13492 3.82732C3.79721 3.70417 3.43067 3.63672 3.04762 3.63672C1.36447 3.63672 0 4.93916 0 6.54581C0 7.96251 1.06091 9.14268 2.46574 9.40193C3.00672 11.489 4.72794 13.1319 6.91451 13.6481C7.18689 14.9881 8.42279 15.9998 9.90625 15.9998C11.5894 15.9998 12.9539 14.6974 12.9539 13.0907C12.9539 12.7255 12.8834 12.376 12.7546 12.054C13.8213 11.0062 14.4781 9.57622 14.4781 7.99982C14.4781 7.16522 14.294 6.37165 13.9624 5.65412Z"/> 4 | </mask> 5 | <path fill-rule="evenodd" clip-rule="evenodd" d="M13.9624 5.65412C15.1484 5.25594 15.9985 4.17728 15.9985 2.90909C15.9985 1.30244 14.634 0 12.9509 0C11.4582 0 10.2161 1.02436 9.95417 2.37682C9.45285 2.2495 8.92613 2.18164 8.38281 2.18164C6.73102 2.18164 5.23268 2.80882 4.13492 3.82732C3.79721 3.70417 3.43067 3.63672 3.04762 3.63672C1.36447 3.63672 0 4.93916 0 6.54581C0 7.96251 1.06091 9.14268 2.46574 9.40193C3.00672 11.489 4.72794 13.1319 6.91451 13.6481C7.18689 14.9881 8.42279 15.9998 9.90625 15.9998C11.5894 15.9998 12.9539 14.6974 12.9539 13.0907C12.9539 12.7255 12.8834 12.376 12.7546 12.054C13.8213 11.0062 14.4781 9.57622 14.4781 7.99982C14.4781 7.16522 14.294 6.37165 13.9624 5.65412Z" fill="#67F144"/> 6 | <path d="M13.9624 5.65412L13.6441 4.70612L12.5868 5.06111L13.0546 6.07358L13.9624 5.65412ZM9.95417 2.37682L9.70802 3.34605L10.7345 3.60675L10.9359 2.56699L9.95417 2.37682ZM4.13492 3.82732L3.79234 4.76681L4.3668 4.97628L4.81506 4.5604L4.13492 3.82732ZM2.46574 9.40193L3.43375 9.15102L3.27386 8.53418L2.64721 8.41854L2.46574 9.40193ZM6.91451 13.6481L7.89446 13.4489L7.76701 12.8218L7.14427 12.6748L6.91451 13.6481ZM12.7546 12.054L12.0539 11.3406L11.579 11.8071L11.8261 12.4251L12.7546 12.054ZM14.9985 2.90909C14.9985 3.71025 14.4594 4.4324 13.6441 4.70612L14.2807 6.60212C15.8373 6.07949 16.9985 4.64432 16.9985 2.90909H14.9985ZM12.9509 1C14.1261 1 14.9985 1.89806 14.9985 2.90909H16.9985C16.9985 0.706829 15.142 -1 12.9509 -1V1ZM10.9359 2.56699C11.1024 1.70752 11.9136 1 12.9509 1V-1C11.0028 -1 9.32989 0.341205 8.97242 2.18665L10.9359 2.56699ZM8.38281 3.18164C8.84264 3.18164 9.28668 3.23904 9.70802 3.34605L10.2003 1.40759C9.61902 1.25996 9.00963 1.18164 8.38281 1.18164V3.18164ZM4.81506 4.5604C5.73072 3.71086 6.9871 3.18164 8.38281 3.18164V1.18164C6.47493 1.18164 4.73465 1.90678 3.45477 3.09424L4.81506 4.5604ZM3.04762 4.63672C3.31268 4.63672 3.56334 4.6833 3.79234 4.76681L4.4775 2.88783C4.03107 2.72504 3.54865 2.63672 3.04762 2.63672V4.63672ZM1 6.54581C1 5.53478 1.8724 4.63672 3.04762 4.63672V2.63672C0.85653 2.63672 -1 4.34355 -1 6.54581H1ZM2.64721 8.41854C1.67521 8.23916 1 7.43926 1 6.54581H-1C-1 8.48576 0.446603 10.0462 2.28426 10.3853L2.64721 8.41854ZM7.14427 12.6748C5.29972 12.2394 3.87687 10.8606 3.43375 9.15102L1.49773 9.65285C2.13658 12.1175 4.15616 14.0244 6.68474 14.6213L7.14427 12.6748ZM9.90625 14.9998C8.87558 14.9998 8.06768 14.301 7.89446 13.4489L5.93455 13.8473C6.3061 15.6752 7.97 16.9998 9.90625 16.9998V14.9998ZM11.9539 13.0907C11.9539 14.1018 11.0815 14.9998 9.90625 14.9998V16.9998C12.0973 16.9998 13.9539 15.293 13.9539 13.0907H11.9539ZM11.8261 12.4251C11.9086 12.6317 11.9539 12.8554 11.9539 13.0907H13.9539C13.9539 12.5956 13.8581 12.1203 13.6832 11.6829L11.8261 12.4251ZM13.4781 7.99982C13.4781 9.29035 12.942 10.4682 12.0539 11.3406L13.4554 12.7674C14.7005 11.5442 15.4781 9.86209 15.4781 7.99982H13.4781ZM13.0546 6.07358C13.3271 6.66323 13.4781 7.31399 13.4781 7.99982H15.4781C15.4781 7.01644 15.2608 6.08008 14.8702 5.23466L13.0546 6.07358Z" fill="#4ED82C" mask="url(#path-1-inside-1_2231_1974)"/> 7 | </svg> 8 | --------------------------------------------------------------------------------