🎮ArcadeLab

电子斗蛐蛐 - 神仙大乱斗

by EpicCoder88
310 lines56.3 KB
▶ Play
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>电子斗蛐蛐 - 神仙大乱斗</title>
<style>
    body { margin: 0; padding: 0; background-color: #1a1a1a; display: flex; justify-content: center; align-items: center; height: 100vh; overflow: hidden; font-family: sans-serif; color: white; }
    .hidden { display: none !important; }
    #menu { text-align: center; }
    #startBtn { padding: 15px 40px; font-size: 24px; background-color: #4CAF50; color: white; border: none; border-radius: 8px; cursor: pointer; transition: background 0.3s; box-shadow: 0 4px 6px rgba(0,0,0,0.3); }
    #startBtn:hover { background-color: #45a049; }
    #selectScreen { display: flex; flex-direction: column; align-items: center; gap: 20px; }
    #selectScreen h2 { font-weight: normal; color: #aaa; font-size: 18px; letter-spacing: 2px; }
    #heroList { display: flex; gap: 15px; flex-wrap: wrap; justify-content: center; max-width: 95vw; max-height: 60vh; overflow-y: auto; padding: 10px; }
    .hero-card { padding: 12px 25px; background-color: #2a2a2a; border: 2px solid #444; border-radius: 8px; font-size: 16px; cursor: pointer; transition: all 0.2s ease; user-select: none; }
    .hero-card:hover { background-color: #3a3a3a; }
    .hero-card.selected { border-color: #00ffcc; background-color: rgba(0, 255, 204, 0.1); color: #00ffcc; box-shadow: 0 0 15px rgba(0, 255, 204, 0.4); }
    #gameCanvas { background-color: #2a2a2a; box-shadow: 0 0 20px rgba(0,0,0,0.5); max-width: 100vw; max-height: 100vh; }
    #gameOverScreen { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.8); display: flex; flex-direction: column; justify-content: center; align-items: center; gap: 30px; z-index: 10; backdrop-filter: blur(4px); }
    #gameOverText { font-size: 36px; font-weight: bold; text-shadow: 0 0 20px rgba(0, 255, 204, 0.5); }
    #restartBtn { padding: 15px 40px; font-size: 20px; background-color: #00ffcc; color: #111; border: none; border-radius: 8px; cursor: pointer; font-weight: bold; transition: transform 0.2s, background 0.3s; }
    #restartBtn:hover { background-color: #00ccaa; transform: scale(1.05); }
</style>
</head>
<body>

<div id="menu"><button id="startBtn">开始游戏</button></div>
<div id="selectScreen" class="hidden"><h2>选择两名英雄进行对决</h2><div id="heroList"></div></div>
<canvas id="gameCanvas" class="hidden" width="800" height="600"></canvas>
<div id="gameOverScreen" class="hidden">
    <div id="gameOverText">胜利!</div>
    <button id="restartBtn">重新选择英雄</button>
</div>

<script>
(function () {
'use strict';

const CONFIG = { GRID_SIZE: 40, GRID_COLS: 24, GRID_ROWS: 18, PADDING: 60 };
const ARENA_WIDTH  = CONFIG.GRID_COLS * CONFIG.GRID_SIZE;
const ARENA_HEIGHT = CONFIG.GRID_ROWS * CONFIG.GRID_SIZE;
const MAP_WIDTH    = ARENA_WIDTH  + CONFIG.PADDING * 2;
const MAP_HEIGHT   = ARENA_HEIGHT + CONFIG.PADDING * 2;
const ARENA_X = CONFIG.PADDING, ARENA_Y = CONFIG.PADDING;
const MAX_DT = 0.05;

const HERO_LIST = [
    { id: 'unknown',  name: '未知数',   color: '#00ffcc', maxHp: 1000, speed: 130 },
    { id: 'hanshou',  name: '寒守',     color: '#ff4444', maxHp: 1000, speed: 250 },
    { id: 'yansien',  name: '延施恩',   color: '#ffaa00', maxHp: 2000, speed: 150 },
    { id: 'deepseek', name: 'Deepseek', color: '#4488ff', maxHp: 900,  speed: 150 },
    { id: 'werewolf', name: '狼人',     color: '#aa00aa', maxHp: 1200, speed: 150 },
    { id: 'newton',   name: '牛顿',     color: '#dddddd', maxHp: 1100, speed: 120 },
    { id: 'knight',   name: '骑士',     color: '#cccccc', maxHp: 1100, speed: 110 },
    { id: 'pla',      name: 'PLA',      color: '#4a5d23', maxHp: 1000, speed: 120 },
    { id: 'tangjiaqi',name: '唐佳琪',   color: '#ffffff', maxHp: 1000, speed: 130 },
    { id: 'principal',name: '贪污校长', color: '#ffd700', maxHp: 1200, speed: 110 },
    { id: 'oldpriest',name: '老牧师',   color: '#8a2be2', maxHp: 1000, speed: 110 },
    { id: 'dongqing', name: '冬青叶',   color: '#2e8b57', maxHp: 1500, speed: 150 },
    { id: 'sunGod',   name: '日神',     color: '#ffb700', maxHp: 900,  speed: 130 },
    { id: 'dummy',    name: '测试假人', color: '#888888', maxHp: 500,  speed: 100 }
];
const HERO_MAP = {}; HERO_LIST.forEach(h => { HERO_MAP[h.id] = h; });

let gameState = 'menu', selectedHeroes = [], heroEntities = [], projectiles = [], damageTexts = [], slashEffects = [], impactEffects = [], muzzleFlashes = [], bleedEffects = [], newtonPrisms = [], apples = [], civilians = [], grenades = [], minions = [], tunnels = [], divineCircles = [], lastTime = 0, rafId = 0;
const camera = { x: 0, y: 0 };

const menu = document.getElementById('menu'), startBtn = document.getElementById('startBtn');
const selectScreen = document.getElementById('selectScreen'), heroList = document.getElementById('heroList');
const canvas = document.getElementById('gameCanvas'), gameOverScreen = document.getElementById('gameOverScreen');
const gameOverText = document.getElementById('gameOverText'), restartBtn = document.getElementById('restartBtn');
const ctx = canvas.getContext('2d');

startBtn.addEventListener('click', () => { if (gameState !== 'menu') return; menu.classList.add('hidden'); selectScreen.classList.remove('hidden'); gameState = 'select'; renderHeroSelection(); });
restartBtn.addEventListener('click', () => { cancelAnimationFrame(rafId); gameOverScreen.classList.add('hidden'); canvas.classList.add('hidden'); selectScreen.classList.remove('hidden'); gameState = 'select'; resetBattleState(); renderHeroSelection(); });

function renderHeroSelection() {
    heroList.innerHTML = ''; selectedHeroes = [];
    HERO_LIST.forEach(hero => {
        const card = document.createElement('div'); card.className = 'hero-card'; card.textContent = hero.name; card.dataset.id = hero.id;
        card.addEventListener('click', () => toggleHeroSelection(hero.id, card)); heroList.appendChild(card);
    });
}
function toggleHeroSelection(id, card) {
    if (gameState !== 'select') return;
    if (selectedHeroes.includes(id)) { selectedHeroes = selectedHeroes.filter(x => x !== id); card.classList.remove('selected'); return; }
    if (selectedHeroes.length >= 2) return;
    selectedHeroes.push(id); card.classList.add('selected');
    if (selectedHeroes.length === 2) { gameState = 'starting'; setTimeout(() => { if (gameState === 'starting') startBattle(); }, 400); }
}
function resetEffects() { projectiles = []; damageTexts = []; slashEffects = []; impactEffects = []; muzzleFlashes = []; bleedEffects = []; newtonPrisms = []; apples = []; civilians = []; grenades = []; minions = []; tunnels = []; divineCircles = []; }
function resetBattleState() { heroEntities = []; resetEffects(); }

function startBattle() {
    gameState = 'playing'; selectScreen.classList.add('hidden'); canvas.classList.remove('hidden');
    camera.x = MAP_WIDTH / 2 - canvas.width / 2; camera.y = MAP_HEIGHT / 2 - canvas.height / 2;
    heroEntities = selectedHeroes.map((id, index) => createHero(id, index));
    resetEffects(); lastTime = performance.now(); cancelAnimationFrame(rafId); rafId = requestAnimationFrame(gameLoop);
}
function createHero(id, index) {
    const db = HERO_MAP[id]; if (!db) throw new Error('未知英雄: ' + id);
    const startX = index === 0 ? ARENA_X + 150 : ARENA_X + ARENA_WIDTH - 150, startY = ARENA_Y + ARENA_HEIGHT / 2;
    const hero = { id: db.id, name: db.name, color: db.color, x: startX, y: startY, radius: 15, hp: db.maxHp, maxHp: db.maxHp, speed: db.speed, attackCooldown: 1000, attackRangeGrid: 1.5, attackTimer: 0, attackAnimTimer: 0, facingRight: index === 0, dr: 0, stunTimer: 0, pinnedBy: null, wallTimer: 0, bleedTickTimer: 0, wanderTimer: Math.random() * 2, wanderAngle: Math.random() * Math.PI * 2, wanderX: 0, wanderY: 0 };
    if (id === 'unknown') { hero.weaponState = 'pistol'; hero.shotsFired = 0; hero.shotgunShots = 0; hero.attackCooldown = 500; hero.attackRangeGrid = 10; }
    if (id === 'hanshou') { hero.hanshouState = 0; hero.pinnedTarget = null; hero.exhaustTimer = 0; hero.chargeTimer = 0; hero.attackCooldown = 1500; hero.attackRangeGrid = 1.125; hero.hasSword = true; }
    if (id === 'werewolf') { hero.battleTimer = 0; hero.enraged = false; hero.dashCooldown = 0; hero.isDashing = false; hero.dashTimer = 0; hero.dashDirX = 0; hero.dashDirY = 0; hero.attackCooldown = 1000; hero.attackRangeGrid = 1.5; }
    if (id === 'yansien') { hero.attackCooldown = 4500; hero.attackRangeGrid = 4; }
    if (id === 'deepseek') { hero.shieldHp = 1500; hero.maxShieldHp = 1500; hero.shieldCooldown = 0; hero.attackCooldown = 1000; }
    if (id === 'newton') { hero.prismCooldown = 5000; hero.appleTimer = 30000; hero.attackCooldown = 99999; hero.attackRangeGrid = 999; hero.satellites = []; hero.satelliteTimer = 5000; }
    if (id === 'knight') { hero.attackDamage = 34; hero.attackCooldown = 1200; hero.attackRangeGrid = 1.5; hero.shieldHp = 0; hero.maxShieldHp = 150; hero.chargeCooldown = 0; hero.isCharging = false; hero.chargeTimer = 0; hero.chargeDirX = 0; hero.chargeDirY = 0; }
    if (id === 'pla') { hero.clipAmmo = 30; hero.maxClipAmmo = 30; hero.isReloading = false; hero.reloadTimer = 0; hero.attackCooldown = 120; hero.attackRangeGrid = 8; hero.baseDamage = 10; hero.damageMult = 1; hero.damageBuffAdd = 0; hero.damageBuffTimer = 0; hero.reactionCooldown = 0; }
    if (id === 'tangjiaqi') { hero.phase = 0; hero.timer = 0; hero.pearls = 10; hero.attackCooldown = 99999; hero.attackRangeGrid = 999; }
    if (id === 'principal') { hero.shieldHp = 0; hero.maxShieldHp = 300; hero.summonCooldown = 8000; hero.escapeUsed = false; hero.attackCooldown = 4000; hero.attackRangeGrid = 6; }
    if (id === 'oldpriest') { hero.wrathTimer = 0; hero.isWrath = false; hero.attackCooldown = 1000; hero.attackRangeGrid = 1.5; }
    if (id === 'dongqing') { hero.tunnelCooldown = 8000; hero.tunnelState = 0; hero.tunnelTimer = 0; hero.attackCooldown = 1200; hero.attackRangeGrid = 1.5; }
    if (id === 'sunGod') { hero.debuffCooldown = 6000; hero.attackCooldown = 1000; hero.attackRangeGrid = 1.5; }
    return hero;
}

function gameLoop(timestamp) {
    if (gameState !== 'playing') return;
    const dt = Math.min((timestamp - lastTime) / 1000, MAX_DT); lastTime = timestamp;
    update(dt); if (gameState === 'playing') { updateCamera(dt); render(); rafId = requestAnimationFrame(gameLoop); }
}
function updateCamera(dt) {
    if (heroEntities.length < 2) return;
    const a = heroEntities[0], b = heroEntities[1];
    let cx = (a.x + b.x) / 2, cy = (a.y + b.y) / 2;
    if (!isFinite(cx)) cx = MAP_WIDTH / 2; if (!isFinite(cy)) cy = MAP_HEIGHT / 2;
    const targetX = Math.max(0, Math.min(MAP_WIDTH - canvas.width, cx - canvas.width / 2));
    const targetY = Math.max(0, Math.min(MAP_HEIGHT - canvas.height, cy - canvas.height / 2));
    const k = Math.min(1, 8 * dt); camera.x += (targetX - camera.x) * k; camera.y += (targetY - camera.y) * k;
}

function update(dt) {
    const alive = heroEntities.filter(h => h.hp > 0);
    if (alive.length <= 1) { gameState = 'gameover'; showGameOver(alive); return; }

    for (const h of heroEntities) {
        if (h.attackTimer > 0) h.attackTimer -= dt * 1000;
        if (h.attackAnimTimer > 0) h.attackAnimTimer -= dt * 1000;
        if (h.stunTimer > 0) h.stunTimer -= dt * 1000;
        if (h.exhaustTimer > 0) h.exhaustTimer -= dt * 1000;
        if (h.shieldCooldown > 0) h.shieldCooldown -= dt * 1000;
        if (h.dashCooldown > 0) h.dashCooldown -= dt * 1000;
        if (h.prismCooldown > 0) h.prismCooldown -= dt * 1000;
        if (h.appleTimer > 0) h.appleTimer -= dt * 1000;
        if (h.satelliteTimer > 0) h.satelliteTimer -= dt * 1000;
        if (h.chargeCooldown > 0) h.chargeCooldown -= dt * 1000;
        if (h.id === 'werewolf' && !h.enraged) h.battleTimer += dt;
        if (h.id === 'pla') { if (h.isReloading) { h.reloadTimer -= dt * 1000; if (h.reloadTimer <= 0) { h.isReloading = false; h.clipAmmo = h.maxClipAmmo; addDamageText(h.x, h.y - 30, '换弹完成!', '#4a5d23'); } } if (h.damageBuffTimer > 0) { h.damageBuffTimer -= dt * 1000; if (h.damageBuffTimer <= 0) h.damageBuffAdd = 0; } if (h.reactionCooldown > 0) h.reactionCooldown -= dt * 1000; }
        if (h.id === 'tangjiaqi') { h.timer += dt; if (h.phase === 0 && h.timer >= 30) { h.phase = 1; h.timer = 0; addDamageText(h.x, h.y - 40, '尾灯双闪!', '#ffffff'); } else if (h.phase === 1 && h.timer >= 10) { h.phase = 2; addDamageText(h.x, h.y - 40, '虚脱...', '#aaaaaa'); } }
        if (h.id === 'oldpriest') { h.wrathTimer += dt; if (!h.isWrath && h.wrathTimer >= 15) { h.isWrath = true; h.wrathTimer = 0; addDamageText(h.x, h.y - 40, '神圣领域!', '#ffd700'); divineCircles.push({ x: h.x, y: h.y, radius: 5 * CONFIG.GRID_SIZE, timer: 8, ownerId: h.id }); } else if (h.isWrath) { h.wrathTimer += dt; if (h.wrathTimer >= 8) { h.isWrath = false; h.wrathTimer = 0; } } }
        if (h.id === 'principal') { h.summonCooldown -= dt * 1000; if (h.summonCooldown <= 0 && minions.length < 6) { h.summonCooldown = 8000; for(let i=0;i<2;i++) minions.push({x: h.x + (Math.random()-0.5)*100, y: h.y + (Math.random()-0.5)*100, radius: 10, hp: 100, ownerId: h.id, explode: false}); addDamageText(h.x, h.y - 40, '家长会!', '#ffd700'); } }
        if (h.id === 'dongqing') { h.tunnelCooldown -= dt * 1000; if (h.tunnelCooldown <= 0 && h.tunnelState === 0 && tunnels.length === 0) { const e = heroEntities.find(x => x.id !== h.id && x.hp > 0); if (e) { tunnels.push({ x: e.x + (Math.random()-0.5)*200, y: e.y + (Math.random()-0.5)*200, state: 'entry', target: e }); h.tunnelState = 1; addDamageText(h.x, h.y - 40, '地道!', '#2e8b57'); } } }
        if (h.id === 'sunGod') { h.debuffCooldown -= dt * 1000; if (h.debuffCooldown <= 0) { h.debuffCooldown = 6000; const e = heroEntities.find(x => x.id !== h.id && x.hp > 0); if(e) { spawnProjectile({x: h.x, y: h.y, startX: h.x, startY: h.y, target: e, speed: 600, damage: 11, type: 'text', ownerId: h.id, text: Math.random() < 0.5 ? '这样倒不像是明确的选择。' : '哦,你那样做可真的是愚蠢透了,我有个更明智的办法。'}); } } }
    }

    for (const h of heroEntities) { if (h.wanderTimer > 0) h.wanderTimer -= dt; else { h.wanderTimer = 1.2 + Math.random() * 1.5; h.wanderAngle += (Math.random() - 0.5) * Math.PI * 1.8; } h.wanderX = Math.cos(h.wanderAngle) * h.speed * 0.5; h.wanderY = Math.sin(h.wanderAngle) * h.speed * 0.5; }

    // 人民刷新 (PLA专属)
    let hasPla = heroEntities.some(h => h.id === 'pla' && h.hp > 0);
    if (hasPla) { if (civilians.length < 5 && Math.random() < 0.01) { let cx = ARENA_X + Math.random() * ARENA_WIDTH, cy = ARENA_Y + Math.random() * ARENA_HEIGHT; civilians.push({ x: cx, y: cy, radius: 10, hp: 300, maxHp: 300, wanderTimer: Math.random() * 2, wanderAngle: Math.random() * Math.PI * 2, wanderX: 0, wanderY: 0, isFleeing: false, fleeTimer: 0 }); addDamageText(cx, cy - 20, '人民来了!', '#ffffff'); } }
    for (let i = civilians.length - 1; i >= 0; i--) { let c = civilians[i]; if (c.hp <= 0) { civilians.splice(i, 1); continue; } if (c.wanderTimer > 0) c.wanderTimer -= dt; else { c.wanderTimer = 1.5 + Math.random() * 2; c.wanderAngle += (Math.random() - 0.5) * Math.PI * 2; } if (c.isFleeing) { c.fleeTimer -= dt; if (c.fleeTimer <= 0) c.isFleeing = false; let pla = heroEntities.find(h => h.id === 'pla' && h.hp > 0); if (pla) { let dx = c.x - pla.x, dy = c.y - pla.y, dist = Math.hypot(dx, dy) || 1; c.x += (dx / dist) * 200 * dt; c.y += (dy / dist) * 200 * dt; } else { c.x += Math.cos(c.wanderAngle) * 150 * dt; c.y += Math.sin(c.wanderAngle) * 150 * dt; } } else { c.x += Math.cos(c.wanderAngle) * 80 * dt; c.y += Math.sin(c.wanderAngle) * 80 * dt; } constrainToArena(c); }

    // 苹果系统 (牛顿)
    for (const hero of heroEntities) { if (hero.id === 'newton' && hero.hp > 0 && hero.appleTimer <= 0) { hero.appleTimer = 30000; for (let i = 0; i < 10; i++) { if (apples.length >= 20) break; let ax = ARENA_X + Math.random() * ARENA_WIDTH, ay = ARENA_Y + Math.random() * ARENA_HEIGHT; let hitEnemy = false; for (let h of heroEntities) { if (h.id !== 'newton' && h.hp > 0 && Math.hypot(h.x - ax, h.y - ay) < h.radius + 10) { applyDamage(h, 40, hero, null); addDamageText(h.x, h.y - 30, '🍎 40', '#ff4444'); hitEnemy = true; break; } } if (!hitEnemy && apples.length < 20) apples.push({ x: ax, y: ay, radius: 8 }); } addDamageText(hero.x, hero.y - 40, '🍎 苹果雨!', '#ff4444'); } }
    for (let i = apples.length - 1; i >= 0; i--) { let apple = apples[i]; for (let h of heroEntities) { if (h.hp <= 0 || h.pinnedBy) continue; if (Math.hypot(h.x - apple.x, h.y - apple.y) < h.radius + apple.radius) { if (h.id === 'newton') { h.hp = Math.min(h.maxHp, h.hp + 50); addDamageText(h.x, h.y - 20, '+50', '#00ff00'); } else { h.hp = Math.min(h.maxHp, h.hp + 10); addDamageText(h.x, h.y - 20, '+10', '#00ff00'); } apples.splice(i, 1); break; } } }

    // 三棱镜 (牛顿)
    for (let i = newtonPrisms.length - 1; i >= 0; i--) { let p = newtonPrisms[i]; p.timer -= dt * 1000; if (p.timer <= 0) { newtonPrisms.splice(i, 1); continue; } p.angle += p.sweepDir * 2.5 * dt; if (p.angle > Math.PI / 3) p.sweepDir = -1; if (p.angle < -Math.PI / 3) p.sweepDir = 1; for (let h of heroEntities) { if (h.id === 'newton' || h.hp <= 0) continue; let dx = h.x - p.x, dy = h.y - p.y, dist = Math.hypot(dx, dy); if (dist < 240) { let angleToTarget = Math.atan2(dy, dx), angleDiff = Math.abs(angleToTarget - p.baseAngle); while (angleDiff > Math.PI) angleDiff = Math.abs(angleDiff - 2 * Math.PI); if (angleDiff < p.angle + Math.PI / 6) { applyDamage(h, 1, heroEntities.find(uh => uh.id === 'newton'), null, true, true); if (!p.damageAccum) p.damageAccum = {}; if (!p.damageAccum[h.id]) p.damageAccum[h.id] = 0; p.damageAccum[h.id] += 1; } } } p.textTimer = (p.textTimer || 0) - dt; if (p.textTimer <= 0) { p.textTimer = 0.5; if (p.damageAccum) { for (let id in p.damageAccum) { if (p.damageAccum[id] > 0) { let h = heroEntities.find(x => x.id === id); if (h && h.hp > 0) addDamageText(h.x, h.y, p.damageAccum[id], '#ff00ff'); p.damageAccum[id] = 0; } } } } }

    // 手榴弹 (PLA)
    for (let i = grenades.length - 1; i >= 0; i--) { let g = grenades[i]; g.timer -= dt; g.x += g.vx * dt; g.y += g.vy * dt; g.vx *= 0.95; g.vy *= 0.95; if (g.timer <= 0) { let radius = 2 * CONFIG.GRID_SIZE; impactEffects.push({ x: g.x, y: g.y, color: '#ff8800', life: 0.5, maxLife: 0.5 }); for (let h of heroEntities) { if (h.hp > 0 && Math.hypot(h.x - g.x, h.y - g.y) < radius + h.radius) applyDamage(h, 50, heroEntities.find(uh => uh.id === 'pla'), null); } for (let c of civilians) { if (Math.hypot(c.x - g.x, c.y - g.y) < radius + c.radius) { c.hp -= 50; c.isFleeing = true; c.fleeTimer = 3; } } grenades.splice(i, 1); } }

    // 神圣领域 (老牧师)
    for (let i = divineCircles.length - 1; i >= 0; i--) { let c = divineCircles[i]; c.timer -= dt; if (c.timer <= 0) { divineCircles.splice(i, 1); continue; } let owner = heroEntities.find(h => h.id === c.ownerId && h.hp > 0); if (owner) { c.x = owner.x; c.y = owner.y; } for (let h of heroEntities) { if (h.id !== c.ownerId && h.hp > 0 && Math.hypot(h.x - c.x, h.y - c.y) < c.radius + h.radius) { applyDamage(h, 46 * dt, owner, null, true, true); } } }

    // 家长爆炸 (贪污校长)
    for (let i = minions.length - 1; i >= 0; i--) { let m = minions[i]; if (m.hp <= 0) { minions.splice(i, 1); continue; } for (let h of heroEntities) { if (h.id !== m.ownerId && h.hp > 0 && Math.hypot(h.x - m.x, h.y - m.y) < h.radius + m.radius) { applyDamage(h, 30, heroEntities.find(x => x.id === m.ownerId), null); h.stunTimer = 1500; let owner = heroEntities.find(x => x.id === m.ownerId); if (owner) owner.shieldHp = Math.min(owner.maxShieldHp, owner.shieldHp + 50); addDamageText(m.x, m.y - 20, '家长爆炸!', '#ffd700'); impactEffects.push({ x: m.x, y: m.y, color: '#ffd700', life: 0.5, maxLife: 0.5 }); minions.splice(i, 1); break; } } }

    // 冬青叶的地道
    for (let i = tunnels.length - 1; i >= 0; i--) { let t = tunnels[i]; if (t.state === 'entry') { let d = heroEntities.find(h => h.id === 'dongqing'); if (d) { d.x = t.x; d.y = t.y; if (Math.hypot(d.x - t.x, d.y - t.y) < 10) { t.state = 'exit'; t.x = t.target.x + (Math.random()-0.5)*100; t.y = t.target.y + (Math.random()-0.5)*100; d.x = t.x; d.y = t.y; d.tunnelState = 2; d.tunnelTimer = 0.5; } } } else if (t.state === 'exit') { let d = heroEntities.find(h => h.id === 'dongqing'); if (d && d.tunnelState === 2) { if (d.tunnelTimer <= 0) { for(let j=0;j<3;j++) { applyDamage(t.target, 130, d, null); addDamageText(t.target.x, t.target.y - j*20, '130', '#2e8b57'); } t.state = 'return'; d.tunnelTimer = 0.5; } } } else if (t.state === 'return') { let d = heroEntities.find(h => h.id === 'dongqing'); if (d) { d.tunnelState = 0; d.tunnelCooldown = 8000; tunnels.splice(i, 1); } } }

    for (const hero of heroEntities) {
        if (hero.hp <= 0) continue;
        if (hero.id === 'dummy') { hero.x += hero.wanderX * dt; hero.y += hero.wanderY * dt; constrainToArena(hero); continue; }
        if (hero.pinnedBy) continue;
        const isImmune = hero.id === 'hanshou' && (hero.hanshouState === 1 || hero.hanshouState === 2);
        if (hero.stunTimer > 0 && !isImmune) continue;

        if (hero.id === 'unknown') updateUnknown(hero, dt);
        else if (hero.id === 'hanshou') updateHanshou(hero, dt);
        else if (hero.id === 'yansien') updateYansien(hero, dt);
        else if (hero.id === 'deepseek') updateDeepseek(hero, dt);
        else if (hero.id === 'werewolf') updateWerewolf(hero, dt);
        else if (hero.id === 'newton') updateNewton(hero, dt);
        else if (hero.id === 'knight') updateKnight(hero, dt);
        else if (hero.id === 'pla') updatePla(hero, dt);
        else if (hero.id === 'tangjiaqi') updateTangjiaqi(hero, dt);
        else if (hero.id === 'principal') updatePrincipal(hero, dt);
        else if (hero.id === 'oldpriest') updateOldPriest(hero, dt);
        else if (hero.id === 'dongqing') updateDongqing(hero, dt);
        else if (hero.id === 'sunGod') updateSunGod(hero, dt);
    }

    for (const h of heroEntities) { if (h.pinnedBy === 'wall') { h.wallTimer -= dt * 1000; h.bleedTickTimer -= dt * 1000; if (h.bleedTickTimer <= 0) { h.bleedTickTimer = 500; applyDamage(h, 20, null, null); bleedEffects.push({ x: h.x, y: h.y, life: 0.5 }); } if (h.wallTimer <= 0) { h.pinnedBy = null; h.x += (h.x < ARENA_X + ARENA_WIDTH / 2) ? 30 : -30; constrainToArena(h); } } }

    resolveCollisions(); updateProjectiles(dt); updateEffects(dt);
}

// --- 英雄AI (精简版) ---
function updateTangjiaqi(hero, dt) { if (hero.phase === 0) { hero.x += hero.wanderX * dt; hero.y += hero.wanderY * dt; constrainToArena(hero); } else if (hero.phase === 1) { for (let h of heroEntities) { if (h.id !== hero.id && h.hp > 0) { applyDamage(h, 95 * dt, hero, null, true, true); h.stunTimer = 100; h.speed = 0.1; } } } else { hero.x += hero.wanderX * dt; hero.y += hero.wanderY * dt; constrainToArena(hero); } }
function updatePrincipal(hero, dt) { const e = heroEntities.find(h => h.id !== hero.id && h.hp > 0); if (e) { const dx = e.x - hero.x, dy = e.y - hero.y, dist = Math.hypot(dx, dy) || 1; if (dist > 4) { hero.x += (dx / dist) * hero.speed * dt; hero.y += (dy / dist) * hero.speed * dt; } else if (hero.attackTimer <= 0) { hero.attackTimer = hero.attackCooldown; applyDamage(e, 20, hero, null); e.debuffTimer = 5; addDamageText(e.x, e.y - 20, '欠费!', '#ffd700'); } } if (hero.hp < 300 && !hero.escapeUsed) { hero.escapeUsed = true; hero.x = ARENA_X + Math.random() * ARENA_WIDTH; hero.y = ARENA_Y + Math.random() * ARENA_HEIGHT; hero.hp += 500; addDamageText(hero.x, hero.y - 20, '卷款跑路!', '#ffd700'); } constrainToArena(hero); }
function updateOldPriest(hero, dt) { const e = heroEntities.find(h => h.id !== hero.id && h.hp > 0); if (e) { const dx = e.x - hero.x, dy = e.y - hero.y, dist = Math.hypot(dx, dy) || 1; if (dist > 1.5) { hero.x += (dx / dist) * hero.speed * dt; hero.y += (dy / dist) * hero.speed * dt; } else if (hero.attackTimer <= 0) { hero.attackTimer = hero.attackCooldown; applyDamage(e, 15, hero, null); } } constrainToArena(hero); }
function updateDongqing(hero, dt) { if (hero.tunnelState !== 0) return; const e = heroEntities.find(h => h.id !== hero.id && h.hp > 0); if (e) { const dx = e.x - hero.x, dy = e.y - hero.y, dist = Math.hypot(dx, dy) || 1; if (dist > 1.5) { hero.x += (dx / dist) * hero.speed * dt; hero.y += (dy / dist) * hero.speed * dt; } else if (hero.attackTimer <= 0) { hero.attackTimer = hero.attackCooldown; applyDamage(e, 45, hero, null); } } constrainToArena(hero); }
function updateSunGod(hero, dt) { const e = heroEntities.find(h => h.id !== hero.id && h.hp > 0); if (e) { const dx = e.x - hero.x, dy = e.y - hero.y, dist = Math.hypot(dx, dy) || 1; if (dist > 6) { hero.x += (dx / dist) * hero.speed * dt; hero.y += (dy / dist) * hero.speed * dt; } else if (dist < 3) { hero.x -= (dx / dist) * hero.speed * dt; hero.y -= (dy / dist) * hero.speed * dt; } else if (hero.attackTimer <= 0) { hero.attackTimer = hero.attackCooldown; applyDamage(e, 67, hero, null); if (Math.random() < 0.1) { e.slowTimer = 30; e.dmgReduce = 0.4; addDamageText(e.x, e.y - 20, '困惑!', '#ffb700'); } } } constrainToArena(hero); }

// --- 原有的英雄AI ---
function updateUnknown(hero, dt) { const enemies = heroEntities.filter(h => h.id !== hero.id && h.hp > 0); if (!enemies.length) return; let target = enemies[0], best = Infinity; for (const e of enemies) { const d = Math.hypot(e.x - hero.x, e.y - hero.y); if (d < best) { best = d; target = e; } } const dx = target.x - hero.x, dy = target.y - hero.y, distPx = Math.hypot(dx, dy) || 1, distGrid = distPx / CONFIG.GRID_SIZE, dirX = dx / distPx, dirY = dy / distPx; hero.facingRight = dirX > 0; let moveX = 0, moveY = 0; if (distGrid < 3) { moveX = -dirX * hero.speed; moveY = -dirY * hero.speed; } else if (distGrid > 6) { moveX = dirX * hero.speed * 0.6; moveY = dirY * hero.speed * 0.6; } else { const tx = -dirY, ty = dirX, slide = Math.sin(performance.now() / 1500) > 0 ? 1 : -1; moveX = tx * hero.speed * 0.6 * slide; moveY = ty * hero.speed * 0.6 * slide; } hero.x += (moveX + hero.wanderX * 0.4) * dt; hero.y += (moveY + hero.wanderY * 0.4) * dt; constrainToArena(hero); if (hero.attackTimer > 0) return; if (hero.weaponState === 'pistol') { if (hero.shotsFired < 8) { hero.shotsFired++; const dmg = Math.max(0, Math.round(-0.5 * distGrid * distGrid + 50)); spawnProjectile({ x: hero.x, y: hero.y, startX: hero.x, startY: hero.y, target, speed: 800, damage: dmg, type: 'bullet', ownerId: hero.id, color: hero.color }); hero.attackTimer = 500; } else { hero.weaponState = 'shotgun'; hero.shotgunShots = 0; spawnProjectile({ x: hero.x, y: hero.y, startX: hero.x, startY: hero.y, target, speed: 700, damage: 67, type: 'thrownPistol', ownerId: hero.id }); hero.attackTimer = 800; } } else if (hero.weaponState === 'shotgun') { if (hero.shotgunShots < 2) { hero.shotgunShots++; const baseAngle = Math.atan2(dy, dx); for (let i = 0; i < 6; i++) { const angle = baseAngle + (i - 2.5) * 0.15; spawnProjectile({ x: hero.x, y: hero.y, startX: hero.x, startY: hero.y, vx: Math.cos(angle) * 600, vy: Math.sin(angle) * 600, damage: 25, type: 'shotgunPellet', ownerId: hero.id, lifetime: 0.6, color: '#ffaa00' }); } hero.attackTimer = 1000; if (hero.shotgunShots >= 2) { hero.weaponState = 'pistol'; hero.shotsFired = 0; hero.shotgunShots = 0; } } } }
function updateHanshou(hero, dt) { if (hero.exhaustTimer > 0) { hero.x += hero.wanderX * 0.3 * dt; hero.y += hero.wanderY * 0.3 * dt; constrainToArena(hero); return; } const enemies = heroEntities.filter(h => h.id !== hero.id && h.hp > 0); if (!enemies.length) return; const target = enemies[0]; const dx = target.x - hero.x, dy = target.y - hero.y, distPx = Math.hypot(dx, dy) || 1, distGrid = distPx / CONFIG.GRID_SIZE, dirX = dx / distPx, dirY = dy / distPx; hero.facingRight = dirX > 0; if (hero.hanshouState === 0) { if (distGrid > hero.attackRangeGrid) { hero.x += (dirX * hero.speed + hero.wanderX * 0.5) * dt; hero.y += (dirY * hero.speed + hero.wanderY * 0.5) * dt; constrainToArena(hero); } else { let tangentDir = (Math.sin(performance.now() / 800 + hero.wanderTimer) > 0) ? 1 : -1; hero.x += (-dirY * tangentDir * hero.speed * 0.6 + hero.wanderX * 0.5) * dt; hero.y += ( dirX * tangentDir * hero.speed * 0.6 + hero.wanderY * 0.5) * dt; constrainToArena(hero); if (hero.attackTimer <= 0 && hero.hasSword) { hero.attackTimer = hero.attackCooldown; applyDamage(target, 58, hero, null); hero.dr = Math.min(76, hero.dr + 10); if (hero.dr >= 76) { hero.dr = 0; hero.hanshouState = 1; hero.chargeTimer = 2.0; addDamageText(hero.x, hero.y - 30, '泥头车!', '#ff00ff'); } } } } else if (hero.hanshouState === 1) { hero.chargeTimer -= dt; hero.x += dirX * 200 * dt; hero.y += dirY * 200 * dt; constrainToArena(hero); const ndx = target.x - hero.x, ndy = target.y - hero.y, nd = Math.hypot(ndx, ndy); if (nd <= hero.radius + target.radius + 10) { applyDamage(target, 220, hero, null); hero.hanshouState = 2; hero.pinnedTarget = target; hero.chargeTimer = 4.0; } else if (hero.chargeTimer <= 0) { hero.hanshouState = 0; hero.exhaustTimer = 1500; hero.dr = 0; } } else if (hero.hanshouState === 2) { hero.chargeTimer -= dt; const pt = hero.pinnedTarget; if (!pt || pt.hp <= 0) { hero.hanshouState = 0; hero.exhaustTimer = 2000; hero.pinnedTarget = null; return; } const dL = hero.x - ARENA_X, dR = ARENA_X + ARENA_WIDTH - hero.x, dT = hero.y - ARENA_Y, dB = ARENA_Y + ARENA_HEIGHT - hero.y; const m = Math.min(dL, dR, dT, dB); let px = 0, py = 0; if (m === dL) px = -1; else if (m === dR) px = 1; else if (m === dT) py = -1; else py = 1; hero.x += px * 400 * dt; hero.y += py * 400 * dt; constrainToArena(hero); pt.x = hero.x + px * (hero.radius + pt.radius + 2); pt.y = hero.y + py * (hero.radius + pt.radius + 2); constrainToArena(pt); const targetHitWall = pt.x <= ARENA_X + pt.radius + 1 || pt.x >= ARENA_X + ARENA_WIDTH - pt.radius - 1 || pt.y <= ARENA_Y + pt.radius + 1 || pt.y >= ARENA_Y + ARENA_HEIGHT - pt.radius - 1; if (targetHitWall || hero.chargeTimer <= 0) { pt.pinnedBy = 'wall'; pt.wallTimer = 4000; pt.bleedTickTimer = 0; hero.hanshouState = 0; hero.exhaustTimer = 6000; hero.pinnedTarget = null; hero.dr = 0; hero.hasSword = false; } } }
function updateYansien(hero, dt) { const enemies = heroEntities.filter(h => h.id !== hero.id && h.hp > 0); if (!enemies.length) return; let target = enemies[0], best = Infinity; for (const e of enemies) { const d = Math.hypot(e.x - hero.x, e.y - hero.y); if (d < best) { best = d; target = e; } } const dx = target.x - hero.x, dy = target.y - hero.y, distPx = Math.hypot(dx, dy) || 1, distGrid = distPx / CONFIG.GRID_SIZE, dirX = dx / distPx, dirY = dy / distPx; hero.facingRight = dirX > 0; let moveX = 0, moveY = 0; if (distGrid < 4) { moveX = -dirX * hero.speed * 0.7; moveY = -dirY * hero.speed * 0.7; } else if (distGrid > 4.5) { moveX = dirX * hero.speed * 0.5; moveY = dirY * hero.speed * 0.5; } hero.x += (moveX + hero.wanderX * 0.4) * dt; hero.y += (moveY + hero.wanderY * 0.4) * dt; constrainToArena(hero); if (hero.attackTimer <= 0 && distGrid <= 4.5) { hero.attackTimer = hero.attackCooldown; const isFireExt = Math.random() < 0.1; spawnProjectile({ x: hero.x, y: hero.y, startX: hero.x, startY: hero.y, target, speed: 600, damage: isFireExt ? 240 : 28, type: isFireExt ? 'fireext' : 'pen', ownerId: hero.id, knockback: isFireExt ? 5 : 0 }); } }
function updateDeepseek(hero, dt) { const enemies = heroEntities.filter(h => h.id !== hero.id && h.hp > 0); if (!enemies.length) return; const target = enemies[0]; const dx = target.x - hero.x, dy = target.y - hero.y, distPx = Math.hypot(dx, dy) || 1, distGrid = distPx / CONFIG.GRID_SIZE, dirX = dx / distPx, dirY = dy / distPx; hero.facingRight = dirX > 0; const hasShield = hero.shieldHp > 0; let moveX = 0, moveY = 0; if (hasShield) { if (distGrid < 12) { moveX = -dirX * hero.speed; moveY = -dirY * hero.speed; } } else { if (distGrid < 7) { moveX = -dirX * hero.speed * 0.6; moveY = -dirY * hero.speed * 0.6; } } hero.x += (moveX + hero.wanderX * 0.4) * dt; hero.y += (moveY + hero.wanderY * 0.4) * dt; constrainToArena(hero); if (!hasShield && hero.attackTimer <= 0) { hero.attackTimer = hero.attackCooldown; spawnProjectile({ x: hero.x, y: hero.y, startX: hero.x, startY: hero.y, target, speed: 400, damage: 13, type: 'text', ownerId: hero.id, knockback: 5, text: '对不起,这个问题我还无法回答。' }); } }
function updateWerewolf(hero, dt) { const enemies = heroEntities.filter(h => h.id !== hero.id && h.hp > 0); if (!enemies.length) return; const target = enemies[0]; const dx = target.x - hero.x, dy = target.y - hero.y, distPx = Math.hypot(dx, dy) || 1, distGrid = distPx / CONFIG.GRID_SIZE, dirX = dx / distPx, dirY = dy / distPx; hero.facingRight = dirX > 0; if (!hero.enraged && hero.battleTimer >= 20) { hero.enraged = true; hero.speed *= 1.5; addDamageText(hero.x, hero.y - 30, '狂怒!', '#ff00ff'); } if (hero.isDashing) { hero.dashTimer -= dt; hero.x += hero.dashDirX * 200 * dt; hero.y += hero.dashDirY * 200 * dt; constrainToArena(hero); const nd = Math.hypot(target.x - hero.x, target.y - hero.y); if (nd <= hero.radius + target.radius + 10) { applyDamage(target, 100, hero, null); hero.isDashing = false; hero.dashCooldown = 5000; } else if (hero.dashTimer <= 0) { hero.isDashing = false; hero.dashCooldown = 5000; } return; } if (hero.dashCooldown <= 0 && distGrid > 1.5 && distGrid < 2.5) { hero.isDashing = true; hero.dashTimer = 0.5; hero.dashDirX = dirX; hero.dashDirY = dirY; addDamageText(hero.x, hero.y - 30, '突进!', '#ff4444'); return; } if (distGrid <= 1.5) { let tangentDir = (Math.sin(performance.now() / 800 + hero.wanderTimer) > 0) ? 1 : -1; hero.x += (-dirY * tangentDir * hero.speed * 0.6 + hero.wanderX * 0.5) * dt; hero.y += ( dirX * tangentDir * hero.speed * 0.6 + hero.wanderY * 0.5) * dt; constrainToArena(hero); if (hero.attackTimer <= 0) { hero.attackTimer = hero.attackCooldown; let dmg = hero.enraged ? 45 : 30; if (target.hp > 0 && target.hp / target.maxHp < 0.1) { dmg = target.hp; addDamageText(target.x, target.y - 30, '终结!', '#ff0000'); } applyDamage(target, dmg, hero, null); } } else { hero.x += (dirX * hero.speed + hero.wanderX * 0.5) * dt; hero.y += (dirY * hero.speed + hero.wanderY * 0.5) * dt; constrainToArena(hero); } }
function updateNewton(hero, dt) { const enemies = heroEntities.filter(h => h.id !== hero.id && h.hp > 0); if (!enemies.length) return; const target = enemies[0]; const dx = target.x - hero.x, dy = target.y - hero.y, distPx = Math.hypot(dx, dy) || 1, distGrid = distPx / CONFIG.GRID_SIZE, dirX = dx / distPx, dirY = dy / distPx; hero.facingRight = dirX > 0; let moveX = 0, moveY = 0; if (distGrid < 3) { moveX = -dirX * hero.speed; moveY = -dirY * hero.speed; } else if (distGrid > 6) { moveX = dirX * hero.speed * 0.5; moveY = dirY * hero.speed * 0.5; } else { let tangentDir = (Math.sin(performance.now() / 2000) > 0) ? 1 : -1; moveX = -dirY * tangentDir * hero.speed * 0.8; moveY = dirX * tangentDir * hero.speed * 0.8; } hero.x += (moveX + hero.wanderX * 0.4) * dt; hero.y += (moveY + hero.wanderY * 0.4) * dt; constrainToArena(hero); if (hero.prismCooldown <= 0) { hero.prismCooldown = 5000; let px = hero.x + (target.x - hero.x) * 0.5, py = hero.y + (target.y - hero.y) * 0.5, baseAngle = Math.atan2(target.y - py, target.x - px); newtonPrisms.push({ x: px, y: py, timer: 10000, baseAngle: baseAngle, angle: 0, sweepDir: 1 }); } if (hero.satellites) { hero.satelliteTimer -= dt * 1000; if (hero.satelliteTimer <= 0) { hero.satelliteTimer = 5000; if (Math.random() < 0.3 && hero.satellites.length < 5) hero.satellites.push({ angle: Math.random() * Math.PI * 2 }); } const orbitRadius = 2 * CONFIG.GRID_SIZE, orbitSpeed = 2.5; for (let i = hero.satellites.length - 1; i >= 0; i--) { let sat = hero.satellites[i]; sat.angle += orbitSpeed * dt; let satX = hero.x + Math.cos(sat.angle) * orbitRadius, satY = hero.y + Math.sin(sat.angle) * orbitRadius; let hit = false; for (let h of heroEntities) { if (h.id === 'newton' || h.hp <= 0) continue; if (Math.hypot(h.x - satX, h.y - satY) < h.radius + 10) { applyDamage(h, 40, hero, null); h.stunTimer = 1000; hit = true; break; } } if (hit) hero.satellites.splice(i, 1); } } }
function updateKnight(hero, dt) { const enemies = heroEntities.filter(h => h.id !== hero.id && h.hp > 0); if (!enemies.length) return; const target = enemies[0]; const dx = target.x - hero.x, dy = target.y - hero.y, distPx = Math.hypot(dx, dy) || 1, distGrid = distPx / CONFIG.GRID_SIZE, dirX = dx / distPx, dirY = dy / distPx; hero.facingRight = dirX > 0; if (hero.isCharging) { hero.chargeTimer -= dt; hero.x += hero.chargeDirX * 300 * dt; hero.y += hero.chargeDirY * 300 * dt; constrainToArena(hero); const nd = Math.hypot(target.x - hero.x, target.y - hero.y); if (nd <= hero.radius + target.radius + 10) { applyDamage(target, 50, hero, null); target.x += (target.x - hero.x) / nd * 1.5 * CONFIG.GRID_SIZE; target.y += (target.y - hero.y) / nd * 1.5 * CONFIG.GRID_SIZE; constrainToArena(target); hero.isCharging = false; hero.chargeCooldown = 8000; } else if (hero.chargeTimer <= 0) { hero.isCharging = false; hero.chargeCooldown = 8000; } return; } if (hero.chargeCooldown <= 0 && distGrid > 2 && distGrid < 3) { hero.isCharging = true; hero.chargeTimer = 0.6; hero.chargeDirX = dirX; hero.chargeDirY = dirY; return; } if (distGrid <= 1.5) { let tangentDir = (Math.sin(performance.now() / 800 + hero.wanderTimer) > 0) ? 1 : -1; hero.x += (-dirY * tangentDir * hero.speed * 0.6 + hero.wanderX * 0.5) * dt; hero.y += ( dirX * tangentDir * hero.speed * 0.6 + hero.wanderY * 0.5) * dt; constrainToArena(hero); if (hero.attackTimer <= 0) { hero.attackTimer = hero.attackCooldown; applyDamage(target, hero.attackDamage, hero, null); hero.shieldHp = Math.min(hero.maxShieldHp, hero.shieldHp + 15); } } else { hero.x += (dirX * hero.speed + hero.wanderX * 0.5) * dt; hero.y += (dirY * hero.speed + hero.wanderY * 0.5) * dt; constrainToArena(hero); } }
function updatePla(hero, dt) { const enemies = heroEntities.filter(h => h.id !== hero.id && h.hp > 0 && h.id !== 'dummy'); if (!enemies.length) return; let target = enemies[0], best = Infinity; for (const e of enemies) { const d = Math.hypot(e.x - hero.x, e.y - hero.y); if (d < best) { best = d; target = e; } } const dx = target.x - hero.x, dy = target.y - hero.y, distPx = Math.hypot(dx, dy) || 1, distGrid = distPx / CONFIG.GRID_SIZE, dirX = dx / distPx, dirY = dy / distPx; hero.facingRight = dirX > 0; let moveX = 0, moveY = 0; if (distGrid < 4) { moveX = -dirX * hero.speed; moveY = -dirY * hero.speed; } else if (distGrid > 7) { moveX = dirX * hero.speed * 0.6; moveY = dirY * hero.speed * 0.6; } else { let tangentDir = (Math.sin(performance.now() / 1500) > 0) ? 1 : -1; moveX = -dirY * tangentDir * hero.speed * 0.6; moveY = dirX * tangentDir * hero.speed * 0.6; } hero.x += (moveX + hero.wanderX * 0.4) * dt; hero.y += (moveY + hero.wanderY * 0.4) * dt; constrainToArena(hero); if (!hero.isReloading && hero.attackTimer <= 0 && distGrid <= hero.attackRangeGrid && hero.clipAmmo > 0) { hero.clipAmmo--; hero.attackTimer = hero.attackCooldown; let finalDmg = Math.round((hero.baseDamage + hero.damageBuffAdd) * hero.damageMult); spawnProjectile({ x: hero.x, y: hero.y, startX: hero.x, startY: hero.y, target, speed: 1000, damage: finalDmg, type: 'bullet', ownerId: hero.id, color: '#ffff00' }); if (Math.random() < 0.1) grenades.push({ x: hero.x + dirX * 20, y: hero.y + dirY * 20, vx: dirX * 300, vy: dirY * 300, timer: 0.8 }); if (hero.clipAmmo <= 0) { hero.isReloading = true; hero.reloadTimer = 500; } } }

function resolveCollisions() { for (let i = 0; i < heroEntities.length; i++) { for (let j = i + 1; j < heroEntities.length; j++) { const h1 = heroEntities[i], h2 = heroEntities[j]; if (h1.hp <= 0 || h2.hp <= 0 || h1.isDashing || h2.isDashing || h1.isCharging || h2.isCharging) continue; if (h1.id === 'hanshou' && (h1.hanshouState === 1 || h1.hanshouState === 2)) continue; if (h2.id === 'hanshou' && (h2.hanshouState === 1 || h2.hanshouState === 2)) continue; let dx = h2.x - h1.x, dy = h2.y - h1.y, dist = Math.hypot(dx, dy), minDist = h1.radius + h2.radius; if (dist < 0.01) { dx = 0.01; dy = 0; dist = 0.01; } if (dist < minDist) { const overlap = minDist - dist, nx = dx / dist, ny = dy / dist; h1.x -= nx * overlap * 0.5; h1.y -= ny * overlap * 0.5; h2.x += nx * overlap * 0.5; h2.y += ny * overlap * 0.5; } } } for (const h of heroEntities) constrainToArena(h); }

function applyDamage(target, amount, attacker, projectile, isTrueDamage = false, silent = false) {
    if (!target || target.hp <= 0) return { absorbed: false, killed: false };
    if (!isFinite(amount)) amount = 0; amount = Math.max(0, amount);
    if (target.id === 'tangjiaqi' && target.phase === 0 && target.pearls > 0 && !isTrueDamage) { target.pearls--; target.x = ARENA_X + Math.random() * ARENA_WIDTH; target.y = ARENA_Y + Math.random() * ARENA_HEIGHT; constrainToArena(target); addDamageText(target.x, target.y - 20, '闪避!', '#ffffff'); return { absorbed: true, killed: false }; }
    if (!isTrueDamage) {
        if (target.id === 'deepseek' && target.shieldHp <= 0 && target.shieldCooldown <= 0) { target.shieldHp = target.maxShieldHp; }
        if (target.id === 'deepseek' && target.shieldHp > 0) { target.shieldHp -= amount; if (target.shieldHp <= 0) { target.shieldHp = 0; target.shieldCooldown = 60000; } if (attacker && attacker.hp > 0 && attacker.id !== 'deepseek' && projectile) { spawnProjectile({ x: target.x, y: target.y, startX: target.x, startY: target.y, target: attacker, speed: 800, damage: Math.round(amount * 0.5), type: 'bullet', ownerId: target.id, color: '#4488ff' }); } return { absorbed: true, killed: false }; }
        if (target.id === 'knight' && target.shieldHp > 0) { target.shieldHp -= amount; if (target.shieldHp < 0) { target.hp += target.shieldHp; target.shieldHp = 0; } return { absorbed: true, killed: false }; }
        if (target.dr > 0) amount = amount * (1 - target.dr / 100);
        if (target.id === 'sunGod' && target.dmgReduce > 0) amount *= (1 - target.dmgReduce);
        if (target.debuffTimer > 0) { amount *= 0.8; }
    }
    amount = Math.round(amount); target.hp -= amount;
    if (!silent) addDamageText(target.x, target.y, amount, isTrueDamage ? '#ff00ff' : '#ff6666');
    if (target.id === 'hanshou' && target.exhaustTimer <= 0 && target.hanshouState === 0) target.dr = Math.max(0, target.dr - 5);
    return { absorbed: false, killed: target.hp <= 0 };
}

function spawnProjectile(opts) { projectiles.push({ x: 0, y: 0, startX: 0, startY: 0, target: null, speed: 600, damage: 0, type: 'bullet', ownerId: null, vx: undefined, vy: undefined, lifetime: undefined, knockback: 0, color: null, text: null, ...opts }); }
function updateProjectiles(dt) { for (let i = projectiles.length - 1; i >= 0; i--) { const p = projectiles[i]; if (p.target && p.target.hp <= 0) { projectiles.splice(i, 1); continue; } let hitTarget = null; if (p.vx !== undefined) { p.x += p.vx * dt; p.y += p.vy * dt; p.lifetime -= dt; if (p.lifetime <= 0) { projectiles.splice(i, 1); continue; } for (const h of heroEntities) { if (h.hp <= 0 || h.id === p.ownerId) continue; if (Math.hypot(h.x - p.x, h.y - p.y) < h.radius + 8) { hitTarget = h; break; } } } else if (p.target) { const t = p.target, pdx = t.x - p.x, pdy = t.y - p.y, pDist = Math.hypot(pdx, pdy) || 1; if (pDist < t.radius + 8) hitTarget = t; else { p.x += (pdx / pDist) * p.speed * dt; p.y += (pdy / pDist) * p.speed * dt; } } if (hitTarget) { const attacker = p.ownerId ? heroEntities.find(h => h.id === p.ownerId) : null; const result = applyDamage(hitTarget, p.damage, attacker, p); if (!result.absorbed) { if (p.knockback > 0) { hitTarget.x += (hitTarget.x - p.x) / pDist * p.knockback * CONFIG.GRID_SIZE; hitTarget.y += (hitTarget.y - p.y) / pDist * p.knockback * CONFIG.GRID_SIZE; constrainToArena(hitTarget); } } projectiles.splice(i, 1); } } 
    for (let i = projectiles.length - 1; i >= 0; i--) { let p = projectiles[i]; if (p.ownerId === 'pla') continue; for (let c of civilians) { if (c.hp <= 0) continue; if (Math.hypot(c.x - p.x, c.y - p.y) < c.radius + 8) { c.hp -= p.damage; c.isFleeing = true; c.fleeTimer = 3; let pla = heroEntities.find(h => h.id === 'pla' && h.hp > 0); if (pla) { pla.damageBuffAdd += 12; pla.damageBuffTimer = 30000; if (c.hp <= 0) { pla.damageMult *= 2; addDamageText(pla.x, pla.y - 70, '人民阵亡! 伤害翻倍!', '#ff0000'); } } projectiles.splice(i, 1); break; } } } }

function updateEffects(dt) { for (let i = muzzleFlashes.length - 1; i >= 0; i--) { muzzleFlashes[i].life -= dt; if (muzzleFlashes[i].life <= 0) muzzleFlashes.splice(i, 1); } for (let i = slashEffects.length - 1; i >= 0; i--) { slashEffects[i].life -= dt; if (slashEffects[i].life <= 0) slashEffects.splice(i, 1); } for (let i = impactEffects.length - 1; i >= 0; i--) { impactEffects[i].life -= dt; if (impactEffects[i].life <= 0) impactEffects.splice(i, 1); } for (let i = damageTexts.length - 1; i >= 0; i--) { damageTexts[i].y -= 30 * dt; damageTexts[i].life -= dt; if (damageTexts[i].life <= 0) damageTexts.splice(i, 1); } for (let i = bleedEffects.length - 1; i >= 0; i--) { bleedEffects[i].life -= dt; if (bleedEffects[i].life <= 0) bleedEffects.splice(i, 1); } }
function constrainToArena(h) { if (!isFinite(h.x)) h.x = ARENA_X + ARENA_WIDTH / 2; if (!isFinite(h.y)) h.y = ARENA_Y + ARENA_HEIGHT / 2; if (h.x < ARENA_X + h.radius) { h.x = ARENA_X + h.radius; h.wanderAngle += Math.PI; } if (h.x > ARENA_X + ARENA_WIDTH - h.radius) { h.x = ARENA_X + ARENA_WIDTH - h.radius; h.wanderAngle += Math.PI; } if (h.y < ARENA_Y + h.radius) { h.y = ARENA_Y + h.radius; h.wanderAngle += Math.PI; } if (h.y > ARENA_Y + ARENA_HEIGHT - h.radius) { h.y = ARENA_Y + ARENA_HEIGHT - h.radius; h.wanderAngle += Math.PI; } }
function addDamageText(x, y, amount, color) { damageTexts.push({ x, y: y - 20, text: String(amount), color: color || '#fff', life: 1.0 }); }
function addImpact(x, y, color, maxLife) { maxLife = maxLife || 0.3; impactEffects.push({ x, y, color, life: maxLife, maxLife }); }
function showGameOver(survivors) { gameOverScreen.classList.remove('hidden'); canvas.classList.add('hidden'); if (survivors.length === 1) { gameOverText.innerText = survivors[0].name + ' 胜利!'; gameOverText.style.color = survivors[0].color; } else { gameOverText.innerText = '同归于尽!'; gameOverText.style.color = '#fff'; } }

// --- 渲染 ---
function render() { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.save(); ctx.translate(-camera.x, -camera.y); drawArena(); drawApples(); drawNewtonPrisms(); drawEffects(); drawGrenades(); drawProjectiles(); drawEntities(); drawCivilians(); drawMinions(); drawTunnels(); drawDamageTexts(); ctx.restore(); }
function drawArena() { ctx.fillStyle = '#2a2a2a'; ctx.fillRect(0, 0, MAP_WIDTH, MAP_HEIGHT); ctx.fillStyle = '#3a3a3a'; ctx.fillRect(ARENA_X, ARENA_Y, ARENA_WIDTH, ARENA_HEIGHT); ctx.strokeStyle = '#00ffcc'; ctx.lineWidth = 6; ctx.strokeRect(ARENA_X, ARENA_Y, ARENA_WIDTH, ARENA_HEIGHT); ctx.strokeStyle = 'rgba(255, 255, 255, 0.05)'; ctx.lineWidth = 1; ctx.beginPath(); for (let i = 1; i < CONFIG.GRID_COLS; i++) { const x = ARENA_X + i * CONFIG.GRID_SIZE; ctx.moveTo(x, ARENA_Y); ctx.lineTo(x, ARENA_Y + ARENA_HEIGHT); } for (let i = 1; i < CONFIG.GRID_ROWS; i++) { const y = ARENA_Y + i * CONFIG.GRID_SIZE; ctx.moveTo(ARENA_X, y); ctx.lineTo(ARENA_X + ARENA_WIDTH, y); } ctx.stroke(); }
function drawApples() { for (let a of apples) { ctx.beginPath(); ctx.arc(a.x, a.y, a.radius, 0, Math.PI * 2); ctx.fillStyle = '#ff4444'; ctx.fill(); ctx.strokeStyle = '#ff0000'; ctx.lineWidth = 2; ctx.stroke(); } }
function drawNewtonPrisms() { for (let p of newtonPrisms) { ctx.save(); ctx.translate(p.x, p.y); ctx.rotate(p.baseAngle); ctx.beginPath(); ctx.moveTo(0, 0); ctx.arc(0, 0, 240, -Math.PI / 6 + p.angle, Math.PI / 6 + p.angle); ctx.closePath(); ctx.fillStyle = 'rgba(255, 255, 255, 0.08)'; ctx.fill(); for (let i = 0; i < 7; i++) { let angle = -Math.PI / 6 + p.angle + (i / 6) * (Math.PI / 3); ctx.beginPath(); ctx.moveTo(0, 0); ctx.lineTo(Math.cos(angle) * 240, Math.sin(angle) * 240); ctx.strokeStyle = `hsl(${i * 51}, 100%, 50%)`; ctx.lineWidth = 2; ctx.stroke(); } ctx.restore(); } }
function drawGrenades() { for (let g of grenades) { ctx.beginPath(); ctx.arc(g.x, g.y, 8, 0, Math.PI * 2); ctx.fillStyle = '#333'; ctx.fill(); ctx.strokeStyle = '#ff8800'; ctx.lineWidth = 3; ctx.stroke(); } }
function drawCivilians() { for (let c of civilians) { if (c.hp <= 0) continue; ctx.beginPath(); ctx.arc(c.x, c.y, c.radius, 0, Math.PI * 2); ctx.fillStyle = '#ffffff'; ctx.fill(); ctx.strokeStyle = '#aaaaaa'; ctx.lineWidth = 2; ctx.stroke(); ctx.fillStyle = '#000'; ctx.font = '10px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('人民', c.x, c.y + 3); } }
function drawMinions() { for (let m of minions) { ctx.beginPath(); ctx.arc(m.x, m.y, m.radius, 0, Math.PI * 2); ctx.fillStyle = '#ffd700'; ctx.fill(); ctx.strokeStyle = '#b8860b'; ctx.lineWidth = 2; ctx.stroke(); ctx.fillStyle = '#000'; ctx.font = '8px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('家长', m.x, m.y + 3); } }
function drawTunnels() { for (let t of tunnels) { ctx.beginPath(); ctx.arc(t.x, t.y, 12, 0, Math.PI * 2); ctx.fillStyle = 'rgba(0,0,0,0.5)'; ctx.fill(); ctx.strokeStyle = '#2e8b57'; ctx.lineWidth = 2; ctx.stroke(); } }
function drawDivineCircles() { for (let c of divineCircles) { ctx.beginPath(); ctx.arc(c.x, c.y, c.radius, 0, Math.PI * 2); ctx.strokeStyle = `rgba(255, 215, 0, ${c.timer / 8})`; ctx.lineWidth = 4; ctx.stroke(); ctx.fillStyle = `rgba(255, 215, 0, 0.1)`; ctx.fill(); } }

function drawEffects() { muzzleFlashes.forEach(e => { const alpha = Math.max(0, Math.min(1, e.life / (e.maxLife || 0.1))); ctx.beginPath(); ctx.arc(e.x, e.y, 8, 0, Math.PI * 2); ctx.fillStyle = `rgba(255, 255, 255, ${alpha})`; ctx.fill(); }); slashEffects.forEach(e => { ctx.save(); ctx.translate(e.x, e.y); ctx.rotate(e.angle); const alpha = Math.max(0, Math.min(1, e.life / (e.maxLife || 0.3))); ctx.beginPath(); ctx.arc(0, 0, 35, -Math.PI / 4, Math.PI / 4); ctx.strokeStyle = e.color ? `rgba(255, 255, 255, ${alpha})` : `rgba(255, 68, 68, ${alpha})`; if (e.color) ctx.strokeStyle = e.color.replace(')', `, ${alpha})`).replace('rgb', 'rgba'); ctx.lineWidth = 6; ctx.stroke(); ctx.restore(); }); impactEffects.forEach(e => { const t = 1 - Math.max(0, Math.min(1, e.life / (e.maxLife || 0.3))); const r = Math.max(0.5, t * 20); ctx.beginPath(); ctx.arc(e.x, e.y, r, 0, Math.PI * 2); ctx.strokeStyle = e.color || '#fff'; ctx.lineWidth = 3; ctx.stroke(); }); bleedEffects.forEach(e => { const alpha = Math.max(0, Math.min(1, e.life / 0.5)); ctx.beginPath(); ctx.arc(e.x, e.y, 6, 0, Math.PI * 2); ctx.fillStyle = `rgba(255, 0, 0, ${alpha})`; ctx.fill(); }); drawDivineCircles(); }

function drawProjectiles() { for (const p of projectiles) { if (p.type === 'text') { ctx.fillStyle = 'rgba(255, 183, 0, 0.2)'; ctx.fillRect(p.x - 120, p.y - 15, 240, 30); ctx.strokeStyle = '#ffb700'; ctx.lineWidth = 2; ctx.strokeRect(p.x - 120, p.y - 15, 240, 30); ctx.fillStyle = '#fff'; ctx.font = '11px sans-serif'; ctx.textAlign = 'center'; ctx.fillText(p.text || '', p.x, p.y + 5); } else if (p.type === 'pen') { ctx.save(); ctx.translate(p.x, p.y); if (p.target) ctx.rotate(Math.atan2(p.target.y - p.startY, p.target.x - p.startX)); ctx.fillStyle = '#ccc'; ctx.fillRect(-15, -2, 30, 4); ctx.restore(); } else if (p.type === 'fireext') { ctx.fillStyle = '#ff0000'; ctx.fillRect(p.x - 8, p.y - 12, 16, 24); ctx.fillStyle = '#fff'; ctx.font = '8px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('灭火器', p.x, p.y + 4); } else if (p.type === 'shotgunPellet') { ctx.beginPath(); ctx.arc(p.x, p.y, 4, 0, Math.PI * 2); ctx.fillStyle = '#ffaa00'; ctx.fill(); } else if (p.color === '#ffff00') { ctx.save(); ctx.translate(p.x, p.y); if (p.target) ctx.rotate(Math.atan2(p.target.y - p.startY, p.target.x - p.startX)); ctx.fillStyle = '#ffff00'; ctx.fillRect(-10, -2, 20, 4); ctx.restore(); } else { ctx.beginPath(); ctx.moveTo(p.startX, p.startY); ctx.lineTo(p.x, p.y); ctx.strokeStyle = p.color || 'rgba(0, 255, 204, 0.8)'; ctx.lineWidth = 5; ctx.stroke(); ctx.beginPath(); ctx.arc(p.x, p.y, 6, 0, Math.PI * 2); ctx.fillStyle = '#fff'; ctx.fill(); ctx.strokeStyle = p.color || '#00ffcc'; ctx.lineWidth = 2; ctx.stroke(); } } }

function drawEntities() {
    for (const entity of heroEntities) {
        ctx.globalAlpha = entity.hp > 0 ? 1.0 : 0.2;
        let scale = 1.0; if (entity.attackAnimTimer > 0) scale = 1.0 + (entity.attackAnimTimer / 300) * 0.3;
        let fillColor = entity.color;
        if (entity.id === 'hanshou') { if (entity.exhaustTimer > 0) fillColor = '#555555'; else if (entity.hanshouState === 1) fillColor = '#ff00ff'; else fillColor = '#ffff00'; }
        if (entity.stunTimer > 0) fillColor = '#ffff00';
        if (entity.id === 'werewolf' && entity.enraged) fillColor = '#cc0000';
        if (entity.id === 'knight' && entity.isCharging) fillColor = '#ffcc00';
        if (entity.id === 'tangjiaqi' && entity.phase === 1) fillColor = '#ffffff';
        ctx.save(); ctx.translate(entity.x, entity.y); if (!entity.facingRight) ctx.scale(-1, 1); ctx.scale(scale, scale);
        if (entity.id === 'pla') { ctx.beginPath(); ctx.arc(0, 0, entity.radius, 0, Math.PI * 2); ctx.fillStyle = '#4a5d23'; ctx.fill(); ctx.strokeStyle = '#ffcc00'; ctx.lineWidth = 3; ctx.stroke(); ctx.fillStyle = '#ffcc00'; ctx.font = 'bold 12px sans-serif'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText('PLA', 0, 1); } 
        else if (entity.id === 'principal') { ctx.beginPath(); ctx.arc(0, 0, entity.radius, 0, Math.PI * 2); ctx.fillStyle = '#ffd700'; ctx.fill(); ctx.strokeStyle = '#b8860b'; ctx.lineWidth = 3; ctx.stroke(); ctx.fillStyle = '#000'; ctx.font = 'bold 10px sans-serif'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText('校长', 0, 1); }
        else if (entity.id === 'oldpriest') { ctx.beginPath(); ctx.arc(0, 0, entity.radius, 0, Math.PI * 2); ctx.fillStyle = '#8a2be2'; ctx.fill(); ctx.strokeStyle = '#fff'; ctx.lineWidth = 2; ctx.stroke(); ctx.fillStyle = '#fff'; ctx.fillRect(-2, -10, 4, 20); ctx.fillRect(-8, -4, 16, 4); }
        else if (entity.id === 'dongqing') { ctx.beginPath(); ctx.arc(0, 0, entity.radius, 0, Math.PI * 2); ctx.fillStyle = '#2e8b57'; ctx.fill(); ctx.strokeStyle = '#0f0'; ctx.lineWidth = 2; ctx.stroke(); }
        else if (entity.id === 'sunGod') { ctx.beginPath(); ctx.arc(0, 0, entity.radius, 0, Math.PI * 2); ctx.fillStyle = '#ffb700'; ctx.fill(); ctx.strokeStyle = '#fff'; ctx.lineWidth = 2; ctx.stroke(); ctx.fillStyle = '#fff'; ctx.beginPath(); ctx.arc(0, 0, 6, 0, Math.PI * 2); ctx.fill(); }
        else if (entity.id === 'tangjiaqi') { ctx.beginPath(); ctx.arc(0, 0, entity.radius, 0, Math.PI * 2); ctx.fillStyle = '#ffffff'; ctx.fill(); ctx.strokeStyle = '#aaa'; ctx.lineWidth = 2; ctx.stroke(); for(let i=0;i<entity.pearls;i++) { let a = (i / 10) * Math.PI * 2; ctx.beginPath(); ctx.arc(Math.cos(a)*20, Math.sin(a)*20, 3, 0, Math.PI*2); ctx.fillStyle = '#00ffff'; ctx.fill(); } }
        else { ctx.beginPath(); ctx.arc(0, 0, entity.radius, 0, Math.PI * 2); ctx.fillStyle = fillColor; ctx.fill(); ctx.strokeStyle = 'rgba(255,255,255,0.8)'; ctx.lineWidth = 2; ctx.stroke(); }
        if (entity.id === 'hanshou' && entity.hasSword) { ctx.save(); ctx.fillStyle = entity.hanshouState === 1 ? '#ffcc00' : '#dddddd'; ctx.fillRect(entity.radius - 2, -3, 20, 6); ctx.restore(); }
        if (entity.id === 'werewolf') { ctx.beginPath(); ctx.moveTo(-8, -entity.radius); ctx.lineTo(-14, -entity.radius - 12); ctx.lineTo(-2, -entity.radius); ctx.fill(); ctx.beginPath(); ctx.moveTo(8, -entity.radius); ctx.lineTo(14, -entity.radius - 12); ctx.lineTo(2, -entity.radius); ctx.fill(); }
        if (entity.id === 'unknown') { ctx.fillStyle = '#666'; if (entity.weaponState === 'pistol') { ctx.fillRect(entity.radius - 2, -5, 12, 10); } else if (entity.weaponState === 'shotgun') { ctx.fillRect(entity.radius - 2, -7, 22, 14); } }
        if (entity.id === 'knight') { ctx.fillStyle = '#aaa'; ctx.fillRect(entity.radius - 2, -12, 8, 24); }
        ctx.restore();

        const barWidth = 50, barHeight = 5, barX = entity.x - barWidth / 2, barY = entity.y - entity.radius - 15, hpPercent = Math.max(0, entity.hp) / entity.maxHp;
        ctx.fillStyle = '#333'; ctx.fillRect(barX, barY, barWidth, barHeight); ctx.fillStyle = hpPercent > 0.3 ? '#00ff00' : '#ff0000'; ctx.fillRect(barX, barY, barWidth * hpPercent, barHeight);
        if (entity.id === 'tangjiaqi') { const pY = barY + barHeight + 2; ctx.fillStyle = '#333'; ctx.fillRect(barX, pY, barWidth, 4); ctx.fillStyle = '#ff00ff'; ctx.fillRect(barX, pY, barWidth * (entity.timer / (entity.phase === 0 ? 30 : 10)), 4); }
        if (entity.id === 'oldpriest') { const wY = barY + barHeight + 2; ctx.fillStyle = '#333'; ctx.fillRect(barX, wY, barWidth, 4); ctx.fillStyle = '#ffd700'; ctx.fillRect(barX, wY, barWidth * (entity.wrathTimer / 15), 4); }
        ctx.fillStyle = '#fff'; ctx.font = '11px sans-serif'; ctx.textAlign = 'center'; ctx.fillText(entity.name, entity.x, barY - 5);
    }
}
function drawDamageTexts() { for (const txt of damageTexts) { ctx.fillStyle = txt.color; ctx.font = 'bold 14px sans-serif'; ctx.textAlign = 'center'; ctx.globalAlpha = Math.max(0, Math.min(1, txt.life)); ctx.fillText(txt.text, txt.x, txt.y); } ctx.globalAlpha = 1.0; }
})();
</script>
</body>
</html>

Game Source: 电子斗蛐蛐 - 神仙大乱斗

Creator: EpicCoder88

Libraries: none

Complexity: complex (310 lines, 56.3 KB)

The full source code is displayed above on this page.

Remix Instructions

To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: game-epiccoder88-mugzyi4k" to link back to the original. Then publish at arcadelab.ai/publish.