trollium_V5promax
// ==UserScript==
// @name bobweb_8
// @namespace http://tampermonkey.net/
// @version 0.0.0.8
// @description trollium_V5promax
// @author bobweb_8
// @match *://*/*
// @grant none
// ==/UserScript==
(function () {
'use strict';
let uiVisible = true;
const SPEED = 3, JUMP_FORCE = -10, GRAVITY = 0.5, GROUND = 300;
const SWORD_RANGE = 120, DAGGER_RANGE = 60;
const BEAM_DMG_INTERVAL = 30, CHARGE_NEEDED = 60, LUNGE_CHARGE_NEEDED = 180;
const SHIELD_COOLDOWN_MAX = 900;
const SWORD_CHARGE_MAX = 60;
const FPS = 60;
const st = {
p1: { x: 100, y: GROUND, hp: 100, facing: 1 },
p2: { x: 620, y: GROUND, hp: 100, facing: -1 },
keys: {},
// P1
swordSwing: 0, swordCooldown: 0,
swordCharge: 0, swordCharging: false, swordCharged: false,
jumpHit: false, jumpHitAnim: 0, jumpHitCooldown: 0,
groundSlamCooldown: 0,
groundSlam: false, groundSlamAnim: 0,
blocking: false, blockFlash: 0,
shieldBroken: false, shieldCooldown: 0,
shieldPushAnim: 0,
bleeding: 0, bleedTimer: 0,
p1Stunned: 0, // stun frames
// P2
daggerSwing: 0, daggerCooldown: 0,
grappleCooldown: 0,
grappleActive: false, grappleAnim: 0, // hook flying out
grappleHooked: false, grapplePullAnim: 0, // hook landed, pulling p1
grappleParryWindow: 0, // frames p2 can parry
grappleX: 0, grappleY: 0, // hook tip position
grappleStunDealt: false,
// Combo state
comboActive: false, comboStep: 0, comboTimer: 0,
comboCooldown: 0,
lungeCharge: 0, lunging: false, lungeAnim: 0,
lungeStartX: 0, lungeEndX: 0,
lungeFinisher: false, lungeFinisherAnim: 0,
headX: 0, headY: 0, headVX: 0, headVY: 0,
beamCharge: 0, beamDmgTimer: 0, beamActive: false,
bullets: [],
// Finisher
jumpHitFinisher: false, jumpHitFinisherAnim: 0,
p2TopHalfY: 0, p2TopVY: 0, p2TopVX: 0, p2BotY: 0,
// Clash
clashAnim: 0,
gameOver: false, winner: '',
particles: [],
slamExplosionAnim: 0, slamExplosionX: 0
};
let vel = { p1: { vx: 0, vy: 0 }, p2: { vx: 0, vy: 0 } };
const container = document.createElement('div');
container.id = 'fighter-container';
container.style.cssText = `position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:999999;font-family:monospace;user-select:none;`;
const canvas = document.createElement('canvas');
canvas.width = 750; canvas.height = 420;
canvas.style.cssText = `display:block;border:3px solid #222;border-radius:8px;background:#1a1a2e;`;
container.appendChild(canvas);
document.body.appendChild(container);
const ctx = canvas.getContext('2d');
document.addEventListener('keydown', e => {
const was = st.keys[e.code];
st.keys[e.code] = true;
if (e.key === 'RightShift') { uiVisible = !uiVisible; container.style.display = uiVisible ? 'block' : 'none'; }
if (!st.gameOver) {
// P1: Space
if (e.code === 'Space' && !was) {
e.preventDefault();
if (st.p1Stunned > 0) { /* stunned, can't act */ }
else if (st.blocking && !st.shieldBroken) { doShieldPush(); }
else if (st.p1.y < GROUND && st.jumpHitCooldown <= 0) { doJumpHit(); }
else if (st.swordCooldown <= 0) { st.swordCharging = true; st.swordCharge = 0; }
}
// P2: P = poison stab
if (e.code === 'KeyP' && !was && st.daggerCooldown <= 0 && !st.p2Stunned) {
doDaggerStab();
}
// P2: Enter = bullet tap
if (e.code === 'Enter' && !was) { shootBullet(); }
// P2: L = dagger tap OR combo continuation
if (e.code === 'KeyL' && !was && !st.p2Stunned) {
if (st.comboActive) {
advanceCombo();
} else if (st.daggerCooldown <= 0 && st.lungeCharge === 0) {
st.daggerSwing = 10; st.daggerCooldown = 14;
doDaggerAttack();
}
}
// P2: M = grapple hook
if (e.code === 'KeyM' && !was && st.grappleCooldown <= 0 && !st.grappleActive && !st.grappleHooked) {
fireGrapple();
}
}
if (e.key === 'r' || e.key === 'R') resetGame();
['ArrowUp','ArrowDown','ArrowLeft','ArrowRight',' '].includes(e.key) && e.preventDefault();
});
document.addEventListener('keyup', e => {
st.keys[e.code] = false;
if (e.code === 'Enter') { st.beamActive = false; st.beamDmgTimer = 0; }
if (e.code === 'KeyL') {
if (st.lungeCharge >= LUNGE_CHARGE_NEEDED && !st.gameOver) doLunge();
st.lungeCharge = 0;
}
if (e.code === 'KeyF') st.blocking = false;
if (e.code === 'Space' && st.swordCharging) releaseSwordSwing();
});
// ---- ACTIONS ----
function releaseSwordSwing() {
st.swordCharging = false;
if (st.swordCooldown > 0) return;
const charged = st.swordCharge >= SWORD_CHARGE_MAX;
st.swordCharged = charged;
st.swordSwing = charged ? 24 : 18;
st.swordCooldown = charged ? 45 : 30;
st.swordCharge = 0;
const dx = (st.p2.x - st.p1.x) * st.p1.facing;
if (dx > 0 && dx < SWORD_RANGE) {
// Parry check: grapple hook p1 at start of swing
if (st.grappleParryWindow > 0) {
doParry();
return;
}
checkClash(charged ? 9 : 1);
}
}
function checkClash(dmg) {
// Clash: if p2 is doing dagger swing at the same time and they're close
const dist = Math.abs(st.p1.x - st.p2.x);
if (st.daggerSwing > 5 && dist < DAGGER_RANGE + 20) {
doClash();
} else {
dealDamage('p2', dmg, false);
if (st.swordCharged) spawnSlashParticles(st.p2.x, st.p2.y - 40, '#ffffff');
}
}
function doClash() {
st.clashAnim = 30;
// Both knocked back
vel.p1.vx = -st.p1.facing * 8; vel.p1.vy = -4;
vel.p2.vx = -st.p2.facing * 8; vel.p2.vy = -4;
dealDamage('p1', 7, false, false, true); // ignoreKnockback flag
dealDamage('p2', 7, false, false, true);
for (let i = 0; i < 16; i++) {
const a = (Math.PI*2/16)*i;
state_spawnParticle(
(st.p1.x+st.p2.x)/2, (st.p1.y+st.p2.y)/2 - 35,
Math.cos(a)*5, Math.sin(a)*5,
i%2===0?'#ffffff':'#ffdd44', 4+Math.random()*3, 25
);
}
}
function doJumpHit() {
st.jumpHit = true; st.jumpHitAnim = 16; st.jumpHitCooldown = 90;
const dx = (st.p2.x - st.p1.x) * st.p1.facing;
if (dx > 0 && dx < SWORD_RANGE + 20) dealDamage('p2', 5, false, true);
spawnSlashParticles(st.p1.x + st.p1.facing * 50, st.p1.y - 40, '#ffdd44');
}
function doShieldPush() {
st.shieldPushAnim = 20;
const dx = (st.p2.x - st.p1.x) * st.p1.facing;
if (dx > 0 && dx < 80) {
dealDamage('p2', 4, false);
vel.p2.vx = st.p1.facing * 12; vel.p2.vy = -5;
}
spawnSlashParticles(st.p1.x + st.p1.facing * 30, st.p1.y - 35, '#88aaff');
}
function doDaggerAttack() {
// Clash check
const dist = Math.abs(st.p1.x - st.p2.x);
if (st.swordSwing > 5 && dist < SWORD_RANGE) {
doClash(); return;
}
const dx = (st.p1.x - st.p2.x) * st.p2.facing;
if (dx > 0 && dx < DAGGER_RANGE) dealDamage('p1', 11, false);
}
function doDaggerStab() {
st.daggerSwing = 12; st.daggerCooldown = 20;
const dx = (st.p1.x - st.p2.x) * st.p2.facing;
if (dx > 0 && dx < DAGGER_RANGE + 10) {
dealDamage('p1', 11, false);
st.bleeding = 5; st.bleedTimer = 60;
spawnHitParticles(st.p1.x, st.p1.y - 40, '#cc0000');
}
}
function shootBullet() {
st.bullets.push({ x: st.p2.x+st.p2.facing*20, y: st.p2.y-50, vx: st.p2.facing*8, dir: st.p2.facing, reflected: false, life: 120 });
}
function fireGrapple() {
st.grappleActive = true;
st.grappleAnim = 0;
st.grappleX = st.p2.x + st.p2.facing * 20;
st.grappleY = st.p2.y - 45;
st.grappleParryWindow = 12; // short window for parry
st.grappleStunDealt = false;
}
function doGrappleHit() {
// Hook lands on p1 — pull them in
st.grappleActive = false;
st.grappleHooked = true;
st.grapplePullAnim = 25;
spawnHitParticles(st.p1.x, st.p1.y - 40, '#44ffaa');
// Stun p1 for 1 second (60 frames)
st.p1Stunned = 60;
// Unlock combo if last action was stab/grapple
st.comboActive = true;
st.comboStep = 0;
st.comboTimer = 180; // 3 seconds to do combo
}
function advanceCombo() {
if (!st.comboActive || st.comboTimer <= 0) return;
const dist = Math.abs(st.p1.x - st.p2.x);
if (dist > DAGGER_RANGE + 20) return; // must be close
st.comboStep++;
st.daggerSwing = 8;
if (st.comboStep < 3) {
// Stab 1 or 2
dealDamage('p1', 8, false); // ~8*3 + kick = 34
spawnSlashParticles(st.p1.x, st.p1.y - 40, '#ff6644');
} else {
// Final kick — combo complete
dealDamage('p1', 10, false);
vel.p1.vx = st.p2.facing * 12; vel.p1.vy = -6;
spawnSlashParticles(st.p1.x, st.p1.y - 30, '#ffaa00');
for (let i=0;i<10;i++) state_spawnParticle(st.p1.x,st.p1.y-30,(Math.random()-0.5)*6,-Math.random()*5,'#ffaa00',4,20);
// Cooldowns on dagger and grapple
st.daggerCooldown = Math.max(st.daggerCooldown, 15 * FPS / 10); // ~9s
st.grappleCooldown = Math.max(st.grappleCooldown, 15 * FPS / 10);
st.comboActive = false; st.comboStep = 0; st.comboTimer = 0;
st.comboCooldown = 180;
}
}
function doParry() {
// P2 grappled p1 at start of P1 swing = parry! P1 stunned 2s
st.p1Stunned = 120;
st.grappleActive = false;
st.grappleCooldown = 5 * FPS;
vel.p1.vx = -st.p1.facing * 6; vel.p1.vy = -4;
spawnShieldBreakParticles(st.p1.x, st.p1.y - 40);
for (let i = 0; i < 8; i++) {
const a = (Math.PI*2/8)*i;
state_spawnParticle(st.p1.x,st.p1.y-40,Math.cos(a)*6,Math.sin(a)*6,'#ffaa00',5,30);
}
}
function doLunge() {
st.lungeStartX = st.p2.x;
const landX = st.p1.x - st.p2.facing * 30;
st.lungeEndX = Math.max(20, Math.min(canvas.width-20, landX));
st.lunging = true; st.lungeAnim = 20;
}
function doGroundSlam(x) {
st.groundSlam = true; st.groundSlamAnim = 30;
st.slamExplosionX = x; st.slamExplosionAnim = 30;
st.groundSlamCooldown = 10 * FPS;
const dist = Math.abs(st.p2.x - x);
if (dist < 160) dealDamage('p2', 7, false);
for (let i=0;i<20;i++) {
const a = Math.random()*Math.PI;
state_spawnParticle(x,GROUND,Math.cos(a)*5,-Math.sin(a)*6,'#87CEEB',4+Math.random()*4,35);
state_spawnParticle(x,GROUND,Math.cos(a)*3,-Math.sin(a)*4,'#ffffff',3+Math.random()*3,25);
}
}
function dealDamage(target, amount, isLunge, isJumpHit=false, isClash=false) {
if (target==='p1' && st.blocking && !st.shieldBroken && !isClash) {
if (isLunge) {
st.shieldBroken=true; st.shieldCooldown=SHIELD_COOLDOWN_MAX; st.blocking=false;
spawnShieldBreakParticles(st.p1.x,st.p1.y-35);
st.p1.hp=Math.max(0,st.p1.hp-17);
spawnHitParticles(st.p1.x,st.p1.y-40,'#ff4444');
if(st.p1.hp<=0) triggerLungeFinisher();
} else { st.blockFlash=20; spawnBlockParticles(st.p1.x,st.p1.y-40); }
return;
}
st[target].hp = Math.max(0, st[target].hp - amount);
spawnHitParticles(st[target].x, st[target].y-40, target==='p1'?'#3498db':'#e74c3c');
if (st[target].hp <= 0) {
if (isLunge && target==='p1') { triggerLungeFinisher(); return; }
if (target==='p2') { triggerJumpHitFinisher(); return; }
st.gameOver=true; st.winner='Player 2';
}
}
function triggerJumpHitFinisher() {
st.jumpHitFinisher=true; st.jumpHitFinisherAnim=100;
st.p2TopHalfY=st.p2.y-30; st.p2BotY=st.p2.y;
st.p2TopVY=-9-Math.random()*3; st.p2TopVX=st.p1.facing*(2+Math.random()*2);
for(let i=0;i<28;i++){const a=Math.random()*Math.PI*2,spd=3+Math.random()*6;state_spawnParticle(st.p2.x,st.p2.y-30,Math.cos(a)*spd,Math.sin(a)*spd-2,Math.random()<0.6?'#cc0000':'#ff4444',3+Math.random()*5,45);}
for(let i=0;i<12;i++){const a=(Math.PI*2/12)*i;state_spawnParticle(st.p2.x,st.p2.y-30,Math.cos(a)*8,Math.sin(a)*8,'#ffffff',4,18);}
}
function triggerLungeFinisher() {
st.lungeFinisher=true; st.lungeFinisherAnim=90;
st.headX=st.p1.x; st.headY=st.p1.y-60;
st.headVX=st.p2.facing*(3+Math.random()*2); st.headVY=-8-Math.random()*3;
for(let i=0;i<30;i++){const a=Math.random()*Math.PI*2,spd=2+Math.random()*5;state_spawnParticle(st.p1.x,st.p1.y-60,Math.cos(a)*spd,Math.sin(a)*spd-3,Math.random()<0.7?'#cc0000':'#ff4444',3+Math.random()*5,40);}
st.lungeEndX=Math.max(20,Math.min(canvas.width-20,st.p1.x+st.p2.facing*120)); st.lungeAnim=15;
}
function state_spawnParticle(x,y,vx,vy,color,size,life){st.particles.push({x,y,vx,vy,color,size,life,maxLife:life});}
function spawnHitParticles(x,y,color){for(let i=0;i<12;i++){const a=(Math.PI*2/12)*i;state_spawnParticle(x,y,Math.cos(a)*(2+Math.random()*3),Math.sin(a)*(2+Math.random()*3),color,3+Math.random()*3,25);}}
function spawnBlockParticles(x,y){for(let i=0;i<16;i++){const a=(Math.PI*2/16)*i;state_spawnParticle(x,y,Math.cos(a)*(1+Math.random()*4),Math.sin(a)*(1+Math.random()*4),'#ffdd44',2+Math.random()*3,20);}}
function spawnShieldBreakParticles(x,y){for(let i=0;i<28;i++){const a=(Math.PI*2/28)*i,spd=2+Math.random()*6;state_spawnParticle(x,y,Math.cos(a)*spd,Math.sin(a)*spd-1,i%3===0?'#88aaff':i%3===1?'#ffffff':'#ffdd44',3+Math.random()*5,35);}}
function spawnSlashParticles(x,y,color){for(let i=0;i<8;i++){const a=(Math.PI*2/8)*i;state_spawnParticle(x,y,Math.cos(a)*(3+Math.random()*3),Math.sin(a)*(3+Math.random()*3),color,3+Math.random()*4,20);}}
function spawnLungeTrail(x,y){for(let i=0;i<6;i++){state_spawnParticle(x+(Math.random()-0.5)*10,y-20-Math.random()*40,(Math.random()-0.5)*2,-Math.random()*2,st.lungeFinisher?(Math.random()<0.5?'#cc0000':'#ff6600'):(i%2===0?'#aa44ff':'#ffffff'),3+Math.random()*4,18);}}
function spawnChargeParticle(){const p2=st.p2,pct=st.beamCharge/CHARGE_NEEDED,a=Math.random()*Math.PI*2,r=30+Math.random()*40;const colors=['#ff2200','#ff6600','#ffaa00','#ff00aa','#ffffff'];state_spawnParticle(p2.x+p2.facing*20+Math.cos(a)*r,p2.y-55+Math.sin(a)*r,-Math.cos(a)*(1+pct*3),-Math.sin(a)*(1+pct*3),colors[Math.floor(Math.random()*colors.length)],2+Math.random()*4*pct,20);}
function resetGame() {
st.p1={x:100,y:GROUND,hp:100,facing:1}; st.p2={x:620,y:GROUND,hp:100,facing:-1};
vel={p1:{vx:0,vy:0},p2:{vx:0,vy:0}};
Object.assign(st,{
swordSwing:0,swordCooldown:0,swordCharge:0,swordCharging:false,swordCharged:false,
jumpHit:false,jumpHitAnim:0,jumpHitCooldown:0,groundSlamCooldown:0,
groundSlam:false,groundSlamAnim:0,
jumpHitFinisher:false,jumpHitFinisherAnim:0,p2TopHalfY:0,p2TopVY:0,p2TopVX:0,p2BotY:0,
blocking:false,blockFlash:0,shieldBroken:false,shieldCooldown:0,shieldPushAnim:0,
bleeding:0,bleedTimer:0,p1Stunned:0,
daggerSwing:0,daggerCooldown:0,
grappleCooldown:0,grappleActive:false,grappleAnim:0,grappleHooked:false,
grapplePullAnim:0,grappleParryWindow:0,grappleX:0,grappleY:0,grappleStunDealt:false,
comboActive:false,comboStep:0,comboTimer:0,comboCooldown:0,
lungeCharge:0,lunging:false,lungeAnim:0,lungeStartX:0,lungeEndX:0,
lungeFinisher:false,lungeFinisherAnim:0,headX:0,headY:0,headVX:0,headVY:0,
beamCharge:0,beamDmgTimer:0,beamActive:false,bullets:[],
clashAnim:0,gameOver:false,winner:'',particles:[],
slamExplosionAnim:0,slamExplosionX:0
});
}
// ---- UPDATE ----
function update() {
if (st.gameOver && !st.lungeFinisher && !st.jumpHitFinisher) return;
// Stun
if (st.p1Stunned > 0) st.p1Stunned--;
if (st.clashAnim > 0) st.clashAnim--;
// Shield CD
if (st.shieldBroken) { st.shieldCooldown--; if(st.shieldCooldown<=0){st.shieldBroken=false;st.shieldCooldown=0;} }
st.blocking = !!st.keys['KeyF'] && !st.shieldBroken && st.p1Stunned===0;
if (st.blockFlash>0) st.blockFlash--;
if (st.shieldPushAnim>0) st.shieldPushAnim--;
// Bleed
if (st.bleeding>0) {
st.bleedTimer--;
if (st.bleedTimer<=0) {
st.bleedTimer=60; st.bleeding--;
if(!st.gameOver){
st.p1.hp=Math.max(0,st.p1.hp-1);
state_spawnParticle(st.p1.x+(Math.random()-0.5)*20,st.p1.y-30-Math.random()*30,(Math.random()-0.5)*2,Math.random()*2,'#cc0000',3+Math.random()*3,25);
if(st.p1.hp<=0&&!st.gameOver){st.gameOver=true;st.winner='Player 2';}
}
}
}
// P1 movement (blocked if stunned)
if (st.p1Stunned===0 && !st.blocking) {
if (st.keys['KeyA']) { st.p1.x -= SPEED; st.p1.facing=-1; }
if (st.keys['KeyD']) { st.p1.x += SPEED; st.p1.facing=1; }
}
if (st.keys['KeyW'] && st.p1.y>=GROUND && st.p1Stunned===0) vel.p1.vy=JUMP_FORCE;
if (st.swordCharging && st.p1Stunned===0) {
st.swordCharge=Math.min(st.swordCharge+1,SWORD_CHARGE_MAX);
if(st.swordCharge>=SWORD_CHARGE_MAX) st.swordCharged=true;
}
// P2 movement
if (!st.lunging) {
if (st.keys['ArrowLeft']) { st.p2.x-=SPEED; st.p2.facing=-1; }
if (st.keys['ArrowRight']) { st.p2.x+=SPEED; st.p2.facing=1; }
if (st.keys['ArrowUp']&&st.p2.y>=GROUND) vel.p2.vy=JUMP_FORCE;
}
// Knockback decay
vel.p1.vx *= 0.8; vel.p2.vx *= 0.8;
['p1','p2'].forEach(p => {
if (st.lungeFinisher && p==='p1') return;
if (st.jumpHitFinisher && p==='p2') return;
vel[p].vy += GRAVITY;
st[p].y += vel[p].vy;
st[p].x += vel[p].vx;
if (st[p].y >= GROUND) {
if (p==='p1' && vel.p1.vy>8 && st.jumpHit && st.groundSlamCooldown<=0) { doGroundSlam(st.p1.x); st.jumpHit=false; }
st[p].y=GROUND; vel[p].vy=0;
}
st[p].x=Math.max(20,Math.min(canvas.width-20,st[p].x));
});
// Cooldowns
if (st.swordSwing>0) st.swordSwing--;
if (st.swordCooldown>0) st.swordCooldown--;
if (st.jumpHitAnim>0) st.jumpHitAnim--;
if (st.jumpHitCooldown>0) st.jumpHitCooldown--;
if (st.groundSlamCooldown>0) st.groundSlamCooldown--;
if (st.groundSlamAnim>0) st.groundSlamAnim--;
if (st.slamExplosionAnim>0) st.slamExplosionAnim--;
if (st.daggerSwing>0) st.daggerSwing--;
if (st.daggerCooldown>0) st.daggerCooldown--;
if (st.grappleCooldown>0) st.grappleCooldown--;
if (st.grappleParryWindow>0) st.grappleParryWindow--;
if (st.comboCooldown>0) st.comboCooldown--;
if (st.comboTimer>0) { st.comboTimer--; if(st.comboTimer<=0){st.comboActive=false;st.comboStep=0;} }
// Grapple hook flying
if (st.grappleActive) {
st.grappleAnim++;
st.grappleX += st.p2.facing * 14;
st.grappleY += 0;
// Hit p1?
if (Math.abs(st.grappleX - st.p1.x) < 25 && Math.abs(st.grappleY - (st.p1.y-40)) < 35) {
// Parry check: if p1 was starting a sword swing
if (st.swordSwing > 14) {
doParry();
} else {
doGrappleHit();
}
}
// Miss: hook too far
if (st.grappleX < -30 || st.grappleX > canvas.width+30) {
st.grappleActive=false;
st.grappleCooldown=3*FPS;
}
}
// Grapple pulling p1 in
if (st.grappleHooked) {
st.grapplePullAnim--;
// Drag p1 toward p2
const dx = st.p2.x - st.p1.x;
st.p1.x += dx * 0.12;
st.grappleX = st.p1.x; st.grappleY = st.p1.y - 40;
if (st.grapplePullAnim <= 0) {
st.grappleHooked = false;
st.grappleCooldown = 3 * FPS; // 3s base cooldown after use
}
}
// Lunge charge
if (st.keys['KeyL'] && !st.lunging && st.lungeCharge < LUNGE_CHARGE_NEEDED) {
st.lungeCharge++;
const pct=st.lungeCharge/LUNGE_CHARGE_NEEDED;
if(Math.random()<0.3+pct*0.5){const a=Math.random()*Math.PI*2,r=15+pct*25;state_spawnParticle(st.p2.x+Math.cos(a)*r,st.p2.y-30+Math.sin(a)*r,-Math.cos(a)*(1+pct*2),-Math.sin(a)*(1+pct*2)-0.5,pct<0.5?'#8844ff':pct<0.85?'#cc44ff':'#ffffff',2+Math.random()*3*pct,16);}
}
// Lunge anim
if (st.lunging) {
st.lungeAnim--;
st.p2.x += (st.lungeEndX-st.p2.x)*0.45;
spawnLungeTrail(st.p2.x,st.p2.y);
if (st.lungeAnim===10&&!st.lungeFinisher) dealDamage('p1',13,true);
if (st.lungeAnim<=0) { st.lunging=false; st.p2.x=st.lungeEndX; if(st.lungeFinisher){st.gameOver=true;st.winner='Player 2';} }
}
// Finisher head
if (st.lungeFinisher) {
st.lungeFinisherAnim--;
st.headX+=st.headVX; st.headY+=st.headVY; st.headVY+=0.4;
if(st.headY>GROUND+20){st.headVY*=-0.4;st.headY=GROUND+20;}
if(st.lungeFinisherAnim>50&&Math.random()<0.4) state_spawnParticle(st.headX,st.headY+12,(Math.random()-0.5)*1,1+Math.random()*2,'#cc0000',2+Math.random()*3,20);
}
// Jump hit finisher halves
if (st.jumpHitFinisher) {
st.jumpHitFinisherAnim--;
st.p2TopHalfY+=st.p2TopVY; st.p2TopVY+=0.45;
st.p2.x+=st.p2TopVX; st.p2TopVX*=0.98;
if(st.p2TopHalfY>GROUND-5){st.p2TopVY*=-0.35;st.p2TopHalfY=GROUND-5;}
if(st.jumpHitFinisherAnim>40&&Math.random()<0.5){
state_spawnParticle(st.p2.x+(Math.random()-0.5)*20,st.p2TopHalfY+20,(Math.random()-0.5)*1,1+Math.random()*2,'#cc0000',2+Math.random()*3,22);
state_spawnParticle(st.p2.x+(Math.random()-0.5)*20,st.p2BotY-30,(Math.random()-0.5)*1,1+Math.random()*2,'#cc0000',2+Math.random()*3,22);
}
if(st.jumpHitFinisherAnim<=0){st.gameOver=true;st.winner='Player 1';}
}
// Bullets
st.bullets=st.bullets.filter(b=>b.life>0&&b.x>-20&&b.x<canvas.width+20);
for (const b of st.bullets) {
b.x+=b.vx; b.life--;
if (!b.reflected&&Math.abs(b.x-st.p1.x)<20&&Math.abs(b.y-(st.p1.y-45))<25) {
if(st.shieldPushAnim>10){b.vx*=-1;b.dir*=-1;b.reflected=true;spawnSlashParticles(b.x,b.y,'#88aaff');}
else{dealDamage('p1',11,false);b.life=0;}
}
if(b.reflected&&Math.abs(b.x-st.p2.x)<20&&Math.abs(b.y-(st.p2.y-45))<25){dealDamage('p2',1,false);b.life=0;}
}
// Beam
if (st.keys['Enter']) {
st.beamCharge=Math.min(st.beamCharge+1,CHARGE_NEEDED);
if(st.beamCharge<CHARGE_NEEDED&&Math.random()<0.6+(st.beamCharge/CHARGE_NEEDED)*0.4) spawnChargeParticle();
if(st.beamCharge>=CHARGE_NEEDED){
st.beamActive=true; st.beamDmgTimer++;
if(st.beamDmgTimer>=BEAM_DMG_INTERVAL){st.beamDmgTimer=0;if(isBeamHitting())dealDamage('p1',11,false);}
}
} else { st.beamActive=false; st.beamCharge=Math.max(0,st.beamCharge-2); st.beamDmgTimer=0; }
st.particles=st.particles.filter(p=>p.life>0);
st.particles.forEach(p=>{p.x+=p.vx;p.y+=p.vy;p.vy+=0.1;p.life--;});
}
function isBeamHitting(){return (st.p1.x-st.p2.x)*st.p2.facing>0;}
// ---- DRAW ----
function drawBeam() {
if(!st.beamActive)return;
const p2=st.p2,t=Date.now()/40,bx=p2.x+p2.facing*15,by=p2.y-55,endX=p2.facing>0?canvas.width+50:-50,dir=p2.facing,beamLen=Math.abs(endX-bx),startX=Math.min(bx,endX);
ctx.save();
for(let i=5;i>=1;i--){const w=20+i*18;ctx.globalAlpha=0.04+i*0.02;ctx.fillStyle=i%2===0?'#ff3300':'#ff8800';ctx.fillRect(startX,by-w/2,beamLen,w);}
ctx.globalAlpha=0.55;ctx.fillStyle='#ff4400';ctx.fillRect(startX,by-22,beamLen,44);
for(const l of [{w:28,c:'#cc2200',a:0.9},{w:18,c:'#ff5500',a:1},{w:10,c:'#ff9900',a:1},{w:5,c:'#ffee88',a:1},{w:2,c:'#ffffff',a:1}]){ctx.globalAlpha=l.a;ctx.fillStyle=l.c;ctx.fillRect(startX,by-l.w/2,beamLen,l.w);}
ctx.globalAlpha=0.8;for(let i=0;i<20;i++){const off=((t*12*dir+i*38)%beamLen),px=dir>0?bx+off:bx-off,py=by+Math.sin(t+i*0.8)*7,sz=4+Math.sin(t*2+i)*3;ctx.fillStyle=i%3===0?'#ffffff':i%3===1?'#ffcc00':'#ff6600';ctx.beginPath();ctx.arc(px,py,sz,0,Math.PI*2);ctx.fill();}
ctx.globalAlpha=0.6;for(let i=0;i<8;i++){const off=((t*10*dir+i*90)%beamLen),px=dir>0?bx+off:bx-off,sy=by+(i%2===0?-1:1)*(14+Math.sin(t+i)*8);ctx.strokeStyle='#ffaa00';ctx.lineWidth=1.5;ctx.beginPath();ctx.moveTo(px,by);ctx.lineTo(px+dir*5,sy);ctx.stroke();}
ctx.globalAlpha=1;
const pulse=1+Math.sin(t*3)*0.15,mR=22*pulse,grd=ctx.createRadialGradient(bx,by,0,bx,by,mR*2);
grd.addColorStop(0,'rgba(255,255,255,1)');grd.addColorStop(0.3,'rgba(255,150,0,0.9)');grd.addColorStop(1,'rgba(255,50,0,0)');
ctx.fillStyle=grd;ctx.beginPath();ctx.arc(bx,by,mR*2,0,Math.PI*2);ctx.fill();
ctx.fillStyle='#ffaa00';for(let i=0;i<8;i++){const a=(Math.PI*2/8)*i+t*0.3,r1=mR*1.4,r2=mR*0.6;ctx.save();ctx.translate(bx,by);ctx.beginPath();ctx.moveTo(Math.cos(a)*r1,Math.sin(a)*r1);ctx.lineTo(Math.cos(a+0.3)*r2,Math.sin(a+0.3)*r2);ctx.lineTo(Math.cos(a+0.6)*r1*0.5,Math.sin(a+0.6)*r1*0.5);ctx.closePath();ctx.globalAlpha=0.7;ctx.fill();ctx.restore();}
ctx.globalAlpha=1;ctx.restore();
}
function drawCharging() {
const p2=st.p2,pct=st.beamCharge/CHARGE_NEEDED;if(pct<=0||st.beamActive)return;
const cx=p2.x+p2.facing*20,cy=p2.y-55,t=Date.now()/60;
for(let ring=0;ring<3;ring++){const r=12+ring*10+Math.sin(t+ring)*3;ctx.save();ctx.globalAlpha=pct*(0.5-ring*0.12);ctx.strokeStyle=ring===0?'#ff2200':ring===1?'#ff8800':'#ffff00';ctx.lineWidth=3-ring*0.5;ctx.beginPath();ctx.arc(cx,cy,r*pct,0,Math.PI*2*pct);ctx.stroke();ctx.restore();}
const orbR=6+pct*10+Math.sin(t*4)*2*pct,grd=ctx.createRadialGradient(cx,cy,0,cx,cy,orbR*2);grd.addColorStop(0,`rgba(255,255,255,${pct})`);grd.addColorStop(0.4,`rgba(255,100,0,${pct*0.9})`);grd.addColorStop(1,`rgba(255,0,0,0)`);ctx.fillStyle=grd;ctx.beginPath();ctx.arc(cx,cy,orbR*2,0,Math.PI*2);ctx.fill();
const nw=Math.floor(3+pct*5);for(let i=0;i<nw;i++){const a=(Math.PI*2/nw)*i+t*(1+pct),r=(18+pct*20)*pct;ctx.save();ctx.globalAlpha=pct*0.9;ctx.strokeStyle=i%2===0?'#ff4400':'#ffaa00';ctx.lineWidth=2;ctx.beginPath();ctx.moveTo(cx,cy);ctx.lineTo(cx+Math.cos(a)*r,cy+Math.sin(a)*r);ctx.stroke();ctx.beginPath();ctx.arc(cx+Math.cos(a)*r,cy+Math.sin(a)*r,3*pct,0,Math.PI*2);ctx.fillStyle='#ffcc00';ctx.fill();ctx.restore();}
ctx.fillStyle='rgba(0,0,0,0.5)';ctx.fillRect(p2.x-22,p2.y-100,44,8);const bc=pct<0.5?'#ff6600':pct<0.85?'#ffaa00':'#00ffaa';ctx.fillStyle=bc;ctx.fillRect(p2.x-22,p2.y-100,44*pct,8);ctx.strokeStyle='#fff';ctx.lineWidth=0.5;ctx.strokeRect(p2.x-22,p2.y-100,44,8);if(pct>=0.99){ctx.fillStyle='#00ffaa';ctx.font='bold 11px monospace';ctx.textAlign='center';ctx.fillText('READY!',p2.x,p2.y-103);}
}
function drawLungeCharge() {
if(st.lungeCharge<=0||st.lunging)return;
const p2=st.p2,pct=st.lungeCharge/LUNGE_CHARGE_NEEDED,t=Date.now()/50,cx=p2.x,cy=p2.y-35;
for(let ring=0;ring<2;ring++){const r=(16+ring*12)*pct;ctx.save();ctx.globalAlpha=pct*(0.7-ring*0.2);ctx.strokeStyle=ring===0?'#8844ff':'#dd88ff';ctx.lineWidth=3-ring;ctx.beginPath();ctx.arc(cx,cy,r,t*(ring===0?1:-0.7),t*(ring===0?1:-0.7)+Math.PI*2*pct);ctx.stroke();ctx.restore();}
const orbR=4+pct*12+Math.sin(t*5)*2*pct,grd=ctx.createRadialGradient(cx,cy,0,cx,cy,orbR*2);grd.addColorStop(0,`rgba(255,255,255,${pct})`);grd.addColorStop(0.4,`rgba(180,50,255,${pct*0.9})`);grd.addColorStop(1,'rgba(100,0,200,0)');ctx.fillStyle=grd;ctx.beginPath();ctx.arc(cx,cy,orbR*2,0,Math.PI*2);ctx.fill();
ctx.fillStyle='rgba(0,0,0,0.5)';ctx.fillRect(p2.x-22,p2.y-110,44,8);const bc=pct<0.6?'#8844ff':pct<0.9?'#cc44ff':'#ffffff';ctx.fillStyle=bc;ctx.fillRect(p2.x-22,p2.y-110,44*pct,8);ctx.strokeStyle='#cc88ff';ctx.lineWidth=0.5;ctx.strokeRect(p2.x-22,p2.y-110,44,8);if(pct>=0.99){ctx.fillStyle='#ffffff';ctx.font='bold 11px monospace';ctx.textAlign='center';ctx.fillText('LUNGE!',p2.x,p2.y-113);}
}
function drawSwordCharge() {
if(!st.swordCharging)return;
const pct=st.swordCharge/SWORD_CHARGE_MAX,t=Date.now()/50;
const cx=st.p1.x+st.p1.facing*20,cy=st.p1.y-40;
const grd=ctx.createRadialGradient(cx,cy,0,cx,cy,8+pct*12);grd.addColorStop(0,`rgba(255,255,255,${pct})`);grd.addColorStop(0.5,`rgba(100,200,255,${pct*0.8})`);grd.addColorStop(1,'rgba(50,100,200,0)');ctx.fillStyle=grd;ctx.beginPath();ctx.arc(cx,cy,8+pct*16,0,Math.PI*2);ctx.fill();
if(pct>=1){ctx.strokeStyle='#ffffff';ctx.lineWidth=2;ctx.globalAlpha=0.5+Math.sin(t*8)*0.3;ctx.beginPath();ctx.arc(cx,cy,20,0,Math.PI*2);ctx.stroke();ctx.globalAlpha=1;}
ctx.fillStyle='rgba(0,0,0,0.5)';ctx.fillRect(st.p1.x-22,st.p1.y-108,44,7);ctx.fillStyle=pct<0.6?'#4488ff':pct<0.9?'#88ddff':'#ffffff';ctx.fillRect(st.p1.x-22,st.p1.y-108,44*pct,7);ctx.strokeStyle='#88aaff';ctx.lineWidth=0.5;ctx.strokeRect(st.p1.x-22,st.p1.y-108,44,7);if(pct>=1){ctx.fillStyle='#ffffff';ctx.font='bold 11px monospace';ctx.textAlign='center';ctx.fillText('CHARGED!',st.p1.x,st.p1.y-112);}
}
function drawSlamExplosion() {
if(st.slamExplosionAnim<=0)return;
const pct=st.slamExplosionAnim/30,x=st.slamExplosionX,y=GROUND,r=(1-pct)*200;
ctx.save();ctx.globalAlpha=pct*0.7;ctx.strokeStyle='#87CEEB';ctx.lineWidth=4;ctx.beginPath();ctx.arc(x,y,r,Math.PI,0);ctx.stroke();ctx.strokeStyle='#ffffff';ctx.lineWidth=2;ctx.beginPath();ctx.arc(x,y,r*0.7,Math.PI,0);ctx.stroke();
ctx.globalAlpha=pct*0.4;const grd=ctx.createRadialGradient(x,y,0,x,y,r);grd.addColorStop(0,'rgba(180,220,255,0.8)');grd.addColorStop(1,'rgba(50,100,200,0)');ctx.fillStyle=grd;ctx.beginPath();ctx.arc(x,y,r,0,Math.PI*2);ctx.fill();ctx.globalAlpha=1;ctx.restore();
}
function drawGrapple() {
if (!st.grappleActive && !st.grappleHooked) return;
const p2=st.p2;
const originX=p2.x+p2.facing*18, originY=p2.y-45;
// Rope
ctx.save();
ctx.strokeStyle='#44ffaa';ctx.lineWidth=2;ctx.globalAlpha=0.85;
ctx.beginPath();ctx.moveTo(originX,originY);ctx.lineTo(st.grappleX,st.grappleY);ctx.stroke();
// Hook tip
ctx.fillStyle='#44ffaa';ctx.globalAlpha=1;
ctx.beginPath();ctx.arc(st.grappleX,st.grappleY,5,0,Math.PI*2);ctx.fill();
// Glow
ctx.globalAlpha=0.3;const grd=ctx.createRadialGradient(st.grappleX,st.grappleY,0,st.grappleX,st.grappleY,12);grd.addColorStop(0,'rgba(68,255,170,0.8)');grd.addColorStop(1,'rgba(68,255,170,0)');ctx.fillStyle=grd;ctx.beginPath();ctx.arc(st.grappleX,st.grappleY,12,0,Math.PI*2);ctx.fill();
ctx.globalAlpha=1;ctx.restore();
}
function drawClash() {
if(st.clashAnim<=0)return;
const pct=st.clashAnim/30,mx=(st.p1.x+st.p2.x)/2,my=(st.p1.y+st.p2.y)/2-35;
ctx.save();ctx.globalAlpha=pct;
const t=Date.now()/30;
for(let i=0;i<8;i++){const a=(Math.PI*2/8)*i+t,r=20*(1-pct)+10;ctx.strokeStyle=i%2===0?'#ffffff':'#ffdd44';ctx.lineWidth=3;ctx.beginPath();ctx.moveTo(mx,my);ctx.lineTo(mx+Math.cos(a)*r,my+Math.sin(a)*r);ctx.stroke();}
ctx.fillStyle='#ffffff';ctx.font=`bold ${Math.floor(18+pct*10)}px monospace`;ctx.textAlign='center';ctx.fillText('CLASH!',mx,my-20);
ctx.globalAlpha=1;ctx.restore();
}
function drawParticles(){st.particles.forEach(p=>{ctx.globalAlpha=p.life/p.maxLife;ctx.fillStyle=p.color;ctx.beginPath();ctx.arc(p.x,p.y,Math.max(0.1,p.size*(p.life/p.maxLife)),0,Math.PI*2);ctx.fill();});ctx.globalAlpha=1;}
function drawSword(x,y,facing,swing,charged) {
const t=swing/(charged?24:18),sa=facing>0?-Math.PI*0.8:Math.PI*1.8,ea=facing>0?Math.PI*0.15:Math.PI*0.85;
const angle=sa+(ea-sa)*(1-t);
ctx.save();ctx.translate(x+facing*10,y-35);ctx.rotate(angle);
if(charged){ctx.shadowColor='#ffffff';ctx.shadowBlur=12;}
ctx.fillStyle='#8B4513';ctx.fillRect(-3,0,6,18);ctx.fillStyle='#aaa';ctx.fillRect(-8,16,16,5);
ctx.fillStyle=charged?'#ffffff':'#e8e8e8';ctx.fillRect(-2,20,4,35);
ctx.beginPath();ctx.moveTo(-2,55);ctx.lineTo(2,55);ctx.lineTo(0,62);ctx.fillStyle='#fff';ctx.fill();
if(t>0.3){ctx.globalAlpha=t*0.5;ctx.strokeStyle=charged?'#ffffff':'#87CEEB';ctx.lineWidth=charged?16:10;ctx.beginPath();ctx.arc(0,0,60,angle-facing*0.5,angle,facing<0);ctx.stroke();ctx.globalAlpha=1;}
ctx.restore();
}
function drawDagger(x, y, facing, swing) {
const maxSwing = swing <= 10 ? 10 : 12;
const t = swing / maxSwing;
const thrust = facing * Math.sin((1 - t) * Math.PI) * 28;
ctx.save();
ctx.translate(x + facing * 18 + thrust, y - 38);
// Flip: tip points TOWARD player body (backward from facing)
ctx.rotate(facing > 0 ? -Math.PI / 2 : Math.PI / 2);
ctx.fillStyle='#5c3317';ctx.fillRect(-3,0,6,12);
ctx.fillStyle='#888';ctx.fillRect(-7,11,14,4);
ctx.fillStyle='#ddeeff';ctx.fillRect(-1.5,14,3,24);
ctx.beginPath();ctx.moveTo(-1.5,38);ctx.lineTo(1.5,38);ctx.lineTo(0,45);ctx.fillStyle='#ffffff';ctx.fill();
if(t<0.5){ctx.globalAlpha=(0.5-t)*1.4;ctx.strokeStyle='#aaddff';ctx.lineWidth=6;ctx.beginPath();ctx.moveTo(0,38);ctx.lineTo(0,56);ctx.stroke();ctx.globalAlpha=1;}
ctx.restore();
}
function drawShield(x,y,facing,broken) {
ctx.save();ctx.translate(x+facing*18,y-35);
if(broken){ctx.globalAlpha=0.5;for(let side of[-1,1]){ctx.save();ctx.rotate(side*0.5);ctx.translate(side*5,side*3);ctx.fillStyle='#223366';ctx.beginPath();ctx.moveTo(side>0?0:-14,-18);ctx.lineTo(side>0?14:0,-10);ctx.lineTo(side>0?14:0,10);ctx.lineTo(side>0?0:-14,20);ctx.closePath();ctx.fill();ctx.strokeStyle='#4466aa';ctx.lineWidth=2;ctx.stroke();ctx.restore();}ctx.restore();return;}
if(st.blockFlash>0){ctx.globalAlpha=st.blockFlash/20*0.6;ctx.fillStyle='#ffdd44';ctx.beginPath();ctx.arc(0,0,22,0,Math.PI*2);ctx.fill();ctx.globalAlpha=1;}
if(st.shieldPushAnim>0){const push=(st.shieldPushAnim/20)*8;ctx.translate(facing*push,0);}
ctx.fillStyle='#4477cc';ctx.beginPath();ctx.moveTo(0,-18);ctx.lineTo(14,-10);ctx.lineTo(14,10);ctx.lineTo(0,20);ctx.lineTo(-14,10);ctx.lineTo(-14,-10);ctx.closePath();ctx.fill();
ctx.strokeStyle='#88aaff';ctx.lineWidth=2;ctx.stroke();ctx.fillStyle='#88aaff';ctx.fillRect(-2,-10,4,20);ctx.fillRect(-8,-2,16,4);
ctx.restore();
}
function drawHeadChopped(){
if(!st.lungeFinisher)return;
ctx.save();ctx.translate(st.headX,st.headY);ctx.rotate((st.headX-st.p1.x)*0.08);
ctx.fillStyle='#3498db';ctx.beginPath();ctx.arc(0,0,12,0,Math.PI*2);ctx.fill();
ctx.strokeStyle='#fff';ctx.lineWidth=2;for(let s of[-1,1]){ctx.beginPath();ctx.moveTo(s*4-2,-4);ctx.lineTo(s*4+2,0);ctx.stroke();ctx.beginPath();ctx.moveTo(s*4+2,-4);ctx.lineTo(s*4-2,0);ctx.stroke();}
ctx.fillStyle='#cc0000';ctx.fillRect(-6,10,12,5);ctx.restore();
}
function drawStunIndicator(x, y, frames) {
if (frames <= 0) return;
const t = Date.now() / 200;
ctx.save();
for (let i = 0; i < 3; i++) {
const a = (Math.PI * 2 / 3) * i + t;
const sx = x + Math.cos(a) * 16, sy = y - 85 + Math.sin(a) * 5;
ctx.fillStyle = '#ffdd44';
ctx.font = '14px monospace';
ctx.textAlign = 'center';
ctx.fillText('★', sx, sy);
}
const secs = Math.ceil(frames / FPS);
ctx.fillStyle = '#ffdd44'; ctx.font = 'bold 10px monospace';
ctx.fillText(`STUNNED ${secs}s`, x, y - 92);
ctx.restore();
}
function drawPlayer(p, color, label) {
const x=st[p].x,y=st[p].y,f=st[p].facing;
if (p==='p1') {
if(st.lungeFinisher){
ctx.fillStyle=color;ctx.fillRect(x-15,y-50,30,40);ctx.fillStyle='#cc0000';ctx.fillRect(x-6,y-52,12,6);ctx.fillStyle=color;ctx.fillRect(x-12,y-10,10,20);ctx.fillRect(x+2,y-10,10,20);
return;
}
if(st.blocking||st.shieldBroken) drawShield(x,y,f,st.shieldBroken);
else if(st.swordSwing>0) drawSword(x,y,f,st.swordSwing,st.swordCharged);
else {
ctx.save();ctx.translate(x+f*10,y-35);ctx.rotate(f>0?-Math.PI*0.6:Math.PI*1.6);
if(st.swordCharging&&st.swordCharge>30){ctx.shadowColor='#87CEEB';ctx.shadowBlur=8;}
ctx.fillStyle='#8B4513';ctx.fillRect(-3,0,6,18);ctx.fillStyle='#aaa';ctx.fillRect(-8,16,16,5);
ctx.fillStyle=st.swordCharge>=SWORD_CHARGE_MAX?'#ffffff':'#e8e8e8';ctx.fillRect(-2,20,4,35);
ctx.fillStyle='#fff';ctx.beginPath();ctx.moveTo(-2,55);ctx.lineTo(2,55);ctx.lineTo(0,62);ctx.fill();ctx.restore();
}
}
if (p==='p2') {
if(st.jumpHitFinisher){
const bx=st.p2.x,botY=st.p2BotY,topY=st.p2TopHalfY,tumble=(100-st.jumpHitFinisherAnim)*0.04;
ctx.fillStyle='#e74c3c';ctx.fillRect(bx-15,botY-25,30,25);ctx.fillRect(bx-12,botY-2,10,18);ctx.fillRect(bx+2,botY-2,10,18);ctx.fillStyle='#cc0000';ctx.fillRect(bx-8,botY-27,16,5);
ctx.save();ctx.translate(bx,topY);ctx.rotate(tumble*f);ctx.fillStyle='#e74c3c';ctx.fillRect(-15,-25,30,25);ctx.beginPath();ctx.arc(0,-35,12,0,Math.PI*2);ctx.fill();
ctx.fillStyle='#fff';ctx.fillRect(f*3-3,-39,6,5);ctx.fillStyle='#000';ctx.fillRect(f*4-2,-38,3,3);
ctx.strokeStyle='#fff';ctx.lineWidth=2;for(let s of[-1,1]){ctx.beginPath();ctx.moveTo(s*4-2,-39);ctx.lineTo(s*4+2,-35);ctx.stroke();ctx.beginPath();ctx.moveTo(s*4+2,-39);ctx.lineTo(s*4-2,-35);ctx.stroke();}
ctx.fillStyle='#cc0000';ctx.fillRect(-8,-1,16,5);ctx.restore();
return;
}
if(st.daggerSwing>0) drawDagger(x,y,f,st.daggerSwing);
else {
ctx.save();ctx.translate(x+f*18,y-30);ctx.rotate(f>0?-Math.PI/2:Math.PI/2);
ctx.fillStyle='#5c3317';ctx.fillRect(-3,0,6,12);ctx.fillStyle='#888';ctx.fillRect(-7,11,14,4);ctx.fillStyle='#ddeeff';ctx.fillRect(-1.5,14,3,22);
ctx.beginPath();ctx.moveTo(-1.5,36);ctx.lineTo(1.5,36);ctx.lineTo(0,42);ctx.fillStyle='#ffffff';ctx.fill();ctx.restore();
}
}
if(p==='p1'&&st.lungeFinisher)return;
if(p==='p2'&&st.jumpHitFinisher)return;
if(p==='p2'&&st.lunging){ctx.save();ctx.globalAlpha=0.35;ctx.fillStyle='#aa44ff';ctx.fillRect(x-15,y-50,30,40);ctx.beginPath();ctx.arc(x,y-60,12,0,Math.PI*2);ctx.fill();ctx.restore();}
if(p==='p1'&&st.bleeding>0){ctx.save();ctx.globalAlpha=0.3;ctx.fillStyle='#cc0000';ctx.fillRect(x-15,y-50,30,40);ctx.beginPath();ctx.arc(x,y-60,12,0,Math.PI*2);ctx.fill();ctx.restore();}
if(p==='p1'&&st.jumpHitAnim>0){ctx.save();ctx.globalAlpha=st.jumpHitAnim/16*0.5;ctx.fillStyle='#ffdd44';ctx.fillRect(x-15,y-50,30,40);ctx.beginPath();ctx.arc(x,y-60,12,0,Math.PI*2);ctx.fill();ctx.restore();}
// Stun flash
if(p==='p1'&&st.p1Stunned>0){ctx.save();ctx.globalAlpha=0.25+Math.sin(Date.now()/60)*0.15;ctx.fillStyle='#ffdd44';ctx.fillRect(x-15,y-50,30,40);ctx.beginPath();ctx.arc(x,y-60,12,0,Math.PI*2);ctx.fill();ctx.restore();}
// Combo active flash on p2
if(p==='p2'&&st.comboActive&&st.comboStep<3){ctx.save();ctx.globalAlpha=0.2+Math.sin(Date.now()/80)*0.15;ctx.fillStyle='#ff6644';ctx.fillRect(x-15,y-50,30,40);ctx.beginPath();ctx.arc(x,y-60,12,0,Math.PI*2);ctx.fill();ctx.restore();}
ctx.fillStyle=color;ctx.fillRect(x-15,y-50,30,40);ctx.beginPath();ctx.arc(x,y-60,12,0,Math.PI*2);ctx.fill();
ctx.fillStyle='#fff';ctx.fillRect(x+f*3-3,y-64,6,5);ctx.fillStyle='#000';ctx.fillRect(x+f*4-2,y-63,3,3);
ctx.fillStyle=color;ctx.fillRect(x-12,y-10,10,20);ctx.fillRect(x+2,y-10,10,20);
if(p==='p1'&&st.blocking&&!st.shieldBroken){ctx.globalAlpha=0.25;ctx.fillStyle='#88aaff';ctx.fillRect(x-15,y-50,30,40);ctx.beginPath();ctx.arc(x,y-60,12,0,Math.PI*2);ctx.fill();ctx.globalAlpha=1;}
ctx.fillStyle='#fff';ctx.font='bold 12px monospace';ctx.textAlign='center';ctx.fillText(label,x,y-76);
}
function drawHPBar(x,y,hp,color,label){
ctx.fillStyle='#333';ctx.fillRect(x,y,200,18);ctx.fillStyle=hp>30?color:'#e74c3c';ctx.fillRect(x,y,hp*2,18);ctx.strokeStyle='rgba(255,255,255,0.4)';ctx.lineWidth=1;ctx.strokeRect(x,y,200,18);ctx.fillStyle='#fff';ctx.font='bold 12px monospace';ctx.textAlign='left';ctx.fillText(`${label}: ${hp}/100`,x+4,y+13);
}
function drawCooldownBar(x, y, cur, max, color) {
if (cur <= 0) return;
const pct = 1 - cur / max;
ctx.fillStyle = 'rgba(0,0,0,0.4)'; ctx.fillRect(x, y, 80, 5);
ctx.fillStyle = color; ctx.fillRect(x, y, 80 * pct, 5);
ctx.strokeStyle = color; ctx.lineWidth = 0.5; ctx.strokeRect(x, y, 80, 5);
}
function draw() {
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.fillStyle='#1a1a2e';ctx.fillRect(0,0,canvas.width,canvas.height);
ctx.fillStyle='#4a4a6a';ctx.fillRect(0,GROUND+10,canvas.width,canvas.height-GROUND-10);
ctx.fillStyle='#6a6a9a';ctx.fillRect(0,GROUND+8,canvas.width,4);
drawSlamExplosion();
drawBeam();drawCharging();drawLungeCharge();drawSwordCharge();
drawGrapple();
drawBullets();
drawParticles();
drawPlayer('p1','#3498db','P1');
drawPlayer('p2','#e74c3c','P2');
drawHeadChopped();
drawClash();
drawStunIndicator(st.p1.x, st.p1.y, st.p1Stunned);
drawHPBar(20,15,st.p1.hp,'#3498db','P1');
drawHPBar(530,15,st.p2.hp,'#e74c3c','P2');
// P1 status
let p1y=40;
drawCooldownBar(20,p1y,st.swordCooldown,45,'rgba(100,180,255,0.6)');p1y+=8;
drawCooldownBar(20,p1y,st.jumpHitCooldown,90,'rgba(255,220,50,0.6)');p1y+=8;
drawCooldownBar(20,p1y,st.groundSlamCooldown,10*FPS,'rgba(135,206,235,0.6)');p1y+=8;
if(st.blocking){ctx.fillStyle='#88aaff';ctx.font='bold 11px monospace';ctx.textAlign='left';ctx.fillText('BLOCKING',20,p1y+8);p1y+=14;}
if(st.shieldBroken){const s=Math.ceil(st.shieldCooldown/60),pct=1-st.shieldCooldown/SHIELD_COOLDOWN_MAX;ctx.fillStyle='#ff4444';ctx.font='bold 11px monospace';ctx.textAlign='left';ctx.fillText(`SHIELD BROKEN (${s}s)`,20,p1y+8);p1y+=12;drawCooldownBar(20,p1y,st.shieldCooldown,SHIELD_COOLDOWN_MAX,'rgba(100,100,255,0.5)');p1y+=8;}
if(st.p1Stunned>0){ctx.fillStyle='#ffdd44';ctx.font='bold 11px monospace';ctx.textAlign='left';ctx.fillText(`STUNNED ${Math.ceil(st.p1Stunned/FPS)}s`,20,p1y+8);p1y+=14;}
if(st.bleeding>0){ctx.fillStyle='#cc4444';ctx.font='bold 11px monospace';ctx.textAlign='left';ctx.fillText(`BLEEDING ${st.bleeding}s`,20,p1y+8);}
// P2 status
let p2y=40;
drawCooldownBar(530,p2y,st.daggerCooldown,20,'rgba(255,150,150,0.6)');p2y+=8;
drawCooldownBar(530,p2y,st.grappleCooldown,3*FPS,'rgba(68,255,170,0.6)');p2y+=8;
drawCooldownBar(530,p2y,st.comboCooldown,180,'rgba(255,100,50,0.6)');p2y+=8;
if(st.comboActive){ctx.fillStyle='#ff6644';ctx.font='bold 11px monospace';ctx.textAlign='right';ctx.fillText(`COMBO ${st.comboStep}/3 — press L!`,canvas.width-16,p2y+8);p2y+=14;}
if(st.lunging){ctx.fillStyle='#cc44ff';ctx.font='bold 11px monospace';ctx.textAlign='right';ctx.fillText('LUNGING!',canvas.width-16,p2y+8);p2y+=14;}
if(st.beamActive){ctx.fillStyle='#ff6600';ctx.font='bold 11px monospace';ctx.textAlign='right';ctx.fillText('BEAM FIRING',canvas.width-16,p2y+8);}
if(st.lungeFinisher&&st.lungeFinisherAnim>60){ctx.save();ctx.globalAlpha=Math.min(1,(st.lungeFinisherAnim-60)/20);ctx.fillStyle='#ff2200';ctx.font='bold 28px monospace';ctx.textAlign='center';ctx.fillText('LUNGE FINISHER',canvas.width/2,90);ctx.restore();}
if(st.jumpHitFinisher&&st.jumpHitFinisherAnim>70){ctx.save();ctx.globalAlpha=Math.min(1,(st.jumpHitFinisherAnim-70)/20);ctx.fillStyle='#ffdd00';ctx.font='bold 28px monospace';ctx.textAlign='center';ctx.fillText('CLEAVE FINISHER',canvas.width/2,90);ctx.restore();}
ctx.fillStyle='rgba(255,255,255,0.22)';ctx.font='9px monospace';ctx.textAlign='center';
ctx.fillText('P1: WASD+Space(swing/charge/jumpHit/shieldPush)+F(block) | P2: Arrows+L(dagger/lunge)+P(poison)+M(grapple)+Enter(bullet/beam)',canvas.width/2,canvas.height-5);
if(st.gameOver){
ctx.fillStyle='rgba(0,0,0,0.65)';ctx.fillRect(0,0,canvas.width,canvas.height);
if(st.lungeFinisher){drawParticles();drawHeadChopped();}
if(st.jumpHitFinisher){drawParticles();drawPlayer('p2','#e74c3c','P2');}
ctx.fillStyle=st.lungeFinisher?'#ff2200':st.jumpHitFinisher?'#ffdd00':'#f1c40f';
ctx.font='bold 48px monospace';ctx.textAlign='center';ctx.fillText(`${st.winner} Wins!`,canvas.width/2,canvas.height/2-20);
if(st.lungeFinisher){ctx.fillStyle='#ff8888';ctx.font='bold 20px monospace';ctx.fillText('EXECUTION',canvas.width/2,canvas.height/2+15);}
if(st.jumpHitFinisher){ctx.fillStyle='#ffee88';ctx.font='bold 20px monospace';ctx.fillText('CLEAVED IN HALF',canvas.width/2,canvas.height/2+15);}
ctx.fillStyle='#fff';ctx.font='20px monospace';ctx.fillText('Press R to play again',canvas.width/2,canvas.height/2+45);
}
}
function drawBullets(){for(const b of st.bullets){ctx.save();ctx.fillStyle=b.reflected?'#88aaff':'#ffcc00';if(b.reflected){ctx.shadowColor='#88aaff';ctx.shadowBlur=8;}ctx.beginPath();ctx.arc(b.x,b.y,6,0,Math.PI*2);ctx.fill();ctx.fillStyle='#ffffff';ctx.beginPath();ctx.arc(b.x-b.dir*2,b.y,3,0,Math.PI*2);ctx.fill();ctx.restore();}}
function loop() { update(); draw(); requestAnimationFrame(loop); }
loop();
})();