285 lines
9.0 KiB (Stored with Git LFS)
Plaintext
285 lines
9.0 KiB (Stored with Git LFS)
Plaintext
// 보스 레이드 웹 제어판 - WebSocket 클라이언트
|
|
const WS_PORT = {{WS_PORT}};
|
|
let ws = null;
|
|
let reconnectTimer = null;
|
|
let currentState = {};
|
|
let lastHP = -1;
|
|
let lastPhase = -1;
|
|
|
|
const RAID_STATE_KR = {
|
|
'Idle': '대기',
|
|
'Appear': '등장',
|
|
'Battle': '전투 중',
|
|
'Defeat': '처치',
|
|
'Result': '결과',
|
|
};
|
|
|
|
// ============ WebSocket ============
|
|
|
|
function connect() {
|
|
const host = window.location.hostname || 'localhost';
|
|
ws = new WebSocket(`ws://${host}:${WS_PORT}/bossraid`);
|
|
|
|
ws.onopen = () => {
|
|
setConnectionStatus(true);
|
|
clearTimeout(reconnectTimer);
|
|
send('get_state');
|
|
addLog('서버 연결됨', 'system');
|
|
};
|
|
|
|
ws.onmessage = (e) => {
|
|
try {
|
|
const msg = JSON.parse(e.data);
|
|
if (msg.type === 'state_update') {
|
|
handleStateUpdate(msg.data);
|
|
}
|
|
} catch (err) {
|
|
console.error('파싱 오류:', err);
|
|
}
|
|
};
|
|
|
|
ws.onclose = () => {
|
|
setConnectionStatus(false);
|
|
scheduleReconnect();
|
|
};
|
|
|
|
ws.onerror = () => { ws.close(); };
|
|
}
|
|
|
|
function scheduleReconnect() {
|
|
clearTimeout(reconnectTimer);
|
|
reconnectTimer = setTimeout(connect, 2000);
|
|
}
|
|
|
|
function send(action, params) {
|
|
if (!ws || ws.readyState !== WebSocket.OPEN) return;
|
|
const msg = Object.assign({ action }, params || {});
|
|
ws.send(JSON.stringify(msg));
|
|
}
|
|
|
|
// ============ 상태 업데이트 ============
|
|
|
|
function handleStateUpdate(data) {
|
|
const prev = currentState;
|
|
currentState = data;
|
|
|
|
// 보스 이름
|
|
setText('boss-name', data.bossName || '-');
|
|
|
|
// 레이드 상태
|
|
const stateEl = document.getElementById('raid-state');
|
|
stateEl.textContent = RAID_STATE_KR[data.raidState] || data.raidState || '대기';
|
|
stateEl.className = 'raid-state ' + (data.raidState || '').toLowerCase();
|
|
|
|
// HP 바
|
|
const ratio = data.hpRatio || 0;
|
|
const fill = document.getElementById('hp-bar-fill');
|
|
fill.style.width = (ratio * 100) + '%';
|
|
fill.style.backgroundColor = getHPColor(ratio);
|
|
setText('hp-text', `${data.currentHP} / ${data.maxHP} (${Math.round(ratio * 100)}%)`);
|
|
|
|
// 페이즈 / 상태
|
|
setText('phase-name', data.phaseName || '-');
|
|
setText('boss-state', data.bossState || '-');
|
|
|
|
// 일시정지 버튼
|
|
const pauseBtn = document.getElementById('pause-btn');
|
|
if (data.isPaused) {
|
|
pauseBtn.textContent = '일시정지 해제';
|
|
pauseBtn.classList.add('btn-paused');
|
|
pauseBtn.classList.remove('btn-yellow');
|
|
} else {
|
|
pauseBtn.textContent = '일시정지';
|
|
pauseBtn.classList.remove('btn-paused');
|
|
pauseBtn.classList.add('btn-yellow');
|
|
}
|
|
|
|
// HP 잠금 슬라이더 동기화
|
|
if (data.hpLockRatio !== undefined) {
|
|
document.getElementById('hp-lock-slider').value = Math.round(data.hpLockRatio * 100);
|
|
document.getElementById('hp-lock-value').textContent = Math.round(data.hpLockRatio * 100) + '%';
|
|
}
|
|
|
|
// 볼륨 슬라이더 동기화
|
|
if (data.bgmVolume !== undefined) {
|
|
document.getElementById('bgm-volume').value = Math.round(data.bgmVolume * 100);
|
|
document.getElementById('bgm-volume-value').textContent = Math.round(data.bgmVolume * 100) + '%';
|
|
}
|
|
if (data.sfxVolume !== undefined) {
|
|
document.getElementById('sfx-volume').value = Math.round(data.sfxVolume * 100);
|
|
document.getElementById('sfx-volume-value').textContent = Math.round(data.sfxVolume * 100) + '%';
|
|
}
|
|
|
|
// 유저 HP
|
|
const pRatio = data.playerHPRatio || 0;
|
|
const pFill = document.getElementById('player-hp-fill');
|
|
if (pFill) {
|
|
pFill.style.width = (pRatio * 100) + '%';
|
|
pFill.style.backgroundColor = pRatio > 0.3 ? '#2979ff' : '#ff1744';
|
|
}
|
|
setText('player-hp-text', `${data.playerHP || 0} / ${data.playerMaxHP || 0} (${Math.round(pRatio * 100)}%)`);
|
|
|
|
const pState = document.getElementById('player-state');
|
|
if (pState) {
|
|
if (data.playerDead) { pState.textContent = '사망'; pState.style.background = '#ff1744'; }
|
|
else if (data.playerGodMode) { pState.textContent = '무적'; pState.style.background = '#ffd600'; pState.style.color = '#1a1a2e'; }
|
|
else { pState.textContent = '생존'; pState.style.background = '#00c853'; pState.style.color = '#fff'; }
|
|
}
|
|
|
|
// 무적 버튼
|
|
const godBtn = document.getElementById('god-mode-btn');
|
|
if (godBtn) {
|
|
if (data.playerGodMode) {
|
|
godBtn.textContent = '무적 해제';
|
|
godBtn.classList.add('btn-paused');
|
|
} else {
|
|
godBtn.textContent = '무적 모드';
|
|
godBtn.classList.remove('btn-paused');
|
|
}
|
|
}
|
|
|
|
// 유저 사망 로그
|
|
if (data.playerDead && prev.playerDead !== true) {
|
|
addLog('유저 사망! GAME OVER', 'crit');
|
|
}
|
|
|
|
// 데미지 로그
|
|
if (lastHP >= 0 && data.currentHP < lastHP && data.raidState === 'Battle') {
|
|
const dmg = lastHP - data.currentHP;
|
|
if (dmg > (data.maxHP * 0.03)) {
|
|
addLog(`크리티컬! -${dmg} 데미지 (HP: ${data.currentHP})`, 'crit');
|
|
} else {
|
|
addLog(`-${dmg} 데미지 (HP: ${data.currentHP})`, 'normal');
|
|
}
|
|
}
|
|
|
|
// 페이즈 전환 로그
|
|
if (lastPhase >= 0 && data.phase !== lastPhase) {
|
|
addLog(`페이즈 전환 → ${data.phaseName}`, 'phase');
|
|
}
|
|
|
|
// 사망 로그
|
|
if (data.isDead && prev.isDead !== true) {
|
|
addLog('보스 처치 완료!', 'system');
|
|
}
|
|
|
|
// 레이드 상태 변경 로그
|
|
if (prev.raidState && data.raidState !== prev.raidState) {
|
|
const from = RAID_STATE_KR[prev.raidState] || prev.raidState;
|
|
const to = RAID_STATE_KR[data.raidState] || data.raidState;
|
|
addLog(`레이드: ${from} → ${to}`, 'system');
|
|
}
|
|
|
|
lastHP = data.currentHP;
|
|
lastPhase = data.phase;
|
|
}
|
|
|
|
// ============ UI 액션 ============
|
|
|
|
function manualHit(isCritical) {
|
|
const dmg = parseInt(document.getElementById('manual-damage').value) || 50;
|
|
send('manual_hit', { damage: dmg, critical: isCritical });
|
|
}
|
|
|
|
function setHP() {
|
|
const val = parseInt(document.getElementById('hp-slider').value) / 100;
|
|
send('set_hp', { ratio: val });
|
|
}
|
|
|
|
function hpSliderChanged() {
|
|
const val = document.getElementById('hp-slider').value;
|
|
document.getElementById('hp-slider-value').textContent = val + '%';
|
|
}
|
|
|
|
function toggleGodMode() {
|
|
const isGod = currentState.playerGodMode || false;
|
|
send('toggle_god_mode', { value: !isGod });
|
|
}
|
|
|
|
function setCooldown() {
|
|
const val = parseFloat(document.getElementById('cooldown').value) || 0.3;
|
|
send('set_cooldown', { value: val });
|
|
addLog(`쿨타임 설정: ${val}초`, 'system');
|
|
}
|
|
|
|
function setDamageCap() {
|
|
const val = parseInt(document.getElementById('damage-cap').value) || 0;
|
|
send('set_damage_cap', { value: val });
|
|
addLog(`데미지 캡: ${val === 0 ? '해제' : val}`, 'system');
|
|
}
|
|
|
|
function setHPLock() {
|
|
const val = parseInt(document.getElementById('hp-lock-slider').value) / 100;
|
|
send('set_hp_lock', { ratio: val });
|
|
addLog(`HP 잠금: ${Math.round(val * 100)}%`, 'system');
|
|
}
|
|
|
|
function hpLockChanged() {
|
|
const val = document.getElementById('hp-lock-slider').value;
|
|
document.getElementById('hp-lock-value').textContent = val + '%';
|
|
}
|
|
|
|
function bgmVolumeChanged() {
|
|
const val = parseInt(document.getElementById('bgm-volume').value);
|
|
document.getElementById('bgm-volume-value').textContent = val + '%';
|
|
send('set_bgm_volume', { value: val / 100 });
|
|
}
|
|
|
|
function sfxVolumeChanged() {
|
|
const val = parseInt(document.getElementById('sfx-volume').value);
|
|
document.getElementById('sfx-volume-value').textContent = val + '%';
|
|
send('set_sfx_volume', { value: val / 100 });
|
|
}
|
|
|
|
function confirmKill() {
|
|
if (confirm('보스를 강제 처치하시겠습니까?')) {
|
|
send('force_kill');
|
|
addLog('강제 처치 실행', 'system');
|
|
}
|
|
}
|
|
|
|
// ============ 헬퍼 ============
|
|
|
|
function setText(id, text) {
|
|
const el = document.getElementById(id);
|
|
if (el) el.textContent = text;
|
|
}
|
|
|
|
function setConnectionStatus(connected) {
|
|
const bar = document.getElementById('connection-bar');
|
|
const status = document.getElementById('connection-status');
|
|
if (connected) {
|
|
bar.className = 'connected';
|
|
status.textContent = '연결됨';
|
|
status.style.color = '#00c853';
|
|
} else {
|
|
bar.className = 'disconnected';
|
|
status.textContent = '연결 끊김 — 재연결 중...';
|
|
status.style.color = '#ff1744';
|
|
}
|
|
}
|
|
|
|
function getHPColor(ratio) {
|
|
if (ratio > 0.5) return '#00c853';
|
|
if (ratio > 0.2) return '#ffd600';
|
|
return '#ff1744';
|
|
}
|
|
|
|
function addLog(text, type) {
|
|
const log = document.getElementById('hit-log');
|
|
const entry = document.createElement('div');
|
|
entry.className = 'log-entry-' + type;
|
|
const time = new Date().toLocaleTimeString('ko-KR', { hour12: false });
|
|
entry.textContent = `[${time}] ${text}`;
|
|
log.prepend(entry);
|
|
|
|
while (log.children.length > 100) {
|
|
log.removeChild(log.lastChild);
|
|
}
|
|
}
|
|
|
|
// ============ 초기화 ============
|
|
|
|
setConnectionStatus(false);
|
|
connect();
|