Spaces:
Running
Running
File size: 23,000 Bytes
b51e03a 3597f5a fcce7e3 b51e03a fcce7e3 369b236 fcce7e3 b51e03a 3597f5a b51e03a 3597f5a b51e03a 3597f5a b51e03a 3597f5a b51e03a 3597f5a b51e03a fcce7e3 369b236 3597f5a 369b236 fcce7e3 b51e03a fcce7e3 b51e03a fcce7e3 3597f5a fcce7e3 369b236 fcce7e3 b51e03a fcce7e3 369b236 fcce7e3 3597f5a fcce7e3 369b236 3597f5a fcce7e3 b51e03a fcce7e3 369b236 fcce7e3 b51e03a fcce7e3 3597f5a fcce7e3 3597f5a fcce7e3 369b236 fcce7e3 369b236 fcce7e3 369b236 fcce7e3 b51e03a 3597f5a fcce7e3 3597f5a fcce7e3 3597f5a fcce7e3 369b236 fcce7e3 369b236 fcce7e3 369b236 fcce7e3 369b236 fcce7e3 b51e03a 3597f5a fcce7e3 3597f5a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 | ;
;
/* ============================================================================
MAP-EXCLUSIVE ENVIRONMENTAL HAZARDS
----------------------------------------------------------------------------
Every map already looked different. None of them PLAYED differently β the
terrain changed the pathing and nothing else, so map choice was decoration.
Each map now owns one hazard nobody else gets, drawn from what that place
actually is:
Vanguard Valley DUST FRONT a squall rolls across the open highway country,
blinding and slowing everything inside it
Highland Scar ROCKSLIDE the cliffs shed; fighting on steep ground has
a cost the plains do not charge
Shattered Isles SQUALL an ocean storm earths itself through the
biggest piece of metal under the cell
Relic Basin RELIC PULSE the thing in the crater is not inert, and the
centre of the map is the richest ground on it
Three rules the whole file obeys:
* every hazard hits ALL THREE TEAMS. A hazard that only inconveniences the
player is a difficulty setting wearing a costume.
* every hazard is telegraphed before it lands, with enough warning to walk
out. Damage from nowhere is not difficulty, it is noise.
* every hazard scales with the difficulty setting, because the whole reason
Easy was unplayable is that the map's third parties did not.
============================================================================ */
const HAZ={ map:'', mode:'', t:0, warn:0, cells:[], front:null, phase:0, count:0, faults:[], vision:[], lava:[] };
/* Shown on the map cards in Battle Setup, so the hazard is part of choosing a
battlefield rather than a surprise thirty seconds in. */
const MAPHAZ={
vanguard:{em:'πͺ', nm:'DUST FRONTS', ds:'Squalls cross the valley β sight and speed cut inside'},
highland:{em:'β°', nm:'FAULT COLLAPSE',ds:'Marked shelves crack, telegraph, then collapse into impassable scars'},
isles: {em:'β‘', nm:'SQUALL LINES', ds:'Lightning earths through the largest massed force'},
crater: {em:'β', nm:'RELIC PULSES', ds:'The basin floor blacks out systems and drains grids'},
};
const MF_HAZARD_PROFILES={
dust:{em:'πͺ',nm:'ASH FRONT',ds:'Abrasive fronts cut sight and movement across exposed ground',mode:'vanguard'},
storm:{em:'π©',nm:'TEMPEST CELLS',ds:'Charged weather blinds advancing formations',mode:'vanguard'},
calm:{em:'β',nm:'CLEAR SKIES',ds:'Working civic weather β no battlefield tax on the opening build',mode:'calm'},
spores:{em:'β',nm:'SPORE BLOOM',ds:'Dense alien spores conceal units and slow movement',mode:'vanguard'},
heat:{em:'β¨',nm:'THERMAL SURGE',ds:'Heat distortion sweeps exposed routes and drains combat tempo',mode:'heat'},
collapse:{em:'β°',nm:'TERRAIN COLLAPSE',ds:'Telegraphed shelves fracture into impassable scars',mode:'highland'},
eruption:{em:'π',nm:'MAGMA ERUPTION',ds:'Unstable vents rupture, then leave dangerous molten ground',mode:'eruption'},
meteor:{em:'β',nm:'ORBITAL DEBRIS',ds:'Non-organic space weather telegraphs destructive impact zones',mode:'meteor'},
squall:{em:'β‘',nm:'SQUALL LINES',ds:'Electrical storms earth through concentrated forces',mode:'isles'},
whiteout:{em:'β',nm:'WHITEOUT',ds:'Frozen fronts erase vision before slamming massed units',mode:'whiteout'},
flood:{em:'β',nm:'FLOOD SURGE',ds:'Rising channels isolate formations before a violent surge',mode:'flood'},
/* Dead MAPDEFS string. Live sites use `heat`; keep the alias so a leftover
key still runs the thermal tick instead of falling through to dust. */
solray_corridor:{em:'π
',nm:'SOLAR FOCUS',ds:'Heat distortion sweeps exposed routes and drains combat tempo',mode:'heat'},
pulse:{em:'β',nm:'RELIC PULSES',ds:'Buried systems black out units and drain local grids',mode:'crater'}
};
function mapHazardKey(map){
const D=(typeof MAPDEFS!=='undefined'&&MAPDEFS[map])?MAPDEFS[map]:null;
return (D&&D.hazard)||({vanguard:'dust',highland:'collapse',isles:'squall',crater:'pulse'}[map])||'storm';
}
function mapHazardDef(map){
const key=mapHazardKey(map);
const P=MF_HAZARD_PROFILES[key]||MF_HAZARD_PROFILES.storm;
const legacy=MAPHAZ[map];
/* Legacy MAPHAZ rows have copy and no mode. Homeworld sites have a
profile key and no MAPHAZ row. Merge so UI and hazTick always agree. */
if(legacy) return {em:legacy.em,nm:legacy.nm,ds:legacy.ds,mode:P.mode||map};
return P;
}
function mapHazardMode(map){ return (mapHazardDef(map).mode)||map; }
function hazDiff(){ return (typeof diffLvl==='function')?diffLvl():1; }
/* Long gaps on Easy, and a much smaller bite. */
function hazEvery(base){
const p=typeof battlefieldPresetDef==='function'?battlefieldPresetDef():{haz:1};
const fac=typeof mapHomeFac==='function'?mapHomeFac():'';
/* Nova homeworlds are beginner theatres: weather exists, it does not bully
the opening. Brood worlds bite sooner. */
const world=fac==='nova'?1.85:fac==='horde'?0.82:1;
return base*[1.85,1.3,1][hazDiff()]*(p.haz||1)*world;
}
function hazDmg(base){ return base*[0.45,0.75,1][hazDiff()]; }
function hazReset(){
HAZ.map=(typeof curMap!=='undefined')?curMap:'';
HAZ.mode=mapHazardMode(HAZ.map);
HAZ.cells.length=0; HAZ.front=null; HAZ.phase=0; HAZ.warn=0; HAZ.count=0;
HAZ.faults.length=0; HAZ.vision.length=0; HAZ.lava.length=0;
/* Nothing happens for the first stretch of any match. A hazard during the
opening build is just a tax on not knowing the map yet. */
HAZ.t=hazEvery([150,120,95][hazDiff()]);
if(HAZ.mode==='calm') HAZ.t=1e9;
if(typeof uhaz!=='undefined') uhaz.fill(0);
if(HAZ.mode==='highland') hazSeedFaults();
}
function hazName(){
const D=mapHazardDef(HAZ.map);
return (D&&D.nm)||'';
}
/* Apply the blind/slow debuff β see uhaz in sim.js, read by movement and by
weapon range. Buildings are immune; they were not going anywhere anyway. */
function hazBlind(x,y,r,secs){
const r2=r*r;
for(let i=0;i<unitHigh;i++){
if(!ualive[i]) continue;
if(dist2(ux[i],uy[i],x,y)<r2 && uhaz[i]<secs) uhaz[i]=secs;
}
}
/* Damage is attributed to team 2 rather than a fourth "environment" team: the
damage accumulator is a three-slot array and a stray index 3 would quietly
poison it with NaN. Nothing reads the attacker for kill credit here β the
attacker index is -1 β so the only effect is that the hive gets blamed for
the weather, which is close enough to true on this planet. */
function hazHurt(x,y,r,dmg){
const r2=r*r;
for(let i=0;i<unitHigh;i++){
if(!ualive[i]) continue;
const d2=dist2(ux[i],uy[i],x,y);
if(d2<r2) dealDamage(i,dmg*(1-Math.sqrt(d2)/r*0.55),2,-1);
}
/* damageBld takes an INDEX into blds, not the object, and it is the only path
that handles death properly β rubble, salvage, territory rebuild. */
for(let b=0;b<blds.length;b++){
const B=blds[b];
if(!B||!B.alive) continue;
const d2=dist2(B.x,B.y,x,y);
if(d2<r2) damageBld(b,dmg*(1-Math.sqrt(d2)/r*0.55)*0.6,2);
}
}
/* ---- VANGUARD VALLEY β the dust front -------------------------------------
A band that crosses the whole map on a fixed heading. It does no damage at
all: it takes away sight and speed, which on open highway country is the
thing that actually decides fights. Armies caught mid-march arrive late and
half-blind; armies that waited lose tempo instead. */
function hazFrontTick(dt){
const F=HAZ.front;
if(!F) return;
F.p+=F.sp*dt;
const span=Math.hypot(MAP,MAP);
/* Sample points along the band and blind everything near them. */
const cx=MAP/2+F.dx*(F.p-span*0.5), cy=MAP/2+F.dy*(F.p-span*0.5);
F.cx=cx; F.cy=cy;
const px=-F.dy, py=F.dx;
for(let k=-6;k<=6;k++){
const sx=cx+px*k*(MAP/13), sy=cy+py*k*(MAP/13);
if(sx<-200||sy<-200||sx>MAP+200||sy>MAP+200) continue;
hazBlind(sx,sy,F.w*0.62,1.1);
/* Whiteout copy promises a slam after the blind. Dust fronts stay
sight/speed only β damage here is the ice, not the ash. */
if(HAZ.mode==='whiteout'&&(tick+k)%6===0) hazHurt(sx,sy,F.w*0.42,hazDmg(18));
if(typeof addParticle==='function' && (tick+k)%2===0 && perfScale>0.35){
for(let n=0;n<2;n++)
addParticle(1, sx+rr(-F.w*0.5,F.w*0.5), sy+rr(-F.w*0.45,F.w*0.45),
F.dx*26+rr(-8,8), F.dy*26+rr(-8,8), 1.5+Math.random(),
52+Math.random()*40, 168,150,116);
}
}
if(F.p>span+F.w){ HAZ.front=null; }
}
/* ---- HIGHLAND SCAR β rockslides -------------------------------------------
Sited by slope, not at random: the hazard belongs to the cliffs, so holding
the high ground is a real trade rather than a free advantage. */
function hazPickSteep(){
let best=null, bs=-1;
for(let t=0;t<40;t++){
const x=rr(220,MAP-220), y=rr(220,MAP-220);
if(typeof hAt!=='function') break;
const h=hAt(x,y);
if(h<0.02) continue; // not underwater
const s=Math.abs(hAt(x+34,y)-h)+Math.abs(hAt(x,y+34)-h)
+Math.abs(hAt(x-34,y)-h)+Math.abs(hAt(x,y-34)-h);
if(s>bs){ bs=s; best=[x,y]; }
}
return bs>0.012?best:null;
}
/* Fault shelves are authored by the live heightfield, not by screen-space
decoration. Dormant shelves remain visible before they arm, giving players
a route-planning choice; the normal hazard warning is still the final clear
signal to evacuate. */
function hazSeedFaults(){
const want=5;
for(let tries=0;HAZ.faults.length<want&&tries<90;tries++){
const p=hazPickSteep(); if(!p) continue;
if(typeof farFromStartZones==='function'&&!farFromStartZones(p[0],p[1],430)) continue;
let clear=true;
for(const F of HAZ.faults) if(dist2(p[0],p[1],F.x,F.y)<280*280){ clear=false; break; }
if(clear) HAZ.faults.push({x:p[0],y:p[1],r:105+Math.random()*28,state:0,at:0});
}
}
/* Fog sources ask this at their own position. Unit debuffs and actual storm
geometry now reduce line of sight, rather than merely lowering weapon range
while the fog map continued to reveal everything. */
function hazVisionMult(x,y,unitIdx){
let m=1;
if(unitIdx>=0&&typeof uhaz!=='undefined'&&uhaz[unitIdx]>0) m=0.44;
const F=HAZ.front;
if(F&&F.cx!=null){
const along=Math.abs((x-F.cx)*F.dx+(y-F.cy)*F.dy);
if(along<F.w*0.64) m=Math.min(m,0.42);
}
const now=typeof stats!=='undefined'?stats.t:0;
for(const V of HAZ.vision){
if(V.until<=now) continue;
if(dist2(x,y,V.x,V.y)<V.r*V.r) m=Math.min(m,V.m||0.45);
}
return m;
}
/* Low ground just above the water table β floodplains and channels β without
calling deformTerrain / stampWaterRipple. Those belong to the crater-water
sibling; this strike is a combat tax, not a mesh rewrite. */
function hazPickChannel(){
const wh=typeof WATER_H==='number'?WATER_H:0.335;
let best=null, bs=1e9;
if(typeof hAt!=='function') return null;
for(let t=0;t<50;t++){
const x=rr(200,MAP-200), y=rr(200,MAP-200);
const h=hAt(x,y);
if(h<wh-0.008||h>wh+0.048) continue;
const score=Math.abs(h-wh);
if(score<bs){ bs=score; best=[x,y]; }
}
return best;
}
/* ---- SHATTERED ISLES β squall lightning -----------------------------------
Earths through the largest mass under the cell. Massing an army in one place
on an ocean map is exactly what you want to do and exactly what gets hit. */
function hazPickMass(){
let best=null, bs=0;
const step=Math.max(1,unitHigh/220|0);
for(let i=0;i<unitHigh;i+=step){
if(!ualive[i]||uteam[i]===2) continue;
let n=0;
for(let j=0;j<unitHigh;j+=step) if(ualive[j]&&dist2(ux[i],uy[i],ux[j],uy[j])<170*170) n++;
if(n>bs){ bs=n; best=[ux[i],uy[i]]; }
}
for(const B of bldLive){
if(!B.alive||B.team===2||B.prog<1) continue;
let n=1.5;
for(const C of bldLive) if(C.alive&&dist2(B.x,B.y,C.x,C.y)<200*200) n+=1.2;
if(n>bs){ bs=n; best=[B.x,B.y]; }
}
return bs>=3?best:null;
}
function hazTick(dt){
if(typeof running==='undefined'||!running||demoMode||gameEnded) return;
if(HAZ.map!==curMap) hazReset();
const D=hazDiff();
const now=typeof stats!=='undefined'?stats.t:0;
for(let i=HAZ.vision.length-1;i>=0;i--) if(HAZ.vision[i].until<=now) HAZ.vision.splice(i,1);
/* Eruptions leave readable, persistent danger instead of becoming a single
orange explosion that players can immediately forget. Damage pulses are
bounded and the pool expires, so it cannot permanently wall off a route. */
for(let i=HAZ.lava.length-1;i>=0;i--){
const L=HAZ.lava[i]; L.life-=dt; L.pulse-=dt;
if(L.life<=0){ HAZ.lava.splice(i,1); continue; }
if(L.pulse<=0){ L.pulse=.78; hazHurt(L.x,L.y,L.r,hazDmg(34)); }
if(typeof addParticle==='function'&&perfScale>.32&&Math.random()<dt*9){
const a=Math.random()*TAU, rr0=Math.sqrt(Math.random())*L.r;
addParticle(Math.random()<.42?0:1,L.x+Math.cos(a)*rr0,L.y+Math.sin(a)*rr0,
rr(-3,3),rr(-18,-5),.5+Math.random()*.7,8+Math.random()*14,255,104,30);
}
}
if(HAZ.front) hazFrontTick(dt);
/* Telegraph phase: markers are on the ground, damage has not landed yet. */
if(HAZ.warn>0){
HAZ.warn-=dt;
for(const c of HAZ.cells){
if(typeof addParticle==='function' && (tick%3)===0 && perfScale>0.35)
addParticle(0, c[0]+rr(-c[2],c[2]), c[1]+rr(-c[2],c[2]), 0,-14, .5, 8,
c[3][0],c[3][1],c[3][2]);
}
if(HAZ.warn<=0){ hazStrike(); HAZ.cells.length=0; }
return;
}
HAZ.t-=dt;
if(HAZ.t>0) return;
if(HAZ.mode==='vanguard'||HAZ.mode==='whiteout'||HAZ.mode==='spore_bloom'||HAZ.mode==='spores'){
HAZ.t=hazEvery(115+Math.random()*50);
const a=Math.random()*TAU;
HAZ.front={dx:Math.cos(a),dy:Math.sin(a),p:0,
sp:118+Math.random()*40, w:[300,400,480][D]};
HAZ.count++;
const Dfn=mapHazardDef(HAZ.map);
toast((Dfn&&Dfn.em||'πͺ')+' '+(Dfn&&Dfn.nm||'FRONT')+' rolling in β sight and speed cut inside it');
sfx('alarm');
}
else if(HAZ.mode==='calm'){
HAZ.t=1e9;
}
else if(HAZ.mode==='heat'){
HAZ.t=hazEvery(90+Math.random()*35);
HAZ.cells.length=0;
const n=[2,3,4][D];
for(let k=0;k<n;k++){
const p=[rr(200,MAP-200),rr(200,MAP-200)];
HAZ.cells.push([p[0],p[1],90+Math.random()*30,[255,180,60]]);
}
HAZ.warn=3.5; HAZ.phase=5;
for(const c of HAZ.cells) mmPing(c[0],c[1]);
toast('π
SOLAR FOCUS BEAM DISPATCHED β clear the illuminated corridor');
sfx('alarm');
}
else if(HAZ.mode==='highland'){
HAZ.t=hazEvery(95+Math.random()*40);
HAZ.cells.length=0;
const n=[1,2,3][D];
for(let k=0;k<n;k++){
let F=HAZ.faults.find(q=>q.state===0);
if(!F){ hazSeedFaults(); F=HAZ.faults.find(q=>q.state===0); }
if(F){
F.state=1; F.at=now;
const c=[F.x,F.y,F.r,[220,176,98]]; c.fault=F; HAZ.cells.push(c);
}
}
if(!HAZ.cells.length){ HAZ.t=20; return; }
HAZ.warn=4.8; HAZ.phase=1;
for(const c of HAZ.cells) mmPing(c[0],c[1]);
toast('β° FAULT COLLAPSE β cracked shelves are giving way, clear the marked ground');
sfx('alarm');
}
else if(HAZ.mode==='eruption'){
HAZ.t=hazEvery(82+Math.random()*38);
HAZ.cells.length=0;
const n=[1,2,3][D];
for(let k=0;k<n;k++){
const p=hazPickSteep()||[rr(240,MAP-240),rr(240,MAP-240)];
HAZ.cells.push([p[0],p[1],86+Math.random()*24,[255,92,28]]);
}
HAZ.warn=4.4; HAZ.phase=4;
for(const c of HAZ.cells) mmPing(c[0],c[1]);
toast('π MAGMA PRESSURE RISING β evacuate the marked vents');
sfx('alarm');
}
else if(HAZ.mode==='flood'){
HAZ.t=hazEvery(85+Math.random()*35);
HAZ.cells.length=0;
const n=[2,3,4][D];
for(let k=0;k<n;k++){
const p=hazPickChannel()||[rr(220,MAP-220),rr(220,MAP-220)];
HAZ.cells.push([p[0],p[1],88+Math.random()*24,[70,190,220]]);
}
HAZ.warn=3.8; HAZ.phase=7;
for(const c of HAZ.cells) mmPing(c[0],c[1]);
toast('β FLOOD SURGE β channels are rising, clear the marked low ground');
sfx('alarm');
}
else if(HAZ.mode==='isles'){
HAZ.t=hazEvery(80+Math.random()*35);
HAZ.cells.length=0;
const n=[2,3,5][D];
for(let k=0;k<n;k++){
const p=hazPickMass();
if(p) HAZ.cells.push([clamp(p[0]+rr(-70,70),40,MAP-40),
clamp(p[1]+rr(-70,70),40,MAP-40),70,[150,225,255]]);
}
if(!HAZ.cells.length){ HAZ.t=18; return; }
HAZ.warn=2.6; HAZ.phase=2;
mmPing(HAZ.cells[0][0],HAZ.cells[0][1]);
toast('β‘ Squall line overhead β lightning earthing through massed armour');
sfx('alarm');
}
else if(HAZ.mode==='meteor'){
HAZ.t=hazEvery(88+Math.random()*40);
HAZ.cells.length=0;
const n=[2,3,4][D];
for(let k=0;k<n;k++){
const p=[rr(220,MAP-220),rr(220,MAP-220)];
HAZ.cells.push([p[0],p[1],80+Math.random()*28,[210,230,255]]);
}
HAZ.warn=3.8; HAZ.phase=6;
for(const c of HAZ.cells) mmPing(c[0],c[1]);
toast('β ORBITAL DEBRIS INBOUND β clear the marked impact lanes');
sfx('alarm');
}
else if(HAZ.mode==='crater'){
HAZ.t=hazEvery(125+Math.random()*45);
HAZ.cells=[[MAP/2,MAP/2,[300,380,460][D],[200,160,255]]];
HAZ.warn=4.0; HAZ.phase=3;
mmPing(MAP/2,MAP/2);
toast('β The relic is waking β pulse building at the basin floor');
sfx('alarm');
}
else {
/* Unknown mode used to wait 60s forever and never strike. Treat it as a
front so an authored MAPDEFS.hazard the profiles missed still plays. */
HAZ.mode='vanguard'; HAZ.t=0;
}
}
function hazStrike(){
const D=hazDiff();
HAZ.count++;
if(HAZ.phase===1){ // rockslide
for(const c of HAZ.cells){
if(c.fault){ c.fault.state=2; c.fault.at=stats.t; }
spawnExplosion(c[0],c[1],c[2]*0.5,2);
hazHurt(c[0],c[1],c[2],hazDmg(210));
hazBlind(c[0],c[1],c[2]*1.2,2.2);
HAZ.vision.push({x:c[0],y:c[1],r:c[2]*1.2,until:stats.t+3.2,m:0.48});
/* A collapse is terrain, not a decal: the deeper bowl is processed by
the existing deformation queue, re-lights the mesh and updates the
passability grid if the shelf tears below the water table. */
if(typeof deformTerrain==='function') deformTerrain(c[0],c[1],c[2]*0.78,0.105);
if(typeof addParticle==='function')
for(let k=0;k<14;k++)
addParticle(1,c[0]+rr(-c[2]*0.6,c[2]*0.6),c[1]+rr(-c[2]*0.6,c[2]*0.6),
rr(-14,14),rr(-24,-6),1.8,44+Math.random()*40, 150,136,116);
}
shake=Math.max(shake,5);
}
else if(HAZ.phase===2){ // lightning
for(const c of HAZ.cells){
spawnExplosion(c[0],c[1],c[2]*0.7,2);
hazHurt(c[0],c[1],c[2],hazDmg(260));
hazBlind(c[0],c[1],c[2]*1.4,1.6); // EMP dazzle
HAZ.vision.push({x:c[0],y:c[1],r:c[2]*1.45,until:stats.t+2.4,m:0.36});
if(typeof addParticle==='function')
for(let k=0;k<10;k++)
addParticle(0,c[0]+rr(-c[2]*0.5,c[2]*0.5),c[1]+rr(-c[2]*0.5,c[2]*0.5),
0,rr(-40,-10),.45,10, 190,240,255);
}
shake=Math.max(shake,4);
if(typeof flashScreen==='function'&&HAZ.cells.length>2) flashScreen();
}
else if(HAZ.phase===3){ // relic pulse
const c=HAZ.cells[0];
spawnExplosion(c[0],c[1],c[2]*0.4,2);
hazHurt(c[0],c[1],c[2],hazDmg(150));
hazBlind(c[0],c[1],c[2]*1.15,4.5); // long systems blackout
HAZ.vision.push({x:c[0],y:c[1],r:c[2]*1.15,until:stats.t+5.2,m:0.32});
/* It eats power too β the basin punishes anyone who parks their grid on it. */
for(let t=0;t<2;t++) if(typeof resE!=='undefined') resE[t]=Math.max(0,resE[t]*0.88);
if(typeof addParticle==='function')
for(let k=0;k<20;k++){
const a=Math.random()*TAU;
addParticle(0,c[0]+Math.cos(a)*c[2]*0.5,c[1]+Math.sin(a)*c[2]*0.5,
Math.cos(a)*70,Math.sin(a)*70,.7,13, 200,150,255);
}
shake=Math.max(shake,7);
toast('β RELIC PULSE β systems down across the basin');
}
else if(HAZ.phase===4){ // magma eruption
for(const c of HAZ.cells){
spawnExplosion(c[0],c[1],c[2]*.75,2);
hazHurt(c[0],c[1],c[2]*1.08,hazDmg(320));
hazBlind(c[0],c[1],c[2]*1.3,2.4);
HAZ.vision.push({x:c[0],y:c[1],r:c[2]*1.35,until:stats.t+4,m:.5});
HAZ.lava.push({x:c[0],y:c[1],r:c[2]*.82,life:34+Math.random()*14,pulse:.2});
if(typeof deformTerrain==='function') deformTerrain(c[0],c[1],c[2]*.65,.055);
if(typeof addParticle==='function') for(let k=0;k<28;k++){
const a=Math.random()*TAU;
addParticle(k%3?0:1,c[0]+Math.cos(a)*rr(0,c[2]*.5),c[1]+Math.sin(a)*rr(0,c[2]*.5),
Math.cos(a)*rr(12,55),Math.sin(a)*rr(12,55),.6+Math.random(),
10+Math.random()*22,255,92,24);
}
}
shake=Math.max(shake,11);
if(typeof flashScreen==='function') flashScreen();
toast('π MAGMA ERUPTION β molten ground remains lethal');
if(typeof sfx==='function'&&HAZ.cells.length) sfx('boom',HAZ.cells[0][0],HAZ.cells[0][1],2.2);
}
else if(HAZ.phase===5){ // thermal / solray
for(const c of HAZ.cells){
spawnExplosion(c[0],c[1],c[2]*0.55,2);
hazHurt(c[0],c[1],c[2],hazDmg(180));
hazBlind(c[0],c[1],c[2]*1.15,2.8);
HAZ.vision.push({x:c[0],y:c[1],r:c[2]*1.2,until:stats.t+3.4,m:0.4});
}
shake=Math.max(shake,4);
toast('β¨ THERMAL SURGE β exposed ground is blinding');
}
else if(HAZ.phase===6){ // orbital debris
for(const c of HAZ.cells){
spawnExplosion(c[0],c[1],c[2]*0.7,2);
hazHurt(c[0],c[1],c[2],hazDmg(240));
hazBlind(c[0],c[1],c[2]*1.25,1.8);
HAZ.vision.push({x:c[0],y:c[1],r:c[2]*1.3,until:stats.t+2.6,m:0.38});
if(typeof addParticle==='function')
for(let k=0;k<12;k++)
addParticle(0,c[0]+rr(-c[2]*0.5,c[2]*0.5),c[1]+rr(-c[2]*0.5,c[2]*0.5),
0,rr(-50,-12),.5,12, 200,230,255);
}
shake=Math.max(shake,6);
if(typeof flashScreen==='function') flashScreen();
toast('β DEBRIS STRIKE β non-organic weather from orbit');
}
else if(HAZ.phase===7){ // flood surge
for(const c of HAZ.cells){
spawnExplosion(c[0],c[1],c[2]*0.55,2);
hazHurt(c[0],c[1],c[2],hazDmg(190));
hazBlind(c[0],c[1],c[2]*1.35,3.6);
HAZ.vision.push({x:c[0],y:c[1],r:c[2]*1.4,until:stats.t+4.2,m:0.40});
if(typeof addParticle==='function')
for(let k=0;k<14;k++)
addParticle(1,c[0]+rr(-c[2]*0.55,c[2]*0.55),c[1]+rr(-c[2]*0.55,c[2]*0.55),
rr(-10,10),rr(-28,-4),1.2,28+Math.random()*22, 70,190,220);
}
shake=Math.max(shake,5);
toast('β SURGE HIT β low ground is isolated');
}
HAZ.phase=0;
}
|