Pyroflame

SpaceVerse v5.7: Nebula Nexus(C)(TM)(N)

Aug 14th, 2025
154
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
HTML 53.92 KB | None | 0 0
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8"/>
  5. <meta name="viewport" content="width=device-width,initial-scale=1"/>
  6. <title>SpaceVerse v5.7 — Combined Fleet Command</title>
  7. <style>
  8. :root {
  9.   --bg-dark: #010214; --bg-panel: rgba(2,6,12,0.85); --text-muted: #9aa;
  10.   --text-accent: #7ef; --teal: #6ee7b7; --danger: #ff6b6b;
  11.   --gold: #ffd166; --purple: #a78bfa;
  12. }
  13. *{ box-sizing: border-box; }
  14. html, body { height: 100%; margin: 0; background: var(--bg-dark); color: #dfe; overflow: hidden; font-family: Inter, system-ui, -apple-system, "Segoe UI", Roboto; }
  15. canvas { display: block; width: 100vw; height: 100vh; cursor: crosshair; background: linear-gradient(180deg, #000011, #02031a); }
  16. .hud-container { position: fixed; z-index: 60; backdrop-filter: blur(4px); }
  17. #hud { left: 12px; top: 12px; background: var(--bg-panel); padding: 12px; border-radius: 10px; border: 1px solid rgba(255,255,255,0.04); }
  18. #hud .title { font-weight: 800; color: var(--text-accent); margin-bottom: 6px; font-size: 14px; }
  19. #hud .row { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 6px; }
  20. #hud button { background: #07202a; border: none; color: #bfe; padding: 8px 12px; border-radius: 8px; cursor: pointer; font-weight: 600; font-size: 12px; transition: background-color .2s, transform .12s; }
  21. #hud button:hover { background: #0a2a36; transform: translateY(-1px); }
  22. #hud small { display: block; color: var(--text-muted); margin-top: 6px; font-size: 11px; line-height: 1.3; max-width: 400px; }
  23. #mega-panel { right: 12px; top: 12px; width: 360px; max-height: calc(100vh - 24px); overflow-y: auto; background: linear-gradient(180deg, rgba(6,10,16,0.95), rgba(3,6,10,0.85)); border-radius: 12px; padding: 12px; border: 1px solid rgba(255,255,255,0.03); display: none; }
  24. #mega-panel h2 { margin: 0 0 12px 0; color: #8ef; font-size: 16px; }
  25. .spawn-row { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
  26. .spawn-btn { flex: 1 1 calc(50% - 3px); background: #06101a; color: #bfe; padding: 8px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.03); cursor: pointer; font-weight: 600; font-size: 12px; text-align: center; transition: all .2s; }
  27. .spawn-btn:hover { background: #0a1520; border-color: rgba(255,255,255,0.08); transform: translateY(-1px); }
  28. #debug { left: 12px; bottom: 12px; color: var(--text-muted); font-size: 12px; background: rgba(2,6,12,0.5); padding: 8px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.02); }
  29. #terminal { position: fixed; bottom: 12px; left: 50%; transform: translateX(-50%); width: 80%; max-width: 600px; background: rgba(2,6,12,0.9); padding: 12px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.04); display: none; z-index: 100; font-family: monospace; font-size: 14px; color: var(--teal); }
  30. #terminal input { width: 100%; background: transparent; border: none; color: var(--teal); font-family: monospace; font-size: 14px; outline: none; padding: 8px; }
  31. #terminal-suggestions { position: absolute; bottom: 100%; left: 0; right: 0; background: rgba(2,6,12,0.95); border-radius: 8px 8px 0 0; border: 1px solid rgba(255,255,255,0.04); max-height: 200px; overflow-y: auto; }
  32. #terminal-suggestions div { padding: 6px 12px; cursor: pointer; border-bottom: 1px solid rgba(255,255,255,0.05); }
  33. #terminal-suggestions div:hover { background: rgba(255,255,255,0.1); }
  34. #shipPanel { right: 12px; bottom: 12px; width: 300px; background: rgba(2,6,12,0.9); border-radius: 8px; padding: 12px; border: 1px solid rgba(255,255,255,0.04); z-index: 70; display: none; }
  35. #shipPanel h3 { margin: 0 0 8px 0; color: var(--text-accent); font-size: 14px; }
  36. #shipPanel .crew-list { margin-top: 8px; max-height: 200px; overflow-y: auto; }
  37. .crew-member { display: flex; justify-content: space-between; padding: 4px 0; border-bottom: 1px solid rgba(255,255,255,0.05); }
  38. .crew-role { color: var(--teal); font-weight: 600; }
  39. .crew-stats { color: var(--text-muted); font-size: 12px; }
  40. #missionPanel { left: 50%; top: 50%; transform: translate(-50%, -50%); width: 80%; max-width: 500px; background: rgba(2,6,12,0.95); padding: 16px; border-radius: 12px; border: 1px solid rgba(255,255,255,0.06); z-index: 90; display: none; backdrop-filter: blur(8px); max-height: 80vh; overflow-y: auto; }
  41. #missionPanel h2 { color: var(--gold); margin-top: 0; }
  42. .mission-card { background: rgba(0,0,0,0.3); border-radius: 8px; padding: 12px; margin-bottom: 12px; }
  43. .mission-card h3 { margin: 0 0 8px 0; color: var(--text-accent); }
  44. .mission-card p { margin: 4px 0; color: var(--text-muted); }
  45. .mission-card button { background: var(--purple); border: none; color: white; padding: 6px 12px; border-radius: 6px; cursor: pointer; }
  46. .pie-container { position: fixed; z-index: 120; user-select: none; pointer-events: none; left: 0; top: 0; transform: translate(-10000px, -10000px); will-change: transform, opacity; }
  47. .pie-wrap { position: relative; width: 1px; height: 1px; pointer-events: none; animation: pieIn .12s ease-out both; }
  48. @keyframes pieIn { from { opacity: 0; transform: scale(0.92); } to { opacity: 1; transform: scale(1); } }
  49. .pie-slice { position: absolute; width: 150px; height: 46px; display: flex; align-items: center; justify-content: center; pointer-events: auto; cursor: pointer; border-radius: 10px; padding: 6px; box-shadow: 0 6px 18px rgba(0,0,0,0.5); transform-origin: center center; transition: transform .1s ease-out, filter .12s; backdrop-filter: blur(4px); }
  50. .pie-slice .label { color: #fff; font-weight: 800; text-shadow: 0 0 6px rgba(0,0,0,0.6); font-size: 13px; text-align: center; }
  51. .pie-slice:hover { transform: scale(1.08); }
  52. .pie-divider { position: absolute; left: -2px; top: -2px; right: -2px; bottom: -2px; border-radius: 12px; border: 1px solid rgba(255,255,255,0.06); pointer-events: none; }
  53. .selection-box { position: fixed; border: 1px dashed var(--text-accent); background: rgba(126,239,255,0.1); pointer-events: none; z-index: 50; }
  54. #statsPanel { position: fixed; left: 12px; top: 50%; transform: translateY(-50%); width: 220px; background: var(--bg-panel); padding: 12px; border-radius: 10px; border: 1px solid rgba(255,255,255,0.04); backdrop-filter: blur(4px); z-index: 59; font-size: 12px; }
  55. #statsPanel h3 { margin: 0 0 10px 0; color: var(--text-accent); font-size: 14px; text-align: center; }
  56. .stats-grid { display: grid; grid-template-columns: auto 1fr; gap: 4px 8px; align-items: center; }
  57. .stats-grid .label { color: var(--text-muted); }
  58. .stats-grid .value { text-align: right; color: var(--teal); font-weight: 600; }
  59. @media (max-width: 640px) { .pie-slice { width: 120px; height: 44px; } #mega-panel, #shipPanel { width: 92%; right: 4%; left: 4%; } #missionPanel { width: 90%; } #statsPanel { display: none; } }
  60. </style>
  61. </head>
  62. <body>
  63. <canvas id="c" tabindex="0" aria-label="Gameplay canvas"></canvas>
  64.  
  65. <div id="hud" class="hud-container" role="toolbar" aria-label="HUD Controls">
  66.   <div class="title">SpaceVerse — v5.7</div>
  67.   <div class="row">
  68.     <button id="btnPause">Pause (P)</button> <button id="btnCenter">Center View</button>
  69.     <button id="btnClear">Clear All</button> <button id="btnMegaToggle">Spawner (M)</button>
  70.     <button id="btnTerminal">Terminal (~)</button> <button id="btnMissions">Missions (J)</button>
  71.     <button id="btnShipPanel">Ship Info (I)</button>
  72.   </div>
  73.   <small>Right-click = Player pie | Ctrl+Right-click = Enemy pie | Alt+Right-click = Astro pie | Shift+Right-click = Structures. Drag to multi-select. WASD to move ships. Space to auto-fire.</small>
  74. </div>
  75.  
  76. <div id="statsPanel">
  77.   <h3>Faction Stats</h3>
  78.   <div class="stats-grid" id="statsGrid"></div>
  79. </div>
  80.  
  81. <div id="mega-panel" class="hud-container" aria-hidden="true">
  82.   <h2>🚀 Mega Spawner</h2>
  83.   <div class="spawn-row"><button class="spawn-btn" data-action="playerMothership">🛸 Player Mothership</button><button class="spawn-btn" data-action="enemyMothership">👾 Enemy Mothership</button></div>
  84.   <div class="spawn-row"><button class="spawn-btn" data-action="fighter">⚡ Fighter</button><button class="spawn-btn" data-action="brawler">💪 Brawler</button></div>
  85.   <div class="spawn-row"><button class="spawn-btn" data-action="sniper">🎯 Sniper</button><button class="spawn-btn" data-action="support">🔧 Support</button></div>
  86.   <div class="spawn-row"><button class="spawn-btn" data-action="kamikaze">💥 Kamikaze</button><button class="spawn-btn" data-action="asteroids">☄️ Asteroid Field</button></div>
  87.   <div class="spawn-row"><button class="spawn-btn" data-action="star">⭐ Star</button><button class="spawn-btn" data-action="planet">🪐 Planet</button></div>
  88.   <div class="spawn-row"><button class="spawn-btn" data-action="lushPlanet">🌿 Lush Planet</button><button class="spawn-btn" data-action="firePlanet">🔥 Fire Planet</button></div>
  89. </div>
  90.  
  91. <div id="terminal" role="dialog" aria-label="Command Terminal">
  92.   <div id="terminal-suggestions"></div>
  93.   <input type="text" id="terminalInput" placeholder="Enter command (e.g., spawn fighter 5, clear, stats)" autocomplete="off">
  94. </div>
  95.  
  96. <div id="shipPanel" class="hud-container">
  97.   <h3>Ship Information</h3>
  98.   <div id="shipInfo"></div>
  99.   <h3>Crew <small id="crewCount"></small></h3>
  100.   <div class="crew-list" id="crewList"></div>
  101. </div>
  102.  
  103. <div id="missionPanel" class="hud-container">
  104.   <h2>Mission Board</h2>
  105.   <div id="missionList"></div>
  106.   <button id="closeMissions" style="margin-top:12px;">Close</button>
  107. </div>
  108.  
  109. <div id="debug" class="hud-container" aria-live="polite">Entities: 0 — Projectiles: 0 — FPS: 0</div>
  110.  
  111. <script>
  112. (() => {
  113.   const CFG = {
  114.     GRAVITY_BASE: 0.0007, MAX_PROJECTILES: 1000, MAX_ENTITIES: 6000,
  115.     POOL_PROJECTILES: 1400, SPATIAL_CELL: 220, DT_MAX: 0.05, FPS_INTERVAL: 1000,
  116.     CREW_SKILLS: ['Pilot', 'Co-Pilot', 'Mechanic', 'Weapons', 'Scientist', 'Engineer', 'Medic', 'Navigator']
  117.   };
  118.  
  119.   const c = document.getElementById('c');
  120.   const ctx = c.getContext('2d', { alpha: false });
  121.   let DPR = Math.max(1, window.devicePixelRatio || 1);
  122.   function resize() {
  123.     DPR = Math.max(1, window.devicePixelRatio || 1);
  124.     c.width = Math.floor(window.innerWidth * DPR); c.height = Math.floor(window.innerHeight * DPR);
  125.     c.style.width = window.innerWidth + 'px'; c.style.height = window.innerHeight + 'px';
  126.     ctx.setTransform(DPR, 0, 0, DPR, 0, 0);
  127.   }
  128.   window.addEventListener('resize', resize);
  129.   resize();
  130.  
  131.   const W = () => window.innerWidth, H = () => window.innerHeight, TAU = Math.PI * 2;
  132.   const rand = (r=1) => (Math.random()-0.5) * 2 * r;
  133.   const hexToRGBA = (hex, a=1) => {
  134.     if (!hex) return `rgba(255,255,255,${a})`;
  135.     const h = hex.replace('#', ''); const r = parseInt(h.slice(0,2), 16), g = parseInt(h.slice(2,4), 16), b = parseInt(h.slice(4,6), 16);
  136.     return `rgba(${r},${g},${b},${a})`;
  137.   };
  138.  
  139.   let entities = [], nextId = 1, running = true, selectedIds = new Set(), followId = null;
  140.   let projCount = 0, lastTime = performance.now(), fps = 0, frameCount = 0, lastFpsTime = performance.now();
  141.   const camera = { x: 0, y: 0, targetX: 0, targetY: 0, lerp: 0.08 };
  142.   const mouse = { screenX: W()/2, screenY: H()/2, x: 0, y: 0, down: false, btn: -1, startX: 0, startY: 0 };
  143.   const TEAL = 'TEAL', RED = 'RED';
  144.   let factions = {}, sideFactions = [];
  145.   let isDragging = false, selectionBox = null;
  146.   let missions = [];
  147.   let terminalHistory = [], terminalHistoryIndex = -1;
  148.   const crewNames = ["Alex", "Jamie", "Taylor", "Jordan", "Casey", "Riley", "Quinn", "Morgan", "Avery", "Skyler", "Dakota", "Peyton", "Rowan", "Charlie", "Emerson", "Finley", "Sawyer", "Hayden", "Kai", "River"];
  149.  
  150.   function generateCrew(shipSize) {
  151.     const crewSize = Math.max(1, Math.floor(shipSize < 10 ? 3 + rand(2) : shipSize < 20 ? 8 + rand(4) : shipSize < 30 ? 20 + rand(10) : 50 + rand(50)));
  152.    const crew = [];
  153.    for (let i = 0; i < crewSize; i++) {
  154.      crew.push({ id: nextId++, name: crewNames[Math.floor(Math.random() * crewNames.length)], role: CFG.CREW_SKILLS[Math.floor(Math.random() * CFG.CREW_SKILLS.length)], skill: Math.floor(1 + Math.random() * 5), morale: Math.floor(50 + Math.random() * 50), salary: 10 + Math.floor(Math.random() * 40) });
  155.    }
  156.    return crew;
  157.  }
  158.  
  159.  function generateMissions() {
  160.    const missionTypes = [
  161.      { name: "Supply Run", desc: "Deliver supplies to a remote outpost.", reward: { money: 500, supplies: 10, xp: 100 }, difficulty: 1, timeLimit: 300 },
  162.      { name: "Pirate Hunt", desc: "Eliminate pirate threats in this sector.", reward: { money: 800, crypto: 50, xp: 150 }, difficulty: 2, timeLimit: 420 },
  163.      { name: "Mineral Scan", desc: "Scan uncharted asteroids for valuable minerals.", reward: { minerals: 25, chips: 5, xp: 200 }, difficulty: 2, timeLimit: 480 },
  164.      { name: "Escort Duty", desc: "Protect a merchant convoy through a dangerous route.", reward: { money: 1200, gifts: 1, xp: 250 }, difficulty: 3, timeLimit: 600 },
  165.      { name: "Emergency Rescue", desc: "Save stranded crew from a disabled vessel.", reward: { money: 2000, food: 20, fluids: 20, xp: 300 }, difficulty: 4, timeLimit: 360 },
  166.      { name: "Blockade Runner", desc: "Smuggle high-value goods past an enemy blockade.", reward: { crypto: 200, nitro: 2, xp: 250 }, difficulty: 4, timeLimit: 300 },
  167.      { name: "Scientific Expedition", desc: "Collect data from a cosmic anomaly.", reward: { chips: 15, gauss: 1, xp: 200 }, difficulty: 3, timeLimit: 500 },
  168.      { name: "Planetary Defense", desc: "Defend a planet from an imminent invasion.", reward: { money: 2500, reputation: 10, xp: 400 }, difficulty: 5, timeLimit: 720 },
  169.      { name: "Anti-Smuggling Op", desc: "Interdict smugglers and confiscate their cargo.", reward: { supplies: 20, gifts: 2, xp: 180 }, difficulty: 3, timeLimit: 400 },
  170.      { name: "Diplomatic Envoy", desc: "Transport a diplomat to peace talks.", reward: { money: 1500, reputation: 5, xp: 150 }, difficulty: 2, timeLimit: 600 },
  171.      { name: "Resource Scavenge", desc: "Scavenge a derelict ship for usable parts.", reward: { minerals: 15, chips: 10, xp: 120 }, difficulty: 2, timeLimit: 380 },
  172.      { name: "System Recon", desc: "Chart a new star system and report back.", reward: { money: 1000, xp: 220 }, difficulty: 3, timeLimit: 900 },
  173.      { name: "Trade Agreement", desc: "Establish a new trade route between two factions.", reward: { money: 1800, reputation: 5, xp: 200 }, difficulty: 3, timeLimit: 800 },
  174.      { name: "Defector Extraction", desc: "Extract a high-value defector from enemy territory.", reward: { crypto: 300, xp: 350 }, difficulty: 5, timeLimit: 500 },
  175.      { name: "Sabotage", desc: "Disable a key enemy structure.", reward: { money: 2200, nitro: 1, xp: 320 }, difficulty: 4, timeLimit: 600 }
  176.    ];
  177.    missions = [];
  178.    for (let i = 0; i < 5; i++) {
  179.      const type = missionTypes[Math.floor(Math.random() * missionTypes.length)];
  180.      missions.push({ ...type, id: nextId++, location: { x: rand(W() * 1.2), y: rand(H() * 1.2) }, faction: Math.random() > 0.5 ? TEAL : RED, accepted: false, progress: 0, startTime: 0 });
  181.     }
  182.   }
  183.  
  184.   function initFactions() {
  185.     const defaultResources = () => ({ money: 10000, crypto: 100, supplies: 50, chips: 20, gauss: 5, nitro: 5, minerals: 0, gifts: 0, food: 100, fluids: 100 });
  186.     factions = {
  187.       [TEAL]: { id: TEAL, name: 'Player', color: '#6ee7b7', members: [], reputation: 50, resources: defaultResources() },
  188.       [RED]: { id: RED, name: 'Enemy', color: '#ff6b6b', members: [], reputation: 50, resources: defaultResources() }
  189.     };
  190.     sideFactions = [ { id: 'PIRATES', name: 'Pirates', color: '#ffd166', reputation: 0, resources: defaultResources() }, { id: 'MERCHANTS', name: 'Merchants', color: '#a78bfa', reputation: 50, resources: defaultResources() } ];
  191.   }
  192.  
  193.   const projPool = [];
  194.   function poolInit() { projPool.length = 0; for (let i = 0; i < CFG.POOL_PROJECTILES; i++) projPool.push({ _pooled: true }); }
  195.  function projAcquire() { return projPool.length ? projPool.pop() : { _pooled: false }; }
  196.  function projRelease(p) { p._pooled = true; p.kind = 'free'; p.ownerId = 0; p.ownerSide = 'NEUTRAL'; projPool.push(p); }
  197.  
  198.  const spatial = new Map();
  199.  const spatialKey = (cx, cy) => `${cx},${cy}`;
  200.   function spatialClear() { spatial.clear(); }
  201.   function spatialInsert(e) {
  202.     const cell = CFG.SPATIAL_CELL; const minX = Math.floor((e.x - (e.size || 0)) / cell); const maxX = Math.floor((e.x + (e.size || 0)) / cell); const minY = Math.floor((e.y - (e.size || 0)) / cell); const maxY = Math.floor((e.y + (e.size || 0)) / cell);
  203.     for (let cx = minX; cx <= maxX; cx++) for (let cy = minY; cy <= maxY; cy++) { const k = spatialKey(cx, cy); if (!spatial.has(k)) spatial.set(k, []); spatial.get(k).push(e); }
  204.  }
  205.  function spatialQueryRegion(x, y, radius) {
  206.    const cell = CFG.SPATIAL_CELL; const minX = Math.floor((x - radius) / cell), maxX = Math.floor((x + radius) / cell); const minY = Math.floor((y - radius) / cell), maxY = Math.floor((y + radius) / cell);
  207.    const set = new Set();
  208.    for (let cx = minX; cx <= maxX; cx++) for (let cy = minY; cy <= maxY; cy++) { const k = spatialKey(cx, cy), arr = spatial.get(k); if (arr) for (const e of arr) set.add(e); }
  209.    return Array.from(set);
  210.  }
  211.  
  212.  function spawn(obj) {
  213.    if (entities.length >= CFG.MAX_ENTITIES) return null;
  214.     const defaults = { id: nextId++, kind: 'unknown', x: Math.random() * W(), y: Math.random() * H(), vx: 0, vy: 0, angle: Math.random() * TAU, size: 8, hp: 100, maxHp: 100, side: 'NEUTRAL', archetype: 'generic', created: performance.now() };
  215.     const e = Object.assign({}, defaults, obj);
  216.     if (e.kind === 'ship') { e.crew = generateCrew(e.size); e.weapons = []; if (e.archetype === 'fighter') e.weapons.push({ type: 'laser', damage: 20, range: 300, rate: 0.5 }); if (e.archetype === 'brawler') e.weapons.push({ type: 'cannon', damage: 40, range: 150, rate: 1.0 }); if (e.archetype === 'sniper') e.weapons.push({ type: 'railgun', damage: 60, range: 500, rate: 1.5 }); if (e.subtype === 'mothership') { e.weapons.push({ type: 'missile', damage: 80, range: 400, rate: 2.0 }); e.weapons.push({ type: 'laser', damage: 30, range: 350, rate: 0.8 }); } e.modules = []; if (e.size > 15) e.modules.push('shield'); if (e.size > 20) e.modules.push('cloak'); }
  217.     if (e.kind === 'celestial' && e.subtype === 'planet') { e.population = Math.floor(1000 + Math.random() * 10000); e.allegiance = Math.random() > 0.5 ? TEAL : RED; }
  218.     entities.push(e); if (e.side && factions[e.side]) factions[e.side].members.push(e.id);
  219.     return e;
  220.   }
  221.  
  222.   const nameGenerators = {
  223.     star: () => ["Sol", "Alpha Cent.", "Sirius", "Vega", "Rigel"][Math.floor(Math.random()*5)] + " " + ["Prime", "Secundus", "Minor"][Math.floor(Math.random()*3)],
  224.     planet: () => ["Terra", "Mars", "Kepler", "Xylos", "Helios"][Math.floor(Math.random()*5)] + " " + ["IV", "V", "VI"][Math.floor(Math.random()*3)],
  225.     structure: () => ["Outpost", "Station", "Platform", "Citadel", "Complex"][Math.floor(Math.random()*5)] + " " + (100 + Math.floor(Math.random()*899))
  226.   };
  227.  
  228.   const spawnActions = {
  229.     playerMothership: (x, y) => spawn({ kind: 'ship', subtype: 'mothership', archetype: 'carrier', side: TEAL, playerControl: true, mass: 5200, size: 30, hp: 2500, maxHp: 2500, shield: 600, maxShield: 600, x, y, spawnTimer: 0, spawnRate: 2.8, name: "TEAL Command" }),
  230.     enemyMothership: (x, y) => spawn({ kind: 'ship', subtype: 'mothership', archetype: 'aggressive', side: RED, mass: 5200, size: 30, hp: 2600, maxHp: 2600, shield: 300, maxShield: 300, x, y, spawnTimer: 0, spawnRate: 5.0, name: "RED Flagship" }),
  231.     fighter: (x, y, side = RED) => spawn({ kind: 'ship', subtype: 'ship', archetype: 'fighter', side, mass: 160, size: 9, hp: 160, maxHp: 160, shield: 40, maxShield: 40, speed: 2.2, fireRate: 0.45, weapon: 'bullet', range: 280, damage: 22, x, y, name: side === TEAL ? "Viper MkII" : "Raider" }),
  232.     brawler: (x, y, side = RED) => spawn({ kind: 'ship', subtype: 'ship', archetype: 'brawler', side, mass: 300, size: 12, hp: 360, maxHp: 360, speed: 1.6, fireRate: 0.25, weapon: 'ram', range: 28, damage: 90, x, y, name: side === TEAL ? "Aegis Defender" : "Hulk" }),
  233.     sniper: (x, y, side = RED) => spawn({ kind: 'ship', subtype: 'ship', archetype: 'sniper', side, mass: 140, size: 8, hp: 120, maxHp: 120, shield: 30, maxShield: 30, speed: 1.9, fireRate: 1.2, weapon: 'laser', range: 620, damage: 46, x, y, name: side === TEAL ? "Longbow" : "Stinger" }),
  234.     support: (x, y, side = RED) => spawn({ kind: 'ship', subtype: 'ship', archetype: 'support', side, mass: 200, size: 10, hp: 170, maxHp: 170, shield: 80, maxShield: 80, speed: 1.6, fireRate: 1.0, weapon: 'repair', range: 160, damage: -20, x, y, name: side === TEAL ? "Field Medic" : "Tender" }),
  235.     kamikaze: (x, y, side = RED) => spawn({ kind: 'ship', subtype: 'ship', archetype: 'kamikaze', side, mass: 120, size: 8, hp: 80, maxHp: 80, speed: 3.0, weapon: 'ram', range: 18, damage: 260, x, y, name: "Firefly" }),
  236.     star: (x, y) => spawn({ kind: 'celestial', subtype: 'star', side: 'NEUTRAL', x, y, mass: 250000, color: '#ffd166', size: 30, fixed: true, name: nameGenerators.star() }),
  237.     planet: (x, y) => spawn({ kind: 'celestial', subtype: 'planet', side: 'NEUTRAL', x, y, mass: 14000, color: '#7fb', size: 18, name: nameGenerators.planet() }),
  238.     asteroids: (x, y) => { for (let i = 0; i < 24; i++) spawn({ kind: 'celestial', subtype: 'asteroid', side: 'NEUTRAL', x: x + rand(160), y: y + rand(160), mass: 20 + Math.random() * 160, color: '#b7a', size: rand(4) + 3 }); },
  239.    shieldGen: (x, y, side = TEAL) => spawn({ kind: 'structure', subtype: 'shieldGen', side, x, y, size: 18, hp: 900, maxHp: 900, shield: 1200, localRadius: 220, regen: 6, name: "Aegis Shield Emitter" }),
  240.     missileBattery: (x, y, side = RED) => spawn({ kind: 'structure', subtype: 'missileBattery', side, x, y, size: 16, hp: 600, maxHp: 600, reloadTime: 3.5, name: "Hydra Missile Turret" }),
  241.     nukeDrop: (x, y, side = RED) => spawn({ kind: 'structure', subtype: 'nukeDevice', side, x, y, size: 14, hp: 300, maxHp: 300, armed: false, countdown: 1.5, name: "Doomsday Device" }),
  242.     capitalCannon: (x, y, side = RED) => spawn({ kind: 'structure', subtype: 'capitalCannon', side, x, y, size: 22, hp: 1500, maxHp: 1500, reloadTime: 5.0, range: 800, damage: 450, name: "Yamato Cannon" }),
  243.     tradersDen: (x, y) => spawn({ kind: 'structure', subtype: 'tradersDen', side: 'NEUTRAL', x, y, size: 16, hp: 500, maxHp: 500, color: '#f1c40f', name: "Smuggler's Cove" }),
  244.     enclaveSatellite: (x, y) => spawn({ kind: 'structure', subtype: 'enclaveSatellite', side: 'NEUTRAL', x, y, size: 12, hp: 400, maxHp: 400, color: '#bdc3c7', name: "Listening Post Epsilon" }),
  245.     lushPlanet: (x, y) => spawn({ kind: 'celestial', subtype: 'planet', side: 'NEUTRAL', x, y, mass: 14000, color: '#2ecc71', size: 18, name: "Veridia" }),
  246.     firePlanet: (x, y) => spawn({ kind: 'celestial', subtype: 'planet', side: 'NEUTRAL', x, y, mass: 18000, color: '#e74c3c', size: 22, name: "Pyralia" })
  247.   };
  248.  
  249.   initFactions(); poolInit(); generateMissions();
  250.   function spawnProjectile(x, y, vx, vy, owner, type = 'bullet', life = 2.0, damage = 18) { if (projCount >= CFG.MAX_PROJECTILES) return null; const p = projAcquire(); Object.assign(p, { kind: 'proj', subtype: type, x, y, vx, vy, ownerId: owner ? owner.id : 0, ownerSide: owner ? owner.side : 'NEUTRAL', life, damage, size: (type === 'laser' ? 2 : 2), created: performance.now() }); entities.push(p); projCount++; return p; }
  251.   function removeEntityByIndex(i) { const e = entities[i]; if (!e) return; if (e.kind === 'proj') { projCount = Math.max(0, projCount - 1); if (e._pooled !== undefined) projRelease(e); } else if (e.side && factions[e.side]) { const idx = factions[e.side].members.indexOf(e.id); if (idx >= 0) factions[e.side].members.splice(idx, 1); } entities.splice(i, 1); selectedIds.delete(e.id); }
  252.   function mkFX(type, x, y, color = '#fff') { spawn({ kind: 'fx', subtype: type, x, y, t: 0, life: type === 'bigExpl' ? 1.2 : 0.28, color }); }
  253.   function explode(x, y, radius = 120, damage = 500) { const near = spatialQueryRegion(x, y, radius); for (const s of near) { if (!s || (s.kind !== 'ship' && s.kind !== 'structure')) continue; const d = Math.hypot(s.x - x, s.y - y); if (d < radius) s.hp -= damage * (1 - d / radius); } mkFX('bigExpl', x, y); }
  254.  function destroyShip(s) { explode(s.x, s.y, (s.size || 12) * 4, (s.maxHp || 100) * 0.45); const idx = entities.indexOf(s); if (idx >= 0) removeEntityByIndex(idx); }
  255.   function chooseTarget(source) { const cand = entities.filter(e => e.kind === 'ship' && e.side !== source.side && e !== source); if (!cand.length) return null; cand.sort((a, b) => Math.hypot(a.x - source.x, a.y - source.y) - Math.hypot(b.x - source.x, b.y - source.y)); return cand[0]; }
  256.  
  257.   function updateMissions(dt) {
  258.     const now = performance.now();
  259.     for (const mission of missions) {
  260.       if (mission.accepted && mission.startTime === 0) mission.startTime = now;
  261.       if (mission.accepted && mission.startTime > 0) {
  262.        const elapsed = (now - mission.startTime) / 1000;
  263.         mission.progress = Math.min(1, elapsed / mission.timeLimit);
  264.         if (elapsed >= mission.timeLimit) {
  265.           mission.accepted = false;
  266.           if (mission.faction === TEAL) { for(const resource in mission.reward) { if (factions[TEAL].resources.hasOwnProperty(resource)) factions[TEAL].resources[resource] += mission.reward[resource]; } factions[TEAL].reputation = Math.min(100, factions[TEAL].reputation + (mission.reward.reputation || 0)); }
  267.           mkFX('spark', mission.location.x, mission.location.y, '#6ee7b7');
  268.         }
  269.       }
  270.     }
  271.   }
  272.  
  273.   function backgroundSim(dt) {
  274.     if (Math.random() < 0.001) { // Low chance each tick
  275.      const side = Math.random() > 0.5 ? TEAL : RED;
  276.       const shipType = ['fighter', 'brawler', 'sniper'][Math.floor(Math.random()*3)];
  277.       const x = Math.random() > 0.5 ? -50 : W() + 50;
  278.       const y = Math.random() * H();
  279.       spawnActions[shipType](x, y, side);
  280.     }
  281.     entities.filter(e => e.kind === 'celestial' && e.subtype === 'planet').forEach(p => {
  282.        if(p.population > 0 && Math.random() < 0.0005) { // chance for population change
  283.            p.population += Math.floor(rand(p.population * 0.01));
  284.             p.population = Math.max(0, p.population);
  285.         }
  286.     });
  287.   }
  288.  
  289.   function step(dt) {
  290.     dt = Math.min(dt, CFG.DT_MAX);
  291.     spatialClear(); for (const e of entities) spatialInsert(e);
  292.     const celestials = entities.filter(e => e.kind === 'celestial'); const ships = entities.filter(e => e.kind === 'ship');
  293.     updateMissions(dt); backgroundSim(dt);
  294.     for (const s of ships) { let ax = 0, ay = 0; for (const c of celestials) { const dx = c.x - s.x, dy = c.y - s.y, r2 = dx*dx + dy*dy + 100; const inv = 1 / Math.sqrt(r2); const f = CFG.GRAVITY_BASE * c.mass * (s.mass || 1) / r2; ax += f * dx * inv / (s.mass || 1); ay += f * dy * inv / (s.mass || 1); } s.vx += ax * dt * 60; s.vy += ay * dt * 60; }
  295.     for (const s of ships) {
  296.       s.lastShot = s.lastShot || 0;
  297.       if (!s.playerControl) {
  298.         if (!s.target || s.target.hp <= 0) { if (Math.random() < 0.02) s.target = chooseTarget(s); }
  299.        const target = entities.find(e => e.id === s.target?.id); s.target = target;
  300.         if (target) { const dx = target.x - s.x, dy = target.y - s.y, r = Math.hypot(dx, dy) + 0.01; switch (s.archetype) { case 'fighter': s.vx += (dx / r) * 0.02; s.vy += (dy / r) * 0.02; break; case 'brawler': s.vx += (dx / r) * 0.03; s.vy += (dy / r) * 0.03; break; case 'sniper': if (r < (s.range || 520) * 0.6) { s.vx -= (dx / r) * 0.02; s.vy -= (dy / r) * 0.02; } else { s.vx += (dx / r) * 0.01; s.vy += (dy / r) * 0.01; } break; case 'kamikaze': s.vx += (dx / r) * 0.05; s.vy += (dy / r) * 0.05; break; case 'support': { const ally = entities.filter(e => e.kind === 'ship' && e.side === s.side && e.hp < e.maxHp).sort((a, b) => a.hp - b.hp)[0]; if (ally) { const adx = ally.x - s.x, ady = ally.y - s.y, ar = Math.hypot(adx, ady) + 0.01; s.vx += (adx / ar) * 0.015; s.vy += (ady / ar) * 0.015; if (ar < s.range * 0.8 && (performance.now() - s.lastShot > (s.fireRate * 1000))) { ally.hp = Math.min(ally.maxHp, ally.hp - s.damage); s.lastShot = performance.now(); } } else { s.vx += rand(0.0006); s.vy += rand(0.0006); } } break; default: s.vx += rand(0.0005); s.vy += rand(0.0005); } }
  301.         else { s.vx += rand(0.0006); s.vy += rand(0.0006); }
  302.       }
  303.       if (s.weapon !== 'ram' && s.weapon !== 'repair') { const now = performance.now(); if (s.target && (now - s.lastShot) > ((s.fireRate || 0.5) * 1000)) { const dx = s.target.x - s.x, dy = s.target.y - s.y, r = Math.hypot(dx, dy); if (r < (s.range || 300)) { const lead = r / (s.weapon === 'laser' ? 15 : 8); const tx = s.target.x + (s.target.vx || 0) * lead, ty = s.target.y + (s.target.vy || 0) * lead; const dirx = tx - s.x, diry = ty - s.y, magd = Math.hypot(dirx, diry) + 0.001; const speed = s.weapon === 'laser' ? 15 : 8.0; spawnProjectile(s.x + (dirx / magd) * s.size, s.y + (diry / magd) * s.size, (dirx / magd) * speed + (s.vx || 0), (diry / magd) * speed + (s.vy || 0), s, s.weapon, 2.5, s.damage || 18); s.lastShot = now; } } }
  304.      if (s.subtype === 'mothership') { s.spawnTimer = (s.spawnTimer || 0) + dt; if (s.spawnTimer > (s.spawnRate || 3.0)) { s.spawnTimer = 0; const choice = ['fighter', 'brawler', 'sniper'][Math.floor(Math.random() * 3)]; spawnActions[choice](s.x + rand(30), s.y + rand(30), s.side); } }
  305.       s.x += (s.vx || 0) * dt * 60; s.y += (s.vy || 0) * dt * 60;
  306.       if (s.x < -400) s.x = W() + 400; if (s.x > W() + 400) s.x = -400; if (s.y < -400) s.y = H() + 400; if (s.y > H() + 400) s.y = -400;
  307.       if (s.hp <= 0) destroyShip(s);
  308.    }
  309.    for (let i = entities.length - 1; i >= 0; i--) { const p = entities[i]; if (p?.kind !== 'proj') continue; p.x += (p.vx || 0) * dt * 60; p.y += (p.vy || 0) * dt * 60; p.life -= dt; if (p.life <= 0) { removeEntityByIndex(i); continue; } const possible = spatialQueryRegion(p.x, p.y, 80); for (const hit of possible) { if (!hit || hit.kind !== 'ship' || hit.side === p.ownerSide) continue; const rr = (hit.size || 8) + (p.size || 2), dx = hit.x - p.x, dy = hit.y - p.y; if (dx * dx + dy * dy < rr * rr) { hit.hp -= p.damage || 18; mkFX('hit', p.x, p.y); removeEntityByIndex(i); if (hit.hp <= 0) destroyShip(hit); break; } } }
  310.    for (const s of ships) { const neighbors = spatialQueryRegion(s.x, s.y, s.size + 80); for (const b of neighbors) { if (!b || b.kind !== 'ship' || b.id <= s.id) continue; const dx = b.x - s.x, dy = b.y - s.y, r = (s.size || 8) + (b.size || 8); if (dx * dx + dy * dy < r * r) { const impact = Math.min(50, Math.hypot(b.vx - s.vx, b.vy - s.vy) * 2); s.hp -= impact; b.hp -= impact; const mag = Math.hypot(dx, dy) || 1, nx = dx / mag, ny = dy / mag; s.vx -= nx * 0.5; s.vy -= ny * 0.5; b.vx += nx * 0.5; b.vy += ny * 0.5; mkFX('spark', (s.x + b.x) / 2, (s.y + b.y) / 2, '#f88'); if (s.hp <= 0) destroyShip(s); if (b.hp <= 0) destroyShip(b); } } }
  311.    for (let i = entities.length - 1; i >= 0; i--) { const e = entities[i]; if (e?.kind === 'fx') { e.t = (e.t || 0) + dt; if (e.t > e.life) removeEntityByIndex(i); } }
  312.   }
  313.  
  314.   function drawShipInfo(ship) { const panel = document.getElementById('shipPanel'), shipInfo = document.getElementById('shipInfo'), crewList = document.getElementById('crewList'), crewCount = document.getElementById('crewCount'); if (!ship) { panel.style.display = 'none'; return; } shipInfo.innerHTML = `<h3>${ship.name || 'Unnamed Ship'}</h3><p>Type: ${ship.archetype || 'Unknown'}</p><p>Faction: ${factions[ship.side]?.name || 'Neutral'}</p><p>HP: ${Math.floor(ship.hp)}/${ship.maxHp}</p>${ship.shield ? `<p>Shield: ${Math.floor(ship.shield)}/${ship.maxShield}</p>` : ''}${ship.weapons?.length ? `<p>Weapons: ${ship.weapons.map(w => w.type).join(', ')}</p>` : ''}${ship.modules?.length ? `<p>Modules: ${ship.modules.join(', ')}</p>` : ''}`; if (ship.crew?.length) { crewCount.textContent = `(${ship.crew.length})`; crewList.innerHTML = ''; ship.crew.forEach(member => { const el = document.createElement('div'); el.className = 'crew-member'; el.innerHTML = `<span class="crew-role">${member.name} (${member.role})</span><span class="crew-stats">Skill: ${member.skill} | Morale: ${member.morale}</span>`; crewList.appendChild(el); }); } else { crewList.innerHTML = '<p>No crew</p>'; } panel.style.display = 'block'; }
  315.   function drawMissionPanel() { const panel = document.getElementById('missionPanel'), missionList = document.getElementById('missionList'); missionList.innerHTML = ''; missions.forEach(mission => { const card = document.createElement('div'); card.className = 'mission-card'; const rewards = Object.entries(mission.reward).map(([key, value]) => `${value} ${key}`).join(', '); card.innerHTML = `<h3>${mission.name}</h3><p>${mission.desc}</p><p>Faction: ${factions[mission.faction]?.name || 'Neutral'}</p><p>Reward: ${rewards}</p><p>Difficulty: ${'★'.repeat(mission.difficulty)}</p>${mission.accepted ? `<progress value="${mission.progress * 100}" max="100"></progress><p>Time remaining: ${Math.ceil(mission.timeLimit * (1 - mission.progress))}s</p>` : `<button class="accept-mission" data-id="${mission.id}">Accept Mission</button>`}`; missionList.appendChild(card); }); panel.style.display = 'block'; }
  316.   function drawStatsPanel() { const grid = document.getElementById('statsGrid'); const playerFaction = factions[TEAL]; if (!grid || !playerFaction) return; let html = ''; for (const resource in playerFaction.resources) { html += `<div class="label">${resource.charAt(0).toUpperCase() + resource.slice(1)}</div><div class="value">${playerFaction.resources[resource]}</div>`; } html += `<div class="label" style="margin-top: 8px; grid-column: 1 / -1; color: var(--text-accent);">Reputation</div>`; html += `<div class="label">TEAL</div><div class="value">${playerFaction.reputation}</div>`; html += `<div class="label">RED</div><div class="value">${factions[RED].reputation}</div>`; grid.innerHTML = html; }
  317.  
  318.   function draw() {
  319.     ctx.fillStyle = '#010214'; ctx.fillRect(0, 0, W(), H()); ctx.fillStyle = 'rgba(255,255,255,0.03)'; for (let i = 0; i < 120; i++) { const x = (i * 123.7 + performance.now() * 0.001) % W(), y = (i * 221.3) % H(); ctx.fillRect(x, y, 1, 1); }
  320.    ctx.save();
  321.    if (followId) { const f = entities.find(e => e.id === followId); if (f) { camera.targetX = W() / 2 - f.x; camera.targetY = H() / 2 - f.y; } }
  322.     camera.x += (camera.targetX - camera.x) * camera.lerp; camera.y += (camera.targetY - camera.y) * camera.lerp;
  323.     ctx.translate(camera.x, camera.y);
  324.  
  325.     missions.forEach(mission => { if (mission.accepted) { ctx.beginPath(); ctx.arc(mission.location.x, mission.location.y, 20, 0, TAU); ctx.fillStyle = hexToRGBA(factions[mission.faction]?.color || '#fff', 0.3); ctx.fill(); ctx.strokeStyle = hexToRGBA(factions[mission.faction]?.color || '#fff', 0.8); ctx.lineWidth = 2; ctx.stroke(); ctx.beginPath(); ctx.arc(mission.location.x, mission.location.y, 20, -Math.PI/2, -Math.PI/2 + TAU * mission.progress); ctx.strokeStyle = hexToRGBA('#6ee7b7', 0.8); ctx.lineWidth = 3; ctx.stroke(); } });
  326.     for (const e of entities) {
  327.       switch (e.kind) {
  328.         case 'celestial': {
  329.           const r = Math.max(2, e.size);
  330.           if (e.subtype === 'star') { const g = ctx.createRadialGradient(e.x, e.y, 0, e.x, e.y, r * 7); g.addColorStop(0, hexToRGBA(e.color || '#ffd166', 0.55)); g.addColorStop(1, hexToRGBA(e.color || '#ffd166', 0)); ctx.fillStyle = g; ctx.beginPath(); ctx.arc(e.x, e.y, r * 7, 0, TAU); ctx.fill(); }
  331.           ctx.beginPath(); ctx.fillStyle = e.color || '#88c'; ctx.arc(e.x, e.y, r, 0, TAU); ctx.fill();
  332.           if(e.subtype === 'planet' && e.allegiance){ ctx.beginPath(); ctx.arc(e.x, e.y, r + 4, 0, TAU); ctx.strokeStyle = factions[e.allegiance].color; ctx.lineWidth = 2; ctx.stroke();}
  333.           if (e.subtype === 'planet' || e.subtype === 'star') { ctx.fillStyle = '#fff'; ctx.font = '10px Inter'; ctx.textAlign = 'center'; ctx.fillText(e.name || 'Unknown', e.x, e.y + r + 15); if(e.population) ctx.fillText(`Pop: ${Math.floor(e.population/1000)}k`, e.x, e.y + r + 28); }
  334.         } break;
  335.         case 'ship': case 'structure': {
  336.           ctx.save(); ctx.translate(e.x, e.y); const ang = Math.atan2(e.vy || 0, e.vx || 0); ctx.rotate(ang + Math.PI / 2); const col = factions[e.side]?.color || '#999';
  337.           if (e.kind === 'ship') { ctx.beginPath(); ctx.moveTo(0, -e.size); ctx.lineTo(-e.size * 0.7, e.size); ctx.lineTo(e.size * 0.7, e.size); ctx.closePath(); ctx.fillStyle = col; ctx.fill(); }
  338.           else { if (e.subtype === 'capitalCannon') { ctx.fillStyle = e.color || '#d33'; ctx.fillRect(-e.size / 2, -e.size / 2, e.size, e.size); } else { ctx.fillStyle = col; ctx.beginPath(); ctx.arc(0,0,e.size,0,TAU); ctx.fill(); } }
  339.           if (e.hp < e.maxHp) { const hW = e.size * 1.5; ctx.fillStyle = 'rgba(0,0,0,0.6)'; ctx.fillRect(-hW / 2, e.size + 6, hW, 5); ctx.fillStyle = '#4caf50'; ctx.fillRect(-hW / 2, e.size + 6, hW * Math.max(0, (e.hp / e.maxHp)), 5); }
  340.          ctx.restore();
  341.          if (selectedIds.has(e.id) && e.name) { ctx.fillStyle = '#fff'; ctx.font = '10px Inter'; ctx.textAlign = 'center'; ctx.fillText(e.name, e.x, e.y - e.size - 10); }
  342.        } break;
  343.        case 'proj': { ctx.fillStyle = e.subtype === 'laser' ? '#9ef' : '#ffc'; ctx.beginPath(); ctx.arc(e.x, e.y, e.size || 2, 0, TAU); ctx.fill(); } break;
  344.        case 'fx': { const p = (e.t / e.life) || 0; if (e.subtype === 'bigExpl') { const r = (p * 36 + 10); ctx.fillStyle = `rgba(255,150,50,${1-p})`; ctx.beginPath(); ctx.arc(e.x, e.y, r, 0, TAU); ctx.fill(); } else if (e.subtype === 'hit') { ctx.fillStyle = `rgba(255,255,255,${1-p})`; ctx.beginPath(); ctx.arc(e.x, e.y, 3+p*8, 0, TAU); ctx.fill(); } else if (e.subtype === 'spark') { ctx.fillStyle = e.color || 'rgba(255,200,50,0.9)'; ctx.beginPath(); ctx.arc(e.x, e.y, 1+p*6, 0, TAU); ctx.fill(); } } break;
  345.      }
  346.    }
  347.    for (const id of selectedIds) { const s = entities.find(e => e.id === id); if (s) { ctx.strokeStyle = '#fff'; ctx.lineWidth = 1 / DPR; ctx.beginPath(); ctx.arc(s.x, s.y, (s.size || 8) + 6, 0, TAU); ctx.stroke(); ctx.beginPath(); ctx.arc(s.x, s.y, 2, 0, TAU); ctx.fillStyle = '#fff'; ctx.fill(); } else { selectedIds.delete(id); } }
  348.     ctx.restore();
  349.     if (isDragging && selectionBox) { ctx.save(); ctx.setTransform(DPR, 0, 0, DPR, 0, 0); ctx.strokeStyle = '#7ef'; ctx.lineWidth = 1/DPR; ctx.setLineDash([5, 5]); ctx.strokeRect( parseFloat(selectionBox.style.left), parseFloat(selectionBox.style.top), parseFloat(selectionBox.style.width), parseFloat(selectionBox.style.height) ); ctx.setLineDash([]); ctx.restore(); }
  350.     document.getElementById('debug').textContent = `E: ${entities.length} — P: ${projCount} — FPS: ${fps} — S: ${selectedIds.size}`;
  351.     drawStatsPanel();
  352.   }
  353.  
  354.   function loop(time) { const dt = (time - lastTime) / 1000; lastTime = time; if (running) step(dt); draw(); frameCount++; if (time > lastFpsTime + CFG.FPS_INTERVAL) { fps = Math.round(frameCount / ((time - lastFpsTime) / 1000)); lastFpsTime = time; frameCount = 0; } requestAnimationFrame(loop); }
  355.   function screenToWorld(screenX, screenY) { return { x: (screenX*DPR - camera.x)/DPR, y: (screenY*DPR - camera.y)/DPR }; }
  356.  
  357.   function handleTerminalCommand(cmd) {
  358.     cmd = cmd.trim(); if (!cmd) return;
  359.     terminalHistory.push(cmd); terminalHistoryIndex = terminalHistory.length;
  360.     const [command, ...args] = cmd.toLowerCase().split(/\s+/); const center = screenToWorld(W() / 2, H() / 2); const playerFaction = factions[TEAL];
  361.     switch (command) {
  362.       case 'spawn': if (args[0] && spawnActions[args[0]]) { const count = parseInt(args[1]) || 1; const side = args[2] === 'teal' ? TEAL : RED; for (let i = 0; i < count; i++) { spawnActions[args[0]](center.x + rand(120), center.y + rand(120), side); } } else { console.log('Available spawn types: ' + Object.keys(spawnActions).join(', ')); } break;
  363.      case 'clear': entities = []; projCount = 0; nextId = 1; selectedIds.clear(); initFactions(); poolInit(); initGame(); break;
  364.      case 'stats': console.log(`Entities: ${entities.length}, Projectiles: ${projCount}, Selected Ships: ${selectedIds.size}`); break;
  365.      case 'explode': explode(center.x, center.y, parseFloat(args[0]) || 120, parseFloat(args[1]) || 500); break;
  366.      case 'teleport': if (selectedIds.size) { const x = parseFloat(args[0]) || center.x, y = parseFloat(args[1]) || center.y; for (const id of selectedIds) { const s = entities.find(e => e.id === id); if (s) { s.x = x + rand(20); s.y = y + rand(20); } } } break;
  367.       case 'mission': if (args[0] === 'list') { missions.forEach(m => console.log(`${m.name} - ${m.desc} (${m.reward.money} credits)`)); } else if (args[0] === 'generate') { generateMissions(); console.log('Generated new missions'); } break;
  368.       case 'crew': if (selectedIds.size) { const ship = entities.find(e => e.id === [...selectedIds][0]); if (ship?.crew) { console.log(`${ship.crew.length} crew members:`); ship.crew.forEach(m => console.log(`${m.name} - ${m.role} (Skill: ${m.skill}, Morale: ${m.morale})`)); } } break;
  369.       case 'add': if (args[0] && playerFaction.resources.hasOwnProperty(args[0])) { playerFaction.resources[args[0]] += parseInt(args[1]) || 1; drawStatsPanel(); } else { console.log('Invalid resource. Available: ' + Object.keys(playerFaction.resources).join(', ')) } break;
  370.       default: console.log('Unknown command. Try: spawn, clear, stats, explode, teleport, mission, crew, add <resource> <amount>');
  371.     }
  372.   }
  373.  
  374.   function updateTerminalSuggestions(input) {
  375.     const suggestions = document.getElementById('terminal-suggestions'); suggestions.innerHTML = '';
  376.     const commands = [ { cmd: 'spawn <type> [count] [side]', desc: 'Spawn entities' }, { cmd: 'clear', desc: 'Clear all entities' }, { cmd: 'stats', desc: 'Show game statistics' }, { cmd: 'explode [radius] [damage]', desc: 'Create explosion' }, { cmd: 'teleport [x] [y]', desc: 'Teleport selected ships' }, { cmd: 'mission list', desc: 'List available missions' }, { cmd: 'mission generate', desc: 'Generate new missions' }, { cmd: 'crew', desc: 'Show crew info' }, { cmd: 'add <resource> <amount>', desc: 'Add resources to player' } ];
  377.     const filtered = input ? commands.filter(c => c.cmd.toLowerCase().startsWith(input.toLowerCase())) : commands;
  378.     if (filtered.length) { filtered.forEach(cmd => { const el = document.createElement('div'); el.textContent = `${cmd.cmd} - ${cmd.desc}`; el.onclick = () => { document.getElementById('terminalInput').value = cmd.cmd.split(' ')[0] + ' '; suggestions.style.display = 'none'; document.getElementById('terminalInput').focus(); }; suggestions.appendChild(el); }); suggestions.style.display = 'block'; } else { suggestions.style.display = 'none'; }
  379.   }
  380.  
  381.   class PieMenu {
  382.     constructor(items) { this.items = items; this.root = null; this.center = { x: 0, y: 0 }; }
  383.     show(clientX, clientY) { this.hide(); const cx = Math.round(clientX), cy = Math.round(clientY); this.center = { x: cx, y: cy }; const root = document.createElement('div'); root.className = 'pie-container'; root.style.transform = `translate(${cx}px, ${cy}px)`; const wrap = document.createElement('div'); wrap.className = 'pie-wrap'; root.appendChild(wrap); const N = this.items.length; const step = TAU / N; const radius = 100; const startOffset = Math.PI / 2;
  384.       for (let i = 0; i < N; i++) { const item = this.items[i]; const slice = document.createElement('div'); slice.className = 'pie-slice'; const ang = i * step + startOffset; const tx = Math.round(Math.cos(ang) * radius); const ty = Math.round(Math.sin(ang) * radius); slice.style.transform = `translate(${tx}px, ${ty}px)`; slice.style.background = hexToRGBA(item.color || '#07202a', 0.88); slice.innerHTML = `<div class="label">${item.label}</div><div class="pie-divider"></div>`; const onDown = (e) => { e.stopPropagation(); const world = screenToWorld(cx, cy); try { item.action(world.x, world.y); } finally { this.hide(); } }; slice.addEventListener('mousedown', onDown); slice.addEventListener('touchstart', onDown, { passive: true }); wrap.appendChild(slice); }
  385.       document.body.appendChild(root); this.root = root;
  386.       setTimeout(() => document.addEventListener('mousedown', this.boundHideOnce || (this.boundHideOnce = this.hide.bind(this)), { once: true }), 0);
  387.       setTimeout(() => document.addEventListener('touchstart', this.boundHideOnceTouch || (this.boundHideOnceTouch = this.hide.bind(this)), { once: true, passive: true }), 0);
  388.     }
  389.     hide() { if (this.root) { this.root.remove(); this.root = null; } }
  390.   }
  391.  
  392.   const playerPie = new PieMenu([ { label: 'Fighter', color: factions[TEAL].color, action: (x, y) => spawnActions.fighter(x, y, TEAL) }, { label: 'Brawler', color: factions[TEAL].color, action: (x, y) => spawnActions.brawler(x, y, TEAL) }, { label: 'Sniper', color: factions[TEAL].color, action: (x, y) => spawnActions.sniper(x, y, TEAL) }, { label: 'Support', color: factions[TEAL].color, action: (x, y) => spawnActions.support(x, y, TEAL) }, { label: 'Mothership', color: factions[TEAL].color, action: (x, y) => spawnActions.playerMothership(x, y) }]);
  393.   const enemyPie = new PieMenu([ { label: 'Enemy Fighter', color: factions[RED].color, action: (x, y) => spawnActions.fighter(x, y, RED) }, { label: 'Enemy Brawler', color: factions[RED].color, action: (x, y) => spawnActions.brawler(x, y, RED) }, { label: 'Enemy Sniper', color: factions[RED].color, action: (x, y) => spawnActions.sniper(x, y, RED) }, { label: 'Enemy Mother', color: factions[RED].color, action: (x, y) => spawnActions.enemyMothership(x, y) }]);
  394.   const astroPie = new PieMenu([ { label: 'Place Star', color: '#ffd166', action: (x, y) => spawnActions.star(x, y) }, { label: 'Place Planet', color: '#7fb', action: (x, y) => spawnActions.planet(x, y) }, { label: 'Asteroid Field', color: '#b7a', action: (x, y) => spawnActions.asteroids(x, y) }]);
  395.   const secretPie = new PieMenu([ { label: 'Capital Cannons', color: '#ff6b6b', action: (x, y) => spawnActions.capitalCannon(x, y, RED) }, { label: "Trader's Den", color: '#f1c40f', action: (x, y) => spawnActions.tradersDen(x, y) }, { label: 'Enclave Satellite', color: '#bdc3c7', action: (x, y) => spawnActions.enclaveSatellite(x, y) }, { label: 'Shield Generator', color: factions[TEAL].color, action:(x,y) => spawnActions.shieldGen(x,y,TEAL) }]);
  396.   const zMenu = new PieMenu([ { label: 'Attack Nearest', color: '#ff6b6b', action: (x, y) => { for (const id of selectedIds) { const s = entities.find(e => e.id === id); if (s && s.playerControl) { s.target = chooseTarget(s); if (s.target) { const dx = s.target.x - s.x; const dy = s.target.y - s.y; const dist = Math.hypot(dx, dy); if (dist < (s.range || 300)) { s.vx += (dx / dist) * 0.1; s.vy += (dy / dist) * 0.1; } } } } } }, { label: 'Move To', color: '#6ee7b7', action: (x, y) => { for (const id of selectedIds) { const s = entities.find(e => e.id === id); if (s && s.playerControl) { s.target = null; const dx = x - s.x; const dy = y - s.y; const dist = Math.hypot(dx, dy); if (dist > 10) { s.vx += (dx / dist) * 0.05; s.vy += (dy / dist) * 0.05; } } } } }, { label: 'Hold Position', color: '#a78bfa', action: () => { for (const id of selectedIds) { const s = entities.find(e => e.id === id); if (s && s.playerControl) { s.vx = 0; s.vy = 0; s.target = null; } } } }, { label: 'Self-Destruct', color: '#e74c3c', action: () => { for (const id of selectedIds) { const s = entities.find(e => e.id === id); if (s && s.playerControl) destroyShip(s); } } }, { label: 'Repair Fleet', color: '#2ecc71', action: () => { for (const id of selectedIds) { const s = entities.find(e => e.id === id); if (s && s.playerControl) { s.hp = Math.min(s.maxHp, s.hp + s.maxHp * 0.25); if (s.shield !== undefined) { s.shield = Math.min(s.maxShield, s.shield + s.maxShield * 0.5); } mkFX('spark', s.x, s.y, '#2ecc71'); } } } }]);
  397.  
  398.   const keys = new Set();
  399.   c.addEventListener('mousemove', (e) => { mouse.screenX = e.clientX; mouse.screenY = e.clientY; if (isDragging && selectionBox) { selectionBox.style.width = Math.abs(e.clientX - mouse.startX) + 'px'; selectionBox.style.height = Math.abs(e.clientY - mouse.startY) + 'px'; selectionBox.style.left = Math.min(e.clientX, mouse.startX) + 'px'; selectionBox.style.top = Math.min(e.clientY, mouse.startY) + 'px'; } });
  400.   c.addEventListener('mousedown', (e) => { if (e.button === 2) { e.preventDefault(); if (keys.has('z')) { zMenu.show(e.clientX, e.clientY); } else if (e.shiftKey) { secretPie.show(e.clientX, e.clientY); } else if (e.ctrlKey || e.metaKey) { enemyPie.show(e.clientX, e.clientY); } else if (e.altKey) { astroPie.show(e.clientX, e.clientY); } else { playerPie.show(e.clientX, e.clientY); } return; } if (e.button === 0) { mouse.down = true; mouse.startX = e.clientX; mouse.startY = e.clientY; if (!e.ctrlKey && !e.metaKey) selectedIds.clear(); const world = screenToWorld(e.clientX, e.clientY); const clicked = spatialQueryRegion(world.x, world.y, 60).slice().reverse().find(en => en.kind === 'ship' && Math.hypot(en.x - world.x, en.y - world.y) < en.size); if (clicked) { selectedIds.add(clicked.id); drawShipInfo(clicked); } else { isDragging = true; selectionBox = document.createElement('div'); selectionBox.className = 'selection-box'; selectionBox.style.left = e.clientX + 'px'; selectionBox.style.top = e.clientY + 'px'; selectionBox.style.width = '0px'; selectionBox.style.height = '0px'; document.body.appendChild(selectionBox); } } });
  401.   c.addEventListener('mouseup', (e) => { if (e.button === 0 && isDragging) { isDragging = false; if (selectionBox) { const rect = selectionBox.getBoundingClientRect(); const worldStart = screenToWorld(rect.left, rect.top); const worldEnd = screenToWorld(rect.right, rect.bottom); entities.filter(s => s.kind === 'ship').forEach(s => { if (s.x > worldStart.x && s.x < worldEnd.x && s.y > worldStart.y && s.y < worldEnd.y) selectedIds.add(s.id); }); if (selectedIds.size) { const firstShip = entities.find(e => e.id === [...selectedIds][0]); drawShipInfo(firstShip); } selectionBox.remove(); selectionBox = null; } } mouse.down = false; });
  402.   c.addEventListener('contextmenu', (e) => e.preventDefault());
  403.   document.addEventListener('keydown', (e) => { const k = e.key.toLowerCase(); keys.add(k); if (document.activeElement === terminalInput) return; if (k === 'p') { running = !running; document.getElementById('btnPause').textContent = running ? 'Pause (P)' : 'Resume (P)'; } if (k === 'm') document.getElementById('btnMegaToggle').click(); if (k === '`' || k === '~') { e.preventDefault(); const t = document.getElementById('terminal'); t.style.display = t.style.display === 'block' ? 'none' : 'block'; if (t.style.display === 'block') { terminalInput.focus(); updateTerminalSuggestions(''); } } if (k === 'f') { const ships = entities.filter(s => s.kind === 'ship'); if (ships.length) { const idx = ships.findIndex(s => s.id === followId); followId = ships[(idx + 1) % ships.length].id; } } if (k === 'j') { drawMissionPanel(); } if (k === 'i') { if (selectedIds.size) { const ship = entities.find(e => e.id === [...selectedIds][0]); drawShipInfo(ship); } else { document.getElementById('shipPanel').style.display = 'none'; } } });
  404.   document.addEventListener('keyup', (e) => { keys.delete(e.key.toLowerCase()); });
  405.   const terminalInput = document.getElementById('terminalInput');
  406.   terminalInput.addEventListener('keydown', (e) => { e.stopPropagation(); if (e.key === 'Enter') { handleTerminalCommand(terminalInput.value); terminalInput.value = ''; document.getElementById('terminal-suggestions').style.display = 'none'; } else if (e.key === 'Escape') { document.getElementById('terminal').style.display = 'none'; } else if (e.key === 'ArrowUp') { e.preventDefault(); if (terminalHistoryIndex > 0) { terminalHistoryIndex--; terminalInput.value = terminalHistory[terminalHistoryIndex] || ''; } } else if (e.key === 'ArrowDown') { e.preventDefault(); if (terminalHistoryIndex < terminalHistory.length - 1) { terminalHistoryIndex++; terminalInput.value = terminalHistory[terminalHistoryIndex] || ''; } else { terminalHistoryIndex = terminalHistory.length; terminalInput.value = ''; } } else { setTimeout(() => updateTerminalSuggestions(terminalInput.value), 0); } });
  407.   terminalInput.addEventListener('focus', () => updateTerminalSuggestions(terminalInput.value));
  408.   terminalInput.addEventListener('blur', () => { setTimeout(() => document.getElementById('terminal-suggestions').style.display = 'none', 150); });
  409.   document.getElementById('missionList').addEventListener('click', (e) => { if (e.target.classList.contains('accept-mission')) { const missionId = parseInt(e.target.dataset.id); const mission = missions.find(m => m.id === missionId); if (mission) { mission.accepted = true; drawMissionPanel(); } } });
  410.   document.getElementById('closeMissions').addEventListener('click', () => { document.getElementById('missionPanel').style.display = 'none'; });
  411.   document.getElementById('btnPause').onclick = () => { running = !running; document.getElementById('btnPause').textContent = running ? 'Pause (P)' : 'Resume (P)'; };
  412.   document.getElementById('btnCenter').onclick = () => { followId = null; camera.targetX = 0; camera.targetY = 0; };
  413.   document.getElementById('btnClear').onclick = () => { entities = []; projCount = 0; nextId = 1; selectedIds.clear(); initFactions(); poolInit(); initGame(); document.getElementById('shipPanel').style.display = 'none'; };
  414.   document.getElementById('btnMegaToggle').onclick = () => { const m = document.getElementById('mega-panel'); m.style.display = m.style.display === 'block' ? 'none' : 'block'; };
  415.   document.getElementById('btnTerminal').onclick = () => { const t = document.getElementById('terminal'); t.style.display = t.style.display === 'block' ? 'none' : 'block'; if (t.style.display === 'block') { terminalInput.focus(); updateTerminalSuggestions(''); } };
  416.   document.getElementById('btnMissions').onclick = () => { drawMissionPanel(); };
  417.   document.getElementById('btnShipPanel').onclick = () => { if (selectedIds.size) { const ship = entities.find(e => e.id === [...selectedIds][0]); drawShipInfo(ship); } else { document.getElementById('shipPanel').style.display = 'none'; } };
  418.   document.getElementById('mega-panel').onclick = (e) => { if (e.target.classList.contains('spawn-btn')) { const action = e.target.dataset.action; if (spawnActions[action]) { const center = screenToWorld(W() / 2, H() / 2); spawnActions[action](center.x + rand(120), center.y + rand(120)); } } };
  419.  
  420.   function initGame() { const center = screenToWorld(W() / 2, H() / 2); spawnActions.star(center.x, center.y); spawnActions.playerMothership(center.x - 420, center.y); spawnActions.enemyMothership(center.x + 420, center.y); spawnActions.planet(center.x - 200, center.y - 150); spawnActions.planet(center.x + 250, center.y + 100); spawnActions.lushPlanet(center.x + 180, center.y - 200); }
  421.   initGame(); requestAnimationFrame(loop);
  422.   window.__SPACEVERSE_V57 = { spawnActions, entities, factions, missions };
  423. })();
  424. </script>
  425. </body>
  426. </html>
  427.  
Advertisement
Add Comment
Please, Sign In to add comment