RTS
ember 1 · full

Reviews & generation details
Reviews
No human review yet. Unreviewed does not mean unsuccessful.
Generation context
Imported historical artifact. Its exact original instructions and lineage are not known.
Generation prompt · exact text unavailable
See the current prompt for reference. It may differ from the instructions used to generate this artifact.
Inspect original source 8,138 bytes · SHA-256 b347e35dafd9
<script>
"use strict";
/* ============ SPATIAL HASH ============ */
const HASH=8, hgrid=new Map();
function hkey(x,z){return Math.floor(x/HASH)+'_'+Math.floor(z/HASH)}
function rebuildHash(){hgrid.clear();for(const e of ents){if(e.dead)continue;const k=hkey(e.x,e.z);let a=hgrid.get(k);if(!a)hgrid.set(k,a=[]);a.push(e)}}
function near(x,z,r,fn){const x0=Math.floor((x-r)/HASH),x1=Math.floor((x+r)/HASH),z0=Math.floor((z-r)/HASH),z1=Math.floor((z+r)/HASH);
for(let i=x0;i<=x1;i++)for(let j=z0;j<=z1;j++){const a=hgrid.get(i+'_'+j);if(a)for(const e of a)if(fn(e))return e}return null}
/* ============ ORDERS ============ */
function giveOrder(u,o,queue){if(!queue)u.orders=[];u.orders.push(o);if(!queue)u.path=null}
function curOrder(u){return u.orders[0]}
function nearestEnemy(u,range,visibleOnly){
let best=null,bd=range*range;
for(const e of ents){if(e.dead||e.f===u.f)continue;
if(!UDEF[u.type]?.aa&&e.fly)continue;
if(u.kind==='u'&&!UDEF[u.type].aa&&e.fly)continue;
if(visibleOnly&&u.f===P0&&fogAt(P0,e.x,e.z)!==2)continue;
if(visibleOnly&&u.f===P1&&fogAt(P1,e.x,e.z)!==2)continue;
const d=dist2(u,e);if(d<bd){bd=d;best=e}}
return best}
function canHit(u,t){if(t.fly)return u.kind==='b'?!!BDEF[u.type].aa:!!UDEF[u.type].aa;return true}
function atkRange(u){return u.kind==='b'?BDEF[u.type].range:UDEF[u.type].range}
function atkDmg(u){const base=u.kind==='b'?BDEF[u.type].dmg:UDEF[u.type].dmg;return base*(1+0.15*upg[u.f].weap)}
function atkDt(u){return u.kind==='b'?BDEF[u.type].dt:UDEF[u.type].dt}
function atkCd(u){return u.kind==='b'?BDEF[u.type].cd:UDEF[u.type].cd}
function fireAt(u,t){
u.cd=atkCd(u);
const rng=atkRange(u);
if(rng>3){projectiles.push({x:u.x,y:u.fly?6:1.4,z:u.z,tx:t.x,ty:t.fly?6:1,tz:t.z,t:0,dur:Math.hypot(t.x-u.x,t.z-u.z)/40+0.05,target:t,dmg:atkDmg(u),dt:atkDt(u),e:atkDt(u)==='eng'});
if(u.f===P0&&dist2(u,{x:cam.tx,z:cam.tz})<3600)SFX.shot()}
else{dealDmg(t,atkDmg(u),atkDt(u));if(u.f===P0&&dist2(u,{x:cam.tx,z:cam.tz})<3600)SFX.shot()}
u.face=Math.atan2(t.x-u.x,t.z-u.z)}
/* ============ UNIT UPDATE ============ */
function moveStep(u,dt,tx,tz){
const d=UDEF[u.type];let sp=d.spd*(u.type==='wasp'?1+0.35*upg[u.f].thrust:1);
const dx=tx-u.x,dz=tz-u.z,dd=Math.hypot(dx,dz);
if(dd<0.1)return true;
u.face=Math.atan2(dx,dz);
let nx=u.x+dx/dd*sp*dt,nz=u.z+dz/dd*sp*dt;
if(!u.fly&&!passable(nx,nz,false)){ // slide
if(passable(nx,u.z,false))nz=u.z;else if(passable(u.x,nz,false))nx=u.x;else{nx=u.x;nz=u.z}}
u.x=nx;u.z=nz;
return dd<0.6}
function followPath(u,dt){
if(!u.path||u.pi>=u.path.length)return true;
const wp=u.path[u.pi];
if(moveStep(u,dt,wp.x,wp.z))u.pi++;
return u.pi>=u.path.length}
function setDest(u,x,z){u.tx=x;u.tz=z;u.path=findPath(u.x,u.z,x,z,u.fly);u.pi=0}
function updateUnit(u,dt){
const d=UDEF[u.type];u.cd=Math.max(0,u.cd-dt);
const o=curOrder(u);
// separation
const sep=near(u.x,u.z,u.r+1.2,e=>e!==u&&e.kind==='u'&&!e.fly===!u.fly&&dist2(u,e)<(u.r+e.r)*(u.r+e.r));
if(sep){const dx=u.x-sep.x,dz=u.z-sep.z,l=Math.hypot(dx,dz)||0.1;u.x+=dx/l*2*dt;u.z+=dz/l*2*dt}
if(!o){ // idle auto-acquire unless hold
if(d.dmg&&u.cd<=0){const t=nearestEnemy(u,u.hold?atkRange(u)+0.5:d.sight,true);
if(t){if(canHit(u,t)&&dist2(u,t)<=atkRange(u)**2)fireAt(u,t);
else if(!u.hold&&canHit(u,t))setDest(u,t.x,t.z),followPath(u,dt)}}
return}
switch(o.k){
case 'move':case 'amove':{
if(o.k==='amove'&&d.dmg&&u.cd<=0){const t=nearestEnemy(u,d.sight,true);
if(t&&canHit(u,t)){if(dist2(u,t)<=atkRange(u)**2){fireAt(u,t);return}
setDest(u,t.x,t.z);followPath(u,dt);return}}
if(!u.path||u._dx!==o.x||u._dz!==o.z){u._dx=o.x;u._dz=o.z;setDest(u,o.x,o.z)}
if(followPath(u,dt))u.orders.shift();
break}
case 'attack':{const t=o.t;
if(t.dead||(u.f===P0&&fogAt(P0,t.x,t.z)!==2&&t.kind==='u')||(u.f===P1&&fogAt(P1,t.x,t.z)!==2&&t.kind==='u')){u.orders.shift();break}
if(!canHit(u,t)){u.orders.shift();break}
if(dist2(u,t)<=atkRange(u)**2){if(u.cd<=0)fireAt(u,t)}
else{if(!u.path||u._at!==t.id){u._at=t.id;setDest(u,t.x,t.z)}followPath(u,dt);
if(u.cd<=0&&dist2(u,t)<=(atkRange(u)+2)**2)fireAt(u,t)}
break}
case 'gather':{const dp=o.dp;
if(dp.amount<=0){const nd=nearestDeposit(u);if(nd)o.dp=nd;else{u.orders.shift();break}}
if(u.carry>=10){u.orders.unshift({k:'return'});break}
if(dist2(u,dp)>4){if(!u.path||u._gx!==dp.x){u._gx=dp.x;setDest(u,dp.x,dp.z)}followPath(u,dt)}
else{u.path=null;u.harvT+=dt;if(u.harvT>1.2){u.harvT=0;const take=Math.min(10-u.carry,dp.amount,5);dp.amount-=take;u.carry+=take;
effects.push({x:dp.x,y:1.5,z:dp.z,t:0.3,k:'hit'})}}
break}
case 'return':{const hq=nearestDrop(u);
if(!hq){u.orders.shift();break}
if(dist2(u,hq)>(hq.r+1.5)**2){if(!u.path||u._rx!==hq.id){u._rx=hq.id;setDest(u,hq.x+rand(-2,2),hq.z+rand(-2,2))}followPath(u,dt)}
else{res[u.f]+=u.carry;if(u.f===P0&&u.carry>0)effects.push({x:u.x,y:2,z:u.z,t:0.4,k:'res'});u.carry=0;u.path=null;u.orders.shift();
if(u.orders[0]?.k==='gather'){/* resume */}}
break}
case 'build':{const b=o.b;
if(b.dead){u.orders.shift();break}
if(b.done){u.orders.shift();break}
if(dist2(u,b)>(b.r+2)**2){if(!u.path||u._bx!==b.id){u._bx=b.id;setDest(u,b.x+b.r+1,b.z)}followPath(u,dt)}
else{u.path=null;b.prog+=dt/BDEF[b.type].time;
if(b.prog>=1){b.prog=1;b.done=true;b.hp=b.maxhp;u.orders.shift();
if(b.f===P0){msg(BDEF[b.type].name+' complete');SFX.done()}}}
break}}
}
function nearestDeposit(u){let best=null,bd=1e9;for(const dp of deposits)if(dp.amount>0){const d=dist2(u,dp);if(d<bd){bd=d;best=dp}}return best}
function nearestDrop(u){let best=null,bd=1e9;for(const e of ents)if(!e.dead&&e.kind==='b'&&e.f===u.f&&e.done&&BDEF[e.type].drop){const d=dist2(u,e);if(d<bd){bd=d;best=e}}return best}
/* ============ BUILDING UPDATE ============ */
function updateBld(b,dt){
const d=BDEF[b.type];b.cd=Math.max(0,b.cd-dt);
if(!b.done)return;
// turret
if(d.dmg&&b.cd<=0){const t=nearestEnemy(b,d.range,true);if(t&&canHit(b,t))fireAt(b,t)}
// production
if(b.queue.length){const q=b.queue[0];const ud=UDEF[q.type];
if(supply(b.f)<ud.sup){q.wait=true}
else{q.wait=false;q.t+=dt;
if(q.t>=ud.time){b.queue.shift();supplyUsed[b.f]+=ud.sup;
const a=Math.random()*6.28;const u=mkUnit(q.type,b.f,b.x+Math.cos(a)*(b.r+1.5),b.z+Math.sin(a)*(b.r+1.5));
if(b.f===P1)u.hp=u.maxhp=ud.hp*(1+0.2*upg[P1].armr);
if(b.f===P0){u.hp=u.maxhp=ud.hp*(1+0.2*upg[P0].armr)}
if(b.rally){if(b.rally.dp)giveOrder(u,{k:'gather',dp:b.rally.dp});else if(b.rally.amove)giveOrder(u,{k:'amove',x:b.rally.x,z:b.rally.z});else giveOrder(u,{k:'move',x:b.rally.x,z:b.rally.z})}
if(b.f===P0){msg(ud.name+' ready');SFX.done()}}}}
// research
if(b.research){const r=b.research;r.t+=dt;
if(r.t>=UPG[r.key].time){upg[b.f][r.key]++;b.research=null;
if(b.f===P0){msg(UPG[r.key].name+' researched');SFX.done()}
else if(b.f===P1){for(const e of ents)if(e.kind==='u'&&e.f===P1)e.maxhp=e.hp=UDEF[e.type].hp*(1+0.2*upg[P1].armr)}}}
}
function trainUnit(b,type){const ud=UDEF[type];
if(res[b.f]<ud.cost){if(b.f===P0){msg('Not enough Crystallite');SFX.err()}return false}
if(b.queue.length>=5){if(b.f===P0)msg('Queue full');return false}
res[b.f]-=ud.cost;b.queue.push({type,t:0});if(b.f===P0)SFX.click();return true}
function cancelQueue(b,i){const q=b.queue[i];if(!q)return;res[b.f]+=UDEF[q.type].cost;b.queue.splice(i,1);SFX.click()}
function startResearch(b,key){const u=UPG[key];
if(b.research||upg[b.f][key]>=u.max)return;
if(res[b.f]<u.cost){if(b.f===P0){msg('Not enough Crystallite');SFX.err()}return}
res[b.f]-=u.cost;b.research={key,t:0};SFX.click()}
/* ============ PROJECTILES / EFFECTS ============ */
function updateProj(dt){
for(const p of projectiles){p.t+=dt;
if(p.t>=p.dur){if(!p.target.dead)dealDmg(p.target,p.dmg,p.dt);p.dead=true}}
projectiles=projectiles.filter(p=>!p.dead);
for(const e of effects)e.t-=dt;
effects=effects.filter(e=>e.t>0)}
</script>