← RTS results

RTS

ember 1 · full

Initial view of RTS

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>