Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e0d0483722 | |||
| 4f1dec3089 | |||
| c4d089e8ff | |||
| f8fe4ee971 | |||
| 378401b5bb | |||
| 0a54a47951 | |||
| 0b71504da3 | |||
| 5a7f5531f2 | |||
| 862477c698 | |||
| 384de1a1b5 | |||
| 0a7d8495c6 | |||
| 6e74582089 | |||
| 20e8713e30 | |||
| 544740b697 | |||
| 404e978377 | |||
| 4a56cdb0a4 | |||
| 48e6901b59 | |||
| 15e910424f | |||
| baba6e2986 | |||
| 6f0a04c20f | |||
| 8522c9a811 | |||
| d5fd5e0d04 |
@@ -1,3 +1,39 @@
|
||||
## 1.4.0
|
||||
### New features
|
||||
- If the [Terrain Ruler module](https://foundryvtt.com/packages/terrain-ruler/) is installed and activated, Drag Ruler will now take difficult terrain that was placed with the [TerrainLayer module](https://foundryvtt.com/packages/TerrainLayer/) into account.
|
||||
- The ruler will now always be drawn from the tokens center (even for tokens larger than 1x1) on all grid types (on Hex Grids this requires the Hex Token Size Support module).
|
||||
- For tokens larger than 1x1 the highlighted path will now reflect the tokens size (on Hex Grids this requires the Hex Token Size Support module)
|
||||
- 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
|
||||
- Fixed a regression where spaces could suddenly change their color during measurement
|
||||
|
||||
|
||||
## 1.3.4
|
||||
### Module compatibility
|
||||
- Increased compatiblility with other modules (namely Drag Ruler and Terrain Ruler are no longer incompatible)
|
||||
|
||||
## 1.3.3
|
||||
### Bugfixes
|
||||
- Speed Provider Settings are now being saved for non GM players
|
||||
|
||||
|
||||
## 1.3.2
|
||||
### Translation
|
||||
- Updated japanese translation (thanks to togue)
|
||||
|
||||
|
||||
## 1.3.1
|
||||
### Bugfixes
|
||||
- Fixed a bug where the coloring of ranges wouldn't work with the generic speed provider if the Dash Multiplier was set to 0
|
||||
|
||||
|
||||
## 1.3.0
|
||||
### New features
|
||||
- The color used to indicate speed ranges is now configurable
|
||||
|
||||
@@ -15,6 +15,13 @@ You can add waypoints to the path by pressing spacebar while you drag the token.
|
||||
|
||||

|
||||
|
||||
## Difficult Terrain
|
||||
**To use support for difficult terrain you must install the [Terrain Ruler module](https://foundryvtt.com/packages/terrain-ruler)**
|
||||
|
||||
With the Terrain Ruler module installed, Drag Ruler is able to take difficult terrain that was placed with the [TerrainLayer module](https://foundryvtt.com/packages/TerrainLayer/) into account when measuring distances.
|
||||
|
||||

|
||||
|
||||
|
||||
## Why would I want to use this instead of ShowDragDistance?
|
||||
ShowDragDistance isn't maintained anymore. This means that it is at risk to stop working with every foundry update. In fact this process has already begun. As of Foundry Version 0.7.9 ShowDragDistance doesn't work anymore on gridless maps. Drag Ruler on the other hand is fully compatible with the current Foundry release and I'll continue updating it for future foundry releases for the forseeable future.
|
||||
@@ -29,6 +36,7 @@ The game systems that offer Drag Ruler integration are:
|
||||
- Cypher System (starting with version 1.13.0)
|
||||
- Pathfinder 1 (starting with version 0.77.3)
|
||||
- Pathfinder 2e (via the module [PF2E Drag Ruler Integration](https://foundryvtt.com/packages/pf2e-dragruler/))
|
||||
- Tormenta20 (starting with version 1.1.37)
|
||||
|
||||
|
||||
## Translations
|
||||
|
||||
+5
-1
@@ -21,6 +21,10 @@
|
||||
"name": "Show PC speed to everyone",
|
||||
"hint": "If enabled the coloring based on actor speed for player characters will shown to everyone, even if they don't have observer permission for the character sheet."
|
||||
},
|
||||
"showGMRulerToPlayers": {
|
||||
"name": "Show GM ruler to players",
|
||||
"hint": "If disabled the ruler of GMs will not be shown for non-GM players"
|
||||
},
|
||||
"speedProviderSettings": {
|
||||
"name": "Speed Provider Settings",
|
||||
"hint": "The Speed Provider Settings contain all the game system specific settings.",
|
||||
@@ -37,7 +41,7 @@
|
||||
"noSettings": "This speed provider doesn't offer any configuration options.",
|
||||
"color": {
|
||||
"name": "Color for {colorName}",
|
||||
"hint": "The color that will be used to color square that are within {colorName} range",
|
||||
"hint": "The color that will be used to color squares that are within {colorName} range",
|
||||
"unreachable": {
|
||||
"name": "unreachable",
|
||||
"hint": "The color for spaces that aren't reachable by the dragged token"
|
||||
|
||||
+43
-16
@@ -1,30 +1,57 @@
|
||||
{
|
||||
"drag-ruler": {
|
||||
"genericSpeedProvider": {
|
||||
"settings": {
|
||||
"dashMultiplier": {
|
||||
"name": "早足の倍率",
|
||||
"hint": "設定すると、移動経路の測定中に第二の色分けが追加されます。0にすると第二速度が無効になります。"
|
||||
},
|
||||
"speedAttribute": {
|
||||
"name": "移動速度の属性値",
|
||||
"hint": "コマの移動速度が定義された属性値を指定します。移動範囲表示の色分けに使用されます。"
|
||||
}
|
||||
},
|
||||
"speeds": {
|
||||
"walk": "歩き",
|
||||
"dash": "早足"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"alwaysShowSpeedForPCs": {
|
||||
"name": "プレイヤーキャラクターの移動速度を全員に公開する",
|
||||
"hint": "有効にすると、アクターの観察者(Observer)権限を持たない人にも,移動範囲の色分けが表示されるようになります。"
|
||||
},
|
||||
"dashMultiplier": {
|
||||
"name": "早足の倍率",
|
||||
"hint": "有効にすると、移動範囲に二つ目の色分けが追加されます。0にすると、この機能は無効になります。"
|
||||
"speedProviderSettings": {
|
||||
"name": "移動速度の提供元",
|
||||
"hint": "ゲームシステムに固有のすべての設定をここで行います。",
|
||||
"button": "移動速度の提供元設定",
|
||||
"windowTitle": "移動速度の提供元設定",
|
||||
"headers": {
|
||||
"speedProvider": "移動速度の提供元",
|
||||
"speedProviderSettings": "提供元に固有の設定"
|
||||
},
|
||||
"speedAttribute": {
|
||||
"name": "移動速度の属性値",
|
||||
"hint": "コマの移動速度が定義された属性値を指定します。移動範囲表示の色分けに使用されます。"
|
||||
"activeProvider": {
|
||||
"name": "現在有効な提供元",
|
||||
"hint": "以下の設定は、現在有効な移動速度の提供元によって変化します。GMが別の提供元を選択すると、利用可能な項目が変更となる場合があります。"
|
||||
},
|
||||
"speedProvider": {
|
||||
"name": "移動速度設定の提供元",
|
||||
"hint": "色分けに使用する速度情報の提供元を選択します。ゲームシステムやモジュールを選択すると、Drag Ruler自体が提供するものより柔軟な色分けが行える可能性があります。",
|
||||
"choices": {
|
||||
"module": "モジュール",
|
||||
"native": "Drag Ruler",
|
||||
"system": "ゲームシステム"
|
||||
"noSettings": "この移動速度提供元には、設定オプションがありません。",
|
||||
"color": {
|
||||
"name": "{colorName}の色",
|
||||
"hint": "{colorName}の範囲に使用するセルの色を指定します。",
|
||||
"unreachable": {
|
||||
"name": "到達不能域",
|
||||
"hint": "ドラッグしたコマが到達できない範囲の色を指定します。"
|
||||
}
|
||||
},
|
||||
"staticFirstColor": {
|
||||
"name": "一つ目の色を固定する",
|
||||
"hint": "一つ目の移動範囲の色に、プレイヤーの色でなく固定された色を使用します。"
|
||||
"speedProvider": {
|
||||
"name": "移動速度の提供元",
|
||||
"hint": "色分けに使用する速度情報の提供元を選択します。Drag Rulerは基本機能を備えた汎用の提供元を備えており、正しく設定すればすべてのゲームシステムで動作するはずです。しかしゲームシステムやモジュールを介することで、より多くの提供元を利用することもできます。こうした提供元を利用すると、ゲームルールとの統合がさらに向上するかもしれません。以下のオプションは、ここで選択した提供元によって変化します。",
|
||||
"choices": {
|
||||
"module": "モジュール {name}",
|
||||
"native": "汎用",
|
||||
"system": "システム {name}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"swapSpacebarRightClick": {
|
||||
"name": "スペースキーと右クリックを入れ替える",
|
||||
|
||||
+4
-3
@@ -2,7 +2,7 @@
|
||||
"name": "drag-ruler",
|
||||
"title": "Drag Ruler",
|
||||
"description": "When dragging a token displays a ruler showing how far you've moved that token.",
|
||||
"version": "1.3.0",
|
||||
"version": "1.4.0",
|
||||
"minimumCoreVersion" : "0.7.9",
|
||||
"compatibleCoreVersion" : "0.7.9",
|
||||
"authors": [
|
||||
@@ -31,9 +31,10 @@
|
||||
}
|
||||
],
|
||||
"url": "https://github.com/manuelVo/foundryvtt-drag-ruler",
|
||||
"download": "https://github.com/manuelVo/foundryvtt-drag-ruler/archive/v1.3.0.zip",
|
||||
"download": "https://github.com/manuelVo/foundryvtt-drag-ruler/archive/v1.4.0.zip",
|
||||
"manifest": "https://raw.githubusercontent.com/manuelVo/foundryvtt-drag-ruler/master/module.json",
|
||||
"readme": "https://github.com/manuelVo/foundryvtt-drag-ruler/blob/master/README.md",
|
||||
"changelog": "https://github.com/manuelVo/foundryvtt-drag-ruler/blob/master/CHANGELOG.md",
|
||||
"bugs": "https://github.com/manuelVo/foundryvtt-drag-ruler/issues"
|
||||
"bugs": "https://github.com/manuelVo/foundryvtt-drag-ruler/issues",
|
||||
"allowBugReporter": true
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
+9
-14
@@ -1,19 +1,14 @@
|
||||
import {getColorForDistance} from "./main.js"
|
||||
import {highlightTokenShape} from "./util.js"
|
||||
|
||||
export function getHexSizeSupportTokenGridCenter(token) {
|
||||
const tokenCenterOffset = CONFIG.hexSizeSupport.getCenterOffset(token)
|
||||
const tokenCenter = {x: token.x + tokenCenterOffset.x, y: token.y + tokenCenterOffset.y}
|
||||
if (token.getFlag("hex-size-support", "borderSize") % 2 === 1)
|
||||
return tokenCenter
|
||||
if (canvas.grid.grid.columns) {
|
||||
let hexOffset = canvas.grid.w / 2
|
||||
if (!CONFIG.hexSizeSupport.getAltOrientationFlag(token))
|
||||
hexOffset *= -1
|
||||
tokenCenter.x += hexOffset
|
||||
return {x: token.x + tokenCenterOffset.x, y: token.y + tokenCenterOffset.y}
|
||||
}
|
||||
else {
|
||||
let hexOffset = canvas.grid.h / 2
|
||||
if (CONFIG.hexSizeSupport.getAltOrientationFlag(token))
|
||||
hexOffset *= -1
|
||||
tokenCenter.y += hexOffset
|
||||
|
||||
export function highlightMeasurementTerrainRuler(ray, startDistance, tokenShape=[{x: 0, y: 0}]) {
|
||||
for (const space of ray.terrainRulerVisitedSpaces.reverse()) {
|
||||
const color = getColorForDistance.call(this, startDistance, space.distance)
|
||||
highlightTokenShape.call(this, space, tokenShape, color)
|
||||
}
|
||||
return tokenCenter
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
// Wrapper to fix a FoundryVTT bug that causes the return values of canvas.grid.grid.getPixelsFromGridPosition to be ordered inconsistently
|
||||
|
||||
// https://gitlab.com/foundrynet/foundryvtt/-/issues/4705
|
||||
export function getPixelsFromGridPosition(xGrid, yGrid) {
|
||||
if (canvas.grid.isHex) {
|
||||
return canvas.grid.grid.getPixelsFromGridPosition(yGrid, xGrid)
|
||||
}
|
||||
const [x, y] = canvas.grid.grid.getPixelsFromGridPosition(xGrid, yGrid)
|
||||
if (canvas.grid.type === CONST.GRID_TYPES.SQUARE)
|
||||
return [y, x]
|
||||
return [x, y]
|
||||
}
|
||||
|
||||
// Wrapper to fix a FoundryVTT bug that causes the return values of canvas.grid.grid.getPixelsFromGridPosition to be ordered inconsistently
|
||||
// https://gitlab.com/foundrynet/foundryvtt/-/issues/4705
|
||||
export function getGridPositionFromPixels(xPixel, yPixel) {
|
||||
const [x, y] = canvas.grid.grid.getGridPositionFromPixels(xPixel, yPixel)
|
||||
if (canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS)
|
||||
return [y, x]
|
||||
return [x, y]
|
||||
}
|
||||
+72
-11
@@ -1,4 +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, 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) {
|
||||
@@ -102,9 +106,12 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
|
||||
return []
|
||||
|
||||
if (snap)
|
||||
destination = new PIXI.Point(...canvas.grid.getCenter(destination.x, destination.y));
|
||||
destination = getSnapPointForToken(destination.x, destination.y, this.draggedToken)
|
||||
|
||||
const waypoints = this.waypoints.concat([destination]);
|
||||
const centeredWaypoints = waypoints.map(w => new PIXI.Point(...canvas.grid.getCenter(w.x, w.y)))
|
||||
// Move the waypoints to the center of the grid if a size is used that measures from edge to edge
|
||||
const centeredWaypoints = applyTokenSizeOffset(waypoints, this.draggedToken)
|
||||
|
||||
const r = this.ruler;
|
||||
this.destination = destination;
|
||||
|
||||
@@ -126,14 +133,22 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
|
||||
centeredSegments.push({ray: centeredRay, label})
|
||||
}
|
||||
|
||||
const shape = getTokenShape(this.draggedToken)
|
||||
|
||||
// Compute measured distance
|
||||
const distances = canvas.grid.measureDistances(centeredSegments, { gridSpaces });
|
||||
const terrainRulerAvailable = game.modules.get("terrain-ruler")?.active && canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS
|
||||
let distances
|
||||
if (terrainRulerAvailable)
|
||||
distances = game.terrainRuler.measureDistances(centeredSegments, {costFunction: (x, y) => getCostFromSpeedProvider(this.draggedToken, getAreaFromPositionAndShape({x, y}, shape), {x, y})});
|
||||
else
|
||||
distances = canvas.grid.measureDistances(centeredSegments, { gridSpaces });
|
||||
|
||||
let totalDistance = 0;
|
||||
for (let [i, d] of distances.entries()) {
|
||||
let s = segments[i];
|
||||
let s = centeredSegments[i];
|
||||
s.startDistance = totalDistance
|
||||
totalDistance += d;
|
||||
s.last = i === (segments.length - 1);
|
||||
s.last = i === (centeredSegments.length - 1);
|
||||
s.distance = d;
|
||||
s.text = this._getSegmentLabel(d, totalDistance, s.last);
|
||||
}
|
||||
@@ -149,24 +164,27 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
|
||||
rulerColor = getColorForDistance.call(this, totalDistance)
|
||||
else
|
||||
rulerColor = this.color
|
||||
for (let s of segments) {
|
||||
const { ray, label, text, last } = s;
|
||||
for (const [s, cs] of zip(segments.reverse(), centeredSegments.reverse())) {
|
||||
const { label, text, last } = cs;
|
||||
|
||||
// Draw line segment
|
||||
r.lineStyle(6, 0x000000, 0.5).moveTo(ray.A.x, ray.A.y).lineTo(ray.B.x, ray.B.y)
|
||||
.lineStyle(4, rulerColor, 0.25).moveTo(ray.A.x, ray.A.y).lineTo(ray.B.x, ray.B.y);
|
||||
r.lineStyle(6, 0x000000, 0.5).moveTo(s.ray.A.x, s.ray.A.y).lineTo(s.ray.B.x, s.ray.B.y)
|
||||
.lineStyle(4, rulerColor, 0.25).moveTo(s.ray.A.x, s.ray.A.y).lineTo(s.ray.B.x, s.ray.B.y);
|
||||
|
||||
// Draw the distance label just after the endpoint of the segment
|
||||
if (label) {
|
||||
label.text = text;
|
||||
label.alpha = last ? 1.0 : 0.5;
|
||||
label.visible = true;
|
||||
let labelPosition = ray.project((ray.distance + 50) / ray.distance);
|
||||
let labelPosition = cs.ray.project((cs.ray.distance + 50) / cs.ray.distance);
|
||||
label.position.set(labelPosition.x, labelPosition.y);
|
||||
}
|
||||
|
||||
// Highlight grid positions
|
||||
this._highlightMeasurement(ray, s.startDistance);
|
||||
if (terrainRulerAvailable)
|
||||
highlightMeasurementTerrainRuler.call(this, cs.ray, cs.startDistance, shape)
|
||||
else
|
||||
highlightMeasurementNative.call(this, cs.ray, cs.startDistance, shape);
|
||||
}
|
||||
|
||||
// Draw endpoints
|
||||
@@ -177,3 +195,46 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
|
||||
// Return the measured segments
|
||||
return segments;
|
||||
}
|
||||
|
||||
export function highlightMeasurementNative(ray, startDistance, tokenShape=[{x: 0, y: 0}]) {
|
||||
const spacer = canvas.scene.data.gridType === CONST.GRID_TYPES.SQUARE ? 1.41 : 1;
|
||||
const nMax = Math.max(Math.floor(ray.distance / (spacer * Math.min(canvas.grid.w, canvas.grid.h))), 1);
|
||||
const tMax = Array.fromRange(nMax+1).map(t => t / nMax);
|
||||
|
||||
// Track prior position
|
||||
let prior = null;
|
||||
|
||||
// Iterate over ray portions
|
||||
for ( let [i, t] of tMax.reverse().entries() ) {
|
||||
let {x, y} = ray.project(t);
|
||||
|
||||
// Get grid position
|
||||
let [x0, y0] = (i === 0) ? [null, null] : prior;
|
||||
let [x1, y1] = canvas.grid.grid.getGridPositionFromPixels(x, y);
|
||||
if ( x0 === x1 && y0 === y1 ) continue;
|
||||
|
||||
// Highlight the grid position
|
||||
let [xg, yg] = canvas.grid.grid.getPixelsFromGridPosition(x1, y1);
|
||||
const subDistance = canvas.grid.measureDistances([{ray: new Ray(ray.A, {x: xg, y: yg})}], {gridSpaces: true})[0]
|
||||
const color = dragRuler.getColorForDistance.call(this, startDistance, subDistance)
|
||||
const snapPoint = getSnapPointForToken(...canvas.grid.getTopLeft(x, y), this.draggedToken);
|
||||
const [snapX, snapY] = getGridPositionFromPixels(snapPoint.x + 1, snapPoint.y + 1);
|
||||
|
||||
prior = [x1, y1];
|
||||
|
||||
// If the positions are not neighbors, also highlight their halfway point
|
||||
if (i > 0 && !canvas.grid.isNeighbor(x0, y0, x1, y1)) {
|
||||
let th = tMax[i - 1] - (0.5 / nMax);
|
||||
let {x, y} = ray.project(th);
|
||||
let [x1h, y1h] = canvas.grid.grid.getGridPositionFromPixels(x, y);
|
||||
let [xgh, ygh] = canvas.grid.grid.getPixelsFromGridPosition(x1h, y1h);
|
||||
const subDistance = canvas.grid.measureDistances([{ray: new Ray(ray.A, {x: xgh, y: ygh})}], {gridSpaces: true})[0]
|
||||
const color = dragRuler.getColorForDistance.call(this, startDistance, subDistance)
|
||||
const snapPoint = getSnapPointForToken(...canvas.grid.getTopLeft(x, y), this.draggedToken);
|
||||
const [snapX, snapY] = getGridPositionFromPixels(snapPoint.x + 1, snapPoint.y + 1);
|
||||
highlightTokenShape.call(this, {x: snapX, y: snapY}, tokenShape, color);
|
||||
}
|
||||
|
||||
highlightTokenShape.call(this, {x: snapX, y: snapY}, tokenShape, color);
|
||||
}
|
||||
}
|
||||
|
||||
+15
-33
@@ -6,6 +6,7 @@ import {measure, moveTokens, onMouseMove} from "./foundry_imports.js"
|
||||
import {performMigrations} from "./migration.js"
|
||||
import {registerSettings, settingsKey} from "./settings.js"
|
||||
import {SpeedProvider} from "./speed_provider.js"
|
||||
import { getSnapPointForToken } from "./util.js"
|
||||
|
||||
Hooks.once("init", () => {
|
||||
registerSettings()
|
||||
@@ -13,7 +14,6 @@ Hooks.once("init", () => {
|
||||
hookTokenDragHandlers()
|
||||
hookRulerFunctions()
|
||||
hookKeyboardManagerFunctions()
|
||||
patchRulerHighlightMeasurement()
|
||||
|
||||
window.dragRuler = {
|
||||
getColorForDistance,
|
||||
@@ -85,6 +85,9 @@ function hookRulerFunctions() {
|
||||
|
||||
const originalUpdate = Ruler.prototype.update
|
||||
Ruler.prototype.update = function (data) {
|
||||
// Don't show a GMs drag ruler to non GM players
|
||||
if (data.draggedToken && this.user.isGM && !game.user.isGM && !game.settings.get(settingsKey, "showGMRulerToPlayers"))
|
||||
return
|
||||
if (data.draggedToken) {
|
||||
this.draggedToken = canvas.tokens.get(data.draggedToken)
|
||||
}
|
||||
@@ -100,6 +103,12 @@ function hookRulerFunctions() {
|
||||
return originalMeasure.call(this, destination, options)
|
||||
}
|
||||
}
|
||||
|
||||
const originalEndMeasurement = Ruler.prototype._endMeasurement
|
||||
Ruler.prototype._endMeasurement = function () {
|
||||
originalEndMeasurement.call(this)
|
||||
this.draggedToken = null
|
||||
}
|
||||
}
|
||||
|
||||
function hookKeyboardManagerFunctions() {
|
||||
@@ -150,7 +159,7 @@ function onTokenLeftDragStart(event) {
|
||||
if (canvas.grid.isHex && game.modules.get("hex-size-support")?.active && CONFIG.hexSizeSupport.getAltSnappingFlag(this))
|
||||
tokenCenter = getHexSizeSupportTokenGridCenter(this)
|
||||
else
|
||||
tokenCenter = {x: this.x + canvas.grid.grid.w / 2, y: this.y + canvas.grid.grid.h / 2}
|
||||
tokenCenter = this.center
|
||||
ruler.clear();
|
||||
ruler._state = Ruler.STATES.STARTING;
|
||||
ruler.rulerOffset = {x: tokenCenter.x - event.data.origin.x, y: tokenCenter.y - event.data.origin.y}
|
||||
@@ -168,15 +177,15 @@ function onTokenDragLeftDrop(event) {
|
||||
if (!ruler.isDragRuler)
|
||||
return false
|
||||
const selectedTokens = canvas.tokens.controlled
|
||||
ruler._state = Ruler.STATES.MOVING
|
||||
moveTokens.call(ruler, ruler.draggedToken, selectedTokens)
|
||||
ruler.draggedToken = null
|
||||
return true
|
||||
}
|
||||
|
||||
function onTokenDragLeftCancel(event) {
|
||||
// This function is invoked by right clicking
|
||||
const ruler = canvas.controls.ruler
|
||||
if (!ruler.isDragRuler)
|
||||
if (!ruler.isDragRuler || ruler._state === Ruler.STATES.MOVING)
|
||||
return false
|
||||
if (ruler._state === Ruler.STATES.MEASURING) {
|
||||
if (!game.settings.get(settingsKey, "swapSpacebarRightClick")) {
|
||||
@@ -208,10 +217,8 @@ function onRulerMoveToken(event) {
|
||||
|
||||
function addWaypoint(point, snap=true) {
|
||||
if (snap)
|
||||
point = canvas.grid.getCenter(point.x, point.y);
|
||||
else
|
||||
point = [point.x, point.y]
|
||||
this.waypoints.push(new PIXI.Point(point[0], point[1]));
|
||||
point = getSnapPointForToken(point.x, point.y, this.draggedToken)
|
||||
this.waypoints.push(new PIXI.Point(point.x, point.y));
|
||||
this.labels.addChild(new PreciseText("", CONFIG.canvasTextStyle));
|
||||
}
|
||||
|
||||
@@ -226,7 +233,6 @@ function deleteWaypoint() {
|
||||
else {
|
||||
const token = ruler.draggedToken
|
||||
ruler._endMeasurement()
|
||||
ruler.draggedToken = null
|
||||
|
||||
// Deactivate the drag workflow in mouse
|
||||
token.mouseInteractionManager._deactivateDragEvents();
|
||||
@@ -238,11 +244,6 @@ function deleteWaypoint() {
|
||||
}
|
||||
}
|
||||
|
||||
function strInsertAfter(haystack, needle, strToInsert) {
|
||||
const pos = haystack.indexOf(needle) + needle.length
|
||||
return haystack.slice(0, pos) + strToInsert + haystack.slice(pos)
|
||||
}
|
||||
|
||||
export function getColorForDistance(startDistance, subDistance=0) {
|
||||
if (!this.isDragRuler)
|
||||
return this.color
|
||||
@@ -263,22 +264,3 @@ export function getColorForDistance(startDistance, subDistance=0) {
|
||||
}, {range: Infinity, color: getUnreachableColorFromSpeedProvider()})
|
||||
return currentRange.color
|
||||
}
|
||||
|
||||
// These patches were written with foundry-0.7.9.js as reference
|
||||
function patchRulerHighlightMeasurement() {
|
||||
let code = Ruler.prototype._highlightMeasurement.toString()
|
||||
// Replace CRLF with LF in case foundry.js has CRLF for some reason
|
||||
code = code.replace(/\r\n/g, "\n")
|
||||
// Remove function signature and closing curly bracket (those are on the first and last line)
|
||||
code = code.slice(code.indexOf("\n"), code.lastIndexOf("\n"))
|
||||
|
||||
const calcColorCode = `
|
||||
let subDistance = canvas.grid.measureDistances([{ray: new Ray(ray.A, {x: xg, y: yg})}], {gridSpaces: true})[0]
|
||||
let color = dragRuler.getColorForDistance.call(this, startDistance, subDistance)
|
||||
`
|
||||
|
||||
code = strInsertAfter(code, "Position(x1, y1);\n", calcColorCode)
|
||||
code = strInsertAfter(code, "Position(x1h, y1h);\n", calcColorCode.replace("x: xg, y: yg", "x: xgh, y: ygh"))
|
||||
code = code.replace(/color: this\.color\}/g, "color}")
|
||||
Ruler.prototype._highlightMeasurement = new Function("ray", "startDistance=undefined", code)
|
||||
}
|
||||
|
||||
+11
-1
@@ -29,6 +29,15 @@ export function registerSettings() {
|
||||
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,
|
||||
})
|
||||
|
||||
// This setting will be modified by the api if modules register to it
|
||||
game.settings.register(settingsKey, "speedProvider", {
|
||||
scope: "world",
|
||||
@@ -111,6 +120,7 @@ class SpeedProviderSettings extends FormApplication {
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -121,7 +131,7 @@ class SpeedProviderSettings extends FormApplication {
|
||||
}
|
||||
|
||||
// Don't change settings for speed providers that aren't currently active
|
||||
if (key !== "speedProvider" && !key.startsWith(formData.speedProvider))
|
||||
if (key !== "speedProvider" && !key.startsWith(selectedSpeedProvider))
|
||||
continue
|
||||
|
||||
// Get the key for the current setting
|
||||
|
||||
+17
-1
@@ -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
|
||||
@@ -115,7 +131,7 @@ export class GenericSpeedProvider extends SpeedProvider {
|
||||
}
|
||||
const dashMultiplier = this.getSetting("dashMultiplier")
|
||||
if (!dashMultiplier)
|
||||
return [{range: tokenSpeed, color: playercolor}]
|
||||
return [{range: tokenSpeed, color: "walk"}]
|
||||
return [{range: tokenSpeed, color: "walk"}, {range: tokenSpeed * dashMultiplier, color: "dash"}]
|
||||
}
|
||||
|
||||
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
import {getPixelsFromGridPosition} from "./foundry_fixes.js"
|
||||
|
||||
export function* zip(it1, it2) {
|
||||
for (let i = 0;i < Math.min(it1.length, it2.length);i++) {
|
||||
yield [it1[i], it2[i]]
|
||||
}
|
||||
}
|
||||
|
||||
export function getSnapPointForToken(x, y, token) {
|
||||
if (canvas.grid.isHex && game.modules.get("hex-size-support")?.active && CONFIG.hexSizeSupport.getAltSnappingFlag(token)) {
|
||||
if (token.getFlag("hex-size-support", "borderSize") % 2 === 0) {
|
||||
const snapPoint = CONFIG.hexSizeSupport.findVertexSnapPoint(x, y, token, canvas.grid.grid)
|
||||
return new PIXI.Point(snapPoint.x, snapPoint.y)
|
||||
}
|
||||
else {
|
||||
return new PIXI.Point(...canvas.grid.getCenter(x, y))
|
||||
}
|
||||
}
|
||||
if (canvas.grid.isHex || token.data.width % 2 === 1) {
|
||||
return new PIXI.Point(...canvas.grid.getCenter(x, y))
|
||||
}
|
||||
const [snappedX, snappedY] = canvas.grid.getCenter(x - canvas.grid.w / 2, y - canvas.grid.h / 2)
|
||||
return new PIXI.Point(snappedX + canvas.grid.w / 2, snappedY + canvas.grid.h / 2)
|
||||
}
|
||||
|
||||
export function highlightTokenShape(position, shape, color) {
|
||||
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)
|
||||
shiftedRow = 1
|
||||
else
|
||||
shiftedRow = 0
|
||||
if (canvas.grid.grid.options.columns) {
|
||||
if (space.x % 2 !== 0 && position.x % 2 !== shiftedRow) {
|
||||
y += 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (space.y % 2 !== 0 && position.y % 2 !== shiftedRow) {
|
||||
x += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return {x, y}
|
||||
});
|
||||
}
|
||||
|
||||
export function getTokenShape(token) {
|
||||
if (token.scene.data.gridType === CONST.GRID_TYPES.GRIDLESS)
|
||||
throw new Error("getTokenShape cannot be called for tokens on gridless maps")
|
||||
if (token.scene.data.gridType === CONST.GRID_TYPES.SQUARE) {
|
||||
const topOffset = -Math.floor(token.data.height / 2)
|
||||
const leftOffset = -Math.floor(token.data.width / 2)
|
||||
const shape = []
|
||||
for (let y = 0;y < token.data.height;y++) {
|
||||
for (let x = 0;x < token.data.width;x++) {
|
||||
shape.push({x: x + leftOffset, y: y + topOffset})
|
||||
}
|
||||
}
|
||||
return shape
|
||||
}
|
||||
else {
|
||||
// Hex grids
|
||||
if (game.modules.get("hex-size-support")?.active && CONFIG.hexSizeSupport.getAltSnappingFlag(token)) {
|
||||
const borderSize = token.data.flags["hex-size-support"].borderSize;
|
||||
let shape = [{x: 0, y: 0}];
|
||||
if (borderSize >= 2)
|
||||
shape = shape.concat([{x: 0, y: -1}, {x: -1, y: -1}]);
|
||||
if (borderSize >= 3)
|
||||
shape = shape.concat([{x: 0, y: 1}, {x: -1, y: 1}, {x: -1, y: 0}, {x: 1, y: 0}]);
|
||||
if (borderSize >= 4)
|
||||
shape = shape.concat([{x: -2, y: -1}, {x: 1, y: -1}, {x: -1, y: -2}, {x: 0, y: -2}, {x: 1, y: -2}])
|
||||
|
||||
if (Boolean(CONFIG.hexSizeSupport.getAltOrientationFlag(token)) !== canvas.grid.grid.options.columns)
|
||||
shape.forEach(space => space.y *= -1);
|
||||
if (canvas.grid.grid.options.columns)
|
||||
shape = shape.map(space => {return {x: space.y, y: space.x}});
|
||||
return shape;
|
||||
}
|
||||
else {
|
||||
return [{x: 0, y: 0}];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getTokenSize(token) {
|
||||
let w, h;
|
||||
const hexSizeSupportBorderSize = token.data.flags["hex-size-support"]?.borderSize;
|
||||
if (hexSizeSupportBorderSize > 0) {
|
||||
w = h = hexSizeSupportBorderSize
|
||||
}
|
||||
else {
|
||||
w = token.data.width
|
||||
h = token.data.height
|
||||
}
|
||||
return {w, h};
|
||||
}
|
||||
|
||||
// Tokens that have a size divisible by two (2x2, 4x4, 2x1) have their ruler at the edge of a cell.
|
||||
// This function applies an offset to to the waypoints that will move the ruler from the edge to the center of the cell
|
||||
export function applyTokenSizeOffset(waypoints, token) {
|
||||
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
|
||||
return waypoints
|
||||
}
|
||||
|
||||
const tokenSize = getTokenSize(token);
|
||||
const waypointOffset = {x: 0, y: 0};
|
||||
if (canvas.grid.isHex) {
|
||||
const isAltOrientation = CONFIG.hexSizeSupport.getAltOrientationFlag(token);
|
||||
if (canvas.grid.grid.options.columns) {
|
||||
if (tokenSize.w % 2 === 0) {
|
||||
waypointOffset.x = canvas.grid.w / 2;
|
||||
if (!isAltOrientation)
|
||||
waypointOffset.x *= -1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (tokenSize.h % 2 === 0) {
|
||||
waypointOffset.y = canvas.grid.h / 2;
|
||||
if (isAltOrientation)
|
||||
waypointOffset.y *= -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (tokenSize.w % 2 === 0) {
|
||||
waypointOffset.x = canvas.grid.w / 2;
|
||||
}
|
||||
if (tokenSize.h % 2 === 0) {
|
||||
waypointOffset.y = canvas.grid.h / 2;
|
||||
}
|
||||
}
|
||||
|
||||
return waypoints.map(w => new PIXI.Point(w.x + waypointOffset.x, w.y + waypointOffset.y))
|
||||
}
|
||||
Reference in New Issue
Block a user