🎮ArcadeLab

电子斗蛐蛐 - 学科大战(诸神黄昏版)

by EpicCoder88
1977 lines90.8 KB
▶ Play
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<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: 12px; flex-wrap: wrap; justify-content: center; max-width: 90vw; }
    .hero-card { padding: 10px 20px; background-color: #2a2a2a; border: 2px solid #444; border-radius: 8px; font-size: 15px; 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); }
    .hero-card.locked { opacity: 0.4; cursor: not-allowed; }
    #confirmBtn { padding: 14px 50px; font-size: 20px; background-color: #4CAF50; color: white; border: none; border-radius: 8px; cursor: pointer; transition: all 0.3s; box-shadow: 0 4px 6px rgba(0,0,0,0.3); font-weight: bold; }
    #confirmBtn:hover:not(:disabled) { background-color: #45a049; transform: scale(1.05); }
    #confirmBtn:disabled { background-color: #444; color: #888; cursor: not-allowed; }
    #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; }
    #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>选择 2~5 名英雄进行混战</h2>
    <div id="heroList"></div>
    <button id="confirmBtn" disabled>至少选 2 名英雄</button>
</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;
const ARENA_Y = CONFIG.PADDING;
const MAX_DT = 0.05;
const WANDER_SPEED  = 1.5;
const WANDER_CHANGE = 1.5;

const HERO_LIST = [
    { id: 'newton',     name: '牛顿',   color: '#dddddd', maxHp: 1100, speed: 120 },
    { id: 'lavoisier',  name: '拉瓦锡', color: '#ff8c00', maxHp: 950,  speed: 115 },
    { id: 'mendel',     name: '孟德尔', color: '#33cc33', maxHp: 850,  speed: 105 },
    { id: 'taylor',     name: '泰勒',   color: '#3366ff', maxHp: 800,  speed: 110 },
    { id: 'starling',   name: '斯他林', color: '#ff44cc', maxHp: 900,  speed: 115 },
    { id: 'faraday',    name: '法拉第', color: '#ffd700', maxHp: 1000, speed: 110 },
    { id: 'hawking',    name: '霍金',   color: '#8a2be2', maxHp: 900,  speed: 100 },
    { id: 'kepler',     name: '开普勒', color: '#4169e1', maxHp: 900,  speed: 110 },
    { id: 'zuchongzhi', name: '祖冲之', color: '#00ced1', maxHp: 850,  speed: 108 },
    { id: 'schrodinger',name: '薛定谔', color: '#9370db', maxHp: 800,  speed: 115 },
    { id: 'qianxuesen', name: '钱学森', color: '#FF4500', maxHp: 1100, speed: 115 },
    { id: 'hooke',      name: '胡克',   color: '#A0522D', maxHp: 900,  speed: 112 },
    { id: 'curie',      name: '居里',   color: '#00FF7F', maxHp: 1020, speed: 110 },
    { id: 'houdebang',  name: '侯德榜', color: '#FF69B4', maxHp: 940,  speed: 113 }
];
const HERO_MAP = {};
HERO_LIST.forEach(h => { HERO_MAP[h.id] = h; });

let gameState = 'menu';
let selectedHeroes = [];
let heroEntities  = [];
let projectiles   = [];
let damageTexts   = [];
let impactEffects = [];
let newtonPrisms  = [];
let apples        = [];
let oxygenFields  = [];
let massParticles = [];
let peaShooters   = [];
let hormonePools  = [];
let geneSeeds     = [];
let wormholes     = [];
let faradayCages  = [];
let radiationZones = [];
let keplerPlanets = [];
let cutCircleBullets = [];
let piShields     = [];
let schrodingerClones = [];
let catBoxes      = [];
let missiles      = [];
let bombardZones  = [];
let springOscillators = [];
let blackHoles    = [];
let radiumBlocks  = [];
let droppedItems  = [];
let lastTime = 0;
let rafId = 0;
const camera = { x: 0, y: 0 };

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

startBtn.addEventListener('click', () => {
    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();
});

confirmBtn.addEventListener('click', () => {
    if (gameState !== 'select' || selectedHeroes.length < 2) return;
    gameState = 'starting';
    startBattle();
});

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);
    });
    updateConfirmButton();
}

function toggleHeroSelection(id, card) {
    if (gameState !== 'select') return;
    if (selectedHeroes.includes(id)) {
        selectedHeroes = selectedHeroes.filter(x => x !== id);
        card.classList.remove('selected');
    } else {
        if (selectedHeroes.length >= 5) return;
        selectedHeroes.push(id);
        card.classList.add('selected');
    }
    const allCards = heroList.querySelectorAll('.hero-card');
    allCards.forEach(c => {
        const cid = c.dataset.id;
        if (selectedHeroes.length >= 5 && !selectedHeroes.includes(cid)) c.classList.add('locked');
        else c.classList.remove('locked');
    });
    updateConfirmButton();
}

function updateConfirmButton() {
    if (selectedHeroes.length >= 2) {
        confirmBtn.disabled = false;
        confirmBtn.textContent = '开始对决 (' + selectedHeroes.length + ' 人混战)';
    } else {
        confirmBtn.disabled = true;
        confirmBtn.textContent = '至少选 2 名英雄';
    }
}

function resetEffects() {
    projectiles = []; damageTexts = []; impactEffects = [];
    newtonPrisms = []; apples = []; oxygenFields = [];
    massParticles = []; peaShooters = []; hormonePools = []; geneSeeds = [];
    wormholes = []; faradayCages = []; radiationZones = [];
    keplerPlanets = []; cutCircleBullets = []; piShields = [];
    schrodingerClones = []; catBoxes = []; missiles = []; bombardZones = [];
    springOscillators = []; blackHoles = []; radiumBlocks = []; droppedItems = [];
}
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;

    const total = selectedHeroes.length;
    heroEntities = selectedHeroes.map((id, index) => createHero(id, index, total));
    resetEffects();
    lastTime = performance.now();
    cancelAnimationFrame(rafId);
    rafId = requestAnimationFrame(gameLoop);
}

function createHero(id, index, total) {
    const data = HERO_MAP[id];
    const hero = {
        id: id, name: data.name, color: data.color,
        hp: data.maxHp, maxHp: data.maxHp,
        radius: 20, speed: data.speed, baseSpeed: data.speed,
        x: 0, y: 0,
        facingRight: true,
        stunTimer: 0, wallPushTimer: 0, wallPushX: 0, wallPushY: 0,
        wanderAngle: Math.random() * Math.PI * 2,
        wanderTimer: 0, wanderX: 0, wanderY: 0,
        attackTimer: 0, attackAnimTimer: 0, slowTimer: 0,
        attackPower: 1, dr: 0,
        chargeStacks: 0, chargeTimer: 0,
        radiationStacks: 0, radiationTimer: 0,
        lowHpTimer: 0, originalHp: 0,
        permanentSlow: 0
    };

    const startAngle = -Math.PI / 2;
    const angle = startAngle + (index / total) * Math.PI * 2;
    const radius = Math.min(ARENA_WIDTH, ARENA_HEIGHT) * 0.33;
    const cx = ARENA_X + ARENA_WIDTH / 2;
    const cy = ARENA_Y + ARENA_HEIGHT / 2;
    hero.x = cx + Math.cos(angle) * radius;
    hero.y = cy + Math.sin(angle) * radius;

    if (id === 'newton') {
        hero.prismCooldown = 5000; hero.appleTimer = 30000;
        hero.satellites = []; hero.satelliteTimer = 5000;
    } else if (id === 'lavoisier') {
        hero.oxygenCooldown = 6000; hero.preciseStacks = 0; hero.ultCooldown = 12000;
    } else if (id === 'mendel') {
        hero.attackCooldown = 2000; hero.peaTimer = 8000; hero.geneSeed = 0;
    } else if (id === 'taylor') {
        hero.attackCooldown = 1500; hero.taylorMark = 0; hero.taylorMarkTimer = 0; hero.ultCooldown = 0;
    } else if (id === 'starling') {
        hero.attackCooldown = 2000; hero.hormoneStormCooldown = 15000; hero.overdriveTimer = 0;
    } else if (id === 'faraday') {
        hero.attackCooldown = 1800; hero.shieldCooldown = 8000; hero.shieldTimer = 0; hero.cageCooldown = 15000;
    } else if (id === 'hawking') {
        hero.attackCooldown = 2000; hero.wheelchairCooldown = 2000; hero.wheelchairTimer = 0;
        hero.wheelchairDirX = 0; hero.wheelchairDirY = 0; hero.wheelchairDamageAccum = 0;
        hero.wheelchairHitMap = {}; hero.wormholeTimer = 5000; hero.radiationCooldown = 18000;
        hero.noDamageTimer = 0; hero.blackHoleCooldown = 10000;
    } else if (id === 'kepler') {
        hero.planetCooldown = 3000; hero.focusCooldown = 8000; hero.planetCount = 0;
    } else if (id === 'zuchongzhi') {
        hero.attackCooldown = 1600; hero.piCooldown = 8000; hero.piShieldTimer = 0;
    } else if (id === 'schrodinger') {
        hero.attackCooldown = 1700; hero.cloneCooldown = 6000; hero.cloneTimer = 0;
        hero.collapseCooldown = 12000; hero.catBoxCooldown = 15000;
    } else if (id === 'qianxuesen') {
        hero.missileCooldown = 1000;
        hero.rocketCooldown = 10000;
        hero.isBombarding = false;
        hero.bombardTimer = 0;
        hero.bombardSpawnTimer = 0;
    } else if (id === 'hooke') {
        hero.springCooldown = 2000;
        hero.attackCooldown = 2500;
    } else if (id === 'curie') {
        hero.radiumCooldown = 6000;
        hero.attackCooldown = 2000;
    } else if (id === 'houdebang') {
        hero.attackCooldown = 1000;
    }
    return hero;
}

function findNearestEnemy(hero) {
    let target = null, minDist = Infinity;
    for (const h of heroEntities) {
        if (h === hero || h.hp <= 0) continue;
        const d = Math.hypot(h.x - hero.x, h.y - hero.y);
        if (d < minDist) { minDist = d; target = h; }
    }
    return target;
}

function gameLoop(timestamp) {
    if (gameState !== 'playing') return;
    try {
        const dt = Math.min((timestamp - lastTime) / 1000, MAX_DT);
        if (!isFinite(dt) || dt <= 0) { rafId = requestAnimationFrame(gameLoop); return; }
        lastTime = timestamp;
        update(dt);
        if (gameState === 'playing') { updateCamera(dt); render(); }
    } catch (e) { console.error("Game Loop Error:", e); }
    rafId = requestAnimationFrame(gameLoop);
}

function updateCamera(dt) {
    let cx = 0, cy = 0, n = 0;
    for (const h of heroEntities) {
        if (h.hp <= 0) continue;
        cx += h.x; cy += h.y; n++;
    }
    if (n === 0) return;
    cx /= n; cy /= n;
    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 updateWander(h, dt) {
    h.wanderTimer -= dt;
    if (h.wanderTimer <= 0) {
        h.wanderTimer = WANDER_CHANGE + Math.random() * 1.0;
        h.wanderAngle += (Math.random() - 0.5) * Math.PI * 3.0;
    }
    const wobble = Math.sin(performance.now() / 400 + h.wanderAngle) * 0.5;
    h.wanderX = Math.cos(h.wanderAngle + wobble) * h.speed * WANDER_SPEED;
    h.wanderY = Math.sin(h.wanderAngle + wobble) * h.speed * WANDER_SPEED;
}

function moveTowardTarget(hero, target, dt) {
    const dx = target.x - hero.x, dy = target.y - hero.y;
    const distPx = Math.hypot(dx, dy) || 1;
    const dirX = dx / distPx, dirY = dy / distPx;
    hero.facingRight = dirX > 0;

    let moveX = dirX * hero.speed;
    let moveY = dirY * hero.speed;

    const minDist = hero.radius + target.radius + 15;
    if (distPx < minDist) {
        moveX = -dirX * hero.speed * 0.4;
        moveY = -dirY * hero.speed * 0.4;
    }

    const tangent = Math.sin(performance.now() / 1400 + hero.wanderAngle * 2) * 0.9;
    moveX += -dirY * tangent * hero.speed;
    moveY +=  dirX * tangent * hero.speed;

    hero.x += (moveX + hero.wanderX) * dt;
    hero.y += (moveY + hero.wanderY) * dt;
    constrainToArena(hero);
    return { distPx, distGrid: distPx / CONFIG.GRID_SIZE, dirX, dirY };
}

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

    for (const h of heroEntities) {
        if (h.hp <= 0) continue;
        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.slowTimer > 0) { h.slowTimer -= dt * 1000; if (h.slowTimer <= 0) h.speed = h.baseSpeed; }
        if (h.wallPushTimer > 0) { h.wallPushTimer -= dt; h.x += h.wallPushX * h.speed * dt; h.y += h.wallPushY * h.speed * dt; }
        if (h.taylorMarkTimer > 0) { h.taylorMarkTimer -= dt; if (h.taylorMarkTimer <= 0) h.taylorMark = 0; }
        if (h.hormoneTimer > 0) { h.hormoneTimer -= dt; if (h.hormoneTimer <= 0) { h.hormoneStacks = 0; h.attackPower = 1; } }
        if (h.overdriveTimer > 0) { h.overdriveTimer -= dt * 1000; if (h.overdriveTimer <= 0 && h.id === 'starling') h.speed = h.baseSpeed; }
        if (h.id === 'starling' && h.overdriveTimer <= 0) {
            h.speed = (h.hp < h.maxHp * 0.5) ? h.baseSpeed * 1.2 : h.baseSpeed;
        }
        if (h.lowHpTimer > 0) {
            h.lowHpTimer -= dt * 1000;
            if (h.lowHpTimer <= 0) {
                h.lowHpTimer = 0;
                h.hp = Math.max(h.hp, Math.min(h.originalHp, h.maxHp));
                h.originalHp = 0;
                addDamageText(h.x, h.y - 30, '血量恢复!', '#00ff00');
            }
        }
        // 永久减速
        if (h.permanentSlow > 0) h.speed = h.baseSpeed * (1 - h.permanentSlow);

        if (h.id === 'faraday') {
            if (h.shieldTimer > 0) h.shieldTimer -= dt * 1000;
            if (h.shieldCooldown > 0) h.shieldCooldown -= dt * 1000;
            if (h.shieldCooldown <= 0 && h.shieldTimer <= 0) {
                h.shieldCooldown = 8000; h.shieldTimer = 5000;
                addDamageText(h.x, h.y - 50, '⚡ 力场护盾!', '#ffd700');
            }
            if (h.cageCooldown > 0) h.cageCooldown -= dt * 1000;
        }
        if (h.id === 'hawking') {
            if (h.wheelchairCooldown > 0) h.wheelchairCooldown -= dt * 1000;
            if (h.wormholeTimer > 0) h.wormholeTimer -= dt * 1000;
            if (h.radiationCooldown > 0) h.radiationCooldown -= dt * 1000;
            if (h.blackHoleCooldown > 0) h.blackHoleCooldown -= dt * 1000;
            if (h.hp < h.maxHp * 0.5 && h.radiationCooldown <= 0) {
                h.radiationCooldown = 18000;
                radiationZones.push({ x: h.x, y: h.y, radius: 120, life: 4, ownerId: h.id });
                addDamageText(h.x, h.y - 50, '🕳️ 霍金辐射!', '#8a2be2');
            }
            h.noDamageTimer += dt;
            if (h.noDamageTimer >= 5 && h.hp < h.maxHp) h.hp = Math.min(h.maxHp, h.hp + 10 * dt);
        }
        if (h.id === 'kepler') {
            if (h.planetCooldown > 0) h.planetCooldown -= dt * 1000;
            if (h.focusCooldown > 0) h.focusCooldown -= dt * 1000;
        }
        if (h.id === 'zuchongzhi') {
            if (h.piCooldown > 0) h.piCooldown -= dt * 1000;
            if (h.piShieldTimer > 0) h.piShieldTimer -= dt * 1000;
        }
        if (h.id === 'schrodinger') {
            if (h.cloneCooldown > 0) h.cloneCooldown -= dt * 1000;
            if (h.cloneTimer > 0) h.cloneTimer -= dt * 1000;
            if (h.collapseCooldown > 0) h.collapseCooldown -= dt * 1000;
            if (h.catBoxCooldown > 0) h.catBoxCooldown -= dt * 1000;
        }
        if (h.id === 'qianxuesen') {
            if (h.missileCooldown > 0) h.missileCooldown -= dt * 1000;
            if (h.rocketCooldown > 0) h.rocketCooldown -= dt * 1000;
            if (h.isBombarding) {
                h.bombardTimer -= dt;
                h.bombardSpawnTimer += dt;
                if (h.bombardSpawnTimer >= 0.3) {
                    h.bombardSpawnTimer = 0;
                    for (let i = 0; i < 3; i++) {
                        bombardZones.push({
                            x: ARENA_X + Math.random() * ARENA_WIDTH,
                            y: ARENA_Y + Math.random() * ARENA_HEIGHT,
                            radius: 60, life: 0.5, maxLife: 0.5,
                            ownerId: h.id, hitMap: {}
                        });
                    }
                }
                if (h.bombardTimer <= 0) h.isBombarding = false;
            }
        }
        if (h.id === 'hooke') { if (h.springCooldown > 0) h.springCooldown -= dt * 1000; }
        if (h.id === 'curie') { if (h.radiumCooldown > 0) h.radiumCooldown -= dt * 1000; }

        if (h.chargeTimer > 0) { h.chargeTimer -= dt; if (h.chargeTimer <= 0) h.chargeStacks = 0; }
        if (h.radiationTimer > 0) { h.radiationTimer -= dt; if (h.radiationTimer <= 0) h.radiationStacks = 0; }
        if (h.radiationStacks > 0) applyDamage(h, h.radiationStacks * 2 * dt, null, null, true, true);

        updateWander(h, dt);
    }

    for (const hero of heroEntities) {
        if (hero.hp <= 0 || hero.stunTimer > 0 || hero.wallPushTimer > 0) continue;
        if (hero.id === 'hawking' && hero.wheelchairTimer > 0) { updateWheelchairCharge(hero, dt); continue; }
        if (hero.id === 'newton')         updateNewton(hero, dt);
        else if (hero.id === 'lavoisier') updateLavoisier(hero, dt);
        else if (hero.id === 'mendel')    updateMendel(hero, dt);
        else if (hero.id === 'taylor')    updateTaylor(hero, dt);
        else if (hero.id === 'starling')  updateStarling(hero, dt);
        else if (hero.id === 'faraday')   updateFaraday(hero, dt);
        else if (hero.id === 'hawking')   updateHawking(hero, dt);
        else if (hero.id === 'kepler')    updateKepler(hero, dt);
        else if (hero.id === 'zuchongzhi')updateZuchongzhi(hero, dt);
        else if (hero.id === 'schrodinger') updateSchrodinger(hero, dt);
        else if (hero.id === 'qianxuesen') updateQianXuesen(hero, dt);
        else if (hero.id === 'hooke')     updateHooke(hero, dt);
        else if (hero.id === 'curie')     updateCurie(hero, dt);
        else if (hero.id === 'houdebang') updateHoudebang(hero, dt);
    }

    updatePrisms(dt); updateOxygenFields(dt); updateMassParticles(dt);
    updatePeaShooters(dt); updateHormonePools(dt); updateGeneSeeds(dt);
    updateWormholes(dt); updateFaradayCages(dt); updateRadiationZones(dt);
    updateKeplerPlanets(dt); updateCutCircleBullets(dt); updatePiShields(dt);
    updateSchrodingerClones(dt); updateCatBoxes(dt); updateMissiles(dt);
    updateBombardZones(dt); updateSpringOscillators(dt); updateBlackHoles(dt);
    updateRadiumBlocks(dt); updateDroppedItems(dt);
    resolveCollisions(); updateProjectiles(dt); updateEffects(dt);
}

// ==========================================
// ♿ 霍金 AI(2秒冲锋 + 黑洞)
// ==========================================
function updateHawking(hero, dt) {
    const target = findNearestEnemy(hero);
    if (!target) return;
    const dx = target.x - hero.x, dy = target.y - hero.y;
    const distPx = Math.hypot(dx, dy) || 1;
    const distGrid = distPx / CONFIG.GRID_SIZE;
    const dirX = dx / distPx, dirY = dy / distPx;
    hero.facingRight = dirX > 0;

    moveTowardTarget(hero, target, dt);

    // 轮椅冲锋(2秒冷却)
    if (hero.wheelchairCooldown <= 0 && distGrid > 1.5) {
        hero.wheelchairCooldown = 2000;
        hero.wheelchairTimer = 1.0;
        hero.wheelchairDirX = dirX; hero.wheelchairDirY = dirY;
        hero.wheelchairDamageAccum = 0; hero.wheelchairHitMap = {};
        addDamageText(hero.x, hero.y - 50, '♿ 轮椅冲锋!', '#8a2be2');
    }

    // 黑洞(10秒冷却)
    if (hero.blackHoleCooldown <= 0) {
        hero.blackHoleCooldown = 10000;
        // 黑洞放在目标附近
        const px = target.x + (Math.random() - 0.5) * CONFIG.GRID_SIZE * 2;
        const py = target.y + (Math.random() - 0.5) * CONFIG.GRID_SIZE * 2;
        blackHoles.push({
            ownerId: hero.id,
            x: clampX(px), y: clampY(py),
            radius: CONFIG.GRID_SIZE * 5,
            life: 10,
            tickAccum: 0
        });
        addDamageText(hero.x, hero.y - 50, '🕳️ 黑洞!', '#000000');
    }

    // 虫洞
    if (hero.wormholeTimer <= 0) {
        hero.wormholeTimer = 5000;
        if (Math.random() < 0.3) {
            const entryAngle = Math.random() * Math.PI * 2;
            const entryDist = CONFIG.GRID_SIZE * 1.5;
            const entryX = hero.x + Math.cos(entryAngle) * entryDist;
            const entryY = hero.y + Math.sin(entryAngle) * entryDist;
            let exitX, exitY, tries = 0;
            do {
                exitX = ARENA_X + Math.random() * ARENA_WIDTH;
                exitY = ARENA_Y + Math.random() * ARENA_HEIGHT;
                tries++;
            } while (Math.hypot(exitX - entryX, exitY - entryY) < CONFIG.GRID_SIZE * 8 && tries < 20);
            wormholes.push({ entryX: clampX(entryX), entryY: clampY(entryY), exitX: clampX(exitX), exitY: clampY(exitY), life: 15, used: false });
        }
    }
}

// ==========================================
// 🕳️ 黑洞逻辑
// ==========================================
function updateBlackHoles(dt) {
    for (let i = blackHoles.length - 1; i >= 0; i--) {
        const bh = blackHoles[i];
        // 如果在霍金辐射范围内,衰减加快
        let inRadiation = false;
        for (const zone of radiationZones) {
            if (zone.ownerId === bh.ownerId && Math.hypot(zone.x - bh.x, zone.y - bh.y) < zone.radius) {
                inRadiation = true;
                break;
            }
        }
        if (inRadiation) bh.life -= dt * 3.33; // 10秒变成3秒
        else bh.life -= dt;

        if (bh.life <= 0) { blackHoles.splice(i, 1); continue; }

        bh.tickAccum += dt;

        for (const h of heroEntities) {
            if (h.hp <= 0 || h.id === bh.ownerId) continue;
            const dx = bh.x - h.x, dy = bh.y - h.y;
            const dist = Math.hypot(dx, dy);
            if (dist < bh.radius && dist > 1) {
                // 吸引
                const pullStrength = 80;
                h.x += (dx / dist) * pullStrength * dt;
                h.y += (dy / dist) * pullStrength * dt;
                constrainToArena(h);
                // 持续伤害
                if (bh.tickAccum >= 1) {
                    applyDamage(h, 10, heroEntities.find(he => he.id === bh.ownerId), null, true, true);
                }
            }
        }
        if (bh.tickAccum >= 1) bh.tickAccum = 0;
    }
}

// ==========================================
// 🚀 钱学森(随机盲狙导弹)
// ==========================================
function updateQianXuesen(hero, dt) {
    const target = findNearestEnemy(hero);
    if (!target) return;
    moveTowardTarget(hero, target, dt);

    // 技能1:1秒1发随机方向200伤导弹
    if (hero.missileCooldown <= 0) {
        hero.missileCooldown = 1000;
        const angle = Math.random() * Math.PI * 2;
        missiles.push({
            ownerId: hero.id,
            x: hero.x, y: hero.y,
            vx: Math.cos(angle) * 800,
            vy: Math.sin(angle) * 800,
            hp: 100, maxHp: 100,
            radius: 10,
            life: 5
        });
    }

    // 技能2:呼叫火箭军
    if (hero.rocketCooldown <= 0 && !hero.isBombarding) {
        hero.rocketCooldown = 10000;
        hero.isBombarding = true;
        hero.bombardTimer = 10;
        hero.bombardSpawnTimer = 0;
        addDamageText(hero.x, hero.y - 60, '💬 呼叫火箭军!', '#FF4500');
    }
}

// 导弹逻辑(无追踪,飞出边界销毁)
function updateMissiles(dt) {
    for (let i = missiles.length - 1; i >= 0; i--) {
        const m = missiles[i];
        m.life -= dt;
        if (m.life <= 0) { missiles.splice(i, 1); continue; }

        m.x += m.vx * dt;
        m.y += m.vy * dt;

        // 飞出地图立刻销毁
        if (m.x < ARENA_X - 20 || m.x > ARENA_X + ARENA_WIDTH + 20 ||
            m.y < ARENA_Y - 20 || m.y > ARENA_Y + ARENA_HEIGHT + 20) {
            missiles.splice(i, 1); continue;
        }

        // 命中敌人
        let hitSomething = false;
        for (const h of heroEntities) {
            if (h.hp <= 0 || h.id === m.ownerId) continue;
            if (Math.hypot(h.x - m.x, h.y - m.y) < h.radius + m.radius) {
                applyDamage(h, 200, heroEntities.find(he => he.id === m.ownerId), null);
                addImpact(m.x, m.y, '#FF4500', 0.8);
                hitSomething = true;
                break;
            }
        }

        // 被弹道击毁
        if (!hitSomething) {
            for (const p of projectiles) {
                if (p.ownerId === m.ownerId) continue;
                if (Math.hypot(p.x - m.x, p.y - m.y) < p.hitRadius + m.radius) {
                    m.hp -= p.damage;
                    addImpact(p.x, p.y, '#ffaa00', 0.3);
                    if (m.hp <= 0) {
                        // 爆炸
                        addImpact(m.x, m.y, '#FF4500', 1.0);
                        for (const h of heroEntities) {
                            if (h.hp > 0 && Math.hypot(h.x - m.x, h.y - m.y) < 100) applyDamage(h, 50, null, null, false, false);
                        }
                        hitSomething = true;
                        break;
                    }
                }
            }
        }

        if (hitSomething) missiles.splice(i, 1);
    }
}

function updateBombardZones(dt) {
    for (let i = bombardZones.length - 1; i >= 0; i--) {
        const z = bombardZones[i];
        z.life -= dt;
        if (z.life <= 0) { bombardZones.splice(i, 1); continue; }
        for (const h of heroEntities) {
            if (h.hp <= 0) continue;
            if (Math.hypot(h.x - z.x, h.y - z.y) < z.radius) {
                if (!z.hitMap[h.id]) {
                    z.hitMap[h.id] = true;
                    applyDamage(h, 50, heroEntities.find(he => he.id === z.ownerId), null, false, false);
                    addDamageText(h.x, h.y - 30, '💥 轰炸50', '#FF4500');
                }
            }
        }
    }
}

// ==========================================
// ☢️ 居里
// ==========================================
function updateCurie(hero, dt) {
    const target = findNearestEnemy(hero);
    if (!target) return;
    moveTowardTarget(hero, target, dt);

    // 技能1:镭块(每6秒)
    if (hero.radiumCooldown <= 0) {
        hero.radiumCooldown = 6000;
        radiumBlocks.push({
            ownerId: hero.id,
            x: hero.x + (Math.random() - 0.5) * 100,
            y: hero.y + (Math.random() - 0.5) * 100,
            angle: 0,
            rotationSpeed: 2, // 2弧度/秒
            rayLength: 250,
            maxLife: Math.PI * 2 / 2, // 转一圈的时间 = 2π/2 ≈ 3.14秒
            life: Math.PI * 2 / 2,
            hitMap: {}
        });
        addDamageText(hero.x, hero.y - 40, '☢️ 镭块!', '#00FF7F');
    }

    // 技能2:U-238(40%概率)
    if (hero.attackTimer <= 0) {
        hero.attackTimer = 2000;
        hero.attackAnimTimer = 300;
        if (Math.random() < 0.4) {
            spawnProjectile({
                x: hero.x, y: hero.y, startX: hero.x, startY: hero.y,
                target, speed: 750, damage: 57, type: 'uranium', ownerId: hero.id, homing: 0.3
            });
            addDamageText(hero.x, hero.y - 40, '☢️ U-238!', '#00FF7F');
        }
    }
}

function updateRadiumBlocks(dt) {
    for (let i = radiumBlocks.length - 1; i >= 0; i--) {
        const rb = radiumBlocks[i];
        rb.life -= dt;
        if (rb.life <= 0) { radiumBlocks.splice(i, 1); continue; }

        rb.angle += rb.rotationSpeed * dt;

        // 四条十字射线(0°, 90°, 180°, 270°)
        const rayAngles = [0, Math.PI/2, Math.PI, Math.PI*1.5];
        const now = performance.now();

        for (const rayBaseAngle of rayAngles) {
            const rayAngle = rayBaseAngle + rb.angle;
            // 检测射线上的所有单位
            for (const h of heroEntities) {
                if (h.hp <= 0) continue;
                const dx = h.x - rb.x, dy = h.y - rb.y;
                const dist = Math.hypot(dx, dy);
                if (dist > rb.rayLength) continue;
                const angleToHero = Math.atan2(dy, dx);
                let angleDiff = Math.abs(angleToHero - rayAngle);
                while (angleDiff > Math.PI) angleDiff = Math.abs(angleDiff - 2 * Math.PI);
                // 射线宽度判定
                if (angleDiff < 0.15 || angleDiff > Math.PI * 2 - 0.15) {
                    const key = h.id + '_' + rayBaseAngle;
                    if (!rb.hitMap[key] || now - rb.hitMap[key] > 500) {
                        rb.hitMap[key] = now;
                        applyDamage(h, 24, heroEntities.find(he => he.id === rb.ownerId), null, false, false);
                        h.slowTimer = 1000;
                        h.speed = h.baseSpeed * 0.95;
                        addDamageText(h.x, h.y - 20, '☢️ 24', '#00FF7F');
                    }
                }
            }
        }
    }
}

// ==========================================
// 🧪 侯德榜
// ==========================================
function updateHoudebang(hero, dt) {
    const target = findNearestEnemy(hero);
    if (!target) return;
    moveTowardTarget(hero, target, dt);

    if (hero.attackTimer <= 0) {
        hero.attackTimer = 1000;
        hero.attackAnimTimer = 200;

        const roll = Math.random();
        if (roll < 0.1) {
            // 10% 四种齐发
            // 标记所有四种弹道,如果全部命中同一目标则触发大招
            const batchId = Date.now() + Math.random();
            const items = ['NaCl', 'H2O', 'CO2', 'all'];
            for (const item of items) {
                spawnProjectile({
                    x: hero.x, y: hero.y, startX: hero.x, startY: hero.y,
                    target, speed: 700 + Math.random() * 100,
                    damage: item === 'NaCl' ? 45 : item === 'H2O' ? 5 : item === 'CO2' ? 10 : 0,
                    type: 'chem_' + item, ownerId: hero.id, homing: 0.4,
                    batchId: batchId
                });
            }
            addDamageText(hero.x, hero.y - 50, '🧪 四种齐发!', '#FF69B4');
        } else if (roll < 0.4) {
            // 30% NaCl(45伤)
            spawnProjectile({ x: hero.x, y: hero.y, startX: hero.x, startY: hero.y, target, speed: 750, damage: 45, type: 'chem_NaCl', ownerId: hero.id, homing: 0.3 });
        } else if (roll < 0.7) {
            // 30% H2O(5伤)
            spawnProjectile({ x: hero.x, y: hero.y, startX: hero.x, startY: hero.y, target, speed: 750, damage: 5, type: 'chem_H2O', ownerId: hero.id, homing: 0.3 });
        } else if (roll < 0.9) {
            // 20% CO2(10伤+20%减速4秒)
            spawnProjectile({ x: hero.x, y: hero.y, startX: hero.x, startY: hero.y, target, speed: 750, damage: 10, type: 'chem_CO2', ownerId: hero.id, homing: 0.3 });
        }
        // 剩余10%什么都不做(保证总概率=100%)
    }
}

// ==========================================
// 🪀 胡克
// ==========================================
function updateHooke(hero, dt) {
    const target = findNearestEnemy(hero);
    if (!target) return;
    moveTowardTarget(hero, target, dt);

    if (hero.springCooldown <= 0) {
        hero.springCooldown = 2000;
        const dx = target.x - hero.x, dy = target.y - hero.y;
        const dist = Math.hypot(dx, dy) || 1;
        const dirX = dx / dist, dirY = dy / dist;
        const px = target.x + dirX * CONFIG.GRID_SIZE * 1.5;
        const py = target.y + dirY * CONFIG.GRID_SIZE * 1.5;
        springOscillators.push({
            ownerId: hero.id, x: px, y: py,
            perpX: -dirY, perpY: dirX,
            oscAmount: 0, oscSpeed: 6, oscRange: 40,
            life: 8, hitMap: {}, visualFlash: 0, armLength: 25
        });
        addDamageText(hero.x, hero.y - 40, '🪀 弹簧振子!', '#A0522D');
    }

    if (hero.attackTimer <= 0) {
        hero.attackTimer = 2500;
        hero.attackAnimTimer = 300;
        if (Math.random() < 0.4) {
            spawnProjectile({ x: hero.x, y: hero.y, startX: hero.x, startY: hero.y, target, speed: 700, damage: 15, type: 'cell', ownerId: hero.id, homing: 0.3 });
        }
    }
}

function updateSpringOscillators(dt) {
    for (let i = springOscillators.length - 1; i >= 0; i--) {
        const s = springOscillators[i];
        s.life -= dt;
        if (s.life <= 0) { springOscillators.splice(i, 1); continue; }
        s.oscAmount += s.oscSpeed * dt;
        const offset = Math.sin(s.oscAmount) * s.oscRange;
        s.currentX = s.x + s.perpX * offset;
        s.currentY = s.y + s.perpY * offset;
        const now = performance.now();
        let hitThisFrame = false;
        for (const h of heroEntities) {
            if (h.hp <= 0 || h.id === s.ownerId) continue;
            if (Math.hypot(h.x - s.currentX, h.y - s.currentY) < h.radius + 18) {
                if (!s.hitMap[h.id] || now - s.hitMap[h.id] > 2000) {
                    s.hitMap[h.id] = now;
                    h.stunTimer = 2000;
                    applyDamage(h, 20, heroEntities.find(he => he.id === s.ownerId), null, false, false);
                    addDamageText(h.x, h.y - 30, '🪀 弹簧眩晕!', '#A0522D');
                    hitThisFrame = true;
                }
            }
        }
        if (hitThisFrame) s.visualFlash = 0.3;
        if (s.visualFlash > 0) s.visualFlash -= dt;
    }
}

// ==========================================
// 🔺 牛顿
// ==========================================
function updateNewton(hero, dt) {
    hero.prismCooldown -= dt * 1000;
    hero.appleTimer -= dt * 1000;
    const target = findNearestEnemy(hero);
    if (!target) return;
    moveTowardTarget(hero, target, dt);

    if (hero.prismCooldown <= 0) {
        hero.prismCooldown = 5000;
        const px = hero.x + (target.x - hero.x) * 0.5;
        const py = hero.y + (target.y - hero.y) * 0.5;
        const baseAngle = Math.atan2(target.y - py, target.x - px);
        newtonPrisms.push({ ownerId: hero.id, x: px, y: py, timer: 10000, baseAngle, angle: 0, sweepDir: 1 });
        addDamageText(hero.x, hero.y - 40, '🔺 三棱镜!', '#00ffff');
    }

    if (hero.appleTimer <= 0) {
        hero.appleTimer = 30000;
        for (let i = 0; i < 10; i++) {
            if (apples.length >= 20) break;
            const ax = ARENA_X + Math.random() * ARENA_WIDTH;
            const ay = ARENA_Y + Math.random() * ARENA_HEIGHT;
            let hitEnemy = false;
            for (const 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);
                    hitEnemy = true; break;
                }
            }
            if (!hitEnemy) apples.push({ x: ax, y: ay, radius: 8 });
        }
    }

    for (let i = apples.length - 1; i >= 0; i--) {
        const ap = apples[i];
        for (const h of heroEntities) {
            if (h.hp <= 0) continue;
            if (Math.hypot(h.x - ap.x, h.y - ap.y) < h.radius + ap.radius) {
                const heal = h.id === 'newton' ? 50 : 10;
                h.hp = Math.min(h.maxHp, h.hp + heal);
                apples.splice(i, 1); break;
            }
        }
    }

    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;
        const orbitSpeed = 2.5;
        for (let i = hero.satellites.length - 1; i >= 0; i--) {
            const sat = hero.satellites[i];
            sat.angle += orbitSpeed * dt;
            const satX = hero.x + Math.cos(sat.angle) * orbitRadius;
            const satY = hero.y + Math.sin(sat.angle) * orbitRadius;
            let hit = false;
            for (const 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;
                    addImpact(satX, satY, '#00ffff', 0.5);
                    hit = true; break;
                }
            }
            if (hit) hero.satellites.splice(i, 1);
        }
    }
}

// ==========================================
// 🔥 拉瓦锡
// ==========================================
function updateLavoisier(hero, dt) {
    hero.oxygenCooldown -= dt * 1000;
    hero.ultCooldown -= dt * 1000;
    const target = findNearestEnemy(hero);
    if (!target) return;
    moveTowardTarget(hero, target, dt);

    if (hero.oxygenCooldown <= 0) {
        hero.oxygenCooldown = 6000;
        spawnProjectile({ x: hero.x, y: hero.y, startX: hero.x, startY: hero.y, target, speed: 500, damage: 20, type: 'oxygen', ownerId: hero.id, homing: 0.4 });
    }
    if (hero.preciseStacks >= 3 && hero.ultCooldown <= 0) {
        hero.preciseStacks = 0; hero.ultCooldown = 12000;
        applyDamage(target, 120, hero, null, false, false);
        addImpact(target.x, target.y, '#4488ff', 1.0);
        const kdx = target.x - hero.x, kdy = target.y - hero.y, kd = Math.hypot(kdx, kdy) || 1;
        target.x += (kdx / kd) * CONFIG.GRID_SIZE;
        target.y += (kdy / kd) * CONFIG.GRID_SIZE;
        constrainToArena(target);
    }
}

function updateMendel(hero, dt) {
    hero.peaTimer -= dt * 1000;
    const target = findNearestEnemy(hero);
    if (!target) return;
    moveTowardTarget(hero, target, dt);
    if (hero.attackTimer <= 0) {
        hero.attackTimer = 2000; hero.attackAnimTimer = 300;
        if (hero.geneSeed) {
            hero.geneSeed = 0;
            spawnProjectile({ x: hero.x, y: hero.y, startX: hero.x, startY: hero.y, target, speed: 900, damage: 50, type: 'superpea', ownerId: hero.id, homing: 0.15 });
        } else {
            spawnProjectile({ x: hero.x, y: hero.y, startX: hero.x, startY: hero.y, target, speed: 700, damage: 15, type: 'pea', ownerId: hero.id, homing: 0.2 });
        }
    }
    if (hero.peaTimer <= 0) {
        hero.peaTimer = 8000;
        if (peaShooters.filter(p => p.ownerId === hero.id).length < 4) {
            peaShooters.push({ ownerId: hero.id, x: hero.x + (Math.random() - 0.5) * 100, y: hero.y + (Math.random() - 0.5) * 100, hp: 50, maxHp: 50, attackTimer: 0 });
        }
    }
}

function updateTaylor(hero, dt) {
    hero.ultCooldown -= dt * 1000;
    const target = findNearestEnemy(hero);
    if (!target) return;
    moveTowardTarget(hero, target, dt);
    if (hero.attackTimer <= 0) {
        hero.attackTimer = 1500; hero.attackAnimTimer = 300;
        spawnProjectile({ x: hero.x, y: hero.y, startX: hero.x, startY: hero.y, target, speed: 900, damage: 5, type: 'taylor', ownerId: hero.id, homing: 0 });
    }
}

function updateStarling(hero, dt) {
    const target = findNearestEnemy(hero);
    if (!target) return;
    moveTowardTarget(hero, target, dt);
    if (hero.attackTimer <= 0) {
        hero.attackTimer = 2000; hero.attackAnimTimer = 300;
        spawnProjectile({ x: hero.x, y: hero.y, startX: hero.x, startY: hero.y, target, speed: 800, damage: 15, type: 'hormone', ownerId: hero.id, homing: 0.25 });
    }
}

function updateFaraday(hero, dt) {
    const target = findNearestEnemy(hero);
    if (!target) return;
    moveTowardTarget(hero, target, dt);
    if (hero.attackTimer <= 0) {
        hero.attackTimer = 1800; hero.attackAnimTimer = 300;
        spawnProjectile({ x: hero.x, y: hero.y, startX: hero.x, startY: hero.y, target, speed: 850, damage: 12, type: 'spark', ownerId: hero.id, homing: 0.3 });
    }
    if (target.chargeStacks >= 4 && hero.cageCooldown <= 0) {
        hero.cageCooldown = 15000;
        faradayCages.push({ x: target.x, y: target.y, radius: 120, life: 5, ownerId: hero.id, tickAccum: 0, shockAccum: 0 });
        target.chargeStacks = 0; target.chargeTimer = 0;
    }
}

function updateKepler(hero, dt) {
    const target = findNearestEnemy(hero);
    if (!target) return;
    moveTowardTarget(hero, target, dt);
    if (hero.planetCooldown <= 0) {
        hero.planetCooldown = 3000;
        if (hero.planetCount < 5) {
            hero.planetCount++;
            keplerPlanets.push({ ownerId: hero.id, angle: Math.random() * Math.PI * 2, orbitRadius: 2.5 * CONFIG.GRID_SIZE, hitMap: {} });
        }
    }
    if (hero.focusCooldown <= 0) {
        hero.focusCooldown = 8000;
        spawnProjectile({ x: hero.x, y: hero.y, startX: hero.x, startY: hero.y, target, speed: 1200, damage: 50, type: 'focus', ownerId: hero.id, homing: 0.6 });
    }
    hero.speed = hero.planetCount > 0 ? h.baseSpeed * (1 + hero.planetCount * 0.08) : h.baseSpeed;
}

function updateZuchongzhi(hero, dt) {
    const target = findNearestEnemy(hero);
    if (!target) return;
    moveTowardTarget(hero, target, dt);
    if (hero.attackTimer <= 0) {
        hero.attackTimer = 1600; hero.attackAnimTimer = 300;
        const dx = target.x - hero.x, dy = target.y - hero.y;
        const d = Math.hypot(dx, dy) || 1;
        cutCircleBullets.push({ ownerId: hero.id, x: hero.x, y: hero.y, dirX: dx / d, dirY: dy / d, speed: 600, damage: 12, sides: 3, splitsLeft: 2, life: 4, traveled: 0 });
    }
    if (hero.piCooldown <= 0 && !hero.piShieldTimer) {
        hero.piCooldown = 8000; hero.piShieldTimer = 6000;
        piShields.push({ ownerId: hero.id, angle: 0, orbitRadius: 2 * CONFIG.GRID_SIZE, life: 6 });
    }
}

function updateSchrodinger(hero, dt) {
    const target = findNearestEnemy(hero);
    if (!target) return;
    moveTowardTarget(hero, target, dt);
    if (hero.attackTimer <= 0) {
        hero.attackTimer = 1700; hero.attackAnimTimer = 300;
        if (Math.random() < 0.5 && schrodingerClones.filter(c => c.ownerId === hero.id).length < 3) {
            const angle = Math.random() * Math.PI * 2;
            const dist = 80 + Math.random() * 80;
            schrodingerClones.push({ ownerId: hero.id, x: hero.x + Math.cos(angle) * dist, y: hero.y + Math.sin(angle) * dist, life: 3 });
        }
        spawnProjectile({ x: hero.x, y: hero.y, startX: hero.x, startY: hero.y, target, speed: 850, damage: 14, type: 'quantum', ownerId: hero.id, homing: 0.1 });
    }
    if (hero.collapseCooldown <= 0 && schrodingerClones.filter(c => c.ownerId === hero.id).length > 0) {
        hero.collapseCooldown = 12000;
        const clones = schrodingerClones.filter(c => c.ownerId === hero.id);
        const chosen = clones[Math.floor(Math.random() * clones.length)];
        applyDamage(target, 60, hero, null, true, false);
        addImpact(hero.x, hero.y, '#9370db', 1.0);
        hero.x = chosen.x; hero.y = chosen.y;
        schrodingerClones = schrodingerClones.filter(c => c.ownerId !== hero.id);
        constrainToArena(hero);
    }
    if (hero.catBoxCooldown <= 0) {
        hero.catBoxCooldown = 15000;
        if (Math.hypot(target.x - hero.x, target.y - hero.y) < 200) {
            catBoxes.push({ x: target.x, y: target.y, life: 1.5, ownerId: hero.id, targetId: target.id });
            target.stunTimer = 1500;
        }
    }
}

function updateWheelchairCharge(hero, dt) {
    hero.wheelchairTimer -= dt;
    hero.x += hero.wheelchairDirX * 450 * dt;
    hero.y += hero.wheelchairDirY * 450 * dt;
    constrainToArena(hero);
    hero.wheelchairDamageAccum += dt;
    while (hero.wheelchairDamageAccum >= 0.1) {
        hero.wheelchairDamageAccum -= 0.1;
        for (const h of heroEntities) {
            if (h.id === hero.id || h.hp <= 0) continue;
            if (Math.hypot(h.x - hero.x, h.y - hero.y) < h.radius + hero.radius + 8) {
                if (hero.wheelchairHitMap[h.id] === undefined) {
                    hero.wheelchairHitMap[h.id] = true;
                    applyDamage(h, 97, hero, null, false, false);
                    h.stunTimer = 500;
                }
            }
        }
    }
    if (hero.wheelchairTimer <= 0) { hero.wheelchairTimer = 0; addImpact(hero.x, hero.y, '#8a2be2', 0.6); }
}

// ==========================================
// 弹道系统
// ==========================================
function updateProjectiles(dt) {
    for (let i = projectiles.length - 1; i >= 0; i--) {
        const p = projectiles[i];
        if (p.homing > 0 && p.target && p.target.hp > 0) {
            const dx = p.target.x - p.x, dy = p.target.y - p.y;
            const targetAngle = Math.atan2(dy, dx);
            const currentAngle = Math.atan2(p.dirY, p.dirX);
            let angleDiff = targetAngle - currentAngle;
            while (angleDiff > Math.PI) angleDiff -= Math.PI * 2;
            while (angleDiff < -Math.PI) angleDiff += Math.PI * 2;
            const rotateAmount = p.homing * 4 * dt;
            const clamped = Math.max(-rotateAmount, Math.min(rotateAmount, angleDiff));
            const newAngle = currentAngle + clamped;
            p.dirX = Math.cos(newAngle); p.dirY = Math.sin(newAngle);
        }
        p.x += p.dirX * p.speed * dt;
        p.y += p.dirY * p.speed * dt;

        // 飞出边界立刻销毁
        if (p.x < ARENA_X - 30 || p.x > ARENA_X + ARENA_WIDTH + 30 ||
            p.y < ARENA_Y - 30 || p.y > ARENA_Y + ARENA_HEIGHT + 30) {
            if (p.type === 'oxygen') oxygenFields.push({ x: clampX(p.x), y: clampY(p.y), radius: 120, life: 5, ownerId: p.ownerId });
            projectiles.splice(i, 1);
            continue;
        }

        let hitTarget = null;
        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 + p.hitRadius) { hitTarget = h; break; }
        }

        if (p.type === 'oxygen' && p.target && p.target.hp > 0) {
            if (Math.hypot(p.target.x - p.x, p.target.y - p.y) < p.target.radius + 20) hitTarget = p.target;
        }

        if (hitTarget) {
            const attacker = p.ownerId ? heroEntities.find(h => h.id === p.ownerId) : null;
            handleProjectileHit(p, hitTarget, attacker);
            projectiles.splice(i, 1);
        }
    }
}

function handleProjectileHit(p, hitTarget, attacker) {
    if (p.type === 'oxygen') {
        applyDamage(hitTarget, p.damage, attacker, p);
        oxygenFields.push({ x: p.x, y: p.y, radius: 120, life: 5, ownerId: p.ownerId });
        addImpact(p.x, p.y, '#ff8c00', 0.5);
    } else if (p.type === 'cell') {
        applyDamage(hitTarget, p.damage, attacker, p);
        const hpBefore = hitTarget.hp;
        hitTarget.originalHp = hpBefore;
        hitTarget.hp = Math.max(1, hpBefore / 3);
        hitTarget.lowHpTimer = 3000;
        addDamageText(hitTarget.x, hitTarget.y - 30, '低血状态!', '#A0522D');
    } else if (p.type === 'uranium') {
        applyDamage(hitTarget, p.damage, attacker, p);
        if (hitTarget.permanentSlow === 0) {
            hitTarget.permanentSlow = 0.1;
            addDamageText(hitTarget.x, hitTarget.y - 30, '永久减速!', '#00FF7F');
        }
    } else if (p.type === 'chem_NaCl') {
        applyDamage(hitTarget, 45, attacker, p);
        addDamageText(hitTarget.x, hitTarget.y - 20, 'NaCl 45', '#FF69B4');
    } else if (p.type === 'chem_H2O') {
        applyDamage(hitTarget, 5, attacker, p);
        addDamageText(hitTarget.x, hitTarget.y - 20, 'H2O 5', '#FF69B4');
    } else if (p.type === 'chem_CO2') {
        applyDamage(hitTarget, 10, attacker, p);
        hitTarget.slowTimer = 4000;
        hitTarget.speed = hitTarget.baseSpeed * 0.8;
        addDamageText(hitTarget.x, hitTarget.y - 20, 'CO2 10', '#FF69B4');
    } else if (p.type === 'pea') {
        applyDamage(hitTarget, p.damage, attacker, p);
        if (Math.random() < 0.75) addDamageText(hitTarget.x, hitTarget.y, '显性!', '#33cc33');
        else { hitTarget.slowTimer = 2000; hitTarget.speed = hitTarget.baseSpeed * 0.7; }
    } else if (p.type === 'superpea') {
        applyDamage(hitTarget, p.damage, attacker, p);
        addImpact(hitTarget.x, hitTarget.y, '#ffff00', 0.8);
    } else if (p.type === 'taylor') {
        applyDamage(hitTarget, p.damage, attacker, p);
        const kdx = hitTarget.x - p.x, kdy = hitTarget.y - p.y, kd = Math.hypot(kdx, kdy) || 1;
        hitTarget.x += (kdx / kd) * 8; hitTarget.y += (kdy / kd) * 8;
        constrainToArena(hitTarget);
        hitTarget.taylorMark = Math.min(5, (hitTarget.taylorMark || 0) + 1);
        hitTarget.taylorMarkTimer = 3;
        if (hitTarget.taylorMark >= 5 && attacker && attacker.ultCooldown <= 0) {
            const dist = Math.hypot(hitTarget.x - attacker.x, hitTarget.y - attacker.y);
            const dmg = Math.min(250, Math.round(100 + (dist / CONFIG.GRID_SIZE) * 15));
            applyDamage(hitTarget, dmg, attacker, null, true, false);
            hitTarget.taylorMark = 0; attacker.ultCooldown = 15000;
        }
    } else if (p.type === 'hormone') {
        applyDamage(hitTarget, p.damage, attacker, p);
        hitTarget.hormoneStacks = Math.min(5, (hitTarget.hormoneStacks || 0) + 1);
        hitTarget.hormoneTimer = 3;
        hitTarget.attackPower = 1 - hitTarget.hormoneStacks * 0.1;
        if (hitTarget.hormoneStacks >= 5 && attacker && attacker.hormoneStormCooldown <= 0) {
            attacker.hormoneStormCooldown = 15000;
            for (const h of heroEntities) {
                if (h.hp > 0 && h.id !== attacker.id && Math.hypot(h.x - hitTarget.x, h.y - hitTarget.y) < 150) applyDamage(h, 130, attacker, null, true, false);
            }
            hitTarget.hormoneStacks = 0; hitTarget.hormoneTimer = 0; hitTarget.attackPower = 1;
            attacker.speed = attacker.baseSpeed * 2;
            attacker.overdriveTimer = 5000;
        }
    } else if (p.type === 'spark') {
        applyDamage(hitTarget, p.damage, attacker, p);
        hitTarget.chargeStacks = Math.min(4, (hitTarget.chargeStacks || 0) + 1);
        hitTarget.chargeTimer = 4;
        if (hitTarget.chargeStacks >= 4) {
            applyDamage(hitTarget, 40, attacker, null, true, false);
            hitTarget.chargeStacks = 0; hitTarget.chargeTimer = 0;
            for (const h of heroEntities) {
                if (h.hp > 0 && h.id !== hitTarget.id && h.id !== attacker.id && Math.hypot(h.x - hitTarget.x, h.y - hitTarget.y) < 80) applyDamage(h, 20, attacker, null, false, false);
            }
        }
    } else if (p.type === 'focus') {
        applyDamage(hitTarget, p.damage, attacker, p);
        addImpact(hitTarget.x, hitTarget.y, '#ffaa00', 1.0);
    } else if (p.type === 'quantum') {
        applyDamage(hitTarget, p.damage, attacker, p);
        if (Math.random() < 0.2) { hitTarget.stunTimer = 800; }
    } else {
        applyDamage(hitTarget, p.damage, attacker, p);
    }
}

function spawnProjectile(opts) {
    const p = Object.assign({
        x: 0, y: 0, startX: 0, startY: 0, target: null,
        speed: 600, damage: 0, type: 'bullet', ownerId: null,
        dirX: 0, dirY: 0, homing: 0, hitRadius: 10
    }, opts);
    if (p.dirX === 0 && p.dirY === 0 && p.target) {
        const dx = p.target.x - p.x, dy = p.target.y - p.y;
        const d = Math.hypot(dx, dy) || 1;
        p.dirX = dx / d; p.dirY = dy / d;
    }
    projectiles.push(p);
}

// ==========================================
// 其他系统更新(其他英雄)
// ==========================================
function updatePrisms(dt) {
    for (let i = newtonPrisms.length - 1; i >= 0; i--) {
        const 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 (const h of heroEntities) {
            if (h.id === 'newton' || h.hp <= 0) continue;
            const ddx = h.x - p.x, ddy = h.y - p.y;
            const dist = Math.hypot(ddx, ddy);
            if (dist < 240) {
                let angleDiff = Math.abs(Math.atan2(ddy, ddx) - p.baseAngle - p.angle);
                while (angleDiff > Math.PI) angleDiff = Math.abs(angleDiff - 2 * Math.PI);
                if (angleDiff < Math.PI / 6) applyDamage(h, 1, null, null, true, true);
            }
        }
    }
}

function updateOxygenFields(dt) {
    for (let i = oxygenFields.length - 1; i >= 0; i--) {
        const f = oxygenFields[i];
        f.life -= dt;
        if (f.life <= 0) { oxygenFields.splice(i, 1); continue; }
        for (const h of heroEntities) {
            if (h.hp <= 0 || h.id === f.ownerId) continue;
            if (Math.hypot(h.x - f.x, h.y - f.y) < f.radius) {
                applyDamage(h, 15 * dt, null, null, false, true);
                h.slowTimer = 500; h.speed = h.baseSpeed * 0.7;
            }
        }
    }
}

function updateMassParticles(dt) {
    for (let i = massParticles.length - 1; i >= 0; i--) {
        const p = massParticles[i];
        p.life -= dt;
        if (p.life <= 0) { massParticles.splice(i, 1); continue; }
        const lavoisier = heroEntities.find(h => h.id === 'lavoisier' && h.hp > 0);
        if (lavoisier && Math.hypot(lavoisier.x - p.x, lavoisier.y - p.y) < lavoisier.radius + 15) {
            lavoisier.hp = Math.min(lavoisier.maxHp, lavoisier.hp + 30);
            lavoisier.preciseStacks = Math.min(3, lavoisier.preciseStacks + 1);
            massParticles.splice(i, 1);
        }
    }
}

function updatePeaShooters(dt) {
    for (let i = peaShooters.length - 1; i >= 0; i--) {
        const p = peaShooters[i];
        p.hp -= dt * 5;
        if (p.hp <= 0) {
            peaShooters.splice(i, 1);
            const mendel = heroEntities.find(h => h.id === 'mendel' && h.hp > 0);
            if (mendel) {
                if (Math.random() < 0.5) mendel.hp = Math.min(mendel.maxHp, mendel.hp + 100);
                else geneSeeds.push({ x: p.x, y: p.y, radius: 10 });
            }
            continue;
        }
        p.attackTimer += dt * 1000;
        if (p.attackTimer >= 1500) {
            p.attackTimer = 0;
            let target = null, minDist = Infinity;
            for (const h of heroEntities) {
                if (h.hp <= 0 || h.id === p.ownerId) continue;
                const d = Math.hypot(h.x - p.x, h.y - p.y);
                if (d < minDist) { minDist = d; target = h; }
            }
            if (target) spawnProjectile({ x: p.x, y: p.y, startX: p.x, startY: p.y, target, speed: 800, damage: 10, type: 'pea', ownerId: p.ownerId, homing: 0.15 });
        }
    }
}

function updateHormonePools(dt) {
    for (let i = hormonePools.length - 1; i >= 0; i--) {
        const p = hormonePools[i];
        p.life -= dt;
        if (p.life <= 0) { hormonePools.splice(i, 1); continue; }
        const starling = heroEntities.find(h => h.id === 'starling' && h.hp > 0);
        if (starling && Math.hypot(starling.x - p.x, starling.y - p.y) < 40) starling.hp = Math.min(starling.maxHp, starling.hp + 30 * dt);
    }
}

function updateGeneSeeds(dt) {
    for (let i = geneSeeds.length - 1; i >= 0; i--) {
        const g = geneSeeds[i];
        const mendel = heroEntities.find(h => h.id === 'mendel' && h.hp > 0);
        if (mendel && Math.hypot(mendel.x - g.x, mendel.y - g.y) < mendel.radius + 15) { mendel.geneSeed = 1; geneSeeds.splice(i, 1); }
    }
}

function updateWormholes(dt) {
    for (let i = wormholes.length - 1; i >= 0; i--) {
        const w = wormholes[i];
        w.life -= dt;
        if (w.life <= 0 || w.used) { wormholes.splice(i, 1); continue; }
        for (const h of heroEntities) {
            if (h.hp <= 0) continue;
            if (Math.hypot(h.x - w.entryX, h.y - w.entryY) < h.radius + 12) {
                h.x = w.exitX; h.y = w.exitY; w.used = true; break;
            }
            if (Math.hypot(h.x - w.exitX, h.y - w.exitY) < h.radius + 12) {
                h.x = w.entryX; h.y = w.entryY; w.used = true; break;
            }
        }
    }
}

function updateFaradayCages(dt) {
    for (let i = faradayCages.length - 1; i >= 0; i--) {
        const cage = faradayCages[i];
        cage.life -= dt;
        if (cage.life <= 0) { faradayCages.splice(i, 1); continue; }
        cage.tickAccum += dt; cage.shockAccum += dt;
        for (const h of heroEntities) {
            if (h.hp <= 0 || h.id === cage.ownerId) continue;
            const dist = Math.hypot(h.x - cage.x, h.y - cage.y);
            if (dist < cage.radius) {
                if (cage.tickAccum >= 0.5) applyDamage(h, 25 * dt, null, null, false, true);
                if (dist > cage.radius - h.radius) {
                    const dirX = (cage.x - h.x) / (dist || 1), dirY = (cage.y - h.y) / (dist || 1);
                    h.x += dirX * h.speed * dt * 0.8; h.y += dirY * h.speed * dt * 0.8;
                }
            }
        }
        if (cage.shockAccum >= 1) {
            cage.shockAccum = 0;
            for (const h of heroEntities) {
                if (h.hp <= 0 || h.id === cage.ownerId) continue;
                if (Math.hypot(h.x - cage.x, h.y - cage.y) < cage.radius) applyDamage(h, 15, null, null, true, false);
            }
        }
    }
}

function updateRadiationZones(dt) {
    for (let i = radiationZones.length - 1; i >= 0; i--) {
        const zone = radiationZones[i];
        zone.life -= dt;
        if (zone.life <= 0) { radiationZones.splice(i, 1); continue; }
        const owner = heroEntities.find(h => h.id === zone.ownerId);
        if (!owner || owner.hp <= 0) { radiationZones.splice(i, 1); continue; }
        for (const h of heroEntities) {
            if (h.hp <= 0) continue;
            if (Math.hypot(h.x - zone.x, h.y - zone.y) < zone.radius) {
                if (h.id === zone.ownerId) owner.hp = Math.min(owner.maxHp, owner.hp + 20 * dt);
                else applyDamage(h, 15 * dt, owner, null, true, true);
            }
        }
    }
}

function updateKeplerPlanets(dt) {
    for (let i = keplerPlanets.length - 1; i >= 0; i--) {
        const planet = keplerPlanets[i];
        const owner = heroEntities.find(h => h.id === planet.ownerId);
        if (!owner || owner.hp <= 0) { keplerPlanets.splice(i, 1); continue; }
        planet.angle += 1.2 * dt;
        const px = owner.x + Math.cos(planet.angle) * planet.orbitRadius;
        const py = owner.y + Math.sin(planet.angle) * planet.orbitRadius;
        for (const h of heroEntities) {
            if (h.id === planet.ownerId || h.hp <= 0) continue;
            if (Math.hypot(h.x - px, h.y - py) < h.radius + 10) {
                const now = performance.now();
                if (!planet.hitMap[h.id] || now - planet.hitMap[h.id] > 500) {
                    planet.hitMap[h.id] = now;
                    applyDamage(h, 18, owner, null, false, false);
                }
            }
        }
    }
}

function updateCutCircleBullets(dt) {
    for (let i = cutCircleBullets.length - 1; i >= 0; i--) {
        const b = cutCircleBullets[i];
        b.life -= dt;
        if (b.life <= 0) { cutCircleBullets.splice(i, 1); continue; }
        b.x += b.dirX * b.speed * dt; b.y += b.dirY * b.speed * dt;
        b.traveled += b.speed * dt;
        if (b.x < ARENA_X - 30 || b.x > ARENA_X + ARENA_WIDTH + 30 ||
            b.y < ARENA_Y - 30 || b.y > ARENA_Y + ARENA_HEIGHT + 30 || b.traveled > 1500) { cutCircleBullets.splice(i, 1); continue; }
        let hit = null;
        for (const h of heroEntities) {
            if (h.hp <= 0 || h.id === b.ownerId) continue;
            if (Math.hypot(h.x - b.x, h.y - b.y) < h.radius + 10) { hit = h; break; }
        }
        if (hit) {
            applyDamage(hit, b.damage, heroEntities.find(h => h.id === b.ownerId), null);
            if (b.splitsLeft > 0) {
                for (let k = 0; k < 2; k++) {
                    const newAngle = Math.atan2(b.dirY, b.dirX) + (k === 0 ? 0.6 : -0.6);
                    cutCircleBullets.push({ ownerId: b.ownerId, x: b.x, y: b.y, dirX: Math.cos(newAngle), dirY: Math.sin(newAngle), speed: b.speed * 1.2, damage: Math.max(4, Math.floor(b.damage * 0.7)), sides: b.sides + 1, splitsLeft: b.splitsLeft - 1, life: 3, traveled: 0 });
                }
            }
            cutCircleBullets.splice(i, 1);
        }
    }
}

function updatePiShields(dt) {
    for (let i = piShields.length - 1; i >= 0; i--) {
        const s = piShields[i];
        s.life -= dt;
        if (s.life <= 0) { piShields.splice(i, 1); continue; }
        const owner = heroEntities.find(h => h.id === s.ownerId);
        if (!owner || owner.hp <= 0) { piShields.splice(i, 1); continue; }
        s.angle += 2.5 * dt;
        const sx = owner.x + Math.cos(s.angle) * s.orbitRadius;
        const sy = owner.y + Math.sin(s.angle) * s.orbitRadius;
        for (const h of heroEntities) {
            if (h.id === s.ownerId || h.hp <= 0) continue;
            if (Math.hypot(h.x - sx, h.y - sy) < h.radius + 14) {
                applyDamage(h, 25, owner, null, false, false);
                piShields.splice(i, 1); break;
            }
        }
    }
    for (const h of heroEntities) {
        if (h.id === 'zuchongzhi' && h.piShieldTimer > 0) {
            h.piShieldTimer -= dt * 1000;
            if (h.piShieldTimer <= 0) { h.piShieldTimer = 0; piShields = piShields.filter(s => s.ownerId !== h.id); }
        }
    }
}

function updateSchrodingerClones(dt) {
    for (let i = schrodingerClones.length - 1; i >= 0; i--) {
        const c = schrodingerClones[i];
        c.life -= dt;
        if (c.life <= 0) { schrodingerClones.splice(i, 1); continue; }
        for (const h of heroEntities) {
            if (h.id === c.ownerId || h.hp <= 0) continue;
            if (Math.hypot(h.x - c.x, h.y - c.y) < h.radius + 12) {
                applyDamage(h, 20, heroEntities.find(he => he.id === c.ownerId), null, true, false);
                schrodingerClones.splice(i, 1); break;
            }
        }
    }
}

function updateCatBoxes(dt) {
    for (let i = catBoxes.length - 1; i >= 0; i--) {
        const box = catBoxes[i];
        box.life -= dt;
        if (box.life <= 0) catBoxes.splice(i, 1);
    }
}

function updateDroppedItems(dt) {
    for (let i = droppedItems.length - 1; i >= 0; i--) {
        const item = droppedItems[i];
        item.life -= dt;
        if (item.life <= 0) { droppedItems.splice(i, 1); continue; }
        for (const h of heroEntities) {
            if (h.hp <= 0) continue;
            if (Math.hypot(h.x - item.x, h.y - item.y) < h.radius + 15) {
                if (item.type === 'NH4Cl') {
                    applyDamage(h, 100, null, null, true, false);
                    addDamageText(h.x, h.y - 30, '☠️ NH4Cl -100', '#8B4513');
                } else {
                    h.hp = Math.min(h.maxHp, h.hp + 100);
                    addDamageText(h.x, h.y - 30, '💚 NaHCO3 +100', '#00ff00');
                }
                droppedItems.splice(i, 1);
                break;
            }
        }
    }
}

// ==========================================
// 通用物理
// ==========================================
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) continue;
            if (h1.id === 'hawking' && h1.wheelchairTimer > 0) continue;
            if (h2.id === 'hawking' && h2.wheelchairTimer > 0) continue;
            let dx = h2.x - h1.x, dy = h2.y - h1.y;
            let dist = Math.hypot(dx, dy);
            const 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 constrainToArena(h) {
    if (!isFinite(h.x)) h.x = ARENA_X + ARENA_WIDTH / 2;
    if (!isFinite(h.y)) h.y = ARENA_Y + ARENA_HEIGHT / 2;
    let pushed = false, pushX = 0, pushY = 0;
    if (h.x < ARENA_X + h.radius) { h.x = ARENA_X + h.radius; pushX = 1; pushed = true; }
    if (h.x > ARENA_X + ARENA_WIDTH - h.radius) { h.x = ARENA_X + ARENA_WIDTH - h.radius; pushX = -1; pushed = true; }
    if (h.y < ARENA_Y + h.radius) { h.y = ARENA_Y + h.radius; pushY = 1; pushed = true; }
    if (h.y > ARENA_Y + ARENA_HEIGHT - h.radius) { h.y = ARENA_Y + ARENA_HEIGHT - h.radius; pushY = -1; pushed = true; }
    if (pushed) { h.wallPushTimer = 0.25; h.wallPushX = pushX; h.wallPushY = pushY; }
}

function clampX(x) { return Math.max(ARENA_X + 20, Math.min(ARENA_X + ARENA_WIDTH - 20, x)); }
function clampY(y) { return Math.max(ARENA_Y + 20, Math.min(ARENA_Y + ARENA_HEIGHT - 20, y)); }

function applyDamage(target, amount, attacker, projectile, isTrueDamage, silent) {
    if (!target || target.hp <= 0) return;
    if (!isFinite(amount)) amount = 0;
    amount = Math.max(0, amount);
    if (!isTrueDamage && attacker && attacker.attackPower !== undefined && attacker.attackPower < 1) amount *= attacker.attackPower;
    if (target.id === 'schrodinger' && !isTrueDamage && Math.random() < 0.3) {
        if (!silent) addDamageText(target.x, target.y - 20, '量子闪避!', '#9370db');
        return;
    }
    if (target.id === 'faraday' && target.shieldTimer > 0 && !isTrueDamage) {
        amount *= 0.7;
        if (attacker && attacker.hp > 0 && attacker.id !== 'faraday' && !silent) {
            const reflectDmg = Math.round(amount * 0.2);
            if (reflectDmg > 0) {
                attacker.hp -= reflectDmg;
                attacker.chargeStacks = Math.min(4, (attacker.chargeStacks || 0) + 1);
                attacker.chargeTimer = 4;
            }
        }
    }
    if (!isTrueDamage && target.dr > 0) amount *= (1 - target.dr / 100);
    if (target.id === 'hawking') target.noDamageTimer = 0;
    target.hp -= amount;
    if (!silent && amount >= 0.5) addDamageText(target.x, target.y, Math.round(amount), isTrueDamage ? '#ff00ff' : '#ff6666');
    if (attacker && attacker.id === 'lavoisier' && amount > 0 && Math.random() < 0.5) {
        if (massParticles.length < 15) massParticles.push({ x: target.x + (Math.random() - 0.5) * 30, y: target.y + (Math.random() - 0.5) * 30, life: 10 });
    }
}

function updateEffects(dt) {
    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); }
}
function addDamageText(x, y, amount, color) {
    if (damageTexts.length > 120) damageTexts.shift();
    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();
    drawOxygenFields(); drawHormonePools(); drawGeneSeeds(); drawApples();
    drawPrisms(); drawSatellites(); drawMassParticles(); drawPeaShooters();
    drawFaradayCages(); drawRadiationZones(); drawWormholes(); drawKeplerPlanets();
    drawCutCircleBullets(); drawPiShields(); drawSchrodingerClones(); drawCatBoxes();
    drawBombardZones(); drawBlackHoles(); drawRadiumBlocks(); drawDroppedItems();
    drawMissiles(); drawSpringOscillators();
    drawEffects(); drawProjectiles(); drawEntities(); 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 drawBlackHoles() {
    for (const bh of blackHoles) {
        // 吸引力场
        ctx.beginPath();
        ctx.arc(bh.x, bh.y, bh.radius, 0, Math.PI * 2);
        ctx.fillStyle = 'rgba(0, 0, 0, 0.15)';
        ctx.fill();
        ctx.strokeStyle = 'rgba(138, 43, 226, 0.4)';
        ctx.lineWidth = 2;
        ctx.setLineDash([8, 4]);
        ctx.stroke();
        ctx.setLineDash([]);
        // 黑洞本体
        const pulse = 20 + Math.sin(performance.now() / 200) * 5;
        ctx.beginPath();
        ctx.arc(bh.x, bh.y, pulse, 0, Math.PI * 2);
        ctx.fillStyle = '#000';
        ctx.fill();
        ctx.strokeStyle = '#8a2be2';
        ctx.lineWidth = 3;
        ctx.stroke();
        // 吸积盘
        for (let i = 0; i < 8; i++) {
            const angle = performance.now() / 500 + i * Math.PI / 4;
            const r = pulse + 8 + Math.sin(performance.now() / 300 + i) * 4;
            ctx.beginPath();
            ctx.arc(bh.x + Math.cos(angle) * r, bh.y + Math.sin(angle) * r, 3, 0, Math.PI * 2);
            ctx.fillStyle = 'rgba(138, 43, 226, 0.8)';
            ctx.fill();
        }
    }
}

function drawRadiumBlocks() {
    for (const rb of radiumBlocks) {
        const alpha = Math.min(1, rb.life / 0.5);
        // 中心镭块
        ctx.beginPath();
        ctx.arc(rb.x, rb.y, 12, 0, Math.PI * 2);
        ctx.fillStyle = `rgba(0, 255, 127, ${alpha})`;
        ctx.fill();
        ctx.strokeStyle = `rgba(255, 255, 255, ${alpha})`;
        ctx.lineWidth = 2;
        ctx.stroke();
        // 四条射线
        const rayAngles = [0, Math.PI / 2, Math.PI, Math.PI * 1.5];
        for (const rayBaseAngle of rayAngles) {
            const rayAngle = rayBaseAngle + rb.angle;
            ctx.beginPath();
            ctx.moveTo(rb.x, rb.y);
            ctx.lineTo(rb.x + Math.cos(rayAngle) * rb.rayLength, rb.y + Math.sin(rayAngle) * rb.rayLength);
            ctx.strokeStyle = `rgba(0, 255, 127, ${0.6 * alpha})`;
            ctx.lineWidth = 4;
            ctx.stroke();
        }
    }
}

function drawDroppedItems() {
    for (const item of droppedItems) {
        const pulse = 10 + Math.sin(performance.now() / 200) * 2;
        ctx.beginPath();
        ctx.arc(item.x, item.y, pulse, 0, Math.PI * 2);
        if (item.type === 'NH4Cl') {
            ctx.fillStyle = 'rgba(139, 69, 19, 0.8)';
            ctx.fill();
            ctx.strokeStyle = '#8B4513';
        } else {
            ctx.fillStyle = 'rgba(0, 255, 0, 0.8)';
            ctx.fill();
            ctx.strokeStyle = '#00ff00';
        }
        ctx.lineWidth = 2;
        ctx.stroke();
        // 标注
        ctx.fillStyle = '#fff';
        ctx.font = 'bold 10px Arial';
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        ctx.fillText(item.type === 'NH4Cl' ? '☠' : '+', item.x, item.y);
    }
}

function drawSpringOscillators() {
    for (const s of springOscillators) {
        const cx = s.currentX !== undefined ? s.currentX : s.x;
        const cy = s.currentY !== undefined ? s.currentY : s.y;
        const alpha = Math.min(1, s.life / 1);
        ctx.beginPath();
        ctx.moveTo(s.x - s.perpX * s.oscRange, s.y - s.perpY * s.oscRange);
        ctx.lineTo(s.x + s.perpX * s.oscRange, s.y + s.perpY * s.oscRange);
        ctx.strokeStyle = `rgba(160, 82, 45, ${0.3 * alpha})`;
        ctx.lineWidth = 1; ctx.setLineDash([3, 3]); ctx.stroke(); ctx.setLineDash([]);
        ctx.save(); ctx.translate(cx, cy); ctx.rotate(Math.atan2(s.perpY, s.perpX));
        ctx.beginPath();
        for (let i = 0; i <= s.armLength; i++) {
            const x = i - s.armLength / 2;
            const y = Math.sin(i * 1.5) * 6;
            if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
        }
        ctx.strokeStyle = `rgba(160, 82, 45, ${alpha})`; ctx.lineWidth = 2; ctx.stroke();
        ctx.restore();
        ctx.beginPath(); ctx.arc(cx, cy, 10, 0, Math.PI * 2);
        ctx.fillStyle = `rgba(160, 82, 45, ${0.8 * alpha})`; ctx.fill();
        ctx.strokeStyle = `rgba(255, 255, 255, ${alpha})`; ctx.lineWidth = 2; ctx.stroke();
        if (s.visualFlash > 0) {
            ctx.beginPath(); ctx.arc(cx, cy, 20, 0, Math.PI * 2);
            ctx.strokeStyle = `rgba(255, 255, 0, ${Math.min(1, s.visualFlash * 3)})`;
            ctx.lineWidth = 3; ctx.stroke();
        }
    }
}

function drawMissiles() {
    for (const m of missiles) {
        ctx.save(); ctx.translate(m.x, m.y);
        const angle = Math.atan2(m.vy, m.vx);
        ctx.rotate(angle);
        ctx.fillStyle = '#FF4500';
        ctx.beginPath();
        ctx.moveTo(12, 0); ctx.lineTo(-8, -6); ctx.lineTo(-8, 6); ctx.closePath();
        ctx.fill();
        ctx.fillStyle = '#ccc'; ctx.fillRect(-8, -6, 4, 12);
        ctx.restore();
        const barW = 20, barH = 3;
        ctx.fillStyle = '#333'; ctx.fillRect(m.x - barW / 2, m.y - 20, barW, barH);
        ctx.fillStyle = '#FF4500'; ctx.fillRect(m.x - barW / 2, m.y - 20, barW * (m.hp / m.maxHp), barH);
    }
}

function drawBombardZones() {
    for (const z of bombardZones) {
        const progress = z.life / z.maxLife;
        ctx.beginPath(); ctx.arc(z.x, z.y, z.radius, 0, Math.PI * 2);
        const alpha = progress > 0.5 ? 0.2 : 0.5;
        ctx.fillStyle = `rgba(255, 69, 0, ${alpha})`; ctx.fill();
        ctx.strokeStyle = `rgba(255, 69, 0, 0.8)`; ctx.lineWidth = 3;
        ctx.setLineDash([5, 5]); ctx.stroke(); ctx.setLineDash([]);
    }
}

function drawOxygenFields() { for (const f of oxygenFields) { ctx.beginPath(); ctx.arc(f.x, f.y, f.radius, 0, Math.PI * 2); ctx.fillStyle = 'rgba(255, 140, 0, 0.18)'; ctx.fill(); ctx.strokeStyle = '#ff8c00'; ctx.lineWidth = 2; ctx.stroke(); } }
function drawHormonePools() { for (const p of hormonePools) { ctx.beginPath(); ctx.arc(p.x, p.y, 40, 0, Math.PI * 2); ctx.fillStyle = 'rgba(255, 68, 204, 0.2)'; ctx.fill(); ctx.strokeStyle = '#ff44cc'; ctx.lineWidth = 2; ctx.stroke(); } }
function drawGeneSeeds() { for (const g of geneSeeds) { ctx.beginPath(); ctx.arc(g.x, g.y, g.radius, 0, Math.PI * 2); ctx.fillStyle = '#ffff00'; ctx.fill(); ctx.strokeStyle = '#fff'; ctx.lineWidth = 2; ctx.stroke(); } }
function drawApples() { for (const 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(); ctx.fillStyle = '#00ff00'; ctx.beginPath(); ctx.ellipse(a.x, a.y - a.radius - 2, 4, 2, Math.PI / 4, 0, Math.PI * 2); ctx.fill(); } }
function drawPrisms() {
    for (const 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++) {
            const 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.beginPath(); ctx.moveTo(0, -12); ctx.lineTo(10, 8); ctx.lineTo(-10, 8); ctx.closePath();
        ctx.fillStyle = '#ffffff'; ctx.fill(); ctx.strokeStyle = '#aaa'; ctx.lineWidth = 1; ctx.stroke();
        ctx.restore();
    }
}
function drawSatellites() { for (const hero of heroEntities) { if (hero.id !== 'newton' || !hero.satellites) continue; const orbitRadius = 2 * CONFIG.GRID_SIZE; for (const sat of hero.satellites) { const satX = hero.x + Math.cos(sat.angle) * orbitRadius; const satY = hero.y + Math.sin(sat.angle) * orbitRadius; ctx.beginPath(); ctx.arc(satX, satY, 8, 0, Math.PI * 2); ctx.fillStyle = '#00ffff'; ctx.fill(); ctx.strokeStyle = '#ffffff'; ctx.lineWidth = 2; ctx.stroke(); } } }
function drawMassParticles() { for (const p of massParticles) { ctx.beginPath(); ctx.arc(p.x, p.y, 6, 0, Math.PI * 2); ctx.fillStyle = '#ffffff'; ctx.fill(); ctx.strokeStyle = '#ff8c00'; ctx.lineWidth = 2; ctx.stroke(); } }
function drawPeaShooters() { for (const p of peaShooters) { ctx.fillStyle = '#33cc33'; ctx.fillRect(p.x - 6, p.y - 6, 12, 12); ctx.strokeStyle = '#fff'; ctx.lineWidth = 1; ctx.strokeRect(p.x - 6, p.y - 6, 12, 12); const hpP = p.hp / p.maxHp; ctx.fillStyle = '#333'; ctx.fillRect(p.x - 15, p.y - 15, 30, 3); ctx.fillStyle = '#00ff00'; ctx.fillRect(p.x - 15, p.y - 15, 30 * hpP, 3); } }
function drawFaradayCages() { for (const cage of faradayCages) { const alpha = Math.min(1, cage.life / 1); ctx.beginPath(); ctx.arc(cage.x, cage.y, cage.radius, 0, Math.PI * 2); ctx.fillStyle = `rgba(255, 215, 0, ${0.12 * alpha})`; ctx.fill(); ctx.strokeStyle = `rgba(255, 215, 0, ${alpha})`; ctx.lineWidth = 3; ctx.stroke(); for (let i = 0; i < 8; i++) { const angle = (i / 8) * Math.PI * 2; ctx.beginPath(); ctx.moveTo(cage.x, cage.y); ctx.lineTo(cage.x + Math.cos(angle) * cage.radius, cage.y + Math.sin(angle) * cage.radius); ctx.strokeStyle = `rgba(255, 215, 0, ${0.4 * alpha})`; ctx.lineWidth = 1; ctx.stroke(); } } }
function drawRadiationZones() { for (const zone of radiationZones) { const alpha = Math.min(1, zone.life / 1); ctx.beginPath(); ctx.arc(zone.x, zone.y, zone.radius, 0, Math.PI * 2); ctx.fillStyle = `rgba(138, 43, 226, ${0.15 * alpha})`; ctx.fill(); ctx.strokeStyle = `rgba(138, 43, 226, ${alpha})`; ctx.lineWidth = 2; ctx.stroke(); } }
function drawWormholes() { for (const w of wormholes) { if (w.used) continue; const alpha = Math.min(1, w.life / 2); ctx.beginPath(); ctx.arc(w.entryX, w.entryY, 18, 0, Math.PI * 2); ctx.fillStyle = `rgba(138, 43, 226, ${0.5 * alpha})`; ctx.fill(); ctx.strokeStyle = `rgba(138, 43, 226, ${alpha})`; ctx.lineWidth = 3; ctx.stroke(); ctx.beginPath(); ctx.arc(w.entryX, w.entryY, 8, 0, Math.PI * 2); ctx.fillStyle = '#000'; ctx.fill(); ctx.beginPath(); ctx.arc(w.exitX, w.exitY, 18, 0, Math.PI * 2); ctx.fillStyle = `rgba(138, 43, 226, ${0.5 * alpha})`; ctx.fill(); ctx.strokeStyle = `rgba(200, 100, 255, ${alpha})`; ctx.lineWidth = 3; ctx.stroke(); } }
function drawKeplerPlanets() { for (const planet of keplerPlanets) { const owner = heroEntities.find(h => h.id === planet.ownerId); if (!owner || owner.hp <= 0) continue; ctx.beginPath(); ctx.arc(owner.x, owner.y, planet.orbitRadius, 0, Math.PI * 2); ctx.strokeStyle = 'rgba(65, 105, 225, 0.15)'; ctx.lineWidth = 1; ctx.stroke(); const px = owner.x + Math.cos(planet.angle) * planet.orbitRadius; const py = owner.y + Math.sin(planet.angle) * planet.orbitRadius; ctx.beginPath(); ctx.arc(px, py, 8, 0, Math.PI * 2); ctx.fillStyle = '#4169e1'; ctx.fill(); ctx.strokeStyle = '#fff'; ctx.lineWidth = 2; ctx.stroke(); } }
function drawCutCircleBullets() { for (const b of cutCircleBullets) { ctx.save(); ctx.translate(b.x, b.y); ctx.beginPath(); for (let i = 0; i <= b.sides; i++) { const angle = (i / b.sides) * Math.PI * 2; const x = Math.cos(angle) * 8, y = Math.sin(angle) * 8; if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); } ctx.closePath(); ctx.fillStyle = '#00ced1'; ctx.fill(); ctx.strokeStyle = '#fff'; ctx.lineWidth = 1; ctx.stroke(); ctx.restore(); } }
function drawPiShields() { for (const s of piShields) { const owner = heroEntities.find(h => h.id === s.ownerId); if (!owner || owner.hp <= 0) continue; const sx = owner.x + Math.cos(s.angle) * s.orbitRadius; const sy = owner.y + Math.sin(s.angle) * s.orbitRadius; ctx.beginPath(); ctx.arc(sx, sy, 14, 0, Math.PI * 2); ctx.fillStyle = 'rgba(0, 206, 209, 0.8)'; ctx.fill(); ctx.strokeStyle = '#fff'; ctx.lineWidth = 2; ctx.stroke(); ctx.fillStyle = '#111'; ctx.font = 'bold 14px Arial'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText('π', sx, sy); } }
function drawSchrodingerClones() { for (const c of schrodingerClones) { const alpha = Math.min(1, c.life / 1) * 0.5; ctx.beginPath(); ctx.arc(c.x, c.y, 20, 0, Math.PI * 2); ctx.fillStyle = `rgba(147, 112, 219, ${alpha})`; ctx.fill(); ctx.strokeStyle = `rgba(147, 112, 219, ${alpha * 1.5})`; ctx.lineWidth = 2; ctx.setLineDash([4, 4]); ctx.stroke(); ctx.setLineDash([]); } }
function drawCatBoxes() { for (const box of catBoxes) { const alpha = Math.min(1, box.life / 0.5); ctx.save(); ctx.translate(box.x, box.y); ctx.strokeStyle = `rgba(147, 112, 219, ${alpha})`; ctx.lineWidth = 3; ctx.strokeRect(-25, -25, 50, 50); ctx.fillStyle = `rgba(147, 112, 219, ${alpha * 0.2})`; ctx.fillRect(-25, -25, 50, 50); ctx.fillStyle = `rgba(255, 255, 255, ${alpha})`; ctx.font = 'bold 12px Arial'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText('?', 0, 0); ctx.restore(); } }
function drawEffects() { for (const e of impactEffects) { const t = 1 - Math.max(0, Math.min(1, e.life / (e.maxLife || 0.3))); ctx.beginPath(); ctx.arc(e.x, e.y, Math.max(0.5, t * 20), 0, Math.PI * 2); ctx.strokeStyle = e.color || '#fff'; ctx.lineWidth = 3; ctx.stroke(); } }
function drawProjectiles() {
    for (const p of projectiles) {
        ctx.beginPath(); ctx.moveTo(p.startX, p.startY); ctx.lineTo(p.x, p.y);
        ctx.strokeStyle = p.type === 'pea' ? '#33cc33'
                        : p.type === 'superpea' ? '#ffff00'
                        : p.type === 'taylor' ? '#3366ff'
                        : p.type === 'hormone' ? '#ff44cc'
                        : p.type === 'oxygen' ? '#ff8c00'
                        : p.type === 'spark' ? '#ffd700'
                        : p.type === 'focus' ? '#ffaa00'
                        : p.type === 'quantum' ? '#9370db'
                        : p.type === 'cell' ? '#A0522D'
                        : p.type === 'uranium' ? '#00FF7F'
                        : p.type === 'chem_NaCl' ? '#FF69B4'
                        : p.type === 'chem_H2O' ? '#87CEEB'
                        : p.type === 'chem_CO2' ? '#808080'
                        : '#fff';
        ctx.lineWidth = 4; ctx.stroke();
        ctx.beginPath(); ctx.arc(p.x, p.y, 5, 0, Math.PI * 2);
        ctx.fillStyle = '#fff'; ctx.fill();
    }
}

function drawEntities() {
    for (const entity of heroEntities) {
        ctx.globalAlpha = entity.hp > 0 ? 1.0 : 0.2;
        ctx.beginPath();
        ctx.arc(entity.x, entity.y, entity.radius, 0, Math.PI * 2);
        ctx.fillStyle = entity.stunTimer > 0 ? '#ffff00' : entity.color;
        ctx.fill();
        ctx.strokeStyle = 'rgba(255,255,255,0.8)';
        ctx.lineWidth = 2; ctx.stroke();

        if (entity.lowHpTimer > 0) {
            ctx.beginPath(); ctx.arc(entity.x, entity.y, entity.radius + 6, 0, Math.PI * 2);
            ctx.strokeStyle = 'rgba(160, 82, 45, 0.9)'; ctx.lineWidth = 3;
            ctx.setLineDash([4, 4]); ctx.stroke(); ctx.setLineDash([]);
        }
        if (entity.id === 'faraday' && entity.shieldTimer > 0) {
            ctx.beginPath(); ctx.arc(entity.x, entity.y, entity.radius + 6, 0, Math.PI * 2);
            ctx.strokeStyle = 'rgba(255, 215, 0, 0.8)'; ctx.lineWidth = 3; ctx.stroke();
        }
        if (entity.id === 'zuchongzhi' && entity.piShieldTimer > 0) {
            ctx.beginPath(); ctx.arc(entity.x, entity.y, entity.radius + 8, 0, Math.PI * 2);
            ctx.strokeStyle = 'rgba(0, 206, 209, 0.7)'; ctx.lineWidth = 2; ctx.setLineDash([5, 5]); ctx.stroke(); ctx.setLineDash([]);
        }
        if (entity.id === 'hawking' && entity.noDamageTimer >= 5 && entity.hp < entity.maxHp) {
            ctx.beginPath(); ctx.arc(entity.x, entity.y, entity.radius + 10, 0, Math.PI * 2);
            ctx.strokeStyle = 'rgba(0, 255, 0, 0.6)'; ctx.lineWidth = 3; ctx.setLineDash([4, 4]); ctx.stroke(); ctx.setLineDash([]);
        }
        if (entity.permanentSlow > 0) {
            ctx.beginPath(); ctx.arc(entity.x, entity.y, entity.radius + 4, 0, Math.PI * 2);
            ctx.strokeStyle = 'rgba(0, 255, 127, 0.5)'; ctx.lineWidth = 2; ctx.stroke();
        }

        ctx.save();
        ctx.translate(entity.x, entity.y);
        if (entity.id === 'newton') {
            ctx.fillStyle = '#fff';
            ctx.beginPath();
            ctx.moveTo(0, -entity.radius - 4); ctx.lineTo(6, -entity.radius + 2); ctx.lineTo(-6, -entity.radius + 2);
            ctx.closePath(); ctx.fill();
        } else if (entity.id === 'lavoisier') {
            ctx.strokeStyle = '#111'; ctx.lineWidth = 2;
            ctx.beginPath(); ctx.moveTo(0, 6); ctx.lineTo(0, -6); ctx.stroke();
            ctx.beginPath(); ctx.moveTo(-8, -4); ctx.lineTo(8, -4); ctx.stroke();
        } else if (entity.id === 'mendel') {
            ctx.beginPath(); ctx.arc(0, 2, 8, 0, Math.PI * 2); ctx.fillStyle = '#33cc33'; ctx.fill();
            ctx.beginPath(); ctx.moveTo(0, 0); ctx.quadraticCurveTo(5, -10, 12, -8); ctx.strokeStyle = '#00ff00'; ctx.lineWidth = 3; ctx.stroke();
        } else if (entity.id === 'taylor') {
            ctx.fillStyle = '#111'; ctx.font = 'bold 18px Arial, sans-serif';
            ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
            ctx.fillText('∑', 0, 2);
        } else if (entity.id === 'starling') {
            ctx.strokeStyle = '#ff44cc'; ctx.lineWidth = 2;
            ctx.beginPath(); ctx.moveTo(-6, -8); ctx.bezierCurveTo(6, -4, -6, 0, 6, 4); ctx.stroke();
            ctx.beginPath(); ctx.moveTo(6, -8); ctx.bezierCurveTo(-6, -4, 6, 0, -6, 4); ctx.stroke();
        } else if (entity.id === 'faraday') {
            ctx.fillStyle = '#111';
            ctx.beginPath();
            ctx.moveTo(-2, -10); ctx.lineTo(4, -2); ctx.lineTo(0, -2);
            ctx.lineTo(3, 8); ctx.lineTo(-4, -1); ctx.lineTo(-1, -1); ctx.closePath();
            ctx.fill();
        } else if (entity.id === 'hawking') {
            ctx.strokeStyle = '#000'; ctx.lineWidth = 2;
            for (let i = 0; i < 3; i++) { ctx.beginPath(); ctx.arc(0, 0, 4 + i * 3, i * 1.5, i * 1.5 + Math.PI * 1.4); ctx.stroke(); }
        } else if (entity.id === 'kepler') {
            ctx.strokeStyle = '#fff'; ctx.lineWidth = 1.5;
            ctx.beginPath(); ctx.ellipse(0, 0, 14, 8, 0, 0, Math.PI * 2); ctx.stroke();
        } else if (entity.id === 'zuchongzhi') {
            ctx.fillStyle = '#111'; ctx.font = 'bold 18px Arial, serif';
            ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
            ctx.fillText('π', 0, 2);
        } else if (entity.id === 'schrodinger') {
            ctx.strokeStyle = '#111'; ctx.lineWidth = 2;
            ctx.beginPath(); ctx.arc(0, 0, 10, 0, Math.PI * 2); ctx.stroke();
        } else if (entity.id === 'qianxuesen') {
            ctx.fillStyle = '#111';
            ctx.beginPath();
            ctx.moveTo(0, -12); ctx.lineTo(6, -4); ctx.lineTo(2, 8); ctx.lineTo(-2, 8); ctx.lineTo(-6, -4); ctx.closePath();
            ctx.fill();
            ctx.fillStyle = '#FF4500'; ctx.fillRect(-3, -8, 6, 4);
        } else if (entity.id === 'hooke') {
            ctx.strokeStyle = '#A0522D'; ctx.lineWidth = 2;
            ctx.beginPath();
            for (let i = 0; i <= 12; i++) {
                const x = -8 + (i / 12) * 16;
                const y = Math.sin(i * 1.2) * 5;
                if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
            }
            ctx.stroke();
        } else if (entity.id === 'curie') {
            ctx.fillStyle = '#111'; ctx.font = 'bold 18px Arial';
            ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
            ctx.fillText('Ra', 0, 2);
        } else if (entity.id === 'houdebang') {
            ctx.fillStyle = '#111'; ctx.font = 'bold 14px Arial';
            ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
            ctx.fillText('NaCl', 0, 2);
        }
        ctx.restore();

        const barW = 50, barH = 5;
        const barX = entity.x - barW / 2;
        const barY = entity.y - entity.radius - 15;
        const hpP = Math.max(0, entity.hp) / entity.maxHp;
        ctx.fillStyle = '#333'; ctx.fillRect(barX, barY, barW, barH);
        ctx.fillStyle = hpP > 0.3 ? '#00ff00' : '#ff0000';
        ctx.fillRect(barX, barY, barW * hpP, barH);
        ctx.fillStyle = '#fff'; ctx.font = '11px sans-serif';
        ctx.textAlign = 'center'; ctx.textBaseline = 'alphabetic';
        ctx.fillText(entity.name, entity.x, barY - 5);
        ctx.globalAlpha = 1.0;
    }
}

function drawDamageTexts() {
    ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
    for (const txt of damageTexts) {
        ctx.fillStyle = txt.color;
        ctx.font = 'bold 12px Arial, sans-serif';
        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 (1977 lines, 90.8 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-munzw2f3" to link back to the original. Then publish at arcadelab.ai/publish.