update card system

This commit is contained in:
2026-05-31 01:00:05 +00:00
parent 43c8f9d49a
commit 8f05d6019d
24 changed files with 1075 additions and 292 deletions
+176
View File
@@ -572,6 +572,182 @@ legend {
color: var(--accent);
}
/* —— Tab group dividers (จัดหมวดเมนูให้สแกนง่าย) —— */
.admin-tab-sep {
flex: 1 1 100%;
display: flex;
align-items: center;
gap: 0.55rem;
margin: 0.35rem 0.15rem 0;
padding: 0 0.15rem;
user-select: none;
}
.admin-tab-sep:first-child {
margin-top: 0;
}
.admin-tab-sep span {
font-size: 0.66rem;
font-weight: 700;
letter-spacing: 0.09em;
text-transform: uppercase;
color: var(--muted);
white-space: nowrap;
}
.admin-tab-sep::after {
content: '';
flex: 1;
height: 1px;
background: linear-gradient(90deg, var(--border), transparent);
}
@media (max-width: 899px) {
/* มือถือ: แถบเลื่อนแนวนอน — ไม่ต้องแสดงหัวข้อกลุ่ม */
.admin-tab-sep { display: none; }
}
/* —— ชุดคำถามพิเศษ (Special Quiz panel) —— */
.sq-title { margin: 0 0 0.35rem; }
.sq-steps {
list-style: none;
display: flex;
flex-wrap: wrap;
gap: 0.6rem;
margin: 0.9rem 0 1.1rem;
padding: 0;
}
.sq-steps li {
display: flex;
align-items: center;
gap: 0.5rem;
flex: 1 1 220px;
padding: 0.55rem 0.8rem;
border: 1px solid var(--border);
border-radius: var(--radius);
background: rgba(255, 255, 255, 0.02);
font-size: 0.88rem;
}
.sq-step-no {
flex: 0 0 auto;
width: 1.65rem;
height: 1.65rem;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background: var(--accent-dim);
color: var(--accent);
font-weight: 700;
font-size: 0.9rem;
}
.sq-fieldset {
margin: 0 0 1.1rem;
padding: 0.95rem 1.1rem 1.1rem;
border: 1px solid var(--border);
border-radius: var(--radius-lg);
background: var(--bg-elevated, rgba(255, 255, 255, 0.02));
}
.sq-fieldset > legend {
font-size: 0.95rem;
font-weight: 700;
padding: 0 0.45rem;
color: var(--text);
}
.sq-pick-row {
display: flex;
flex-wrap: wrap;
gap: 1rem;
align-items: flex-end;
}
.sq-select { min-width: 200px; }
.sq-set-tag {
margin-left: auto;
align-self: center;
font-size: 0.8rem;
font-weight: 600;
color: var(--accent);
background: var(--accent-dim);
padding: 0.3rem 0.7rem;
border-radius: 999px;
white-space: nowrap;
}
.sq-card-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 0.85rem;
}
.sq-card-grid .sq-card-url { grid-column: 1 / -1; }
.sq-card-grid input,
.sq-card-grid select { width: 100%; }
.sq-count {
font-weight: 400;
font-size: 0.8rem;
color: var(--muted);
margin-left: 0.3rem;
}
/* —— คำถามตัวเลือก A/B/C/D (Special Quiz MCQ rows) —— */
.sq-mcq-row {
display: flex;
flex-direction: column;
gap: 0.7rem;
padding: 0.85rem 0.95rem;
margin-bottom: 0.85rem;
border: 1px solid var(--border);
border-radius: var(--radius);
background: rgba(255, 255, 255, 0.02);
}
.sq-mcq-q {
display: flex;
flex-direction: column;
gap: 0.3rem;
font-size: 0.82rem;
font-weight: 600;
color: var(--muted);
}
.sq-mcq-text { width: 100%; }
.sq-mcq-choices {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 0.55rem;
}
.sq-mcq-choice {
display: flex;
align-items: center;
gap: 0.5rem;
}
.sq-mcq-letter {
flex: 0 0 auto;
width: 1.7rem;
height: 1.7rem;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 8px;
background: var(--accent-dim);
color: var(--accent);
font-weight: 700;
font-size: 0.85rem;
}
.sq-mcq-choice-input { flex: 1 1 auto; min-width: 0; }
.sq-mcq-foot {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
flex-wrap: wrap;
}
.sq-mcq-ans {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.82rem;
font-weight: 600;
color: var(--text);
}
.sq-mcq-correct { min-width: 5rem; }
@media (max-width: 620px) {
.sq-mcq-choices { grid-template-columns: 1fr; }
}
.tab-panel[hidden] {
display: none !important;
}
+199 -1
View File
@@ -246,6 +246,189 @@
});
}
/* ===== ชุดคำถามพิเศษ (Level×Case = 15 ชุด) รับการ์ดพิเศษ ===== */
var SPECIAL_CARD_RARITIES = ['common', 'rare', 'legendary'];
var specialQuizSetsState = {};
var specialQuizCurrentKey = 'L1_C1';
function specialQuizSelectorKey() {
var L = el('special-quiz-level');
var C = el('special-quiz-case');
return 'L' + ((L && L.value) || '1') + '_C' + ((C && C.value) || '1');
}
function updateSpecialQuizCount() {
var c = el('special-quiz-count');
if (!c) return;
var n = document.querySelectorAll('#special-quiz-questions-list .quiz-admin-q-row').length;
c.textContent = '(' + n + ' ข้อ)';
}
var SPECIAL_QUIZ_CHOICE_COUNT = 4;
var SPECIAL_QUIZ_LETTERS = ['A', 'B', 'C', 'D', 'E', 'F'];
/** คำถามตัวเลือก A/B/C/D — เลือกคำตอบถูก 1 ข้อ */
function addSpecialQuizRow(q) {
q = q || {};
var list = el('special-quiz-questions-list');
if (!list) return;
var choices = Array.isArray(q.choices) ? q.choices : [];
var correctIndex = Number(q.correctIndex);
if (!(correctIndex >= 0 && correctIndex < SPECIAL_QUIZ_CHOICE_COUNT)) correctIndex = 0;
var row = document.createElement('div');
row.className = 'quiz-admin-q-row sq-mcq-row';
var qLab = document.createElement('label');
qLab.className = 'sq-mcq-q';
qLab.appendChild(document.createTextNode('คำถาม'));
var qInp = document.createElement('input');
qInp.type = 'text';
qInp.className = 'sq-mcq-text';
qInp.maxLength = 500;
qInp.placeholder = 'พิมพ์คำถาม...';
qInp.value = q.text || '';
qLab.appendChild(qInp);
var grid = document.createElement('div');
grid.className = 'sq-mcq-choices';
for (var i = 0; i < SPECIAL_QUIZ_CHOICE_COUNT; i++) {
var cl = document.createElement('label');
cl.className = 'sq-mcq-choice';
var badge = document.createElement('span');
badge.className = 'sq-mcq-letter';
badge.textContent = SPECIAL_QUIZ_LETTERS[i];
var ci = document.createElement('input');
ci.type = 'text';
ci.className = 'sq-mcq-choice-input';
ci.maxLength = 200;
ci.placeholder = 'ตัวเลือก ' + SPECIAL_QUIZ_LETTERS[i];
ci.value = choices[i] || '';
ci.setAttribute('data-choice-index', String(i));
cl.appendChild(badge);
cl.appendChild(ci);
grid.appendChild(cl);
}
var foot = document.createElement('div');
foot.className = 'sq-mcq-foot';
var ansLab = document.createElement('label');
ansLab.className = 'sq-mcq-ans';
ansLab.appendChild(document.createTextNode('คำตอบที่ถูก '));
var ansSel = document.createElement('select');
ansSel.className = 'sq-mcq-correct';
for (var j = 0; j < SPECIAL_QUIZ_CHOICE_COUNT; j++) {
var o = document.createElement('option');
o.value = String(j);
o.textContent = SPECIAL_QUIZ_LETTERS[j];
if (j === correctIndex) o.selected = true;
ansSel.appendChild(o);
}
ansLab.appendChild(ansSel);
var rm = document.createElement('button');
rm.type = 'button';
rm.className = 'btn btn-danger btn-sm quiz-admin-q-remove';
rm.setAttribute('aria-label', 'ลบข้อนี้');
rm.textContent = 'ลบ';
rm.addEventListener('click', function () {
row.remove();
updateSpecialQuizCount();
});
foot.appendChild(ansLab);
foot.appendChild(rm);
row.appendChild(qLab);
row.appendChild(grid);
row.appendChild(foot);
list.appendChild(row);
}
function renderSpecialQuizSet(key) {
var set = specialQuizSetsState[key] || { questions: [], specialCard: {} };
var card = (set.specialCard && typeof set.specialCard === 'object') ? set.specialCard : {};
if (el('special-card-th')) el('special-card-th').value = card.th || '';
if (el('special-card-en')) el('special-card-en').value = card.en || '';
if (el('special-card-rarity')) {
el('special-card-rarity').value = SPECIAL_CARD_RARITIES.indexOf(card.rarity) >= 0 ? card.rarity : 'rare';
}
if (el('special-card-image')) el('special-card-image').value = card.imageUrl || '';
var list = el('special-quiz-questions-list');
if (list) {
list.innerHTML = '';
var qs = (set.questions && set.questions.length)
? set.questions
: [{ type: 'mcq', text: '', choices: ['', '', '', ''], correctIndex: 0 }];
qs.forEach(addSpecialQuizRow);
}
var tag = el('special-quiz-set-tag');
if (tag) tag.textContent = 'ชุดที่แก้: ' + key;
updateSpecialQuizCount();
}
function captureSpecialQuizForm(key) {
var questions = [];
document.querySelectorAll('#special-quiz-questions-list .sq-mcq-row').forEach(function (row) {
var inp = row.querySelector('.sq-mcq-text');
var t = inp && inp.value ? String(inp.value).trim() : '';
if (!t) return;
var choices = [];
row.querySelectorAll('.sq-mcq-choice-input').forEach(function (ci) {
choices.push(String(ci.value || '').trim());
});
if (choices.filter(function (c) { return c; }).length < 2) return;
var sel = row.querySelector('.sq-mcq-correct');
var correctIndex = sel ? parseInt(sel.value, 10) : 0;
if (!(correctIndex >= 0 && correctIndex < choices.length) || !choices[correctIndex]) {
correctIndex = choices.findIndex(function (c) { return c; });
if (correctIndex < 0) correctIndex = 0;
}
questions.push({ type: 'mcq', text: t, choices: choices, correctIndex: correctIndex });
});
var rar = el('special-card-rarity') ? el('special-card-rarity').value : 'rare';
specialQuizSetsState[key] = {
questions: questions,
specialCard: {
th: el('special-card-th') ? String(el('special-card-th').value || '').trim() : '',
en: el('special-card-en') ? String(el('special-card-en').value || '').trim() : '',
rarity: SPECIAL_CARD_RARITIES.indexOf(rar) >= 0 ? rar : 'rare',
imageUrl: el('special-card-image') ? String(el('special-card-image').value || '').trim() : '',
},
};
}
function onSpecialQuizSelectorChange() {
captureSpecialQuizForm(specialQuizCurrentKey);
specialQuizCurrentKey = specialQuizSelectorKey();
renderSpecialQuizSet(specialQuizCurrentKey);
}
function loadSpecialQuizPanel() {
setMsg('special-quiz-msg', 'กำลังโหลด...', '');
gameQuizFetch('GET').then(function (data) {
specialQuizSetsState = (data.specialQuizSets && typeof data.specialQuizSets === 'object') ? data.specialQuizSets : {};
specialQuizCurrentKey = specialQuizSelectorKey();
renderSpecialQuizSet(specialQuizCurrentKey);
setMsg('special-quiz-msg', '', '');
}).catch(function (e) {
setMsg('special-quiz-msg', e.message || 'โหลดไม่ได้', 'error');
});
}
function saveSpecialQuizPanel() {
captureSpecialQuizForm(specialQuizCurrentKey);
setMsg('special-quiz-msg', 'กำลังบันทึก...', '');
gameQuizFetch('PUT', { specialQuizSets: specialQuizSetsState }).then(function (res) {
if (res && res.specialQuizSets && typeof res.specialQuizSets === 'object') {
specialQuizSetsState = res.specialQuizSets;
}
setMsg('special-quiz-msg', 'บันทึกแล้ว (15 ชุด)', 'ok');
renderSpecialQuizSet(specialQuizCurrentKey);
}).catch(function (e) {
setMsg('special-quiz-msg', e.message || 'บันทึกไม่ได้', 'error');
});
}
function quizTfThemeHiddenId(kind) {
if (kind === 'bg') return 'quiz-tf-theme-bg';
if (kind === 'border') return 'quiz-tf-theme-border';
@@ -3740,6 +3923,7 @@
}
});
if (name === 'quiz') loadQuizSettingsPanel();
if (name === 'special-quiz') loadSpecialQuizPanel();
if (name === 'quiz-carry') loadQuizCarryPanel();
if (name === 'quiz-battle') loadQbBattlePanel();
if (name === 'jump-survive') loadJumpSurviveTimingPanel();
@@ -3935,6 +4119,20 @@
}
if (btnQuizSave) btnQuizSave.addEventListener('click', saveQuizSettingsPanel);
var btnSpecialQuizAdd = el('btn-special-quiz-add');
var btnSpecialQuizSave = el('btn-special-quiz-save');
if (btnSpecialQuizAdd) {
btnSpecialQuizAdd.addEventListener('click', function () {
addSpecialQuizRow({ type: 'tf', text: '', answerTrue: true });
updateSpecialQuizCount();
});
}
if (btnSpecialQuizSave) btnSpecialQuizSave.addEventListener('click', saveSpecialQuizPanel);
var specialQuizLevelSel = el('special-quiz-level');
var specialQuizCaseSel = el('special-quiz-case');
if (specialQuizLevelSel) specialQuizLevelSel.addEventListener('change', onSpecialQuizSelectorChange);
if (specialQuizCaseSel) specialQuizCaseSel.addEventListener('change', onSpecialQuizSelectorChange);
var btnQuizCarryAdd = el('btn-quiz-carry-add');
var btnQuizCarrySave = el('btn-quiz-carry-save');
if (btnQuizCarryAdd) {
@@ -4228,7 +4426,7 @@
try {
want = new URL(window.location.href).searchParams.get('tab');
} catch (e) { /* ignore */ }
var allowed = { 'change-password': true, oauth: true, 'map-editor': true, quiz: true, 'quiz-carry': true, 'quiz-battle': true, 'jump-survive': true, 'space-shooter': true, 'mega-virus': true, 'game-timing': true, 'stack-game': true, characters: true, accounts: true, admins: true };
var allowed = { 'change-password': true, oauth: true, 'map-editor': true, quiz: true, 'special-quiz': true, 'quiz-carry': true, 'quiz-battle': true, 'jump-survive': true, 'space-shooter': true, 'mega-virus': true, 'game-timing': true, 'stack-game': true, characters: true, accounts: true, admins: true };
if (!want || !allowed[want]) return 'oauth';
if (want === 'admins' && (!admin || admin.role !== 'super')) return 'oauth';
return want;
+4 -1
View File
@@ -52,7 +52,7 @@ function merge_quiz_settings_disk_from_put(string $rawBody): bool
if ($hadFileButInvalidJson) {
return false;
}
$mergeKeys = ['readMs', 'answerMs', 'betweenMs', 'carryReadMs', 'carryAnswerMs', 'carrySessionLength', 'carryMapPanelTheme', 'quizMapPanelTheme', 'carryEmbedCountdownTheme', 'carryChoicePlaqueTheme', 'carryChoicePlaqueThemes', 'carryChoicePlaqueMapScale', 'carryWalkSpeedMultForMapId', 'carryWalkSpeedMult', 'quizRoundQuestionCount', 'questions', 'carryQuestions', 'battleQuizMcq'];
$mergeKeys = ['readMs', 'answerMs', 'betweenMs', 'carryReadMs', 'carryAnswerMs', 'carrySessionLength', 'carryMapPanelTheme', 'quizMapPanelTheme', 'carryEmbedCountdownTheme', 'carryChoicePlaqueTheme', 'carryChoicePlaqueThemes', 'carryChoicePlaqueMapScale', 'carryWalkSpeedMultForMapId', 'carryWalkSpeedMult', 'quizRoundQuestionCount', 'questions', 'carryQuestions', 'battleQuizMcq', 'specialQuizSets'];
foreach ($mergeKeys as $k) {
if (array_key_exists($k, $patch)) {
$base[$k] = $patch[$k];
@@ -96,6 +96,9 @@ function overlay_quiz_settings_from_disk(string $nodeBody): string
if (array_key_exists('battleQuizMcq', $disk)) {
$j['battleQuizMcq'] = $disk['battleQuizMcq'];
}
if (array_key_exists('specialQuizSets', $disk) && is_array($disk['specialQuizSets'])) {
$j['specialQuizSets'] = $disk['specialQuizSets'];
}
if (array_key_exists('carryMapPanelTheme', $disk) && is_array($disk['carryMapPanelTheme'])) {
$j['carryMapPanelTheme'] = $disk['carryMapPanelTheme'];
}
+79 -4
View File
@@ -10,7 +10,7 @@
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Kanit:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="admin.css?v=31">
<link rel="stylesheet" href="admin.css?v=33">
</head>
<body>
<a class="skip-link" href="#admin-main">ข้ามไปเนื้อหา</a>
@@ -66,9 +66,12 @@
<div id="panel-app" class="admin-app" hidden>
<nav class="admin-tabs" role="tablist" aria-label="เมนูหลัก Admin">
<button type="button" class="tab" data-tab="change-password" role="tab" id="tab-change-password" aria-selected="false" aria-controls="tab-panel-change-password"><span class="tab-label">รหัสผ่าน</span><span class="tab-desc tab-desc--wide">เปลี่ยนรหัสของคุณ · กดซ้ำเพื่อปิด</span></button>
<div class="admin-tab-sep" aria-hidden="true"><span>ทั่วไป &amp; ฉาก</span></div>
<button type="button" class="tab is-active" data-tab="oauth" role="tab" id="tab-oauth" aria-selected="true" aria-controls="tab-panel-oauth"><span class="tab-label">OAuth</span><span class="tab-desc">Facebook / Google</span></button>
<button type="button" class="tab" data-tab="map-editor" role="tab" id="tab-map-editor" aria-controls="tab-panel-map-editor"><span class="tab-label">Editor</span><span class="tab-desc">แผนที่ฉาก</span></button>
<button type="button" class="tab" data-tab="map-editor" role="tab" id="tab-map-editor" aria-controls="tab-panel-map-editor"><span class="tab-label">Editor ฉาก</span><span class="tab-desc">แผนที่ฉาก / โซนเกม</span></button>
<button type="button" class="tab" data-tab="change-password" role="tab" id="tab-change-password" aria-selected="false" aria-controls="tab-panel-change-password"><span class="tab-label">รหัสผ่าน</span><span class="tab-desc tab-desc--wide">เปลี่ยนรหัสของคุณ · กดซ้ำเพื่อปิด</span></button>
<div class="admin-tab-sep" aria-hidden="true"><span>มินิเกม</span></div>
<button type="button" class="tab" data-tab="quiz" role="tab" id="tab-quiz" aria-controls="tab-panel-quiz"><span class="tab-heading-row"><img class="tab-heading-icon" src="/Game/img/QUESTION/admin-tab-minigame-1.png" width="22" height="22" alt="" decoding="async"><span class="tab-label">Minigame-1-จริงหรือไม่</span></span><span class="tab-desc" aria-hidden="true"></span></button>
<button type="button" class="tab" data-tab="game-timing" role="tab" id="tab-game-timing" aria-controls="tab-panel-game-timing"><span class="tab-heading-row"><img class="tab-heading-icon" src="/Game/img/gauntlet-assets/gauntlet-dd51baed17270995.png" width="22" height="22" alt="" decoding="async"><span class="tab-label">Minigame-2-วิ่งหลบสิ่งกีดขวาง</span></span><span class="tab-desc" aria-hidden="true"></span></button>
<button type="button" class="tab" data-tab="stack-game" role="tab" id="tab-stack-game" aria-controls="tab-panel-stack-game"><span class="tab-heading-row"><img class="tab-heading-icon tab-heading-icon--stack-tab" src="/Game/img/TowerBlock/admin-tab-minigame-3.png" width="48" height="18" alt="" decoding="async"><span class="tab-label">Minigame-3-Tower block</span></span><span class="tab-desc" aria-hidden="true"></span></button>
@@ -77,6 +80,11 @@
<button type="button" class="tab" data-tab="jump-survive" role="tab" id="tab-jump-survive" aria-controls="tab-panel-jump-survive"><span class="tab-heading-row"><img class="tab-heading-icon" src="/Game/img/Jumper/admin-tab-minigame-5.png" width="22" height="22" alt="" decoding="async"><span class="tab-label">Minigame-5-กระโดดขึ้นแท่น</span></span><span class="tab-desc" aria-hidden="true"></span></button>
<button type="button" class="tab" data-tab="space-shooter" role="tab" id="tab-space-shooter" aria-controls="tab-panel-space-shooter"><span class="tab-heading-row"><img class="tab-heading-icon" src="/Game/img/ViolentCrime/Meteo-1.png" width="22" height="22" alt="" decoding="async"><span class="tab-label">Minigame-6-ยิงอุกาบาต Violent Crime</span></span><span class="tab-desc" aria-hidden="true"></span></button>
<button type="button" class="tab" data-tab="mega-virus" role="tab" id="tab-mega-virus" aria-controls="tab-panel-mega-virus"><span class="tab-label">Minigame-7-ยิง Mega Virus</span><span class="tab-desc" aria-hidden="true"></span></button>
<div class="admin-tab-sep" aria-hidden="true"><span>คำถาม &amp; การ์ดพิเศษ</span></div>
<button type="button" class="tab" data-tab="special-quiz" role="tab" id="tab-special-quiz" aria-controls="tab-panel-special-quiz"><span class="tab-label">ชุดคำถามพิเศษ</span><span class="tab-desc">15 ชุด (Level × Case) · การ์ดรางวัล</span></button>
<div class="admin-tab-sep" aria-hidden="true"><span>ระบบ &amp; บัญชี</span></div>
<button type="button" class="tab" data-tab="characters" role="tab" id="tab-characters" aria-controls="tab-panel-characters"><span class="tab-label">ตัวละคร</span><span class="tab-desc">อัปโหลด · เลือกใช้ในเกม</span></button>
<button type="button" class="tab" data-tab="accounts" role="tab" id="tab-accounts" aria-controls="tab-panel-accounts"><span class="tab-label">ผู้ใช้</span><span class="tab-desc">บัญชี &amp; COINS</span></button>
<button type="button" class="tab" data-tab="admins" role="tab" id="tab-admins" aria-controls="tab-panel-admins"><span class="tab-label">แอดมิน</span><span class="tab-desc">สิทธิ์ระบบ</span></button>
@@ -226,6 +234,73 @@
<p id="quiz-settings-msg" class="msg" role="status"></p>
</section>
<section id="tab-panel-special-quiz" class="tab-panel card" hidden role="tabpanel" aria-labelledby="tab-special-quiz">
<h2 class="sq-title">ชุดคำถามพิเศษ — รับการ์ดพิเศษ</h2>
<p class="muted">มี <strong>15 ชุด</strong> = <strong>ระดับ (Level 15)</strong> × <strong>คดี (Case 13)</strong> · แต่ละชุดมีคำถามเป็นของตัวเอง (สุ่มมาเล่นในเกม) และให้ <strong>การ์ดพิเศษ 1 ใบ</strong> เป็นรางวัล · เก็บที่ <code>specialQuizSets</code> ใน <code>quiz-settings.json</code> · <em>English:</em> 15 sets (Level × Case), multiple-choice (A/B/C/D), each grants one special card.</p>
<ol class="sq-steps">
<li><span class="sq-step-no">1</span> เลือก <strong>ระดับ + คดี</strong> ที่จะแก้</li>
<li><span class="sq-step-no">2</span> ตั้ง <strong>การ์ดพิเศษ</strong> ของชุดนั้น</li>
<li><span class="sq-step-no">3</span> ใส่ <strong>คำถาม</strong> แล้วกด <strong>บันทึก</strong></li>
</ol>
<fieldset class="sq-fieldset sq-pick">
<legend>1 · เลือกชุดที่จะแก้ไข</legend>
<div class="sq-pick-row">
<label class="admin-field">ระดับ (Level)
<select id="special-quiz-level" class="admin-inp-num sq-select">
<option value="1">ระดับ 1 · ROOKIE</option>
<option value="2">ระดับ 2</option>
<option value="3">ระดับ 3</option>
<option value="4">ระดับ 4</option>
<option value="5">ระดับ 5</option>
</select>
</label>
<label class="admin-field">คดี (Case)
<select id="special-quiz-case" class="admin-inp-num sq-select">
<option value="1">คดี 1 · คดีโจรกรรมไซเบอร์</option>
<option value="2">คดี 2 · คดีปล้นร้านอัญมณี</option>
<option value="3">คดี 3</option>
</select>
</label>
<span id="special-quiz-set-tag" class="sq-set-tag">ชุดที่แก้: L1_C1</span>
</div>
</fieldset>
<fieldset class="sq-fieldset sq-card">
<legend>2 · การ์ดพิเศษของชุดนี้</legend>
<div class="sq-card-grid">
<label class="admin-field">ชื่อการ์ด (ไทย)
<input type="text" id="special-card-th" maxlength="160" placeholder="เช่น เบาะแสลับ">
</label>
<label class="admin-field">ชื่อการ์ด (อังกฤษ)
<input type="text" id="special-card-en" maxlength="160" placeholder="e.g. Secret Clue">
</label>
<label class="admin-field">ระดับการ์ด
<select id="special-card-rarity" class="admin-inp-num">
<option value="common">Common</option>
<option value="rare" selected>Rare</option>
<option value="legendary">Legendary</option>
</select>
</label>
<label class="admin-field sq-card-url">URL รูปการ์ด
<input type="text" id="special-card-image" maxlength="600" placeholder="/Main-Lobby/IMAGE/... หรือ https://...">
</label>
</div>
</fieldset>
<fieldset class="sq-fieldset sq-questions">
<legend>3 · คำถามของชุดนี้ <span id="special-quiz-count" class="sq-count"></span></legend>
<p class="muted" style="margin:0 0 0.7rem">รูปแบบ <strong>ตัวเลือก A B C D</strong> — กรอกตัวเลือกแล้วเลือก “คำตอบที่ถูก” 1 ข้อ (อย่างน้อย 2 ตัวเลือกต่อข้อ) · สุ่มมาเล่นตามจำนวน “สุ่มสูงสุดกี่ข้อต่อรอบ” ที่ตั้งในแท็บ <strong>Minigame-1-จริงหรือไม่</strong></p>
<div id="special-quiz-questions-list" class="quiz-admin-questions-list"></div>
<div class="quiz-admin-actions">
<button type="button" class="btn btn-ghost" id="btn-special-quiz-add">+ เพิ่มคำถาม</button>
<button type="button" class="btn btn-primary" id="btn-special-quiz-save">บันทึกชุดคำถามพิเศษทั้งหมด (15 ชุด)</button>
</div>
</fieldset>
<p id="special-quiz-msg" class="msg" role="status"></p>
</section>
<section id="tab-panel-quiz-carry" class="tab-panel card" hidden role="tabpanel" aria-labelledby="tab-quiz-carry">
<h2>คำถามหลายตัวเลือก (หยิบมาวาง)</h2>
<p class="muted">ใช้กับฉากประเภท <strong>ตอบคำถาม — หยิบคำตอบมาวางกลาง</strong> ใน Editor — ผู้เล่นหยิบตัวเลือกไปวางโซนกลาง · ถ้ามีรายการที่นี่ เกมจะใช้<strong>เฉพาะชุดนี้</strong> (ไม่ผสมกับคำถามถูก/ผิดแท็บคำถามเกม) · เก็บที่ <code>/Game/data/quiz-settings.json</code> ฟิลด์ <code>carryQuestions</code></p>
@@ -927,6 +1002,6 @@
</div>
</main>
</div>
<script src="admin.js?v=71"></script>
<script src="admin.js?v=73"></script>
</body>
</html>
+422 -285
View File
@@ -1,291 +1,428 @@
{
"readMs": 15000,
"answerMs": 5000,
"betweenMs": 3000,
"carryReadMs": 3000,
"carryAnswerMs": 15000,
"carrySessionLength": 2,
"carryMapPanelTheme": {
"panelBg": "rgba(255, 0, 0, 0)",
"panelBorder": "rgba(255, 255, 255, 0)",
"textColor": "rgba(241, 245, 249, 1)",
"borderWidthPx": 0,
"questionFontMinPx": 10,
"questionFontMaxPx": 24
"readMs": 15000,
"answerMs": 5000,
"betweenMs": 3000,
"carryReadMs": 3000,
"carryAnswerMs": 15000,
"carrySessionLength": 2,
"carryMapPanelTheme": {
"panelBg": "rgba(255, 0, 0, 0)",
"panelBorder": "rgba(255, 255, 255, 0)",
"borderWidthPx": 0,
"textColor": "rgba(241, 245, 249, 1)",
"questionFontMinPx": 10,
"questionFontMaxPx": 24
},
"quizMapPanelTheme": {
"panelBg": "rgba(12, 14, 28, 0)",
"panelBorder": "rgba(255, 214, 102, 0)",
"borderWidthPx": 2,
"textColor": "rgba(255, 224, 102, 1)",
"questionFontMinPx": 10,
"questionFontMaxPx": 28
},
"carryEmbedCountdownTheme": {
"overlayBackdrop": "rgba(8, 10, 20, 0)",
"innerBg": "rgba(12, 14, 28, 0)",
"innerBorder": "rgba(122, 162, 247, 0)",
"innerBorderWpx": 1,
"innerRadiusPx": 12,
"digitColor": "#f2f2f2",
"mapDigitCqmin": 78,
"mapDigitCqh": 82,
"mapDigitMaxPx": 200,
"screenDigitVw": 28,
"screenDigitMaxPx": 132
},
"carryChoicePlaqueThemes": [
{
"borderMode": "fixed",
"fixedBorder": "rgba(122, 200, 255, 0)",
"fillBg": "rgba(12, 10, 20, 0)",
"textColor": "rgba(248, 249, 255, 1)",
"borderWidthPx": 0,
"plaqueImageUrl": "https://srv1361159.hstgr.cloud/Game/img/quiz-carry/board-2.png"
},
"quizMapPanelTheme": {
"panelBg": "rgba(12, 14, 28, 0)",
"panelBorder": "rgba(255, 214, 102, 0)",
"borderWidthPx": 2,
"textColor": "rgba(255, 224, 102, 1)",
"questionFontMinPx": 10,
"questionFontMaxPx": 28
{
"borderMode": "fixed",
"fixedBorder": "rgba(122, 200, 255, 0)",
"fillBg": "rgba(12, 10, 20, 0)",
"textColor": "rgba(248, 249, 255, 1)",
"borderWidthPx": 0,
"plaqueImageUrl": "https://srv1361159.hstgr.cloud/Game/img/quiz-carry/board-2.png"
},
"carryEmbedCountdownTheme": {
"overlayBackdrop": "rgba(8, 10, 20, 0)",
"innerBg": "rgba(12, 14, 28, 0)",
"innerBorder": "rgba(122, 162, 247, 0)",
"innerBorderWpx": 1,
"innerRadiusPx": 12,
"digitColor": "#f2f2f2",
"mapDigitCqmin": 78,
"mapDigitCqh": 82,
"mapDigitMaxPx": 200,
"screenDigitVw": 28,
"screenDigitMaxPx": 132
{
"borderMode": "fixed",
"fixedBorder": "rgba(122, 200, 255, 0)",
"fillBg": "rgba(12, 10, 20, 0)",
"textColor": "rgba(248, 249, 255, 1)",
"borderWidthPx": 0,
"plaqueImageUrl": "https://srv1361159.hstgr.cloud/Game/img/quiz-carry/board-1.png"
},
"carryChoicePlaqueThemes": [
{
"borderMode": "fixed",
"fixedBorder": "rgba(122, 200, 255, 0)",
"fillBg": "rgba(12, 10, 20, 0)",
"textColor": "rgba(248, 249, 255, 1)",
"borderWidthPx": 0,
"plaqueImageUrl": "https:\/\/srv1361159.hstgr.cloud\/Game\/img\/quiz-carry\/board-2.png"
},
{
"borderMode": "fixed",
"fixedBorder": "rgba(122, 200, 255, 0)",
"fillBg": "rgba(12, 10, 20, 0)",
"textColor": "rgba(248, 249, 255, 1)",
"borderWidthPx": 0,
"plaqueImageUrl": "https:\/\/srv1361159.hstgr.cloud\/Game\/img\/quiz-carry\/board-2.png"
},
{
"borderMode": "fixed",
"fixedBorder": "rgba(122, 200, 255, 0)",
"fillBg": "rgba(12, 10, 20, 0)",
"textColor": "rgba(248, 249, 255, 1)",
"borderWidthPx": 0,
"plaqueImageUrl": "https:\/\/srv1361159.hstgr.cloud\/Game\/img\/quiz-carry\/board-1.png"
},
{
"borderMode": "fixed",
"fixedBorder": "rgba(122, 200, 255, 0)",
"fillBg": "rgba(12, 10, 20, 0)",
"textColor": "rgba(248, 249, 255, 1)",
"borderWidthPx": 0,
"plaqueImageUrl": "https:\/\/srv1361159.hstgr.cloud\/Game\/img\/quiz-carry\/board-1.png"
},
{
"borderMode": "fixed",
"fixedBorder": "rgba(122, 200, 255, 0)",
"fillBg": "rgba(12, 10, 20, 0)",
"textColor": "rgba(248, 249, 255, 1)",
"borderWidthPx": 0,
"plaqueImageUrl": "https:\/\/srv1361159.hstgr.cloud\/Game\/img\/quiz-carry\/board-1.png"
},
{
"borderMode": "fixed",
"fixedBorder": "rgba(122, 200, 255, 0)",
"fillBg": "rgba(12, 10, 20, 0)",
"textColor": "rgba(248, 249, 255, 1)",
"borderWidthPx": 0,
"plaqueImageUrl": "https:\/\/srv1361159.hstgr.cloud\/Game\/img\/quiz-carry\/board-3.png"
},
{
"borderMode": "fixed",
"fixedBorder": "rgba(122, 200, 255, 0)",
"fillBg": "rgba(12, 10, 20, 0)",
"textColor": "rgba(248, 249, 255, 1)",
"borderWidthPx": 0,
"plaqueImageUrl": "https:\/\/srv1361159.hstgr.cloud\/Game\/img\/quiz-carry\/board-3.png"
},
{
"borderMode": "fixed",
"fixedBorder": "rgba(122, 200, 255, 0)",
"fillBg": "rgba(12, 10, 20, 0)",
"textColor": "rgba(248, 249, 255, 1)",
"borderWidthPx": 0,
"plaqueImageUrl": "https:\/\/srv1361159.hstgr.cloud\/Game\/img\/quiz-carry\/board-4.png"
},
{
"borderMode": "fixed",
"fixedBorder": "rgba(122, 200, 255, 0)",
"fillBg": "rgba(12, 10, 20, 0)",
"textColor": "rgba(248, 249, 255, 1)",
"borderWidthPx": 0,
"plaqueImageUrl": "https:\/\/srv1361159.hstgr.cloud\/Game\/img\/quiz-carry\/board-4.png"
},
{
"borderMode": "fixed",
"fixedBorder": "rgba(122, 200, 255, 0)",
"fillBg": "rgba(12, 10, 20, 0)",
"textColor": "rgba(248, 249, 255, 1)",
"borderWidthPx": 0,
"plaqueImageUrl": "https:\/\/srv1361159.hstgr.cloud\/Game\/img\/quiz-carry\/board-5.png"
},
{
"borderMode": "fixed",
"fixedBorder": "rgba(122, 200, 255, 0)",
"fillBg": "rgba(12, 10, 20, 0)",
"textColor": "rgba(248, 249, 255, 1)",
"borderWidthPx": 0,
"plaqueImageUrl": "https:\/\/srv1361159.hstgr.cloud\/Game\/img\/quiz-carry\/board-5.png"
},
{
"borderMode": "fixed",
"fixedBorder": "rgba(122, 200, 255, 0)",
"fillBg": "rgba(12, 10, 20, 0)",
"textColor": "rgba(248, 249, 255, 1)",
"borderWidthPx": 0,
"plaqueImageUrl": "https:\/\/srv1361159.hstgr.cloud\/Game\/img\/quiz-carry\/board-5.png"
},
{
"borderMode": "fixed",
"fixedBorder": "rgba(122, 200, 255, 0)",
"fillBg": "rgba(12, 10, 20, 0)",
"textColor": "rgba(248, 249, 255, 1)",
"borderWidthPx": 0,
"plaqueImageUrl": "https:\/\/srv1361159.hstgr.cloud\/Game\/img\/quiz-carry\/board-6.png"
},
{
"borderMode": "fixed",
"fixedBorder": "rgba(122, 200, 255, 0)",
"fillBg": "rgba(12, 10, 20, 0)",
"textColor": "rgba(248, 249, 255, 1)",
"borderWidthPx": 0,
"plaqueImageUrl": "https:\/\/srv1361159.hstgr.cloud\/Game\/img\/quiz-carry\/board-6.png"
},
{
"borderMode": "neon",
"fixedBorder": "rgba(122, 200, 255, 0.9)",
"fillBg": "rgba(12, 10, 20, 0.88)",
"textColor": "rgba(248, 249, 255, 1)",
"borderWidthPx": 2.5,
"plaqueImageUrl": ""
},
{
"borderMode": "neon",
"fixedBorder": "rgba(122, 200, 255, 0.9)",
"fillBg": "rgba(12, 10, 20, 0.88)",
"textColor": "rgba(248, 249, 255, 1)",
"borderWidthPx": 2.5,
"plaqueImageUrl": ""
}
],
"carryChoicePlaqueTheme": {
"borderMode": "fixed",
"fixedBorder": "rgba(122, 200, 255, 0)",
"fillBg": "rgba(12, 10, 20, 0)",
"textColor": "rgba(248, 249, 255, 1)",
"borderWidthPx": 0,
"plaqueImageUrl": "https:\/\/srv1361159.hstgr.cloud\/Game\/img\/quiz-carry\/board-2.png"
{
"borderMode": "fixed",
"fixedBorder": "rgba(122, 200, 255, 0)",
"fillBg": "rgba(12, 10, 20, 0)",
"textColor": "rgba(248, 249, 255, 1)",
"borderWidthPx": 0,
"plaqueImageUrl": "https://srv1361159.hstgr.cloud/Game/img/quiz-carry/board-1.png"
},
"carryChoicePlaqueMapScale": 1.9,
"carryWalkSpeedMultForMapId": "",
"carryWalkSpeedMult": null,
"quizRoundQuestionCount": 3,
"questions": [
{
"text": "test1",
"answerTrue": true
},
{
"text": "test2",
"answerTrue": true
},
{
"text": "test3",
"answerTrue": true
},
{
"text": "test4",
"answerTrue": true
},
{
"text": "test5",
"answerTrue": true
},
{
"text": "test6",
"answerTrue": true
},
{
"text": "test7",
"answerTrue": true
},
{
"text": "test8",
"answerTrue": true
},
{
"text": "test9",
"answerTrue": true
},
{
"text": "test10",
"answerTrue": true
},
{
"text": "test11",
"answerTrue": true
},
{
"text": "test12",
"answerTrue": true
},
{
"text": "test13",
"answerTrue": true
},
{
"text": "test14",
"answerTrue": true
}
],
"carryQuestions": [
{
"text": "ผู้มีอํานาจในการพิจารณาพิพากษาตัดสินคดีความในศาล",
"choices": [
"จําเลย",
"ผู้เสียหาย",
"อัยการ",
"ทนายความ",
"โจทก์",
"ตํารวจ",
"ผู้ไกล่เกลี่ย",
"ผู้พิพากษา",
"นิติกร",
"พยานบุคคล",
"หมายค้น",
"หมายจับ",
"หมายเรียก",
"พยานหลักฐาน"
],
"correctIndex": 7
},
{
"text": "ผู้ที่ถูกกล่าวหาว่ากระทําความผิดทางอาญา และถูกฟ้องต่อศาลแล้ว",
"choices": [
"ผู้พิพากษา",
"โจทก์",
"พยานบุคคล",
"การประกันตัว",
"นิติกร",
"ผู้เสียหาย",
"สัญญา",
"หมายจับ",
"ตํารวจ",
"ผู้ไกล่เกลี่ย",
"อัยการ",
"ทนายความ",
"จําเลย",
"พยานหลักฐาน"
],
"correctIndex": 12
}
],
"battleQuizMcq": [
{
"categoryId": "cybercrime",
"text": "ฟหกฟหก",
"choices": [
"ๆไำ",
"ฟหฟหด",
"ฟหกไๆไก"
],
"correctIndex": 0
}
]
{
"borderMode": "fixed",
"fixedBorder": "rgba(122, 200, 255, 0)",
"fillBg": "rgba(12, 10, 20, 0)",
"textColor": "rgba(248, 249, 255, 1)",
"borderWidthPx": 0,
"plaqueImageUrl": "https://srv1361159.hstgr.cloud/Game/img/quiz-carry/board-1.png"
},
{
"borderMode": "fixed",
"fixedBorder": "rgba(122, 200, 255, 0)",
"fillBg": "rgba(12, 10, 20, 0)",
"textColor": "rgba(248, 249, 255, 1)",
"borderWidthPx": 0,
"plaqueImageUrl": "https://srv1361159.hstgr.cloud/Game/img/quiz-carry/board-3.png"
},
{
"borderMode": "fixed",
"fixedBorder": "rgba(122, 200, 255, 0)",
"fillBg": "rgba(12, 10, 20, 0)",
"textColor": "rgba(248, 249, 255, 1)",
"borderWidthPx": 0,
"plaqueImageUrl": "https://srv1361159.hstgr.cloud/Game/img/quiz-carry/board-3.png"
},
{
"borderMode": "fixed",
"fixedBorder": "rgba(122, 200, 255, 0)",
"fillBg": "rgba(12, 10, 20, 0)",
"textColor": "rgba(248, 249, 255, 1)",
"borderWidthPx": 0,
"plaqueImageUrl": "https://srv1361159.hstgr.cloud/Game/img/quiz-carry/board-4.png"
},
{
"borderMode": "fixed",
"fixedBorder": "rgba(122, 200, 255, 0)",
"fillBg": "rgba(12, 10, 20, 0)",
"textColor": "rgba(248, 249, 255, 1)",
"borderWidthPx": 0,
"plaqueImageUrl": "https://srv1361159.hstgr.cloud/Game/img/quiz-carry/board-4.png"
},
{
"borderMode": "fixed",
"fixedBorder": "rgba(122, 200, 255, 0)",
"fillBg": "rgba(12, 10, 20, 0)",
"textColor": "rgba(248, 249, 255, 1)",
"borderWidthPx": 0,
"plaqueImageUrl": "https://srv1361159.hstgr.cloud/Game/img/quiz-carry/board-5.png"
},
{
"borderMode": "fixed",
"fixedBorder": "rgba(122, 200, 255, 0)",
"fillBg": "rgba(12, 10, 20, 0)",
"textColor": "rgba(248, 249, 255, 1)",
"borderWidthPx": 0,
"plaqueImageUrl": "https://srv1361159.hstgr.cloud/Game/img/quiz-carry/board-5.png"
},
{
"borderMode": "fixed",
"fixedBorder": "rgba(122, 200, 255, 0)",
"fillBg": "rgba(12, 10, 20, 0)",
"textColor": "rgba(248, 249, 255, 1)",
"borderWidthPx": 0,
"plaqueImageUrl": "https://srv1361159.hstgr.cloud/Game/img/quiz-carry/board-5.png"
},
{
"borderMode": "fixed",
"fixedBorder": "rgba(122, 200, 255, 0)",
"fillBg": "rgba(12, 10, 20, 0)",
"textColor": "rgba(248, 249, 255, 1)",
"borderWidthPx": 0,
"plaqueImageUrl": "https://srv1361159.hstgr.cloud/Game/img/quiz-carry/board-6.png"
},
{
"borderMode": "fixed",
"fixedBorder": "rgba(122, 200, 255, 0)",
"fillBg": "rgba(12, 10, 20, 0)",
"textColor": "rgba(248, 249, 255, 1)",
"borderWidthPx": 0,
"plaqueImageUrl": "https://srv1361159.hstgr.cloud/Game/img/quiz-carry/board-6.png"
},
{
"borderMode": "neon",
"fixedBorder": "rgba(122, 200, 255, 0.9)",
"fillBg": "rgba(12, 10, 20, 0.88)",
"textColor": "rgba(248, 249, 255, 1)",
"borderWidthPx": 2.5,
"plaqueImageUrl": ""
},
{
"borderMode": "neon",
"fixedBorder": "rgba(122, 200, 255, 0.9)",
"fillBg": "rgba(12, 10, 20, 0.88)",
"textColor": "rgba(248, 249, 255, 1)",
"borderWidthPx": 2.5,
"plaqueImageUrl": ""
}
],
"carryChoicePlaqueTheme": {
"borderMode": "fixed",
"fixedBorder": "rgba(122, 200, 255, 0)",
"fillBg": "rgba(12, 10, 20, 0)",
"textColor": "rgba(248, 249, 255, 1)",
"borderWidthPx": 0,
"plaqueImageUrl": "https://srv1361159.hstgr.cloud/Game/img/quiz-carry/board-2.png"
},
"carryChoicePlaqueMapScale": 1.9,
"carryWalkSpeedMultForMapId": "",
"carryWalkSpeedMult": null,
"quizRoundQuestionCount": 3,
"questions": [
{
"text": "test1",
"answerTrue": true
},
{
"text": "test2",
"answerTrue": true
},
{
"text": "test3",
"answerTrue": true
},
{
"text": "test4",
"answerTrue": true
},
{
"text": "test5",
"answerTrue": true
},
{
"text": "test6",
"answerTrue": true
},
{
"text": "test7",
"answerTrue": true
},
{
"text": "test8",
"answerTrue": true
},
{
"text": "test9",
"answerTrue": true
},
{
"text": "test10",
"answerTrue": true
},
{
"text": "test11",
"answerTrue": true
},
{
"text": "test12",
"answerTrue": true
},
{
"text": "test13",
"answerTrue": true
},
{
"text": "test14",
"answerTrue": true
}
],
"carryQuestions": [
{
"text": "ผู้มีอํานาจในการพิจารณาพิพากษาตัดสินคดีความในศาล",
"choices": [
"จําเลย",
"ผู้เสียหาย",
"อัยการ",
"ทนายความ",
"โจทก์",
"ตํารวจ",
"ผู้ไกล่เกลี่ย",
"ผู้พิพากษา",
"นิติกร",
"พยานบุคคล",
"หมายค้น",
"หมายจับ",
"หมายเรียก",
"พยานหลักฐาน"
],
"correctIndex": 7
},
{
"text": "ผู้ที่ถูกกล่าวหาว่ากระทําความผิดทางอาญา และถูกฟ้องต่อศาลแล้ว",
"choices": [
"ผู้พิพากษา",
"โจทก์",
"พยานบุคคล",
"การประกันตัว",
"นิติกร",
"ผู้เสียหาย",
"สัญญา",
"หมายจับ",
"ตํารวจ",
"ผู้ไกล่เกลี่ย",
"อัยการ",
"ทนายความ",
"จําเลย",
"พยานหลักฐาน"
],
"correctIndex": 12
}
],
"battleQuizMcq": [
{
"categoryId": "cybercrime",
"text": "ฟหกฟหก",
"choices": [
"ๆไำ",
"ฟหฟหด",
"ฟหกไๆไก"
],
"correctIndex": 0
}
],
"specialQuizSets": {
"L1_C1": {
"questions": [],
"specialCard": {
"th": "",
"en": "",
"rarity": "rare",
"imageUrl": ""
}
},
"L1_C2": {
"questions": [],
"specialCard": {
"th": "",
"en": "",
"rarity": "rare",
"imageUrl": ""
}
},
"L1_C3": {
"questions": [],
"specialCard": {
"th": "",
"en": "",
"rarity": "rare",
"imageUrl": ""
}
},
"L2_C1": {
"questions": [],
"specialCard": {
"th": "",
"en": "",
"rarity": "rare",
"imageUrl": ""
}
},
"L2_C2": {
"questions": [],
"specialCard": {
"th": "",
"en": "",
"rarity": "rare",
"imageUrl": ""
}
},
"L2_C3": {
"questions": [],
"specialCard": {
"th": "",
"en": "",
"rarity": "rare",
"imageUrl": ""
}
},
"L3_C1": {
"questions": [],
"specialCard": {
"th": "",
"en": "",
"rarity": "rare",
"imageUrl": ""
}
},
"L3_C2": {
"questions": [],
"specialCard": {
"th": "",
"en": "",
"rarity": "rare",
"imageUrl": ""
}
},
"L3_C3": {
"questions": [],
"specialCard": {
"th": "",
"en": "",
"rarity": "rare",
"imageUrl": ""
}
},
"L4_C1": {
"questions": [],
"specialCard": {
"th": "",
"en": "",
"rarity": "rare",
"imageUrl": ""
}
},
"L4_C2": {
"questions": [],
"specialCard": {
"th": "",
"en": "",
"rarity": "rare",
"imageUrl": ""
}
},
"L4_C3": {
"questions": [],
"specialCard": {
"th": "",
"en": "",
"rarity": "rare",
"imageUrl": ""
}
},
"L5_C1": {
"questions": [],
"specialCard": {
"th": "",
"en": "",
"rarity": "rare",
"imageUrl": ""
}
},
"L5_C2": {
"questions": [],
"specialCard": {
"th": "",
"en": "",
"rarity": "rare",
"imageUrl": ""
}
},
"L5_C3": {
"questions": [],
"specialCard": {
"th": "",
"en": "",
"rarity": "rare",
"imageUrl": ""
}
}
}
}
+101
View File
@@ -11243,6 +11243,103 @@
};
}
/* ===== () =====
* ดตอเขาเกมจร (trigger) จะ wire ภายหลงตามทใชระบ
* ใช: SpecialQuiz.fetchSettings() -> getSet(level,caseId) -> pickQuestions(set,n)
* เลนจบแล awardCard(set, correct, total) -> set mission.specialCardAwarded = card
*/
let __specialQuizSettingsCache = null;
let __specialQuizFetchPromise = null;
function specialQuizFetchSettings() {
if (__specialQuizSettingsCache) return Promise.resolve(__specialQuizSettingsCache);
if (__specialQuizFetchPromise) return __specialQuizFetchPromise;
__specialQuizFetchPromise = fetch(BASE + '/api/quiz-settings', { cache: 'no-store' })
.then(function (r) { return r.json(); })
.then(function (j) { __specialQuizSettingsCache = j || {}; return __specialQuizSettingsCache; })
.catch(function () { __specialQuizFetchPromise = null; return {}; });
return __specialQuizFetchPromise;
}
function specialQuizCurrentLevelCase() {
const meta = window.__detectiveLobbyMeta || {};
let level = Number(meta.level);
let caseId = Number(meta.caseId);
if (!(level >= 1 && level <= 5)) {
const lp = Number(params.get('lobbyLevel'));
if (lp >= 1 && lp <= 5) level = lp;
}
if (!(caseId >= 1 && caseId <= 3)) {
const cp = Number(params.get('case') || params.get('caseId'));
if (cp >= 1 && cp <= 3) caseId = cp;
}
return {
level: (level >= 1 && level <= 5) ? level : 1,
caseId: (caseId >= 1 && caseId <= 3) ? caseId : 1,
};
}
function specialQuizGetSet(settings, level, caseId) {
const sets = (settings && settings.specialQuizSets) || {};
const key = 'L' + level + '_C' + caseId;
return sets[key] || sets.L1_C1 || { questions: [], specialCard: {} };
}
function specialQuizPickQuestions(set, count) {
const qs = (set && Array.isArray(set.questions)) ? set.questions.slice() : [];
for (let i = qs.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
const t = qs[i]; qs[i] = qs[j]; qs[j] = t;
}
let n = Number(count);
if (!(n >= 1)) n = qs.length;
return qs.slice(0, Math.min(n, qs.length));
}
/** กติกาได้การ์ด: ตอบถูก >= ครึ่งหนึ่ง และชุดนั้นตั้งการ์ดไว้ — คืน card หรือ null */
function specialQuizAwardCard(set, correctCount, totalCount) {
const card = (set && set.specialCard) || null;
if (!card || !(card.th || card.en || card.imageUrl)) return null;
const total = Number(totalCount) || 0;
const correct = Number(correctCount) || 0;
const passNeed = total > 0 ? Math.ceil(total / 2) : 1;
if (correct < passNeed) return null;
return card;
}
/** วาดการ์ดพิเศษลงในโซนโบนัสของสรุปผล (#gcm-bonus / .sm-bonus-right) */
function renderSpecialCardInBonus(bonusEl, card) {
if (!bonusEl || !card || !(card.th || card.en || card.imageUrl)) return;
const wrap = document.createElement('div');
wrap.className = 'gcm-special-card sm-bonus-special rarity-' + (card.rarity || 'rare');
if (card.imageUrl) {
const img = document.createElement('img');
img.className = 'gcm-special-card-img';
img.src = card.imageUrl;
img.alt = card.th || card.en || '';
wrap.appendChild(img);
}
const cap = document.createElement('div');
cap.className = 'gcm-special-card-cap';
const strong = document.createElement('strong');
strong.textContent = card.th || '';
const span = document.createElement('span');
span.textContent = card.en || '';
cap.appendChild(strong);
cap.appendChild(span);
wrap.appendChild(cap);
bonusEl.appendChild(wrap);
}
window.SpecialQuiz = {
fetchSettings: specialQuizFetchSettings,
currentLevelCase: specialQuizCurrentLevelCase,
getSet: specialQuizGetSet,
pickQuestions: specialQuizPickQuestions,
awardCard: specialQuizAwardCard,
renderCardInBonus: renderSpecialCardInBonus,
};
function showGauntletCrownMissionOverlay(mission) {
const ov = document.getElementById('gauntlet-crown-mission-overlay');
const rowEl = document.getElementById('gcm-rank-row');
@@ -11495,6 +11592,10 @@
pl.innerHTML = '<strong>ไม่ได้รับการ์ด</strong><span>เกรด ' + gLetter + ' · ภารกิจไม่ผ่านเกณฑ์</span>';
bonusEl.appendChild(pl);
}
/* การ์ดพิเศษจากชุดคำถาม (ถ้ารอบนี้ได้รับ) — wire จุด trigger ภายหลัง */
if (mission && mission.specialCardAwarded) {
renderSpecialCardInBonus(bonusEl, mission.specialCardAwarded);
}
}
const gcmHead = document.getElementById('gcm-heading');
if (gcmHead) {
+19 -1
View File
@@ -1608,6 +1608,24 @@
}
.gcm-plaque strong { color: #f1f5f9; display: block; }
.gcm-plaque span { color: #7af8ff; font-size: 0.92em; }
.gcm-special-card {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
margin-top: 8px;
padding: 8px 10px;
border-radius: 12px;
border: 1px solid rgba(255, 214, 102, 0.6);
background: linear-gradient(165deg, rgba(40, 30, 10, 0.96), rgba(20, 14, 6, 0.98));
max-width: 200px;
}
.gcm-special-card.rarity-rare { border-color: rgba(122, 162, 247, 0.75); }
.gcm-special-card.rarity-legendary { border-color: rgba(255, 184, 77, 0.9); box-shadow: 0 0 12px rgba(255, 184, 77, 0.45); }
.gcm-special-card-img { width: 100%; max-width: 120px; height: auto; border-radius: 8px; display: block; }
.gcm-special-card-cap { text-align: center; line-height: 1.25; }
.gcm-special-card-cap strong { display: block; color: #ffe066; font-size: 0.95em; }
.gcm-special-card-cap span { color: #cbd5e1; font-size: 0.82em; }
.btn-gcm-done {
display: block;
margin: 4px auto 0;
@@ -3842,7 +3860,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.0499"></script>
<script src="js/play.js?v=0.0500"></script>
<div class="version-tag">v —</div>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

+75
View File
@@ -283,6 +283,72 @@ const defaultMap = () => ({
jumpSurviveMovePxPerSec: 200,
});
/* ===== Special Quiz Sets (Level 1-5 × Case 1-3 = 15 ชุด) — คำถามรับการ์ดพิเศษ ใช้ได้หลายเกม ===== */
const SPECIAL_QUIZ_LEVELS = [1, 2, 3, 4, 5];
const SPECIAL_QUIZ_CASES = [1, 2, 3];
const SPECIAL_QUIZ_CARD_RARITIES = ['common', 'rare', 'legendary'];
function specialQuizSetKey(level, caseId) {
return 'L' + level + '_C' + caseId;
}
/** คำถาม 1 ข้อ — รองรับทั้งจริง/เท็จ (tf) และตัวเลือก A/B/C (mcq) เพื่อขยายภายหลัง */
function sanitizeSpecialQuizQuestion(q) {
if (!q || typeof q !== 'object') return null;
const text = String(q.text == null ? '' : q.text).trim();
if (!text) return null;
const type = q.type === 'mcq' ? 'mcq' : 'tf';
if (type === 'mcq') {
/* รักษาตำแหน่งตัวเลือก (A=0,B=1,...) — ตัดเฉพาะช่องว่างท้ายสุด ไม่ขยับ index คำตอบ */
const choices = (Array.isArray(q.choices) ? q.choices : [])
.map((c) => String(c == null ? '' : c).trim())
.slice(0, 8);
while (choices.length && choices[choices.length - 1] === '') choices.pop();
if (choices.filter((c) => c).length < 2) return null;
let ci = Number(q.correctIndex);
if (!Number.isInteger(ci) || ci < 0 || ci >= choices.length || choices[ci] === '') {
ci = choices.findIndex((c) => c);
if (ci < 0) ci = 0;
}
return { type: 'mcq', text: text.slice(0, 500), choices, correctIndex: ci };
}
return { type: 'tf', text: text.slice(0, 500), answerTrue: !!q.answerTrue };
}
function sanitizeSpecialCard(c) {
const src = (c && typeof c === 'object') ? c : {};
const rarity = SPECIAL_QUIZ_CARD_RARITIES.indexOf(src.rarity) >= 0 ? src.rarity : 'rare';
return {
th: String(src.th == null ? '' : src.th).trim().slice(0, 160),
en: String(src.en == null ? '' : src.en).trim().slice(0, 160),
rarity,
imageUrl: String(src.imageUrl == null ? '' : src.imageUrl).trim().slice(0, 600),
};
}
/** คืน object 15 ชุดเสมอ (ชุดที่ยังไม่ตั้ง = ว่าง) */
function sanitizeSpecialQuizSets(obj) {
const out = {};
SPECIAL_QUIZ_LEVELS.forEach((L) => SPECIAL_QUIZ_CASES.forEach((C) => {
const key = specialQuizSetKey(L, C);
const src = (obj && typeof obj === 'object' && obj[key] && typeof obj[key] === 'object') ? obj[key] : {};
const questions = (Array.isArray(src.questions) ? src.questions : [])
.map(sanitizeSpecialQuizQuestion)
.filter(Boolean)
.slice(0, 50);
out[key] = { questions, specialCard: sanitizeSpecialCard(src.specialCard) };
}));
return out;
}
/** เลือกชุดคำถามตาม level+case (fallback ชุด L1_C1) */
function getSpecialQuizSet(settings, level, caseId) {
const sets = (settings && settings.specialQuizSets) || sanitizeSpecialQuizSets({});
const L = SPECIAL_QUIZ_LEVELS.indexOf(Number(level)) >= 0 ? Number(level) : 1;
const C = SPECIAL_QUIZ_CASES.indexOf(Number(caseId)) >= 0 ? Number(caseId) : 1;
return sets[specialQuizSetKey(L, C)] || sets[specialQuizSetKey(1, 1)] || { questions: [], specialCard: sanitizeSpecialCard({}) };
}
function defaultQuizSettings() {
return {
readMs: 10000,
@@ -314,6 +380,8 @@ function defaultQuizSettings() {
questions: [],
carryQuestions: [],
battleQuizMcq: [],
/** 15 ชุดคำถามพิเศษ (Level×Case) สำหรับรับการ์ดพิเศษ */
specialQuizSets: sanitizeSpecialQuizSets({}),
};
}
@@ -684,6 +752,7 @@ function loadQuizSettings() {
questions,
carryQuestions,
battleQuizMcq,
specialQuizSets: sanitizeSpecialQuizSets(j.specialQuizSets),
};
return mergeQuizCarryFromMirrorIfSet(out);
}
@@ -1489,6 +1558,9 @@ function saveQuizSettings(d) {
const quizRoundQuestionCount = d.quizRoundQuestionCount != null
? clampQuizRoundQuestionCount(d.quizRoundQuestionCount, prev.quizRoundQuestionCount)
: prev.quizRoundQuestionCount;
const specialQuizSets = d.specialQuizSets != null
? sanitizeSpecialQuizSets(d.specialQuizSets)
: sanitizeSpecialQuizSets(prev.specialQuizSets);
const out = {
readMs,
answerMs,
@@ -1508,12 +1580,14 @@ function saveQuizSettings(d) {
questions,
carryQuestions,
battleQuizMcq,
specialQuizSets,
};
fs.writeFileSync(QUIZ_SETTINGS_PATH, JSON.stringify(out, null, 2), 'utf8');
return {
ok: true,
battleQuizMcqSaved: (out.battleQuizMcq || []).length,
carryQuestionsSaved: (out.carryQuestions || []).length,
specialQuizSets: out.specialQuizSets,
carryMapPanelTheme: out.carryMapPanelTheme,
quizMapPanelTheme: out.quizMapPanelTheme,
carryEmbedCountdownTheme: out.carryEmbedCountdownTheme,
@@ -2979,6 +3053,7 @@ const server = http.createServer((req, res) => {
ok: true,
battleQuizMcqSaved: saved.battleQuizMcqSaved,
carryQuestionsSaved: saved.carryQuestionsSaved,
specialQuizSets: saved.specialQuizSets,
carryMapPanelTheme: saved.carryMapPanelTheme,
carryEmbedCountdownTheme: saved.carryEmbedCountdownTheme,
carryChoicePlaqueThemes: saved.carryChoicePlaqueThemes,