Compare commits

..

26 Commits

Author SHA1 Message Date
Manuel Vögele 0aee541eb3 Release v1.4.2 2021-03-18 07:45:18 +01:00
Manuel Vögele 40dde08ddd Allow getTokenShape on gridless maps (fixes #44) 2021-03-18 07:41:30 +01:00
Manuel Vögele 8234aa566d Release v1.4.1 2021-03-17 09:26:46 +01:00
Manuel Vögele 2862643ba8 Add missing check for whether hex-size-support module is present (fixes #43) 2021-03-17 09:25:30 +01:00
Manuel Vögele e3abdeab3a Mention chinese translation in changelog 2021-03-16 09:27:51 +01:00
zeteticl 2ff4e98fe9 Add chinese localization (#39) 2021-03-16 09:02:32 +01:00
Manuel Vögele e0d0483722 Release v1.4.0 2021-03-15 20:52:52 +01:00
Manuel Vögele 4f1dec3089 Support for difficult terrain with tokens larger than 1x1 2021-03-15 20:52:52 +01:00
Manuel Vögele c4d089e8ff Always draw the ruler from the token's center for larger tokens (even on grids). Highlight all spaces occupied by larger tokens. 2021-03-15 20:17:09 +01:00
Manuel Vögele f8fe4ee971 Allow bug reporter 2021-03-15 18:05:14 +01:00
Manuel Vögele 378401b5bb Merge branch 'master' into develop 2021-03-10 11:55:22 +01:00
Manuel Vögele 0a54a47951 Release v1.3.5 2021-03-10 11:51:41 +01:00
Manuel Vögele 0b71504da3 Fix regression introduced in 6e745820 that made colors flicker during measurement 2021-03-10 11:50:55 +01:00
Manuel Vögele 5a7f5531f2 When multiple colors overlap on one square draw the one representing the furthest range with the highest priority 2021-03-09 14:51:55 +01:00
Manuel Vögele 862477c698 Add a setting that allows hiding the GM's drag ruler from non gm players (resolves #23) 2021-03-09 12:54:28 +01:00
Manuel Vögele 384de1a1b5 Add support for difficult terrain via Terrain Ruler module (resolves #1) 2021-03-09 10:08:51 +01:00
Manuel Vögele 0a7d8495c6 Release v1.3.4 2021-03-09 09:21:09 +01:00
Manuel Vögele 6e74582089 Stop patching _highlightMeasurement and import it instead to increase compatibility with other modules 2021-03-09 09:09:50 +01:00
Manuel Vögele 20e8713e30 Release v1.3.3 2021-03-03 09:51:01 +01:00
Manuel Vögele 544740b697 Pull current speed provider from game settings for non GM users (fixes #34) 2021-03-03 09:49:08 +01:00
Manuel Vögele 404e978377 Release v1.3.2 2021-03-02 08:30:41 +01:00
Manuel Vögele 4a56cdb0a4 Merge branch 'master' into develop 2021-03-02 08:30:03 +01:00
Manuel Vögele 48e6901b59 Add 15e9104 to the changelog 2021-03-02 08:28:57 +01:00
Brother Sharp 15e910424f update japanese localization (#32) 2021-03-02 08:24:04 +01:00
Manuel Vögele baba6e2986 Fix typo in english translation 2021-02-26 12:29:56 +01:00
Manuel Vögele 6f0a04c20f Add Tormenta20 to list of game systems with integration 2021-02-23 16:47:10 +01:00
14 changed files with 481 additions and 84 deletions
+43
View File
@@ -1,3 +1,46 @@
## 1.4.2
### Bugfixes
- Drag Ruler now works again on gridless maps
## 1.4.1
### Bugfixes
- Fixed a bug where Drag Ruler wouldn't work at all on hex grids when the Hex Token Size Support isn't installed
### Translation
- Added chinese translation (thanks to zeteticl)
## 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 ## 1.3.1
### Bugfixes ### 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 - Fixed a bug where the coloring of ranges wouldn't work with the generic speed provider if the Dash Multiplier was set to 0
+9
View File
@@ -15,6 +15,13 @@ You can add waypoints to the path by pressing spacebar while you drag the token.
![Demonstration of Waypoints](https://raw.githubusercontent.com/manuelVo/foundryvtt-drag-ruler/5177746fbb4edb28b6ba09137247d142af575c47/media/waypoints.webp) ![Demonstration of Waypoints](https://raw.githubusercontent.com/manuelVo/foundryvtt-drag-ruler/5177746fbb4edb28b6ba09137247d142af575c47/media/waypoints.webp)
## 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.
![Demonstration of Difficult Terrain support](https://raw.githubusercontent.com/manuelVo/foundryvtt-drag-ruler/bb8ac5d1acb9d6374d06f7e9c8e2c2dd61192d56/media/terrain_layer.webp)
## Why would I want to use this instead of ShowDragDistance? ## 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. 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,10 +36,12 @@ The game systems that offer Drag Ruler integration are:
- Cypher System (starting with version 1.13.0) - Cypher System (starting with version 1.13.0)
- Pathfinder 1 (starting with version 0.77.3) - Pathfinder 1 (starting with version 0.77.3)
- Pathfinder 2e (via the module [PF2E Drag Ruler Integration](https://foundryvtt.com/packages/pf2e-dragruler/)) - Pathfinder 2e (via the module [PF2E Drag Ruler Integration](https://foundryvtt.com/packages/pf2e-dragruler/))
- Tormenta20 (starting with version 1.1.37)
## Translations ## Translations
Drag Ruler is available in the follwing languages: Drag Ruler is available in the follwing languages:
- Chinese (thanks to zeteticl)
- English - English
- Japanese (thanks to touge) - Japanese (thanks to touge)
+5 -1
View File
@@ -21,6 +21,10 @@
"name": "Show PC speed to everyone", "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." "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": { "speedProviderSettings": {
"name": "Speed Provider Settings", "name": "Speed Provider Settings",
"hint": "The Speed Provider Settings contain all the game system specific 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.", "noSettings": "This speed provider doesn't offer any configuration options.",
"color": { "color": {
"name": "Color for {colorName}", "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": { "unreachable": {
"name": "unreachable", "name": "unreachable",
"hint": "The color for spaces that aren't reachable by the dragged token" "hint": "The color for spaces that aren't reachable by the dragged token"
+46 -19
View File
@@ -1,31 +1,58 @@
{ {
"drag-ruler": { "drag-ruler": {
"genericSpeedProvider": {
"settings": {
"dashMultiplier": {
"name": "早足の倍率",
"hint": "設定すると、移動経路の測定中に第二の色分けが追加されます。0にすると第二速度が無効になります。"
},
"speedAttribute": {
"name": "移動速度の属性値",
"hint": "コマの移動速度が定義された属性値を指定します。移動範囲表示の色分けに使用されます。"
}
},
"speeds": {
"walk": "歩き",
"dash": "早足"
}
},
"settings": { "settings": {
"alwaysShowSpeedForPCs": { "alwaysShowSpeedForPCs": {
"name": "プレイヤーキャラクターの移動速度を全員に公開する", "name": "プレイヤーキャラクターの移動速度を全員に公開する",
"hint": "有効にすると、アクターの観察者(Observer)権限を持たない人にも,移動範囲の色分けが表示されるようになります。" "hint": "有効にすると、アクターの観察者(Observer)権限を持たない人にも,移動範囲の色分けが表示されるようになります。"
}, },
"dashMultiplier": { "speedProviderSettings": {
"name": "早足の倍率", "name": "移動速度の提供元",
"hint": "有効にすると、移動範囲に二つ目の色分けが追加されます。0にすると、この機能は無効になります。" "hint": "ゲームシステムに固有のすべての設定をここで行います。",
}, "button": "移動速度の提供元設定",
"speedAttribute": { "windowTitle": "移動速度の提供元設定",
"name": "移動速度の属性値", "headers": {
"hint": "コマの移動速度が定義された属性値を指定します。移動範囲表示の色分けに使用されます。" "speedProvider": "移動速度の提供元",
}, "speedProviderSettings": "提供元に固有の設定"
"speedProvider": { },
"name": "移動速度設定の提供元", "activeProvider": {
"hint": "色分けに使用する速度情報の提供元を選択します。ゲームシステムやモジュールを選択すると、Drag Ruler自体が提供するものより柔軟な色分けが行える可能性があります。", "name": "現在有効な提供元",
"choices": { "hint": "以下の設定は、現在有効な移動速度の提供元によって変化します。GMが別の提供元を選択すると、利用可能な項目が変更となる場合があります。"
"module": "モジュール", },
"native": "Drag Ruler", "noSettings": "この移動速度提供元には、設定オプションがありません。",
"system": "ゲームシステム" "color": {
"name": "{colorName}の色",
"hint": "{colorName}の範囲に使用するセルの色を指定します。",
"unreachable": {
"name": "到達不能域",
"hint": "ドラッグしたコマが到達できない範囲の色を指定します。"
}
},
"speedProvider": {
"name": "移動速度の提供元",
"hint": "色分けに使用する速度情報の提供元を選択します。Drag Rulerは基本機能を備えた汎用の提供元を備えており、正しく設定すればすべてのゲームシステムで動作するはずです。しかしゲームシステムやモジュールを介することで、より多くの提供元を利用することもできます。こうした提供元を利用すると、ゲームルールとの統合がさらに向上するかもしれません。以下のオプションは、ここで選択した提供元によって変化します。",
"choices": {
"module": "モジュール {name}",
"native": "汎用",
"system": "システム {name}"
}
} }
}, },
"staticFirstColor": {
"name": "一つ目の色を固定する",
"hint": "一つ目の移動範囲の色に、プレイヤーの色でなく固定された色を使用します。"
},
"swapSpacebarRightClick": { "swapSpacebarRightClick": {
"name": "スペースキーと右クリックを入れ替える", "name": "スペースキーと右クリックを入れ替える",
"hint": "ドラッグ中のスペースキーと右クリックの機能を入れ替えます。有効にすると右クリックがウェイポイントの配置、スペースキーが削除になります。" "hint": "ドラッグ中のスペースキーと右クリックの機能を入れ替えます。有効にすると右クリックがウェイポイントの配置、スペースキーが削除になります。"
+66
View File
@@ -0,0 +1,66 @@
{
"drag-ruler": {
"genericSpeedProvider": {
"settings": {
"dashMultiplier": {
"name": "快步 倍數",
"hint": "這可用於在對測量路徑進行著色的過程中為棋子賦予第二速度。將其設定為0可禁用第二速度。"
},
"speedAttribute": {
"name": "速度屬性",
"hint": "定義棋子的行走速度的屬性。在對測量路徑進行著色時使用。"
}
},
"speeds": {
"walk": "走路",
"dash": "快步"
}
},
"settings": {
"alwaysShowSpeedForPCs": {
"name": "向所有人顯示PC速度",
"hint": "如果啟用, 玩家的角色(actor)的速度將顯示給所有人,即使他們沒有角色表的觀察者(Observer)權限也是如此。"
},
"showGMRulerToPlayers": {
"name": "向玩家顯示GM標尺",
"hint": "如果禁用,則不為非GM玩家顯示GM的標尺"
},
"speedProviderSettings": {
"name": "速度提供者設定",
"hint": "速度提供者設定 包括 所有遊戲系統特定的設定。",
"button": "速度提供者設定",
"windowTitle": "速度提供者設定",
"headers": {
"speedProvider": "速度提供者設定",
"speedProviderSettings": "特定於速度提供者的設定"
},
"activeProvider": {
"name": "當前活動的速度提供者",
"hint": "下面顯示的設定 取決於活動的 速度提供者。如果GM選擇其他速度提供者,則可用的設定可能會更改。"
},
"noSettings": "該速度提供者不提供任何配置選項。",
"color": {
"name": "{colorName}的顏色",
"hint": "將用於為{colorName}範圍內的正方形上色的顏色",
"unreachable": {
"name": "無法到達",
"hint": "指定拖動棋子無法到達的範圍的顏色"
}
},
"speedProvider": {
"name": "速度設定的提供者",
"hint": "選擇在著色期間為棋子提供速度信息的人。DragRuler提供了一個通用速度提供程序, 該提供程序提供了基本功能, 並且如果配置正確,則可以在所有遊戲系統上使用。可以通過遊戲系統和已安裝的Mod來提供更多速度提供程序。選擇與 標準速度提供者 不同的速度提供者可以更好地集成到您所使用的遊戲系統規則中。以下選項取決於此處選擇的速度提供者。",
"choices": {
"module": "Mod {name}",
"native": "一般",
"system": "系統 {name}"
}
}
},
"swapSpacebarRightClick": {
"name": "交換空格鍵並鼠標右鍵",
"hint": "交換空格鍵和右鍵點擊在拖動過程中的功能。如果啟用,右鍵單擊將放置航點,空格鍵將刪除它們。"
}
}
}
}
+9 -3
View File
@@ -2,7 +2,7 @@
"name": "drag-ruler", "name": "drag-ruler",
"title": "Drag Ruler", "title": "Drag Ruler",
"description": "When dragging a token displays a ruler showing how far you've moved that token.", "description": "When dragging a token displays a ruler showing how far you've moved that token.",
"version": "1.3.1", "version": "1.4.2",
"minimumCoreVersion" : "0.7.9", "minimumCoreVersion" : "0.7.9",
"compatibleCoreVersion" : "0.7.9", "compatibleCoreVersion" : "0.7.9",
"authors": [ "authors": [
@@ -28,12 +28,18 @@
"lang": "ja", "lang": "ja",
"name": "日本語", "name": "日本語",
"path": "lang/ja.json" "path": "lang/ja.json"
},
{
"lang": "zh-tw",
"name": "正體中文",
"path": "lang/zh-tw.json"
} }
], ],
"url": "https://github.com/manuelVo/foundryvtt-drag-ruler", "url": "https://github.com/manuelVo/foundryvtt-drag-ruler",
"download": "https://github.com/manuelVo/foundryvtt-drag-ruler/archive/v1.3.1.zip", "download": "https://github.com/manuelVo/foundryvtt-drag-ruler/archive/v1.4.2.zip",
"manifest": "https://raw.githubusercontent.com/manuelVo/foundryvtt-drag-ruler/master/module.json", "manifest": "https://raw.githubusercontent.com/manuelVo/foundryvtt-drag-ruler/master/module.json",
"readme": "https://github.com/manuelVo/foundryvtt-drag-ruler/blob/master/README.md", "readme": "https://github.com/manuelVo/foundryvtt-drag-ruler/blob/master/README.md",
"changelog": "https://github.com/manuelVo/foundryvtt-drag-ruler/blob/master/CHANGELOG.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
} }
+7
View File
@@ -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) { export function registerModule(moduleId, speedProvider) {
// Check if a module with the given id exists and is currently enabled // Check if a module with the given id exists and is currently enabled
const module = game.modules.get(moduleId) const module = game.modules.get(moduleId)
+11 -16
View File
@@ -1,19 +1,14 @@
import {getColorForDistance} from "./main.js"
import {highlightTokenShape} from "./util.js"
export function getHexSizeSupportTokenGridCenter(token) { export function getHexSizeSupportTokenGridCenter(token) {
const tokenCenterOffset = CONFIG.hexSizeSupport.getCenterOffset(token) const tokenCenterOffset = CONFIG.hexSizeSupport.getCenterOffset(token)
const tokenCenter = {x: token.x + tokenCenterOffset.x, y: token.y + tokenCenterOffset.y} return {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) { export function highlightMeasurementTerrainRuler(ray, startDistance, tokenShape=[{x: 0, y: 0}]) {
let hexOffset = canvas.grid.w / 2 for (const space of ray.terrainRulerVisitedSpaces.reverse()) {
if (!CONFIG.hexSizeSupport.getAltOrientationFlag(token)) const color = getColorForDistance.call(this, startDistance, space.distance)
hexOffset *= -1 highlightTokenShape.call(this, space, tokenShape, color)
tokenCenter.x += hexOffset }
}
else {
let hexOffset = canvas.grid.h / 2
if (CONFIG.hexSizeSupport.getAltOrientationFlag(token))
hexOffset *= -1
tokenCenter.y += hexOffset
}
return tokenCenter
} }
+21
View File
@@ -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
View File
@@ -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 {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 // This is a modified version of Ruler.moveToken from foundry 0.7.9
export async function moveTokens(draggedToken, selectedTokens) { export async function moveTokens(draggedToken, selectedTokens) {
@@ -102,9 +106,12 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
return [] return []
if (snap) 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 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; const r = this.ruler;
this.destination = destination; this.destination = destination;
@@ -126,14 +133,22 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
centeredSegments.push({ray: centeredRay, label}) centeredSegments.push({ray: centeredRay, label})
} }
const shape = getTokenShape(this.draggedToken)
// Compute measured distance // 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; let totalDistance = 0;
for (let [i, d] of distances.entries()) { for (let [i, d] of distances.entries()) {
let s = segments[i]; let s = centeredSegments[i];
s.startDistance = totalDistance s.startDistance = totalDistance
totalDistance += d; totalDistance += d;
s.last = i === (segments.length - 1); s.last = i === (centeredSegments.length - 1);
s.distance = d; s.distance = d;
s.text = this._getSegmentLabel(d, totalDistance, s.last); 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) rulerColor = getColorForDistance.call(this, totalDistance)
else else
rulerColor = this.color rulerColor = this.color
for (let s of segments) { for (const [s, cs] of zip(segments.reverse(), centeredSegments.reverse())) {
const { ray, label, text, last } = s; const { label, text, last } = cs;
// Draw line segment // Draw line segment
r.lineStyle(6, 0x000000, 0.5).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(ray.A.x, ray.A.y).lineTo(ray.B.x, 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 // Draw the distance label just after the endpoint of the segment
if (label) { if (label) {
label.text = text; label.text = text;
label.alpha = last ? 1.0 : 0.5; label.alpha = last ? 1.0 : 0.5;
label.visible = true; 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); label.position.set(labelPosition.x, labelPosition.y);
} }
// Highlight grid positions // 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 // Draw endpoints
@@ -177,3 +195,46 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
// Return the measured segments // Return the measured segments
return 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
View File
@@ -6,6 +6,7 @@ import {measure, moveTokens, onMouseMove} from "./foundry_imports.js"
import {performMigrations} from "./migration.js" import {performMigrations} from "./migration.js"
import {registerSettings, settingsKey} from "./settings.js" import {registerSettings, settingsKey} from "./settings.js"
import {SpeedProvider} from "./speed_provider.js" import {SpeedProvider} from "./speed_provider.js"
import { getSnapPointForToken } from "./util.js"
Hooks.once("init", () => { Hooks.once("init", () => {
registerSettings() registerSettings()
@@ -13,7 +14,6 @@ Hooks.once("init", () => {
hookTokenDragHandlers() hookTokenDragHandlers()
hookRulerFunctions() hookRulerFunctions()
hookKeyboardManagerFunctions() hookKeyboardManagerFunctions()
patchRulerHighlightMeasurement()
window.dragRuler = { window.dragRuler = {
getColorForDistance, getColorForDistance,
@@ -85,6 +85,9 @@ function hookRulerFunctions() {
const originalUpdate = Ruler.prototype.update const originalUpdate = Ruler.prototype.update
Ruler.prototype.update = function (data) { 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) { if (data.draggedToken) {
this.draggedToken = canvas.tokens.get(data.draggedToken) this.draggedToken = canvas.tokens.get(data.draggedToken)
} }
@@ -100,6 +103,12 @@ function hookRulerFunctions() {
return originalMeasure.call(this, destination, options) return originalMeasure.call(this, destination, options)
} }
} }
const originalEndMeasurement = Ruler.prototype._endMeasurement
Ruler.prototype._endMeasurement = function () {
originalEndMeasurement.call(this)
this.draggedToken = null
}
} }
function hookKeyboardManagerFunctions() { function hookKeyboardManagerFunctions() {
@@ -150,7 +159,7 @@ function onTokenLeftDragStart(event) {
if (canvas.grid.isHex && game.modules.get("hex-size-support")?.active && CONFIG.hexSizeSupport.getAltSnappingFlag(this)) if (canvas.grid.isHex && game.modules.get("hex-size-support")?.active && CONFIG.hexSizeSupport.getAltSnappingFlag(this))
tokenCenter = getHexSizeSupportTokenGridCenter(this) tokenCenter = getHexSizeSupportTokenGridCenter(this)
else else
tokenCenter = {x: this.x + canvas.grid.grid.w / 2, y: this.y + canvas.grid.grid.h / 2} tokenCenter = this.center
ruler.clear(); ruler.clear();
ruler._state = Ruler.STATES.STARTING; ruler._state = Ruler.STATES.STARTING;
ruler.rulerOffset = {x: tokenCenter.x - event.data.origin.x, y: tokenCenter.y - event.data.origin.y} 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) if (!ruler.isDragRuler)
return false return false
const selectedTokens = canvas.tokens.controlled const selectedTokens = canvas.tokens.controlled
ruler._state = Ruler.STATES.MOVING
moveTokens.call(ruler, ruler.draggedToken, selectedTokens) moveTokens.call(ruler, ruler.draggedToken, selectedTokens)
ruler.draggedToken = null
return true return true
} }
function onTokenDragLeftCancel(event) { function onTokenDragLeftCancel(event) {
// This function is invoked by right clicking // This function is invoked by right clicking
const ruler = canvas.controls.ruler const ruler = canvas.controls.ruler
if (!ruler.isDragRuler) if (!ruler.isDragRuler || ruler._state === Ruler.STATES.MOVING)
return false return false
if (ruler._state === Ruler.STATES.MEASURING) { if (ruler._state === Ruler.STATES.MEASURING) {
if (!game.settings.get(settingsKey, "swapSpacebarRightClick")) { if (!game.settings.get(settingsKey, "swapSpacebarRightClick")) {
@@ -208,10 +217,8 @@ function onRulerMoveToken(event) {
function addWaypoint(point, snap=true) { function addWaypoint(point, snap=true) {
if (snap) if (snap)
point = canvas.grid.getCenter(point.x, point.y); point = getSnapPointForToken(point.x, point.y, this.draggedToken)
else this.waypoints.push(new PIXI.Point(point.x, point.y));
point = [point.x, point.y]
this.waypoints.push(new PIXI.Point(point[0], point[1]));
this.labels.addChild(new PreciseText("", CONFIG.canvasTextStyle)); this.labels.addChild(new PreciseText("", CONFIG.canvasTextStyle));
} }
@@ -226,7 +233,6 @@ function deleteWaypoint() {
else { else {
const token = ruler.draggedToken const token = ruler.draggedToken
ruler._endMeasurement() ruler._endMeasurement()
ruler.draggedToken = null
// Deactivate the drag workflow in mouse // Deactivate the drag workflow in mouse
token.mouseInteractionManager._deactivateDragEvents(); 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) { export function getColorForDistance(startDistance, subDistance=0) {
if (!this.isDragRuler) if (!this.isDragRuler)
return this.color return this.color
@@ -263,22 +264,3 @@ export function getColorForDistance(startDistance, subDistance=0) {
}, {range: Infinity, color: getUnreachableColorFromSpeedProvider()}) }, {range: Infinity, color: getUnreachableColorFromSpeedProvider()})
return currentRange.color 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
View File
@@ -29,6 +29,15 @@ export function registerSettings() {
default: true, 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 // This setting will be modified by the api if modules register to it
game.settings.register(settingsKey, "speedProvider", { game.settings.register(settingsKey, "speedProvider", {
scope: "world", scope: "world",
@@ -111,6 +120,7 @@ class SpeedProviderSettings extends FormApplication {
} }
async _updateObject(event, formData) { async _updateObject(event, formData) {
const selectedSpeedProvider = game.user.isGM ? formData.speedProvider : game.settings.get(settingsKey, "speedProvider")
for (let [key, value] of Object.entries(formData)) { for (let [key, value] of Object.entries(formData)) {
// Check if this is color, convert the value to an integer // Check if this is color, convert the value to an integer
const splitKey = key.split(".", 3) 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 // 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 continue
// Get the key for the current setting // Get the key for the current setting
+16
View File
@@ -56,6 +56,22 @@ export class SpeedProvider {
return 0xFF0000 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. * 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 * If this is returns `false` for a token Drag Ruler will be disabled for that token. Dragging a token for which this function
+150
View File
@@ -0,0 +1,150 @@
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) {
return [{x: 0, y: 0}]
}
else 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) {
if (game.modules.get("hex-size-support")?.active) {
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;
}
}
}
// If hex size support isn't active leave the waypoints like they are
}
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))
}