00:00
00:00
PyroflamePC
I just like to make and play games! Way cool, huh? Tell me now, what is your all-time favorite game... Could it be.. Crumbled 3?

Male

Gamification Pro

Full Sail University

Waycross, GA

Joined on 6/27/25

Level:
2
Exp Points:
42 / 50
Exp Rank:
> 100,000
Vote Power:
2.60 votes
Rank:
Civilian
Global Rank:
> 100,000
Blams:
3
Saves:
23
B/P Bonus:
0%
Whistle:
Normal
Supporter:
1m 14d

Space v.4.8.2 (V4HB)

Posted by PyroflamePC - 17 hours ago


Space V4H2 --> https://pastebin.com/CH9K2tU3


Source:


<!doctype html>

<html lang="en">

<head>

<meta charset="utf-8" />

<meta name="viewport" content="width=device-width,initial-scale=1" />

<title>SPACE v4H — Pie Menu FIXED</title>

<style>

:root{

 --bg:#010214; --panel:rgba(2,6,12,0.78); --muted:#9aa; --accent:#7ef;

 --teal:#6ee7b7; --danger:#ff6b6b;

}

*{box-sizing:border-box}

html,body{height:100%;margin:0;background:var(--bg);color:#dfe;overflow:hidden;font-family:Inter,system-ui,-apple-system,"Segoe UI",Roboto}

canvas{display:block;width:100vw;height:100vh;cursor:crosshair;background:linear-gradient(180deg,#000011,#02031a)}

#hud{position:fixed;left:12px;top:12px;z-index:60;background:var(--panel);padding:12px;border-radius:10px;border:1px solid rgba(255,255,255,0.04);backdrop-filter:blur(4px)}

#hud .title{font-weight:800;color:var(--accent);margin-bottom:6px;font-size:14px}

#hud .row{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:6px}

#hud button{background:#07202a;border:none;color:#bfe;padding:8px 12px;border-radius:8px;cursor:pointer;font-weight:600;font-size:12px}

#hud button:hover{background:#0a2a36}

#hud small{display:block;color:var(--muted);margin-top:6px;font-size:11px;line-height:1.3}


.pie-container{position:fixed;z-index:120;user-select:none;pointer-events:auto;transform-origin:center center;transition:transform .12s ease,opacity .12s ease}

.pie-slice{position:absolute;width:150px;height:46px;display:flex;align-items:center;justify-content:center;pointer-events:auto;cursor:pointer;border-radius:8px;padding:6px;box-shadow:0 4px 12px rgba(0,0,0,0.45)}

.pie-slice .label{color:#fff;font-weight:700;text-shadow:0 0 6px rgba(0,0,0,0.6);font-size:13px;text-align:center}

.pie-slice:hover{transform:scale(1.06)}


#mega{position:fixed;right:12px;top:12px;width:360px;max-height:calc(100vh - 24px);overflow-y:auto;z-index:80;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;backdrop-filter:blur(10px)}

#mega h2{margin:0 0 12px 0;color:#8ef;font-size:16px}

.spawn-row{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:8px}

.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}

.spawn-btn:hover{background:#0a1520;border-color:rgba(255,255,255,0.08);transform:translateY(-1px)}


#debug{position:fixed;left:12px;bottom:12px;color:var(--muted);font-size:12px;z-index:55;background:rgba(2,6,12,0.5);padding:8px;border-radius:8px;border:1px solid rgba(255,255,255,0.02)}

@media (max-width:640px){ .pie-slice{width:110px;height:40px} #mega{width:92%;right:4%;left:4%} }

</style>

</head>

<body>

<canvas id="c" tabindex=0></canvas>


<div id="hud">

 <div class="title">SPACE — v4H (Pie fixed)</div>

 <div class="row">

  <button id="btnPause">Pause (P)</button>

  <button id="btnCenter">Center View</button>

  <button id="btnClear">Clear All</button>

  <button id="btnMegaToggle">Toggle Menu (M)</button>

 </div>

 <small>Right-click = Player pie | Ctrl/Cmd+Right-click = Enemy pie | Alt+Right-click = Astro pie<br>

 Left-click = select | WASD = control selected ship | Space = fire | F = cycle follow</small>

</div>


<div id="mega" aria-hidden="true">

 <h2>🚀 Mega Spawner</h2>

 <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>

 <div class="spawn-row">

  <button class="spawn-btn" data-action="fighter">⚡ Fighter</button>

  <button class="spawn-btn" data-action="brawler">💪 Brawler</button>

 </div>

 <div class="spawn-row">

  <button class="spawn-btn" data-action="sniper">🎯 Sniper</button>

  <button class="spawn-btn" data-action="support">🔧 Support</button>

 </div>

 <div class="spawn-row">

  <button class="spawn-btn" data-action="kamikaze">💥 Kamikaze</button>

  <button class="spawn-btn" data-action="asteroids">☄️ Asteroid Field</button>

 </div>

 <div class="spawn-row">

  <button class="spawn-btn" data-action="star">⭐ Star</button>

  <button class="spawn-btn" data-action="planet">🪐 Planet</button>

 </div>

 <div style="font-size:12px;color:#9aa;margin-top:8px">• Mega spawns near camera center; pies spawn at your cursor.</div>

</div>


<div id="debug">Entities: 0 — Projectiles: 0 — FPS: 0</div>


<script>

(() => {

 // ---------- CONFIG ----------

 const CONFIG = {

  GRAVITY_BASE: 0.0007,

  MAX_PROJECTILES: 900,

  MAX_ENTITIES: 5000,

  POOL_PROJECTILES: 1200,

  SPATIAL_CELL: 220,

  DT_MAX: 0.05,

  FPS_INTERVAL: 1000

 };


 // ---------- Canvas ----------

 const canvas = document.getElementById('c');

 const ctx = canvas.getContext('2d', { alpha: false });

 let DPR = Math.max(1, window.devicePixelRatio || 1);

 function resizeCanvas(){

  DPR = Math.max(1, window.devicePixelRatio || 1);

  canvas.width = Math.floor(window.innerWidth * DPR);

  canvas.height = Math.floor(window.innerHeight * DPR);

  canvas.style.width = window.innerWidth + 'px';

  canvas.style.height = window.innerHeight + 'px';

  ctx.setTransform(DPR,0,0,DPR,0,0);

 }

 window.addEventListener('resize', resizeCanvas);

 resizeCanvas();

 const W = () => window.innerWidth;

 const H = () => window.innerHeight;


 // ---------- Utils ----------

 const TAU = Math.PI * 2;

 const rand = (r=1) => (Math.random()-0.5)*2*r;

 const hexToRGBA = (hex, a=1) => {

  if(!hex) return `rgba(255,255,255,${a})`;

  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);

  return `rgba(${r},${g},${b},${a})`;

 };


 // ---------- State ----------

 let entities = [];

 let nextId = 1;

 let running = true;

 let selectedId = null;

 let followId = null;

 let projCount = 0;

 let lastTime = performance.now();

 let fps = 0, frameCount = 0, lastFpsTime = performance.now();


 const camera = { x:0, y:0, targetX:0, targetY:0, lerp:0.08 };


 const mouse = { screenX: W()/2, screenY: H()/2, x:0, y:0, down:false, btn:-1 };


 const TEAL = 'TEAL', RED = 'RED';

 let factions = {}, sideFactions = [];

 function initFactions(){

  factions = {

   [TEAL]: { id:TEAL, name:'TEAL', color:'#6ee7b7', members:[] },

   [RED]: { id:RED, name:'RED', color:'#ff6b6b', members:[] }

  };

  sideFactions = [{id:'PIRATES',name:'Pirates',color:'#ffd166'},{id:'MERCHANTS',name:'Merchants',color:'#a78bfa'}];

 }

 initFactions();


 // ---------- Pools & Spatial ----------

 const projPool = [];

 function poolInit(){ projPool.length=0; for(let i=0;i<CONFIG.POOL_PROJECTILES;i++) projPool.push({ _pooled:true }); }

 poolInit();

 function projAcquire(){ return projPool.length ? projPool.pop() : { _pooled:false }; }

 function projRelease(p){ p._pooled = true; p.kind='free'; p.ownerId = 0; p.ownerSide = 'NEUTRAL'; projPool.push(p); }


 const spatial = new Map();

 function spatialKey(cx,cy){ return `${cx},${cy}`; }

 function spatialClear(){ spatial.clear(); }

 function spatialInsert(e){

  const cell = CONFIG.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);

  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);

   }

  }

 }

 function spatialQueryRegion(x,y,radius){

  const cell = CONFIG.SPATIAL_CELL;

  const minX = Math.floor((x - radius) / cell);

  const maxX = Math.floor((x + radius) / cell);

  const minY = Math.floor((y - radius) / cell);

  const maxY = Math.floor((y + radius) / cell);

  const set = new Set();

  for(let cx=minX; cx<=maxX; cx++){

   for(let cy=minY; cy<=maxY; cy++){

    const k = spatialKey(cx,cy);

    const arr = spatial.get(k);

    if(arr) for(const e of arr) set.add(e);

   }

  }

  return Array.from(set);

 }


 // ---------- Spawner ----------

 function spawn(obj){

  if(entities.length >= CONFIG.MAX_ENTITIES) return null;

  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()

  };

  const e = Object.assign({}, defaults, obj);

  entities.push(e);

  if(e.side && factions[e.side]) factions[e.side].members.push(e.id);

  return e;

 }


 const spawnActions = {

  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 }),

  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 }),

  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 }),

  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 }),

  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 }),

  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 }),

  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 }),

  star: (x,y) => spawn({ kind:'celestial', subtype:'star', side:'NEUTRAL', x,y, mass:250000, color:'#ffd166', size:30, fixed:true }),

  planet: (x,y) => spawn({ kind:'celestial', subtype:'planet', side:'NEUTRAL', x,y, mass:14000, color:'#7fb', size:18 }),

  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 }); },

  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 }),

  missileBattery: (x,y,side=RED) => spawn({ kind:'structure', subtype:'missileBattery', side, x,y, size:16, hp:600, maxHp:600, reloadTime:3.5 }),

  nukeDrop: (x,y,side=RED) => spawn({ kind:'structure', subtype:'nukeDevice', side, x,y, size:14, hp:300, maxHp:300, armed:false, countdown:1.5 }),

  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 })

 };


 // ---------- Projectiles ----------

 function spawnProjectile(x,y,vx,vy,owner,type='bullet',life=2.0,damage=18){

  if (projCount >= CONFIG.MAX_PROJECTILES) return null;

  const p = projAcquire();

  p.kind = 'proj'; p.subtype = type;

  p.x = x; p.y = y; p.vx = vx; p.vy = vy;

  p.ownerId = owner ? owner.id : 0; p.ownerSide = owner ? owner.side : 'NEUTRAL';

  p.life = life; p.damage = damage; p.size = (type==='laser'?2:2); p.created = performance.now();

  entities.push(p); projCount++; return p;

 }


 // ---------- Removal ----------

 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);

 }

 function removeEntityById(id){ const i = entities.findIndex(e=>e.id===id); if (i>=0) removeEntityByIndex(i); }


 function mkFX(type,x,y,color='#fff'){ spawn({ kind:'fx', subtype:type, x,y, t:0, life: type==='bigExpl'?1.2:0.28, color }); }

 function explode(x,y,radius=120,damage=500){ const nearby = spatialQueryRegion(x,y,radius); for(const s of nearby){ 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); }

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


 // ---------- Physics & AI ----------

 function step(dt){

  dt = Math.min(dt, CONFIG.DT_MAX);

  spatialClear(); for(const e of entities) spatialInsert(e);

  const celestials = entities.filter(e=>e.kind==='celestial');

  const ships = entities.filter(e=>e.kind==='ship');

  const projs = entities.filter(e=>e.kind==='proj');


  // gravity on ships

  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;

    const r2 = dx*dx + dy*dy + 64; const inv = 1 / Math.sqrt(r2);

    const f = CONFIG.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;

  }


  // ships update

  for(const s of ships){

   s.lastShot = s.lastShot || 0; s.hp = s.hp || s.maxHp || 100;

   if (!s.playerControl){ if (!s.target || s.target.hp <= 0) { if (Math.random() < 0.02) s.target = chooseTarget(s); } }

   // archetype behaviours

   if (!s.playerControl){

    switch(s.archetype){

     case 'fighter':

      if (s.target){ const dx=s.target.x - s.x, dy=s.target.y - s.y, r=Math.hypot(dx,dy)+0.001; s.vx += (dx/r)*0.02; s.vy += (dy/r)*0.02; } else { s.vx += rand(0.0006); s.vy += rand(0.0006); }

      break;

     case 'brawler':

      if (s.target){ const dx=s.target.x - s.x, dy=s.target.y - s.y, r=Math.hypot(dx,dy)+0.001; s.vx += (dx/r)*0.03; s.vy += (dy/r)*0.03; } else { s.vx += rand(0.002); s.vy += rand(0.002); }

      break;

     case 'sniper':

      if (s.target){ const dx=s.target.x - s.x, dy=s.target.y - s.y, r=Math.hypot(dx,dy)+0.001; if (r < (s.range||520)*0.6){ s.vx -= (dx/r)*0.02; s.vy -= (dy/r)*0.02; } else { s.vx += rand(0.0006); s.vy += rand(0.0006); } } else { s.vx *= 0.999; s.vy *= 0.999; }

      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 dx=ally.x - s.x, dy=ally.y - s.y, r=Math.hypot(dx,dy)+0.001; s.vx += (dx/r)*0.015; s.vy += (dy/r)*0.015; if (r < (s.range||160)*0.6 && (performance.now() - (s.lastShot||0) > (s.fireRate*1000 || 1000))){ ally.hp = Math.min(ally.maxHp, ally.hp + Math.abs(s.damage || -20)); s.lastShot = performance.now(); } } else { s.vx += rand(0.0006); s.vy += rand(0.0006); }

      }

      break;

     case 'kamikaze':

      if (s.target){ const dx=s.target.x - s.x, dy=s.target.y - s.y, r=Math.hypot(dx,dy)+0.001; s.vx += (dx/r)*0.05; s.vy += (dy/r)*0.05; } else { s.vx += rand(0.01); s.vy += rand(0.01); }

      break;

     default:

      s.vx += rand(0.0005); s.vy += rand(0.0005);

    }

   }


   // firing (non-ram)

   if (s.weapon !== 'ram'){

    const now = performance.now();

    if (s.target && s.weapon !== 'repair' && (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 = 0.4;

      const tx = s.target.x + (s.target.vx || 0) * lead;

      const ty = s.target.y + (s.target.vy || 0) * lead;

      const dirx = tx - s.x, diry = ty - s.y; const magd = Math.hypot(dirx,diry)+0.001;

      const speed = s.weapon === 'laser' ? 7.5 : 6.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, s.damage || 18);

      s.lastShot = now;

     }

    }

   }


   // mothership spawn

   if (s.subtype === 'mothership'){

    s.spawnTimer = s.spawnTimer || 0; s.spawnTimer += dt;

    const spawnInterval = s.spawnRate || 3.0;

    if (s.spawnTimer > spawnInterval){

     s.spawnTimer = 0;

     const droneTypes = s.archetype === 'aggressive' ? ['brawler','fighter'] : (s.archetype === 'carrier' ? ['fighter','support'] : ['fighter']);

     const choice = droneTypes[Math.floor(Math.random()*droneTypes.length)];

     spawnActions[choice] && spawnActions[choice](s.x + rand(30), s.y + rand(30), s.side);

    }

   }


   s.x += (s.vx || 0) * dt * 60; s.y += (s.vy || 0) * dt * 60;

   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;

   if (s.hp <= 0) destroyShip(s);

  }


  // projectiles

  for(let i=projs.length-1;i>=0;i--){

   const p = projs[i];

   let ax=0, ay=0;

   for(const c of celestials){

    const dx=c.x - p.x, dy = c.y - p.y; const r2 = dx*dx + dy*dy + 64; const inv = 1 / Math.sqrt(r2); const f = CONFIG.GRAVITY_BASE * c.mass / r2;

    ax += f * dx * inv; ay += f * dy * inv;

   }

   p.vx += ax * dt * 60; p.vy += ay * dt * 60;

   p.x += (p.vx || 0) * dt * 60; p.y += (p.vy || 0) * dt * 60;

   p.life -= dt;

   if (p.life <= 0){ const idx = entities.indexOf(p); if (idx>=0) removeEntityByIndex(idx); continue; }


   const possible = spatialQueryRegion(p.x, p.y, 80);

   for(const hit of possible){

    if (!hit || hit.kind !== 'ship') continue;

    if (hit.side === p.ownerSide) continue;

    const rr = (hit.size || 8) + (p.size || 2);

    const 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, '#fff');

     const idx = entities.indexOf(p); if (idx >= 0) removeEntityByIndex(idx);

     if (hit.hp <= 0) destroyShip(hit);

     break;

    }

   }

  }


  // ship collisions (neighbors)

  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; const r = (s.size||8) + (b.size||8);

    if (dx*dx + dy*dy < r*r){

     const relx = b.vx - s.vx, rely = b.vy - s.vy;

     const impact = Math.min(500, Math.hypot(relx,rely) * ((s.mass||1) + (b.mass||1)) * 0.01);

     s.hp -= impact * 0.02; b.hp -= impact * 0.02;

     const mag = Math.hypot(dx,dy) || 1; const 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);

    }

   }

  }


  // fx lifecycle

  for(let i = entities.length - 1; i >= 0; i--){

   const e = entities[i];

   if (e.kind === 'fx'){ e.t = e.t || 0; e.t += dt; if (e.t > e.life) removeEntityByIndex(i); }

  }

 }


 function chooseTarget(source){

  const candidates = entities.filter(e => e.kind === 'ship' && e.side !== source.side && e !== source);

  if (!candidates.length) return null;

  candidates.sort((a,b) => {

   const da = Math.hypot(a.x - source.x, a.y - source.y), db = Math.hypot(b.x - source.x, b.y - source.y);

   const sa = da * (a.archetype === 'sniper' ? 1.3 : 1) - (a.hp || 0) * 0.0005;

   const sb = db * (b.archetype === 'sniper' ? 1.3 : 1) - (b.hp || 0) * 0.0005;

   return sa - sb;

  });

  return candidates[0];

 }


 // ---------- DRAW ----------

 function draw(){

  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(); const y = (i*221.3) % H(); ctx.fillRect(x,y,1,1); }


  ctx.save();

  if (followId){ const f = entities.find(e=>e.id===followId); if (f){ camera.targetX = W()/2 - f.x; camera.targetY = H()/2 - f.y; } }

  camera.x += (camera.targetX - camera.x) * camera.lerp; camera.y += (camera.targetY - camera.y) * camera.lerp;

  ctx.translate(camera.x, camera.y);


  // draw celestials

  for(const e of entities){

   if (e.kind === 'celestial'){

    const r = Math.max(2, e.size);

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

    ctx.beginPath(); ctx.fillStyle = e.color || '#88c'; ctx.arc(e.x,e.y,r,0,TAU); ctx.fill();

    ctx.lineWidth = 1; ctx.strokeStyle = 'rgba(255,255,255,0.04)'; ctx.stroke();

   }

  }


  // draw ships/structures

  for(const s of entities){

   if (s.kind === 'ship' || s.kind === 'structure'){

    ctx.save(); ctx.translate(s.x, s.y);

    const ang = Math.atan2(s.vy||0, s.vx||0); ctx.rotate(ang + Math.PI/2);

    const col = (s.side === TEAL) ? '#6ee7b7' : ((s.archetype === 'brawler') ? '#ff9b6b' : '#ff6b6b');

    ctx.beginPath(); ctx.moveTo(0, -s.size); ctx.lineTo(-s.size*0.6, s.size); ctx.lineTo(s.size*0.6, s.size); ctx.closePath();

    ctx.fillStyle = col; ctx.fill(); ctx.lineWidth = 1; ctx.strokeStyle = 'rgba(255,255,255,0.06)'; ctx.stroke();

    if (s.hp < s.maxHp){ const hW = s.size * 2; ctx.fillStyle = 'rgba(0,0,0,0.6)'; ctx.fillRect(-hW/2, s.size+6, hW, 5); ctx.fillStyle = 'rgba(0,255,100,0.9)'; ctx.fillRect(-hW/2, s.size+6, hW * Math.max(0, (s.hp/s.maxHp)), 5); }

    ctx.restore();

   }

  }


  // projectiles

  for(const p of entities){ if (p.kind === 'proj'){ ctx.beginPath(); ctx.fillStyle = p.subtype === 'laser' ? 'rgba(160,220,255,0.95)' : 'rgba(255,220,150,0.95)'; ctx.arc(p.x,p.y,p.size||2,0,TAU); ctx.fill(); } }


  // fx

  for(const fx of entities){ if (fx.kind === 'fx'){ if (fx.subtype === 'bigExpl'){ const s = (fx.t/fx.life)||0; const r = (s*36 + 10); ctx.beginPath(); ctx.fillStyle = `rgba(255,150,50,${1-s})`; ctx.arc(fx.x,fx.y,r,0,TAU); ctx.fill(); } else if (fx.subtype === 'hit'){ const s = (fx.t/fx.life)||0; ctx.beginPath(); ctx.fillStyle = `rgba(255,255,255,${1-s})`; ctx.arc(fx.x,fx.y,3 + s*8, 0, TAU); ctx.fill(); } else if (fx.subtype === 'spark'){ ctx.beginPath(); ctx.fillStyle = 'rgba(255,200,120,0.9)'; ctx.arc(fx.x,fx.y,2,0,TAU); ctx.fill(); } } }


  // selection indicator

  if (selectedId){ const s = entities.find(e=>e.id===selectedId); if (s){ ctx.save(); ctx.strokeStyle = '#fff'; ctx.lineWidth = 1; ctx.beginPath(); ctx.arc(s.x, s.y, (s.size||8)+6, 0, TAU); ctx.stroke(); ctx.restore(); } }


  ctx.restore();


  document.getElementById('debug').textContent = `Entities: ${entities.length} — Projectiles: ${projCount} — FPS: ${fps}`;

 }


 // ---------- Loop ----------

 function loop(time){

  const rawDt = (time - lastTime) / 1000;

  const dt = Math.min(rawDt, CONFIG.DT_MAX);

  lastTime = time;

  if (running) step(dt);

  draw();


  frameCount++;

  if (time > lastFpsTime + CONFIG.FPS_INTERVAL){ fps = Math.round(frameCount / ((time - lastFpsTime)/1000)); lastFpsTime = time; frameCount = 0; }


  requestAnimationFrame(loop);

 }


 // ---------- Input / Pie Fix ----------

 resizeCanvas();


 // robust screen->world using canvas rect and camera offset

 function screenToWorldFromClient(clientX, clientY){

  const rect = canvas.getBoundingClientRect();

  const localX = clientX - rect.left;

  const localY = clientY - rect.top;

  return { x: localX - camera.x, y: localY - camera.y };

 }


 // Pie menu with fixed open position & STOP propagation on slice mousedown

 class PieMenu {

  constructor(items){ this.items = items; this.el = null; this.openClientX = 0; this.openClientY = 0; }

  show(clientX, clientY){

   this.hide();

   this.openClientX = clientX; this.openClientY = clientY;

   const el = document.createElement('div'); el.className = 'pie-container';

   el.style.left = `${clientX}px`; el.style.top = `${clientY}px`; el.style.transform = 'translate(-50%,-50%)';

   const N = this.items.length; const angleStep = 360 / N;

   for(let i=0;i<N;i++){

    const item = this.items[i];

    const slice = document.createElement('div'); slice.className = 'pie-slice';

    const rot = i * angleStep - 90 - angleStep/2;

    slice.style.transform = `rotate(${rot}deg) translate(72px) rotate(${-rot}deg)`;

    slice.style.background = hexToRGBA(item.color || '#07202a', 0.72);

    slice.style.border = '1px solid rgba(255,255,255,0.04)';

    const label = document.createElement('div'); label.className = 'label'; label.textContent = item.label;

    slice.appendChild(label);


    // IMPORTANT: stop propagation at mousedown/touchstart so global onDocDown doesn't remove pie before we act

    const doAction = (ev) => {

     try { ev.stopPropagation(); ev.preventDefault && ev.preventDefault(); } catch(e){}

     const world = screenToWorldFromClient(this.openClientX, this.openClientY);

     if (item.action) item.action(world.x, world.y);

     this.hide();

    };

    slice.addEventListener('mousedown', (ev) => { doAction(ev); }); // immediate

    slice.addEventListener('touchstart', (ev) => { doAction(ev); }, {passive:false});

    // click fallback (if mousedown didn't run for some reason)

    slice.addEventListener('click', (ev) => { ev.stopPropagation(); const world = screenToWorldFromClient(this.openClientX, this.openClientY); if (item.action) item.action(world.x, world.y); this.hide(); });


    el.appendChild(slice);

   }

   document.body.appendChild(el);

   this.el = el;

   // add a small delay before registering the global close to avoid catching the initial right-click

   setTimeout(()=> window.addEventListener('mousedown', onDocDown), 10);

  }

  hide(){ if (this.el){ this.el.remove(); this.el = null; window.removeEventListener('mousedown', onDocDown); } }

 }


 function onDocDown(){ playerPie.hide(); enemyPie.hide(); astroPie.hide(); }


 const playerPie = new PieMenu([

  { label:'Player Fighter', color:'#6ee7b7', action:(x,y) => spawnActions.fighter(x,y,TEAL) },

  { label:'Player Brawler', color:'#6ee7b7', action:(x,y) => spawnActions.brawler(x,y,TEAL) },

  { label:'Player Sniper', color:'#6ee7b7', action:(x,y) => spawnActions.sniper(x,y,TEAL) },

  { label:'Shield Generator', color:'#a78bfa', action:(x,y) => spawnActions.shieldGen(x,y,TEAL) },

  { label:'Player Mothership', color:'#6ee7b7', action:(x,y) => spawnActions.playerMothership(x,y) }

 ]);


 const enemyPie = new PieMenu([

  { label:'Enemy Fighter', color:'#ff6b6b', action:(x,y) => spawnActions.fighter(x,y,RED) },

  { label:'Enemy Brawler', color:'#ff6b6b', action:(x,y) => spawnActions.brawler(x,y,RED) },

  { label:'Enemy Sniper', color:'#ff6b6b', action:(x,y) => spawnActions.sniper(x,y,RED) },

  { label:'Enemy Mothership', color:'#ff6b6b', action:(x,y) => spawnActions.enemyMothership(x,y) },

  { label:'Chaos Wave', color:'#f8a', action:(x,y) => { for(let i=0;i<16;i++) spawnActions.fighter(x+rand(220), y+rand(220), RED); } }

 ]);


 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) },

  { label:'Place Shield Gen', color:'#a78bfa', action:(x,y) => spawnActions.shieldGen(x,y) },

  { label:'Place Capital Cannon', color:'#ff9b6b', action:(x,y) => spawnActions.capitalCannon(x,y) }

 ]);


 // ---------- Mouse events ----------

 canvas.addEventListener('mousemove', (e) => {

  const r = canvas.getBoundingClientRect();

  mouse.screenX = e.clientX - r.left; mouse.screenY = e.clientY - r.top;

  mouse.x = mouse.screenX; mouse.y = mouse.screenY;

 });


 canvas.addEventListener('mousedown', (e) => {

  const r = canvas.getBoundingClientRect(); const sx = e.clientX - r.left, sy = e.clientY - r.top;

  mouse.down = true; mouse.btn = e.button;

  if (e.button === 2){

   // Right-click: choose pie by modifiers

   if (e.ctrlKey || e.metaKey){ enemyPie.show(e.clientX, e.clientY); playerPie.hide(); astroPie.hide(); }

   else if (e.altKey){ astroPie.show(e.clientX, e.clientY); playerPie.hide(); enemyPie.hide(); }

   else { playerPie.show(e.clientX, e.clientY); enemyPie.hide(); astroPie.hide(); }

   return;

  }

  // left click: select entity

  const world = screenToWorldFromClient(e.clientX, e.clientY);

  const nearby = spatialQueryRegion(world.x, world.y, 60);

  const clicked = nearby.slice().reverse().find(en => {

   const rr = (en.kind === 'ship' ? en.size : Math.max(4, en.size || 6));

   const dx = en.x - world.x, dy = en.y - world.y;

   return dx*dx + dy*dy <= rr*rr;

  });

  selectedId = clicked ? clicked.id : null;

  playerPie.hide(); enemyPie.hide(); astroPie.hide();

 });


 canvas.addEventListener('mouseup', (e) => { mouse.down = false; mouse.btn = -1; });

 canvas.addEventListener('contextmenu', (e) => e.preventDefault());


 // ---------- Mega panel ----------

 document.getElementById('mega').addEventListener('click', (ev) => {

  if (ev.target.classList.contains('spawn-btn')){

   const action = ev.target.dataset.action;

   if (spawnActions[action]){

    const cx = (W()/2 - camera.x), cy = (H()/2 - camera.y);

    spawnActions[action](cx + rand(120), cy + rand(120));

   }

  }

 });


 // ---------- Keyboard ----------

 const keys = new Set();

 document.addEventListener('keydown', (e) => {

  const k = (e.key || '').toLowerCase();

  if (k === ' ') keys.add('space'); else keys.add(k);

  if (k === 'p') running = !running;

  if (k === 'm') document.getElementById('btnMegaToggle').click();

  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; } }

 });

 document.addEventListener('keyup', (e) => { const k = (e.key || '').toLowerCase(); if (k === ' ') keys.delete('space'); else keys.delete(k); });


 // player control interval

 setInterval(() => {

  if (!selectedId) return;

  const s = entities.find(e => e.id === selectedId && e.kind === 'ship');

  if (!s) return;

  if (s.playerControl){

   if (keys.has('a')) s.vx -= 0.03;

   if (keys.has('d')) s.vx += 0.03;

   if (keys.has('w')) s.vy -= 0.03;

   if (keys.has('s')) s.vy += 0.03;

   if (keys.has('space')){

    const now = performance.now();

    const interval = (s.fireRate || 0.22) * 1000;

    if (!s.lastShot || (now - s.lastShot) > interval){

     // spawn projectile towards cursor world position

     const rect = canvas.getBoundingClientRect();

     const clientX = rect.left + mouse.screenX, clientY = rect.top + mouse.screenY;

     const target = screenToWorldFromClient(clientX, clientY);

     const dir = Math.atan2(target.y - s.y, target.x - s.x);

     const speed = s.weapon === 'laser' ? 8 : 7;

     spawnProjectile(s.x + Math.cos(dir)*s.size, s.y + Math.sin(dir)*s.size, Math.cos(dir)*speed + (s.vx||0), Math.sin(dir)*speed + (s.vy||0), s, s.weapon || 'bullet', 2.0, s.damage || 22);

     s.lastShot = now;

    }

   }

  }

 }, 40);


 // ---------- HUD buttons ----------

 document.getElementById('btnPause').addEventListener('click', () => running = !running);

 document.getElementById('btnCenter').addEventListener('click', () => { followId = null; camera.targetX = 0; camera.targetY = 0; });

 document.getElementById('btnClear').addEventListener('click', () => initWorld());

 document.getElementById('btnMegaToggle').addEventListener('click', () => {

  const mega = document.getElementById('mega'); const isHidden = mega.style.display === 'none' || mega.style.display === '';

  mega.style.display = isHidden ? 'block' : 'none';

 });


 // ---------- Helpers ----------

 function assignShipDefaults(){

  for (const s of entities.filter(e=>e.kind==='ship')){

   if (!s.maxHp) s.maxHp = s.hp || 100; if (!s.hp) s.hp = s.maxHp;

   if (!s.weapon){

    if (s.archetype === 'sniper'){ s.weapon='laser'; s.fireRate=1.2; s.range=520; s.damage=45; s.maxHp=90; }

    else if (s.archetype === 'brawler'){ s.weapon='ram'; s.fireRate=0.2; s.range=28; s.damage=80; s.maxHp=260; }

    else if (s.archetype === 'support'){ s.weapon='repair'; s.fireRate=1.0; s.range=150; s.damage=-18; s.maxHp=150; }

    else if (s.archetype === 'kamikaze'){ s.weapon='ram'; s.fireRate=0; s.range=18; s.damage=200; s.maxHp=80; }

    else { s.weapon='bullet'; s.fireRate = s.fireRate || 0.45; s.range = s.range || 260; s.damage = s.damage || 22; s.maxHp = s.maxHp || 120; }

   }

  }

 }

 setInterval(assignShipDefaults, 700);


 function prune(){

  for (let i = entities.length - 1; i >= 0; i--){

   const e = entities[i];

   if (e.kind === 'proj'){ if (e.x < -2000 || e.x > W()+2000 || e.y < -2000 || e.y > H()+2000) removeEntityByIndex(i); }


// READ THE REST ON THE PASTEBIN LINc!:!;!


LINE COUNT: 664


Tags:

Comments

Comments ain't a thing here.