Move piano glue behind behavior registry
This commit is contained in:
@@ -19,8 +19,8 @@ type KeyboardControllerDeps = {
|
|||||||
handleModeInput: (input: ModeInput) => void;
|
handleModeInput: (input: ModeInput) => void;
|
||||||
canOpenCommandPaletteInMode: (mode: GameMode) => boolean;
|
canOpenCommandPaletteInMode: (mode: GameMode) => boolean;
|
||||||
openCommandPalette: () => void;
|
openCommandPalette: () => void;
|
||||||
shouldForwardModeKeyUp: (mode: GameMode) => boolean;
|
getModeKeyUpTarget: (activeMode: GameMode) => GameMode | null;
|
||||||
onModeKeyUp: (input: Pick<ModeInput, 'code' | 'shiftKey'>) => void;
|
onModeKeyUp: (mode: GameMode, input: Pick<ModeInput, 'code' | 'shiftKey'>) => void;
|
||||||
pasteIntoActiveTextInput: (text: string) => boolean;
|
pasteIntoActiveTextInput: (text: string) => boolean;
|
||||||
updateStatus: (message: string) => void;
|
updateStatus: (message: string) => void;
|
||||||
setReplaceTextOnNextType: (value: boolean) => void;
|
setReplaceTextOnNextType: (value: boolean) => void;
|
||||||
@@ -157,8 +157,9 @@ export function setupKeyboardInputHandlers(deps: KeyboardControllerDeps): void {
|
|||||||
|
|
||||||
document.addEventListener('keyup', (event) => {
|
document.addEventListener('keyup', (event) => {
|
||||||
const code = normalizeInputCode(event);
|
const code = normalizeInputCode(event);
|
||||||
if (code && deps.shouldForwardModeKeyUp(deps.state.mode)) {
|
const keyUpMode = deps.getModeKeyUpTarget(deps.state.mode);
|
||||||
deps.onModeKeyUp({
|
if (code && keyUpMode) {
|
||||||
|
deps.onModeKeyUp(keyUpMode, {
|
||||||
code,
|
code,
|
||||||
shiftKey: event.shiftKey,
|
shiftKey: event.shiftKey,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -77,6 +77,27 @@ export class ItemBehaviorRegistry {
|
|||||||
return false;
|
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. */
|
/** Returns palette-visible commands for the active item-owned mode, if any. */
|
||||||
getModeCommands(mode: GameMode): CommandDescriptor[] {
|
getModeCommands(mode: GameMode): CommandDescriptor[] {
|
||||||
const commands: CommandDescriptor[] = [];
|
const commands: CommandDescriptor[] = [];
|
||||||
@@ -99,24 +120,20 @@ export class ItemBehaviorRegistry {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Routes incoming item-piano-note packets to the item behavior owning that protocol. */
|
/** Gives item behaviors a chance to consume custom incoming packets. */
|
||||||
onRemotePianoNote(message: Extract<IncomingMessage, { type: 'item_piano_note' }>): void {
|
onIncomingMessage(message: IncomingMessage): boolean {
|
||||||
for (const behavior of this.behaviors) {
|
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. */
|
/** Notifies behaviors that a peer left so they can release sender-owned runtime state. */
|
||||||
onPianoStatus(message: Extract<IncomingMessage, { type: 'item_piano_status' }>): void {
|
onPeerLeft(senderId: string): void {
|
||||||
for (const behavior of this.behaviors) {
|
for (const behavior of this.behaviors) {
|
||||||
behavior.onPianoStatus?.(message);
|
behavior.onPeerLeft?.(senderId);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,9 +31,6 @@ export function createPianoBehavior(deps: ItemBehaviorDeps): ItemBehavior {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
onPianoStatus: (message) => {
|
|
||||||
controller.onPianoStatus(message);
|
|
||||||
},
|
|
||||||
onPropertyPreviewChange: (item, key, value) => {
|
onPropertyPreviewChange: (item, key, value) => {
|
||||||
controller.onPreviewPropertyChange(item, key, value);
|
controller.onPreviewPropertyChange(item, key, value);
|
||||||
},
|
},
|
||||||
@@ -50,6 +47,12 @@ export function createPianoBehavior(deps: ItemBehaviorDeps): ItemBehavior {
|
|||||||
controller.handleModeKeyUp(input);
|
controller.handleModeKeyUp(input);
|
||||||
return true;
|
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) => {
|
getModeCommands: (mode) => {
|
||||||
if (mode !== 'pianoUse') return [];
|
if (mode !== 'pianoUse') return [];
|
||||||
return controller.getModeCommands();
|
return controller.getModeCommands();
|
||||||
@@ -58,7 +61,8 @@ export function createPianoBehavior(deps: ItemBehaviorDeps): ItemBehavior {
|
|||||||
if (mode !== 'pianoUse') return false;
|
if (mode !== 'pianoUse') return false;
|
||||||
return controller.runModeCommand(commandId);
|
return controller.runModeCommand(commandId);
|
||||||
},
|
},
|
||||||
onRemotePianoNote: (message) => {
|
onIncomingMessage: (message) => {
|
||||||
|
if (message.type === 'item_piano_note') {
|
||||||
if (message.on) {
|
if (message.on) {
|
||||||
controller.playRemoteNote({
|
controller.playRemoteNote({
|
||||||
itemId: message.itemId,
|
itemId: message.itemId,
|
||||||
@@ -79,8 +83,15 @@ export function createPianoBehavior(deps: ItemBehaviorDeps): ItemBehavior {
|
|||||||
} else {
|
} else {
|
||||||
controller.stopRemoteNote(message.senderId, message.keyId);
|
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);
|
controller.stopAllRemoteNotesForSender(senderId);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -32,9 +32,10 @@ export type ItemBehavior = {
|
|||||||
onWorldUpdate?: () => void;
|
onWorldUpdate?: () => void;
|
||||||
handleModeInput?: (mode: GameMode, input: ModeInput) => boolean;
|
handleModeInput?: (mode: GameMode, input: ModeInput) => boolean;
|
||||||
handleModeKeyUp?: (mode: GameMode, input: Pick<ModeInput, 'code' | 'shiftKey'>) => 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[];
|
getModeCommands?: (mode: GameMode) => CommandDescriptor[];
|
||||||
runModeCommand?: (mode: GameMode, commandId: string) => boolean;
|
runModeCommand?: (mode: GameMode, commandId: string) => boolean;
|
||||||
onRemotePianoNote?: (message: Extract<IncomingMessage, { type: 'item_piano_note' }>) => void;
|
onIncomingMessage?: (message: IncomingMessage) => boolean;
|
||||||
onPianoStatus?: (message: Extract<IncomingMessage, { type: 'item_piano_status' }>) => void;
|
onPeerLeft?: (senderId: string) => void;
|
||||||
onStopAllRemoteNotesForSender?: (senderId: string) => void;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1483,9 +1483,8 @@ const onAppMessage = createOnMessageHandler({
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
handleItemActionResultStatus: (message) => itemBehaviorRegistry.onActionResultStatus(message),
|
handleItemActionResultStatus: (message) => itemBehaviorRegistry.onActionResultStatus(message),
|
||||||
handleRemotePianoNote: (message) => itemBehaviorRegistry.onRemotePianoNote(message),
|
handleItemBehaviorIncomingMessage: (message) => itemBehaviorRegistry.onIncomingMessage(message),
|
||||||
handlePianoStatus: (message) => itemBehaviorRegistry.onPianoStatus(message),
|
handleItemBehaviorPeerLeft: (senderId) => itemBehaviorRegistry.onPeerLeft(senderId),
|
||||||
stopAllRemoteNotesForSender: (senderId) => itemBehaviorRegistry.stopAllRemoteNotesForSender(senderId),
|
|
||||||
TELEPORT_SOUND_URL,
|
TELEPORT_SOUND_URL,
|
||||||
TELEPORT_START_SOUND_URL,
|
TELEPORT_START_SOUND_URL,
|
||||||
getAudioLayers: () => audioLayers,
|
getAudioLayers: () => audioLayers,
|
||||||
@@ -2034,7 +2033,7 @@ function getAvailableCommandPaletteEntriesForMode(mode: GameMode): Array<Command
|
|||||||
run: mainModeCommandHandlers[descriptor.id],
|
run: mainModeCommandHandlers[descriptor.id],
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
if (mode === 'pianoUse') {
|
if (itemBehaviorRegistry.canOpenModeCommandPalette(mode)) {
|
||||||
return itemBehaviorRegistry.getModeCommands(mode).map((descriptor) => ({
|
return itemBehaviorRegistry.getModeCommands(mode).map((descriptor) => ({
|
||||||
...descriptor,
|
...descriptor,
|
||||||
run: () => {
|
run: () => {
|
||||||
@@ -2046,11 +2045,7 @@ function getAvailableCommandPaletteEntriesForMode(mode: GameMode): Array<Command
|
|||||||
}
|
}
|
||||||
|
|
||||||
function canOpenCommandPaletteInMode(mode: GameMode): boolean {
|
function canOpenCommandPaletteInMode(mode: GameMode): boolean {
|
||||||
return mode === 'normal' || mode === 'pianoUse' || mode === 'commandPalette';
|
return mode === 'normal' || mode === 'commandPalette' || itemBehaviorRegistry.canOpenModeCommandPalette(mode);
|
||||||
}
|
|
||||||
|
|
||||||
function shouldForwardPianoKeyUp(currentMode: GameMode): boolean {
|
|
||||||
return currentMode === 'pianoUse' || (currentMode === 'commandPalette' && commandPaletteReturnMode === 'pianoUse');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function openCommandPalette(): void {
|
function openCommandPalette(): void {
|
||||||
@@ -2536,6 +2531,9 @@ function handleNicknameModeInput(code: string, key: string, ctrlKey: boolean): v
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleModeInput(input: ModeInput): void {
|
function handleModeInput(input: ModeInput): void {
|
||||||
|
if (itemBehaviorRegistry.handleModeInput(state.mode, input)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
dispatchModeInput({
|
dispatchModeInput({
|
||||||
mode: state.mode,
|
mode: state.mode,
|
||||||
input,
|
input,
|
||||||
@@ -2546,9 +2544,6 @@ function handleModeInput(input: ModeInput): void {
|
|||||||
handleChatModeInput(currentCode, currentKey, currentCtrlKey),
|
handleChatModeInput(currentCode, currentKey, currentCtrlKey),
|
||||||
micGainEdit: ({ code: currentCode, key: currentKey, ctrlKey: currentCtrlKey }) =>
|
micGainEdit: ({ code: currentCode, key: currentKey, ctrlKey: currentCtrlKey }) =>
|
||||||
handleMicGainEditModeInput(currentCode, currentKey, currentCtrlKey),
|
handleMicGainEditModeInput(currentCode, currentKey, currentCtrlKey),
|
||||||
pianoUse: (currentInput) => {
|
|
||||||
itemBehaviorRegistry.handleModeInput(state.mode, currentInput);
|
|
||||||
},
|
|
||||||
commandPalette: ({ code: currentCode, key: currentKey }) => handleCommandPaletteModeInput(currentCode, currentKey),
|
commandPalette: ({ code: currentCode, key: currentKey }) => handleCommandPaletteModeInput(currentCode, currentKey),
|
||||||
effectSelect: ({ code: currentCode, key: currentKey }) => handleEffectSelectModeInput(currentCode, currentKey),
|
effectSelect: ({ code: currentCode, key: currentKey }) => handleEffectSelectModeInput(currentCode, currentKey),
|
||||||
helpView: ({ code: currentCode }) => handleHelpViewModeInput(currentCode),
|
helpView: ({ code: currentCode }) => handleHelpViewModeInput(currentCode),
|
||||||
@@ -2617,9 +2612,9 @@ setupKeyboardInputHandlers({
|
|||||||
handleModeInput,
|
handleModeInput,
|
||||||
canOpenCommandPaletteInMode,
|
canOpenCommandPaletteInMode,
|
||||||
openCommandPalette,
|
openCommandPalette,
|
||||||
shouldForwardModeKeyUp: shouldForwardPianoKeyUp,
|
getModeKeyUpTarget: (activeMode) => itemBehaviorRegistry.getModeKeyUpTarget(activeMode, commandPaletteReturnMode),
|
||||||
onModeKeyUp: ({ code, shiftKey }) => {
|
onModeKeyUp: (mode, { code, shiftKey }) => {
|
||||||
itemBehaviorRegistry.handleModeKeyUp('pianoUse', {
|
itemBehaviorRegistry.handleModeKeyUp(mode, {
|
||||||
code,
|
code,
|
||||||
shiftKey,
|
shiftKey,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -46,9 +46,8 @@ type MessageHandlerDeps = {
|
|||||||
randomFootstepUrl: () => string;
|
randomFootstepUrl: () => string;
|
||||||
playRemoteSpatialStepOrTeleport: (url: string, peerX: number, peerY: number) => void;
|
playRemoteSpatialStepOrTeleport: (url: string, peerX: number, peerY: number) => void;
|
||||||
handleItemActionResultStatus: (message: Extract<IncomingMessage, { type: 'item_action_result' }>) => boolean;
|
handleItemActionResultStatus: (message: Extract<IncomingMessage, { type: 'item_action_result' }>) => boolean;
|
||||||
handleRemotePianoNote: (message: Extract<IncomingMessage, { type: 'item_piano_note' }>) => void;
|
handleItemBehaviorIncomingMessage: (message: IncomingMessage) => boolean;
|
||||||
handlePianoStatus: (message: Extract<IncomingMessage, { type: 'item_piano_status' }>) => void;
|
handleItemBehaviorPeerLeft: (senderId: string) => void;
|
||||||
stopAllRemoteNotesForSender: (senderId: string) => void;
|
|
||||||
TELEPORT_SOUND_URL: string;
|
TELEPORT_SOUND_URL: string;
|
||||||
TELEPORT_START_SOUND_URL: string;
|
TELEPORT_START_SOUND_URL: string;
|
||||||
getAudioLayers: () => { world: boolean; item: boolean };
|
getAudioLayers: () => { world: boolean; item: boolean };
|
||||||
@@ -225,7 +224,7 @@ export function createOnMessageHandler(deps: MessageHandlerDeps): (message: Inco
|
|||||||
if (peer) {
|
if (peer) {
|
||||||
deps.updateStatus(`${peer.nickname} has left.`);
|
deps.updateStatus(`${peer.nickname} has left.`);
|
||||||
}
|
}
|
||||||
deps.stopAllRemoteNotesForSender(message.id);
|
deps.handleItemBehaviorPeerLeft(message.id);
|
||||||
deps.state.peers.delete(message.id);
|
deps.state.peers.delete(message.id);
|
||||||
deps.peerManager.removePeer(message.id);
|
deps.peerManager.removePeer(message.id);
|
||||||
break;
|
break;
|
||||||
@@ -335,7 +334,7 @@ export function createOnMessageHandler(deps: MessageHandlerDeps): (message: Inco
|
|||||||
|
|
||||||
case 'item_piano_note': {
|
case 'item_piano_note': {
|
||||||
if (!deps.getAudioLayers().item) break;
|
if (!deps.getAudioLayers().item) break;
|
||||||
deps.handleRemotePianoNote(message);
|
deps.handleItemBehaviorIncomingMessage(message);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -346,7 +345,7 @@ export function createOnMessageHandler(deps: MessageHandlerDeps): (message: Inco
|
|||||||
}
|
}
|
||||||
|
|
||||||
case 'item_piano_status': {
|
case 'item_piano_status': {
|
||||||
deps.handlePianoStatus(message);
|
deps.handleItemBehaviorIncomingMessage(message);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user