From 4f95714a927f9d70c7a67345c07deac15cffda7b Mon Sep 17 00:00:00 2001 From: Nyeogmi <economicsbat@gmail.com> Date: Sat, 1 Feb 2025 13:13:44 -0800 Subject: [PATCH] Initial commit --- .gitignore | 24 + index.html | 11 + package-lock.json | 906 ++++++++++++++++++ package.json | 15 + src/art/bgs/sampleroom1.png | Bin 0 -> 874 bytes src/art/characters/bat.png | Bin 0 -> 950 bytes src/art/characters/kobold.png | Bin 0 -> 835 bytes src/art/characters/raccoon walking 2.aseprite | Bin 0 -> 3761 bytes src/art/characters/raccoon.png | Bin 0 -> 840 bytes src/art/characters/raccoon_walking.png | Bin 0 -> 1734 bytes src/art/characters/robot.png | Bin 0 -> 772 bytes src/art/characters/snake.png | Bin 0 -> 885 bytes src/art/fonts/vga_8x16.png | Bin 0 -> 7267 bytes src/art/mapdata/sampleroom1.ldtk | 71 ++ src/assets.ts | 41 + src/clock.ts | 44 + src/colors.ts | 1 + src/counter.ts | 9 + src/font.ts | 146 +++ src/game.ts | 172 ++++ src/input.ts | 108 +++ src/main.ts | 87 ++ src/screen.ts | 62 ++ src/sprite.ts | 48 + src/sprites.ts | 14 + src/style.css | 12 + src/vite-env.d.ts | 1 + tsconfig.json | 24 + 28 files changed, 1796 insertions(+) create mode 100644 .gitignore create mode 100644 index.html create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/art/bgs/sampleroom1.png create mode 100644 src/art/characters/bat.png create mode 100644 src/art/characters/kobold.png create mode 100644 src/art/characters/raccoon walking 2.aseprite create mode 100644 src/art/characters/raccoon.png create mode 100644 src/art/characters/raccoon_walking.png create mode 100644 src/art/characters/robot.png create mode 100644 src/art/characters/snake.png create mode 100644 src/art/fonts/vga_8x16.png create mode 100644 src/art/mapdata/sampleroom1.ldtk create mode 100644 src/assets.ts create mode 100644 src/clock.ts create mode 100644 src/colors.ts create mode 100644 src/counter.ts create mode 100644 src/font.ts create mode 100644 src/game.ts create mode 100644 src/input.ts create mode 100644 src/main.ts create mode 100644 src/screen.ts create mode 100644 src/sprite.ts create mode 100644 src/sprites.ts create mode 100644 src/style.css create mode 100644 src/vite-env.d.ts create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/index.html b/index.html new file mode 100644 index 0000000..05ebd27 --- /dev/null +++ b/index.html @@ -0,0 +1,11 @@ +<!doctype html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <title>Prototype 2</title> + </head> + <body> + <canvas id="game" style="cursor: none"></canvas> + <script type="module" src="src/main.ts"></script> + </body> +</html> diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..c140d79 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,906 @@ +{ + "name": "prototype1", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "prototype1", + "version": "0.0.0", + "devDependencies": { + "typescript": "~5.6.2", + "vite": "^6.0.5" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", + "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz", + "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz", + "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz", + "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz", + "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz", + "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz", + "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz", + "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz", + "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz", + "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz", + "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz", + "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz", + "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz", + "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz", + "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz", + "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz", + "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz", + "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz", + "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz", + "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz", + "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz", + "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz", + "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz", + "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz", + "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.31.0.tgz", + "integrity": "sha512-9NrR4033uCbUBRgvLcBrJofa2KY9DzxL2UKZ1/4xA/mnTNyhZCWBuD8X3tPm1n4KxcgaraOYgrFKSgwjASfmlA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.31.0.tgz", + "integrity": "sha512-iBbODqT86YBFHajxxF8ebj2hwKm1k8PTBQSojSt3d1FFt1gN+xf4CowE47iN0vOSdnd+5ierMHBbu/rHc7nq5g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.31.0.tgz", + "integrity": "sha512-WHIZfXgVBX30SWuTMhlHPXTyN20AXrLH4TEeH/D0Bolvx9PjgZnn4H677PlSGvU6MKNsjCQJYczkpvBbrBnG6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.31.0.tgz", + "integrity": "sha512-hrWL7uQacTEF8gdrQAqcDy9xllQ0w0zuL1wk1HV8wKGSGbKPVjVUv/DEwT2+Asabf8Dh/As+IvfdU+H8hhzrQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.31.0.tgz", + "integrity": "sha512-S2oCsZ4hJviG1QjPY1h6sVJLBI6ekBeAEssYKad1soRFv3SocsQCzX6cwnk6fID6UQQACTjeIMB+hyYrFacRew==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.31.0.tgz", + "integrity": "sha512-pCANqpynRS4Jirn4IKZH4tnm2+2CqCNLKD7gAdEjzdLGbH1iO0zouHz4mxqg0uEMpO030ejJ0aA6e1PJo2xrPA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.31.0.tgz", + "integrity": "sha512-0O8ViX+QcBd3ZmGlcFTnYXZKGbFu09EhgD27tgTdGnkcYXLat4KIsBBQeKLR2xZDCXdIBAlWLkiXE1+rJpCxFw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.31.0.tgz", + "integrity": "sha512-w5IzG0wTVv7B0/SwDnMYmbr2uERQp999q8FMkKG1I+j8hpPX2BYFjWe69xbhbP6J9h2gId/7ogesl9hwblFwwg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.31.0.tgz", + "integrity": "sha512-JyFFshbN5xwy6fulZ8B/8qOqENRmDdEkcIMF0Zz+RsfamEW+Zabl5jAb0IozP/8UKnJ7g2FtZZPEUIAlUSX8cA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.31.0.tgz", + "integrity": "sha512-kpQXQ0UPFeMPmPYksiBL9WS/BDiQEjRGMfklVIsA0Sng347H8W2iexch+IEwaR7OVSKtr2ZFxggt11zVIlZ25g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.31.0.tgz", + "integrity": "sha512-pMlxLjt60iQTzt9iBb3jZphFIl55a70wexvo8p+vVFK+7ifTRookdoXX3bOsRdmfD+OKnMozKO6XM4zR0sHRrQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.31.0.tgz", + "integrity": "sha512-D7TXT7I/uKEuWiRkEFbed1UUYZwcJDU4vZQdPTcepK7ecPhzKOYk4Er2YR4uHKme4qDeIh6N3XrLfpuM7vzRWQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.31.0.tgz", + "integrity": "sha512-wal2Tc8O5lMBtoePLBYRKj2CImUCJ4UNGJlLwspx7QApYny7K1cUYlzQ/4IGQBLmm+y0RS7dwc3TDO/pmcneTw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.31.0.tgz", + "integrity": "sha512-O1o5EUI0+RRMkK9wiTVpk2tyzXdXefHtRTIjBbmFREmNMy7pFeYXCFGbhKFwISA3UOExlo5GGUuuj3oMKdK6JQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.31.0.tgz", + "integrity": "sha512-zSoHl356vKnNxwOWnLd60ixHNPRBglxpv2g7q0Cd3Pmr561gf0HiAcUBRL3S1vPqRC17Zo2CX/9cPkqTIiai1g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.31.0.tgz", + "integrity": "sha512-ypB/HMtcSGhKUQNiFwqgdclWNRrAYDH8iMYH4etw/ZlGwiTVxBz2tDrGRrPlfZu6QjXwtd+C3Zib5pFqID97ZA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.31.0.tgz", + "integrity": "sha512-JuhN2xdI/m8Hr+aVO3vspO7OQfUFO6bKLIRTAy0U15vmWjnZDLrEgCZ2s6+scAYaQVpYSh9tZtRijApw9IXyMw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.31.0.tgz", + "integrity": "sha512-U1xZZXYkvdf5MIWmftU8wrM5PPXzyaY1nGCI4KI4BFfoZxHamsIe+BtnPLIvvPykvQWlVbqUXdLa4aJUuilwLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.31.0.tgz", + "integrity": "sha512-ul8rnCsUumNln5YWwz0ted2ZHFhzhRRnkpBZ+YRuHoRAlUji9KChpOUOndY7uykrPEPXVbHLlsdo6v5yXo/TXw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true + }, + "node_modules/esbuild": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz", + "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.24.2", + "@esbuild/android-arm": "0.24.2", + "@esbuild/android-arm64": "0.24.2", + "@esbuild/android-x64": "0.24.2", + "@esbuild/darwin-arm64": "0.24.2", + "@esbuild/darwin-x64": "0.24.2", + "@esbuild/freebsd-arm64": "0.24.2", + "@esbuild/freebsd-x64": "0.24.2", + "@esbuild/linux-arm": "0.24.2", + "@esbuild/linux-arm64": "0.24.2", + "@esbuild/linux-ia32": "0.24.2", + "@esbuild/linux-loong64": "0.24.2", + "@esbuild/linux-mips64el": "0.24.2", + "@esbuild/linux-ppc64": "0.24.2", + "@esbuild/linux-riscv64": "0.24.2", + "@esbuild/linux-s390x": "0.24.2", + "@esbuild/linux-x64": "0.24.2", + "@esbuild/netbsd-arm64": "0.24.2", + "@esbuild/netbsd-x64": "0.24.2", + "@esbuild/openbsd-arm64": "0.24.2", + "@esbuild/openbsd-x64": "0.24.2", + "@esbuild/sunos-x64": "0.24.2", + "@esbuild/win32-arm64": "0.24.2", + "@esbuild/win32-ia32": "0.24.2", + "@esbuild/win32-x64": "0.24.2" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "node_modules/postcss": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz", + "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.31.0.tgz", + "integrity": "sha512-9cCE8P4rZLx9+PjoyqHLs31V9a9Vpvfo4qNcs6JCiGWYhw2gijSetFbH6SSy1whnkgcefnUwr8sad7tgqsGvnw==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.31.0", + "@rollup/rollup-android-arm64": "4.31.0", + "@rollup/rollup-darwin-arm64": "4.31.0", + "@rollup/rollup-darwin-x64": "4.31.0", + "@rollup/rollup-freebsd-arm64": "4.31.0", + "@rollup/rollup-freebsd-x64": "4.31.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.31.0", + "@rollup/rollup-linux-arm-musleabihf": "4.31.0", + "@rollup/rollup-linux-arm64-gnu": "4.31.0", + "@rollup/rollup-linux-arm64-musl": "4.31.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.31.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.31.0", + "@rollup/rollup-linux-riscv64-gnu": "4.31.0", + "@rollup/rollup-linux-s390x-gnu": "4.31.0", + "@rollup/rollup-linux-x64-gnu": "4.31.0", + "@rollup/rollup-linux-x64-musl": "4.31.0", + "@rollup/rollup-win32-arm64-msvc": "4.31.0", + "@rollup/rollup-win32-ia32-msvc": "4.31.0", + "@rollup/rollup-win32-x64-msvc": "4.31.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/vite": { + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.0.9.tgz", + "integrity": "sha512-MSgUxHcaXLtnBPktkbUSoQUANApKYuxZ6DrbVENlIorbhL2dZydTLaZ01tjUoE3szeFzlFk9ANOKk0xurh4MKA==", + "dev": true, + "dependencies": { + "esbuild": "^0.24.2", + "postcss": "^8.4.49", + "rollup": "^4.23.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..00fedad --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "prototype1", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "devDependencies": { + "typescript": "~5.6.2", + "vite": "^6.0.5" + } +} diff --git a/src/art/bgs/sampleroom1.png b/src/art/bgs/sampleroom1.png new file mode 100644 index 0000000000000000000000000000000000000000..921912328eb00ceb1977adb3cad38dd6eff49edf GIT binary patch literal 874 zcmeAS@N?(olHy`uVBq!ia0y~yU;#2295|SOBzMN7)eH=bt(nfw0iMpz3I#>^X_+~x z3=A3*YbV-z9Cna78XxRBv8&nDg~d2TT-QUiaEg}LL8naZEw2)aUW%J!dIZJDuwHL! zmhTQ_7f+9sjbU~Cz<lK9LC=e>UVq|)G6WwVEVRFOxBAXr=9a3NE7OjvEXbNRvB&kO zPg&@jDpv+&i31i&LdIV!vJ838e=OyfkAHv2=JfA7^B0GooXPW{^GLCexy~crM>BoB zpYQRPNpba5v{RpF@S^ERf6VmQy2mjE5f5fMD$kvx&L`B}YBufm3{l>x6|*dstdM%j zEjeBET=bl)MN>`RpNy9{k*Te@?y`&<k9UVMqoC0$jfFhiiBYCOJ}Q~^uO7-5{oyP+ z`9!O6Vlt!XiU~6IKji<syLQIk_0AFpkry=^-}VPC$QIAIlJoobjcwBE4gV!~T#EYV zB|GD<%HC_2oZKDmn-vt^z4XNPK@qz|%_UXmA{BdI#rf>_&cB$w$>G25h0V{tUAuGq zCG&)3>!#&5G&?7(ak%+f?)|&G_iyhTpZ!<IAn#f9X6eNxyMXb+S>O>_42&LO5N2eU zHAey{$X?><>&pI;QItVP^(ya0Ibg(9c)B=-RNQ)dYa>^Kf{4RKuKic{nl4gYu{Hi3 zze$r*<?}UGD=zPTutTmAs6T0j4I={s2ax3e#0;SL7Xac0AO@$24_iYbZcpF&baDRk z(7Ts^h%zudcpg~!s=Q>~ybE9W8613CJ#UCB08MZKYG?prur37<186QDgF#9hfS7@4 zf#+|d{SizI4I6^qiyORPW;js152(2TB*_BA3Lplth5?nLxF5G>+%gW_j2q@ozYcVQ toM_w6LfcL3K*uT6FaiSyw=?X@nYTO;E4s+fz8UBS22WQ%mvv4FO#qf@4@v+4 literal 0 HcmV?d00001 diff --git a/src/art/characters/bat.png b/src/art/characters/bat.png new file mode 100644 index 0000000000000000000000000000000000000000..3d51fb872571b11c4b0c24ecb8b79767fc974cb1 GIT binary patch literal 950 zcmV;n14;aeP)<h;3K|Lk000e1NJLTq002M$002M;1^@s6s%dfF0004nX+uL$Nkc;* zaB^>EX>4Tx04R}tkv&MmKpe$iQ$>-AgGEFHGgKEXNELCEDi*;)X)CnqU~=gfG-*gu zTpR`0f`cE6RR<SmT^(EnLGS~_*}+NCMN0f%QfLw5!Ery{-Fw`<1B7~+sb<F{plX(p zj77yvc2(?pMF1lhz&IijGxd0CF%8f0bq^n3@4`IG``n+SSIL<S@QK88OgAjz4dR(i zOXs{#9ArgFAwDObFzABBk6f2se&bwlSm2pKBb}Tl4ibxnHkR9%6%CbmnmDAW8s!Ta zmle)ioYiubHSft^7|3ZWDX!BTMhr`cBLNXIswklh3t?I{QcNUiKkDHha{LK$$>b`5 zkz)ZBsE`~#_#gc4*33^u+@wGh=zOv5k5Qm&7iiRM`}^3o8z+GO8Mx9~{z@H~{Up8C z(n3c--!^b@-O}Ve;Bp5Te9|RDawI=ZA)g1{&*+=7K>sb!v*z~J+{ftykfyGdZ-9eC zV5~sd>mKj!Ztv~iGtK^f03S(m?4{A9x&QzG32;bRa{vGf6951U69E94oEQKA00(qQ zO+^Rk0Tlo?8q-Dw_y7O^8FWQhbVF}#ZDnqB07G(RVRU6=Aa`kWXdp*PO;A^X4i^9b z0iQ`kK~#9!?U=o7!!Qtqp9BwK4PdC?1|C2os1+dBE)A8b1uu{y37}RRBm-oEs4zT$ z7f9v8OeKJ(sh<GxSz_J?7_dbG^4+~V9uFXd5JE&K(@d9Xru){V|5G4kH~<`)696Dq z5dfg5tH8C-etu6vsTZ}z+uu#&UC+I7z;7HSnF`^5G4!DhDC#O`^S-M_*eN3AoXMqt zx2J%sz?rHr*JBllodjaXjeQi_4zN?k>mq0xzN?Se)yErWU9i>{thF}^aH-&>`Mz4; zr>m>qtz$xb23{zjm3FM6PWd026C@iV@f0xg+l=@=T{*kOdq!V81%@s@cI4lhkH9+x zxRlV!ezQE|_3Itm=O=8IXMg$t2RG5rRU#h!N7Bv(`aa(F0eVW2SV$Y6s|0-)BkAFY zNS@^?(XLLgsH?91_P+TxLbmbdaaX1phKk{wQ$UX~M%rL01q>QLrrkA^qUYiOJvQ<~ zSK_%1M08gH8;bszefJ!YEdJ8?v6b7Fbl2V~3{43kgb+dqA%qY@2qA<JLI@#*5Hf#$ Y09YEs*_#bbTL1t607*qoM6N<$f@bQPW&i*H literal 0 HcmV?d00001 diff --git a/src/art/characters/kobold.png b/src/art/characters/kobold.png new file mode 100644 index 0000000000000000000000000000000000000000..d2f4f96fd4a4349ed37225cf70ac7a9759e4e9fa GIT binary patch literal 835 zcmV-J1HAl+P)<h;3K|Lk000e1NJLTq002M$002M;1^@s6s%dfF0004nX+uL$Nkc;* zaB^>EX>4Tx04R}tkv&MmKpe$iQ$>-AgGEFHGgKEXNELCEDi*;)X)CnqU~=gfG-*gu zTpR`0f`cE6RR<SmT^(EnLGS~_*}+NCMN0f%QfLw5!Ery{-Fw`<1B7~+sb<F{plX(p zj77yvc2(?pMF1lhz&IijGxd0CF%8f0bq^n3@4`IG``n+SSIL<S@QK88OgAjz4dR(i zOXs{#9ArgFAwDObFzABBk6f2se&bwlSm2pKBb}Tl4ibxnHkR9%6%CbmnmDAW8s!Ta zmle)ioYiubHSft^7|3ZWDX!BTMhr`cBLNXIswklh3t?I{QcNUiKkDHha{LK$$>b`5 zkz)ZBsE`~#_#gc4*33^u+@wGh=zOv5k5Qm&7iiRM`}^3o8z+GO8Mx9~{z@H~{Up8C z(n3c--!^b@-O}Ve;Bp5Te9|RDawI=ZA)g1{&*+=7K>sb!v*z~J+{ftykfyGdZ-9eC zV5~sd>mKj!Ztv~iGtK^f03S(m?4{A9x&QzG32;bRa{vGf6951U69E94oEQKA00(qQ zO+^Rk0Tlo<ByUjf761SM8FWQhbVF}#ZDnqB07G(RVRU6=Aa`kWXdp*PO;A^X4i^9b z0W3*GK~#9!?btC+!$1&);kQ=mG?D0>ThNe#)Jra4Xw0Rk)1X|yavch)G@Jo<NEC1Y zh6Y;}ib=(e75|?U$(ACm=I!ik6bS$T0Kk8$8ndb~n{?4*3?ZZlp?%)uAsh=}lZSA& z0sIbOh*t<%ibakU7xR^3zUtn;bmek1W~p}n_5D-J#eDF4b@QxBveX@s^a!*eoC@RY z0vMVF7f<`{`SNb9!{c6W*XyM7Ao<`qH4r^-gycayynML-CdeGtw4aZbi@Az2x`q%O zitl$p+x9NUQI5NBlB^}kygDKC+WoP}rd+(&5FC}6=i;~bH<Rgr@Yk(Vlu^5F?Q#ov zT|HKmv3v0<-YWp>@4R;iu0wFQt^3DdTK^XU0000000000004kwz5%Udc)7oqq~`zt N002ovPDHLkV1ft{Z*%|v literal 0 HcmV?d00001 diff --git a/src/art/characters/raccoon walking 2.aseprite b/src/art/characters/raccoon walking 2.aseprite new file mode 100644 index 0000000000000000000000000000000000000000..ed9497b2604245cacee47aff1d9a2806721e2f90 GIT binary patch literal 3761 zcmchZdrZ?;6vt2B1xs7SF@#Eg4mPp~uE1tMQ5YI@Nx=?KK}zh)=b+S5Cr;d|IK>SF z!;-=v5yz%kBvFi;nI@Bh4;FAS5hqh((D*>-Kh$XumEX?&wb+L|wg|gTZ%^;N=bn2% z_uTLA*DMC;{Xq!H_@RIW00#WJiyyE^BS`Tx8RZ5SV6fkxiRzC5PO$+56jolPfoWhj z<m(HHxC(#*qs5%DB!$Y(Sw#u@Km>9Kg4qzO)fbu4GE=j>oW1@$`GeUcT#hDld3BBd zC%~|#NDsiGaA>mLWVWEKL3{2Ni;37ZXiwL#*CIkz6WUQ`Tp-%rq+RE<9};^VV>6qY zZqCYB1i+q-?bJFCTjycx*P9D)%=<Vd!cbtu`Ptb{+d%9RVi)9Z#<ekpoS0~had{^G zM&MTlOW-3&0~H>z4mQIE$Oj8V<F650iXa2~Q%3O*Vf<C-r9-a`phu6zPzVK}W&DAh z0gl1qW?Qs2E|zIxiBbS~H)#6N+jy?k057U&`q@F6e(@aurw5?L==Lvc0EmB#?N#o! zhywtUT>w9yb+_qk04MhV1pjH$nzc+E4%Y0oYXKaG0RnOW0xtph=Q_DzAL)n2s{mQZ zN_i2W_FI5=*5TahQT8oFhnL*O9OL4rLWdcY9|?qoxQ|e1fcYvl)`r!2SZ5Y~f%Oo~ z!}?rt4%W64=dr$g;|@I&-MJLAaMwiG0X$G@x2`eEN9+_hJE_>QyJN=gj!PzSD1cA| zKr=~-4d4e{D6ZFQG#cUWM`=l`NTr!6eI(zdEwOCPxYE*P@9NDIfbK!$0z!gEu^<qF zJ^Y0rVEmJL_NR~O87Axf+<h+uzcn6V^?zt?cTjVc;=HiDXr-ubmEm;rFFaeW_EQe< zAEr-T$;`*b^#qMHAN*ylp%vv3ou(6SIg*6o9i;>9W~zRge9DFPTLZTb7bfs8v^y}U zM!7WXHec<a62KT}+Cy#&4*`dQ#A8}NJi6lgx?k^f)Av=KR_>wjy`?78=L>?{x(<GA zALtuA)L2{DB*}ZP(^%7!#GkE5y&Ha7k{8e=4nF@tlE1OaerMaVk6YRB<GqRxG%TWS zTt?lv7;X_0l8_Dc2r0x<C1kDE-sgif)oJB5bmR!mJ{M@!gBtU|?fq98I5mkv4#e%A z6_d_PF&bRw;UsPEh*9vHXH-F1<($eUzAdq=qKRLZcs!xc+}*SDVw7d8<tGTPUDZ!i zgokoF7Ft{of8UD5Ljo->Vl1{f0jCn%w?pho(@nftF1AvSjlM2hP0Kv$F74vmajh~` z>BDy+6BZ!Mi!aZ`SED&5E_*7kUoAN=QYDuMmsLjbTNN?V-S;`rCJWz`>OwmWLOd4j z4}9iyS|o=G6Q0Oz#UvR@yA=zSVR^Dp`GF8!*R1PbcBEUja3GOqn6-RFq=Mn>XR_53 zX<0>-xb@xsW{tAE=kMqXL#2x2Y7Fs}3^UoTn418Nch%eehg@Cm!_rBEh~S91>fFb? z_%lOE!Z?amB~ymgg_Jn@$W<0qFImaqKzw`Tf~78|fnH4ghM9^9)1>$_Vdb0lHXhlr z_u!Eo{o0L}&S~>Y9EyaR`NwK2L~toJ*Pi8~DuXvX&W7)zYISM=URP(LR>ha4261kF zGmyRW*5#A3XOcFmzxVlP^|vU|@hLg7?}H#zvdr#=D)yN3b(7Rf0DPe)Gus$lT1kQu zX_eNmrhTO~CN0u7(bAgn604`Q{%5RxW@?jE4o?HpuDhX5nyRkw$<%RFb@;}is`Bn# zrC(9i{bwdxRfnm1tLnd(fHsA44&I5QGLgQT1LxMOpSY<`q@H@E>M|dy<7p{*3n-i^ z<-Dq;`=*A!IWbx~S&t_-&PhwsY#*HC$f@(ue+{2+qsnR0Cy};^l2i99m8YD(d_{B0 M>Fc~wO`4+q1Dy{llK=n! literal 0 HcmV?d00001 diff --git a/src/art/characters/raccoon.png b/src/art/characters/raccoon.png new file mode 100644 index 0000000000000000000000000000000000000000..0f9273c1e24f17676830eb78aaf2d28a1ac6bf3c GIT binary patch literal 840 zcmV-O1GoH%P)<h;3K|Lk000e1NJLTq002M$002M;1^@s6s%dfF0004nX+uL$Nkc;* zaB^>EX>4Tx04R}tkv&MmKpe$iQ$>-AgGEFHGgKEXNELCEDi*;)X)CnqU~=gfG-*gu zTpR`0f`cE6RR<SmT^(EnLGS~_*}+NCMN0f%QfLw5!Ery{-Fw`<1B7~+sb<F{plX(p zj77yvc2(?pMF1lhz&IijGxd0CF%8f0bq^n3@4`IG``n+SSIL<S@QK88OgAjz4dR(i zOXs{#9ArgFAwDObFzABBk6f2se&bwlSm2pKBb}Tl4ibxnHkR9%6%CbmnmDAW8s!Ta zmle)ioYiubHSft^7|3ZWDX!BTMhr`cBLNXIswklh3t?I{QcNUiKkDHha{LK$$>b`5 zkz)ZBsE`~#_#gc4*33^u+@wGh=zOv5k5Qm&7iiRM`}^3o8z+GO8Mx9~{z@H~{Up8C z(n3c--!^b@-O}Ve;Bp5Te9|RDawI=ZA)g1{&*+=7K>sb!v*z~J+{ftykfyGdZ-9eC zV5~sd>mKj!Ztv~iGtK^f03S(m?4{A9x&QzHen~_@RCt{2*P&0tKorOEZwG$ha3qir z_AhM6Ei)5K76i%ufeBJaa0`wgNT#Y$hy^#Zbawp%K!9Nhf&>RB6gdPO8?4(*cSpkS zquQ&u_pa|=uOK2KA|fIp^6vyLCyRQCvDd9yF<3P2LeAx60E4*CAnt!B9rY4p$9!1z zF=@F{1;%gQ-uvLR++h61Et~ux?sL^SaUPHJ3b4s{J23$JU*p|Q?0!Gup$DXud%032 zM*?7`9xj>ll7pXJ-)hy0_R>pC&Xwv-Q>r)3&FImjEr=^#R(p7SU;vKpRscBLeW!GK z=_MBDJ-{XpP{;=q^1;-&OFkO!pW@^*81)ikkGR)+hvNs`2!Qjq9T#DwTFL@U9(L?! zuMxJ5kAu!4nQnnb*q+<b8)4hbI`jKloXjeq5w;gLv56$j`pXD_+gHQz!e3=L=>ciK zxX)`_JU!gIn0~Izw!o|sp7(#hvAGR=9*VMxLqtSGL_|bHL_|bHL_|bH<hKty8hy?S S*x5b+0000<MNUMnLSTY|$$u#T literal 0 HcmV?d00001 diff --git a/src/art/characters/raccoon_walking.png b/src/art/characters/raccoon_walking.png new file mode 100644 index 0000000000000000000000000000000000000000..0e6dd0b3bceeae3f3fe7c2e8ed4d4ae852dc0801 GIT binary patch literal 1734 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 literal 0 HcmV?d00001 diff --git a/src/art/characters/robot.png b/src/art/characters/robot.png new file mode 100644 index 0000000000000000000000000000000000000000..0867c769e21c75773d1dab3ec929ea8c69109633 GIT binary patch literal 772 zcmV+f1N;1mP)<h;3K|Lk000e1NJLTq002M$002M;1^@s6s%dfF0004nX+uL$Nkc;* zaB^>EX>4Tx04R}tkv&MmKpe$iQ$>-AgGEFHGgKEXNELCEDi*;)X)CnqU~=gfG-*gu zTpR`0f`cE6RR<SmT^(EnLGS~_*}+NCMN0f%QfLw5!Ery{-Fw`<1B7~+sb<F{plX(p zj77yvc2(?pMF1lhz&IijGxd0CF%8f0bq^n3@4`IG``n+SSIL<S@QK88OgAjz4dR(i zOXs{#9ArgFAwDObFzABBk6f2se&bwlSm2pKBb}Tl4ibxnHkR9%6%CbmnmDAW8s!Ta zmle)ioYiubHSft^7|3ZWDX!BTMhr`cBLNXIswklh3t?I{QcNUiKkDHha{LK$$>b`5 zkz)ZBsE`~#_#gc4*33^u+@wGh=zOv5k5Qm&7iiRM`}^3o8z+GO8Mx9~{z@H~{Up8C z(n3c--!^b@-O}Ve;Bp5Te9|RDawI=ZA)g1{&*+=7K>sb!v*z~J+{ftykfyGdZ-9eC zV5~sd>mKj!Ztv~iGtK^f03S(m?4{A9x&QzG32;bRa{vGf6951U69E94oEQKA00(qQ zO+^Rk0Tlo-H`&<CN&o-=8FWQhbVF}#ZDnqB07G(RVRU6=Aa`kWXdp*PO;A^X4i^9b z0PRUcK~#9!?bgu^gCG!q;ludOB2M5O9kX*d0gJeR^hJ!dkk%#~1pY5EqAz;K9UuY# z0000TGMXRr&idOwIIX6cZ0`wpyJGJyS34Dussb~u$9R)jL_`Kzj^}-x<zGkjp1YLv zx3&n0$YrdpO>zhP`Mo;UhCX(IRQ#zVm}xcJWA?*yX=f++WoPK30#b`#Q~UnSSy!L^ z;=4ei@B5hj>-{utH}F`{kz};j0^V64O4~bIRhg?Fglg;Yp+GAXJ^868;9UMSR>7z_ z^ReK=noz1LjK=K$8(bHsLI3~&0000000000klhO?{Ds3)#sp#j0000<MNUMnLSTZk C*-g>_ literal 0 HcmV?d00001 diff --git a/src/art/characters/snake.png b/src/art/characters/snake.png new file mode 100644 index 0000000000000000000000000000000000000000..59924e57b0c3feeefb7a04e9acc9b25275aeef97 GIT binary patch literal 885 zcmV-*1B(2KP)<h;3K|Lk000e1NJLTq002M$002M;1^@s6s%dfF0004nX+uL$Nkc;* zaB^>EX>4Tx04R}tkv&MmKpe$iQ$>-AgGEFHGgKEXNELCEDi*;)X)CnqU~=gfG-*gu zTpR`0f`cE6RR<SmT^(EnLGS~_*}+NCMN0f%QfLw5!Ery{-Fw`<1B7~+sb<F{plX(p zj77yvc2(?pMF1lhz&IijGxd0CF%8f0bq^n3@4`IG``n+SSIL<S@QK88OgAjz4dR(i zOXs{#9ArgFAwDObFzABBk6f2se&bwlSm2pKBb}Tl4ibxnHkR9%6%CbmnmDAW8s!Ta zmle)ioYiubHSft^7|3ZWDX!BTMhr`cBLNXIswklh3t?I{QcNUiKkDHha{LK$$>b`5 zkz)ZBsE`~#_#gc4*33^u+@wGh=zOv5k5Qm&7iiRM`}^3o8z+GO8Mx9~{z@H~{Up8C z(n3c--!^b@-O}Ve;Bp5Te9|RDawI=ZA)g1{&*+=7K>sb!v*z~J+{ftykfyGdZ-9eC zV5~sd>mKj!Ztv~iGtK^f03S(m?4{A9x&QzG32;bRa{vGf6951U69E94oEQKA00(qQ zO+^Rk0Tlo>685m&2><{98FWQhbVF}#ZDnqB07G(RVRU6=Aa`kWXdp*PO;A^X4i^9b z0bWT&K~#9!?bp3d!!Q&D;A5*|ho#+l3#PC#Ai-89rb@g)e_*JJl}A8A4B6O<#K77t z@Ca<|^^^xdiWv}<o2F_9@TKwjrZgSgb91lV07OJYL_|?i5{nu)bN=U%I#y8>pc=eN zq7W^q@B=kkT&K<e07@J=jW^ZE0&F971_p2;yFrHvw{4)(765?BeCjrh%ua5C#?4%5 z3+VR#uNH2nMqpF;Arjcn0-e1B)wmAuyTEtTIT!gY!4`e(cbDclPkShBfy2A!#l4Gh z7Q6@=LXyEfuG?qgCr>L>%jaJ3s*kJF?~8GU#&<xT_SSwknNM9bBM{}w(9a*6A(Ey0 zxAsZwBnZA>_wr<=%^$NDvA4mXBTzTvlHr3EVBH2+*AY={g3?buXaOXSw|}z<`r+UE z4=WwukAOs1=)Vl^hp!%Sb+a$M9}p1{5fKp)5fKp)5fKp)Q8Rr3BW!y3yFbVM00000 LNkvXXu0mjfw61-S literal 0 HcmV?d00001 diff --git a/src/art/fonts/vga_8x16.png b/src/art/fonts/vga_8x16.png new file mode 100644 index 0000000000000000000000000000000000000000..4d995ccf24dd72d30516974e0fbe287e11b506fb GIT binary patch literal 7267 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 F{5N)#^-TZ( literal 0 HcmV?d00001 diff --git a/src/art/mapdata/sampleroom1.ldtk b/src/art/mapdata/sampleroom1.ldtk new file mode 100644 index 0000000..d676f67 --- /dev/null +++ b/src/art/mapdata/sampleroom1.ldtk @@ -0,0 +1,71 @@ +{ + "__header__": { + "fileType": "LDtk Project JSON", + "app": "LDtk", + "doc": "https://ldtk.io/json", + "schema": "https://ldtk.io/files/JSON_SCHEMA.json", + "appAuthor": "Sebastien 'deepnight' Benard", + "appVersion": "1.5.3", + "url": "https://ldtk.io" + }, + "iid": "018dc430-c210-11ef-bffb-99d47c7356b6", + "jsonVersion": "1.5.3", + "appBuildId": 473703, + "nextUid": 1, + "identifierStyle": "Capitalize", + "toc": [], + "worldLayout": "Free", + "worldGridWidth": 256, + "worldGridHeight": 256, + "defaultLevelWidth": 256, + "defaultLevelHeight": 256, + "defaultPivotX": 0, + "defaultPivotY": 0, + "defaultGridSize": 16, + "defaultEntityWidth": 16, + "defaultEntityHeight": 16, + "bgColor": "#40465B", + "defaultLevelBgColor": "#696A79", + "minifyJson": false, + "externalLevels": false, + "exportTiled": false, + "simplifiedExport": false, + "imageExportMode": "None", + "exportLevelBg": true, + "pngFilePattern": null, + "backupOnSave": false, + "backupLimit": 10, + "backupRelPath": null, + "levelNamePattern": "Level_%idx", + "tutorialDesc": null, + "customCommands": [], + "flags": [], + "defs": { "layers": [], "entities": [], "tilesets": [], "enums": [], "externalEnums": [], "levelFields": [] }, + "levels": [ + { + "identifier": "Level_0", + "iid": "018deb40-c210-11ef-bffb-151cf6f68a2d", + "uid": 0, + "worldX": 0, + "worldY": 0, + "worldDepth": 0, + "pxWid": 256, + "pxHei": 256, + "__bgColor": "#696A79", + "bgColor": null, + "useAutoIdentifier": true, + "bgRelPath": null, + "bgPos": null, + "bgPivotX": 0.5, + "bgPivotY": 0.5, + "__smartColor": "#ADADB5", + "__bgPos": null, + "externalRelPath": null, + "fieldInstances": [], + "layerInstances": [], + "__neighbours": [] + } + ], + "worlds": [], + "dummyWorldIid": "018dc431-c210-11ef-bffb-cb15d2e5f164" +} \ No newline at end of file diff --git a/src/assets.ts b/src/assets.ts new file mode 100644 index 0000000..37822fa --- /dev/null +++ b/src/assets.ts @@ -0,0 +1,41 @@ +class Assets { + #images: Record<string, HTMLImageElement>; + + constructor() { + this.#images = {}; + } + + isLoaded(): boolean { + // you could use this, if so inclined, to check if a certain + // list of assets had been loaded prior to game start + // + // (to do so, you would call getImage() for each desired asset + // and then wait for isLoaded to return true) + for (let filename in this.#images) { + if (!this.#images[filename].complete) { + return false + } + } + + return true; + } + + getImage(filename: string): HTMLImageElement { + let element: HTMLImageElement; + if (this.#images[filename]) { + element = this.#images[filename]; + } else { + element = document.createElement("img"); + element.src = filename; + this.#images[filename] = element; + } + return element + } +} + +let active: Assets = new Assets(); + +export function getAssets(): Assets { + return active; +} + diff --git a/src/clock.ts b/src/clock.ts new file mode 100644 index 0000000..ba841fa --- /dev/null +++ b/src/clock.ts @@ -0,0 +1,44 @@ +const MAX_UPDATES_BANKED: number = 20.0; + +// always run physics at 240 hz +const UPDATES_PER_MS: number = 1/(1000.0/240.0); + +class Clock { + #lastTimestamp: number | undefined; + #updatesBanked: number + + constructor() { + this.#lastTimestamp = undefined; + this.#updatesBanked = 0.0 + } + + + recordTimestamp(timestamp: number) { + if (this.#lastTimestamp) { + let delta = timestamp - this.#lastTimestamp; + this.#bankDelta(delta); + } + this.#lastTimestamp = timestamp; + } + + popUpdate() { + // return true if we should do an update right now + // and remove one draw from the bank + if (this.#updatesBanked > 1) { + this.#updatesBanked -= 1; + return true + } + return false; + } + #bankDelta(delta: number) { + this.#updatesBanked = Math.min(delta * UPDATES_PER_MS, MAX_UPDATES_BANKED); + } +} + +let active: Clock = new Clock(); + +export function getClock(): Clock { + return active; +} + + diff --git a/src/colors.ts b/src/colors.ts new file mode 100644 index 0000000..3a58941 --- /dev/null +++ b/src/colors.ts @@ -0,0 +1 @@ +export const BG_OUTER = "#000"; \ No newline at end of file diff --git a/src/counter.ts b/src/counter.ts new file mode 100644 index 0000000..09e5afd --- /dev/null +++ b/src/counter.ts @@ -0,0 +1,9 @@ +export function setupCounter(element: HTMLButtonElement) { + let counter = 0 + const setCounter = (count: number) => { + counter = count + element.innerHTML = `count is ${counter}` + } + element.addEventListener('click', () => setCounter(counter + 1)) + setCounter(0) +} diff --git a/src/font.ts b/src/font.ts new file mode 100644 index 0000000..8935b44 --- /dev/null +++ b/src/font.ts @@ -0,0 +1,146 @@ +import {getAssets} from "./assets.ts"; +import fontSheet from './art/fonts/vga_8x16.png'; + +export enum AlignX { + Left = 0, + Center = 1, + Right = 2 +} + +export enum AlignY { + Top = 0, + Middle = 1, + Right = 2, +} + +class Font { + #filename: string; + #cx: number; + #cy: number; + #px: number; + #py: number; + #tintingCanvas: HTMLCanvasElement; + #tintedVersions: Record<string, HTMLImageElement>; + + constructor(filename: string, cx: number, cy: number, px: number, py: number) { + this.#filename = filename; + this.#cx = cx; + this.#cy = cy; + this.#px = px; + this.#py = py; + this.#tintingCanvas = document.createElement("canvas"); + this.#tintedVersions = {} + } + + #getTintedImage(color: string): HTMLImageElement | null { + let image = getAssets().getImage(this.#filename); + + if (!image.complete) { return null; } + + let tintedVersion = this.#tintedVersions[color]; + if (tintedVersion != undefined) { + return tintedVersion; + } + + let w = image.width; + let h = image.height; + + if (!(w == this.#cx * this.#px && h == this.#cy * this.#py)) { + throw `unexpected image dimensions for font ${this.#filename}: ${w} x ${h}` + } + + this.#tintingCanvas.width = w; + this.#tintingCanvas.height = h; + let ctx = this.#tintingCanvas.getContext("2d")!; + ctx.clearRect(0, 0, w, h); + ctx.drawImage(image, 0, 0); + ctx.globalCompositeOperation = "source-in"; + ctx.fillStyle = color; + ctx.rect(0, 0, w, h); + ctx.fill(); + + let result = new Image(); + result.src = this.#tintingCanvas.toDataURL(); + this.#tintedVersions[color] = result; + return result; + } + + drawText({ctx, text, x, y, alignX, alignY, forceWidth, color}: { + ctx: CanvasRenderingContext2D, + text: string, + x: number, y: number, alignX?: AlignX, alignY?: AlignY, + forceWidth?: number, color?: string + }) { + alignX = alignX == undefined ? AlignX.Left : alignX; + alignY = alignY == undefined ? AlignY.Top : alignY; + forceWidth = forceWidth == undefined ? 65535 : forceWidth; + color = color == undefined ? "#ffffff" : color; + + let image = this.#getTintedImage(color) + if (image == null) { + return; + } + + let wcx = Math.floor(forceWidth / this.#px); + + let sz = this.#glyphwise(text, wcx, () => {}); + let offsetX = x; + let offsetY = y; + offsetX += (alignX == AlignX.Left ? 0 : alignX == AlignX.Center ? -sz.w / 2 : - sz.w) + offsetY += (alignY == AlignY.Top ? 0 : alignY == AlignY.Middle ? -sz.h / 2 : - sz.h) + + this.#glyphwise(text, wcx, (cx, cy, char) => { + let srcIx = char.charCodeAt(0); + this.#drawGlyph({ctx: ctx, image: image, ix: srcIx, x: offsetX + cx * this.#px, y: offsetY + cy * this.#py}); + }) + } + + #drawGlyph({ctx, image, ix, x, y}: {ctx: CanvasRenderingContext2D, image: HTMLImageElement, ix: number, x: number, y: number}) { + let srcCx = ix % this.#cx; + let srcCy = Math.floor(ix / this.#cx); + let srcPx = srcCx * this.#px; + let srcPy = srcCy * this.#py; + ctx.drawImage( + image, + srcPx, srcPy, this.#px, this.#py, + Math.floor(x), Math.floor(y), this.#px, this.#py + ); + } + + measureText({text, forceWidth}: {text: string, forceWidth?: number}): {w: number, h: number} { + return this.#glyphwise(text, forceWidth, () => {}); + } + + #glyphwise(text: string, forceWidth: number | undefined, callback: (x: number, y: number, char: string) => void): {w: number, h: number} { + let cx = 0; + let cy = 0; + let cw = 0; + let ch = 0; + let wcx = forceWidth == undefined ? undefined : Math.floor(forceWidth / this.#px); + + for (let i = 0; i < text.length; i++) { + let char = text[i] + if (char == '\n') { + cx = 0; + cy += 1; + ch = cy + 1; + } else { + // console.log("cx, cy, char", [cx, cy, char]) + callback(cx, cy, char) + cx += 1; + cw = Math.max(cw, cx); + ch = cy + 1; + + if (wcx != undefined && cx > wcx) { + cx = 0; + cy += 1; + ch = cy + 1; + } + } + } + + return { w: cw * this.#px, h: ch * this.#py }; + } +} + +export let mainFont = new Font(fontSheet, 32, 8, 8, 16); \ No newline at end of file diff --git a/src/game.ts b/src/game.ts new file mode 100644 index 0000000..c6ddb95 --- /dev/null +++ b/src/game.ts @@ -0,0 +1,172 @@ +import {desiredHeight, desiredWidth, getScreen} from "./screen.ts"; +import {BG_OUTER} from "./colors.ts"; +import {mainFont} from "./font.ts"; +import {getInput} from "./input.ts"; + +type Point = {x: number, y: number} + +class MenuCamera { + // measured in whole screens + position: Point; + target: Point + + constructor({position, target}: {position: Point, target: Point}) { + this.position = position; + this.target = target; + } + + update() { + let adjust = (x0: number, x1: number) => { + if (Math.abs(x1 - x0) < 0.01) { return x1; } + return (x0 * 8 + x1 * 2) / 10; + } + this.position.x = adjust(this.position.x, this.target.x); + this.position.y = adjust(this.position.y, this.target.y); + } +} + +type GameState = "Overview" | "Gameplay" | "Thralls"; + +function getScreenLocation(state: GameState): {x: number, y: number} { + if (state === "Overview") { + return {x: 0.0, y: 0.0} + } + if (state === "Gameplay") { + return {x: 1.0, y: 0.0} + } + if (state === "Thralls") { + return {x: 0.0, y: 1.0} + } + + throw `invalid state: ${state}` +} + +export class Game { + msg: string; + camera: MenuCamera; + state: GameState; + + constructor() { + this.msg = "You have been given a gift."; + this.camera = new MenuCamera({ + position: {x: 0.0, y: 0.0}, + target: {x: 0.0, y: 0.0} + }); + this.state = "Overview"; + } + + update() { + if (getInput().isPressed("a") || getInput().isPressed("w")) { + this.state = "Overview" + } + if (getInput().isPressed("d")) { + this.state = "Gameplay" + } + if (getInput().isPressed("s")) { + this.state = "Thralls" + } + + if (getInput().isPressed("leftMouse")) { + this.msg = "Left click, asshole!" + } + if (getInput().isReleased("leftMouse")) { + this.msg = "Un-left click, asshole!" + } + + this.camera.target = getScreenLocation(this.state); + this.camera.update(); + } + + draw() { + let screen = getScreen(); + let ctx = screen.makeContext(); + + // draw screen background + ctx.fillStyle = BG_OUTER; + ctx.fillRect(0, 0, screen.w, screen.h); + + this.drawOverview(); + // this.drawGameplay(); + + // we draw all states at once and pan between them + // mainFont.drawText({ctx: ctx, text: "You have been given a gift.", x: 0, y: 0}) + let xy = this.getCameraMouseXy(); + if (xy != null) { + ctx = this.makeCameraContext(); + ctx.fillStyle = "#fff"; + ctx.strokeStyle = "#fff"; + ctx.fillRect(xy.x - 1, xy.y - 1, 3, 3); + } + } + + getCameraOffset(): Point { + let screen = getScreen(); + let {w, h} = screen; + return { + x: Math.floor(w * this.camera.position.x), + y: Math.floor(h * this.camera.position.y) + }; + } + + getCameraMouseXy(): Point | null { + let xy = getInput().mouseXy(); + if (xy == null) { + return null; + } + let {x: dx, y: dy} = this.getCameraOffset(); + return { + x: xy.x + dx, + y: xy.y + dy, + }; + } + + makeCameraContext() { + let screen = getScreen(); + let ctx = screen.makeContext(); + let {x, y} = this.getCameraOffset(); + + ctx.translate(-x, -y); + return ctx; + } + + getPaneRegionForGameState(gameState: GameState) { + let screen = getScreen(); + let {w, h} = screen; + let overallScreenLocation = getScreenLocation(gameState); + + let bigPaneX = overallScreenLocation.x * w; + let bigPaneY = overallScreenLocation.y * h; + let bigPaneW = w; + let bigPaneH = h; + + let smallPaneW = desiredWidth; + let smallPaneH = desiredHeight; + let smallPaneX = Math.floor(bigPaneX + (bigPaneW - smallPaneW) / 2) + let smallPaneY = Math.floor(bigPaneY + (bigPaneH - smallPaneH) / 2) + + return { + big: { + position: {x: bigPaneX, y: bigPaneY}, + size: {x: bigPaneW, y: bigPaneH} + }, + small: { + position: {x: smallPaneX, y: smallPaneY}, + size: {x: smallPaneW, y: smallPaneH} + } + } + + } + + drawOverview() { + let region = this.getPaneRegionForGameState("Overview") + let ctx = this.makeCameraContext() + ctx.translate(region.small.position.x, region.small.position.y) + + // ctx.strokeStyle = "#ffffff"; + // ctx.strokeRect(0.5, 0.5, region.small.size.x - 1, region.small.size.y - 1); + + mainFont.drawText({ctx: ctx, text: this.msg, x: 0, y: 0}) + } +} + +export let game = new Game(); diff --git a/src/input.ts b/src/input.ts new file mode 100644 index 0000000..bacf65b --- /dev/null +++ b/src/input.ts @@ -0,0 +1,108 @@ +import {getScreen} from "./screen.ts"; + +function handleKey(e: KeyboardEvent, down: boolean) { + active.handleDown(e.key, down); +} +function handleMouseOut() { + active.handleMouseMove(-1, -1); +} + +function handleMouseMove(canvas: HTMLCanvasElement, m: MouseEvent) { + if (canvas.offsetWidth == 0 || canvas.offsetHeight == 0) { + return; + } + active.handleMouseMove(m.offsetX / canvas.offsetWidth, m.offsetY / canvas.offsetHeight); +} + +function handleMouseButton(canvas: HTMLCanvasElement, m: MouseEvent, down: boolean) { + if (canvas.offsetWidth == 0 || canvas.offsetHeight == 0) { + return; + } + active.handleMouseMove(m.offsetX / canvas.offsetWidth, m.offsetY / canvas.offsetHeight); + let button: KnownButton | null = ( + m.button == 0 ? "leftMouse" : + m.button == 1 ? "rightMouse" : + null + ) + if (button != null) { + active.handleDown(button, down); + } +} + + +export function setupInput(canvas: HTMLCanvasElement) { + canvas.addEventListener("keyup", (k) => handleKey(k, false)); + document.addEventListener("keyup", (k) => handleKey(k, false)); + canvas.addEventListener("keydown", (k) => handleKey(k, true)); + document.addEventListener("keydown", (k) => handleKey(k, true)); + canvas.addEventListener("mouseout", (_) => handleMouseOut()); + canvas.addEventListener("mousemove", (m) => handleMouseMove(canvas, m)); + canvas.addEventListener("mousedown", (m) => handleMouseButton(canvas, m, true)); + canvas.addEventListener("mouseup", (m) => handleMouseButton(canvas, m, false)); +} + +type KnownKey = "w" | "a" | "s" | "d"; +type KnownButton = "leftMouse" | "rightMouse"; + +class Input { + #down: Record<string, boolean>; + #previousDown: Record<string, boolean>; + #mouseXy: {x: number, y: number} | null; + + constructor() { + this.#down = {}; + this.#previousDown = {}; + this.#mouseXy = null; + } + + update() { + this.#previousDown = {...this.#down}; + } + + handleDown(name: string, down: boolean) { + if (down) { + this.#down[name] = down; + } + else { + delete this.#down[name]; + } + } + + handleMouseMove(x: number, y: number) { + let screen = getScreen(); + if (x < 0.0 || x >= 1.0) { this.#mouseXy = null; } + if (y < 0.0 || y >= 1.0) { this.#mouseXy = null; } + + let w = screen.w; + let h = screen.h; + this.#mouseXy = { + x: Math.floor(x * w), + y: Math.floor(y * h), + } + } + + isDown(key: KnownKey | KnownButton) : boolean { + return this.#down[key]; + } + + isPressed(key: KnownKey | KnownButton) : boolean { + return this.#down[key] && !this.#previousDown[key]; + } + + isReleased(key: KnownKey | KnownButton) : boolean { + return !this.#down[key] && this.#previousDown[key]; + } + + mouseXy(): {x: number, y: number} | null { + if (this.#mouseXy == null) { + return null; + } + return {...this.#mouseXy} + } +} + +let active = new Input(); + +export function getInput(): Input { + return active; +} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..9139762 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,87 @@ +import './style.css' +import {pollAndTouch} from "./screen.ts"; +import {getClock} from "./clock.ts"; +import {game} from "./game.ts"; +import {getInput, setupInput} from "./input.ts"; +// import typescriptLogo from './typescript.svg' +// import viteLogo from '/vite.svg' +// import { setupCounter } from './counter.ts' + +// import {AlignX, mainFont} from "./font.ts"; + + +function setupGame() { + let gameCanvas = document.getElementById("game") as HTMLCanvasElement; + setupInput(gameCanvas); + onFrame(undefined); // start on-frame draw loop, set up screen +} + +function onFrame(timestamp: number | undefined) { + let gameCanvas = document.getElementById("game") as HTMLCanvasElement; + requestAnimationFrame(onFrame); + + if (timestamp) { + getClock().recordTimestamp(timestamp); + } + onFrameFixScreen(gameCanvas); + + while (getClock().popUpdate()) { + game.update(); + getInput().update(); + } + + game.draw(); + + /* + let ctx = getScreen().canvas.getContext("2d")!; + ctx.fillStyle = "#000"; + ctx.fillRect(0, 0, gameCanvas.width, gameCanvas.height); + + // ctx.drawImage(getAssets().getImage(font), 0, frame % (getScreen().h + 256) - 128); + // console.log(mainFont.measureText({text: "a!\nb\n"})); + mainFont.drawText({ + ctx: ctx, + text: "Hello, world!\nI wish you luck!", + x: gameCanvas.width, + y: 0, + color: "#f00", + alignX: AlignX.Right, + }); + mainFont.drawText({ + ctx: ctx, + text: "^._.^", + x: gameCanvas.width/2, + y: 32, + color: "#0ff", + alignX: AlignX.Center, + }); + */ +} + +function onFrameFixScreen(canvas: HTMLCanvasElement) { + pollAndTouch(canvas); +} + +setupGame(); + +/* +document.querySelector<HTMLDivElement>('#app')!.innerHTML = ` + <div> + <a href="https://vite.dev" target="_blank"> + <img src="${viteLogo}" class="logo" alt="Vite logo" /> + </a> + <a href="https://www.typescriptlang.org/" target="_blank"> + <img src="${typescriptLogo}" class="logo vanilla" alt="TypeScript logo" /> + </a> + <h1>Vite + TypeScript</h1> + <div class="card"> + <button id="counter" type="button"></button> + </div> + <p class="read-the-docs"> + Click on the Vite and TypeScript logos to learn more + </p> + </div> +` + +setupCounter(document.querySelector<HTMLButtonElement>('#counter')!) + */ \ No newline at end of file diff --git a/src/screen.ts b/src/screen.ts new file mode 100644 index 0000000..19e1936 --- /dev/null +++ b/src/screen.ts @@ -0,0 +1,62 @@ +class Screen { + #canvas: HTMLCanvasElement + w: number + h: number + + constructor(canvas: HTMLCanvasElement, w: number, h: number) { + this.#canvas = canvas; + this.w = w; + this.h = h; + } + + makeContext(): CanvasRenderingContext2D { + let ctx = this.#canvas.getContext("2d")!; + + // TODO: Other stuff to do here? + ctx.strokeStyle = "#000"; + ctx.fillStyle = "#000"; + ctx.resetTransform(); + ctx.imageSmoothingEnabled = false; + + return ctx; + } +} + + +let active: Screen | undefined = undefined +export let desiredWidth = 384; +export let desiredHeight = 384; + +export function pollAndTouch(canvas: HTMLCanvasElement) { + let realWidth = canvas.offsetWidth; + let realHeight = canvas.offsetHeight; + + let divisors = [0.25, 0.5, 1, 2, 3, 4, 5, 6]; + for (let i = 0; i < divisors.length; i++) { + // TODO: Is this necessary? + divisors[i] /= window.devicePixelRatio; + } + + let div = 0; + while ( + (div < divisors.length - 1) && + (realWidth / divisors[div + 1] >= desiredWidth) && + (realHeight / divisors[div + 1] >= desiredHeight) + ) { + div += 1; + } + realWidth = Math.floor(canvas.offsetWidth / divisors[div]); + realHeight = Math.floor(canvas.offsetHeight / divisors[div]); + canvas.width = realWidth; + canvas.height = realHeight; + active = new Screen(canvas, realWidth, realHeight); +} + +export function getScreen(): Screen { + if (active === undefined) { + throw `screen should have been defined: ${active}` + } + return active; +} + + diff --git a/src/sprite.ts b/src/sprite.ts new file mode 100644 index 0000000..2b84c5f --- /dev/null +++ b/src/sprite.ts @@ -0,0 +1,48 @@ +import {getAssets} from "./assets.ts"; + + +export class Sprite { + readonly imageSet: string; + // spritesheet params + // image size (px, py) + readonly px: number; readonly py: number; + // origin (ox, oy) + readonly ox: number; readonly oy: number; + // dimension in cells (cx, cy) + readonly cx: number; readonly cy: number; + // number of frames + readonly nFrames: number; + + constructor(imageSet: string, px: number, py: number, ox: number, oy: number, cx: number, cy: number, nFrames: number) { + this.imageSet = imageSet; + this.px = px; this.py = py; + this.ox = ox; this.oy = oy; + this.cx = cx; this.cy = cy; + this.nFrames = nFrames; + + if (this.nFrames < this.cx * this.cy) { + throw `can't have ${this.nFrames} with a spritesheet dimension of ${this.cx * this.cy}`; + } + } + + draw(ctx: CanvasRenderingContext2D, {x, y, ix, xScale, yScale, angle}: {x: number, y: number, ix?: number, xScale?: number, yScale?: number, angle?: number}) { + ix = ix == undefined ? 0 : ix; + xScale = xScale == undefined ? 1.0 : xScale; + yScale = yScale == undefined ? 1.0 : yScale; + angle = angle == undefined ? 0.0 : angle; + + // ctx.translate(Math.floor(x), Math.floor(y)); + ctx.translate(x, y); + ctx.rotate(angle * Math.PI / 180); + ctx.scale(xScale, yScale); + ctx.translate(-this.ox, -this.oy); + + let me = getAssets().getImage(this.imageSet); + let srcCx = ix % this.cx; + let srcCy = Math.floor(ix / this.cx); + let srcPx = srcCx * this.px; + let srcPy = srcCy * this.py; + console.log(`src px and py ${srcPx} ${srcPy}`) + ctx.drawImage(me, srcPx, srcPy, this.px, this.py, 0, 0, this.px, this.py); + } +} diff --git a/src/sprites.ts b/src/sprites.ts new file mode 100644 index 0000000..fea8d68 --- /dev/null +++ b/src/sprites.ts @@ -0,0 +1,14 @@ +import {Sprite} from "./sprite.ts"; +import imgBat from "./art/characters/bat.png"; +import imgKobold from "./art/characters/kobold.png"; +import imgRaccoon from "./art/characters/raccoon.png"; +import imgRaccoonWalking from "./art/characters/raccoon_walking.png"; +import imgRobot from "./art/characters/robot.png"; +import imgSnake from "./art/characters/snake.png"; + +export let sprBat = new Sprite(imgBat, 64, 64, 32, 32, 1, 1, 1); +export let sprKobold = new Sprite(imgKobold, 64, 64, 32, 32, 1, 1, 1); +export let sprRaccoon = new Sprite(imgRaccoon, 64, 64, 32, 32, 1, 1, 1); +export let sprRaccoonWalking = new Sprite(imgRaccoonWalking, 64, 64, 32, 32, 8, 1, 8); +export let sprRobot = new Sprite(imgRobot, 64, 64, 32, 32, 1, 1, 1); +export let sprSnake = new Sprite(imgSnake, 64, 64, 32, 32, 1, 1, 1); diff --git a/src/style.css b/src/style.css new file mode 100644 index 0000000..42ed2b5 --- /dev/null +++ b/src/style.css @@ -0,0 +1,12 @@ +html, body { + padding: 0; + margin: 0; + width: 100%; + height: 100%; + overflow: hidden; +} +#game { + image-rendering: pixelated; + width: 100%; + height: 100%; +} \ No newline at end of file diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1 @@ +/// <reference types="vite/client" /> diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..a4883f2 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"] +}