Mirror airship-phaser files
This commit is contained in:
parent
1c86d66e46
commit
9c8f7b3e60
42
README.md
Normal file
42
README.md
Normal file
@ -0,0 +1,42 @@
|
||||
# airship-phaser
|
||||
|
||||
Top-down Phaser.js airship combat prototype.
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm test
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Production builds run the regression suite before Vite:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
## Runtime diagnostics
|
||||
|
||||
The browser keeps the latest 300 structured events in memory.
|
||||
|
||||
In Chrome DevTools:
|
||||
|
||||
```js
|
||||
airshipDebug.getLogs()
|
||||
airshipDebug.snapshot()
|
||||
airshipDebug.clearLogs()
|
||||
```
|
||||
|
||||
Useful events include:
|
||||
|
||||
- `scene:init`, `scene:create`, `scene:heartbeat`, `scene:shutdown`
|
||||
- `combat:broadside-fired`
|
||||
- `combat:player-hit-queued`
|
||||
- `combat:player-hit-processed`
|
||||
- `combat:pirate-hit`
|
||||
- `game:finish`
|
||||
- `hud:image-error`, `hud:helm-frame-error`, `hud:helm-knob-error`
|
||||
- `runtime:error`, `runtime:unhandled-rejection`
|
||||
|
||||
If gameplay appears frozen, inspect `airshipDebug.snapshot()` first, then the tail of `airshipDebug.getLogs()`.
|
||||
234
index.html
Normal file
234
index.html
Normal file
@ -0,0 +1,234 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
||||
<meta name="theme-color" content="#0f1720" />
|
||||
<title>Arrowstack Airship</title>
|
||||
<style>
|
||||
body {
|
||||
touch-action: none;
|
||||
overscroll-behavior: none;
|
||||
}
|
||||
|
||||
#mobile-controls {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 100;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
#drag-stick {
|
||||
position: absolute;
|
||||
left: max(18px, env(safe-area-inset-left));
|
||||
bottom: max(22px, env(safe-area-inset-bottom));
|
||||
width: 132px;
|
||||
height: 132px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.28);
|
||||
border-radius: 50%;
|
||||
background: rgba(7, 16, 24, 0.42);
|
||||
box-shadow: inset 0 0 0 1px rgba(140, 211, 255, 0.08);
|
||||
backdrop-filter: blur(5px);
|
||||
-webkit-backdrop-filter: blur(5px);
|
||||
pointer-events: auto;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
#drag-stick::before,
|
||||
#drag-stick::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
background: rgba(232, 242, 247, 0.18);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#drag-stick::before {
|
||||
left: 50%;
|
||||
top: 12px;
|
||||
bottom: 12px;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
#drag-stick::after {
|
||||
top: 50%;
|
||||
left: 12px;
|
||||
right: 12px;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
#drag-thumb {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 58px;
|
||||
height: 58px;
|
||||
margin: -29px 0 0 -29px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid rgba(255, 255, 255, 0.5);
|
||||
background: rgba(140, 211, 255, 0.28);
|
||||
box-shadow: 0 3px 18px rgba(0, 0, 0, 0.28);
|
||||
pointer-events: none;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.mobile-control {
|
||||
position: absolute;
|
||||
min-width: 72px;
|
||||
height: 64px;
|
||||
padding: 0 14px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.35);
|
||||
border-radius: 18px;
|
||||
background: rgba(7, 16, 24, 0.68);
|
||||
color: #e8f2f7;
|
||||
font: 700 13px/1 system-ui, sans-serif;
|
||||
backdrop-filter: blur(6px);
|
||||
-webkit-backdrop-filter: blur(6px);
|
||||
pointer-events: auto;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.mobile-control:active,
|
||||
.mobile-control.active {
|
||||
background: rgba(140, 211, 255, 0.34);
|
||||
transform: scale(0.96);
|
||||
}
|
||||
|
||||
.mobile-control.port {
|
||||
right: max(18px, env(safe-area-inset-right));
|
||||
bottom: max(100px, calc(env(safe-area-inset-bottom) + 82px));
|
||||
}
|
||||
|
||||
.mobile-control.starboard {
|
||||
right: max(18px, env(safe-area-inset-right));
|
||||
bottom: max(26px, env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.mobile-control.restart {
|
||||
top: max(14px, env(safe-area-inset-top));
|
||||
right: max(14px, env(safe-area-inset-right));
|
||||
width: 48px;
|
||||
min-width: 48px;
|
||||
height: 48px;
|
||||
padding: 0;
|
||||
border-radius: 50%;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
#mobile-controls { display: none; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<div id="game"></div>
|
||||
</div>
|
||||
|
||||
<div id="mobile-controls" aria-label="Touch controls">
|
||||
<div id="drag-stick" aria-label="Drag to steer and throttle">
|
||||
<div id="drag-thumb"></div>
|
||||
</div>
|
||||
<button class="mobile-control port" data-code="KeyQ" data-key="q" data-keycode="81" aria-label="Fire port broadside">PORT</button>
|
||||
<button class="mobile-control starboard" data-code="KeyE" data-key="e" data-keycode="69" aria-label="Fire starboard broadside">STARBOARD</button>
|
||||
<button class="mobile-control restart" data-code="KeyR" data-key="r" data-keycode="82" aria-label="Restart">↻</button>
|
||||
</div>
|
||||
|
||||
<script type="module" src="/src/game-entry.js"></script>
|
||||
<script type="module">
|
||||
const dispatchKey = (type, key, code, keyCode) => {
|
||||
const event = new KeyboardEvent(type, {
|
||||
key,
|
||||
code,
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
});
|
||||
Object.defineProperty(event, 'keyCode', { get: () => keyCode });
|
||||
Object.defineProperty(event, 'which', { get: () => keyCode });
|
||||
window.dispatchEvent(event);
|
||||
};
|
||||
|
||||
const emitHelmVector = (x, y) => {
|
||||
window.dispatchEvent(new CustomEvent('airship:helm-vector', { detail: { x, y } }));
|
||||
};
|
||||
|
||||
const releaseStick = () => {
|
||||
document.querySelector('#drag-thumb').style.transform = 'translate(0px, 0px)';
|
||||
emitHelmVector(0, 0);
|
||||
};
|
||||
|
||||
const stick = document.querySelector('#drag-stick');
|
||||
const thumb = document.querySelector('#drag-thumb');
|
||||
const radius = 45;
|
||||
let stickPointer = null;
|
||||
|
||||
const updateStick = (event) => {
|
||||
const rect = stick.getBoundingClientRect();
|
||||
let dx = event.clientX - (rect.left + rect.width / 2);
|
||||
let dy = event.clientY - (rect.top + rect.height / 2);
|
||||
const length = Math.hypot(dx, dy);
|
||||
if (length > radius) {
|
||||
dx = dx / length * radius;
|
||||
dy = dy / length * radius;
|
||||
}
|
||||
|
||||
thumb.style.transform = `translate(${dx}px, ${dy}px)`;
|
||||
emitHelmVector(dx / radius, dy / radius);
|
||||
};
|
||||
|
||||
stick.addEventListener('pointerdown', (event) => {
|
||||
event.preventDefault();
|
||||
stickPointer = event.pointerId;
|
||||
stick.setPointerCapture?.(event.pointerId);
|
||||
updateStick(event);
|
||||
});
|
||||
|
||||
stick.addEventListener('pointermove', (event) => {
|
||||
if (event.pointerId !== stickPointer) return;
|
||||
event.preventDefault();
|
||||
updateStick(event);
|
||||
});
|
||||
|
||||
const endStick = (event) => {
|
||||
if (stickPointer !== null && event.pointerId !== undefined && event.pointerId !== stickPointer) return;
|
||||
event.preventDefault?.();
|
||||
stickPointer = null;
|
||||
releaseStick();
|
||||
};
|
||||
|
||||
stick.addEventListener('pointerup', endStick);
|
||||
stick.addEventListener('pointercancel', endStick);
|
||||
stick.addEventListener('lostpointercapture', endStick);
|
||||
stick.addEventListener('contextmenu', (event) => event.preventDefault());
|
||||
|
||||
for (const button of document.querySelectorAll('.mobile-control')) {
|
||||
const fire = (type) => {
|
||||
dispatchKey(type, button.dataset.key, button.dataset.code, Number(button.dataset.keycode));
|
||||
};
|
||||
|
||||
const release = (event) => {
|
||||
event.preventDefault();
|
||||
fire('keyup');
|
||||
button.classList.remove('active');
|
||||
};
|
||||
|
||||
button.addEventListener('pointerdown', (event) => {
|
||||
event.preventDefault();
|
||||
button.setPointerCapture?.(event.pointerId);
|
||||
fire('keydown');
|
||||
button.classList.add('active');
|
||||
});
|
||||
button.addEventListener('pointerup', release);
|
||||
button.addEventListener('pointercancel', release);
|
||||
button.addEventListener('lostpointercapture', () => {
|
||||
fire('keyup');
|
||||
button.classList.remove('active');
|
||||
});
|
||||
button.addEventListener('contextmenu', (event) => event.preventDefault());
|
||||
}
|
||||
|
||||
window.addEventListener('blur', releaseStick);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
15
nginx.conf
Normal file
15
nginx.conf
Normal file
@ -0,0 +1,15 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
location = /health {
|
||||
add_header Content-Type text/plain;
|
||||
return 200 'ok';
|
||||
}
|
||||
}
|
||||
19
package.json
Normal file
19
package.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "airship-phaser",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --host 0.0.0.0",
|
||||
"test": "node --test tests/*.test.mjs",
|
||||
"test:watch": "node --test --watch tests/*.test.mjs",
|
||||
"build": "npm test && node scripts/materialize-controller-assets.mjs && vite build",
|
||||
"preview": "vite preview --host 0.0.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"phaser": "^3.90.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^7.0.0"
|
||||
}
|
||||
}
|
||||
19
scripts/materialize-controller-assets.mjs
Normal file
19
scripts/materialize-controller-assets.mjs
Normal file
@ -0,0 +1,19 @@
|
||||
import { mkdir, writeFile } from 'node:fs/promises';
|
||||
import topHudData from '../src/assets/controller/top-hud.js';
|
||||
import helmData from '../src/assets/controller/helm-action.js';
|
||||
import cannonBase64 from '../src/assets/controller/cannon-roundel.js';
|
||||
|
||||
const outDir = new URL('../public/assets/controller/', import.meta.url);
|
||||
await mkdir(outDir, { recursive: true });
|
||||
|
||||
const writeDataUrl = async (name, dataUrl) => {
|
||||
const match = /^data:[^;]+;base64,(.+)$/.exec(dataUrl);
|
||||
if (!match) throw new Error(`Invalid data URL for ${name}`);
|
||||
await writeFile(new URL(name, outDir), Buffer.from(match[1], 'base64'));
|
||||
};
|
||||
|
||||
await writeDataUrl('top-hud.webp', topHudData);
|
||||
await writeDataUrl('helm-sheet.webp', helmData);
|
||||
await writeFile(new URL('cannon-roundel.jpg', outDir), Buffer.from(cannonBase64, 'base64'));
|
||||
|
||||
console.log('Materialized controller assets');
|
||||
51
src/assets/airships.svg
Normal file
51
src/assets/airships.svg
Normal file
@ -0,0 +1,51 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="64" viewBox="0 0 512 64" shape-rendering="crispEdges">
|
||||
<defs>
|
||||
<linearGradient id="playerBalloon" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0" stop-color="#f7f0d6"/>
|
||||
<stop offset="0.5" stop-color="#d7c39e"/>
|
||||
<stop offset="1" stop-color="#8f6947"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="pirateBalloon" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0" stop-color="#8995a1"/>
|
||||
<stop offset="0.55" stop-color="#4a5660"/>
|
||||
<stop offset="1" stop-color="#202833"/>
|
||||
</linearGradient>
|
||||
<symbol id="player" viewBox="0 0 64 64">
|
||||
<path d="M32 3 26 9h12z" fill="#b34d2d" stroke="#2a1b16" stroke-width="2"/>
|
||||
<rect x="31" y="3" width="2" height="10" fill="#493026"/>
|
||||
<ellipse cx="32" cy="26" rx="17" ry="21" fill="url(#playerBalloon)" stroke="#3a2a22" stroke-width="2"/>
|
||||
<path d="M20 13c7 6 17 6 24 0M17 25c9 5 21 5 30 0M20 38c7-5 17-5 24 0" fill="none" stroke="#8d5d36" stroke-width="2"/>
|
||||
<path d="M32 6v39M25 8c-4 11-4 26 1 37M39 8c4 11 4 26-1 37" fill="none" stroke="#9c6a3d" stroke-width="2"/>
|
||||
<path d="M12 30 21 26l-2 10zM52 30 43 26l2 10z" fill="#b97842" stroke="#3a2a22" stroke-width="2"/>
|
||||
<path d="M24 43h16l6 8-14 10-14-10z" fill="#6f4528" stroke="#2b1b13" stroke-width="2"/>
|
||||
<rect x="25" y="45" width="14" height="6" fill="#a96b38"/>
|
||||
<rect x="29" y="48" width="6" height="7" fill="#2e2522"/>
|
||||
<circle cx="21" cy="48" r="2" fill="#c78d4c"/>
|
||||
<circle cx="43" cy="48" r="2" fill="#c78d4c"/>
|
||||
<path d="M31 12h3v28h-3z" fill="#f7ecd1" opacity=".65"/>
|
||||
</symbol>
|
||||
<symbol id="pirate" viewBox="0 0 64 64">
|
||||
<rect x="31" y="2" width="2" height="11" fill="#38241c"/>
|
||||
<path d="M33 3h14l-5 5 5 4H33z" fill="#b33b2f" stroke="#381f1a" stroke-width="2"/>
|
||||
<ellipse cx="32" cy="27" rx="17" ry="21" fill="url(#pirateBalloon)" stroke="#241b1b" stroke-width="2"/>
|
||||
<path d="M20 14c7 5 17 5 24 0M17 27c9 5 21 5 30 0M20 39c7-5 17-5 24 0" fill="none" stroke="#6e4a36" stroke-width="2"/>
|
||||
<path d="M32 6v40M25 9c-4 11-4 25 1 36M39 9c4 11 4 25-1 36" fill="none" stroke="#72503c" stroke-width="2"/>
|
||||
<path d="M10 31 21 25l-2 13zM54 31 43 25l2 13z" fill="#6f4934" stroke="#211818" stroke-width="2"/>
|
||||
<path d="M21 43h22l5 9-16 9-16-9z" fill="#493126" stroke="#211716" stroke-width="2"/>
|
||||
<rect x="22" y="45" width="20" height="7" fill="#6f4933"/>
|
||||
<circle cx="20" cy="48" r="2" fill="#c85e46"/>
|
||||
<circle cx="44" cy="48" r="2" fill="#c85e46"/>
|
||||
<path d="M28 47h8v4h-8z" fill="#1e2228"/>
|
||||
</symbol>
|
||||
</defs>
|
||||
|
||||
<g transform="translate(0 0)"><use href="#player" width="64" height="64"/><circle cx="29" cy="60" r="3" fill="#d7e4e9" opacity=".5"/><circle cx="35" cy="61" r="2" fill="#d7e4e9" opacity=".35"/></g>
|
||||
<g transform="translate(64 0)"><use href="#player" width="64" height="64"/><circle cx="28" cy="59" r="4" fill="#d7e4e9" opacity=".45"/><circle cx="36" cy="60" r="3" fill="#d7e4e9" opacity=".32"/></g>
|
||||
<g transform="translate(128 0)"><use href="#player" width="64" height="64"/><circle cx="30" cy="61" r="2" fill="#d7e4e9" opacity=".4"/><circle cx="35" cy="59" r="4" fill="#d7e4e9" opacity=".28"/></g>
|
||||
<g transform="translate(192 0)"><use href="#player" width="64" height="64"/><circle cx="32" cy="60" r="4" fill="#d7e4e9" opacity=".42"/></g>
|
||||
|
||||
<g transform="translate(256 0)"><use href="#pirate" width="64" height="64"/><circle cx="29" cy="60" r="3" fill="#7d8790" opacity=".35"/></g>
|
||||
<g transform="translate(320 0)"><use href="#pirate" width="64" height="64"/><circle cx="35" cy="60" r="4" fill="#7d8790" opacity=".28"/></g>
|
||||
<g transform="translate(384 0)"><use href="#pirate" width="64" height="64"/><circle cx="28" cy="60" r="4" fill="#7d8790" opacity=".3"/><circle cx="36" cy="61" r="2" fill="#7d8790" opacity=".2"/></g>
|
||||
<g transform="translate(448 0)"><use href="#pirate" width="64" height="64"/><circle cx="32" cy="60" r="3" fill="#7d8790" opacity=".32"/></g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.9 KiB |
BIN
src/assets/cannonball.png
Normal file
BIN
src/assets/cannonball.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
1
src/assets/controller/cannon-action.js
Normal file
1
src/assets/controller/cannon-action.js
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/controller/cannon-roundel-0.js
Normal file
1
src/assets/controller/cannon-roundel-0.js
Normal file
@ -0,0 +1 @@
|
||||
export default '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';
|
||||
1
src/assets/controller/cannon-roundel-1.js
Normal file
1
src/assets/controller/cannon-roundel-1.js
Normal file
@ -0,0 +1 @@
|
||||
export default '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';
|
||||
1
src/assets/controller/cannon-roundel-2.js
Normal file
1
src/assets/controller/cannon-roundel-2.js
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/controller/cannon-roundel-3.js
Normal file
1
src/assets/controller/cannon-roundel-3.js
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/controller/cannon-roundel.js
Normal file
1
src/assets/controller/cannon-roundel.js
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/controller/enemy-health-hires-0.js
Normal file
1
src/assets/controller/enemy-health-hires-0.js
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/controller/enemy-health-hires-1.js
Normal file
1
src/assets/controller/enemy-health-hires-1.js
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/controller/enemy-health-hires-2.js
Normal file
1
src/assets/controller/enemy-health-hires-2.js
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/controller/enemy-health-v2-0.js
Normal file
1
src/assets/controller/enemy-health-v2-0.js
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/controller/enemy-health-v2-1.js
Normal file
1
src/assets/controller/enemy-health-v2-1.js
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/controller/enemy-health-v2-2.js
Normal file
1
src/assets/controller/enemy-health-v2-2.js
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/controller/enemy-health-v2-3.js
Normal file
1
src/assets/controller/enemy-health-v2-3.js
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/controller/enemy-health-v2-4.js
Normal file
1
src/assets/controller/enemy-health-v2-4.js
Normal file
File diff suppressed because one or more lines are too long
10
src/assets/controller/enemy-health.js
Normal file
10
src/assets/controller/enemy-health.js
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/controller/helm-action.js
Normal file
1
src/assets/controller/helm-action.js
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/controller/helm-frame.js
Normal file
1
src/assets/controller/helm-frame.js
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/controller/helm-knob.js
Normal file
1
src/assets/controller/helm-knob.js
Normal file
@ -0,0 +1 @@
|
||||
export default 'data:image/webp;base64,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';
|
||||
1
src/assets/controller/helm-sheet-0.js
Normal file
1
src/assets/controller/helm-sheet-0.js
Normal file
@ -0,0 +1 @@
|
||||
// Deprecated transfer fragment; helm now uses the sprite sheet in helm-action.js.\n
|
||||
1
src/assets/controller/left-status.js
Normal file
1
src/assets/controller/left-status.js
Normal file
@ -0,0 +1 @@
|
||||
export default 'data:image/webp;base64,UklGRiINAABXRUJQVlA4WAoAAAAQAAAA0AAAOwAAQUxQSOEEAAABoGzbtuq42ec8mZkL5Aoz4weY2WFOWszMKUthZugzMzOnzWi2S1ZUYTbdc3ZDpSc9ybpJMyImAE0qqqoCEbT94cecfGLNk054uLyOpLPOZhBRxYACqEibq3PUVrte8OIGmjeXJAAwJd/dnZ+MjqEAEmlvkhoApBDI4E0jqsDQzvl3fL+uVCp/d8+yN+Z0DgOkraUXkQSDj126kTRvDgWw3Xkvl9cz5fryy+dtC1WJg2oBJiy4bQUZPDtR5Bbdvpwkg7m7m7mR5JpDAKhIKskYgMgAki1EWgEiALYq/EaaeTYK9DwaSDN3pnS3wN96dxwMQJPaioyTRIGkGtkqkKhK0wGiCsy+fTlJy0AUydFryOBsoJG/PNYzaBjSdowaMyrfXTs/aWS+u3Z+lADoHIrqIVO7G5+fgmHDAUCaD4AosN15LwZ6wwToeTTQnI11I5c9+8qtvcVCdbH3puUffvJ+uVS7/PU75VLtdR8+uGTJTStfvqJYKF7xQqWv1PDy0mtfee3Ki3aaBGkFQBXIzXuTDhFIeohIguToNaSx8W7cYvcvmwltCUASYPQbTABBegGAMY8FmjNTNwtpzcw8rZmnNQshmIVq8ywtmAUj3xoDaQ1ABuE8hw7F5Hx37fxkTO6ZMuct0timPfCZCSItApHd+nsffvn+b8ul2uXv7vvuh7UbaM72bSxCWwYd37DBxnZuvmor5ESkFQA5JZiZeVozc3O2d+N1CgDSEhjKGHWuf+HcHbumQFVaAO4RMmCltOJQACrSbEKaR4ibkfy1d8dBAFSbC+t/YaS6G/nzq3M7OgBVqW6W+ftcUfEoIelGbuhbXtwKA2sysIqIJjVV6gJwO0OkkG4kubqwfVd3d34KGq31aE5n91u0kO5mZKWvVFq39OYlxUKhcNVFO3Tlu3a46KpCoVC46qKdB0PqABS3M8RLtRnrrPSV+iqs+ctjPVCpR2b/To8a0qstDGystjCgkWsOQd2CQ/pjp16vZk03/n70kETTIcHNHmKqbmO4DPUmciEt4mj8uTBRJRVk8iv0iKOTRYikSnCFh5ijhZ+PykFSySW0qKMzPJBTSQGZ9Dot6mgMR0HTJDjHIo/uv06HplDZ+WfzuKPx7QkitSDDXmOIPAYWkQoHvkWPPPOVW0NqQTDmLVrcMfAKJCmQw9wN5lHn/ut8aBrRIa/RYs5t81EQpBYc+LFbxBkfy0kdSNDLEG/Gt8ZCUKfqQRvMY81twxwo6hUZ/Co3e6QZP50oUhcEM34jzWPM+NbWCRopOGzpRtI8tty4egakIRBMmPfyZtLiyp1f7Q5FgwXIzX95My2mnH7dVhA0XBTIzXuTwaPJ+MtlAkGWosCYZ0hvDm//geEyqCJjSTDmsU1uWbkFM0bgH0cPUUH2iuQEMngGboHV69eWMu3768e+0pa9r/LaDDSpInfZ76Q1yoxkeP7SO56aPyXfnWXntF07u7fwU4dApTkggpkvb2awRpiRK268+qAckCACFU0rCj16DWnmnsrNyNWFrQAgEahkrCpbfjSzAlsVV5NkMHenu5uRXF3YCtBEBRBEtiiw7Xkvl9ez2ln949KrtgJUEeuqwNDORYUXKmv72b+28tK5O00CVBHzkgDAkKkdO52+U8fUIQBUEfsimqB2ooL/hiKiqiKC/+MBAFZQOCAaCAAAMCYAnQEq0QA8AD7tYqZPqaUkIiyYvhkwHYlqbMgstPo0cVgy3t9FRe4v7Tx7/ou+z6PNvvz4NlL4Cr/QO3T/OeG/nXCrGwwW9uoP7k7q7ZqvlFTQP2VS//yd4E9bpq8X2YJHvCkwF8qteZNR6r3zfOPLIN68D17FM4Vus2K6lhG0AUdsHd+pnVNym1PwiIn9+AUnLclw6BW0wmgLLks6yCPEqwAYJNZq9LhrjT4yo9DMrXsvvoFA6zhqsxTWSfpLjf0cq6BKqEN4bAa8B+7ng/7RkxsjxE/4wgawv43z/8xbZEnynQ9lJkt1Ki9winOqFc4ibM7ard5l1pFyhX6bmThOkFzF7lBKKq1Fi2Lpv03dFhy8UQ2SikutZQp6d7P5HL/ST+1eG8TGQuj3XarjDdpJoB2BAQ4KbgAA/mxWcEZ3ne+RSU4l/POgD7r9DicH0xfHqN54Npe4qtvV190xwxFrqEs2WOR7MAsUoaMMGgU9LxkmlROcb6g/8ESHEaAXbjtj9P88qS4kLnaojD43eStQahwi3oFqWINFcKY5omWDI+KK2zChhGh/SIJSk96EMHUneyd/o+ndlQqWyDT8Um/0iGLGCHGLc6BXQKjewfOra7CJCGLbMF5vUCSfQ7p5GrE4m8uetZz0c85dY2pSpChu2P6c1alw+SjWOAU4euJ9K0FQ8vcBeArQ+wRhKzQ1UCyynxB376X9OSFiHknVnReCxwcvsLZvn8yngh1uQFflHCCGdSUvcMqk/J9N8VUFWw+CXK9H3OPmK+tpJfBsyVL0Fx57l358fm2YqIbOmStemPh/J2H/YUBP9wBUgEP2f5pihb3xQzoEyI1bjgmGj9ncPNkzFYppetQXP34D0LPQacV/rS1CF/DKrqb2HpKwCh+cV7F95kAOlyAAYT7mgHLGZJ6WFDcLZsGmarYzeLMRKZN0q7qhzP5/WFqzhgaa0mxmQaKc5sQAtz6fq43mz1Anm0PfoeAEBODtzuuKXRH/P7X3LJAKDa5JdqBalync5/1sUwVjfHde6Tclmvk22bRo0ee4WpKAFEQ/GwtmN3hx3GLquFd4KPNWuoEuAi6aPwGnoKqpmNAS686aggi+vqbqOOmgPNPp2ecjDnBwYecwLW/MaKSzpiLmPziPCEnPDFCsTF3YWb82qVgNufrRKcCZNIeDClF2ryWF/joR9AMtlzbU3Z4LWWAHpviFkeHqrs5vRVjcp9F1CWKhqu/ADUzY3rSdh/a/aCfTM6p3RbwrF0H2hNg2Jjz7ZP3PTtmZT+h1Q3QmSOFbtkeWY9bTQaJmCpv18gqdKW7j/Gqi7oNGitaORAAdMAjZHfRwt/QzpdnW7mDOTd44tmuIXNfZSCw4clGtlC25TB1+loC6d9+g2atR+ssNuZpiyPiyuMEIe/e59dBD7sdsLbZn0fVqIBJHC5Sf8U5WQEzxcahRZBKbUm5KLaokKfDjtBYz81TWWl3/IT5QYsdrbobc0M/f5yOKOgtiwgqkaf9a97GubUUvu1kQQLYRn1U6BiJFSZxWTeJ9R3cOXkGGkgPLw205lfogrnDiofY+e0pbEhSfyQweGaEV/ZvUJODgxgAJO5wgzBFe1Lnw5TsVJ2pX1O80vzZINGdcW7xiLX3lVv83Muz+wb/g3CIQCP/X4Ra/K+FjY3v3Cf6hvtiA4t2taZl97R98Rri06u3U0NI0tCXWOGofxV5qEnA9ykLoUtFsTNdDisJYH/4ItXfy2L1eCEktft9ReG4KEgdvCXCzDjMqlY3iggMCQI6+xDrnOv2hHJeKN/zz6d07f//0XcVQ0lS3mlGIyX9U2xGQhh0clvtjQO2pELN7RL4dad9K+BZW25Ec0XwwsSIbx2FBmtU/C9Vg4URHwFCBb285t+QRWuaPTmjMBkEAPPUwykCjXVTemUmGVz50gVowT3R1Jugq/A9Y+gvlaLmVgfi0pg9H4lC3bEFjKdhAAvDKi68yZpU5aAfggy+TgNChkS6CR3RmzdLec5aqnJFRUghe4HX54OqLzIrvx9M5hyB9n4zAXTWtAcaKEd24zMbZ4l9HL1jqtZzE+DNQy//IxhnRemTNdbP4kGENiKx1p1rahKFSaJpgOYpD0mUVgjouGukV1Oo8hp575KCGY8Ypvwpr8CJXtkhUVj8HmQMaIVTsAxJjhswmCGPjTQToBXhMzTcXfbOy7RniODCAWa9+2kkKh8cuerfyuABtn7C72to6tJ4Fa73xNEsSplBo5lNEA7sX1wNWfkpQwYC842wGJNZqi6IKHGzEzM8lAMlwr8Wy4/F0HAUQEn8SP9uaVy9HTll2Z2yGAVSS/RMZcvH8xPRUoZC7aN0r33lVcxtMQtFO//Vw+XzF5kNuvrxqY5JV9NsbPR9GILmuUmnYJ65OCE/+C9O0TJDP5ujcrwYE9dkWM7vxknFIKcH+bt1dBU/J47idSbkTxiHDkyD4ZmndW5VCAz4RX+b4w1XtPtCmHwtZ9gg8fpBnfqHRn0BnZ+4s7ujcMZhhBrST/aF/+2VGdgwfmyPImSasTEy7iVM1oHar0lHLx98xnbQhJZSoBCEBW5J+YYaHbD+F3VvaePM1PR98A/IKA2cDa7WFJddESIsMG+HpiGF5SkflpvRPL+RA091U7mr3Uomouv/XDWMlibzEP6YN0/NwkWvDpfKpHZBwtJyFUHXfrDn1nhogBlH3iXe78Iig/ZdyeHbNpIvYCvNlnT2eoiOyBifPSe2n6F7i6ELLlRW9b/LohbgCTQ/i6YAAAA==';
|
||||
1
src/assets/controller/player-health-0.js
Normal file
1
src/assets/controller/player-health-0.js
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/controller/player-health-frame-00.js
Normal file
1
src/assets/controller/player-health-frame-00.js
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/controller/player-health-frame-01.js
Normal file
1
src/assets/controller/player-health-frame-01.js
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/controller/player-health-frame-02.js
Normal file
1
src/assets/controller/player-health-frame-02.js
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/controller/player-health-frame-03.js
Normal file
1
src/assets/controller/player-health-frame-03.js
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/controller/player-health-frame-v2-00.js
Normal file
1
src/assets/controller/player-health-frame-v2-00.js
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/controller/player-health-frame-v2-02.js
Normal file
1
src/assets/controller/player-health-frame-v2-02.js
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/controller/player-health-frame-v2-03.js
Normal file
1
src/assets/controller/player-health-frame-v2-03.js
Normal file
File diff suppressed because one or more lines are too long
BIN
src/assets/controller/player-health-fresh-sheet.png
Normal file
BIN
src/assets/controller/player-health-fresh-sheet.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 715 KiB |
1
src/assets/controller/player-health-hires-0.js
Normal file
1
src/assets/controller/player-health-hires-0.js
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/controller/player-health-hires65-00.js
Normal file
1
src/assets/controller/player-health-hires65-00.js
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/controller/player-health-hires65-01.js
Normal file
1
src/assets/controller/player-health-hires65-01.js
Normal file
@ -0,0 +1 @@
|
||||
export default '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';
|
||||
1
src/assets/controller/player-health-hires65-02.js
Normal file
1
src/assets/controller/player-health-hires65-02.js
Normal file
@ -0,0 +1 @@
|
||||
export default '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';
|
||||
1
src/assets/controller/player-health-hires65-03.js
Normal file
1
src/assets/controller/player-health-hires65-03.js
Normal file
@ -0,0 +1 @@
|
||||
export default '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';
|
||||
1
src/assets/controller/player-health-hq-frame.js
Normal file
1
src/assets/controller/player-health-hq-frame.js
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/controller/player-health-v2-1.js
Normal file
1
src/assets/controller/player-health-v2-1.js
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/controller/player-health-v2-2.js
Normal file
1
src/assets/controller/player-health-v2-2.js
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/controller/player-health-v2-3.js
Normal file
1
src/assets/controller/player-health-v2-3.js
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/controller/player-health-v2-4.js
Normal file
1
src/assets/controller/player-health-v2-4.js
Normal file
@ -0,0 +1 @@
|
||||
export default 'qec+vpCfLWLP0E2SH5jhZYDQAOhmTfJuU/5zZNNa/mq+PawgCql9a6Ehxb7rdYEFuZ1AAA==';
|
||||
1
src/assets/controller/player-health-v4-0.js
Normal file
1
src/assets/controller/player-health-v4-0.js
Normal file
@ -0,0 +1 @@
|
||||
export default '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';
|
||||
1
src/assets/controller/player-health-v4-1.js
Normal file
1
src/assets/controller/player-health-v4-1.js
Normal file
@ -0,0 +1 @@
|
||||
export default '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';
|
||||
1
src/assets/controller/player-health-v4-2.js
Normal file
1
src/assets/controller/player-health-v4-2.js
Normal file
@ -0,0 +1 @@
|
||||
export default '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';
|
||||
1
src/assets/controller/player-health-v4-3.js
Normal file
1
src/assets/controller/player-health-v4-3.js
Normal file
@ -0,0 +1 @@
|
||||
export default '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';
|
||||
6
src/assets/controller/player-health.js
Normal file
6
src/assets/controller/player-health.js
Normal file
@ -0,0 +1,6 @@
|
||||
import c0 from './player-health-v4-0.js';
|
||||
import c1 from './player-health-v4-1.js';
|
||||
import c2 from './player-health-v4-2.js';
|
||||
import c3 from './player-health-v4-3.js';
|
||||
|
||||
export default `data:image/webp;base64,${c0}${c1}${c2}${c3}`;
|
||||
1
src/assets/controller/radar-panel.js
Normal file
1
src/assets/controller/radar-panel.js
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/controller/right-controls.js
Normal file
1
src/assets/controller/right-controls.js
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/controller/top-hud-0.js
Normal file
1
src/assets/controller/top-hud-0.js
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/controller/top-hud-1.js
Normal file
1
src/assets/controller/top-hud-1.js
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/controller/top-hud-2.js
Normal file
1
src/assets/controller/top-hud-2.js
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/controller/top-hud-v2-0.js
Normal file
1
src/assets/controller/top-hud-v2-0.js
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/controller/top-hud-v2-1.js
Normal file
1
src/assets/controller/top-hud-v2-1.js
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/controller/top-hud-v3-0.js
Normal file
1
src/assets/controller/top-hud-v3-0.js
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/controller/top-hud-v4-0.js
Normal file
1
src/assets/controller/top-hud-v4-0.js
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/controller/top-hud-v4-1.js
Normal file
1
src/assets/controller/top-hud-v4-1.js
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/controller/top-hud-v5-0.js
Normal file
1
src/assets/controller/top-hud-v5-0.js
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/controller/top-hud-v5-1.js
Normal file
1
src/assets/controller/top-hud-v5-1.js
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/controller/top-hud-v5-2.js
Normal file
1
src/assets/controller/top-hud-v5-2.js
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/controller/top-hud-v5-3.js
Normal file
1
src/assets/controller/top-hud-v5-3.js
Normal file
@ -0,0 +1 @@
|
||||
export default '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';
|
||||
6
src/assets/controller/top-hud.js
Normal file
6
src/assets/controller/top-hud.js
Normal file
@ -0,0 +1,6 @@
|
||||
import c0 from './top-hud-v5-0.js';
|
||||
import c1 from './top-hud-v5-1.js';
|
||||
import c2 from './top-hud-v5-2.js';
|
||||
import c3 from './top-hud-v5-3.js';
|
||||
|
||||
export default `data:image/webp;base64,${c0}${c1}${c2}${c3}`;
|
||||
56
src/assets/explosions.svg
Normal file
56
src/assets/explosions.svg
Normal file
@ -0,0 +1,56 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="384" height="64" viewBox="0 0 384 64" shape-rendering="crispEdges">
|
||||
<defs>
|
||||
<radialGradient id="blast" cx="50%" cy="50%" r="55%">
|
||||
<stop offset="0" stop-color="#fff6b0"/>
|
||||
<stop offset="0.28" stop-color="#ffd457"/>
|
||||
<stop offset="0.58" stop-color="#f28b2b"/>
|
||||
<stop offset="0.82" stop-color="#b23d25"/>
|
||||
<stop offset="1" stop-color="#4b241f" stop-opacity=".2"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="smoke" cx="50%" cy="45%" r="60%">
|
||||
<stop offset="0" stop-color="#6d635e"/>
|
||||
<stop offset=".65" stop-color="#3d3a3d"/>
|
||||
<stop offset="1" stop-color="#20242b" stop-opacity=".1"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
|
||||
<g transform="translate(0 0)">
|
||||
<circle cx="32" cy="32" r="8" fill="url(#blast)"/>
|
||||
<path d="M32 15l4 11 12-6-7 12 10 7-13 1-2 12-7-10-11 7 6-12-10-7 13-1z" fill="#ffb52e" opacity=".88"/>
|
||||
</g>
|
||||
<g transform="translate(64 0)">
|
||||
<circle cx="32" cy="32" r="15" fill="url(#blast)"/>
|
||||
<circle cx="20" cy="31" r="7" fill="#f47a25"/>
|
||||
<circle cx="45" cy="27" r="8" fill="#ff9a2e"/>
|
||||
<circle cx="36" cy="45" r="8" fill="#d94f24"/>
|
||||
</g>
|
||||
<g transform="translate(128 0)">
|
||||
<circle cx="32" cy="32" r="22" fill="url(#blast)"/>
|
||||
<circle cx="16" cy="28" r="8" fill="#f36f25"/>
|
||||
<circle cx="48" cy="25" r="9" fill="#f7932f"/>
|
||||
<circle cx="43" cy="46" r="9" fill="#cf4a24"/>
|
||||
<circle cx="23" cy="47" r="8" fill="#e15c25"/>
|
||||
</g>
|
||||
<g transform="translate(192 0)">
|
||||
<circle cx="32" cy="32" r="19" fill="#b84a27" opacity=".9"/>
|
||||
<circle cx="19" cy="26" r="10" fill="#ed7029" opacity=".88"/>
|
||||
<circle cx="45" cy="29" r="11" fill="#d65b25" opacity=".86"/>
|
||||
<circle cx="35" cy="45" r="10" fill="#7a3427" opacity=".82"/>
|
||||
<circle cx="31" cy="30" r="7" fill="#ffc948" opacity=".8"/>
|
||||
</g>
|
||||
<g transform="translate(256 0)">
|
||||
<circle cx="25" cy="30" r="13" fill="url(#smoke)" opacity=".92"/>
|
||||
<circle cx="42" cy="28" r="14" fill="url(#smoke)" opacity=".88"/>
|
||||
<circle cx="34" cy="43" r="13" fill="url(#smoke)" opacity=".84"/>
|
||||
<circle cx="22" cy="46" r="8" fill="#272b31" opacity=".7"/>
|
||||
<circle cx="46" cy="45" r="7" fill="#272b31" opacity=".65"/>
|
||||
<circle cx="31" cy="29" r="4" fill="#cc5927" opacity=".8"/>
|
||||
</g>
|
||||
<g transform="translate(320 0)">
|
||||
<circle cx="24" cy="31" r="12" fill="url(#smoke)" opacity=".7"/>
|
||||
<circle cx="41" cy="30" r="13" fill="url(#smoke)" opacity=".68"/>
|
||||
<circle cx="34" cy="43" r="12" fill="url(#smoke)" opacity=".62"/>
|
||||
<circle cx="18" cy="42" r="6" fill="#252a31" opacity=".45"/>
|
||||
<circle cx="48" cy="43" r="6" fill="#252a31" opacity=".42"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
BIN
src/assets/player-airship-4frame.png
Normal file
BIN
src/assets/player-airship-4frame.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 212 KiB |
1
src/assets/steampunk-hud-compact/p0.js
Normal file
1
src/assets/steampunk-hud-compact/p0.js
Normal file
@ -0,0 +1 @@
|
||||
export default 'iVBORw0KGgoAAAANSUhEUgAAAMMAAAFaCAMAAACUvxmnAAAAwFBMVEUAAAATJCafYhxYKwduSRrTjjFpHALgp1Fca13GdSFtYwkOOkIiS1JUKgWpj1iiWwlTKQOSWxezhDX4zGiPKwYqFgiQVhD43Zeac0f/AAAvFQNzhXF2RxB0Rg5hlJj//ADq8d6Vo52k09rfuoWoMQA5Qzr3egHnoA5PeoGJMQWMNAGZkEhrdGcAAHGevMPdeUP///8STk5+ucOYZkz/yDsAAP8AfwA9hJMA//94h0t5xM+Ja1DOJQX/AP/NhxLQi0W9tSUYAAAAQHRSTlMA/Pr6+vwN/Pz8Cvz8YvwOmGzz/PtdmPz8AZr9YqD9Af39/fwI/QQJ/VWZCwgC/fsBBf0L/QEC/QER/W/9AW0L6qf8SQAAH4dJREFUeNrtXQd74zbSJjEgQVJiUae08tpeb8sl1+/r7f//q28KAHZJzvou1j2cxCoUBeLF9BloGQR/T4q/rZRS+zKLgzulYxnsQRkF++Dz8S4RZCX+KVgfVKhWQVCWd7Pyj7/EZZYdCUAQ53sVgoJQQ07ChB+Un/FF/J4lK+vowh5VATEAIA5Uitwe//aNPnyvEMqg3O/3u1VZxlmulEFNCEP6C6E2kdrmZXnePyCcVRxkfwcFOf41+0ETFGdBqRQtOf2lpsbJa7WOiBuWF0rLh6rKekz7VXKb9ZcQB0ULiP+9nvCLZfavQUB2VPGcSYhCKDbGbPC9idJIMSLEphW9QXsbfM3irCQajugPdz4tHfG7wSIG5eoH16VE+cfVLgywCoTabA6IAeUJMUQGZUmHrOBqg2zRUP2oSmSrVU+QldYPDw8V/p3PK0co3Sv5n17SO3f8wb22R/cPDyIrh802JA2oD4igxmkzgYqQkDeoHuYQgVb4v963LnWBxk7CGeAAVWMbyrhET8SXieSxTWl0mdbyx0IEgKqLEGiiGxW2CT9PFfMIzxK1iNbrdXQbpcNZ0JKdvVZl5E4VsAWRZQN5BIBwhMBS/ygxAA9vUY6UKfjgIgw/ehRqrRgB8AWguQo9+VHlLZ8hg9rzQ3eifIuYDuAwoDLoFoAfIDcBYzQPt0RafFyEywV/rLcqtbN8q6utBMTxEU1imr5qVAi/jB5XjMFsWI6WCWFYfmckC5GnSNmVtYSH5aNfQ8gKKBlETDYx8qPquh79QtHFMH5hoLkXJmpBoKcFgVgwiFSJdXKU6M6wlpatx84H+IVCN1dDeRJn+Y2Ey325QCMe6cHktK61HsGAT0nSxoDB0UEgtGhBIOzSkV432l48HYweTrU29FkxAq7AEfw3yIiU5NOyb1UYuVELOlurIYZQdTH4QZ8+ffq0bBuDwtA1lo4NDQ7iBIj389Kw2WxMOjKsMmFiYPhBwfbZY4hCIL+ciVWyjMLnL4RvIOdofFvQ7JiLZfIJzc7yL8nSnYc2iU1SM3n7euGkCcUW/CTWFIu0VqoFIhoTMhPqtfZ8pJAyZsNaKo8B1/pLOsIITbZXD5m+TMLk+Tnxkktu2LLBU5KQPiytOAG5umYSGi+lRsU+GlWUNc4NwhYGtUcmBDHOzWMQRtcDhVjTSYMxcVrPnz5+/PS8tDqB+DGwaLPhD09I3xdWnugUkls3lI4KFY3xARVCmRGdxglCWKvWO6geMdUiYM3CMMOHC6OLkXVZJB+/86J9XLYweI1GVX5CiP/2n0/PoWeEIsvkVUBHbaPuL1FHYUeidYNB1c1xwqB0HOw7GMKu0WyNr6Zsa9u+45iRaazSx6e/0IcL4pRlQ8gRR2t2';
|
||||
1
src/assets/steampunk-hud-compact/p1.js
Normal file
1
src/assets/steampunk-hud-compact/p1.js
Normal file
@ -0,0 +1 @@
|
||||
export default 'OhzBoPsr2DV9HQxQBWiq1FCJBxgWHXQTDhFSXFgSZJadj59I4dlJ4+vnZDGGoWf9wmvz6J6vkO2gAoAuH2757iQGRRiQSQuUHPZRqC9PT8/MDIawIGnQb4iBXETA4csUhvDVGFA+ISRbxLYqIX14/vTpmfWfnAZhmOQDvB4DxZBBDmH4agx6GgNwuIeLjkYXhYmQkFT94Q+Juy68KQYIq6B80G/GB9ZpNuwkOeLgWIYWGPm566aRfjMMKWHIKHlQb4khTVXbwCGCxbJt8BRZ09dimOC7SkMyrYghbvz0D+p0yCmaspOXYHVBnCAlt6dw5PpGGEiWcomX9NthUBKZijQtBAOL08K6EaD4CN6ODyHHS1nwAG8lS+ixWoxYuIkLFJdW29TyLTCQUZJUdI/CpF+JYTE5i1ZOOMzSwHRi7x/DoCNtMQTBmQsNb+IfKPiOfBS36GZjHAgRhqkVe51dApNSoGGrAjkJwOs4MZGlc4W1PdYi/N5eOdPPRX89aWQpVE29L5dqKLps3SElD9A7wsegeza9ww83UlltTfJ7V5KA1Jq8efvLSulXEdBXSBu+NmW/GA9COloPu7GKlVLRCjwj2ll/B4KwgU2wrcqZ6PVEU6VlOLXKrWXVqnpJAahNzpD0DkO7XAb+m1w0aUAkLeaTUW2N1xuuN/bYTDqzJMPartrmeOh0qvKGqqriB6H+B/Rs39k3u9PpFHpoqm8lwBg5xHOo5Fp2ZHul3uVaF21e4qtKroXj5P269Vs0zHK/gqorULpAbVZ+BU9v0bEppyrnGXUT4slebMzdEttRGzkpr1w11bYhUKq0KogHDEFkI/86OX6/tdA5MabZZc3F4/Eh7IvHH+sJBbvKozCYuQkCjm1+pOMQd4DFEw214Fu5WuWrMguO2ZFaPNzlyeQv40VwryxNLEKZW62QMJBY47smx/7Vs6sUx8c4I/lZ7VfnmNoNEw2ux+DzyrmA8ysWfZy+nitny+gpz6Z4f3MDqtzT5KjbE3fbea7HRYxCBCi6hi1Iddv+hIsnZZWNE6o4+5F+LnE3i/ekWBF1/DRUJbUPY+rMff7cbSgXh4Mp2PeQc1Y30UMZHyfFl8In5ERwobEufexbqNgcyMVgUKlAt+1q0+NCNpnCV1kixSAuAuEoQWHYFU91myWahEkAmEeqKLo8db4IQF2YAlzAiu+bRl2wX/tzDXUhXDGLAje4ITbD0Ccf7BFo7BM7vXzaCpbtWuWlaFIfbB18YQNkZ/fWKgjsS4D1RnNVZbm0SdJt0SV+9YEaw2KybCvZ8uUYxBVBaMTLNrTLmDvk8QpUelNErAua3XKZFNLiST0IvUO3xDEnFaw1N29C16dKb0vuNDcK8+zS1o2RYwRohfij265SSDX98HTYcOEEUomcQaN6Pwbig8LI8IzWZrORuq++LcyngiHB0Odz7n1sNnAYfCx2B8/nhwrNRrercYkPhmeXHA6IIbViwnsqFAlkcJbcgee93hzoNDxRugnXUei1MNVG6k63quGyI8dReumMte8w40VvSL60nV2oeO8BCznaO8awRwTHY3BCtoQ61bQ/wSAbNhTTK5b06+kI8aAbSltrVfZineBcF0qSpSZgl5To2jXQoEpLBZkW2aYLpMCFUVqcY/xIjADur5BUpanbNEJHQVIun2hBk8tpmOzCc/miAwK5UJuhcLIL7+SPYP/kNX1AF7GNW5bbsPY9pxQNuyxQLFGN67/bzTq22wxXSfs5iCnXtkRftYKpLDgXEnvrvu3XoxmQi+HtjgEncNGGew0Og1LWbJckqAAp';
|
||||
1
src/assets/steampunk-hud-compact/p2.js
Normal file
1
src/assets/steampunk-hud-compact/p2.js
Normal file
@ -0,0 +1 @@
|
||||
export default 'dDIrW0kGzDua3Tc2EaFXrYPyjnWVshReDeA1yloQaOMMhImq8s5YOFreDDtCeQ6hq7sk1GsEKdVQCaW5RFY+gKt9a+h0XKB6bWzzc1YpWQBapMyzei3TKPSrg6VfBAS3RWu0Y0mRWqsJ4JPROMhZuFzL14ptbUP+uBW2uz1XEhCL6ewv3IOypVdoxXlg97QoyMr+Bi7/KhuPKnNougumsKJEneoWp/G0CshrSIjhkkg6WVP/F0f/j1/awXweZ/ai8SDSP1d8OSrA7IIWehC/j8yJ+6lBGxBT+XOZ/bX8+Wd7NVIG+XZdFOhUqG2rea/XIBFu8l0XQzQ6vm6IQxR3jA2+O7r22+BwmVJ9Cto6nYP4/dZY1+JU72wkQiL3JUNoYgMLejwFopGkX12PQwhQdiWigujX1/c0dOva9YiuYoy2C6HZbaIL1U7sJXwvHE/ooVgXRTG1lDjbYeliB6ofZReDpfcsVuvueCE0QSjP7sSz7qwThpgiOFI7VEPXoJv6InTKhi034S3zqRzG23lo3bj357o1eMevuepn65ywNTt8W40nhpkUIyLr40757Vnn3592FfiUAWe3m65xucUPBzW0t/glwQ9+3YtGeIovXOQxP3G7NLeuqVO68tQ+4OtavTMulsDcF67sCA46L4K90we4tB4Se2TvQHTGNgHnymnPtTLI8V0i4HTEYTgFd0oYN6oQkkRD37fdFwYbq98xhozTdkUYjncqTGWOniFJKC+K7/OHXqjSZ8XRAMYa+X0yIYjP2uaxqBF5/OM/ZPnHc2GldEGlL6oCUZHvIbgzxS6Dla4PB1PbIgfVjPflXYHgOl5xqFubr8hE5XckTiRI9YGygWTZVLQVwP2IU/xYKk07sJeJ7LteuML3wx1J04M2G7AQErtPllsQ+Z34ujjItN4Y+qEFilKYbJJw4Qrf1Z1oRIw2SW1sh+rZGLPRC9cfUeW9qLVGayp7yF82qt6o0P2mBdR9RH8YrmopTS3DAtmRFG5/nVZXs6F3I0oYJjGGxQJZsGw21+m78RFlULmfHy7RvvJOWfkZFfNhdx/mtfI1vohUAQVpo93O0B/dxvKP4sPe74NWxvZI/e7W+3DVJZXuXRObNsQvXfU+pd8G3UmwlNutjgwC/YMtCuuRyvd7pWMAraI3+EZVsaWfPtxL1Br7Xla7O0BV1zsKvashiOKOJIkwHGPobTalBgS0N7PcAyPs1mSnzWtpj+zuKJH7n1z2WHuy7fN7wkC9Z3D/igD41/cSaPiwT7uN52D/cQjkxh0VBTLeAzDSK93dk07nanRX0x1hiIPTPysGpe+pH/Sn0V8QoZ4/3hEIGO6PZDN7RxCCE6z9zma7j4E83eme7FLV/DBINs/JJrnqnnwcRd/h6bTzdOLfJeTB3f7bhZZ29zVd6fu7f9rP/xToeOdceN//UuFMM80000wzzTTTTDPNNNNMM80000wzzTTTTDPNNNNMM80000wzzTTTTDPNNNNMM80000wzzTTTTDPNNNNMM80000wzzTTTTDPNNNNMM80000wzzTTTTDPNNNNMM80000wzzTTTTDPNNNNMM80000wzzXSHFGfZvSOg+zvdP4qyDO75jmdZsKrUWlX5nc7/iBwI9vZ2t9W3oLw3ZmQlP61Aqc2B7om+/z0fzu7lBm6lAIhXzIXDIVKgldqLQGX3wI2SbuGZWSHSAGlEd2OUG0vu85juW38HMiTrz7cdphtX892r+R3DiIN3fUe97Cs+nPd8y2dV1zXdhRtUxEDordybm4Xq';
|
||||
1
src/assets/steampunk-hud-compact/p2a.js
Normal file
1
src/assets/steampunk-hud-compact/p2a.js
Normal file
@ -0,0 +1 @@
|
||||
export default 'dDIrW0kGzDua3Tc2EaFXrYPyjnWVshReDeA1yloQaOMMhImq8s5YOFreDDtCeQ6hq7sk1GsEKdVQCaW5RFY+gKt9a+h0XKB6bWzzc1YpWQBapMyzei3TKPSrg6VfBAS3RWu0Y0mRWqsJ4JPROMhZuFzL14ptbUP+uBW2uz1XEhCL6ewv3IOypVdoxXlg97QoyMr+Bi7/KhuPKnNougumsKJEneoWp/G0CshrSIjhkkg6WVP/F0f/j1/awXweZ/ai8SDSP1d8OSrA7IIWehC/j8yJ+6lBGxBT+XOZ/bX8+Wd7NVIG+XZdFOhUqG2rea/XIBFu8l0XQzQ6vm6IQxR3jA2+O7r22+BwmVJ9Cto6nYP4/dZY1+JU72wkQiL3JUNoYgMLejwFopGkX12PQwhQdiWigujX1/c0dOva9YiuYoy2C6HZbaIL1U7sJXwvHE/ooVgXRTG1lDjbYeliB6ofZReDpfcsVuvueCE0QSjP7sSz7qwThpgiOFI7VEPXoJv6InTKhi034S3zqRzG23lo3bj357o1eMevuepn65ywNTt8W40nhpkUIyLr40757Vnn3592FfiUAWe3m65xucUPBzW0t/glwQ9+3YtGeIovXOQxP3G7NLeuqVO68tQ+4OtavTMulsDcF67sCA46L4K90we4tB4Se2TvQHTGNgHnymnPtTLI8V0i4HTEYTgFd0oYN6oQkkRD37fdFwYbq98xhozTdkUYjncqTGWOniFJKC+K7/OHXqjSZ8XRAMYa+X0yIYjP2uaxqBF5/OM/ZPnHc2GldEGlL6oCUZHvIbgzxS6Dla4PB1PbIgfVjPflXYHgOl5xqFubr8hE5XckTiRI9YGygWTZVLQVwP2IU/xYKk07sJeJ7LteuML3wx1J04M2G7AQ';
|
||||
1
src/assets/steampunk-hud-compact/p2b.js
Normal file
1
src/assets/steampunk-hud-compact/p2b.js
Normal file
@ -0,0 +1 @@
|
||||
export default 'ErtPllsQ+Z34ujjItN4Y+qEFilKYbJJw4Qrf1Z1oRIw2SW1sh+rZGLPRC9cfUeW9qLVGayp7yF82qt6o0P2mBdR9RH8YrmopTS3DAtmRFG5/nVZXs6F3I0oYJjGGxQJZsGw21+m78RFlULmfHy7RvvJOWfkZFfNhdx/mtfI1vohUAQVpo93O0B/dxvKP4sPe74NWxvZI/e7W+3DVJZXuXRObNsQvXfU+pd8G3UmwlNutjgwC/YMtCuuRyvd7pWMAraI3+EZVsaWfPtxL1Br7Xla7O0BV1zsKvashiOKOJIkwHGPobTalBgS0N7PcAyPs1mSnzWtpj+zuKJH7n1z2WHuy7fN7wkC9Z3D/igD41/cSaPiwT7uN52D/cQjkxh0VBTLeAzDSK93dk07nanRX0x1hiIPTPysGpe+pH/Sn0V8QoZ4/3hEIGO6PZDN7RxCCE6z9zma7j4E83eme7FLV/DBINs/JJrnqnnwcRd/h6bTzdOLfJeTB3f7bhZZ29zVd6fu7f9rP/xToeOdceN//UuFMM80000wzzTTTTDPNNNNMM80000wzzTTTTDPNNNNMM80000wzzTTTTDPNNNNMM80000wzzTTTTDPNNNNMM80000wzzTTTTDPNNNNMM80000wzzTTTTDPNNNNMM80000wzzTTTTDPNNNNMM80000wzzTTTTDPNNNNMM80000wzzXSHFGfZvSOg+zvdP4qyDO75jmdZsKrUWlX5nc7/iBwI9vZ2t9W3oLw3ZmQlP61Aqc2B7om+/z0fzu7lBm6lAIhXzIXDIVKgldqLQGX3wI2SbuGZWSHSAGlEd2OUG0vu85juW38HMiTrz7cdphtX892r+R3DiIN3fUe97Cs+nPd8y2dV1zXdhRtUxEDordybm4Xq';
|
||||
1
src/assets/steampunk-hud-compact/p3.js
Normal file
1
src/assets/steampunk-hud-compact/p3.js
Normal file
@ -0,0 +1 @@
|
||||
export default 'vSoGerJ4z/fcxhnr2t5n1d8VXeSJuUHMeI+sQEtaMgIEkNC0dV0U5rAxBv83BfKGcNQWBqKI3533RkU4C4KC5b84HDY4+SiKVIQYDL3hO7xrVbOdyt+bdmNERGKECGieCECmj3OPTBQafhMRR0TB6y2xonxHul3STM4kIzUpgcY1l/kT0f3cFSERQqliFae7oxMr3pFEfRMmCAKcr4mMZQR5hwZBlHoUxIpq9V7EabU/aYaAUysOhADfiSojrZUgwuNklwyhUAyCTNY+z9+DICELSGJqUgSWIpwzKFZhcPexJlOEh9FvGHsC+T5QhL367c1RhVqsDXtjYgIvOD7o4b3EEYAoN0kUIlbICIL3qhsqZ/GjW7w30qSYQlMUcPZjTnqMGbsZuqBoVIPlKVQYEL4CRDZMEq+DLi+Gy1lQKoieNhREhIIg6iNYdGFozwoEAOrA341vA0EsKPfkSCkOXmU3OHuXBscX2IADplGKs4ZNpFICUfR4sFwOeEEQUD9IllSKj9vbbjGeiRvVupbbrEvIEuM6x1lDZRzzsSzDp4xTgP35YmxTaVxMEoqNSTekyaovP8tkIFEoUArXHzU/hS26D4Ab5WivdFKgyK4V2Ttk4MM17Bj+oGXR+oL1O+Zy3/YUBYSk2wwVYYiBQNCXyNjynd/D3U2ql+PqYwSAUYtWG+YlDoKLvFrtVx06n+Upr/aaLSJdJ8/GWfE5+J3cdJ70ACRV6NKCZGmxGOg28U4mQShu0AeGUGw2B1NrSaooFJOc5AKh4qSsc2pC68rgJ5QGnNOa7WmkhpbIYhiCwFAQ9TriNzfog0BAMdIdxUq1qJePAwakFJs+eqgeh1eJj+X2jzRvtXHWsj17O3PEsORXXRzs7FKGDen2mnXNHkulyXV2rB35SLB+lOIXeenfN+5VkkjIx9jwO5QGTSaJ12AAgVEIhkWfF7x+KZU8gFT7ijRlwZ5UgZiQJEsiGU6lKZDKakSjmeyTvCaMND6nLhgbPPbd4leRJFYG4iK0lqg1Z4shHAGRIkW0UqmqLtY7ymClahOtCYHDsLQglCs4TBFbDYmV816c/Dn4vDXbRpKg4UC46GLg1/LQsU4IgrwEKgRJU3YJwx7j4YIG8xhwOErX03QiJmjZD+YD639uy3fWqn7+k0hSaHrK0BMb8XGLRrr8h+wdVSqMgPJ4QaFjjeEk4FiIgab/JQy/oIEFYcRlDORJ6WoUodnykB/3J/VHkASHMLQ1oW1MvyTJl5Z+tDGQsWBHzRpxQa3j4KyVWdN6IANCjfHK2hyQtIjIdU5wbSJ0EuWSluxPFelTGEr8o4bKTDkpZREFJRHawevYpqjNiEtajU6drCqyAQP9w+GJ/xdjfpURPH3gB3ZGiMIpdIlCDBR2c7oGbQhLlnzK6EyxNoVW67Vhw77oogCJm0jptpcZoTUr3GJpNoenpydM1zf0VbgdA7vVLYU24DKWDD00mTV0b8akjTZ4iaFkYr0uimKt9Hqt1Fpsb5cTKEyGhElMUxlPGdYzYeBvHLhcEim2RwziNgwSGmyRAHauME9sQA4BpWvK9CQJX7xsoqKgikxRozAVaK3XbH0X3cCJclgWpgs+An005X9W+mj9SQe2tvgGt2NAAIQh/3cfZbBGkyjpvjZQPmSIQQdTOELfo6LBxRTFrpF2zrqcwLADzSG9iVwlugnNQN2EgR4KTG2QE7kP9jhgE6tktcF6MoYQkeV4KcglfaCH2lDWve6PrslFpBJwqCo4TmNwIQVEwgZoJngNgsdQox1APvwkonTMFIkSiHNoXLRV';
|
||||
1
src/assets/steampunk-hud-compact/p4.js
Normal file
1
src/assets/steampunk-hud-compact/p4.js
Normal file
@ -0,0 +1 @@
|
||||
export default 'WpyuUs8Fz/8DY0iSgmtKA7PHkmGjmolWUUayFKXOqRBHoaNTt2FIoX5BEMby4TOpAwoSgOn4N+fdDClzIvN3GJKid3E7pdRi2FIoUE7pg/fHKsRXshYLB+pWPhRoEVC+VxbDnqwSxmsDDDRwgWrwMsCQkPYPGMEegoNOqtJMWVfSeWXno6DNUZQtfbM+IIKXF8FwfIyVWFZteqHSwrLBFG7mH/yrYo2M6BlYaCnEFn6eMq4VYiBhSgpy6xotQaRDj+kqH8SfEwaSppUtEoBgkEAD2nEGlS7Uumhm7l+RNHHpu4tBKZtFKIiPE0ETCROdhVelBI5ycFcmuY5BNxheTINhBZLASTUYupUYNKTtiX9oY2BGtDEoCZkksFxNlvM1NAvenrNR2+sQwMnSCwVZHLxSNG+tsqI0RvWqSWbdxtDoQ5KsHQaPosHAUj4Zge84cx7Mr4iUug6BDTHZVgoUTU18eAwqpQ4RmmqbfPRKYhhZTGAgdz2IKQsagUaiJnD5OOEiyK2laR+CSNK1HIhnqNNQH14shjgoqQCMf1FID2D1dLEUQgEzKEAfrFFqY1BcA8SkSHIY0clNJDXLaJNOli0xDJG+altwdFFLcceloKpJRME907cYO323fnky1i6V4upJp5Vz+zTF79+/Y4pCpqJGc1QgiuePHxpKPtTEVkrFvnNOabUAh8FwLAxJsqeDV5QmipE8DE2RA1y1SbCWeqhNGI31D+Q37ZcpBNL9MAilvhGlDuH19Vg8lto4gipNk5QLJ9A+oagir72DT10dJm09+jcoKinYTIlivtrZpb3HEKk0go5pDYuOTncwiJPr2KXUaRSNcrG8kYO1ASpyks4FmWu05WIincxxq8QahGG7tcFKmrYqGlJrwKmOYihQTFW3Fi5+2qaJ4bV2SgWuXuQzUKhORNWQTic7L8YQOj5sHYYcSE8g5PpKX5b0egoDalr/3KjxDymc4su1viCuWt+Hapdf/MK3+CwskByU87j/BrAYdkpKS8Ai15dxcgMjIApl2dDN5JxQ3NbWik92G0h18iZrGJtw2yF2WgR4aoySCMwHwUBOjmtPEvMNgl+ycV4jWnErjLgHUgeJ+SgevVhlsrz4PfUcgmutkWb7hT+HOOJl6THYUyZKWsj6P4hYIpQZ82wxfHRxEwxjb/BOkodXp8uz4v7I69pwsXuISRBZw20eh1EKMAhcx07Q5/27t00WQ1GgDZ4QJcGgCcYFw5SV7U7czW1IBP05bvsYWxMo0a9RnZOcA1rgYSYlO0sKj+FDIbXnkRK+soUNkvKtmtSImPYVrVb7/cODlOpu2z10LFu9yDj4XIXCB1wr7vJz8kF92rFkH80oxULkrRMqCbBpGDhRw1kr2wbqjPh8fUywS7fBkXo7K9vx5ObbUYhy5A7FzLrsf1exhYHPO8FwpOAFFYEnpahxPsTAeq1dRZrq6zBMRKWzK94WpB4K2UTROONeXIHuec3N+6bieLkft6INYZB/tUoRo48RWYq3UnCmUCeiCpEaA0F80qgYIVswrUZL0rZiQ6sxjSELzojgZYMUgdlw/wf0flWWWXmBsnhFUZ+idDV3BYecMByprwSh6DTPVY3WFkBYQRkrGdWR6EwcXGrLuryDLpwo3SveEMVRnm/F8RK4vol95ftwTRclPfyNapWu1xSTLB0/U/5AyyZuVys1kdSyKHGLYzS+hKKxrChJvC5jtQ1MG5U2m0PRFsJU9myOdOCiToOu1Y+zxowsQvY1AJIjxXvDbG8ymsxqp+Nj1mffp8ElI3bsRptAlDaLe0yK';
|
||||
1
src/assets/steampunk-hud-compact/p5.js
Normal file
1
src/assets/steampunk-hud-compact/p5.js
Normal file
@ -0,0 +1 @@
|
||||
export default 'lqiCv4QPKUL34Bt1UieGqG3zEAaxARFQjk9QaaWvV6oGkiQQuFCJbOf4Hsf6NrKfQm8Oa27HLDF1C1utuBD0FWpajQZ27bKboh0B4BvmvGkhmlru5QR/TNpKZEG67VTb6Hm5IwbJhTQHqKWEpmHhi2O99nQ6moq6fpymsUtXa6XcF6/9ZVkYZyERw3ocxHI5AYFdtBglFYLPC3sY4iDTmhdsmVDMLpdMpFJxtTrW6sexDYidj6fmmKHskzZ6hl84m4NJlZjAUBin0NTlXUeSpmD4d+5iQJsE602EOUeyUWGxoY05OjE3doBIZF0/ThET7a7zGFc94gQag1PquKGzFmkaFadRDFAb33glQ6hSiYO5L9el/8PwkdlAvY6ITKG/0G2M6PfjKjRMOQ6qKLMvksLoQttmEcnjmDiNYYCiBcF6OHb72xR2fY0muyH5KzKCpUi5+rG6hRFNL8v3K4IVUMudKl6cmDuNVGqzVYN0aBQDNY82vDvUblORzQJUCYoGedBZShKIAVWhoCJbwXJwWwuo0wdylz+hfNIQX5LE8cc2WAlIOjSxQwyKzDE5NdkOxwE4ouBMKO1hoO66s9vGNsMpQfmVGJZkFk6/BMphSOhDmWEqloWyifW1fWQ1GVWwUsQxq+GNaIRhqwayVElIWeg6UhxlpNQVU9Tcguh2DFRR0dJqwys8cPj2pdBfhA+kERx0irWO+vv5lv29lbYpqLwQRnY7PoSp6ek0YWAnFXFhSaXiBSJpCSu4DYMWDMok5Fp2R8qyuTKmLEAyGqnyeaVKowv7KgsjfU3eHtDsQHCFsjQd44MV2YgvRG1UK0v6VgyWD+rlcEANwCtkwGynD9YRO05kqraeEyMAaijwZq9BBFgTgtQHumDjVdYkroeC2fYqfTGy3ekDzl56QFT/ZQyv1Af18oIeX++k2sqibGrayODqsawP1D2mxg5v020sH8WuNW0cTQWjXXho2pquXz7sZ+W+jUVcoiXzOdcrbKvU7mtqltc5jyolVml4W5Xmim/I+wxT0onIbr2n1Es2vuK5Zs2sknXdpuLhlN3bQmxQ/e2JGeWMdrk1XQ0xaKfKRunwNRhUTS6NMMRlxb197/tCGnNr02XepoeyEQk7PNF+K4nj3eJhJuimT2VdWgqMWb4O+4k2WEjQtlJ9l7w0L119oyg1GAqK3akPxL2AVuNEG5fVSeiuqeIkRUyaMMZryqGgeES5GncI0hPh6p9mSaIUKOsz4oH8nxbFc7JHRg2Mui3e17Y/jbKkMSevc1u7BZeThNIXoUgemi9FXA/HaUe8kdjucOQtqV7DW9sUwIW/4dd4pHsSKuV3izVpeXGbNnAy6figE23qs4xbkXKxcmLEyRBkL4XPrFiteeai6pF77fx6JLY59a6eLPLYthPusDcNM+W2PtbqJjZQkAhbwaAPL6gPKm9X0XnfoqtKUG9ByYZSaOqQkThX5UNMXJY1nyWtJM49AAMt9tX5eLWxksTcC6GEXNuWTxql1OdALm5NDocXU5xdGJO3i+jg7Zzd5BhZloArnNuNprbGJe0bcdShOiAoKn5O7RN4jHmDZzsUq50gudy53UzxR+32VhYA4K8vn9FCnluGu7LbVCEctPuGjWTo1wa0RMQIwYjL3k3vX8oonGp+wKmc7wGwjUN5kK2u7kle4MwOf1PgMrTFQj//V6eeXMlywqDDe5PFkw6QVez8cu26clyUFpuSnibxmzapkVqu2WzLtgPl7Lm0BpQ0PzkCXYSLXbctc2L/DFxffTUGCrhtUSW/9jsLp4DyW1QrRzDN';
|
||||
1
src/assets/steampunk-hud-compact/p6.js
Normal file
1
src/assets/steampunk-hud-compact/p6.js
Normal file
@ -0,0 +1 @@
|
||||
export default '9uYttCRgQRHoYjfW54PxJv5VDCRMhON0y+9QRHR9e/B0yvNdl/rvkaoWBnC73AYlrHInynurz2z0we5IguqmH8AeuR0lYRmcbv5BWsnfoWnLPMMRDPSb7l0lcY9+BQL05myXKEG/7bdJ3P+J83O+i2MvePFl4lPyynYeTzad2Y02i2Kx4KpVXbvodTgTQ8adXvWD8LjZqP3KXpbj84l14jRxHv9ez0d57Mb8byjQdJDV4HDNR4BkJ063tAX73Hjtz2S7ukYodpO7KXTLx/iirW/+NMZBPiHjHf4Wv9rdTWKIW34bwhYYGNS/myO74Leg06XrlvTrxz///l/aLcmvXycE+Fv5m/10ehrD3fyzOH/+jfj/thrxT4BhppnekP4f81ecpmNs8W4AAAAASUVORK5CYII=';
|
||||
12
src/assets/steampunk-hud-mobile-v2.js
Normal file
12
src/assets/steampunk-hud-mobile-v2.js
Normal file
@ -0,0 +1,12 @@
|
||||
import p0 from './steampunk-hud-compact/p0.js';
|
||||
import p1 from './steampunk-hud-compact/p1.js';
|
||||
import p2a from './steampunk-hud-compact/p2a.js';
|
||||
import p2b from './steampunk-hud-compact/p2b.js';
|
||||
import p3 from './steampunk-hud-compact/p3.js';
|
||||
import p4 from './steampunk-hud-compact/p4.js';
|
||||
import p5 from './steampunk-hud-compact/p5.js';
|
||||
import p6 from './steampunk-hud-compact/p6.js';
|
||||
|
||||
const base64 = [p0, p1, p2a, p2b, p3, p4, p5, p6].join('').replace(/\s+/g, '');
|
||||
|
||||
export default `data:image/png;base64,${base64}`;
|
||||
1
src/assets/steampunk-hud-mobile.js
Normal file
1
src/assets/steampunk-hud-mobile.js
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/steampunk-hud-mobile/chunk-00.js
Normal file
1
src/assets/steampunk-hud-mobile/chunk-00.js
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/steampunk-hud-mobile/chunk-01.js
Normal file
1
src/assets/steampunk-hud-mobile/chunk-01.js
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/steampunk-hud-mobile/chunk-02.js
Normal file
1
src/assets/steampunk-hud-mobile/chunk-02.js
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/steampunk-hud-mobile/chunk-02a.js
Normal file
1
src/assets/steampunk-hud-mobile/chunk-02a.js
Normal file
@ -0,0 +1 @@
|
||||
export default 'Ok8bi3QNHBaCZxzWPJV7hBWkW2cC/8SEz7WMVtyEmQYrto0IV6ajWrD5OYQrZhquCIP0e/PQheDtL7h4ARuk627euYyZhusBIcg1c4VIfo9G7lYvSL++RW8iDFeEoVua6YAQGy3q2i3SsDtomBsiXYcElx/5vQ2yW6Mhg2VCXJgRYbgeDFgYYGR/CVN/nxNC0kiylFbxxK4jlPRSYDGr3+UzWN/l7CdSExGGaznSomME3swW7jcToVwCryLCcDUYBEW590e6eYJ95IZrWkrqyG2wSqbRi44wRBgcDDG0F2GIMHgYYizjKiAkWSryI2HAfEQ8suvA0Aqpj4NhooEb4kDWa9BzshaywbarA5VKhtyGVK6TT/HUrsENEhM+gxboMeLe9Bjbuw4MC2FqXNpwQCIZyQPGIgzXgkHIW03Dw8L8ZxjIQAiU1FUtY1H9NWHIcbmYoWHQChOfhgN8RjbLRiscFQ3f05qTCMOVYLhPbWlG6lY28HhuTaPSgy2t/KS4B+5KJGSQ4Ul7qZ+tR2LV3vVMJWNzzakQe8Lc9hnxxK4FxMItrW95jJ6fLJll2T1R6wGJzHA1ms8WQPdF8vteVd6m8KSY9fksEurwI5E+3+mP0SqiEilSpEiRIkWKFClSpEiRIkWKFClSpEiRIkWKFClSpEiRIkWKFClSpEiRIkWKFClSpEiRIkWKFClSpEiRIkWKFClSpEiRIkWKFClSpEiRIkWKFClSpEiRIkWKFClSpEiRIkWKFClSpEiRIkWKFClSpEiRIkWKFClSpEiRIkWKFClSpEiRIkWKFClSpEiRIkWKFClSpEiRIkWKFClSpEiRIkWKFClSpEiRIkWKFClSpEiRIkWKFClSpEiRIkWKFClSpEiRIkWKFClSpEiRIkWKFClSpEiRIkWKFClSpEiRIkWKFClSpEiRIkWKFClSpEiRIkWKFClSpEiRIkWKFClSpEiRIkWKFClSpEiRIkWKFClSpEiRIkWKFClSpEiRIkWKFClSpEiRIkWKFClSpEiRIkWKFClSpEiRIkWKFClSpEiRIkWKFClSpEiRIkWKFClSpEiRIkWKFClSpEiRIkWKFClSpEiRIkWKFOmiVHz8ZR5P4YtS1oGRxdP4UnwA/58XT0ny9DQPIYn0eVEonlohhJRSiHXGsET67AKpwPOXy1LDv9Lcf4o4fH5WmCcvUqocWMHAv4hDW0QcPjcKWZLR+SthZA4g6CUwxktUEJ+XFYrnokWJJIwqq/InJUuZpkJmoLKLyBKfQSMUGd3xH9ayTkUlVXV7e1sqEEsp4vCLhWkVT+qqCgHpP/OndgHKQAhtEIZS5WAupcKkQrSzF37ScxRQ1+EDOt5i9rJu0UbVpVYCjl9VFShqYIRUKUkEUDzNrUUbj+2yGhlv7eJpvUYEZJ7naCSJFI4/VyV+J/A/gTpbMRTrGUIRNcXF+eDJgGmE5wzmKZ07KIM0RVAABvwWv5A3R3YsuHSzeYFAROl0CU6A+xmUwSs4arnKFaqEFO98Ifjk8eZP6Tv3UJqapQJwpDHrFzas4jG+neZrkkQobogF4OQ3SAalUipqYgZgBHoQDp/hQQkFf9bOCrSvIhBvs4yen0AjKwsBiX+tLQZAqB+0qpkJhMFfaK1/RZGETIECy8inzEu2SGdJo2K2ZhAEiR8FZ6xZLFk5lEoFXgPYqlYswf+NRChyRoJ4wrQzUvHxTE8HAaXRE0ojJaXDYLOBO96qZ4eEZFFlf7IqwiBX/MoctMRXMOsnBjbSKYwwT7KXNdo8yAgg/rVuQBDRHe+ZAY+brCfTKWeQRIJ/FMgUxBOC/Yl2hkoiHu5JOuFpTZE7iboWZMzGhGcNgqfRGMaoQgJ51SEEzyHxxMxCSIh1zA6dJI/m1jgiaaT1o5ngnW/vdqnp0Muy1Fpp9CPgX6VLxqLUVkYZyy0bEE7kT6BoeplHHI711pJkLZkTUssJrH3xbJkHSjp9QgAOtyYc+EdCo1xK680h41ggUoHW1jpLsgjEYVqB47uWgnUCCJXNZGMljWA2KC0DMClZlZLA0h0WCIVlChBNAGGjKeBRIxAvWTSZjjGQnigcQSCgeyBAIqGHAHwwgICsV1WV8Fz7mHJYaERMG2vEgtre/CprUtZCxrT1EQIJtQJFjdBNm+AJkoYFWVQ5DEIkyLlGYdUxg5dXFTKKBQJMWC+Z5DrisA8D8HSzNWpm9JZ1A/LEpKgX6NZ2wr9Hqq7ZEhrCwD+AdFLCGbdyIykUCPSKJlNUEePy6F3ytyfOGAj5q97gXYwgqIoYgUVOHwSt2EUY4QbtWUKRVELabKTzIjAM/hyBGPIBA9FSkQXGjTaC1WvakFK2hxwoAAsDB1dTyTDoHgrSKuxSGWs3mY2UtrKpRZc6yyJP9OIWGLpIpFzmrBUmKacQJIBgjz+vKoBHhRaRsjiAT2A1cwcE/CX43vBNnmt4DX450BA5M0QOvh1FwaNw6nDInl7u79GKYd2MWoFu3tLqZTz8WoEE0p1lCnc5ERhLCj25vljK65/g6fQdP9eyDfED4wBqoiVuiDiQcfRkKOIgRC4NmakcthCNV8yIA0l3Pu3SHnuD8SSKKknOTgdQCFEDDN3fE0OklJUgTY1sRFqozSIOiMJM4nlpWfN9ilYq2folyqPuHDG6tLQYNEGwOwi2wqE2Dgm8+2Wos4FtbDoIcahJ+uVklM0iDkUyA1GEyRvye+EcDaeVG/LVbNSIThbUN0OwDUAPC0wMwfOlkSqUUjkxBEfBKUxiiwcAiD89PwAKIFPIkDGVBjRs5EIgK3ir1CsD1hoHCA9aDgwnhCH3DEF2K4g5srEAhnb+57aYsjnXQJIZKTQ4a52BFPoHJFRKfQQGnXjadquRq8iwwrdxeW1ikCvH+1arjCjJVl+vYiAVawCLWto6F4m5gz4MoI+PxcCzRLMNhLb8AE6h86hJLM2uFt0YloVkX1+ZCKKA5w92aUmFwXyEjRdI3lvWMj2DkCX6osm/EjKDlFTxVzMO76/wAZ8LvP0/YakUsUMx/4OgKb4uxfBCJmN5e1tRNA++hxMCZa1CBHS5D4Tp9CTZlKPJlYKRCwatLAkG0triOtEZzKevDdxrhrKHRraLp/n80iKvKP6Yn18Ut0pahCFXFVcDY5nFAAVKq+0VRzc36X4gdDkQTYCDVDWgwPWvNZeetf+8sHZYwet9xHy6XHbuDEfMXp+yC97Lg4jQGUFtifehpOgPwYAGTIUJNelxAMWcij04TG8mh5W1j/WRm0cuCPiK+MY5BQev4D3MqcwN37wxweUYq46e5pfQRlSiC6L9JVnPCgL+HByfKMsmJdyRXJRNvJBLDQ9Yjig3';
|
||||
1
src/assets/steampunk-hud-mobile/chunk-s0.js
Normal file
1
src/assets/steampunk-hud-mobile/chunk-s0.js
Normal file
@ -0,0 +1 @@
|
||||
export default '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';
|
||||
1
src/assets/steampunk-hud/chunk-00.js
Normal file
1
src/assets/steampunk-hud/chunk-00.js
Normal file
File diff suppressed because one or more lines are too long
1
src/assets/steampunk-hud/chunk-01.js
Normal file
1
src/assets/steampunk-hud/chunk-01.js
Normal file
File diff suppressed because one or more lines are too long
89
src/diagnostics.js
Normal file
89
src/diagnostics.js
Normal file
@ -0,0 +1,89 @@
|
||||
const MAX_LOG_ENTRIES = 300;
|
||||
const logBuffer = [];
|
||||
let snapshotProvider = null;
|
||||
let browserDiagnosticsInstalled = false;
|
||||
|
||||
const safeError = (error) => {
|
||||
if (!error) return null;
|
||||
if (error instanceof Error) {
|
||||
return {
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
stack: error.stack || null,
|
||||
};
|
||||
}
|
||||
return { message: String(error) };
|
||||
};
|
||||
|
||||
const trimLogs = () => {
|
||||
if (logBuffer.length > MAX_LOG_ENTRIES) {
|
||||
logBuffer.splice(0, logBuffer.length - MAX_LOG_ENTRIES);
|
||||
}
|
||||
};
|
||||
|
||||
export const airshipLog = (event, data = {}, level = 'info') => {
|
||||
const entry = {
|
||||
ts: new Date().toISOString(),
|
||||
level,
|
||||
event,
|
||||
...data,
|
||||
};
|
||||
|
||||
logBuffer.push(entry);
|
||||
trimLogs();
|
||||
|
||||
const method = level === 'error' ? 'error' : level === 'warn' ? 'warn' : 'log';
|
||||
console[method]('[airship]', event, entry);
|
||||
return entry;
|
||||
};
|
||||
|
||||
export const registerDebugSnapshot = (provider) => {
|
||||
snapshotProvider = typeof provider === 'function' ? provider : null;
|
||||
};
|
||||
|
||||
export const getAirshipLogs = () => logBuffer.map((entry) => ({ ...entry }));
|
||||
|
||||
export const clearAirshipLogs = () => {
|
||||
logBuffer.length = 0;
|
||||
};
|
||||
|
||||
export const installBrowserDiagnostics = () => {
|
||||
if (typeof window === 'undefined' || browserDiagnosticsInstalled) return;
|
||||
browserDiagnosticsInstalled = true;
|
||||
|
||||
window.__AIRSHIP_LOGS__ = logBuffer;
|
||||
window.airshipDebug = {
|
||||
getLogs: getAirshipLogs,
|
||||
clearLogs: clearAirshipLogs,
|
||||
snapshot: () => {
|
||||
try {
|
||||
return snapshotProvider?.() ?? null;
|
||||
} catch (error) {
|
||||
airshipLog('debug:snapshot-error', { error: safeError(error) }, 'error');
|
||||
return null;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
window.addEventListener('error', (event) => {
|
||||
airshipLog('runtime:error', {
|
||||
message: event.message || 'Unknown window error',
|
||||
source: event.filename || null,
|
||||
line: event.lineno || null,
|
||||
column: event.colno || null,
|
||||
error: safeError(event.error),
|
||||
}, 'error');
|
||||
});
|
||||
|
||||
window.addEventListener('unhandledrejection', (event) => {
|
||||
airshipLog('runtime:unhandled-rejection', {
|
||||
error: safeError(event.reason),
|
||||
}, 'error');
|
||||
});
|
||||
|
||||
airshipLog('diagnostics:installed', {
|
||||
userAgent: navigator?.userAgent || 'unknown',
|
||||
});
|
||||
};
|
||||
|
||||
installBrowserDiagnostics();
|
||||
119
src/environment-physics.js
Normal file
119
src/environment-physics.js
Normal file
@ -0,0 +1,119 @@
|
||||
export const ENVIRONMENT_ISLANDS = [
|
||||
{
|
||||
id: 'west-island',
|
||||
x: 430,
|
||||
y: 480,
|
||||
radius: 82,
|
||||
turbulenceRadius: 220,
|
||||
},
|
||||
{
|
||||
id: 'south-east-island',
|
||||
x: 1940,
|
||||
y: 1840,
|
||||
radius: 96,
|
||||
turbulenceRadius: 250,
|
||||
},
|
||||
];
|
||||
|
||||
const WIND_PATTERN_MS = 16000;
|
||||
const WIND_MIN_STRENGTH = 7;
|
||||
const WIND_MAX_STRENGTH = 24;
|
||||
|
||||
const clamp01 = (value) => Math.max(0, Math.min(1, value));
|
||||
const smootherStep = (value) => {
|
||||
const t = clamp01(value);
|
||||
return t * t * t * (t * (t * 6 - 15) + 10);
|
||||
};
|
||||
const hash01 = (index, salt = 0) => {
|
||||
const value = Math.sin(index * 12.9898 + salt * 78.233) * 43758.5453123;
|
||||
return value - Math.floor(value);
|
||||
};
|
||||
const shortestAngleDelta = (from, to) => {
|
||||
let delta = (to - from) % (Math.PI * 2);
|
||||
if (delta > Math.PI) delta -= Math.PI * 2;
|
||||
if (delta < -Math.PI) delta += Math.PI * 2;
|
||||
return delta;
|
||||
};
|
||||
|
||||
const windTargetForSlot = (slot) => ({
|
||||
angle: hash01(slot, 1) * Math.PI * 2,
|
||||
strength: WIND_MIN_STRENGTH
|
||||
+ hash01(slot, 2) * (WIND_MAX_STRENGTH - WIND_MIN_STRENGTH),
|
||||
});
|
||||
|
||||
export const semiRandomWindPattern = (timeMs = 0) => {
|
||||
const safeTime = Math.max(0, Number(timeMs) || 0);
|
||||
const slot = Math.floor(safeTime / WIND_PATTERN_MS);
|
||||
const phase = (safeTime % WIND_PATTERN_MS) / WIND_PATTERN_MS;
|
||||
const current = windTargetForSlot(slot);
|
||||
const next = windTargetForSlot(slot + 1);
|
||||
|
||||
// Hold each weather state briefly, then ease toward the next random target.
|
||||
const blend = smootherStep((phase - 0.18) / 0.82);
|
||||
const angle = current.angle
|
||||
+ shortestAngleDelta(current.angle, next.angle) * blend;
|
||||
const strength = current.strength
|
||||
+ (next.strength - current.strength) * blend;
|
||||
|
||||
// A smaller deterministic gust keeps the air alive without frame-to-frame jitter.
|
||||
const gust = Math.sin(safeTime / 2300 + slot * 0.73)
|
||||
* (1.1 + hash01(slot, 3) * 1.8);
|
||||
|
||||
return {
|
||||
angle,
|
||||
strength: Math.max(4, strength + gust),
|
||||
};
|
||||
};
|
||||
|
||||
export const environmentWindAt = ({
|
||||
x = 0,
|
||||
y = 0,
|
||||
timeMs = 0,
|
||||
} = {}) => {
|
||||
const time = timeMs / 1000;
|
||||
const pattern = semiRandomWindPattern(timeMs);
|
||||
|
||||
// Small spatial variation prevents the whole map feeling like one rigid conveyor belt.
|
||||
const localAngle = pattern.angle
|
||||
+ Math.sin((x + y) / 880 + time / 31) * 0.055;
|
||||
const localStrength = pattern.strength
|
||||
+ Math.sin((x - y) / 760 + time / 4.7) * 0.9;
|
||||
|
||||
let windX = Math.cos(localAngle) * localStrength;
|
||||
let windY = Math.sin(localAngle) * localStrength;
|
||||
|
||||
for (const island of ENVIRONMENT_ISLANDS) {
|
||||
const dx = x - island.x;
|
||||
const dy = y - island.y;
|
||||
const distance = Math.hypot(dx, dy);
|
||||
if (distance <= 1 || distance >= island.turbulenceRadius) continue;
|
||||
|
||||
const influence = 1 - (distance / island.turbulenceRadius);
|
||||
const normalX = dx / distance;
|
||||
const normalY = dy / distance;
|
||||
const tangentX = -normalY;
|
||||
const tangentY = normalX;
|
||||
const updraft = 11 * influence * influence;
|
||||
const swirl = Math.sin(time * 1.35 + distance * 0.018) * 5 * influence;
|
||||
|
||||
windX += normalX * updraft + tangentX * swirl;
|
||||
windY += normalY * updraft + tangentY * swirl;
|
||||
}
|
||||
|
||||
return {
|
||||
x: windX,
|
||||
y: windY,
|
||||
strength: Math.hypot(windX, windY),
|
||||
heading: ((Math.atan2(windY, windX) * 180 / Math.PI) + 450) % 360,
|
||||
};
|
||||
};
|
||||
|
||||
export const velocityWithEnvironment = (
|
||||
velocityX,
|
||||
velocityY,
|
||||
wind,
|
||||
exposure = 1,
|
||||
) => ({
|
||||
x: velocityX + (wind?.x || 0) * exposure,
|
||||
y: velocityY + (wind?.y || 0) * exposure,
|
||||
});
|
||||
2
src/game-entry.js
Normal file
2
src/game-entry.js
Normal file
@ -0,0 +1,2 @@
|
||||
import './game-v4.js';
|
||||
import './mobile-hud.js';
|
||||
40
src/game-logic.js
Normal file
40
src/game-logic.js
Normal file
@ -0,0 +1,40 @@
|
||||
export const PLAYER_MAX_HULL = 100;
|
||||
export const DEFAULT_PIRATE_SHOT_DAMAGE = 6;
|
||||
|
||||
export const normalizePlayerDamage = (
|
||||
rawDamage,
|
||||
fallback = DEFAULT_PIRATE_SHOT_DAMAGE,
|
||||
) => {
|
||||
const parsed = Number(rawDamage);
|
||||
const value = Number.isFinite(parsed) ? parsed : fallback;
|
||||
return Math.max(1, Math.min(25, value));
|
||||
};
|
||||
|
||||
export const reducePlayerProjectileHits = ({
|
||||
hull,
|
||||
hits,
|
||||
maxHull = PLAYER_MAX_HULL,
|
||||
fallbackDamage = DEFAULT_PIRATE_SHOT_DAMAGE,
|
||||
}) => {
|
||||
const currentHull = Number.isFinite(hull) ? hull : maxHull;
|
||||
const totalDamage = hits.reduce(
|
||||
(sum, hit) => sum + normalizePlayerDamage(hit?.damage, fallbackDamage),
|
||||
0,
|
||||
);
|
||||
const nextHull = Math.max(0, currentHull - totalDamage);
|
||||
|
||||
return {
|
||||
hull: nextHull,
|
||||
totalDamage,
|
||||
appliedHits: hits.length,
|
||||
gameOver: nextHull <= 0,
|
||||
};
|
||||
};
|
||||
|
||||
export const resolveTaggedProjectile = (first, second, owner) => {
|
||||
for (const candidate of [first, second]) {
|
||||
if (!candidate || typeof candidate.getData !== 'function') continue;
|
||||
if (candidate.getData('projectileOwner') === owner) return candidate;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
409
src/game-v2.js
Normal file
409
src/game-v2.js
Normal file
@ -0,0 +1,409 @@
|
||||
import Phaser from 'phaser';
|
||||
import './style.css';
|
||||
|
||||
const WORLD_SIZE = 2400;
|
||||
const PLAYER_MAX_SPEED = 180;
|
||||
const PLAYER_REVERSE_SPEED = -55;
|
||||
const PLAYER_ACCELERATION = 85;
|
||||
const PLAYER_BRAKE = 110;
|
||||
const PLAYER_TURN_RATE = 72;
|
||||
const PIRATE_SPEED = 118;
|
||||
const PIRATE_TURN_RATE = 52;
|
||||
const SHOT_SPEED = 520;
|
||||
const SHOT_LIFETIME = 900;
|
||||
const PLAYER_MAX_HULL = 100;
|
||||
const PIRATE_MAX_HULL = 120;
|
||||
|
||||
const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
|
||||
const degToRad = (deg) => deg * Math.PI / 180;
|
||||
const normalizeDegrees = (deg) => ((deg % 360) + 360) % 360;
|
||||
const signedAngleDelta = (target, current) => {
|
||||
let delta = normalizeDegrees(target) - normalizeDegrees(current);
|
||||
if (delta > 180) delta -= 360;
|
||||
if (delta < -180) delta += 360;
|
||||
return delta;
|
||||
};
|
||||
const bearingTo = (from, to) => normalizeDegrees(Math.atan2(to.y - from.y, to.x - from.x) * 180 / Math.PI + 90);
|
||||
const headingRad = (ship) => degToRad(ship.angle - 90);
|
||||
|
||||
class GameScene extends Phaser.Scene {
|
||||
constructor() {
|
||||
super('game');
|
||||
this.playerSpeed = 0;
|
||||
this.playerHull = PLAYER_MAX_HULL;
|
||||
this.pirateHull = PIRATE_MAX_HULL;
|
||||
this.gameState = 'playing';
|
||||
this.lastPlayerShot = 0;
|
||||
this.lastPirateShot = 0;
|
||||
}
|
||||
|
||||
create() {
|
||||
this.physics.world.setBounds(0, 0, WORLD_SIZE, WORLD_SIZE);
|
||||
this.drawWorld();
|
||||
this.createTextures();
|
||||
|
||||
this.player = this.physics.add.sprite(1200, 1550, 'player-ship');
|
||||
this.player.setDepth(6).setCollideWorldBounds(true);
|
||||
this.player.body.setAllowGravity(false);
|
||||
this.player.body.setSize(42, 56, true);
|
||||
|
||||
this.pirate = this.physics.add.image(1200, 800, 'pirate-ship');
|
||||
this.pirate.setDepth(6).setCollideWorldBounds(true);
|
||||
this.pirate.body.setAllowGravity(false);
|
||||
this.pirate.body.setSize(46, 58, true);
|
||||
this.pirate.angle = 180;
|
||||
|
||||
this.playerShots = this.physics.add.group();
|
||||
this.pirateShots = this.physics.add.group();
|
||||
this.arcGraphics = this.add.graphics().setDepth(4);
|
||||
|
||||
this.physics.add.overlap(this.playerShots, this.pirate, this.hitPirate, null, this);
|
||||
this.physics.add.overlap(this.pirateShots, this.player, this.hitPlayer, null, this);
|
||||
|
||||
this.cursors = this.input.keyboard.createCursorKeys();
|
||||
this.keys = this.input.keyboard.addKeys({
|
||||
firePort: Phaser.Input.Keyboard.KeyCodes.Q,
|
||||
fireStarboard: Phaser.Input.Keyboard.KeyCodes.E,
|
||||
stop: Phaser.Input.Keyboard.KeyCodes.SPACE,
|
||||
restart: Phaser.Input.Keyboard.KeyCodes.R,
|
||||
});
|
||||
|
||||
this.cameras.main.setBounds(0, 0, WORLD_SIZE, WORLD_SIZE);
|
||||
this.cameras.main.startFollow(this.player, true, 0.08, 0.08);
|
||||
this.cameras.main.setZoom(1.12);
|
||||
|
||||
this.title = this.add.text(18, 16, 'ARROWSTACK AIRSHIP', {
|
||||
fontFamily: 'monospace',
|
||||
fontSize: '12px',
|
||||
color: '#8cd3ff',
|
||||
}).setScrollFactor(0).setDepth(30);
|
||||
|
||||
this.hud = this.add.text(18, 42, '', {
|
||||
fontFamily: 'monospace',
|
||||
fontSize: '14px',
|
||||
color: '#e8f2f7',
|
||||
backgroundColor: '#071018cc',
|
||||
padding: { x: 12, y: 10 },
|
||||
lineSpacing: 4,
|
||||
}).setScrollFactor(0).setDepth(30);
|
||||
|
||||
this.status = this.add.text(this.scale.width / 2, 22, '', {
|
||||
fontFamily: 'monospace',
|
||||
fontSize: '18px',
|
||||
color: '#ffffff',
|
||||
backgroundColor: '#071018dd',
|
||||
padding: { x: 16, y: 10 },
|
||||
align: 'center',
|
||||
}).setOrigin(0.5, 0).setScrollFactor(0).setDepth(31);
|
||||
|
||||
this.healthGraphics = this.add.graphics().setScrollFactor(0).setDepth(30);
|
||||
this.healthLabels = {
|
||||
player: this.add.text(18, this.scale.height - 64, 'SPARROW', {
|
||||
fontFamily: 'monospace', fontSize: '12px', color: '#d8ecff',
|
||||
}).setScrollFactor(0).setDepth(31),
|
||||
pirate: this.add.text(this.scale.width - 18, this.scale.height - 64, 'CUTLASS PIRATE', {
|
||||
fontFamily: 'monospace', fontSize: '12px', color: '#ffd0d0',
|
||||
}).setOrigin(1, 0).setScrollFactor(0).setDepth(31),
|
||||
};
|
||||
|
||||
this.scale.on('resize', this.layoutHud, this);
|
||||
this.layoutHud({ width: this.scale.width, height: this.scale.height });
|
||||
}
|
||||
|
||||
drawWorld() {
|
||||
const bg = this.add.graphics();
|
||||
bg.fillStyle(0x132335, 1);
|
||||
bg.fillRect(0, 0, WORLD_SIZE, WORLD_SIZE);
|
||||
bg.lineStyle(1, 0x8db3cc, 0.08);
|
||||
for (let x = 0; x <= WORLD_SIZE; x += 100) bg.lineBetween(x, 0, x, WORLD_SIZE);
|
||||
for (let y = 0; y <= WORLD_SIZE; y += 100) bg.lineBetween(0, y, WORLD_SIZE, y);
|
||||
|
||||
const clouds = this.add.graphics();
|
||||
for (let i = 0; i < 46; i++) {
|
||||
const x = Phaser.Math.Between(80, WORLD_SIZE - 80);
|
||||
const y = Phaser.Math.Between(80, WORLD_SIZE - 80);
|
||||
const r = Phaser.Math.Between(24, 62);
|
||||
clouds.fillStyle(0xbcd2df, Phaser.Math.FloatBetween(0.025, 0.08));
|
||||
clouds.fillCircle(x, y, r);
|
||||
clouds.fillCircle(x + r * 0.55, y + 8, r * 0.7);
|
||||
clouds.fillCircle(x - r * 0.45, y + 5, r * 0.6);
|
||||
}
|
||||
|
||||
const island = this.add.graphics();
|
||||
island.fillStyle(0x5d694e, 1);
|
||||
island.fillEllipse(430, 480, 230, 150);
|
||||
island.fillStyle(0xa18a62, 1);
|
||||
island.fillEllipse(430, 470, 190, 105);
|
||||
island.fillStyle(0x283827, 1);
|
||||
island.fillCircle(390, 440, 18);
|
||||
island.fillCircle(455, 450, 14);
|
||||
island.fillCircle(490, 425, 12);
|
||||
|
||||
const island2 = this.add.graphics();
|
||||
island2.fillStyle(0x5d694e, 1);
|
||||
island2.fillEllipse(1940, 1840, 260, 165);
|
||||
island2.fillStyle(0xa18a62, 1);
|
||||
island2.fillEllipse(1940, 1830, 210, 112);
|
||||
}
|
||||
|
||||
createTextures() {
|
||||
const makeShip = (key, hullColor, balloonColor) => {
|
||||
const g = this.make.graphics({ x: 0, y: 0, add: false });
|
||||
g.fillStyle(hullColor, 1);
|
||||
g.fillTriangle(32, 2, 8, 64, 56, 64);
|
||||
g.fillStyle(0x715438, 1);
|
||||
g.fillRect(17, 31, 30, 20);
|
||||
g.fillStyle(balloonColor, 1);
|
||||
g.fillEllipse(32, 23, 42, 16);
|
||||
g.lineStyle(2, 0x201914, 0.9);
|
||||
g.strokeTriangle(32, 2, 8, 64, 56, 64);
|
||||
g.generateTexture(key, 64, 68);
|
||||
g.destroy();
|
||||
};
|
||||
|
||||
makeShip('player-ship', 0xd8a65f, 0xe8dfc7);
|
||||
makeShip('pirate-ship', 0x9f3d3d, 0x5c2020);
|
||||
|
||||
const playerShot = this.make.graphics({ x: 0, y: 0, add: false });
|
||||
playerShot.fillStyle(0xffd36a, 1);
|
||||
playerShot.fillCircle(5, 5, 5);
|
||||
playerShot.generateTexture('player-shot', 10, 10);
|
||||
playerShot.destroy();
|
||||
|
||||
const pirateShot = this.make.graphics({ x: 0, y: 0, add: false });
|
||||
pirateShot.fillStyle(0xff6b62, 1);
|
||||
pirateShot.fillCircle(5, 5, 5);
|
||||
pirateShot.generateTexture('pirate-shot', 10, 10);
|
||||
pirateShot.destroy();
|
||||
}
|
||||
|
||||
update(_time, deltaMs) {
|
||||
const dt = Math.min(deltaMs / 1000, 0.05);
|
||||
|
||||
if (this.gameState !== 'playing') {
|
||||
if (Phaser.Input.Keyboard.JustDown(this.keys.restart)) this.scene.restart();
|
||||
this.drawHud();
|
||||
return;
|
||||
}
|
||||
|
||||
this.updatePlayer(dt, deltaMs);
|
||||
this.updatePirate(dt);
|
||||
this.updateProjectiles();
|
||||
this.drawWeaponArcs();
|
||||
this.drawHud();
|
||||
this.checkGameState();
|
||||
}
|
||||
|
||||
updatePlayer(dt, deltaMs) {
|
||||
if (!this.player?.body) return;
|
||||
|
||||
if (this.cursors.up.isDown) this.playerSpeed += PLAYER_ACCELERATION * dt;
|
||||
if (this.cursors.down.isDown) this.playerSpeed -= PLAYER_BRAKE * dt;
|
||||
if (this.keys.stop.isDown) this.playerSpeed *= Math.pow(0.025, dt);
|
||||
|
||||
if (!this.cursors.up.isDown && !this.cursors.down.isDown && !this.keys.stop.isDown) {
|
||||
this.playerSpeed *= Math.pow(0.985, deltaMs / 16.667);
|
||||
}
|
||||
|
||||
this.playerSpeed = clamp(this.playerSpeed, PLAYER_REVERSE_SPEED, PLAYER_MAX_SPEED);
|
||||
const turnStrength = clamp(Math.abs(this.playerSpeed) / 100, 0.28, 1);
|
||||
const directionSign = this.playerSpeed < 0 ? -1 : 1;
|
||||
|
||||
if (this.cursors.left.isDown) this.player.angle -= PLAYER_TURN_RATE * turnStrength * directionSign * dt;
|
||||
if (this.cursors.right.isDown) this.player.angle += PLAYER_TURN_RATE * turnStrength * directionSign * dt;
|
||||
|
||||
const h = headingRad(this.player);
|
||||
this.player.setVelocity(Math.cos(h) * this.playerSpeed, Math.sin(h) * this.playerSpeed);
|
||||
|
||||
if (Phaser.Input.Keyboard.JustDown(this.keys.firePort)) this.playerBroadside(-1);
|
||||
if (Phaser.Input.Keyboard.JustDown(this.keys.fireStarboard)) this.playerBroadside(1);
|
||||
}
|
||||
|
||||
updatePirate(dt) {
|
||||
if (!this.pirate.active) return;
|
||||
|
||||
const dx = this.player.x - this.pirate.x;
|
||||
const dy = this.player.y - this.pirate.y;
|
||||
const distance = Math.hypot(dx, dy);
|
||||
const bearing = bearingTo(this.pirate, this.player);
|
||||
|
||||
let desiredHeading = bearing;
|
||||
if (distance < 360) {
|
||||
const optionA = normalizeDegrees(bearing + 90);
|
||||
const optionB = normalizeDegrees(bearing - 90);
|
||||
desiredHeading = Math.abs(signedAngleDelta(optionA, this.pirate.angle)) < Math.abs(signedAngleDelta(optionB, this.pirate.angle)) ? optionA : optionB;
|
||||
}
|
||||
|
||||
const delta = signedAngleDelta(desiredHeading, this.pirate.angle);
|
||||
this.pirate.angle += clamp(delta, -PIRATE_TURN_RATE * dt, PIRATE_TURN_RATE * dt);
|
||||
|
||||
const speed = distance > 430 ? PIRATE_SPEED : distance < 210 ? 72 : 96;
|
||||
const h = headingRad(this.pirate);
|
||||
this.pirate.setVelocity(Math.cos(h) * speed, Math.sin(h) * speed);
|
||||
|
||||
const relative = signedAngleDelta(bearing, this.pirate.angle);
|
||||
const broadsideError = Math.abs(Math.abs(relative) - 90);
|
||||
if (distance < 420 && broadsideError < 38 && this.time.now - this.lastPirateShot > 1650) {
|
||||
const side = relative >= 0 ? 1 : -1;
|
||||
this.fireBroadside(this.pirate, side, this.pirateShots, 'pirate-shot', 12, 0.9);
|
||||
this.lastPirateShot = this.time.now;
|
||||
}
|
||||
}
|
||||
|
||||
playerBroadside(side) {
|
||||
if (this.time.now - this.lastPlayerShot < 850) return;
|
||||
this.fireBroadside(this.player, side, this.playerShots, 'player-shot', 16, 1.0);
|
||||
this.lastPlayerShot = this.time.now;
|
||||
}
|
||||
|
||||
fireBroadside(ship, side, group, texture, damage, velocityScale) {
|
||||
const base = headingRad(ship) + side * Math.PI / 2;
|
||||
const offsets = [-7, 0, 7];
|
||||
|
||||
for (const offset of offsets) {
|
||||
const angle = base + degToRad(offset);
|
||||
const x = ship.x + Math.cos(base) * 34;
|
||||
const y = ship.y + Math.sin(base) * 34;
|
||||
const shot = group.create(x, y, texture);
|
||||
shot.setDepth(8);
|
||||
shot.body.setAllowGravity(false);
|
||||
shot.setData('bornAt', this.time.now);
|
||||
shot.setData('damage', damage);
|
||||
shot.setVelocity(
|
||||
Math.cos(angle) * SHOT_SPEED * velocityScale + ship.body.velocity.x * 0.25,
|
||||
Math.sin(angle) * SHOT_SPEED * velocityScale + ship.body.velocity.y * 0.25,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
hitPirate(shot) {
|
||||
if (!shot.active || this.gameState !== 'playing') return;
|
||||
const damage = shot.getData('damage') || 10;
|
||||
shot.destroy();
|
||||
this.pirateHull = Math.max(0, this.pirateHull - damage);
|
||||
this.flashShip(this.pirate, 0xffcccc);
|
||||
}
|
||||
|
||||
hitPlayer(shot) {
|
||||
if (!shot.active || this.gameState !== 'playing') return;
|
||||
const damage = shot.getData('damage') || 10;
|
||||
shot.destroy();
|
||||
this.playerHull = Math.max(0, this.playerHull - damage);
|
||||
this.flashShip(this.player, 0xffcccc);
|
||||
}
|
||||
|
||||
flashShip(ship, tint) {
|
||||
ship.setTint(tint);
|
||||
this.time.delayedCall(90, () => {
|
||||
if (ship.active) ship.clearTint();
|
||||
});
|
||||
}
|
||||
|
||||
updateProjectiles() {
|
||||
for (const group of [this.playerShots, this.pirateShots]) {
|
||||
for (const shot of group.getChildren()) {
|
||||
if (!shot.active) continue;
|
||||
if (this.time.now - shot.getData('bornAt') > SHOT_LIFETIME) shot.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drawWeaponArcs() {
|
||||
this.arcGraphics.clear();
|
||||
if (this.gameState !== 'playing') return;
|
||||
|
||||
const radius = 190;
|
||||
const forward = headingRad(this.player);
|
||||
this.arcGraphics.lineStyle(2, 0xffc857, 0.28);
|
||||
|
||||
for (const side of [-1, 1]) {
|
||||
const center = forward + side * Math.PI / 2;
|
||||
this.arcGraphics.beginPath();
|
||||
this.arcGraphics.arc(this.player.x, this.player.y, radius, center - Math.PI / 4, center + Math.PI / 4);
|
||||
this.arcGraphics.strokePath();
|
||||
}
|
||||
}
|
||||
|
||||
drawHud() {
|
||||
const heading = normalizeDegrees(this.player.angle);
|
||||
const range = Math.hypot(this.player.x - this.pirate.x, this.player.y - this.pirate.y);
|
||||
this.hud.setText([
|
||||
`speed ${Math.abs(this.playerSpeed).toFixed(0).padStart(3, ' ')} ${this.playerSpeed < 0 ? 'REV' : 'FWD'}`,
|
||||
`heading ${heading.toFixed(0).padStart(3, ' ')}°`,
|
||||
`pirate ${range.toFixed(0).padStart(4, ' ')}m`,
|
||||
'',
|
||||
'↑ / ↓ throttle / reverse',
|
||||
'← / → steer',
|
||||
'Q port broadside',
|
||||
'E starboard broadside',
|
||||
'SPACE brake',
|
||||
]);
|
||||
|
||||
const w = this.scale.width;
|
||||
const h = this.scale.height;
|
||||
const barWidth = Math.min(280, w * 0.32);
|
||||
const barHeight = 16;
|
||||
const y = h - 38;
|
||||
|
||||
this.healthGraphics.clear();
|
||||
this.healthGraphics.fillStyle(0x071018, 0.85);
|
||||
this.healthGraphics.fillRect(18, y, barWidth, barHeight);
|
||||
this.healthGraphics.fillRect(w - 18 - barWidth, y, barWidth, barHeight);
|
||||
|
||||
this.healthGraphics.fillStyle(0x61c98b, 1);
|
||||
this.healthGraphics.fillRect(18, y, barWidth * (this.playerHull / PLAYER_MAX_HULL), barHeight);
|
||||
this.healthGraphics.fillStyle(0xd65757, 1);
|
||||
this.healthGraphics.fillRect(w - 18 - barWidth, y, barWidth * (this.pirateHull / PIRATE_MAX_HULL), barHeight);
|
||||
|
||||
this.healthLabels.player.setText(`SPARROW ${this.playerHull}/${PLAYER_MAX_HULL}`);
|
||||
this.healthLabels.pirate.setText(`CUTLASS PIRATE ${this.pirateHull}/${PIRATE_MAX_HULL}`);
|
||||
}
|
||||
|
||||
checkGameState() {
|
||||
if (this.pirateHull <= 0) this.finishGame('won');
|
||||
else if (this.playerHull <= 0) this.finishGame('lost');
|
||||
}
|
||||
|
||||
finishGame(result) {
|
||||
this.gameState = result;
|
||||
this.player.setVelocity(0, 0);
|
||||
this.pirate.setVelocity(0, 0);
|
||||
this.playerShots.clear(true, true);
|
||||
this.pirateShots.clear(true, true);
|
||||
|
||||
if (result === 'won') {
|
||||
this.pirate.setTint(0x555555);
|
||||
this.status.setText('PIRATE DISABLED\nYou win\n\nPress R to restart');
|
||||
} else {
|
||||
this.player.setTint(0x555555);
|
||||
this.status.setText('YOUR SHIP IS DISABLED\nYou lose\n\nPress R to restart');
|
||||
}
|
||||
}
|
||||
|
||||
layoutHud(gameSize) {
|
||||
const width = gameSize.width;
|
||||
const height = gameSize.height;
|
||||
if (this.status) this.status.setPosition(width / 2, 22);
|
||||
if (this.healthLabels) {
|
||||
this.healthLabels.player.setPosition(18, height - 64);
|
||||
this.healthLabels.pirate.setPosition(width - 18, height - 64);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new Phaser.Game({
|
||||
type: Phaser.AUTO,
|
||||
parent: 'game',
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight,
|
||||
backgroundColor: '#132335',
|
||||
physics: {
|
||||
default: 'arcade',
|
||||
arcade: { debug: false },
|
||||
},
|
||||
scale: {
|
||||
mode: Phaser.Scale.RESIZE,
|
||||
autoCenter: Phaser.Scale.CENTER_BOTH,
|
||||
},
|
||||
scene: GameScene,
|
||||
});
|
||||
453
src/game-v3.js
Normal file
453
src/game-v3.js
Normal file
@ -0,0 +1,453 @@
|
||||
import Phaser from 'phaser';
|
||||
import './style.css';
|
||||
import airshipSheetUrl from './assets/airships.svg?url';
|
||||
import explosionSheetUrl from './assets/explosions.svg?url';
|
||||
|
||||
const WORLD_SIZE = 2400;
|
||||
const PLAYER_MAX_SPEED = 180;
|
||||
const PLAYER_REVERSE_SPEED = -55;
|
||||
const PLAYER_ACCELERATION = 85;
|
||||
const PLAYER_BRAKE = 110;
|
||||
const PLAYER_TURN_RATE = 72;
|
||||
const PIRATE_SPEED = 118;
|
||||
const PIRATE_TURN_RATE = 52;
|
||||
const SHOT_SPEED = 520;
|
||||
const SHOT_LIFETIME = 900;
|
||||
const PLAYER_MAX_HULL = 100;
|
||||
const PIRATE_MAX_HULL = 120;
|
||||
|
||||
const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
|
||||
const degToRad = (deg) => deg * Math.PI / 180;
|
||||
const normalizeDegrees = (deg) => ((deg % 360) + 360) % 360;
|
||||
const signedAngleDelta = (target, current) => {
|
||||
let delta = normalizeDegrees(target) - normalizeDegrees(current);
|
||||
if (delta > 180) delta -= 360;
|
||||
if (delta < -180) delta += 360;
|
||||
return delta;
|
||||
};
|
||||
const bearingTo = (from, to) => normalizeDegrees(Math.atan2(to.y - from.y, to.x - from.x) * 180 / Math.PI + 90);
|
||||
const headingRad = (ship) => degToRad(ship.angle - 90);
|
||||
|
||||
class GameScene extends Phaser.Scene {
|
||||
constructor() {
|
||||
super('game');
|
||||
this.playerSpeed = 0;
|
||||
this.playerHull = PLAYER_MAX_HULL;
|
||||
this.pirateHull = PIRATE_MAX_HULL;
|
||||
this.gameState = 'playing';
|
||||
this.lastPlayerShot = 0;
|
||||
this.lastPirateShot = 0;
|
||||
}
|
||||
|
||||
preload() {
|
||||
this.load.spritesheet('airships', airshipSheetUrl, {
|
||||
frameWidth: 64,
|
||||
frameHeight: 64,
|
||||
});
|
||||
this.load.spritesheet('explosions', explosionSheetUrl, {
|
||||
frameWidth: 64,
|
||||
frameHeight: 64,
|
||||
});
|
||||
}
|
||||
|
||||
create() {
|
||||
this.physics.world.setBounds(0, 0, WORLD_SIZE, WORLD_SIZE);
|
||||
this.drawWorld();
|
||||
this.createTextures();
|
||||
this.createAnimations();
|
||||
|
||||
this.player = this.physics.add.sprite(1200, 1550, 'airships', 0);
|
||||
this.player.setDepth(6).setCollideWorldBounds(true).setDisplaySize(74, 74);
|
||||
this.player.body.setAllowGravity(false);
|
||||
this.player.body.setSize(42, 56, true);
|
||||
this.player.play('player-idle');
|
||||
|
||||
this.pirate = this.physics.add.sprite(1200, 800, 'airships', 4);
|
||||
this.pirate.setDepth(6).setCollideWorldBounds(true).setDisplaySize(78, 78);
|
||||
this.pirate.body.setAllowGravity(false);
|
||||
this.pirate.body.setSize(46, 58, true);
|
||||
this.pirate.angle = 180;
|
||||
this.pirate.play('pirate-idle');
|
||||
|
||||
this.playerShots = this.physics.add.group();
|
||||
this.pirateShots = this.physics.add.group();
|
||||
this.arcGraphics = this.add.graphics().setDepth(4);
|
||||
|
||||
this.physics.add.overlap(this.playerShots, this.pirate, this.hitPirate, null, this);
|
||||
this.physics.add.overlap(this.pirateShots, this.player, this.hitPlayer, null, this);
|
||||
|
||||
this.cursors = this.input.keyboard.createCursorKeys();
|
||||
this.keys = this.input.keyboard.addKeys({
|
||||
firePort: Phaser.Input.Keyboard.KeyCodes.Q,
|
||||
fireStarboard: Phaser.Input.Keyboard.KeyCodes.E,
|
||||
stop: Phaser.Input.Keyboard.KeyCodes.SPACE,
|
||||
restart: Phaser.Input.Keyboard.KeyCodes.R,
|
||||
});
|
||||
|
||||
this.cameras.main.setBounds(0, 0, WORLD_SIZE, WORLD_SIZE);
|
||||
this.cameras.main.startFollow(this.player, true, 0.08, 0.08);
|
||||
this.cameras.main.setZoom(1.12);
|
||||
|
||||
this.title = this.add.text(18, 16, 'ARROWSTACK AIRSHIP', {
|
||||
fontFamily: 'monospace',
|
||||
fontSize: '12px',
|
||||
color: '#8cd3ff',
|
||||
}).setScrollFactor(0).setDepth(30);
|
||||
|
||||
this.hud = this.add.text(18, 42, '', {
|
||||
fontFamily: 'monospace',
|
||||
fontSize: '14px',
|
||||
color: '#e8f2f7',
|
||||
backgroundColor: '#071018cc',
|
||||
padding: { x: 12, y: 10 },
|
||||
lineSpacing: 4,
|
||||
}).setScrollFactor(0).setDepth(30);
|
||||
|
||||
this.status = this.add.text(this.scale.width / 2, 22, '', {
|
||||
fontFamily: 'monospace',
|
||||
fontSize: '18px',
|
||||
color: '#ffffff',
|
||||
backgroundColor: '#071018dd',
|
||||
padding: { x: 16, y: 10 },
|
||||
align: 'center',
|
||||
}).setOrigin(0.5, 0).setScrollFactor(0).setDepth(31);
|
||||
|
||||
this.healthGraphics = this.add.graphics().setScrollFactor(0).setDepth(30);
|
||||
this.healthLabels = {
|
||||
player: this.add.text(18, this.scale.height - 64, 'SPARROW', {
|
||||
fontFamily: 'monospace', fontSize: '12px', color: '#d8ecff',
|
||||
}).setScrollFactor(0).setDepth(31),
|
||||
pirate: this.add.text(this.scale.width - 18, this.scale.height - 64, 'CUTLASS PIRATE', {
|
||||
fontFamily: 'monospace', fontSize: '12px', color: '#ffd0d0',
|
||||
}).setOrigin(1, 0).setScrollFactor(0).setDepth(31),
|
||||
};
|
||||
|
||||
this.scale.on('resize', this.layoutHud, this);
|
||||
this.layoutHud({ width: this.scale.width, height: this.scale.height });
|
||||
}
|
||||
|
||||
createAnimations() {
|
||||
if (!this.anims.exists('player-idle')) {
|
||||
this.anims.create({
|
||||
key: 'player-idle',
|
||||
frames: this.anims.generateFrameNumbers('airships', { start: 0, end: 3 }),
|
||||
frameRate: 5,
|
||||
repeat: -1,
|
||||
});
|
||||
}
|
||||
|
||||
if (!this.anims.exists('pirate-idle')) {
|
||||
this.anims.create({
|
||||
key: 'pirate-idle',
|
||||
frames: this.anims.generateFrameNumbers('airships', { start: 4, end: 7 }),
|
||||
frameRate: 5,
|
||||
repeat: -1,
|
||||
});
|
||||
}
|
||||
|
||||
if (!this.anims.exists('impact-explosion')) {
|
||||
this.anims.create({
|
||||
key: 'impact-explosion',
|
||||
frames: this.anims.generateFrameNumbers('explosions', { start: 0, end: 5 }),
|
||||
frameRate: 18,
|
||||
repeat: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
drawWorld() {
|
||||
const bg = this.add.graphics();
|
||||
bg.fillStyle(0x132335, 1);
|
||||
bg.fillRect(0, 0, WORLD_SIZE, WORLD_SIZE);
|
||||
bg.lineStyle(1, 0x8db3cc, 0.08);
|
||||
for (let x = 0; x <= WORLD_SIZE; x += 100) bg.lineBetween(x, 0, x, WORLD_SIZE);
|
||||
for (let y = 0; y <= WORLD_SIZE; y += 100) bg.lineBetween(0, y, WORLD_SIZE, y);
|
||||
|
||||
const clouds = this.add.graphics();
|
||||
for (let i = 0; i < 46; i++) {
|
||||
const x = Phaser.Math.Between(80, WORLD_SIZE - 80);
|
||||
const y = Phaser.Math.Between(80, WORLD_SIZE - 80);
|
||||
const r = Phaser.Math.Between(24, 62);
|
||||
clouds.fillStyle(0xbcd2df, Phaser.Math.FloatBetween(0.025, 0.08));
|
||||
clouds.fillCircle(x, y, r);
|
||||
clouds.fillCircle(x + r * 0.55, y + 8, r * 0.7);
|
||||
clouds.fillCircle(x - r * 0.45, y + 5, r * 0.6);
|
||||
}
|
||||
|
||||
const island = this.add.graphics();
|
||||
island.fillStyle(0x5d694e, 1);
|
||||
island.fillEllipse(430, 480, 230, 150);
|
||||
island.fillStyle(0xa18a62, 1);
|
||||
island.fillEllipse(430, 470, 190, 105);
|
||||
island.fillStyle(0x283827, 1);
|
||||
island.fillCircle(390, 440, 18);
|
||||
island.fillCircle(455, 450, 14);
|
||||
island.fillCircle(490, 425, 12);
|
||||
|
||||
const island2 = this.add.graphics();
|
||||
island2.fillStyle(0x5d694e, 1);
|
||||
island2.fillEllipse(1940, 1840, 260, 165);
|
||||
island2.fillStyle(0xa18a62, 1);
|
||||
island2.fillEllipse(1940, 1830, 210, 112);
|
||||
}
|
||||
|
||||
createTextures() {
|
||||
const playerShot = this.make.graphics({ x: 0, y: 0, add: false });
|
||||
playerShot.fillStyle(0xffd36a, 1);
|
||||
playerShot.fillCircle(5, 5, 5);
|
||||
playerShot.lineStyle(1, 0xfff0b0, 1);
|
||||
playerShot.strokeCircle(5, 5, 4);
|
||||
playerShot.generateTexture('player-shot', 10, 10);
|
||||
playerShot.destroy();
|
||||
|
||||
const pirateShot = this.make.graphics({ x: 0, y: 0, add: false });
|
||||
pirateShot.fillStyle(0xff6b62, 1);
|
||||
pirateShot.fillCircle(5, 5, 5);
|
||||
pirateShot.lineStyle(1, 0xffb0aa, 1);
|
||||
pirateShot.strokeCircle(5, 5, 4);
|
||||
pirateShot.generateTexture('pirate-shot', 10, 10);
|
||||
pirateShot.destroy();
|
||||
}
|
||||
|
||||
update(_time, deltaMs) {
|
||||
const dt = Math.min(deltaMs / 1000, 0.05);
|
||||
|
||||
if (this.gameState !== 'playing') {
|
||||
if (Phaser.Input.Keyboard.JustDown(this.keys.restart)) this.scene.restart();
|
||||
this.drawHud();
|
||||
return;
|
||||
}
|
||||
|
||||
this.updatePlayer(dt, deltaMs);
|
||||
this.updatePirate(dt);
|
||||
this.updateProjectiles();
|
||||
this.drawWeaponArcs();
|
||||
this.drawHud();
|
||||
this.checkGameState();
|
||||
}
|
||||
|
||||
updatePlayer(dt, deltaMs) {
|
||||
if (!this.player?.body) return;
|
||||
|
||||
if (this.cursors.up.isDown) this.playerSpeed += PLAYER_ACCELERATION * dt;
|
||||
if (this.cursors.down.isDown) this.playerSpeed -= PLAYER_BRAKE * dt;
|
||||
if (this.keys.stop.isDown) this.playerSpeed *= Math.pow(0.025, dt);
|
||||
|
||||
if (!this.cursors.up.isDown && !this.cursors.down.isDown && !this.keys.stop.isDown) {
|
||||
this.playerSpeed *= Math.pow(0.985, deltaMs / 16.667);
|
||||
}
|
||||
|
||||
this.playerSpeed = clamp(this.playerSpeed, PLAYER_REVERSE_SPEED, PLAYER_MAX_SPEED);
|
||||
const turnStrength = clamp(Math.abs(this.playerSpeed) / 100, 0.28, 1);
|
||||
const directionSign = this.playerSpeed < 0 ? -1 : 1;
|
||||
|
||||
if (this.cursors.left.isDown) this.player.angle -= PLAYER_TURN_RATE * turnStrength * directionSign * dt;
|
||||
if (this.cursors.right.isDown) this.player.angle += PLAYER_TURN_RATE * turnStrength * directionSign * dt;
|
||||
|
||||
const h = headingRad(this.player);
|
||||
this.player.setVelocity(Math.cos(h) * this.playerSpeed, Math.sin(h) * this.playerSpeed);
|
||||
|
||||
if (Phaser.Input.Keyboard.JustDown(this.keys.firePort)) this.playerBroadside(-1);
|
||||
if (Phaser.Input.Keyboard.JustDown(this.keys.fireStarboard)) this.playerBroadside(1);
|
||||
}
|
||||
|
||||
updatePirate(dt) {
|
||||
if (!this.pirate.active) return;
|
||||
|
||||
const dx = this.player.x - this.pirate.x;
|
||||
const dy = this.player.y - this.pirate.y;
|
||||
const distance = Math.hypot(dx, dy);
|
||||
const bearing = bearingTo(this.pirate, this.player);
|
||||
|
||||
let desiredHeading = bearing;
|
||||
if (distance < 360) {
|
||||
const optionA = normalizeDegrees(bearing + 90);
|
||||
const optionB = normalizeDegrees(bearing - 90);
|
||||
desiredHeading = Math.abs(signedAngleDelta(optionA, this.pirate.angle)) < Math.abs(signedAngleDelta(optionB, this.pirate.angle)) ? optionA : optionB;
|
||||
}
|
||||
|
||||
const delta = signedAngleDelta(desiredHeading, this.pirate.angle);
|
||||
this.pirate.angle += clamp(delta, -PIRATE_TURN_RATE * dt, PIRATE_TURN_RATE * dt);
|
||||
|
||||
const speed = distance > 430 ? PIRATE_SPEED : distance < 210 ? 72 : 96;
|
||||
const h = headingRad(this.pirate);
|
||||
this.pirate.setVelocity(Math.cos(h) * speed, Math.sin(h) * speed);
|
||||
|
||||
const relative = signedAngleDelta(bearing, this.pirate.angle);
|
||||
const broadsideError = Math.abs(Math.abs(relative) - 90);
|
||||
if (distance < 420 && broadsideError < 38 && this.time.now - this.lastPirateShot > 1650) {
|
||||
const side = relative >= 0 ? 1 : -1;
|
||||
this.fireBroadside(this.pirate, side, this.pirateShots, 'pirate-shot', 12, 0.9);
|
||||
this.lastPirateShot = this.time.now;
|
||||
}
|
||||
}
|
||||
|
||||
playerBroadside(side) {
|
||||
if (this.time.now - this.lastPlayerShot < 850) return;
|
||||
this.fireBroadside(this.player, side, this.playerShots, 'player-shot', 16, 1.0);
|
||||
this.lastPlayerShot = this.time.now;
|
||||
}
|
||||
|
||||
fireBroadside(ship, side, group, texture, damage, velocityScale) {
|
||||
const base = headingRad(ship) + side * Math.PI / 2;
|
||||
const offsets = [-7, 0, 7];
|
||||
|
||||
for (const offset of offsets) {
|
||||
const angle = base + degToRad(offset);
|
||||
const x = ship.x + Math.cos(base) * 34;
|
||||
const y = ship.y + Math.sin(base) * 34;
|
||||
const shot = group.create(x, y, texture);
|
||||
shot.setDepth(8);
|
||||
shot.body.setAllowGravity(false);
|
||||
shot.setData('bornAt', this.time.now);
|
||||
shot.setData('damage', damage);
|
||||
shot.setVelocity(
|
||||
Math.cos(angle) * SHOT_SPEED * velocityScale + ship.body.velocity.x * 0.25,
|
||||
Math.sin(angle) * SHOT_SPEED * velocityScale + ship.body.velocity.y * 0.25,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
hitPirate(shot) {
|
||||
if (!shot.active || this.gameState !== 'playing') return;
|
||||
const damage = shot.getData('damage') || 10;
|
||||
const { x, y } = shot;
|
||||
shot.destroy();
|
||||
this.spawnExplosion(x, y, 0.7);
|
||||
this.pirateHull = Math.max(0, this.pirateHull - damage);
|
||||
this.flashShip(this.pirate, 0xffcccc);
|
||||
}
|
||||
|
||||
hitPlayer(shot) {
|
||||
if (!shot.active || this.gameState !== 'playing') return;
|
||||
const damage = shot.getData('damage') || 10;
|
||||
const { x, y } = shot;
|
||||
shot.destroy();
|
||||
this.spawnExplosion(x, y, 0.7);
|
||||
this.playerHull = Math.max(0, this.playerHull - damage);
|
||||
this.flashShip(this.player, 0xffcccc);
|
||||
}
|
||||
|
||||
spawnExplosion(x, y, scale = 1) {
|
||||
const fx = this.add.sprite(x, y, 'explosions', 0).setDepth(20).setScale(scale);
|
||||
fx.play('impact-explosion');
|
||||
fx.once(Phaser.Animations.Events.ANIMATION_COMPLETE, () => fx.destroy());
|
||||
}
|
||||
|
||||
flashShip(ship, tint) {
|
||||
ship.setTint(tint);
|
||||
this.time.delayedCall(90, () => {
|
||||
if (ship.active) ship.clearTint();
|
||||
});
|
||||
}
|
||||
|
||||
updateProjectiles() {
|
||||
for (const group of [this.playerShots, this.pirateShots]) {
|
||||
for (const shot of group.getChildren()) {
|
||||
if (!shot.active) continue;
|
||||
if (this.time.now - shot.getData('bornAt') > SHOT_LIFETIME) shot.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drawWeaponArcs() {
|
||||
this.arcGraphics.clear();
|
||||
if (this.gameState !== 'playing') return;
|
||||
|
||||
const radius = 190;
|
||||
const forward = headingRad(this.player);
|
||||
this.arcGraphics.lineStyle(2, 0xffc857, 0.28);
|
||||
|
||||
for (const side of [-1, 1]) {
|
||||
const center = forward + side * Math.PI / 2;
|
||||
this.arcGraphics.beginPath();
|
||||
this.arcGraphics.arc(this.player.x, this.player.y, radius, center - Math.PI / 4, center + Math.PI / 4);
|
||||
this.arcGraphics.strokePath();
|
||||
}
|
||||
}
|
||||
|
||||
drawHud() {
|
||||
const heading = normalizeDegrees(this.player.angle);
|
||||
const range = Math.hypot(this.player.x - this.pirate.x, this.player.y - this.pirate.y);
|
||||
this.hud.setText([
|
||||
`speed ${Math.abs(this.playerSpeed).toFixed(0).padStart(3, ' ')} ${this.playerSpeed < 0 ? 'REV' : 'FWD'}`,
|
||||
`heading ${heading.toFixed(0).padStart(3, ' ')}°`,
|
||||
`pirate ${range.toFixed(0).padStart(4, ' ')}m`,
|
||||
'',
|
||||
'↑ / ↓ throttle / reverse',
|
||||
'← / → steer',
|
||||
'Q port broadside',
|
||||
'E starboard broadside',
|
||||
'SPACE brake',
|
||||
]);
|
||||
|
||||
const w = this.scale.width;
|
||||
const h = this.scale.height;
|
||||
const barWidth = Math.min(280, w * 0.32);
|
||||
const barHeight = 16;
|
||||
const y = h - 38;
|
||||
|
||||
this.healthGraphics.clear();
|
||||
this.healthGraphics.fillStyle(0x071018, 0.85);
|
||||
this.healthGraphics.fillRect(18, y, barWidth, barHeight);
|
||||
this.healthGraphics.fillRect(w - 18 - barWidth, y, barWidth, barHeight);
|
||||
|
||||
this.healthGraphics.fillStyle(0x61c98b, 1);
|
||||
this.healthGraphics.fillRect(18, y, barWidth * (this.playerHull / PLAYER_MAX_HULL), barHeight);
|
||||
this.healthGraphics.fillStyle(0xd65757, 1);
|
||||
this.healthGraphics.fillRect(w - 18 - barWidth, y, barWidth * (this.pirateHull / PIRATE_MAX_HULL), barHeight);
|
||||
|
||||
this.healthLabels.player.setText(`SPARROW ${this.playerHull}/${PLAYER_MAX_HULL}`);
|
||||
this.healthLabels.pirate.setText(`CUTLASS PIRATE ${this.pirateHull}/${PIRATE_MAX_HULL}`);
|
||||
}
|
||||
|
||||
checkGameState() {
|
||||
if (this.pirateHull <= 0) this.finishGame('won');
|
||||
else if (this.playerHull <= 0) this.finishGame('lost');
|
||||
}
|
||||
|
||||
finishGame(result) {
|
||||
this.gameState = result;
|
||||
this.player.setVelocity(0, 0);
|
||||
this.pirate.setVelocity(0, 0);
|
||||
this.playerShots.clear(true, true);
|
||||
this.pirateShots.clear(true, true);
|
||||
|
||||
if (result === 'won') {
|
||||
this.spawnExplosion(this.pirate.x, this.pirate.y, 1.25);
|
||||
this.pirate.setTint(0x555555);
|
||||
this.status.setText('PIRATE DISABLED\nYou win\n\nPress R to restart');
|
||||
} else {
|
||||
this.spawnExplosion(this.player.x, this.player.y, 1.25);
|
||||
this.player.setTint(0x555555);
|
||||
this.status.setText('YOUR SHIP IS DISABLED\nYou lose\n\nPress R to restart');
|
||||
}
|
||||
}
|
||||
|
||||
layoutHud(gameSize) {
|
||||
const width = gameSize.width;
|
||||
const height = gameSize.height;
|
||||
if (this.status) this.status.setPosition(width / 2, 22);
|
||||
if (this.healthLabels) {
|
||||
this.healthLabels.player.setPosition(18, height - 64);
|
||||
this.healthLabels.pirate.setPosition(width - 18, height - 64);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new Phaser.Game({
|
||||
type: Phaser.AUTO,
|
||||
parent: 'game',
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight,
|
||||
backgroundColor: '#132335',
|
||||
physics: {
|
||||
default: 'arcade',
|
||||
arcade: { debug: false },
|
||||
},
|
||||
scale: {
|
||||
mode: Phaser.Scale.RESIZE,
|
||||
autoCenter: Phaser.Scale.CENTER_BOTH,
|
||||
},
|
||||
scene: GameScene,
|
||||
});
|
||||
1350
src/game-v4.js
Normal file
1350
src/game-v4.js
Normal file
File diff suppressed because it is too large
Load Diff
216
src/main.js
Normal file
216
src/main.js
Normal file
@ -0,0 +1,216 @@
|
||||
import Phaser from 'phaser';
|
||||
import './style.css';
|
||||
|
||||
const WORLD_SIZE = 2400;
|
||||
const MAX_FORWARD_SPEED = 180;
|
||||
const MAX_REVERSE_SPEED = -55;
|
||||
const ACCELERATION = 85;
|
||||
const BRAKE = 110;
|
||||
const TURN_RATE = 72;
|
||||
const BROADSIDE_SPEED = 520;
|
||||
|
||||
class GameScene extends Phaser.Scene {
|
||||
constructor() {
|
||||
super('game');
|
||||
this.speed = 0;
|
||||
}
|
||||
|
||||
create() {
|
||||
this.physics.world.setBounds(0, 0, WORLD_SIZE, WORLD_SIZE);
|
||||
this.drawWorld();
|
||||
this.createTextures();
|
||||
|
||||
this.ship = this.physics.add.image(WORLD_SIZE / 2, WORLD_SIZE / 2, 'ship');
|
||||
this.ship.setDepth(5);
|
||||
this.ship.setCollideWorldBounds(true);
|
||||
this.ship.body.setAllowGravity(false);
|
||||
|
||||
this.projectiles = this.physics.add.group();
|
||||
this.arcGraphics = this.add.graphics().setDepth(4);
|
||||
|
||||
this.cursors = this.input.keyboard.createCursorKeys();
|
||||
this.keys = this.input.keyboard.addKeys({
|
||||
firePort: Phaser.Input.Keyboard.KeyCodes.Q,
|
||||
fireStarboard: Phaser.Input.Keyboard.KeyCodes.E,
|
||||
stop: Phaser.Input.Keyboard.KeyCodes.SPACE,
|
||||
});
|
||||
|
||||
this.cameras.main.setBounds(0, 0, WORLD_SIZE, WORLD_SIZE);
|
||||
this.cameras.main.startFollow(this.ship, true, 0.08, 0.08);
|
||||
this.cameras.main.setZoom(1.15);
|
||||
|
||||
this.hud = this.add.text(18, 18, '', {
|
||||
fontFamily: 'monospace',
|
||||
fontSize: '15px',
|
||||
color: '#e8f2ff',
|
||||
backgroundColor: '#071018cc',
|
||||
padding: { x: 12, y: 10 },
|
||||
lineSpacing: 4,
|
||||
}).setScrollFactor(0).setDepth(20);
|
||||
|
||||
this.title = this.add.text(18, 18, 'ARROWSTACK AIRSHIP', {
|
||||
fontFamily: 'monospace',
|
||||
fontSize: '12px',
|
||||
color: '#8cd3ff',
|
||||
}).setScrollFactor(0).setDepth(21);
|
||||
|
||||
this.hud.y = 42;
|
||||
}
|
||||
|
||||
drawWorld() {
|
||||
const background = this.add.graphics();
|
||||
background.fillStyle(0x132335, 1);
|
||||
background.fillRect(0, 0, WORLD_SIZE, WORLD_SIZE);
|
||||
|
||||
background.lineStyle(1, 0x8db3cc, 0.08);
|
||||
for (let x = 0; x <= WORLD_SIZE; x += 100) background.lineBetween(x, 0, x, WORLD_SIZE);
|
||||
for (let y = 0; y <= WORLD_SIZE; y += 100) background.lineBetween(0, y, WORLD_SIZE, y);
|
||||
|
||||
const clouds = this.add.graphics();
|
||||
for (let i = 0; i < 42; i++) {
|
||||
const x = Phaser.Math.Between(80, WORLD_SIZE - 80);
|
||||
const y = Phaser.Math.Between(80, WORLD_SIZE - 80);
|
||||
const radius = Phaser.Math.Between(25, 65);
|
||||
clouds.fillStyle(0xbcd2df, Phaser.Math.FloatBetween(0.025, 0.09));
|
||||
clouds.fillCircle(x, y, radius);
|
||||
clouds.fillCircle(x + radius * 0.6, y + 8, radius * 0.7);
|
||||
clouds.fillCircle(x - radius * 0.5, y + 5, radius * 0.6);
|
||||
}
|
||||
|
||||
const island = this.add.graphics();
|
||||
island.fillStyle(0x5d694e, 1);
|
||||
island.fillEllipse(430, 480, 230, 150);
|
||||
island.fillStyle(0xa18a62, 1);
|
||||
island.fillEllipse(430, 470, 190, 105);
|
||||
island.fillStyle(0x283827, 1);
|
||||
island.fillCircle(390, 440, 18);
|
||||
island.fillCircle(455, 450, 14);
|
||||
island.fillCircle(490, 425, 12);
|
||||
}
|
||||
|
||||
createTextures() {
|
||||
const ship = this.make.graphics({ x: 0, y: 0, add: false });
|
||||
ship.fillStyle(0xd8a65f, 1);
|
||||
ship.fillTriangle(32, 2, 8, 62, 56, 62);
|
||||
ship.fillStyle(0x715438, 1);
|
||||
ship.fillRect(17, 31, 30, 20);
|
||||
ship.fillStyle(0xe8dfc7, 1);
|
||||
ship.fillEllipse(32, 24, 42, 16);
|
||||
ship.lineStyle(2, 0x201914, 0.8);
|
||||
ship.strokeTriangle(32, 2, 8, 62, 56, 62);
|
||||
ship.generateTexture('ship', 64, 68);
|
||||
ship.destroy();
|
||||
|
||||
const shot = this.make.graphics({ x: 0, y: 0, add: false });
|
||||
shot.fillStyle(0xffd36a, 1);
|
||||
shot.fillCircle(5, 5, 5);
|
||||
shot.generateTexture('shot', 10, 10);
|
||||
shot.destroy();
|
||||
}
|
||||
|
||||
update(_time, deltaMs) {
|
||||
const dt = deltaMs / 1000;
|
||||
|
||||
if (this.cursors.up.isDown) this.speed += ACCELERATION * dt;
|
||||
if (this.cursors.down.isDown) this.speed -= BRAKE * dt;
|
||||
if (this.keys.stop.isDown) this.speed *= Math.pow(0.025, dt);
|
||||
|
||||
if (!this.cursors.up.isDown && !this.cursors.down.isDown && !this.keys.stop.isDown) {
|
||||
this.speed *= Math.pow(0.985, deltaMs / 16.667);
|
||||
}
|
||||
|
||||
this.speed = Phaser.Math.Clamp(this.speed, MAX_REVERSE_SPEED, MAX_FORWARD_SPEED);
|
||||
|
||||
const turnStrength = Phaser.Math.Clamp(Math.abs(this.speed) / 100, 0.28, 1);
|
||||
const directionSign = this.speed < 0 ? -1 : 1;
|
||||
|
||||
if (this.cursors.left.isDown) this.ship.angle -= TURN_RATE * turnStrength * directionSign * dt;
|
||||
if (this.cursors.right.isDown) this.ship.angle += TURN_RATE * turnStrength * directionSign * dt;
|
||||
|
||||
const heading = Phaser.Math.DegToRad(this.ship.angle - 90);
|
||||
this.ship.setVelocity(Math.cos(heading) * this.speed, Math.sin(heading) * this.speed);
|
||||
|
||||
if (Phaser.Input.Keyboard.JustDown(this.keys.firePort)) this.fireBroadside(-1);
|
||||
if (Phaser.Input.Keyboard.JustDown(this.keys.fireStarboard)) this.fireBroadside(1);
|
||||
|
||||
this.drawWeaponArcs();
|
||||
this.cleanupProjectiles();
|
||||
|
||||
const normalisedHeading = Phaser.Math.Angle.NormalizeDegrees(this.ship.angle);
|
||||
this.hud.setText([
|
||||
`speed ${Math.abs(this.speed).toFixed(0).padStart(3, ' ')} ${this.speed < 0 ? 'REV' : 'FWD'}`,
|
||||
`heading ${normalisedHeading.toFixed(0).padStart(3, ' ')}°`,
|
||||
'',
|
||||
'↑ / ↓ throttle / reverse',
|
||||
'← / → steer',
|
||||
'Q port broadside',
|
||||
'E starboard broadside',
|
||||
'SPACE brake',
|
||||
]);
|
||||
}
|
||||
|
||||
fireBroadside(side) {
|
||||
const broadsideAngle = Phaser.Math.DegToRad(this.ship.angle - 90 + side * 90);
|
||||
const muzzleOffset = 34;
|
||||
const x = this.ship.x + Math.cos(broadsideAngle) * muzzleOffset;
|
||||
const y = this.ship.y + Math.sin(broadsideAngle) * muzzleOffset;
|
||||
|
||||
const shot = this.projectiles.create(x, y, 'shot');
|
||||
shot.setDepth(6);
|
||||
shot.body.setAllowGravity(false);
|
||||
shot.setData('bornAt', this.time.now);
|
||||
shot.setVelocity(
|
||||
Math.cos(broadsideAngle) * BROADSIDE_SPEED + this.ship.body.velocity.x * 0.3,
|
||||
Math.sin(broadsideAngle) * BROADSIDE_SPEED + this.ship.body.velocity.y * 0.3,
|
||||
);
|
||||
|
||||
this.tweens.add({
|
||||
targets: shot,
|
||||
alpha: 0.15,
|
||||
duration: 700,
|
||||
ease: 'Quad.easeIn',
|
||||
});
|
||||
}
|
||||
|
||||
cleanupProjectiles() {
|
||||
for (const shot of this.projectiles.getChildren()) {
|
||||
if (!shot.active) continue;
|
||||
if (this.time.now - shot.getData('bornAt') > 800) shot.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
drawWeaponArcs() {
|
||||
const g = this.arcGraphics;
|
||||
g.clear();
|
||||
|
||||
const radius = 190;
|
||||
const base = Phaser.Math.DegToRad(this.ship.angle - 90);
|
||||
|
||||
g.lineStyle(2, 0xffc857, 0.28);
|
||||
|
||||
g.beginPath();
|
||||
g.arc(this.ship.x, this.ship.y, radius, base + Phaser.Math.DegToRad(-135), base + Phaser.Math.DegToRad(-45));
|
||||
g.strokePath();
|
||||
|
||||
g.beginPath();
|
||||
g.arc(this.ship.x, this.ship.y, radius, base + Phaser.Math.DegToRad(45), base + Phaser.Math.DegToRad(135));
|
||||
g.strokePath();
|
||||
}
|
||||
}
|
||||
|
||||
new Phaser.Game({
|
||||
type: Phaser.AUTO,
|
||||
parent: 'game',
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight,
|
||||
backgroundColor: '#132335',
|
||||
physics: {
|
||||
default: 'arcade',
|
||||
arcade: { debug: false },
|
||||
},
|
||||
scale: {
|
||||
mode: Phaser.Scale.RESIZE,
|
||||
autoCenter: Phaser.Scale.CENTER_BOTH,
|
||||
},
|
||||
scene: GameScene,
|
||||
});
|
||||
761
src/mobile-hud.js
Normal file
761
src/mobile-hud.js
Normal file
@ -0,0 +1,761 @@
|
||||
import topHudSrc from './assets/controller/top-hud.js';
|
||||
import playerHealthSrc from './assets/controller/player-health-fresh-sheet.png';
|
||||
import enemyHealthSrc from './assets/controller/enemy-health.js';
|
||||
import cannonRoundelSrc from './assets/controller/cannon-roundel.js';
|
||||
import helmFrameSrc from './assets/controller/helm-frame.js';
|
||||
import helmKnobSrc from './assets/controller/helm-knob.js';
|
||||
import { airshipLog } from './diagnostics.js';
|
||||
|
||||
const isDesktopPointer = window.matchMedia('(hover: hover) and (pointer: fine)');
|
||||
|
||||
if (!isDesktopPointer.matches) {
|
||||
const root = document.querySelector('#mobile-controls');
|
||||
|
||||
if (root) {
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
#mobile-controls.controller-active {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#controller-hud {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 1;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
color: #f8e8b7;
|
||||
font-family: Georgia, 'Times New Roman', serif;
|
||||
font-weight: 700;
|
||||
text-shadow: 0 1px 2px #000, 0 0 5px rgba(47, 22, 2, .95);
|
||||
}
|
||||
|
||||
.steam-piece {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.steam-piece > img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
.steam-top {
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
aspect-ratio: 960 / 246;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.steam-top-value {
|
||||
position: absolute;
|
||||
top: 63%;
|
||||
transform: translate(-50%, -50%);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
min-width: 0;
|
||||
height: 15%;
|
||||
padding: 0 2px;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
color: #f7dfa1;
|
||||
font-size: clamp(9px, 2.45vw, 12px);
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-feature-settings: 'tnum' 1;
|
||||
letter-spacing: .015em;
|
||||
line-height: 1;
|
||||
text-shadow: 0 1px 1px rgba(0, 0, 0, .95), 0 0 3px rgba(42, 19, 2, .78);
|
||||
}
|
||||
|
||||
.steam-speed { left: 9.3%; width: 7%; }
|
||||
.steam-heading { left: 21.2%; width: 12%; }
|
||||
.steam-wave { left: 50%; top: 67%; width: 8%; }
|
||||
.steam-hostiles { left: 65.4%; width: 7%; }
|
||||
.steam-kills { left: 78.2%; width: 7%; }
|
||||
.steam-range { left: 91.1%; width: 12%; }
|
||||
|
||||
.steam-player-card,
|
||||
.steam-enemy-card {
|
||||
top: clamp(80px, 10.3vh, 90px);
|
||||
aspect-ratio: 360 / 130;
|
||||
width: min(31vw, 120px);
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.steam-health-art {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 400% 300%;
|
||||
background-position: 0 0;
|
||||
background-color: transparent;
|
||||
image-rendering: auto;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.steam-player-card {
|
||||
left: 1%;
|
||||
}
|
||||
|
||||
.steam-player-card.steam-hit {
|
||||
animation: steam-player-hit 220ms ease-out;
|
||||
}
|
||||
|
||||
@keyframes steam-player-hit {
|
||||
0% { filter: brightness(1); transform: translateX(0); }
|
||||
25% { filter: brightness(1.9); transform: translateX(-4px); }
|
||||
55% { filter: brightness(1.35); transform: translateX(4px); }
|
||||
100% { filter: brightness(1); transform: translateX(0); }
|
||||
}
|
||||
|
||||
.steam-enemy-card {
|
||||
right: 1%;
|
||||
}
|
||||
|
||||
.steam-card-name,
|
||||
.steam-card-value {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
transform: translate(-50%, -50%);
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.steam-card-name {
|
||||
top: 28%;
|
||||
color: #f6d58e;
|
||||
font-size: clamp(8px, 2.2vw, 11px);
|
||||
letter-spacing: .06em;
|
||||
}
|
||||
|
||||
.steam-player-card .steam-card-name {
|
||||
left: 61%;
|
||||
}
|
||||
|
||||
.steam-enemy-card .steam-card-name {
|
||||
left: 39%;
|
||||
}
|
||||
|
||||
.steam-card-value {
|
||||
top: 67%;
|
||||
color: #fff0bd;
|
||||
font-size: clamp(8px, 2.4vw, 12px);
|
||||
}
|
||||
|
||||
.steam-player-card .steam-card-value {
|
||||
left: 61%;
|
||||
}
|
||||
|
||||
.steam-enemy-card .steam-card-value {
|
||||
left: 39%;
|
||||
}
|
||||
|
||||
.steam-radar-wrap {
|
||||
left: 1%;
|
||||
bottom: max(8px, env(safe-area-inset-bottom));
|
||||
width: min(39vw, 174px);
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
|
||||
.steam-helm-frame {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.steam-piece > img.steam-helm-knob {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 46%;
|
||||
width: 16%;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
transform: translate(-50%, -50%);
|
||||
pointer-events: none;
|
||||
z-index: 3;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.steam-helm-label {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 5%;
|
||||
transform: translateX(-50%);
|
||||
width: 55%;
|
||||
text-align: center;
|
||||
color: #ffe5a0;
|
||||
font-size: clamp(8px, 2.5vw, 12px);
|
||||
letter-spacing: .12em;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#steam-radar-contacts {
|
||||
position: absolute;
|
||||
left: 17%;
|
||||
top: 14%;
|
||||
width: 66%;
|
||||
height: 69%;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.steam-radar-player,
|
||||
.steam-radar-dot {
|
||||
position: absolute;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
margin: -3px 0 0 -3px;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 6px currentColor;
|
||||
}
|
||||
|
||||
.steam-radar-player {
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
color: #ffe08c;
|
||||
background: currentColor;
|
||||
}
|
||||
|
||||
.steam-radar-dot {
|
||||
color: #ff5c42;
|
||||
background: currentColor;
|
||||
}
|
||||
|
||||
.steam-cannon-port,
|
||||
.steam-cannon-starboard {
|
||||
right: 3%;
|
||||
width: min(22vw, 104px);
|
||||
aspect-ratio: 1;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.steam-cannon-port {
|
||||
bottom: max(116px, calc(env(safe-area-inset-bottom) + 112px));
|
||||
}
|
||||
|
||||
.steam-cannon-starboard {
|
||||
bottom: max(8px, env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.steam-cannon-port > img,
|
||||
.steam-cannon-starboard > img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: 50%;
|
||||
clip-path: circle(49% at 50% 50%);
|
||||
}
|
||||
|
||||
.steam-reload-track {
|
||||
position: absolute;
|
||||
left: 12%;
|
||||
right: 12%;
|
||||
bottom: -7%;
|
||||
height: 5px;
|
||||
border: 1px solid rgba(232, 184, 91, .42);
|
||||
border-radius: 999px;
|
||||
background: rgba(8, 25, 31, .72);
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.steam-reload-fill {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transform: scaleX(1);
|
||||
transform-origin: left center;
|
||||
background: rgba(255, 224, 140, .92);
|
||||
transition: transform 60ms linear;
|
||||
}
|
||||
|
||||
.steam-action-label {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 7%;
|
||||
transform: translateX(-50%);
|
||||
min-width: 48%;
|
||||
padding: 2px 7px;
|
||||
border: 1px solid rgba(232, 184, 91, .55);
|
||||
border-radius: 999px;
|
||||
background: rgba(8, 25, 31, .82);
|
||||
text-align: center;
|
||||
color: #ffe5a0;
|
||||
font-size: clamp(8px, 2.3vw, 11px);
|
||||
line-height: 1.1;
|
||||
letter-spacing: .1em;
|
||||
white-space: nowrap;
|
||||
box-shadow: 0 1px 4px rgba(0,0,0,.5);
|
||||
}
|
||||
|
||||
.steam-status {
|
||||
display: none;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 27%;
|
||||
transform: translateX(-50%);
|
||||
max-width: 72%;
|
||||
padding: 7px 12px;
|
||||
border: 1px solid rgba(201, 146, 60, .7);
|
||||
border-radius: 8px;
|
||||
background: rgba(8, 20, 24, .82);
|
||||
color: #fff0c3;
|
||||
font-size: clamp(10px, 3vw, 14px);
|
||||
text-align: center;
|
||||
white-space: pre-line;
|
||||
box-shadow: 0 4px 18px rgba(0,0,0,.35);
|
||||
}
|
||||
|
||||
.steam-status.visible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#mobile-controls.controller-active #drag-stick {
|
||||
left: 1%;
|
||||
bottom: max(8px, env(safe-area-inset-bottom));
|
||||
top: auto;
|
||||
width: min(39vw, 174px);
|
||||
height: auto;
|
||||
aspect-ratio: 1;
|
||||
border: 0;
|
||||
border-radius: 50%;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
#mobile-controls.controller-active #drag-stick::before,
|
||||
#mobile-controls.controller-active #drag-stick::after,
|
||||
#mobile-controls.controller-active #drag-thumb {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
#mobile-controls.controller-active .mobile-control {
|
||||
min-width: 0;
|
||||
height: auto;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
color: transparent;
|
||||
font-size: 0;
|
||||
box-shadow: none;
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
transform: none;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
#mobile-controls.controller-active .mobile-control.active,
|
||||
#mobile-controls.controller-active .mobile-control:active {
|
||||
background: rgba(255, 210, 112, .08);
|
||||
transform: none;
|
||||
}
|
||||
|
||||
#mobile-controls.controller-active .mobile-control.port,
|
||||
#mobile-controls.controller-active .mobile-control.starboard {
|
||||
left: auto;
|
||||
right: 3%;
|
||||
width: min(22vw, 104px);
|
||||
aspect-ratio: 1;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
#mobile-controls.controller-active .mobile-control.port {
|
||||
top: auto;
|
||||
bottom: max(116px, calc(env(safe-area-inset-bottom) + 112px));
|
||||
}
|
||||
|
||||
#mobile-controls.controller-active .mobile-control.starboard {
|
||||
top: auto;
|
||||
bottom: max(8px, env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
#mobile-controls.controller-active .mobile-control.restart {
|
||||
left: auto;
|
||||
right: 1%;
|
||||
top: clamp(147px, 20.5vh, 179px);
|
||||
bottom: auto;
|
||||
width: 14%;
|
||||
height: auto;
|
||||
aspect-ratio: 1;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
@media (max-height: 700px) and (max-width: 699px) {
|
||||
.steam-player-card,
|
||||
.steam-enemy-card {
|
||||
top: 76px;
|
||||
}
|
||||
|
||||
.steam-radar-wrap {
|
||||
width: 34vw;
|
||||
bottom: max(6px, env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
#mobile-controls.controller-active #drag-stick {
|
||||
width: 34vw;
|
||||
bottom: max(6px, env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.steam-cannon-port,
|
||||
.steam-cannon-starboard,
|
||||
#mobile-controls.controller-active .mobile-control.port,
|
||||
#mobile-controls.controller-active .mobile-control.starboard {
|
||||
width: min(20vw, 90px);
|
||||
}
|
||||
|
||||
.steam-cannon-port,
|
||||
#mobile-controls.controller-active .mobile-control.port {
|
||||
bottom: max(106px, calc(env(safe-area-inset-bottom) + 102px));
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 700px) {
|
||||
:root {
|
||||
--steam-wide-helm-size: clamp(118px, 28vh, 180px);
|
||||
--steam-wide-cannon-size: clamp(72px, 18vh, 100px);
|
||||
--steam-wide-edge-right: max(18px, env(safe-area-inset-right));
|
||||
--steam-wide-edge-left: max(18px, env(safe-area-inset-left));
|
||||
--steam-wide-bottom: max(12px, env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.steam-top {
|
||||
left: 50%;
|
||||
width: min(64vw, 560px);
|
||||
height: auto;
|
||||
aspect-ratio: 960 / 246;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.steam-top-value {
|
||||
font-size: clamp(9px, 1.15vw, 13px);
|
||||
}
|
||||
|
||||
.steam-player-card,
|
||||
.steam-enemy-card {
|
||||
top: clamp(70px, 20vh, 104px);
|
||||
}
|
||||
|
||||
.steam-player-card {
|
||||
left: max(12px, env(safe-area-inset-left));
|
||||
width: min(18vw, 180px);
|
||||
}
|
||||
|
||||
.steam-enemy-card {
|
||||
right: max(12px, env(safe-area-inset-right));
|
||||
width: min(18vw, 180px);
|
||||
}
|
||||
|
||||
.steam-card-name {
|
||||
font-size: clamp(8px, 1.15vw, 12px);
|
||||
}
|
||||
|
||||
.steam-card-value {
|
||||
font-size: clamp(8px, 1.25vw, 12px);
|
||||
}
|
||||
|
||||
.steam-radar-wrap,
|
||||
#mobile-controls.controller-active #drag-stick {
|
||||
left: var(--steam-wide-edge-left);
|
||||
bottom: var(--steam-wide-bottom);
|
||||
width: var(--steam-wide-helm-size);
|
||||
}
|
||||
|
||||
.steam-cannon-port,
|
||||
.steam-cannon-starboard,
|
||||
#mobile-controls.controller-active .mobile-control.port,
|
||||
#mobile-controls.controller-active .mobile-control.starboard {
|
||||
top: auto;
|
||||
bottom: var(--steam-wide-bottom);
|
||||
width: var(--steam-wide-cannon-size);
|
||||
}
|
||||
|
||||
.steam-cannon-starboard,
|
||||
#mobile-controls.controller-active .mobile-control.starboard {
|
||||
right: var(--steam-wide-edge-right);
|
||||
}
|
||||
|
||||
.steam-cannon-port,
|
||||
#mobile-controls.controller-active .mobile-control.port {
|
||||
right: calc(var(--steam-wide-edge-right) + var(--steam-wide-cannon-size) + 14px);
|
||||
}
|
||||
|
||||
#mobile-controls.controller-active .mobile-control.restart {
|
||||
right: max(12px, env(safe-area-inset-right));
|
||||
top: clamp(118px, 30vh, 168px);
|
||||
width: clamp(52px, 8vw, 76px);
|
||||
}
|
||||
|
||||
.steam-status {
|
||||
top: clamp(92px, 24vh, 144px);
|
||||
max-width: min(44%, 430px);
|
||||
font-size: clamp(10px, 1.25vw, 14px);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1100px) {
|
||||
.steam-top {
|
||||
width: min(52vw, 560px);
|
||||
}
|
||||
|
||||
.steam-player-card {
|
||||
width: min(28vw, 360px);
|
||||
}
|
||||
|
||||
.steam-enemy-card {
|
||||
width: min(25vw, 330px);
|
||||
}
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
root.classList.add('controller-active');
|
||||
|
||||
const hud = document.createElement('div');
|
||||
hud.id = 'controller-hud';
|
||||
root.prepend(hud);
|
||||
|
||||
|
||||
const addPiece = (className, src, inner = '') => {
|
||||
const piece = document.createElement('div');
|
||||
piece.className = `steam-piece ${className}`;
|
||||
|
||||
const image = document.createElement('img');
|
||||
image.src = src;
|
||||
image.alt = '';
|
||||
image.draggable = false;
|
||||
image.addEventListener('error', () => {
|
||||
airshipLog('hud:image-error', {
|
||||
component: className,
|
||||
srcType: src?.slice?.(0, 32) || 'unknown',
|
||||
}, 'error');
|
||||
});
|
||||
piece.appendChild(image);
|
||||
|
||||
if (inner) piece.insertAdjacentHTML('beforeend', inner);
|
||||
hud.appendChild(piece);
|
||||
return piece;
|
||||
};
|
||||
|
||||
addPiece('steam-top', topHudSrc, `
|
||||
<span class="steam-top-value steam-speed" data-steam="speed">0</span>
|
||||
<span class="steam-top-value steam-heading" data-steam="heading">000°</span>
|
||||
<span class="steam-top-value steam-wave" data-steam="wave">1</span>
|
||||
<span class="steam-top-value steam-hostiles" data-steam="hostiles">0</span>
|
||||
<span class="steam-top-value steam-kills" data-steam="kills">0</span>
|
||||
<span class="steam-top-value steam-range" data-steam="range">--</span>
|
||||
`);
|
||||
|
||||
const addHealthPiece = (className, src, inner = '') => {
|
||||
const piece = document.createElement('div');
|
||||
piece.className = `steam-piece ${className}`;
|
||||
|
||||
const art = document.createElement('span');
|
||||
art.className = 'steam-health-art';
|
||||
art.style.backgroundImage = `url("${src}")`;
|
||||
piece.appendChild(art);
|
||||
|
||||
const probe = new Image();
|
||||
probe.addEventListener('load', () => airshipLog('hud:health-sheet-loaded', { component: className }));
|
||||
probe.addEventListener('error', () => airshipLog('hud:health-sheet-error', { component: className }, 'error'));
|
||||
probe.src = src;
|
||||
|
||||
if (inner) piece.insertAdjacentHTML('beforeend', inner);
|
||||
hud.appendChild(piece);
|
||||
return piece;
|
||||
};
|
||||
|
||||
const playerHealthPiece = addHealthPiece('steam-player-card', playerHealthSrc, `
|
||||
<span class="steam-card-name">SPARROW</span>
|
||||
<span class="steam-card-value" data-steam="player-hull">100/100</span>
|
||||
`);
|
||||
|
||||
const enemyHealthPiece = addHealthPiece('steam-enemy-card', enemyHealthSrc, `
|
||||
<span class="steam-card-name" data-steam="enemy-name">PIRATE</span>
|
||||
<span class="steam-card-value" data-steam="enemy-hull">CLEAR</span>
|
||||
`);
|
||||
|
||||
const radarPiece = document.createElement('div');
|
||||
radarPiece.className = 'steam-piece steam-radar-wrap';
|
||||
|
||||
const helmFrame = document.createElement('img');
|
||||
helmFrame.className = 'steam-helm-frame';
|
||||
helmFrame.src = helmFrameSrc;
|
||||
helmFrame.alt = '';
|
||||
helmFrame.draggable = false;
|
||||
helmFrame.addEventListener('load', () => airshipLog('hud:helm-frame-loaded'));
|
||||
helmFrame.addEventListener('error', () => airshipLog('hud:helm-frame-error', {}, 'error'));
|
||||
radarPiece.appendChild(helmFrame);
|
||||
|
||||
const helmKnob = document.createElement('img');
|
||||
helmKnob.className = 'steam-helm-knob';
|
||||
helmKnob.src = helmKnobSrc;
|
||||
helmKnob.alt = '';
|
||||
helmKnob.draggable = false;
|
||||
helmKnob.addEventListener('load', () => airshipLog('hud:helm-knob-loaded'));
|
||||
helmKnob.addEventListener('error', () => airshipLog('hud:helm-knob-error', {}, 'error'));
|
||||
radarPiece.appendChild(helmKnob);
|
||||
|
||||
radarPiece.insertAdjacentHTML('beforeend', `
|
||||
<div id="steam-radar-contacts">
|
||||
<span class="steam-radar-player"></span>
|
||||
</div>
|
||||
<span class="steam-helm-label">HELM</span>
|
||||
`);
|
||||
hud.appendChild(radarPiece);
|
||||
|
||||
addPiece('steam-cannon-port', cannonRoundelSrc, `
|
||||
<span class="steam-action-label">PORT</span>
|
||||
<span class="steam-reload-track"><span class="steam-reload-fill" data-steam="port-reload"></span></span>
|
||||
`);
|
||||
|
||||
addPiece('steam-cannon-starboard', cannonRoundelSrc, `
|
||||
<span class="steam-action-label">STBD</span>
|
||||
<span class="steam-reload-track"><span class="steam-reload-fill" data-steam="starboard-reload"></span></span>
|
||||
`);
|
||||
|
||||
const status = document.createElement('div');
|
||||
status.className = 'steam-status';
|
||||
status.dataset.steam = 'status';
|
||||
hud.appendChild(status);
|
||||
|
||||
const fields = Object.fromEntries(
|
||||
[...hud.querySelectorAll('[data-steam]')].map((el) => [el.dataset.steam, el]),
|
||||
);
|
||||
|
||||
const radarContacts = radarPiece.querySelector('#steam-radar-contacts');
|
||||
|
||||
const setHelmKnob = (x = 0, y = 0) => {
|
||||
const maxOffset = Math.min(radarPiece.clientWidth * 0.16, 24);
|
||||
const px = Math.max(-1, Math.min(1, x)) * maxOffset;
|
||||
const py = Math.max(-1, Math.min(1, y)) * maxOffset;
|
||||
helmKnob.style.transform = `translate(-50%, -50%) translate(${px}px, ${py}px)`;
|
||||
};
|
||||
|
||||
window.addEventListener('airship:helm-vector', (event) => {
|
||||
setHelmKnob(event.detail?.x ?? 0, event.detail?.y ?? 0);
|
||||
});
|
||||
|
||||
window.addEventListener('blur', () => setHelmKnob(0, 0));
|
||||
const radarDots = Array.from({ length: 10 }, () => {
|
||||
const dot = document.createElement('span');
|
||||
dot.className = 'steam-radar-dot';
|
||||
dot.hidden = true;
|
||||
radarContacts.appendChild(dot);
|
||||
return dot;
|
||||
});
|
||||
|
||||
const clamp01 = (value) => Math.max(0, Math.min(1, value));
|
||||
|
||||
const healthFrame = (current, max) => {
|
||||
const pct = max > 0 ? clamp01(current / max) : 0;
|
||||
return Math.max(0, Math.min(11, Math.round((1 - pct) * 11)));
|
||||
};
|
||||
|
||||
const setHealthFrame = (piece, frame) => {
|
||||
const col = frame % 4;
|
||||
const row = Math.floor(frame / 4);
|
||||
const art = piece.querySelector('.steam-health-art');
|
||||
if (!art) return;
|
||||
art.style.backgroundPosition = `${(col / 3) * 100}% ${(row / 2) * 100}%`;
|
||||
};
|
||||
|
||||
const updateRadar = (contacts = []) => {
|
||||
radarDots.forEach((dot, index) => {
|
||||
const contact = contacts[index];
|
||||
if (!contact) {
|
||||
dot.hidden = true;
|
||||
return;
|
||||
}
|
||||
|
||||
let dx = Number(contact.dx) || 0;
|
||||
let dy = Number(contact.dy) || 0;
|
||||
const distance = Math.hypot(dx, dy);
|
||||
if (distance > 1) {
|
||||
dx /= distance;
|
||||
dy /= distance;
|
||||
}
|
||||
|
||||
dot.style.left = `${50 + dx * 36}%`;
|
||||
dot.style.top = `${50 + dy * 36}%`;
|
||||
dot.hidden = false;
|
||||
});
|
||||
};
|
||||
|
||||
let lastPlayerHitSequence = 0;
|
||||
|
||||
const pulsePlayerHit = (sequence) => {
|
||||
if (!Number.isFinite(sequence) || sequence <= lastPlayerHitSequence) return;
|
||||
lastPlayerHitSequence = sequence;
|
||||
playerHealthPiece.classList.remove('steam-hit');
|
||||
void playerHealthPiece.offsetWidth;
|
||||
playerHealthPiece.classList.add('steam-hit');
|
||||
};
|
||||
|
||||
const renderHudState = (state) => {
|
||||
if (!state?.player) return;
|
||||
|
||||
const playerHull = Math.max(0, state.player.hull ?? 100);
|
||||
const playerMaxHull = Math.max(1, state.player.maxHull ?? 100);
|
||||
const target = state.target || null;
|
||||
|
||||
fields.speed.textContent = `${Math.abs(state.speed || 0).toFixed(0)}${state.speed < 0 ? 'R' : ''}`;
|
||||
fields.heading.textContent = `${(state.heading || 0).toFixed(0).padStart(3, '0')}°`;
|
||||
fields.wave.textContent = String(state.wave || 1);
|
||||
fields.hostiles.textContent = String(state.hostiles || 0);
|
||||
fields.kills.textContent = String(state.kills || 0);
|
||||
fields.range.textContent = target && Number.isFinite(target.range) ? `${Math.round(target.range)}m` : '--';
|
||||
fields['player-hull'].textContent = `${playerHull}/${playerMaxHull}`;
|
||||
fields['enemy-name'].textContent = target?.name || 'PIRATE';
|
||||
fields['enemy-hull'].textContent = target ? `${Math.max(0, target.hull || 0)}/${Math.max(1, target.maxHull || 120)}` : 'CLEAR';
|
||||
|
||||
setHealthFrame(
|
||||
playerHealthPiece,
|
||||
healthFrame(playerHull, playerMaxHull),
|
||||
);
|
||||
setHealthFrame(
|
||||
enemyHealthPiece,
|
||||
target ? healthFrame(target.hull || 0, target.maxHull || 120) : 11,
|
||||
);
|
||||
|
||||
fields['port-reload'].style.transform = `scaleX(${clamp01(state.reload?.port ?? 1)})`;
|
||||
fields['starboard-reload'].style.transform = `scaleX(${clamp01(state.reload?.starboard ?? 1)})`;
|
||||
|
||||
const statusText = String(state.status || '').trim();
|
||||
fields.status.textContent = statusText;
|
||||
fields.status.classList.toggle('visible', Boolean(statusText));
|
||||
|
||||
pulsePlayerHit(state.player.hitSequence ?? 0);
|
||||
updateRadar(state.radar || []);
|
||||
};
|
||||
|
||||
window.addEventListener('airship:hud-state', (event) => {
|
||||
renderHudState(event.detail);
|
||||
});
|
||||
|
||||
airshipLog('hud:mounted', {
|
||||
mobile: true,
|
||||
pieces: hud.querySelectorAll('.steam-piece').length,
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
159
src/pirate-ai.js
Normal file
159
src/pirate-ai.js
Normal file
@ -0,0 +1,159 @@
|
||||
export const PIRATE_CLASSES = {
|
||||
raider: {
|
||||
key: 'raider',
|
||||
label: 'RAIDER',
|
||||
hull: 80,
|
||||
speed: 148,
|
||||
turnRate: 70,
|
||||
reloadMs: 1280,
|
||||
projectileDamage: 4,
|
||||
preferredRange: 300,
|
||||
retreatRange: 175,
|
||||
broadsideRange: 380,
|
||||
broadsideError: 34,
|
||||
displaySize: 70,
|
||||
},
|
||||
frigate: {
|
||||
key: 'frigate',
|
||||
label: 'FRIGATE',
|
||||
hull: 120,
|
||||
speed: 118,
|
||||
turnRate: 52,
|
||||
reloadMs: 1650,
|
||||
projectileDamage: 6,
|
||||
preferredRange: 335,
|
||||
retreatRange: 210,
|
||||
broadsideRange: 420,
|
||||
broadsideError: 38,
|
||||
displaySize: 78,
|
||||
},
|
||||
captain: {
|
||||
key: 'captain',
|
||||
label: 'CAPTAIN',
|
||||
hull: 180,
|
||||
speed: 102,
|
||||
turnRate: 44,
|
||||
reloadMs: 1450,
|
||||
projectileDamage: 8,
|
||||
preferredRange: 365,
|
||||
retreatRange: 225,
|
||||
broadsideRange: 470,
|
||||
broadsideError: 32,
|
||||
displaySize: 88,
|
||||
},
|
||||
};
|
||||
|
||||
export const PIRATE_CLASS_COSTS = {
|
||||
raider: 1,
|
||||
frigate: 2,
|
||||
captain: 3,
|
||||
};
|
||||
|
||||
const normalizeDegrees = (deg) => ((deg % 360) + 360) % 360;
|
||||
|
||||
const signedAngleDelta = (target, current) => {
|
||||
let delta = normalizeDegrees(target) - normalizeDegrees(current);
|
||||
if (delta > 180) delta -= 360;
|
||||
if (delta < -180) delta += 360;
|
||||
return delta;
|
||||
};
|
||||
|
||||
export const waveDifficultyBudget = (wave) => {
|
||||
const safeWave = Math.max(1, Math.floor(Number(wave) || 1));
|
||||
return safeWave + 1 + Math.floor((safeWave - 1) / 2);
|
||||
};
|
||||
|
||||
export const pirateRosterForWave = (wave, maxPirates = 6) => {
|
||||
const safeWave = Math.max(1, Math.floor(Number(wave) || 1));
|
||||
const safeMaxPirates = Math.max(1, Math.floor(Number(maxPirates) || 1));
|
||||
const count = Math.min(safeWave, safeMaxPirates);
|
||||
let remainingBudget = Math.max(count, waveDifficultyBudget(safeWave));
|
||||
let captainSlots = safeWave >= 4 ? Math.max(1, Math.floor(safeWave / 4)) : 0;
|
||||
const roster = [];
|
||||
|
||||
for (let index = 0; index < count; index += 1) {
|
||||
const remainingSlots = count - index - 1;
|
||||
const minimumRemainingCost = remainingSlots * PIRATE_CLASS_COSTS.raider;
|
||||
|
||||
if (
|
||||
captainSlots > 0
|
||||
&& remainingBudget - PIRATE_CLASS_COSTS.captain >= minimumRemainingCost
|
||||
) {
|
||||
roster.push(PIRATE_CLASSES.captain);
|
||||
remainingBudget -= PIRATE_CLASS_COSTS.captain;
|
||||
captainSlots -= 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (remainingBudget - PIRATE_CLASS_COSTS.frigate >= minimumRemainingCost) {
|
||||
roster.push(PIRATE_CLASSES.frigate);
|
||||
remainingBudget -= PIRATE_CLASS_COSTS.frigate;
|
||||
continue;
|
||||
}
|
||||
|
||||
roster.push(PIRATE_CLASSES.raider);
|
||||
remainingBudget -= PIRATE_CLASS_COSTS.raider;
|
||||
}
|
||||
|
||||
return roster;
|
||||
};
|
||||
|
||||
export const pirateDesiredHeading = ({
|
||||
bearing,
|
||||
currentHeading,
|
||||
distance,
|
||||
preferredRange,
|
||||
retreatRange,
|
||||
}) => {
|
||||
if (distance < retreatRange) {
|
||||
return normalizeDegrees(bearing + 180);
|
||||
}
|
||||
|
||||
if (distance < preferredRange + 40) {
|
||||
const optionA = normalizeDegrees(bearing + 90);
|
||||
const optionB = normalizeDegrees(bearing - 90);
|
||||
return Math.abs(signedAngleDelta(optionA, currentHeading))
|
||||
< Math.abs(signedAngleDelta(optionB, currentHeading))
|
||||
? optionA
|
||||
: optionB;
|
||||
}
|
||||
|
||||
return normalizeDegrees(bearing);
|
||||
};
|
||||
|
||||
export const pirateSpeedForRange = ({
|
||||
distance,
|
||||
preferredRange,
|
||||
retreatRange,
|
||||
baseSpeed,
|
||||
}) => {
|
||||
if (distance < retreatRange) return baseSpeed * 0.9;
|
||||
if (distance > preferredRange + 100) return baseSpeed;
|
||||
return baseSpeed * 0.82;
|
||||
};
|
||||
|
||||
export const separationVector = (ship, ships, radius = 125) => {
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
|
||||
for (const other of ships) {
|
||||
if (!other || other === ship || !other.active) continue;
|
||||
|
||||
const dx = other.x - ship.x;
|
||||
const dy = other.y - ship.y;
|
||||
const distance = Math.hypot(dx, dy);
|
||||
if (distance <= 0 || distance >= radius) continue;
|
||||
|
||||
const strength = (radius - distance) / radius;
|
||||
x -= (dx / distance) * strength;
|
||||
y -= (dy / distance) * strength;
|
||||
}
|
||||
|
||||
const magnitude = Math.hypot(x, y);
|
||||
if (magnitude > 1) {
|
||||
x /= magnitude;
|
||||
y /= magnitude;
|
||||
}
|
||||
|
||||
return { x, y };
|
||||
};
|
||||
125
src/progression/modules.js
Normal file
125
src/progression/modules.js
Normal file
@ -0,0 +1,125 @@
|
||||
export const SHIP_BASE_STATS = {
|
||||
maxHull: 100,
|
||||
maxSpeed: 180,
|
||||
reverseSpeed: -55,
|
||||
acceleration: 92,
|
||||
brake: 118,
|
||||
turnRate: 76,
|
||||
reloadMs: 850,
|
||||
cannonDamage: 16,
|
||||
windExposure: 1,
|
||||
cargoCapacity: 4,
|
||||
salvageCapacity: 2,
|
||||
radarRange: 900,
|
||||
};
|
||||
|
||||
export const MODULE_CATALOG = {
|
||||
'stock-engine': {
|
||||
id: 'stock-engine',
|
||||
name: 'Stock Twin Propellers',
|
||||
slot: 'engine',
|
||||
rarity: 'common',
|
||||
price: 0,
|
||||
modifiers: {},
|
||||
},
|
||||
'tuned-propellers': {
|
||||
id: 'tuned-propellers',
|
||||
name: 'Tuned Propellers',
|
||||
slot: 'engine',
|
||||
rarity: 'improved',
|
||||
price: 280,
|
||||
modifiers: { maxSpeed: 20, acceleration: 10 },
|
||||
},
|
||||
'corsair-overdrive': {
|
||||
id: 'corsair-overdrive',
|
||||
name: 'Corsair Overdrive',
|
||||
slot: 'engine',
|
||||
rarity: 'rare',
|
||||
price: 690,
|
||||
modifiers: { maxSpeed: 34, acceleration: 18, turnRate: -5 },
|
||||
},
|
||||
'standard-broadside': {
|
||||
id: 'standard-broadside',
|
||||
name: 'Standard Broadside',
|
||||
slot: 'weapon',
|
||||
rarity: 'common',
|
||||
price: 0,
|
||||
modifiers: {},
|
||||
},
|
||||
'long-nine-cannons': {
|
||||
id: 'long-nine-cannons',
|
||||
name: 'Long Nine Cannons',
|
||||
slot: 'weapon',
|
||||
rarity: 'improved',
|
||||
price: 360,
|
||||
modifiers: { cannonDamage: 4, reloadMs: 70 },
|
||||
},
|
||||
'quicklock-breeches': {
|
||||
id: 'quicklock-breeches',
|
||||
name: 'Quicklock Breeches',
|
||||
slot: 'weapon',
|
||||
rarity: 'rare',
|
||||
price: 620,
|
||||
modifiers: { cannonDamage: 2, reloadMs: -180 },
|
||||
},
|
||||
'timber-hull': {
|
||||
id: 'timber-hull',
|
||||
name: 'Timber Hull',
|
||||
slot: 'hull',
|
||||
rarity: 'common',
|
||||
price: 0,
|
||||
modifiers: {},
|
||||
},
|
||||
'reinforced-keel': {
|
||||
id: 'reinforced-keel',
|
||||
name: 'Reinforced Keel',
|
||||
slot: 'hull',
|
||||
rarity: 'improved',
|
||||
price: 420,
|
||||
modifiers: { maxHull: 28, maxSpeed: -7 },
|
||||
},
|
||||
'stormglass-nav': {
|
||||
id: 'stormglass-nav',
|
||||
name: 'Stormglass Navigator',
|
||||
slot: 'navigation',
|
||||
rarity: 'rare',
|
||||
price: 560,
|
||||
modifiers: { windExposure: -0.22, radarRange: 240 },
|
||||
},
|
||||
'brass-compass': {
|
||||
id: 'brass-compass',
|
||||
name: 'Brass Compass',
|
||||
slot: 'navigation',
|
||||
rarity: 'common',
|
||||
price: 0,
|
||||
modifiers: {},
|
||||
},
|
||||
'salvage-rig': {
|
||||
id: 'salvage-rig',
|
||||
name: 'Deck Salvage Rig',
|
||||
slot: 'utility',
|
||||
rarity: 'improved',
|
||||
price: 330,
|
||||
modifiers: { salvageCapacity: 2 },
|
||||
},
|
||||
'expanded-hold': {
|
||||
id: 'expanded-hold',
|
||||
name: 'Expanded Cargo Hold',
|
||||
slot: 'cargo',
|
||||
rarity: 'improved',
|
||||
price: 350,
|
||||
modifiers: { cargoCapacity: 4, maxSpeed: -4 },
|
||||
},
|
||||
};
|
||||
|
||||
export const STARTER_LOADOUT = {
|
||||
engine: 'stock-engine',
|
||||
weapon: 'standard-broadside',
|
||||
hull: 'timber-hull',
|
||||
navigation: 'brass-compass',
|
||||
utility: null,
|
||||
cargo: null,
|
||||
};
|
||||
|
||||
export const purchasableModules = () => Object.values(MODULE_CATALOG)
|
||||
.filter((module) => module.price > 0);
|
||||
153
src/progression/ship-loadout.js
Normal file
153
src/progression/ship-loadout.js
Normal file
@ -0,0 +1,153 @@
|
||||
import {
|
||||
MODULE_CATALOG,
|
||||
SHIP_BASE_STATS,
|
||||
STARTER_LOADOUT,
|
||||
purchasableModules,
|
||||
} from './modules.js';
|
||||
import { createSeededRandom } from '../world/seeded-random.js';
|
||||
|
||||
const clamp = (value, min, max) => Math.max(min, Math.min(max, value));
|
||||
|
||||
export const createStarterProgression = () => ({
|
||||
credits: 650,
|
||||
scrap: 0,
|
||||
loadout: { ...STARTER_LOADOUT },
|
||||
inventory: [],
|
||||
});
|
||||
|
||||
export const deriveShipStats = (loadout = STARTER_LOADOUT) => {
|
||||
const stats = { ...SHIP_BASE_STATS };
|
||||
|
||||
for (const moduleId of Object.values(loadout)) {
|
||||
if (!moduleId) continue;
|
||||
const module = MODULE_CATALOG[moduleId];
|
||||
if (!module) continue;
|
||||
for (const [key, value] of Object.entries(module.modifiers || {})) {
|
||||
stats[key] = (stats[key] ?? 0) + value;
|
||||
}
|
||||
}
|
||||
|
||||
stats.maxHull = Math.max(1, stats.maxHull);
|
||||
stats.maxSpeed = Math.max(40, stats.maxSpeed);
|
||||
stats.acceleration = Math.max(10, stats.acceleration);
|
||||
stats.turnRate = Math.max(15, stats.turnRate);
|
||||
stats.reloadMs = Math.max(260, stats.reloadMs);
|
||||
stats.cannonDamage = Math.max(1, stats.cannonDamage);
|
||||
stats.windExposure = clamp(stats.windExposure, 0.35, 1.5);
|
||||
stats.cargoCapacity = Math.max(1, Math.floor(stats.cargoCapacity));
|
||||
stats.salvageCapacity = Math.max(1, Math.floor(stats.salvageCapacity));
|
||||
return stats;
|
||||
};
|
||||
|
||||
export const installModule = (progression, moduleId) => {
|
||||
const module = MODULE_CATALOG[moduleId];
|
||||
if (!module) return { ...progression, installed: false, reason: 'unknown-module' };
|
||||
|
||||
const inventory = [...(progression.inventory || [])];
|
||||
const inventoryIndex = inventory.indexOf(moduleId);
|
||||
if (inventoryIndex >= 0) inventory.splice(inventoryIndex, 1);
|
||||
|
||||
const loadout = { ...(progression.loadout || STARTER_LOADOUT) };
|
||||
const replaced = loadout[module.slot] || null;
|
||||
loadout[module.slot] = moduleId;
|
||||
if (replaced && MODULE_CATALOG[replaced]?.price > 0) inventory.push(replaced);
|
||||
|
||||
return {
|
||||
...progression,
|
||||
loadout,
|
||||
inventory,
|
||||
installed: true,
|
||||
replaced,
|
||||
installedModule: moduleId,
|
||||
};
|
||||
};
|
||||
|
||||
export const buyAndInstallModule = (progression, moduleId) => {
|
||||
const module = MODULE_CATALOG[moduleId];
|
||||
if (!module || module.price <= 0) return { ...progression, purchased: false, reason: 'not-for-sale' };
|
||||
if ((progression.credits || 0) < module.price) {
|
||||
return { ...progression, purchased: false, reason: 'insufficient-credits' };
|
||||
}
|
||||
|
||||
const purchased = {
|
||||
...progression,
|
||||
credits: progression.credits - module.price,
|
||||
inventory: [...(progression.inventory || []), moduleId],
|
||||
};
|
||||
return { ...installModule(purchased, moduleId), purchased: true };
|
||||
};
|
||||
|
||||
export const marketForPort = (port) => {
|
||||
const stock = purchasableModules().filter((module) => {
|
||||
if (port?.type === 'industrial') return ['engine', 'hull', 'utility', 'cargo'].includes(module.slot);
|
||||
if (port?.type === 'military') return ['weapon', 'hull', 'navigation'].includes(module.slot);
|
||||
if (port?.type === 'fuel') return ['engine', 'navigation', 'cargo'].includes(module.slot);
|
||||
return true;
|
||||
});
|
||||
|
||||
return stock.sort((a, b) => a.price - b.price);
|
||||
};
|
||||
|
||||
export const salvageForPirate = ({
|
||||
seed = 'salvage',
|
||||
classKey = 'raider',
|
||||
wave = 1,
|
||||
sequence = 0,
|
||||
} = {}) => {
|
||||
const random = createSeededRandom(`${seed}:${classKey}:${wave}:${sequence}`);
|
||||
const chance = classKey === 'captain' ? 0.86 : classKey === 'frigate' ? 0.62 : 0.42;
|
||||
const modulePool = purchasableModules();
|
||||
const module = random() < chance
|
||||
? modulePool[Math.floor(random() * modulePool.length)]
|
||||
: null;
|
||||
const scrap = 1 + Math.floor(random() * (classKey === 'captain' ? 8 : classKey === 'frigate' ? 5 : 3));
|
||||
const credits = 20 + Math.floor(random() * (35 + wave * 8));
|
||||
|
||||
return {
|
||||
moduleId: module?.id || null,
|
||||
scrap,
|
||||
credits,
|
||||
condition: module ? clamp(0.28 + random() * 0.67, 0.28, 0.95) : null,
|
||||
};
|
||||
};
|
||||
|
||||
export const collectSalvage = (progression, salvage, stats = deriveShipStats(progression.loadout)) => {
|
||||
const inventory = [...(progression.inventory || [])];
|
||||
let moduleCollected = false;
|
||||
|
||||
if (salvage?.moduleId && inventory.length < stats.salvageCapacity) {
|
||||
inventory.push(salvage.moduleId);
|
||||
moduleCollected = true;
|
||||
}
|
||||
|
||||
return {
|
||||
...progression,
|
||||
inventory,
|
||||
scrap: (progression.scrap || 0) + Math.max(0, salvage?.scrap || 0),
|
||||
credits: (progression.credits || 0) + Math.max(0, salvage?.credits || 0),
|
||||
lastSalvage: { ...salvage, moduleCollected },
|
||||
};
|
||||
};
|
||||
|
||||
export const bestAffordableMarketUpgrade = (progression, port) => {
|
||||
const stats = deriveShipStats(progression.loadout);
|
||||
const candidates = marketForPort(port)
|
||||
.filter((module) => module.price <= (progression.credits || 0))
|
||||
.filter((module) => progression.loadout?.[module.slot] !== module.id)
|
||||
.map((module) => {
|
||||
const next = deriveShipStats({ ...progression.loadout, [module.slot]: module.id });
|
||||
const score = (next.maxHull - stats.maxHull) * 0.7
|
||||
+ (next.maxSpeed - stats.maxSpeed)
|
||||
+ (next.acceleration - stats.acceleration) * 0.45
|
||||
+ (next.turnRate - stats.turnRate) * 0.35
|
||||
+ (stats.reloadMs - next.reloadMs) * 0.06
|
||||
+ (next.cannonDamage - stats.cannonDamage) * 5
|
||||
+ (stats.windExposure - next.windExposure) * 30
|
||||
+ (next.salvageCapacity - stats.salvageCapacity) * 8
|
||||
+ (next.cargoCapacity - stats.cargoCapacity) * 4;
|
||||
return { module, score };
|
||||
})
|
||||
.sort((a, b) => b.score - a.score);
|
||||
|
||||
return candidates[0]?.module || null;
|
||||
};
|
||||
15
src/style.css
Normal file
15
src/style.css
Normal file
@ -0,0 +1,15 @@
|
||||
html, body, #app, #game {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
background: #0f1720;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
}
|
||||
|
||||
canvas {
|
||||
display: block;
|
||||
}
|
||||
22
src/world/seeded-random.js
Normal file
22
src/world/seeded-random.js
Normal file
@ -0,0 +1,22 @@
|
||||
export const hashSeed = (seed = '') => {
|
||||
let hash = 2166136261;
|
||||
for (const char of String(seed)) {
|
||||
hash ^= char.charCodeAt(0);
|
||||
hash = Math.imul(hash, 16777619);
|
||||
}
|
||||
return hash >>> 0;
|
||||
};
|
||||
|
||||
export const createSeededRandom = (seed = '') => {
|
||||
let state = hashSeed(seed) || 0x6d2b79f5;
|
||||
return () => {
|
||||
state += 0x6d2b79f5;
|
||||
let value = state;
|
||||
value = Math.imul(value ^ (value >>> 15), value | 1);
|
||||
value ^= value + Math.imul(value ^ (value >>> 7), value | 61);
|
||||
return ((value ^ (value >>> 14)) >>> 0) / 4294967296;
|
||||
};
|
||||
};
|
||||
|
||||
export const randomBetween = (random, min, max) => min + (max - min) * random();
|
||||
export const randomInt = (random, min, max) => Math.floor(randomBetween(random, min, max + 1));
|
||||
Loading…
x
Reference in New Issue
Block a user