Initial commit: base game dari AETHER CLASH (Bang Tutorial), MIT + CC BY-NC 4.0

This commit is contained in:
rizkyaz0 2026-10-01 10:11:31 +07:00
commit a0d1ffd393
333 changed files with 8004 additions and 0 deletions

13
.gitignore vendored Normal file
View File

@ -0,0 +1,13 @@
# OS
.DS_Store
Thumbs.db
desktop.ini
# Editors
.vscode/
.idea/
*.swp
# Tooling
node_modules/
*.log

20
.htaccess Normal file
View File

@ -0,0 +1,20 @@
# AETHER CLASH (Apache / LiteSpeed). nginx ignores this file; see install.md.
# Game files are revalidated on every load (ETag/Last-Modified); the game keeps its own hashed copies in Cache Storage.
<IfModule mod_mime.c>
AddType text/javascript .js
AddType image/webp .webp
AddType audio/mpeg .mp3
AddType video/mp4 .mp4
AddType font/ttf .ttf
AddType application/json .json
AddType image/svg+xml .svg
</IfModule>
<IfModule mod_headers.c>
Header set Cache-Control "no-cache"
</IfModule>
# Text only: MP3/MP4 must stay uncompressed so range requests (206) keep working.
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/css text/javascript application/javascript application/json image/svg+xml
</IfModule>

140
DEPLOY.md Normal file
View File

@ -0,0 +1,140 @@
# Deploying AETHER CLASH
AETHER CLASH is a static website: HTML, CSS, JavaScript, WebP images, MP3 audio, one MP4 loop and TTF fonts (about 34 MB, listed in `files.json`). It needs **no** Node.js, PHP, database or build step. Any web server that serves static files can host it.
The examples below install it in a subfolder, `https://example.com/aether-clash/`. For a domain root, drop the `/aether-clash` prefix.
## 1. Requirements
- **HTTPS.** The loading screen caches files through a service worker, and browsers only enable service workers on HTTPS (or `localhost`). Without HTTPS the game still runs, just without the cache.
- **Trailing slash.** Every path in the game is relative, so the page must be opened as `/aether-clash/`. Without the slash the browser looks for assets at the domain root and gets 404s. Redirect `/aether-clash` to `/aether-clash/` with a 301. nginx and Apache do this on their own for real folders; for an nginx `alias` add the redirect yourself (below).
- The service worker's scope is limited to its own folder (`/aether-clash/`), so it never touches the rest of the site.
## 2. Copy the files
Upload the repository contents (everything except `.git`, `README.md`, `DEPLOY.md`, the license files, `docs/` and `guide/`) into the site's document root, in a folder called `aether-clash`:
```bash
DOCROOT=/var/www/example.com # your site's document root
sudo mkdir -p "$DOCROOT/aether-clash"
sudo rsync -a --delete \
--exclude .git --exclude README.md --exclude DEPLOY.md --exclude 'LICENSE*' --exclude docs --exclude guide \
./ "$DOCROOT/aether-clash/"
```
Give the files to the web server's user (`www-data` on Debian/Ubuntu, `nginx` or `apache` on RHEL, or your panel's site user):
```bash
OWNER=$(stat -c '%U:%G' "$DOCROOT")
sudo chown -R "$OWNER" "$DOCROOT/aether-clash"
sudo find "$DOCROOT/aether-clash" -type d -exec chmod 755 {} \;
sudo find "$DOCROOT/aether-clash" -type f -exec chmod 644 {} \;
```
If the main site runs WordPress or another CMS, that is fine: a real `aether-clash/` folder is served directly as static files.
## 3. Configure the web server
Aim for:
1. `/aether-clash` → 301 → `/aether-clash/`
2. Correct MIME types: `.js` JavaScript, `.webp` `image/webp`, `.mp3` `audio/mpeg`, `.mp4` `video/mp4`, `.ttf` `font/ttf`
3. `Cache-Control: no-cache` for the game files. Browsers still reuse their copy after an ETag/Last-Modified check, and the game keeps hashed copies in Cache Storage, so updates show up at once. Avoid long `expires`/`max-age` rules for this path.
4. MP3/MP4 served with range requests (206), which is the default for static files, and not gzipped.
### nginx
Inside the site's HTTPS `server { … }` block:
```nginx
location = /aether-clash { return 301 /aether-clash/; }
location ^~ /aether-clash/ {
# If the files are NOT inside the document root, point to them (keep the trailing slash):
# alias /var/www/aether-clash/;
try_files $uri $uri/ =404;
add_header Cache-Control "no-cache" always;
gzip on;
gzip_types text/css application/javascript text/javascript application/json image/svg+xml;
}
```
`^~` keeps other rules (image caching, PHP) from taking over this path. Recent nginx versions already map `webp`, `mp3`, `mp4` and `js` in `/etc/nginx/mime.types`. If `font/ttf` is missing, add `font/ttf ttf;` to that file; don't use a `types { }` block inside the location, because it replaces every other MIME type. Then reload:
```bash
sudo nginx -t && sudo systemctl reload nginx
```
### Apache / LiteSpeed
The included `.htaccess` sets the MIME types, `no-cache` and compression. It needs `AllowOverride` to allow at least `FileInfo` for the document root, plus `mod_mime`, `mod_headers` and `mod_deflate`:
```bash
sudo a2enmod headers mime deflate
sudo systemctl reload apache2
```
`mod_dir` adds the trailing-slash redirect for the folder automatically.
### Caddy
```caddy
redir /aether-clash /aether-clash/ 301
header /aether-clash/* Cache-Control "no-cache"
```
### GitHub Pages, Netlify and similar hosts
Publish the repository as it is. These hosts already send correct MIME types and add the trailing-slash redirect for folders.
## 4. Check the deployment
```bash
BASE=https://example.com/aether-clash
curl -sI "$BASE" | grep -iE "^HTTP|^location" # 301 -> /aether-clash/
curl -sI "$BASE/" | grep -iE "^HTTP|content-type" # 200 text/html
curl -sI "$BASE/sw.js" | grep -iE "^HTTP|content-type|cache-control" # 200, javascript, no-cache
curl -sI "$BASE/assets/stage.webp" | grep -iE "^HTTP|content-type" # 200 image/webp
curl -s -o /dev/null -w "%{http_code}\n" -H "Range: bytes=0-99" \
"$BASE/assets/audio/music/midday-showdown.mp3" # 206
```
Check that every file in `files.json` is reachable:
```bash
python3 - <<'PY'
import json, urllib.request
base = "https://example.com/aether-clash/"
files = json.load(urllib.request.urlopen(base + "files.json"))["list"]
bad = []
for f in files:
try:
with urllib.request.urlopen(urllib.request.Request(base + f["file"], method="HEAD")) as r:
if r.status != 200: bad.append((f["file"], r.status))
except Exception as e:
bad.append((f["file"], str(e)))
print(len(files), "files checked,", len(bad), "problems")
for b in bad[:30]: print(b)
PY
```
Then open the game in a browser:
1. The first visit shows the loading screen ("Mengunduh aset game · X%") until all assets are downloaded, then the main menu.
2. DevTools → Console shows no 404 or MIME errors, and Application → Service Workers lists `sw.js` with the scope `…/aether-clash/`.
3. A reload is much faster (served from the cache).
4. On a phone, the page switches to `mobile.html` (landscape with touch controls).
## Updating
Upload the new files over the old ones (the `rsync --delete` above) and fix the ownership again. No cache purge is needed: HTML and JS use `no-cache`, and `precache.js` carries a hash for every file, so players only download what changed.
## Troubleshooting
| Symptom | Cause and fix |
| --- | --- |
| Blank page or every asset 404 | Opened without the trailing slash, or the files are in the wrong folder. Check the redirect and where `index.html` is |
| Loading stalls or the service worker fails | `sw.js` is not served as JavaScript, or is cached for too long. Check `curl -sI …/sw.js` |
| No audio/video on Safari or iPhone | Range requests are not supported. Serve MP3/MP4 directly as static files, without gzip or a proxy that drops the `Range` header |
| Changes don't appear | A long cache rule (a global `expires`, a cache plugin or a CDN). Exclude `/aether-clash/` from it |
| HTTP 500 after upload (Apache/LiteSpeed) | `AllowOverride` does not allow the `.htaccess` directives. Allow `FileInfo`, or delete `.htaccess` and set MIME types and headers in the server config |
| Behind Cloudflare | Bypass the cache for `/aether-clash/*` (or purge after each deploy) and turn Rocket Loader off for this path |

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Bang Tutorial
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

34
LICENSE-ASSETS.md Normal file
View File

@ -0,0 +1,34 @@
# Asset license
This repository uses two licenses.
| What | License |
| --- | --- |
| **Source code**: every `.html`, `.css` and `.js` file, including the `manifest.js` and `*-config.js` data files | [MIT](LICENSE) |
| **Assets**: character designs, sprite atlases, portraits, icons, cut-ins, effects, arena and menu artwork, the menu video, voices and announcer clips, the background music "Midday Showdown" (generated with Suno on a paid plan), screenshots, and the documents in `guide/` | [Creative Commons Attribution-NonCommercial 4.0 International (CC BY-NC 4.0)](https://creativecommons.org/licenses/by-nc/4.0/) |
Copyright © 2026 Bang Tutorial.
## CC BY-NC 4.0 in short
You may:
- **Share**: copy and redistribute the assets in any medium or format.
- **Adapt**: remix, transform and build upon them.
Under these terms:
- **Attribution**: credit "AETHER CLASH by Bang Tutorial", link to this repository or to the [YouTube tutorial](https://www.youtube.com/watch?v=UN_0bNC2wTU), and say whether you made changes.
- **NonCommercial**: do not use the assets for commercial purposes. Selling them, putting them in a paid game or app, or using them in paid advertising all need written permission from Bang Tutorial.
This summary is not the license itself. The full legal text is at <https://creativecommons.org/licenses/by-nc/4.0/legalcode>.
## Not covered by these licenses
| File(s) | Terms |
| --- | --- |
| `assets/fonts/Rajdhani-*.ttf` | Rajdhani by Indian Type Foundry, under the [SIL Open Font License 1.1](assets/fonts/OFL.txt) |
## Commercial use
For commercial use of the assets, contact Bang Tutorial through the [YouTube channel](https://www.youtube.com/watch?v=UN_0bNC2wTU).

234
README.md Normal file
View File

@ -0,0 +1,234 @@
> [!NOTE]
> **Konoha Fighting** adalah proyek turunan dari **AETHER CLASH** karya **Bang Tutorial** ([repo asli](https://github.com/bangtutorial/aether-clash), [tutorial YouTube](https://www.youtube.com/watch?v=UN_0bNC2wTU)). Kode asli berlisensi [MIT](LICENSE); aset bawaan (karakter, seni, audio, panduan) berlisensi [CC BY-NC 4.0](LICENSE-ASSETS.md), jadi **tidak untuk penggunaan komersial**. Perubahan di sini: roster karakter diganti dengan karakter parodi buatan sendiri. Bagian di bawah ini masih dokumentasi asli dan akan diperbarui bertahap.
<div align="center">
# ⚔️ AETHER CLASH
**A 2D browser fighting game — Mecha vs Demi-Human.**
Twelve chibi fighters, cinematic ultimates and a CPU that reads your moves. Runs in any modern browser, on desktop and mobile, with no install.
[![Play Online](https://img.shields.io/badge/▶_Play-Online-f0b15e?style=for-the-badge)](https://bangtutorial.id/aether-clash/)
[![YouTube Tutorial](https://img.shields.io/badge/YouTube-Tutorial-FF0000?style=for-the-badge&logo=youtube&logoColor=white)](https://www.youtube.com/watch?v=UN_0bNC2wTU)
![HTML5](https://img.shields.io/badge/HTML5-E34F26?style=flat-square&logo=html5&logoColor=white)
![CSS3](https://img.shields.io/badge/CSS3-1572B6?style=flat-square&logo=css3&logoColor=white)
![JavaScript](https://img.shields.io/badge/JavaScript-ES2020-F7DF1E?style=flat-square&logo=javascript&logoColor=black)
![Canvas 2D](https://img.shields.io/badge/Canvas-2D-4a90d9?style=flat-square)
![Dependencies](https://img.shields.io/badge/dependencies-0-brightgreen?style=flat-square)
![Build](https://img.shields.io/badge/build_step-none-brightgreen?style=flat-square)
![Platforms](https://img.shields.io/badge/platform-desktop_%7C_mobile-8a5cf6?style=flat-square)
![Offline cache](https://img.shields.io/badge/service_worker-offline_cache-0f9d58?style=flat-square)
[![Code license: MIT](https://img.shields.io/badge/code-MIT-blue?style=flat-square)](LICENSE)
[![Assets license: CC BY-NC 4.0](https://img.shields.io/badge/assets-CC_BY--NC_4.0-lightgrey?style=flat-square)](LICENSE-ASSETS.md)
<img src="docs/screenshots/menu.jpg" alt="AETHER CLASH main menu" width="860">
</div>
---
## 📺 Tutorial
The whole game — fighters, animations, voices and code — was made step by step on YouTube (in Bahasa Indonesia):
<a href="https://www.youtube.com/watch?v=UN_0bNC2wTU">
<img src="docs/youtube-tutorial.jpg" alt="Watch the tutorial on YouTube: Bikin Game Fighting Pakai ChatGPT (GPT-6 Astra + Higgsfield MCP)" width="640">
</a>
**[Bikin Game Fighting Pakai ChatGPT (GPT-6 Astra + Higgsfield MCP)](https://www.youtube.com/watch?v=UN_0bNC2wTU)** — by **Bang Tutorial**
## 📑 Contents
- [Features](#-features)
- [Screenshots](#-screenshots)
- [Roster](#-roster)
- [Controls](#-controls)
- [Getting started](#-getting-started)
- [Deployment](#-deployment)
- [Project structure](#-project-structure)
- [Guides](#-guides)
- [How it works](#-how-it-works)
- [Credits](#-credits)
- [License](#-license)
## ✨ Features
- **12 playable fighters** in two factions (6 Mecha, 6 Demi-Human). Each has a 3-hit basic chain, two skills and an ultimate with a full-screen cut-in and a voice line.
- **VS Computer**: best of 3 rounds, 90 seconds each, with **4 CPU levels** (Easy, Medium, Hard, Excellent). The CPU dodges projectiles, punishes whiffs, anti-airs jumps and only continues combos that actually landed.
- **Training mode** with unlimited time, a passive dummy, a combo tracker and a hitbox view.
- **5 arenas**: Bellora Courtyard, Sunspire Terrace, Azure Harbor, Elderwood Glade and Moonrise Bastion.
- **Announcer** calls for character select, rounds, K.O. and the winner.
- **Mobile ready**: phones open a landscape shell with touch controls (d-pad plus MOBA-style skill buttons with cooldown timers) and a fullscreen button.
- **Fast loading**: the first visit downloads every asset behind a progress screen (about 34 MB); after that a service worker serves images from the cache, so screens change instantly and reloads are near-instant.
- **Settings**: master volume, background music, sound test, hitboxes and fullscreen.
- **No framework, no build step**: plain HTML, CSS and JavaScript with Canvas 2D. Deploy it as static files anywhere.
## 🖼️ Screenshots
<table>
<tr>
<td width="50%"><img src="docs/screenshots/roster.jpg" alt="Character select"><br><sub><b>Character select</b> — 12 fighters, player then rival then arena</sub></td>
<td width="50%"><img src="docs/screenshots/fight.jpg" alt="SOLAN vs NIB at Moonrise Bastion"><br><sub><b>VS Computer</b> — SOLAN's Solar Crescent at Moonrise Bastion</sub></td>
</tr>
<tr>
<td width="50%"><img src="docs/screenshots/ultimate-cutin.jpg" alt="Elder Tortoise cut-in"><br><sub><b>Ultimate cut-in</b> — EDDA calls the Elder Tortoise</sub></td>
<td width="50%"><img src="docs/screenshots/ultimate.jpg" alt="Elder Tortoise stomp"><br><sub><b>Ultimate</b> — the spirit stomps three times</sub></td>
</tr>
<tr>
<td colspan="2" align="center"><img src="docs/screenshots/mobile.jpg" alt="Mobile layout" width="640"><br><sub><b>Mobile</b> — landscape layout with touch controls</sub></td>
</tr>
</table>
## 🥊 Roster
| | Fighter | Faction | Style | Basic | Ultimate |
| :-: | --- | --- | --- | --- | --- |
| <img src="assets/ui/arco-avatar.webp" width="48"> | **ARCO** — The Aether Arm | Mecha | Brawler / drone summon | Iron Chain | Helios Squadron |
| <img src="assets/fenr/ui/portrait-human.webp" width="48"> | **FENR** — The Wolf Ranger | Demi-Human | Agile / werewolf transformation | Ranger Chain | Feral Awakening |
| <img src="assets/mira/ui/portrait.webp" width="48"> | **MIRA** — The Candy Pilot | Mecha | Heavy mech / rocket barrage | Mitten Chain | Rocket Parade |
| <img src="assets/cora/ui/portrait.webp" width="48"> | **CORA** — The Raven Dancer | Demi-Human | Agile / raven swarm | Feather Waltz | Night Murmuration |
| <img src="assets/naja/ui/portrait.webp" width="48"> | **NAJA** — The Dune Cobra | Demi-Human | Mid-range / urumi whip and sand serpent | Viper Lash | Dune Serpent |
| <img src="assets/haldor/ui/portrait.webp" width="48"> | **HALDOR** — The Walking Forge | Mecha | Heavy tank / steam forge hammer | Forge Chain | Forge Quake |
| <img src="assets/zanni/ui/portrait.webp" width="48"> | **ZANNI** — The Clockwork Jester | Mecha | Trickster / scissor arms and bladed rings | Scissor Jab | Grand Finale |
| <img src="assets/isolde/ui/portrait.webp" width="48"> | **ISOLDE** — The White Strider | Mecha | Long-range lancer / stilt-leg charges | Lance Line | Skyfall Lances |
| <img src="assets/rhea/ui/portrait.webp" width="48"> | **RHEA** — The Little Orrery | Mecha | Zoner / orbiting orrery spheres | Orbit Strike | Grand Orrery |
| <img src="assets/solan/ui/portrait.webp" width="48"> | **SOLAN** — The Sunmane | Demi-Human | Powerhouse / greatsword and roar | Sunblade | Sunmane Roar |
| <img src="assets/nib/ui/portrait.webp" width="48"> | **NIB** — The Rooftop Post | Demi-Human | Rushdown / twin courier batons | Baton Flurry | Special Delivery |
| <img src="assets/edda/ui/portrait.webp" width="48"> | **EDDA** — The Shell Sage | Demi-Human | Counter defender / staff and shell guard | Staff Forms | Elder Tortoise |
## 🎮 Controls
### Keyboard
| Key | Action |
| --- | --- |
| `A` / `D` | Move left / right |
| Double-tap `A` / `D` and hold | Run |
| `W` | Jump (press again in the air to double jump) |
| `S` | Crouch on the ground, fast fall in the air; double-tap for a somersault double jump |
| `Space` | Basic attack (tap up to 3 times for the full chain) |
| `I` | Skill 1 |
| `O` | Skill 2 |
| `P` | Ultimate |
| `Esc` | Pause / resume |
| `R` | Restart (the round in Training, the match in VS Computer) |
Skills cancel a basic attack at any moment. The ultimate pose locks you for only 0.5 s; the summon then finishes on its own while you keep fighting.
In the menus: `W A S D` / arrow keys to select, `Enter` to confirm, `Esc` to go back.
### Touch (phones and tablets)
- **Left**: ◀ ▼ ▶ with ▲ above ▼. Slide your finger between buttons; double-tap ◀/▶ to run, tap ▲ again in the air to double jump.
- **Right**: a big **Basic Attack** button with **Skill 1 / Skill 2 / Ultimate** around it, showing each fighter's icons and cooldowns.
- Phones open `mobile.html` automatically in landscape. Add `?touch=1` to force the mobile layout on desktop, or `?touch=0` to turn it off.
## 🚀 Getting started
The game is a set of static files, so there is nothing to install or build.
```bash
git clone https://github.com/<your-username>/aether-clash.git
cd aether-clash
# any static file server works, for example:
python -m http.server 8000
# or
npx serve .
```
Then open <http://localhost:8000/>.
> [!TIP]
> Serve the folder over `http://localhost` or HTTPS to get the loading screen and the service-worker cache. Opening `index.html` straight from disk (`file://`) still plays, just without caching.
**Browser support:** current Chrome, Edge, Firefox and Safari (desktop and mobile). Audio starts after the first click or tap, because browsers require a user gesture.
## 🌐 Deployment
Upload the folder to any static host (nginx, Apache, LiteSpeed, Caddy, GitHub Pages, Netlify and so on). All paths are relative, so it works at a domain root or in a subfolder such as `https://example.com/aether-clash/`.
Server checklist:
- Open the game **with the trailing slash** (`/aether-clash/`) and redirect `/aether-clash` to it.
- Serve `.js` as JavaScript, `.webp` as `image/webp`, `.mp3` as `audio/mpeg`, `.mp4` as `video/mp4` and `.ttf` as `font/ttf`.
- Send `Cache-Control: no-cache` for the game files. The game keeps its own hashed copies in Cache Storage, so updates show up immediately.
- Serve MP3/MP4 files with range requests (the default for static files) and without gzip.
Step-by-step instructions for nginx, Apache/LiteSpeed and Caddy, plus a script that checks every file after upload, are in **[DEPLOY.md](DEPLOY.md)**. An `.htaccess` for Apache/LiteSpeed is included.
## 🗂️ Project structure
```text
.
├── index.html # desktop entry: menu, HUD, dialogs, script order
├── mobile.html # landscape shell for phones (rotation, fullscreen)
├── style.css # arena HUD, cut-ins, touch controls
├── menu.css # main menu, character and arena select, results
├── game.js # engine: loop, physics, combat, CPU brain, summons, rendering, HUD
├── menu.js # front end: menu flow, roster, arena and difficulty select
├── match.js # match rules: rounds, timer, stages, difficulty tuning
├── announcer.js # announcer voice queue
├── touch.js # on-screen touch controls
├── fenr.js … edda.js # one kit file per fighter (moves, balance, timings)
├── preload.js # first-visit loading screen, fills the cache
├── precache.js # generated list of runtime files with sizes and hashes
├── sw.js # service worker: cache-first images, network-first code
├── files.json # file list with sizes (used by the deploy check)
├── LICENSE # MIT (source code)
├── LICENSE-ASSETS.md # CC BY-NC 4.0 (art, audio, guides) and exceptions
├── guide/ # design and production guides (Bahasa Indonesia)
├── docs/screenshots/ # images for this README
└── assets/
├── <fighter>/ # sprite atlas (WebP) + manifest.js, portrait, icons, cut-in, FX, voice
├── audio/ # announcer clips and background music
├── menu/ # menu art, arena backgrounds, character select art
├── ui/ # shared HUD art (ARCO kit, drone, cut-in)
└── fonts/ # Rajdhani (SIL Open Font License)
```
## 📚 Guides
The [`guide/`](guide/) folder holds the design and production notes behind the game (in Bahasa Indonesia). Start from [guide/README.md](guide/README.md).
| Topic | Guide |
| --- | --- |
| Adding a new fighter, step by step | [character-workflow.md](guide/character-workflow.md) |
| Shared gameplay rules (movement, combos, HP, cooldowns) | [gameplay-standard.md](guide/gameplay-standard.md) |
| Sprite prompts, pipeline, QA and known issues | [sprite-prompts.md](guide/sprite-prompts.md), [sprite-pipeline.md](guide/sprite-pipeline.md), [sprite-qa.md](guide/sprite-qa.md), [sprite-known-issues.md](guide/sprite-known-issues.md) |
| CPU brain and difficulty benchmark | [cpu-ai.md](guide/cpu-ai.md) |
| Menu flow and match rules | [main-menu.md](guide/main-menu.md) |
| Announcer and ultimate voices | [announcer-system.md](guide/announcer-system.md), [ultimate-voice.md](guide/ultimate-voice.md) |
| Arena backgrounds and menu video | [stage-background.md](guide/stage-background.md), [menu-video.md](guide/menu-video.md) |
| One reference per fighter (kit, balance, assets, voice) | [arco](guide/arco-reference.md) · [fenr](guide/fenr-reference.md) · [mira](guide/mira-reference.md) · [cora](guide/cora-reference.md) · [naja](guide/naja-reference.md) · [haldor](guide/haldor-reference.md) · [zanni](guide/zanni-reference.md) · [isolde](guide/isolde-reference.md) · [rhea](guide/rhea-reference.md) · [solan](guide/solan-reference.md) · [nib](guide/nib-reference.md) · [edda](guide/edda-reference.md) |
The guides also mention the full production workspace (raw generation sources, pipeline tools, sprite lab pages). Those are several GB and are not part of this repository.
## 🔧 How it works
- **Fixed-step simulation.** The game advances in 1/120 s steps with a seeded random generator, so combat plays the same way on every machine.
- **Sprites.** Each fighter is a WebP sprite atlas (idle, walk, run, jump, crouch, three attacks, two skills, ultimate, hurt, down and recover) with a manifest of frames, foot anchors and measured hit points. Sprites are drawn pixel-exact, never rescaled at runtime.
- **Kits.** The rules for each fighter (damage, reach, cooldowns, projectile and summon behaviour) live in their own small file. The player and the CPU use exactly the same kit.
- **CPU brain.** The CPU reacts with a human-like delay: it jumps projectiles, backs out of attacks, punishes recovery, anti-airs and confirms combos. Each difficulty level changes reaction time, aggression and mistakes.
- **Loading.** `precache.js` lists every runtime file with a content hash. `preload.js` downloads them into Cache Storage behind the loading screen, and `sw.js` serves images and fonts from there. On later visits only files whose hash changed are downloaded again.
## 🙏 Credits
- **Game design and development**: [Bang Tutorial](https://www.youtube.com/watch?v=UN_0bNC2wTU)
- **Visuals**: generated with Higgsfield (GPT Image)
- **Voices and announcer**: ElevenLabs voices via Higgsfield
- **Typography**: [Rajdhani](https://fonts.google.com/specimen/Rajdhani) by Indian Type Foundry, under the SIL Open Font License (see `assets/fonts/OFL.txt`)
- **Background music**: "Midday Showdown", generated with [Suno](https://suno.com)
## 📄 License
- **Source code** (HTML, CSS, JavaScript) is released under the [MIT License](LICENSE).
- **Assets** (characters, sprites, artwork, effects, video, voices, background music, screenshots and the `guide/` documents) are released under [CC BY-NC 4.0](LICENSE-ASSETS.md): free to share and adapt with credit, but not for commercial use.
- **Exception**: the Rajdhani font keeps the [SIL Open Font License](assets/fonts/OFL.txt). See [LICENSE-ASSETS.md](LICENSE-ASSETS.md).
<div align="center">
If you enjoyed the project, give it a ⭐ and check out the [tutorial on YouTube](https://www.youtube.com/watch?v=UN_0bNC2wTU)!
</div>

48
announcer.js Normal file
View File

@ -0,0 +1,48 @@
/* Grady is the locked system announcer. One serialized speaker, never overlapping cues. */
(() => {
'use strict';
function create({clips,canPlay,isPaused,onStart=()=>{},onActivity=()=>{}}){
const bank={},failed=new Set();let queue=[],active=null,serial=0,playToken=0,starts=0,lastCue='',error='',volume=.45,muted=false;
function mix(){for(const media of Object.values(bank)){media.volume=Math.max(0,Math.min(1,volume*1.25));media.muted=muted;}onActivity();}
function clear(){serial++;playToken++;queue=[];if(active){active.media.pause();try{active.media.currentTime=0;}catch(_){}}active=null;onActivity();}
function finish(){if(active)active.media.pause();active=null;playToken++;next();}
function next(){
if(active)return;
const key=queue.shift();if(!key){onActivity();return;}
if(failed.has(key)){next();return;}
const media=bank[key];active={key,media,elapsed:0,serial:++serial};lastCue=key;starts++;error='';try{media.currentTime=0;}catch(_){}
onStart();mix();sync();
}
function sync(){
if(!active)return;
const entry=active;
if(!canPlay()||isPaused()){playToken++;entry.media.pause();onActivity();return;}
if(!entry.media.paused)return;
const token=++playToken;
try{const p=entry.media.play();p?.catch(e=>{if(active!==entry||token!==playToken)return;error=e?.name||'audio-playback-failed';finish();});}
catch(e){if(active===entry&&token===playToken){error=e?.name||'audio-playback-failed';finish();}}
}
for(const [key,clip] of Object.entries(clips)){
try{
const media=new Audio();bank[key]=media;media.preload='auto';
media.addEventListener('ended',()=>{if(active?.media===media)finish();});
media.addEventListener('error',()=>{failed.add(key);if(active?.media===media){error='audio-load-failed';finish();}});
media.addEventListener('playing',()=>{if(active?.media!==media||!canPlay()||isPaused()){media.pause();return;}mix();});
media.src=clip.file;media.load();
}catch(_){failed.add(key);}
}
return {
play(keys,{replace=true}={}){
if(!canPlay())return false;
const valid=(Array.isArray(keys)?keys:[keys]).filter(k=>Object.hasOwn(bank,k)&&!failed.has(k));
if(!valid.length)return false;if(replace)clear();queue.push(...valid);queue=queue.slice(0,4);next();return true;
},
clear,sync,setMix(v,m){volume=v;muted=m;for(const media of Object.values(bank)){media.volume=Math.max(0,Math.min(1,v*1.25));media.muted=m;}},
tick(dt){if(!active||!canPlay()||isPaused())return;active.elapsed+=dt;const duration=clips[active.key].duration||3;if(active.elapsed>duration+.4)finish();},
get busy(){return !!active||queue.length>0;},
get speaking(){return !!active&&!active.media.paused;},
snapshot(){return {voice:'Grady',cue:active?.key||'',lastCue,active:!!active,paused:active?.media.paused??true,time:active?.media.currentTime||0,readyState:active?.media.readyState||0,starts,error,queue:[...queue]};}
};
}
window.SystemAnnouncer={create};
})();

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
window.ANNOUNCER_MANIFEST = {"voice":{"name":"Grady","voice_id":"e2a2d2e6-9ed2-59cd-82af-feaa27f8a678","voice_type":"preset","provider":"Higgsfield","model":"text2speech_v2","variant":"elevenlabs","language":"English","locked":true},"clips":{"select_arco":{"file":"assets/audio/announcer/select_arco.mp3","duration":0.914286,"text":"Arco!"},"select_fenr":{"file":"assets/audio/announcer/select_fenr.mp3","duration":1.07102,"text":"Fenr!"},"round_1":{"file":"assets/audio/announcer/round_1.mp3","duration":1.07102,"text":"Round one!"},"round_2":{"file":"assets/audio/announcer/round_2.mp3","duration":2.194286,"text":"Round two!"},"round_3":{"file":"assets/audio/announcer/round_3.mp3","duration":1.306122,"text":"Round three!"},"fight":{"file":"assets/audio/announcer/fight.mp3","duration":1.07102,"text":"Fight!"},"ko":{"file":"assets/audio/announcer/ko.mp3","duration":0.679184,"text":"K.O.!"},"arco_wins":{"file":"assets/audio/announcer/arco_wins.mp3","duration":1.410612,"text":"Arco wins!"},"fenr_wins":{"file":"assets/audio/announcer/fenr_wins.mp3","duration":1.48898,"text":"Fenr wins!"},"draw":{"file":"assets/audio/announcer/draw.mp3","duration":1.07102,"text":"Draw!"},"time_up":{"file":"assets/audio/announcer/time_up.mp3","duration":1.227755,"text":"Time up!"},"double_ko":{"file":"assets/audio/announcer/double_ko.mp3","duration":1.48898,"text":"Double K.O.!"},"select_mira":{"file":"assets/audio/announcer/select_mira.mp3","duration":1.306122,"text":"Mira!"},"mira_wins":{"file":"assets/audio/announcer/mira_wins.mp3","duration":1.645714,"text":"Mira wins!"},"select_cora":{"file":"assets/audio/announcer/select_cora.mp3","duration":0.914286,"text":"Cora!"},"cora_wins":{"file":"assets/audio/announcer/cora_wins.mp3","duration":1.306122,"text":"Cora wins!"},"select_naja":{"file":"assets/audio/announcer/select_naja.mp3","duration":1.149388,"text":"Naja!"},"naja_wins":{"file":"assets/audio/announcer/naja_wins.mp3","duration":1.410612,"text":"Naja wins!"},"select_haldor":{"file":"assets/audio/announcer/select_haldor.mp3","duration":1.149388,"text":"Haldor!"},"haldor_wins":{"file":"assets/audio/announcer/haldor_wins.mp3","duration":1.306122,"text":"Haldor wins!"},"select_zanni":{"file":"assets/audio/announcer/select_zanni.mp3","duration":1.149388,"text":"Zanni!"},"zanni_wins":{"file":"assets/audio/announcer/zanni_wins.mp3","duration":1.48898,"text":"Zanni wins!"},"select_isolde":{"file":"assets/audio/announcer/select_isolde.mp3","duration":0.679,"text":"Isolde!"},"isolde_wins":{"file":"assets/audio/announcer/isolde_wins.mp3","duration":1.489,"text":"Isolde wins!"},"select_rhea":{"file":"assets/audio/announcer/select_rhea.mp3","duration":0.993,"text":"Rhea!"},"rhea_wins":{"file":"assets/audio/announcer/rhea_wins.mp3","duration":1.306,"text":"Rhea wins!"},"select_solan":{"file":"assets/audio/announcer/select_solan.mp3","duration":0.914286,"text":"Solan!"},"solan_wins":{"file":"assets/audio/announcer/solan_wins.mp3","duration":1.38449,"text":"Solan wins!"},"select_nib":{"file":"assets/audio/announcer/select_nib.mp3","duration":0.914286,"text":"Nib!"},"nib_wins":{"file":"assets/audio/announcer/nib_wins.mp3","duration":1.410612,"text":"Nib wins!"},"select_edda":{"file":"assets/audio/announcer/select_edda.mp3","duration":0.992653,"text":"Edda!"},"edda_wins":{"file":"assets/audio/announcer/edda_wins.mp3","duration":1.07102,"text":"Edda wins!"}}};

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

2
assets/cora/manifest.js Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

BIN
assets/cora/ui/cutin.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

BIN
assets/cora/ui/fx-gust.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

2
assets/edda/manifest.js Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

BIN
assets/edda/ui/cutin.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

BIN
assets/fenr/ui/cutin.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

BIN
assets/fenr/ui/fx-bite.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

BIN
assets/fenr/ui/fx-claw.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

BIN
assets/fenr/ui/fx-gale.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

BIN
assets/fenr/ui/fx-howl.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
assets/fenr/ui/fx-rush.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

93
assets/fonts/OFL.txt Normal file
View File

@ -0,0 +1,93 @@
Copyright (c) 2014, Indian Type Foundry (info@indiantypefoundry.com).
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More