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:
30 / 50
Exp Rank:
> 100,000
Vote Power:
2.27 votes
Rank:
Civilian
Global Rank:
> 100,000
Blams:
2
Saves:
9
B/P Bonus:
0%
Whistle:
Normal
Supporter:
1m 13d

SPACE V3

Posted by PyroflamePC - 7 hours ago


SPACE #3 — Space Ships, Mothership AI & Archetypes: https://pastebin.com/GDAS0KL8


Source: 


<!doctype html>

<html>

<head>

<meta charset="utf-8"/>

<title>Space — Version #3: Motherships, Archetypes & Combat</title>

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

<style>

 html,body{height:100%;margin:0;background:#04050a;color:#cfe;overflow:hidden;font-family:Inter,system-ui, -apple-system, "Segoe UI", Roboto;}

 canvas{display:block;width:100vw;height:100vh;}

 #hud{position:fixed;left:10px;top:10px;z-index:30;background:rgba(0,0,0,0.45);padding:8px;border-radius:8px;border:1px solid rgba(255,255,255,0.03)}

 #hud button{margin:4px;padding:6px 8px;border-radius:6px;border:0;background:#0b1220;color:#9ff;cursor:pointer}

 .note{font-size:12px;color:#9aa;margin-top:6px}

 /* pie menu */

 .pie{position:fixed;pointer-events:none;z-index:35}

 .pie .slice{position:absolute;width:120px;height:120px;margin:-60px;border-radius:50%;transform-origin:60px 60px;display:flex;align-items:center;justify-content:center;pointer-events:auto;color:#012;font-weight:700; cursor:pointer;}

 .label{pointer-events:none;font-size:12px;text-align:center;color:#ccf}

</style>

</head>

<body>

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

<div id="hud">

 <div style="font-weight:700;color:#7ef">SPACE v3 — Motherships & Archetypes</div>

 <div style="margin-top:6px">

  <button id="spawnEnemy">Spawn Enemy Mothership</button>

  <button id="center">Center</button>

  <button id="clear">Clear</button>

  <button id="spawnChaos">Chaos Wave</button>

 </div>

 <div class="note">Right-click — pie spawn. Left-click — select. WASD — control selected ship (if player).</div>

</div>


<script>

/* ---------- Setup ---------- */

const canvas = document.getElementById('c'), ctx = canvas.getContext('2d');

let W = canvas.width = innerWidth, H = canvas.height = innerHeight;

onresize = ()=>{ W = canvas.width = innerWidth; H = canvas.height = innerHeight; };


let running = true, followId = null;

const G = 0.0007;    // gravity base

const MAX_PROJECTILES = 400;

let entities = [];    // unified: ships, celestials, projectiles, effects

let nextId = 1;


/* ---------- Helpers ---------- */

function rand(range){ return (Math.random()-0.5)*2*range; }

function dist2(a,b){ const dx=a.x-b.x, dy=a.y-b.y; return dx*dx+dy*dy; }

function mag(vx,vy){ return Math.hypot(vx,vy); }

function hexToRGBA(hex, a=1){ 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})`; }


/* ---------- Entity creation ---------- */

function mkMothership(x,y,side='enemy',player=false, archetype='carrier'){

 // mothership: spawns drones, heavy hull, basic weapons

 const ms = { id: nextId++, kind:'ship', subtype:'mothership', archetype, side, playerControl:player, x,y, vx:rand(0.5), vy:rand(0.5),

        angle:0, mass:5000, size:24, hp:1200, maxHp:1200, spawnTimer:0, spawnRate:2.5, target:null };

 entities.push(ms); return ms;

}

function mkShip(x,y,type='fighter',side='enemy',parent=null,player=false, arche='fighter'){

 // ship archetypes: fighter, brawler, sniper, support, kamikaze

 let archetype = arche;

 const def = {

  fighter: { mass:160, size:9, hp:120, speed:2.0, fireRate:0.5, weapon:'bullet', range:260, damage:20, aggression:0.6 },

  brawler: { mass:300, size:12, hp:260, speed:1.4, fireRate:0.25, weapon:'ram', range:28, damage:80, aggression:0.9 },

  sniper: { mass:140, size:8, hp:90, speed:1.6, fireRate:1.2, weapon:'laser', range:520, damage:45, aggression:0.4 },

  support:{ mass:200, size:10, hp:150, speed:1.5, fireRate:1.0, weapon:'repair', range:160, damage:-18, aggression:0.2 },

  kamikaze:{ mass:120, size:8, hp:80, speed:2.6, fireRate:0.0, weapon:'ram', range:18, damage:200, aggression:1.0 }

 }[archetype] || def.fighter;

 const s = Object.assign({

  id: nextId++, kind:'ship', subtype:type, archetype, side, parent, playerControl:player,

  x,y, vx:rand(0.6), vy:rand(0.6), angle:Math.random()*Math.PI*2,

  lastShot:0, target:null

 }, def);

 entities.push(s); return s;

}

function mkCelestial(x,y,mass,subtype='planet',fixed=false,color='#ccc'){

 const c = { id: nextId++, kind:'celestial', subtype, x,y, vx:0, vy:0, mass, fixed, color, size: Math.cbrt(mass)*0.7 };

 entities.push(c); return c;

}

function mkProjectile(x,y,vx,vy,owner,weapon='bullet',life=2){

 if(entities.filter(e=>e.kind==='proj').length > MAX_PROJECTILES) return null;

 const p = { id: nextId++, kind:'proj', x,y, vx,vy, owner, weapon, life, damage: weapon==='laser'?30:18, size: weapon==='laser'?2:2, created: performance.now() };

 entities.push(p); return p;

}

function mkEffect(x,y,type='expl',life=0.5){

 const e = { id: nextId++, kind:'fx', x,y, type, life, t:0 };

 entities.push(e); return e;

}


/* ---------- Initial demo ---------- */

mkCelestial(W*0.5,H*0.5, 300000, 'star', true, '#ffd166');

mkMothership(W*0.5+220, H*0.5, 'enemy', false, 'carrier');

mkMothership(W*0.5-300, H*0.5, 'enemy', false, 'aggressive');

for(let i=0;i<8;i++) mkShip(W*0.5+rand(300), H*0.5+rand(300), 'fighter','enemy', null, false, ['fighter','brawler','sniper','kamikaze'][Math.floor(Math.random()*4)] );


/* ---------- Combat & AI ---------- */

function chooseTarget(source){

 // prefer opposite side ships within some range, else nearest anything else

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

 if(enemies.length===0) return null;

 // prioritize by proximity and archetype: snipers dodge close, brawlers prefer close

 enemies.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 scoreA = da * (a.archetype==='sniper'?1.4:1) - (a.hp || 0)*0.001;

  const scoreB = db * (b.archetype==='sniper'?1.4:1) - (b.hp || 0)*0.001;

  return scoreA - scoreB;

 });

 return enemies[0];

}


/* ---------- Physics step ---------- */

function step(dt){

 // gravity from celestials affects ships and projectiles slightly

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

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

 // ships acceleration from celestials (pairwise)

 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 = G * c.mass * s.mass / r2;

   ax += f * dx * inv / s.mass; ay += f * dy * inv / s.mass;

  }

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

 }


 // Ship AI + actions

 for(const s of ships){

  // reduce target if dead

  if(s.target && (!entities.find(e=>e.id===s.target.id && e.kind==='ship' && e.hp>0))) s.target=null;


  // choose target occasionally

  if(!s.target && Math.random() < 0.02) s.target = chooseTarget(s);


  // archetype behaviors

  if(!s.playerControl){

   // aggression factor influences picking fights

   if(s.archetype === 'fighter'){

    // attempt to intercept: steer towards target

    if(s.target){

     const dx=s.target.x-s.x, dy=s.target.y-s.y, r=Math.hypot(dx,dy)+0.01;

     const desiredVel = s.speed;

     s.vx += (dx/r) * 0.02; s.vy += (dy/r) * 0.02;

    } else {

     // patrol

     s.vx += Math.cos((s.id + performance.now()*0.0005))*0.001;

     s.vy += Math.sin((s.id + performance.now()*0.0004))*0.001;

    }

   } else if(s.archetype === 'brawler'){

    if(s.target){

     const dx=s.target.x-s.x, dy=s.target.y-s.y, r=Math.hypot(dx,dy)+0.01;

     // charge in

     s.vx += (dx/r) * 0.03; s.vy += (dy/r) * 0.03;

    } else {

     s.vx += Math.cos(s.aiTimer || 0)*0.001; s.vy += Math.sin((s.aiTimer||0) + s.id*0.1)*0.001;

    }

   } else if(s.archetype === 'sniper'){

    if(s.target){

     const dx=s.target.x-s.x, dy=s.target.y-s.y, r=Math.hypot(dx,dy)+0.01;

     // maintain distance: back up if too close

     if(r < s.range * 0.6){ s.vx -= (dx/r)*0.02; s.vy -= (dy/r)*0.02; }

     else { s.vx += (Math.cos(s.id + performance.now()*0.0004))*0.0006; s.vy += (Math.sin(performance.now()*0.0006 + s.id))*0.0006; }

    } else { s.vx *= 0.999; s.vy *= 0.999; }

   } else if(s.archetype === 'support'){

    // find allied damaged ship to approach and repair (negative damage)

    let 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.01;

     s.vx += dx/r*0.015; s.vy += dy/r*0.015;

     if(r < s.range*0.6 && (performance.now() - (s.lastShot||0) > s.fireRate*1000)){

      // "repair" by firing negative-damage projectile (instant apply)

      ally.hp = Math.min(ally.maxHp, ally.hp + Math.abs(s.damage));

      s.lastShot = performance.now();

     }

    } else {

     s.vx += Math.cos(s.id*0.3 + performance.now()*0.0003)*0.0006;

    }

   } else if(s.archetype === 'kamikaze'){

    if(s.target){

     const dx=s.target.x-s.x, dy=s.target.y-s.y, r=Math.hypot(dx,dy)+0.01;

     s.vx += (dx/r)*0.05; s.vy += (dy/r)*0.05;

    } else { s.vx += rand(0.01); s.vy += rand(0.01); }

   }

  } else {

   // player-controlled ships: thrust managed elsewhere (keyboard)

  }


  // shooting logic (non-ram weapons)

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

   const now = performance.now();

   s.lastShot = s.lastShot || 0;

   if(s.target && s.weapon !== 'repair' && (now - s.lastShot) > (s.fireRate*1000)){

    // if in range, fire projectile

    const dx = s.target.x - s.x, dy = s.target.y - s.y;

    const r = Math.hypot(dx,dy);

    if(r < s.range){

     // fire projectile towards predicted target (simple lead)

     const lead = 0.5; // crude

     const tx = s.target.x + s.target.vx * lead, ty = s.target.y + s.target.vy * 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;

     mkProjectile(s.x + (dirx/magd)*s.size, s.y + (diry/magd)*s.size, (dirx/magd)*speed + s.vx, (diry/magd)*speed + s.vy, s, s.weapon);

     s.lastShot = now;

    }

   }

  }


  // spawn drones if mothership

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

   s.spawnTimer += dt;

   const spawnInterval = (s.archetype === 'carrier')? s.spawnRate : 6.0;

   if(s.spawnTimer > spawnInterval){

    s.spawnTimer = 0;

    // spawn type choice depends on archetype

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

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

    mkShip(s.x + rand(30), s.y + rand(30), 'drone', s.side, s, false, choice);

   }

  }


  // integrate movement

  s.x += s.vx * dt * 60; s.y += s.vy * dt * 60;


  // keep within map wrap

  if(s.x < -300) s.x = W + 300;

  if(s.x > W + 300) s.x = -300;

  if(s.y < -300) s.y = H + 300;

  if(s.y > H + 300) s.y = -300;

 }


 // projectiles: move and decay

 for(const p of entities.filter(e=>e.kind==='proj')){

  // gravity affect tiny bit from celestials

  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 = G * 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 * dt * 60; p.y += p.vy * dt * 60;

  p.life -= dt;

  if(p.life <= 0) { mkEffect(p.x,p.y,'small',0.15); removeById(p.id); continue; }

  // collision with ships

  const hit = entities.find(e=>e.kind==='ship' && e.side !== p.owner.side && ((e.x-p.x)**2 + (e.y-p.y)**2) < (e.size+4)**2);

  if(hit){

   // apply damage

   hit.hp -= p.damage;

   mkEffect(p.x,p.y,'hit',0.18);

   removeById(p.id);

   if(hit.hp <= 0){ shipDestroyed(hit); }

  }

 }


 // ship-ship collisions (ram/hull damage)

 for(const a of ships){

  for(const b of ships){

   if(a.id >= b.id) continue;

   const r = a.size + b.size;

   const dx = b.x - a.x, dy = b.y - a.y;

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

    // collision: apply bump impulse and damage if aggressive/hull-types

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

    const impact = Math.min(200, Math.hypot(relx,rely) * (a.mass+b.mass)*0.02);

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

    // simple separation impulse

    const nx = dx/Math.hypot(dx,dy || 1), ny = dy/Math.hypot(dx,dy || 1);

    a.vx -= nx*0.5; a.vy -= ny*0.5; b.vx += nx*0.5; b.vy += ny*0.5;

    mkEffect((a.x+b.x)/2,(a.y+b.y)/2,'spark',0.16);

    if(a.hp <= 0) shipDestroyed(a);

    if(b.hp <= 0) shipDestroyed(b);

   }

  }

 }


 // effects lifecycle

 for(const fx of entities.filter(e=>e.kind==='fx')){ fx.t += dt; if(fx.t > fx.life) removeById(fx.id); }


 // cleanup celestials (none for now)

}


/* ---------- Remove & destruction ---------- */

function removeById(id){

 const idx = entities.findIndex(e=>e.id===id); if(idx>=0) entities.splice(idx,1);

}

function shipDestroyed(s){

 mkEffect(s.x,s.y,'expl',0.8);

 // spawn debris / small projectiles

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

  mkProjectile(s.x, s.y, rand(6), rand(6), s, 'shrap', 0.8);

 }

 removeById(s.id);

}


/* ---------- Draw ---------- */

function draw(){

 ctx.fillStyle = 'rgba(1,2,6,0.6)'; ctx.fillRect(0,0,W,H);

 // starfield

 ctx.fillStyle = 'rgba(255,255,255,0.03)';

 for(let i=0;i<140;i++){ const x=(i*71.1)%W, y=(i*127.3)%H; ctx.fillRect(x,y,1,1); }


 ctx.save();

 if(followId){

  const f = entities.find(e=>e.id===followId); if(f) ctx.translate(W/2 - f.x, H/2 - f.y);

 }


 // celestials

 for(const c of entities.filter(e=>e.kind==='celestial')){

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

  if(c.subtype === 'star'){ const g = ctx.createRadialGradient(c.x,c.y,0,c.x,c.y,r*7); g.addColorStop(0,hexToRGBA(c.color,0.5)); g.addColorStop(1,hexToRGBA(c.color,0)); ctx.fillStyle=g; ctx.beginPath(); ctx.arc(c.x,c.y,r*7,0,Math.PI*2); ctx.fill(); }

  ctx.beginPath(); ctx.fillStyle = c.color; ctx.arc(c.x,c.y,r,0,Math.PI*2); ctx.fill(); ctx.lineWidth=1; ctx.strokeStyle='rgba(255,255,255,0.04)'; ctx.stroke();

 }


 // ships

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

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

  const ang = Math.atan2(s.vy,s.vx);

  ctx.rotate(ang + Math.PI/2);

  // hull color by side

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

  // draw ship triangle (size varies)

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

  // health bar

  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||0)/s.maxHp), 5);

  ctx.restore();

 }


 // projectiles

 for(const p of entities.filter(e=>e.kind==='proj')){

  ctx.beginPath(); ctx.fillStyle = p.weapon === 'laser' ? 'rgba(160,220,255,0.95)' : 'rgba(255,220,150,0.95)';

  ctx.arc(p.x,p.y, p.size, 0, Math.PI*2); ctx.fill();

 }


 // effects

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

  if(fx.type==='expl'){

   const s = (fx.t/fx.life); const r= (s*30 + 10);

   ctx.beginPath(); ctx.fillStyle = `rgba(255,150,50,${1-s})`; ctx.arc(fx.x,fx.y,r,0,Math.PI*2); ctx.fill();

  }

 }


 ctx.restore();


 // HUD bottom-left

 ctx.fillStyle = 'rgba(255,255,255,0.06)'; ctx.font='12px system-ui';

 ctx.fillText(`Entities: ${entities.length}  Follow: ${followId||'none'}  Running: ${running}`, 12, H-10);

}


/* ---------- Loop ---------- */

let last = performance.now();

function loop(now){

 const dt = Math.min(0.033, (now-last)/1000); last = now;

 if(running) step(dt);

 draw();

 requestAnimationFrame(loop);

}

requestAnimationFrame(loop);


/* ---------- Selection / Pie Menu / Input ---------- */

let mouse = { x:0,y:0,down:false,btn:0 }, pie=null, selectedId=null, dragging=null, dragOffset={x:0,y:0}, spawnDrag=null;

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

 const r = canvas.getBoundingClientRect(); mouse.x = e.clientX - r.left; mouse.y = e.clientY - r.top;

 if(dragging){ const world = screenToWorld(mouse.x, mouse.y); dragging.x = world.x + dragOffset.x; dragging.y = world.y + dragOffset.y; dragging.vx=dragging.vy=0; }

 if(spawnDrag) spawnDrag.current = { x:mouse.x, y:mouse.y };

});

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

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

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

 if(e.button === 2){ openPie(sx,sy); return; }

 const world = screenToWorld(sx,sy);

 const hit = entities.slice().reverse().find(ent=>{ const rr = (ent.kind==='ship'? ent.size : Math.max(4, ent.size)); const dx = ent.x - world.x, dy = ent.y - world.y; return dx*dx + dy*dy <= rr*rr; });

 if(hit){

  selectedId = hit.id;

  if(hit.kind === 'celestial'){ dragging = hit; dragOffset.x = hit.x - world.x; dragOffset.y = hit.y - world.y; }

  if(hit.kind === 'ship'){ if(hit.side === 'player') hit.playerControl = true; }

  closePie();

 } else selectedId = null;

});

canvas.addEventListener('mouseup', e=>{

 mouse.down=false; dragging=null;

 if(spawnDrag){ const pd = spawnDrag; spawnDrag=null; const w=screenToWorld(pd.start.x,pd.start.y); const v = {x:(pd.start.x-pd.current.x)*0.02, y:(pd.start.y-pd.current.y)*0.02}; const s = mkShip(w.x,w.y,'fighter','player',null,true,'fighter'); s.vx=v.x; s.vy=v.y; selectedId = s.id; closePie(); }

});

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


function screenToWorld(sx,sy){ if(followId){ const f = entities.find(e=>e.id===followId); if(f) return {x: sx - W/2 + f.x, y: sy - H/2 + f.y}; } return {x:sx,y:sy}; }


/* Pie menu */

function openPie(sx,sy){

 closePie();

 pie = document.createElement('div'); pie.className='pie'; pie.style.left = sx+'px'; pie.style.top = sy+'px';

 const options = [

  {label:'Player Mothership', action:()=>{ const w=screenToWorld(sx,sy); mkMothership(w.x,w.y,'player',true,'carrier'); }},

  {label:'Player Fighter (drag)', actionStart:()=>{ spawnDrag = { start:{x:sx,y:sy}, current:{x:sx,y:sy} }; }},

  {label:'Enemy Mothership', action:()=>{ const w=screenToWorld(sx,sy); mkMothership(w.x,w.y,'enemy',false,['aggressive','carrier'][Math.floor(Math.random()*2)]); }},

  {label:'Place Star', action:()=>{ const w=screenToWorld(sx,sy); mkCelestial(w.x,w.y,220000,'star',true,'#ffd166'); }},

  {label:'Place Planet', action:()=>{ const w=screenToWorld(sx,sy); mkCelestial(w.x,w.y,14000,'planet',false,'#88c'); }},

  {label:'Place Asteroid Field', action:()=>{ const w=screenToWorld(sx,sy); for(let i=0;i<30;i++){ mkCelestial(w.x+rand(140), w.y+rand(140), 40+Math.random()*120,'asteroid', false, '#b7a'); } }},

  {label:'Chaos (wave)', action:()=>{ for(let i=0;i<20;i++){ mkShip(Math.random()*W, Math.random()*H,'fighter','enemy', null, false, ['fighter','brawler','sniper'][Math.floor(Math.random()*3)]); } }}

 ];

 const N = options.length;

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

  const s = document.createElement('div'); s.className='slice'; const angle = -Math.PI/2 + (i/N)*Math.PI*2;

  s.style.transform = `rotate(${angle}rad) translate(${80}px) rotate(${-angle}rad)`;

  s.style.background = 'linear-gradient(180deg, rgba(255,255,255,0.02), rgba(0,0,0,0.2))'; s.style.border = '1px solid rgba(255,255,255,0.04)';

  s.innerHTML = `<div class="label">${options[i].label}</div>`;

  s.onclick = (ev)=>{ ev.stopPropagation(); if(options[i].action) options[i].action(); closePie(); };

  s.onmousedown = (ev)=>{ ev.stopPropagation(); if(options[i].actionStart) options[i].actionStart(); };

  pie.appendChild(s);

 }

 document.body.appendChild(pie); setTimeout(()=>window.addEventListener('mousedown', onDocDown), 10);

}

function closePie(){ if(pie){ pie.remove(); pie=null; spawnDrag=null; window.removeEventListener('mousedown', onDocDown); } }

function onDocDown(){ closePie(); }


/* ---------- Keyboard controls (player ship) ---------- */

const keys = {};

window.addEventListener('keydown', e=>{ keys[e.key.toLowerCase()] = true; if(e.key==='f' || e.key==='F'){ cycleFollow(); } if(e.key==='p'||e.key==='P'){ running=!running; } if(e.key==='c'||e.key==='C'){ entities = []; nextId=1; } });

window.addEventListener('keyup', e=> keys[e.key.toLowerCase()] = false );


function cycleFollow(){ const ships = entities.filter(e=>e.kind==='ship'); if(ships.length===0){ followId = null; return; } let idx = ships.findIndex(s=>s.id===followId); idx = (idx+1)%ships.length; followId = ships[idx].id; }


// Apply player thrust if selected ship is playerControl

setInterval(()=>{

 if(!selectedId) return;

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

 if(!s) return;

 if(s.playerControl){

  // rotate with A/D, thrust with W, brake with S

  if(keys['a']) s.angle -= 0.12;

  if(keys['d']) s.angle += 0.12;

  if(keys['w']) { s.vx += Math.cos(s.angle)*0.06; s.vy += Math.sin(s.angle)*0.06; }

  if(keys['s']) { s.vx *= 0.98; s.vy *= 0.98; }

  // shoot with space

  if(keys[' ']) {

   const now = performance.now();

   if((now - (s.lastShot||0)) > (s.fireRate*1000 || 250)){

    const speed = 7;

    const vx = Math.cos(s.angle)*speed + (s.vx||0), vy = Math.sin(s.angle)*speed + (s.vy||0);

    mkProjectile(s.x + Math.cos(s.angle)*s.size, s.y + Math.sin(s.angle)*s.size, vx, vy, s, s.weapon || 'bullet', 2.2);

    s.lastShot = now;

   }

  }

 } else {

  // toggle control with T

  if(keys['t']){ s.playerControl = !s.playerControl; keys['t']=false; }

 }

}, 40);


/* ---------- Buttons ---------- */

document.getElementById('spawnEnemy').onclick = ()=> mkMothership(Math.random()*W, Math.random()*H, 'enemy', false, ['carrier','aggressive'][Math.floor(Math.random()*2)]);

document.getElementById('center').onclick = ()=> followId = null;

document.getElementById('clear').onclick = ()=> entities=[], nextId=1;

document.getElementById('spawnChaos').onclick = ()=>{ for(let i=0;i<40;i++) mkShip(Math.random()*W, Math.random()*H,'fighter','enemy',null,false,['fighter','brawler','sniper'][Math.floor(Math.random()*3)]); };


/* ---------- Effects / misc ---------- */

function mkShipDefaults(){

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

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

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

  // assign weapons from archetype if not present

  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=0.55; s.range=260; s.damage=20; s.maxHp=120; }

   s.hp = s.maxHp = s.maxHp || s.hp;

  }

 }

}

setInterval(mkShipDefaults, 700);


/* ---------- Helper: remove expired dead etc ---------- */

function prune(){

 // remove projectiles out of bounds long life handled via life var

 // nothing here now

}

setInterval(prune, 2000);


/* ---------- Small helper effects draw overlay (spawn preview) ---------- */

function renderSpawnPreview(){

 if(!spawnDrag) return;

 const s = spawnDrag;

 ctx.save();

 ctx.strokeStyle = 'rgba(180,255,200,0.95)'; ctx.lineWidth = 2;

 ctx.beginPath(); ctx.moveTo(s.start.x, s.start.y); ctx.lineTo(s.current.x, s.current.y); ctx.stroke();

 ctx.fillStyle = 'rgba(180,255,200,0.9)'; ctx.beginPath(); ctx.arc(s.start.x, s.start.y, 6,0,Math.PI*2); ctx.fill();

 ctx.restore();

}


/* ---------- Integrate draws with main draw to show preview ---------- */

const baseDraw = draw;

draw = function(){ baseDraw(); renderSpawnPreview(); };


/* ---------- Start message for guidance in console ---------- */

console.log('SPACE v3 ready — right-click to spawn, select ship and press WASD + Space to fire (if controlled).');


/* ---------- Done ---------- */

</script>

</body>

</html>


Tags:

Comments

Comments ain't a thing here.