update Endflow

This commit is contained in:
2026-06-17 08:36:18 +00:00
parent e416f0f9b5
commit 8fb403a368
5 changed files with 113 additions and 10 deletions
+35 -2
View File
@@ -19,15 +19,44 @@ if ($limit > 200) $limit = 200;
$caseId = preg_replace('/[^0-9]/', '', (string) ($_GET['caseId'] ?? ''));
/* ชื่อคดี (caseId -> name) จาก Game/data/case-media.json */
function lb_case_names(): array
{
$path = __DIR__ . '/../../Game/data/case-media.json';
if (!is_file($path)) return [];
$j = json_decode((string) @file_get_contents($path), true);
$cases = (is_array($j) && isset($j['cases']) && is_array($j['cases'])) ? $j['cases'] : [];
$out = [];
foreach ($cases as $cid => $c) {
$nm = is_array($c) ? trim((string) ($c['name'] ?? '')) : '';
if ($nm !== '') $out[(string) $cid] = $nm;
}
return $out;
}
$caseNames = lb_case_names();
$store = read_store();
$accounts = (isset($store['accounts']) && is_array($store['accounts'])) ? $store['accounts'] : [];
$rows = [];
foreach ($accounts as $a) {
$bestCaseId = '';
if ($caseId !== '') {
/* เจาะจงคดี */
$score = max(0, (int) (($a['scoreByCase'][$caseId] ?? 0)));
$bestCaseId = $caseId;
} else {
$score = max(0, (int) ($a['score'] ?? 0));
/* กระดานรวม — คะแนนสูงสุดของ user จากคดีไหนก็ได้ */
$score = 0;
$sbc = (isset($a['scoreByCase']) && is_array($a['scoreByCase'])) ? $a['scoreByCase'] : [];
foreach ($sbc as $cid => $sc) {
$sc = max(0, (int) $sc);
if ($sc > $score) { $score = $sc; $bestCaseId = (string) $cid; }
}
if ($score <= 0) { /* legacy: ไม่มีคะแนนรายคดี ใช้คะแนนรวม (ไม่มีชื่อคดี) */
$score = max(0, (int) ($a['score'] ?? 0));
$bestCaseId = '';
}
}
if ($score <= 0) {
continue;
@@ -39,6 +68,8 @@ foreach ($accounts as $a) {
'name' => $name,
'score' => $score,
'coins' => max(0, (int) ($a['coins'] ?? 0)),
'caseId' => $bestCaseId,
'caseName' => ($bestCaseId !== '' && isset($caseNames[$bestCaseId])) ? $caseNames[$bestCaseId] : '',
'key' => (string) ($a['providerUserId'] ?? ''),
];
}
@@ -57,7 +88,7 @@ $myKey = trim((string) ($_GET['playerKey'] ?? ''));
if ($myKey !== '') {
for ($i = 0; $i < $total; $i++) {
if ($rows[$i]['key'] === $myKey) {
$me = ['rank' => $i + 1, 'name' => $rows[$i]['name'], 'score' => $rows[$i]['score'], 'coins' => $rows[$i]['coins']];
$me = ['rank' => $i + 1, 'name' => $rows[$i]['name'], 'score' => $rows[$i]['score'], 'coins' => $rows[$i]['coins'], 'caseId' => $rows[$i]['caseId'], 'caseName' => $rows[$i]['caseName']];
break;
}
}
@@ -71,6 +102,8 @@ for ($i = 0; $i < $n; $i++) {
'name' => $rows[$i]['name'],
'score' => $rows[$i]['score'],
'coins' => $rows[$i]['coins'],
'caseId' => $rows[$i]['caseId'],
'caseName' => $rows[$i]['caseName'],
];
}
+23 -5
View File
@@ -38,9 +38,9 @@
"providerUserId": "p_1775109142385_wq7wfy1p32j",
"notes": "auto: player-coins",
"blocked": false,
"coins": 545,
"coins": 565,
"createdAt": "2026-04-02T05:52:21+00:00",
"updatedAt": "2026-06-17T07:52:36+00:00",
"updatedAt": "2026-06-17T08:31:26+00:00",
"daily": {
"anchorMs": 1781197200000,
"claimedDays": [
@@ -54,7 +54,7 @@
],
"lockUntilMs": 1781715600000
},
"score": 470,
"score": 490,
"scoreByCase": {
"1": 10,
"10": 120,
@@ -63,11 +63,14 @@
"13": 20,
"14": 20,
"9": 40,
"12": 40,
"12": 60,
"15": 20,
"4": 10
},
"lbName": "Q"
"lbName": "Q",
"achievements": {
"d1_minigame_solver": 2
}
},
{
"id": "1d64c56fadb64a93eae68a1d",
@@ -173,6 +176,21 @@
"coins": 0,
"createdAt": "2026-06-16T05:42:47+00:00",
"updatedAt": "2026-06-16T05:42:47+00:00"
},
{
"id": "0b016b9f790d0b90d99d7ba6",
"email": "",
"displayName": "Guest",
"loginType": "guest",
"providerUserId": "achvtest_12345678",
"notes": "auto: achievements",
"blocked": false,
"coins": 0,
"achievements": {
"d1_minigame_solver": 3
},
"createdAt": "2026-06-17T08:22:51+00:00",
"updatedAt": "2026-06-17T08:22:51+00:00"
}
]
}
+51
View File
@@ -1760,6 +1760,7 @@ function computeRunGradeForEvidence(space) {
/* ===== แจกเหรียญตามอันดับ + คะแนนสะสม (high score) แบบ server-authoritative ===== */
const GAME_AWARD_URL = process.env.GAME_AWARD_URL || 'https://srv1361159.hstgr.cloud/Admin/api/game-award.php';
const ACHIEVEMENTS_URL = process.env.ACHIEVEMENTS_URL || GAME_AWARD_URL.replace('game-award.php', 'achievements.php');
const GAME_AWARD_SECRET_PATH = path.join(__dirname, '..', 'Admin', 'private', 'game-award-secret.txt');
let __gameAwardSecret = null;
function gameAwardSecret() {
@@ -1807,6 +1808,25 @@ function submitGameAwards(awards) {
} catch (e) { console.error('[game-award] throw', e && e.message); }
}
/* progress achievement (server-authoritative achievements.php secret game-award)
items: [{ playerKey, id, inc }] หน POST อรายการ (achievements.php บทละ id) */
function submitAchievementProgress(items) {
const secret = gameAwardSecret();
if (!secret || !Array.isArray(items) || !items.length) return;
items.forEach((it) => {
if (!it || !it.playerKey || !it.id) return;
try {
fetch(ACHIEVEMENTS_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'progress', secret, playerKey: it.playerKey, id: it.id, inc: it.inc || 1 }),
}).then((r) => r.json()).then((d) => {
if (!d || !d.ok) console.error('[achv] failed', it.id, d);
}).catch((e) => console.error('[achv] error', e && e.message));
} catch (e) { console.error('[achv] throw', e && e.message); }
});
}
/* แจกเหรียญตามอันดับคะแนน (เฉพาะผู้รอดชีวิต ผู้ตาย = 0) + คะแนนสะสมเข้า leaderboard — ครั้งเดียวต่อรอบ */
function awardMinigameRankCoins(sid, space) {
if (!space || space.coinAwardDoneForRun) return;
@@ -1829,6 +1849,9 @@ function awardMinigameRankCoins(sid, space) {
const awards = rows.filter((r) => r.coins > 0 && r.playerKey).map((r) => ({ playerKey: r.playerKey, nickname: r.nickname, coins: r.coins, caseId: caseId }));
submitGameAwards(awards);
/* Achievement: d1 Minigame Solver — รอดมินิเกมสำเร็จ (เฉพาะผู้รอดชีวิตที่เป็นผู้เล่นจริง) */
submitAchievementProgress(survivors.filter((r) => r.playerKey).map((r) => ({ playerKey: r.playerKey, id: 'd1_minigame_solver', inc: 1 })));
io.to(sid).emit('minigame-coins', {
gameType,
coinsTable: MINIGAME_RANK_COINS,
@@ -3636,6 +3659,34 @@ function computeAndEmitTrialResult(sid, space) {
disruptorId,
hasDisruptor: !!disruptorId,
});
/* ===== Achievement auto-track ===== */
try {
const achv = [];
/* a1 First Deduction (target 1) + a5 Truth Hunter (target 20) — ผู้โหวตถูก (ผู้เล่นจริงเท่านั้น) */
winners.forEach((id) => {
const p = space.peers.get(id);
if (p && p.playerKey) {
achv.push({ playerKey: p.playerKey, id: 'a1_first_deduction', inc: 1 });
achv.push({ playerKey: p.playerKey, id: 'a5_truth_hunter', inc: 1 });
}
});
/* ตัวป่วน — นับเฉพาะผู้เล่นจริง (มี playerKey) */
if (disruptorId && space.disruptorPlayerKey) {
const dKey = space.disruptorPlayerKey;
achv.push({ playerKey: dKey, id: 'e2_the_impostor', inc: 1 }); /* target 1 */
achv.push({ playerKey: dKey, id: 'e4_agent_of_chaos', inc: 1 }); /* target 10 */
/* ตัวป่วนชนะ = ทีมโหวตผิดคน (ไม่จับคนร้ายตัวจริง) */
let mv = 0; for (let k = 1; k <= 2; k++) { if (counts[k] > counts[mv]) mv = k; }
const anyV = (counts[0] + counts[1] + counts[2]) > 0;
if (anyV && mv !== culprit) {
achv.push({ playerKey: dKey, id: 'e3_master_of_doubt', inc: 1 }); /* target 1 */
achv.push({ playerKey: dKey, id: 'e5_slippery_eel', inc: 1 }); /* target 5 */
}
}
submitAchievementProgress(achv);
} catch (e) { console.error('[achv] trial-result', e && e.message); }
space.silencedPlayerId = null;
}
+1 -1
View File
@@ -231,6 +231,6 @@
<script src="../Game/js/customize-popup.js?v=31" data-customize-triggers="#btn-cloth" data-customize-asset-base="/Game/img/03-5-Customize"></script>
<script src="../Game/js/achievements.js?v=0.002" data-asset-base="/Game/img/03-6-Profile"></script>
<script src="../Game/js/profile-popup.js?v=7" data-profile-trigger="#btn-myprofile" data-profile-asset-base="/Game/img/03-6-Profile"></script>
<script src="lobby.js?v=0.0191"></script>
<script src="lobby.js?v=0.0192"></script>
</body>
</html>
+3 -2
View File
@@ -268,14 +268,15 @@
var rank = parseInt(r.rank, 10) || (i + 1);
var name = lbEscapeHtml(r.name || 'ผู้เล่น');
var score = Math.max(0, parseInt(r.score, 10) || 0);
var coins = Math.max(0, parseInt(r.coins, 10) || 0);
var caseName = lbEscapeHtml((r.caseName || '').toString());
var caseLabel = caseName ? ('(คดี : ' + caseName + ')') : '';
var head = rank <= 3
? '<img src="IMAGE/leaderboard-' + rank + '.png" alt="" class="lobby-rank-icon" role="presentation" decoding="async">'
: '<span class="lobby-rank-num">' + rank + '</span>';
html += '<li>' + head +
'<div class="lobby-rank-info">' +
'<span class="lobby-rank-name">' + name + '</span>' +
'<span class="lobby-rank-case">(เหรียญ : ' + coins + ')</span>' +
'<span class="lobby-rank-case">' + caseLabel + '</span>' +
'</div>' +
'<span class="lobby-rank-score">' + score + '</span>' +
'</li>';