update mg quest and mobile some path
This commit is contained in:
+124
-12
@@ -2078,14 +2078,34 @@ function evidencePoolCounts() {
|
||||
return out;
|
||||
}
|
||||
|
||||
/* แปลงเกรด -> rarity key ('legendary'=ชี้คนร้าย/'rare'/'common') ตามเปอร์เซ็นต์ที่กำหนด */
|
||||
function rollEvidenceRarityKey(grade) {
|
||||
/* แปลงเกรด -> rarity key ('legendary'=ชี้คนร้าย/'rare'/'common') ตามเปอร์เซ็นต์ที่กำหนด
|
||||
[2026-07-31] allowLegendary=false → ตัดทองออกแล้ว "เกลี่ยสัดส่วนที่เหลือตามเดิม"
|
||||
(A: rare:common = 50:20 → 5/7 : 2/7 · B: 30:50 → 3/8 : 5/8) ไม่ใช่ยัดไปทางใดทางหนึ่ง */
|
||||
function rollEvidenceRarityKey(grade, allowLegendary) {
|
||||
const r = Math.random();
|
||||
if (grade === 'A') return r < 0.30 ? 'legendary' : (r < 0.80 ? 'rare' : 'common');
|
||||
if (grade === 'B') return r < 0.20 ? 'legendary' : (r < 0.50 ? 'rare' : 'common');
|
||||
const allow = allowLegendary !== false;
|
||||
if (grade === 'A') {
|
||||
if (allow) return r < 0.30 ? 'legendary' : (r < 0.80 ? 'rare' : 'common');
|
||||
return r < (5 / 7) ? 'rare' : 'common';
|
||||
}
|
||||
if (grade === 'B') {
|
||||
if (allow) return r < 0.20 ? 'legendary' : (r < 0.50 ? 'rare' : 'common');
|
||||
return r < (3 / 8) ? 'rare' : 'common';
|
||||
}
|
||||
return r < 0.20 ? 'rare' : 'common'; // C ลงไป: ไม่มีชี้คนร้าย
|
||||
}
|
||||
|
||||
/** [2026-07-31] เงื่อนไขของการ์ด "ทอง" (legendary = ชี้ชัดตัวคนร้าย) — ต้องผ่านครบทั้ง 2 ข้อ
|
||||
* 1) แฟ้มที่กำลังสืบต้องเป็น "คนร้ายตัวจริง" — ทองในแฟ้มคนบริสุทธิ์ = หลักฐานชี้ผิดคน
|
||||
* 2) ทั้งรอบมินิเกมออกได้ใบเดียว ไม่ว่าจะมีผู้เล่นกี่คน (รวมการ์ดพิเศษใบ 7 หลักฐานฟรีด้วย)
|
||||
* ธงข้อ 2 อยู่บน space จึงคุมทุกเส้นทางแจกในรอบเดียวกันโดยอัตโนมัติ */
|
||||
function legendaryAllowedForRun(space, suspectIndex) {
|
||||
if (!space) return false;
|
||||
if (space.legendaryGrantedThisRun) return false;
|
||||
const culprit = (typeof space.culpritIndex === 'number') ? space.culpritIndex : -1;
|
||||
return culprit >= 0 && Number(suspectIndex) === culprit;
|
||||
}
|
||||
|
||||
/* โฟลเดอร์รูปของผู้ต้องสงสัยตาม config คดี (fallback = suspect-(idx+1)) */
|
||||
function evidenceImageDirForSuspect(space, suspectIndex) {
|
||||
const def = EVIDENCE_IMAGE_DIRS[suspectIndex] || EVIDENCE_IMAGE_DIRS[0];
|
||||
@@ -2104,10 +2124,12 @@ function pickEvidenceCardForSuspect(space, suspectIndex, grade) {
|
||||
const imageDir = evidenceImageDirForSuspect(space, suspectIndex);
|
||||
const pools = evidenceImagePools();
|
||||
const buckets = pools[imageDir] || pools[EVIDENCE_IMAGE_DIRS[0]] || { common: [], rare: [], legendary: [] };
|
||||
let want = rollEvidenceRarityKey(grade || 'C');
|
||||
const allowLegendary = legendaryAllowedForRun(space, suspectIndex);
|
||||
let want = rollEvidenceRarityKey(grade || 'C', allowLegendary);
|
||||
let pool = buckets[want];
|
||||
if (!pool || !pool.length) {
|
||||
const tryOrder = ['common', 'rare', 'legendary'];
|
||||
/* fallback ตอนพูลว่าง — ถ้าทองยังไม่ได้รับอนุญาต ห้ามหล่นมาลงทองแทน */
|
||||
const tryOrder = allowLegendary ? ['common', 'rare', 'legendary'] : ['common', 'rare'];
|
||||
for (const k of tryOrder) { if (buckets[k] && buckets[k].length) { pool = buckets[k]; want = k; break; } }
|
||||
}
|
||||
if (!pool || !pool.length) return null;
|
||||
@@ -2119,11 +2141,14 @@ function evidenceGradeFromPct(avgPct) {
|
||||
return avgPct >= 80 ? 'A' : avgPct >= 60 ? 'B' : 'C';
|
||||
}
|
||||
|
||||
/* คะแนนดิบของผู้เล่นต่อ gameType (ใช้กรณี normalize เทียบ best) */
|
||||
/* คะแนนดิบของผู้เล่นต่อ gameType (ใช้กรณี normalize เทียบ best)
|
||||
[2026-07-31] MG5 เดิมอ่าน p.gauntletScore ซึ่ง jump_survive ไม่เคยเขียน (เกมเขียนลง
|
||||
p.reportedMiniScore = รอด 100 / ตาย 0 ที่ server.js:9686) → ได้ 0 ทุกคน = เกรด C ตลอด
|
||||
แก้ให้อ่านฟิลด์ที่เกมเขียนจริง → เกรดกลายเป็น "% คนที่รอด" ซึ่งตรงกับธรรมชาติเกมเอาชีวิตรอด */
|
||||
function evidencePlayerRawScore(space, p, gameType) {
|
||||
switch (gameType) {
|
||||
case 'gauntlet':
|
||||
case 'jump_survive': return Math.max(0, p.gauntletScore | 0);
|
||||
case 'gauntlet': return Math.max(0, p.gauntletScore | 0);
|
||||
case 'jump_survive': return Math.max(0, p.reportedMiniScore | 0);
|
||||
case 'space_shooter': return Math.max(0, p.spaceShooterScore | 0);
|
||||
case 'balloon_boss': return Math.max(0, p.balloonBossScore | 0);
|
||||
default:
|
||||
@@ -2149,6 +2174,27 @@ function computeRunGradeForEvidence(space) {
|
||||
return evidenceGradeFromPct(avgPct);
|
||||
}
|
||||
}
|
||||
/* [2026-07-31] MG4 คำศัพท์ — % สัมบูรณ์แบบเดียวกับ MG1 (เป็นเกมตอบคำถามเหมือนกัน วัดด้วยไม้บรรทัดเดียวกัน)
|
||||
เดิมตกไปเข้าสาขา default ที่อ่าน gauntlet/spaceShooter/balloonBossScore ซึ่ง MG4 ไม่เคยเขียน → C ตลอด */
|
||||
if (gameType === 'quiz_carry' && space.quizCarrySession && space.quizCarrySession.players) {
|
||||
const len = Math.max(0, Number(space.quizCarrySession.sessionLength) || 0);
|
||||
const total = len * QUIZ_CARRY_POINTS_PER_CORRECT;
|
||||
const ids = Object.keys(space.quizCarrySession.players);
|
||||
if (total > 0 && ids.length) {
|
||||
const avgPct = ids.reduce((s, id) => {
|
||||
const sc = Math.max(0, Number(space.quizCarrySession.players[id].score) || 0);
|
||||
return s + Math.min(100, (sc / total) * 100);
|
||||
}, 0) / ids.length;
|
||||
return evidenceGradeFromPct(avgPct);
|
||||
}
|
||||
}
|
||||
/* [2026-07-31] MG3 Tower block — เป็นเกม "ทีม" (ตึกใบเดียว หัวใจกองกลาง) การเทียบกับคนเก่งสุด
|
||||
ในห้องไม่มีความหมาย → ใช้ความคืบหน้าถอดรหัสของทีมเป็นเกรดสัมบูรณ์ (แนวเดียวกับ MG7)
|
||||
เดิมตกไปสาขา default ที่อ่านฟิลด์ที่ MG3 ไม่เคยเขียน (เกมเขียนลง p.reportedMiniScore) → C ตลอด */
|
||||
if (gameType === 'stack' && space.stackSession) {
|
||||
const pct = Math.max(0, Math.min(100, Number(space.stackSession.progressPct) || 0));
|
||||
return evidenceGradeFromPct(pct);
|
||||
}
|
||||
/* Last Light (crown gauntlet): ใช้เกรดเดียวกับ mission payload (รวมบอท) — แหล่งเดียวกับที่แสดง
|
||||
F (ไม่มีผู้รอด) ไม่มี tier ในหลักฐาน → map เป็น C */
|
||||
if (gameType === 'gauntlet' && isGauntletCrownHeistMapBySpace(space) && space.lastCrownMissionGrade) {
|
||||
@@ -4219,11 +4265,56 @@ function awardRandomEvidenceCardToPlayer(space, playerId, suspectIndex, grade) {
|
||||
return null;
|
||||
}
|
||||
list.push(card);
|
||||
/* [2026-07-31] ปักธงตอน "แจกสำเร็จจริง" เท่านั้น (ไม่ใช่ตอนสุ่มได้) — ถ้าช่องเต็ม 3 ใบแล้ว return null
|
||||
ไปก่อนหน้านี้ ทองจะยังไม่ถูกใช้สิทธิ์ทิ้ง → คนถัดไปในรอบเดียวกันยังมีสิทธิ์ได้ */
|
||||
if (card.rarity === 'legendary') space.legendaryGrantedThisRun = true;
|
||||
space.playerEvidence[playerId][suspectIndex] = list;
|
||||
syncEvidenceByNick(space, playerId);
|
||||
return card;
|
||||
}
|
||||
|
||||
/** [2026-07-31] ตายในมินิเกมรอบนี้ไหม — "ตายแล้วไม่ได้การ์ดหลักฐาน" (รวมการ์ดพิเศษใบ 7 ด้วย)
|
||||
* MG3 stack / MG4 quiz_carry ไม่มีตกรอบ → ทุกคนได้ตามเดิม
|
||||
* อ่าน 2 ทาง: ธง/เซสชันฝั่ง server + ค่าที่ client รายงานตอนจบ (reportedMiniSurvived)
|
||||
* เพื่อให้ได้คำตอบเดียวกันทั้งเส้นทาง returnDetective… และ detective-minigame-finished */
|
||||
const EVIDENCE_DEATH_GAME_TYPES = ['quiz', 'gauntlet', 'jump_survive', 'space_shooter', 'balloon_boss'];
|
||||
function playerDiedThisRunForEvidence(space, playerId) {
|
||||
const p = space && space.peers ? space.peers.get(playerId) : null;
|
||||
if (!p) return false;
|
||||
/* ⚠️ ห้ามอ่าน gameType จาก space.mapId ตอนนี้ — เส้นทาง detective-minigame-finished ทำงานตอน
|
||||
แมปถูกสลับเป็น LobbyB ไปแล้ว (server.js:4636) จะได้ชนิดผิดแล้วปล่อยคนตายผ่าน
|
||||
ใช้ค่าที่จำไว้ตั้งแต่ "ตอนเริ่มรอบ" แทน (fallback = แมปปัจจุบัน เผื่อ space เก่าที่ยังไม่มีฟิลด์นี้) */
|
||||
let gameType = space.detectiveRunGameType || null;
|
||||
if (!gameType) {
|
||||
const md = (space.mapId && maps.get(space.mapId)) || space.mapData;
|
||||
gameType = md && md.gameType;
|
||||
}
|
||||
if (EVIDENCE_DEATH_GAME_TYPES.indexOf(gameType) < 0) return false;
|
||||
if (p.reportedMiniSurvived === false) return true;
|
||||
switch (gameType) {
|
||||
case 'quiz': {
|
||||
const st = space.quizSession && space.quizSession.players ? space.quizSession.players[playerId] : null;
|
||||
return !!(st && st.eliminated);
|
||||
}
|
||||
case 'gauntlet':
|
||||
return !!p.gauntletEliminated;
|
||||
case 'jump_survive': {
|
||||
const sj = space.jumpSurviveSession;
|
||||
const st = sj && sj.players ? sj.players[playerId] : null;
|
||||
return !!(st && st.eliminated);
|
||||
}
|
||||
case 'space_shooter': {
|
||||
const ss = space.spaceShooterSession;
|
||||
const st = ss && ss.players ? ss.players[playerId] : null;
|
||||
return !!((st && st.eliminated) || p.spaceShooterEliminated);
|
||||
}
|
||||
case 'balloon_boss':
|
||||
return !!p.balloonBossEliminated;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* [achv Phase A 2026-07-05] นับหลักฐานที่ผู้เล่นเก็บได้ → achievements
|
||||
b4_data_miner(รวมทุกใบ ×100) · b1_evidence_collector(common ×20) · a2_sharp_eye(rare/Silver ×10) · b3_hidden_fragment(legendary ครั้งแรก)
|
||||
เฉพาะผู้เล่นจริง (มี playerKey) — ข้ามบอท + เรียกจาก "จุดแจกจริง" 2 ที่เท่านั้น (ไม่รวม debug hotkey) */
|
||||
@@ -4278,10 +4369,22 @@ function grantDetectiveEvidenceForCurrentRun(sid, space, playerIds) {
|
||||
if (space.detectiveAwardDoneForRun) {
|
||||
return { suspectIdx, awarded: {}, alreadyGranted: true };
|
||||
}
|
||||
const ids = (playerIds && playerIds.length) ? playerIds : [...space.peers.keys()];
|
||||
const grade = computeRunGradeForEvidence(space);
|
||||
const idsRaw = (playerIds && playerIds.length) ? playerIds : [...space.peers.keys()];
|
||||
/* [2026-07-31] คนที่ "ตายในเกมที่ตายได้" ไม่ได้การ์ดหลักฐาน — กรองที่จุดเดียวตรงนี้
|
||||
ครอบทั้งการแจกปกติและการ์ดพิเศษใบ 7 (หลักฐานฟรี) ที่วนรายชื่อชุดเดียวกัน */
|
||||
const died = [];
|
||||
const ids = idsRaw.filter((pid) => {
|
||||
if (!playerDiedThisRunForEvidence(space, pid)) return true;
|
||||
died.push(pid);
|
||||
return false;
|
||||
});
|
||||
if (died.length) {
|
||||
try { console.log('[evidence] ตายในรอบนี้ ' + died.length + ' คน → ไม่ได้การ์ด (' + died.join(',') + ')'); } catch (e) { /* ignore */ }
|
||||
}
|
||||
/* [2026-07-31] ใช้เกรดที่ snapshot ไว้ตอนจบรอบ (ก่อนเซสชันถูกล้าง) — ถ้าไม่มีค่อยคิดสด */
|
||||
const grade = space.pendingRunEvidenceGrade || computeRunGradeForEvidence(space);
|
||||
const awardedList = {};
|
||||
ids.forEach((pid) => { awardedList[pid] = []; });
|
||||
idsRaw.forEach((pid) => { awardedList[pid] = []; });
|
||||
const awarded = awardDetectiveEvidenceForMinigameEnd(space, suspectIdx, ids, grade);
|
||||
ids.forEach((pid) => { if (awarded[pid]) awardedList[pid].push(awarded[pid]); });
|
||||
/* Card 7 Free Evidence — ทุกคนรับหลักฐานเพิ่ม (after_game) */
|
||||
@@ -4360,6 +4463,9 @@ function beginDetectiveSuspectMinigame(sid, space, cardEntry, selectedIndex) {
|
||||
armLobbyBLoadedSafety(sid, space); /* backstop กัน deadlock ถ้า signal ไม่ครบ */
|
||||
space.detectiveAwardDoneForRun = false;
|
||||
space.coinAwardDoneForRun = false;
|
||||
space.legendaryGrantedThisRun = false; /* [2026-07-31] ทองออกได้ใบเดียวต่อรอบมินิเกม */
|
||||
space.detectiveRunGameType = md.gameType || null; /* [2026-07-31] จำชนิดเกมของรอบนี้ไว้ตัดสิน "ตายแล้วไม่ได้การ์ด" */
|
||||
space.pendingRunEvidenceGrade = null; /* [2026-07-31] เกรดของรอบใหม่ ยังไม่คิด */
|
||||
space.lastCrownMissionGrade = null;
|
||||
/* รีเซ็ตคะแนนที่ client รายงาน (stack/quiz_carry) ของรอบก่อน */
|
||||
space.peers.forEach((p) => { p.reportedMiniScore = 0; p.reportedMiniSurvived = true; });
|
||||
@@ -4488,6 +4594,12 @@ function beginDetectiveSuspectMinigame(sid, space, cardEntry, selectedIndex) {
|
||||
|
||||
function returnDetectiveSpaceToLobbyB(sid, space, message, awardOptions) {
|
||||
ensurePostCaseLobbyMapLoaded();
|
||||
/* [2026-07-31] 🔴 ต้องคิดเกรดหลักฐาน "ตรงนี้" ก่อนล้างเซสชัน
|
||||
เดิมไปคิดตอนแจกการ์ด (บรรทัด ~4657) ซึ่งอยู่ "หลัง" การล้าง quiz/stack/quizCarry/jumpSurvive
|
||||
Session = null (4600-4628) → สาขาที่อ่านเซสชันตายหมด ตกไปสาขา default ที่อ่าน
|
||||
gauntlet/spaceShooter/balloonBossScore → ได้ 0 = เกรด C ตลอด (กระทบ MG1, MG3, MG4, MG5)
|
||||
เกมที่เก็บคะแนนไว้บน peer (MG2/MG6/MG7) ไม่โดน เพราะ peer ไม่ถูกล้าง */
|
||||
space.pendingRunEvidenceGrade = computeRunGradeForEvidence(space);
|
||||
/* แจกเหรียญตามอันดับ + คะแนนสะสม ก่อนล้างสถานะมินิเกม (คะแนน/ผู้รอดยังครบ) */
|
||||
awardMinigameRankCoins(sid, space);
|
||||
clearSpaceQuizTimers(space);
|
||||
|
||||
Reference in New Issue
Block a user