massfront-playtest / src /intel.js
CREATORJD's picture
Publish MASSFRONT web 1.33.33 (part 3)
3597f5a verified
Raw
History Blame Contribute Delete
8.53 kB
;
;
/* ============================================================================
BATTLEFIELD INTEL — vision, radar, detection, stealth
----------------------------------------------------------------------------
Fog already hid models, bars, FX and the minimap. What it did not do was
read the intel data the roster already carried:
TYPES[].scout Kestrel (and faction scouts) used the same air bubble as a
Wasp. The card said "150 sight"; fog stamped 14 cells.
umode === 4 GHOST: findEnemy skipped them, firing broke cover, hulls
went translucent — and the player still SAW them the
moment they entered a sensor circle. "Unseen until you
fire" was targeting-only.
BT.uplink Targeting Array / radar dish. Boosted towers and the build
grid. Vision was the generic 10-cell building stamp, so a
radar mast saw the same as a silo.
WC.dark "vision suffers" only forced night. Fog Bank (fogb) was
the only wildcard that actually shortened sensors.
Jamming has no authored radius/flag. Ghost Net already stuns and scans; a
dedicated radar-denial layer is a later plan item, not invented here.
Radar is a COMMAND-MAP layer, not a second 3D reveal. Uplink/techlab stamp
fogRadar; the minimap draws those contacts; render3d keeps using
fogEntityVisible (visual). That split is why this file must not live in
the renderer — and why it never writes GL state.
Detection is the answer to GHOST. Scouts, uplinks, techlabs, HQs and live
survey scans stamp fogDetect. intelCanTarget (sim.js seam) lets a detector
lock a silent-running hull; fogEntityVisible hides it from the player
until then. Same numbers both ways, so a cloak that fools the eye also
fools the gun until a sensor is in range.
============================================================================ */
const INTEL_VIS_GROUND=10, INTEL_VIS_AIR=14, INTEL_VIS_SCOUT=18, INTEL_VIS_GHOST=4;
const INTEL_VIS_HQ=22, INTEL_VIS_TURRET=12, INTEL_VIS_BLD=10, INTEL_VIS_CARRIER=24;
/* Radar cells are larger than visual on purpose: the mast hears further than
it sees. Contacts are minimap-only (see intelRadarContact). */
const INTEL_RADAR_UPLINK=22, INTEL_RADAR_TECHLAB=16;
/* Detect is shorter than radar. Omni-style pierce, not a second map hack. */
const INTEL_DET_SCOUT=16, INTEL_DET_UPLINK=14, INTEL_DET_TECHLAB=12, INTEL_DET_HQ=8;
const fogRadar=new Uint8Array(FN*FN);
const fogDetect=new Uint8Array(FN*FN);
const intelGhostIdx=[];
let intelGhostStamp=-1;
const intelHasGhost=[0,0,0];
function intelVisionScale(r){
let s=r;
if(typeof WC!=='undefined'){
if(WC.fogb) s=Math.max(4,Math.round(s*0.6));
/* Total Eclipse already forces night (sim.js dayT). The card also promised
shorter sensors; without this it was a free payout on a cosmetic sky. */
if(WC.dark) s=Math.max(4,Math.round(s*0.72));
}
return s;
}
function intelUnitVision(i,vis,hm){
const T=TYPES[utype[i]];
let base=T.scout?INTEL_VIS_SCOUT:(T.air?INTEL_VIS_AIR:INTEL_VIS_GROUND);
if(umode[i]===4) base+=INTEL_VIS_GHOST;
const scaled=vis(base);
return Math.max(6,Math.round(6+(scaled-6)*hm));
}
function intelBldVision(B,vis,hm){
const t=B.type==='hq'?INTEL_VIS_HQ:B.type==='turret'?INTEL_VIS_TURRET:INTEL_VIS_BLD;
return Math.max(4,Math.round(vis(t)*hm));
}
function intelCell(wx,wy){
return clamp(wy/MAP*FN|0,0,FN-1)*FN+clamp(wx/MAP*FN|0,0,FN-1);
}
function intelStamp(grid,wx,wy,rc,bit){
if(rc<=0) return;
const cx=clamp(wx/MAP*FN|0,0,FN-1), cy=clamp(wy/MAP*FN|0,0,FN-1), r2=rc*rc;
for(let y=Math.max(0,cy-rc);y<=Math.min(FN-1,cy+rc);y++)
for(let x=Math.max(0,cx-rc);x<=Math.min(FN-1,cx+rc);x++){
const dx=x-cx,dy=y-cy;
if(dx*dx+dy*dy<=r2) grid[y*FN+x]|=bit;
}
}
function intelDetectedAt(wx,wy,team){
const bit=1<<(team|0);
return !!(fogDetect[intelCell(wx,wy)]&bit);
}
function intelRadarContact(wx,wy){
if(typeof fogGameplayActive==='function'&&!fogGameplayActive()) return false;
return !!(fogRadar[intelCell(wx,wy)]&1);
}
function intelGhostRefresh(){
const t=(typeof stats!=='undefined'&&stats)?stats.t:-2;
if(t===intelGhostStamp) return;
intelGhostStamp=t;
intelGhostIdx.length=0;
intelHasGhost[0]=intelHasGhost[1]=intelHasGhost[2]=0;
for(let i=0;i<unitHigh;i++){
if(!ualive[i]||umode[i]!==4) continue;
intelGhostIdx.push(i);
intelHasGhost[uteam[i]]=1;
}
}
function intelGhostCloaked(team,wx,wy){
if(team===0||!intelHasGhost[team]) return false;
intelGhostRefresh();
for(let n=0;n<intelGhostIdx.length;n++){
const j=intelGhostIdx[n];
if(!ualive[j]||uteam[j]!==team||umode[j]!==4) continue;
if(ux[j]!==wx||uy[j]!==wy) continue;
return !intelDetectedAt(wx,wy,0);
}
return false;
}
intelCanTarget=function(j,team){
if(!ualive[j]||uteam[j]===team) return false;
if(umode[j]===4&&!intelDetectedAt(ux[j],uy[j],team)) return false;
return true;
};
function intelSensorBit(team){ return team===1?2:team===0?1:0; }
function intelStampSensors(){
fogRadar.fill(0); fogDetect.fill(0);
const vis=intelVisionScale;
for(let i=0;i<unitHigh;i++){
if(!ualive[i]) continue;
const bit=intelSensorBit(uteam[i]); if(!bit) continue;
const T=TYPES[utype[i]];
if(T.scout){
const hm=typeof hazVisionMult==='function'?hazVisionMult(ux[i],uy[i],i):1;
intelStamp(fogDetect,ux[i],uy[i],Math.max(4,Math.round(vis(INTEL_DET_SCOUT)*hm)),bit);
}
}
for(const B of blds){
if(!B.alive||B.prog<1) continue;
const bit=intelSensorBit(B.team); if(!bit) continue;
const hm=typeof hazVisionMult==='function'?hazVisionMult(B.x,B.y,-1):1;
if(B.type==='uplink'){
intelStamp(fogRadar,B.x,B.y,Math.max(6,Math.round(vis(INTEL_RADAR_UPLINK)*hm)),bit);
intelStamp(fogDetect,B.x,B.y,Math.max(4,Math.round(vis(INTEL_DET_UPLINK)*hm)),bit);
}else if(B.type==='techlab'){
intelStamp(fogRadar,B.x,B.y,Math.max(6,Math.round(vis(INTEL_RADAR_TECHLAB)*hm)),bit);
intelStamp(fogDetect,B.x,B.y,Math.max(4,Math.round(vis(INTEL_DET_TECHLAB)*hm)),bit);
}else if(B.type==='hq'){
intelStamp(fogDetect,B.x,B.y,Math.max(4,Math.round(vis(INTEL_DET_HQ)*hm)),bit);
}
}
if(typeof carrier!=='undefined'&&carrier&&carrier.active)
intelStamp(fogDetect,carrier.x,carrier.y,vis(12),1);
if(typeof fogScans!=='undefined'){
for(let i=0;i<fogScans.length;i++){
const S=fogScans[i];
intelStamp(fogDetect,S.x,S.y,vis(S.r*2),1);
intelStamp(fogRadar,S.x,S.y,vis(S.r*2),1);
}
}
intelGhostStamp=-1;
intelGhostRefresh();
}
function intelReset(){
fogRadar.fill(0); fogDetect.fill(0);
intelGhostIdx.length=0;
intelHasGhost[0]=intelHasGhost[1]=intelHasGhost[2]=0;
intelGhostStamp=-1;
}
(function mfIntelInstall(){
const baseFog=updateFog;
updateFog=function(){
baseFog();
intelStampSensors();
};
const baseVis=fogEntityVisible;
fogEntityVisible=function(team,wx,wy){
if(!baseVis(team,wx,wy)) return false;
/* Fog-off / attract / menus keep the old translucent GHOST hull. The
stance still needs a detector to be shot; hiding the model is a fog-on
intel rule so a silent Wasp cannot silhouette against black map. */
if(typeof fogGameplayActive==='function'&&!fogGameplayActive()) return true;
if(intelGhostCloaked(team,wx,wy)) return false;
return true;
};
if(typeof setMode==='function'&&!setMode._mfIntel){
const baseMode=setMode;
setMode=function(i,m){ const r=baseMode(i,m); intelGhostStamp=-1; return r; };
setMode._mfIntel=1;
}
if(typeof resetWorld==='function'&&!resetWorld._mfIntel){
const baseReset=resetWorld;
resetWorld=function(){ baseReset.apply(this,arguments); intelReset(); };
resetWorld._mfIntel=1;
}
const basePurpose=intelUnitPurpose;
intelUnitPurpose=function(T){
let s=basePurpose(T);
if(T.scout&&!/detect/i.test(s))
s=s.replace(/\.?$/,'')+'. Wider sensors, and it can detect silent-running chassis.';
return s;
};
const baseBld=intelBldPurpose;
intelBldPurpose=function(key){
let s=baseBld(key);
if(key==='uplink'&&!/radar/i.test(s))
s=s.replace(/\.?$/,'')+'. Paints radar contacts on the command map and detects GHOST chassis.';
else if(key==='techlab'&&!/detect/i.test(s))
s=s.replace(/\.?$/,'')+'. Sensor dish: shorter radar than an Uplink, still detects silent running.';
return s;
};
})();