slight improvements to the controller
This commit is contained in:
parent
12b25e8821
commit
7c63796358
94
client.html
94
client.html
@ -38,6 +38,36 @@
|
||||
font-size: 13px;
|
||||
z-index: 10;
|
||||
}
|
||||
#idle-warning {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 10px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: rgba(180,60,0,0.85);
|
||||
padding: 6px 14px;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
z-index: 20;
|
||||
}
|
||||
#idle-warning.visible { display: block; }
|
||||
|
||||
#sound-toggle {
|
||||
position: fixed;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
background: rgba(0,0,0,0.7);
|
||||
color: #fff;
|
||||
border: 1px solid #444;
|
||||
padding: 6px 12px;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
font-family: monospace;
|
||||
cursor: pointer;
|
||||
z-index: 10;
|
||||
}
|
||||
#sound-toggle:hover { background: rgba(40,40,40,0.85); }
|
||||
#sound-toggle.unmuted { color: #4fc; border-color: #4fc; }
|
||||
#gameCanvas {
|
||||
width: 1280px;
|
||||
height: 720px;
|
||||
@ -105,6 +135,9 @@
|
||||
</head>
|
||||
<body>
|
||||
<div id="status">Connecting...</div>
|
||||
<button id="sound-toggle">🔇 Unmute</button>
|
||||
|
||||
<div id="idle-warning">Idle — control will be released soon. Move your mouse to stay in control.</div>
|
||||
|
||||
<div id="queue-panel">
|
||||
<h3>Controller</h3>
|
||||
@ -136,17 +169,16 @@
|
||||
<p>However be aware that the Ingame server browser is not usable as it is filled with fake redirect servers.</p>
|
||||
</div>
|
||||
<div class="faq-item">
|
||||
<h3>How can i join the server?</h3>
|
||||
<p>The IP is 51.195.188.106:27015.</p>
|
||||
<h3>How can i join the servers?</h3>
|
||||
<p>The zombie escape IP: 51.195.188.106:27015.</p>
|
||||
<p>The zombie escape2 IP: 51.195.188.106:27035.</p>
|
||||
<p>The zombie riot IP: 51.195.188.106:27016.</p>
|
||||
<p>The minigame IP: 51.195.188.106:27017.</p>
|
||||
</div>
|
||||
<div class="faq-item">
|
||||
<h3>Want to chat with us?</h3>
|
||||
<p>We have our own self hosted stoat instance: https://unloze.com/stoat/</p>
|
||||
</div>
|
||||
<div class="faq-item">
|
||||
<h3>Render Distance</h3>
|
||||
<p>For the sake of optimizing game performance we sometimes dont render far distance.</p>
|
||||
</div>
|
||||
<div class="faq-item">
|
||||
<h3>Opening the in-game menu</h3>
|
||||
<p>Press ESC to release mouse control, then press ESC again to open the menu.</p>
|
||||
@ -197,6 +229,8 @@
|
||||
const hintEl = document.getElementById('hint');
|
||||
const queueList = document.getElementById('queue-list');
|
||||
const loadingEl = document.getElementById('loading-overlay');
|
||||
const idleWarnEl = document.getElementById('idle-warning');
|
||||
const soundToggleEl = document.getElementById('sound-toggle');
|
||||
|
||||
let sigWs = null;
|
||||
let pc = null;
|
||||
@ -205,6 +239,7 @@
|
||||
let isControlling = false;
|
||||
let isInQueue = false;
|
||||
let isWaitingForRestart = false;
|
||||
let idleWarnTimer = null;
|
||||
|
||||
function setStatus(msg) { statusEl.textContent = msg; }
|
||||
|
||||
@ -212,6 +247,36 @@
|
||||
loadingEl.classList.toggle('visible', visible);
|
||||
}
|
||||
|
||||
// Mirrors the server's IDLE_TIMEOUT (60s); warns 15s before release so
|
||||
// losing control is never a silent surprise.
|
||||
const IDLE_TIMEOUT_MS = 60000;
|
||||
const IDLE_WARN_MS = 15000;
|
||||
function resetIdleWarning() {
|
||||
idleWarnEl.classList.remove('visible');
|
||||
if (idleWarnTimer) clearTimeout(idleWarnTimer);
|
||||
if (!isControlling) return;
|
||||
idleWarnTimer = setTimeout(() => {
|
||||
idleWarnEl.classList.add('visible');
|
||||
}, IDLE_TIMEOUT_MS - IDLE_WARN_MS);
|
||||
}
|
||||
|
||||
function pingActive() {
|
||||
resetIdleWarning();
|
||||
if (sigWs && sigWs.readyState === WebSocket.OPEN) {
|
||||
sigWs.send(JSON.stringify({ type: 'controller_active' }));
|
||||
}
|
||||
}
|
||||
|
||||
function setMuted(muted) {
|
||||
canvas.muted = muted;
|
||||
soundToggleEl.textContent = muted ? '🔇 Unmute' : '🔊 Mute';
|
||||
soundToggleEl.classList.toggle('unmuted', !muted);
|
||||
}
|
||||
|
||||
soundToggleEl.addEventListener('click', () => {
|
||||
setMuted(!canvas.muted);
|
||||
});
|
||||
|
||||
function updateQueuePanel(controller, queue, restarting) {
|
||||
let html = '';
|
||||
if (restarting) {
|
||||
@ -296,9 +361,10 @@
|
||||
isInQueue = false;
|
||||
isWaitingForRestart = false;
|
||||
canvas.classList.replace('watching', 'controlling');
|
||||
canvas.muted = false;
|
||||
setMuted(false);
|
||||
canvas.requestPointerLock();
|
||||
hintEl.textContent = 'Mouse captured — ESC to release';
|
||||
resetIdleWarning();
|
||||
}
|
||||
|
||||
function exitControl() {
|
||||
@ -308,6 +374,8 @@
|
||||
canvas.classList.replace('controlling', 'watching');
|
||||
hintEl.textContent = 'Click to take control';
|
||||
stopMic();
|
||||
if (idleWarnTimer) clearTimeout(idleWarnTimer);
|
||||
idleWarnEl.classList.remove('visible');
|
||||
if (document.pointerLockElement === canvas) document.exitPointerLock();
|
||||
}
|
||||
|
||||
@ -421,7 +489,7 @@
|
||||
|
||||
canvas.addEventListener('click', () => {
|
||||
if (isControlling) {
|
||||
canvas.muted = false;
|
||||
setMuted(false);
|
||||
canvas.requestPointerLock();
|
||||
} else {
|
||||
requestControl();
|
||||
@ -434,9 +502,7 @@
|
||||
hintEl.textContent = 'Mouse released — click to recapture or wait to release control';
|
||||
} else if (pointerLocked) {
|
||||
hintEl.textContent = 'Mouse captured — ESC to release';
|
||||
if (sigWs && sigWs.readyState === WebSocket.OPEN) {
|
||||
sigWs.send(JSON.stringify({ type: 'controller_active' }));
|
||||
}
|
||||
pingActive();
|
||||
}
|
||||
});
|
||||
|
||||
@ -450,7 +516,7 @@
|
||||
|
||||
setInterval(() => {
|
||||
if (pointerLocked && isControlling && sigWs && sigWs.readyState === WebSocket.OPEN) {
|
||||
sigWs.send(JSON.stringify({ type: 'controller_active' }));
|
||||
pingActive();
|
||||
}
|
||||
}, 3000);
|
||||
|
||||
@ -461,9 +527,7 @@
|
||||
if (now - lastMouseMove < 16) return;
|
||||
lastMouseMove = now;
|
||||
sendInput({ type: 'mouse_move', dx: e.movementX, dy: e.movementY });
|
||||
if (sigWs && sigWs.readyState === WebSocket.OPEN) {
|
||||
sigWs.send(JSON.stringify({ type: 'controller_active' }));
|
||||
}
|
||||
pingActive();
|
||||
});
|
||||
|
||||
canvas.addEventListener('mousedown', e => {
|
||||
|
||||
@ -22,7 +22,7 @@ const CF_APP_ID = process.env.CF_APP_ID || '';
|
||||
const CF_APP_TOKEN = process.env.CF_APP_TOKEN || '';
|
||||
const CF_BASE = `https://rtc.live.cloudflare.com/v1/apps/${CF_APP_ID}`;
|
||||
const CONTAINER = process.env.CONTAINER_NAME || 'css-test';
|
||||
const IDLE_TIMEOUT = 10000; // 10 seconds
|
||||
const IDLE_TIMEOUT = 60000; // 60 seconds
|
||||
const NAME_SALT = process.env.NAME_SALT || 'change-this-secret';
|
||||
|
||||
// How long to wait after relaunching CSS before granting control
|
||||
@ -77,8 +77,10 @@ const controlQueue = [];
|
||||
let containerRestarting = false;
|
||||
|
||||
function broadcastQueueState() {
|
||||
const queueNames = controlQueue.map(e => e.visitorName);
|
||||
const controllerName = currentController ? currentController.visitorName : null;
|
||||
const queueNames = controlQueue
|
||||
.map(e => e.visitorName)
|
||||
.filter(name => name !== controllerName);
|
||||
const msg = JSON.stringify({
|
||||
type: 'queue_state',
|
||||
controller: controllerName,
|
||||
@ -99,6 +101,12 @@ function resetIdleTimer(entry) {
|
||||
}
|
||||
|
||||
async function applyController(entry) {
|
||||
// Defensive: if this exact entry is sitting in the queue (e.g. a race
|
||||
// between request_control calls), pull it out so it's never displayed
|
||||
// as both controller and queued.
|
||||
const qIdx = controlQueue.indexOf(entry);
|
||||
if (qIdx !== -1) controlQueue.splice(qIdx, 1);
|
||||
|
||||
containerRestarting = true;
|
||||
broadcastQueueState();
|
||||
|
||||
@ -287,6 +295,10 @@ wss.on('connection', ws => {
|
||||
|
||||
if (!currentController) {
|
||||
applyController(entry);
|
||||
} else if (currentController.visitorName === visitorName) {
|
||||
// Same identity already controlling (e.g. duplicate tab) — don't queue
|
||||
// behind yourself.
|
||||
return;
|
||||
} else {
|
||||
controlQueue.push(entry);
|
||||
console.log(`[sig] ${visitorName} joined queue position ${controlQueue.length}`);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user