minigame 5 jumper 1.3
@@ -38,6 +38,11 @@ JUMPER="$DST/Game/public/img/Jumper"
|
||||
mkdir -p "$JUMPER"
|
||||
chown www-data:www-data "$JUMPER"
|
||||
chmod 2775 "$JUMPER"
|
||||
# Space shooter / Violent Crime — ชื่อโฟลเดอร์ไม่มีช่องว่าง (FTP MKD "Violent Crime" มัก 550)
|
||||
VIOLENT_CRIME="$DST/Game/public/img/ViolentCrime"
|
||||
mkdir -p "$VIOLENT_CRIME"
|
||||
chown www-data:www-data "$VIOLENT_CRIME"
|
||||
chmod 2775 "$VIOLENT_CRIME"
|
||||
|
||||
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
|
||||
|
||||
@@ -2630,6 +2630,38 @@
|
||||
});
|
||||
}
|
||||
|
||||
function loadSpaceShooterTimingPanel() {
|
||||
gameTimingFetch('GET')
|
||||
.then(function (data) {
|
||||
var inpT = el('space-shooter-mission-sec');
|
||||
if (inpT) {
|
||||
var ss = Number(data.spaceShooterMissionTimeSec);
|
||||
inpT.value = String(Number.isFinite(ss) && ss > 0 ? Math.floor(ss) : 0);
|
||||
}
|
||||
setMsg('space-shooter-timing-msg', '', '');
|
||||
})
|
||||
.catch(function (e) {
|
||||
setMsg('space-shooter-timing-msg', e.message || 'โหลดไม่ได้', 'error');
|
||||
});
|
||||
}
|
||||
|
||||
function saveSpaceShooterTimingPanel() {
|
||||
var limSs = el('space-shooter-mission-sec') ? parseInt(String(el('space-shooter-mission-sec').value), 10) : 0;
|
||||
if (Number.isNaN(limSs) || limSs < 0) limSs = 0;
|
||||
limSs = limSs <= 0 ? 0 : Math.max(10, Math.min(7200, limSs));
|
||||
gameTimingFetch('GET')
|
||||
.then(function (data) {
|
||||
data.spaceShooterMissionTimeSec = limSs;
|
||||
return gameTimingFetch('PUT', data);
|
||||
})
|
||||
.then(function () {
|
||||
setMsg('space-shooter-timing-msg', 'บันทึกแล้ว — ผู้เล่นยิงยานอวกาศได้ค่าใหม่เมื่อรีเฟรชหน้าเล่นหรือโหลด /api/game-timing', 'ok');
|
||||
})
|
||||
.catch(function (e) {
|
||||
setMsg('space-shooter-timing-msg', e.message || 'บันทึกไม่ได้', 'error');
|
||||
});
|
||||
}
|
||||
|
||||
function saveJumpSurviveTimingPanel() {
|
||||
var inp = el('jump-survive-height-mult');
|
||||
var jumpSurvMult = inp ? parseFloat(String(inp.value)) : 1.5;
|
||||
@@ -2681,7 +2713,7 @@
|
||||
return gameTimingFetch('PUT', data);
|
||||
})
|
||||
.then(function () {
|
||||
setMsg('game-timing-msg', 'บันทึกแล้ว — พรมแดง tick·กระโดด / รูปอุปสรรค์ · มินิเกมกระโดดขึ้นแท่นตั้งที่แท็บ Minigame-5 · ค่า Stack แก้ที่แท็บ Stack เท่านั้น · จำกัดเวลาพรมแดงใช้กับรอบถัดไปที่โฮสต์เริ่มเกม', 'ok');
|
||||
setMsg('game-timing-msg', 'บันทึกแล้ว — พรมแดง tick·กระโดด / รูปอุปสรรค์ · Minigame-5 / Minigame-6 ตั้งแท็บของแต่ละเกม · ค่า Stack แก้ที่แท็บ Stack เท่านั้น · จำกัดเวลาพรมแดงใช้กับรอบถัดไปที่โฮสต์เริ่มเกม', 'ok');
|
||||
})
|
||||
.catch(function (e) {
|
||||
setMsg('game-timing-msg', e.message || 'บันทึกไม่ได้', 'error');
|
||||
@@ -2725,6 +2757,7 @@
|
||||
if (name === 'quiz-carry') loadQuizCarryPanel();
|
||||
if (name === 'quiz-battle') loadQbBattlePanel();
|
||||
if (name === 'jump-survive') loadJumpSurviveTimingPanel();
|
||||
if (name === 'space-shooter') loadSpaceShooterTimingPanel();
|
||||
if (name === 'game-timing') loadGameTimingPanel();
|
||||
if (name === 'stack-game') loadStackGamePanel();
|
||||
}
|
||||
@@ -2850,6 +2883,10 @@
|
||||
if (btnJumpSurviveSave) {
|
||||
btnJumpSurviveSave.addEventListener('click', saveJumpSurviveTimingPanel);
|
||||
}
|
||||
var btnSpaceShooterSave = el('btn-space-shooter-save');
|
||||
if (btnSpaceShooterSave) {
|
||||
btnSpaceShooterSave.addEventListener('click', saveSpaceShooterTimingPanel);
|
||||
}
|
||||
var btnStackGameSave = el('btn-stack-game-save');
|
||||
if (btnStackGameSave) {
|
||||
btnStackGameSave.addEventListener('click', saveStackGamePanel);
|
||||
@@ -3160,7 +3197,7 @@
|
||||
try {
|
||||
want = new URL(window.location.href).searchParams.get('tab');
|
||||
} catch (e) { /* ignore */ }
|
||||
var allowed = { 'change-password': true, oauth: true, 'map-editor': true, quiz: true, 'quiz-carry': true, 'quiz-battle': true, 'jump-survive': true, 'game-timing': true, 'stack-game': true, characters: true, accounts: true, admins: true };
|
||||
var allowed = { 'change-password': true, oauth: true, 'map-editor': true, quiz: true, 'quiz-carry': true, 'quiz-battle': true, 'jump-survive': true, 'space-shooter': true, 'game-timing': true, 'stack-game': true, characters: true, accounts: true, admins: true };
|
||||
if (!want || !allowed[want]) return 'oauth';
|
||||
if (want === 'admins' && (!admin || admin.role !== 'super')) return 'oauth';
|
||||
return want;
|
||||
|
||||
@@ -72,6 +72,7 @@
|
||||
<button type="button" class="tab" data-tab="quiz-carry" role="tab" id="tab-quiz-carry" aria-controls="tab-panel-quiz-carry"><span class="tab-label">Minigame-4-คำศัพท์ในกระบวนการยุติธรรม</span><span class="tab-desc" aria-hidden="true"></span></button>
|
||||
<button type="button" class="tab" data-tab="quiz-battle" role="tab" id="tab-quiz-battle" aria-controls="tab-panel-quiz-battle"><span class="tab-label">Quiz Battle</span><span class="tab-desc">โดม · A B C · หมวด</span></button>
|
||||
<button type="button" class="tab" data-tab="jump-survive" role="tab" id="tab-jump-survive" aria-controls="tab-panel-jump-survive"><span class="tab-label">Minigame-5-กระโดดขึ้นแท่น</span><span class="tab-desc" aria-hidden="true"></span></button>
|
||||
<button type="button" class="tab" data-tab="space-shooter" role="tab" id="tab-space-shooter" aria-controls="tab-panel-space-shooter"><span class="tab-label">Minigame-6-ยิงอุกาบาต Violent Crime</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-label">Minigame-2-วิ่งหลบสิ่งกีดขวาง</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">Stack</span><span class="tab-desc">สวิง · ความยาวแท่ง</span></button>
|
||||
<button type="button" class="tab" data-tab="characters" role="tab" id="tab-characters" aria-controls="tab-panel-characters"><span class="tab-label">ตัวละคร</span><span class="tab-desc">อัปโหลด · เลือกใช้ในเกม</span></button>
|
||||
@@ -464,9 +465,25 @@
|
||||
<p id="jump-survive-timing-msg" class="msg" role="status"></p>
|
||||
</section>
|
||||
|
||||
<section id="tab-panel-space-shooter" class="tab-panel card" hidden role="tabpanel" aria-labelledby="tab-space-shooter">
|
||||
<h2>ยิงยานอวกาศ (Violent Crime / space_shooter) — ตั้งค่าเวลา</h2>
|
||||
<p class="muted">เก็บที่ <code>/Game/data/game-timing.json</code> ฟิลด์ <code>spaceShooterMissionTimeSec</code> · ผู้เล่นได้ค่าใหม่เมื่อโหลดหน้าเล่น / <code>GET /api/game-timing</code> · รูปเกมอัปโหลดไปที่ <code>/Game/public/img/ViolentCrime/</code> (ชื่อโฟลเดอร์<strong>ไม่มีช่องว่าง</strong> — FTP หลายตัวจะ <code>550</code> ถ้าใช้ <code>Violent Crime</code>)</p>
|
||||
<fieldset class="quiz-timing-fieldset">
|
||||
<legend>จำกัดเวลารอบ (ยิงอุกาบาต)</legend>
|
||||
<div class="form-grid form-inline quiz-timing-grid">
|
||||
<label title="0 = ใช้ค่าเริ่ม 90 วินาทีในเกม · ตั้งอย่างน้อย 10 วินาทีถ้าต้องการกำหนดเอง">เวลารอบ (วินาที) <input type="number" id="space-shooter-mission-sec" min="0" max="7200" step="5" value="0"></label>
|
||||
</div>
|
||||
</fieldset>
|
||||
<p class="muted" style="margin-top:-0.25rem;margin-bottom:0.65rem">นับจากเริ่มรอบในเกม · ถ้าในแมปตั้ง <code>spaceShooterTimeSec</code> > 0 จะใช้ค่าบนแมปแทนทั้งหมด · ตั้ง <code>spaceShooterTimeSec</code> = 0 บนแมป = ไม่จับเวลา</p>
|
||||
<div class="quiz-admin-actions">
|
||||
<button type="button" class="btn btn-primary" id="btn-space-shooter-save">บันทึก</button>
|
||||
</div>
|
||||
<p id="space-shooter-timing-msg" class="msg" role="status"></p>
|
||||
</section>
|
||||
|
||||
<section id="tab-panel-game-timing" class="tab-panel card" hidden role="tabpanel" aria-labelledby="tab-game-timing">
|
||||
<h2>เวลาเกม — พรมแดงสุดท้าย (Gauntlet)</h2>
|
||||
<p class="muted">เก็บที่เซิร์ฟเวอร์เกม <code>/Game/data/game-timing.json</code> · หลังบันทึกห้องที่กำลังเล่นจะปรับความถี่ tick ทันที · ค่าจะส่งไปยังผู้เล่นผ่าน <code>gauntlet-sync</code> · <strong>จำกัดเวลา</strong>นับจากตอนโฮสต์เริ่มเกมพรมแดง — หมดเวลาแล้วเกมจบและส่งทุกคนกลับฉากล็อบบี้บนเซิร์ฟเวอร์ · ถ้าขึ้น Not Found ให้<strong>รีสตาร์ท Node</strong>ที่รัน <code>Game/server.js</code> (เช่น <code>pm2 restart</code>) หลัง deploy · <strong>เวลามินิเกมกระโดดขึ้นแท่น</strong>ตั้งที่แท็บ Minigame-5</p>
|
||||
<p class="muted">เก็บที่เซิร์ฟเวอร์เกม <code>/Game/data/game-timing.json</code> · หลังบันทึกห้องที่กำลังเล่นจะปรับความถี่ tick ทันที · ค่าจะส่งไปยังผู้เล่นผ่าน <code>gauntlet-sync</code> · <strong>จำกัดเวลา</strong>นับจากตอนโฮสต์เริ่มเกมพรมแดง — หมดเวลาแล้วเกมจบและส่งทุกคนกลับฉากล็อบบี้บนเซิร์ฟเวอร์ · ถ้าขึ้น Not Found ให้<strong>รีสตาร์ท Node</strong>ที่รัน <code>Game/server.js</code> (เช่น <code>pm2 restart</code>) หลัง deploy · <strong>เวลามินิเกมกระโดดขึ้นแท่น</strong>ตั้งที่แท็บ Minigame-5 · <strong>เวลายิงอุกาบาต</strong>ตั้งที่แท็บ Minigame-6</p>
|
||||
<fieldset class="quiz-timing-fieldset">
|
||||
<legend>พารามิเตอร์</legend>
|
||||
<div class="form-grid form-inline quiz-timing-grid">
|
||||
|
||||
@@ -17,5 +17,6 @@
|
||||
"stackSwingHz": 0.1,
|
||||
"stackBlockWidthTiles": 3.2,
|
||||
"jumpSurviveJumpHeightMult": 1.5,
|
||||
"jumpSurviveMissionTimeSec": 0
|
||||
"jumpSurviveMissionTimeSec": 0,
|
||||
"spaceShooterMissionTimeSec": 0
|
||||
}
|
||||
@@ -44,6 +44,7 @@
|
||||
|
||||
<nav class="admin-tabs" role="tablist" aria-label="เมนูหลัก Admin">
|
||||
<button type="button" role="tab" id="tab-jump" aria-selected="true" aria-controls="panel-jump" data-panel="panel-jump">กระโดดให้รอด</button>
|
||||
<button type="button" role="tab" id="tab-shooter" aria-selected="false" aria-controls="panel-shooter" data-panel="panel-shooter">ยิงอุกาบาต · Violent Crime</button>
|
||||
<button type="button" role="tab" id="tab-gauntlet" aria-selected="false" aria-controls="panel-gauntlet" data-panel="panel-gauntlet">พรมแดง · tick & กระโดด</button>
|
||||
<button type="button" role="tab" id="tab-stack" aria-selected="false" aria-controls="panel-stack" data-panel="panel-stack">Stack · สวิง</button>
|
||||
</nav>
|
||||
@@ -62,6 +63,15 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="panel-shooter" class="panel" role="tabpanel" aria-labelledby="tab-shooter" hidden>
|
||||
<h2>ยิงยานอวกาศ (space_shooter)</h2>
|
||||
<div class="row">
|
||||
<label for="spaceShooterMissionTimeSec">spaceShooterMissionTimeSec — เวลารอบ (วินาที · 0 = เกมใช้ค่าเริ่ม 90)</label>
|
||||
<input type="number" id="spaceShooterMissionTimeSec" min="0" max="7200" step="5" value="0">
|
||||
<p class="hint">ถ้าในแมปตั้ง <code>spaceShooterTimeSec</code> > 0 จะใช้ค่าบนแมปแทน · ตั้งเป็น 0 บนแมป = ไม่จับเวลา</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="panel-gauntlet" class="panel" role="tabpanel" aria-labelledby="tab-gauntlet" hidden>
|
||||
<h2>พรมแดง (Gauntlet)</h2>
|
||||
<div class="row">
|
||||
@@ -102,6 +112,7 @@
|
||||
const tabs = document.querySelectorAll('nav.admin-tabs [role="tab"]');
|
||||
const panels = {
|
||||
'panel-jump': document.getElementById('panel-jump'),
|
||||
'panel-shooter': document.getElementById('panel-shooter'),
|
||||
'panel-gauntlet': document.getElementById('panel-gauntlet'),
|
||||
'panel-stack': document.getElementById('panel-stack'),
|
||||
};
|
||||
@@ -126,6 +137,8 @@
|
||||
document.getElementById('jumpSurviveJumpHeightMult').value = timing.jumpSurviveJumpHeightMult != null ? timing.jumpSurviveJumpHeightMult : 1.5;
|
||||
const jms = Number(timing.jumpSurviveMissionTimeSec);
|
||||
document.getElementById('jumpSurviveMissionTimeSec').value = Number.isFinite(jms) && jms > 0 ? Math.floor(jms) : 0;
|
||||
const sms = Number(timing.spaceShooterMissionTimeSec);
|
||||
document.getElementById('spaceShooterMissionTimeSec').value = Number.isFinite(sms) && sms > 0 ? Math.floor(sms) : 0;
|
||||
document.getElementById('gauntletTickMs').value = timing.gauntletTickMs != null ? timing.gauntletTickMs : 150;
|
||||
document.getElementById('gauntletJumpTicks').value = timing.gauntletJumpTicks != null ? timing.gauntletJumpTicks : 5;
|
||||
document.getElementById('gauntletTimeLimitSec').value = timing.gauntletTimeLimitSec != null ? timing.gauntletTimeLimitSec : 0;
|
||||
@@ -139,10 +152,14 @@
|
||||
let jmt = parseInt(document.getElementById('jumpSurviveMissionTimeSec').value, 10);
|
||||
if (Number.isNaN(jmt) || jmt < 0) jmt = 0;
|
||||
jmt = jmt <= 0 ? 0 : Math.max(10, Math.min(7200, jmt));
|
||||
let sst = parseInt(document.getElementById('spaceShooterMissionTimeSec').value, 10);
|
||||
if (Number.isNaN(sst) || sst < 0) sst = 0;
|
||||
sst = sst <= 0 ? 0 : Math.max(10, Math.min(7200, sst));
|
||||
return {
|
||||
...timing,
|
||||
jumpSurviveJumpHeightMult: Number.isFinite(jm) ? jm : 1.5,
|
||||
jumpSurviveMissionTimeSec: jmt,
|
||||
spaceShooterMissionTimeSec: sst,
|
||||
gauntletTickMs: parseInt(document.getElementById('gauntletTickMs').value, 10),
|
||||
gauntletJumpTicks: parseInt(document.getElementById('gauntletJumpTicks').value, 10),
|
||||
gauntletTimeLimitSec: parseInt(document.getElementById('gauntletTimeLimitSec').value, 10),
|
||||
|
||||
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 5.3 MiB |
|
After Width: | Height: | Size: 2.9 MiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 117 KiB |
|
After Width: | Height: | Size: 37 KiB |
@@ -0,0 +1,19 @@
|
||||
Violent Crime / space_shooter — รูปเกม
|
||||
========================================
|
||||
|
||||
ใช้โฟลเดอร์นี้ชื่อ ViolentCrime (camelCase, ไม่มีช่องว่าง)
|
||||
|
||||
เหตุผล: คำสั่ง FTP MKD สำหรับโฟลเดอร์ที่มีช่องว่าง เช่น "Violent Crime"
|
||||
มักได้รหัส 550 Create directory operation failed บนเซิร์ฟเวอร์หลายแบบ
|
||||
|
||||
อัปโหลดรูปผ่าน FTP/SFTP มาที่:
|
||||
/Game/public/img/ViolentCrime/
|
||||
|
||||
English: Use this folder name without spaces; many FTP servers reject MKD with spaces in the path.
|
||||
|
||||
ไฟล์ที่ client อ้างอิง (ชื่อตรงนี้):
|
||||
popup-Howto.png — overlay วิธีเล่น + READY
|
||||
popup-result.png — พื้นหลังป๊อปอัปสรุปภารกิจ
|
||||
result-timeup.png — (embed) ข้อความ TIME'S UP บนโต๊ะ หลังสรุป — ถ้าไม่มีจะ fallback quiz-carry
|
||||
result-complete.png — (embed) หลัง timeup ถ้าผ่านเกณฑ์ — fallback quiz-carry
|
||||
result-gameover.png — (embed) หลัง timeup ถ้าไม่ผ่าน — fallback quiz-carry
|
||||
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 57 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 22 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: 13 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 508 KiB |
|
After Width: | Height: | Size: 203 B |
|
After Width: | Height: | Size: 236 B |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 256 B |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
@@ -23,6 +23,8 @@
|
||||
const GAUNTLET_FACE_RIGHT_MAP_ID = 'mno9kb07';
|
||||
/** Jump Survival ฉาก Jumper — UI สรุปภารกิจแบบ crown + รูปใน /img/Jumper/ (editor ?map=mnptfts2) */
|
||||
const JUMP_SURVIVE_MISSION_MAP_ID = 'mnptfts2';
|
||||
/** Space shooter ฉาก Violent Crime — flow เดียวกับ crown/howto (รูปใน /img/ViolentCrime/) */
|
||||
const SPACE_SHOOTER_MISSION_MAP_ID = 'mnpz6rkp';
|
||||
/** จนกว่าโหลด /api/characters — placeholder ชั่วคราวก่อน join */
|
||||
const LEGACY_PLACEHOLDER_CHARACTER_ID = 'Chatest';
|
||||
let firstCharacterDefaultResolved = null;
|
||||
@@ -692,6 +694,8 @@
|
||||
let playJumpSurviveJumpHeightMult = 1.5;
|
||||
/** จำกัดเวลารอบภารกิจกระโดดขึ้นแท่น (วินาที) — จาก GET /api/game-timing; 0 = ใช้ค่าเริ่ม 60 ในเกม */
|
||||
let playJumpSurviveMissionTimeSec = 0;
|
||||
/** ยิงยานอวกาศ (space_shooter): เวลารอบจาก game-timing; 0 = ใช้ค่าเริ่ม 90 ในเกมถ้าแมปไม่ทับ */
|
||||
let playSpaceShooterMissionTimeSec = 0;
|
||||
/** 0 = ไม่จำกัด — จาก game-timing / gauntlet-sync (พรมแดง Gauntlet เท่านั้น) */
|
||||
let gauntletRuntimeTimeLimitSec = 0;
|
||||
/** เวลาสิ้นสุดรอบ (epoch ms) จากเซิร์ฟเวอร์ — null = ไม่จับเวลา */
|
||||
@@ -837,6 +841,8 @@
|
||||
let spaceShooterSessionStartMs = 0;
|
||||
let spaceShooterLastMoveEmit = 0;
|
||||
let spaceShooterGameEnded = false;
|
||||
let spaceShooterMissionPhase = null;
|
||||
let spaceShooterMissionCountdownTimer = null;
|
||||
|
||||
/** balloon_boss — ลูกโป้งยิงบอส (Mega Virus) */
|
||||
let balloonBossPendingShots = [];
|
||||
@@ -2832,6 +2838,272 @@
|
||||
showGauntletCrownMissionOverlay(mission);
|
||||
}
|
||||
function isSpaceShooter() { return mapData && mapData.gameType === 'space_shooter'; }
|
||||
|
||||
function isSpaceShooterMissionUiMapPlay() {
|
||||
return isSpaceShooter() && currentPlayMapId() === SPACE_SHOOTER_MISSION_MAP_ID;
|
||||
}
|
||||
|
||||
function isSpaceShooterMissionPregameBlockingPlay() {
|
||||
if (!isSpaceShooterMissionUiMapPlay()) return false;
|
||||
return spaceShooterMissionPhase != null && spaceShooterMissionPhase !== 'live' && spaceShooterMissionPhase !== 'ended';
|
||||
}
|
||||
|
||||
function violentCrimeAssetUrl(file) {
|
||||
return BASE + '/img/ViolentCrime/' + String(file || '').replace(/^\//, '');
|
||||
}
|
||||
|
||||
function applySpaceShooterMissionPanelImages() {
|
||||
const howtoBg = document.querySelector('#gauntlet-crown-howto-overlay .gch-bg');
|
||||
if (howtoBg) {
|
||||
howtoBg.src = violentCrimeAssetUrl('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 = violentCrimeAssetUrl('popup-result.png');
|
||||
resBg.onerror = function () {
|
||||
this.onerror = null;
|
||||
this.src = BASE + '/img/gauntlet-assets/popup-result.png';
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function showSpaceShooterMissionHowtoOverlay() {
|
||||
if (!isSpaceShooterMissionUiMapPlay()) return;
|
||||
hideConflictingOverlaysForGauntletCrown();
|
||||
applySpaceShooterMissionPanelImages();
|
||||
if (spaceShooterMissionCountdownTimer) {
|
||||
clearTimeout(spaceShooterMissionCountdownTimer);
|
||||
spaceShooterMissionCountdownTimer = null;
|
||||
}
|
||||
const cd = document.getElementById('gauntlet-crown-countdown');
|
||||
if (cd) cd.classList.add('is-hidden');
|
||||
spaceShooterMissionPhase = '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 beginSpaceShooterMissionCountdownThenRun() {
|
||||
if (!isSpaceShooterMissionUiMapPlay()) return;
|
||||
if (spaceShooterMissionCountdownTimer) {
|
||||
clearTimeout(spaceShooterMissionCountdownTimer);
|
||||
spaceShooterMissionCountdownTimer = null;
|
||||
}
|
||||
spaceShooterMissionPhase = '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 (spaceShooterMissionCountdownTimer) {
|
||||
clearTimeout(spaceShooterMissionCountdownTimer);
|
||||
spaceShooterMissionCountdownTimer = null;
|
||||
}
|
||||
spaceShooterMissionPhase = 'live';
|
||||
spaceShooterGameEnded = false;
|
||||
spaceShooterBullets = [];
|
||||
spaceShooterAsteroids = [];
|
||||
spaceShooterPopups = [];
|
||||
spaceShooterLastTickMs = performance.now();
|
||||
spaceShooterSpawnAccMs = 0;
|
||||
spaceShooterFireCd = 0;
|
||||
spaceShooterSessionStartMs = performance.now();
|
||||
spaceShooterLastMoveEmit = 0;
|
||||
if (myId != null) me.spaceShooterScore = 0;
|
||||
others.forEach(function (o) {
|
||||
if (o) o.spaceShooterScore = 0;
|
||||
});
|
||||
if (mapData) applySpaceShooterSpawnLayoutPlay();
|
||||
};
|
||||
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);
|
||||
spaceShooterMissionCountdownTimer = setTimeout(step, 1000);
|
||||
} else {
|
||||
spaceShooterMissionCountdownTimer = null;
|
||||
runFinish();
|
||||
}
|
||||
};
|
||||
spaceShooterMissionCountdownTimer = setTimeout(step, 1000);
|
||||
}
|
||||
|
||||
function spaceShooterBuildMissionPayload() {
|
||||
const rows = [];
|
||||
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,
|
||||
rankBonus: 0,
|
||||
finalScore: Math.max(0, Number(score) || 0),
|
||||
});
|
||||
}
|
||||
if (myId != null) {
|
||||
pushEnt(myId, (me.nickname || nick || 'คุณ').trim() || 'คุณ', me.characterId || getPlayCharacterId(), me.spaceShooterScore);
|
||||
}
|
||||
others.forEach(function (o, id) {
|
||||
if (!o) return;
|
||||
pushEnt(id, o.nickname, o.characterId, o.spaceShooterScore);
|
||||
});
|
||||
rows.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 = rows.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,
|
||||
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);
|
||||
const grade = averageScore >= 80 ? 'A' : averageScore >= 60 ? 'B' : 'C';
|
||||
const rewardCard = gauntletCrownRollRewardCardLocal(grade);
|
||||
return {
|
||||
ranked: ranked,
|
||||
totalSum: totalSum,
|
||||
averageScore: averageScore,
|
||||
grade: grade,
|
||||
rewardCard: rewardCard,
|
||||
totalParts: totalParts,
|
||||
uiSkin: 'violent_crime',
|
||||
survivorCount: ranked.length,
|
||||
participantCount: rows.length,
|
||||
};
|
||||
}
|
||||
|
||||
function spaceShooterMergePreviewBotsMission(mission) {
|
||||
if (!mission || mission.uiSkin !== 'violent_crime' || !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,
|
||||
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.spaceShooterScore) || 0),
|
||||
eliminated: false,
|
||||
rankBonus: 0,
|
||||
finalScore: Math.max(0, Number(o.spaceShooterScore) || 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,
|
||||
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);
|
||||
const grade = averageScore >= 80 ? 'A' : averageScore >= 60 ? 'B' : 'C';
|
||||
return {
|
||||
ranked: ranked,
|
||||
totalSum: totalSum,
|
||||
averageScore: averageScore,
|
||||
grade: grade,
|
||||
rewardCard: gauntletCrownRollRewardCardLocal(grade),
|
||||
totalParts: totalParts,
|
||||
uiSkin: 'violent_crime',
|
||||
survivorCount: ranked.length,
|
||||
participantCount: baseRows.length,
|
||||
};
|
||||
}
|
||||
|
||||
function endSpaceShooterMissionRound() {
|
||||
if (!isSpaceShooterMissionUiMapPlay() || spaceShooterGameEnded) return;
|
||||
spaceShooterGameEnded = true;
|
||||
spaceShooterMissionPhase = 'ended';
|
||||
applySpaceShooterMissionPanelImages();
|
||||
spaceShooterBullets = [];
|
||||
spaceShooterAsteroids = [];
|
||||
spaceShooterSpawnAccMs = 0;
|
||||
spaceShooterPopups = [];
|
||||
showGauntletCrownMissionOverlay(spaceShooterBuildMissionPayload());
|
||||
}
|
||||
|
||||
function isBalloonBoss() { return mapData && mapData.gameType === 'balloon_boss'; }
|
||||
function isQuizCarry() { return mapData && mapData.gameType === 'quiz_carry'; }
|
||||
function isQuizBattle() { return mapData && mapData.gameType === 'quiz_battle'; }
|
||||
@@ -3957,7 +4229,16 @@
|
||||
const img = document.getElementById('quiz-carry-result-end-img');
|
||||
if (!el || !img) return;
|
||||
const fname = useComplete ? 'result-complete.png' : 'result-gameover.png';
|
||||
img.src = BASE + '/img/quiz-carry/' + fname + '?v=' + String(Date.now());
|
||||
if (isSpaceShooterMissionUiMapPlay()) {
|
||||
img.onerror = function () {
|
||||
this.onerror = null;
|
||||
this.src = BASE + '/img/quiz-carry/' + fname + '?v=' + String(Date.now());
|
||||
};
|
||||
img.src = violentCrimeAssetUrl(fname) + '?v=' + String(Date.now());
|
||||
} else {
|
||||
img.onerror = null;
|
||||
img.src = BASE + '/img/quiz-carry/' + fname + '?v=' + String(Date.now());
|
||||
}
|
||||
el.classList.remove('is-hidden');
|
||||
el.setAttribute('aria-hidden', 'false');
|
||||
}
|
||||
@@ -4009,6 +4290,12 @@
|
||||
this.src = BASE + '/img/quiz-carry/timeup-txt.png?v=' + String(Date.now());
|
||||
};
|
||||
img.src = jumperAssetUrl('result-timeup.png') + '?v=' + String(Date.now());
|
||||
} else if (isSpaceShooterMissionUiMapPlay()) {
|
||||
img.onerror = function () {
|
||||
this.onerror = null;
|
||||
this.src = BASE + '/img/quiz-carry/timeup-txt.png?v=' + String(Date.now());
|
||||
};
|
||||
img.src = violentCrimeAssetUrl('result-timeup.png') + '?v=' + String(Date.now());
|
||||
} else {
|
||||
img.onerror = null;
|
||||
img.src = BASE + '/img/quiz-carry/timeup-txt.png?v=' + String(Date.now());
|
||||
@@ -5379,6 +5666,14 @@
|
||||
playJumpSurviveMissionTimeSec = 0;
|
||||
}
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(payload, 'spaceShooterMissionTimeSec')) {
|
||||
const st = Number(payload.spaceShooterMissionTimeSec);
|
||||
if (Number.isFinite(st) && st > 0) {
|
||||
playSpaceShooterMissionTimeSec = Math.max(10, Math.min(7200, Math.floor(st)));
|
||||
} else {
|
||||
playSpaceShooterMissionTimeSec = 0;
|
||||
}
|
||||
}
|
||||
if (isStack() && stackMini) reapplyStackMiniSizingFromGlobals();
|
||||
}
|
||||
|
||||
@@ -5755,6 +6050,10 @@
|
||||
/** Editor embed: quiz_carry / crown ใช้เกรด F = gameover — Jumper: ภาพจบใช้เฉพาะเมื่อไม่มีผู้รอด (0 คน) */
|
||||
function gauntletCrownEmbedMissionAnyOk(mission) {
|
||||
if (!mission) return false;
|
||||
if (mission.uiSkin === 'violent_crime') {
|
||||
const g = String(mission.grade || '').trim().toUpperCase().charAt(0);
|
||||
return g !== 'F';
|
||||
}
|
||||
if (mission.uiSkin === 'jumper') {
|
||||
const sc = Number(mission.survivorCount);
|
||||
return Number.isFinite(sc) ? sc > 0 : (String(mission.grade || '').trim().toUpperCase().charAt(0) !== 'F');
|
||||
@@ -5792,7 +6091,7 @@
|
||||
*/
|
||||
function gauntletCrownBuildDisplayMission(mission) {
|
||||
if (!mission || !Array.isArray(mission.ranked)) return mission;
|
||||
if (mission.uiSkin === 'jumper') return mission;
|
||||
if (mission.uiSkin === 'jumper' || mission.uiSkin === 'violent_crime') return mission;
|
||||
if (!(previewFillBots && isGauntletCrownHeistMapPlay() && others && typeof others.forEach === 'function')) return mission;
|
||||
|
||||
const seen = new Set();
|
||||
@@ -5875,6 +6174,8 @@
|
||||
let disp = gauntletCrownBuildDisplayMission(mission);
|
||||
if (mission.uiSkin === 'jumper') {
|
||||
disp = jumpSurviveMergePreviewBotsMission(disp) || disp;
|
||||
} else if (mission.uiSkin === 'violent_crime') {
|
||||
disp = spaceShooterMergePreviewBotsMission(disp) || disp;
|
||||
}
|
||||
if (!disp || !Array.isArray(disp.ranked)) return;
|
||||
rowEl.innerHTML = '';
|
||||
@@ -5929,7 +6230,7 @@
|
||||
nick.textContent = r.nickname || '—';
|
||||
const sc = document.createElement('div');
|
||||
sc.className = 'gcm-sc';
|
||||
const jumpPlain = mission && mission.uiSkin === 'jumper';
|
||||
const jumpPlain = mission && (mission.uiSkin === 'jumper' || mission.uiSkin === 'violent_crime');
|
||||
const botScorePlain = previewFillBots && isGauntletCrownHeistMapPlay() && isPreviewBotId(r.id);
|
||||
sc.textContent = (jumpPlain || botScorePlain)
|
||||
? String(Math.max(0, Number(r.baseScore) || 0))
|
||||
@@ -6560,10 +6861,13 @@
|
||||
const t = Number(mapData && mapData.spaceShooterTimeSec);
|
||||
if (Number.isFinite(t) && t === 0) return 0;
|
||||
if (Number.isFinite(t) && t > 0 && t < 7200) return Math.floor(t);
|
||||
const g = Number(playSpaceShooterMissionTimeSec);
|
||||
if (Number.isFinite(g) && g > 0 && g < 7200) return Math.floor(g);
|
||||
return 90;
|
||||
}
|
||||
|
||||
function spaceShooterRemainingSecPlay() {
|
||||
if (isSpaceShooterMissionUiMapPlay() && spaceShooterMissionPhase !== 'live') return null;
|
||||
const lim = spaceShooterTimeLimitSecPlay();
|
||||
if (lim <= 0) return null;
|
||||
const elapsed = (performance.now() - spaceShooterSessionStartMs) / 1000;
|
||||
@@ -6572,6 +6876,10 @@
|
||||
|
||||
function endSpaceShooterTimeUp() {
|
||||
if (spaceShooterGameEnded || !mapData || mapData.gameType !== 'space_shooter') return;
|
||||
if (isSpaceShooterMissionUiMapPlay()) {
|
||||
endSpaceShooterMissionRound();
|
||||
return;
|
||||
}
|
||||
spaceShooterGameEnded = true;
|
||||
spaceShooterBullets = [];
|
||||
spaceShooterAsteroids = [];
|
||||
@@ -6631,6 +6939,7 @@
|
||||
|
||||
function stepSpaceShooterPreviewBots(dt) {
|
||||
if (spaceShooterGameEnded || !previewFillBots || !mapData || mapData.gameType !== 'space_shooter') return;
|
||||
if (isSpaceShooterMissionUiMapPlay() && spaceShooterMissionPhase !== 'live') return;
|
||||
const ts = tileSize;
|
||||
const mw = (mapData.width || 20) * ts;
|
||||
const mh = (mapData.height || 15) * ts;
|
||||
@@ -6670,6 +6979,7 @@
|
||||
|
||||
function spaceShooterTickFrame() {
|
||||
if (!mapData || mapData.gameType !== 'space_shooter') return;
|
||||
if (isSpaceShooterMissionUiMapPlay() && spaceShooterMissionPhase !== 'live') return;
|
||||
const w = mapData.width || 20, h = mapData.height || 15;
|
||||
const ts = tileSize;
|
||||
const mw = w * ts, mh = h * ts;
|
||||
@@ -8283,6 +8593,13 @@
|
||||
if (prevWasSpaceShooter) {
|
||||
const gend = document.getElementById('gauntlet-ended-overlay');
|
||||
if (gend) gend.classList.add('is-hidden');
|
||||
spaceShooterMissionPhase = null;
|
||||
if (spaceShooterMissionCountdownTimer) {
|
||||
clearTimeout(spaceShooterMissionCountdownTimer);
|
||||
spaceShooterMissionCountdownTimer = null;
|
||||
}
|
||||
const gccLeave = document.getElementById('gauntlet-crown-countdown');
|
||||
if (gccLeave) gccLeave.classList.add('is-hidden');
|
||||
}
|
||||
}
|
||||
if (mapData.gameType !== 'balloon_boss') {
|
||||
@@ -8476,16 +8793,32 @@
|
||||
}
|
||||
}
|
||||
if (mapData.gameType === 'space_shooter') {
|
||||
spaceShooterGameEnded = false;
|
||||
normalizeShooterSpawnSlotsInPlay(mapData);
|
||||
spaceShooterBullets = [];
|
||||
spaceShooterAsteroids = [];
|
||||
spaceShooterPopups = [];
|
||||
spaceShooterLastTickMs = performance.now();
|
||||
spaceShooterSpawnAccMs = 0;
|
||||
spaceShooterFireCd = 0;
|
||||
spaceShooterSessionStartMs = performance.now();
|
||||
spaceShooterLastMoveEmit = 0;
|
||||
if (spaceShooterMissionCountdownTimer) {
|
||||
clearTimeout(spaceShooterMissionCountdownTimer);
|
||||
spaceShooterMissionCountdownTimer = null;
|
||||
}
|
||||
if (isSpaceShooterMissionUiMapPlay()) {
|
||||
spaceShooterMissionPhase = 'howto';
|
||||
hideConflictingOverlaysForGauntletCrown();
|
||||
applySpaceShooterMissionPanelImages();
|
||||
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 () { showSpaceShooterMissionHowtoOverlay(); }, 50);
|
||||
} else {
|
||||
spaceShooterMissionPhase = null;
|
||||
spaceShooterGameEnded = false;
|
||||
spaceShooterBullets = [];
|
||||
spaceShooterAsteroids = [];
|
||||
spaceShooterPopups = [];
|
||||
spaceShooterLastTickMs = performance.now();
|
||||
spaceShooterSpawnAccMs = 0;
|
||||
spaceShooterFireCd = 0;
|
||||
spaceShooterSessionStartMs = performance.now();
|
||||
spaceShooterLastMoveEmit = 0;
|
||||
}
|
||||
}
|
||||
if (mapData.gameType === 'balloon_boss') {
|
||||
balloonBossGameEnded = false;
|
||||
@@ -8524,6 +8857,15 @@
|
||||
if (gccRj) gccRj.classList.add('is-hidden');
|
||||
gauntletCrownPregamePhase = null;
|
||||
gauntletCrownLobbyReadyMap = {};
|
||||
} else if (mapData.gameType === 'space_shooter' && isSpaceShooterMissionUiMapPlay()) {
|
||||
if (spaceShooterMissionCountdownTimer) {
|
||||
clearTimeout(spaceShooterMissionCountdownTimer);
|
||||
spaceShooterMissionCountdownTimer = null;
|
||||
}
|
||||
const gccRs = document.getElementById('gauntlet-crown-countdown');
|
||||
if (gccRs) gccRs.classList.add('is-hidden');
|
||||
gauntletCrownPregamePhase = null;
|
||||
gauntletCrownLobbyReadyMap = {};
|
||||
} else {
|
||||
const hov = document.getElementById('gauntlet-crown-howto-overlay');
|
||||
if (hov) {
|
||||
@@ -8942,6 +9284,11 @@
|
||||
}
|
||||
if (mapData.gameType !== 'space_shooter') {
|
||||
spaceShooterGameEnded = false;
|
||||
spaceShooterMissionPhase = null;
|
||||
if (spaceShooterMissionCountdownTimer) {
|
||||
clearTimeout(spaceShooterMissionCountdownTimer);
|
||||
spaceShooterMissionCountdownTimer = null;
|
||||
}
|
||||
}
|
||||
if (mapData.gameType !== 'balloon_boss') {
|
||||
balloonBossGameEnded = false;
|
||||
@@ -9041,7 +9388,8 @@
|
||||
}
|
||||
} else {
|
||||
gauntletEndsAtMs = null;
|
||||
const skipHideCrownShell = mapData.gameType === 'jump_survive' && isJumpSurviveMissionUiMapPlay();
|
||||
const skipHideCrownShell = (mapData.gameType === 'jump_survive' && isJumpSurviveMissionUiMapPlay())
|
||||
|| (mapData.gameType === 'space_shooter' && isSpaceShooterMissionUiMapPlay());
|
||||
if (!skipHideCrownShell) {
|
||||
const hto3 = document.getElementById('gauntlet-crown-howto-overlay');
|
||||
if (hto3) hto3.classList.add('is-hidden');
|
||||
@@ -9108,16 +9456,32 @@
|
||||
}
|
||||
}
|
||||
if (mapData.gameType === 'space_shooter') {
|
||||
spaceShooterGameEnded = false;
|
||||
normalizeShooterSpawnSlotsInPlay(mapData);
|
||||
spaceShooterBullets = [];
|
||||
spaceShooterAsteroids = [];
|
||||
spaceShooterPopups = [];
|
||||
spaceShooterLastTickMs = performance.now();
|
||||
spaceShooterSpawnAccMs = 0;
|
||||
spaceShooterFireCd = 0;
|
||||
spaceShooterSessionStartMs = performance.now();
|
||||
spaceShooterLastMoveEmit = 0;
|
||||
if (spaceShooterMissionCountdownTimer) {
|
||||
clearTimeout(spaceShooterMissionCountdownTimer);
|
||||
spaceShooterMissionCountdownTimer = null;
|
||||
}
|
||||
if (isSpaceShooterMissionUiMapPlay()) {
|
||||
spaceShooterMissionPhase = 'howto';
|
||||
hideConflictingOverlaysForGauntletCrown();
|
||||
applySpaceShooterMissionPanelImages();
|
||||
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 () { showSpaceShooterMissionHowtoOverlay(); }, 60);
|
||||
} else {
|
||||
spaceShooterMissionPhase = null;
|
||||
spaceShooterGameEnded = false;
|
||||
spaceShooterBullets = [];
|
||||
spaceShooterAsteroids = [];
|
||||
spaceShooterPopups = [];
|
||||
spaceShooterLastTickMs = performance.now();
|
||||
spaceShooterSpawnAccMs = 0;
|
||||
spaceShooterFireCd = 0;
|
||||
spaceShooterSessionStartMs = performance.now();
|
||||
spaceShooterLastMoveEmit = 0;
|
||||
}
|
||||
}
|
||||
if (mapData.gameType === 'balloon_boss') {
|
||||
balloonBossGameEnded = false;
|
||||
@@ -10199,7 +10563,19 @@
|
||||
timeVal.textContent = '∞';
|
||||
}
|
||||
} else if (isSpaceShooter()) {
|
||||
if (spaceShooterGameEnded) {
|
||||
if (isSpaceShooterMissionUiMapPlay()) {
|
||||
if (spaceShooterMissionPhase !== 'live' || spaceShooterGameEnded) {
|
||||
const limSs = spaceShooterTimeLimitSecPlay();
|
||||
if (spaceShooterMissionPhase === 'howto' || spaceShooterMissionPhase === 'countdown') {
|
||||
timeVal.textContent = String(limSs > 0 ? limSs : 0);
|
||||
} else {
|
||||
timeVal.textContent = '0';
|
||||
}
|
||||
} else {
|
||||
const remSs = spaceShooterRemainingSecPlay();
|
||||
timeVal.textContent = remSs != null ? String(remSs) : '∞';
|
||||
}
|
||||
} else if (spaceShooterGameEnded) {
|
||||
timeVal.textContent = '0';
|
||||
} else {
|
||||
const rem = spaceShooterRemainingSecPlay();
|
||||
@@ -11174,6 +11550,12 @@
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (isMovementKey(e.code) && isChatFocused()) return;
|
||||
if (isSpaceShooter() && mapData && !isChatFocused() && isSpaceShooterMissionPregameBlockingPlay()) {
|
||||
if (['Space', '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();
|
||||
@@ -11291,6 +11673,12 @@
|
||||
return;
|
||||
}
|
||||
if (isSpaceShooter()) {
|
||||
if (isSpaceShooterMissionPregameBlockingPlay()) {
|
||||
me.isWalking = false;
|
||||
draw();
|
||||
requestAnimationFrame(tick);
|
||||
return;
|
||||
}
|
||||
spaceShooterTickFrame();
|
||||
draw();
|
||||
requestAnimationFrame(tick);
|
||||
@@ -11507,6 +11895,13 @@
|
||||
beginJumpSurviveMissionCountdownThenRun();
|
||||
return;
|
||||
}
|
||||
if (isSpaceShooterMissionUiMapPlay()) {
|
||||
if (spaceShooterMissionPhase !== 'howto') return;
|
||||
const humansSs = quizCarryPregameHumanIds();
|
||||
if (!(humansSs.length === 1 || isMePlayHost())) return;
|
||||
beginSpaceShooterMissionCountdownThenRun();
|
||||
return;
|
||||
}
|
||||
if (!isGauntletCrownHeistMapPlay()) return;
|
||||
if (gauntletCrownPregamePhase !== 'howto') return;
|
||||
if (myId == null || !isMePlayHost()) return;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// ทุกครั้งที่มีการเพิ่มหรือเปลี่ยน ให้เพิ่ม v +0.0001
|
||||
// หลังแก้ค่าแล้วต้อง copy ไป path ที่ Nginx ชี้ (หรือรัน copy-frogger-files-only.sh) ถึงจะเห็นบนเว็บ
|
||||
window.APP_VERSION = '0.0182';
|
||||
window.APP_VERSION = '0.0183';
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var t = document.querySelector('.version-tag');
|
||||
if (t) t.textContent = 'v ' + window.APP_VERSION;
|
||||
|
||||
@@ -987,12 +987,9 @@
|
||||
justify-content: center;
|
||||
padding: clamp(4px, 1.2vw, 10px);
|
||||
border-radius: 14px;
|
||||
border: 2px solid rgba(0, 255, 240, 0.75);
|
||||
box-shadow:
|
||||
0 0 0 1px rgba(255, 0, 200, 0.55),
|
||||
0 0 28px rgba(0, 255, 240, 0.22),
|
||||
0 0 40px rgba(255, 0, 200, 0.12);
|
||||
background: rgba(6, 8, 18, 0.35);
|
||||
/* border: 2px solid rgba(0, 255, 240, 0.75); */
|
||||
/* box-shadow: 0 0 0 1px rgba(255, 0, 200, 0.55), 0 0 28px rgba(0, 255, 240, 0.22), 0 0 40px rgba(255, 0, 200, 0.12); */
|
||||
/* background: rgba(6, 8, 18, 0.35); */
|
||||
pointer-events: auto;
|
||||
}
|
||||
.gch-bg,
|
||||
@@ -1018,10 +1015,10 @@
|
||||
box-sizing: border-box;
|
||||
color: #e2e8f0;
|
||||
font-family: ui-sans-serif, system-ui, 'Segoe UI', 'Kanit', sans-serif;
|
||||
background: rgba(8, 10, 22, 0.72);
|
||||
/* background: rgba(8, 10, 22, 0.72); */
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(0, 255, 240, 0.35);
|
||||
box-shadow: inset 0 0 24px rgba(0, 0, 0, 0.35);
|
||||
/* border: 1px solid rgba(0, 255, 240, 0.35); */
|
||||
/* box-shadow: inset 0 0 24px rgba(0, 0, 0, 0.35); */
|
||||
}
|
||||
.gch-inner.gch-inner--art {
|
||||
left: 0;
|
||||
@@ -1899,8 +1896,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<script src="/Game/socket.io/socket.io.js"></script>
|
||||
<script src="js/version.js?v=0.0182"></script>
|
||||
<script src="js/play.js?v=0.210"></script>
|
||||
<script src="js/version.js?v=0.0183"></script>
|
||||
<script src="js/play.js?v=0.213"></script>
|
||||
<div class="version-tag">v —</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -692,6 +692,8 @@
|
||||
let playJumpSurviveJumpHeightMult = 1.5;
|
||||
/** จำกัดเวลารอบภารกิจกระโดดขึ้นแท่น (วินาที) — จาก GET /api/game-timing; 0 = ใช้ค่าเริ่ม 60 ในเกม */
|
||||
let playJumpSurviveMissionTimeSec = 0;
|
||||
/** ยิงยานอวกาศ (space_shooter): เวลารอบจาก game-timing; 0 = ใช้ค่าเริ่ม 90 ในเกมถ้าแมปไม่ทับ */
|
||||
let playSpaceShooterMissionTimeSec = 0;
|
||||
/** 0 = ไม่จำกัด — จาก game-timing / gauntlet-sync (พรมแดง Gauntlet เท่านั้น) */
|
||||
let gauntletRuntimeTimeLimitSec = 0;
|
||||
/** เวลาสิ้นสุดรอบ (epoch ms) จากเซิร์ฟเวอร์ — null = ไม่จับเวลา */
|
||||
@@ -5379,6 +5381,14 @@
|
||||
playJumpSurviveMissionTimeSec = 0;
|
||||
}
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(payload, 'spaceShooterMissionTimeSec')) {
|
||||
const st = Number(payload.spaceShooterMissionTimeSec);
|
||||
if (Number.isFinite(st) && st > 0) {
|
||||
playSpaceShooterMissionTimeSec = Math.max(10, Math.min(7200, Math.floor(st)));
|
||||
} else {
|
||||
playSpaceShooterMissionTimeSec = 0;
|
||||
}
|
||||
}
|
||||
if (isStack() && stackMini) reapplyStackMiniSizingFromGlobals();
|
||||
}
|
||||
|
||||
@@ -6560,6 +6570,8 @@
|
||||
const t = Number(mapData && mapData.spaceShooterTimeSec);
|
||||
if (Number.isFinite(t) && t === 0) return 0;
|
||||
if (Number.isFinite(t) && t > 0 && t < 7200) return Math.floor(t);
|
||||
const g = Number(playSpaceShooterMissionTimeSec);
|
||||
if (Number.isFinite(g) && g > 0 && g < 7200) return Math.floor(g);
|
||||
return 90;
|
||||
}
|
||||
|
||||
|
||||
@@ -540,6 +540,8 @@ function defaultGameTiming() {
|
||||
jumpSurviveJumpHeightMult: 1.5,
|
||||
/** จำกัดเวลารอบภารกิจ/มินิเกมกระโดดขึ้นแท่น (วินาที) — 0 = ไคลเอนต์ใช้ 60 วิ; ไม่ใช่ค่าเดียวกับพรมแดง */
|
||||
jumpSurviveMissionTimeSec: 0,
|
||||
/** ยิงยานอวกาศ / space_shooter: จำกัดเวลารอบ (วินาที) — 0 = ไคลเอนต์ใช้ค่าเริ่ม 90; แมป spaceShooterTimeSec > 0 ทับ */
|
||||
spaceShooterMissionTimeSec: 0,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -570,6 +572,12 @@ function clampJumpSurviveMissionTimeSec(n) {
|
||||
return Math.max(10, Math.min(7200, Math.floor(v)));
|
||||
}
|
||||
|
||||
function clampSpaceShooterMissionTimeSec(n) {
|
||||
const v = Number(n);
|
||||
if (Number.isNaN(v) || v <= 0) return 0;
|
||||
return Math.max(10, Math.min(7200, Math.floor(v)));
|
||||
}
|
||||
|
||||
function clampGauntletTickMs(n) {
|
||||
const v = Number(n);
|
||||
if (Number.isNaN(v)) return GAUNTLET_DEFAULT_TICK_MS;
|
||||
@@ -681,6 +689,9 @@ function loadGameTiming() {
|
||||
jumpSurviveMissionTimeSec: Object.prototype.hasOwnProperty.call(j, 'jumpSurviveMissionTimeSec')
|
||||
? clampJumpSurviveMissionTimeSec(j.jumpSurviveMissionTimeSec)
|
||||
: 0,
|
||||
spaceShooterMissionTimeSec: Object.prototype.hasOwnProperty.call(j, 'spaceShooterMissionTimeSec')
|
||||
? clampSpaceShooterMissionTimeSec(j.spaceShooterMissionTimeSec)
|
||||
: 0,
|
||||
};
|
||||
}
|
||||
} catch (e) { console.error('loadGameTiming', e.message); }
|
||||
@@ -727,6 +738,12 @@ function saveGameTimingToFile(d) {
|
||||
const jumpMissionSec = Object.prototype.hasOwnProperty.call(d, 'jumpSurviveMissionTimeSec')
|
||||
? clampJumpSurviveMissionTimeSec(d.jumpSurviveMissionTimeSec)
|
||||
: clampJumpSurviveMissionTimeSec(prevJumpMissionSec);
|
||||
const prevSpaceShooterMissionSec = Object.prototype.hasOwnProperty.call(prev, 'spaceShooterMissionTimeSec')
|
||||
? prev.spaceShooterMissionTimeSec
|
||||
: 0;
|
||||
const spaceShooterMissionSec = Object.prototype.hasOwnProperty.call(d, 'spaceShooterMissionTimeSec')
|
||||
? clampSpaceShooterMissionTimeSec(d.spaceShooterMissionTimeSec)
|
||||
: clampSpaceShooterMissionTimeSec(prevSpaceShooterMissionSec);
|
||||
const out = {
|
||||
gauntletTickMs: clampGauntletTickMs(d.gauntletTickMs),
|
||||
gauntletJumpTicks: clampGauntletJumpTicks(d.gauntletJumpTicks),
|
||||
@@ -736,6 +753,7 @@ function saveGameTimingToFile(d) {
|
||||
stackBlockWidthTiles: stackBlockW,
|
||||
jumpSurviveJumpHeightMult: jumpMult,
|
||||
jumpSurviveMissionTimeSec: jumpMissionSec,
|
||||
spaceShooterMissionTimeSec: spaceShooterMissionSec,
|
||||
};
|
||||
fs.writeFileSync(GAME_TIMING_PATH, JSON.stringify(out, null, 2), 'utf8');
|
||||
runtimeGameTiming = out;
|
||||
|
||||