PILOT: ACCESCO-ONE100
Sector 01
READY...
CORE: OMEGA100
RANK D
2
Combo
[W] Jump | [S] Parry | [Shift] Dash | [J] Strike | [K] Heavy | [L] Special (50 EN)
COMBAT ENGINE V5.0

ACCESCO.FIGHTER

FIGHT!
const WIDTH = 1000; const HEIGHT = 500; const GROUND_Y = 430; const GRAVITY = 0.85; const ACCEL = 1.3; const FRICTION = 0.84; const MAX_SPEED = 9.5; const DASH_POWER = 25; const JUMP_FORCE = -17; canvas.width = WIDTH; canvas.height = HEIGHT; let gameState = 'title'; let currentLevel = 1; let particles = []; let notifications = []; let screenShake = 0; let hitstopGlobal = 0; let superFreeze = 0; const LEVELS = [ { name: "Scout-X", color: "#94a3b8", hp: 100, pwr: 1, intel: 0.3 }, { name: "Iron Guard", color: "#fb923c", hp: 160, pwr: 1.2, intel: 0.5 }, { name: "Neon Wraith", color: "#22d3ee", hp: 140, pwr: 1.1, intel: 0.9 }, { name: "Core Crusher", color: "#f43f5e", hp: 280, pwr: 1.5, intel: 0.7 }, { name: "Executioner", color: "#ffffff", hp: 450, pwr: 2.0, intel: 0.98 } ]; const ATTACK_DATA = { 'jab': { startup: 4, active: 3, recovery: 6, dmg: 6, reach: 80, lunge: 3, kb: 4, stop: 3, en: 6 }, 'cross': { startup: 6, active: 4, recovery: 8, dmg: 10, reach: 90, lunge: 5, kb: 6, stop: 5, en: 10 }, 'uppercut': { startup: 10, active: 5, recovery: 15, dmg: 18, reach: 70, lunge: 2, kb: 20, stop: 12, en: 15 }, 'heavy': { startup: 15, active: 6, recovery: 22, dmg: 30, reach: 115, lunge: 12, kb: 25, stop: 18, en: 20 }, 'special': { startup: 15, active: 15, recovery: 20, dmg: 50, reach: 250, lunge: 30, kb: 35, stop: 25, en: -50 } }; class Fighter { constructor(config) { this.isPlayer = config.isPlayer; this.x = config.x; this.y = GROUND_Y; this.color = config.color; this.hp = config.hp; this.maxHp = config.hp; this.en = 0; this.pwr = config.pwr || 1; this.intel = config.intel || 0; this.velX = 0; this.velY = 0; this.facing = this.isPlayer ? 1 : -1; this.state = 'idle'; this.stateFrames = 0; this.currentAttack = null; this.comboStep = 0; this.comboWindow = 0; this.comboCount = 0; this.comboTimer = 0; this.parryFrames = 0; this.ghosts = []; this.hasHit = false; this.dashCooldown = 0; this.isGrounded = false; this.keys = {}; } draw() { ctx.save(); // Draw Ghosts this.ghosts.forEach((g, i) => { ctx.save(); ctx.globalAlpha = (i / this.ghosts.length) * 0.3; ctx.translate(g.x, g.y); this.renderBody(g.facing, g.state, g.frames, true); ctx.restore(); }); ctx.translate(this.x, this.y); this.renderBody(this.facing, this.state, this.stateFrames, false); ctx.restore(); } renderBody(dir, state, frames, isGhost) { const anim = frames * 0.15; const bounce = state === 'run' ? Math.sin(anim * 2) * 5 : Math.sin(anim) * 2; if (!isGhost) { ctx.shadowBlur = 15; ctx.shadowColor = this.color; } ctx.fillStyle = this.color; ctx.strokeStyle = '#fff'; ctx.lineWidth = 1; const bodyY = -60 + bounce; // Attack Slash Arcs if (this.currentAttack && frames >= ATTACK_DATA[this.currentAttack].startup && frames < ATTACK_DATA[this.currentAttack].startup + ATTACK_DATA[this.currentAttack].active) { this.drawSlashTrail(dir, bodyY, frames); } // Legs const legWalk = state === 'run' ? Math.sin(anim * 1.5) * 25 : 0; this.drawSolidLimb(dir * 10, -20, dir * 15, 0, legWalk); this.drawSolidLimb(dir * -10, -20, dir * -15, 0, -legWalk); // Torso ctx.beginPath(); ctx.moveTo(dir * -18, bodyY); ctx.lineTo(dir * 20, bodyY); ctx.lineTo(dir * 14, bodyY + 45); ctx.lineTo(dir * -14, bodyY + 45); ctx.closePath(); ctx.fill(); ctx.stroke(); // Arms let armAngle = Math.sin(anim) * 0.2; if (this.currentAttack) { const d = ATTACK_DATA[this.currentAttack]; if (frames < d.startup) armAngle = 1.2; else if (frames < d.startup + d.active) armAngle = -1.8; else armAngle = -0.6; } else if (state === 'hit') armAngle = 1.0; else if (state === 'block') armAngle = -1.5; this.drawSolidLimb(dir * 18, bodyY + 10, dir * 28, armAngle * dir, 0, true); // Head ctx.beginPath(); ctx.roundRect(dir * -14, bodyY - 25, 28, 24, [10, 10, 2, 2]); ctx.fill(); ctx.stroke(); ctx.fillStyle = '#000'; ctx.fillRect(dir * -14, bodyY - 18, 28, 6); ctx.fillStyle = this.isPlayer ? '#06b6d4' : '#f43f5e'; ctx.fillRect(dir * 4, bodyY - 16, 8 * dir, 2); } drawSlashTrail(dir, bodyY, frames) { const data = ATTACK_DATA[this.currentAttack]; const progress = (frames - data.startup) / data.active; ctx.save(); ctx.globalAlpha = 0.5 * (1 - progress); ctx.fillStyle = '#fff'; ctx.beginPath(); ctx.arc(0, bodyY + 20, data.reach, -0.6 * Math.PI, 0.6 * Math.PI, dir < 0); ctx.lineTo(0, bodyY + 20); ctx.fill(); ctx.restore(); } drawSolidLimb(sx, sy, ex, ay, rot = 0, isArm = false) { ctx.save(); ctx.translate(sx, sy); ctx.rotate(rot * Math.PI / 180); ctx.beginPath(); ctx.roundRect(-7, 0, 14, 25, 5); ctx.fill(); ctx.stroke(); ctx.translate(0, 25); ctx.rotate(ay); ctx.beginPath(); if (isArm) ctx.arc(0, 5, 8, 0, Math.PI*2); else ctx.roundRect(-9, 0, 20, 10, 3); ctx.fill(); ctx.restore(); } update(opponent) { if (this.hp <= 0) return; this.stateFrames++; if (this.dashCooldown > 0) this.dashCooldown--; if (this.comboWindow > 0) this.comboWindow--; if (this.parryFrames > 0) this.parryFrames--; if (this.comboTimer > 0) { this.comboTimer--; if (this.comboTimer === 0) this.comboCount = 0; } // Ghosting Logic if (this.state === 'dash' || this.currentAttack === 'special') { this.ghosts.push({ x: this.x, y: this.y, facing: this.facing, state: this.state, frames: this.stateFrames }); if (this.ghosts.length > 5) this.ghosts.shift(); } else { if (this.ghosts.length > 0) this.ghosts.shift(); } if (!this.isPlayer) this.updateAI(opponent); if (this.state === 'hit') { if (this.stateFrames > 25) this.setState('idle'); } else if (this.currentAttack) { this.updateAttack(opponent); } else { this.handleMovement(); this.handleInput(); } this.velY += GRAVITY; this.velX *= FRICTION; this.x += this.velX; this.y += this.velY; if (this.y >= GROUND_Y) { this.y = GROUND_Y; this.velY = 0; this.isGrounded = true; } this.x = Math.max(40, Math.min(WIDTH - 40, this.x)); if (this.isPlayer) this.updateComboUI(); } setState(newState) { if (this.state === newState) return; this.state = newState; this.stateFrames = 0; } handleMovement() { let move = 0; if (this.keys['KeyA'] || this.keys['ArrowLeft']) move = -1; else if (this.keys['KeyD'] || this.keys['ArrowRight']) move = 1; if (this.state === 'block') { this.velX *= 0.5; } else if (move !== 0) { this.velX += move * ACCEL; this.facing = move; this.setState('run'); } else if (this.isGrounded) { this.setState('idle'); } if (!this.isGrounded) this.setState(this.velY < 0 ? 'jump' : 'fall'); if (Math.abs(this.velX) > MAX_SPEED && this.state !== 'dash') this.velX = Math.sign(this.velX) * MAX_SPEED; } handleInput() { // Parry/Block logic if (this.keys['KeyS'] || this.keys['ArrowDown']) { if (this.state !== 'block') { this.parryFrames = 8; // Window for perfect parry } this.setState('block'); return; } if (this.keys['ShiftLeft'] && this.dashCooldown <= 0) { this.velX = this.facing * DASH_POWER; this.dashCooldown = 35; this.setState('dash'); createDust(this.x, this.y, this.color); } if ((this.keys['KeyW'] || this.keys['ArrowUp']) && this.isGrounded) { this.velY = JUMP_FORCE; this.isGrounded = false; } if (this.keys['KeyJ']) { if (this.comboWindow > 0) this.comboStep = (this.comboStep + 1) % 3; else this.comboStep = 0; const moves = ['jab', 'cross', 'uppercut']; this.startAttack(moves[this.comboStep]); } if (this.keys['KeyK']) this.startAttack('heavy'); if (this.keys['KeyL'] && this.en >= 50) { superFreeze = 20; // Brief freeze before Special this.startAttack('special'); this.en -= 50; this.updateUI(); } } startAttack(type) { this.currentAttack = type; this.stateFrames = 0; this.hasHit = false; this.setState('attack'); const data = ATTACK_DATA[type]; this.velX = this.facing * data.lunge; } updateAttack(opponent) { const data = ATTACK_DATA[this.currentAttack]; if (!this.hasHit && this.stateFrames >= data.startup && this.stateFrames < data.startup + data.active) { const reach = data.reach; const hitX = this.facing > 0 ? this.x : this.x - reach; // Detailed Box Collision const oppX = opponent.x - 30; const oppY = opponent.y - 100; if (hitX < oppX + 60 && hitX + reach > oppX && this.y - 100 < oppY + 100 && this.y > oppY) { // Parry Detection if (opponent.state === 'block' && opponent.parryFrames > 0) { spawnNotify(opponent.x, opponent.y - 120, "PERFECT PARRY!", "#fbbf24"); opponent.en = Math.min(100, opponent.en + 30); opponent.parryFrames = 0; opponent.updateUI(); screenShake = 10; this.takeDamage(0, -this.facing, 10, 20); // Bounce attacker back return; } let isCounter = opponent.currentAttack && opponent.stateFrames < ATTACK_DATA[opponent.currentAttack].startup; let damage = data.dmg * this.pwr; if (isCounter) { damage *= 1.5; spawnNotify(opponent.x, opponent.y - 120, "COUNTER!", "#fff"); } opponent.takeDamage(damage, this.facing, data.kb, data.stop); this.hasHit = true; this.en = Math.min(100, this.en + data.en); this.comboCount++; this.comboTimer = 140; this.comboWindow = 30; screenShake = data.kb / 2; this.updateUI(); } } if (this.stateFrames >= data.startup + data.active + data.recovery) { this.currentAttack = null; this.setState('idle'); } } takeDamage(dmg, dir, kb, stop) { if (this.state === 'block' && dmg > 0) { dmg *= 0.2; // 80% damage reduction kb *= 0.3; spawnNotify(this.x, this.y - 80, "GUARD", "#94a3b8"); } this.hp -= dmg; if (dmg > 0) { spawnNotify(this.x, this.y - 60, Math.ceil(dmg), "#fff", true); this.velX = dir * kb; this.velY = -kb/3; this.setState('hit'); this.currentAttack = null; this.comboCount = 0; } hitstopGlobal = stop; createImpact(this.x, this.y - 50, this.color); this.updateUI(); if (this.hp <= 0) handleMatchEnd(); } updateAI(player) { const dist = player.x - this.x; const absDist = Math.abs(dist); this.keys = {}; if (absDist > 150) { if (dist > 0) this.keys['KeyD'] = true; else this.keys['KeyA'] = true; } else if (absDist < 70) { if (dist > 0) this.keys['KeyA'] = true; else this.keys['KeyD'] = true; } // AI Reactive Blocking if (player.currentAttack && absDist < 100 && Math.random() < this.intel) { this.keys['KeyS'] = true; } if (absDist < 100 && Math.random() < 0.04 * this.intel) this.keys['KeyJ'] = true; if (absDist < 120 && Math.random() < 0.02 * this.intel) this.keys['KeyK'] = true; } updateUI() { const hpP = Math.max(0, (this.hp / this.maxHp) * 100); const enP = Math.max(0, this.en); if (this.isPlayer) { playerHPFill.style.width = hpP + '%'; playerENFill.style.width = enP + '%'; playerENFill.classList.toggle('ready', this.en >= 50); document.getElementById('player-hp-val').innerText = Math.ceil(this.hp); } else { enemyHPFill.style.width = hpP + '%'; document.getElementById('enemy-hp-val').innerText = Math.ceil(this.hp); } } updateComboUI() { if (this.comboCount > 1) { comboUI.classList.add('active'); comboCountText.innerText = this.comboCount; let rank = "D"; if (this.comboCount > 15) rank = "SSS"; else if (this.comboCount > 12) rank = "SS"; else if (this.comboCount > 9) rank = "S"; else if (this.comboCount > 6) rank = "A"; else if (this.comboCount > 4) rank = "B"; else if (this.comboCount > 2) rank = "C"; comboRankText.innerText = "RANK " + rank; const colors = { D: "#94a3b8", C: "#22c55e", B: "#3b82f6", A: "#a855f7", S: "#f59e0b", SS: "#ef4444", SSS: "#fff" }; comboRankText.style.color = colors[rank]; } else { comboUI.classList.remove('active'); } } } function spawnNotify(x, y, txt, color, isDmg = false) { notifications.push({ x, y, txt, life: 1, vy: isDmg ? -4 : -2, color, isDmg }); } function createDust(x, y, color) { for(let i=0; i<10; i++) { particles.push({ x, y, vx: (Math.random()-0.5)*8, vy: -Math.random()*5, life: 1, color, size: Math.random()*12 }); } } function createImpact(x, y, color) { for(let i=0; i<30; i++) { particles.push({ x, y, vx: (Math.random()-0.5)*35, vy: (Math.random()-0.5)*35, life: 1, color: '#fff', size: Math.random()*6 }); } } let player, enemy; function startLevel(lvl) { const config = LEVELS[lvl-1]; player = new Fighter({ isPlayer: true, x: 200, color: '#3b82f6', hp: 150 }); enemy = new Fighter({ isPlayer: false, x: 800, color: config.color, ...config }); document.getElementById('lvl-num').innerText = lvl.toString().padStart(2, '0'); document.getElementById('enemy-label').innerText = config.name.toUpperCase(); document.getElementById('enemy-name').innerText = "TARGET: " + config.name; player.updateUI(); enemy.updateUI(); fightStartText.style.opacity = 1; setTimeout(() => fightStartText.style.opacity = 0, 800); } function handleMatchEnd() { if (enemy.hp <= 0) { if (currentLevel < LEVELS.length) { currentLevel++; setTimeout(() => startLevel(currentLevel), 1500); } else { showMenu("CYBER CHAMPION"); } } else { showMenu("SYSTEM FAILURE"); } } function showMenu(txt) { gameState = 'title'; splash.style.display = 'flex'; document.querySelector('.title-text').innerHTML = txt; startBtn.innerText = "RESTART"; currentLevel = 1; } startBtn.onclick = () => { splash.style.display = 'none'; gameState = 'playing'; startLevel(currentLevel); }; window.addEventListener('keydown', e => { if(player) player.keys[e.code] = true; }); window.addEventListener('keyup', e => { if(player) player.keys[e.code] = false; }); function drawWorld() { ctx.strokeStyle = 'rgba(59, 130, 246, 0.05)'; for(let i=0; i 0) { superFreeze--; ctx.fillStyle = 'rgba(255, 255, 255, 0.1)'; ctx.fillRect(0, 0, WIDTH, HEIGHT); requestAnimationFrame(loop); return; } if (hitstopGlobal > 0) { hitstopGlobal--; requestAnimationFrame(loop); return; } ctx.clearRect(0, 0, WIDTH, HEIGHT); if (screenShake > 0) { ctx.save(); ctx.translate((Math.random()-0.5)*screenShake, (Math.random()-0.5)*screenShake); screenShake *= 0.85; } drawWorld(); if (gameState === 'playing') { player.update(enemy); enemy.update(player); } if (player) player.draw(); if (enemy) enemy.draw(); // Particles for(let i=particles.length-1; i>=0; i--) { const p = particles[i]; p.x += p.vx; p.y += p.vy; p.life -= 0.03; ctx.globalAlpha = p.life; ctx.fillStyle = p.color; ctx.fillRect(p.x, p.y, p.size, p.size); if (p.life <= 0) particles.splice(i,1); } // Notifications for(let i=notifications.length-1; i>=0; i--) { const n = notifications[i]; n.y += n.vy; n.life -= 0.02; ctx.globalAlpha = n.life; ctx.fillStyle = n.color || '#fff'; ctx.font = n.isDmg ? '900 24px Orbitron' : 'bold 18px Orbitron'; ctx.textAlign = 'center'; ctx.fillText(n.txt, n.x, n.y); if (n.life <= 0) notifications.splice(i,1); } ctx.globalAlpha = 1; if (screenShake > 0) ctx.restore(); requestAnimationFrame(loop); } loop();