From 4f1dec3089f455c17f920ca268e5cfb1e59e811c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20V=C3=B6gele?= Date: Mon, 15 Mar 2021 20:17:38 +0100 Subject: [PATCH] Support for difficult terrain with tokens larger than 1x1 --- CHANGELOG.md | 3 +++ src/api.js | 7 +++++++ src/foundry_imports.js | 8 +++++--- src/speed_provider.js | 16 ++++++++++++++++ src/util.js | 23 +++++++++++++++-------- 5 files changed, 46 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd18019..957f0f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ - The GM's Drag Ruler can now be hidden from non GM players via a setting. - When multiple different colors apply to a single grid space because the path crosses itself the color representing ranges further away will take priortiy over colors representing closer ranges. +### API +- Speed providers can now selectively ignore difficult terrain and even implement their own cost functions, if the default Drag Ruler behavior doesn't fit the game system. This can be achieved by overriding the new, optional `SpeedProvider` function `getCostForStep`. + ## 1.3.5 ### Bugfixes diff --git a/src/api.js b/src/api.js index bcd139d..5f80b7d 100644 --- a/src/api.js +++ b/src/api.js @@ -97,6 +97,13 @@ export function getUnreachableColorFromSpeedProvider() { } } +export function getCostFromSpeedProvider(token, area) { + if (currentSpeedProvider instanceof Function) { + return SpeedProvider.prototype.getCostForStep.call(undefined, token, area); + } + return currentSpeedProvider.getCostForStep(token, area); +} + export function registerModule(moduleId, speedProvider) { // Check if a module with the given id exists and is currently enabled const module = game.modules.get(moduleId) diff --git a/src/foundry_imports.js b/src/foundry_imports.js index 9acaf95..a3c5f32 100644 --- a/src/foundry_imports.js +++ b/src/foundry_imports.js @@ -1,7 +1,8 @@ +import { getCostFromSpeedProvider } from "./api.js"; import {highlightMeasurementTerrainRuler} from "./compatibility.js"; import {getGridPositionFromPixels} from "./foundry_fixes.js"; import {getColorForDistance} from "./main.js" -import {applyTokenSizeOffset, getSnapPointForToken, getTokenShape, getTokenSize, highlightTokenShape, zip} from "./util.js"; +import {applyTokenSizeOffset, getAreaFromPositionAndShape, getSnapPointForToken, getTokenShape, highlightTokenShape, zip} from "./util.js"; // This is a modified version of Ruler.moveToken from foundry 0.7.9 export async function moveTokens(draggedToken, selectedTokens) { @@ -132,11 +133,13 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) { centeredSegments.push({ray: centeredRay, label}) } + const shape = getTokenShape(this.draggedToken) + // Compute measured distance const terrainRulerAvailable = game.modules.get("terrain-ruler")?.active && canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS let distances if (terrainRulerAvailable) - distances = game.terrainRuler.measureDistances(centeredSegments) + distances = game.terrainRuler.measureDistances(centeredSegments, {costFunction: (x, y) => getCostFromSpeedProvider(this.draggedToken, getAreaFromPositionAndShape({x, y}, shape), {x, y})}); else distances = canvas.grid.measureDistances(centeredSegments, { gridSpaces }); @@ -161,7 +164,6 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) { rulerColor = getColorForDistance.call(this, totalDistance) else rulerColor = this.color - const shape = getTokenShape(this.draggedToken) for (const [s, cs] of zip(segments.reverse(), centeredSegments.reverse())) { const { label, text, last } = cs; diff --git a/src/speed_provider.js b/src/speed_provider.js index 323f03c..58a8d2c 100644 --- a/src/speed_provider.js +++ b/src/speed_provider.js @@ -56,6 +56,22 @@ export class SpeedProvider { return 0xFF0000 } + /** + * Returns the cost for a token to step into the specificed area. + * The area indicates the whole area that the token will occupy (for tokens larger than 1x1) the array will more than one entry. + * The return value should be an integer indicating a multiplicator by that the cost of that step should be increased. + * (1 is regular cost, 2 costs double, 3 costs triple, ...) + * This function is only called if the TerrainLayer and TerrainRuler modules are enabled. + * + * Implementing this method is optional and only needs to be done if you want to provide a custom cost function (for example to allow tokens to ignore difficult terrain) + */ + getCostForStep(token, area) { + // Lookup the cost for each square occupied by the token + const costs = area.map(space => canvas.terrain.costGrid[space.y]?.[space.x]?.multiple ?? 1) + // Return the maximum of the costs + return costs.reduce((max, current) => Math.max(max, current)) + } + /** * Returns a boolean indicating whether this token will use a Ruler or not. * If this is returns `false` for a token Drag Ruler will be disabled for that token. Dragging a token for which this function diff --git a/src/util.js b/src/util.js index b8e919f..1cb0f56 100644 --- a/src/util.js +++ b/src/util.js @@ -24,9 +24,17 @@ export function getSnapPointForToken(x, y, token) { } export function highlightTokenShape(position, shape, color) { - for (const space of shape) { - let gridX = position.x + space.x; - let gridY = position.y + space.y; + const area = getAreaFromPositionAndShape(position, shape); + for (const space of area) { + const [x, y] = getPixelsFromGridPosition(space.x, space.y); + canvas.grid.highlightPosition(this.name, {x, y, color}) + } +} + +export function getAreaFromPositionAndShape(position, shape) { + return shape.map(space => { + let x = position.x + space.x; + let y = position.y + space.y; if (canvas.grid.isHex) { let shiftedRow; if (canvas.grid.grid.options.even) @@ -35,18 +43,17 @@ export function highlightTokenShape(position, shape, color) { shiftedRow = 0 if (canvas.grid.grid.options.columns) { if (space.x % 2 !== 0 && position.x % 2 !== shiftedRow) { - gridY += 1; + y += 1; } } else { if (space.y % 2 !== 0 && position.y % 2 !== shiftedRow) { - gridX += 1; + x += 1; } } } - const [x, y] = getPixelsFromGridPosition(gridX, gridY); - canvas.grid.highlightPosition(this.name, {x, y, color}) - } + return {x, y} + }); } export function getTokenShape(token) {