คำถามหลายตัวเลือก (หยิบมาวาง)
ใช้กับฉากประเภท ตอบคำถาม — หยิบคำตอบมาวางกลาง ใน Editor — ผู้เล่นหยิบตัวเลือกไปวางโซนกลาง · ถ้ามีรายการที่นี่ เกมจะใช้เฉพาะชุดนี้ (ไม่ผสมกับคำถามถูก/ผิดแท็บคำถามเกม) · เก็บที่ /Game/data/quiz-settings.json ฟิลด์ carryQuestions
- อย่างน้อย 2 ตัวเลือกต่อข้อ (สูงสุด 6) · เวลายังตั้งที่แท็บ คำถามเกมได้ (ใช้ร่วมกับโหมดถามตอบอื่น)
+ อย่างน้อย 2 ตัวเลือกต่อข้อ (สูงสุด 6) · English: Per-round timing below applies only to quiz carry (after 3-2-1 in embed preview: question first, then options).
+
+
+
+
รายการคำถาม
diff --git a/www/html/Game/public/js/play.js b/www/html/Game/public/js/play.js
index 17ebe87..260b7a4 100644
--- a/www/html/Game/public/js/play.js
+++ b/www/html/Game/public/js/play.js
@@ -565,6 +565,10 @@
let quizCarryEmbedPendingQuestion = null;
let quizCarryEmbedCountdownStartAt = 0;
let quizCarryEmbedCountdownEndAt = 0;
+ /** quiz_carry: epoch ให้หยิบตัวเลือกได้ · epoch ปิดรอบตอบ (ตั้งที่ Admin แท็บหยิบมาวาง) */
+ let quizCarryOptionRevealAt = 0;
+ let quizCarryAnswerCloseAt = 0;
+ let quizCarryCarryTimingMs = { carryReadMs: 3000, carryAnswerMs: 5000 };
/** jump_survive — กล้องตามตัวในกรอบ + แพลตฟอร์มเลื่อนขึ้น (scroll) ไม่ลากทั้งฉาก */
let jumpSurviveCamCenterX = 0;
@@ -1660,6 +1664,7 @@
function drawQuizCarryChoiceLabels(ctx, worldToScreen, zoom) {
if (!isQuizCarry() || !mapData || !quizCarryCurrent) return;
+ if (!quizCarryOptionsPickableNow()) return;
const choices = quizCarryCurrent.choices;
if (!choices || !choices.length) return;
const maxSlots = choices.length;
@@ -1858,6 +1863,67 @@
return !!(previewMode && editorEmbedReturn && isQuizCarry() && quizCarryEmbedCountdownEndAt > Date.now());
}
+ function quizCarryOptionsPickableNow() {
+ if (!isQuizCarry() || !quizCarryCurrent) return true;
+ if (!quizCarryOptionRevealAt || quizCarryOptionRevealAt <= 0) return true;
+ return Date.now() >= quizCarryOptionRevealAt;
+ }
+
+ function quizCarryApplyPhaseTimersForCurrentQuestion() {
+ if (!quizCarryCurrent) {
+ quizCarryOptionRevealAt = 0;
+ quizCarryAnswerCloseAt = 0;
+ return;
+ }
+ const readMs = Math.max(0, Math.floor(Number(quizCarryCarryTimingMs.carryReadMs)) || 0);
+ const ansMs = Math.max(1000, Math.floor(Number(quizCarryCarryTimingMs.carryAnswerMs)) || 5000);
+ const t0 = Date.now();
+ if (readMs <= 0) {
+ quizCarryOptionRevealAt = 0;
+ quizCarryAnswerCloseAt = t0 + ansMs;
+ } else {
+ quizCarryOptionRevealAt = t0 + readMs;
+ quizCarryAnswerCloseAt = quizCarryOptionRevealAt + ansMs;
+ }
+ }
+
+ function updateQuizCarryCarryPhaseHud() {
+ if (!isQuizCarry() || !quizCarryCurrent) return;
+ const phaseEl = document.getElementById('quiz-game-phase-label');
+ if (!phaseEl) return;
+ const now = Date.now();
+ if (quizCarryOptionRevealAt > 0 && now < quizCarryOptionRevealAt) {
+ const remain = Math.max(0, Math.ceil((quizCarryOptionRevealAt - now) / 1000));
+ phaseEl.textContent = 'อ่านคำถาม — ตัวเลือกขึ้นใน ' + remain + ' วิ';
+ return;
+ }
+ if (quizCarryAnswerCloseAt > now) {
+ const remain = Math.max(0, Math.ceil((quizCarryAnswerCloseAt - now) / 1000));
+ phaseEl.textContent = 'หยิบ/ส่งคำตอบ — เหลือ ' + remain + ' วิ';
+ return;
+ }
+ quizCarryRestoreEmbedPhaseLabel();
+ }
+
+ function tickQuizCarryRoundTimers() {
+ if (!isQuizCarry() || !mapData || !quizCarryCurrent) return;
+ if (quizCarryEmbedCountdownEndAt > Date.now()) return;
+ if (quizCarryEmbedPendingQuestion) return;
+ if (!quizCarryAnswerCloseAt || quizCarryAnswerCloseAt <= 0) return;
+ if (Date.now() < quizCarryAnswerCloseAt) return;
+ quizCarryAnswerCloseAt = 0;
+ quizCarryOptionRevealAt = 0;
+ showQuizCarryToast('หมดเวลา — ข้อถัดไป', false);
+ me.quizCarryHeld = null;
+ others.forEach((o) => {
+ if (!o) return;
+ o.quizCarryHeld = null;
+ o.botPath = [];
+ });
+ renderPlayQuizScoreboard(playLiveQuizScores);
+ quizCarryPickNextQuestion();
+ }
+
/** นับ 3–2–1: ไม่โชว์ข้อความคำถามใน overlay — มีแค่เลข */
function hideQuizCarryEmbedCountdownQuestionChrome() {
const cq = document.getElementById('quiz-carry-embed-countdown-q');
@@ -1901,6 +1967,7 @@
o.botQuizCarryPathfindAfter = 0;
});
syncQuizCarryEmbedQuestionStrip();
+ quizCarryApplyPhaseTimersForCurrentQuestion();
return;
}
const elapsed = now - quizCarryEmbedCountdownStartAt;
@@ -1920,6 +1987,8 @@
quizCarryEmbedPendingQuestion = null;
quizCarryEmbedCountdownEndAt = 0;
quizCarryEmbedCountdownStartAt = 0;
+ quizCarryOptionRevealAt = 0;
+ quizCarryAnswerCloseAt = 0;
hideQuizCarryEmbedCountdownOverlay();
playQuizText = 'ไม่มีคำถาม — ใส่ quizQuestions ในแมป (choices + correctIndex) หรือตั้งใน Admin';
return;
@@ -1936,8 +2005,12 @@
quizCarryEmbedCountdownEndAt = quizCarryEmbedCountdownStartAt + 3000;
quizCarryCurrent = null;
playQuizText = '';
+ quizCarryOptionRevealAt = 0;
+ quizCarryAnswerCloseAt = 0;
+ me.quizCarryHeld = null;
others.forEach((o) => {
if (!o) return;
+ o.quizCarryHeld = null;
o.botPath = [];
o.botPathStuckTicks = 0;
/* ต้องใช้ performance.now() ช่วงกับ stepQuizCarryPreviewBots — ห้ามใช้ Date.now() epoch ไม่งั้นบอทค้าง pathfind ตลอด */
@@ -1955,6 +2028,12 @@
playQuizText = formatQuizCarryQuestionHud(q);
const qEl = document.getElementById('quiz-game-question');
if (qEl) qEl.textContent = playQuizText;
+ me.quizCarryHeld = null;
+ others.forEach((o) => {
+ if (!o) return;
+ o.quizCarryHeld = null;
+ });
+ quizCarryApplyPhaseTimersForCurrentQuestion();
}
function showQuizCarryToast(msg, ok) {
@@ -1989,6 +2068,7 @@
const correct = quizCarryCurrent.correctIndex;
let held = ent.quizCarryHeld;
if (held != null && canSubmitAnswer) {
+ if (!quizCarryOptionsPickableNow()) return false;
ent.quizCarryHeld = null;
const ok = held === correct;
if (ok) {
@@ -2004,6 +2084,7 @@
return true;
}
if (held == null && pickupIdx != null) {
+ if (!quizCarryOptionsPickableNow()) return false;
if (quizCarryOptionHeldByAnyone(pickupIdx)) {
if (opts.fromKey && actorId === myId && !opts.silent) {
showQuizCarryToast('ตัวเลือกนี้มีคนถืออยู่แล้ว — เลือกข้ออื่น', false);
@@ -2039,6 +2120,8 @@
quizCarryEmbedPendingQuestion = null;
quizCarryEmbedCountdownStartAt = 0;
quizCarryEmbedCountdownEndAt = 0;
+ quizCarryOptionRevealAt = 0;
+ quizCarryAnswerCloseAt = 0;
hideQuizCarryEmbedCountdownOverlay();
me.quizCarryHeld = null;
others.forEach((o) => {
@@ -2054,6 +2137,10 @@
const r = await fetch(BASE + '/api/quiz-settings');
if (r.ok) {
const s = await r.json();
+ quizCarryCarryTimingMs = {
+ carryReadMs: clampPreviewMs(s.carryReadMs, 3000, 0, 120000),
+ carryAnswerMs: clampPreviewMs(s.carryAnswerMs, 5000, 1000, 300000),
+ };
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++) {
@@ -2195,20 +2282,24 @@
if (typeof o.botQuizCarryPathfindAfter !== 'number') o.botQuizCarryPathfindAfter = 0;
if (nowPf < o.botQuizCarryPathfindAfter) return;
const jitter = (String(id).length * 31 + ((o.x | 0) ^ (o.y | 0)) * 7) % 160;
- if (o.quizCarryHeld == null && wantIdx != null) {
- const dest = pickRandomTileForQuizCarryOption(mapData, wantIdx, o);
- if (dest) {
- const path = pathfindPlayForBot(o.x, o.y, dest.x + 0.5, dest.y + 0.5, o);
- if (path && path.length > 1) o.botPath = path.slice(1);
+ if (quizCarryOptionsPickableNow()) {
+ if (o.quizCarryHeld == null && wantIdx != null) {
+ const dest = pickRandomTileForQuizCarryOption(mapData, wantIdx, o);
+ if (dest) {
+ const path = pathfindPlayForBot(o.x, o.y, dest.x + 0.5, dest.y + 0.5, o);
+ if (path && path.length > 1) o.botPath = path.slice(1);
+ }
+ } else {
+ const sub = pickRandomTileForQuizCarrySubmit(mapData, o);
+ if (sub) {
+ const path = pathfindPlayForBot(o.x, o.y, sub.x + 0.5, sub.y + 0.5, o);
+ if (path && path.length > 1) o.botPath = path.slice(1);
+ }
}
+ o.botQuizCarryPathfindAfter = nowPf + pathfindMinGap + jitter;
} else {
- const sub = pickRandomTileForQuizCarrySubmit(mapData, o);
- if (sub) {
- const path = pathfindPlayForBot(o.x, o.y, sub.x + 0.5, sub.y + 0.5, o);
- if (path && path.length > 1) o.botPath = path.slice(1);
- }
+ o.botQuizCarryPathfindAfter = nowPf + 120;
}
- o.botQuizCarryPathfindAfter = nowPf + pathfindMinGap + jitter;
} else {
stepPreviewBotAlongPath(o, w, h);
}
@@ -7529,6 +7620,8 @@
if (o.ty != null) o.y += (o.ty - o.y) * LERP;
});
tickQuizCarryEmbedCountdown();
+ tickQuizCarryRoundTimers();
+ updateQuizCarryCarryPhaseHud();
stepPreviewBots();
if (isChatFocused()) {
me.isWalking = false;
diff --git a/www/html/Game/public/play.html b/www/html/Game/public/play.html
index 2ac3a6d..444c2cf 100644
--- a/www/html/Game/public/play.html
+++ b/www/html/Game/public/play.html
@@ -765,7 +765,7 @@
-
+
v —