Move piano glue behind behavior registry

This commit is contained in:
Jage9
2026-03-08 20:31:35 -04:00
parent ffba9530c8
commit 34818d30f5
6 changed files with 89 additions and 65 deletions

View File

@@ -19,8 +19,8 @@ type KeyboardControllerDeps = {
handleModeInput: (input: ModeInput) => void;
canOpenCommandPaletteInMode: (mode: GameMode) => boolean;
openCommandPalette: () => void;
shouldForwardModeKeyUp: (mode: GameMode) => boolean;
onModeKeyUp: (input: Pick<ModeInput, 'code' | 'shiftKey'>) => void;
getModeKeyUpTarget: (activeMode: GameMode) => GameMode | null;
onModeKeyUp: (mode: GameMode, input: Pick<ModeInput, 'code' | 'shiftKey'>) => void;
pasteIntoActiveTextInput: (text: string) => boolean;
updateStatus: (message: string) => void;
setReplaceTextOnNextType: (value: boolean) => void;
@@ -157,8 +157,9 @@ export function setupKeyboardInputHandlers(deps: KeyboardControllerDeps): void {
document.addEventListener('keyup', (event) => {
const code = normalizeInputCode(event);
if (code && deps.shouldForwardModeKeyUp(deps.state.mode)) {
deps.onModeKeyUp({
const keyUpMode = deps.getModeKeyUpTarget(deps.state.mode);
if (code && keyUpMode) {
deps.onModeKeyUp(keyUpMode, {
code,
shiftKey: event.shiftKey,
});

View File

@@ -77,6 +77,27 @@ export class ItemBehaviorRegistry {
return false;
}
/** Returns whether any item-owned mode supports opening the command palette. */
canOpenModeCommandPalette(mode: GameMode): boolean {
for (const behavior of this.behaviors) {
if (behavior.canOpenModeCommandPalette?.(mode)) {
return true;
}
}
return false;
}
/** Resolves an optional suspended mode that still wants key-up events while another overlay is active. */
getModeKeyUpTarget(activeMode: GameMode, returnMode: GameMode): GameMode | null {
for (const behavior of this.behaviors) {
const target = behavior.getModeKeyUpTarget?.(activeMode, returnMode);
if (target) {
return target;
}
}
return null;
}
/** Returns palette-visible commands for the active item-owned mode, if any. */
getModeCommands(mode: GameMode): CommandDescriptor[] {
const commands: CommandDescriptor[] = [];
@@ -99,24 +120,20 @@ export class ItemBehaviorRegistry {
return false;
}
/** Routes incoming item-piano-note packets to the item behavior owning that protocol. */
onRemotePianoNote(message: Extract<IncomingMessage, { type: 'item_piano_note' }>): void {
/** Gives item behaviors a chance to consume custom incoming packets. */
onIncomingMessage(message: IncomingMessage): boolean {
for (const behavior of this.behaviors) {
behavior.onRemotePianoNote?.(message);
if (behavior.onIncomingMessage?.(message)) {
return true;
}
}
return false;
}
/** Routes incoming item-piano-status packets to behavior modules that track piano runtime state. */
onPianoStatus(message: Extract<IncomingMessage, { type: 'item_piano_status' }>): void {
/** Notifies behaviors that a peer left so they can release sender-owned runtime state. */
onPeerLeft(senderId: string): void {
for (const behavior of this.behaviors) {
behavior.onPianoStatus?.(message);
}
}
/** Stops all remote notes for one sender across behavior modules that own remote note runtimes. */
stopAllRemoteNotesForSender(senderId: string): void {
for (const behavior of this.behaviors) {
behavior.onStopAllRemoteNotesForSender?.(senderId);
behavior.onPeerLeft?.(senderId);
}
}
}

View File

@@ -31,9 +31,6 @@ export function createPianoBehavior(deps: ItemBehaviorDeps): ItemBehavior {
}
return true;
},
onPianoStatus: (message) => {
controller.onPianoStatus(message);
},
onPropertyPreviewChange: (item, key, value) => {
controller.onPreviewPropertyChange(item, key, value);
},
@@ -50,6 +47,12 @@ export function createPianoBehavior(deps: ItemBehaviorDeps): ItemBehavior {
controller.handleModeKeyUp(input);
return true;
},
canOpenModeCommandPalette: (mode) => mode === 'pianoUse',
getModeKeyUpTarget: (activeMode, returnMode) => {
if (activeMode === 'pianoUse') return 'pianoUse';
if (activeMode === 'commandPalette' && returnMode === 'pianoUse') return 'pianoUse';
return null;
},
getModeCommands: (mode) => {
if (mode !== 'pianoUse') return [];
return controller.getModeCommands();
@@ -58,7 +61,8 @@ export function createPianoBehavior(deps: ItemBehaviorDeps): ItemBehavior {
if (mode !== 'pianoUse') return false;
return controller.runModeCommand(commandId);
},
onRemotePianoNote: (message) => {
onIncomingMessage: (message) => {
if (message.type === 'item_piano_note') {
if (message.on) {
controller.playRemoteNote({
itemId: message.itemId,
@@ -79,8 +83,15 @@ export function createPianoBehavior(deps: ItemBehaviorDeps): ItemBehavior {
} else {
controller.stopRemoteNote(message.senderId, message.keyId);
}
return true;
}
if (message.type === 'item_piano_status') {
controller.onPianoStatus(message);
return true;
}
return false;
},
onStopAllRemoteNotesForSender: (senderId) => {
onPeerLeft: (senderId) => {
controller.stopAllRemoteNotesForSender(senderId);
},
};

View File

@@ -32,9 +32,10 @@ export type ItemBehavior = {
onWorldUpdate?: () => void;
handleModeInput?: (mode: GameMode, input: ModeInput) => boolean;
handleModeKeyUp?: (mode: GameMode, input: Pick<ModeInput, 'code' | 'shiftKey'>) => boolean;
canOpenModeCommandPalette?: (mode: GameMode) => boolean;
getModeKeyUpTarget?: (activeMode: GameMode, returnMode: GameMode) => GameMode | null;
getModeCommands?: (mode: GameMode) => CommandDescriptor[];
runModeCommand?: (mode: GameMode, commandId: string) => boolean;
onRemotePianoNote?: (message: Extract<IncomingMessage, { type: 'item_piano_note' }>) => void;
onPianoStatus?: (message: Extract<IncomingMessage, { type: 'item_piano_status' }>) => void;
onStopAllRemoteNotesForSender?: (senderId: string) => void;
onIncomingMessage?: (message: IncomingMessage) => boolean;
onPeerLeft?: (senderId: string) => void;
};

View File

@@ -1483,9 +1483,8 @@ const onAppMessage = createOnMessageHandler({
);
},
handleItemActionResultStatus: (message) => itemBehaviorRegistry.onActionResultStatus(message),
handleRemotePianoNote: (message) => itemBehaviorRegistry.onRemotePianoNote(message),
handlePianoStatus: (message) => itemBehaviorRegistry.onPianoStatus(message),
stopAllRemoteNotesForSender: (senderId) => itemBehaviorRegistry.stopAllRemoteNotesForSender(senderId),
handleItemBehaviorIncomingMessage: (message) => itemBehaviorRegistry.onIncomingMessage(message),
handleItemBehaviorPeerLeft: (senderId) => itemBehaviorRegistry.onPeerLeft(senderId),
TELEPORT_SOUND_URL,
TELEPORT_START_SOUND_URL,
getAudioLayers: () => audioLayers,
@@ -2034,7 +2033,7 @@ function getAvailableCommandPaletteEntriesForMode(mode: GameMode): Array<Command
run: mainModeCommandHandlers[descriptor.id],
}));
}
if (mode === 'pianoUse') {
if (itemBehaviorRegistry.canOpenModeCommandPalette(mode)) {
return itemBehaviorRegistry.getModeCommands(mode).map((descriptor) => ({
...descriptor,
run: () => {
@@ -2046,11 +2045,7 @@ function getAvailableCommandPaletteEntriesForMode(mode: GameMode): Array<Command
}
function canOpenCommandPaletteInMode(mode: GameMode): boolean {
return mode === 'normal' || mode === 'pianoUse' || mode === 'commandPalette';
}
function shouldForwardPianoKeyUp(currentMode: GameMode): boolean {
return currentMode === 'pianoUse' || (currentMode === 'commandPalette' && commandPaletteReturnMode === 'pianoUse');
return mode === 'normal' || mode === 'commandPalette' || itemBehaviorRegistry.canOpenModeCommandPalette(mode);
}
function openCommandPalette(): void {
@@ -2536,6 +2531,9 @@ function handleNicknameModeInput(code: string, key: string, ctrlKey: boolean): v
}
function handleModeInput(input: ModeInput): void {
if (itemBehaviorRegistry.handleModeInput(state.mode, input)) {
return;
}
dispatchModeInput({
mode: state.mode,
input,
@@ -2546,9 +2544,6 @@ function handleModeInput(input: ModeInput): void {
handleChatModeInput(currentCode, currentKey, currentCtrlKey),
micGainEdit: ({ code: currentCode, key: currentKey, ctrlKey: currentCtrlKey }) =>
handleMicGainEditModeInput(currentCode, currentKey, currentCtrlKey),
pianoUse: (currentInput) => {
itemBehaviorRegistry.handleModeInput(state.mode, currentInput);
},
commandPalette: ({ code: currentCode, key: currentKey }) => handleCommandPaletteModeInput(currentCode, currentKey),
effectSelect: ({ code: currentCode, key: currentKey }) => handleEffectSelectModeInput(currentCode, currentKey),
helpView: ({ code: currentCode }) => handleHelpViewModeInput(currentCode),
@@ -2617,9 +2612,9 @@ setupKeyboardInputHandlers({
handleModeInput,
canOpenCommandPaletteInMode,
openCommandPalette,
shouldForwardModeKeyUp: shouldForwardPianoKeyUp,
onModeKeyUp: ({ code, shiftKey }) => {
itemBehaviorRegistry.handleModeKeyUp('pianoUse', {
getModeKeyUpTarget: (activeMode) => itemBehaviorRegistry.getModeKeyUpTarget(activeMode, commandPaletteReturnMode),
onModeKeyUp: (mode, { code, shiftKey }) => {
itemBehaviorRegistry.handleModeKeyUp(mode, {
code,
shiftKey,
});

View File

@@ -46,9 +46,8 @@ type MessageHandlerDeps = {
randomFootstepUrl: () => string;
playRemoteSpatialStepOrTeleport: (url: string, peerX: number, peerY: number) => void;
handleItemActionResultStatus: (message: Extract<IncomingMessage, { type: 'item_action_result' }>) => boolean;
handleRemotePianoNote: (message: Extract<IncomingMessage, { type: 'item_piano_note' }>) => void;
handlePianoStatus: (message: Extract<IncomingMessage, { type: 'item_piano_status' }>) => void;
stopAllRemoteNotesForSender: (senderId: string) => void;
handleItemBehaviorIncomingMessage: (message: IncomingMessage) => boolean;
handleItemBehaviorPeerLeft: (senderId: string) => void;
TELEPORT_SOUND_URL: string;
TELEPORT_START_SOUND_URL: string;
getAudioLayers: () => { world: boolean; item: boolean };
@@ -225,7 +224,7 @@ export function createOnMessageHandler(deps: MessageHandlerDeps): (message: Inco
if (peer) {
deps.updateStatus(`${peer.nickname} has left.`);
}
deps.stopAllRemoteNotesForSender(message.id);
deps.handleItemBehaviorPeerLeft(message.id);
deps.state.peers.delete(message.id);
deps.peerManager.removePeer(message.id);
break;
@@ -335,7 +334,7 @@ export function createOnMessageHandler(deps: MessageHandlerDeps): (message: Inco
case 'item_piano_note': {
if (!deps.getAudioLayers().item) break;
deps.handleRemotePianoNote(message);
deps.handleItemBehaviorIncomingMessage(message);
break;
}
@@ -346,7 +345,7 @@ export function createOnMessageHandler(deps: MessageHandlerDeps): (message: Inco
}
case 'item_piano_status': {
deps.handlePianoStatus(message);
deps.handleItemBehaviorIncomingMessage(message);
break;
}
}