990 lines
34 KiB
JavaScript
990 lines
34 KiB
JavaScript
(function () {
|
|
'use strict';
|
|
|
|
var BASE = typeof appPath === 'function' ? appPath('/Game') : '/Game';
|
|
var SERVER = (typeof GAME_SERVER !== 'undefined' ? GAME_SERVER : '') + '/Game';
|
|
var CHAR_KEY = 'gameCharacterId';
|
|
/** รูป composite idle ทิศ down จากหน้า Character (preview-idle-layer-down) — ตรงกับ character.js */
|
|
var LOBBY_IDLE_DOWN_PREFIX = 'jdCharLobbyIdleDown:';
|
|
|
|
function getStoredLobbyIdleDownDataUrl(safeId) {
|
|
if (!safeId) return '';
|
|
try {
|
|
var v = localStorage.getItem(LOBBY_IDLE_DOWN_PREFIX + safeId) || '';
|
|
if (typeof v === 'string' && v.indexOf('data:image/') === 0 && v.length > 80) return v;
|
|
} catch (e) { /* ignore */ }
|
|
return '';
|
|
}
|
|
|
|
var GUIDE_KEY = 'mainLobbyGuideDone';
|
|
var MAX_GUIDE = 5;
|
|
var ML_GUIDE = typeof appPath === 'function' ? appPath('/Main-Lobby/IMAGE/guide') : '/Main-Lobby/IMAGE/guide';
|
|
|
|
/**
|
|
* ตำแหน่งแถบคำแนะนำ + จุดไฮไลต์ต่อ guide-01…05 (ปรับลำดับให้ตรงไฟล์รูป)
|
|
* position: 'bottom' | 'top' — ตาม mock แถบล่าง/บน
|
|
* highlight: selector ของปุ่มใน lobby (null = ไม่วงกรอบ)
|
|
*/
|
|
var GUIDE_STEPS = [
|
|
{ position: 'bottom', highlight: null, pad: 10 },
|
|
{ position: 'top', highlight: '.lobby-footer-center', pad: 10 },
|
|
{ position: 'top', highlight: '#btn-ai-chat', pad: 8 },
|
|
{ position: 'bottom', highlight: '#btn-cloth', pad: 10 },
|
|
{ position: 'bottom', highlight: '#btn-daily', pad: 10 },
|
|
];
|
|
|
|
function checkLogin() {
|
|
if (localStorage.getItem('isLoggedIn') !== 'true') {
|
|
window.location.href = typeof appPath === 'function' ? appPath('/Login/') : '/Login/';
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (!checkLogin()) return;
|
|
|
|
function toast(msg) {
|
|
var el = document.getElementById('lobby-toast');
|
|
if (!el) return;
|
|
el.textContent = msg;
|
|
el.classList.add('lobby-toast--show');
|
|
clearTimeout(toast._t);
|
|
toast._t = setTimeout(function () {
|
|
el.classList.remove('lobby-toast--show');
|
|
}, 2600);
|
|
}
|
|
|
|
function padAgent(n) {
|
|
var s = String(n);
|
|
while (s.length < 6) s = '0' + s;
|
|
return s;
|
|
}
|
|
|
|
function ensureAgentId() {
|
|
var k = 'agentDisplayId';
|
|
var v = localStorage.getItem(k);
|
|
if (!v || !/^\d{6}$/.test(v)) {
|
|
v = padAgent(100000 + Math.floor(Math.random() * 899999));
|
|
try {
|
|
localStorage.setItem(k, v);
|
|
} catch (e) { /* ignore */ }
|
|
}
|
|
return v;
|
|
}
|
|
|
|
function getSelectedCharacterId() {
|
|
try {
|
|
return (localStorage.getItem(CHAR_KEY) || '').trim();
|
|
} catch (e) {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
/** ใช้เฉพาะ id ที่ปลอดเป็น segment ใน URL — กันพาธแปลก/อักขระที่เซิร์ฟไม่มีไฟล์ */
|
|
function normalizeCharacterAssetId(id) {
|
|
var s = String(id == null ? '' : id).trim();
|
|
if (!s) return '';
|
|
if (s.length > 96) s = s.slice(0, 96);
|
|
if (!/^[a-zA-Z0-9._-]+$/.test(s)) return '';
|
|
return s;
|
|
}
|
|
|
|
function mainMenuCharFallbackUrl() {
|
|
return typeof appPath === 'function' ? appPath('/Main-Menu/char-main.png') : '/Main-Menu/char-main.png';
|
|
}
|
|
|
|
/**
|
|
* URL รูปโลบี้ — ลองทิศ down ก่อน (idle แล้ว walk) เพื่อไม่ยิง _up_idle_ ฯลฯ โดยไม่จำเป็น
|
|
* ถ้า down ไม่มีค่อยลอง up / left / right
|
|
* @param {string} safeId
|
|
* @param {boolean} preferIdle
|
|
*/
|
|
function characterSpriteCandidateUrls(safeId, preferIdle) {
|
|
if (!safeId) return [];
|
|
var enc = encodeURIComponent(safeId);
|
|
var q = '?ch=' + encodeURIComponent(safeId);
|
|
var dirsPrimary = ['down'];
|
|
var dirsFallback = ['up', 'left', 'right'];
|
|
var wantIdle = preferIdle !== false;
|
|
var urls = [];
|
|
|
|
function pushForDir(d) {
|
|
if (wantIdle) {
|
|
urls.push(SERVER + '/img/characters/' + enc + '_' + d + '_idle.png' + q);
|
|
urls.push(SERVER + '/img/characters/' + enc + '_' + d + '_idle_0.png' + q);
|
|
}
|
|
urls.push(SERVER + '/img/characters/' + enc + '_' + d + '.png' + q);
|
|
urls.push(SERVER + '/img/characters/' + enc + '_' + d + '_0.png' + q);
|
|
}
|
|
|
|
for (var a = 0; a < dirsPrimary.length; a++) pushForDir(dirsPrimary[a]);
|
|
for (var b = 0; b < dirsFallback.length; b++) pushForDir(dirsFallback[b]);
|
|
return urls;
|
|
}
|
|
|
|
/** หลังลองครบแล้วไม่มีรูป — ลดการยิงซ้ำในเซสชัน (ไม่บล็อกการลองใหม่หลังอัปโหลดไฟล์แล้ว — ลบคีย์ตอนโหลดสำเร็จ) */
|
|
var CHAR_ASSET_MISS_PREFIX = 'jdCharAssetMissing:';
|
|
|
|
function markCharacterDownAssetMissing(safeId) {
|
|
if (!safeId) return;
|
|
try {
|
|
sessionStorage.setItem(CHAR_ASSET_MISS_PREFIX + safeId, '1');
|
|
} catch (e) { /* ignore */ }
|
|
}
|
|
|
|
function clearCharacterDownAssetMissing(safeId) {
|
|
if (!safeId) return;
|
|
try {
|
|
sessionStorage.removeItem(CHAR_ASSET_MISS_PREFIX + safeId);
|
|
} catch (e) { /* ignore */ }
|
|
}
|
|
|
|
/**
|
|
* ลอง URL ตามลำดับด้วย Image — ตรงกับพฤติกรรม <img> ใน character-grid
|
|
* @param {string[]} urls
|
|
* @param {(url: string | null) => void} cb
|
|
*/
|
|
function probeFirstLoadableImageUrl(urls, cb) {
|
|
var i = 0;
|
|
function next() {
|
|
if (i >= urls.length) {
|
|
cb(null);
|
|
return;
|
|
}
|
|
var u = urls[i++];
|
|
var img = new Image();
|
|
img.onload = function () {
|
|
if (img.naturalWidth > 0) cb(u);
|
|
else next();
|
|
};
|
|
img.onerror = function () {
|
|
next();
|
|
};
|
|
img.src = u;
|
|
}
|
|
next();
|
|
}
|
|
|
|
/**
|
|
* @param {string} safeId
|
|
* @param {(url: string | null) => void} cb
|
|
*/
|
|
function characterLobbySpriteFirstLiveUrl(safeId, cb) {
|
|
if (!safeId) {
|
|
cb(null);
|
|
return;
|
|
}
|
|
var urls = characterSpriteCandidateUrls(safeId, true);
|
|
if (!urls.length) {
|
|
cb(null);
|
|
return;
|
|
}
|
|
probeFirstLoadableImageUrl(urls, function (url) {
|
|
if (url) {
|
|
clearCharacterDownAssetMissing(safeId);
|
|
cb(url);
|
|
} else {
|
|
markCharacterDownAssetMissing(safeId);
|
|
cb(null);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* id จาก localStorage หรือถ้าว่าง — ตัวสุดท้ายในรายการ API (ใหม่สุดตามลำดับเซิร์ฟ)
|
|
* @param {(safeId: string) => void} callback
|
|
*/
|
|
function resolveLobbyCharacterId(callback) {
|
|
var norm = normalizeCharacterAssetId(getSelectedCharacterId());
|
|
if (norm) {
|
|
callback(norm);
|
|
return;
|
|
}
|
|
fetch(BASE + '/api/characters', { credentials: 'same-origin', cache: 'no-store' })
|
|
.then(function (r) { return r.json(); })
|
|
.then(function (list) {
|
|
if (!Array.isArray(list) || !list.length) {
|
|
callback('');
|
|
return;
|
|
}
|
|
var last = list[list.length - 1];
|
|
var id = last && last.id ? normalizeCharacterAssetId(String(last.id)) : '';
|
|
callback(id || '');
|
|
})
|
|
.catch(function () {
|
|
callback('');
|
|
});
|
|
}
|
|
|
|
var PLAYER_KEY = 'jdPlayerKey';
|
|
var DISPLAY_NAME_STORAGE_KEY = 'roomLobbyDisplayName';
|
|
|
|
function ensurePlayerKey() {
|
|
var k;
|
|
try {
|
|
k = localStorage.getItem(PLAYER_KEY);
|
|
} catch (e) {
|
|
k = '';
|
|
}
|
|
if (!k || String(k).length < 8) {
|
|
k = 'p_' + Date.now() + '_' + Math.random().toString(36).slice(2, 14);
|
|
try {
|
|
localStorage.setItem(PLAYER_KEY, k);
|
|
} catch (e2) { /* ignore */ }
|
|
}
|
|
return k;
|
|
}
|
|
|
|
/** ดึง COINS จากเซิร์ฟ (บัญชี guest สร้างอัตโนมัติถ้ายังไม่มี) */
|
|
function syncCoinsFromServer() {
|
|
var key = ensurePlayerKey();
|
|
fetch((typeof appPath === 'function' ? appPath('/Admin/api/player-coins.php') : '/Admin/api/player-coins.php') + '?playerKey=' + encodeURIComponent(key), { credentials: 'omit' })
|
|
.then(function (r) { return r.json(); })
|
|
.then(function (d) {
|
|
if (!d || !d.ok) return;
|
|
var c = String(Math.max(0, parseInt(d.coins, 10) || 0));
|
|
try {
|
|
localStorage.setItem('jdCoins', c);
|
|
} catch (e) { /* ignore */ }
|
|
var coinsEl = document.getElementById('lobby-profile-coins');
|
|
if (coinsEl) coinsEl.textContent = c;
|
|
})
|
|
.catch(function () { /* offline */ });
|
|
}
|
|
|
|
/** รีเฟรชหลังเลือกตัวละคร / สลับแท็บ / ย้อนกลับบน tablet (bfcache) */
|
|
function syncMainLobbyCharacterUi() {
|
|
applyProfileTexts();
|
|
syncCoinsFromServer();
|
|
function applyCharAssets(urlOrNull) {
|
|
applyProfileAvatar(urlOrNull);
|
|
applyCenterCharacterResolved(urlOrNull);
|
|
}
|
|
resolveLobbyCharacterId(function (id) {
|
|
if (!id) {
|
|
applyCharAssets(null);
|
|
return;
|
|
}
|
|
var idleCached = getStoredLobbyIdleDownDataUrl(id);
|
|
if (idleCached) {
|
|
applyCharAssets(idleCached);
|
|
return;
|
|
}
|
|
characterLobbySpriteFirstLiveUrl(id, applyCharAssets);
|
|
});
|
|
}
|
|
|
|
function getProfileDisplayName() {
|
|
if (typeof window.jdReadDisplayName === 'function') {
|
|
return window.jdReadDisplayName();
|
|
}
|
|
var fromRoomLobby = '';
|
|
var fromPlayerName = '';
|
|
try {
|
|
fromRoomLobby = (localStorage.getItem(DISPLAY_NAME_STORAGE_KEY) || '').trim();
|
|
fromPlayerName = (localStorage.getItem('playerName') || '').trim();
|
|
} catch (e) { /* ignore */ }
|
|
return fromRoomLobby || fromPlayerName || 'MONE';
|
|
}
|
|
|
|
function applyProfileTexts() {
|
|
var name = getProfileDisplayName();
|
|
var coins = localStorage.getItem('jdCoins') || '0';
|
|
var nameEl = document.getElementById('lobby-profile-name');
|
|
var agentEl = document.getElementById('lobby-profile-agent-id');
|
|
var coinsEl = document.getElementById('lobby-profile-coins');
|
|
if (nameEl) nameEl.textContent = name.toUpperCase();
|
|
if (agentEl) agentEl.textContent = ensureAgentId();
|
|
if (coinsEl) coinsEl.textContent = coins;
|
|
}
|
|
|
|
/** @param {string | null} urlOrNull — URL ที่ HEAD ผ่านแล้ว หรือ null = ใช้ char-main */
|
|
function applyProfileAvatar(urlOrNull) {
|
|
var av = document.getElementById('lobby-profile-avatar');
|
|
if (!av) return;
|
|
var fb = mainMenuCharFallbackUrl();
|
|
av.onerror = null;
|
|
if (!urlOrNull) {
|
|
av.src = fb;
|
|
return;
|
|
}
|
|
av.onerror = function () {
|
|
av.onerror = null;
|
|
av.src = fb;
|
|
};
|
|
av.src = urlOrNull;
|
|
}
|
|
|
|
function applyCenterCharacterResolved(urlOrNull) {
|
|
var centerEl = document.getElementById('lobby-character-img');
|
|
if (!centerEl) return;
|
|
|
|
function showSceneOnly() {
|
|
centerEl.classList.remove('lobby-character-img--visible');
|
|
centerEl.removeAttribute('src');
|
|
}
|
|
|
|
function revealChar() {
|
|
centerEl.classList.add('lobby-character-img--visible');
|
|
}
|
|
|
|
function showFallbackCenterCharacter() {
|
|
var fb = mainMenuCharFallbackUrl();
|
|
centerEl.onload = revealChar;
|
|
centerEl.onerror = function () {
|
|
centerEl.onerror = null;
|
|
showSceneOnly();
|
|
};
|
|
centerEl.src = fb;
|
|
if (centerEl.complete && centerEl.naturalWidth > 0) {
|
|
revealChar();
|
|
}
|
|
}
|
|
|
|
function showWithChar(url) {
|
|
centerEl.onload = revealChar;
|
|
centerEl.onerror = function () {
|
|
showFallbackCenterCharacter();
|
|
};
|
|
centerEl.src = url;
|
|
if (centerEl.complete && centerEl.naturalWidth > 0) {
|
|
revealChar();
|
|
}
|
|
}
|
|
|
|
if (!urlOrNull) {
|
|
showFallbackCenterCharacter();
|
|
return;
|
|
}
|
|
showWithChar(urlOrNull);
|
|
}
|
|
|
|
// คำนวณ offset จากความสูงจอด้วย linear interpolation:
|
|
// 1080 -> 18cqh, 650 -> 25cqh
|
|
function applyCharacterFootOffsetByViewport() {
|
|
var root = document.documentElement;
|
|
if (!root) return;
|
|
var h = Math.max(1, window.innerHeight || 0);
|
|
var h1 = 650;
|
|
var y1 = 25;
|
|
var h2 = 1080;
|
|
var y2 = 18;
|
|
var t = (h - h1) / (h2 - h1);
|
|
var cqhValue = y1 + ((y2 - y1) * t);
|
|
// กันหลุดช่วงเมื่อจอเล็ก/ใหญ่เกินช่วงอ้างอิง
|
|
cqhValue = Math.max(18, Math.min(25, cqhValue));
|
|
root.style.setProperty('--lobby-character-foot-offset', cqhValue.toFixed(3) + 'cqh');
|
|
}
|
|
|
|
function syncLeaderboardScrollbarThumb() {
|
|
var scrollEl = document.querySelector('.lobby-leaderboard-scroll');
|
|
var thumbEl = document.getElementById('lobby-leaderboard-scroll-thumb');
|
|
var trackEl = document.getElementById('lobby-leaderboard-scroll-track');
|
|
if (!scrollEl || !thumbEl) return;
|
|
|
|
var styles = window.getComputedStyle(thumbEl);
|
|
var topInset = parseFloat(styles.top) || 0;
|
|
var minThumb = parseFloat(styles.minHeight) || 18;
|
|
var maxScroll = Math.max(0, scrollEl.scrollHeight - scrollEl.clientHeight);
|
|
if (maxScroll <= 0) {
|
|
thumbEl.style.display = 'none';
|
|
if (trackEl) trackEl.style.display = 'none';
|
|
return;
|
|
}
|
|
if (trackEl) trackEl.style.display = 'block';
|
|
thumbEl.style.display = 'block';
|
|
|
|
var trackHeight = (trackEl && trackEl.clientHeight) ? trackEl.clientHeight : scrollEl.clientHeight;
|
|
var visibleRatio = scrollEl.clientHeight / Math.max(1, scrollEl.scrollHeight);
|
|
var thumbHeight = Math.max(minThumb, Math.round(trackHeight * visibleRatio));
|
|
var maxY = Math.max(0, trackHeight - topInset - thumbHeight);
|
|
var y = topInset + (maxY * (scrollEl.scrollTop / maxScroll));
|
|
|
|
thumbEl.style.height = thumbHeight + 'px';
|
|
thumbEl.style.transform = 'translateY(' + y.toFixed(2) + 'px)';
|
|
}
|
|
|
|
// วาง leaderboard ให้ต่อจากปุ่ม daily โดยไม่ทับกัน
|
|
function placeLeaderboardBelowDaily() {
|
|
var dailyBtn = document.getElementById('btn-daily');
|
|
var leaderboard = document.querySelector('.lobby-leaderboard');
|
|
var lobbyUi = document.querySelector('.lobby-ui');
|
|
if (!dailyBtn || !leaderboard || !lobbyUi) return;
|
|
|
|
var boardStyle = window.getComputedStyle(leaderboard);
|
|
if (boardStyle.position !== 'absolute') {
|
|
leaderboard.style.top = '';
|
|
return;
|
|
}
|
|
|
|
var uiRect = lobbyUi.getBoundingClientRect();
|
|
var dailyRect = dailyBtn.getBoundingClientRect();
|
|
var boardRect = leaderboard.getBoundingClientRect();
|
|
|
|
var cssGap = boardStyle.getPropertyValue('--lobby-daily-leaderboard-gap').trim();
|
|
var gapPx = 8;
|
|
if (cssGap) {
|
|
var n = parseFloat(cssGap);
|
|
if (!Number.isNaN(n)) {
|
|
if (cssGap.endsWith('cqh')) gapPx = (uiRect.height * n) / 100;
|
|
else if (cssGap.endsWith('px') || /^[+-]?\d+(\.\d+)?$/.test(cssGap)) gapPx = n;
|
|
else if (cssGap.endsWith('vh')) gapPx = ((window.innerHeight || 0) * n) / 100;
|
|
else if (cssGap.endsWith('%')) gapPx = (uiRect.height * n) / 100;
|
|
}
|
|
}
|
|
|
|
var nextTop = (dailyRect.bottom - uiRect.top) + gapPx;
|
|
var maxTop = Math.max(0, uiRect.height - boardRect.height - Math.max(0, gapPx));
|
|
var clampedTop = Math.max(0, Math.min(nextTop, maxTop));
|
|
|
|
leaderboard.style.top = clampedTop.toFixed(2) + 'px';
|
|
}
|
|
|
|
var leaderboardLayoutRaf = 0;
|
|
function scheduleLeaderboardPlacement() {
|
|
if (leaderboardLayoutRaf) cancelAnimationFrame(leaderboardLayoutRaf);
|
|
leaderboardLayoutRaf = requestAnimationFrame(function () {
|
|
leaderboardLayoutRaf = 0;
|
|
placeLeaderboardBelowDaily();
|
|
syncLeaderboardScrollbarThumb();
|
|
});
|
|
}
|
|
|
|
function bindLeaderboardScrollSync() {
|
|
var scrollEl = document.querySelector('.lobby-leaderboard-scroll');
|
|
if (!scrollEl || scrollEl.dataset.scrollSyncBound === '1') return;
|
|
scrollEl.addEventListener('scroll', syncLeaderboardScrollbarThumb, { passive: true });
|
|
scrollEl.dataset.scrollSyncBound = '1';
|
|
}
|
|
|
|
function bindLeaderboardWheelSupport() {
|
|
var scrollEl = document.querySelector('.lobby-leaderboard-scroll');
|
|
if (!scrollEl || scrollEl.dataset.wheelSupportBound === '1') return;
|
|
|
|
scrollEl.addEventListener('wheel', function (e) {
|
|
var maxScroll = Math.max(0, scrollEl.scrollHeight - scrollEl.clientHeight);
|
|
if (maxScroll <= 0) return;
|
|
scrollEl.scrollTop += e.deltaY;
|
|
syncLeaderboardScrollbarThumb();
|
|
e.preventDefault();
|
|
}, { passive: false });
|
|
|
|
scrollEl.dataset.wheelSupportBound = '1';
|
|
}
|
|
|
|
function bindLeaderboardThumbDrag() {
|
|
var scrollEl = document.querySelector('.lobby-leaderboard-scroll');
|
|
var thumbEl = document.getElementById('lobby-leaderboard-scroll-thumb');
|
|
var trackEl = document.getElementById('lobby-leaderboard-scroll-track');
|
|
if (!scrollEl || !thumbEl || !trackEl || thumbEl.dataset.dragBound === '1') return;
|
|
|
|
var dragging = false;
|
|
var dragOffsetY = 0;
|
|
|
|
function getThumbMetrics() {
|
|
var styles = window.getComputedStyle(thumbEl);
|
|
var topInset = parseFloat(styles.top) || 0;
|
|
var thumbHeight = thumbEl.clientHeight || parseFloat(thumbEl.style.height) || 18;
|
|
var trackHeight = trackEl.clientHeight || scrollEl.clientHeight || 1;
|
|
var maxTranslate = Math.max(topInset, trackHeight - thumbHeight);
|
|
var travel = Math.max(1, maxTranslate - topInset);
|
|
return { topInset: topInset, thumbHeight: thumbHeight, trackHeight: trackHeight, maxTranslate: maxTranslate, travel: travel };
|
|
}
|
|
|
|
function stopDrag() {
|
|
if (!dragging) return;
|
|
dragging = false;
|
|
thumbEl.classList.remove('is-dragging');
|
|
document.body.classList.remove('lobby-leaderboard-dragging');
|
|
}
|
|
|
|
thumbEl.addEventListener('mousedown', function (e) {
|
|
if (e.button !== 0) return;
|
|
var maxScroll = Math.max(0, scrollEl.scrollHeight - scrollEl.clientHeight);
|
|
if (maxScroll <= 0) return;
|
|
var metrics = getThumbMetrics();
|
|
var thumbRect = thumbEl.getBoundingClientRect();
|
|
dragOffsetY = Math.max(0, Math.min(metrics.thumbHeight, e.clientY - thumbRect.top));
|
|
dragging = true;
|
|
thumbEl.classList.add('is-dragging');
|
|
document.body.classList.add('lobby-leaderboard-dragging');
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
});
|
|
|
|
window.addEventListener('mousemove', function (e) {
|
|
if (!dragging) return;
|
|
var maxScroll = Math.max(0, scrollEl.scrollHeight - scrollEl.clientHeight);
|
|
if (maxScroll <= 0) {
|
|
stopDrag();
|
|
return;
|
|
}
|
|
var metrics = getThumbMetrics();
|
|
var trackRect = trackEl.getBoundingClientRect();
|
|
var pointerY = e.clientY - trackRect.top;
|
|
var nextTranslate = pointerY - dragOffsetY;
|
|
nextTranslate = Math.max(metrics.topInset, Math.min(metrics.maxTranslate, nextTranslate));
|
|
var ratio = (nextTranslate - metrics.topInset) / metrics.travel;
|
|
scrollEl.scrollTop = ratio * maxScroll;
|
|
syncLeaderboardScrollbarThumb();
|
|
e.preventDefault();
|
|
});
|
|
|
|
window.addEventListener('mouseup', stopDrag);
|
|
window.addEventListener('blur', stopDrag);
|
|
|
|
thumbEl.dataset.dragBound = '1';
|
|
}
|
|
|
|
function bindLeaderboardDragScroll() {
|
|
var scrollEl = document.querySelector('.lobby-leaderboard-scroll');
|
|
if (!scrollEl || scrollEl.dataset.dragScrollBound === '1') return;
|
|
|
|
var dragging = false;
|
|
var moved = false;
|
|
var startY = 0;
|
|
var startScrollTop = 0;
|
|
|
|
function stopDrag() {
|
|
if (!dragging) return;
|
|
dragging = false;
|
|
window.setTimeout(function () { moved = false; }, 0);
|
|
document.body.classList.remove('lobby-leaderboard-dragging');
|
|
scrollEl.classList.remove('is-dragging');
|
|
}
|
|
|
|
scrollEl.addEventListener('mousedown', function (e) {
|
|
if (e.button !== 0) return;
|
|
var maxScroll = Math.max(0, scrollEl.scrollHeight - scrollEl.clientHeight);
|
|
if (maxScroll <= 0) return;
|
|
dragging = true;
|
|
moved = false;
|
|
startY = e.clientY;
|
|
startScrollTop = scrollEl.scrollTop;
|
|
document.body.classList.add('lobby-leaderboard-dragging');
|
|
scrollEl.classList.add('is-dragging');
|
|
e.preventDefault();
|
|
});
|
|
|
|
window.addEventListener('mousemove', function (e) {
|
|
if (!dragging) return;
|
|
var deltaY = e.clientY - startY;
|
|
if (Math.abs(deltaY) > 2) moved = true;
|
|
scrollEl.scrollTop = startScrollTop - deltaY;
|
|
syncLeaderboardScrollbarThumb();
|
|
e.preventDefault();
|
|
});
|
|
|
|
window.addEventListener('mouseup', stopDrag);
|
|
window.addEventListener('blur', stopDrag);
|
|
scrollEl.addEventListener('mouseleave', function (e) {
|
|
if (!dragging) return;
|
|
if (e.buttons === 0) stopDrag();
|
|
});
|
|
|
|
scrollEl.addEventListener('click', function (e) {
|
|
if (!moved) return;
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
}, true);
|
|
|
|
scrollEl.dataset.dragScrollBound = '1';
|
|
}
|
|
|
|
var guideStep = 1;
|
|
var guideImg = document.getElementById('guide-img');
|
|
var guideBar = document.getElementById('lobby-guide-bar');
|
|
var guideDim = document.getElementById('lobby-guide-dim');
|
|
var guideFocus = document.getElementById('lobby-guide-focus');
|
|
|
|
function getGuideStepConfig(step) {
|
|
return GUIDE_STEPS[step - 1] || { position: 'bottom', highlight: null, pad: 10 };
|
|
}
|
|
|
|
function hideGuideFocus() {
|
|
if (!guideFocus) return;
|
|
guideFocus.classList.add('hidden');
|
|
guideFocus.style.left = '0';
|
|
guideFocus.style.top = '0';
|
|
guideFocus.style.width = '0';
|
|
guideFocus.style.height = '0';
|
|
}
|
|
|
|
function scheduleGuideFocusUpdate() {
|
|
requestAnimationFrame(function () {
|
|
requestAnimationFrame(updateGuideFocus);
|
|
});
|
|
}
|
|
|
|
function updateGuideFocus() {
|
|
if (!guideFocus || !guideBar || guideBar.classList.contains('hidden')) {
|
|
hideGuideFocus();
|
|
return;
|
|
}
|
|
var cfg = getGuideStepConfig(guideStep);
|
|
if (!cfg.highlight) {
|
|
hideGuideFocus();
|
|
return;
|
|
}
|
|
var target = document.querySelector(cfg.highlight);
|
|
if (!target) {
|
|
hideGuideFocus();
|
|
return;
|
|
}
|
|
var pad = typeof cfg.pad === 'number' ? cfg.pad : 8;
|
|
var r = target.getBoundingClientRect();
|
|
if (r.width < 4 || r.height < 4) {
|
|
hideGuideFocus();
|
|
return;
|
|
}
|
|
var cs = window.getComputedStyle(target);
|
|
var br = parseInt(cs.borderRadius, 10) || 0;
|
|
var radius = Math.max(br + 4, 14);
|
|
|
|
guideFocus.classList.remove('hidden');
|
|
guideFocus.style.left = (r.left - pad) + 'px';
|
|
guideFocus.style.top = (r.top - pad) + 'px';
|
|
guideFocus.style.width = (r.width + pad * 2) + 'px';
|
|
guideFocus.style.height = (r.height + pad * 2) + 'px';
|
|
guideFocus.style.borderRadius = radius + 'px';
|
|
}
|
|
|
|
function syncGuideBodyClass() {
|
|
if (!guideBar) return;
|
|
var open = !guideBar.classList.contains('hidden');
|
|
if (open) {
|
|
document.body.classList.add('lobby-guide-active');
|
|
var cfg = getGuideStepConfig(guideStep);
|
|
document.body.classList.toggle('lobby-guide-top-step', cfg.position === 'top');
|
|
var hasFocus = !!cfg.highlight;
|
|
if (guideDim) guideDim.classList.toggle('hidden', hasFocus);
|
|
} else {
|
|
document.body.classList.remove('lobby-guide-active');
|
|
document.body.classList.remove('lobby-guide-top-step');
|
|
if (guideDim) guideDim.classList.add('hidden');
|
|
}
|
|
if (open) {
|
|
scheduleGuideFocusUpdate();
|
|
} else {
|
|
hideGuideFocus();
|
|
}
|
|
}
|
|
|
|
function showGuideStep(n) {
|
|
if (!guideImg || !guideBar) return;
|
|
if (n < 1 || n > MAX_GUIDE) {
|
|
guideBar.classList.add('hidden');
|
|
guideBar.classList.remove('lobby-guide-bar--top');
|
|
hideGuideFocus();
|
|
try {
|
|
localStorage.setItem(GUIDE_KEY, '1');
|
|
} catch (e) { /* ignore */ }
|
|
syncGuideBodyClass();
|
|
return;
|
|
}
|
|
guideStep = n;
|
|
var cfg = getGuideStepConfig(guideStep);
|
|
guideImg.src = ML_GUIDE + '/guide-' + (n < 10 ? '0' + n : String(n)) + '.png';
|
|
guideImg.alt = 'คำแนะนำ ขั้นที่ ' + n;
|
|
guideBar.classList.toggle('lobby-guide-bar--top', cfg.position === 'top');
|
|
var nextBtn = document.getElementById('btn-guide-next');
|
|
if (nextBtn) {
|
|
nextBtn.setAttribute('aria-label', n >= MAX_GUIDE ? 'รับทราบ' : 'ถัดไป');
|
|
}
|
|
syncGuideBodyClass();
|
|
guideImg.onload = function () {
|
|
scheduleGuideFocusUpdate();
|
|
};
|
|
guideImg.onerror = function () {
|
|
scheduleGuideFocusUpdate();
|
|
};
|
|
scheduleGuideFocusUpdate();
|
|
}
|
|
|
|
function initGuide() {
|
|
if (!guideBar || !guideImg) return;
|
|
try {
|
|
if (localStorage.getItem(GUIDE_KEY) === '1') {
|
|
guideBar.classList.add('hidden');
|
|
syncGuideBodyClass();
|
|
return;
|
|
}
|
|
} catch (e) { /* ignore */ }
|
|
guideBar.classList.remove('hidden');
|
|
showGuideStep(1);
|
|
}
|
|
|
|
document.getElementById('btn-guide-next')?.addEventListener('click', function () {
|
|
if (guideStep >= MAX_GUIDE) {
|
|
if (guideBar) {
|
|
guideBar.classList.add('hidden');
|
|
guideBar.classList.remove('lobby-guide-bar--top');
|
|
}
|
|
try {
|
|
localStorage.setItem(GUIDE_KEY, '1');
|
|
} catch (e) { /* ignore */ }
|
|
syncGuideBodyClass();
|
|
return;
|
|
}
|
|
showGuideStep(guideStep + 1);
|
|
});
|
|
|
|
document.addEventListener('keydown', function (e) {
|
|
if (e.key !== 'Escape') return;
|
|
if (!guideBar || guideBar.classList.contains('hidden')) return;
|
|
e.preventDefault();
|
|
guideBar.classList.add('hidden');
|
|
guideBar.classList.remove('lobby-guide-bar--top');
|
|
hideGuideFocus();
|
|
syncGuideBodyClass();
|
|
});
|
|
|
|
window.addEventListener('resize', function () {
|
|
if (guideBar && !guideBar.classList.contains('hidden')) {
|
|
scheduleGuideFocusUpdate();
|
|
}
|
|
});
|
|
window.addEventListener('orientationchange', function () {
|
|
setTimeout(scheduleGuideFocusUpdate, 350);
|
|
});
|
|
|
|
/* ใช้ capture + data-href บน .lobby-footer-center — ลดโอกาสถูกเลเยอร์อื่นแย่งคลิกก่อนถึงปุ่ม */
|
|
var footerCenter = document.querySelector('.lobby-footer-center');
|
|
if (footerCenter) {
|
|
footerCenter.addEventListener('click', function (e) {
|
|
var btn = e.target.closest('button[data-href]');
|
|
if (!btn || !footerCenter.contains(btn)) return;
|
|
var href = btn.getAttribute('data-href');
|
|
if (!href) return;
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
e.stopImmediatePropagation();
|
|
window.location.href = href;
|
|
}, true);
|
|
}
|
|
|
|
document.getElementById('btn-cloth')?.addEventListener('click', function () {
|
|
if (typeof window.openCustomizePopup === 'function') {
|
|
window.openCustomizePopup();
|
|
return;
|
|
}
|
|
window.location.href = BASE + '/character.html';
|
|
});
|
|
|
|
document.getElementById('btn-daily')?.addEventListener('click', function () {
|
|
if (typeof window.openDailyPopup === 'function') {
|
|
window.openDailyPopup();
|
|
return;
|
|
}
|
|
toast('รางวัลประจำวัน — เข้าเกมทุกวันเพื่อรับรางวัลต่อเนื่อง!');
|
|
});
|
|
|
|
document.getElementById('btn-myprofile')?.addEventListener('click', function () {
|
|
if (typeof window.openProfilePopup === 'function') {
|
|
window.openProfilePopup();
|
|
return;
|
|
}
|
|
window.location.href = typeof appPath === 'function' ? appPath('/Main-Menu/') : '/Main-Menu/';
|
|
});
|
|
|
|
function getMlAiSessionId() {
|
|
try {
|
|
var k = 'mlAiChatSession';
|
|
var v = localStorage.getItem(k);
|
|
if (!v) {
|
|
v = 'ml-' + Date.now() + '-' + Math.random().toString(36).slice(2, 11);
|
|
localStorage.setItem(k, v);
|
|
}
|
|
return v;
|
|
} catch (e) {
|
|
return 'ml-' + String(Date.now());
|
|
}
|
|
}
|
|
|
|
var aiChatCloseBtn = document.getElementById('lobby-ai-chat-close-btn');
|
|
var aiChatPanel = document.getElementById('lobby-ai-chat-panel');
|
|
var aiChatToggleImg = document.getElementById('lobby-ai-chat-toggle-img');
|
|
var btnAiChat = document.getElementById('btn-ai-chat');
|
|
|
|
function syncAiChatPanelUi() {
|
|
if (!aiChatPanel) return;
|
|
var hidden = aiChatPanel.classList.contains('lobby-ai-chat-hidden');
|
|
if (btnAiChat) btnAiChat.style.display = hidden ? '' : 'none';
|
|
if (hidden || !aiChatToggleImg || !aiChatCloseBtn) return;
|
|
aiChatToggleImg.src = BASE + '/img/chat-close-btn.png';
|
|
aiChatToggleImg.alt = 'ปิด';
|
|
aiChatCloseBtn.setAttribute('title', 'ปิดแชท AI');
|
|
aiChatCloseBtn.setAttribute('aria-label', 'ปิดแชท AI');
|
|
}
|
|
|
|
if (aiChatCloseBtn && aiChatPanel) {
|
|
aiChatCloseBtn.addEventListener('click', function () {
|
|
aiChatPanel.classList.add('lobby-ai-chat-hidden');
|
|
aiChatPanel.classList.remove('chat-panel-collapsed');
|
|
syncAiChatPanelUi();
|
|
});
|
|
}
|
|
|
|
if (btnAiChat && aiChatPanel) {
|
|
btnAiChat.addEventListener('click', function () {
|
|
if (aiChatPanel.classList.contains('lobby-ai-chat-hidden')) {
|
|
aiChatPanel.classList.remove('lobby-ai-chat-hidden');
|
|
aiChatPanel.classList.remove('chat-panel-collapsed');
|
|
syncAiChatPanelUi();
|
|
var inp = document.getElementById('lobby-ai-chat-input');
|
|
if (inp) {
|
|
try {
|
|
inp.focus();
|
|
} catch (e) { /* ignore */ }
|
|
}
|
|
} else {
|
|
aiChatPanel.classList.add('lobby-ai-chat-hidden');
|
|
syncAiChatPanelUi();
|
|
}
|
|
});
|
|
}
|
|
syncAiChatPanelUi();
|
|
|
|
var aiChatForm = document.getElementById('lobby-ai-chat-form');
|
|
var aiChatInput = document.getElementById('lobby-ai-chat-input');
|
|
var aiChatSendBtn = aiChatForm ? aiChatForm.querySelector('button[type="submit"]') : null;
|
|
var aiChatLoadingEl = null;
|
|
|
|
function appendAiMessage(text, isAi) {
|
|
var el = document.getElementById('lobby-ai-chat-messages');
|
|
if (!el) return;
|
|
if (isAi) removeAiChatLoading();
|
|
var div = document.createElement('div');
|
|
div.className = 'lobby-ai-chat-msg ' + (isAi ? 'lobby-ai-chat-msg-ai' : 'lobby-ai-chat-msg-user');
|
|
div.textContent = (isAi ? 'AI: ' : '') + text;
|
|
el.appendChild(div);
|
|
el.scrollTop = 1e9;
|
|
}
|
|
|
|
function showAiChatLoading() {
|
|
var el = document.getElementById('lobby-ai-chat-messages');
|
|
if (!el || aiChatLoadingEl) return;
|
|
aiChatLoadingEl = document.createElement('div');
|
|
aiChatLoadingEl.className = 'lobby-ai-chat-msg lobby-ai-chat-msg-ai lobby-ai-chat-typing';
|
|
aiChatLoadingEl.setAttribute('aria-label', 'กำลังพิมพ์');
|
|
aiChatLoadingEl.innerHTML = '<span class="lobby-ai-typing-dot"></span><span class="lobby-ai-typing-dot"></span><span class="lobby-ai-typing-dot"></span>';
|
|
el.appendChild(aiChatLoadingEl);
|
|
el.scrollTop = 1e9;
|
|
}
|
|
|
|
function removeAiChatLoading() {
|
|
if (aiChatLoadingEl && aiChatLoadingEl.parentNode) {
|
|
aiChatLoadingEl.parentNode.removeChild(aiChatLoadingEl);
|
|
aiChatLoadingEl = null;
|
|
}
|
|
}
|
|
|
|
function setAiChatWaiting(waiting) {
|
|
if (aiChatInput) aiChatInput.disabled = waiting;
|
|
if (aiChatSendBtn) aiChatSendBtn.disabled = waiting;
|
|
if (waiting) showAiChatLoading();
|
|
else removeAiChatLoading();
|
|
}
|
|
|
|
if (aiChatForm && aiChatInput) {
|
|
aiChatForm.addEventListener('submit', function (e) {
|
|
e.preventDefault();
|
|
var text = (aiChatInput.value || '').trim();
|
|
if (!text) return;
|
|
appendAiMessage(text, false);
|
|
setAiChatWaiting(true);
|
|
aiChatInput.value = '';
|
|
fetch(SERVER + '/api/ai-chat', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ message: text, sessionId: getMlAiSessionId() }),
|
|
})
|
|
.then(function (r) { return r.json(); })
|
|
.then(function (data) {
|
|
if (data.response != null) appendAiMessage(data.response, true);
|
|
else if (data.error) appendAiMessage('[ข้อผิดพลาด: ' + data.error + ']', true);
|
|
})
|
|
.catch(function () {
|
|
appendAiMessage('[ส่งไม่สำเร็จ]', true);
|
|
})
|
|
.finally(function () {
|
|
setAiChatWaiting(false);
|
|
});
|
|
});
|
|
}
|
|
|
|
var howto = document.getElementById('lobby-howto-overlay');
|
|
function openHowto() {
|
|
if (howto) howto.classList.remove('hidden');
|
|
}
|
|
function closeHowto() {
|
|
if (howto) howto.classList.add('hidden');
|
|
}
|
|
|
|
/** ปุ่ม ? — คู่มือทีละขั้น ตาม /Main-Lobby/IMAGE/guide */
|
|
function openGuideTutorial() {
|
|
if (!guideBar || !guideImg) return;
|
|
closeHowto();
|
|
guideBar.classList.remove('hidden');
|
|
showGuideStep(1);
|
|
}
|
|
|
|
document.getElementById('btn-tutorial')?.addEventListener('click', openGuideTutorial);
|
|
document.getElementById('btn-howto-got-it')?.addEventListener('click', closeHowto);
|
|
howto?.addEventListener('click', function (e) {
|
|
if (e.target === howto) closeHowto();
|
|
});
|
|
|
|
applyCharacterFootOffsetByViewport();
|
|
syncMainLobbyCharacterUi();
|
|
bindLeaderboardScrollSync();
|
|
bindLeaderboardWheelSupport();
|
|
bindLeaderboardDragScroll();
|
|
bindLeaderboardThumbDrag();
|
|
scheduleLeaderboardPlacement();
|
|
initGuide();
|
|
|
|
window.addEventListener('pageshow', function () {
|
|
applyCharacterFootOffsetByViewport();
|
|
syncMainLobbyCharacterUi();
|
|
scheduleLeaderboardPlacement();
|
|
});
|
|
window.addEventListener('resize', function () {
|
|
applyCharacterFootOffsetByViewport();
|
|
bindLeaderboardScrollSync();
|
|
bindLeaderboardWheelSupport();
|
|
bindLeaderboardDragScroll();
|
|
bindLeaderboardThumbDrag();
|
|
scheduleLeaderboardPlacement();
|
|
});
|
|
window.addEventListener('orientationchange', function () {
|
|
setTimeout(function () {
|
|
applyCharacterFootOffsetByViewport();
|
|
scheduleLeaderboardPlacement();
|
|
}, 250);
|
|
});
|
|
window.addEventListener('load', function () {
|
|
bindLeaderboardScrollSync();
|
|
bindLeaderboardWheelSupport();
|
|
bindLeaderboardDragScroll();
|
|
bindLeaderboardThumbDrag();
|
|
scheduleLeaderboardPlacement();
|
|
});
|
|
document.addEventListener('visibilitychange', function () {
|
|
if (document.visibilityState === 'visible') {
|
|
applyCharacterFootOffsetByViewport();
|
|
syncMainLobbyCharacterUi();
|
|
scheduleLeaderboardPlacement();
|
|
}
|
|
});
|
|
window.addEventListener('storage', function (e) {
|
|
if (e.key == null || e.key === CHAR_KEY || e.key === 'playerName' || e.key === DISPLAY_NAME_STORAGE_KEY) {
|
|
syncMainLobbyCharacterUi();
|
|
return;
|
|
}
|
|
if (e.key && String(e.key).indexOf(LOBBY_IDLE_DOWN_PREFIX) === 0) {
|
|
syncMainLobbyCharacterUi();
|
|
}
|
|
});
|
|
})();
|