update flow design 1.33

This commit is contained in:
2026-05-27 18:05:48 +00:00
parent a978f3feab
commit 3c632dc754
5 changed files with 76 additions and 24 deletions
+46 -18
View File
@@ -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();
}
});
+16 -1
View File
@@ -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)) {
+1 -1
View File
@@ -3214,7 +3214,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.0469"></script>
<script src="js/play.js?v=0.0472"></script>
<div class="version-tag">v —</div>
</body>
</html>
+1 -1
View File
@@ -1576,7 +1576,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.0247"></script>
<script src="js/room-lobby.js?v=0.0248"></script>
<div class="version-tag">v —</div>
</body>
</html>
+12 -3
View File
@@ -4639,7 +4639,7 @@ function spaceAllowsQuizCarryLobbyRelaxed(space) {
}
io.on('connection', (socket) => {
socket.on('join-space', ({ spaceId, nickname, characterId, playMapId }, cb) => {
socket.on('join-space', ({ spaceId, nickname, characterId, playMapId, desiredLobbyColorThemeIndex, desiredLobbySkinToneIndex }, cb) => {
const space = spaces.get(spaceId);
if (!space || !space.mapData) return cb && cb({ ok: false, error: 'ไม่พบห้อง' });
const maxPlayers = space.maxPlayers || 10;
@@ -4674,11 +4674,20 @@ io.on('connection', (socket) => {
const spawnJoinOrder = space.peers.size;
const spawnPt = pickSpawnForJoin(mdJoin, spawnJoinOrder);
const bbStartBalloons = Math.max(1, Math.min(12, Math.floor(Number(mdJoin.balloonBossBalloonsPerPlayer)) || 3));
/* ใช้สีที่ client เลือกไว้ใน Main-Lobby ถ้าผ่านการตรวจสอบ + ยังไม่ถูกใช้ในห้องนี้ */
let chosenThemeIdx = parseInt(desiredLobbyColorThemeIndex, 10);
if (!(chosenThemeIdx >= 1 && chosenThemeIdx <= LOBBY_THEME_COUNT) || getTakenLobbyThemeIndices(space).has(chosenThemeIdx)) {
chosenThemeIdx = pickFreeLobbyThemeIndex(space);
}
let chosenSkinIdx = parseInt(desiredLobbySkinToneIndex, 10);
if (!(chosenSkinIdx >= 1 && chosenSkinIdx <= LOBBY_SKIN_COUNT)) {
chosenSkinIdx = pickLobbySkinIndexForSlot(spawnJoinOrder);
}
const peer = {
id: socket.id, x: +spawnPt.x, y: +spawnPt.y, direction: 'down', nickname: nickname || 'ผู้เล่น', ready: false, characterId: characterId || null, voiceMicOn: true,
spawnJoinOrder,
lobbyColorThemeIndex: pickFreeLobbyThemeIndex(space),
lobbySkinToneIndex: pickLobbySkinIndexForSlot(spawnJoinOrder),
lobbyColorThemeIndex: chosenThemeIdx,
lobbySkinToneIndex: chosenSkinIdx,
gauntletJumpTicks: 0, gauntletScore: 0, gauntletJumpPending: false, gauntletEliminated: false, spaceShooterScore: 0,
balloonBossScore: 0, balloonBossBossDmg: 0, balloonBossBalloons: mdJoin.gameType === 'balloon_boss' ? bbStartBalloons : 5, balloonBossEliminated: false,
};