├── 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 |
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 |
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 |
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 |
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 |
5 |
--------------------------------------------------------------------------------
/static/cursors/cursor-hammer.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/static/cursors/cursor-arrow.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/lib/components/PauseIcon.svelte:
--------------------------------------------------------------------------------
1 |
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 |
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 |
5 |
--------------------------------------------------------------------------------
/static/cursors/build4.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/static/cursors/build1.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/static/projectiles/Fireball/Follow/3.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/static/cursors/build2.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/static/projectiles/Fireball/Follow/1.svg:
--------------------------------------------------------------------------------
1 |
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 |
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 |
9 |
--------------------------------------------------------------------------------
/static/projectiles/Poisonball/Die/2.svg:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/static/projectiles/Thunderbolt/Die/2.svg:
--------------------------------------------------------------------------------
1 |
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 | 
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 |
7 |
--------------------------------------------------------------------------------
/static/projectiles/Poisonball/Follow/3.svg:
--------------------------------------------------------------------------------
1 |
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 |
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 |
10 |
--------------------------------------------------------------------------------
/static/projectiles/Icebolt/Follow/0.svg:
--------------------------------------------------------------------------------
1 |
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 |
14 |
--------------------------------------------------------------------------------
/static/projectiles/Poisonball/Die/1.svg:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/static/projectiles/Thunderbolt/Die/1.svg:
--------------------------------------------------------------------------------
1 |
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 |
8 |
--------------------------------------------------------------------------------
/static/projectiles/Icebolt/Die/0.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/static/projectiles/Thunderbolt/Die/0.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/static/projectiles/Thunderbolt/Follow/3.svg:
--------------------------------------------------------------------------------
1 |
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 |
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 |
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 |
15 |
--------------------------------------------------------------------------------
/src/lib/components/Bg1.svelte:
--------------------------------------------------------------------------------
1 |
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 |
43 |
44 |
64 |
--------------------------------------------------------------------------------
/src/lib/components/Bg3.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 |
63 |
--------------------------------------------------------------------------------
/src/lib/components/DynamicEntity.svelte:
--------------------------------------------------------------------------------
1 |
36 |
37 |
47 |
48 |
67 |
--------------------------------------------------------------------------------
/src/lib/components/Gui/StartScreen.svelte:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
16 |
17 |
18 |
Hint:
19 |
- Click on an Enemy to kill it
20 |
- Click on a gray spot to build a Tower
21 |
- Click on a Tower to upgrade
22 |
23 |
Manage your resources carefully if you want to survive until the last wave!
24 |
25 |
26 |
Upgrading towers costs 35
27 |
28 |
29 |
30 |
31 |
32 |
Enemy click costs 5
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
79 |
--------------------------------------------------------------------------------
/src/lib/components/Gui/PauseScreen.svelte:
--------------------------------------------------------------------------------
1 |
22 |
23 |
24 |
25 |
26 |
31 |
32 |
Paused
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
93 |
--------------------------------------------------------------------------------
/src/lib/components/Bg2.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 |
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 |
25 |
--------------------------------------------------------------------------------
/static/projectiles/Click/1.svg:
--------------------------------------------------------------------------------
1 |
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 |
7 |
8 |
88 |
--------------------------------------------------------------------------------
/static/loot.svg:
--------------------------------------------------------------------------------
1 |
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 |
35 |
--------------------------------------------------------------------------------
/src/lib/components/Gui/WinLoseScreen.svelte:
--------------------------------------------------------------------------------
1 |
22 |
23 |
24 |
25 |
{stageManager.stageResult === 'win' ? 'You win!' : 'You lose!'}
26 |
27 |
Enemies killed: {lootTracker.killsEnemy}
28 | Score: {lootTracker.score}
29 |
30 |
31 |
Hint:
32 |
- Click on an Enemy to kill it
33 |
- Click on a gray spot to build a Tower
34 |
- Click on a Tower to upgrade
35 |
36 |
Manage your resources carefully if you want to survive until the last wave!
37 |
38 |
39 |
Upgrading towers costs 35
40 |
41 |
42 |
43 |
44 |
45 |
Enemy click costs 5
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
97 |
--------------------------------------------------------------------------------
/static/projectiles/Click/2.svg:
--------------------------------------------------------------------------------
1 |
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 |
17 |
18 |
19 |
20 | {#each segmentMarkers as marker}
21 |
22 | {/each}
23 |
20 && healthPercentage <= 60}
28 | class:high={healthPercentage > 60}
29 | />
30 |
31 |
32 |
33 |
119 |
--------------------------------------------------------------------------------
/static/towers/FireTower/Base0.svg:
--------------------------------------------------------------------------------
1 |
38 |
--------------------------------------------------------------------------------
/static/towers/IceTower/Base0.svg:
--------------------------------------------------------------------------------
1 |
38 |
--------------------------------------------------------------------------------
/static/towers/PoisonTower/Base0.svg:
--------------------------------------------------------------------------------
1 |
38 |
--------------------------------------------------------------------------------
/static/towers/ThunderTower/Base0.svg:
--------------------------------------------------------------------------------
1 |
38 |
--------------------------------------------------------------------------------
/static/enemies/RedBlobElite/Die/2.svg:
--------------------------------------------------------------------------------
1 |
25 |
--------------------------------------------------------------------------------
/src/lib/components/GameArea.svelte:
--------------------------------------------------------------------------------
1 |
20 |
21 |
22 | {#each entityManager.loot as loot (loot.id)}
23 |
24 | {/each}
25 |
26 | {#each entityManager.projectiles as projectile (projectile.id)}
27 |
28 | {/each}
29 |
30 | {#each entityManager.enemies as enemy (enemy.id)}
31 |
32 | {/each}
33 |
34 |
35 | {#each entityManager.topTowers as tower, index (tower.id)}
36 |
37 |
46 |
47 | {/each}
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | {#each entityManager.bottomTowers as tower, index (tower.id)}
57 |
58 |
67 |
68 | {/each}
69 |
70 |
71 |
72 |
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
= {
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([]);
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 |
44 |
--------------------------------------------------------------------------------
/static/towers/IceTower/Upgrade1.svg:
--------------------------------------------------------------------------------
1 |
44 |
--------------------------------------------------------------------------------
/static/towers/PoisonTower/Upgrade1.svg:
--------------------------------------------------------------------------------
1 |
44 |
--------------------------------------------------------------------------------
/static/towers/ThunderTower/Upgrade1.svg:
--------------------------------------------------------------------------------
1 |
44 |
--------------------------------------------------------------------------------
/static/towers/FireTower/Base1.svg:
--------------------------------------------------------------------------------
1 |
44 |
--------------------------------------------------------------------------------
/static/towers/IceTower/Base1.svg:
--------------------------------------------------------------------------------
1 |
44 |
--------------------------------------------------------------------------------
/static/towers/PoisonTower/Base1.svg:
--------------------------------------------------------------------------------
1 |
44 |
--------------------------------------------------------------------------------
/static/towers/ThunderTower/Base1.svg:
--------------------------------------------------------------------------------
1 |
44 |
--------------------------------------------------------------------------------
/src/lib/components/Gui/Pause.svelte:
--------------------------------------------------------------------------------
1 |
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 |
8 |
--------------------------------------------------------------------------------