Initial commit: base game dari AETHER CLASH (Bang Tutorial), MIT + CC BY-NC 4.0
|
|
@ -0,0 +1,13 @@
|
|||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
desktop.ini
|
||||
|
||||
# Editors
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
|
||||
# Tooling
|
||||
node_modules/
|
||||
*.log
|
||||
|
|
@ -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>
|
||||
|
|
@ -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 |
|
||||
|
|
@ -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.
|
||||
|
|
@ -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).
|
||||
|
|
@ -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.
|
||||
|
||||
[](https://bangtutorial.id/aether-clash/)
|
||||
[](https://www.youtube.com/watch?v=UN_0bNC2wTU)
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
[](LICENSE)
|
||||
[](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>
|
||||
|
|
@ -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};
|
||||
})();
|
||||
|
|
@ -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!"}}};
|
||||
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 185 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 258 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 117 KiB |
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 9.8 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 9.7 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 8.4 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 2.3 MiB |
|
|
@ -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.
|
||||