users: make shift-u list include self and handle self volume adjustment explicitly

This commit is contained in:
Jage9
2026-02-22 19:22:22 -05:00
parent fe230bd53d
commit c4494f2f99
2 changed files with 67 additions and 30 deletions

View File

@@ -1,5 +1,5 @@
// Maintainer-controlled web client version. // Maintainer-controlled web client version.
// Format: YYYY.MM.DD Rn (example: 2026.02.20 R2) // Format: YYYY.MM.DD Rn (example: 2026.02.20 R2)
window.CHGRID_WEB_VERSION = "2026.02.22 R171"; window.CHGRID_WEB_VERSION = "2026.02.22 R172";
// Optional display timezone for timestamps. Falls back to America/Detroit if unset/invalid. // Optional display timezone for timestamps. Falls back to America/Detroit if unset/invalid.
window.CHGRID_TIME_ZONE = "America/Detroit"; window.CHGRID_TIME_ZONE = "America/Detroit";

View File

@@ -81,6 +81,7 @@ const HEARTBEAT_INTERVAL_MS = 10_000;
const RECONNECT_DELAY_MS = 5_000; const RECONNECT_DELAY_MS = 5_000;
const RECONNECT_MAX_ATTEMPTS = 3; const RECONNECT_MAX_ATTEMPTS = 3;
const AUTO_RECONNECT_AFTER_RELOAD_KEY = 'chatGridAutoReconnectAfterReload'; const AUTO_RECONNECT_AFTER_RELOAD_KEY = 'chatGridAutoReconnectAfterReload';
const SELF_LIST_ENTRY_ID = '__self__';
declare global { declare global {
interface Window { interface Window {
@@ -517,6 +518,31 @@ function applyConfiguredPeerListenGains(): void {
} }
} }
/** Resolves a list user entry id to either local player or a remote peer. */
function getListUserEntry(peerId: string):
| { id: string; nickname: string; x: number; y: number; isSelf: true }
| { id: string; nickname: string; x: number; y: number; isSelf: false }
| null {
if (peerId === SELF_LIST_ENTRY_ID) {
return {
id: SELF_LIST_ENTRY_ID,
nickname: state.player.nickname,
x: state.player.x,
y: state.player.y,
isSelf: true,
};
}
const peer = state.peers.get(peerId);
if (!peer) return null;
return {
id: peer.id,
nickname: peer.nickname,
x: peer.x,
y: peer.y,
isSelf: false,
};
}
/** Applies current layer toggles to peer voice, media streams, and item emitters. */ /** Applies current layer toggles to peer voice, media streams, and item emitters. */
async function applyAudioLayerState(): Promise<void> { async function applyAudioLayerState(): Promise<void> {
audio.setVoiceLayerEnabled(audioLayers.voice); audio.setVoiceLayerEnabled(audioLayers.voice);
@@ -1470,24 +1496,25 @@ function handleNormalModeInput(code: string, shiftKey: boolean): void {
return; return;
} }
case 'listUsersAlphabetical': case 'listUsersAlphabetical':
if (state.peers.size === 0) { state.sortedPeerIds = [
updateStatus('No users to list.'); ...Array.from(state.peers.entries()).map(([id, peer]) => ({ id, nickname: peer.nickname })),
audio.sfxUiCancel(); { id: SELF_LIST_ENTRY_ID, nickname: state.player.nickname },
return; ]
} .sort((a, b) => a.nickname.localeCompare(b.nickname, undefined, { sensitivity: 'base' }))
state.sortedPeerIds = Array.from(state.peers.entries()) .map((entry) => entry.id);
.sort((a, b) => a[1].nickname.localeCompare(b[1].nickname, undefined, { sensitivity: 'base' }))
.map(([id]) => id);
state.listIndex = 0; state.listIndex = 0;
state.mode = 'listUsers'; state.mode = 'listUsers';
{ {
const first = state.peers.get(state.sortedPeerIds[0]); const first = getListUserEntry(state.sortedPeerIds[0]);
if (first) { if (first) {
const userCount = state.sortedPeerIds.length; const userCount = state.sortedPeerIds.length;
const userLabelText = userCount === 1 ? 'user' : 'users'; const userLabelText = userCount === 1 ? 'user' : 'users';
const gain = getPeerListenGainForNickname(first.nickname); const gain = first.isSelf ? 1 : getPeerListenGainForNickname(first.nickname);
const gainPhrase = first.isSelf
? 'self volume not adjustable'
: `volume ${formatSteppedNumber(gain, MIC_INPUT_GAIN_STEP)}`;
updateStatus( updateStatus(
`${userCount} ${userLabelText}. ${first.nickname}, volume ${formatSteppedNumber(gain, MIC_INPUT_GAIN_STEP)}.`, `${userCount} ${userLabelText}. ${first.nickname}, ${gainPhrase}.`,
); );
} }
} }
@@ -1867,15 +1894,20 @@ function handleListModeInput(code: string, key: string): void {
} }
if (code === 'ArrowLeft' || code === 'ArrowRight') { if (code === 'ArrowLeft' || code === 'ArrowRight') {
const peer = state.peers.get(state.sortedPeerIds[state.listIndex]); const entry = getListUserEntry(state.sortedPeerIds[state.listIndex]);
if (!peer) return; if (!entry) return;
const current = getPeerListenGainForNickname(peer.nickname); if (entry.isSelf) {
updateStatus('Your own listen volume is not adjustable.');
audio.sfxUiCancel();
return;
}
const current = getPeerListenGainForNickname(entry.nickname);
const delta = code === 'ArrowRight' ? MIC_INPUT_GAIN_STEP : -MIC_INPUT_GAIN_STEP; const delta = code === 'ArrowRight' ? MIC_INPUT_GAIN_STEP : -MIC_INPUT_GAIN_STEP;
const attempted = snapNumberToStep(current + delta, MIC_INPUT_GAIN_STEP, MIC_CALIBRATION_MIN_GAIN); const attempted = snapNumberToStep(current + delta, MIC_INPUT_GAIN_STEP, MIC_CALIBRATION_MIN_GAIN);
const next = clampMicInputGain(attempted); const next = clampMicInputGain(attempted);
setPeerListenGainForNickname(peer.nickname, next); setPeerListenGainForNickname(entry.nickname, next);
peerManager.setPeerListenGain(peer.id, next); peerManager.setPeerListenGain(entry.id, next);
updateStatus(`${peer.nickname} volume ${formatSteppedNumber(next, MIC_INPUT_GAIN_STEP)}.`); updateStatus(`${entry.nickname} volume ${formatSteppedNumber(next, MIC_INPUT_GAIN_STEP)}.`);
if (Math.abs(next - current) < 1e-9 || Math.abs(next - attempted) > 1e-9) { if (Math.abs(next - current) < 1e-9 || Math.abs(next - attempted) > 1e-9) {
audio.sfxUiCancel(); audio.sfxUiCancel();
} else { } else {
@@ -1884,14 +1916,19 @@ function handleListModeInput(code: string, key: string): void {
return; return;
} }
const control = handleListControlKey(code, key, state.sortedPeerIds, state.listIndex, (peerId) => state.peers.get(peerId)?.nickname ?? ''); const control = handleListControlKey(code, key, state.sortedPeerIds, state.listIndex, (peerId) => {
const entry = getListUserEntry(peerId);
return entry?.nickname ?? '';
});
if (control.type === 'move') { if (control.type === 'move') {
state.listIndex = control.index; state.listIndex = control.index;
const peer = state.peers.get(state.sortedPeerIds[state.listIndex]); const entry = getListUserEntry(state.sortedPeerIds[state.listIndex]);
if (!peer) return; if (!entry) return;
const gain = getPeerListenGainForNickname(peer.nickname); const gainPhrase = entry.isSelf
? 'self volume not adjustable'
: `volume ${formatSteppedNumber(getPeerListenGainForNickname(entry.nickname), MIC_INPUT_GAIN_STEP)}`;
updateStatus( updateStatus(
`${peer.nickname}, volume ${formatSteppedNumber(gain, MIC_INPUT_GAIN_STEP)}, ${distanceDirectionPhrase(state.player.x, state.player.y, peer.x, peer.y)}, ${peer.x}, ${peer.y}`, `${entry.nickname}, ${gainPhrase}, ${distanceDirectionPhrase(state.player.x, state.player.y, entry.x, entry.y)}, ${entry.x}, ${entry.y}`,
); );
if (control.reason === 'initial') { if (control.reason === 'initial') {
audio.sfxUiBlip(); audio.sfxUiBlip();
@@ -1900,19 +1937,19 @@ function handleListModeInput(code: string, key: string): void {
} }
if (control.type === 'select') { if (control.type === 'select') {
const peer = state.peers.get(state.sortedPeerIds[state.listIndex]); const entry = getListUserEntry(state.sortedPeerIds[state.listIndex]);
if (!peer) return; if (!entry) return;
if (state.player.x === peer.x && state.player.y === peer.y) { if (state.player.x === entry.x && state.player.y === entry.y) {
updateStatus('Already here.'); updateStatus('Already here.');
return; return;
} }
state.player.x = peer.x; state.player.x = entry.x;
state.player.y = peer.y; state.player.y = entry.y;
persistPlayerPosition(); persistPlayerPosition();
void audio.playSample(TELEPORT_SOUND_URL, FOOTSTEP_GAIN); void audio.playSample(TELEPORT_SOUND_URL, FOOTSTEP_GAIN);
signaling.send({ type: 'update_position', x: peer.x, y: peer.y }); signaling.send({ type: 'update_position', x: entry.x, y: entry.y });
state.mode = 'normal'; state.mode = 'normal';
updateStatus(`Moved to ${peer.nickname}.`); updateStatus(`Moved to ${entry.nickname}.`);
return; return;
} }