update card

This commit is contained in:
2026-06-14 14:31:49 +00:00
parent cdfd19c7a1
commit 3d375adfaa
8 changed files with 101 additions and 28 deletions
+5 -5
View File
@@ -38,9 +38,9 @@
"providerUserId": "p_1775109142385_wq7wfy1p32j",
"notes": "auto: player-coins",
"blocked": false,
"coins": 110,
"coins": 180,
"createdAt": "2026-04-02T05:52:21+00:00",
"updatedAt": "2026-06-12T08:29:33+00:00",
"updatedAt": "2026-06-14T14:25:05+00:00",
"daily": {
"anchorMs": 1781197200000,
"claimedDays": [
@@ -52,13 +52,13 @@
false,
false
],
"lockUntilMs": 1781283600000
"lockUntilMs": 0
},
"score": 100,
"score": 170,
"scoreByCase": {
"1": 10,
"10": 70,
"8": 20
"8": 90
},
"lbName": "Q"
},
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

+32 -12
View File
@@ -36,6 +36,8 @@
const previewMode = params.get('preview') === '1';
/* Card 3 Ban — ผู้ถูกแบนรอบนี้: เข้ามาดูเพื่อนเล่นได้ แต่ควบคุมตัวละคร/ทำแอ็กชันไม่ได้ (รอบหน้าเล่นปกติ) */
const playBannedSpectator = params.get('banned') === '1';
/* Card 3 Ban (กรณีโหวตแบน "บอท") — slot ของ __pv_bot_N ที่ถูกแบน → ไม่ spawn ลงเล่นรอบนี้ (-1 = ไม่มี) */
const playBannedBotSlot = (() => { const v = parseInt(params.get('banBot'), 10); return Number.isFinite(v) && v >= 0 ? v : -1; })();
/* shield (z 200) pointer/touch (jump z59/drop z60/joystick/shoot)+canvas
แตำกว modal (z 10000+) เลย special quiz / สรปผล งกดไดปกต + ายบอกสถานะดานบน */
function setupBannedSpectatorUi() {
@@ -5003,7 +5005,12 @@
function rebalancePreviewBots() {
if (!playBotsEnabled() || !mapData) return;
const human = countPlayHumans();
const wantBots = Math.max(0, playBotTargetHeadcount() - human);
let wantBots = Math.max(0, playBotTargetHeadcount() - human);
/* Card 3 Ban — บอทที่ถูกแบนรอบนี้ (slot N) ไม่ลงเล่น: ลบทิ้ง + ลดเป้าจำนวนลง 1 + ข้าม slot นั้นตอนสร้าง */
if (playBannedBotSlot >= 0) {
others.delete(PREVIEW_BOT_PREFIX + playBannedBotSlot);
wantBots = Math.max(0, wantBots - 1);
}
[...others.keys()].filter(isPreviewBotId).forEach((bid) => {
const o = others.get(bid);
if (!o) return;
@@ -5043,7 +5050,7 @@
while (botIds.length < wantBots) {
/* หา slot ว่างเล็กสุด (0,1,2,…) — IDX ของ ID ต้องตรงกับ playLobbyBotThemes[idx] เพื่อให้สีตรง lobby */
let nextSlot = 0;
while (others.has(PREVIEW_BOT_PREFIX + nextSlot)) nextSlot++;
while (others.has(PREVIEW_BOT_PREFIX + nextSlot) || nextSlot === playBannedBotSlot) nextSlot++;
previewBotSeq = Math.max(previewBotSeq, nextSlot + 1);
const id = PREVIEW_BOT_PREFIX + nextSlot;
const joinIdx = countPlayHumans() + botIds.length;
@@ -7454,6 +7461,8 @@
const top = rows.slice(0, 5);
const ranked = top.map(function (row, idx) {
const pos = idx + 1;
/* โบนัสอยู่รอดจนจบเกม = +20 (เฉพาะคนไม่ตาย) — รวมใน finalScore เหมือน rankBonus ของ MG2 */
const surviveBonus = row.eliminated ? 0 : 20;
return {
id: row.id,
nickname: row.nickname,
@@ -7462,11 +7471,11 @@
eliminated: !!row.eliminated,
rank: pos,
rankLabel: pos === 1 ? '1st' : pos === 2 ? '2nd' : pos === 3 ? '3rd' : String(pos),
rankBonus: 0,
finalScore: row.baseScore,
rankBonus: surviveBonus,
finalScore: row.baseScore + surviveBonus,
};
});
const totalParts = ranked.map(function (r) { return r.baseScore; });
const totalParts = ranked.map(function (r) { return r.finalScore; });
const totalSum = totalParts.reduce(function (s, n) { return s + n; }, 0);
const n = ranked.length || 1;
const averageScore = Math.floor(totalSum / n);
@@ -7597,9 +7606,13 @@
return String(a.nickname).localeCompare(String(b.nickname), 'th') || String(a.id).localeCompare(String(b.id));
});
const top = baseRows.slice(0, 5);
/* โบนัสจบเกม: ฆ่าบอส(+100)→ทุกคนที่ร่วมเล่น · รอดชีวิต(+10)→เฉพาะคนไม่ตาย (รวมใน finalScore, clamp ≥0) */
const endRBonus = mission && mission.balloonBossEndReason;
const killBonusAll = endRBonus === 'victory' ? 100 : 0;
const ranked = top.map(function (row, idx) {
const pos = idx + 1;
const bs = Math.max(0, Number(row.baseScore) || 0);
const bonus = killBonusAll + (row.eliminated ? 0 : 10);
return {
id: row.id,
nickname: row.nickname,
@@ -7608,11 +7621,11 @@
eliminated: !!row.eliminated,
rank: pos,
rankLabel: pos === 1 ? '1st' : pos === 2 ? '2nd' : pos === 3 ? '3rd' : String(pos),
rankBonus: 0,
finalScore: bs,
rankBonus: bonus,
finalScore: Math.max(0, bs + bonus),
};
});
const totalParts = ranked.map(function (r) { return r.baseScore; });
const totalParts = ranked.map(function (r) { return r.finalScore; });
const totalSum = totalParts.reduce(function (s, n) { return s + n; }, 0);
const n2 = ranked.length || 1;
const averageScore = Math.floor(totalSum / n2);
@@ -7668,9 +7681,12 @@
|| String(a.nickname).localeCompare(String(b.nickname), 'th')
|| String(a.id).localeCompare(String(b.id)));
const top = rows.slice(0, 5);
/* โบนัสจบเกม: ฆ่าบอส(+100)→ทุกคนที่ร่วมเล่น · รอดชีวิต(+10)→เฉพาะคนไม่ตาย (รวมใน finalScore, clamp ≥0) */
const killBonusAll = reason === 'victory' ? 100 : 0;
const ranked = top.map((row, idx) => {
const pos = idx + 1;
const bs = Math.max(0, Number(row.baseScore) || 0);
const bonus = killBonusAll + (row.eliminated ? 0 : 10);
return {
id: row.id,
nickname: row.nickname,
@@ -7679,11 +7695,11 @@
eliminated: !!row.eliminated,
rank: pos,
rankLabel: pos === 1 ? '1st' : pos === 2 ? '2nd' : pos === 3 ? '3rd' : String(pos),
rankBonus: 0,
finalScore: bs,
rankBonus: bonus,
finalScore: Math.max(0, bs + bonus),
};
});
const totalParts = ranked.map((r) => r.baseScore);
const totalParts = ranked.map((r) => r.finalScore);
const totalSum = totalParts.reduce((s, n) => s + n, 0);
const n2 = ranked.length || 1;
const averageScore = Math.floor(totalSum / n2);
@@ -15154,7 +15170,7 @@
if (Number.isFinite(t) && t > 0 && t < 7200) return Math.floor(t);
const g = Number(playSpaceShooterMissionTimeSec);
if (Number.isFinite(g) && g > 0 && g < 7200) return Math.floor(g);
return 90;
return 60; /* สเปก MG6: เล่นภายใน 60 วินาที (default เมื่อ map/game-timing ไม่ได้ตั้งค่า) */
}
function spaceShooterRemainingSecPlay() {
@@ -16001,6 +16017,8 @@
vy: bvy,
ownerId: bid,
});
/* สเปก MG7: บอทยิง 1 ครั้ง = −5 คะแนน เหมือนผู้เล่น (ต่ำสุด 0) */
o.balloonBossScore = Math.max(0, (o.balloonBossScore | 0) - 5);
}
});
}
@@ -16275,6 +16293,8 @@
vy,
ownerId: myId,
});
/* สเปก MG7: ยิง 1 ครั้ง = −5 คะแนน (ต่ำสุด 0 ไม่ติดลบ) */
me.balloonBossScore = Math.max(0, (me.balloonBossScore | 0) - 5);
}
stepBalloonBossPreviewBots(dt, mw, mh, bossC.cx, bossC.cy, ts);
+11 -4
View File
@@ -6521,10 +6521,17 @@
if (lobbyLevelStr) q += '&lobbyLevel=' + encodeURIComponent(lobbyLevelStr);
if (caseIdStr) q += '&case=' + encodeURIComponent(caseIdStr);
if (data && data.detectiveMinigame) q += '&detectiveReturn=1';
/* Card 3 Ban — ผู้ที่ถูกแบนรอบนี้: เข้าไปดูเพื่อนเล่นได้ แต่ควบคุมตัวละครไม่ได้ (รอบหน้าเล่นปกติ) */
if (data && data.bannedPlayerId && data.bannedPlayerId === socket.id) {
q += '&banned=1';
appendLobbySystemChat('— คุณถูกแบนรอบนี้ (Ban Card) · เข้าไปดูเพื่อนเล่นได้ แต่เล่นเองไม่ได้ · รอบหน้าเล่นได้ปกติ');
/* Card 3 Ban : ()
าถกแบนเป "บอท" (__lobby_bot_N) banBot=N ให play.js นบอทตวนนไมใหลงเล ( client จำลองบอท) */
if (data && data.bannedPlayerId) {
if (data.bannedPlayerId === socket.id) {
q += '&banned=1';
appendLobbySystemChat('— คุณถูกแบนรอบนี้ (Ban Card) · เข้าไปดูเพื่อนเล่นได้ แต่เล่นเองไม่ได้ · รอบหน้าเล่นได้ปกติ');
} else if (String(data.bannedPlayerId).indexOf('__lobby_bot_') === 0) {
var banBotSlot = parseInt(String(data.bannedPlayerId).slice('__lobby_bot_'.length), 10);
if (!isNaN(banBotSlot) && banBotSlot >= 0) q += '&banBot=' + banBotSlot;
appendLobbySystemChat('— บอทถูกแบนรอบนี้ (Ban Card) · ไม่ได้ลงเล่นมินิเกมรอบนี้');
}
}
location.href = q;
});
+1 -1
View File
@@ -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.006131817"></script>
<script src="js/play.js?v=0.006131820"></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)
+1 -1
View File
@@ -1613,7 +1613,7 @@
<script src="js/display-name.js?v=2"></script>
<script src="js/version.js?v=0.0122"></script>
<script src="js/customize-popup.js?v=31" data-customize-triggers="" data-customize-asset-base="img/03-5-Customize"></script>
<script src="js/room-lobby.js?v=0.0277"></script>
<script src="js/room-lobby.js?v=0.0278"></script>
<div class="version-tag">v —</div>
</body>
</html>
+51 -5
View File
@@ -594,6 +594,47 @@ function specialQuizTileInGameZone(md, x, y) {
function pickSpecialQuizSpawnTile(md) {
const w = md.width || 20;
const h = md.height || 15;
/* quiz_carry (): / "" blockPlayer
องวางกลางระหวาง hub บโต interactive เหมอนเดนไดแตจร โตะบ เกบไอคอนไมได
เกดไอคอน "ใกล้จุดที่ผู้เล่นเกิด/เดิน" + เว buffer 2 องรอบ hub/interactive/option (นโตะทวาดเลยกร) */
if (md.gameType === 'quiz_carry') {
const nearMarkedTable = (x, y) => {
for (const g of [md.quizCarryHubArea, md.interactive, md.quizCarryOptionArea]) {
if (!Array.isArray(g)) continue;
for (let dy = -2; dy <= 2; dy++) {
const gy = g[y + dy];
if (!gy) continue;
for (let dx = -2; dx <= 2; dx++) {
if (gy[x + dx]) return true;
}
}
}
return false;
};
const seeds = (Array.isArray(md.lobbyPlayerSpawns) && md.lobbyPlayerSpawns.length)
? md.lobbyPlayerSpawns
: [md.spawn || { x: Math.floor(w / 2), y: Math.floor(h / 2) }];
const qcCands = [];
seeds.forEach((s) => {
const bx = Math.floor(Number(s.x));
const by = Math.floor(Number(s.y));
for (let dy = -5; dy <= 5; dy++) {
for (let dx = -5; dx <= 5; dx++) {
const dist = Math.abs(dx) + Math.abs(dy);
if (dist < 2 || dist > 6) continue;
const xx = bx + dx;
const yy = by + dy;
if (specialQuizTileWalkable(md, xx, yy) && !specialQuizTileInGameZone(md, xx, yy) && !nearMarkedTable(xx, yy)) {
qcCands.push({ x: xx, y: yy });
}
}
}
});
if (qcCands.length) {
const p = qcCands[Math.floor(Math.random() * qcCands.length)];
return { x: p.x + 0.5, y: p.y + 0.5 };
}
}
/* gauntlet (): "" gauntletPlayerSpawns
( x กลางทาง) เลนวงผานเกบได/คลกได ไมนลอยออกนอกเลนเกบไมได */
if (md.gameType === 'gauntlet' && Array.isArray(md.gauntletPlayerSpawns) && md.gauntletPlayerSpawns.length) {
@@ -1638,10 +1679,8 @@ function computeRunGradeForEvidence(space) {
try {
const md = (space.mapId && maps.get(space.mapId)) || space.mapData;
const gameType = md && md.gameType;
if (gameType === 'gauntlet' || gameType === 'jump_survive') {
return buildGauntletCrownMissionPayload(space).grade || 'C';
}
/* เกมจริงหรือไม่ (quiz TF): เฉลี่ย % ของคะแนนเทียบเต็ม (จำนวนข้อ × แต้มต่อข้อ) */
/* : % A(80)/B(60)/C
quiz ใช % มบรณ (/เต), เกมอ normalize เทยบคนคะแนนสงสดในหองแลวเฉล */
if (gameType === 'quiz' && space.quizSession && space.quizSession.players) {
const total = (space.quizSession.questions || []).length * QUIZ_TF_POINTS_PER_CORRECT;
const ids = Object.keys(space.quizSession.players);
@@ -3261,6 +3300,7 @@ function beginDetectiveSuspectMinigame(sid, space, cardEntry, selectedIndex) {
space.peers.forEach((p) => { p.reportedMiniScore = 0; p.reportedMiniSurvived = true; });
/* Card 3 Ban — ผู้ที่ถูกแบนไม่ได้เล่นมินิเกมรอบนี้ (1 รอบ) */
space.bannedThisRunPlayerId = space.bannedPlayerId || null;
console.log('[ban-debug] startMinigame: bannedThisRunPlayerId=', space.bannedThisRunPlayerId);
space.bannedPlayerId = null;
space.detectiveMinigameCardIndex = cardEntry.cardIndex;
// จำว่ากำลังสืบผู้ต้องสงสัยคนไหน เพื่อเติมหลักฐาน (ติกถูก) ตอนเล่นจบ
@@ -7256,16 +7296,22 @@ io.on('connection', (socket) => {
/* Card 3 Ban: LobbyB + Ban
(คน rejoin คนแรกเปนคนเปดโหวต; คนท rejoin หลงขณะโหวตเปดอย งสถานะใหเหนดวย) */
if (serverMapIsPostCaseLobbyB(space)) {
console.log('[ban-debug] join-space rejoin: postLobbyB=true pendingBan=', !!space.pendingBanVoteCard, 'pickVote=', !!space.pickVote, 'by', socket.id);
if (space.pendingBanVoteCard && !space.pickVote) {
const banCard = space.pendingBanVoteCard;
space.pendingBanVoteCard = null;
/* หน่วงให้ room-lobby ฝั่ง client โหลด/เซ็ตอัป UI เสร็จก่อน แล้วค่อยเปิดโหวต (ไม่งั้น overlay โดน reset/ทับตอนหน้ายังไม่พร้อม) */
setTimeout(() => {
const sp = spaces.get(spaceId);
if (!sp || sp.pickVote || !serverMapIsPostCaseLobbyB(sp)) return;
if (!sp || sp.pickVote || !serverMapIsPostCaseLobbyB(sp)) {
console.log('[ban-debug] setTimeout BAIL: sp=', !!sp, 'pickVote=', !!(sp && sp.pickVote), 'postLobbyB=', !!(sp && serverMapIsPostCaseLobbyB(sp)));
return;
}
console.log('[ban-debug] OPEN ban vote (peers=', sp.peers.size, ')');
io.to(spaceId).emit('special-card-applied', { card: specialCardClientPayload(banCard), context: 'ban' });
startPlayerPickVote(spaceId, sp, 'ban', (targetId) => {
const sp2 = spaces.get(spaceId);
console.log('[ban-debug] ban vote RESOLVED target=', targetId);
if (sp2) sp2.bannedPlayerId = targetId || null;
});
}, 2000);