Route main-mode key commands through main command router

This commit is contained in:
Jage9
2026-02-22 16:49:15 -05:00
parent e198ef5f80
commit ab26ceaafc
2 changed files with 336 additions and 317 deletions

View File

@@ -0,0 +1,55 @@
export type MainModeCommand =
| 'editNickname'
| 'toggleMute'
| 'toggleOutputMode'
| 'toggleLoopback'
| 'toggleVoiceLayer'
| 'toggleItemLayer'
| 'toggleMediaLayer'
| 'toggleWorldLayer'
| 'openEffectSelect'
| 'effectValueUp'
| 'effectValueDown'
| 'speakCoordinates'
| 'openMicGainEdit'
| 'calibrateMicrophone'
| 'useItemOrUsersSummary'
| 'addItem'
| 'locateOrListItems'
| 'pickupDropOrDelete'
| 'editOrInspectItem'
| 'pingServer'
| 'locateOrListUsers'
| 'openHelp'
| 'openChat'
| 'chatPrev'
| 'chatNext'
| 'chatFirst'
| 'chatLast'
| 'escape';
export function resolveMainModeCommand(code: string, shiftKey: boolean): MainModeCommand | null {
if (code === 'KeyN') return 'editNickname';
if (code === 'KeyM') return shiftKey ? 'toggleOutputMode' : 'toggleMute';
if (code === 'Digit1') return shiftKey ? 'toggleLoopback' : 'toggleVoiceLayer';
if (code === 'Digit2') return 'toggleItemLayer';
if (code === 'Digit3') return 'toggleMediaLayer';
if (code === 'Digit4') return 'toggleWorldLayer';
if (code === 'KeyE') return 'openEffectSelect';
if (code === 'Equal' || code === 'NumpadAdd') return 'effectValueUp';
if (code === 'Minus' || code === 'NumpadSubtract') return 'effectValueDown';
if (code === 'KeyC') return 'speakCoordinates';
if (code === 'KeyV') return shiftKey ? 'calibrateMicrophone' : 'openMicGainEdit';
if (code === 'KeyU') return 'useItemOrUsersSummary';
if (code === 'KeyA') return 'addItem';
if (code === 'KeyI') return 'locateOrListItems';
if (code === 'KeyD') return 'pickupDropOrDelete';
if (code === 'KeyO') return 'editOrInspectItem';
if (code === 'KeyP') return 'pingServer';
if (code === 'KeyL') return 'locateOrListUsers';
if (code === 'Slash') return shiftKey ? 'openHelp' : 'openChat';
if (code === 'Comma') return shiftKey ? 'chatFirst' : 'chatPrev';
if (code === 'Period') return shiftKey ? 'chatLast' : 'chatNext';
if (code === 'Escape') return 'escape';
return null;
}

View File

@@ -28,6 +28,7 @@ import {
moveCursorWordRight, moveCursorWordRight,
shouldReplaceCurrentText, shouldReplaceCurrentText,
} from './input/textInput'; } from './input/textInput';
import { resolveMainModeCommand } from './input/mainCommandRouter';
import { cycleIndex, findNextIndexByInitial } from './input/listNavigation'; import { cycleIndex, findNextIndexByInitial } from './input/listNavigation';
import { formatSteppedNumber, snapNumberToStep } from './input/numeric'; import { formatSteppedNumber, snapNumberToStep } from './input/numeric';
import { type IncomingMessage, type OutgoingMessage } from './network/protocol'; import { type IncomingMessage, type OutgoingMessage } from './network/protocol';
@@ -1437,8 +1438,11 @@ function handleNormalModeInput(code: string, shiftKey: boolean): void {
if (code !== 'Escape' && pendingEscapeDisconnect) { if (code !== 'Escape' && pendingEscapeDisconnect) {
pendingEscapeDisconnect = false; pendingEscapeDisconnect = false;
} }
const command = resolveMainModeCommand(code, shiftKey);
if (!command) return;
if (code === 'KeyN') { switch (command) {
case 'editNickname':
state.mode = 'nickname'; state.mode = 'nickname';
state.nicknameInput = state.player.nickname; state.nicknameInput = state.player.nickname;
state.cursorPos = state.player.nickname.length; state.cursorPos = state.player.nickname.length;
@@ -1446,48 +1450,34 @@ function handleNormalModeInput(code: string, shiftKey: boolean): void {
updateStatus(`Nickname edit: ${state.nicknameInput}`); updateStatus(`Nickname edit: ${state.nicknameInput}`);
audio.sfxUiBlip(); audio.sfxUiBlip();
return; return;
} case 'toggleMute':
toggleMute();
if (code === 'KeyM') { return;
if (shiftKey) { case 'toggleOutputMode':
outputMode = audio.toggleOutputMode(); outputMode = audio.toggleOutputMode();
localStorage.setItem(AUDIO_OUTPUT_MODE_STORAGE_KEY, outputMode); localStorage.setItem(AUDIO_OUTPUT_MODE_STORAGE_KEY, outputMode);
updateStatus(outputMode === 'mono' ? 'Mono output.' : 'Stereo output.'); updateStatus(outputMode === 'mono' ? 'Mono output.' : 'Stereo output.');
audio.sfxUiBlip(); audio.sfxUiBlip();
return; return;
} case 'toggleLoopback': {
toggleMute();
return;
}
if (code === 'Digit1' && shiftKey) {
const enabled = audio.toggleLoopback(); const enabled = audio.toggleLoopback();
updateStatus(enabled ? 'Loopback on.' : 'Loopback off.'); updateStatus(enabled ? 'Loopback on.' : 'Loopback off.');
audio.sfxUiBlip(); audio.sfxUiBlip();
return; return;
} }
case 'toggleVoiceLayer':
if (code === 'Digit1') {
toggleAudioLayer('voice'); toggleAudioLayer('voice');
return; return;
} case 'toggleItemLayer':
if (code === 'Digit2') {
toggleAudioLayer('item'); toggleAudioLayer('item');
return; return;
} case 'toggleMediaLayer':
if (code === 'Digit3') {
toggleAudioLayer('media'); toggleAudioLayer('media');
return; return;
} case 'toggleWorldLayer':
if (code === 'Digit4') {
toggleAudioLayer('world'); toggleAudioLayer('world');
return; return;
} case 'openEffectSelect': {
if (code === 'KeyE') {
const currentEffect = audio.getCurrentEffect(); const currentEffect = audio.getCurrentEffect();
const currentIndex = EFFECT_SEQUENCE.findIndex((effect) => effect.id === currentEffect.id); const currentIndex = EFFECT_SEQUENCE.findIndex((effect) => effect.id === currentEffect.id);
state.effectSelectIndex = currentIndex >= 0 ? currentIndex : 0; state.effectSelectIndex = currentIndex >= 0 ? currentIndex : 0;
@@ -1496,30 +1486,21 @@ function handleNormalModeInput(code: string, shiftKey: boolean): void {
audio.sfxUiBlip(); audio.sfxUiBlip();
return; return;
} }
case 'effectValueUp':
if (code === 'Equal' || code === 'NumpadAdd' || code === 'Minus' || code === 'NumpadSubtract') { case 'effectValueDown': {
const step = code === 'Equal' || code === 'NumpadAdd' ? 5 : -5; const step = command === 'effectValueUp' ? 5 : -5;
const adjusted = audio.adjustCurrentEffectLevel(step); const adjusted = audio.adjustCurrentEffectLevel(step);
if (!adjusted) { if (!adjusted) return;
return;
}
persistEffectLevels(); persistEffectLevels();
audio.sfxEffectLevel(adjusted.value === adjusted.defaultValue); audio.sfxEffectLevel(adjusted.value === adjusted.defaultValue);
updateStatus(`${adjusted.label} ${adjusted.value}`); updateStatus(`${adjusted.label} ${adjusted.value}`);
return; return;
} }
case 'speakCoordinates':
if (code === 'KeyC') {
updateStatus(`${state.player.x}, ${state.player.y}`); updateStatus(`${state.player.x}, ${state.player.y}`);
audio.sfxUiBlip(); audio.sfxUiBlip();
return; return;
} case 'openMicGainEdit':
if (code === 'KeyV') {
if (shiftKey) {
void calibrateMicInputGain();
return;
}
state.mode = 'micGainEdit'; state.mode = 'micGainEdit';
state.nicknameInput = formatSteppedNumber(audio.getOutboundInputGain(), MIC_INPUT_GAIN_STEP); state.nicknameInput = formatSteppedNumber(audio.getOutboundInputGain(), MIC_INPUT_GAIN_STEP);
state.cursorPos = state.nicknameInput.length; state.cursorPos = state.nicknameInput.length;
@@ -1527,9 +1508,10 @@ function handleNormalModeInput(code: string, shiftKey: boolean): void {
updateStatus(`Set microphone gain: ${state.nicknameInput}`); updateStatus(`Set microphone gain: ${state.nicknameInput}`);
audio.sfxUiBlip(); audio.sfxUiBlip();
return; return;
} case 'calibrateMicrophone':
void calibrateMicInputGain();
if (code === 'KeyU') { return;
case 'useItemOrUsersSummary':
if (shiftKey) { if (shiftKey) {
const allUsers = [state.player.nickname, ...Array.from(state.peers.values()).map((p) => p.nickname)]; const allUsers = [state.player.nickname, ...Array.from(state.peers.values()).map((p) => p.nickname)];
const label = allUsers.length === 1 ? 'user' : 'users'; const label = allUsers.length === 1 ? 'user' : 'users';
@@ -1537,6 +1519,7 @@ function handleNormalModeInput(code: string, shiftKey: boolean): void {
audio.sfxUiBlip(); audio.sfxUiBlip();
return; return;
} }
{
const carried = getCarriedItem(); const carried = getCarriedItem();
if (carried) { if (carried) {
useItem(carried); useItem(carried);
@@ -1556,8 +1539,7 @@ function handleNormalModeInput(code: string, shiftKey: boolean): void {
beginItemSelection('use', usable); beginItemSelection('use', usable);
return; return;
} }
case 'addItem': {
if (code === 'KeyA') {
const itemTypeSequence = getItemTypeSequence(); const itemTypeSequence = getItemTypeSequence();
if (itemTypeSequence.length === 0) { if (itemTypeSequence.length === 0) {
updateStatus('No item types available.'); updateStatus('No item types available.');
@@ -1570,8 +1552,7 @@ function handleNormalModeInput(code: string, shiftKey: boolean): void {
audio.sfxUiBlip(); audio.sfxUiBlip();
return; return;
} }
case 'locateOrListItems':
if (code === 'KeyI') {
if (shiftKey) { if (shiftKey) {
if (state.items.size === 0) { if (state.items.size === 0) {
updateStatus('No items to list.'); updateStatus('No items to list.');
@@ -1602,6 +1583,7 @@ function handleNormalModeInput(code: string, shiftKey: boolean): void {
audio.sfxUiBlip(); audio.sfxUiBlip();
return; return;
} }
{
const nearest = getNearestItem(state); const nearest = getNearestItem(state);
if (!nearest.itemId) { if (!nearest.itemId) {
updateStatus('No items to locate.'); updateStatus('No items to locate.');
@@ -1616,8 +1598,7 @@ function handleNormalModeInput(code: string, shiftKey: boolean): void {
); );
return; return;
} }
case 'pickupDropOrDelete': {
if (code === 'KeyD') {
const carried = getCarriedItem(); const carried = getCarriedItem();
if (shiftKey) { if (shiftKey) {
const squareItems = getItemsAtPosition(state.player.x, state.player.y); const squareItems = getItemsAtPosition(state.player.x, state.player.y);
@@ -1633,7 +1614,6 @@ function handleNormalModeInput(code: string, shiftKey: boolean): void {
beginItemSelection('delete', squareItems); beginItemSelection('delete', squareItems);
return; return;
} }
if (carried) { if (carried) {
signaling.send({ type: 'item_drop', itemId: carried.id, x: state.player.x, y: state.player.y }); signaling.send({ type: 'item_drop', itemId: carried.id, x: state.player.x, y: state.player.y });
return; return;
@@ -1651,8 +1631,7 @@ function handleNormalModeInput(code: string, shiftKey: boolean): void {
beginItemSelection('pickup', squareItems); beginItemSelection('pickup', squareItems);
return; return;
} }
case 'editOrInspectItem': {
if (code === 'KeyO') {
const squareItems = getItemsAtPosition(state.player.x, state.player.y); const squareItems = getItemsAtPosition(state.player.x, state.player.y);
const carried = getCarriedItem(); const carried = getCarriedItem();
if (shiftKey) { if (shiftKey) {
@@ -1672,7 +1651,6 @@ function handleNormalModeInput(code: string, shiftKey: boolean): void {
beginItemSelection('inspect', squareItems); beginItemSelection('inspect', squareItems);
return; return;
} }
if (squareItems.length === 0) { if (squareItems.length === 0) {
if (!carried) { if (!carried) {
updateStatus('No editable item here.'); updateStatus('No editable item here.');
@@ -1689,13 +1667,10 @@ function handleNormalModeInput(code: string, shiftKey: boolean): void {
beginItemSelection('edit', squareItems); beginItemSelection('edit', squareItems);
return; return;
} }
case 'pingServer':
if (code === 'KeyP') {
signaling.send({ type: 'ping', clientSentAt: Date.now() }); signaling.send({ type: 'ping', clientSentAt: Date.now() });
return; return;
} case 'locateOrListUsers':
if (code === 'KeyL') {
if (shiftKey) { if (shiftKey) {
if (state.peers.size === 0) { if (state.peers.size === 0) {
updateStatus('No users to list.'); updateStatus('No users to list.');
@@ -1720,7 +1695,7 @@ function handleNormalModeInput(code: string, shiftKey: boolean): void {
audio.sfxUiBlip(); audio.sfxUiBlip();
return; return;
} }
{
const nearest = getNearestPeer(state); const nearest = getNearestPeer(state);
if (!nearest.peerId) { if (!nearest.peerId) {
updateStatus('No users to locate.'); updateStatus('No users to locate.');
@@ -1735,13 +1710,10 @@ function handleNormalModeInput(code: string, shiftKey: boolean): void {
); );
return; return;
} }
case 'openHelp':
if (code === 'Slash' && shiftKey) {
openHelpViewer(); openHelpViewer();
return; return;
} case 'openChat':
if (code === 'Slash' && !shiftKey) {
state.mode = 'chat'; state.mode = 'chat';
state.nicknameInput = ''; state.nicknameInput = '';
state.cursorPos = 0; state.cursorPos = 0;
@@ -1749,27 +1721,19 @@ function handleNormalModeInput(code: string, shiftKey: boolean): void {
updateStatus('Chat.'); updateStatus('Chat.');
audio.sfxUiBlip(); audio.sfxUiBlip();
return; return;
} case 'chatPrev':
if (code === 'Comma') {
if (shiftKey) {
navigateChatBuffer('first');
} else {
navigateChatBuffer('prev'); navigateChatBuffer('prev');
}
return; return;
} case 'chatNext':
if (code === 'Period') {
if (shiftKey) {
navigateChatBuffer('last');
} else {
navigateChatBuffer('next'); navigateChatBuffer('next');
}
return; return;
} case 'chatFirst':
navigateChatBuffer('first');
if (code === 'Escape') { return;
case 'chatLast':
navigateChatBuffer('last');
return;
case 'escape':
if (pendingEscapeDisconnect) { if (pendingEscapeDisconnect) {
pendingEscapeDisconnect = false; pendingEscapeDisconnect = false;
disconnect(); disconnect();