update mock design

This commit is contained in:
2026-06-07 10:46:44 +00:00
parent b3c646d148
commit adfa083809
28 changed files with 1397 additions and 26 deletions
File diff suppressed because one or more lines are too long
+10 -1
View File
@@ -126,6 +126,7 @@
<optgroup label="Quiz Battle">
<option value="quizBattlePath" id="draw-mode-option-quiz-battle-path" hidden>เส้นทางเดิน (ม่วง — วาดอย่างน้อย 1 ช่อง = จำกัดเดินเฉพาะเส้นทาง)</option>
<option value="quizBattleDome" id="draw-mode-option-quiz-battle-dome" hidden>โดมคำถาม (ช่องกด E ในเกม)</option>
<option value="quizBattlePathGate" id="draw-mode-option-quiz-battle-path-gate" hidden>จุดปลดล็อก / ประตู (แดง — วางบนเส้นทาง ต้องตอบสถานีก่อนผ่าน)</option>
</optgroup>
<optgroup label="กระโดดให้รอด">
<option value="jumpSurvivePlatform1" id="draw-mode-option-jump-platform-1" hidden>แพลตฟอร์ม 1 (ยืนได้ — รูปช่อง 1 จาก Admin)</option>
@@ -162,6 +163,14 @@
</optgroup>
</select>
</label>
<span id="quiz-battle-gate-wrap" class="editor-quiz-battle-gate-wrap" style="display:none;flex-wrap:wrap;align-items:center;gap:0.35rem 0.65rem;max-width:100%;" title="เลือกคำถามที่ต้องตอบถูกก่อนผ่าน — คลิกโดมเพื่อเลือก · คลิกเส้นทางม่วงวางประตู · ขวาลบ">
<label style="display:flex;flex-direction:column;gap:0.15rem;min-width:12rem;flex:1 1 220px;">คำถามปลดล็อก
<select id="quiz-battle-gate-question" style="max-width:100%;">
<option value="1">กำลังโหลดคำถาม…</option>
</select>
</label>
<button type="button" id="quiz-battle-gate-reload" class="editor-btn-secondary" style="font-size:0.72rem;padding:0.2rem 0.45rem;">โหลดคำถามใหม่</button>
</span>
<span id="cell-color-wrap" class="editor-cell-color" style="display:none;">
<label>สี <input type="color" id="cell-color" value="#24283b" title="สี"></label>
<label>Alpha <input type="range" id="cell-alpha" min="0" max="100" value="100" title="ความโปร่งใส 0=ใส 100=ทึบ"> <span id="cell-alpha-value">100</span>%</label>
@@ -407,7 +416,7 @@
</div>
</div>
<script src="js/version.js?v=0.0258"></script>
<script src="js/editor.js?v=0.0336"></script>
<script src="js/editor.js?v=0.0338"></script>
<div class="version-tag">v —</div>
<!-- ===== QB overlay layout: map เต็มจอ + แถบเครื่องมือลอย ดัก/พับ/ปิดเปิดได้ (เพิ่มทับ ไม่แตะ editor.js) ===== -->
+244 -3
View File
@@ -108,6 +108,13 @@
let quizBattleDomeArea = [];
/** quiz_battle: กริด 0/1 — ถ้ามีอย่างน้อย 1 ช่อง = ในเกมเดินได้เฉพาะบนเส้นทาง */
let quizBattlePathArea = [];
/** quiz_battle: กริด 0/สถานี — จุดประตูบนเส้นทาง (ต้องตอบสถานี N ก่อนผ่านช่องนี้และข้างหน้า) */
let quizBattlePathGateArea = [];
/** quiz_battle: หมวดคำถามของห้อง (topic1..topic10) — กรองจาก battleQuizMcq */
let editorQuizBattleCategoryId = '';
/** quiz_battle: รายการคำถามใน Editor [{ compId, text }] */
let editorQuizBattleMcqList = [];
let editorQuizBattleDomeComp = null;
let stackReleaseArea = [], stackLandArea = [];
/** jump_survive: แพลตฟอร์ม { x, y, w, h } tile — ใช้เต็มที่เมื่อมีมุมข้าง */
let jumpSurvivePlatforms = [];
@@ -1243,6 +1250,157 @@
}
}
function ensureQuizBattlePathGateArea() {
if (!quizBattlePathGateArea.length || quizBattlePathGateArea.length !== height) {
const existing = quizBattlePathGateArea.slice().map(r => r && r.slice());
quizBattlePathGateArea = [];
for (let y = 0; y < height; y++) {
const row = existing[y] && existing[y].length === width ? existing[y].slice() : Array(width).fill(0);
quizBattlePathGateArea.push(row);
}
} else {
for (let y = 0; y < height; y++) {
if (!quizBattlePathGateArea[y] || quizBattlePathGateArea[y].length !== width) quizBattlePathGateArea[y] = Array(width).fill(0);
}
}
}
function inferQuizBattleCategoryFromMapId(id) {
const m = /^qbroom(\d+)$/i.exec(String(id || '').trim());
if (!m) return '';
const n = parseInt(m[1], 10);
return n > 0 ? ('topic' + n) : '';
}
function rebuildEditorQuizBattleDomeComp() {
if (!quizBattleDomeArea.length) {
editorQuizBattleDomeComp = null;
return;
}
const w = width, h = height;
const dome = quizBattleDomeArea;
const comp = Array(h).fill(0).map(() => Array(w).fill(0));
const seen = Array(h).fill(0).map(() => Array(w).fill(false));
let compId = 0;
for (let y = 0; y < h; y++) {
for (let x = 0; x < w; x++) {
if (!dome[y] || dome[y][x] !== 1 || seen[y][x]) continue;
compId++;
const q = [[x, y]];
seen[y][x] = true;
while (q.length) {
const [cx, cy] = q.shift();
comp[cy][cx] = compId;
for (const [dx, dy] of [[1, 0], [-1, 0], [0, 1], [0, -1]]) {
const nx = cx + dx, ny = cy + dy;
if (nx < 0 || ny < 0 || nx >= w || ny >= h) continue;
if (!dome[ny] || dome[ny][nx] !== 1 || seen[ny][nx]) continue;
seen[ny][nx] = true;
q.push([nx, ny]);
}
}
}
}
editorQuizBattleDomeComp = comp;
}
function getEditorQuizBattleDomeCompAt(tx, ty) {
if (!editorQuizBattleDomeComp) rebuildEditorQuizBattleDomeComp();
if (!editorQuizBattleDomeComp || ty < 0 || tx < 0 || ty >= height || tx >= width) return 0;
return editorQuizBattleDomeComp[ty][tx] || 0;
}
function quizBattleGateQuestionSnippet(compId) {
const row = editorQuizBattleMcqList.find((q) => q.compId === compId);
if (!row || !row.text) return '';
const t = String(row.text).trim();
return t.length > 48 ? t.slice(0, 48) + '…' : t;
}
function sanitizeEditorBattleMcqRow(raw, roomCat) {
if (!raw || !String(raw.text || '').trim()) return null;
if (roomCat && String(raw.categoryId || '').trim() !== roomCat) return null;
const ch = Array.isArray(raw.choices) ? raw.choices : [];
if (ch.length !== 3) return null;
const a = String(ch[0] || '').trim();
const b = String(ch[1] || '').trim();
const c = String(ch[2] || '').trim();
if (!a || !b || !c) return null;
return { text: String(raw.text).trim() };
}
function rebuildQuizBattleGateQuestionSelect() {
const sel = document.getElementById('quiz-battle-gate-question');
if (!sel) return;
const prev = sel.value;
sel.innerHTML = '';
if (!editorQuizBattleMcqList.length) {
const opt = document.createElement('option');
opt.value = '1';
opt.textContent = 'ยังไม่มีคำถาม — บันทึกใน Admin → Quiz Battle';
sel.appendChild(opt);
return;
}
editorQuizBattleMcqList.forEach((row) => {
const opt = document.createElement('option');
opt.value = String(row.compId);
const snip = row.text.length > 56 ? row.text.slice(0, 56) + '…' : row.text;
opt.textContent = 'ข้อ ' + row.compId + ': ' + snip;
opt.title = row.text;
sel.appendChild(opt);
});
if (prev && editorQuizBattleMcqList.some((q) => String(q.compId) === prev)) sel.value = prev;
}
async function loadEditorQuizBattleMcqList() {
const roomCat = editorQuizBattleCategoryId ? String(editorQuizBattleCategoryId).trim() : '';
editorQuizBattleMcqList = [];
const bust = '_=' + Date.now();
const tryUrls = [
BASE + '/api/quiz-settings?' + bust,
BASE + '/api-quiz-battle-mcq.php?' + bust,
];
for (let u = 0; u < tryUrls.length; u++) {
try {
const r = await fetch(tryUrls[u], { cache: 'no-store' });
if (!r.ok) continue;
const s = await r.json();
const arr = s && Array.isArray(s.battleQuizMcq) ? s.battleQuizMcq : [];
let compId = 0;
for (let i = 0; i < arr.length; i++) {
const row = sanitizeEditorBattleMcqRow(arr[i], roomCat);
if (!row) continue;
compId++;
editorQuizBattleMcqList.push({ compId: compId, text: row.text });
}
if (editorQuizBattleMcqList.length) break;
} catch (e) { /* next */ }
}
rebuildQuizBattleGateQuestionSelect();
if (statusEl && drawModeEl && drawModeEl.value === 'quizBattlePathGate') {
const n = editorQuizBattleMcqList.length;
statusEl.textContent = n
? ('โหลดคำถามแล้ว ' + n + ' ข้อ' + (roomCat ? (' (' + roomCat + ')') : '') + ' — เลือกคำถามแล้วคลิกเส้นทางม่วง')
: 'ยังไม่มีคำถามใน Admin → Quiz Battle' + (roomCat ? (' สำหรับ ' + roomCat) : '');
}
}
function readQuizBattleGateStationInput() {
const sel = document.getElementById('quiz-battle-gate-question');
let n = sel ? parseInt(sel.value, 10) : 1;
if (!(n > 0)) n = 1;
if (editorQuizBattleMcqList.length) n = Math.min(n, editorQuizBattleMcqList.length);
else if (n > 99) n = 99;
return n;
}
function setQuizBattleGateQuestionSelect(compId) {
const sel = document.getElementById('quiz-battle-gate-question');
if (!sel || !(compId > 0)) return;
const v = String(compId);
if ([...sel.options].some((o) => o.value === v)) sel.value = v;
}
function ensureStartGameArea() {
if (!startGameArea.length || startGameArea.length !== height) {
const existing = startGameArea.slice().map(r => r && r.slice());
@@ -1912,6 +2070,7 @@
const dqCarryOpts = document.querySelectorAll('.quiz-carry-mode-opt');
const dqBattleDome = document.getElementById('draw-mode-option-quiz-battle-dome');
const dqBattlePath = document.getElementById('draw-mode-option-quiz-battle-path');
const dqBattleGate = document.getElementById('draw-mode-option-quiz-battle-path-gate');
if (!froggerWrap) return;
const gt = gameTypeEl ? gameTypeEl.value : 'zep';
if (drawModeEl && drawModeEl.value === 'jumpSurvivePlatform') drawModeEl.value = 'jumpSurvivePlatform1';
@@ -1932,6 +2091,10 @@
dqBattlePath.hidden = gt !== 'quiz_battle';
if (gt !== 'quiz_battle' && drawModeEl && drawModeEl.value === 'quizBattlePath') drawModeEl.value = 'wall';
}
if (dqBattleGate) {
dqBattleGate.hidden = gt !== 'quiz_battle';
if (gt !== 'quiz_battle' && drawModeEl && drawModeEl.value === 'quizBattlePathGate') drawModeEl.value = 'wall';
}
if (hint) hint.style.removeProperty('display');
if (gt === 'frogger') {
froggerWrap.style.display = 'block';
@@ -2046,10 +2209,15 @@
draw();
} else if (gt === 'quiz_battle') {
froggerWrap.style.display = 'none';
if (!editorQuizBattleCategoryId && mapId) {
editorQuizBattleCategoryId = inferQuizBattleCategoryFromMapId(mapId);
}
loadEditorQuizBattleMcqList();
if (hint) {
hint.innerHTML = '<p class="editor-hint-lead"><strong>Quiz Battle</strong> — เส้นทาง + โดมถาม A / B / C</p>' + editorHintBulletList([
'โหมด <strong>เส้นทางเดิน (ม่วง)</strong> — วาดซิกแซกเหมือนทางในเกม · ถ้ามีอย่างน้อย 1 ช่อง ผู้เล่น<strong>เดินได้เฉพาะบนเส้นทาง</strong> · ไม่วาดเลย = เดินอิสระทั้งแมป',
'โหมด <strong>โดมคำถาม</strong> — วางบนเส้นทาง (หรือทั่วแมปถ้าไม่ใช้เส้นทาง) · ในเกมกด <kbd>E</kbd>',
'โหมด <strong>จุดปลดล็อก / ประตู (แดง)</strong> — เลือก<strong>คำถาม</strong>จากเมนู · คลิกโดมเพื่อเลือกคำถามนั้น · คลิกเส้นทางม่วงหลังโดมเพื่อวางประตู · ไม่วาด = คำนวณอัตโนมัติ',
'รูป <strong>พื้นหลัง</strong> เปลี่ยนทีหลังได้ — ตรรกะเส้นทาง/โดมอยู่ที่กริด ไม่ผูกกราฟิก',
'ข้อสอบจาก <strong>Admin → Quiz Battle</strong> (<code>battleQuizMcq</code>) · แนะนำ <strong>พื้นที่สุ่มจุดเกิด</strong> ให้อยู่บนเส้นทางหรือใกล้จุดเริ่ม',
]);
@@ -2146,7 +2314,7 @@
if (drawModeEl && carryModes.indexOf(drawModeEl.value) >= 0 && gt !== 'quiz_carry') {
drawModeEl.value = 'wall';
}
if (drawModeEl && (drawModeEl.value === 'quizBattleDome' || drawModeEl.value === 'quizBattlePath') && gt !== 'quiz_battle') {
if (drawModeEl && (drawModeEl.value === 'quizBattleDome' || drawModeEl.value === 'quizBattlePath' || drawModeEl.value === 'quizBattlePathGate') && gt !== 'quiz_battle') {
drawModeEl.value = 'wall';
}
const jPlat1 = document.getElementById('draw-mode-option-jump-platform-1');
@@ -2190,6 +2358,7 @@
carryEmbedCountdownArea = Array(height).fill(0).map(() => Array(width).fill(0));
quizBattleDomeArea = Array(height).fill(0).map(() => Array(width).fill(0));
quizBattlePathArea = Array(height).fill(0).map(() => Array(width).fill(0));
quizBattlePathGateArea = Array(height).fill(0).map(() => Array(width).fill(0));
stackReleaseArea = Array(height).fill(0).map(() => Array(width).fill(0));
stackLandArea = Array(height).fill(0).map(() => Array(width).fill(0));
jumpSurvivePlatformArea = Array(height).fill(0).map(() => Array(width).fill(0));
@@ -2317,6 +2486,8 @@
if (gtDraw === 'quiz_battle') {
ensureQuizBattlePathArea();
ensureQuizBattleDomeArea();
ensureQuizBattlePathGateArea();
rebuildEditorQuizBattleDomeComp();
}
if (gtDraw === 'stack') ensureStackAreas();
if (gtDraw === 'jump_survive') {
@@ -2658,6 +2829,7 @@
ctx.strokeRect(tx + 1, ty + 1, tileSize - 2, tileSize - 2);
}
if (gtDraw === 'quiz_battle' && quizBattleDomeArea[y] && quizBattleDomeArea[y][x] === 1) {
const dComp = getEditorQuizBattleDomeCompAt(x, y);
ctx.fillStyle = 'rgba(100, 200, 255, 0.42)';
ctx.fillRect(tx + 2, ty + 2, tileSize - 4, tileSize - 4);
ctx.strokeStyle = 'rgba(255, 100, 130, 0.92)';
@@ -2667,7 +2839,21 @@
ctx.fillStyle = '#1a1b26';
ctx.font = 'bold 10px sans-serif';
ctx.textAlign = 'center';
ctx.fillText('E', tx + tileSize / 2, ty + tileSize / 2 + 3);
ctx.fillText(dComp > 0 ? ('E' + dComp) : 'E', tx + tileSize / 2, ty + tileSize / 2 + 3);
ctx.textAlign = 'left';
}
if (gtDraw === 'quiz_battle' && quizBattlePathGateArea[y] && quizBattlePathGateArea[y][x] > 0) {
const gn = quizBattlePathGateArea[y][x];
ctx.fillStyle = 'rgba(255, 55, 55, 0.62)';
ctx.fillRect(tx + Math.floor(tileSize / 2) - 4, ty + 2, 8, tileSize - 4);
ctx.strokeStyle = 'rgba(255, 220, 120, 0.95)';
ctx.lineWidth = 2;
ctx.strokeRect(tx + Math.floor(tileSize / 2) - 4, ty + 2, 8, tileSize - 4);
ctx.lineWidth = 1;
ctx.fillStyle = '#fff8e8';
ctx.font = 'bold 8px sans-serif';
ctx.textAlign = 'center';
ctx.fillText('Q' + gn, tx + tileSize / 2, ty + tileSize - 5);
ctx.textAlign = 'left';
}
}
@@ -3191,6 +3377,39 @@
if ((gameTypeEl ? gameTypeEl.value : gameType) !== 'quiz_battle') return;
ensureQuizBattleDomeArea();
quizBattleDomeArea[y][x] = left ? 1 : 0;
rebuildEditorQuizBattleDomeComp();
} else if (drawModeEl.value === 'quizBattlePathGate') {
if ((gameTypeEl ? gameTypeEl.value : gameType) !== 'quiz_battle') return;
ensureQuizBattlePathArea();
ensureQuizBattlePathGateArea();
if (left && quizBattleDomeArea[y] && quizBattleDomeArea[y][x] === 1) {
const dComp = getEditorQuizBattleDomeCompAt(x, y);
if (dComp > 0) {
setQuizBattleGateQuestionSelect(dComp);
const snip = quizBattleGateQuestionSnippet(dComp);
if (statusEl) {
statusEl.textContent = 'เลือกคำถามข้อ ' + dComp + (snip ? (' — 「' + snip + '」') : '') + ' · คลิกเส้นทางม่วงเพื่อวางประตู';
}
}
return;
}
if (!quizBattlePathArea[y] || quizBattlePathArea[y][x] !== 1) {
if (statusEl) statusEl.textContent = 'ประตูต้องวางบนเส้นทางม่วง (คลิกโดม E1/E2… เพื่อเลือกคำถาม)';
return;
}
if (left) {
const st = readQuizBattleGateStationInput();
quizBattlePathGateArea[y][x] = st;
const snip = quizBattleGateQuestionSnippet(st);
if (statusEl) {
statusEl.textContent = 'ประตูข้อ ' + st + (snip ? (' 「' + snip + '」') : '') + ' → (' + x + ',' + y + ')';
}
const next = editorQuizBattleMcqList.find((q) => q.compId > st);
if (next) setQuizBattleGateQuestionSelect(next.compId);
} else {
quizBattlePathGateArea[y][x] = 0;
if (statusEl) statusEl.textContent = 'ลบประตูช่อง (' + x + ',' + y + ')';
}
} else if (drawModeEl.value === 'carryEmbedCountdown') {
if ((gameTypeEl ? gameTypeEl.value : gameType) !== 'quiz_carry') return;
ensureQuizCarryAreas();
@@ -3613,7 +3832,7 @@
if (drawModeEl && drawModeEl.value === 'shooterSpawnPaint' && gameType !== 'space_shooter' && gameType !== 'jump_survive') drawModeEl.value = 'wall';
if (drawModeEl && drawModeEl.value === 'balloonBossPlayerPaint' && gameType !== 'balloon_boss') drawModeEl.value = 'wall';
if (drawModeEl && drawModeEl.value === 'balloonBossBossPaint' && gameType !== 'balloon_boss') drawModeEl.value = 'wall';
if (drawModeEl && (drawModeEl.value === 'quizBattleDome' || drawModeEl.value === 'quizBattlePath') && gameType !== 'quiz_battle') drawModeEl.value = 'wall';
if (drawModeEl && (drawModeEl.value === 'quizBattleDome' || drawModeEl.value === 'quizBattlePath' || drawModeEl.value === 'quizBattlePathGate') && gameType !== 'quiz_battle') drawModeEl.value = 'wall';
if (drawModeEl && drawModeEl.value === 'lobbyPlayerSpawn' && !supportsLobbySpawnPaint(gameType)) drawModeEl.value = 'wall';
toggleFroggerUI();
syncEditorStackHudMock();
@@ -3740,6 +3959,7 @@
ensureQuizCarryAreas();
ensureQuizBattleDomeArea();
ensureQuizBattlePathArea();
ensureQuizBattlePathGateArea();
ensureJumpSurvivePlatformArea();
ensureJumpSurviveHazardArea();
ensureShooterSpawnSlots();
@@ -3802,6 +4022,10 @@
})(),
quizBattleDomeArea: gameType === 'quiz_battle' ? quizBattleDomeArea.map(r => r.slice()) : [],
quizBattlePathArea: gameType === 'quiz_battle' ? quizBattlePathArea.map(r => r.slice()) : [],
quizBattlePathGateArea: gameType === 'quiz_battle' ? quizBattlePathGateArea.map(r => r.slice()) : [],
quizBattleCategoryId: gameType === 'quiz_battle' && editorQuizBattleCategoryId
? String(editorQuizBattleCategoryId).trim()
: undefined,
stackReleaseArea: gameType === 'stack' ? stackReleaseArea.map(r => r.slice()) : [],
stackLandArea: gameType === 'stack' ? stackLandArea.map(r => r.slice()) : [],
jumpSurvivePlatforms: gameType === 'jump_survive' ? jumpSurvivePlatforms.slice() : [],
@@ -3927,6 +4151,17 @@
const gt = gameTypeEl ? gameTypeEl.value : gameType;
bindWrap.style.display = (drawModeEl.value === 'cellImage' && gt === 'quiz_carry') ? 'flex' : 'none';
}
const gateWrap = document.getElementById('quiz-battle-gate-wrap');
if (gateWrap) {
const gt = gameTypeEl ? gameTypeEl.value : gameType;
const showGate = drawModeEl.value === 'quizBattlePathGate' && gt === 'quiz_battle';
gateWrap.style.display = showGate ? 'inline-flex' : 'none';
if (showGate && !editorQuizBattleMcqList.length) loadEditorQuizBattleMcqList();
}
}
const gateReloadBtn = document.getElementById('quiz-battle-gate-reload');
if (gateReloadBtn) {
gateReloadBtn.addEventListener('click', () => { loadEditorQuizBattleMcqList(); });
}
if (drawModeEl) drawModeEl.addEventListener('change', () => { syncDrawModeAuxUi(); syncLobbySpawnAuxUi(); });
syncDrawModeAuxUi();
@@ -4119,6 +4354,12 @@
: Array(height).fill(0).map(() => Array(width).fill(0));
quizBattleDomeArea = m.quizBattleDomeArea && m.quizBattleDomeArea.length ? m.quizBattleDomeArea.map(r => r && r.slice()) : Array(height).fill(0).map(() => Array(width).fill(0));
quizBattlePathArea = m.quizBattlePathArea && m.quizBattlePathArea.length ? m.quizBattlePathArea.map(r => r && r.slice()) : Array(height).fill(0).map(() => Array(width).fill(0));
quizBattlePathGateArea = m.quizBattlePathGateArea && m.quizBattlePathGateArea.length ? m.quizBattlePathGateArea.map(r => r && r.slice()) : Array(height).fill(0).map(() => Array(width).fill(0));
editorQuizBattleCategoryId = m.quizBattleCategoryId
? String(m.quizBattleCategoryId).trim()
: inferQuizBattleCategoryFromMapId(mapId);
rebuildEditorQuizBattleDomeComp();
if (m.gameType === 'quiz_battle') loadEditorQuizBattleMcqList();
jumpSurvivePlatforms = Array.isArray(m.jumpSurvivePlatforms) ? m.jumpSurvivePlatforms.map((p) => (p && typeof p === 'object' ? { ...p } : null)).filter(Boolean) : [];
jumpSurvivePlatformArea = m.jumpSurvivePlatformArea && m.jumpSurvivePlatformArea.length ? m.jumpSurvivePlatformArea.map((r) => r && r.slice()) : Array(height).fill(0).map(() => Array(width).fill(0));
jumpSurvivePlatformVariantArea = m.jumpSurvivePlatformVariantArea && m.jumpSurvivePlatformVariantArea.length
+264 -8
View File
@@ -12541,6 +12541,213 @@
md.quizBattleDomeComp = comp;
}
function hasQuizBattleManualPathGatesPlay(md) {
const mg = md && md.quizBattlePathGateArea;
if (!mg || !Array.isArray(mg)) return false;
for (let ty = 0; ty < mg.length; ty++) {
const row = mg[ty];
if (!row) continue;
for (let tx = 0; tx < row.length; tx++) {
if ((row[tx] | 0) > 0) return true;
}
}
return false;
}
function buildQuizBattlePathGateFromManualPlay(md) {
const w = md.width || 20, h = md.height || 15;
const path = md.quizBattlePathArea;
const dome = md.quizBattleDomeArea;
const manual = md.quizBattlePathGateArea;
const gate = Array(h).fill(0).map(() => Array(w).fill(0));
const bestReq = Array(h).fill(0).map(() => Array(w).fill(-1));
const queue = [];
function enqueue(tx, ty, req) {
if (tx < 0 || ty < 0 || tx >= w || ty >= h) return;
const onPath = path[ty] && path[ty][tx] === 1;
const onDome = dome && dome[ty] && dome[ty][tx] === 1;
if (!onPath && !onDome) return;
if (bestReq[ty][tx] >= 0 && req >= bestReq[ty][tx]) return;
bestReq[ty][tx] = req;
queue.push({ tx, ty, req });
}
const sp = md.spawn || { x: 1, y: 1 };
const stx = Math.max(0, Math.min(w - 1, Math.floor(Number(sp.x)) || 0));
const sty = Math.max(0, Math.min(h - 1, Math.floor(Number(sp.y)) || 0));
enqueue(stx, sty, 0);
if (bestReq[sty][stx] < 0) {
for (const [dx, dy] of [[0, 0], [1, 0], [-1, 0], [0, 1], [0, -1]]) enqueue(stx + dx, sty + dy, 0);
}
const sa = md.spawnArea;
if (sa && Array.isArray(sa)) {
for (let ty = 0; ty < h; ty++) {
const row = sa[ty];
if (!row) continue;
for (let tx = 0; tx < w; tx++) {
if (row[tx] === 1) enqueue(tx, ty, 0);
}
}
}
while (queue.length) {
const cur = queue.shift();
const { tx, ty, req } = cur;
if (bestReq[ty][tx] !== req) continue;
const onDome = dome && dome[ty] && dome[ty][tx] === 1;
let enterReq = req;
const mg = manual && manual[ty] && manual[ty][tx] ? (manual[ty][tx] | 0) : 0;
if (onDome) enterReq = 0;
else if (mg > 0) enterReq = Math.max(enterReq, mg);
if (path[ty] && path[ty][tx] === 1) gate[ty][tx] = enterReq;
let outReq = enterReq;
if (mg > 0) outReq = Math.max(outReq, mg);
for (const [dx, dy] of [[1, 0], [-1, 0], [0, 1], [0, -1]]) {
enqueue(tx + dx, ty + dy, outReq);
}
}
md.quizBattlePathGateRequired = gate;
}
/** ประตูบนเลน — เลนหลังโดม C ต้องตอบถูกโดม 1..C ก่อน (ยืนบนโดมได้โดยไม่ต้องตอบโดมนั้น) */
function buildQuizBattlePathGateRequiredPlay(md) {
if (!md || md.gameType !== 'quiz_battle' || !quizBattlePathModeActive(md)) {
if (md) md.quizBattlePathGateRequired = null;
return;
}
if (hasQuizBattleManualPathGatesPlay(md)) {
buildQuizBattlePathGateFromManualPlay(md);
return;
}
normalizeQuizBattleDomeInPlay(md);
const w = md.width || 20, h = md.height || 15;
const path = md.quizBattlePathArea;
const dome = md.quizBattleDomeArea;
const domeComp = md.quizBattleDomeComp;
const gate = Array(h).fill(0).map(() => Array(w).fill(0));
const dist = Array(h).fill(0).map(() => Array(w).fill(-1));
const queue = [];
const sp = md.spawn || { x: 1, y: 1 };
const stx = Math.max(0, Math.min(w - 1, Math.floor(Number(sp.x)) || 0));
const sty = Math.max(0, Math.min(h - 1, Math.floor(Number(sp.y)) || 0));
function tryEnqueue(tx, ty, d) {
if (tx < 0 || ty < 0 || tx >= w || ty >= h) return;
if (dist[ty][tx] >= 0) return;
const onPath = path[ty] && path[ty][tx] === 1;
const onDome = dome && dome[ty] && dome[ty][tx] === 1;
if (!onPath && !onDome) return;
dist[ty][tx] = d;
queue.push({ tx, ty, d });
}
tryEnqueue(stx, sty, 0);
if (dist[sty][stx] < 0) {
for (const [dx, dy] of [[0, 0], [1, 0], [-1, 0], [0, 1], [0, -1]]) tryEnqueue(stx + dx, sty + dy, 0);
}
const sa = md.spawnArea;
if (sa && Array.isArray(sa)) {
for (let ty = 0; ty < h; ty++) {
const row = sa[ty];
if (!row) continue;
for (let tx = 0; tx < w; tx++) {
if (row[tx] === 1) tryEnqueue(tx, ty, 0);
}
}
}
const domeMinDist = {};
let maxComp = 0;
while (queue.length) {
const cur = queue.shift();
const { tx, ty, d } = cur;
const dc = domeComp[ty] && domeComp[ty][tx] ? domeComp[ty][tx] : 0;
if (dc > 0) {
maxComp = Math.max(maxComp, dc);
if (domeMinDist[dc] == null || d < domeMinDist[dc]) domeMinDist[dc] = d;
}
for (const [dx, dy] of [[1, 0], [-1, 0], [0, 1], [0, -1]]) {
const nx = tx + dx, ny = ty + dy;
if (nx < 0 || ny < 0 || nx >= w || ny >= h) continue;
if (dist[ny][nx] >= 0) continue;
const onPath = path[ny] && path[ny][nx] === 1;
const onDome = dome && dome[ny] && dome[ny][nx] === 1;
if (onPath || onDome) tryEnqueue(nx, ny, d + 1);
}
}
for (let ty = 0; ty < h; ty++) {
for (let tx = 0; tx < w; tx++) {
if (!path[ty] || path[ty][tx] !== 1) continue;
const d = dist[ty][tx];
if (d < 0) {
gate[ty][tx] = maxComp;
continue;
}
let req = 0;
for (let c = 1; c <= maxComp; c++) {
if (domeMinDist[c] != null && domeMinDist[c] < d) req = Math.max(req, c);
}
gate[ty][tx] = req;
}
}
md.quizBattlePathGateRequired = gate;
}
function quizBattlePathGateRequiredAtPlay(md, px, py) {
const gate = md && md.quizBattlePathGateRequired;
if (!gate || !quizBattlePathModeActive(md)) return 0;
const path = md.quizBattlePathArea;
const tiles = quizBattleSpriteBoundsTilesPlay(md, px, py);
let maxReq = 0;
for (const k of tiles) {
const p = k.split(',');
const tx = +p[0], ty = +p[1];
if (!path[ty] || path[ty][tx] !== 1) continue;
maxReq = Math.max(maxReq, gate[ty][tx] || 0);
}
return maxReq;
}
function quizBattleFirstMissingGateCompPlay(md, px, py, solvedSet) {
const maxReq = quizBattlePathGateRequiredAtPlay(md, px, py);
if (maxReq <= 0) return 0;
for (let c = 1; c <= maxReq; c++) {
if (!solvedSet.has(c)) return c;
}
return 0;
}
function quizBattlePathGateAllowsPlay(md, px, py, solvedSet) {
if (quizBattleFootprintOnDomePlay(md, px, py)) return true;
return quizBattleFirstMissingGateCompPlay(md, px, py, solvedSet) === 0;
}
let quizBattleGateFlashAt = 0;
function quizBattleGateQuestionSnippetPlay(compId) {
if (!(compId > 0) || !quizBattleMcqPool.length) return '';
const q = quizBattleMcqPool[(compId - 1) % quizBattleMcqPool.length];
if (!q || !q.text) return '';
const t = String(q.text).trim();
return t.length > 42 ? t.slice(0, 42) + '…' : t;
}
function quizBattleFlashGateBlocked(md, px, py, solvedSet) {
const need = quizBattleFirstMissingGateCompPlay(md, px, py, solvedSet);
if (!need) return;
const now = performance.now();
if (now - quizBattleGateFlashAt < 1400) return;
quizBattleGateFlashAt = now;
const snip = quizBattleGateQuestionSnippetPlay(need);
flashQuizBattleFeedback(
snip
? ('ตอบข้อ ' + need + ' ก่อนผ่าน: 「' + snip + '」')
: ('ตอบคำถามข้อ ' + need + ' ก่อนผ่านทางนี้'),
false
);
}
/** เส้นทาง Quiz Battle — วาดอย่างน้อย 1 ช่องแล้วจะเดินได้เฉพาะบนเส้นทาง (เปลี่ยนแค่รูปพื้นหลังได้) */
function normalizeQuizBattlePathInPlay(md) {
if (!md || md.gameType !== 'quiz_battle') return;
@@ -12767,6 +12974,41 @@
return Math.min.apply(null, arr);
}
function quizBattleFootprintOnDomePlay(md, px, py) {
const dome = md && md.quizBattleDomeArea;
if (!dome) return false;
for (const k of quizTilesFootprintPlay(px, py)) {
const p = k.split(',');
const tx = +p[0], ty = +p[1];
if (dome[ty] && dome[ty][tx] === 1) return true;
}
return false;
}
/** โดมใต้เท้า หรือช่องติดกัน (8 ทิศ) — ให้กด E / เด้งคำถามได้แม้ยืนข้างสถานี */
function getNearestQuizBattleDomeCompAt(px, py) {
const direct = getPrimaryQuizBattleDomeCompAt(px, py);
if (direct != null) return direct;
if (!mapData || !isQuizBattle()) return null;
const dome = mapData.quizBattleDomeArea;
const comp = mapData.quizBattleDomeComp;
if (!dome) return null;
const w = mapData.width || 20, h = mapData.height || 15;
let best = null;
for (const k of quizTilesFootprintPlay(px, py)) {
const p = k.split(',');
const tx = +p[0], ty = +p[1];
for (const [dx, dy] of [[0, 1], [0, -1], [1, 0], [-1, 0], [1, 1], [-1, 1], [1, -1], [-1, -1]]) {
const nx = tx + dx, ny = ty + dy;
if (nx < 0 || ny < 0 || nx >= w || ny >= h) continue;
if (!dome[ny] || dome[ny][nx] !== 1) continue;
const cid = comp && comp[ny] && comp[ny][nx] ? comp[ny][nx] : 1;
if (best == null || cid < best) best = cid;
}
}
return best;
}
function showQuizBattleMcqModal(q) {
const ov = document.getElementById('quiz-battle-mcq-overlay');
const textEl = document.getElementById('quiz-battle-mcq-text');
@@ -12799,12 +13041,15 @@
const ok = Number(q.correctIndex) === Number(idx);
if (ok) {
quizBattleAnsweredComps.add(compId);
if (typeof socket !== 'undefined' && socket && socket.connected) {
try { socket.emit('quizbattle-solve', { compId: compId }, function () {}); } catch (e) { /* ignore */ }
}
if (myId != null) {
if (!playLiveQuizScores) playLiveQuizScores = {};
playLiveQuizScores[myId] = (playLiveQuizScores[myId] || 0) + 1;
renderPlayQuizScoreboard(playLiveQuizScores);
}
flashQuizBattleFeedback('ถูกต้อง · +1', true);
flashQuizBattleFeedback('ถูกต้อง · +1 · ทางไปข้างหน้าเปิดแล้ว', true);
} else {
flashQuizBattleFeedback('ยังไม่ถูก — ลองใหม่ (กด E อีกครั้ง)', false);
}
@@ -12812,15 +13057,15 @@
hideQuizBattleMcqModal();
}
/** หา comp โดมที่ตัวละครเหยียบอยู่ — เช็คหลายจุด (กลาง/เท้า/หัว/ซ้าย-ขวา) ให้เด้งง่ายเมื่อถึงสถานี */
/** หา comp โดมที่ตัวละครเหยียบอยู่ — เช็คหลายจุด + ช่องติดโดม ให้เด้งง่ายเมื่อถึงสถานี */
function quizBattleDomeAtMe() {
if (!isQuizBattle()) return null;
const pts = [[me.x, me.y], [me.x, me.y + 0.45], [me.x, me.y - 0.25], [me.x + 0.3, me.y], [me.x - 0.3, me.y]];
const pts = [[me.x, me.y], [me.x, me.y + 0.45], [me.x, me.y - 0.25], [me.x + 0.35, me.y], [me.x - 0.35, me.y]];
for (let i = 0; i < pts.length; i++) {
const c = getPrimaryQuizBattleDomeCompAt(pts[i][0], pts[i][1]);
const c = getNearestQuizBattleDomeCompAt(pts[i][0], pts[i][1]);
if (c != null) return c;
}
return null;
return getNearestQuizBattleDomeCompAt(me.x, me.y);
}
/** ZEP-style: เหยียบสถานี (โดม) ที่ยังไม่ตอบ → เปิดคำถามอัตโนมัติ (ครั้งเดียวต่อการเข้า · เดินออกแล้วกลับมาใหม่ถึงเด้งอีก) */
@@ -12835,7 +13080,10 @@
qbAutoLastComp = compNow;
if (quizBattleAnsweredComps.has(compNow)) return;
const pool = quizBattleMcqPool;
if (!pool.length) return;
if (!pool.length) {
flashQuizBattleFeedback('ยังไม่มีคำถาม — บันทึกใน Admin → Quiz Battle แล้วรีเฟรช', false);
return;
}
const q = pool[(compNow - 1) % pool.length];
if (!q) return;
quizBattleModalCompId = compNow;
@@ -12894,9 +13142,9 @@
function tryOpenQuizBattleFromKey() {
if (!isQuizBattle() || !mapData || myId == null) return;
const compId = getPrimaryQuizBattleDomeCompAt(me.x, me.y);
const compId = quizBattleDomeAtMe();
if (compId == null) {
flashQuizBattleFeedback('ยังไม่ยืนบนโดมคำถาม', false);
flashQuizBattleFeedback('เข้าใกล้สถานีคำถาม (หีบ) แล้วกด E', false);
return;
}
if (quizBattleAnsweredComps.has(compId)) {
@@ -15797,6 +16045,7 @@
if (!mapData.quizBattlePathArea) mapData.quizBattlePathArea = [];
normalizeQuizBattlePathInPlay(mapData);
normalizeQuizBattleDomeInPlay(mapData);
buildQuizBattlePathGateRequiredPlay(mapData);
}
tileSize = mapData.tileSize || 32;
mapBackgroundImg = null;
@@ -17074,6 +17323,7 @@
if (!mapData.quizBattlePathArea) mapData.quizBattlePathArea = [];
normalizeQuizBattlePathInPlay(mapData);
normalizeQuizBattleDomeInPlay(mapData);
buildQuizBattlePathGateRequiredPlay(mapData);
if (quizBattlePathModeActive(mapData)) {
const ms = snapPositionOntoQuizBattlePathIfNeeded(me.x, me.y);
me.x = ms.x;
@@ -17559,6 +17809,10 @@
if (isQuizCarry() && quizCarryFootprintOverlapsHub(x, y)) return false;
if (isQuizBattle() && quizBattlePathModeActive(mapData)) {
if (!quizBattlePositionAllowedPlay(mapData, x, y)) return false;
if (!quizBattlePathGateAllowsPlay(mapData, x, y, quizBattleAnsweredComps)) {
quizBattleFlashGateBlocked(mapData, x, y, quizBattleAnsweredComps);
return false;
}
}
return true;
}
@@ -17607,6 +17861,8 @@
if (isQuizCarry() && quizCarryFootprintOverlapsHub(x, y)) return false;
if (isQuizBattle() && quizBattlePathModeActive(mapData)) {
if (!quizBattlePositionAllowedPlay(mapData, x, y)) return false;
if (o && isPreviewBotId(o.id || o.peerId)) { /* preview bots ไม่ล็อกประตู */ }
else if (!quizBattlePathGateAllowsPlay(mapData, x, y, quizBattleAnsweredComps)) return false;
}
return true;
}
+1 -1
View File
@@ -3974,7 +3974,7 @@
<script src="/app-base.js?v=2"></script>
<script src="/Game/socket.io/socket.io.js"></script>
<script src="js/version.js?v=0.0306"></script>
<script src="js/play.js?v=0.006071209"></script>
<script src="js/play.js?v=0.00607144417"></script>
<div class="version-tag">v —</div>
</body>
</html>
+248 -8
View File
@@ -3702,6 +3702,204 @@ function normalizeQuizBattleDomeAreaOnMap(m) {
m.quizBattleDomeArea = rows;
}
/** โดมติดกัน = 1 ข้อ (comp id 1,2,3…) — ตรงกับ play.js */
function normalizeQuizBattleDomeCompOnMap(m) {
if (!m || m.gameType !== 'quiz_battle') return;
normalizeQuizBattleDomeAreaOnMap(m);
const w = m.width || 20, h = m.height || 15;
const grid = m.quizBattleDomeArea;
const comp = Array(h).fill(0).map(() => Array(w).fill(0));
let nextId = 0;
for (let y = 0; y < h; y++) {
for (let x = 0; x < w; x++) {
if (!grid[y] || grid[y][x] !== 1 || comp[y][x]) continue;
nextId++;
const stack = [[x, y]];
comp[y][x] = nextId;
while (stack.length) {
const c = stack.pop();
const cx = c[0], cy = c[1];
for (let i = 0; i < 4; i++) {
const dx = (i === 0 ? 1 : i === 1 ? -1 : 0);
const dy = (i === 2 ? 1 : i === 3 ? -1 : 0);
const nx = cx + dx, ny = cy + dy;
if (nx < 0 || ny < 0 || nx >= w || ny >= h) continue;
if (!grid[ny] || grid[ny][nx] !== 1 || comp[ny][nx]) continue;
comp[ny][nx] = nextId;
stack.push([nx, ny]);
}
}
}
}
m.quizBattleDomeComp = comp;
}
/**
* ประตบนเลน องเลนทอย "หลัง" โดม C องตอบถกโดม 1..C อน
* นบนโดมไดโดยไมองตอบ (dist เทาก) · านจดหลงโดมตองตอบกอน
*/
function hasQuizBattleManualPathGates(m) {
const mg = m && m.quizBattlePathGateArea;
if (!mg || !Array.isArray(mg)) return false;
for (let ty = 0; ty < mg.length; ty++) {
const row = mg[ty];
if (!row) continue;
for (let tx = 0; tx < row.length; tx++) {
if ((row[tx] | 0) > 0) return true;
}
}
return false;
}
function buildQuizBattlePathGateFromManualOnMap(m) {
const w = m.width || 20, h = m.height || 15;
const path = m.quizBattlePathArea;
const dome = m.quizBattleDomeArea;
const manual = m.quizBattlePathGateArea;
const gate = Array(h).fill(0).map(() => Array(w).fill(0));
const bestReq = Array(h).fill(0).map(() => Array(w).fill(-1));
const queue = [];
function enqueue(tx, ty, req) {
if (tx < 0 || ty < 0 || tx >= w || ty >= h) return;
const onPath = path[ty] && path[ty][tx] === 1;
const onDome = dome && dome[ty] && dome[ty][tx] === 1;
if (!onPath && !onDome) return;
/* เก็บ req ต่ำสุดที่เข้าช่องได้ — กันเส้นทางวนกลับไปยก req ทับ spawn */
if (bestReq[ty][tx] >= 0 && req >= bestReq[ty][tx]) return;
bestReq[ty][tx] = req;
queue.push({ tx, ty, req });
}
const sp = m.spawn || { x: 1, y: 1 };
const stx = Math.max(0, Math.min(w - 1, Math.floor(Number(sp.x)) || 0));
const sty = Math.max(0, Math.min(h - 1, Math.floor(Number(sp.y)) || 0));
enqueue(stx, sty, 0);
if (bestReq[sty][stx] < 0) {
for (const [dx, dy] of [[0, 0], [1, 0], [-1, 0], [0, 1], [0, -1]]) enqueue(stx + dx, sty + dy, 0);
}
const sa = m.spawnArea;
if (sa && Array.isArray(sa)) {
for (let ty = 0; ty < h; ty++) {
const row = sa[ty];
if (!row) continue;
for (let tx = 0; tx < w; tx++) {
if (row[tx] === 1) enqueue(tx, ty, 0);
}
}
}
while (queue.length) {
const cur = queue.shift();
const { tx, ty, req } = cur;
if (bestReq[ty][tx] !== req) continue;
const onDome = dome && dome[ty] && dome[ty][tx] === 1;
let enterReq = req;
const mg = manual && manual[ty] && manual[ty][tx] ? (manual[ty][tx] | 0) : 0;
if (onDome) enterReq = 0;
else if (mg > 0) enterReq = Math.max(enterReq, mg);
if (path[ty] && path[ty][tx] === 1) gate[ty][tx] = enterReq;
let outReq = enterReq;
if (mg > 0) outReq = Math.max(outReq, mg);
for (const [dx, dy] of [[1, 0], [-1, 0], [0, 1], [0, -1]]) {
enqueue(tx + dx, ty + dy, outReq);
}
}
m.quizBattlePathGateRequired = gate;
}
function buildQuizBattlePathGateRequiredOnMap(m) {
if (!m || m.gameType !== 'quiz_battle' || !quizBattlePathModeActiveServer(m)) {
if (m) m.quizBattlePathGateRequired = null;
return;
}
if (hasQuizBattleManualPathGates(m)) {
buildQuizBattlePathGateFromManualOnMap(m);
return;
}
normalizeQuizBattleDomeCompOnMap(m);
const w = m.width || 20, h = m.height || 15;
const path = m.quizBattlePathArea;
const dome = m.quizBattleDomeArea;
const domeComp = m.quizBattleDomeComp;
const gate = Array(h).fill(0).map(() => Array(w).fill(0));
const dist = Array(h).fill(0).map(() => Array(w).fill(-1));
const queue = [];
const sp = m.spawn || { x: 1, y: 1 };
const stx = Math.max(0, Math.min(w - 1, Math.floor(Number(sp.x)) || 0));
const sty = Math.max(0, Math.min(h - 1, Math.floor(Number(sp.y)) || 0));
function tryEnqueue(tx, ty, d) {
if (tx < 0 || ty < 0 || tx >= w || ty >= h) return;
if (dist[ty][tx] >= 0) return;
const onPath = path[ty] && path[ty][tx] === 1;
const onDome = dome && dome[ty] && dome[ty][tx] === 1;
if (!onPath && !onDome) return;
dist[ty][tx] = d;
queue.push({ tx, ty, d });
}
tryEnqueue(stx, sty, 0);
if (dist[sty][stx] < 0) {
for (const [dx, dy] of [[0, 0], [1, 0], [-1, 0], [0, 1], [0, -1]]) tryEnqueue(stx + dx, sty + dy, 0);
}
const sa = m.spawnArea;
if (sa && Array.isArray(sa)) {
for (let ty = 0; ty < h; ty++) {
const row = sa[ty];
if (!row) continue;
for (let tx = 0; tx < w; tx++) {
if (row[tx] === 1) tryEnqueue(tx, ty, 0);
}
}
}
const domeMinDist = {};
let maxComp = 0;
while (queue.length) {
const cur = queue.shift();
const { tx, ty, d } = cur;
const dc = domeComp[ty] && domeComp[ty][tx] ? domeComp[ty][tx] : 0;
if (dc > 0) {
maxComp = Math.max(maxComp, dc);
if (domeMinDist[dc] == null || d < domeMinDist[dc]) domeMinDist[dc] = d;
}
for (const [dx, dy] of [[1, 0], [-1, 0], [0, 1], [0, -1]]) {
const nx = tx + dx, ny = ty + dy;
if (nx < 0 || ny < 0 || nx >= w || ny >= h) continue;
if (dist[ny][nx] >= 0) continue;
const onPath = path[ny] && path[ny][nx] === 1;
const onDome = dome && dome[ny] && dome[ny][nx] === 1;
if (onPath || onDome) tryEnqueue(nx, ny, d + 1);
}
}
for (let ty = 0; ty < h; ty++) {
for (let tx = 0; tx < w; tx++) {
if (!path[ty] || path[ty][tx] !== 1) continue;
const d = dist[ty][tx];
if (d < 0) {
gate[ty][tx] = maxComp;
continue;
}
let req = 0;
for (let c = 1; c <= maxComp; c++) {
if (domeMinDist[c] != null && domeMinDist[c] < d) req = Math.max(req, c);
}
gate[ty][tx] = req;
}
}
m.quizBattlePathGateRequired = gate;
}
function finalizeQuizBattleMapLayersOnMap(m) {
if (!m || m.gameType !== 'quiz_battle') return;
normalizeQuizBattleDomeAreaOnMap(m);
normalizeQuizBattlePathAreaOnMap(m);
normalizeQuizBattleDomeCompOnMap(m);
buildQuizBattlePathGateRequiredOnMap(m);
}
function normalizeQuizBattlePathAreaOnMap(m) {
if (!m || m.gameType !== 'quiz_battle') return;
const w = m.width || 20, h = m.height || 15;
@@ -3755,6 +3953,49 @@ function quizBattlePositionAllowedServer(m, px, py) {
return true;
}
function quizBattlePlayerSolvedCompsServer(space, socketId) {
if (!space || !space.qbwSolved) return new Set();
const set = space.qbwSolved.get(socketId);
return set || new Set();
}
function quizBattlePathGateRequiredAtServer(m, px, py) {
const gate = m && m.quizBattlePathGateRequired;
if (!gate || !quizBattlePathModeActiveServer(m)) return 0;
const keys = quizBattleSpriteBoundsTileKeys(m, px, py);
const path = m.quizBattlePathArea;
let maxReq = 0;
for (const k of keys) {
const [tx, ty] = k.split(',').map(Number);
if (!path[ty] || path[ty][tx] !== 1) continue;
maxReq = Math.max(maxReq, gate[ty][tx] || 0);
}
return maxReq;
}
function quizBattleFootprintOnDomeServer(m, px, py) {
const dome = m && m.quizBattleDomeArea;
if (!dome) return false;
const keys = quizBattleSpriteBoundsTileKeys(m, px, py);
for (const k of keys) {
const [tx, ty] = k.split(',').map(Number);
if (dome[ty] && dome[ty][tx] === 1) return true;
}
return false;
}
function quizBattlePathGateAllowsServer(m, space, socketId, px, py) {
if (!m || !quizBattlePathModeActiveServer(m)) return true;
if (quizBattleFootprintOnDomeServer(m, px, py)) return true;
const maxReq = quizBattlePathGateRequiredAtServer(m, px, py);
if (maxReq <= 0) return true;
const solved = quizBattlePlayerSolvedCompsServer(space, socketId);
for (let c = 1; c <= maxReq; c++) {
if (!solved.has(c)) return false;
}
return true;
}
function serverFootprintClearOfWalls(md, px, py) {
const w = md.width || 20, h = md.height || 15;
const keys = serverWallCollisionTileKeys(md, px, py);
@@ -4053,8 +4294,7 @@ function loadMaps() {
normalizeJumpSurvivePlatformAreaOnMap(data);
normalizeJumpSurvivePlatformVariantAreaOnMap(data);
normalizeJumpSurviveHazardAreaOnMap(data);
normalizeQuizBattleDomeAreaOnMap(data);
normalizeQuizBattlePathAreaOnMap(data);
finalizeQuizBattleMapLayersOnMap(data);
applyStackTowerMissionMapPlayPolicy(id, data);
maps.set(id, data);
} catch (e) { console.error('load map', f, e.message); }
@@ -4076,8 +4316,7 @@ function rehydrateSingleMapFromDisk(safeId) {
normalizeJumpSurvivePlatformAreaOnMap(data);
normalizeJumpSurvivePlatformVariantAreaOnMap(data);
normalizeJumpSurviveHazardAreaOnMap(data);
normalizeQuizBattleDomeAreaOnMap(data);
normalizeQuizBattlePathAreaOnMap(data);
finalizeQuizBattleMapLayersOnMap(data);
applyStackTowerMissionMapPlayPolicy(safeId, data);
maps.set(safeId, data);
return true;
@@ -4185,8 +4424,7 @@ const server = http.createServer((req, res) => {
normalizeJumpSurvivePlatformAreaOnMap(m);
normalizeJumpSurvivePlatformVariantAreaOnMap(m);
normalizeJumpSurviveHazardAreaOnMap(m);
normalizeQuizBattleDomeAreaOnMap(m);
normalizeQuizBattlePathAreaOnMap(m);
finalizeQuizBattleMapLayersOnMap(m);
maps.set(id, m);
saveMap(id, m);
res.writeHead(200, { 'Content-Type': 'application/json' });
@@ -4229,8 +4467,7 @@ const server = http.createServer((req, res) => {
normalizeJumpSurvivePlatformAreaOnMap(m);
normalizeJumpSurvivePlatformVariantAreaOnMap(m);
normalizeJumpSurviveHazardAreaOnMap(m);
normalizeQuizBattleDomeAreaOnMap(m);
normalizeQuizBattlePathAreaOnMap(m);
finalizeQuizBattleMapLayersOnMap(m);
applyStackTowerMissionMapPlayPolicy(id, m);
maps.set(id, m);
saveMap(id, m);
@@ -7514,6 +7751,9 @@ io.on('connection', (socket) => {
} else if (quizBattlePathModeActiveServer(md) && !quizBattlePositionAllowedServer(md, txN, tyN)) {
nx = p.x;
ny = p.y;
} else if (quizBattlePathModeActiveServer(md) && !quizBattlePathGateAllowsServer(md, space, socket.id, txN, tyN)) {
nx = p.x;
ny = p.y;
}
if (quizBattlePathModeActiveServer(md)) {
const sn = snapPositionOntoQuizBattlePathServer(md, Number(nx), Number(ny));
@@ -29,7 +29,124 @@
<img class="mg-host-face" src="" alt="">
</div>
<div class="layer mg-mic"><img src="../cut/btn-mic_on.png" alt="mic"></div>
<div class="layer mg4-time"><img class="mg-time-label" src="../cut/time.png" alt="TIME :"><span class="num">0</span></div><div class="layer sm-overlay"></div><div class="layer sm-popup"><img src="../cut/popup-result.png" alt=""></div><div class="layer sm-btn-done btn"><img src="../cut/btn-done.png" alt=""></div>
<div class="layer mg4-time"><img class="mg-time-label" src="../cut/time.png" alt="TIME :"><span class="num">0</span></div><div class="layer sm-overlay"></div><div class="layer sm-popup"><img src="../cut/popup-result.png" alt=""></div><div class="layer sm-content">
<div class="sm-ranks">
<div class="sm-rank is-1st" style="--bg:#ffd96b; --hair:#3a2210; --body:#e09f2a; --skin:#f5d6b3;">
<img class="pos" src="../cut/result-1st.png" alt="">
<div class="av">
<div class="portrait">
<div class="chibi">
<div class="ch-body"></div>
<div class="ch-head"></div>
<div class="ch-hair"></div>
</div>
</div>
<img class="frame" src="../cut/player-frame.png" alt="">
</div>
<div class="nm">JACKSON</div>
<div class="sc">100</div>
</div>
<div class="sm-rank is-2nd" style="--bg:#f5b9c8; --hair:#ec5fa0; --body:#ffffff; --skin:#f7d8c2;">
<img class="pos" src="../cut/result-2nd.png" alt="">
<div class="av">
<div class="portrait">
<div class="chibi">
<div class="ch-body"></div>
<div class="ch-head"></div>
<div class="ch-hair"></div>
</div>
</div>
<img class="frame" src="../cut/player-frame.png" alt="">
</div>
<div class="nm">MONE</div>
<div class="sc">100</div>
</div>
<div class="sm-rank is-3rd" style="--bg:#ff7b7b; --hair:#c8252a; --body:#9b1f23; --skin:#f5cba7;">
<img class="pos" src="../cut/result-3rd.png" alt="">
<div class="av">
<div class="portrait">
<div class="chibi">
<div class="ch-body"></div>
<div class="ch-head"></div>
<div class="ch-hair"></div>
</div>
</div>
<img class="frame" src="../cut/player-frame.png" alt="">
</div>
<div class="nm">MICKY</div>
<div class="sc">100</div>
</div>
<div class="sm-rank is-4th" style="--bg:#7e84d1; --hair:#2a3580; --body:#3848a8; --skin:#f5d6b3;">
<img class="pos" src="../cut/result-txt-1.png" alt="">
<div class="av">
<div class="portrait">
<div class="chibi">
<div class="ch-body"></div>
<div class="ch-head"></div>
<div class="ch-hair"></div>
</div>
</div>
<img class="frame" src="../cut/player-frame.png" alt="">
</div>
<div class="nm">RAINY</div>
<div class="sc">90</div>
</div>
<div class="sm-rank is-5th" style="--bg:#85d28a; --hair:#216a2c; --body:#2f8a4a; --skin:#f5d6b3;">
<img class="pos" src="../cut/result-txt-2.png" alt="">
<div class="av">
<div class="portrait">
<div class="chibi">
<div class="ch-body"></div>
<div class="ch-head"></div>
<div class="ch-hair"></div>
</div>
</div>
<img class="frame" src="../cut/player-frame.png" alt="">
</div>
<div class="nm">JAME</div>
<div class="sc">90</div>
</div>
</div>
<img class="sm-line sm-line-1 sm-line-above" src="../cut/popup-result-Line1.png" alt="">
<div class="sm-total">
คะแนนรวม (100+100+100+90+90) =<span class="num">480</span>
</div>
<img class="sm-line sm-line-1 sm-line-below" src="../cut/popup-result-Line1.png" alt="">
<div class="sm-bottom">
<section class="sm-col sm-col-grade">
<h3 class="head">ระดับความสำเร็จ</h3>
<p class="sm-grade" aria-label="เกรด A">A</p>
</section>
<div class="sm-vrule" aria-hidden="true">
<img class="sm-line-2" src="../cut/popup-result-Line2.png" alt="">
</div>
<section class="sm-col sm-col-bonus">
<div class="sm-bonus">
<div class="sm-bonus-left">
<h3 class="head">โบนัสพิเศษ</h3>
<img class="complete" src="../cut/mission-complete-select.png" alt="ภารกิจสำเร็จ">
</div>
<div class="sm-bonus-right">
<img class="bail-card" src="../../cut/10-Game-1-result/card-5.png" alt="Bail Coin">
</div>
</div>
</section>
</div>
</div>
<div class="layer sm-btn-done btn"><img src="../cut/btn-done.png" alt=""></div>
</div>
</div>
@@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<title>08 · สรุปผลภารกิจ</title>
<meta name="viewport" content="width=1920, initial-scale=1">
<link rel="stylesheet" href="../assets/css/base.css">
<link rel="stylesheet" href="../assets/css/game-hud.css">
<link rel="stylesheet" href="../assets/css/game.css">
<link rel="stylesheet" href="../assets/css/summary.css">
</head>
<body>
<div class="stage-wrap">
<div class="stage mg-stage ">
<div class="layer mg-bg"><img src="../cut/BG.png" alt="bg"></div>
<div class="layer mg2-score-bar">
<img class="mg2-score-label" src="../cut/score.png" alt="SCORE :">
<div class="mg2-players">
<div class="mg2-player"><div class="nm">Mone</div><div class="av"></div><div class="sc">100</div></div>
<div class="mg2-player"><div class="nm">Jackson</div><div class="av"></div><div class="sc">100</div></div>
<div class="mg2-player"><div class="nm">Micky</div><div class="av"></div><div class="sc">100</div></div>
<div class="mg2-player"><div class="nm">Rainy</div><div class="av"></div><div class="sc">100</div></div>
<div class="mg2-player"><div class="nm">Jame</div><div class="av"></div><div class="sc">100</div></div>
</div>
</div>
<div class="layer mg-host">
<img class="mg-host-frame" src="../cut/Avartar.png" alt="">
<img class="mg-host-face" src="" alt="">
</div>
<div class="layer mg-mic"><img src="../cut/btn-mic_on.png" alt="mic"></div>
<div class="layer mg4-time"><img class="mg-time-label" src="../cut/time.png" alt="TIME :"><span class="num">0</span></div><div class="layer sm-overlay"></div><div class="layer sm-popup"><img src="../cut/popup-result.png" alt=""></div><div class="layer sm-btn-done btn"><img src="../cut/btn-done.png" alt=""></div>
</div>
</div>
<script src="../assets/js/scale.js"></script>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 484 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 481 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 507 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 473 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 485 B

@@ -26,7 +26,124 @@
<div class="layer mg-mic"><img src="../cut/btn-mic_on.png" alt="mic"></div>
<div class="layer mg5-joy" data-joystick><img class="joy-base" src="../cut/btn-joystick-1.png" alt=""><img class="joy-thumb" src="../cut/btn-joystick-2.png" alt=""></div>
<div class="layer mg5-jump btn"><img src="../cut/btn-jump.png" alt="JUMP"></div>
<div class="layer sm-overlay"></div><div class="layer sm-popup"><img src="../cut/popup-result.png" alt=""></div><div class="layer sm-btn-done btn"><img src="../cut/btn-done.png" alt=""></div>
<div class="layer sm-overlay"></div><div class="layer sm-popup"><img src="../cut/popup-result.png" alt=""></div><div class="layer sm-content">
<div class="sm-ranks">
<div class="sm-rank is-1st" style="--bg:#ffd96b; --hair:#3a2210; --body:#e09f2a; --skin:#f5d6b3;">
<img class="pos" src="../cut/result-1st.png" alt="">
<div class="av">
<div class="portrait">
<div class="chibi">
<div class="ch-body"></div>
<div class="ch-head"></div>
<div class="ch-hair"></div>
</div>
</div>
<img class="frame" src="../cut/player-frame.png" alt="">
</div>
<div class="nm">JACKSON</div>
<div class="sc">100</div>
</div>
<div class="sm-rank is-2nd" style="--bg:#f5b9c8; --hair:#ec5fa0; --body:#ffffff; --skin:#f7d8c2;">
<img class="pos" src="../cut/result-2nd.png" alt="">
<div class="av">
<div class="portrait">
<div class="chibi">
<div class="ch-body"></div>
<div class="ch-head"></div>
<div class="ch-hair"></div>
</div>
</div>
<img class="frame" src="../cut/player-frame.png" alt="">
</div>
<div class="nm">MONE</div>
<div class="sc">100</div>
</div>
<div class="sm-rank is-3rd" style="--bg:#ff7b7b; --hair:#c8252a; --body:#9b1f23; --skin:#f5cba7;">
<img class="pos" src="../cut/result-3rd.png" alt="">
<div class="av">
<div class="portrait">
<div class="chibi">
<div class="ch-body"></div>
<div class="ch-head"></div>
<div class="ch-hair"></div>
</div>
</div>
<img class="frame" src="../cut/player-frame.png" alt="">
</div>
<div class="nm">MICKY</div>
<div class="sc">100</div>
</div>
<div class="sm-rank is-4th" style="--bg:#7e84d1; --hair:#2a3580; --body:#3848a8; --skin:#f5d6b3;">
<img class="pos" src="../cut/result-txt-1.png" alt="">
<div class="av">
<div class="portrait">
<div class="chibi">
<div class="ch-body"></div>
<div class="ch-head"></div>
<div class="ch-hair"></div>
</div>
</div>
<img class="frame" src="../cut/player-frame.png" alt="">
</div>
<div class="nm">RAINY</div>
<div class="sc">90</div>
</div>
<div class="sm-rank is-5th" style="--bg:#85d28a; --hair:#216a2c; --body:#2f8a4a; --skin:#f5d6b3;">
<img class="pos" src="../cut/result-txt-2.png" alt="">
<div class="av">
<div class="portrait">
<div class="chibi">
<div class="ch-body"></div>
<div class="ch-head"></div>
<div class="ch-hair"></div>
</div>
</div>
<img class="frame" src="../cut/player-frame.png" alt="">
</div>
<div class="nm">JAME</div>
<div class="sc">90</div>
</div>
</div>
<img class="sm-line sm-line-1 sm-line-above" src="../cut/popup-result-Line1.png" alt="">
<div class="sm-total">
คะแนนรวม (100+100+100+90+90) =<span class="num">480</span>
</div>
<img class="sm-line sm-line-1 sm-line-below" src="../cut/popup-result-Line1.png" alt="">
<div class="sm-bottom">
<section class="sm-col sm-col-grade">
<h3 class="head">ระดับความสำเร็จ</h3>
<p class="sm-grade" aria-label="เกรด A">A</p>
</section>
<div class="sm-vrule" aria-hidden="true">
<img class="sm-line-2" src="../cut/popup-result-Line2.png" alt="">
</div>
<section class="sm-col sm-col-bonus">
<div class="sm-bonus">
<div class="sm-bonus-left">
<h3 class="head">โบนัสพิเศษ</h3>
<img class="complete" src="../cut/mission-complete-select.png" alt="ภารกิจสำเร็จ">
</div>
<div class="sm-bonus-right">
<img class="bail-card" src="../../cut/10-Game-1-result/card-5.png" alt="Bail Coin">
</div>
</div>
</section>
</div>
</div>
<div class="layer sm-btn-done btn"><img src="../cut/btn-done.png" alt=""></div>
</div>
</div>
<script src="../assets/js/scale.js"></script>
@@ -0,0 +1,36 @@
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<title>08 · สรุปผลภารกิจ</title>
<meta name="viewport" content="width=1920, initial-scale=1">
<link rel="stylesheet" href="../assets/css/base.css">
<link rel="stylesheet" href="../assets/css/game-hud.css">
<link rel="stylesheet" href="../assets/css/game.css">
<link rel="stylesheet" href="../assets/css/summary.css">
</head>
<body>
<div class="stage-wrap">
<div class="stage mg-stage ">
<div class="layer mg-bg"><img src="../cut/BG.png" alt="bg"></div>
<div class="layer mg-time mg5-time">
<img class="mg-time-label" src="../cut/time.png" alt="TIME :">
<span class="num">0</span>
</div>
<div class="layer mg-host">
<img class="mg-host-frame" src="../cut/Avartar.png" alt="">
<img class="mg-host-face" src="" alt="">
</div>
<div class="layer mg-mic"><img src="../cut/btn-mic_on.png" alt="mic"></div>
<div class="layer mg5-joy" data-joystick><img class="joy-base" src="../cut/btn-joystick-1.png" alt=""><img class="joy-thumb" src="../cut/btn-joystick-2.png" alt=""></div>
<div class="layer mg5-jump btn"><img src="../cut/btn-jump.png" alt="JUMP"></div>
<div class="layer sm-overlay"></div><div class="layer sm-popup"><img src="../cut/popup-result.png" alt=""></div><div class="layer sm-btn-done btn"><img src="../cut/btn-done.png" alt=""></div>
</div>
</div>
<script src="../assets/js/scale.js"></script>
<script src="../assets/js/joystick.js"></script>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 484 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 481 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 507 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 473 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 485 B

@@ -34,7 +34,124 @@
<img class="mg-host-face" src="" alt="">
</div>
<div class="layer mg-mic"><img src="../cut/btn-mic_on.png" alt="mic"></div>
<div class="layer sm-overlay"></div><div class="layer sm-popup"><img src="../cut/popup-result.png" alt=""></div><div class="layer sm-btn-done btn"><img src="../cut/btn-done.png" alt=""></div>
<div class="layer sm-overlay"></div><div class="layer sm-popup"><img src="../cut/popup-result.png" alt=""></div><div class="layer sm-content">
<div class="sm-ranks">
<div class="sm-rank is-1st" style="--bg:#ffd96b; --hair:#3a2210; --body:#e09f2a; --skin:#f5d6b3;">
<img class="pos" src="../cut/result-1st.png" alt="">
<div class="av">
<div class="portrait">
<div class="chibi">
<div class="ch-body"></div>
<div class="ch-head"></div>
<div class="ch-hair"></div>
</div>
</div>
<img class="frame" src="../cut/player-frame.png" alt="">
</div>
<div class="nm">JACKSON</div>
<div class="sc">100</div>
</div>
<div class="sm-rank is-2nd" style="--bg:#f5b9c8; --hair:#ec5fa0; --body:#ffffff; --skin:#f7d8c2;">
<img class="pos" src="../cut/result-2nd.png" alt="">
<div class="av">
<div class="portrait">
<div class="chibi">
<div class="ch-body"></div>
<div class="ch-head"></div>
<div class="ch-hair"></div>
</div>
</div>
<img class="frame" src="../cut/player-frame.png" alt="">
</div>
<div class="nm">MONE</div>
<div class="sc">100</div>
</div>
<div class="sm-rank is-3rd" style="--bg:#ff7b7b; --hair:#c8252a; --body:#9b1f23; --skin:#f5cba7;">
<img class="pos" src="../cut/result-3rd.png" alt="">
<div class="av">
<div class="portrait">
<div class="chibi">
<div class="ch-body"></div>
<div class="ch-head"></div>
<div class="ch-hair"></div>
</div>
</div>
<img class="frame" src="../cut/player-frame.png" alt="">
</div>
<div class="nm">MICKY</div>
<div class="sc">100</div>
</div>
<div class="sm-rank is-4th" style="--bg:#7e84d1; --hair:#2a3580; --body:#3848a8; --skin:#f5d6b3;">
<img class="pos" src="../cut/result-txt-1.png" alt="">
<div class="av">
<div class="portrait">
<div class="chibi">
<div class="ch-body"></div>
<div class="ch-head"></div>
<div class="ch-hair"></div>
</div>
</div>
<img class="frame" src="../cut/player-frame.png" alt="">
</div>
<div class="nm">RAINY</div>
<div class="sc">90</div>
</div>
<div class="sm-rank is-5th" style="--bg:#85d28a; --hair:#216a2c; --body:#2f8a4a; --skin:#f5d6b3;">
<img class="pos" src="../cut/result-txt-2.png" alt="">
<div class="av">
<div class="portrait">
<div class="chibi">
<div class="ch-body"></div>
<div class="ch-head"></div>
<div class="ch-hair"></div>
</div>
</div>
<img class="frame" src="../cut/player-frame.png" alt="">
</div>
<div class="nm">JAME</div>
<div class="sc">90</div>
</div>
</div>
<img class="sm-line sm-line-1 sm-line-above" src="../cut/popup-result-Line1.png" alt="">
<div class="sm-total">
คะแนนรวม (100+100+100+90+90) =<span class="num">480</span>
</div>
<img class="sm-line sm-line-1 sm-line-below" src="../cut/popup-result-Line1.png" alt="">
<div class="sm-bottom">
<section class="sm-col sm-col-grade">
<h3 class="head">ระดับความสำเร็จ</h3>
<p class="sm-grade" aria-label="เกรด A">A</p>
</section>
<div class="sm-vrule" aria-hidden="true">
<img class="sm-line-2" src="../cut/popup-result-Line2.png" alt="">
</div>
<section class="sm-col sm-col-bonus">
<div class="sm-bonus">
<div class="sm-bonus-left">
<h3 class="head">โบนัสพิเศษ</h3>
<img class="complete" src="../cut/mission-complete-select.png" alt="ภารกิจสำเร็จ">
</div>
<div class="sm-bonus-right">
<img class="bail-card" src="../../cut/10-Game-1-result/card-5.png" alt="Bail Coin">
</div>
</div>
</section>
</div>
</div>
<div class="layer sm-btn-done btn"><img src="../cut/btn-done.png" alt=""></div>
</div>
</div>
<script src="../assets/js/scale.js"></script>
@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<title>08 · สรุปผลภารกิจ</title>
<meta name="viewport" content="width=1920, initial-scale=1">
<link rel="stylesheet" href="../assets/css/base.css">
<link rel="stylesheet" href="../assets/css/game-hud.css">
<link rel="stylesheet" href="../assets/css/game.css">
<link rel="stylesheet" href="../assets/css/summary.css">
</head>
<body>
<div class="stage-wrap">
<div class="stage mg-stage ">
<div class="layer mg-bg"><img src="../cut/BG-2_Loop.png" alt="bg"></div>
<div class="layer mg-score-panel">
<div class="mg-score-title">SCORE</div>
<div class="mg-score-list">
<div class="mg-score-row"><div class="av"></div><div class="meta"><span class="nm">MONE</span><span class="sc">20</span></div></div>
<div class="mg-score-row"><div class="av"></div><div class="meta"><span class="nm">JACKSON</span><span class="sc">20</span></div></div>
<div class="mg-score-row"><div class="av"></div><div class="meta"><span class="nm">MICKY</span><span class="sc">20</span></div></div>
<div class="mg-score-row"><div class="av"></div><div class="meta"><span class="nm">RAINY</span><span class="sc">20</span></div></div>
<div class="mg-score-row"><div class="av"></div><div class="meta"><span class="nm">JAME</span><span class="sc">20</span></div></div>
</div>
</div>
<div class="layer mg-time">
<img class="mg-time-label" src="../cut/time.png" alt="TIME :">
<span class="num">0</span>
</div>
<div class="layer mg-host">
<img class="mg-host-frame" src="../cut/Avartar.png" alt="">
<img class="mg-host-face" src="" alt="">
</div>
<div class="layer mg-mic"><img src="../cut/btn-mic_on.png" alt="mic"></div>
<div class="layer sm-overlay"></div><div class="layer sm-popup"><img src="../cut/popup-result.png" alt=""></div><div class="layer sm-btn-done btn"><img src="../cut/btn-done.png" alt=""></div>
</div>
</div>
<script src="../assets/js/scale.js"></script>
</body>
</html>
@@ -30,7 +30,124 @@
<img class="mg-host-face" src="" alt="">
</div>
<div class="layer mg-mic"><img src="../cut/btn-mic_on.png" alt="mic"></div>
<div class="layer sm-overlay"></div><div class="layer sm-popup"><img src="../cut/popup-result.png" alt=""></div><div class="layer sm-btn-done btn"><img src="../cut/btn-done.png" alt=""></div>
<div class="layer sm-overlay"></div><div class="layer sm-popup"><img src="../cut/popup-result.png" alt=""></div><div class="layer sm-content">
<div class="sm-ranks">
<div class="sm-rank is-1st" style="--bg:#ffd96b; --hair:#3a2210; --body:#e09f2a; --skin:#f5d6b3;">
<img class="pos" src="../cut/result-1st.png" alt="">
<div class="av">
<div class="portrait">
<div class="chibi">
<div class="ch-body"></div>
<div class="ch-head"></div>
<div class="ch-hair"></div>
</div>
</div>
<img class="frame" src="../cut/player-frame.png" alt="">
</div>
<div class="nm">JACKSON</div>
<div class="sc">100</div>
</div>
<div class="sm-rank is-2nd" style="--bg:#f5b9c8; --hair:#ec5fa0; --body:#ffffff; --skin:#f7d8c2;">
<img class="pos" src="../cut/result-2nd.png" alt="">
<div class="av">
<div class="portrait">
<div class="chibi">
<div class="ch-body"></div>
<div class="ch-head"></div>
<div class="ch-hair"></div>
</div>
</div>
<img class="frame" src="../cut/player-frame.png" alt="">
</div>
<div class="nm">MONE</div>
<div class="sc">100</div>
</div>
<div class="sm-rank is-3rd" style="--bg:#ff7b7b; --hair:#c8252a; --body:#9b1f23; --skin:#f5cba7;">
<img class="pos" src="../cut/result-3rd.png" alt="">
<div class="av">
<div class="portrait">
<div class="chibi">
<div class="ch-body"></div>
<div class="ch-head"></div>
<div class="ch-hair"></div>
</div>
</div>
<img class="frame" src="../cut/player-frame.png" alt="">
</div>
<div class="nm">MICKY</div>
<div class="sc">100</div>
</div>
<div class="sm-rank is-4th" style="--bg:#7e84d1; --hair:#2a3580; --body:#3848a8; --skin:#f5d6b3;">
<img class="pos" src="../cut/result-txt-1.png" alt="">
<div class="av">
<div class="portrait">
<div class="chibi">
<div class="ch-body"></div>
<div class="ch-head"></div>
<div class="ch-hair"></div>
</div>
</div>
<img class="frame" src="../cut/player-frame.png" alt="">
</div>
<div class="nm">RAINY</div>
<div class="sc">90</div>
</div>
<div class="sm-rank is-5th" style="--bg:#85d28a; --hair:#216a2c; --body:#2f8a4a; --skin:#f5d6b3;">
<img class="pos" src="../cut/result-txt-2.png" alt="">
<div class="av">
<div class="portrait">
<div class="chibi">
<div class="ch-body"></div>
<div class="ch-head"></div>
<div class="ch-hair"></div>
</div>
</div>
<img class="frame" src="../cut/player-frame.png" alt="">
</div>
<div class="nm">JAME</div>
<div class="sc">90</div>
</div>
</div>
<img class="sm-line sm-line-1 sm-line-above" src="../cut/popup-result-Line1.png" alt="">
<div class="sm-total">
คะแนนรวม (100+100+100+90+90) =<span class="num">480</span>
</div>
<img class="sm-line sm-line-1 sm-line-below" src="../cut/popup-result-Line1.png" alt="">
<div class="sm-bottom">
<section class="sm-col sm-col-grade">
<h3 class="head">ระดับความสำเร็จ</h3>
<p class="sm-grade" aria-label="เกรด A">A</p>
</section>
<div class="sm-vrule" aria-hidden="true">
<img class="sm-line-2" src="../cut/popup-result-Line2.png" alt="">
</div>
<section class="sm-col sm-col-bonus">
<div class="sm-bonus">
<div class="sm-bonus-left">
<h3 class="head">โบนัสพิเศษ</h3>
<img class="complete" src="../cut/mission-complete-select.png" alt="ภารกิจสำเร็จ">
</div>
<div class="sm-bonus-right">
<img class="bail-card" src="../../cut/10-Game-1-result/card-5.png" alt="Bail Coin">
</div>
</div>
</section>
</div>
</div>
<div class="layer sm-btn-done btn"><img src="../cut/btn-done.png" alt=""></div>
</div>
</div>
<script src="../assets/js/scale.js"></script>
@@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<title>08 · สรุปผลภารกิจ</title>
<meta name="viewport" content="width=1920, initial-scale=1">
<link rel="stylesheet" href="../assets/css/base.css">
<link rel="stylesheet" href="../assets/css/game-hud.css">
<link rel="stylesheet" href="../assets/css/game.css">
<link rel="stylesheet" href="../assets/css/summary.css">
</head>
<body>
<div class="stage-wrap">
<div class="stage mg-stage ">
<div class="layer mg-bg"><img src="../cut/BG.png" alt="bg"></div>
<div class="layer mg2-score-bar">
<img class="mg2-score-label" src="../cut/score.png" alt="SCORE :">
<div class="mg2-players">
<div class="mg2-player"><div class="nm">Mone</div><div class="av"></div><div class="sc">200</div></div>
<div class="mg2-player"><div class="nm">Jackson</div><div class="av"></div><div class="sc">180</div></div>
<div class="mg2-player"><div class="nm">Micky</div><div class="av"></div><div class="sc">160</div></div>
<div class="mg2-player"><div class="nm">Rainy</div><div class="av"></div><div class="sc">190</div></div>
<div class="mg2-player"><div class="nm">Jame</div><div class="av"></div><div class="sc">150</div></div>
</div>
</div>
<div class="layer mg-host">
<img class="mg-host-frame" src="../cut/Avartar.png" alt="">
<img class="mg-host-face" src="" alt="">
</div>
<div class="layer mg-mic"><img src="../cut/btn-mic_on.png" alt="mic"></div>
<div class="layer sm-overlay"></div><div class="layer sm-popup"><img src="../cut/popup-result.png" alt=""></div><div class="layer sm-btn-done btn"><img src="../cut/btn-done.png" alt=""></div>
</div>
</div>
<script src="../assets/js/scale.js"></script>
</body>
</html>