update all games
This commit is contained in:
@@ -3969,6 +3969,56 @@
|
||||
});
|
||||
}
|
||||
|
||||
function applyForcedMinigameKeysToForm(keys) {
|
||||
var arr = Array.isArray(keys) ? keys : [];
|
||||
for (var i = 1; i <= 3; i++) {
|
||||
var sel = el('forced-mg-' + i);
|
||||
if (sel) sel.value = arr[i - 1] || '';
|
||||
}
|
||||
}
|
||||
|
||||
function readForcedMinigameKeysFromForm() {
|
||||
var out = [];
|
||||
for (var i = 1; i <= 3; i++) {
|
||||
var sel = el('forced-mg-' + i);
|
||||
out.push(sel ? String(sel.value || '') : '');
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function loadForcedMinigamePanel() {
|
||||
gameTimingFetch('GET')
|
||||
.then(function (data) {
|
||||
applyForcedMinigameKeysToForm(data && data.forcedMinigameKeys);
|
||||
setMsg('forced-minigame-msg', '', '');
|
||||
})
|
||||
.catch(function (e) {
|
||||
setMsg('forced-minigame-msg', e.message || 'โหลดไม่ได้', 'error');
|
||||
});
|
||||
}
|
||||
|
||||
function saveForcedMinigamePanel() {
|
||||
var keys = readForcedMinigameKeysFromForm();
|
||||
var anyForced = keys.some(function (k) { return !!k; });
|
||||
var btn = el('btn-forced-minigame-save');
|
||||
if (btn) btn.disabled = true;
|
||||
gameTimingFetch('GET')
|
||||
.then(function (data) {
|
||||
data.forcedMinigameKeys = keys;
|
||||
return gameTimingFetch('PUT', data);
|
||||
})
|
||||
.then(function (res) {
|
||||
if (res && typeof res === 'object') applyForcedMinigameKeysToForm(res.forcedMinigameKeys);
|
||||
setMsg('forced-minigame-msg', anyForced ? 'บันทึกแล้ว — เริ่มคดีใหม่เพื่อเทสต์การ์ดที่ตั้งไว้' : 'กลับเป็นปกติ (สุ่มทั้ง 3 ใบ) แล้ว', 'ok');
|
||||
})
|
||||
.catch(function (e) {
|
||||
setMsg('forced-minigame-msg', e.message || 'บันทึกไม่ได้', 'error');
|
||||
})
|
||||
.then(function () {
|
||||
if (btn) btn.disabled = false;
|
||||
});
|
||||
}
|
||||
|
||||
function saveJumpSurviveTimingPanel() {
|
||||
var inp = el('jump-survive-height-mult');
|
||||
var jumpSurvMult = inp ? parseFloat(String(inp.value)) : 1.5;
|
||||
@@ -4487,6 +4537,7 @@
|
||||
if (name === 'game-timing') loadGameTimingPanel();
|
||||
if (name === 'stack-game') loadStackGamePanel();
|
||||
if (name === 'highscore') loadHighscore();
|
||||
if (name === 'test-mode') loadForcedMinigamePanel();
|
||||
}
|
||||
|
||||
function boot() {
|
||||
@@ -4710,6 +4761,8 @@
|
||||
syncTestModeUi();
|
||||
});
|
||||
}
|
||||
var forcedMgSaveBtn = el('btn-forced-minigame-save');
|
||||
if (forcedMgSaveBtn) forcedMgSaveBtn.addEventListener('click', saveForcedMinigamePanel);
|
||||
syncTestModeUi();
|
||||
/* ฟัง storage event — sync เมื่อเปิดหลายแท็บ */
|
||||
window.addEventListener('storage', function (e) {
|
||||
|
||||
@@ -1112,6 +1112,49 @@
|
||||
</ul>
|
||||
|
||||
<p id="test-mode-msg" class="msg" role="status" style="margin-top:14px;"></p>
|
||||
|
||||
<h3 class="admin-subheading" style="margin-top:28px;">บังคับมินิเกม (เทสต์ตอนเล่นจริง)</h3>
|
||||
<p class="muted">เลือกเกมต่อการ์ดผู้ต้องสงสัย 3 ใบ → บันทึก → เริ่มคดีใหม่ (สร้างห้อง → LobbyA → เลือกคดี → LobbyB) · ช่องที่เลือก "— สุ่ม —" จะสุ่มจาก 7 เกมเหมือนเดิม · เลือก "— สุ่ม —" ครบทั้ง 3 ช่อง = กลับเป็นปกติ</p>
|
||||
<div class="row" style="display:flex;align-items:flex-end;gap:14px;margin:12px 0;flex-wrap:wrap;">
|
||||
<label style="display:flex;flex-direction:column;gap:4px;font-size:13px;color:#cfd6ea;">การ์ดใบที่ 1
|
||||
<select id="forced-mg-1" style="padding:8px 10px;min-width:300px;font-size:15px;">
|
||||
<option value="">— สุ่ม —</option>
|
||||
<option value="quiz">Minigame-1 จริงหรือไม่</option>
|
||||
<option value="gauntlet">Minigame-2 วิ่งหลบสิ่งกีดขวาง</option>
|
||||
<option value="stack">Minigame-3 Tower block</option>
|
||||
<option value="quiz_carry">Minigame-4 คำศัพท์ในกระบวนการยุติธรรม</option>
|
||||
<option value="jump_survive">Minigame-5 กระโดดขึ้นแท่น</option>
|
||||
<option value="space_shooter">Minigame-6 ยิงอุกาบาต Violent Crime</option>
|
||||
<option value="balloon_boss">Minigame-7 ยิง Mega Virus</option>
|
||||
</select>
|
||||
</label>
|
||||
<label style="display:flex;flex-direction:column;gap:4px;font-size:13px;color:#cfd6ea;">การ์ดใบที่ 2
|
||||
<select id="forced-mg-2" style="padding:8px 10px;min-width:300px;font-size:15px;">
|
||||
<option value="">— สุ่ม —</option>
|
||||
<option value="quiz">Minigame-1 จริงหรือไม่</option>
|
||||
<option value="gauntlet">Minigame-2 วิ่งหลบสิ่งกีดขวาง</option>
|
||||
<option value="stack">Minigame-3 Tower block</option>
|
||||
<option value="quiz_carry">Minigame-4 คำศัพท์ในกระบวนการยุติธรรม</option>
|
||||
<option value="jump_survive">Minigame-5 กระโดดขึ้นแท่น</option>
|
||||
<option value="space_shooter">Minigame-6 ยิงอุกาบาต Violent Crime</option>
|
||||
<option value="balloon_boss">Minigame-7 ยิง Mega Virus</option>
|
||||
</select>
|
||||
</label>
|
||||
<label style="display:flex;flex-direction:column;gap:4px;font-size:13px;color:#cfd6ea;">การ์ดใบที่ 3
|
||||
<select id="forced-mg-3" style="padding:8px 10px;min-width:300px;font-size:15px;">
|
||||
<option value="">— สุ่ม —</option>
|
||||
<option value="quiz">Minigame-1 จริงหรือไม่</option>
|
||||
<option value="gauntlet">Minigame-2 วิ่งหลบสิ่งกีดขวาง</option>
|
||||
<option value="stack">Minigame-3 Tower block</option>
|
||||
<option value="quiz_carry">Minigame-4 คำศัพท์ในกระบวนการยุติธรรม</option>
|
||||
<option value="jump_survive">Minigame-5 กระโดดขึ้นแท่น</option>
|
||||
<option value="space_shooter">Minigame-6 ยิงอุกาบาต Violent Crime</option>
|
||||
<option value="balloon_boss">Minigame-7 ยิง Mega Virus</option>
|
||||
</select>
|
||||
</label>
|
||||
<button type="button" class="btn" id="btn-forced-minigame-save">บันทึก</button>
|
||||
</div>
|
||||
<p id="forced-minigame-msg" class="msg" role="status"></p>
|
||||
</section>
|
||||
</div><!-- /.admin-content -->
|
||||
</div>
|
||||
|
||||
@@ -77,14 +77,20 @@
|
||||
{
|
||||
"id": "d1d5c39d42a2dce9a4949d5a",
|
||||
"email": "",
|
||||
"displayName": "Guest",
|
||||
"displayName": "ผู้เล่น",
|
||||
"loginType": "guest",
|
||||
"providerUserId": "p_1777015659667_xqfjh247bgp",
|
||||
"notes": "auto: player-coins",
|
||||
"blocked": false,
|
||||
"coins": 0,
|
||||
"coins": 90,
|
||||
"createdAt": "2026-04-24T07:28:02+00:00",
|
||||
"updatedAt": "2026-04-24T07:28:02+00:00"
|
||||
"updatedAt": "2026-06-14T04:39:10+00:00",
|
||||
"score": 90,
|
||||
"scoreByCase": {
|
||||
"8": 60,
|
||||
"5": 30
|
||||
},
|
||||
"lbName": "ผู้เล่น"
|
||||
},
|
||||
{
|
||||
"id": "d666c2fbf6005b69ee192882",
|
||||
|
||||
@@ -87,5 +87,10 @@
|
||||
"/Game/img/MegaVirus/balloon-6.png"
|
||||
],
|
||||
"balloonBossPlayerBalloonFallbackUrl": "/Game/img/MegaVirus/Artboard%209.png",
|
||||
"balloonBossBalloonsPerPlayer": 3
|
||||
"balloonBossBalloonsPerPlayer": 3,
|
||||
"forcedMinigameKeys": [
|
||||
"quiz",
|
||||
"quiz_carry",
|
||||
"balloon_boss"
|
||||
]
|
||||
}
|
||||
@@ -47,6 +47,7 @@
|
||||
<button type="button" role="tab" id="tab-shooter" aria-selected="false" aria-controls="panel-shooter" data-panel="panel-shooter">ยิงอุกาบาต · Violent Crime</button>
|
||||
<button type="button" role="tab" id="tab-gauntlet" aria-selected="false" aria-controls="panel-gauntlet" data-panel="panel-gauntlet">พรมแดง · tick & กระโดด</button>
|
||||
<button type="button" role="tab" id="tab-stack" aria-selected="false" aria-controls="panel-stack" data-panel="panel-stack">Stack · สวิง</button>
|
||||
<button type="button" role="tab" id="tab-test" aria-selected="false" aria-controls="panel-test" data-panel="panel-test">เทสต์ · บังคับมินิเกม</button>
|
||||
</nav>
|
||||
|
||||
<div id="panel-jump" class="panel active" role="tabpanel" aria-labelledby="tab-jump">
|
||||
@@ -100,6 +101,50 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="panel-test" class="panel" role="tabpanel" aria-labelledby="tab-test" hidden>
|
||||
<h2>เทสต์ — บังคับมินิเกมที่ออก</h2>
|
||||
<p class="hint">เลือกเกมต่อการ์ดผู้ต้องสงสัย 3 ใบ — ช่องที่เลือก "— สุ่ม —" จะสุ่มจาก 7 เกมเหมือนเดิม · ตั้งครบแล้วเริ่มคดีใหม่ (LobbyA→LobbyB) เพื่อเทสต์ตอนเล่นจริง</p>
|
||||
<div class="row">
|
||||
<label for="forcedMg1">การ์ดใบที่ 1</label>
|
||||
<select id="forcedMg1">
|
||||
<option value="">— สุ่ม —</option>
|
||||
<option value="quiz">Minigame-1 จริงหรือไม่</option>
|
||||
<option value="gauntlet">Minigame-2 วิ่งหลบสิ่งกีดขวาง</option>
|
||||
<option value="stack">Minigame-3 Tower block</option>
|
||||
<option value="quiz_carry">Minigame-4 คำศัพท์ในกระบวนการยุติธรรม</option>
|
||||
<option value="jump_survive">Minigame-5 กระโดดขึ้นแท่น</option>
|
||||
<option value="space_shooter">Minigame-6 ยิงอุกาบาต Violent Crime</option>
|
||||
<option value="balloon_boss">Minigame-7 ยิง Mega Virus</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label for="forcedMg2">การ์ดใบที่ 2</label>
|
||||
<select id="forcedMg2">
|
||||
<option value="">— สุ่ม —</option>
|
||||
<option value="quiz">Minigame-1 จริงหรือไม่</option>
|
||||
<option value="gauntlet">Minigame-2 วิ่งหลบสิ่งกีดขวาง</option>
|
||||
<option value="stack">Minigame-3 Tower block</option>
|
||||
<option value="quiz_carry">Minigame-4 คำศัพท์ในกระบวนการยุติธรรม</option>
|
||||
<option value="jump_survive">Minigame-5 กระโดดขึ้นแท่น</option>
|
||||
<option value="space_shooter">Minigame-6 ยิงอุกาบาต Violent Crime</option>
|
||||
<option value="balloon_boss">Minigame-7 ยิง Mega Virus</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label for="forcedMg3">การ์ดใบที่ 3</label>
|
||||
<select id="forcedMg3">
|
||||
<option value="">— สุ่ม —</option>
|
||||
<option value="quiz">Minigame-1 จริงหรือไม่</option>
|
||||
<option value="gauntlet">Minigame-2 วิ่งหลบสิ่งกีดขวาง</option>
|
||||
<option value="stack">Minigame-3 Tower block</option>
|
||||
<option value="quiz_carry">Minigame-4 คำศัพท์ในกระบวนการยุติธรรม</option>
|
||||
<option value="jump_survive">Minigame-5 กระโดดขึ้นแท่น</option>
|
||||
<option value="space_shooter">Minigame-6 ยิงอุกาบาต Violent Crime</option>
|
||||
<option value="balloon_boss">Minigame-7 ยิง Mega Virus</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button type="button" class="primary" id="btn-save">บันทึกทั้งหมด</button>
|
||||
</div>
|
||||
@@ -115,6 +160,7 @@
|
||||
'panel-shooter': document.getElementById('panel-shooter'),
|
||||
'panel-gauntlet': document.getElementById('panel-gauntlet'),
|
||||
'panel-stack': document.getElementById('panel-stack'),
|
||||
'panel-test': document.getElementById('panel-test'),
|
||||
};
|
||||
tabs.forEach((btn) => {
|
||||
btn.addEventListener('click', () => {
|
||||
@@ -145,6 +191,11 @@
|
||||
document.getElementById('stackSwingHz').value = timing.stackSwingHz != null ? timing.stackSwingHz : 0.1;
|
||||
const sb = document.getElementById('stackBlockWidthTiles');
|
||||
sb.value = timing.stackBlockWidthTiles != null && timing.stackBlockWidthTiles !== '' ? timing.stackBlockWidthTiles : '';
|
||||
const fks = Array.isArray(timing.forcedMinigameKeys) ? timing.forcedMinigameKeys : [];
|
||||
for (let i = 1; i <= 3; i++) {
|
||||
const s = document.getElementById('forcedMg' + i);
|
||||
if (s) s.value = fks[i - 1] || '';
|
||||
}
|
||||
}
|
||||
|
||||
function readForm() {
|
||||
@@ -168,6 +219,10 @@
|
||||
const v = document.getElementById('stackBlockWidthTiles').value.trim();
|
||||
return v === '' ? null : parseFloat(v, 10);
|
||||
})(),
|
||||
forcedMinigameKeys: [1, 2, 3].map(function (i) {
|
||||
const s = document.getElementById('forcedMg' + i);
|
||||
return s ? (s.value || '') : '';
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -8220,6 +8220,22 @@
|
||||
return lines;
|
||||
}
|
||||
|
||||
/** ตัดบรรทัดระดับตัวอักษรให้แต่ละบรรทัดพอดีกว้าง (รองรับคำไทยยาวไม่มีช่องว่าง) — คืนทุกบรรทัด */
|
||||
function quizCarryWrapLinesFit(ctx, text, maxWidth) {
|
||||
const raw = String(text || '').trim();
|
||||
if (!raw) return [];
|
||||
const lines = [];
|
||||
let cur = '';
|
||||
for (const chr of raw) {
|
||||
if (chr === '\n') { lines.push(cur); cur = ''; continue; }
|
||||
const test = cur + chr;
|
||||
if (cur === '' || ctx.measureText(test).width <= maxWidth) cur = test;
|
||||
else { lines.push(cur); cur = chr; }
|
||||
}
|
||||
if (cur) lines.push(cur);
|
||||
return lines;
|
||||
}
|
||||
|
||||
function quizCarryOptionHeldByAnyone(optionIdx) {
|
||||
if (optionIdx == null || optionIdx < 0) return false;
|
||||
if (me.quizCarryHeld === optionIdx) return true;
|
||||
@@ -15931,28 +15947,17 @@
|
||||
if (o.balloonBossBotNextFire <= 0) {
|
||||
o.balloonBossBotNextFire = 0.9 + Math.random() * 1.1;
|
||||
const bspd = 500 + Math.random() * 90;
|
||||
let bvx;
|
||||
let bvy;
|
||||
let bdelay;
|
||||
if (isMegaVirusMissionShellMapPlay()) {
|
||||
/** Mega Virus: บอทยิงตามมุม "ลูกศรวงกลม" ของตัวเอง (ทันที) เหมือนผู้เล่น — ไม่ auto-aim ใส่บอส */
|
||||
const aim = (typeof o.balloonBossAimRad === 'number' && Number.isFinite(o.balloonBossAimRad))
|
||||
? o.balloonBossAimRad
|
||||
: Math.atan2(bossCy - o.balloonBossCy, bossCx - o.balloonBossCx);
|
||||
bvx = Math.cos(aim) * bspd;
|
||||
bvy = Math.sin(aim) * bspd;
|
||||
bdelay = 0;
|
||||
} else {
|
||||
const dx = bossCx - o.balloonBossCx;
|
||||
const dy = bossCy - o.balloonBossCy;
|
||||
const L = Math.hypot(dx, dy) || 1;
|
||||
bvx = (dx / L) * bspd;
|
||||
bvy = (dy / L) * bspd;
|
||||
bdelay = balloonBossFireDelayMsPlay();
|
||||
}
|
||||
/** บอทยิงตามมุม "ลูกศรวงกลม" ของตัวเอง (ทันที) เหมือนผู้เล่น — ลูกศรหมุนตลอดทั้งทดสอบ/เล่นจริง ไม่ auto-aim ใส่บอส */
|
||||
const aim = (typeof o.balloonBossAimRad === 'number' && Number.isFinite(o.balloonBossAimRad))
|
||||
? o.balloonBossAimRad
|
||||
: Math.atan2(bossCy - o.balloonBossCy, bossCx - o.balloonBossCx);
|
||||
const bvx = Math.cos(aim) * bspd;
|
||||
const bvy = Math.sin(aim) * bspd;
|
||||
/** ยิงจากปลายลูกศรเหมือนผู้เล่น */
|
||||
balloonBossPendingShots.push({
|
||||
releaseAt: performance.now() + bdelay,
|
||||
sx: o.balloonBossCx, sy: o.balloonBossCy,
|
||||
releaseAt: performance.now(),
|
||||
sx: o.balloonBossCx + Math.cos(aim) * 26,
|
||||
sy: o.balloonBossCy - 10.5 + Math.sin(aim) * 26,
|
||||
vx: bvx,
|
||||
vy: bvy,
|
||||
ownerId: bid,
|
||||
@@ -16214,17 +16219,19 @@
|
||||
if (wantFire && balloonBossPlayerFireCd <= 0 && me.balloonBossCy != null) {
|
||||
balloonBossPlayerFireCd = 0.35;
|
||||
/** ยิงตามมุม "ลูกศรวงกลม" เสมอ (me.balloonBossAimRad อัปเดตทุกเฟรม) — fallback เล็งบอสเฉพาะตอนมุมยังไม่พร้อม */
|
||||
/** Mega Virus: ยิงทันที (delay 0) ไม่งั้นลูกศรหมุนต่อระหว่างหน่วง → กระสุนโผล่ไม่ตรงลูกศร */
|
||||
const delayMs = isMegaVirusMissionShellMapPlay() ? 0 : balloonBossFireDelayMsPlay();
|
||||
/** ลูกศรหมุนตลอดสำหรับ balloon_boss → ยิงทันที (delay 0) ทุกกรณี ทั้งทดสอบ/เล่นจริง ไม่งั้นกระสุนโผล่ไม่ตรงลูกศร */
|
||||
const delayMs = 0;
|
||||
const bspd = 480;
|
||||
const aim = (typeof me.balloonBossAimRad === 'number' && Number.isFinite(me.balloonBossAimRad))
|
||||
? me.balloonBossAimRad
|
||||
: Math.atan2(bossC.cy - me.balloonBossCy, bossC.cx - me.balloonBossCx);
|
||||
const vx = Math.cos(aim) * bspd;
|
||||
const vy = Math.sin(aim) * bspd;
|
||||
/** ยิงออกจาก "ปลายลูกศร" ไม่ใช่กลางตัว — วงอยู่เหนือตัว ~10.5px (world) รัศมีปลายหาง ~26px */
|
||||
balloonBossPendingShots.push({
|
||||
releaseAt: now + delayMs,
|
||||
sx: me.balloonBossCx, sy: me.balloonBossCy,
|
||||
sx: me.balloonBossCx + Math.cos(aim) * 26,
|
||||
sy: me.balloonBossCy - 10.5 + Math.sin(aim) * 26,
|
||||
vx,
|
||||
vy,
|
||||
ownerId: myId,
|
||||
@@ -18499,6 +18506,30 @@
|
||||
}
|
||||
});
|
||||
}
|
||||
/** เกมจริง: bot ไม่อยู่ในผล server → เช็คโซนยืนเอง ผิด = เป็นผี (เหมือน path preview) */
|
||||
if (isQuiz() && Array.isArray(r.results) && playBotsEnabled()) {
|
||||
const correctTrueBot = !!r.correctTrue;
|
||||
others.forEach(function (o, id) {
|
||||
if (!isPreviewBotId(id) || !o) return;
|
||||
const br = quizResolveChoiceFromStanding(o.x, o.y, correctTrueBot);
|
||||
o.botPath = [];
|
||||
o.botAnswerWander = false;
|
||||
if (br.right) {
|
||||
playLiveQuizScores[id] = (playLiveQuizScores[id] || 0) + QUIZ_TF_POINTS_PER_CORRECT;
|
||||
} else {
|
||||
playQuizEverWrong[String(id)] = true;
|
||||
o.quizCannotTrue = true;
|
||||
o.quizCannotFalse = true;
|
||||
const ordB = quizPreviewJoinOrderForRespawn(id);
|
||||
const sp = pickSpawnForJoinPlay(mapData, ordB);
|
||||
const pos = findNearestOutsideQuizAnswerZonesPlay(mapData, sp.x + 0.5, sp.y + 0.5);
|
||||
o.x = pos.x + (Math.random() - 0.5) * 0.35;
|
||||
o.y = pos.y + (Math.random() - 0.5) * 0.35;
|
||||
o.tx = o.x;
|
||||
o.ty = o.y;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (isQuiz() && Array.isArray(r.results) && r.results.some(function (row) { return row && row.right; })) {
|
||||
spawnQuizTrueFalseScorePopupPlay(!!r.correctTrue);
|
||||
}
|
||||
@@ -22069,24 +22100,31 @@
|
||||
const baseMaxW = Math.min(280, Math.max(100, halfW * 2.4 + tileSize * zDraw * 0.5));
|
||||
const maxPlaqueW = Math.min(340, Math.round(baseMaxW * heldPs));
|
||||
|
||||
const fontPx = Math.max(12, Math.min(22, tileSize * zDraw * 0.22 * heldPs));
|
||||
ctx.font = `600 ${fontPx}px system-ui, "Segoe UI", "Kanit", sans-serif`;
|
||||
const padX = 12;
|
||||
const padY = 9;
|
||||
const maxInner = Math.min(maxPlaqueW - padX * 2, Math.max(72, tileSize * zDraw * 4.2));
|
||||
const MAX_LINES = 3;
|
||||
const MIN_FONT = 8;
|
||||
let fontPx = Math.max(12, Math.min(22, tileSize * zDraw * 0.22 * heldPs));
|
||||
let lines;
|
||||
if (!raw) {
|
||||
lines = [];
|
||||
ctx.font = `600 ${fontPx}px system-ui, "Segoe UI", "Kanit", sans-serif`;
|
||||
} else {
|
||||
lines = canvasWordWrapLines(ctx, raw, maxInner);
|
||||
if (!lines.length) lines = [raw];
|
||||
if (lines.length > 3) lines = lines.slice(0, 3);
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
let s = lines[i];
|
||||
if (ctx.measureText(s).width <= maxInner) continue;
|
||||
while (s.length > 2 && ctx.measureText(s + '…').width > maxInner) s = s.slice(0, -1);
|
||||
lines[i] = s + '…';
|
||||
/* ย่อฟอนต์จนข้อความพอดีกรอบ (คำไทยยาวไม่มีช่องว่าง → char-wrap + ย่อ ไม่ตัด/ล้นกรอบ) */
|
||||
for (;;) {
|
||||
ctx.font = `600 ${fontPx}px system-ui, "Segoe UI", "Kanit", sans-serif`;
|
||||
lines = quizCarryWrapLinesFit(ctx, raw, maxInner);
|
||||
if (lines.length <= MAX_LINES || fontPx <= MIN_FONT) break;
|
||||
fontPx -= 1;
|
||||
}
|
||||
if (lines.length > MAX_LINES) {
|
||||
lines = lines.slice(0, MAX_LINES);
|
||||
let s = lines[MAX_LINES - 1];
|
||||
while (s.length > 1 && ctx.measureText(s + '…').width > maxInner) s = s.slice(0, -1);
|
||||
lines[MAX_LINES - 1] = s + '…';
|
||||
}
|
||||
if (!lines.length) lines = [raw];
|
||||
}
|
||||
let maxLineW = 0;
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
|
||||
@@ -5180,7 +5180,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.006131806"></script>
|
||||
<script src="js/play.js?v=0.006131808"></script>
|
||||
<div class="version-tag">v —</div>
|
||||
<style id="qc-howto-mg2-fix">
|
||||
/* HOW TO PLAY ของ quiz_carry (Minigame-4) -> ให้เหมือน Minigame-2 (gch-mg2-mock)
|
||||
|
||||
+34
-2
@@ -26,6 +26,18 @@ const DETECTIVE_MINIGAME_POOL = [
|
||||
{ key: 'space_shooter', mapId: 'mnpz6rkp', labelTh: 'Minigame-6 ยิงอุกาบาต Violent Crime' },
|
||||
{ key: 'balloon_boss', mapId: 'mnq1eml7', labelTh: 'Minigame-7 ยิง Mega Virus' },
|
||||
];
|
||||
/** โหมดเทสต์: บังคับเกมต่อการ์ด 3 ใบ — array[3], แต่ละช่อง '' = สุ่ม หรือ key จาก DETECTIVE_MINIGAME_POOL · รับ array หรือ key เดี่ยว (เก่า) */
|
||||
function normalizeForcedMinigameKeys(v) {
|
||||
let arr;
|
||||
if (Array.isArray(v)) arr = v;
|
||||
else if (v != null && String(v).trim()) arr = [v, v, v];
|
||||
else arr = [];
|
||||
const one = (k) => {
|
||||
const s = (k == null ? '' : String(k)).trim();
|
||||
return s && DETECTIVE_MINIGAME_POOL.some((e) => e.key === s) ? s : '';
|
||||
};
|
||||
return [one(arr[0]), one(arr[1]), one(arr[2])];
|
||||
}
|
||||
const LOBBY_SLOT_TOTAL = 6;
|
||||
const LOBBY_THEME_COUNT = 8;
|
||||
const LOBBY_SKIN_COUNT = 3;
|
||||
@@ -1805,6 +1817,8 @@ function defaultGameTiming() {
|
||||
balloonBossPlayerBalloonFallbackUrl: '/Game/img/MegaVirus/Artboard 9.png',
|
||||
/** จำนวนลูกโป่งต่อคน (0 = ใช้ค่าจากแมป หรือ default 3) */
|
||||
balloonBossBalloonsPerPlayer: 0,
|
||||
/** โหมดเทสต์: เกมบังคับต่อการ์ด 3 ใบ — ['','',''] = สุ่มหมด, แต่ละช่องใส่ key เพื่อเจาะจงใบนั้น */
|
||||
forcedMinigameKeys: ['', '', ''],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2189,6 +2203,9 @@ function loadGameTiming() {
|
||||
balloonBossBalloonsPerPlayer: Object.prototype.hasOwnProperty.call(j, 'balloonBossBalloonsPerPlayer')
|
||||
? Math.max(0, Math.min(12, Math.floor(Number(j.balloonBossBalloonsPerPlayer)) || 0))
|
||||
: 0,
|
||||
forcedMinigameKeys: normalizeForcedMinigameKeys(
|
||||
j.forcedMinigameKeys != null ? j.forcedMinigameKeys : j.forcedMinigameKey,
|
||||
),
|
||||
};
|
||||
}
|
||||
} catch (e) { console.error('loadGameTiming', e.message); }
|
||||
@@ -2394,6 +2411,11 @@ function saveGameTimingToFile(d) {
|
||||
balloonBossPlayerBalloonImageUrls,
|
||||
balloonBossPlayerBalloonFallbackUrl,
|
||||
balloonBossBalloonsPerPlayer,
|
||||
forcedMinigameKeys: Object.prototype.hasOwnProperty.call(d, 'forcedMinigameKeys')
|
||||
? normalizeForcedMinigameKeys(d.forcedMinigameKeys)
|
||||
: (Object.prototype.hasOwnProperty.call(d, 'forcedMinigameKey')
|
||||
? normalizeForcedMinigameKeys(d.forcedMinigameKey)
|
||||
: normalizeForcedMinigameKeys(prev.forcedMinigameKeys)),
|
||||
};
|
||||
fs.writeFileSync(GAME_TIMING_PATH, JSON.stringify(out, null, 2), 'utf8');
|
||||
runtimeGameTiming = out;
|
||||
@@ -2930,8 +2952,18 @@ function normalizeDetectiveCardEntry(entry) {
|
||||
|
||||
function assignDetectiveSuspectCardMinigames(space) {
|
||||
const available = DETECTIVE_MINIGAME_POOL.filter((e) => maps.has(e.mapId));
|
||||
const shuffled = shuffleQuizQuestions(available.length ? available : [{ key: 'quiz', mapId: SUSPECT_INVESTIGATION_QUIZ_MAP_ID, labelTh: 'QuestionGame' }]);
|
||||
space.suspectCardMinigames = shuffled.slice(0, 3).map((entry, i) => {
|
||||
/** โหมดเทสต์ (admin game-timing): เลือกเกมต่อการ์ด 3 ใบ — ช่องที่ตั้ง key = เจาะจง, ช่องว่าง = สุ่ม */
|
||||
const forcedKeys = normalizeForcedMinigameKeys(runtimeGameTiming && runtimeGameTiming.forcedMinigameKeys);
|
||||
const randomPool = shuffleQuizQuestions(available.length ? available : [{ key: 'quiz', mapId: SUSPECT_INVESTIGATION_QUIZ_MAP_ID, labelTh: 'QuestionGame' }]);
|
||||
let ri = 0;
|
||||
const pick3 = [];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const fk = forcedKeys[i];
|
||||
let entry = fk ? available.find((e) => e.key === fk) : null;
|
||||
if (!entry) { entry = randomPool[ri % randomPool.length]; ri += 1; }
|
||||
pick3.push(entry);
|
||||
}
|
||||
space.suspectCardMinigames = pick3.map((entry, i) => {
|
||||
const md = maps.get(entry.mapId);
|
||||
return {
|
||||
cardIndex: i,
|
||||
|
||||
Reference in New Issue
Block a user