minigame 3 Tower block 1.1
@@ -68,6 +68,11 @@ QUESTION_IMG="$DST/Game/public/img/QUESTION"
|
||||
mkdir -p "$QUESTION_IMG"
|
||||
chown www-data:www-data "$QUESTION_IMG"
|
||||
chmod 2775 "$QUESTION_IMG"
|
||||
# Stack (Tower block) — ชื่อโฟลเดอร์ไม่มีช่องว่าง (FTP MKD "Tower block" มัก 550)
|
||||
TOWER_BLOCK_IMG="$DST/Game/public/img/TowerBlock"
|
||||
mkdir -p "$TOWER_BLOCK_IMG"
|
||||
chown www-data:www-data "$TOWER_BLOCK_IMG"
|
||||
chmod 2775 "$TOWER_BLOCK_IMG"
|
||||
|
||||
GAME_SRV_HASH_AFTER="$(hash_game_server_js "$GAME_SERVER_JS")"
|
||||
if [[ -n "$GAME_SRV_HASH_AFTER" && "$GAME_SRV_HASH_BEFORE" != "$GAME_SRV_HASH_AFTER" ]]; then
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
<button type="button" class="tab" data-tab="change-password" role="tab" id="tab-change-password" aria-selected="false" aria-controls="tab-panel-change-password"><span class="tab-label">รหัสผ่าน</span><span class="tab-desc tab-desc--wide">เปลี่ยนรหัสของคุณ · กดซ้ำเพื่อปิด</span></button>
|
||||
<button type="button" class="tab is-active" data-tab="oauth" role="tab" id="tab-oauth" aria-selected="true" aria-controls="tab-panel-oauth"><span class="tab-label">OAuth</span><span class="tab-desc">Facebook / Google</span></button>
|
||||
<button type="button" class="tab" data-tab="map-editor" role="tab" id="tab-map-editor" aria-controls="tab-panel-map-editor"><span class="tab-label">Editor</span><span class="tab-desc">แผนที่ฉาก</span></button>
|
||||
<button type="button" class="tab" data-tab="quiz" role="tab" id="tab-quiz" aria-controls="tab-panel-quiz"><span class="tab-label">Minigame-1-จริงหรือไม่</span><span class="tab-desc" aria-hidden="true"></span></button>
|
||||
<button type="button" class="tab" data-tab="quiz" role="tab" id="tab-quiz" aria-controls="tab-panel-quiz"><span class="tab-heading-row"><img class="tab-heading-icon" src="/Game/img/QUESTION/admin-tab-minigame-1.png" width="22" height="22" alt="" decoding="async"><span class="tab-label">Minigame-1-จริงหรือไม่</span></span><span class="tab-desc" aria-hidden="true"></span></button>
|
||||
<button type="button" class="tab" data-tab="game-timing" role="tab" id="tab-game-timing" aria-controls="tab-panel-game-timing"><span class="tab-heading-row"><img class="tab-heading-icon" src="/Game/img/gauntlet-assets/gauntlet-dd51baed17270995.png" width="22" height="22" alt="" decoding="async"><span class="tab-label">Minigame-2-วิ่งหลบสิ่งกีดขวาง</span></span><span class="tab-desc" aria-hidden="true"></span></button>
|
||||
<button type="button" class="tab" data-tab="stack-game" role="tab" id="tab-stack-game" aria-controls="tab-panel-stack-game"><span class="tab-label">Minigame-3-Tower block</span><span class="tab-desc" aria-hidden="true"></span></button>
|
||||
<button type="button" class="tab" data-tab="quiz-carry" role="tab" id="tab-quiz-carry" aria-controls="tab-panel-quiz-carry"><span class="tab-heading-row"><img class="tab-heading-icon tab-heading-icon--wide" src="/Game/img/quiz-carry/admin-tab-minigame-4.png" width="32" height="22" alt="" decoding="async"><span class="tab-label">Minigame-4-คำศัพท์ในกระบวนการยุติธรรม</span></span><span class="tab-desc" aria-hidden="true"></span></button>
|
||||
|
||||
@@ -292,7 +292,7 @@
|
||||
<li><strong>พื้นที่เริ่มเกม</strong> (ห้องโถง — ส้ม) — host ยืนแล้วกดเริ่ม</li>
|
||||
<li><strong>ถามตอบ</strong>: โซนถูก/ผิด + พื้นที่คำถาม (ทอง)</li>
|
||||
<li><strong>หยิบมาวาง</strong>: ม่วง = โซนกลาง (กำแพง) · <strong>ทอง</strong> = พื้นที่โชว์ข้อความคำถามบนแผนที่ (ถ้าไม่วาดทอง ข้อความไปที่โซนม่วง) · <strong>interactive เขียว</strong> = ยืนแล้วกด F ส่งคำตอบ · โหมดวาด <strong>ตัวเลือก 1–16</strong> = ช่องบนกริดตรงกับลำดับ <code>choices</code> · พรีวิวนับ <strong>3-2-1</strong> ไฮไลต์โซนข้อถูก (หรือใส่ <code>countdownHighlightSlot</code> 1–16 ต่อข้อใน <code>quizQuestions</code>) · <code>correctIndex</code> / <code>answerTrue</code></li>
|
||||
<li><strong>Stack</strong>: โหมดวาดจุดปล่อย (ฟ้า) · จุดซ้อนตึก (ชมพู)</li>
|
||||
<li><strong>Stack</strong>: โหมดวาดจุดปล่อย (ฟ้า) · จุดซ้อนตึก (ชมพู) · ถ้าอัปโหลดรูปทาง FTP ใช้โฟลเดอร์ <code>Game/public/img/TowerBlock</code> (ไม่มีช่องว่าง) — ชื่อ <code>Tower block</code> มี space มักได้ FTP <strong>550</strong></li>
|
||||
<li><strong>กระโดดให้รอด</strong>: โหมด <strong>แพลตฟอร์ม</strong> (ฟ้าอมเขียว) · กำแพง = ขอบซ้ายขวา/บน · Space / W = กระโดด</li>
|
||||
<li><strong>ลูกโป้งยิงบอส</strong>: จุดเกิดผู้เล่น P1–P6 + <strong>จุดเกิดบอส</strong> 1 ช่อง · ในเกมเดินช้า ยิงมีดีเลย์ · ลูกโป้งหมด = ตกรอบ</li>
|
||||
<li><strong>สีช่อง</strong>: เลือกโหมดสี + Alpha แล้วคลิกบนกริด</li>
|
||||
@@ -307,7 +307,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<script src="js/version.js?v=0.0169"></script>
|
||||
<script src="js/editor.js?v=20260427-bg-down-start"></script>
|
||||
<script src="js/editor.js?v=20260502-towerblock-ftp-hint"></script>
|
||||
<div class="version-tag">v —</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -10,10 +10,11 @@ QUESTION — รูปสำหรับฉาก / อัปโหลด
|
||||
- **hud-question-plaque.png** — (ทางเลือก) แผ่นพื้นหลังข้อความคำถามใต้ TIME ตาม mock
|
||||
- **score+.png** — เอฟเฟกต์ “+10” กลางโซนคำตอบที่ถูก (SAFE หรือ SCAM) เมื่อมีผู้เล่นตอบถูก · คะแนนต่อข้อ = 10 แต้ม (สอดคล้อง `QUIZ_TF_POINTS_PER_CORRECT` บนเซิร์ฟเวอร์)
|
||||
- **result-Lose_stamp.png** — แสตมป์ทับอวาตาร์ในหน้าสรุปภารกิจ (ฉาก quiz แบบ mission) สำหรับผู้เล่นที่เคยตอบผิดในรอบนั้น · ถ้าไฟล์หายระบบ fallback ไป `/Game/img/gauntlet-assets/result-Lose_stamp.png`
|
||||
- **admin-tab-minigame-1.png** — ไอคอนแท็บ Admin «Minigame-1-จริงหรือไม่» (เครื่องหมายถูก neon เขียวบนพื้นดำ)
|
||||
|
||||
ถ้าไฟล์หาย ระบบ fallback ไป `/Game/img/gauntlet-assets/` ชื่อเดียวกัน (เฉพาะ popup-*) · แผ่น HUD ถ้าไม่มีไฟล์จะใช้ข้อความล้วน
|
||||
|
||||
English: Map **mng8a80o** uses mission UI; place **popup-Howto.png** and **popup-result.png** here. Optional HUD plates: **hud-time-plaque.png**, **hud-question-plaque.png**. For correct-answer FX: **score+.png** (+10 popup over the correct zone). For mission summary wrong-answer stamp on avatars: **result-Lose_stamp.png** (falls back to gauntlet-assets if missing).
|
||||
English: Map **mng8a80o** uses mission UI; place **popup-Howto.png** and **popup-result.png** here. Optional HUD plates: **hud-time-plaque.png**, **hud-question-plaque.png**. For correct-answer FX: **score+.png** (+10 popup over the correct zone). For mission summary wrong-answer stamp on avatars: **result-Lose_stamp.png** (falls back to gauntlet-assets if missing). Admin True/False tab icon: **admin-tab-minigame-1.png**.
|
||||
|
||||
โฟลเดอร์นี้ถูกสร้างจาก repo + สคริปต์ deploy เพื่อหลีกเลี่ยงข้อผิดพลาด FTP:
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 526 B |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 2.7 MiB |
|
After Width: | Height: | Size: 3.9 MiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 23 KiB |
@@ -0,0 +1,26 @@
|
||||
Tower block (Stack) — รูปสำหรับฉาก / FTP
|
||||
========================================
|
||||
|
||||
โหมดเกม **stack** (สลับจังหวะลงตึก) — ถ้าอัปโหลดรูปผ่าน **FTP** ให้ใช้โฟลเดอร์นี้เท่านั้น:
|
||||
|
||||
**`/Game/public/img/TowerBlock`** (ตัวพิมพ์ใหญ่ T และ B — **ไม่มีช่องว่าง**)
|
||||
|
||||
อย่าใช้ชื่อ **`Tower block`** (มีช่องว่าง) — บนเซิร์ฟหลายแบบคำสั่ง FTP `MKD "Tower block"` จะได้ **550 Create directory operation failed** เหมือนกรณีโฟลเดอร์ "Violent Crime" / ชื่อมี space อื่น ๆ
|
||||
|
||||
English: Upload Stack / tower assets under **`TowerBlock`** (no spaces). **`Tower block`** often fails with FTP **550**.
|
||||
|
||||
โฟลเดอร์นี้ถูกสร้างตอนรัน `scripts/deploy-www.sh` (mkdir + www-data + setgid 2775) เพื่อให้อัปโหลดได้ทันที
|
||||
|
||||
ถ้า FTP ยัง 550: ตรวจว่าเชื่อมที่โฟลเดอร์ **public/img** ใต้ document root (เช่น `/var/www/html/Game/public/img/TowerBlock`) ไม่ใช่ chroot คนละระดับกับ `/Game/...`
|
||||
|
||||
---
|
||||
|
||||
ฉากภารกิจ Stack (editor id **mnn93hpi**) — `play.js` โหลดจาก `/Game/img/TowerBlock/` พร้อม fallback ไป `/Game/img/gauntlet-assets/` ถ้าไฟล์หาย
|
||||
|
||||
| ไฟล์ | ใช้ |
|
||||
|------|------|
|
||||
| `popup-Howto.png` | แผง HOW TO PLAY + พื้นหลังช่วงนับถอยหลัง |
|
||||
| `popup-result.png` | แผงสรุปภารกิจ (GCM) |
|
||||
| `result-check.png` (ถ้ามี) | ไอคอนติ๊ก «ภารกิจสำเร็จ» บนสรุป — ไม่มีจะใช้ของ gauntlet |
|
||||
|
||||
English: Mission map **mnn93hpi** loads **`popup-Howto.png`**, **`popup-result.png`**, optional **`result-check.png`** from this folder.
|
||||
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 8.8 KiB |
|
After Width: | Height: | Size: 515 KiB |
|
After Width: | Height: | Size: 203 B |
|
After Width: | Height: | Size: 236 B |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 397 B |
|
After Width: | Height: | Size: 484 B |
|
After Width: | Height: | Size: 481 B |
|
After Width: | Height: | Size: 507 B |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 473 B |
|
After Width: | Height: | Size: 485 B |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 256 B |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 8.1 KiB |
|
After Width: | Height: | Size: 8.0 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 290 B |
@@ -27,6 +27,9 @@
|
||||
const SPACE_SHOOTER_MISSION_MAP_ID = 'mnpz6rkp';
|
||||
/** Quiz ฉากสอบสวน — flow เดียวกับ crown/howto แต่รูปใน /img/QUESTION/ */
|
||||
const QUIZ_QUESTION_MISSION_MAP_ID = 'mng8a80o';
|
||||
/** Stack ซ้อนตึก — ฉากภารกิจ (HOWTO → นับถอยหลัง → เล่น → สรุป) รูปใน `/Game/img/TowerBlock` */
|
||||
const STACK_TOWER_MISSION_MAP_ID = 'mnn93hpi';
|
||||
const STACK_TOWER_MISSION_TIME_SEC = 90;
|
||||
/** Violent Crime mission: asteroid strikes ship → invuln → 3 strikes = eliminated */
|
||||
const SPACE_SHOOTER_MISSION_MAX_ASTEROID_HITS = 3;
|
||||
const SPACE_SHOOTER_MISSION_HIT_INVULN_MS = 1400;
|
||||
@@ -1127,6 +1130,12 @@
|
||||
let quizQuestionMissionPhase = null;
|
||||
let quizQuestionMissionCountdownTimer = null;
|
||||
let quizQuestionMissionDeferredPhase = null;
|
||||
/** Stack Tower (mnn93hpi) — flow เดียวกับ crown / quiz mission */
|
||||
let stackTowerMissionPhase = null;
|
||||
let stackTowerMissionCountdownTimer = null;
|
||||
let stackTowerMissionDeferredPhase = null;
|
||||
let stackTowerSessionStartAt = 0;
|
||||
let stackTowerMissionEndedOnce = false;
|
||||
|
||||
function spaceShooterSpawnAsteroidExplosion(worldX, worldY, r) {
|
||||
const urls = playSpaceShooterAsteroidSpriteUrls;
|
||||
@@ -3100,6 +3109,323 @@
|
||||
return quizQuestionMissionPhase != null && quizQuestionMissionPhase !== 'live' && quizQuestionMissionPhase !== 'ended';
|
||||
}
|
||||
|
||||
function isStackTowerMissionUiMapPlay() {
|
||||
return isStack() && currentPlayMapId() === STACK_TOWER_MISSION_MAP_ID;
|
||||
}
|
||||
|
||||
function isStackTowerMissionPregameBlockingPlay() {
|
||||
if (!isStackTowerMissionUiMapPlay()) return false;
|
||||
return stackTowerMissionPhase != null && stackTowerMissionPhase !== 'live' && stackTowerMissionPhase !== 'ended';
|
||||
}
|
||||
|
||||
function isStackTowerMissionHudActivePlay() {
|
||||
return isStackTowerMissionUiMapPlay() && stackTowerMissionPhase === 'live';
|
||||
}
|
||||
|
||||
function stackTowerAssetUrl(file) {
|
||||
return BASE + '/img/TowerBlock/' + String(file || '').replace(/^\/+/, '');
|
||||
}
|
||||
|
||||
function teardownStackTowerMissionUiPlay() {
|
||||
stackTowerMissionPhase = null;
|
||||
stackTowerMissionDeferredPhase = null;
|
||||
stackTowerSessionStartAt = 0;
|
||||
stackTowerMissionEndedOnce = false;
|
||||
if (stackTowerMissionCountdownTimer) {
|
||||
clearTimeout(stackTowerMissionCountdownTimer);
|
||||
stackTowerMissionCountdownTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
function stackTowerDecryptPctPlay() {
|
||||
if (!stackMini) return 0;
|
||||
const teamScore = stackMini.score || 0;
|
||||
return Math.min(100, Math.floor((stackMini.layers.length || 0) * 11 + Math.min(40, teamScore * 3)));
|
||||
}
|
||||
|
||||
function stackTowerMissionElapsedSecPlay() {
|
||||
if (stackTowerSessionStartAt <= 0) return 0;
|
||||
return (performance.now() - stackTowerSessionStartAt) / 1000;
|
||||
}
|
||||
|
||||
function applyStackTowerMissionPanelImages() {
|
||||
const howtoBg = document.querySelector('#gauntlet-crown-howto-overlay .gch-bg');
|
||||
if (howtoBg) {
|
||||
howtoBg.src = stackTowerAssetUrl('popup-Howto.png');
|
||||
howtoBg.onerror = function () {
|
||||
this.onerror = null;
|
||||
this.src = BASE + '/img/gauntlet-assets/popup-Howto.png';
|
||||
};
|
||||
}
|
||||
const resBg = document.querySelector('#gauntlet-crown-mission-overlay .gcm-bg');
|
||||
if (resBg) {
|
||||
resBg.src = stackTowerAssetUrl('popup-result.png');
|
||||
resBg.onerror = function () {
|
||||
this.onerror = null;
|
||||
this.src = BASE + '/img/gauntlet-assets/popup-result.png';
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function showStackTowerMissionHowtoOverlay() {
|
||||
if (!isStackTowerMissionUiMapPlay()) return;
|
||||
hideConflictingOverlaysForGauntletCrown();
|
||||
applyStackTowerMissionPanelImages();
|
||||
if (stackTowerMissionCountdownTimer) {
|
||||
clearTimeout(stackTowerMissionCountdownTimer);
|
||||
stackTowerMissionCountdownTimer = null;
|
||||
}
|
||||
const cd = document.getElementById('gauntlet-crown-countdown');
|
||||
if (cd) cd.classList.add('is-hidden');
|
||||
stackTowerMissionPhase = 'howto';
|
||||
const ov = document.getElementById('gauntlet-crown-howto-overlay');
|
||||
if (!ov) return;
|
||||
gauntletCrownHowtoVisible = true;
|
||||
ov.classList.remove('is-hidden');
|
||||
const st = document.getElementById('gauntlet-crown-howto-status');
|
||||
if (st) {
|
||||
st.textContent = '';
|
||||
st.classList.remove('gch-status--jumper');
|
||||
st.classList.add('is-hidden');
|
||||
}
|
||||
const btn = document.getElementById('btn-gch-ready');
|
||||
if (btn) {
|
||||
const humans = quizCarryPregameHumanIds();
|
||||
const soleHuman = humans.length === 1;
|
||||
const canGo = soleHuman || isMePlayHost();
|
||||
btn.classList.remove('is-start-phase');
|
||||
btn.classList.toggle('is-read-only', !canGo);
|
||||
btn.disabled = !canGo;
|
||||
btn.title = canGo ? 'READY — เริ่มนับถอยหลัง' : 'รอโฮสต์ · Wait for host';
|
||||
btn.setAttribute('aria-pressed', 'false');
|
||||
}
|
||||
}
|
||||
|
||||
function beginStackTowerMissionCountdownThenRun() {
|
||||
if (!isStackTowerMissionUiMapPlay()) return;
|
||||
if (stackTowerMissionCountdownTimer) {
|
||||
clearTimeout(stackTowerMissionCountdownTimer);
|
||||
stackTowerMissionCountdownTimer = null;
|
||||
}
|
||||
stackTowerMissionPhase = 'countdown';
|
||||
const howto = document.getElementById('gauntlet-crown-howto-overlay');
|
||||
if (howto) howto.classList.add('is-hidden');
|
||||
gauntletCrownHowtoVisible = false;
|
||||
const st = document.getElementById('gauntlet-crown-howto-status');
|
||||
if (st) {
|
||||
st.classList.add('is-hidden');
|
||||
st.classList.remove('gch-status--jumper');
|
||||
}
|
||||
const cd = document.getElementById('gauntlet-crown-countdown');
|
||||
const numEl = document.getElementById('gauntlet-crown-countdown-num');
|
||||
const runFinish = function () {
|
||||
if (cd) cd.classList.add('is-hidden');
|
||||
if (stackTowerMissionCountdownTimer) {
|
||||
clearTimeout(stackTowerMissionCountdownTimer);
|
||||
stackTowerMissionCountdownTimer = null;
|
||||
}
|
||||
stackTowerMissionPhase = 'live';
|
||||
stackTowerMissionEndedOnce = false;
|
||||
stackTowerSessionStartAt = performance.now();
|
||||
lastStackTickMs = performance.now();
|
||||
resetStackMinigameState();
|
||||
};
|
||||
if (!cd || !numEl) {
|
||||
runFinish();
|
||||
return;
|
||||
}
|
||||
cd.classList.remove('is-hidden');
|
||||
let n = 3;
|
||||
numEl.textContent = String(n);
|
||||
const step = function () {
|
||||
n--;
|
||||
if (n > 0) {
|
||||
numEl.textContent = String(n);
|
||||
stackTowerMissionCountdownTimer = setTimeout(step, 1000);
|
||||
} else {
|
||||
stackTowerMissionCountdownTimer = null;
|
||||
runFinish();
|
||||
}
|
||||
};
|
||||
stackTowerMissionCountdownTimer = setTimeout(step, 1000);
|
||||
}
|
||||
|
||||
function stackTowerMissionBuildPayload(endOpts) {
|
||||
const outcome = endOpts && endOpts.outcome ? String(endOpts.outcome) : 'decrypt_complete';
|
||||
const rows = [];
|
||||
const teamPts = Math.max(0, Number(stackMini && stackMini.score) || 0);
|
||||
function pushEnt(id, nickname, characterId, score) {
|
||||
rows.push({
|
||||
id: id,
|
||||
nickname: (nickname && String(nickname).trim()) ? String(nickname).trim() : String(id),
|
||||
characterId: characterId ?? null,
|
||||
baseScore: Math.max(0, Number(score) || 0),
|
||||
eliminated: false,
|
||||
hadQuizWrong: false,
|
||||
rankBonus: 0,
|
||||
finalScore: Math.max(0, Number(score) || 0),
|
||||
});
|
||||
}
|
||||
if (previewFillBots && stackPreviewTurnOrder && stackPreviewTurnOrder.length === STACK_PREVIEW_TURN_COUNT) {
|
||||
if (myId != null) {
|
||||
pushEnt(myId, (me.nickname || nick || 'คุณ').trim() || 'คุณ', me.characterId || getPlayCharacterId(), me.stackPreviewHumanPts || 0);
|
||||
}
|
||||
stackPreviewTurnOrder.forEach(function (entry) {
|
||||
if (!entry || entry.kind !== 'bot' || !entry.botId) return;
|
||||
const o = others.get(entry.botId);
|
||||
pushEnt(entry.botId, o ? o.nickname : entry.botId, o ? o.characterId : null, o ? (o.stackBotScore || 0) : 0);
|
||||
});
|
||||
} else {
|
||||
if (myId != null) {
|
||||
pushEnt(myId, (me.nickname || nick || 'คุณ').trim() || 'คุณ', me.characterId || getPlayCharacterId(), teamPts);
|
||||
}
|
||||
others.forEach(function (o, id) {
|
||||
if (!o) return;
|
||||
if (previewFillBots && isPreviewBotId(id)) return;
|
||||
pushEnt(id, o.nickname, o.characterId, teamPts);
|
||||
});
|
||||
}
|
||||
const seen = new Set();
|
||||
const uniq = [];
|
||||
rows.forEach(function (r) {
|
||||
const sid = String(r.id);
|
||||
if (seen.has(sid)) return;
|
||||
seen.add(sid);
|
||||
uniq.push(r);
|
||||
});
|
||||
uniq.sort(function (a, b) {
|
||||
if (b.baseScore !== a.baseScore) return b.baseScore - a.baseScore;
|
||||
return String(a.nickname).localeCompare(String(b.nickname), 'th') || String(a.id).localeCompare(String(b.id));
|
||||
});
|
||||
const top = uniq.slice(0, 5);
|
||||
const ranked = top.map(function (row, idx) {
|
||||
const pos = idx + 1;
|
||||
return {
|
||||
id: row.id,
|
||||
nickname: row.nickname,
|
||||
characterId: row.characterId,
|
||||
baseScore: row.baseScore,
|
||||
eliminated: false,
|
||||
hadQuizWrong: false,
|
||||
rank: pos,
|
||||
rankLabel: pos === 1 ? '1st' : pos === 2 ? '2nd' : pos === 3 ? '3rd' : String(pos),
|
||||
rankBonus: 0,
|
||||
finalScore: row.baseScore,
|
||||
};
|
||||
});
|
||||
const totalParts = ranked.map(function (r) { return r.baseScore; });
|
||||
const totalSum = totalParts.reduce(function (s, n) { return s + n; }, 0);
|
||||
const n = ranked.length || 1;
|
||||
const averageScore = Math.floor(totalSum / n);
|
||||
let grade = averageScore >= 80 ? 'A' : averageScore >= 60 ? 'B' : 'C';
|
||||
if (totalSum <= 0 && uniq.length) grade = 'F';
|
||||
const rewardCard = grade === 'F' ? null : gauntletCrownRollRewardCardLocal(grade);
|
||||
return {
|
||||
ranked: ranked,
|
||||
totalSum: totalSum,
|
||||
averageScore: averageScore,
|
||||
grade: grade,
|
||||
rewardCard: rewardCard,
|
||||
totalParts: totalParts,
|
||||
uiSkin: 'stack_tower',
|
||||
survivorCount: uniq.length,
|
||||
participantCount: uniq.length,
|
||||
stackTowerOutcome: outcome,
|
||||
};
|
||||
}
|
||||
|
||||
function stackTowerMissionMergePreviewBotsMission(mission) {
|
||||
if (!mission || mission.uiSkin !== 'stack_tower' || !previewFillBots || !others || typeof others.forEach !== 'function') return mission;
|
||||
const seen = new Set();
|
||||
(mission.ranked || []).forEach(function (r) {
|
||||
if (r && r.id != null) seen.add(String(r.id));
|
||||
});
|
||||
const baseRows = (mission.ranked || []).map(function (r) {
|
||||
return {
|
||||
id: r.id,
|
||||
nickname: r.nickname,
|
||||
characterId: r.characterId,
|
||||
baseScore: Math.max(0, Number(r.baseScore) || 0),
|
||||
eliminated: false,
|
||||
hadQuizWrong: false,
|
||||
rankBonus: 0,
|
||||
finalScore: Math.max(0, Number(r.finalScore != null ? r.finalScore : r.baseScore) || 0),
|
||||
};
|
||||
});
|
||||
others.forEach(function (o, id) {
|
||||
if (!o || !isPreviewBotId(id)) return;
|
||||
const sid = String(id);
|
||||
if (seen.has(sid)) return;
|
||||
seen.add(sid);
|
||||
baseRows.push({
|
||||
id: id,
|
||||
nickname: (o.nickname && String(o.nickname).trim()) ? String(o.nickname).trim() : sid,
|
||||
characterId: o.characterId ?? null,
|
||||
baseScore: Math.max(0, Number(o.stackBotScore) || 0),
|
||||
eliminated: false,
|
||||
hadQuizWrong: false,
|
||||
rankBonus: 0,
|
||||
finalScore: Math.max(0, Number(o.stackBotScore) || 0),
|
||||
});
|
||||
});
|
||||
baseRows.sort(function (a, b) {
|
||||
if (b.baseScore !== a.baseScore) return b.baseScore - a.baseScore;
|
||||
return String(a.nickname).localeCompare(String(b.nickname), 'th') || String(a.id).localeCompare(String(b.id));
|
||||
});
|
||||
const top = baseRows.slice(0, 5);
|
||||
const ranked = top.map(function (row, idx) {
|
||||
const pos = idx + 1;
|
||||
const bs = Math.max(0, Number(row.baseScore) || 0);
|
||||
return {
|
||||
id: row.id,
|
||||
nickname: row.nickname,
|
||||
characterId: row.characterId,
|
||||
baseScore: bs,
|
||||
eliminated: false,
|
||||
hadQuizWrong: false,
|
||||
rank: pos,
|
||||
rankLabel: pos === 1 ? '1st' : pos === 2 ? '2nd' : pos === 3 ? '3rd' : String(pos),
|
||||
rankBonus: 0,
|
||||
finalScore: bs,
|
||||
};
|
||||
});
|
||||
const totalParts = ranked.map(function (r) { return r.baseScore; });
|
||||
const totalSum = totalParts.reduce(function (s, n) { return s + n; }, 0);
|
||||
const n2 = ranked.length || 1;
|
||||
const averageScore = Math.floor(totalSum / n2);
|
||||
let grade = averageScore >= 80 ? 'A' : averageScore >= 60 ? 'B' : 'C';
|
||||
if (totalSum <= 0 && baseRows.length) grade = 'F';
|
||||
const rewardCard = grade === 'F' ? null : gauntletCrownRollRewardCardLocal(grade);
|
||||
return {
|
||||
ranked: ranked,
|
||||
totalSum: totalSum,
|
||||
averageScore: averageScore,
|
||||
grade: grade,
|
||||
rewardCard: rewardCard,
|
||||
totalParts: totalParts,
|
||||
uiSkin: 'stack_tower',
|
||||
survivorCount: baseRows.length,
|
||||
participantCount: baseRows.length,
|
||||
stackTowerOutcome: mission.stackTowerOutcome,
|
||||
};
|
||||
}
|
||||
|
||||
function stackTowerMissionMaybeEndPlay() {
|
||||
if (!isStackTowerMissionUiMapPlay()) return;
|
||||
if (stackTowerMissionPhase !== 'live') return;
|
||||
if (stackTowerMissionEndedOnce) return;
|
||||
const pct = stackTowerDecryptPctPlay();
|
||||
const elapsed = stackTowerMissionElapsedSecPlay();
|
||||
if (pct >= 100 || elapsed >= STACK_TOWER_MISSION_TIME_SEC) {
|
||||
stackTowerMissionEndedOnce = true;
|
||||
stackTowerMissionPhase = 'ended';
|
||||
applyStackTowerMissionPanelImages();
|
||||
showGauntletCrownMissionOverlay(stackTowerMissionBuildPayload({
|
||||
outcome: pct >= 100 ? 'decrypt_complete' : 'time_up',
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
function isJumpSurviveMissionUiMapPlay() {
|
||||
return isJumpSurvive() && currentPlayMapId() === JUMP_SURVIVE_MISSION_MAP_ID;
|
||||
}
|
||||
@@ -6208,6 +6534,7 @@
|
||||
/** @returns {boolean} เริ่มแอนิเมชันร่วงแล้วหรือไม่ */
|
||||
function tryHumanStackDrop() {
|
||||
if (!stackMini || !isStack() || stackFall || stackMini.settling) return false;
|
||||
if (isStackTowerMissionUiMapPlay() && stackTowerMissionPhase !== 'live') return false;
|
||||
if (previewFillBots && mapData && mapData.gameType === 'stack' &&
|
||||
(!stackPreviewTurnOrder || stackPreviewTurnOrder.length !== STACK_PREVIEW_TURN_COUNT)) {
|
||||
rebuildStackPreviewTurnOrder();
|
||||
@@ -6237,6 +6564,7 @@
|
||||
/** Stack preview: สลับตา — เฉพาะบอทที่ถึงตาถึงจะจำลองปล่อยหนึ่งครั้งแล้วเลื่อนตา */
|
||||
function stepPreviewStackTurnBased(nowMs) {
|
||||
if (!previewFillBots || !isStack() || !stackMini || stackFall || stackMini.settling) return;
|
||||
if (isStackTowerMissionUiMapPlay() && stackTowerMissionPhase !== 'live') return;
|
||||
if (mapData && mapData.gameType === 'stack' &&
|
||||
(!stackPreviewTurnOrder || stackPreviewTurnOrder.length !== STACK_PREVIEW_TURN_COUNT)) {
|
||||
rebuildStackPreviewTurnOrder();
|
||||
@@ -6264,6 +6592,7 @@
|
||||
}
|
||||
|
||||
function stackTickFrame() {
|
||||
stackTowerMissionMaybeEndPlay();
|
||||
const now = performance.now();
|
||||
const dt = Math.min(0.06, (now - lastStackTickMs) / 1000);
|
||||
lastStackTickMs = now;
|
||||
@@ -7003,7 +7332,7 @@
|
||||
const sc = Number(mission.survivorCount);
|
||||
return Number.isFinite(sc) ? sc > 0 : (String(mission.grade || '').trim().toUpperCase().charAt(0) !== 'F');
|
||||
}
|
||||
if (mission.uiSkin === 'question_mission') {
|
||||
if (mission.uiSkin === 'question_mission' || mission.uiSkin === 'stack_tower') {
|
||||
const g = String(mission.grade || '').trim().toUpperCase().charAt(0);
|
||||
return g !== 'F';
|
||||
}
|
||||
@@ -7040,7 +7369,7 @@
|
||||
*/
|
||||
function gauntletCrownBuildDisplayMission(mission) {
|
||||
if (!mission || !Array.isArray(mission.ranked)) return mission;
|
||||
if (mission.uiSkin === 'jumper' || mission.uiSkin === 'violent_crime' || mission.uiSkin === 'question_mission') return mission;
|
||||
if (mission.uiSkin === 'jumper' || mission.uiSkin === 'violent_crime' || mission.uiSkin === 'question_mission' || mission.uiSkin === 'stack_tower') return mission;
|
||||
if (!(previewFillBots && isGauntletCrownHeistMapPlay() && others && typeof others.forEach === 'function')) return mission;
|
||||
|
||||
const seen = new Set();
|
||||
@@ -7127,6 +7456,8 @@
|
||||
disp = spaceShooterMergePreviewBotsMission(disp) || disp;
|
||||
} else if (mission.uiSkin === 'question_mission') {
|
||||
disp = quizQuestionMissionMergePreviewBotsMission(disp) || disp;
|
||||
} else if (mission.uiSkin === 'stack_tower') {
|
||||
disp = stackTowerMissionMergePreviewBotsMission(disp) || disp;
|
||||
}
|
||||
if (!disp || !Array.isArray(disp.ranked)) return;
|
||||
rowEl.innerHTML = '';
|
||||
@@ -7191,9 +7522,10 @@
|
||||
nick.textContent = r.nickname || '—';
|
||||
const sc = document.createElement('div');
|
||||
sc.className = 'gcm-sc';
|
||||
const jumpPlain = mission && (mission.uiSkin === 'jumper' || mission.uiSkin === 'violent_crime' || mission.uiSkin === 'question_mission');
|
||||
const jumpPlain = mission && (mission.uiSkin === 'jumper' || mission.uiSkin === 'violent_crime' || mission.uiSkin === 'question_mission' || mission.uiSkin === 'stack_tower');
|
||||
const botScorePlain = (previewFillBots && isGauntletCrownHeistMapPlay() && isPreviewBotId(r.id))
|
||||
|| (previewFillBots && mission && mission.uiSkin === 'question_mission' && isPreviewBotId(r.id));
|
||||
|| (previewFillBots && mission && mission.uiSkin === 'question_mission' && isPreviewBotId(r.id))
|
||||
|| (previewFillBots && mission && mission.uiSkin === 'stack_tower' && isPreviewBotId(r.id));
|
||||
sc.textContent = (jumpPlain || botScorePlain)
|
||||
? String(Math.max(0, Number(r.baseScore) || 0))
|
||||
: String(r.baseScore || 0) + '(+' + String(r.rankBonus || 0) + ')';
|
||||
@@ -7221,6 +7553,9 @@
|
||||
} else if (questionMissionSkin) {
|
||||
const pc = Math.max(0, Math.floor(Number(disp.participantCount) || 0));
|
||||
totalEl.innerHTML = 'คะแนนตอบคำถาม <span class="gcm-total-nums">(' + parts.join('+') + ') = ' + sum + '</span> · ผู้เล่น ' + pc + ' → เกรด';
|
||||
} else if (mission && mission.uiSkin === 'stack_tower') {
|
||||
const pc = Math.max(0, Math.floor(Number(disp.participantCount) || 0));
|
||||
totalEl.innerHTML = 'คะแนนรวม <span class="gcm-total-nums">(' + parts.join('+') + ') = ' + sum + '</span> · ผู้เล่น ' + pc + ' → เกรด';
|
||||
} else {
|
||||
totalEl.innerHTML = 'คะแนนรวม <span class="gcm-total-nums">(' + parts.join('+') + ') = ' + sum + '</span> · เฉลี่ย ' + avg + ' → เกรด';
|
||||
}
|
||||
@@ -7240,8 +7575,15 @@
|
||||
row.style.alignItems = 'center';
|
||||
row.style.gap = '8px';
|
||||
const chk = document.createElement('img');
|
||||
chk.src = BASE + '/img/gauntlet-assets/result-check.png';
|
||||
const stackTS = mission && mission.uiSkin === 'stack_tower';
|
||||
chk.src = stackTS ? stackTowerAssetUrl('result-check.png') : (BASE + '/img/gauntlet-assets/result-check.png');
|
||||
chk.alt = '';
|
||||
if (stackTS) {
|
||||
chk.onerror = function () {
|
||||
this.onerror = null;
|
||||
this.src = BASE + '/img/gauntlet-assets/result-check.png';
|
||||
};
|
||||
}
|
||||
chk.width = 26;
|
||||
chk.height = 26;
|
||||
const sp = document.createElement('span');
|
||||
@@ -7269,7 +7611,7 @@
|
||||
}
|
||||
const gcmHead = document.getElementById('gcm-heading');
|
||||
if (gcmHead) {
|
||||
if (mission && mission.uiSkin === 'question_mission') gcmHead.classList.add('sr-only');
|
||||
if (mission && (mission.uiSkin === 'question_mission' || mission.uiSkin === 'stack_tower')) gcmHead.classList.add('sr-only');
|
||||
else gcmHead.classList.remove('sr-only');
|
||||
}
|
||||
ov.classList.remove('is-hidden');
|
||||
@@ -7283,7 +7625,7 @@
|
||||
if (previewMode && editorEmbedReturn) {
|
||||
ov.classList.add('is-hidden');
|
||||
if (gcmHead) gcmHead.classList.remove('sr-only');
|
||||
if (mission && mission.uiSkin === 'question_mission') {
|
||||
if (mission && (mission.uiSkin === 'question_mission' || mission.uiSkin === 'stack_tower')) {
|
||||
cancelQuizCarryResultEndAfterTimeup();
|
||||
hideQuizCarryTimeupOnDeskLayer();
|
||||
hideQuizCarryResultEndLayer();
|
||||
@@ -9872,8 +10214,28 @@
|
||||
.catch(() => {
|
||||
if (stackMini && isStack()) stackMini.phaseSpeed = playStackSwingHz;
|
||||
});
|
||||
if (isStackTowerMissionUiMapPlay()) {
|
||||
stackTowerMissionEndedOnce = false;
|
||||
stackTowerMissionDeferredPhase = null;
|
||||
if (stackTowerMissionCountdownTimer) {
|
||||
clearTimeout(stackTowerMissionCountdownTimer);
|
||||
stackTowerMissionCountdownTimer = null;
|
||||
}
|
||||
stackTowerMissionPhase = 'howto';
|
||||
stackTowerSessionStartAt = 0;
|
||||
hideConflictingOverlaysForGauntletCrown();
|
||||
applyStackTowerMissionPanelImages();
|
||||
fetch(BASE + '/api/game-timing?_=' + Date.now(), { cache: 'no-store' })
|
||||
.then(function (r) { return r.ok ? r.json() : null; })
|
||||
.then(function (t) { if (t) applyGauntletTimingFromServer(t); })
|
||||
.catch(function () {});
|
||||
setTimeout(function () { showStackTowerMissionHowtoOverlay(); }, 50);
|
||||
} else {
|
||||
teardownStackTowerMissionUiPlay();
|
||||
}
|
||||
} else {
|
||||
stackMini = null;
|
||||
teardownStackTowerMissionUiPlay();
|
||||
}
|
||||
playPath = [];
|
||||
me.isWalking = false;
|
||||
@@ -10001,6 +10363,15 @@
|
||||
if (gccRq) gccRq.classList.add('is-hidden');
|
||||
gauntletCrownPregamePhase = null;
|
||||
gauntletCrownLobbyReadyMap = {};
|
||||
} else if (isStack() && isStackTowerMissionUiMapPlay()) {
|
||||
if (stackTowerMissionCountdownTimer) {
|
||||
clearTimeout(stackTowerMissionCountdownTimer);
|
||||
stackTowerMissionCountdownTimer = null;
|
||||
}
|
||||
const gccRst = document.getElementById('gauntlet-crown-countdown');
|
||||
if (gccRst) gccRst.classList.add('is-hidden');
|
||||
gauntletCrownPregamePhase = null;
|
||||
gauntletCrownLobbyReadyMap = {};
|
||||
} else {
|
||||
const hov = document.getElementById('gauntlet-crown-howto-overlay');
|
||||
if (hov) {
|
||||
@@ -10015,6 +10386,7 @@
|
||||
}
|
||||
const gccR2 = document.getElementById('gauntlet-crown-countdown');
|
||||
if (gccR2) gccR2.classList.add('is-hidden');
|
||||
teardownStackTowerMissionUiPlay();
|
||||
}
|
||||
resizeCanvas(); draw(); tick();
|
||||
}
|
||||
@@ -10440,6 +10812,9 @@
|
||||
quizQuestionMissionCountdownTimer = null;
|
||||
}
|
||||
}
|
||||
if (!isStackTowerMissionUiMapPlay()) {
|
||||
teardownStackTowerMissionUiPlay();
|
||||
}
|
||||
if (mapData.gameType !== 'space_shooter') {
|
||||
spaceShooterGameEnded = false;
|
||||
spaceShooterMissionPhase = null;
|
||||
@@ -10549,7 +10924,8 @@
|
||||
gauntletEndsAtMs = null;
|
||||
const skipHideCrownShell = (mapData.gameType === 'jump_survive' && isJumpSurviveMissionUiMapPlay())
|
||||
|| (mapData.gameType === 'space_shooter' && isSpaceShooterMissionUiMapPlay())
|
||||
|| (mapData.gameType === 'quiz' && isQuizQuestionMissionUiMapPlay());
|
||||
|| (mapData.gameType === 'quiz' && isQuizQuestionMissionUiMapPlay())
|
||||
|| (mapData.gameType === 'stack' && isStackTowerMissionUiMapPlay());
|
||||
if (!skipHideCrownShell) {
|
||||
const hto3 = document.getElementById('gauntlet-crown-howto-overlay');
|
||||
if (hto3) hto3.classList.add('is-hidden');
|
||||
@@ -10588,8 +10964,28 @@
|
||||
.catch(() => {
|
||||
if (stackMini && isStack()) stackMini.phaseSpeed = playStackSwingHz;
|
||||
});
|
||||
if (isStackTowerMissionUiMapPlay()) {
|
||||
stackTowerMissionEndedOnce = false;
|
||||
stackTowerMissionDeferredPhase = null;
|
||||
if (stackTowerMissionCountdownTimer) {
|
||||
clearTimeout(stackTowerMissionCountdownTimer);
|
||||
stackTowerMissionCountdownTimer = null;
|
||||
}
|
||||
stackTowerMissionPhase = 'howto';
|
||||
stackTowerSessionStartAt = 0;
|
||||
hideConflictingOverlaysForGauntletCrown();
|
||||
applyStackTowerMissionPanelImages();
|
||||
fetch(BASE + '/api/game-timing?_=' + Date.now(), { cache: 'no-store' })
|
||||
.then((r) => (r.ok ? r.json() : null))
|
||||
.then((t) => { if (t) applyGauntletTimingFromServer(t); })
|
||||
.catch(() => {});
|
||||
setTimeout(function () { showStackTowerMissionHowtoOverlay(); }, 60);
|
||||
} else {
|
||||
teardownStackTowerMissionUiPlay();
|
||||
}
|
||||
} else {
|
||||
stackMini = null;
|
||||
teardownStackTowerMissionUiPlay();
|
||||
}
|
||||
if (mapData.gameType === 'jump_survive') {
|
||||
if (!Array.isArray(mapData.jumpSurvivePlatforms)) mapData.jumpSurvivePlatforms = [];
|
||||
@@ -11680,7 +12076,7 @@
|
||||
|
||||
function useCyberPlayHud() {
|
||||
return !!(mapData && (isJumpSurvive() || isGauntlet() || isSpaceShooter() || isBalloonBoss() || isQuizCarry()
|
||||
|| isQuizQuestionMissionHudActivePlay()));
|
||||
|| isQuizQuestionMissionHudActivePlay() || isStackTowerMissionHudActivePlay()));
|
||||
}
|
||||
|
||||
/** Cyber HUD: quiz_carry ใช้ playLiveQuizScores (ถูก +QUIZ_CARRY_POINTS_PER_CORRECT ตอนส่งป้ายถูกที่ฮับ) */
|
||||
@@ -11769,12 +12165,14 @@
|
||||
? (playQuizPhaseLocal === 'read' ? 'READ · อ่านคำถาม'
|
||||
: (playQuizPhaseLocal === 'answer' ? 'ANSWER · เดินไปโซน จริง / เท็จ'
|
||||
: 'QUIZ MISSION · ประลองความรู้'))
|
||||
: (isQuizCarry()
|
||||
? 'QUIZ CARRY · COURT — หยิบป้ายถูกแล้วส่งที่ฮับ (F / Grab)'
|
||||
: (isGauntlet() ? 'GAUNTLET · SURVIVAL RUN'
|
||||
: (isSpaceShooter() ? 'SPACE SHOOTER · ARCADE'
|
||||
: (isBalloonBoss() ? 'BALLOON BOSS · MEGA VIRUS'
|
||||
: (isJumpSurviveMissionUiMapPlay() ? 'JUMPER · SURVIVE — เหลือเวลา (วินาที) · TIME (sec)' : 'JUMP SURVIVE · NODE UPLINK')))));
|
||||
: (isStackTowerMissionHudActivePlay()
|
||||
? 'TOWER STACK · DECRYPT — TIME (sec) · เหลือเวลาถอดรหัส'
|
||||
: (isQuizCarry()
|
||||
? 'QUIZ CARRY · COURT — หยิบป้ายถูกแล้วส่งที่ฮับ (F / Grab)'
|
||||
: (isGauntlet() ? 'GAUNTLET · SURVIVAL RUN'
|
||||
: (isSpaceShooter() ? 'SPACE SHOOTER · ARCADE'
|
||||
: (isBalloonBoss() ? 'BALLOON BOSS · MEGA VIRUS'
|
||||
: (isJumpSurviveMissionUiMapPlay() ? 'JUMPER · SURVIVE — เหลือเวลา (วินาที) · TIME (sec)' : 'JUMP SURVIVE · NODE UPLINK'))))));
|
||||
}
|
||||
if (timeVal) {
|
||||
if (isQuiz() && isQuizQuestionMissionUiMapPlay()) {
|
||||
@@ -11785,6 +12183,15 @@
|
||||
} else {
|
||||
timeVal.textContent = String(Math.max(0, Math.ceil((playQuizPhaseEndsAt - Date.now()) / 1000)));
|
||||
}
|
||||
} else if (isStack() && isStackTowerMissionUiMapPlay()) {
|
||||
if (stackTowerMissionPhase === 'live') {
|
||||
const remSt = Math.max(0, Math.ceil(STACK_TOWER_MISSION_TIME_SEC - stackTowerMissionElapsedSecPlay()));
|
||||
timeVal.textContent = String(remSt);
|
||||
} else if (stackTowerMissionPhase === 'howto' || stackTowerMissionPhase === 'countdown') {
|
||||
timeVal.textContent = '···';
|
||||
} else {
|
||||
timeVal.textContent = '0';
|
||||
}
|
||||
} else if (isQuizCarry()) {
|
||||
if (quizCarrySessionEnded) {
|
||||
timeVal.textContent = '0';
|
||||
@@ -11881,6 +12288,8 @@
|
||||
if (statusEl) {
|
||||
if (isQuizQuestionMissionHudActivePlay()) {
|
||||
statusEl.textContent = 'QUIZ LINK · ประลองความรู้';
|
||||
} else if (isStackTowerMissionHudActivePlay()) {
|
||||
statusEl.textContent = 'DECRYPT UPLINK · TOWER';
|
||||
} else if (isQuizCarry() && quizCarrySessionEnded) {
|
||||
statusEl.textContent = 'SESSION COMPLETE · จบชุดคำถาม';
|
||||
} else if (isJumpSurvive() && jumpSurviveEliminated) {
|
||||
@@ -11896,6 +12305,8 @@
|
||||
hintEl.textContent = playQuizPhaseLocal === 'answer'
|
||||
? 'เดินไปโซน จริง / เท็จ ตามคำถามด้านบน · หมดเวลาแล้วไปข้อถัดไป'
|
||||
: 'อ่านคำถามกลางจอ — เมื่อเข้ารอบตอบให้เดินไปโซนสีฟ้า (จริง) หรือชมพู (เท็จ)';
|
||||
} else if (isStackTowerMissionHudActivePlay()) {
|
||||
hintEl.textContent = 'กด SPACE / ENTER หรือคลิกที่จอ = DROP · วางต่อเนื่องแม่น = โบนัส · ถอดรหัสให้ครบ 100% ภายในเวลา';
|
||||
} else if (isQuizCarry()) {
|
||||
hintEl.textContent = quizCarrySessionEnded
|
||||
? 'ชุดคำถามจบแล้ว · Session ended — ดูคะแนนด้าน SCORE'
|
||||
@@ -11945,16 +12356,20 @@
|
||||
const safeYv = (v) => (typeof v === 'number' && !isNaN(v) ? v : 1);
|
||||
const jumpMissionHud = isJumpSurvive() && isJumpSurviveMissionUiMapPlay();
|
||||
const quizMissionHud = isQuizQuestionMissionHudActivePlay();
|
||||
const stackMissionHud = isStackTowerMissionHudActivePlay();
|
||||
const stackTeamPts = Math.max(0, Number(stackMini && stackMini.score) || 0);
|
||||
const rows = [];
|
||||
rows.push({
|
||||
id: myId,
|
||||
nickname: me.nickname || nick,
|
||||
characterId: me.characterId || getPlayCharacterId(),
|
||||
score: (isQuizCarry() || quizMissionHud) ? cyberHudQuizCarryPeerScore(myId)
|
||||
: (isGauntlet() ? (me.gauntletScore || 0)
|
||||
: (isSpaceShooter() ? (me.spaceShooterScore || 0)
|
||||
: (isBalloonBoss() ? (me.balloonBossScore || 0)
|
||||
: (jumpMissionHud ? (jumpSurviveEliminated ? 0 : 100) : 0)))),
|
||||
: (stackMissionHud
|
||||
? (previewFillBots ? (me.stackPreviewHumanPts || 0) : stackTeamPts)
|
||||
: (isGauntlet() ? (me.gauntletScore || 0)
|
||||
: (isSpaceShooter() ? (me.spaceShooterScore || 0)
|
||||
: (isBalloonBoss() ? (me.balloonBossScore || 0)
|
||||
: (jumpMissionHud ? (jumpSurviveEliminated ? 0 : 100) : 0))))),
|
||||
y: safeYv(me.y),
|
||||
eliminated: !!(isJumpSurvive() && jumpSurviveEliminated) || !!(isBalloonBoss() && me.balloonBossEliminated)
|
||||
|| !!(isGauntletCrownHeistMapPlay() && me.gauntletEliminated),
|
||||
@@ -11966,10 +12381,12 @@
|
||||
nickname: o.nickname || id.slice(0, 8),
|
||||
characterId: o.characterId,
|
||||
score: (isQuizCarry() || quizMissionHud) ? cyberHudQuizCarryPeerScore(id)
|
||||
: (isGauntlet() ? (o.gauntletScore || 0)
|
||||
: (isSpaceShooter() ? (o.spaceShooterScore || 0)
|
||||
: (isBalloonBoss() ? (o.balloonBossScore || 0)
|
||||
: (jumpMissionHud ? (o.jumpSurviveEliminated ? 0 : 100) : 0)))),
|
||||
: (stackMissionHud
|
||||
? (previewFillBots && isPreviewBotId(id) ? (o.stackBotScore || 0) : stackTeamPts)
|
||||
: (isGauntlet() ? (o.gauntletScore || 0)
|
||||
: (isSpaceShooter() ? (o.spaceShooterScore || 0)
|
||||
: (isBalloonBoss() ? (o.balloonBossScore || 0)
|
||||
: (jumpMissionHud ? (o.jumpSurviveEliminated ? 0 : 100) : 0))))),
|
||||
y: safeYv(o.y),
|
||||
eliminated: !!(isJumpSurvive() && isPreviewBotId(id) && o.jumpSurviveEliminated)
|
||||
|| !!(isBalloonBoss() && o.balloonBossEliminated)
|
||||
@@ -11978,7 +12395,7 @@
|
||||
});
|
||||
});
|
||||
let leaderId = null;
|
||||
if (isGauntlet() || isSpaceShooter() || isBalloonBoss() || isQuizCarry() || jumpMissionHud || quizMissionHud) {
|
||||
if (isGauntlet() || isSpaceShooter() || isBalloonBoss() || isQuizCarry() || jumpMissionHud || quizMissionHud || stackMissionHud) {
|
||||
let mx = -1;
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
if (rows[i].score > mx) {
|
||||
@@ -11996,7 +12413,7 @@
|
||||
}
|
||||
}
|
||||
rows.sort((a, b) => {
|
||||
if (isGauntlet() || isSpaceShooter() || isBalloonBoss() || isQuizCarry() || jumpMissionHud || quizMissionHud) {
|
||||
if (isGauntlet() || isSpaceShooter() || isBalloonBoss() || isQuizCarry() || jumpMissionHud || quizMissionHud || stackMissionHud) {
|
||||
if (b.score !== a.score) return b.score - a.score;
|
||||
if (a.eliminated !== b.eliminated) return a.eliminated ? 1 : -1;
|
||||
return String(a.nickname || '').localeCompare(String(b.nickname || ''), 'th');
|
||||
@@ -12049,7 +12466,7 @@
|
||||
mid.appendChild(st);
|
||||
const sc = document.createElement('span');
|
||||
sc.className = 'play-cyber-score-val';
|
||||
if (isGauntlet() || isSpaceShooter() || isBalloonBoss() || isQuizCarry() || jumpMissionHud || quizMissionHud) {
|
||||
if (isGauntlet() || isSpaceShooter() || isBalloonBoss() || isQuizCarry() || jumpMissionHud || quizMissionHud || stackMissionHud) {
|
||||
sc.textContent = String(row.score);
|
||||
} else {
|
||||
sc.textContent = row.eliminated ? '—' : String(r + 1);
|
||||
@@ -12829,13 +13246,19 @@
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (isStack() && mapData && !isChatFocused() && isStackTowerMissionPregameBlockingPlay()) {
|
||||
if (['Space', 'Enter', 'NumpadEnter', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'KeyW', 'KeyA', 'KeyS', 'KeyD'].includes(e.code)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if ((isSpaceShooter() || isBalloonBoss()) && mapData && !isChatFocused()) {
|
||||
if (['Space', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'KeyW', 'KeyA', 'KeyS', 'KeyD'].includes(e.code)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
if (isStack() && mapData && !isChatFocused()) {
|
||||
if (e.code === 'Space') {
|
||||
if (e.code === 'Space' || e.code === 'Enter' || e.code === 'NumpadEnter') {
|
||||
e.preventDefault();
|
||||
tryHumanStackDrop();
|
||||
return;
|
||||
@@ -12927,6 +13350,12 @@
|
||||
function tick() {
|
||||
if (!mapData) { requestAnimationFrame(tick); return; }
|
||||
if (isStack()) {
|
||||
if (isStackTowerMissionPregameBlockingPlay()) {
|
||||
me.isWalking = false;
|
||||
draw();
|
||||
requestAnimationFrame(tick);
|
||||
return;
|
||||
}
|
||||
stackTickFrame();
|
||||
me.isWalking = false;
|
||||
draw();
|
||||
@@ -13188,6 +13617,13 @@
|
||||
beginQuizQuestionMissionCountdownThenRun();
|
||||
return;
|
||||
}
|
||||
if (isStackTowerMissionUiMapPlay()) {
|
||||
if (stackTowerMissionPhase !== 'howto') return;
|
||||
const humansSt = quizCarryPregameHumanIds();
|
||||
if (!(humansSt.length === 1 || isMePlayHost())) return;
|
||||
beginStackTowerMissionCountdownThenRun();
|
||||
return;
|
||||
}
|
||||
if (!isGauntletCrownHeistMapPlay()) return;
|
||||
if (gauntletCrownPregamePhase !== 'howto') return;
|
||||
if (myId == null || !isMePlayHost()) return;
|
||||
|
||||
@@ -1966,7 +1966,7 @@
|
||||
</div>
|
||||
<script src="/Game/socket.io/socket.io.js"></script>
|
||||
<script src="js/version.js?v=0.0184"></script>
|
||||
<script src="js/play.js?v=0.245"></script>
|
||||
<script src="js/play.js?v=0.246"></script>
|
||||
<div class="version-tag">v —</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||