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 5.C

Posted by PyroflamePC - 15 hours ago


Space V5.C --> https://pastebin.com/Rea8CA00


Source:


<!doctype html>

<html lang="en">

<head>

<meta charset="utf-8" />

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

<title>SPACE v5 - Complete Engine</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;transition:background-color 0.2s}

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

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


/* New CSS for the pie menus */

.pie-container{

position:fixed;

z-index:120;

user-select:none;

pointer-events:none;

transform:translate(-50%,-50%);

transition: transform .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);

transition:transform 0.1s ease-out;

transform-origin: center center;

}

.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.08) !important;}


#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;transition: all 0.2s;}

.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 - v5</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 Hash Grid ----------

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

}


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

// New spawn actions for the secret Shift pie menu

rebelShieldGen: (x,y) => spawn({ kind:'structure', subtype:'rebelShieldGen', side:'RED', x,y, size:20, hp:1200, maxHp:1200, shield:1500, localRadius:250, regen:8, color:'#e74c3c' }),

tradersDen: (x,y) => spawn({ kind:'structure', subtype:'tradersDen', side:'NEUTRAL', x,y, size:16, hp:500, maxHp:500, color:'#f1c40f' }),

enclaveSatellite: (x,y) => spawn({ kind:'structure', subtype:'enclaveSatellite', side:'NEUTRAL', x,y, size:12, hp:400, maxHp:400, color:'#bdc3c7' }),

seaPlanet: (x,y) => spawn({ kind:'celestial', subtype:'planet', side:'NEUTRAL', x,y, mass:16000, color:'#3498db', size:20 }),

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

flamePlanet: (x,y) => spawn({ kind:'celestial', subtype:'planet', side:'NEUTRAL', x,y, mass:18000, color:'#e74c3c', size:22 }),

earthPlanet: (x,y) => spawn({ kind:'celestial', subtype:'planet', side:'NEUTRAL', x,y, mass:15000, color:'#2c3e50', size:19 }),

};


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

if (projCount >= CONFIG.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;

}


// ---------- Entity Management & Effects ----------

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

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


// 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 + 100; 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;

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


// AI behaviors

if (!s.playerControl){

const target = entities.find(e => e.id === s.target?.id);

s.target = target; // Refresh target reference

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

}

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

}


// Firing logic

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;

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' ? 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;

}

}

}


// Mothership spawning

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

}

}


// Update position and check bounds

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 update

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 possibleHits = spatialQueryRegion(p.x, p.y, 80);

for(const hit of possibleHits){

if (!hit || hit.kind !== 'ship' || 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);

removeEntityByIndex(i);

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

break;

}

}

}


// Ship collisions

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 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; 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) + 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) => Math.hypot(a.x - source.x, a.y - source.y) - Math.hypot(b.x - source.x, b.y - source.y));

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


for(const e of entities){

switch(e.kind) {

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

break;

case 'ship':

case 'structure':

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

// Simple polygon for most ships and structures

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

// Handle unique shapes for specific structures

if (e.subtype === 'rebelShieldGen'){

ctx.beginPath(); ctx.arc(0, 0, e.size, 0, TAU); ctx.strokeStyle = e.color; ctx.lineWidth = 2; ctx.stroke();

} else if (e.subtype === 'capitalCannon'){

ctx.fillStyle = e.color; ctx.beginPath(); ctx.fillRect(-e.size/2, -e.size/2, e.size, e.size); ctx.fill();

} else if (e.subtype === 'tradersDen' || e.subtype === 'enclaveSatellite'){

ctx.beginPath(); ctx.arc(0, 0, e.size, 0, TAU); ctx.fillStyle = e.color; ctx.fill();

}

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

ctx.restore();

break;

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;

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

break;

}

}


if (selectedId){ const s = entities.find(e=>e.id===selectedId); 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.restore();

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

}


// ---------- Game Loop ----------

function loop(time){

const dt = (time - lastTime) / 1000;

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

function screenToWorld(screenX, screenY){

return { x: screenX - camera.x, y: screenY - camera.y };

}


// Reworked PieMenu class for the requested behavior

class PieMenu {

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

show(clientX, clientY){

this.hide();

// Create the pie menu container

this.el = document.createElement('div');

this.el.className = 'pie-container';


// Apply the "stationary shuffle" wobble of 5-10px

const wobbleX = Math.random() * 10 - 5; // -5 to +5

const wobbleY = Math.random() * 10 - 5; // -5 to +5

this.el.style.left = `${clientX + wobbleX}px`;

this.el.style.top = `${clientY + wobbleY}px`;


const N = this.items.length;

const angleStep = TAU / N;

const radius = 100; // Radius from the center

const startOffset = Math.PI / 2; // Start from the top

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

const item = this.items[i];

const slice = document.createElement('div');

slice.className = 'pie-slice';

const angle = i * angleStep + startOffset;

slice.style.transform = `translate(${Math.cos(angle)*radius}px, ${Math.sin(angle)*radius}px)`;

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

slice.innerHTML = `<div class="label">${item.label}</div>`;

const action = (e) => {

e.stopPropagation();

const worldPos = screenToWorld(clientX, clientY);

item.action(worldPos.x, worldPos.y);

this.hide();

};

slice.addEventListener('mousedown', action);

this.el.appendChild(slice);

}

document.body.appendChild(this.el);

setTimeout(()=> document.addEventListener('mousedown', this.hide.bind(this), {once:true}), 10);

}

hide(){ if (this.el){ this.el.remove(); this.el = null; } }

}


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

]);

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

]);

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

]);

const secretPie = new PieMenu([

{ label:'Capital Cannons', color:'#ff6b6b', action:(x,y) => spawnActions.capitalCannon(x,y,RED) },

{ label:'Rebel Shield Gen', color:'#e74c3c', action:(x,y) => spawnActions.rebelShieldGen(x,y) },

{ 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:'Earth Planet', color:'#2c3e50', action:(x,y) => spawnActions.earthPlanet(x,y) }

]);


canvas.addEventListener('mousemove', (e) => { mouse.screenX = e.clientX; mouse.screenY = e.clientY; });

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

// Check for right-click button (button === 2)

if (e.button === 2){

e.preventDefault();

// Check key combinations to show the correct menu

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;

}

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

selectedId = clicked ? clicked.id : null;

});

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


const keys = new Set();

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

const k = e.key.toLowerCase();

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) => { keys.delete(e.key.toLowerCase()); });


// Player control interval

setInterval(() => {

if (!running || !selectedId) return;

const s = entities.find(e => e.id === selectedId && e.playerControl);

if (!s) return;

if (keys.has('a')) s.vx -= 0.08; if (keys.has('d')) s.vx += 0.08;

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

if (keys.has(' ')){

const now = performance.now();

if (!s.lastShot || (now - s.lastShot) > (s.fireRate || 0.22) * 1000){

const target = screenToWorld(mouse.screenX, mouse.screenY);

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

const speed = 12;

spawnProjectile(s.x, s.y, Math.cos(dir)*speed + s.vx, Math.sin(dir)*speed + s.vy, s, 'bullet', 2.5, 35);

s.lastShot = now;

}

}

}, 16);


// UI Buttons

document.getElementById('btnPause').onclick = () => running = !running;

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

document.getElementById('btnClear').onclick = () => { entities = []; projCount = 0; initFactions(); initGame(); };

document.getElementById('btnMegaToggle').onclick = () => { const m = document.getElementById('mega'); m.style.display = m.style.display==='block'?'none':'block'; };

document.getElementById('mega').onclick = (e) => {

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

const action = e.target.dataset.action;

if(spawnActions[action]){

const worldCenter = screenToWorld(W()/2, H()/2);

spawnActions[action](worldCenter.x + rand(120), worldCenter.y + rand(120));

}

}

};


// ---------- Init ----------

function initGame() {

const worldCenter = screenToWorld(W()/2, H()/2);

spawnActions.star(worldCenter.x, worldCenter.y);

spawnActions.playerMothership(worldCenter.x - 400, worldCenter.y);

spawnActions.enemyMothership(worldCenter.x + 400, worldCenter.y);

}


initFactions();

poolInit();

initGame();

requestAnimationFrame(loop);


})();

</script>

</body>

</html>


LINE COUNT: 621


Tags:

Comments