diff --git a/www/html/Game/public/js/play.js b/www/html/Game/public/js/play.js index a459fe0..22da66f 100644 --- a/www/html/Game/public/js/play.js +++ b/www/html/Game/public/js/play.js @@ -2708,31 +2708,49 @@ quizWrongGhostImg = img; return img; } - /** วาด "ผี" คุมทับตัวผู้เล่นในแมป mng8a80o เมื่อตอบผิด — ครอบทั้งตัวละครเลย */ - function drawQuizWrongGhostOverlayPlay(ax, ay, entId) { - if (!isQuizQuestionMissionUiMapPlay()) return; + /** ตรวจว่า peer/me ตอบผิดในแมป mng8a80o แล้ว → คุมผีแทน avatar */ + function isQuizWrongGhostActiveForEnt(entId) { + if (!isQuizQuestionMissionUiMapPlay()) return false; const sid = String(entId == null ? '' : entId); const isMeEnt = (myId != null && sid === String(myId)); - const wrong = isMeEnt + return isMeEnt ? !!(playQuizPlayerLocal && (playQuizPlayerLocal.cannotTrue || playQuizPlayerLocal.cannotFalse)) : !!playQuizEverWrong[sid]; - if (!wrong) return; + } + + /** วาด "ผี" แทน avatar ทั้งตัวในแมป mng8a80o เมื่อตอบผิด — คุมผีเลย */ + function drawQuizWrongGhostOverlayPlay(ax, ay, entId, nameLabel) { const img = getQuizWrongGhostImg(); if (!img || !img.complete || !img.naturalWidth) return; - /* ผีครอบทับ avatar ทั้งตัว — กว้าง ~2.4 tile, สูงตามสัดส่วน, ลอยนิดๆ */ const tile = (typeof tileSize === 'number' && tileSize > 0) ? tileSize : 32; - const wPx = tile * 2.4; + /* ผีขนาดเท่าตัวละครเดิม: avatar ปกติ ~1 tile กว้าง, ~1.6 tile สูง → ผีกว้าง ~1.4, สูงตามสัดส่วน */ + const wPx = tile * 1.4; const ratio = img.naturalHeight / Math.max(1, img.naturalWidth); const hPx = wPx * ratio; - const tBob = Math.sin(performance.now() / 320) * (tile * 0.05); - /* ทับตรงกลางตัว avatar — avatar กว้าง 1 tile, สูง ~1.6 tile (วาดจากมุมบนซ้าย) */ - const avatarCenterY = ay + tile * 0.55; + const sid = String(entId == null ? '' : entId); + const tBob = Math.sin(performance.now() / 360 + (sid ? sid.charCodeAt(0) || 0 : 0)) * (tile * 0.06); + /* วาดให้ "เท้าผี" ตรงกับเท้า avatar (avatar สูง 1.6 tile จากจุดบนซ้าย ay) */ + const groundY = ay + tile * 1.6; const drawX = ax + (tile - wPx) / 2; - const drawY = avatarCenterY - hPx / 2 + tBob; + const drawY = groundY - hPx + tBob; ctx.save(); - ctx.globalAlpha = 0.85; + ctx.globalAlpha = 0.98; ctx.drawImage(img, drawX, drawY, wPx, hPx); ctx.restore(); + /* ชื่อใต้ผี (ถ้ามี — เพื่อระบุว่าเป็นใคร) */ + if (nameLabel) { + ctx.save(); + ctx.font = '600 12px system-ui, "Segoe UI", "Kanit", sans-serif'; + ctx.textAlign = 'center'; + ctx.fillStyle = 'rgba(255,255,255,0.95)'; + ctx.strokeStyle = 'rgba(0,0,0,0.85)'; + ctx.lineWidth = 3; + const nx = ax + tile / 2; + const ny = groundY + 14; + ctx.strokeText(nameLabel, nx, ny); + ctx.fillText(nameLabel, nx, ny); + ctx.restore(); + } } /** เกมถูก/ผิด — ตรงกับ server QUIZ_TF_POINTS_PER_CORRECT */ const QUIZ_TF_POINTS_PER_CORRECT = 10; @@ -18471,22 +18489,32 @@ if (shouldGauntletCrownHeistSkipAvatarDrawPlay(o, false, off.ax, off.ay)) return; const axO = safeX(o.x) + off.ax; const ayO = safeY(o.y) + off.ay; - if (playQuizAvatarHideWhileOverlappingAnswerZone(o, id, axO, ayO)) return; + /* ผีต้องโชว์เสมอ แม้ทับโซนคำตอบ (เดินไปไหนก็ได้) */ + const ghostActiveO = isQuizWrongGhostActiveForEnt(id); + if (!ghostActiveO && playQuizAvatarHideWhileOverlappingAnswerZone(o, id, axO, ayO)) return; if (botOut) ctx.save(); if (botOut) ctx.globalAlpha = 0.4; - drawAvatar(axO, ayO, false, peerName, o.characterId, faceDirOther, otherWalk, ot, (o.gauntletJumpVis != null ? o.gauntletJumpVis : o.gauntletJumpTicks) || 0, o.gauntletScore || 0, quizCarrySignForEntity(o), isGauntletCrownHeistMapPlay() ? (o.gauntletCrownPenaltyFxUntil || 0) : 0); - drawQuizWrongGhostOverlayPlay(axO, ayO, id); + /* ตอบผิดในแมป mng8a80o → แทน avatar ด้วยผีเลย */ + if (ghostActiveO) { + drawQuizWrongGhostOverlayPlay(axO, ayO, id, peerName); + } else { + drawAvatar(axO, ayO, false, peerName, o.characterId, faceDirOther, otherWalk, ot, (o.gauntletJumpVis != null ? o.gauntletJumpVis : o.gauntletJumpTicks) || 0, o.gauntletScore || 0, quizCarrySignForEntity(o), isGauntletCrownHeistMapPlay() ? (o.gauntletCrownPenaltyFxUntil || 0) : 0); + } if (botOut) ctx.restore(); } else { if (shouldGauntletCrownHeistSkipAvatarDrawPlay(me, true, 0, 0)) return; const axMe = safeX(me.x); const ayMe = safeY(me.y); - if (playQuizAvatarHideWhileOverlappingAnswerZone(me, myId, axMe, ayMe)) return; + const ghostActiveMe = isQuizWrongGhostActiveForEnt(myId); + if (!ghostActiveMe && playQuizAvatarHideWhileOverlappingAnswerZone(me, myId, axMe, ayMe)) return; if (isJumpSurvive() && jumpSurviveEliminated) ctx.save(); if (isJumpSurvive() && jumpSurviveEliminated) ctx.globalAlpha = 0.4; const faceDirMe = isGauntletFaceRightMapMno9kb07() ? 'right' : me.direction; - drawAvatar(axMe, ayMe, true, me.nickname + meTag, me.characterId, faceDirMe, meWalking, mt, meGauntletJumpVis, me.gauntletScore || 0, quizCarrySignForEntity(me), isGauntletCrownHeistMapPlay() ? (me.gauntletCrownPenaltyFxUntil || 0) : 0); - drawQuizWrongGhostOverlayPlay(axMe, ayMe, myId); + if (ghostActiveMe) { + drawQuizWrongGhostOverlayPlay(axMe, ayMe, myId, me.nickname + meTag); + } else { + drawAvatar(axMe, ayMe, true, me.nickname + meTag, me.characterId, faceDirMe, meWalking, mt, meGauntletJumpVis, me.gauntletScore || 0, quizCarrySignForEntity(me), isGauntletCrownHeistMapPlay() ? (me.gauntletCrownPenaltyFxUntil || 0) : 0); + } if (isJumpSurvive() && jumpSurviveEliminated) ctx.restore(); } }); diff --git a/www/html/Game/public/js/room-lobby.js b/www/html/Game/public/js/room-lobby.js index b555dee..e77d9ec 100644 --- a/www/html/Game/public/js/room-lobby.js +++ b/www/html/Game/public/js/room-lobby.js @@ -2750,7 +2750,22 @@ socket.on('peer-display-name', applyPeerDisplayNameUpdate); socket.on('connect', () => { - socket.emit('join-space', { spaceId, nickname: getProfileDisplayName(), characterId: getStoredCharacterId() }, (res) => { + /* ส่งค่าสีที่เลือกใน Main-Lobby ติดไปด้วย — server จะใช้ค่านี้ถ้ายังว่าง (กันโชว์สีแดง default ตอนเข้าใหม่) */ + var savedThemeIdx = null; + var savedSkinIdx = null; + try { + var t = parseInt(localStorage.getItem('lobbyThemeColor'), 10); + var s = parseInt(localStorage.getItem('lobbySkinTone'), 10); + if (t >= 1 && t <= 8) savedThemeIdx = t; + if (s >= 1 && s <= 3) savedSkinIdx = s; + } catch (e) { /* ignore */ } + socket.emit('join-space', { + spaceId, + nickname: getProfileDisplayName(), + characterId: getStoredCharacterId(), + desiredLobbyColorThemeIndex: savedThemeIdx, + desiredLobbySkinToneIndex: savedSkinIdx, + }, (res) => { if (!res || !res.ok) { var joinErr = (res && res.error) || 'เข้าไม่ได้'; if (/เริ่มคดี|ไม่รับผู้เล่น/.test(joinErr)) { diff --git a/www/html/Game/public/play.html b/www/html/Game/public/play.html index e484740..bd45c05 100644 --- a/www/html/Game/public/play.html +++ b/www/html/Game/public/play.html @@ -3214,7 +3214,7 @@ - +