This commit is contained in:
2026-07-01 02:23:54 +00:00
parent 789be85df6
commit 0d3342027b
6 changed files with 13142 additions and 1 deletions
+1
View File
@@ -54,6 +54,7 @@
function botCountForSlot(n) {
var humans = Math.min(LOBBY_TOTAL_SLOTS, Math.max(1, parseInt(n, 10) || 1));
if (humans >= 4) return 0; /* เลือก ≥ 4 คน → ไม่มีบอท (รอคนจริง) — ต้องตรงกับ server effectiveBotSlotCount */
return Math.max(0, LOBBY_TOTAL_SLOTS - humans);
}
@@ -0,0 +1,255 @@
(function () {
'use strict';
if (typeof ensureLocalDevLogin === 'function') ensureLocalDevLogin();
function checkLoginStatus() {
var isLoggedIn = localStorage.getItem('isLoggedIn');
if (!isLoggedIn || isLoggedIn !== 'true') {
window.location.href = typeof appPath === 'function' ? appPath('/Login/') : '/Login/';
return false;
}
return true;
}
if (!checkLoginStatus()) return;
var BASE = typeof appPath === 'function' ? appPath('/Game') : '/Game';
var PROD_GAME_API = 'https://srv1361159.hstgr.cloud/Game';
function resolveCreateRoomApiBase() {
if (typeof resolveGameApiBase === 'function') {
return resolveGameApiBase().replace(/\/$/, '');
}
if (typeof window.__JD_GAME_API_BASE__ === 'string' && window.__JD_GAME_API_BASE__) {
return window.__JD_GAME_API_BASE__.replace(/\/$/, '');
}
var pageGame = BASE.replace(/\/$/, '');
try {
var h = (location.hostname || '').toLowerCase();
if (h === 'localhost' || h === '127.0.0.1') {
try {
if (localStorage.getItem('jdUseLocalGameApi') === '1') {
var port = localStorage.getItem('jdGameNodePort') || '13010';
return 'http://127.0.0.1:' + String(port).replace(/\D/g, '') + '/Game';
}
} catch (e) { /* ignore */ }
return PROD_GAME_API;
}
} catch (e2) { /* ignore */ }
return pageGame;
}
var SERVER = resolveCreateRoomApiBase();
var LOBBY_A_MAP_ID = 'mlsbbxfe';
var LOBBY_TOTAL_SLOTS = 6;
var selectedSlot = 3;
var isPrivate = false;
function getNick() {
return (localStorage.getItem('playerName') || '').trim() || 'ผู้เล่น';
}
function botCountForSlot(n) {
var humans = Math.min(LOBBY_TOTAL_SLOTS, Math.max(1, parseInt(n, 10) || 1));
return Math.max(0, LOBBY_TOTAL_SLOTS - humans);
}
function maxPlayersForSlot(n) {
var humans = Math.min(LOBBY_TOTAL_SLOTS, Math.max(1, parseInt(n, 10) || 3));
return Math.min(10, Math.max(1, humans));
}
var btnBack = document.getElementById('btn-back');
var tabPublic = document.getElementById('tab-public');
var tabPrivate = document.getElementById('tab-private');
var privateBlock = document.getElementById('create-private-block');
var inputRoomName = document.getElementById('create-room-name');
var inputPrivatePin = document.getElementById('create-private-pin');
var playerBtnsWrap = document.getElementById('create-player-btns');
var btnConfirm = document.getElementById('btn-confirm');
var statusEl = document.getElementById('create-status');
var summaryEl = document.getElementById('create-summary');
function updateSummary() {
if (!summaryEl) return;
var bots = botCountForSlot(selectedSlot);
summaryEl.textContent = 'สรุปจำนวน : ' + selectedSlot + ' ผู้เล่น + ' + bots + ' Bot';
}
btnBack?.addEventListener('click', function () {
window.location.href = typeof appPath === 'function' ? appPath('/Main-Menu/') : '/Main-Menu/';
});
inputPrivatePin?.addEventListener('input', function (e) {
e.target.value = e.target.value.replace(/[^0-9]/g, '').slice(0, 4);
updateConfirmButtonEnabled();
});
function updateModeImages() {
[tabPublic, tabPrivate].forEach(function (btn) {
if (!btn) return;
var img = btn.querySelector('.create-mode-btn-img');
if (!img) return;
var isActive = btn.classList.contains('create-mode-btn--active');
img.src = isActive ? btn.getAttribute('data-img-h') : btn.getAttribute('data-img');
});
}
function setTab(privateMode) {
isPrivate = privateMode;
tabPublic?.classList.toggle('create-mode-btn--active', !privateMode);
tabPrivate?.classList.toggle('create-mode-btn--active', privateMode);
tabPublic?.setAttribute('aria-selected', privateMode ? 'false' : 'true');
tabPrivate?.setAttribute('aria-selected', privateMode ? 'true' : 'false');
if (privateBlock) privateBlock.classList.toggle('create-view--hidden', !privateMode);
updateModeImages();
updateConfirmButtonEnabled();
}
tabPublic?.addEventListener('click', function () { setTab(false); });
tabPrivate?.addEventListener('click', function () { setTab(true); });
function updatePlayerImages() {
if (!playerBtnsWrap) return;
var buttons = playerBtnsWrap.querySelectorAll('.create-num-btn');
buttons.forEach(function (btn) {
var v = parseInt(btn.getAttribute('data-slot'), 10);
var on = v === selectedSlot;
var img = btn.querySelector('.create-num-btn-img');
if (img) {
img.src = on ? btn.getAttribute('data-img-h') : btn.getAttribute('data-img');
}
btn.setAttribute('aria-pressed', on ? 'true' : 'false');
});
}
function selectSlot(n) {
selectedSlot = Math.min(6, Math.max(1, parseInt(n, 10) || 3));
updatePlayerImages();
updateSummary();
}
function buildPlayerButtons() {
if (!playerBtnsWrap) return;
playerBtnsWrap.innerHTML = '';
for (var i = 1; i <= 6; i++) {
var b = document.createElement('button');
b.type = 'button';
b.className = 'create-num-btn';
b.setAttribute('data-slot', String(i));
b.setAttribute('data-img', 'IMAGE/player-' + i + '.png');
b.setAttribute('data-img-h', 'IMAGE/player-' + i + '-h.png');
b.setAttribute('aria-label', 'เลือก ' + i + ' ผู้เล่น รวม ' + LOBBY_TOTAL_SLOTS + ' ที่นั่ง (Bot ' + botCountForSlot(i) + ' ตัว)');
b.setAttribute('aria-pressed', i === selectedSlot ? 'true' : 'false');
var img = document.createElement('img');
img.className = 'create-num-btn-img';
img.alt = String(i);
img.decoding = 'async';
img.src = i === selectedSlot ? 'IMAGE/player-' + i + '-h.png' : 'IMAGE/player-' + i + '.png';
b.appendChild(img);
b.addEventListener('click', function () {
selectSlot(this.getAttribute('data-slot'));
});
playerBtnsWrap.appendChild(b);
}
updateSummary();
}
function getRoomName() {
return (inputRoomName && inputRoomName.value || '').trim();
}
function getPrivatePinDigits() {
if (!inputPrivatePin) return '';
return String(inputPrivatePin.value || '').replace(/\D/g, '').slice(0, 4);
}
function isPrivatePinComplete() {
return /^\d{4}$/.test(getPrivatePinDigits());
}
function updateConfirmButtonEnabled() {
if (!btnConfirm) return;
if (btnConfirm.getAttribute('data-create-loading') === '1') return;
btnConfirm.disabled = isPrivate && !isPrivatePinComplete();
}
btnConfirm?.addEventListener('click', function () {
if (statusEl) statusEl.textContent = '';
var mapId = LOBBY_A_MAP_ID;
var name = getRoomName();
if (!name) {
if (statusEl) statusEl.textContent = 'กรุณาใส่ชื่อห้อง';
return;
}
if (isPrivate && !isPrivatePinComplete()) {
if (statusEl) statusEl.textContent = 'กรุณาใส่รหัสผ่าน 4 หลัก (ตัวเลข) สำหรับห้องส่วนตัว';
inputPrivatePin?.focus();
return;
}
btnConfirm.setAttribute('data-create-loading', '1');
btnConfirm.disabled = true;
var body = {
mapId: mapId,
name: name,
isPrivate: isPrivate,
maxPlayers: maxPlayersForSlot(selectedSlot),
password: isPrivate ? getPrivatePinDigits() : '',
};
fetch(SERVER + '/api/spaces', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
})
.then(function (r) { return r.json().then(function (j) { return { ok: r.ok, j: j }; }); })
.then(function (res) {
if (!res.j || !res.j.ok) {
if (statusEl) statusEl.textContent = (res.j && res.j.error) || 'สร้างห้องไม่สำเร็จ';
btnConfirm.removeAttribute('data-create-loading');
updateConfirmButtonEnabled();
return;
}
var sid = res.j.spaceId;
if (!sid) {
if (statusEl) statusEl.textContent = 'ไม่ได้รับรหัสห้อง';
btnConfirm.removeAttribute('data-create-loading');
updateConfirmButtonEnabled();
return;
}
if (isPrivate) {
try { localStorage.setItem('roomPinHint_' + sid, getPrivatePinDigits()); } catch (e) {}
}
try {
localStorage.setItem('lastCreatedSpaceId', sid);
localStorage.setItem('lastCreatedSpaceName', name);
} catch (e) {}
var lobbyUrl = BASE + '/room-lobby.html?space=' + encodeURIComponent(sid) + '&nick=' + encodeURIComponent(getNick());
lobbyUrl += '&displayRoom=' + encodeURIComponent(name);
if (isPrivate) lobbyUrl += '&pass=' + encodeURIComponent(getPrivatePinDigits()); /* host เข้าห้องตัวเองผ่านด่านรหัส */
try {
var caseId = localStorage.getItem('caseId');
var prevSpaceName = localStorage.getItem('prevSpaceName');
if (caseId) lobbyUrl += '&caseId=' + encodeURIComponent(caseId);
if (prevSpaceName) lobbyUrl += '&prevSpaceName=' + encodeURIComponent(prevSpaceName);
} catch (e) {}
window.location.replace((typeof appPath === 'function' ? appPath('/Loading/') : '/Loading/') + '?redirect=' + encodeURIComponent(lobbyUrl));
})
.catch(function () {
if (statusEl) statusEl.textContent = 'เชื่อมต่อเซิร์ฟเวอร์ไม่ได้';
btnConfirm.removeAttribute('data-create-loading');
updateConfirmButtonEnabled();
});
});
inputRoomName?.addEventListener('input', function () {
updateConfirmButtonEnabled();
});
buildPlayerButtons();
setTab(false);
})();
+1 -1
View File
@@ -87,7 +87,7 @@
</div>
<script src="../app-base.js"></script>
<script src="create-room.js?v=20260623"></script>
<script src="create-room.js?v=20260701"></script>
<script>
(function(){
var content = document.querySelector('.create-content');
+135
View File
@@ -0,0 +1,135 @@
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>สร้างห้องเกม — JD JUSTICE DIVERS</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Kanit:wght@500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="style.css?v=1.007">
</head>
<body>
<div class="create-bg" aria-hidden="true">
<div class="create-bg-grid"></div>
<img src="../Main-Menu/bg.png" alt="" class="create-bg-img">
</div>
<div class="create-char-layer" aria-hidden="true">
<img src="../Main-Menu/char-main.png" alt="" class="create-char-img">
</div>
<div class="create-vignette" aria-hidden="true"></div>
<div class="create-ui">
<button type="button" class="menu-btn-back" id="btn-back" aria-label="ย้อนกลับ">
<img src="../Main-Menu/IMAGE/btn-back.png" alt="ย้อนกลับ" class="menu-btn-back-img" decoding="async">
</button>
<main class="create-content">
<img src="IMAGE/txt-createroom.png" alt="สร้างห้องเกม" class="create-title-img" decoding="async">
<img src="IMAGE/line.png" alt="" class="create-title-line" aria-hidden="true" decoding="async">
<section class="create-panel" aria-label="ฟอร์มสร้างห้อง">
<!-- ชื่อห้อง -->
<div class="create-field create-field--inline create-field--room">
<img src="IMAGE/txt-roomname.png" alt="ชื่อห้อง :" class="create-label-img" decoding="async">
<div class="create-input-shell">
<input type="text" id="create-room-name" class="create-input" maxlength="48" autocomplete="off" spellcheck="false" placeholder="ตั้งชื่อห้อง" aria-label="ชื่อห้อง">
</div>
</div>
<img src="IMAGE/line.png" alt="" class="create-line" aria-hidden="true" decoding="async">
<!-- โหมด -->
<div class="create-field create-field--inline create-field--mode">
<img src="IMAGE/txt-mode.png" alt="โหมด :" class="create-label-img" decoding="async">
<div class="create-mode-row" role="tablist" aria-label="โหมดห้อง">
<button type="button" class="create-mode-btn create-mode-btn--active" id="tab-public" role="tab" aria-selected="true" data-img="IMAGE/btn-publish.png" data-img-h="IMAGE/btn-publish-h.png">
<img src="IMAGE/btn-publish-h.png" alt="ห้องสาธารณะ" class="create-mode-btn-img" decoding="async">
</button>
<button type="button" class="create-mode-btn" id="tab-private" role="tab" aria-selected="false" data-img="IMAGE/btn-private.png" data-img-h="IMAGE/btn-private-h.png">
<img src="IMAGE/btn-private.png" alt="ห้องส่วนตัว" class="create-mode-btn-img" decoding="async">
</button>
</div>
</div>
<!-- PASSWORD (ซ่อน default) -->
<div id="create-private-block" class="create-private-block create-view--hidden" role="tabpanel">
<img src="IMAGE/txt-password.png" alt="PASSWORD : (รหัสผ่าน 4 ตัว)" class="create-label-img create-label-img--password" decoding="async">
<div class="create-input-shell create-input-shell--pin">
<input type="text" id="create-private-pin" class="create-input create-input--pin" placeholder="0000" maxlength="4" inputmode="numeric" autocomplete="off" aria-label="รหัสผ่าน 4 หลัก">
</div>
</div>
<img src="IMAGE/line.png" alt="" class="create-line" aria-hidden="true" decoding="async">
<!-- จำนวนผู้เล่น -->
<div class="create-field create-field--inline create-field--players">
<img src="IMAGE/txt-players.png" alt="จำนวนผู้เล่น :" class="create-label-img" decoding="async">
<div class="create-num-grid" id="create-player-btns" role="group" aria-label="จำนวนผู้เล่น"></div>
</div>
<img src="IMAGE/line.png" alt="" class="create-line" aria-hidden="true" decoding="async">
<!-- สรุป -->
<div class="create-summary-wrap">
<div class="create-summary" id="create-summary" aria-live="polite">สรุปจำนวน : 3 ผู้เล่น + 3 Bot</div>
</div>
<!-- ยืนยัน -->
<button type="button" class="create-confirm" id="btn-confirm" aria-label="ยืนยันสร้างห้อง">
<img src="IMAGE/btn-confirm.png" alt="ยืนยัน" class="create-confirm-img" decoding="async">
</button>
<p class="create-status" id="create-status" role="status"></p>
</section>
</main>
</div>
<script src="../app-base.js"></script>
<script src="create-room.js?v=20260623"></script>
<script>
(function(){
var content = document.querySelector('.create-content');
if(!content) return;
var tid = 0;
function autoFit(){
clearTimeout(tid);
tid = setTimeout(function(){
var isLand = window.matchMedia('(orientation:landscape) and (max-width:1200px)').matches
|| window.matchMedia('(orientation:landscape) and (max-height:620px)').matches;
if(!isLand){ content.style.zoom = ''; return; }
content.style.zoom = '1';
requestAnimationFrame(function(){
var vpH = window.innerHeight;
var rect = content.getBoundingClientRect();
var need = rect.top + rect.height;
if(need > vpH){
var s = Math.max(0.4, (vpH * 0.97) / need);
content.style.zoom = String(Math.floor(s * 1000) / 1000);
} else {
content.style.zoom = '1';
}
});
}, 30);
}
autoFit();
window.addEventListener('resize', autoFit);
window.addEventListener('orientationchange', function(){ setTimeout(autoFit, 300); });
var panel = document.querySelector('.create-panel');
if(panel){
new MutationObserver(function(){ setTimeout(autoFit, 80); }).observe(panel, {childList:true, subtree:true, attributes:true, attributeFilter:['class']});
}
/* summary เด้ง (bump) ทุกครั้งที่ตัวเลขสรุปเปลี่ยน */
var sumEl = document.getElementById('create-summary');
if(sumEl){
new MutationObserver(function(){
sumEl.classList.remove('is-bump');
void sumEl.offsetWidth; /* reflow → restart animation */
sumEl.classList.add('is-bump');
}).observe(sumEl, {childList:true, characterData:true, subtree:true});
}
})();
</script>
</body>
</html>
+2
View File
@@ -215,6 +215,8 @@ function effectiveBotSlotCount(space) {
let n = parseInt(space && space.botSlotCount, 10);
if (isNaN(n) || n < 0) n = 0;
const maxP = space && space.maxPlayers ? parseInt(space.maxPlayers, 10) : 0;
/* เลือกผู้เล่น ≥ 4 คนตอนสร้างห้อง → ไม่เติมบอท (รอคนจริง) — เว้นแต่ host ตั้งบอทเองภายหลัง (n>0) */
if (n === 0 && maxP >= 4) return 0;
if (n === 0 && maxP > 0 && maxP < LOBBY_SLOT_TOTAL) {
n = LOBBY_SLOT_TOTAL - maxP;
if (space) space.botSlotCount = n;
File diff suppressed because it is too large Load Diff