Spaces:
Running
Running
File size: 14,920 Bytes
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 | ;
;
/* ============================================================================
WAR TABLE PRIMER β first-run orientation for the deployment route
----------------------------------------------------------------------------
KEEL (src/tutorial.js) teaches the battlefield. Nothing taught the screen
the player has to get through BEFORE a battlefield exists: the war table's
galaxy -> system -> planet -> region -> deploy chain. A new commander who
opens it sees four stars, three of them locked, and no explanation of what
a "region" is or why there are twelve sites; the stepper across the top
names the five stages but not what any of them are for.
This module walks that chain once. It is deliberately NOT part of KEEL:
* KEEL is gated on live match state and only exists inside a drop. The
war table is a front screen with no match, no units and no sim to read.
* KEEL's panel is fixed-position HUD chrome. On this screen a floating
card would have to fight the stage stepper, the mode contract row and
the footer dock for the same narrow phone lane, and lose.
So the card is inserted INTO the active stage panel as its first child. It
scrolls with the content it describes, can never overlap chrome, and needs
no z-index or pointer-events rules at all. galaxyui.js rebuilds the system,
planet and region panels with innerHTML on every stage change, which would
delete the card β so the poll below re-inserts it whenever it has gone
missing, the same self-healing pattern tutorial.js uses to keep its card
alive across renderOps().
galaxyui.js is not rewritten. A one-line stage hook (wtpOnStage) plus a
takeover of openPlanetarySetup let this file compact the lecture after
Training and keep the two must-see cards alive across SKIP GUIDE.
============================================================================ */
(function(){
/* ---------------------------------------------------------------------------
THE SCRIPT β one card per stage, in the order the route walks them. Copy
names the real controls and the real numbers the screen prints ("TAP AN
UNLOCKED STAR", "4 REGIONS Β· 12 SITES", "3 BATTLEFIELD SITES") so the card
and the panel underneath it can never disagree.
--------------------------------------------------------------------------- */
var PRIMER_VERSION=2;
var STAGES=[
{ id:'galaxy', nm:'GALAXY',
tx:'Sombrero-I is the only open star. Andromeda, Orion and Helios stay locked β tap a locked chip and you remain on this galaxy, no warp. Drag the hologram, TAP AN UNLOCKED STAR, then ENTER SOMBRERO-I.' },
{ id:'system', nm:'SYSTEM',
tx:'One playable homeworld; other bodies on the rings are lore, not drops. The dossier reads 4 REGIONS Β· 12 SITES β clearing them unlocks the next star. Tap the homeworld, then ENTER AELOS.' },
{ id:'planet', nm:'PLANET',
tx:'The planet splits into four regions, each a different battlefield theme and hazard. Drag the globe or use the chips below it, then tap a region. Locked regions open as neighbouring ones are liberated.' },
{ id:'region', nm:'REGION',
tx:'Three sites per region, in order: COMPACT (_small, 2.2 km), STANDARD (_medium, 2.6 km), LARGE (_large, 3.2 km). A Standard War Room drop lands on the medium theatre β that is the map the mode is balanced around. Pick STANDARD / medium unless you want the short Compact fight.' },
{ id:'deploy', nm:'DEPLOY',
tx:'The deployment plan: commander, objective, threat, modifiers and payout, all editable below. START BATTLE sends the carrier β you still pick landing ground, then DEPLOY BASE HERE. In the drop the HUD pop counter reads n/1K; 1K is this commander\'s cap.' }
];
var CLOSER='That is the route. Twelve sites per system, and the next star opens when the last one falls.';
/* The two facts a first Standard visit must still teach after Training or
SKIP GUIDE. Optional stages (system / planet / deploy) may be dropped. */
var MUST_SEE=['galaxy','region'];
/* ---------------------------------------------------------------------------
STATE β rides the existing profile save, like META.tutorial does. This
module never writes its own storage blob.
--------------------------------------------------------------------------- */
function primerMeta(){
if(typeof META==='undefined'||!META) return {done:false,version:0,seen:{}};
META.warPrimer=META.warPrimer||{done:false,version:0,seen:{}};
if(!META.warPrimer.seen) META.warPrimer.seen={};
return META.warPrimer;
}
function isMust(id){
return MUST_SEE.indexOf(id)>=0;
}
function mustPending(){
var M=primerMeta();
for(var i=0;i<MUST_SEE.length;i++) if(!M.seen[MUST_SEE[i]]) return true;
return false;
}
function markSeen(id){
var M=primerMeta();
M.seen=M.seen||{};
if(id) M.seen[id]=true;
}
function markOptionalSeen(){
var M=primerMeta(),changed=false,i,id;
M.seen=M.seen||{};
for(i=0;i<STAGES.length;i++){
id=STAGES[i].id;
if(!isMust(id)&&!M.seen[id]){ M.seen[id]=true; changed=true; }
}
return changed;
}
function trainedCareer(){
/* Training / KEEL skip never open the war table. Compact to must-see so
Standard is not a five-card lecture after the field guide. */
if(typeof META==='undefined'||!META||!META.tutorial) return false;
var T=META.tutorial;
return !!(T.done||T.skipped);
}
function ensureMustSeeRoute(persist){
/* A prior SKIP GUIDE set done without recording the two facts. Re-open
only those cards β do not rebuild the five-stage lecture. Replay sets
forceFull so a Training graduate can still walk all five on purpose. */
var M=primerMeta(),changed=false;
if(M.forceFull) return false;
if(mustPending()&&(M.done||trainedCareer())) changed=markOptionalSeen();
if(changed&&persist&&typeof metaSave==='function') metaSave();
return changed;
}
function unseenLeft(){
var M=primerMeta(),i;
for(i=0;i<STAGES.length;i++) if(!M.seen[STAGES[i].id]) return true;
return false;
}
function visibleCount(id){
var M=primerMeta(),n=0,at=0,i,sid;
for(i=0;i<STAGES.length;i++){
sid=STAGES[i].id;
if(M.seen&&M.seen[sid]&&sid!==id) continue;
n++;
if(sid===id) at=n;
}
return {at:at,n:n,last:at===n};
}
function armed(){
ensureMustSeeRoute(false);
if(mustPending()) return true;
var M=primerMeta();
return !M.done||(M.version|0)<PRIMER_VERSION;
}
function finish(){
var M=primerMeta(),i;
M.done=true; M.version=PRIMER_VERSION; M.forceFull=false;
M.seen=M.seen||{};
/* finish() is the end of the route, never a skip. Must-see are already
seen when we get here; stamp the rest so a later load does not revive them. */
for(i=0;i<STAGES.length;i++) M.seen[STAGES[i].id]=true;
if(typeof metaSave==='function') metaSave();
var card=document.getElementById('wtpCard');
if(card) card.remove();
if(typeof sfx==='function'){ try{ sfx('ui'); }catch(e){} }
if(typeof renderSettings==='function'&&shown(document.getElementById('settingsScr'))) renderSettings();
}
function replay(){
var M=primerMeta();
M.done=false; M.version=0; M.seen={}; M.forceFull=true;
if(typeof metaSave==='function') metaSave();
if(typeof sfx==='function'){ try{ sfx('ui'); }catch(e){} }
if(typeof running!=='undefined'&&running){ if(typeof toast==='function') toast('War table primer armed β it shows on the next deployment'); }
else if(typeof showFrontScreen==='function') showFrontScreen('setupScr');
if(typeof renderSettings==='function') renderSettings();
}
/* ---------------------------------------------------------------------------
PLACEMENT
--------------------------------------------------------------------------- */
function shown(el){
if(!el||!el.isConnected) return false;
var s=getComputedStyle(el);
if(s.display==='none'||s.visibility==='hidden') return false;
var r=el.getBoundingClientRect();
return r.width>1&&r.height>1;
}
function activePanel(){
var scr=document.getElementById('setupScr');
if(!shown(scr)) return null;
var list=scr.querySelectorAll('.mfStagePanel.on');
for(var i=0;i<list.length;i++) if(shown(list[i])) return list[i];
return null;
}
function stageIdx(id){
for(var i=0;i<STAGES.length;i++) if(STAGES[i].id===id) return i;
return -1;
}
function dismissStage(){
/* GOT IT hides THIS stage only. The old DISMISS called finish() and wiped
the rest of the route β so a player who cleared the galaxy card to see
the hologram never learned that Standard is the medium theatre. */
var panel=activePanel(),id=panel&&panel.dataset.stage;
markSeen(id);
if(!unseenLeft()){ finish(); return; }
if(typeof metaSave==='function') metaSave();
var card=document.getElementById('wtpCard');
if(card) card.remove();
if(typeof sfx==='function'){ try{ sfx('ui'); }catch(e){} }
}
function skipGuide(){
/* SKIP GUIDE drops the optional lecture. It must not stamp done until the
two must-see cards have actually been on screen β otherwise SKIP on
galaxy erases the medium-theatre card. */
var panel=activePanel(),id=panel&&panel.dataset.stage;
markSeen(id);
markOptionalSeen();
if(!mustPending()){ finish(); return; }
if(typeof metaSave==='function') metaSave();
var card=document.getElementById('wtpCard');
if(card) card.remove();
if(typeof sfx==='function'){ try{ sfx('ui'); }catch(e){} }
}
function build(S,idx,last){
var vis=visibleCount(S.id);
var d=document.createElement('aside');
d.id='wtpCard';
d.className='wtpCard'+(last?' wtpLast':'');
d.dataset.stage=S.id;
d.setAttribute('aria-label','War table orientation');
d.innerHTML='<header><span>WAR TABLE ORIENTATION</span><b>'+vis.at+' / '+vis.n+' Β· '+S.nm+'</b></header>'
+'<p>'+S.tx+(last?' '+CLOSER:'')+'</p>'
+'<div class="wtpFoot"><i>'+STAGES.map(function(q,n){
return '<em class="'+(n===idx?'on':n<idx?'past':'')+'">'+q.nm+'</em>';
}).join('<u>βΊ</u>')+'</i>'
+'<button type="button" class="wtpDone">'+(last?'GOT IT Β· FINISH':'GOT IT')+'</button>'
+(last?'':'<button type="button" class="wtpSkip">SKIP GUIDE</button>')+'</div>';
var b=d.querySelector('.wtpDone');
/* mfBindTap is the codebase's touch-safe binder; fall back to click so an
OTA shell that predates it still gets a working dismiss. */
var doneFn=last?finish:dismissStage;
if(typeof mfBindTap==='function') mfBindTap(b,doneFn); else b.addEventListener('click',doneFn);
var sk=d.querySelector('.wtpSkip');
if(sk){ if(typeof mfBindTap==='function') mfBindTap(sk,skipGuide); else sk.addEventListener('click',skipGuide); }
return d;
}
function sync(){
if(!armed()){
var stale=document.getElementById('wtpCard');
if(stale) stale.remove();
return;
}
var panel=activePanel();
if(!panel) return;
var id=panel.dataset.stage,idx=stageIdx(id);
if(idx<0) return;
var M=primerMeta();
var card=document.getElementById('wtpCard');
if(M.seen&&M.seen[id]){
/* Optional stages already skipped still rebuild their panels. Drop a
leftover must-see card so it cannot ride a hidden galaxy panel. */
if(card) card.remove();
return;
}
/* Re-insert on ANY of: panel rebuilt beneath us, stage advanced, or the
card landing in a panel that is no longer the visible one. Comparing the
rendered stage against the panel's own dataset is what makes a rebuild
indistinguishable from a stage change here β both are just "the card
that should be on screen is not". */
if(card&&card.dataset.stage===id&&card.parentNode===panel) return;
if(card) card.remove();
var vis=visibleCount(id);
panel.insertBefore(build(STAGES[idx],idx,vis.last),panel.firstChild);
}
/* ---------------------------------------------------------------------------
SETTINGS β one replay row, appended after renderSettings() rebuilds
#setList. src/game/meta.js replaces that list's entire innerHTML on every
call, so the row has to be re-appended each time rather than inserted once;
src/tutorial.js already wraps the same function for its own two rows and
this wrap simply chains onto that one.
--------------------------------------------------------------------------- */
function appendPrimerSettingsRow(){
var list=document.getElementById('setList');
if(!list||document.getElementById('wtpSetRow')) return;
var M=primerMeta(),done=!!M.done&&(M.version|0)>=PRIMER_VERSION&&!mustPending();
var row=document.createElement('div');
row.className='sItem setRow';
row.id='wtpSetRow';
row.setAttribute('role','button');
row.setAttribute('tabindex','0');
row.innerHTML='<div class="sTx"><b>πΊ War Table Primer</b><div class="sDs">'
+(done?'Seen':'Armed for the next visit')
+' β walks galaxy, system, planet, region and deployment</div></div>'
+'<div class="sBuy">'+(done?'βΊ REPLAY':'ARMED')+'</div>';
var act=function(ev){ if(ev){ ev.preventDefault(); } if(done) replay(); };
row.addEventListener('pointerdown',act);
row.addEventListener('keydown',function(ev){ if(ev.key==='Enter'||ev.key===' ') act(ev); });
list.appendChild(row);
}
if(typeof renderSettings==='function'&&!window.__wtpSettingsWrapped){
window.__wtpSettingsWrapped=true;
var _wtpRenderSettings=renderSettings;
renderSettings=function(){ _wtpRenderSettings(); appendPrimerSettingsRow(); };
}
/* ---------------------------------------------------------------------------
BOOT β poll keeps the card alive across galaxyui innerHTML rebuilds. The
Standard entry wrap (takeover, after initGalaxyUI) and wtpOnStage hook
make the first paint immediate so SKIP / Training compact is not a 320 ms
late.
--------------------------------------------------------------------------- */
function onStandardOpen(){
ensureMustSeeRoute(true);
try{ sync(); }catch(e){}
}
function wrapStandardEntry(){
if(window.__wtpOpenWrapped) return;
var prev=window.openPlanetarySetup;
if(typeof prev!=='function') return;
window.__wtpOpenWrapped=true;
window.openPlanetarySetup=function(mode){
var r=prev.apply(this,arguments);
if(mode==='standard') onStandardOpen();
return r;
};
}
function initWarPrimer(){
if(window.__wtpInit) return;
window.__wtpInit=true;
wrapStandardEntry();
ensureMustSeeRoute(true);
setInterval(function(){ try{ sync(); }catch(e){ if(window.console) console.error('war primer',e); } },320);
}
window.initWarPrimer=initWarPrimer;
window.wtpEnsureMustSee=function(){ return ensureMustSeeRoute(true); };
window.wtpOnStage=function(){ try{ sync(); }catch(e){} };
/* Read-only inspection handle for verification scripts, matching the
__tutDebug hook src/tutorial.js already exposes. */
window.__wtpDebug=function(){ return {STAGES:STAGES,MUST_SEE:MUST_SEE,armed:armed(),
mustPending:mustPending(),trained:trainedCareer(),meta:primerMeta(),
stage:(activePanel()||{dataset:{}}).dataset.stage||'',
finish:finish,replay:replay,skipGuide:skipGuide}; };
})();
|