feat: initialize sunrise with 100-player shared rooms
This commit is contained in:
+131
@@ -0,0 +1,131 @@
|
||||
import './style.css';
|
||||
import './lobby.css';
|
||||
import { APP_NAME, LOBBY, type LobbyData } from './constants/lobby.ts';
|
||||
|
||||
const lobby = document.getElementById('lobby')!;
|
||||
const title = document.getElementById('lobby-title')!;
|
||||
const status = document.getElementById('lobby-status')!;
|
||||
const options = document.getElementById('lobby-options')!;
|
||||
const back = document.getElementById('lobby-back') as HTMLButtonElement;
|
||||
const retry = document.getElementById('lobby-retry') as HTMLButtonElement;
|
||||
const hud = document.querySelector<HTMLElement>('.hud')!;
|
||||
hud.inert = true;
|
||||
document.title = APP_NAME;
|
||||
let channel: LobbyData['channel'];
|
||||
let channelRequest: AbortController | undefined;
|
||||
let background: { dispose(): void } | undefined;
|
||||
let backgroundCancelled = false;
|
||||
|
||||
function stopBackground() {
|
||||
backgroundCancelled = true;
|
||||
background?.dispose();
|
||||
background = undefined;
|
||||
}
|
||||
|
||||
function message(text: string) { status.textContent = text; status.hidden = !text; }
|
||||
function screen(heading = '', text = '') {
|
||||
title.textContent = heading;
|
||||
title.hidden = !heading;
|
||||
message(text);
|
||||
options.replaceChildren();
|
||||
back.hidden = retry.hidden = true;
|
||||
if (heading) title.focus({ preventScroll: true });
|
||||
}
|
||||
function choice(name: string, detail: string, disabled: boolean, action: () => void) {
|
||||
const button = document.createElement('button');
|
||||
button.type = 'button'; button.className = 'lobby-choice'; button.disabled = disabled;
|
||||
const label = document.createElement('span'); label.textContent = name; button.append(label);
|
||||
if (detail) { const description = document.createElement('small'); description.textContent = detail; button.append(description); }
|
||||
button.addEventListener('click', action); options.append(button);
|
||||
}
|
||||
function home(focus = false) {
|
||||
channelRequest?.abort();
|
||||
screen();
|
||||
choice('혼자보기', '', false, () => { void enter(false); });
|
||||
choice('같이 보기', '', false, () => { void showChannels(); });
|
||||
if (focus) options.querySelector('button')!.focus();
|
||||
}
|
||||
async function showChannels() {
|
||||
channelRequest?.abort();
|
||||
const request = new AbortController(); channelRequest = request;
|
||||
screen('채널 선택', '불러오는 중…');
|
||||
back.hidden = false; back.onclick = () => home(true);
|
||||
try {
|
||||
const response = await fetch(LOBBY.endpoint, { cache: 'no-store', signal: AbortSignal.any([request.signal, AbortSignal.timeout(LOBBY.timeoutMs)]) });
|
||||
if (!response.ok) throw new Error('채널 조회 실패');
|
||||
const data = await response.json() as LobbyData;
|
||||
if (request.signal.aborted) return;
|
||||
channel = data.channel;
|
||||
message(channel.available ? '' : '지금은 입장할 수 없습니다.');
|
||||
choice(channel.name, `${channel.players} / ${channel.capacity}명`, !channel.available, () => { void enter(true); });
|
||||
if (!channel.available) { retry.hidden = false; retry.onclick = () => { void showChannels(); }; }
|
||||
} catch {
|
||||
if (request.signal.aborted) return;
|
||||
message('채널에 연결하지 못했습니다.');
|
||||
retry.hidden = false; retry.onclick = () => { void showChannels(); };
|
||||
}
|
||||
}
|
||||
function reveal() {
|
||||
lobby.hidden = true;
|
||||
document.body.classList.remove('in-lobby');
|
||||
hud.inert = false;
|
||||
document.querySelector<HTMLButtonElement>('.scene-toolbar button')?.focus();
|
||||
}
|
||||
async function enter(shared: boolean) {
|
||||
stopBackground();
|
||||
screen('', '불러오는 중…');
|
||||
const url = new URL(location.href);
|
||||
url.searchParams.set('site', 'gwanaksan');
|
||||
url.searchParams.set('mode', shared ? 'shared' : 'solo');
|
||||
url.searchParams.delete('terrain'); url.searchParams.delete('profile');
|
||||
history.replaceState(null, '', url);
|
||||
let ready = false, connected = !shared, entered = false;
|
||||
const finish = () => {
|
||||
if (!ready || !connected || entered) return;
|
||||
entered = true;
|
||||
requestAnimationFrame(() => requestAnimationFrame(reveal));
|
||||
};
|
||||
const fail = () => {
|
||||
message('입장하지 못했습니다.');
|
||||
retry.textContent = '다시 시도'; retry.hidden = false;
|
||||
retry.onclick = () => location.reload();
|
||||
};
|
||||
try {
|
||||
const scene = await import('./main.ts');
|
||||
await scene.start({
|
||||
roomId: shared ? channel.id : undefined,
|
||||
connection(text, joined) {
|
||||
connected = !shared || joined;
|
||||
if (entered || !shared) return;
|
||||
message(joined ? '불러오는 중…' : text);
|
||||
retry.hidden = joined;
|
||||
retry.textContent = '다시 입장';
|
||||
retry.onclick = () => document.getElementById('retry-connection')!.click();
|
||||
finish();
|
||||
},
|
||||
ready() { ready = true; finish(); },
|
||||
error: fail,
|
||||
});
|
||||
} catch { fail(); }
|
||||
}
|
||||
|
||||
// Explicit solo/comparison and developer links retain direct entry.
|
||||
const query = new URLSearchParams(location.search);
|
||||
if (query.get('mode') === 'solo' || (import.meta.env.DEV && query.has('profile'))) {
|
||||
void import('./main.ts').then(module => module.start({ ready: reveal, error() {
|
||||
message('장면을 불러오지 못했습니다.'); retry.hidden = false; retry.onclick = () => location.reload();
|
||||
} })).catch(() => { message('장면을 불러오지 못했습니다.'); retry.hidden = false; retry.onclick = () => location.reload(); });
|
||||
} else {
|
||||
if (query.get('mode') === 'shared') void showChannels();
|
||||
else home();
|
||||
void import('./scene/lobbySunrise.ts').then(module => {
|
||||
if (backgroundCancelled) return;
|
||||
const scene = module.createLobbySunrise(document.querySelector<HTMLCanvasElement>('#lobby-sunrise')!);
|
||||
background = scene;
|
||||
return scene.ready;
|
||||
}).catch(error => {
|
||||
background?.dispose();
|
||||
console.error('시작 화면 지형을 불러오지 못했습니다.', error);
|
||||
});
|
||||
}
|
||||
window.addEventListener('pagehide', event => { if (!event.persisted) stopBackground(); });
|
||||
Reference in New Issue
Block a user