🎮ArcadeLab

深空舰队 - 拦截与全阵营版

by EpicCoder88
677 lines31.5 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; overflow: hidden; background: #03050a; font-family: monospace; touch-action: none; }
  canvas { display: block; width: 100vw; height: 100vh; }
  #ui-layer { position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: flex; flex-direction: column; justify-content: center; align-items: center; background: rgba(3, 5, 10, 0.9); z-index: 10; }
  .panel { background: rgba(15, 25, 45, 0.95); border: 1px solid #3a5a8a; padding: 15px; border-radius: 8px; color: #fff; width: 90%; max-width: 600px; }
  h1 { text-align: center; font-size: 18px; color: #66ccff; margin: 0 0 15px 0; }
  .fleet-config { display: flex; justify-content: space-between; margin-bottom: 15px; }
  .side { width: 48%; }
  .side h3 { font-size: 13px; margin: 0 0 8px 0; color: #a0b0d0; border-bottom: 1px solid #3a5a8a; padding-bottom: 4px; text-align: center;}
  .row { display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; font-size: 11px; }
  .btn-group button { width: 20px; height: 20px; background: #2a4a7a; border: none; color: #fff; border-radius: 3px; cursor: pointer; }
  .val { width: 16px; text-align: center; font-weight: bold; color: #66ccff;}
  #start-btn { width: 100%; padding: 10px; background: #0055ff; color: #fff; border: none; border-radius: 4px; font-size: 15px; font-weight: bold; cursor: pointer; }
  #combat-ui { position: absolute; top: 10px; left: 10px; color: #fff; pointer-events: none; display: none; }
  .log { font-size: 11px; color: #aaa; max-width: 300px; text-shadow: 1px 1px 2px #000; }
</style>
</head>
<body>

<div id="ui-layer">
  <div class="panel">
    <h1>深空舰队配置 (全阵营解锁)</h1>
    <div class="fleet-config">
      <div class="side">
        <h3 style="color:#66ccff">蓝方</h3>
        <div class="row"><span>激光舰</span> <div class="btn-group"><button onclick="updateCount('bLaser',-1)">-</button><span class="val" id="val-bLaser">2</span><button onclick="updateCount('bLaser',1)">+</button></div></div>
        <div class="row"><span>突击舰</span> <div class="btn-group"><button onclick="updateCount('bAssault',-1)">-</button><span class="val" id="val-bAssault">1</span><button onclick="updateCount('bAssault',1)">+</button></div></div>
        <div class="row"><span>导弹舰</span> <div class="btn-group"><button onclick="updateCount('bMissile',-1)">-</button><span class="val" id="val-bMissile">0</span><button onclick="updateCount('bMissile',1)">+</button></div></div>
        <div class="row"><span>补给舰</span> <div class="btn-group"><button onclick="updateCount('bSupply',-1)">-</button><span class="val" id="val-bSupply">0</span><button onclick="updateCount('bSupply',1)">+</button></div></div>
      </div>
      <div class="side">
        <h3 style="color:#ff3366">红方</h3>
        <div class="row"><span>激光舰</span> <div class="btn-group"><button onclick="updateCount('rLaser',-1)">-</button><span class="val" id="val-rLaser">0</span><button onclick="updateCount('rLaser',1)">+</button></div></div>
        <div class="row"><span>突击舰</span> <div class="btn-group"><button onclick="updateCount('rAssault',-1)">-</button><span class="val" id="val-rAssault">0</span><button onclick="updateCount('rAssault',1)">+</button></div></div>
        <div class="row"><span>导弹舰</span> <div class="btn-group"><button onclick="updateCount('rMissile',-1)">-</button><span class="val" id="val-rMissile">2</span><button onclick="updateCount('rMissile',1)">+</button></div></div>
        <div class="row"><span>补给舰</span> <div class="btn-group"><button onclick="updateCount('rSupply',-1)">-</button><span class="val" id="val-rSupply">1</span><button onclick="updateCount('rSupply',1)">+</button></div></div>
      </div>
    </div>
    <button id="start-btn" onclick="startBattle()">开 战</button>
  </div>
</div>

<div id="combat-ui"><div class="log" id="log"></div></div>
<canvas id="gameCanvas"></canvas>

<script>
// ================= 基础设定 =================
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
let W = window.innerWidth, H = window.innerHeight;
function resize() { W = canvas.width = window.innerWidth; H = canvas.height = window.innerHeight; }
window.addEventListener('resize', resize); resize();

const WORLD_W = 8000; const WORLD_H = 6000;
let cam = { x: WORLD_W/2, y: WORLD_H/2, zoom: 0.15 };
let isDragging = false, lastTouchDist = 0, startTouch = { x: 0, y: 0 }, camStart = { x: 0, y: 0 };

function dist(a, b) { return Math.hypot(a.x - b.x, a.y - b.y); }
function angleTo(a, b) { return Math.atan2(b.y - a.y, b.x - a.x); }
function normalizeAngle(a) { while (a > Math.PI) a -= Math.PI*2; while (a < -Math.PI) a += Math.PI*2; return a; }
function logMsg(msg) { document.getElementById('log').innerHTML = `> ${msg}<br>` + document.getElementById('log').innerHTML.slice(0, 150); }

const config = { bLaser: 2, bAssault: 1, bMissile: 0, bSupply: 0, rLaser: 0, rAssault: 0, rMissile: 2, rSupply: 1 };
function updateCount(type, delta) { config[type] = Math.max(0, config[type] + delta); document.getElementById('val-' + type).innerText = config[type]; }

let entities = [], bullets = [], particles = [];
let gameState = 'config';

// 触屏
canvas.addEventListener('touchstart', (e) => {
  if (gameState !== 'playing') return;
  if (e.touches.length === 1) { isDragging = true; startTouch = { x: e.touches[0].clientX, y: e.touches[0].clientY }; camStart = { x: cam.x, y: cam.y }; }
  else if (e.touches.length === 2) { isDragging = false; lastTouchDist = Math.hypot(e.touches[0].clientX - e.touches[1].clientX, e.touches[0].clientY - e.touches[1].clientY); }
});
canvas.addEventListener('touchmove', (e) => {
  if (gameState !== 'playing') return; e.preventDefault(); 
  if (e.touches.length === 1 && isDragging) {
    cam.x = camStart.x - (e.touches[0].clientX - startTouch.x) / cam.zoom;
    cam.y = camStart.y - (e.touches[0].clientY - startTouch.y) / cam.zoom;
  } else if (e.touches.length === 2) {
    const currentDist = Math.hypot(e.touches[0].clientX - e.touches[1].clientX, e.touches[0].clientY - e.touches[1].clientY);
    cam.zoom = Math.max(0.05, Math.min(cam.zoom * (currentDist / lastTouchDist), 1.5));
    lastTouchDist = currentDist;
  }
});
canvas.addEventListener('touchend', () => { isDragging = false; });

// ================= 模块绘制 =================
const MOD_W = 45, MOD_H = 26;

function drawTurret(ctx, x, y, angle, type) {
  ctx.save(); ctx.translate(x, y); ctx.rotate(angle);
  if (type === 'railgun') {
    ctx.fillStyle = '#2a3b4a'; ctx.beginPath(); ctx.arc(0, 0, 8, 0, Math.PI*2); ctx.fill();
    ctx.strokeStyle = '#5c738a'; ctx.lineWidth = 1.5; ctx.stroke();
    ctx.fillStyle = '#111'; ctx.fillRect(4, -5, 22, 4); ctx.fillRect(4, 1, 22, 4);
    ctx.fillStyle = '#00ffcc'; ctx.fillRect(20, -5, 6, 4); ctx.fillRect(20, 1, 6, 4);
  } else if (type === 'pd') {
    ctx.fillStyle = '#3b3b3b'; ctx.beginPath(); ctx.arc(0, 0, 5, 0, Math.PI*2); ctx.fill();
    ctx.fillStyle = '#888'; ctx.fillRect(3, -3, 10, 6);
  } else if (type === 'laser_intercept') {
    ctx.fillStyle = '#1e2b38'; ctx.beginPath(); ctx.arc(0, 0, 5, 0, Math.PI*2); ctx.fill();
    ctx.strokeStyle = '#66ccff'; ctx.lineWidth = 1; ctx.stroke();
    ctx.fillStyle = '#111'; ctx.fillRect(3, -1.5, 10, 3);
    ctx.fillStyle = '#66ccff'; ctx.beginPath(); ctx.arc(13, 0, 2, 0, Math.PI*2); ctx.fill();
  }
  ctx.restore();
}

function drawFixedMainCannon(ctx, x, y, isOverheated, broken) {
  ctx.save(); ctx.translate(x, y);
  ctx.fillStyle = broken ? '#111' : (isOverheated ? '#552222' : '#1e2b38');
  ctx.beginPath(); ctx.moveTo(-10, -12); ctx.lineTo(15, -8); ctx.lineTo(15, 8); ctx.lineTo(-10, 12); ctx.closePath(); ctx.fill();
  if(!broken) {
    ctx.strokeStyle = '#4a6b8c'; ctx.lineWidth = 1.5; ctx.stroke();
    ctx.fillStyle = '#0a0a0a'; ctx.fillRect(15, -6, 35, 4); ctx.fillRect(15, 2, 35, 4);
    if (!isOverheated) {
      ctx.fillStyle = '#66ccff'; ctx.fillRect(15, -3, 35, 2);
      ctx.beginPath(); ctx.arc(50, -4, 3, 0, Math.PI*2); ctx.fill(); ctx.beginPath(); ctx.arc(50, 4, 3, 0, Math.PI*2); ctx.fill();
    }
  }
  ctx.restore();
}

function drawFire(ctx, x, y) {
  const t = Date.now() * 0.01;
  for(let i=0; i<3; i++) {
    const size = Math.random() * 8 + 4;
    ctx.fillStyle = Math.random() > 0.5 ? 'rgba(255, 100, 0, 0.8)' : 'rgba(255, 200, 0, 0.8)';
    ctx.beginPath(); ctx.arc(x + (Math.random()-0.5)*20, y + (Math.random()-0.5)*10 - Math.sin(t+i)*5, size, 0, Math.PI*2); ctx.fill();
  }
}

function drawModule(ctx, module, x, y, isLast) {
  ctx.save(); ctx.translate(x, y);
  const isBroken = module.hp <= 0;
  const isCrit = module.hp > 0 && module.hp <= 20;
  
  if (module.type === 'solar' || module.type === 'radiator') {
    if (isBroken || isCrit) {
      ctx.fillStyle = '#222'; ctx.fillRect(6, -28, MOD_W-12, 28); ctx.fillRect(6, MOD_H, MOD_W-12, 28);
      if (!isBroken) { ctx.fillStyle = 'rgba(255,50,50,0.3)'; ctx.fillRect(6, -28, MOD_W-12, 28); ctx.fillRect(6, MOD_H, MOD_W-12, 28); }
    } else if (module.type === 'solar') {
      ctx.fillStyle = '#1a2b4c'; ctx.strokeStyle = '#4a7aab'; ctx.lineWidth = 1;
      ctx.fillRect(6, -28, MOD_W-12, 28); ctx.strokeRect(6, -28, MOD_W-12, 28);
      ctx.fillRect(6, MOD_H, MOD_W-12, 28); ctx.strokeRect(6, MOD_H, MOD_W-12, 28);
      ctx.strokeStyle = 'rgba(102, 204, 255, 0.3)';
      for(let i=6; i<MOD_W-6; i+=8) { ctx.beginPath(); ctx.moveTo(i, -28); ctx.lineTo(i, 0); ctx.stroke(); ctx.beginPath(); ctx.moveTo(i, MOD_H); ctx.lineTo(i, MOD_H+28); ctx.stroke(); }
    } else {
      ctx.fillStyle = '#331111'; ctx.fillRect(8, -22, MOD_W-16, 22); ctx.fillRect(8, MOD_H, MOD_W-16, 22);
      if(isCrit) { drawFire(ctx, MOD_W/2, -10); drawFire(ctx, MOD_W/2, MOD_H+10); }
    }
    ctx.fillStyle = '#555'; ctx.fillRect(MOD_W/2-2, -5, 4, MOD_H+10);
    ctx.restore(); return;
  }

  let baseColor = (isBroken || isCrit) ? '#111' : '#2c3e50', highlight = (isBroken || isCrit) ? '#222' : '#4a6b8c', shadow = (isBroken || isCrit) ? '#000' : '#1a252f';
  if (module.type === 'engine' && !isBroken && !isCrit) { baseColor = '#3d2b2b'; highlight = '#6b4c4c'; }
  if (module.type.includes('weapon') && !isBroken && !isCrit) { baseColor = '#1a2b3b'; highlight = '#2e4a6b'; } 

  const chamfer = 4;
  ctx.beginPath(); ctx.moveTo(chamfer, 0); ctx.lineTo(MOD_W - chamfer, 0); ctx.lineTo(MOD_W, chamfer);
  ctx.lineTo(MOD_W, MOD_H - chamfer); ctx.lineTo(MOD_W - chamfer, MOD_H); ctx.lineTo(chamfer, MOD_H);
  ctx.lineTo(0, MOD_H - chamfer); ctx.lineTo(0, chamfer); ctx.closePath();
  const grad = ctx.createLinearGradient(0, 0, 0, MOD_H);
  grad.addColorStop(0, highlight); grad.addColorStop(0.5, baseColor); grad.addColorStop(1, shadow);
  ctx.fillStyle = grad; ctx.fill();
  
  ctx.strokeStyle = (isBroken || isCrit) ? '#000' : '#050505'; ctx.lineWidth = 2;
  ctx.beginPath(); ctx.moveTo(0, 0); ctx.lineTo(0, MOD_H); ctx.stroke();
  if (isLast) { ctx.beginPath(); ctx.moveTo(MOD_W, 0); ctx.lineTo(MOD_W, MOD_H); ctx.stroke(); }

  if (!isBroken && !isCrit) {
    if (module.type === 'engine') {
      const flameLen = 15 + Math.random() * 10;
      const g = ctx.createLinearGradient(0, MOD_H/2, -flameLen, MOD_H/2);
      g.addColorStop(0, 'rgba(102, 204, 255, 0.9)'); g.addColorStop(1, 'rgba(0, 100, 255, 0)');
      ctx.fillStyle = g; ctx.beginPath(); ctx.moveTo(0, 6); ctx.lineTo(-flameLen, MOD_H/2); ctx.lineTo(0, MOD_H-6); ctx.fill();
    }
    else if (module.type === 'weapon_laser') { drawFixedMainCannon(ctx, MOD_W - 5, MOD_H/2, module.isOverheated, false); }
    else if (module.type === 'supply') {
      ctx.fillStyle = '#888'; ctx.fillRect(MOD_W/2 - 6, -10, 12, 10); ctx.fillRect(MOD_W/2 - 6, MOD_H, 12, 10); ctx.fillRect(MOD_W, MOD_H/2 - 6, 10, 12);
      ctx.fillStyle = '#0f0'; ctx.fillRect(MOD_W/2 - 4, -10, 8, 2); ctx.fillRect(MOD_W/2 - 4, MOD_H+8, 8, 2); ctx.fillRect(MOD_W+8, MOD_H/2 - 4, 2, 8);
    }
    else if (module.type === 'core') { ctx.fillStyle = 'rgba(102, 204, 255, 0.4)'; ctx.fillRect(10, 8, 25, 10); }
  }

  if (isCrit) { drawFire(ctx, MOD_W/2, MOD_H/2); }

  if (!isBroken && module.hp < 100) {
    ctx.fillStyle = 'rgba(0,0,0,0.8)'; ctx.fillRect(0, -12, MOD_W, 6);
    ctx.fillStyle = module.hp > 50 ? '#00ff88' : (module.hp > 20 ? '#ffaa00' : '#ff0044');
    ctx.fillRect(0, -12, MOD_W * (module.hp/100), 6);
  }
  ctx.restore();
}

// ================= 实体与战斗逻辑 =================
class Ship {
  constructor(x, y, isEnemy, color, modulesData, extrasData) {
    this.x = x; this.y = y; this.isEnemy = isEnemy; this.color = color;
    this.vx = 0; this.vy = 0; this.angle = isEnemy ? Math.PI : 0;
    this.maxSpeed = 3.0; this.turnSpeed = 0.04; this.radius = 30; this.dead = false;
    this.target = null; this.turretAngle = 0;
    this.interceptCd = 0; // 拦截冷却
    
    this.modules = modulesData.map(type => ({ type, hp: 100, maxHp: 100, isOverheated: false }));
    this.totalWidth = this.modules.length * MOD_W;
    this.extras = extrasData || [];
    
    this.crew = [{ id: 0, role: 'captain', pos: 'core' }];
    for(let i=1; i<8; i++) this.crew.push({ id: i, role: 'engineer', targetIndex: null, progress: 0, pos: Math.floor(Math.random() * this.modules.length) });
  }

  isModuleFunctional(type) {
    const mods = this.modules.filter(m => m.type === type);
    if (mods.length === 0) return false;
    return mods.some(m => m.hp > 20);
  }

  takeDamage(hitX, hitY, damage) {
    if (this.dead) return;
    const cos = Math.cos(-this.angle), sin = Math.sin(-this.angle);
    const localX = (hitX - this.x) * cos - (hitY - this.y) * sin; 
    const localY = (hitX - this.x) * sin + (hitY - this.y) * cos;
    if (Math.abs(localY) > MOD_H / 2 + 5) return; 

    const index = Math.floor((localX - (-this.totalWidth / 2)) / MOD_W);
    if (index >= 0 && index < this.modules.length) {
      const mod = this.modules[index];
      if (mod.hp > 0) {
        mod.hp -= damage;
        if (mod.hp <= 0) {
          mod.hp = 0;
          this.crew.forEach(c => { if(c.pos === index && c.role !== 'captain') c.pos = -1; });
          for(let i=0; i<15; i++) particles.push({x: hitX, y: hitY, vx: (Math.random()-0.5)*8, vy: (Math.random()-0.5)*8, life: 30, maxLife: 30, color: '#ffaa00', size: 3});
          
          if (mod.type.includes('weapon')) {
             logMsg(`一艘战舰武器舱殉爆!`);
             if(index > 0) this.modules[index-1].hp -= 30;
             if(index < this.modules.length-1) this.modules[index+1].hp -= 30;
             for(let i=0; i<20; i++) particles.push({x: hitX, y: hitY, vx: (Math.random()-0.5)*15, vy: (Math.random()-0.5)*15, life: 40, maxLife: 40, color: '#ff5500', size: 4});
          }

          if (mod.type === 'core') {
            this.dead = true;
            for(let i=0; i<80; i++) particles.push({x: this.x, y: this.y, vx: (Math.random()-0.5)*25, vy: (Math.random()-0.5)*25, life: 80, maxLife: 80, color: Math.random()>0.5?'#fff':'#ff3300', size: Math.random()*6+2});
            logMsg(`一艘${this.isEnemy ? '红方' : '蓝方'}战舰核心舱被摧毁!`);
          }
        }
      }
    }
  }

  // 主动拦截逻辑:寻找附近的敌方导弹并开火
  tryIntercept() {
    if (this.interceptCd > 0) { this.interceptCd--; return; }
    
    // 检查是否有近防或拦截激光武器
    const hasLaserPD = this.isModuleFunctional('weapon_laser');
    const hasRailgunPD = this.isModuleFunctional('railgun');
    const hasPd = this.isModuleFunctional('supply') || this.isModuleFunctional('missile'); 
    if (!hasLaserPD && !hasRailgunPD && !hasPd) return;

    // 找距离自己 500 以内的敌方导弹
    let threat = null;
    let minDist = 500;
    for (let b of bullets) {
      if (b.isEnemy !== this.isEnemy && b.type === 'missile') {
        let d = dist(this, b);
        if (d < minDist) { minDist = d; threat = b; }
      }
    }

    if (threat) {
      this.interceptCd = 15; // 拦截射速
      const angle = angleTo(this, threat);
      
      // 根据不同舰船发射不同的拦截弹
      if (hasLaserPD) {
        // 激光舰
        bullets.push(new Bullet(this.x, this.y, angle, this.isEnemy, 'laser_pd'));
      } else if (hasRailgunPD) {
        // 突击舰
        bullets.push(new Bullet(this.x, this.y, angle, this.isEnemy, 'pd_shell'));
      } else {
        // 其他舰船的常规近防炮
        bullets.push(new Bullet(this.x, this.y, angle, this.isEnemy, 'pd_shell'));
      }
    }
  }

  updateCrew() {
    this.crew.forEach(c => {
      if (c.role === 'captain' || c.pos === -1) return;
      if (c.targetIndex !== null) {
        const mod = this.modules[c.targetIndex];
        if (mod.hp >= 100 || mod.hp <= 0) { c.targetIndex = null; c.progress = 0; }
      }
      if (c.targetIndex === null) {
        for(let i=0; i<this.modules.length; i++) {
          if(this.modules[i].hp > 0 && this.modules[i].hp < 100) {
             if(!this.crew.some(other => other.targetIndex === i)) { c.targetIndex = i; c.progress = 0; break; }
          }
        }
      }
      if (c.targetIndex !== null) {
        if (c.pos !== c.targetIndex) {
           c.progress += 20 * (1/60); 
           if (c.progress >= 20) { c.pos = c.targetIndex; c.progress = 0; }
        } else {
           this.modules[c.targetIndex].hp += 0.5 * (1/60);
           if(this.modules[c.targetIndex].hp > 100) this.modules[c.targetIndex].hp = 100;
        }
      }
    });
  }

  update() {
    if (this.dead) return;
    this.updateCrew();
    this.tryIntercept(); // 尝试拦截导弹

    if (this.x < 200 || this.x > WORLD_W - 200) this.vx *= -0.8;
    if (this.y < 200 || this.y > WORLD_H - 200) this.vy *= -0.8;
    this.vx *= 0.98; this.vy *= 0.98; 
    this.x += this.vx; this.y += this.vy;
    
    const engineFunctional = this.isModuleFunctional('engine');
    if (!engineFunctional) { this.vx *= 0.9; this.vy *= 0.9; return; }
    this.logic();
  }

  aimTurret(target) {
    if (!target) { this.turretAngle *= 0.95; return; }
    const ta = angleTo(this, target);
    const relativeAngle = normalizeAngle(ta - this.angle);
    const diff = normalizeAngle(relativeAngle - this.turretAngle);
    this.turretAngle += Math.sign(diff) * Math.min(Math.abs(diff), 0.08);
  }

  moveToTarget(targetAngle) {
    const diff = normalizeAngle(targetAngle - this.angle);
    this.angle += Math.sign(diff) * Math.min(Math.abs(diff), this.turnSpeed);
    if (Math.abs(diff) < 0.3) {
      this.vx += Math.cos(this.angle) * 0.06; this.vy += Math.sin(this.angle) * 0.06;
      const speed = Math.hypot(this.vx, this.vy);
      if (speed > this.maxSpeed) { this.vx = (this.vx / speed) * this.maxSpeed; this.vy = (this.vy / speed) * this.maxSpeed; }
    }
  }

  keepDistance(target, minDist) {
    const d = dist(this, target);
    const ta = angleTo(this, target);
    if (d < minDist) this.moveToTarget(ta + Math.PI);
    else this.moveToTarget(ta);
  }

  draw() {
    if (this.dead) return;
    ctx.save(); ctx.translate(this.x, this.y); ctx.rotate(this.angle);
    this.extras.forEach(ext => {
      const module = this.modules[ext.index];
      if(module) drawModule(ctx, { type: ext.type, hp: module.hp }, ext.index * MOD_W - this.totalWidth/2, -MOD_H/2, false);
    });
    this.modules.forEach((mod, i) => drawModule(ctx, mod, i * MOD_W - this.totalWidth/2, -MOD_H/2, i === this.modules.length - 1));
    this.drawExtra(ctx);
    ctx.restore();
  }
  drawExtra(ctx) {}
  logic() {}
}

class LaserShip extends Ship {
  constructor(x, y, isEnemy) {
    super(x, y, isEnemy, '#66ccff', ['engine', 'supply', 'core', 'hab', 'weapon_laser'], [{ index: 1, type: 'solar' }, { index: 3, type: 'radiator' }]);
    this.maxSpeed = 2.5;
  }
  logic() {
    const weaponFunctional = this.isModuleFunctional('weapon_laser');
    const mainGun = this.modules[4];
    let attackMultiplier = this.isModuleFunctional('solar') ? 1.0 : 0.8;
    let coolingRate = this.isModuleFunctional('radiator') ? 0.3 : 0.15;

    if (mainGun && mainGun.hp > 0 && mainGun.isOverheated) {
      mainGun.heat -= coolingRate; if (mainGun.heat <= 0) mainGun.isOverheated = false;
    }
    let target = null, minDist = 3500; 
    entities.forEach(e => { if(e.isEnemy !== this.isEnemy && !e.dead) { let d = dist(this, e); if(d < minDist) { minDist = d; target = e; } } });
    this.target = target;

    if (target) {
      this.aimTurret(target);
      this.keepDistance(target, 800);

      if (weaponFunctional && !mainGun.isOverheated && Math.abs(normalizeAngle(angleTo(this, target) - this.angle)) < 0.1) {
        bullets.push(new Bullet(this.x, this.y, this.angle, this.isEnemy, 'laser_main', this, attackMultiplier));
        mainGun.heat = (mainGun.heat || 0) + 0.8;
        if (mainGun.heat >= 100) mainGun.isOverheated = true;
      }
    }
  }
  drawExtra(ctx) {
    if (this.isModuleFunctional('weapon_laser') && this.target) {
      drawTurret(ctx, -20, -MOD_H/2+2, this.turretAngle, 'laser_intercept');
      drawTurret(ctx, -20, MOD_H/2-2, this.turretAngle, 'laser_intercept');
      drawTurret(ctx, 5, -MOD_H/2+2, this.turretAngle, 'laser_intercept');
      drawTurret(ctx, 5, MOD_H/2-2, this.turretAngle, 'laser_intercept');
    }
  }
}

class AssaultShip extends Ship {
  constructor(x, y, isEnemy) {
    super(x, y, isEnemy, '#00ffcc', ['engine', 'supply', 'core', 'hab', 'missile', 'radar', 'railgun'], [{ index: 1, type: 'solar' }, { index: 2, type: 'solar' }, { index: 4, type: 'radiator' }]);
    this.railgunAmmo = 24; this.railgunCd = 0;
    this.maxSpeed = 4.5;
  }
  logic() {
    const weaponFunctional = this.isModuleFunctional('railgun');
    const radarFunctional = this.isModuleFunctional('radar');
    const detectRange = radarFunctional ? 5000 : 1500; 

    if (this.railgunCd > 0) this.railgunCd--;
    let target = null, minDist = detectRange;
    entities.forEach(e => { if(e.isEnemy !== this.isEnemy && !e.dead) { let d = dist(this, e); if(d < minDist) { minDist = d; target = e; } } });
    this.target = target;

    if (target) {
      this.aimTurret(target);
      this.keepDistance(target, 400);
      if (weaponFunctional && this.railgunAmmo > 0 && this.railgunCd <= 0 && Math.abs(this.turretAngle) < 0.1) {
        bullets.push(new Bullet(this.x, this.y, this.angle + this.turretAngle, this.isEnemy, 'railgun'));
        this.railgunAmmo--; this.railgunCd = 120;
      }
    }
  }
  drawExtra(ctx) {
    if (this.isModuleFunctional('railgun') && this.target) {
      drawTurret(ctx, 60, -MOD_H/2, this.turretAngle, 'railgun');
      drawTurret(ctx, 60, MOD_H/2, this.turretAngle, 'railgun');
    }
  }
}

class MissileShip extends Ship {
  constructor(x, y, isEnemy) {
    super(x, y, isEnemy, '#ff3366', ['engine', 'hab', 'missile', 'solar', 'missile', 'missile', 'core', 'missile', 'missile', 'missile', 'supply', 'supply']);
    this.maxAmmo = 80; this.ammo = 80; this.cd = 0;
    this.maxSpeed = 2.0;
  }
  logic() {
    if (this.cd > 0) this.cd--;
    if (this.ammo <= 0) { this.moveToTarget(this.angle + Math.PI); return; }

    let target = null, minDist = 4500;
    entities.forEach(e => { if(e.isEnemy !== this.isEnemy && !e.dead) { let d = dist(this, e); if(d < minDist) { minDist = d; target = e; } } });
    this.target = target;

    if (target) {
      this.keepDistance(target, 1200);
      if (this.cd <= 0 && Math.abs(normalizeAngle(angleTo(this, target) - this.angle)) < 0.3) {
        bullets.push(new Bullet(this.x, this.y, this.angle, this.isEnemy, 'missile', this));
        this.ammo--; this.cd = 80;
      }
    }
  }
  drawExtra(ctx) {
    ctx.save(); ctx.rotate(-this.angle); 
    ctx.fillStyle = '#fff'; ctx.font = '12px monospace'; ctx.textAlign = 'center';
    ctx.fillText(`弹:${this.ammo}`, 0, 45);
    ctx.restore();
  }
}

class SupplyShip extends Ship {
  constructor(x, y, isEnemy) {
    super(x, y, isEnemy, '#ffaa00', ['engine', 'core', 'supply']);
    this.state = 'search'; this.friend = null;
    this.maxSpeed = 4.0;
  }
  logic() {
    if (this.state === 'search') {
      if (this.isModuleFunctional('supply')) {
        this.friend = entities.find(e => e.isEnemy === this.isEnemy && !e.dead && e.ammo !== undefined && e.ammo < 20);
      } else { this.friend = null; }

      if (this.friend) {
        this.moveToTarget(angleTo(this, this.friend));
        if (dist(this, this.friend) < 50) { this.state = 'docking'; }
      } else {
        this.moveToTarget(this.angle + (Math.random()-0.5)*0.1);
      }
    } else if (this.state === 'docking') {
      this.vx = 0; this.vy = 0;
      if (!this.friend || this.friend.dead || !this.isModuleFunctional('supply')) { this.state = 'search'; return; }
      this.angle = angleTo(this, this.friend);
      if (this.friend.ammo < this.friend.maxAmmo) {
        this.friend.ammo += 0.5;
        if (Math.random() < 0.1) particles.push({x: (this.x+this.friend.x)/2, y: (this.y+this.friend.y)/2, vx:0, vy:0, life: 15, maxLife:15, color: '#00ff00', size: 2});
      } else { this.state = 'search'; }
    }
  }
}

// ================= 导弹与子弹类 =================
class Bullet {
  constructor(x, y, angle, isEnemy, type, owner = null, multiplier = 1.0) {
    this.x = x; this.y = y; this.isEnemy = isEnemy; this.type = type; this.angle = angle;
    this.dead = false; this.life = 200;
    this.hp = 1; // 默认子弹血量为1
    
    if (type === 'missile') { 
      this.speed = 7; 
      this.damage = 25 * multiplier; 
      this.turnSpeed = 0.08; 
      this.life = 800; 
      this.target = null;
      this.hp = 12; // 导弹的血量,打中一两下就爆
    }
    else if (type === 'railgun') { this.speed = 40; this.damage = 80 * multiplier; this.life = 200; }
    else if (type === 'laser_main') { this.speed = 30; this.damage = 4 * multiplier; this.life = 20; }
    else if (type === 'laser_pd') { this.speed = 35; this.damage = 6; this.life = 20; } // 激光拦截伤害
    else if (type === 'pd_shell') { this.speed = 25; this.damage = 4; this.life = 30; } // 近防炮实弹伤害
    
    if (type.includes('laser') && owner) { this.vx = owner.vx + Math.cos(angle)*this.speed; this.vy = owner.vy + Math.sin(angle)*this.speed; }
    else { this.vx = Math.cos(angle)*this.speed; this.vy = Math.sin(angle)*this.speed; }
  }
  update() {
    if (this.dead) return;
    
    // 导弹追踪
    if (this.type === 'missile') {
      if (!this.target || this.target.dead) {
         let minDist = Infinity;
         entities.forEach(e => {
            if(e.isEnemy !== this.isEnemy && !e.dead) {
               let d = dist(this, e); if(d < minDist) { minDist = d; this.target = e; }
            }
         });
      }
      if (this.target) {
        const ta = angleTo(this, this.target);
        const currentAngle = Math.atan2(this.vy, this.vx);
        const diff = normalizeAngle(ta - currentAngle);
        const newAngle = currentAngle + Math.sign(diff) * this.turnSpeed;
        this.vx = Math.cos(newAngle) * this.speed; this.vy = Math.sin(newAngle) * this.speed;
        this.angle = newAngle;
      }
    }
    
    this.x += this.vx; this.y += this.vy;
    this.life--;
    if (this.life <= 0 || this.x < 0 || this.x > WORLD_W || this.y < 0 || this.y > WORLD_H) this.dead = true;

    for (let e of entities) {
      if (!e.dead && e.isEnemy !== this.isEnemy) {
        // 拦截弹命中导弹
        if (this.type === 'laser_pd' || this.type === 'pd_shell') {
           // 这个是拦截弹,要找空中的敌方导弹
           for (let b of bullets) {
             if (!b.dead && b.isEnemy !== this.isEnemy && b.type === 'missile') {
               if (dist(this, b) < 30) {
                 b.hp -= this.damage; // 扣除导弹血量
                 this.dead = true;
                 if (b.hp <= 0) {
                   b.dead = true;
                   // 导弹被拦截的爆炸特效
                   for(let i=0; i<8; i++) particles.push({x:b.x, y:b.y, vx:(Math.random()-0.5)*6, vy:(Math.random()-0.5)*6, life:15, maxLife:15, color:'#fff', size:2});
                 }
                 return;
               }
             }
           }
        } 
        // 其它子弹命中舰船
        else if (this.type !== 'laser_pd' && this.type !== 'pd_shell') {
          if (dist(this, e) < e.radius) {
            e.takeDamage(this.x, this.y, this.damage);
            this.dead = true;
          }
        }
      }
    }
  }
  draw() {
    if (this.dead) return;
    ctx.save(); ctx.translate(this.x, this.y); ctx.rotate(this.angle);
    if (this.type === 'missile') {
      ctx.fillStyle = '#ffaa00'; ctx.fillRect(-10, -3, 20, 6);
      ctx.fillStyle = 'rgba(255,50,0,0.8)'; ctx.beginPath(); ctx.moveTo(-10, -3); ctx.lineTo(-22, 0); ctx.lineTo(-10, 3); ctx.fill();
    } else if (this.type === 'railgun') {
      ctx.fillStyle = '#00ffcc'; ctx.fillRect(-25, -3, 50, 6);
    } else if (this.type === 'laser_main') {
      ctx.fillStyle = 'rgba(102, 204, 255, 0.9)'; ctx.fillRect(-30, -2, 60, 4);
    } else if (this.type === 'laser_pd') {
      ctx.fillStyle = '#fff'; ctx.fillRect(-15, -1.5, 30, 3);
    } else if (this.type === 'pd_shell') {
      ctx.fillStyle = '#ffaa00'; ctx.fillRect(-4, -1.5, 8, 3); // 实弹拦截弹
    }
    ctx.restore();
  }
}

// ================= 游戏主循环 =================
function startBattle() {
  document.getElementById('ui-layer').style.opacity = '0';
  setTimeout(() => { document.getElementById('ui-layer').style.display = 'none'; }, 300);
  document.getElementById('combat-ui').style.display = 'block';
  gameState = 'playing'; entities = []; bullets = []; particles = [];
  document.getElementById('log').innerHTML = "舰队跃迁至战区。全阵营解锁,拦截系统已上线。";

  const genShips = (prefix, isEnemy, baseX) => {
    let yOffset = -500;
    for(let i=0; i<config[prefix+'Laser']; i++) { entities.push(new LaserShip(baseX, WORLD_H/2 + yOffset, isEnemy)); yOffset += 300; }
    for(let i=0; i<config[prefix+'Assault']; i++) { entities.push(new AssaultShip(baseX + 300, WORLD_H/2 + yOffset, isEnemy)); yOffset += 300; }
    for(let i=0; i<config[prefix+'Missile']; i++) { entities.push(new MissileShip(baseX, WORLD_H/2 + yOffset, isEnemy)); yOffset += 300; }
    for(let i=0; i<config[prefix+'Supply']; i++) { entities.push(new SupplyShip(baseX + 300, WORLD_H/2 + yOffset, isEnemy)); yOffset += 300; }
  };

  genShips('b', false, 1000);
  genShips('r', true, WORLD_W - 1000);
  
  cam.x = WORLD_W/2; cam.y = WORLD_H/2; cam.zoom = 0.15;
  loop();
}

function loop() {
  if (gameState !== 'playing') return;
  
  ctx.fillStyle = '#03050a'; ctx.fillRect(0, 0, W, H);
  
  ctx.save();
  ctx.translate(W / 2, H / 2); 
  ctx.scale(cam.zoom, cam.zoom); 
  ctx.translate(-cam.x, -cam.y); 

  ctx.strokeStyle = 'rgba(50, 100, 150, 0.1)';
  for(let i=0; i<WORLD_W; i+=400) { ctx.beginPath(); ctx.moveTo(i, 0); ctx.lineTo(i, WORLD_H); ctx.stroke(); }
  for(let i=0; i<WORLD_H; i+=400) { ctx.beginPath(); ctx.moveTo(0, i); ctx.lineTo(WORLD_W, i); ctx.stroke(); }

  for (let e of entities) { e.update(); if (!e.dead) e.draw(); }
  for (let i = bullets.length - 1; i >= 0; i--) {
    bullets[i].update();
    if (!bullets[i].dead) bullets[i].draw(); else bullets.splice(i, 1);
  }
  for (let i = particles.length - 1; i >= 0; i--) {
    let p = particles[i]; p.x += p.vx; p.y += p.vy; p.life--;
    ctx.globalAlpha = Math.max(0, p.life / p.maxLife); ctx.fillStyle = p.color;
    ctx.beginPath(); ctx.arc(p.x, p.y, p.size, 0, Math.PI*2); ctx.fill();
    if (p.life <= 0) particles.splice(i, 1);
  }
  ctx.globalAlpha = 1;
  
  ctx.restore(); 

  let blueAlive = entities.some(e => !e.isEnemy && !e.dead);
  let redAlive = entities.some(e => e.isEnemy && !e.dead);
  if (!blueAlive || !redAlive) {
    gameState = 'over';
    ctx.fillStyle = 'rgba(0,0,0,0.8)'; ctx.fillRect(0, 0, W, H);
    ctx.fillStyle = '#fff'; ctx.font = 'bold 30px monospace'; ctx.textAlign = 'center';
    ctx.fillText(blueAlive ? '蓝方舰队 胜利' : '红方舰队 胜利', W/2, H/2);
    ctx.fillText('刷新页面重新配置', W/2, H/2 + 40);
    return;
  }
  requestAnimationFrame(loop);
}
</script>
</body>
</html>

Game Source: 深空舰队 - 拦截与全阵营版

Creator: EpicCoder88

Libraries: none

Complexity: complex (677 lines, 31.5 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-mvnk9z0d" to link back to the original. Then publish at arcadelab.ai/publish.