diff --git a/langs/en-ca.json b/langs/en-ca.json index 02cd095..777e3c9 100644 --- a/langs/en-ca.json +++ b/langs/en-ca.json @@ -65,6 +65,12 @@ "label": "Configure Hotbar" } }, + "keybindings": { + "toggleMouseBroadcast": { + "name": "Toggle Mouse Position", + "hint": "(v13+) Temporarily turns off or on the mouse cursor position that players see." + } + }, "apps": { "no-settings-to-display": "No settings to display", "make-global-reference": "Make Global Reference" @@ -77,6 +83,12 @@ "ignoreAndDontShowAgain": "", "disableEntirely": "" } + }, + "notifs": { + "toggleMouseBroadcast": { + "hidingCursor": "Hiding your cursor from others!", + "showingCursor": "Showing your cursor to others!" + } } } } diff --git a/module/hooks/init.mjs b/module/hooks/init.mjs index 60cde1b..d8bae1f 100644 --- a/module/hooks/init.mjs +++ b/module/hooks/init.mjs @@ -1,5 +1,6 @@ // Settings import { preventMovementHistory } from "../settings/preventMovementHistory.mjs"; +import { toggleMouseBroadcast } from "../settings/toggleMouseBroadcast.mjs"; // Utils import { Logger } from "../utils/Logger.mjs"; @@ -14,4 +15,5 @@ Hooks.on(`init`, () => { Logger.log(`Initializing`); preventMovementHistory(); + toggleMouseBroadcast(); }); diff --git a/module/settings/toggleMouseBroadcast.mjs b/module/settings/toggleMouseBroadcast.mjs new file mode 100644 index 0000000..6f70692 --- /dev/null +++ b/module/settings/toggleMouseBroadcast.mjs @@ -0,0 +1,130 @@ +import { SettingStatusEnum, status } from "../utils/SettingStatus.mjs"; +import { __ID__ } from "../consts.mjs"; +import { Logger } from "../utils/Logger.mjs"; + +const key = `toggleMouseBroadcast`; + +/** @type {number | null} */ +let notifID = null; + +export function toggleMouseBroadcast() { + status[key] = SettingStatusEnum.Unknown; + + // #region Registration + const prevented = Hooks.call(`${__ID__}.preventSetting`, key); + if (!prevented) { + Logger.log(`Preventing setting "${key}" from being registered`); + status[key] = SettingStatusEnum.Blocked; + return; + }; + Logger.log(`Registering setting: ${key}`); + + // MARK: setting + game.settings.register(__ID__, key, { + scope: `client`, + config: false, + default: true, + }); + + // MARK: keybind + game.keybindings.register(__ID__, key, { + name: `OFT.keybindings.${key}.name`, + hint: `OFT.keybindings.${key}.hint`, + precedence: CONST.KEYBINDING_PRECEDENCE.NORMAL, + restricted: true, + editable: [ + { key: `KeyH` }, + ], + onDown: (event) => { + if (!game.user.hasPermission(`SHOW_CURSOR`)) { return }; + event.preventDefault?.(); + const current = game.settings.get(__ID__, key); + + if (current) { + notifID = ui.notifications.warn( + `OFT.notifs.${key}.hidingCursor`, + { console: false, localize: true, permanent: true }, + ).id; + } else { + if (notifID != null) { + ui.notifications.remove(notifID); + }; + ui.notifications.success( + `OFT.notifs.${key}.showingCursor`, + { console: false, localize: true }, + ); + }; + + game.settings.set(__ID__, key, !current); + + // Hide the existing cursor + game.user.broadcastActivity({ cursor: null }); + return true; + }, + }); + // #endregion Registration + + // #region Implementation + Hooks.on(`renderControlsConfig`, renderControlsConfigHandler); + Hooks.once(`ready`, readyHandler); + + class OFTControlsLayer extends CONFIG.Canvas.layers.controls.layerClass { + _onMouseMove(currentPos) { + if (!game.settings.get(__ID__, key)) { + game.user.broadcastActivity({}); + }; + super._onMouseMove(currentPos); + }; + }; + CONFIG.Canvas.layers.controls.layerClass = OFTControlsLayer; + + // #endregion Implementation + + status[key] = SettingStatusEnum.Registered; +}; + +// #region Helpers +const tabGroup = `categories`; + +/** + * Handle showing the "hiding your cursor" notification when the user + * connects to the server initially, allowing the current state to be + * correctly described to the user. + */ +function readyHandler() { + const hideCursor = !game.settings.get(__ID__, key); + const canShowCursor = game.user.hasPermission(`SHOW_CURSOR`); + if (hideCursor && canShowCursor) { + notifID = ui.notifications.warn( + `OFT.notifs.${key}.hidingCursor`, + { console: false, localize: true, permanent: true }, + ).id; + }; +}; + +/** + * Handles hiding the keybinding from the configuration if and when the user + * does not have the SHOW_CURSOR permission since this keybinding is really + * useless if they can't even broadcast their cursor in the first place. + */ +function renderControlsConfigHandler(_app, element) { + + if (game.user.hasPermission(`SHOW_CURSOR`)) { return }; + + const keybindingList = element.querySelector(`section[data-group="${tabGroup}"][data-tab="${__ID__}"]`); + const tabButton = element.querySelector(`button[data-group="${tabGroup}"][data-tab="${__ID__}"]`); + + if (keybindingList.childElementCount === 0) { + tabButton?.remove(); + return; + }; + + const keybind = keybindingList.querySelector(`.form-group[data-action-id="${__ID__}.${key}"]`); + keybind?.remove(); + + if (keybindingList.childElementCount === 0) { + keybindingList.remove(); + tabButton.remove(); + }; +}; +// #endregion Helpers