import {measureDistances} from "./compatibility.js"; import {getMovementHistory} from "./movement_tracking.js"; import {GenericSpeedProvider, SpeedProvider} from "./speed_provider.js"; import {settingsKey} from "./settings.js"; import {getAreaFromPositionAndShape, getTokenShape} from "./util.js"; export const availableSpeedProviders = {}; export let currentSpeedProvider = undefined; function register(module, type, speedProvider) { const id = `${type}.${module.id}`; let providerInstance; if (speedProvider.prototype instanceof SpeedProvider) { providerInstance = new speedProvider(id); } else { console.warn( `Drag Ruler | The ${type} '${module.id}' uses the old, deprecated version of the Drag Ruler API. ` + "That old API will be removed in a future Drag Ruler version. " + `Please update the ${type} ${module.id} to stay compatible with future Drag Ruler versions.`, ); speedProvider.id = id; speedProvider.usesRuler = () => true; providerInstance = speedProvider; } setupProvider(providerInstance); } function setupProvider(speedProvider) { if (speedProvider instanceof SpeedProvider) { const unreachableColor = { id: "unreachable", default: speedProvider.defaultUnreachableColor, name: "drag-ruler.settings.speedProviderSettings.color.unreachable.name", }; for (const color of speedProvider.colors.concat([unreachableColor])) { game.settings.register(settingsKey, `speedProviders.${speedProvider.id}.color.${color.id}`, { config: false, scope: "client", type: Number, default: color.default, }); } for (const setting of speedProvider.settings) { setting.config = false; game.settings.register( settingsKey, `speedProviders.${speedProvider.id}.setting.${setting.id}`, setting, ); } } availableSpeedProviders[speedProvider.id] = speedProvider; game.settings.settings.get("drag-ruler.speedProvider").default = getDefaultSpeedProvider(); updateSpeedProvider(); } export function getDefaultSpeedProvider() { const providerIds = Object.keys(availableSpeedProviders); // Game systems take the highest precedence for the being the default const gameSystem = providerIds.find(key => key.startsWith("system.")); if (gameSystem) return gameSystem; // If no game system is registered modules are next up. // For lack of a method to select the best module we're just falling back to taking the next best module // settingKeys should always be sorted the same way so this should achive a stable default const module = providerIds.find(key => key.startsWith("module.")); if (module) return module; // If neither a game system or a module is found fall back to the native implementation return providerIds[0]; } export function updateSpeedProvider() { // If the configured provider is registered use that one. If not use the default provider const configuredProvider = game.settings.get("drag-ruler", "speedProvider"); currentSpeedProvider = availableSpeedProviders[configuredProvider] ?? availableSpeedProviders[game.settings.settings.get("drag-ruler.speedProvider").default]; } export function initApi() { const genericSpeedProviderInstance = new GenericSpeedProvider("native"); setupProvider(genericSpeedProviderInstance); } export function getRangesFromSpeedProvider(token) { try { if (currentSpeedProvider instanceof Function) return currentSpeedProvider(token, 0x00ff00); const ranges = currentSpeedProvider.getRanges(token); for (const range of ranges) { range.color = game.settings.get( settingsKey, `speedProviders.${currentSpeedProvider.id}.color.${range.color}`, ); } return ranges; } catch (e) { console.error(e); return []; } } export function getUnreachableColorFromSpeedProvider() { if (currentSpeedProvider instanceof Function) return 0xff0000; try { return game.settings.get( settingsKey, `speedProviders.${currentSpeedProvider.id}.color.unreachable`, ); } catch (e) { console.error(e); return 0xff0000; } } export function getColorForDistanceAndToken(distance, token, ranges = null) { if (!ranges) { ranges = getRangesFromSpeedProvider(token); } if (ranges.length === 0) return null; const currentRange = ranges.reduce( (minRange, currentRange) => { if (distance <= currentRange.range && currentRange.range < minRange.range) return currentRange; return minRange; }, {range: Infinity, color: getUnreachableColorFromSpeedProvider()}, ); return currentRange.color; } export function getMovedDistanceFromToken(token) { const terrainRulerAvailable = game.modules.get("terrain-ruler")?.active; const history = getMovementHistory(token); const segments = CONFIG.Canvas.rulerClass .dragRulerGetRaysFromWaypoints(history, {x: token.x, y: token.y}) .map(ray => { return {ray}; }); const shape = getTokenShape(token); const distances = measureDistances(segments, token, shape, { enableTerrainRuler: terrainRulerAvailable, }); // Sum up the distances return distances.reduce((acc, val) => acc + val, 0); } export function buildCostFunction(token, shape) { return (x, y, costOptions = {}) => { costOptions.token = token; const area = getAreaFromPositionAndShape({x, y}, shape); const costs = area.map(space => canvas.terrain.cost({x: space.x, y: space.y}, costOptions)); return costs.reduce((max, current) => Math.max(max, current)); }; } export function registerModule(moduleId, speedProvider) { // Check if a module with the given id exists and is currently enabled const module = game.modules.get(moduleId); // If it doesn't the calling module did something wrong. Log a warning and ignore this module if (!module) { console.warn( `Drag Ruler | A module tried to register with the id "${moduleId}". However no active module with this id was found.` + "This api registration call was ignored. " + "If you are the author of that module please check that the id passed to `registerModule` matches the id in your manifest exactly." + "If this call was made form a game system instead of a module please use `registerSystem` instead.", ); return; } // Using Drag Ruler's id is not allowed if (moduleId === "drag-ruler") { console.warn( `Drag Ruler | A module tried to register with the id "${moduleId}", which is not allowed. This api registration call was ignored. ` + "If you're the author of the module please use the id of your own module as it's specified in your manifest to register to this api. " + "If this call was made form a game system instead of a module please use `registerSystem` instead.", ); return; } register(module, "module", speedProvider); } export function registerSystem(systemId, speedProvider) { const system = game.system; // If the current system id doesn't match the provided id something went wrong. Log a warning and ignore this module if (system.id != systemId) { console.warn( `Drag Ruler | A system tried to register with the id "${systemId}". However the active system has a different id.` + "This api registration call was ignored. " + "If you are the author of that system please check that the id passed to `registerSystem` matches the id in your manifest exactly." + "If this call was made form a module instead of a game system please use `registerModule` instead.", ); return; } register(system, "system", speedProvider); }