done font minigame4
This commit is contained in:
@@ -365,6 +365,11 @@
|
||||
var ams = Number(data.carryAnswerMs);
|
||||
ansSec.value = String(Number.isFinite(ams) ? Math.round(ams / 1000) : 5);
|
||||
}
|
||||
var sessLen = el('quiz-carry-session-len');
|
||||
if (sessLen) {
|
||||
var sl = parseInt(String(data.carrySessionLength), 10);
|
||||
sessLen.value = String(Number.isFinite(sl) && sl >= 0 ? sl : 0);
|
||||
}
|
||||
setMsg('quiz-carry-settings-msg', '', '');
|
||||
}).catch(function (e) {
|
||||
setMsg('quiz-carry-settings-msg', e.message || 'โหลดไม่ได้', 'error');
|
||||
@@ -416,11 +421,16 @@
|
||||
if (ansSec > 300) ansSec = 300;
|
||||
var carryReadMs = readSec * 1000;
|
||||
var carryAnswerMs = ansSec * 1000;
|
||||
var sessEl = el('quiz-carry-session-len');
|
||||
var carrySessionLength = sessEl ? parseInt(String(sessEl.value || '0'), 10) : 0;
|
||||
if (!Number.isFinite(carrySessionLength) || carrySessionLength < 0) carrySessionLength = 0;
|
||||
if (carrySessionLength > 500) carrySessionLength = 500;
|
||||
|
||||
gameQuizFetch('PUT', {
|
||||
carryQuestions: carryQuestions,
|
||||
carryReadMs: carryReadMs,
|
||||
carryAnswerMs: carryAnswerMs,
|
||||
carrySessionLength: carrySessionLength,
|
||||
}).then(function () {
|
||||
setMsg('quiz-carry-settings-msg', 'บันทึกแล้ว', 'ok');
|
||||
}).catch(function (e) {
|
||||
|
||||
@@ -46,7 +46,7 @@ function merge_quiz_settings_disk_from_put(string $rawBody): bool
|
||||
}
|
||||
}
|
||||
}
|
||||
$mergeKeys = ['readMs', 'answerMs', 'betweenMs', 'carryReadMs', 'carryAnswerMs', 'questions', 'carryQuestions', 'battleQuizMcq'];
|
||||
$mergeKeys = ['readMs', 'answerMs', 'betweenMs', 'carryReadMs', 'carryAnswerMs', 'carrySessionLength', 'questions', 'carryQuestions', 'battleQuizMcq'];
|
||||
foreach ($mergeKeys as $k) {
|
||||
if (array_key_exists($k, $patch)) {
|
||||
$base[$k] = $patch[$k];
|
||||
|
||||
@@ -175,6 +175,10 @@
|
||||
<input type="number" id="quiz-carry-answer-sec" class="admin-inp-num" min="1" max="300" step="1" value="5" aria-describedby="quiz-carry-answer-hint" />
|
||||
<span id="quiz-carry-answer-hint" class="muted" style="display:block;font-size:0.85rem;margin-top:0.2rem">นับจากเมื่อหยิบได้ · สูงสุด 300 วิ</span>
|
||||
</label>
|
||||
<label class="admin-field">จำนวนข้อต่อเซสชันก่อนจบ (พรีวิว)
|
||||
<input type="number" id="quiz-carry-session-len" class="admin-inp-num" min="0" max="500" step="1" value="0" aria-describedby="quiz-carry-session-hint" />
|
||||
<span id="quiz-carry-session-hint" class="muted" style="display:block;font-size:0.85rem;margin-top:0.2rem">สุ่มคำถามจากชุดจนกว่าจะครบจำนวนนี้ (นับทุกข้อที่จบด้วยถูกหรือหมดเวลา) · 0 = เล่นต่อไม่จบอัตโนมัติ · <em>English:</em> Preview / editor embed only.</span>
|
||||
</label>
|
||||
</div>
|
||||
<h3 class="admin-subheading">รายการคำถาม</h3>
|
||||
<div id="quiz-carry-admin-list" class="quiz-carry-admin-list"></div>
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 538 KiB |
@@ -569,6 +569,15 @@
|
||||
let quizCarryOptionRevealAt = 0;
|
||||
let quizCarryAnswerCloseAt = 0;
|
||||
let quizCarryCarryTimingMs = { carryReadMs: 3000, carryAnswerMs: 5000 };
|
||||
/** พรีวิว (รวม editor embed): จำนวนข้อที่เล่นก่อนโอเวอร์เลย์จบ — 0 = ไม่จบอัตโนมัติ */
|
||||
let quizCarrySessionLength = 0;
|
||||
let quizCarryRoundsCompleted = 0;
|
||||
let quizCarrySessionEnded = false;
|
||||
/** embed พรีวิว: รอ Ready / START ของโฮสต์ก่อนนับ 3–2–1 */
|
||||
let quizCarryPregameActive = false;
|
||||
let playHostId = null;
|
||||
/** ซิงก์จากเซิร์ฟเวอร์: socketId → กด Ready แล้ว */
|
||||
let quizCarryLobbyReadyMap = {};
|
||||
|
||||
/** jump_survive — กล้องตามตัวในกรอบ + แพลตฟอร์มเลื่อนขึ้น (scroll) ไม่ลากทั้งฉาก */
|
||||
let jumpSurviveCamCenterX = 0;
|
||||
@@ -789,6 +798,12 @@
|
||||
return Math.max(minV, Math.min(maxV, Math.floor(v)));
|
||||
}
|
||||
|
||||
function clampCarrySessionLen(n, def) {
|
||||
const v = Number(n);
|
||||
if (Number.isNaN(v)) return def;
|
||||
return Math.max(0, Math.min(500, Math.floor(v)));
|
||||
}
|
||||
|
||||
function buildQuizPoolFromMap(md) {
|
||||
if (!md || !Array.isArray(md.quizQuestions)) return [];
|
||||
return md.quizQuestions
|
||||
@@ -1157,6 +1172,7 @@
|
||||
if (mapData.gameType === 'balloon_boss') {
|
||||
applyBalloonBossSpawnLayoutPlay();
|
||||
}
|
||||
if (quizCarryPregameActive && mapData && mapData.gameType === 'quiz_carry') updateQuizCarryPregameHud();
|
||||
}
|
||||
|
||||
function pickRandomWalkableCellInAnswerGrid(grid, o) {
|
||||
@@ -1863,6 +1879,120 @@
|
||||
return !!(previewMode && editorEmbedReturn && isQuizCarry() && quizCarryEmbedCountdownEndAt > Date.now());
|
||||
}
|
||||
|
||||
function isQuizCarryEmbedLobbyBlockingMovement() {
|
||||
return !!(previewMode && editorEmbedReturn && isQuizCarry() && quizCarryPregameActive);
|
||||
}
|
||||
|
||||
function quizCarryPregameHumanIds() {
|
||||
const ids = [];
|
||||
if (myId != null && !isPreviewBotId(myId)) ids.push(String(myId));
|
||||
others.forEach((_, id) => {
|
||||
if (!isPreviewBotId(id)) ids.push(String(id));
|
||||
});
|
||||
return ids;
|
||||
}
|
||||
|
||||
function quizCarryPregameBotCount() {
|
||||
let n = 0;
|
||||
others.forEach((_, id) => {
|
||||
if (isPreviewBotId(id)) n++;
|
||||
});
|
||||
return n;
|
||||
}
|
||||
|
||||
function quizCarryPregameReadyNumerator() {
|
||||
let n = quizCarryPregameBotCount();
|
||||
quizCarryPregameHumanIds().forEach((id) => {
|
||||
if (quizCarryLobbyReadyMap[id]) n++;
|
||||
});
|
||||
return n;
|
||||
}
|
||||
|
||||
function quizCarryPregameTotalPlayers() {
|
||||
return quizCarryPregameHumanIds().length + quizCarryPregameBotCount();
|
||||
}
|
||||
|
||||
function isMePlayHost() {
|
||||
if (myId == null) return false;
|
||||
if (playHostId == null) return true;
|
||||
return String(playHostId) === String(myId);
|
||||
}
|
||||
|
||||
function hideQuizCarryPregameOverlay() {
|
||||
const ov = document.getElementById('quiz-carry-pregame-overlay');
|
||||
if (ov) {
|
||||
ov.classList.add('is-hidden');
|
||||
ov.setAttribute('aria-hidden', 'true');
|
||||
}
|
||||
}
|
||||
|
||||
function showQuizCarryPregameOverlay() {
|
||||
const ov = document.getElementById('quiz-carry-pregame-overlay');
|
||||
if (ov) {
|
||||
ov.classList.remove('is-hidden');
|
||||
ov.setAttribute('aria-hidden', 'false');
|
||||
}
|
||||
updateQuizCarryPregameHud();
|
||||
}
|
||||
|
||||
function quizCarrySyncGuestReadyIfNeeded() {
|
||||
if (!quizCarryPregameActive || myId == null || isMePlayHost()) return;
|
||||
const sid = String(myId);
|
||||
if (quizCarryLobbyReadyMap[sid]) return;
|
||||
quizCarryLobbyReadyMap[sid] = true;
|
||||
if (socket && socket.connected) socket.emit('quiz-carry-lobby-ready', { ready: true });
|
||||
}
|
||||
|
||||
function updateQuizCarryPregameHud() {
|
||||
if (!quizCarryPregameActive) return;
|
||||
const st = document.getElementById('quiz-carry-pregame-status');
|
||||
const primary = document.getElementById('quiz-carry-pregame-primary');
|
||||
const primaryImg = document.getElementById('quiz-carry-pregame-primary-img');
|
||||
const num = quizCarryPregameReadyNumerator();
|
||||
const tot = Math.max(1, quizCarryPregameTotalPlayers());
|
||||
if (st) {
|
||||
st.textContent = 'Ready Status : ' + num + '/' + tot;
|
||||
}
|
||||
const humans = quizCarryPregameHumanIds();
|
||||
const humansReady = humans.length > 0 && humans.every((id) => !!quizCarryLobbyReadyMap[id]);
|
||||
const meReady = !!(myId && quizCarryLobbyReadyMap[String(myId)]);
|
||||
if (primaryImg) {
|
||||
primaryImg.src = humansReady ? '/Game/img/quiz-carry/btn-start.png' : '/Game/img/quiz-carry/btn-ready.png';
|
||||
primaryImg.alt = humansReady ? 'START' : 'READY';
|
||||
}
|
||||
if (primary) {
|
||||
primary.classList.toggle('is-start-phase', humansReady);
|
||||
primary.classList.toggle('is-read-only', !isMePlayHost());
|
||||
primary.classList.toggle('is-pressed', isMePlayHost() && meReady && !humansReady);
|
||||
primary.disabled = !isMePlayHost();
|
||||
primary.setAttribute('aria-pressed', humansReady ? 'false' : (meReady ? 'true' : 'false'));
|
||||
primary.title = isMePlayHost()
|
||||
? (humansReady ? 'START' : (meReady ? 'ยกเลิก READY' : 'READY'))
|
||||
: (humansReady ? 'START (โฮสต์เท่านั้น)' : 'READY (โฮสต์เท่านั้น)');
|
||||
}
|
||||
}
|
||||
|
||||
function beginQuizCarryEmbedPregame() {
|
||||
if (!(previewMode && editorEmbedReturn && isQuizCarry())) return;
|
||||
quizCarryPregameActive = true;
|
||||
quizCarryLobbyReadyMap = {};
|
||||
quizCarryPregameHumanIds().forEach((id) => { quizCarryLobbyReadyMap[id] = false; });
|
||||
showQuizCarryPregameOverlay();
|
||||
if (socket && socket.connected) socket.emit('quiz-carry-lobby-sync-request');
|
||||
quizCarrySyncGuestReadyIfNeeded();
|
||||
updateQuizCarryPregameHud();
|
||||
}
|
||||
|
||||
function endQuizCarryEmbedPregameAndStart() {
|
||||
if (!quizCarryPregameActive) return;
|
||||
quizCarryPregameActive = false;
|
||||
hideQuizCarryPregameOverlay();
|
||||
quizCarryPickNextQuestion();
|
||||
if (!(previewMode && editorEmbedReturn && quizCarryEmbedCountdownEndAt > Date.now())) {
|
||||
quizCarryRestoreEmbedPhaseLabel();
|
||||
}
|
||||
}
|
||||
|
||||
function quizCarryOptionsPickableNow() {
|
||||
if (!isQuizCarry() || !quizCarryCurrent) return true;
|
||||
if (!quizCarryOptionRevealAt || quizCarryOptionRevealAt <= 0) return true;
|
||||
@@ -1888,6 +2018,8 @@
|
||||
}
|
||||
|
||||
function updateQuizCarryCarryPhaseHud() {
|
||||
if (quizCarrySessionEnded) return;
|
||||
if (quizCarryPregameActive) return;
|
||||
if (!isQuizCarry() || !quizCarryCurrent) return;
|
||||
const phaseEl = document.getElementById('quiz-game-phase-label');
|
||||
if (!phaseEl) return;
|
||||
@@ -1906,6 +2038,8 @@
|
||||
}
|
||||
|
||||
function tickQuizCarryRoundTimers() {
|
||||
if (quizCarrySessionEnded) return;
|
||||
if (quizCarryPregameActive) return;
|
||||
if (!isQuizCarry() || !mapData || !quizCarryCurrent) return;
|
||||
if (quizCarryEmbedCountdownEndAt > Date.now()) return;
|
||||
if (quizCarryEmbedPendingQuestion) return;
|
||||
@@ -1913,7 +2047,9 @@
|
||||
if (Date.now() < quizCarryAnswerCloseAt) return;
|
||||
quizCarryAnswerCloseAt = 0;
|
||||
quizCarryOptionRevealAt = 0;
|
||||
showQuizCarryToast('หมดเวลา — ข้อถัดไป', false);
|
||||
const limT = previewMode && isQuizCarry() ? quizCarrySessionLength : 0;
|
||||
const willEndT = limT > 0 && (quizCarryRoundsCompleted + 1) >= limT;
|
||||
showQuizCarryToast(willEndT ? 'หมดเวลา — ครบชุดคำถาม' : 'หมดเวลา — ข้อถัดไป', false);
|
||||
me.quizCarryHeld = null;
|
||||
others.forEach((o) => {
|
||||
if (!o) return;
|
||||
@@ -1921,7 +2057,7 @@
|
||||
o.botPath = [];
|
||||
});
|
||||
renderPlayQuizScoreboard(playLiveQuizScores);
|
||||
quizCarryPickNextQuestion();
|
||||
quizCarryAfterRoundResolved();
|
||||
}
|
||||
|
||||
/** นับ 3–2–1: ไม่โชว์ข้อความคำถามใน overlay — มีแค่เลข */
|
||||
@@ -1982,6 +2118,8 @@
|
||||
}
|
||||
|
||||
function quizCarryPickNextQuestion() {
|
||||
if (quizCarrySessionEnded) return;
|
||||
if (quizCarryPregameActive) return;
|
||||
if (!quizCarryPool.length) {
|
||||
quizCarryCurrent = null;
|
||||
quizCarryEmbedPendingQuestion = null;
|
||||
@@ -2036,6 +2174,91 @@
|
||||
quizCarryApplyPhaseTimersForCurrentQuestion();
|
||||
}
|
||||
|
||||
/** หลังจบข้อหนึ่งข้อ (ถูกหรือหมดเวลา): พรีวิวนับรอบจนครบ carrySessionLength แล้วจบเซสชัน */
|
||||
function quizCarryAfterRoundResolved() {
|
||||
if (!isQuizCarry()) return;
|
||||
if (!previewMode) {
|
||||
quizCarryPickNextQuestion();
|
||||
return;
|
||||
}
|
||||
quizCarryRoundsCompleted++;
|
||||
const lim = quizCarrySessionLength;
|
||||
if (lim > 0 && quizCarryRoundsCompleted >= lim) {
|
||||
showQuizCarrySessionCompleteOverlay();
|
||||
return;
|
||||
}
|
||||
quizCarryPickNextQuestion();
|
||||
}
|
||||
|
||||
function showQuizCarrySessionCompleteOverlay() {
|
||||
if (quizCarrySessionEnded) return;
|
||||
quizCarrySessionEnded = true;
|
||||
quizCarryPregameActive = false;
|
||||
hideQuizCarryPregameOverlay();
|
||||
quizCarryCurrent = null;
|
||||
quizCarryEmbedPendingQuestion = null;
|
||||
quizCarryEmbedCountdownEndAt = 0;
|
||||
quizCarryEmbedCountdownStartAt = 0;
|
||||
quizCarryOptionRevealAt = 0;
|
||||
quizCarryAnswerCloseAt = 0;
|
||||
hideQuizCarryEmbedCountdownOverlay();
|
||||
me.quizCarryHeld = null;
|
||||
others.forEach((o) => {
|
||||
if (!o) return;
|
||||
o.quizCarryHeld = null;
|
||||
o.botPath = [];
|
||||
});
|
||||
playQuizText = 'ครบชุดคำถามแล้ว';
|
||||
const qEl = document.getElementById('quiz-game-question');
|
||||
if (qEl) qEl.textContent = playQuizText;
|
||||
const phaseEl = document.getElementById('quiz-game-phase-label');
|
||||
if (phaseEl) phaseEl.textContent = 'ครบ ' + String(quizCarryRoundsCompleted) + ' ข้อ';
|
||||
const ov = document.getElementById('gauntlet-ended-overlay');
|
||||
const msgEl = document.getElementById('gauntlet-ended-message');
|
||||
const titleEl = document.getElementById('gauntlet-ended-title');
|
||||
const listEl = document.getElementById('gauntlet-ended-rankings');
|
||||
const btn = document.getElementById('btn-gauntlet-ended-lobby');
|
||||
if (!ov || !msgEl || !listEl) return;
|
||||
if (titleEl) titleEl.textContent = 'เกมจบ · Game over';
|
||||
msgEl.textContent = 'ครบจำนวนคำถามที่ตั้งไว้ (' + String(quizCarryRoundsCompleted) + ' ข้อ) · Quiz carry session finished';
|
||||
listEl.innerHTML = '';
|
||||
const ranks = [];
|
||||
if (myId != null) {
|
||||
ranks.push({
|
||||
id: myId,
|
||||
nickname: (me.nickname || nick || 'คุณ').trim() || 'คุณ',
|
||||
score: Math.max(0, Number(playLiveQuizScores[myId]) || 0),
|
||||
});
|
||||
}
|
||||
others.forEach((o, id) => {
|
||||
if (!o) return;
|
||||
ranks.push({
|
||||
id,
|
||||
nickname: (o.nickname && String(o.nickname).trim()) ? String(o.nickname).trim() : String(id),
|
||||
score: Math.max(0, Number(playLiveQuizScores[id]) || 0),
|
||||
});
|
||||
});
|
||||
ranks.sort((a, b) => b.score - a.score || String(a.nickname).localeCompare(String(b.nickname), 'th'));
|
||||
ranks.forEach((r, i) => {
|
||||
const li = document.createElement('li');
|
||||
const isMe = myId != null && String(r.id) === String(myId);
|
||||
li.textContent = `${i + 1}. ${(r && r.nickname) || '—'} — ${Math.max(0, Number(r && r.score) || 0)}`;
|
||||
if (isMe) li.className = 'gauntlet-ended-me';
|
||||
listEl.appendChild(li);
|
||||
});
|
||||
ov.classList.remove('is-hidden');
|
||||
function goLobby() {
|
||||
window.location.href = 'room-lobby.html?space=' + encodeURIComponent(spaceId) + '&nick=' + encodeURIComponent(nick);
|
||||
}
|
||||
if (btn) {
|
||||
btn.onclick = () => {
|
||||
if (previewMode && editorEmbedReturn) ov.classList.add('is-hidden');
|
||||
else goLobby();
|
||||
};
|
||||
}
|
||||
renderPlayQuizScoreboard(playLiveQuizScores);
|
||||
}
|
||||
|
||||
function showQuizCarryToast(msg, ok) {
|
||||
const el = document.getElementById('play-quiz-feedback');
|
||||
if (!el) return;
|
||||
@@ -2052,6 +2275,8 @@
|
||||
*/
|
||||
function tryQuizCarryInteractionForPlayer(actorId, ox, oy, opts) {
|
||||
opts = opts || {};
|
||||
if (quizCarryPregameActive) return false;
|
||||
if (quizCarrySessionEnded) return false;
|
||||
if (!isQuizCarry() || !mapData || !quizCarryCurrent) return false;
|
||||
const md = mapData;
|
||||
const footprint = quizTilesFootprintPlay(ox, oy);
|
||||
@@ -2073,10 +2298,12 @@
|
||||
const ok = held === correct;
|
||||
if (ok) {
|
||||
playLiveQuizScores[actorId] = (playLiveQuizScores[actorId] || 0) + 1;
|
||||
showQuizCarryToast('ถูกต้อง +1 แต้ม — สุ่มคำถามใหม่', true);
|
||||
const lim = previewMode && isQuizCarry() ? quizCarrySessionLength : 0;
|
||||
const willEnd = lim > 0 && (quizCarryRoundsCompleted + 1) >= lim;
|
||||
showQuizCarryToast(willEnd ? 'ถูกต้อง +1 แต้ม — ครบชุดคำถาม' : 'ถูกต้อง +1 แต้ม — สุ่มคำถามใหม่', true);
|
||||
me.quizCarryHeld = null;
|
||||
others.forEach((o) => { if (o) o.quizCarryHeld = null; });
|
||||
quizCarryPickNextQuestion();
|
||||
quizCarryAfterRoundResolved();
|
||||
} else {
|
||||
showQuizCarryToast('ผิด — คืนป้ายที่โซนตัวเลือกแล้ว กด F หยิบใหม่ได้', false);
|
||||
}
|
||||
@@ -2115,6 +2342,8 @@
|
||||
}
|
||||
|
||||
function resetQuizCarryPlayState() {
|
||||
quizCarryPregameActive = false;
|
||||
hideQuizCarryPregameOverlay();
|
||||
quizCarryPool = [];
|
||||
quizCarryCurrent = null;
|
||||
quizCarryEmbedPendingQuestion = null;
|
||||
@@ -2122,6 +2351,8 @@
|
||||
quizCarryEmbedCountdownEndAt = 0;
|
||||
quizCarryOptionRevealAt = 0;
|
||||
quizCarryAnswerCloseAt = 0;
|
||||
quizCarryRoundsCompleted = 0;
|
||||
quizCarrySessionEnded = false;
|
||||
hideQuizCarryEmbedCountdownOverlay();
|
||||
me.quizCarryHeld = null;
|
||||
others.forEach((o) => {
|
||||
@@ -2132,6 +2363,9 @@
|
||||
}
|
||||
|
||||
async function loadQuizCarryPoolAndStart() {
|
||||
quizCarryRoundsCompleted = 0;
|
||||
quizCarrySessionEnded = false;
|
||||
quizCarrySessionLength = 0;
|
||||
let pool = [];
|
||||
try {
|
||||
const r = await fetch(BASE + '/api/quiz-settings');
|
||||
@@ -2141,6 +2375,7 @@
|
||||
carryReadMs: clampPreviewMs(s.carryReadMs, 3000, 0, 120000),
|
||||
carryAnswerMs: clampPreviewMs(s.carryAnswerMs, 5000, 1000, 300000),
|
||||
};
|
||||
quizCarrySessionLength = clampCarrySessionLen(s.carrySessionLength, 0);
|
||||
const carryOnly = s && Array.isArray(s.carryQuestions) && s.carryQuestions.length > 0;
|
||||
const source = carryOnly ? s.carryQuestions : (s && Array.isArray(s.questions) ? s.questions : []);
|
||||
for (let i = 0; i < source.length; i++) {
|
||||
@@ -2160,7 +2395,11 @@
|
||||
if (qEl) qEl.textContent = playQuizText;
|
||||
return;
|
||||
}
|
||||
quizCarryPickNextQuestion();
|
||||
if (previewMode && editorEmbedReturn) {
|
||||
beginQuizCarryEmbedPregame();
|
||||
} else {
|
||||
quizCarryPickNextQuestion();
|
||||
}
|
||||
if (!(previewMode && editorEmbedReturn && quizCarryEmbedCountdownEndAt > Date.now())) {
|
||||
quizCarryRestoreEmbedPhaseLabel();
|
||||
}
|
||||
@@ -2252,6 +2491,8 @@
|
||||
}
|
||||
|
||||
function stepQuizCarryPreviewBots() {
|
||||
if (quizCarrySessionEnded) return;
|
||||
if (quizCarryPregameActive) return;
|
||||
if (!previewFillBots || !isQuizCarry() || !mapData || !quizCarryCurrent) return;
|
||||
const w = mapData.width || 20, h = mapData.height || 15;
|
||||
const nowPf = performance.now();
|
||||
@@ -5062,6 +5303,7 @@
|
||||
const prevWasSpaceShooter = mapData && mapData.gameType === 'space_shooter';
|
||||
const prevWasBalloonBoss = mapData && mapData.gameType === 'balloon_boss';
|
||||
mapData = gameMapData;
|
||||
if (res && res.hostId != null) playHostId = res.hostId;
|
||||
if (mapData.gameType !== 'space_shooter') {
|
||||
spaceShooterGameEnded = false;
|
||||
if (prevWasSpaceShooter) {
|
||||
@@ -5336,6 +5578,23 @@
|
||||
quizCarryHeld: null,
|
||||
});
|
||||
if (previewFillBots) rebalancePreviewBots();
|
||||
if (quizCarryPregameActive && isQuizCarry()) updateQuizCarryPregameHud();
|
||||
});
|
||||
socket.on('host-changed', (d) => {
|
||||
if (d && d.hostId != null) playHostId = d.hostId;
|
||||
if (quizCarryPregameActive && isQuizCarry()) updateQuizCarryPregameHud();
|
||||
});
|
||||
socket.on('quiz-carry-lobby-sync', (d) => {
|
||||
if (!d || typeof d.readyMap !== 'object') return;
|
||||
quizCarryLobbyReadyMap = { ...d.readyMap };
|
||||
if (quizCarryPregameActive && isQuizCarry()) {
|
||||
quizCarrySyncGuestReadyIfNeeded();
|
||||
updateQuizCarryPregameHud();
|
||||
}
|
||||
});
|
||||
socket.on('quiz-carry-lobby-started', () => {
|
||||
if (!(previewMode && editorEmbedReturn && isQuizCarry())) return;
|
||||
endQuizCarryEmbedPregameAndStart();
|
||||
});
|
||||
socket.on('user-move', (data) => {
|
||||
if (data && myId != null && data.id === myId) {
|
||||
@@ -7630,7 +7889,7 @@
|
||||
requestAnimationFrame(tick);
|
||||
return;
|
||||
}
|
||||
if (isQuizCarryEmbedCountdownBlockingMovement()) {
|
||||
if (isQuizCarryEmbedLobbyBlockingMovement() || isQuizCarryEmbedCountdownBlockingMovement()) {
|
||||
me.isWalking = false;
|
||||
playPath = [];
|
||||
enforceQuizBattleLaneOnMePlay();
|
||||
@@ -7758,6 +8017,32 @@
|
||||
}
|
||||
});
|
||||
})();
|
||||
(function wireQuizCarryPregameOverlay() {
|
||||
const primary = document.getElementById('quiz-carry-pregame-primary');
|
||||
if (!primary) return;
|
||||
primary.addEventListener('click', () => {
|
||||
if (!quizCarryPregameActive || myId == null) return;
|
||||
if (!isMePlayHost()) return;
|
||||
const humans = quizCarryPregameHumanIds();
|
||||
const humansReady = humans.length > 0 && humans.every((id) => !!quizCarryLobbyReadyMap[id]);
|
||||
if (humansReady) {
|
||||
/* ผู้เล่นจริงคนเดียว + พรีวิว: ไม่ต้องรอ ACK เซิร์ฟ (ห้องที่ map บนเซิร์ฟยังไม่ใช่ quiz_carry จะเคยล้มเหลว) */
|
||||
if (humans.length === 1 && isMePlayHost()) {
|
||||
endQuizCarryEmbedPregameAndStart();
|
||||
return;
|
||||
}
|
||||
socket.emit('quiz-carry-lobby-start', {}, (r) => {
|
||||
if (!r || !r.ok) showQuizCarryToast((r && r.error) || 'START ไม่ได้', false);
|
||||
});
|
||||
return;
|
||||
}
|
||||
const sid = String(myId);
|
||||
const next = !quizCarryLobbyReadyMap[sid];
|
||||
quizCarryLobbyReadyMap[sid] = next;
|
||||
updateQuizCarryPregameHud();
|
||||
socket.emit('quiz-carry-lobby-ready', { ready: next });
|
||||
});
|
||||
})();
|
||||
window.addEventListener('beforeunload', () => socket.emit('leave-space'));
|
||||
window.addEventListener('resize', () => {
|
||||
if (!mapData || !canvas) return;
|
||||
|
||||
@@ -186,6 +186,126 @@
|
||||
z-index: 10120;
|
||||
top: max(52px, calc(env(safe-area-inset-top) + 46px));
|
||||
}
|
||||
/* พรีวิว embed quiz_carry: How to play + Ready / START ก่อน 3–2–1 */
|
||||
#quiz-carry-pregame-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 10078;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: max(10px, env(safe-area-inset-top)) max(10px, env(safe-area-inset-right)) max(10px, env(safe-area-inset-bottom)) max(10px, env(safe-area-inset-left));
|
||||
box-sizing: border-box;
|
||||
}
|
||||
#quiz-carry-pregame-overlay.is-hidden { display: none !important; }
|
||||
.quiz-carry-pregame-backdrop {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(4, 6, 14, 0.78);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
.quiz-carry-pregame-sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
.quiz-carry-pregame-card {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
max-width: min(94vw, 900px);
|
||||
width: 100%;
|
||||
max-height: min(94vh, 900px);
|
||||
overflow: hidden;
|
||||
background: rgba(10, 12, 26, 0.55);
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
box-shadow: none;
|
||||
pointer-events: auto;
|
||||
}
|
||||
.quiz-carry-pregame-art {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: auto;
|
||||
background: transparent;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
}
|
||||
.quiz-carry-pregame-howto-img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-height: min(62vh, 560px);
|
||||
object-fit: contain;
|
||||
object-position: top center;
|
||||
border: none;
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
.quiz-carry-pregame-footer {
|
||||
flex: 0 0 auto;
|
||||
padding: 0.85rem 1rem 1rem;
|
||||
background: linear-gradient(180deg, rgba(8, 10, 22, 0.2), rgba(8, 10, 22, 0.96));
|
||||
border-top: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.65rem;
|
||||
}
|
||||
.quiz-carry-pregame-status {
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
font: 700 clamp(0.8rem, 2.4vw, 0.95rem) / 1.35 system-ui, "Kanit", sans-serif;
|
||||
color: #fbcfe8;
|
||||
text-shadow: 0 0 16px rgba(244, 114, 182, 0.5);
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
.quiz-carry-pregame-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.85rem 1.25rem;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.quiz-carry-pregame-primary-btn {
|
||||
background: transparent;
|
||||
border: none;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
line-height: 0;
|
||||
border-radius: 6px;
|
||||
transition: transform 0.12s ease, filter 0.12s ease, opacity 0.15s ease;
|
||||
}
|
||||
.quiz-carry-pregame-primary-btn:hover:not(:disabled):not(.is-read-only) {
|
||||
transform: scale(1.03);
|
||||
filter: brightness(1.08);
|
||||
}
|
||||
.quiz-carry-pregame-primary-btn.is-pressed:not(.is-start-phase) {
|
||||
filter: brightness(1.12) drop-shadow(0 0 12px rgba(94, 234, 212, 0.55));
|
||||
}
|
||||
.quiz-carry-pregame-primary-btn.is-read-only,
|
||||
.quiz-carry-pregame-primary-btn:disabled {
|
||||
opacity: 0.38;
|
||||
pointer-events: none;
|
||||
cursor: default;
|
||||
filter: grayscale(0.2);
|
||||
}
|
||||
.quiz-carry-pregame-primary-btn img {
|
||||
display: block;
|
||||
width: min(220px, 42vw);
|
||||
height: auto;
|
||||
}
|
||||
#play-quiz-scoreboard {
|
||||
position: fixed;
|
||||
top: max(56px, env(safe-area-inset-top));
|
||||
@@ -696,6 +816,23 @@
|
||||
<ul id="play-quiz-scoreboard-list" class="play-quiz-scoreboard-list"></ul>
|
||||
</div>
|
||||
<div id="play-quiz-feedback" class="is-hidden play-quiz-feedback" role="status" aria-atomic="true"></div>
|
||||
<div id="quiz-carry-pregame-overlay" class="is-hidden" role="dialog" aria-modal="true" aria-labelledby="quiz-carry-pregame-sr-title" aria-hidden="true">
|
||||
<div class="quiz-carry-pregame-backdrop" aria-hidden="true"></div>
|
||||
<div class="quiz-carry-pregame-card">
|
||||
<h2 id="quiz-carry-pregame-sr-title" class="quiz-carry-pregame-sr-only">ประลองความรู้ คำศัพท์ในกระบวนการยุติธรรม — How to play</h2>
|
||||
<div class="quiz-carry-pregame-art">
|
||||
<img id="quiz-carry-pregame-howto-img" class="quiz-carry-pregame-howto-img" src="/Game/img/quiz-carry/howto.png" width="900" height="520" alt="HOW TO PLAY — ประลองความรู้ คำศัพท์ในกระบวนการยุติธรรม" loading="lazy" decoding="async" />
|
||||
</div>
|
||||
<div class="quiz-carry-pregame-footer">
|
||||
<p id="quiz-carry-pregame-status" class="quiz-carry-pregame-status" aria-live="polite"></p>
|
||||
<div class="quiz-carry-pregame-actions">
|
||||
<button type="button" id="quiz-carry-pregame-primary" class="quiz-carry-pregame-primary-btn" aria-pressed="false" title="READY">
|
||||
<img id="quiz-carry-pregame-primary-img" src="/Game/img/quiz-carry/btn-ready.png" width="220" height="56" alt="READY" decoding="async" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="quiz-carry-embed-countdown" class="is-hidden" role="alert" aria-live="assertive" aria-atomic="true" aria-hidden="true">
|
||||
<div id="quiz-carry-embed-countdown-inner">
|
||||
<p id="quiz-carry-embed-countdown-kicker" class="quiz-carry-embed-countdown-kicker">คำถาม · Question</p>
|
||||
@@ -765,7 +902,7 @@
|
||||
</div>
|
||||
<script src="/Game/socket.io/socket.io.js"></script>
|
||||
<script src="js/version.js?v=0.0166"></script>
|
||||
<script src="js/play.js?v=0.095"></script>
|
||||
<script src="js/play.js?v=0.100"></script>
|
||||
<div class="version-tag">v —</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -88,6 +88,8 @@ function defaultQuizSettings() {
|
||||
carryReadMs: 3000,
|
||||
/** quiz_carry: หลังตัวเลือกขึ้น ให้เวลาตอบ (มิลลิวิ) */
|
||||
carryAnswerMs: 5000,
|
||||
/** quiz_carry: จำนวนข้อต่อเซสชันก่อนจบเกม (พรีวิว) — 0 = ไม่จบอัตโนมัติ */
|
||||
carrySessionLength: 0,
|
||||
questions: [],
|
||||
carryQuestions: [],
|
||||
battleQuizMcq: [],
|
||||
@@ -163,6 +165,12 @@ function clampQuizBetweenMs(n, def) {
|
||||
return Math.max(0, Math.min(300000, Math.floor(v)));
|
||||
}
|
||||
|
||||
function clampCarrySessionLength(n, def) {
|
||||
const v = Number(n);
|
||||
if (Number.isNaN(v)) return def;
|
||||
return Math.max(0, Math.min(500, Math.floor(v)));
|
||||
}
|
||||
|
||||
function loadQuizSettings() {
|
||||
try {
|
||||
if (fs.existsSync(QUIZ_SETTINGS_PATH)) {
|
||||
@@ -177,6 +185,7 @@ function loadQuizSettings() {
|
||||
betweenMs: clampQuizBetweenMs(j.betweenMs, d.betweenMs),
|
||||
carryReadMs: clampQuizBetweenMs(j.carryReadMs, d.carryReadMs),
|
||||
carryAnswerMs: clampQuizMs(j.carryAnswerMs, d.carryAnswerMs),
|
||||
carrySessionLength: clampCarrySessionLength(j.carrySessionLength, d.carrySessionLength),
|
||||
questions,
|
||||
carryQuestions,
|
||||
battleQuizMcq,
|
||||
@@ -474,6 +483,9 @@ function saveQuizSettings(d) {
|
||||
const betweenMs = d.betweenMs != null ? clampQuizBetweenMs(d.betweenMs, prev.betweenMs) : prev.betweenMs;
|
||||
const carryReadMs = d.carryReadMs != null ? clampQuizBetweenMs(d.carryReadMs, prev.carryReadMs) : prev.carryReadMs;
|
||||
const carryAnswerMs = d.carryAnswerMs != null ? clampQuizMs(d.carryAnswerMs, prev.carryAnswerMs) : prev.carryAnswerMs;
|
||||
const carrySessionLength = d.carrySessionLength != null
|
||||
? clampCarrySessionLength(d.carrySessionLength, prev.carrySessionLength)
|
||||
: prev.carrySessionLength;
|
||||
const questions = Array.isArray(d.questions)
|
||||
? (d.questions || [])
|
||||
.filter((q) => q && String(q.text || '').trim())
|
||||
@@ -491,6 +503,7 @@ function saveQuizSettings(d) {
|
||||
betweenMs,
|
||||
carryReadMs,
|
||||
carryAnswerMs,
|
||||
carrySessionLength,
|
||||
questions,
|
||||
carryQuestions,
|
||||
battleQuizMcq,
|
||||
@@ -2259,6 +2272,22 @@ function scheduleTroublesomeRoll(sid) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ห้องทดสอบเอดิเตอร์ / join ด้วย mapId: mapData บน space อาจยังเป็น lobby แต่ไคลเอนต์เล่น quiz_carry จากพารามิเตอร์
|
||||
* — ให้ lobby Ready/START ทำงานถ้า mapId ชี้แมป quiz_carry หรือเป็นห้อง preview
|
||||
*/
|
||||
function spaceAllowsQuizCarryLobbyRelaxed(space) {
|
||||
if (!space) return false;
|
||||
if (space.mapId) {
|
||||
const byId = maps.get(space.mapId);
|
||||
if (byId && byId.gameType === 'quiz_carry') return true;
|
||||
}
|
||||
const md = space.mapData;
|
||||
if (md && md.gameType === 'quiz_carry') return true;
|
||||
return !!(space.previewEditorTest
|
||||
|| (space.isPrivate && String(space.spaceName || '').toLowerCase() === 'preview'));
|
||||
}
|
||||
|
||||
io.on('connection', (socket) => {
|
||||
socket.on('join-space', ({ spaceId, nickname, characterId }, cb) => {
|
||||
const space = spaces.get(spaceId);
|
||||
@@ -2304,6 +2333,11 @@ io.on('connection', (socket) => {
|
||||
peer.y = sn.y;
|
||||
}
|
||||
space.peers.set(socket.id, peer);
|
||||
if (spaceAllowsQuizCarryLobbyRelaxed(space)) {
|
||||
if (!space.quizCarryLobbyReady || typeof space.quizCarryLobbyReady !== 'object') space.quizCarryLobbyReady = {};
|
||||
space.quizCarryLobbyReady[socket.id] = false;
|
||||
io.to(spaceId).emit('quiz-carry-lobby-sync', { readyMap: { ...space.quizCarryLobbyReady } });
|
||||
}
|
||||
if (mdJoin.gameType === 'gauntlet') {
|
||||
const ord = [...space.peers.keys()].indexOf(socket.id);
|
||||
const pos = gauntletSpawnPositions(mdJoin, space.peers.size);
|
||||
@@ -2624,6 +2658,12 @@ io.on('connection', (socket) => {
|
||||
if (space.troublesomeEligible) space.troublesomeEligible.delete(socket.id);
|
||||
if (space.quizSession && space.quizSession.players) delete space.quizSession.players[socket.id];
|
||||
if (space.gauntletPreviewRowsBySocket) space.gauntletPreviewRowsBySocket.delete(socket.id);
|
||||
if (space.quizCarryLobbyReady && typeof space.quizCarryLobbyReady === 'object') {
|
||||
delete space.quizCarryLobbyReady[socket.id];
|
||||
if (spaceAllowsQuizCarryLobbyRelaxed(space)) {
|
||||
io.to(sid).emit('quiz-carry-lobby-sync', { readyMap: { ...space.quizCarryLobbyReady } });
|
||||
}
|
||||
}
|
||||
space.peers.delete(socket.id);
|
||||
if (space.peers.size === 0) {
|
||||
stopGauntletTicker(space);
|
||||
@@ -2672,6 +2712,48 @@ io.on('connection', (socket) => {
|
||||
space.gauntletPreviewRowsBySocket.set(socket.id, set);
|
||||
});
|
||||
|
||||
socket.on('quiz-carry-lobby-sync-request', () => {
|
||||
const sid = socket.data.spaceId;
|
||||
const space = sid ? spaces.get(sid) : null;
|
||||
if (!space || !space.peers.has(socket.id)) return;
|
||||
if (!spaceAllowsQuizCarryLobbyRelaxed(space)) return;
|
||||
if (!space.quizCarryLobbyReady || typeof space.quizCarryLobbyReady !== 'object') {
|
||||
space.quizCarryLobbyReady = {};
|
||||
}
|
||||
for (const id of space.peers.keys()) {
|
||||
if (space.quizCarryLobbyReady[id] === undefined) space.quizCarryLobbyReady[id] = false;
|
||||
}
|
||||
socket.emit('quiz-carry-lobby-sync', { readyMap: { ...space.quizCarryLobbyReady } });
|
||||
});
|
||||
|
||||
socket.on('quiz-carry-lobby-ready', (data) => {
|
||||
const sid = socket.data.spaceId;
|
||||
const space = sid ? spaces.get(sid) : null;
|
||||
if (!space || !space.peers.has(socket.id)) return;
|
||||
if (!spaceAllowsQuizCarryLobbyRelaxed(space)) return;
|
||||
if (!space.quizCarryLobbyReady || typeof space.quizCarryLobbyReady !== 'object') space.quizCarryLobbyReady = {};
|
||||
for (const id of space.peers.keys()) {
|
||||
if (space.quizCarryLobbyReady[id] === undefined) space.quizCarryLobbyReady[id] = false;
|
||||
}
|
||||
space.quizCarryLobbyReady[socket.id] = !!(data && data.ready);
|
||||
io.to(sid).emit('quiz-carry-lobby-sync', { readyMap: { ...space.quizCarryLobbyReady } });
|
||||
});
|
||||
|
||||
socket.on('quiz-carry-lobby-start', (_data, cb) => {
|
||||
const reply = typeof cb === 'function' ? cb : () => {};
|
||||
const sid = socket.data.spaceId;
|
||||
const space = sid ? spaces.get(sid) : null;
|
||||
if (!space || !space.peers.has(socket.id)) return reply({ ok: false, error: 'ไม่อยู่ในห้อง' });
|
||||
if (!spaceAllowsQuizCarryLobbyRelaxed(space)) return reply({ ok: false, error: 'ห้องนี้ไม่ใช่โหมด lobby หยิบมาวาง / พรีวิว' });
|
||||
if (space.hostId !== socket.id) return reply({ ok: false, error: 'เฉพาะโฮสต์กด START' });
|
||||
const rm = space.quizCarryLobbyReady || {};
|
||||
const humanIds = [...space.peers.keys()];
|
||||
const allReady = humanIds.length > 0 && humanIds.every((id) => rm[id]);
|
||||
if (!allReady) return reply({ ok: false, error: 'ยังมีผู้เล่นที่ยังไม่ Ready' });
|
||||
io.to(sid).emit('quiz-carry-lobby-started', {});
|
||||
reply({ ok: true });
|
||||
});
|
||||
|
||||
socket.on('move', (data) => {
|
||||
for (const [sid, space] of spaces) {
|
||||
if (space.peers.has(socket.id)) {
|
||||
|
||||
Reference in New Issue
Block a user