minigame 5 jumper 1.1
@@ -33,6 +33,11 @@ GA="$DST/Game/public/img/gauntlet-assets"
|
||||
mkdir -p "$GA"
|
||||
chown www-data:www-data "$GA"
|
||||
chmod 2775 "$GA"
|
||||
# Jump Survival (Jumper) — โฟลเดอร์รูป; FTP มัก MKD ไม่ได้ถ้ายังไม่มีบนเซิร์ฟ
|
||||
JUMPER="$DST/Game/public/img/Jumper"
|
||||
mkdir -p "$JUMPER"
|
||||
chown www-data:www-data "$JUMPER"
|
||||
chmod 2775 "$JUMPER"
|
||||
|
||||
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
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
<button type="button" class="tab" data-tab="quiz" role="tab" id="tab-quiz" aria-controls="tab-panel-quiz"><span class="tab-label">คำถามเกม</span><span class="tab-desc">ถูก/ผิด · เวลา</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-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">กระโดดให้รอด</span><span class="tab-desc">ความสูงกระโดด · timing</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="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>
|
||||
|
||||
|
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: 5.5 MiB |
@@ -0,0 +1,9 @@
|
||||
Assets for Jump Survival / Jumper mode — sprites, tiles, UI.
|
||||
Upload files here; path in browser: /Game/img/Jumper/<filename> (public URL)
|
||||
|
||||
ฉาก editor id **mnptfts2** — ไฟล์ UI ที่ play.js อ้างอิง (ถ้าไม่มีจะ fallback ไป gauntlet-assets):
|
||||
- **popup-Howto.png** — พื้นหลัง HOW TO ก่อนเริ่ม (ข้อความวิธีเล่นใส่ในรูปนี้ — หน้าเกมไม่ซ้อนข้อความ HTML)
|
||||
- **popup-result.png** — พื้นหลังสรุปภารกิจตอนจบ
|
||||
- **stamp-sacrifice.png** — ตราประทับ “ผู้เสียสละ” บนผู้เล่นที่ตกรอบ (optional; ถ้าโหลดไม่ได้ใช้ result-Lose_stamp)
|
||||
|
||||
โฟลเดอร์นี้สร้างจาก repo + deploy — ถ้า FTP ยังอัปโหลดไม่ได้ ให้เช็คสิทธิ์ parent
|
||||
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 48 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: 212 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 473 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: 3.9 KiB |
@@ -21,6 +21,8 @@
|
||||
const quizCarryUseMissionSummaryOverlay = playMapIdFromQuery === quizCarryMissionSummaryMapId;
|
||||
/** Gauntlet พรมแดง — ฉากนี้จาก editor (?map=mno9kb07): วาดตัวหันขวา + ใช้รูป lane/laser จาก game-timing */
|
||||
const GAUNTLET_FACE_RIGHT_MAP_ID = 'mno9kb07';
|
||||
/** Jump Survival ฉาก Jumper — UI สรุปภารกิจแบบ crown + รูปใน /img/Jumper/ (editor ?map=mnptfts2) */
|
||||
const JUMP_SURVIVE_MISSION_MAP_ID = 'mnptfts2';
|
||||
/** จนกว่าโหลด /api/characters — placeholder ชั่วคราวก่อน join */
|
||||
const LEGACY_PLACEHOLDER_CHARACTER_ID = 'Chatest';
|
||||
let firstCharacterDefaultResolved = null;
|
||||
@@ -818,6 +820,10 @@
|
||||
let jumpSurviveJumpQueued = false;
|
||||
let jumpSurviveOnGround = false;
|
||||
let jumpSurviveLastEmitT = 0;
|
||||
/** mnptfts2: howto → countdown → live → ended (คะแนน 100 ผู้รอดสุดท้าย) */
|
||||
let jumpSurviveMissionPhase = null;
|
||||
let jumpSurviveGameEnded = false;
|
||||
let jumperMissionCountdownTimer = null;
|
||||
|
||||
/** space_shooter — ยิงหิน (จุดเกิดจากกริด P1–P6) */
|
||||
let spaceShooterBullets = [];
|
||||
@@ -2471,6 +2477,358 @@
|
||||
}
|
||||
function isStack() { return mapData && mapData.gameType === 'stack'; }
|
||||
function isJumpSurvive() { return mapData && mapData.gameType === 'jump_survive'; }
|
||||
|
||||
function currentPlayMapId() {
|
||||
const q = (playSessionMapId || '').trim();
|
||||
if (q) return q;
|
||||
if (mapData && mapData.id != null && String(mapData.id).trim() !== '') return String(mapData.id).trim();
|
||||
return (playMapIdFromQuery || '').trim();
|
||||
}
|
||||
|
||||
function isJumpSurviveMissionUiMapPlay() {
|
||||
return isJumpSurvive() && currentPlayMapId() === JUMP_SURVIVE_MISSION_MAP_ID;
|
||||
}
|
||||
|
||||
function isJumpSurviveMissionPregameBlockingPlay() {
|
||||
if (!isJumpSurviveMissionUiMapPlay()) return false;
|
||||
return jumpSurviveMissionPhase != null && jumpSurviveMissionPhase !== 'live' && jumpSurviveMissionPhase !== 'ended';
|
||||
}
|
||||
|
||||
function jumperAssetUrl(file) {
|
||||
return BASE + '/img/Jumper/' + file;
|
||||
}
|
||||
|
||||
function hideConflictingOverlaysForJumpSurviveMission() {
|
||||
hideConflictingOverlaysForGauntletCrown();
|
||||
}
|
||||
|
||||
function jumpSurviveTimeLimitSecForMission() {
|
||||
const m = Number(mapData && mapData.jumpSurviveTimeSec);
|
||||
if (Number.isFinite(m) && m > 0 && m < 7200) return Math.floor(m);
|
||||
const g = Number(gauntletRuntimeTimeLimitSec);
|
||||
if (Number.isFinite(g) && g > 0 && g < 7200) return Math.floor(g);
|
||||
return 60;
|
||||
}
|
||||
|
||||
function jumpSurviveRemainingSecMission() {
|
||||
if (!isJumpSurviveMissionUiMapPlay() || jumpSurviveMissionPhase !== 'live' || jumpSurviveSessionStartMs <= 0) return null;
|
||||
const lim = jumpSurviveTimeLimitSecForMission();
|
||||
if (!(lim > 0)) return null;
|
||||
const elapsed = (performance.now() - jumpSurviveSessionStartMs) / 1000;
|
||||
return Math.max(0, Math.ceil(lim - elapsed));
|
||||
}
|
||||
|
||||
function applyJumpSurviveJumperPanelImages() {
|
||||
const howtoBg = document.querySelector('#gauntlet-crown-howto-overlay .gch-bg');
|
||||
if (howtoBg) {
|
||||
howtoBg.src = jumperAssetUrl('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 = jumperAssetUrl('popup-result.png');
|
||||
resBg.onerror = function () {
|
||||
this.onerror = null;
|
||||
this.src = BASE + '/img/gauntlet-assets/popup-result.png';
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function showJumpSurviveMissionHowtoOverlay() {
|
||||
if (!isJumpSurviveMissionUiMapPlay()) return;
|
||||
hideConflictingOverlaysForJumpSurviveMission();
|
||||
applyJumpSurviveJumperPanelImages();
|
||||
if (jumperMissionCountdownTimer) {
|
||||
clearTimeout(jumperMissionCountdownTimer);
|
||||
jumperMissionCountdownTimer = null;
|
||||
}
|
||||
const cd = document.getElementById('gauntlet-crown-countdown');
|
||||
if (cd) cd.classList.add('is-hidden');
|
||||
jumpSurviveMissionPhase = '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 beginJumpSurviveMissionCountdownThenRun() {
|
||||
if (!isJumpSurviveMissionUiMapPlay()) return;
|
||||
if (jumperMissionCountdownTimer) {
|
||||
clearTimeout(jumperMissionCountdownTimer);
|
||||
jumperMissionCountdownTimer = null;
|
||||
}
|
||||
jumpSurviveMissionPhase = '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 (jumperMissionCountdownTimer) {
|
||||
clearTimeout(jumperMissionCountdownTimer);
|
||||
jumperMissionCountdownTimer = null;
|
||||
}
|
||||
jumpSurviveMissionPhase = 'live';
|
||||
jumpSurviveGameEnded = false;
|
||||
jumpSurviveEliminated = false;
|
||||
jumpSurviveInitRuntime();
|
||||
};
|
||||
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);
|
||||
jumperMissionCountdownTimer = setTimeout(step, 1000);
|
||||
} else {
|
||||
jumperMissionCountdownTimer = null;
|
||||
runFinish();
|
||||
}
|
||||
};
|
||||
jumperMissionCountdownTimer = setTimeout(step, 1000);
|
||||
}
|
||||
|
||||
/** Jumper ภารกิจ mnptfts2: เกรดจากจำนวนผู้รอด — 6=A … 3=D; น้อยกว่า 2 = F; พอดี 2 คน = E */
|
||||
function jumpSurviveGradeFromSurvivorCount(survivors) {
|
||||
const s = Math.max(0, Math.floor(Number(survivors) || 0));
|
||||
if (s >= 6) return 'A';
|
||||
if (s === 5) return 'B';
|
||||
if (s === 4) return 'C';
|
||||
if (s === 3) return 'D';
|
||||
if (s === 2) return 'E';
|
||||
return 'F';
|
||||
}
|
||||
|
||||
function jumpSurviveRollRewardCardForGrade(grade) {
|
||||
if (grade === 'F') return null;
|
||||
const r = Math.random();
|
||||
if (grade === 'A' || grade === 'B') {
|
||||
if (r < 0.35) return { kind: 'bail', th: 'เหรียญประกัน · Bail Coin', en: 'Bail Coin' };
|
||||
if (r < 0.7) return { kind: 'rare', th: 'การ์ด Rare', en: 'Rare card' };
|
||||
return { kind: 'common', th: 'การ์ดธรรมดา', en: 'Common card' };
|
||||
}
|
||||
if (grade === 'C' || grade === 'D') {
|
||||
if (r < 0.25) return { kind: 'bail', th: 'เหรียญประกัน · Bail Coin', en: 'Bail Coin' };
|
||||
if (r < 0.45) return { kind: 'rare', th: 'การ์ด Rare', en: 'Rare card' };
|
||||
return { kind: 'common', th: 'การ์ดธรรมดา', en: 'Common card' };
|
||||
}
|
||||
if (grade === 'E') {
|
||||
if (r < 0.12) return { kind: 'rare', th: 'การ์ด Rare', en: 'Rare card' };
|
||||
return { kind: 'common', th: 'การ์ดธรรมดา', en: 'Common card' };
|
||||
}
|
||||
if (r < 0.25) return { kind: 'bail', th: 'เหรียญประกัน · Bail Coin', en: 'Bail Coin' };
|
||||
if (r < 0.45) return { kind: 'rare', th: 'การ์ด Rare', en: 'Rare card' };
|
||||
return { kind: 'common', th: 'การ์ดธรรมดา', en: 'Common card' };
|
||||
}
|
||||
|
||||
function jumpSurviveMissionTotalParticipants() {
|
||||
let n = myId != null ? 1 : 0;
|
||||
if (others && typeof others.size === 'number') n += others.size;
|
||||
return n;
|
||||
}
|
||||
|
||||
function jumpSurviveMissionCountAlive() {
|
||||
let n = 0;
|
||||
if (myId != null && !jumpSurviveEliminated) n++;
|
||||
if (others && typeof others.forEach === 'function') {
|
||||
others.forEach(function (o) {
|
||||
if (o && !o.jumpSurviveEliminated) n++;
|
||||
});
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/** จบทันทีเมื่อไม่มีผู้รอด หรือเหลือคนเดียว (หลายคนในรอบ) — โหมด solo 1 คนยังเล่นจนหมดเวลา */
|
||||
function jumpSurviveMissionMaybeEarlyFinish() {
|
||||
if (!isJumpSurviveMissionUiMapPlay() || jumpSurviveMissionPhase !== 'live' || jumpSurviveGameEnded) return;
|
||||
const total = jumpSurviveMissionTotalParticipants();
|
||||
const alive = jumpSurviveMissionCountAlive();
|
||||
if (alive === 0) {
|
||||
endJumpSurviveMissionRound();
|
||||
return;
|
||||
}
|
||||
if (total >= 2 && alive === 1) {
|
||||
endJumpSurviveMissionRound();
|
||||
}
|
||||
}
|
||||
|
||||
function jumpSurviveBuildMissionPayload() {
|
||||
const rows = [];
|
||||
const pushEnt = function (id, nickname, characterId, eliminated) {
|
||||
const alive = !eliminated;
|
||||
const baseScore = alive ? 100 : 0;
|
||||
rows.push({
|
||||
id: id,
|
||||
nickname: (nickname && String(nickname).trim()) ? String(nickname).trim() : String(id),
|
||||
characterId: characterId ?? null,
|
||||
baseScore: baseScore,
|
||||
eliminated: !!eliminated,
|
||||
rankBonus: 0,
|
||||
finalScore: baseScore,
|
||||
});
|
||||
};
|
||||
if (myId != null) {
|
||||
pushEnt(myId, (me.nickname || nick || 'คุณ').trim() || 'คุณ', me.characterId || getPlayCharacterId(), jumpSurviveEliminated);
|
||||
}
|
||||
others.forEach(function (o, id) {
|
||||
if (!o) return;
|
||||
pushEnt(id, o.nickname, o.characterId, !!o.jumpSurviveEliminated);
|
||||
});
|
||||
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));
|
||||
});
|
||||
let survivorCount = 0;
|
||||
for (let si = 0; si < rows.length; si++) {
|
||||
if (rows[si] && !rows[si].eliminated) survivorCount++;
|
||||
}
|
||||
const participantCount = rows.length;
|
||||
const grade = jumpSurviveGradeFromSurvivorCount(survivorCount);
|
||||
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: row.eliminated,
|
||||
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 rewardCard = jumpSurviveRollRewardCardForGrade(grade);
|
||||
return {
|
||||
ranked: ranked,
|
||||
totalSum: totalSum,
|
||||
averageScore: averageScore,
|
||||
grade: grade,
|
||||
rewardCard: rewardCard,
|
||||
totalParts: totalParts,
|
||||
uiSkin: 'jumper',
|
||||
survivorCount: survivorCount,
|
||||
participantCount: participantCount,
|
||||
};
|
||||
}
|
||||
|
||||
function jumpSurviveMergePreviewBotsMission(mission) {
|
||||
if (!mission || mission.uiSkin !== 'jumper' || !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: !!r.eliminated,
|
||||
rankBonus: 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: o.jumpSurviveEliminated ? 0 : 100,
|
||||
eliminated: !!o.jumpSurviveEliminated,
|
||||
rankBonus: 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));
|
||||
});
|
||||
let survivorCount = 0;
|
||||
for (let si = 0; si < baseRows.length; si++) {
|
||||
if (baseRows[si] && !baseRows[si].eliminated) survivorCount++;
|
||||
}
|
||||
const participantCount = baseRows.length;
|
||||
const grade = jumpSurviveGradeFromSurvivorCount(survivorCount);
|
||||
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: !!row.eliminated,
|
||||
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 n = ranked.length || 1;
|
||||
const averageScore = Math.floor(totalSum / n);
|
||||
return {
|
||||
ranked: ranked,
|
||||
totalSum: totalSum,
|
||||
averageScore: averageScore,
|
||||
grade: grade,
|
||||
rewardCard: jumpSurviveRollRewardCardForGrade(grade),
|
||||
totalParts: totalParts,
|
||||
uiSkin: 'jumper',
|
||||
survivorCount: survivorCount,
|
||||
participantCount: participantCount,
|
||||
};
|
||||
}
|
||||
|
||||
function endJumpSurviveMissionRound() {
|
||||
if (!isJumpSurviveMissionUiMapPlay() || jumpSurviveGameEnded) return;
|
||||
jumpSurviveGameEnded = true;
|
||||
jumpSurviveMissionPhase = 'ended';
|
||||
applyJumpSurviveJumperPanelImages();
|
||||
const mission = jumpSurviveBuildMissionPayload();
|
||||
showGauntletCrownMissionOverlay(mission);
|
||||
}
|
||||
function isSpaceShooter() { return mapData && mapData.gameType === 'space_shooter'; }
|
||||
function isBalloonBoss() { return mapData && mapData.gameType === 'balloon_boss'; }
|
||||
function isQuizCarry() { return mapData && mapData.gameType === 'quiz_carry'; }
|
||||
@@ -5375,9 +5733,13 @@
|
||||
updateGauntletCrownHowtoHud();
|
||||
}
|
||||
|
||||
/** Editor embed: เกรด F = ภาพจบแบบ gameover — เหมือน logic สรุป quiz_carry mission */
|
||||
/** Editor embed: quiz_carry / crown ใช้เกรด F = gameover — Jumper: ภาพจบใช้เฉพาะเมื่อไม่มีผู้รอด (0 คน) */
|
||||
function gauntletCrownEmbedMissionAnyOk(mission) {
|
||||
if (!mission) return false;
|
||||
if (mission.uiSkin === 'jumper') {
|
||||
const sc = Number(mission.survivorCount);
|
||||
return Number.isFinite(sc) ? sc > 0 : (String(mission.grade || '').trim().toUpperCase().charAt(0) !== 'F');
|
||||
}
|
||||
const g = String(mission.grade || '').trim().toUpperCase().charAt(0);
|
||||
return g !== 'F';
|
||||
}
|
||||
@@ -5411,6 +5773,7 @@
|
||||
*/
|
||||
function gauntletCrownBuildDisplayMission(mission) {
|
||||
if (!mission || !Array.isArray(mission.ranked)) return mission;
|
||||
if (mission.uiSkin === 'jumper') return mission;
|
||||
if (!(previewFillBots && isGauntletCrownHeistMapPlay() && others && typeof others.forEach === 'function')) return mission;
|
||||
|
||||
const seen = new Set();
|
||||
@@ -5490,7 +5853,10 @@
|
||||
const bonusEl = document.getElementById('gcm-bonus');
|
||||
const btn = document.getElementById('btn-gcm-done');
|
||||
if (!ov || !rowEl || !mission || !Array.isArray(mission.ranked)) return;
|
||||
const disp = gauntletCrownBuildDisplayMission(mission);
|
||||
let disp = gauntletCrownBuildDisplayMission(mission);
|
||||
if (mission.uiSkin === 'jumper') {
|
||||
disp = jumpSurviveMergePreviewBotsMission(disp) || disp;
|
||||
}
|
||||
if (!disp || !Array.isArray(disp.ranked)) return;
|
||||
rowEl.innerHTML = '';
|
||||
disp.ranked.forEach((r) => {
|
||||
@@ -5529,8 +5895,14 @@
|
||||
if (r.eliminated) {
|
||||
const st = document.createElement('img');
|
||||
st.className = 'gcm-stamp';
|
||||
st.src = BASE + '/img/gauntlet-assets/result-Lose_stamp.png';
|
||||
st.alt = 'เสียชีวิต';
|
||||
const jSkin = mission && mission.uiSkin === 'jumper';
|
||||
st.src = jSkin ? jumperAssetUrl('stamp-sacrifice.png') : (BASE + '/img/gauntlet-assets/result-Lose_stamp.png');
|
||||
st.alt = jSkin ? 'ผู้เสียสละ' : 'เสียชีวิต';
|
||||
st.onerror = function () {
|
||||
this.onerror = null;
|
||||
this.src = BASE + '/img/gauntlet-assets/result-Lose_stamp.png';
|
||||
this.alt = 'เสียชีวิต';
|
||||
};
|
||||
frame.appendChild(st);
|
||||
}
|
||||
const nick = document.createElement('div');
|
||||
@@ -5538,8 +5910,9 @@
|
||||
nick.textContent = r.nickname || '—';
|
||||
const sc = document.createElement('div');
|
||||
sc.className = 'gcm-sc';
|
||||
const jumpPlain = mission && mission.uiSkin === 'jumper';
|
||||
const botScorePlain = previewFillBots && isGauntletCrownHeistMapPlay() && isPreviewBotId(r.id);
|
||||
sc.textContent = botScorePlain
|
||||
sc.textContent = (jumpPlain || botScorePlain)
|
||||
? String(Math.max(0, Number(r.baseScore) || 0))
|
||||
: String(r.baseScore || 0) + '(+' + String(r.rankBonus || 0) + ')';
|
||||
cell.appendChild(tag);
|
||||
@@ -5552,7 +5925,14 @@
|
||||
const parts = (disp.totalParts || []).map((n) => String(Math.max(0, Number(n) || 0)));
|
||||
const sum = Math.max(0, Number(disp.totalSum) || 0);
|
||||
const avg = Math.max(0, Math.floor(Number(disp.averageScore) || 0));
|
||||
totalEl.innerHTML = 'คะแนนรวม <span class="gcm-total-nums">(' + parts.join('+') + ') = ' + sum + '</span> · เฉลี่ย ' + avg + ' → เกรด';
|
||||
const jumperSkin = mission && mission.uiSkin === 'jumper';
|
||||
if (jumperSkin) {
|
||||
const sc = Math.max(0, Math.floor(Number(disp.survivorCount) || 0));
|
||||
const pc = Math.max(0, Math.floor(Number(disp.participantCount) || 0));
|
||||
totalEl.innerHTML = 'คะแนนรวม <span class="gcm-total-nums">(' + parts.join('+') + ') = ' + sum + '</span> · ผู้รอด ' + sc + '/' + pc + ' → เกรด';
|
||||
} else {
|
||||
totalEl.innerHTML = 'คะแนนรวม <span class="gcm-total-nums">(' + parts.join('+') + ') = ' + sum + '</span> · เฉลี่ย ' + avg + ' → เกรด';
|
||||
}
|
||||
}
|
||||
if (gradeEl) {
|
||||
const g = String(disp.grade || 'C').toUpperCase().charAt(0);
|
||||
@@ -5561,28 +5941,39 @@
|
||||
}
|
||||
if (bonusEl) {
|
||||
bonusEl.innerHTML = '';
|
||||
const row = document.createElement('div');
|
||||
row.style.display = 'flex';
|
||||
row.style.alignItems = 'center';
|
||||
row.style.gap = '8px';
|
||||
const chk = document.createElement('img');
|
||||
chk.src = BASE + '/img/gauntlet-assets/result-check.png';
|
||||
chk.alt = '';
|
||||
chk.width = 26;
|
||||
chk.height = 26;
|
||||
const sp = document.createElement('span');
|
||||
sp.style.color = '#9ece6a';
|
||||
sp.style.fontWeight = '700';
|
||||
sp.textContent = 'ภารกิจสำเร็จ';
|
||||
row.appendChild(chk);
|
||||
row.appendChild(sp);
|
||||
bonusEl.appendChild(row);
|
||||
const gLetter = String(disp.grade || 'C').toUpperCase().charAt(0);
|
||||
const missionOk = gLetter !== 'F';
|
||||
if (missionOk) {
|
||||
const row = document.createElement('div');
|
||||
row.style.display = 'flex';
|
||||
row.style.alignItems = 'center';
|
||||
row.style.gap = '8px';
|
||||
const chk = document.createElement('img');
|
||||
chk.src = BASE + '/img/gauntlet-assets/result-check.png';
|
||||
chk.alt = '';
|
||||
chk.width = 26;
|
||||
chk.height = 26;
|
||||
const sp = document.createElement('span');
|
||||
sp.style.color = '#9ece6a';
|
||||
sp.style.fontWeight = '700';
|
||||
sp.textContent = 'ภารกิจสำเร็จ';
|
||||
row.appendChild(chk);
|
||||
row.appendChild(sp);
|
||||
bonusEl.appendChild(row);
|
||||
}
|
||||
const rc = disp.rewardCard;
|
||||
if (rc && rc.th) {
|
||||
if (missionOk && rc && rc.th) {
|
||||
const pl = document.createElement('div');
|
||||
pl.className = 'gcm-plaque';
|
||||
pl.innerHTML = '<strong>' + String(rc.th) + '</strong><span>' + String(rc.en || '') + '</span>';
|
||||
bonusEl.appendChild(pl);
|
||||
} else if (!missionOk) {
|
||||
const pl = document.createElement('div');
|
||||
pl.className = 'gcm-plaque';
|
||||
pl.style.borderColor = 'rgba(148, 153, 168, 0.45)';
|
||||
pl.style.opacity = '0.95';
|
||||
pl.innerHTML = '<strong>ไม่ได้รับการ์ด</strong><span>เกรด ' + gLetter + ' · ภารกิจไม่ผ่านเกณฑ์</span>';
|
||||
bonusEl.appendChild(pl);
|
||||
}
|
||||
}
|
||||
ov.classList.remove('is-hidden');
|
||||
@@ -7342,8 +7733,14 @@
|
||||
jumpSurvivePlatformScrollPx = 0;
|
||||
const { cw, ch } = getCharacterFootprintWH(mapData);
|
||||
const ts = tileSize;
|
||||
jumpSurviveCamCenterX = (me.x + cw * 0.5) * ts;
|
||||
jumpSurviveCamCenterY = (me.y + ch * 0.5) * ts;
|
||||
if (isJumpSurviveMissionUiMapPlay()) {
|
||||
const w = mapData.width || 20, h = mapData.height || 15;
|
||||
jumpSurviveCamCenterX = (w * ts) * 0.5;
|
||||
jumpSurviveCamCenterY = (h * ts) * 0.5;
|
||||
} else {
|
||||
jumpSurviveCamCenterX = (me.x + cw * 0.5) * ts;
|
||||
jumpSurviveCamCenterY = (me.y + ch * 0.5) * ts;
|
||||
}
|
||||
}
|
||||
|
||||
function jumpSurviveOverlapsObjectSolids(md, left, top, right, bottom) {
|
||||
@@ -7420,8 +7817,12 @@
|
||||
}
|
||||
|
||||
function jumpSurviveCameraTargetPx(md) {
|
||||
const { cw, ch } = getCharacterFootprintWH(md);
|
||||
const ts = tileSize;
|
||||
const w = md.width || 20, h = md.height || 15;
|
||||
if (isJumpSurviveMissionUiMapPlay()) {
|
||||
return { px: (w * ts) * 0.5, py: (h * ts) * 0.5 };
|
||||
}
|
||||
const { cw, ch } = getCharacterFootprintWH(md);
|
||||
const cx = (e) => (e.x + cw * 0.5) * ts;
|
||||
const cy = (e) => (e.y + ch * 0.5) * ts;
|
||||
if (!jumpSurviveEliminated) return { px: cx(me), py: cy(me) };
|
||||
@@ -7527,6 +7928,14 @@
|
||||
|
||||
function jumpSurviveTickFrame() {
|
||||
if (!mapData || !isJumpSurvive()) return;
|
||||
if (isJumpSurviveMissionUiMapPlay()) {
|
||||
if (jumpSurviveMissionPhase !== 'live' || jumpSurviveGameEnded) return;
|
||||
const remEnd = jumpSurviveRemainingSecMission();
|
||||
if (remEnd !== null && remEnd <= 0) {
|
||||
endJumpSurviveMissionRound();
|
||||
return;
|
||||
}
|
||||
}
|
||||
const md = mapData;
|
||||
const ts = tileSize;
|
||||
const now = performance.now();
|
||||
@@ -7544,6 +7953,7 @@
|
||||
|
||||
if (jumpSurviveEliminated) {
|
||||
stepJumpSurvivePreviewBots(dt, halfViewH);
|
||||
jumpSurviveMissionMaybeEarlyFinish();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -7592,6 +8002,8 @@
|
||||
socket.emit('move', { x: me.x, y: me.y, direction: me.direction });
|
||||
}
|
||||
}
|
||||
|
||||
jumpSurviveMissionMaybeEarlyFinish();
|
||||
}
|
||||
|
||||
function gridImageLibIdleUrlPlay(entry) {
|
||||
@@ -7907,6 +8319,7 @@
|
||||
gauntletJumpVis: typeof p.gauntletJumpTicks === 'number' ? p.gauntletJumpTicks : 0,
|
||||
gauntletScore: (() => { const s = Number(p.gauntletScore); return Number.isFinite(s) ? Math.max(0, s) : 0; })(),
|
||||
gauntletEliminated: !!p.gauntletEliminated,
|
||||
jumpSurviveEliminated: false,
|
||||
spaceShooterScore: (() => { const s = Number(p.spaceShooterScore); return Number.isFinite(s) ? Math.max(0, s) : 0; })(),
|
||||
balloonBossScore: (() => { const s = Number(p.balloonBossScore); return Number.isFinite(s) ? Math.max(0, s) : 0; })(),
|
||||
balloonBossBalloons: (() => {
|
||||
@@ -7969,7 +8382,24 @@
|
||||
if (!mapData.jumpSurvivePlatformArea) mapData.jumpSurvivePlatformArea = [];
|
||||
normalizeJumpSurvivePlatformAreaInPlay(mapData);
|
||||
jumpSurviveEliminated = false;
|
||||
jumpSurviveInitRuntime();
|
||||
jumpSurviveGameEnded = false;
|
||||
if (jumperMissionCountdownTimer) {
|
||||
clearTimeout(jumperMissionCountdownTimer);
|
||||
jumperMissionCountdownTimer = null;
|
||||
}
|
||||
if (isJumpSurviveMissionUiMapPlay()) {
|
||||
jumpSurviveMissionPhase = 'howto';
|
||||
hideConflictingOverlaysForJumpSurviveMission();
|
||||
applyJumpSurviveJumperPanelImages();
|
||||
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 () { showJumpSurviveMissionHowtoOverlay(); }, 50);
|
||||
} else {
|
||||
jumpSurviveMissionPhase = null;
|
||||
jumpSurviveInitRuntime();
|
||||
}
|
||||
}
|
||||
if (mapData.gameType === 'space_shooter') {
|
||||
spaceShooterGameEnded = false;
|
||||
@@ -8011,6 +8441,15 @@
|
||||
const gccR = document.getElementById('gauntlet-crown-countdown');
|
||||
if (gccR) gccR.classList.add('is-hidden');
|
||||
setTimeout(function () { showGauntletCrownHowtoOverlay(); }, 50);
|
||||
} else if (mapData.gameType === 'jump_survive' && isJumpSurviveMissionUiMapPlay()) {
|
||||
if (gauntletCrownCountdownTimer) {
|
||||
clearTimeout(gauntletCrownCountdownTimer);
|
||||
gauntletCrownCountdownTimer = null;
|
||||
}
|
||||
const gccRj = document.getElementById('gauntlet-crown-countdown');
|
||||
if (gccRj) gccRj.classList.add('is-hidden');
|
||||
gauntletCrownPregamePhase = null;
|
||||
gauntletCrownLobbyReadyMap = {};
|
||||
} else {
|
||||
const hov = document.getElementById('gauntlet-crown-howto-overlay');
|
||||
if (hov) {
|
||||
@@ -8105,6 +8544,7 @@
|
||||
gauntletJumpTicks: typeof data.gauntletJumpTicks === 'number' ? data.gauntletJumpTicks : 0,
|
||||
gauntletJumpVis: typeof data.gauntletJumpTicks === 'number' ? data.gauntletJumpTicks : 0,
|
||||
gauntletScore: (() => { const s = Number(data.gauntletScore); return Number.isFinite(s) ? Math.max(0, s) : 0; })(),
|
||||
jumpSurviveEliminated: false,
|
||||
spaceShooterScore: (() => { const s = Number(data.spaceShooterScore); return Number.isFinite(s) ? Math.max(0, s) : 0; })(),
|
||||
balloonBossScore: (() => { const s = Number(data.balloonBossScore); return Number.isFinite(s) ? Math.max(0, s) : 0; })(),
|
||||
balloonBossBalloons: (() => {
|
||||
@@ -8527,9 +8967,12 @@
|
||||
}
|
||||
} else {
|
||||
gauntletEndsAtMs = null;
|
||||
const hto3 = document.getElementById('gauntlet-crown-howto-overlay');
|
||||
if (hto3) hto3.classList.add('is-hidden');
|
||||
gauntletCrownHowtoVisible = false;
|
||||
const skipHideCrownShell = mapData.gameType === 'jump_survive' && isJumpSurviveMissionUiMapPlay();
|
||||
if (!skipHideCrownShell) {
|
||||
const hto3 = document.getElementById('gauntlet-crown-howto-overlay');
|
||||
if (hto3) hto3.classList.add('is-hidden');
|
||||
gauntletCrownHowtoVisible = false;
|
||||
}
|
||||
gauntletCrownPregamePhase = null;
|
||||
gauntletCrownLobbyReadyMap = {};
|
||||
if (gauntletCrownCountdownTimer) {
|
||||
@@ -8571,7 +9014,24 @@
|
||||
if (!mapData.jumpSurvivePlatformArea) mapData.jumpSurvivePlatformArea = [];
|
||||
normalizeJumpSurvivePlatformAreaInPlay(mapData);
|
||||
jumpSurviveEliminated = false;
|
||||
jumpSurviveInitRuntime();
|
||||
jumpSurviveGameEnded = false;
|
||||
if (jumperMissionCountdownTimer) {
|
||||
clearTimeout(jumperMissionCountdownTimer);
|
||||
jumperMissionCountdownTimer = null;
|
||||
}
|
||||
if (isJumpSurviveMissionUiMapPlay()) {
|
||||
jumpSurviveMissionPhase = 'howto';
|
||||
hideConflictingOverlaysForJumpSurviveMission();
|
||||
applyJumpSurviveJumperPanelImages();
|
||||
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 () { showJumpSurviveMissionHowtoOverlay(); }, 60);
|
||||
} else {
|
||||
jumpSurviveMissionPhase = null;
|
||||
jumpSurviveInitRuntime();
|
||||
}
|
||||
}
|
||||
if (mapData.gameType === 'space_shooter') {
|
||||
spaceShooterGameEnded = false;
|
||||
@@ -9630,7 +10090,8 @@
|
||||
? 'QUIZ CARRY · COURT — หยิบป้ายถูกแล้วส่งที่ฮับ (F / Grab)'
|
||||
: (isGauntlet() ? 'GAUNTLET · SURVIVAL RUN'
|
||||
: (isSpaceShooter() ? 'SPACE SHOOTER · ARCADE'
|
||||
: (isBalloonBoss() ? 'BALLOON BOSS · MEGA VIRUS' : 'JUMP SURVIVE · NODE UPLINK')));
|
||||
: (isBalloonBoss() ? 'BALLOON BOSS · MEGA VIRUS'
|
||||
: (isJumpSurviveMissionUiMapPlay() ? 'JUMPER · SURVIVE — เหลือเวลา (วินาที) · TIME (sec)' : 'JUMP SURVIVE · NODE UPLINK'))));
|
||||
}
|
||||
if (timeVal) {
|
||||
if (isQuizCarry()) {
|
||||
@@ -9677,6 +10138,13 @@
|
||||
const rem = balloonBossRemainingSecPlay();
|
||||
timeVal.textContent = rem != null ? String(rem) : '∞';
|
||||
}
|
||||
} else if (isJumpSurviveMissionUiMapPlay()) {
|
||||
if (jumpSurviveMissionPhase !== 'live' || jumpSurviveGameEnded) {
|
||||
timeVal.textContent = jumpSurviveMissionPhase === 'howto' ? '···' : (jumpSurviveMissionPhase === 'countdown' ? '···' : '0');
|
||||
} else {
|
||||
const rem = jumpSurviveRemainingSecMission();
|
||||
timeVal.textContent = rem != null ? String(rem) : '∞';
|
||||
}
|
||||
} else {
|
||||
const t = jumpSurviveSessionStartMs > 0
|
||||
? Math.max(0, Math.floor((performance.now() - jumpSurviveSessionStartMs) / 1000))
|
||||
@@ -9724,9 +10192,15 @@
|
||||
? 'ชุดคำถามจบแล้ว · Session ended — ดูคะแนนด้าน SCORE'
|
||||
: ('ยืนโซนตัวเลือกแล้วกด F/Grab หยิบ · ถือป้ายถูกแล้วไปฮับส่ง — ถูก +' + QUIZ_CARRY_POINTS_PER_CORRECT + ' แต้ม (correct +' + QUIZ_CARRY_POINTS_PER_CORRECT + ' on hub)');
|
||||
} else if (isJumpSurvive()) {
|
||||
hintEl.textContent = jumpSurviveEliminated
|
||||
? 'Spectate remaining nodes · Exit room to reset session'
|
||||
: 'Space / W / ↑ jump · A D / arrows move · Hit ceiling/floor = out (watch others)';
|
||||
if (isJumpSurviveMissionUiMapPlay()) {
|
||||
hintEl.textContent = jumpSurviveEliminated
|
||||
? 'คุณตกน้ำแล้ว — ดูเพื่อนต่อ — รอจบเพื่อรับคะแนน (ผู้รอดได้ 100 คะแนน)'
|
||||
: 'กระโดดตามแท่น — หลีกหนาม/กับดักด้านล่าง · หมดเวลาแล้วสรุปคะแนน (ผู้รอด 100) · Space / W / ↑ กระโดด · A D / ← → เดิน';
|
||||
} else {
|
||||
hintEl.textContent = jumpSurviveEliminated
|
||||
? 'Spectate remaining nodes · Exit room to reset session'
|
||||
: 'Space / W / ↑ jump · A D / arrows move · Hit ceiling/floor = out (watch others)';
|
||||
}
|
||||
} else if (isSpaceShooter()) {
|
||||
hintEl.textContent = spaceShooterGameEnded
|
||||
? 'จบแล้ว · Session ended — ดูอันดับบน overlay / see rankings on overlay'
|
||||
@@ -9758,6 +10232,7 @@
|
||||
const ul = document.getElementById('play-cyber-score-list');
|
||||
if (!ul) return;
|
||||
const safeYv = (v) => (typeof v === 'number' && !isNaN(v) ? v : 1);
|
||||
const jumpMissionHud = isJumpSurvive() && isJumpSurviveMissionUiMapPlay();
|
||||
const rows = [];
|
||||
rows.push({
|
||||
id: myId,
|
||||
@@ -9766,7 +10241,8 @@
|
||||
score: isQuizCarry() ? cyberHudQuizCarryPeerScore(myId)
|
||||
: (isGauntlet() ? (me.gauntletScore || 0)
|
||||
: (isSpaceShooter() ? (me.spaceShooterScore || 0)
|
||||
: (isBalloonBoss() ? (me.balloonBossScore || 0) : 0))),
|
||||
: (isBalloonBoss() ? (me.balloonBossScore || 0)
|
||||
: (jumpMissionHud ? (jumpSurviveEliminated ? 0 : 100) : 0)))),
|
||||
y: safeYv(me.y),
|
||||
eliminated: !!(isJumpSurvive() && jumpSurviveEliminated) || !!(isBalloonBoss() && me.balloonBossEliminated)
|
||||
|| !!(isGauntletCrownHeistMapPlay() && me.gauntletEliminated),
|
||||
@@ -9780,7 +10256,8 @@
|
||||
score: isQuizCarry() ? cyberHudQuizCarryPeerScore(id)
|
||||
: (isGauntlet() ? (o.gauntletScore || 0)
|
||||
: (isSpaceShooter() ? (o.spaceShooterScore || 0)
|
||||
: (isBalloonBoss() ? (o.balloonBossScore || 0) : 0))),
|
||||
: (isBalloonBoss() ? (o.balloonBossScore || 0)
|
||||
: (jumpMissionHud ? (o.jumpSurviveEliminated ? 0 : 100) : 0)))),
|
||||
y: safeYv(o.y),
|
||||
eliminated: !!(isJumpSurvive() && isPreviewBotId(id) && o.jumpSurviveEliminated)
|
||||
|| !!(isBalloonBoss() && o.balloonBossEliminated)
|
||||
@@ -9789,7 +10266,7 @@
|
||||
});
|
||||
});
|
||||
let leaderId = null;
|
||||
if (isGauntlet() || isSpaceShooter() || isBalloonBoss() || isQuizCarry()) {
|
||||
if (isGauntlet() || isSpaceShooter() || isBalloonBoss() || isQuizCarry() || jumpMissionHud) {
|
||||
let mx = -1;
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
if (rows[i].score > mx) {
|
||||
@@ -9807,7 +10284,11 @@
|
||||
}
|
||||
}
|
||||
rows.sort((a, b) => {
|
||||
if (isGauntlet() || isSpaceShooter() || isBalloonBoss() || isQuizCarry()) return b.score - a.score;
|
||||
if (isGauntlet() || isSpaceShooter() || isBalloonBoss() || isQuizCarry() || jumpMissionHud) {
|
||||
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');
|
||||
}
|
||||
if (a.eliminated !== b.eliminated) return a.eliminated ? 1 : -1;
|
||||
return a.y - b.y;
|
||||
});
|
||||
@@ -9856,7 +10337,7 @@
|
||||
mid.appendChild(st);
|
||||
const sc = document.createElement('span');
|
||||
sc.className = 'play-cyber-score-val';
|
||||
if (isGauntlet() || isSpaceShooter() || isBalloonBoss() || isQuizCarry()) {
|
||||
if (isGauntlet() || isSpaceShooter() || isBalloonBoss() || isQuizCarry() || jumpMissionHud) {
|
||||
sc.textContent = String(row.score);
|
||||
} else {
|
||||
sc.textContent = row.eliminated ? '—' : String(r + 1);
|
||||
@@ -10652,6 +11133,10 @@
|
||||
}
|
||||
}
|
||||
if (isJumpSurvive() && mapData && !isChatFocused()) {
|
||||
if (isJumpSurviveMissionPregameBlockingPlay()) {
|
||||
if (['Space', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'KeyW', 'KeyA', 'KeyS', 'KeyD'].includes(e.code)) e.preventDefault();
|
||||
return;
|
||||
}
|
||||
if (jumpSurviveEliminated) {
|
||||
if (['Space', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'KeyW', 'KeyA', 'KeyS', 'KeyD'].includes(e.code)) e.preventDefault();
|
||||
return;
|
||||
@@ -10720,6 +11205,12 @@
|
||||
return;
|
||||
}
|
||||
if (isJumpSurvive()) {
|
||||
if (isJumpSurviveMissionPregameBlockingPlay()) {
|
||||
me.isWalking = false;
|
||||
draw();
|
||||
requestAnimationFrame(tick);
|
||||
return;
|
||||
}
|
||||
jumpSurviveTickFrame();
|
||||
draw();
|
||||
requestAnimationFrame(tick);
|
||||
@@ -10935,6 +11426,13 @@
|
||||
const btn = document.getElementById('btn-gch-ready');
|
||||
if (!btn) return;
|
||||
btn.addEventListener('click', () => {
|
||||
if (isJumpSurviveMissionUiMapPlay()) {
|
||||
if (jumpSurviveMissionPhase !== 'howto') return;
|
||||
const humans = quizCarryPregameHumanIds();
|
||||
if (!(humans.length === 1 || isMePlayHost())) return;
|
||||
beginJumpSurviveMissionCountdownThenRun();
|
||||
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.0175';
|
||||
window.APP_VERSION = '0.0180';
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var t = document.querySelector('.version-tag');
|
||||
if (t) t.textContent = 'v ' + window.APP_VERSION;
|
||||
|
||||
@@ -572,15 +572,21 @@
|
||||
overflow: auto;
|
||||
}
|
||||
.play-quiz-scoreboard-list li {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 0.4rem;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(2.25rem, auto);
|
||||
align-items: baseline;
|
||||
column-gap: 0.5rem;
|
||||
font-size: 0.74rem;
|
||||
color: #c0caf5;
|
||||
padding: 0.15rem 0;
|
||||
}
|
||||
.play-quiz-scoreboard-me { color: #9ece6a !important; font-weight: 700; }
|
||||
.play-quiz-scoreboard-val { font-variant-numeric: tabular-nums; font-weight: 800; }
|
||||
.play-quiz-scoreboard-val {
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-weight: 800;
|
||||
text-align: right;
|
||||
justify-self: end;
|
||||
}
|
||||
.play-quiz-scoreboard-me .play-quiz-scoreboard-val { color: #9ece6a; }
|
||||
#play-quiz-feedback {
|
||||
position: fixed;
|
||||
@@ -686,7 +692,7 @@
|
||||
width: clamp(68px, 16vw, 104px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
align-items: stretch;
|
||||
gap: 4px;
|
||||
text-align: center;
|
||||
color: #e2e8f0;
|
||||
@@ -731,20 +737,26 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
.qc-mission-rank-nick {
|
||||
flex: 1 1 auto;
|
||||
min-height: 2.5em;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
font-size: clamp(0.52rem, 1.45vw, 0.68rem);
|
||||
font-weight: 700;
|
||||
color: #e2e8f0;
|
||||
line-height: 1.15;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
word-break: break-word;
|
||||
hyphens: auto;
|
||||
text-align: center;
|
||||
}
|
||||
.qc-mission-rank-score {
|
||||
flex: 0 0 auto;
|
||||
margin-top: auto;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-size: clamp(0.62rem, 1.85vw, 0.78rem);
|
||||
font-weight: 800;
|
||||
color: #f6e08a;
|
||||
@@ -799,6 +811,8 @@
|
||||
.qc-mission-grade--a { color: #9ece6a; text-shadow: 0 0 24px rgba(158, 206, 106, 0.55); }
|
||||
.qc-mission-grade--b { color: #7aa2f7; text-shadow: 0 0 24px rgba(122, 162, 247, 0.5); }
|
||||
.qc-mission-grade--c { color: #e0af68; text-shadow: 0 0 22px rgba(224, 175, 104, 0.45); }
|
||||
.qc-mission-grade--d { color: #bb9af7; text-shadow: 0 0 20px rgba(187, 154, 247, 0.4); }
|
||||
.qc-mission-grade--e { color: #7dcfff; text-shadow: 0 0 20px rgba(125, 207, 255, 0.35); }
|
||||
.qc-mission-grade--f { color: #f7768e; text-shadow: 0 0 22px rgba(247, 118, 140, 0.45); }
|
||||
.qc-mission-bonus-row {
|
||||
display: flex;
|
||||
@@ -1134,6 +1148,7 @@
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
align-items: stretch;
|
||||
gap: clamp(6px, 1.5vw, 10px);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
@@ -1141,8 +1156,16 @@
|
||||
flex: 0 0 auto;
|
||||
width: clamp(72px, 18vw, 100px);
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
.gcm-rank-tag {
|
||||
flex: 0 0 auto;
|
||||
min-height: 1.15em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: clamp(0.55rem, 1.4vw, 0.65rem);
|
||||
font-weight: 800;
|
||||
color: #f6e08a;
|
||||
@@ -1150,6 +1173,7 @@
|
||||
}
|
||||
.gcm-frame {
|
||||
position: relative;
|
||||
flex: 0 0 auto;
|
||||
width: 100%;
|
||||
aspect-ratio: 1;
|
||||
border-radius: 8px;
|
||||
@@ -1173,17 +1197,28 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
.gcm-nick {
|
||||
flex: 1 1 auto;
|
||||
min-height: 2.55em;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
font-size: clamp(0.52rem, 1.35vw, 0.65rem);
|
||||
font-weight: 700;
|
||||
color: #e2e8f0;
|
||||
margin-top: 4px;
|
||||
word-break: break-word;
|
||||
line-height: 1.15;
|
||||
}
|
||||
.gcm-sc {
|
||||
flex: 0 0 auto;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-size: clamp(0.58rem, 1.5vw, 0.72rem);
|
||||
font-weight: 800;
|
||||
color: #f6e08a;
|
||||
margin-top: 2px;
|
||||
margin-top: auto;
|
||||
padding-top: 3px;
|
||||
}
|
||||
.gcm-total {
|
||||
text-align: center;
|
||||
@@ -1219,6 +1254,9 @@
|
||||
.gcm-grade--a { color: #9ece6a; text-shadow: 0 0 20px rgba(158, 206, 106, 0.5); }
|
||||
.gcm-grade--b { color: #7aa2f7; }
|
||||
.gcm-grade--c { color: #e0af68; }
|
||||
.gcm-grade--d { color: #bb9af7; text-shadow: 0 0 16px rgba(187, 154, 247, 0.4); }
|
||||
.gcm-grade--e { color: #7dcfff; text-shadow: 0 0 16px rgba(125, 207, 255, 0.35); }
|
||||
.gcm-grade--f { color: #f7768e; text-shadow: 0 0 18px rgba(247, 118, 140, 0.45); }
|
||||
.gcm-bonus {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -1373,7 +1411,7 @@
|
||||
}
|
||||
.play-cyber-score-row {
|
||||
display: grid;
|
||||
grid-template-columns: 36px 1fr auto;
|
||||
grid-template-columns: 36px minmax(0, 1fr) minmax(2.75rem, auto);
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
padding: 0.28rem 0;
|
||||
@@ -1418,6 +1456,9 @@
|
||||
color: #7af8ff;
|
||||
text-shadow: 0 0 10px rgba(0, 255, 240, 0.35);
|
||||
font-variant-numeric: tabular-nums;
|
||||
text-align: right;
|
||||
justify-self: end;
|
||||
min-width: 2.5rem;
|
||||
}
|
||||
.play-cyber-lead-crown {
|
||||
display: inline-block;
|
||||
@@ -1858,8 +1899,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<script src="/Game/socket.io/socket.io.js"></script>
|
||||
<script src="js/version.js?v=0.0175"></script>
|
||||
<script src="js/play.js?v=0.201"></script>
|
||||
<script src="js/version.js?v=0.0180"></script>
|
||||
<script src="js/play.js?v=0.206"></script>
|
||||
<div class="version-tag">v —</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
const quizCarryUseMissionSummaryOverlay = playMapIdFromQuery === quizCarryMissionSummaryMapId;
|
||||
/** Gauntlet พรมแดง — ฉากนี้จาก editor (?map=mno9kb07): วาดตัวหันขวา + ใช้รูป lane/laser จาก game-timing */
|
||||
const GAUNTLET_FACE_RIGHT_MAP_ID = 'mno9kb07';
|
||||
/** Jump Survival ฉาก Jumper — UI สรุปภารกิจแบบ crown + รูปใน /img/Jumper/ (editor ?map=mnptfts2) */
|
||||
const JUMP_SURVIVE_MISSION_MAP_ID = 'mnptfts2';
|
||||
/** จนกว่าโหลด /api/characters — placeholder ชั่วคราวก่อน join */
|
||||
const LEGACY_PLACEHOLDER_CHARACTER_ID = 'Chatest';
|
||||
let firstCharacterDefaultResolved = null;
|
||||
@@ -818,6 +820,10 @@
|
||||
let jumpSurviveJumpQueued = false;
|
||||
let jumpSurviveOnGround = false;
|
||||
let jumpSurviveLastEmitT = 0;
|
||||
/** mnptfts2: howto → countdown → live → ended (คะแนน 100 ผู้รอดสุดท้าย) */
|
||||
let jumpSurviveMissionPhase = null;
|
||||
let jumpSurviveGameEnded = false;
|
||||
let jumperMissionCountdownTimer = null;
|
||||
|
||||
/** space_shooter — ยิงหิน (จุดเกิดจากกริด P1–P6) */
|
||||
let spaceShooterBullets = [];
|
||||
@@ -2471,6 +2477,358 @@
|
||||
}
|
||||
function isStack() { return mapData && mapData.gameType === 'stack'; }
|
||||
function isJumpSurvive() { return mapData && mapData.gameType === 'jump_survive'; }
|
||||
|
||||
function currentPlayMapId() {
|
||||
const q = (playSessionMapId || '').trim();
|
||||
if (q) return q;
|
||||
if (mapData && mapData.id != null && String(mapData.id).trim() !== '') return String(mapData.id).trim();
|
||||
return (playMapIdFromQuery || '').trim();
|
||||
}
|
||||
|
||||
function isJumpSurviveMissionUiMapPlay() {
|
||||
return isJumpSurvive() && currentPlayMapId() === JUMP_SURVIVE_MISSION_MAP_ID;
|
||||
}
|
||||
|
||||
function isJumpSurviveMissionPregameBlockingPlay() {
|
||||
if (!isJumpSurviveMissionUiMapPlay()) return false;
|
||||
return jumpSurviveMissionPhase != null && jumpSurviveMissionPhase !== 'live' && jumpSurviveMissionPhase !== 'ended';
|
||||
}
|
||||
|
||||
function jumperAssetUrl(file) {
|
||||
return BASE + '/img/Jumper/' + file;
|
||||
}
|
||||
|
||||
function hideConflictingOverlaysForJumpSurviveMission() {
|
||||
hideConflictingOverlaysForGauntletCrown();
|
||||
}
|
||||
|
||||
function jumpSurviveTimeLimitSecForMission() {
|
||||
const m = Number(mapData && mapData.jumpSurviveTimeSec);
|
||||
if (Number.isFinite(m) && m > 0 && m < 7200) return Math.floor(m);
|
||||
const g = Number(gauntletRuntimeTimeLimitSec);
|
||||
if (Number.isFinite(g) && g > 0 && g < 7200) return Math.floor(g);
|
||||
return 60;
|
||||
}
|
||||
|
||||
function jumpSurviveRemainingSecMission() {
|
||||
if (!isJumpSurviveMissionUiMapPlay() || jumpSurviveMissionPhase !== 'live' || jumpSurviveSessionStartMs <= 0) return null;
|
||||
const lim = jumpSurviveTimeLimitSecForMission();
|
||||
if (!(lim > 0)) return null;
|
||||
const elapsed = (performance.now() - jumpSurviveSessionStartMs) / 1000;
|
||||
return Math.max(0, Math.ceil(lim - elapsed));
|
||||
}
|
||||
|
||||
function applyJumpSurviveJumperPanelImages() {
|
||||
const howtoBg = document.querySelector('#gauntlet-crown-howto-overlay .gch-bg');
|
||||
if (howtoBg) {
|
||||
howtoBg.src = jumperAssetUrl('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 = jumperAssetUrl('popup-result.png');
|
||||
resBg.onerror = function () {
|
||||
this.onerror = null;
|
||||
this.src = BASE + '/img/gauntlet-assets/popup-result.png';
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function showJumpSurviveMissionHowtoOverlay() {
|
||||
if (!isJumpSurviveMissionUiMapPlay()) return;
|
||||
hideConflictingOverlaysForJumpSurviveMission();
|
||||
applyJumpSurviveJumperPanelImages();
|
||||
if (jumperMissionCountdownTimer) {
|
||||
clearTimeout(jumperMissionCountdownTimer);
|
||||
jumperMissionCountdownTimer = null;
|
||||
}
|
||||
const cd = document.getElementById('gauntlet-crown-countdown');
|
||||
if (cd) cd.classList.add('is-hidden');
|
||||
jumpSurviveMissionPhase = '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 beginJumpSurviveMissionCountdownThenRun() {
|
||||
if (!isJumpSurviveMissionUiMapPlay()) return;
|
||||
if (jumperMissionCountdownTimer) {
|
||||
clearTimeout(jumperMissionCountdownTimer);
|
||||
jumperMissionCountdownTimer = null;
|
||||
}
|
||||
jumpSurviveMissionPhase = '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 (jumperMissionCountdownTimer) {
|
||||
clearTimeout(jumperMissionCountdownTimer);
|
||||
jumperMissionCountdownTimer = null;
|
||||
}
|
||||
jumpSurviveMissionPhase = 'live';
|
||||
jumpSurviveGameEnded = false;
|
||||
jumpSurviveEliminated = false;
|
||||
jumpSurviveInitRuntime();
|
||||
};
|
||||
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);
|
||||
jumperMissionCountdownTimer = setTimeout(step, 1000);
|
||||
} else {
|
||||
jumperMissionCountdownTimer = null;
|
||||
runFinish();
|
||||
}
|
||||
};
|
||||
jumperMissionCountdownTimer = setTimeout(step, 1000);
|
||||
}
|
||||
|
||||
/** Jumper ภารกิจ mnptfts2: เกรดจากจำนวนผู้รอด — 6=A … 3=D; น้อยกว่า 2 = F; พอดี 2 คน = E */
|
||||
function jumpSurviveGradeFromSurvivorCount(survivors) {
|
||||
const s = Math.max(0, Math.floor(Number(survivors) || 0));
|
||||
if (s >= 6) return 'A';
|
||||
if (s === 5) return 'B';
|
||||
if (s === 4) return 'C';
|
||||
if (s === 3) return 'D';
|
||||
if (s === 2) return 'E';
|
||||
return 'F';
|
||||
}
|
||||
|
||||
function jumpSurviveRollRewardCardForGrade(grade) {
|
||||
if (grade === 'F') return null;
|
||||
const r = Math.random();
|
||||
if (grade === 'A' || grade === 'B') {
|
||||
if (r < 0.35) return { kind: 'bail', th: 'เหรียญประกัน · Bail Coin', en: 'Bail Coin' };
|
||||
if (r < 0.7) return { kind: 'rare', th: 'การ์ด Rare', en: 'Rare card' };
|
||||
return { kind: 'common', th: 'การ์ดธรรมดา', en: 'Common card' };
|
||||
}
|
||||
if (grade === 'C' || grade === 'D') {
|
||||
if (r < 0.25) return { kind: 'bail', th: 'เหรียญประกัน · Bail Coin', en: 'Bail Coin' };
|
||||
if (r < 0.45) return { kind: 'rare', th: 'การ์ด Rare', en: 'Rare card' };
|
||||
return { kind: 'common', th: 'การ์ดธรรมดา', en: 'Common card' };
|
||||
}
|
||||
if (grade === 'E') {
|
||||
if (r < 0.12) return { kind: 'rare', th: 'การ์ด Rare', en: 'Rare card' };
|
||||
return { kind: 'common', th: 'การ์ดธรรมดา', en: 'Common card' };
|
||||
}
|
||||
if (r < 0.25) return { kind: 'bail', th: 'เหรียญประกัน · Bail Coin', en: 'Bail Coin' };
|
||||
if (r < 0.45) return { kind: 'rare', th: 'การ์ด Rare', en: 'Rare card' };
|
||||
return { kind: 'common', th: 'การ์ดธรรมดา', en: 'Common card' };
|
||||
}
|
||||
|
||||
function jumpSurviveMissionTotalParticipants() {
|
||||
let n = myId != null ? 1 : 0;
|
||||
if (others && typeof others.size === 'number') n += others.size;
|
||||
return n;
|
||||
}
|
||||
|
||||
function jumpSurviveMissionCountAlive() {
|
||||
let n = 0;
|
||||
if (myId != null && !jumpSurviveEliminated) n++;
|
||||
if (others && typeof others.forEach === 'function') {
|
||||
others.forEach(function (o) {
|
||||
if (o && !o.jumpSurviveEliminated) n++;
|
||||
});
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/** จบทันทีเมื่อไม่มีผู้รอด หรือเหลือคนเดียว (หลายคนในรอบ) — โหมด solo 1 คนยังเล่นจนหมดเวลา */
|
||||
function jumpSurviveMissionMaybeEarlyFinish() {
|
||||
if (!isJumpSurviveMissionUiMapPlay() || jumpSurviveMissionPhase !== 'live' || jumpSurviveGameEnded) return;
|
||||
const total = jumpSurviveMissionTotalParticipants();
|
||||
const alive = jumpSurviveMissionCountAlive();
|
||||
if (alive === 0) {
|
||||
endJumpSurviveMissionRound();
|
||||
return;
|
||||
}
|
||||
if (total >= 2 && alive === 1) {
|
||||
endJumpSurviveMissionRound();
|
||||
}
|
||||
}
|
||||
|
||||
function jumpSurviveBuildMissionPayload() {
|
||||
const rows = [];
|
||||
const pushEnt = function (id, nickname, characterId, eliminated) {
|
||||
const alive = !eliminated;
|
||||
const baseScore = alive ? 100 : 0;
|
||||
rows.push({
|
||||
id: id,
|
||||
nickname: (nickname && String(nickname).trim()) ? String(nickname).trim() : String(id),
|
||||
characterId: characterId ?? null,
|
||||
baseScore: baseScore,
|
||||
eliminated: !!eliminated,
|
||||
rankBonus: 0,
|
||||
finalScore: baseScore,
|
||||
});
|
||||
};
|
||||
if (myId != null) {
|
||||
pushEnt(myId, (me.nickname || nick || 'คุณ').trim() || 'คุณ', me.characterId || getPlayCharacterId(), jumpSurviveEliminated);
|
||||
}
|
||||
others.forEach(function (o, id) {
|
||||
if (!o) return;
|
||||
pushEnt(id, o.nickname, o.characterId, !!o.jumpSurviveEliminated);
|
||||
});
|
||||
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));
|
||||
});
|
||||
let survivorCount = 0;
|
||||
for (let si = 0; si < rows.length; si++) {
|
||||
if (rows[si] && !rows[si].eliminated) survivorCount++;
|
||||
}
|
||||
const participantCount = rows.length;
|
||||
const grade = jumpSurviveGradeFromSurvivorCount(survivorCount);
|
||||
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: row.eliminated,
|
||||
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 rewardCard = jumpSurviveRollRewardCardForGrade(grade);
|
||||
return {
|
||||
ranked: ranked,
|
||||
totalSum: totalSum,
|
||||
averageScore: averageScore,
|
||||
grade: grade,
|
||||
rewardCard: rewardCard,
|
||||
totalParts: totalParts,
|
||||
uiSkin: 'jumper',
|
||||
survivorCount: survivorCount,
|
||||
participantCount: participantCount,
|
||||
};
|
||||
}
|
||||
|
||||
function jumpSurviveMergePreviewBotsMission(mission) {
|
||||
if (!mission || mission.uiSkin !== 'jumper' || !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: !!r.eliminated,
|
||||
rankBonus: 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: o.jumpSurviveEliminated ? 0 : 100,
|
||||
eliminated: !!o.jumpSurviveEliminated,
|
||||
rankBonus: 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));
|
||||
});
|
||||
let survivorCount = 0;
|
||||
for (let si = 0; si < baseRows.length; si++) {
|
||||
if (baseRows[si] && !baseRows[si].eliminated) survivorCount++;
|
||||
}
|
||||
const participantCount = baseRows.length;
|
||||
const grade = jumpSurviveGradeFromSurvivorCount(survivorCount);
|
||||
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: !!row.eliminated,
|
||||
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 n = ranked.length || 1;
|
||||
const averageScore = Math.floor(totalSum / n);
|
||||
return {
|
||||
ranked: ranked,
|
||||
totalSum: totalSum,
|
||||
averageScore: averageScore,
|
||||
grade: grade,
|
||||
rewardCard: jumpSurviveRollRewardCardForGrade(grade),
|
||||
totalParts: totalParts,
|
||||
uiSkin: 'jumper',
|
||||
survivorCount: survivorCount,
|
||||
participantCount: participantCount,
|
||||
};
|
||||
}
|
||||
|
||||
function endJumpSurviveMissionRound() {
|
||||
if (!isJumpSurviveMissionUiMapPlay() || jumpSurviveGameEnded) return;
|
||||
jumpSurviveGameEnded = true;
|
||||
jumpSurviveMissionPhase = 'ended';
|
||||
applyJumpSurviveJumperPanelImages();
|
||||
const mission = jumpSurviveBuildMissionPayload();
|
||||
showGauntletCrownMissionOverlay(mission);
|
||||
}
|
||||
function isSpaceShooter() { return mapData && mapData.gameType === 'space_shooter'; }
|
||||
function isBalloonBoss() { return mapData && mapData.gameType === 'balloon_boss'; }
|
||||
function isQuizCarry() { return mapData && mapData.gameType === 'quiz_carry'; }
|
||||
@@ -5375,9 +5733,13 @@
|
||||
updateGauntletCrownHowtoHud();
|
||||
}
|
||||
|
||||
/** Editor embed: เกรด F = ภาพจบแบบ gameover — เหมือน logic สรุป quiz_carry mission */
|
||||
/** Editor embed: quiz_carry / crown ใช้เกรด F = gameover — Jumper: ภาพจบใช้เฉพาะเมื่อไม่มีผู้รอด (0 คน) */
|
||||
function gauntletCrownEmbedMissionAnyOk(mission) {
|
||||
if (!mission) return false;
|
||||
if (mission.uiSkin === 'jumper') {
|
||||
const sc = Number(mission.survivorCount);
|
||||
return Number.isFinite(sc) ? sc > 0 : (String(mission.grade || '').trim().toUpperCase().charAt(0) !== 'F');
|
||||
}
|
||||
const g = String(mission.grade || '').trim().toUpperCase().charAt(0);
|
||||
return g !== 'F';
|
||||
}
|
||||
@@ -5411,6 +5773,7 @@
|
||||
*/
|
||||
function gauntletCrownBuildDisplayMission(mission) {
|
||||
if (!mission || !Array.isArray(mission.ranked)) return mission;
|
||||
if (mission.uiSkin === 'jumper') return mission;
|
||||
if (!(previewFillBots && isGauntletCrownHeistMapPlay() && others && typeof others.forEach === 'function')) return mission;
|
||||
|
||||
const seen = new Set();
|
||||
@@ -5490,7 +5853,10 @@
|
||||
const bonusEl = document.getElementById('gcm-bonus');
|
||||
const btn = document.getElementById('btn-gcm-done');
|
||||
if (!ov || !rowEl || !mission || !Array.isArray(mission.ranked)) return;
|
||||
const disp = gauntletCrownBuildDisplayMission(mission);
|
||||
let disp = gauntletCrownBuildDisplayMission(mission);
|
||||
if (mission.uiSkin === 'jumper') {
|
||||
disp = jumpSurviveMergePreviewBotsMission(disp) || disp;
|
||||
}
|
||||
if (!disp || !Array.isArray(disp.ranked)) return;
|
||||
rowEl.innerHTML = '';
|
||||
disp.ranked.forEach((r) => {
|
||||
@@ -5529,8 +5895,14 @@
|
||||
if (r.eliminated) {
|
||||
const st = document.createElement('img');
|
||||
st.className = 'gcm-stamp';
|
||||
st.src = BASE + '/img/gauntlet-assets/result-Lose_stamp.png';
|
||||
st.alt = 'เสียชีวิต';
|
||||
const jSkin = mission && mission.uiSkin === 'jumper';
|
||||
st.src = jSkin ? jumperAssetUrl('stamp-sacrifice.png') : (BASE + '/img/gauntlet-assets/result-Lose_stamp.png');
|
||||
st.alt = jSkin ? 'ผู้เสียสละ' : 'เสียชีวิต';
|
||||
st.onerror = function () {
|
||||
this.onerror = null;
|
||||
this.src = BASE + '/img/gauntlet-assets/result-Lose_stamp.png';
|
||||
this.alt = 'เสียชีวิต';
|
||||
};
|
||||
frame.appendChild(st);
|
||||
}
|
||||
const nick = document.createElement('div');
|
||||
@@ -5538,8 +5910,9 @@
|
||||
nick.textContent = r.nickname || '—';
|
||||
const sc = document.createElement('div');
|
||||
sc.className = 'gcm-sc';
|
||||
const jumpPlain = mission && mission.uiSkin === 'jumper';
|
||||
const botScorePlain = previewFillBots && isGauntletCrownHeistMapPlay() && isPreviewBotId(r.id);
|
||||
sc.textContent = botScorePlain
|
||||
sc.textContent = (jumpPlain || botScorePlain)
|
||||
? String(Math.max(0, Number(r.baseScore) || 0))
|
||||
: String(r.baseScore || 0) + '(+' + String(r.rankBonus || 0) + ')';
|
||||
cell.appendChild(tag);
|
||||
@@ -5552,7 +5925,14 @@
|
||||
const parts = (disp.totalParts || []).map((n) => String(Math.max(0, Number(n) || 0)));
|
||||
const sum = Math.max(0, Number(disp.totalSum) || 0);
|
||||
const avg = Math.max(0, Math.floor(Number(disp.averageScore) || 0));
|
||||
totalEl.innerHTML = 'คะแนนรวม <span class="gcm-total-nums">(' + parts.join('+') + ') = ' + sum + '</span> · เฉลี่ย ' + avg + ' → เกรด';
|
||||
const jumperSkin = mission && mission.uiSkin === 'jumper';
|
||||
if (jumperSkin) {
|
||||
const sc = Math.max(0, Math.floor(Number(disp.survivorCount) || 0));
|
||||
const pc = Math.max(0, Math.floor(Number(disp.participantCount) || 0));
|
||||
totalEl.innerHTML = 'คะแนนรวม <span class="gcm-total-nums">(' + parts.join('+') + ') = ' + sum + '</span> · ผู้รอด ' + sc + '/' + pc + ' → เกรด';
|
||||
} else {
|
||||
totalEl.innerHTML = 'คะแนนรวม <span class="gcm-total-nums">(' + parts.join('+') + ') = ' + sum + '</span> · เฉลี่ย ' + avg + ' → เกรด';
|
||||
}
|
||||
}
|
||||
if (gradeEl) {
|
||||
const g = String(disp.grade || 'C').toUpperCase().charAt(0);
|
||||
@@ -5561,28 +5941,39 @@
|
||||
}
|
||||
if (bonusEl) {
|
||||
bonusEl.innerHTML = '';
|
||||
const row = document.createElement('div');
|
||||
row.style.display = 'flex';
|
||||
row.style.alignItems = 'center';
|
||||
row.style.gap = '8px';
|
||||
const chk = document.createElement('img');
|
||||
chk.src = BASE + '/img/gauntlet-assets/result-check.png';
|
||||
chk.alt = '';
|
||||
chk.width = 26;
|
||||
chk.height = 26;
|
||||
const sp = document.createElement('span');
|
||||
sp.style.color = '#9ece6a';
|
||||
sp.style.fontWeight = '700';
|
||||
sp.textContent = 'ภารกิจสำเร็จ';
|
||||
row.appendChild(chk);
|
||||
row.appendChild(sp);
|
||||
bonusEl.appendChild(row);
|
||||
const gLetter = String(disp.grade || 'C').toUpperCase().charAt(0);
|
||||
const missionOk = gLetter !== 'F';
|
||||
if (missionOk) {
|
||||
const row = document.createElement('div');
|
||||
row.style.display = 'flex';
|
||||
row.style.alignItems = 'center';
|
||||
row.style.gap = '8px';
|
||||
const chk = document.createElement('img');
|
||||
chk.src = BASE + '/img/gauntlet-assets/result-check.png';
|
||||
chk.alt = '';
|
||||
chk.width = 26;
|
||||
chk.height = 26;
|
||||
const sp = document.createElement('span');
|
||||
sp.style.color = '#9ece6a';
|
||||
sp.style.fontWeight = '700';
|
||||
sp.textContent = 'ภารกิจสำเร็จ';
|
||||
row.appendChild(chk);
|
||||
row.appendChild(sp);
|
||||
bonusEl.appendChild(row);
|
||||
}
|
||||
const rc = disp.rewardCard;
|
||||
if (rc && rc.th) {
|
||||
if (missionOk && rc && rc.th) {
|
||||
const pl = document.createElement('div');
|
||||
pl.className = 'gcm-plaque';
|
||||
pl.innerHTML = '<strong>' + String(rc.th) + '</strong><span>' + String(rc.en || '') + '</span>';
|
||||
bonusEl.appendChild(pl);
|
||||
} else if (!missionOk) {
|
||||
const pl = document.createElement('div');
|
||||
pl.className = 'gcm-plaque';
|
||||
pl.style.borderColor = 'rgba(148, 153, 168, 0.45)';
|
||||
pl.style.opacity = '0.95';
|
||||
pl.innerHTML = '<strong>ไม่ได้รับการ์ด</strong><span>เกรด ' + gLetter + ' · ภารกิจไม่ผ่านเกณฑ์</span>';
|
||||
bonusEl.appendChild(pl);
|
||||
}
|
||||
}
|
||||
ov.classList.remove('is-hidden');
|
||||
@@ -7342,8 +7733,14 @@
|
||||
jumpSurvivePlatformScrollPx = 0;
|
||||
const { cw, ch } = getCharacterFootprintWH(mapData);
|
||||
const ts = tileSize;
|
||||
jumpSurviveCamCenterX = (me.x + cw * 0.5) * ts;
|
||||
jumpSurviveCamCenterY = (me.y + ch * 0.5) * ts;
|
||||
if (isJumpSurviveMissionUiMapPlay()) {
|
||||
const w = mapData.width || 20, h = mapData.height || 15;
|
||||
jumpSurviveCamCenterX = (w * ts) * 0.5;
|
||||
jumpSurviveCamCenterY = (h * ts) * 0.5;
|
||||
} else {
|
||||
jumpSurviveCamCenterX = (me.x + cw * 0.5) * ts;
|
||||
jumpSurviveCamCenterY = (me.y + ch * 0.5) * ts;
|
||||
}
|
||||
}
|
||||
|
||||
function jumpSurviveOverlapsObjectSolids(md, left, top, right, bottom) {
|
||||
@@ -7420,8 +7817,12 @@
|
||||
}
|
||||
|
||||
function jumpSurviveCameraTargetPx(md) {
|
||||
const { cw, ch } = getCharacterFootprintWH(md);
|
||||
const ts = tileSize;
|
||||
const w = md.width || 20, h = md.height || 15;
|
||||
if (isJumpSurviveMissionUiMapPlay()) {
|
||||
return { px: (w * ts) * 0.5, py: (h * ts) * 0.5 };
|
||||
}
|
||||
const { cw, ch } = getCharacterFootprintWH(md);
|
||||
const cx = (e) => (e.x + cw * 0.5) * ts;
|
||||
const cy = (e) => (e.y + ch * 0.5) * ts;
|
||||
if (!jumpSurviveEliminated) return { px: cx(me), py: cy(me) };
|
||||
@@ -7527,6 +7928,14 @@
|
||||
|
||||
function jumpSurviveTickFrame() {
|
||||
if (!mapData || !isJumpSurvive()) return;
|
||||
if (isJumpSurviveMissionUiMapPlay()) {
|
||||
if (jumpSurviveMissionPhase !== 'live' || jumpSurviveGameEnded) return;
|
||||
const remEnd = jumpSurviveRemainingSecMission();
|
||||
if (remEnd !== null && remEnd <= 0) {
|
||||
endJumpSurviveMissionRound();
|
||||
return;
|
||||
}
|
||||
}
|
||||
const md = mapData;
|
||||
const ts = tileSize;
|
||||
const now = performance.now();
|
||||
@@ -7544,6 +7953,7 @@
|
||||
|
||||
if (jumpSurviveEliminated) {
|
||||
stepJumpSurvivePreviewBots(dt, halfViewH);
|
||||
jumpSurviveMissionMaybeEarlyFinish();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -7592,6 +8002,8 @@
|
||||
socket.emit('move', { x: me.x, y: me.y, direction: me.direction });
|
||||
}
|
||||
}
|
||||
|
||||
jumpSurviveMissionMaybeEarlyFinish();
|
||||
}
|
||||
|
||||
function gridImageLibIdleUrlPlay(entry) {
|
||||
@@ -7907,6 +8319,7 @@
|
||||
gauntletJumpVis: typeof p.gauntletJumpTicks === 'number' ? p.gauntletJumpTicks : 0,
|
||||
gauntletScore: (() => { const s = Number(p.gauntletScore); return Number.isFinite(s) ? Math.max(0, s) : 0; })(),
|
||||
gauntletEliminated: !!p.gauntletEliminated,
|
||||
jumpSurviveEliminated: false,
|
||||
spaceShooterScore: (() => { const s = Number(p.spaceShooterScore); return Number.isFinite(s) ? Math.max(0, s) : 0; })(),
|
||||
balloonBossScore: (() => { const s = Number(p.balloonBossScore); return Number.isFinite(s) ? Math.max(0, s) : 0; })(),
|
||||
balloonBossBalloons: (() => {
|
||||
@@ -7969,7 +8382,24 @@
|
||||
if (!mapData.jumpSurvivePlatformArea) mapData.jumpSurvivePlatformArea = [];
|
||||
normalizeJumpSurvivePlatformAreaInPlay(mapData);
|
||||
jumpSurviveEliminated = false;
|
||||
jumpSurviveInitRuntime();
|
||||
jumpSurviveGameEnded = false;
|
||||
if (jumperMissionCountdownTimer) {
|
||||
clearTimeout(jumperMissionCountdownTimer);
|
||||
jumperMissionCountdownTimer = null;
|
||||
}
|
||||
if (isJumpSurviveMissionUiMapPlay()) {
|
||||
jumpSurviveMissionPhase = 'howto';
|
||||
hideConflictingOverlaysForJumpSurviveMission();
|
||||
applyJumpSurviveJumperPanelImages();
|
||||
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 () { showJumpSurviveMissionHowtoOverlay(); }, 50);
|
||||
} else {
|
||||
jumpSurviveMissionPhase = null;
|
||||
jumpSurviveInitRuntime();
|
||||
}
|
||||
}
|
||||
if (mapData.gameType === 'space_shooter') {
|
||||
spaceShooterGameEnded = false;
|
||||
@@ -8011,6 +8441,15 @@
|
||||
const gccR = document.getElementById('gauntlet-crown-countdown');
|
||||
if (gccR) gccR.classList.add('is-hidden');
|
||||
setTimeout(function () { showGauntletCrownHowtoOverlay(); }, 50);
|
||||
} else if (mapData.gameType === 'jump_survive' && isJumpSurviveMissionUiMapPlay()) {
|
||||
if (gauntletCrownCountdownTimer) {
|
||||
clearTimeout(gauntletCrownCountdownTimer);
|
||||
gauntletCrownCountdownTimer = null;
|
||||
}
|
||||
const gccRj = document.getElementById('gauntlet-crown-countdown');
|
||||
if (gccRj) gccRj.classList.add('is-hidden');
|
||||
gauntletCrownPregamePhase = null;
|
||||
gauntletCrownLobbyReadyMap = {};
|
||||
} else {
|
||||
const hov = document.getElementById('gauntlet-crown-howto-overlay');
|
||||
if (hov) {
|
||||
@@ -8105,6 +8544,7 @@
|
||||
gauntletJumpTicks: typeof data.gauntletJumpTicks === 'number' ? data.gauntletJumpTicks : 0,
|
||||
gauntletJumpVis: typeof data.gauntletJumpTicks === 'number' ? data.gauntletJumpTicks : 0,
|
||||
gauntletScore: (() => { const s = Number(data.gauntletScore); return Number.isFinite(s) ? Math.max(0, s) : 0; })(),
|
||||
jumpSurviveEliminated: false,
|
||||
spaceShooterScore: (() => { const s = Number(data.spaceShooterScore); return Number.isFinite(s) ? Math.max(0, s) : 0; })(),
|
||||
balloonBossScore: (() => { const s = Number(data.balloonBossScore); return Number.isFinite(s) ? Math.max(0, s) : 0; })(),
|
||||
balloonBossBalloons: (() => {
|
||||
@@ -8527,9 +8967,12 @@
|
||||
}
|
||||
} else {
|
||||
gauntletEndsAtMs = null;
|
||||
const hto3 = document.getElementById('gauntlet-crown-howto-overlay');
|
||||
if (hto3) hto3.classList.add('is-hidden');
|
||||
gauntletCrownHowtoVisible = false;
|
||||
const skipHideCrownShell = mapData.gameType === 'jump_survive' && isJumpSurviveMissionUiMapPlay();
|
||||
if (!skipHideCrownShell) {
|
||||
const hto3 = document.getElementById('gauntlet-crown-howto-overlay');
|
||||
if (hto3) hto3.classList.add('is-hidden');
|
||||
gauntletCrownHowtoVisible = false;
|
||||
}
|
||||
gauntletCrownPregamePhase = null;
|
||||
gauntletCrownLobbyReadyMap = {};
|
||||
if (gauntletCrownCountdownTimer) {
|
||||
@@ -8571,7 +9014,24 @@
|
||||
if (!mapData.jumpSurvivePlatformArea) mapData.jumpSurvivePlatformArea = [];
|
||||
normalizeJumpSurvivePlatformAreaInPlay(mapData);
|
||||
jumpSurviveEliminated = false;
|
||||
jumpSurviveInitRuntime();
|
||||
jumpSurviveGameEnded = false;
|
||||
if (jumperMissionCountdownTimer) {
|
||||
clearTimeout(jumperMissionCountdownTimer);
|
||||
jumperMissionCountdownTimer = null;
|
||||
}
|
||||
if (isJumpSurviveMissionUiMapPlay()) {
|
||||
jumpSurviveMissionPhase = 'howto';
|
||||
hideConflictingOverlaysForJumpSurviveMission();
|
||||
applyJumpSurviveJumperPanelImages();
|
||||
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 () { showJumpSurviveMissionHowtoOverlay(); }, 60);
|
||||
} else {
|
||||
jumpSurviveMissionPhase = null;
|
||||
jumpSurviveInitRuntime();
|
||||
}
|
||||
}
|
||||
if (mapData.gameType === 'space_shooter') {
|
||||
spaceShooterGameEnded = false;
|
||||
@@ -9630,7 +10090,8 @@
|
||||
? 'QUIZ CARRY · COURT — หยิบป้ายถูกแล้วส่งที่ฮับ (F / Grab)'
|
||||
: (isGauntlet() ? 'GAUNTLET · SURVIVAL RUN'
|
||||
: (isSpaceShooter() ? 'SPACE SHOOTER · ARCADE'
|
||||
: (isBalloonBoss() ? 'BALLOON BOSS · MEGA VIRUS' : 'JUMP SURVIVE · NODE UPLINK')));
|
||||
: (isBalloonBoss() ? 'BALLOON BOSS · MEGA VIRUS'
|
||||
: (isJumpSurviveMissionUiMapPlay() ? 'JUMPER · SURVIVE — เหลือเวลา (วินาที) · TIME (sec)' : 'JUMP SURVIVE · NODE UPLINK'))));
|
||||
}
|
||||
if (timeVal) {
|
||||
if (isQuizCarry()) {
|
||||
@@ -9677,6 +10138,13 @@
|
||||
const rem = balloonBossRemainingSecPlay();
|
||||
timeVal.textContent = rem != null ? String(rem) : '∞';
|
||||
}
|
||||
} else if (isJumpSurviveMissionUiMapPlay()) {
|
||||
if (jumpSurviveMissionPhase !== 'live' || jumpSurviveGameEnded) {
|
||||
timeVal.textContent = jumpSurviveMissionPhase === 'howto' ? '···' : (jumpSurviveMissionPhase === 'countdown' ? '···' : '0');
|
||||
} else {
|
||||
const rem = jumpSurviveRemainingSecMission();
|
||||
timeVal.textContent = rem != null ? String(rem) : '∞';
|
||||
}
|
||||
} else {
|
||||
const t = jumpSurviveSessionStartMs > 0
|
||||
? Math.max(0, Math.floor((performance.now() - jumpSurviveSessionStartMs) / 1000))
|
||||
@@ -9724,9 +10192,15 @@
|
||||
? 'ชุดคำถามจบแล้ว · Session ended — ดูคะแนนด้าน SCORE'
|
||||
: ('ยืนโซนตัวเลือกแล้วกด F/Grab หยิบ · ถือป้ายถูกแล้วไปฮับส่ง — ถูก +' + QUIZ_CARRY_POINTS_PER_CORRECT + ' แต้ม (correct +' + QUIZ_CARRY_POINTS_PER_CORRECT + ' on hub)');
|
||||
} else if (isJumpSurvive()) {
|
||||
hintEl.textContent = jumpSurviveEliminated
|
||||
? 'Spectate remaining nodes · Exit room to reset session'
|
||||
: 'Space / W / ↑ jump · A D / arrows move · Hit ceiling/floor = out (watch others)';
|
||||
if (isJumpSurviveMissionUiMapPlay()) {
|
||||
hintEl.textContent = jumpSurviveEliminated
|
||||
? 'คุณตกน้ำแล้ว — ดูเพื่อนต่อ — รอจบเพื่อรับคะแนน (ผู้รอดได้ 100 คะแนน)'
|
||||
: 'กระโดดตามแท่น — หลีกหนาม/กับดักด้านล่าง · หมดเวลาแล้วสรุปคะแนน (ผู้รอด 100) · Space / W / ↑ กระโดด · A D / ← → เดิน';
|
||||
} else {
|
||||
hintEl.textContent = jumpSurviveEliminated
|
||||
? 'Spectate remaining nodes · Exit room to reset session'
|
||||
: 'Space / W / ↑ jump · A D / arrows move · Hit ceiling/floor = out (watch others)';
|
||||
}
|
||||
} else if (isSpaceShooter()) {
|
||||
hintEl.textContent = spaceShooterGameEnded
|
||||
? 'จบแล้ว · Session ended — ดูอันดับบน overlay / see rankings on overlay'
|
||||
@@ -9758,6 +10232,7 @@
|
||||
const ul = document.getElementById('play-cyber-score-list');
|
||||
if (!ul) return;
|
||||
const safeYv = (v) => (typeof v === 'number' && !isNaN(v) ? v : 1);
|
||||
const jumpMissionHud = isJumpSurvive() && isJumpSurviveMissionUiMapPlay();
|
||||
const rows = [];
|
||||
rows.push({
|
||||
id: myId,
|
||||
@@ -9766,7 +10241,8 @@
|
||||
score: isQuizCarry() ? cyberHudQuizCarryPeerScore(myId)
|
||||
: (isGauntlet() ? (me.gauntletScore || 0)
|
||||
: (isSpaceShooter() ? (me.spaceShooterScore || 0)
|
||||
: (isBalloonBoss() ? (me.balloonBossScore || 0) : 0))),
|
||||
: (isBalloonBoss() ? (me.balloonBossScore || 0)
|
||||
: (jumpMissionHud ? (jumpSurviveEliminated ? 0 : 100) : 0)))),
|
||||
y: safeYv(me.y),
|
||||
eliminated: !!(isJumpSurvive() && jumpSurviveEliminated) || !!(isBalloonBoss() && me.balloonBossEliminated)
|
||||
|| !!(isGauntletCrownHeistMapPlay() && me.gauntletEliminated),
|
||||
@@ -9780,7 +10256,8 @@
|
||||
score: isQuizCarry() ? cyberHudQuizCarryPeerScore(id)
|
||||
: (isGauntlet() ? (o.gauntletScore || 0)
|
||||
: (isSpaceShooter() ? (o.spaceShooterScore || 0)
|
||||
: (isBalloonBoss() ? (o.balloonBossScore || 0) : 0))),
|
||||
: (isBalloonBoss() ? (o.balloonBossScore || 0)
|
||||
: (jumpMissionHud ? (o.jumpSurviveEliminated ? 0 : 100) : 0)))),
|
||||
y: safeYv(o.y),
|
||||
eliminated: !!(isJumpSurvive() && isPreviewBotId(id) && o.jumpSurviveEliminated)
|
||||
|| !!(isBalloonBoss() && o.balloonBossEliminated)
|
||||
@@ -9789,7 +10266,7 @@
|
||||
});
|
||||
});
|
||||
let leaderId = null;
|
||||
if (isGauntlet() || isSpaceShooter() || isBalloonBoss() || isQuizCarry()) {
|
||||
if (isGauntlet() || isSpaceShooter() || isBalloonBoss() || isQuizCarry() || jumpMissionHud) {
|
||||
let mx = -1;
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
if (rows[i].score > mx) {
|
||||
@@ -9807,7 +10284,11 @@
|
||||
}
|
||||
}
|
||||
rows.sort((a, b) => {
|
||||
if (isGauntlet() || isSpaceShooter() || isBalloonBoss() || isQuizCarry()) return b.score - a.score;
|
||||
if (isGauntlet() || isSpaceShooter() || isBalloonBoss() || isQuizCarry() || jumpMissionHud) {
|
||||
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');
|
||||
}
|
||||
if (a.eliminated !== b.eliminated) return a.eliminated ? 1 : -1;
|
||||
return a.y - b.y;
|
||||
});
|
||||
@@ -9856,7 +10337,7 @@
|
||||
mid.appendChild(st);
|
||||
const sc = document.createElement('span');
|
||||
sc.className = 'play-cyber-score-val';
|
||||
if (isGauntlet() || isSpaceShooter() || isBalloonBoss() || isQuizCarry()) {
|
||||
if (isGauntlet() || isSpaceShooter() || isBalloonBoss() || isQuizCarry() || jumpMissionHud) {
|
||||
sc.textContent = String(row.score);
|
||||
} else {
|
||||
sc.textContent = row.eliminated ? '—' : String(r + 1);
|
||||
@@ -10652,6 +11133,10 @@
|
||||
}
|
||||
}
|
||||
if (isJumpSurvive() && mapData && !isChatFocused()) {
|
||||
if (isJumpSurviveMissionPregameBlockingPlay()) {
|
||||
if (['Space', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'KeyW', 'KeyA', 'KeyS', 'KeyD'].includes(e.code)) e.preventDefault();
|
||||
return;
|
||||
}
|
||||
if (jumpSurviveEliminated) {
|
||||
if (['Space', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'KeyW', 'KeyA', 'KeyS', 'KeyD'].includes(e.code)) e.preventDefault();
|
||||
return;
|
||||
@@ -10720,6 +11205,12 @@
|
||||
return;
|
||||
}
|
||||
if (isJumpSurvive()) {
|
||||
if (isJumpSurviveMissionPregameBlockingPlay()) {
|
||||
me.isWalking = false;
|
||||
draw();
|
||||
requestAnimationFrame(tick);
|
||||
return;
|
||||
}
|
||||
jumpSurviveTickFrame();
|
||||
draw();
|
||||
requestAnimationFrame(tick);
|
||||
@@ -10935,6 +11426,13 @@
|
||||
const btn = document.getElementById('btn-gch-ready');
|
||||
if (!btn) return;
|
||||
btn.addEventListener('click', () => {
|
||||
if (isJumpSurviveMissionUiMapPlay()) {
|
||||
if (jumpSurviveMissionPhase !== 'howto') return;
|
||||
const humans = quizCarryPregameHumanIds();
|
||||
if (!(humans.length === 1 || isMePlayHost())) return;
|
||||
beginJumpSurviveMissionCountdownThenRun();
|
||||
return;
|
||||
}
|
||||
if (!isGauntletCrownHeistMapPlay()) return;
|
||||
if (gauntletCrownPregamePhase !== 'howto') return;
|
||||
if (myId == null || !isMePlayHost()) return;
|
||||
|
||||