<!DOCTYPE html>
<html>
<head>
<title>Naginata Trainer - Fast Death + Auto Respawn</title>
<style>
body {
background: #121212;
color: #eee;
sans-serif;
display: flex;
flex-direction: column;
align-items: center;
padding-top: 20px;
}
#canvas-container {
position: relative;
width: 800px;
height: 500px;
background: #000;
}
canvas {
background: #050505;
border: 2px solid #d40000;
display: block;
}
#overlay {
position: absolute; top: 0; left: 0;
width: 100%; height: 100%;
background: rgba(0,0,0,0.85);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: #ff4444;
font-weight: bold;
cursor: pointer;
z-index: 20;
font-size: 28px;
}
.ui-main {
15px;
background: #1a1a1a;
padding: 20px;
border-radius: 8px;
width: 800px;
display: grid;
grid-template-columns: 1.5fr 1fr;
gap: 30px;
border: 1px solid #333;
box-sizing: border-box;
}
.slider-group { 12px; }
.slider-group label {
display: block;
font-size: 13px;
color: #ff4444;
4px;
font-weight: bold;
}
button {
background: #d40000;
color: white;
border: none;
padding: 10px;
cursor: pointer;
border-radius: 4px;
font-weight: bold;
width: 100%;
}
input[type=range] { width: 100%; accent-color: #d40000; }
</style>
</head>
<body>
<h2 style="color: #d40000;">NSX NAGINATA SIM - Fast Death</h2>
<div id="canvas-container">
<canvas id="simCanvas" width="800" height="500"></canvas>
<div id="overlay">CLICK TO START</div>
</div>
<div class="ui-main">
<div class="controls">
<div class="slider-group">
<label>Distance: <span id="distVal">35</span>m</label>
<input type="range" id="distSlider" min="10" max="100" value="35">
</div>
<div class="slider-group">
<label>Model Height (Stretching): <span id="hScaleVal">1.0</span>x</label>
<input type="range" id="hScaleSlider" min="0.5" max="2.5" step="0.05" value="1.0">
</div>
<div class="slider-group">
<label>Vertical Recoil: <span id="recoilVal">0.50</span>°</label>
<input type="range" id="recoilSlider" min="0" max="1.5" step="0.01" value="0.50">
</div>
<div class="slider-group">
<label>Strafe Speed: <span id="speedVal">2.6</span> m/s</label>
<input type="range" id="speedSlider" min="0" max="6.5" step="0.1" value="2.6">
</div>
<button id="resetBtn">RESET SESSION</button>
</div>
<div class="stats-panel" style=" monospace; color: #00ff00;">
KILLS: <span id="killCount" style="font-size: 24px;">0</span><br>
TARGET HP: <span id="hpVal">1000</span><br>
<div style="font-size: 11px; color: #666; 20px;">
• Realistic strafe bob<br>
• Crosshair + bullets + markers on top<br>
• Fast death (0.65s) + instant respawn
</div>
</div>
</div>
<script>
const canvas = document.getElementById('simCanvas');
const ctx = canvas.getContext('2d');
const overlay = document.getElementById('overlay');
// ============== AUDIO ==============
let audioCtx = null;
function initAudio() {
if (!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)();
if (audioCtx.state === 'suspended') audioCtx.resume();
}
function playShot() {
if (!audioCtx) return;
const n = audioCtx.createBufferSource(), g = audioCtx.createGain(), f = audioCtx.createBiquadFilter();
const b = audioCtx.createBuffer(1, audioCtx.sampleRate * 0.06, audioCtx.sampleRate), d = b.getChannelData(0);
for (let i = 0; i < b.length; i++) d[i] = Math.random() * 2 - 1;
n.buffer = b; f.type = "lowpass"; f.frequency.value = 850;
g.gain.setValueAtTime(0.12, audioCtx.currentTime);
g.gain.exponentialRampToValueAtTime(0.008, audioCtx.currentTime + 0.06);
n.connect(f); f.connect(g); g.connect(audioCtx.destination); n.start();
}
function playHit(isHead) {
if (!audioCtx) return;
const o = audioCtx.createOscillator(), g = audioCtx.createGain();
o.frequency.setValueAtTime(isHead ? 1150 : 620, audioCtx.currentTime);
g.gain.setValueAtTime(0.06, audioCtx.currentTime);
g.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.045);
o.connect(g); g.connect(audioCtx.destination); o.start(); o.stop(audioCtx.currentTime + 0.045);
}
// ============== IMAGE ==============
const playerImg = new Image();
let imgReady = false;
playerImg.onload = () => { imgReady = true; };
playerImg.src = 'https://www-cdn.planetside2.com/images/players/player/profile/char-default-tr-13-female.png';
// ============== GAME VARIABLES ==============
const RPM = 659, FIRE_INT = 1000 / (RPM / 60);
const BLOOM = 0.05, MAX_COF = 0.6, FSRM = 1.8, H_RECOIL = 0.16;
const VELO = 490, RECOV = 13, SCALE_X = 80, SCALE_Y = 26.6;
let isFiring = false, lastFire = 0, currentCof = 0.1, shots = 0, kills = 0;
let aimX = 400, aimY = 250, recV = 0, recH = 0;
let bullets = [], markers = [];
let target = { x_deg: 0, hp: 1000, alive: true, dir: 1, lastFlip: 0 };
let animTime = 0;
let deathTimer = 0; // countdown while dying
let spawnTimer = 0;
overlay.addEventListener('click', () => {
initAudio();
aimX = 400; aimY = 250; recV = 0; recH = 0;
canvas.requestPointerLock();
});
document.addEventListener('pointerlockchange', () => {
overlay.style.display = (document.pointerLockElement === canvas) ? 'none' : 'flex';
});
canvas.addEventListener('mousemove', (e) => {
if (document.pointerLockElement === canvas) {
aimX += e.movementX; aimY += e.movementY;
}
});
canvas.addEventListener('mousedown', () => { if (document.pointerLockElement === canvas) isFiring = true; });
window.addEventListener('mouseup', () => { isFiring = false; shots = 0; });
document.getElementById('resetBtn').addEventListener('click', () => {
kills = 0; document.getElementById('killCount').innerText = "0";
spawnTarget();
});
function spawnTarget() {
target.x_deg = (Math.random() - 0.5) * 4;
target.hp = 1000;
target.alive = true;
currentCof = 0.1; recV = 0; recH = 0; shots = 0;
deathTimer = 0;
spawnTimer = 0.7; // short spawn pop-in
}
function update() {
const now = performance.now();
const dt = 1/60;
animTime += dt;
const dist = parseFloat(document.getElementById('distSlider').value);
const hStretch = parseFloat(document.getElementById('hScaleSlider').value);
const spd = parseFloat(document.getElementById('speedSlider').value);
const rvb = parseFloat(document.getElementById('recoilSlider').value);
document.getElementById('distVal').innerText = dist;
document.getElementById('hScaleVal').innerText = hStretch.toFixed(2);
document.getElementById('speedVal').innerText = spd.toFixed(1);
document.getElementById('recoilVal').innerText = rvb.toFixed(2);
// Strafe
if (target.alive && deathTimer <= 0) {
let angSpd = (Math.atan(spd / dist) * 180 / Math.PI);
target.x_deg += target.dir * angSpd * dt;
if (target.x_deg > 4.5) target.dir = -1;
if (target.x_deg < -4.5) target.dir = 1;
}
// Firing (only when alive and not dying)
if (!isFiring) {
recV = Math.max(0, recV - RECOV * dt);
recH *= 0.85; currentCof = 0.1;
} else if (target.alive && deathTimer <= 0 && document.pointerLockElement === canvas) {
if (now - lastFire > FIRE_INT) {
playShot();
let k = (shots === 0) ? (rvb * FSRM) : rvb;
recV += k; recH += (Math.random() - 0.5) * H_RECOIL;
currentCof = Math.min(currentCof + BLOOM, MAX_COF);
let vX = aimX + (recH * SCALE_X), vY = aimY - (recV * SCALE_Y);
let a = Math.random() * 6.28, d = Math.random() * (currentCof/2 * SCALE_X);
bullets.push({ s: now, ox: vX, oy: vY + 20, dx: vX + Math.cos(a) * d, dy: vY + Math.sin(a) * d });
shots++; lastFire = now;
}
}
// Bullets & hits
let tTime = (dist / VELO) * 1000;
const sX = 35 / dist;
const sY = (35 / dist) * hStretch;
for (let i = bullets.length - 1; i >= 0; i--) {
let b = bullets[i], p = (now - b.s) / tTime;
if (p >= 1) {
const tx = 400 + (target.x_deg * SCALE_X), ty = 250;
let hit = null;
if (b.dx > tx-(15*sX) && b.dx < tx+(15*sX) && b.dy > ty-(110*sY) && b.dy < ty-(80*sY)) hit = 'H';
else if (b.dx > tx-(30*sX) && b.dx < tx+(30*sX) && b.dy > ty-(80*sY) && b.dy < ty+(100*sY)) hit = 'B';
if (hit && target.alive && deathTimer <= 0) {
playHit(hit === 'H');
let dmg = Math.max(125, Math.min(150, 150 - (25/55)*(dist-10)));
target.hp -= (hit === 'H' ? dmg * 2 : dmg);
markers.push({ x: b.dx, y: b.dy, t: hit, l: 0.15 });
if (target.hp <= 0 && deathTimer <= 0) {
target.alive = false;
kills++;
document.getElementById('killCount').innerText = kills;
deathTimer = 0.65; // FAST death (0.65 seconds)
}
}
bullets.splice(i, 1);
} else {
b.cx = b.ox + (b.dx - b.ox) * p;
b.cy = b.oy + (b.dy - b.oy) * p;
b.sc = 1.0 - (p * 0.7);
}
}
markers.forEach((m, i) => { m.l -= dt; if(m.l <= 0) markers.splice(i,1); });
// Timers
if (deathTimer > 0) {
deathTimer -= dt;
if (deathTimer <= 0) {
spawnTarget(); // instant new enemy after death animation
}
}
if (spawnTimer > 0) spawnTimer -= dt;
}
function draw() {
update();
ctx.clearRect(0, 0, 800, 500);
const dist = parseFloat(document.getElementById('distSlider').value);
const hStretch = parseFloat(document.getElementById('hScaleSlider').value);
const sX = 35 / dist;
const sY = (35 / dist) * hStretch;
let tx = 400 + (target.x_deg * SCALE_X);
let ty = 250;
// Draw target with animations
if (target.alive || deathTimer > 0) {
ctx.save();
let drawX = tx - 100 * sX;
let drawY = ty - 125 * sY;
let w = 200 * sX;
let h = 250 * sY;
// Spawn pop-in
if (spawnTimer > 0) {
const p = 1 - spawnTimer / 0.7;
const scale = 0.3 + p * 0.7;
const jump = Math.sin(p * Math.PI) * 35;
ctx.translate(drawX + w/2, drawY + h/2 + jump);
ctx.scale(scale, scale);
ctx.translate(-w/2, -h/2);
}
// Fast death flop
else if (deathTimer > 0) {
const p = (0.65 - deathTimer) / 0.65;
const fall = p * 110;
const rot = p * 42;
ctx.translate(drawX + w/2, drawY + h/2 + fall);
ctx.rotate(rot * Math.PI / 180);
ctx.scale(1 - p * 0.3, 1 - p * 0.15);
ctx.globalAlpha = 1 - p * 0.65;
ctx.translate(-w/2, -h/2);
}
// Normal realistic bob
else {
const bobY = Math.sin(animTime * 9) * 6;
const swayX = Math.sin(animTime * 4.5) * 7;
const lean = Math.sin(animTime * 4.5) * 3;
ctx.translate(drawX + w/2 + swayX, drawY + h/2 + bobY);
ctx.rotate(lean * Math.PI / 180);
ctx.translate(-w/2, -h/2);
}
if (imgReady) {
ctx.drawImage(playerImg, 0, 0, w, h);
} else {
ctx.fillStyle = '#400';
ctx.fillRect(0, 0, w, h);
}
ctx.restore();
}
// Bullets
bullets.forEach(b => {
ctx.fillStyle = `rgba(255, 255, 180, ${b.sc})`;
ctx.beginPath(); ctx.arc(b.cx, b.cy, 3 * b.sc, 0, 7); ctx.fill();
});
// Hit markers
markers.forEach(m => {
ctx.strokeStyle = m.t === 'H' ? "#ff0" : "#f00";
ctx.lineWidth = m.t === 'H' ? 3 : 1;
let sz = m.t === 'H' ? 10 : 6;
ctx.beginPath(); ctx.moveTo(m.x-sz, m.y-sz); ctx.lineTo(m.x+sz, m.y+sz);
ctx.moveTo(m.x+sz, m.y-sz); ctx.lineTo(m.x-sz, m.y+sz); ctx.stroke();
});
// Crosshair (always on top)
const vx = aimX + (recH * SCALE_X), vy = aimY - (recV * SCALE_Y);
ctx.strokeStyle = 'rgba(0, 212, 255, 0.5)';
ctx.beginPath(); ctx.arc(vx, vy, (currentCof/2 * SCALE_X), 0, 7); ctx.stroke();
ctx.strokeStyle = '#0f0';
ctx.beginPath(); ctx.moveTo(vx-8, vy); ctx.lineTo(vx+8, vy);
ctx.moveTo(vx, vy-8); ctx.lineTo(vx, vy+8); ctx.stroke();
// HP bar (only when alive or dying)
if (target.alive || deathTimer > 0) {
ctx.fillStyle = '#555';
ctx.fillRect(tx-40, ty - 115*sY - 22, 80, 5);
ctx.fillStyle = '#f00';
ctx.fillRect(tx-40, ty - 115*sY - 22, (target.hp/1000)*80, 5);
}
document.getElementById('hpVal').innerText = Math.max(0, Math.round(target.hp));
requestAnimationFrame(draw);
}
spawnTarget();
draw();
</script>
</body>
</html>