import { availableSpeedProviders, currentSpeedProvider, getDefaultSpeedProvider, updateSpeedProvider, } from "./api.js"; import {SpeedProvider} from "./speed_provider.js"; import {wipePathfindingCache} from "./pathfinding.js"; import {early_isGM} from "./util.js"; export const settingsKey = "drag-ruler"; export const RightClickAction = Object.freeze({ CREATE_WAYPOINT: 0, DELETE_WAYPOINT: 1, ABORT_DRAG: 2, }); function delayedReload() { window.setTimeout(() => location.reload(), 500); } export function registerSettings() { game.settings.register(settingsKey, "dataVersion", { scope: "world", config: false, type: String, default: "fresh install", }); game.settings.register(settingsKey, "clientDataVersion", { scope: "client", config: false, type: String, default: "fresh install", }); game.settings.register(settingsKey, "rightClickAction", { name: "drag-ruler.settings.rightClickAction.name", hint: "drag-ruler.settings.rightClickAction.hint", config: true, type: Number, default: RightClickAction.DELETE_WAYPOINT, choices: { 0: "drag-ruler.settings.rightClickAction.choices.create", 1: "drag-ruler.settings.rightClickAction.choices.delete", 2: "drag-ruler.settings.rightClickAction.choices.cancel", }, }); game.settings.register(settingsKey, "autoStartMeasurement", { name: "drag-ruler.settings.autoStartMeasurement.name", hint: "drag-ruler.settings.autoStartMeasurement.hint", scope: "client", config: true, type: Boolean, default: true, }); game.settings.register(settingsKey, "useGridlessRaster", { name: "drag-ruler.settings.useGridlessRaster.name", hint: "drag-ruler.settings.useGridlessRaster.hint", scope: "client", config: true, type: Boolean, default: true, }); game.settings.register(settingsKey, "alwaysShowSpeedForPCs", { name: "drag-ruler.settings.alwaysShowSpeedForPCs.name", hint: "drag-ruler.settings.alwaysShowSpeedForPCs.hint", scope: "world", config: true, type: Boolean, default: true, }); game.settings.register(settingsKey, "showGMRulerToPlayers", { name: "drag-ruler.settings.showGMRulerToPlayers.name", hint: "drag-ruler.settings.showGMRulerToPlayers.hint", scope: "world", config: true, type: Boolean, default: true, }); game.settings.register(settingsKey, "enableMovementHistory", { name: "drag-ruler.settings.enableMovementHistory.name", hint: "drag-ruler.settings.enableMovementHistory.hint", scope: "world", config: true, type: Boolean, default: true, }); game.settings.register(settingsKey, "allowPathfinding", { name: "drag-ruler.settings.allowPathfinding.name", hint: "drag-ruler.settings.allowPathfinding.hint", scope: "world", config: true, type: Boolean, default: false, onChange: delayedReload, }); game.settings.register(settingsKey, "autoPathfinding", { name: "drag-ruler.settings.autoPathfinding.name", hint: "drag-ruler.settings.autoPathfinding.hint", scope: "client", config: early_isGM() || game.settings.get(settingsKey, "allowPathfinding"), type: Boolean, default: false, }); game.settings.register(settingsKey, "pathfindingRadius", { scope: "world", config: false, type: Number, default: 0.9, onChange: wipePathfindingCache, }); game.settings.register(settingsKey, "lastTerrainRulerHintTime", { config: false, type: Number, default: 0, }); game.settings.register(settingsKey, "neverShowTerrainRulerHint", { config: false, type: Boolean, default: false, }); // This setting will be modified by the api if modules register to it game.settings.register(settingsKey, "speedProvider", { scope: "world", config: false, type: String, default: getDefaultSpeedProvider(), onChange: updateSpeedProvider, }); game.settings.registerMenu(settingsKey, "speedProviderSettings", { name: "drag-ruler.settings.speedProviderSettings.name", hint: "drag-ruler.settings.speedProviderSettings.hint", label: "drag-ruler.settings.speedProviderSettings.button", icon: "fas fa-tachometer-alt", type: SpeedProviderSettings, restricted: false, }); } class SpeedProviderSettings extends FormApplication { static get defaultOptions() { return mergeObject(super.defaultOptions, { id: "drag-ruler-speed-provider-settings", title: game.i18n.localize("drag-ruler.settings.speedProviderSettings.windowTitle"), template: "modules/drag-ruler/templates/speed_provider_settings.html", width: 600, }); } getData(options = {}) { const data = {}; data.isGM = game.user.isGM; const selectedProvider = currentSpeedProvider.id; // Insert all speed providers into the template data data.providers = Object.values(availableSpeedProviders).map(speedProvider => { const provider = {}; provider.id = speedProvider.id; provider.hasSettings = speedProvider instanceof SpeedProvider; if (provider.hasSettings) provider.settings = enumerateProviderSettings(speedProvider); let dotPosition = provider.id.indexOf("."); if (dotPosition === -1) dotPosition = provider.id.length; const type = provider.id.substring(0, dotPosition); const id = provider.id.substring(dotPosition + 1); if (type === "native") { provider.selectTitle = game.i18n.localize( "drag-ruler.settings.speedProviderSettings.speedProvider.choices.native", ); } else { let name; if (type === "module") { name = game.modules.get(id).data.title; } else { name = game.system.data.title; } provider.selectTitle = game.i18n.format( `drag-ruler.settings.speedProviderSettings.speedProvider.choices.${type}`, {name}, ); } provider.isSelected = provider.id === selectedProvider; return provider; }); data.selectedProviderName = data.providers.find(provider => provider.isSelected).selectTitle; data.providerSelection = { id: "speedProvider", name: game.i18n.localize("drag-ruler.settings.speedProviderSettings.speedProvider.name"), hint: game.i18n.localize("drag-ruler.settings.speedProviderSettings.speedProvider.hint"), type: String, choices: data.providers.reduce((choices, provider) => { choices[provider.id] = provider.selectTitle; return choices; }, {}), value: selectedProvider, isCheckbox: false, isSelect: true, isRange: false, }; return data; } async _updateObject(event, formData) { const selectedSpeedProvider = game.user.isGM ? formData.speedProvider : game.settings.get(settingsKey, "speedProvider"); for (let [key, value] of Object.entries(formData)) { // Check if this is color, convert the value to an integer const splitKey = key.split(".", 3); if (splitKey[0] !== "native") splitKey.shift(); if (splitKey.length >= 2 && splitKey[1] == "color") { value = parseInt(value.substring(1), 16); } // Don't change settings for speed providers that aren't currently active if (key !== "speedProvider" && !key.startsWith(selectedSpeedProvider)) continue; // Get the key for the current setting let setting; if (key === "speedProvider") setting = "speedProvider"; else setting = `speedProviders.${key}`; // Get the old setting value const oldValue = game.settings.get(settingsKey, setting); // Only update the setting if it has been changed (this leaves the default in place if it hasn't been touched) if (value !== oldValue) game.settings.set(settingsKey, setting, value); } // Activate the configured speed provider updateSpeedProvider(); } activateListeners(html) { super.activateListeners(html); html.find("select[name=speedProvider]").change(this.onSpeedProviderChange.bind(this)); } onSpeedProviderChange(event) { // Hide all module settings document .querySelectorAll(".drag-ruler-provider-settings") .forEach(element => (element.style.display = "none")); // Show the settings block for the currently selected module document.getElementById(`drag-ruler.provider.${event.currentTarget.value}`).style.display = ""; // Recalculate window height this.element[0].style.height = null; this.position.height = undefined; } } function toDomHex(value) { const hex = value.toString(16); return "#" + "0".repeat(Math.max(0, 6 - hex.length)) + hex; } function enumerateProviderSettings(provider) { const colorSettings = []; const unreachableColor = { id: "unreachable", name: "drag-ruler.settings.speedProviderSettings.color.unreachable.name", }; // Resolve settings for the colors for (const color of provider.colors.concat([unreachableColor])) { // Localize the name, if avaliable. If no name is available use the id as name const colorName = color.name ? game.i18n.localize(color.name) : color.id; let hint; if (color === unreachableColor) hint = game.i18n.localize("drag-ruler.settings.speedProviderSettings.color.unreachable.hint"); else hint = game.i18n.format("drag-ruler.settings.speedProviderSettings.color.hint", {colorName}); colorSettings.push({ id: `${provider.id}.color.${color.id}`, name: game.i18n.format("drag-ruler.settings.speedProviderSettings.color.name", {colorName}), hint: hint, type: Number, value: toDomHex( game.settings.get(settingsKey, `speedProviders.${provider.id}.color.${color.id}`), ), isCheckbox: false, isSelect: false, isRange: false, isColor: true, }); } // Prepare regular settings const settings = []; for (const setting of provider.settings) { try { if (setting.scope === "world" && !game.user.isGM) continue; const s = duplicate(setting); s.id = `${provider.id}.setting.${s.id}`; s.name = game.i18n.localize(s.name); s.hint = game.i18n.localize(s.hint); s.value = provider.getSetting(setting.id); s.type = setting.type instanceof Function ? setting.type.name : "String"; s.isCheckbox = setting.type === Boolean; s.isSelect = s.choices !== undefined; s.isRange = setting.type === Number && s.range; s.isColor = false; settings.push(s); } catch (e) { console.warn( `Drag Ruler | The following error occured while rendering setting "${setting.id}" of module/system "${this.id}. It won't be displayed.`, ); console.error(e); } } return settings.concat(colorSettings); }