minigame 3 Tower block 1.1

This commit is contained in:
2026-05-02 16:59:09 +00:00
parent c1b4086f30
commit 0a8debca15
77 changed files with 501 additions and 33 deletions
+5
View File
@@ -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
+1 -1
View File
@@ -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>
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -292,7 +292,7 @@
<li><strong>พื้นที่เริ่มเกม</strong> (ห้องโถง — ส้ม) — host ยืนแล้วกดเริ่ม</li>
<li><strong>ถามตอบ</strong>: โซนถูก/ผิด + พื้นที่คำถาม (ทอง)</li>
<li><strong>หยิบมาวาง</strong>: ม่วง = โซนกลาง (กำแพง) · <strong>ทอง</strong> = พื้นที่โชว์ข้อความคำถามบนแผนที่ (ถ้าไม่วาดทอง ข้อความไปที่โซนม่วง) · <strong>interactive เขียว</strong> = ยืนแล้วกด F ส่งคำตอบ · โหมดวาด <strong>ตัวเลือก 116</strong> = ช่องบนกริดตรงกับลำดับ <code>choices</code> · พรีวิวนับ <strong>3-2-1</strong> ไฮไลต์โซนข้อถูก (หรือใส่ <code>countdownHighlightSlot</code> 116 ต่อข้อใน <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>
+2 -1
View File
@@ -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:
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 526 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

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.
Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 515 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 397 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 484 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 481 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 507 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 473 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 485 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 B

+463 -27
View File
@@ -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;
+1 -1
View File
@@ -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>