Compare commits

..

38 Commits

Author SHA1 Message Date
Manuel Vögele 4b68b82590 Release v1.5.1 2021-04-15 17:19:32 +02:00
Manuel Vögele 447995977b Don't show the difficult terrain measurement hint when no terrain module is enabled 2021-04-15 17:17:38 +02:00
Manuel Vögele 9e5b344b0d Release v1.5.0 2021-04-14 22:51:05 +02:00
Manuel Vögele 43c5df42d4 If a terrain module is installed, suggest installing Terrain Ruler to the user 2021-04-14 22:49:18 +02:00
Manuel Vögele d0ee47d551 Fix typo that made regular rulers not work 2021-04-14 19:45:17 +02:00
Manuel Vögele 27ffb2d8cb Don't return any value in _socketUpdateCombatantDragRulerFlags to reduce the amount of network traffic 2021-04-14 17:32:40 +02:00
Manuel Vögele b734257515 Check if all required dependencies are present and show an error if this is not the case 2021-04-14 16:37:46 +02:00
Manuel Vögele ff5d46ebbe Add a setting to disable the movement history 2021-04-14 16:10:11 +02:00
Manuel Vögele 8f896d9b82 Fix a bug that caused Drag Ruler to measure euclidic distance when Terrain Ruler isn't active 2021-04-14 15:54:59 +02:00
Manuel Vögele f7683e8945 Update the movement tracking flags in the GM client, becauase the user isn't allowed to modify them 2021-04-14 15:54:59 +02:00
Manuel Vögele 9787a41fd1 If Terran Ruler is enabled, store the length of the traveled path so changes to difficult terrain aren't reflected in the tokens movement history 2021-04-12 18:45:57 +02:00
Manuel Vögele 5d93c61f0c Merge branch 'master' into develop 2021-04-11 14:31:44 +02:00
Manuel Vögele 00e8a9781e Release v1.4.6 2021-04-11 14:27:09 +02:00
Manuel Vögele d2f2307e4d Update the ruler before dropping the token to get a more precise target location if the token has been moved very quickly (fixes #51) 2021-04-11 14:24:23 +02:00
Manuel Vögele 65912df989 Add support for the Enhanced Terrain Layer module (resolves #50) 2021-04-10 15:02:35 +02:00
Manuel Vögele 9ae9dae427 Add c66ec46 to changelog 2021-04-10 15:00:48 +02:00
Manuel Vögele 8cd5d7f276 Migrate Drag Ruler to Terrain Ruler's new API 2021-04-10 14:43:45 +02:00
Manuel Vögele 35624a37aa Add api endpoint to determine the distance that a token has moved already 2021-04-10 14:25:01 +02:00
Manuel Vögele c66ec46aed Track the path taken by tokens in combat and display it when reactivating DragRuler in the same turn 2021-04-08 09:35:04 +02:00
Manuel Vögele dec422fa3a Fix typo in changelog 2021-04-01 22:57:13 +02:00
Manuel Vögele 6ad1f4c522 Merge branch 'master' into develop 2021-04-01 22:36:06 +02:00
Manuel Vögele fb9a2ee60c Release v1.4.5 2021-04-01 22:35:23 +02:00
Manuel Vögele 57ed545e30 Snap tiny tokens (0.5x0.5 or smaller) to the corners of the grid squares like it is done in vanilla foundry (fixes #49) 2021-04-01 22:25:21 +02:00
Manuel Vögele 94fe223529 Fix measurement errors when disabling token snapping while the Terrain Ruler module isn't enabled 2021-04-01 00:07:26 +02:00
Manuel Vögele 305a6116bc Refactor: Override ruler class instead of hooking each function individually 2021-03-31 17:19:12 +02:00
Manuel Vögele 942bfa04e8 Release v1.4.4 2021-03-25 01:57:41 +01:00
Manuel Vögele b4ac65e478 Fix snapping for tokens that are smaller than 1x1 (fixes #48) 2021-03-25 01:55:48 +01:00
Manuel Vögele 0de01379e9 Release v1.4.3 2021-03-24 12:35:40 +01:00
Manuel Vögele f6eb5eff9c Add german translation 2021-03-24 12:35:05 +01:00
Manuel Vögele fee0983841 Mention 3de044e in changelog 2021-03-24 12:08:10 +01:00
Brother Sharp 3de044ea45 Update japanese translation (#47) 2021-03-24 12:05:10 +01:00
Manuel Vögele e473fea121 Add defaults for swade game system to generic speed provider 2021-03-18 19:06:35 +01:00
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
18 changed files with 648 additions and 146 deletions
+52 -1
View File
@@ -1,3 +1,54 @@
## 1.5.1
### Bugfixes
- The hint that tells users how to enable difficult terrain measurement in Drag Ruler is no longer shown if no terrain layer module is installed.
## 1.5.0
### New features
- In combat Drag Ruler will now remember the path that was taken by a token during the turn. Picking the token up during the same turn will continue the previous measurement, taking steps that are already taken into account.
### Module compatibility
- Drag Ruler is now fully compatible with the `Enhanced Terrain Layer` module. ([#50](https://github.com/manuelVo/foundryvtt-drag-ruler/issues/50))
## 1.4.6
### Bugfixes
- Fixed a bug where a token would move to the incorrect location if it is was dragged and released very quickly ([#51](https://github.com/manuelVo/foundryvtt-drag-ruler/issues/51)) - thanks to Silver Raven for helping me track down this bug!
## 1.4.5
### Bugfixes
- Tiny tokens (0.5x0.5 or smaller) now snap to the coners of a square like they do in vanilla foundry ([#49](https://github.com/manuelVo/foundryvtt-drag-ruler/issues/49))
- Fixed a bug that could cause a meausred distance to be wrong when disabling token snapping using the shift key
- Fixed a bug where the highlighted path could have gaps when disabling token snapping using the shift key
## 1.4.4
### Bugfixes
- Fix snapping for tokens that are smaller than 1x1
## 1.4.3
### System compatibility
- Drag Ruler's Generic SpeedProvider is now aware of good default values for the Savage Worlds Adventure Edition game system
### Translation
- Added german translation
- Updated japanese translation (thanks to touge)
## 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 ## 1.4.0
### New features ### 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. - 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.
@@ -26,7 +77,7 @@
## 1.3.2 ## 1.3.2
### Translation ### Translation
- Updated japanese translation (thanks to togue) - Updated japanese translation (thanks to touge)
## 1.3.1 ## 1.3.1
+2
View File
@@ -41,7 +41,9 @@ The game systems that offer Drag Ruler integration are:
## Translations ## Translations
Drag Ruler is available in the follwing languages: Drag Ruler is available in the follwing languages:
- Chinese (thanks to zeteticl)
- English - English
- German
- Japanese (thanks to touge) - Japanese (thanks to touge)
## API ## API
+82
View File
@@ -0,0 +1,82 @@
{
"drag-ruler": {
"dependencies": {
"ok": "OK",
"terrain-ruler": {
"title": "Wie man schwieriges Gelände mit Drag Ruler misst",
"text": "Du hast das {moduleName} Modul installiert. Drag Ruler kann schwieriges Gelände, das mit diesem Modul platziert wurde in seinen Messungen berücksichtigen. Hierzu greift Drag Ruler auf die Funktionalität von Terrain Ruler zurück. Wenn Terrain Ruler installiert und aktiviert ist wird Drag Ruler automatisch anfangen schwiriges Gelände in den Messungen zu berücksichtigen.",
"neverShowAgain": "Zeige dies nie wieder an"
},
"socketlib": {
"title": "Socketlib Modul fehlt",
"text": "Drag Ruler benötigt das socketlib-Modul, um korrekt zu funktionieren. Bitte aktiviere das socketlib-Modul in dieser Welt."
}
},
"genericSpeedProvider": {
"settings": {
"dashMultiplier": {
"name": "Sprint Multiplikator",
"hint": "Diese Einstellung kann genutzt werden, um eine zweite Geschwindigkeitsfarbe im gemessenen Pfad zu erhalten. Die zweite Farbe wird deaktiviert, wenn diese Einstellung auf 0 gesetzt ist."
},
"speedAttribute": {
"name": "Geschwindigkeitsattribut",
"hint": "Das Attribut, das die Gehgeschwindigkeit einer Spielfigur definiert. Diese Attribut wird während der Färbung des hervorgehobenen Pfads verwendet."
}
},
"speeds": {
"walk": "gehen",
"dash": "sprinten"
}
},
"settings": {
"alwaysShowSpeedForPCs": {
"name": "Geschwindigkeit von Spielercharakteren für jeden anzeigen",
"hint": "Wenn diese Einstellung aktiviert ist wird die Färbung der hervorgehobenen Felder bei Spielercharakteren allen Spielern angezeigt, selbst wenn diese keinen Zugriff auf den Charakterbogen haben."
},
"enableMovementHistory": {
"name": "Bewegungsverlauf während des Kampfes aktivieren",
"hint": "Wenn diesen Option aktiviert ist merkt sich Drag Ruler die Bewegungen, die während einer Runde eines Kampfes gemacht wurden und zeigt diese an, wenn das Token weiterbewegt wird."
},
"showGMRulerToPlayers": {
"name": "Lineal des Sipelleiters bei Spielern anzeigen",
"hint": "Wenn diese Option deaktiviert ist wird das Lineal des Spielleiters bei den anderen Spielern nicht angezeigt."
},
"speedProviderSettings": {
"name": "Einstellungen zur Spielsystemintegration",
"hint": "In diesem Menü befinden sich alle spielsystemspezifischen Einstellungen.",
"button": "Einstellungen zur Spielsystemintegration",
"windowTitle": "Einstellungen zur Spielsystemintegration",
"headers": {
"speedProvider": "Spielsystemintegration",
"speedProviderSettings": "Integrationsspezifische Einstellungen"
},
"activeProvider": {
"name": "Verwendete Spielsystemintegration",
"hint": "Die unten angezeigten Einstellungen hängen von der verwendeten Spielsystemintegration ab. Wenn der Spielleiter eine andere Spielsystemintegration auswählt können sich die zur Verfügung stehnden Einstellungen ändern."
},
"noSettings": "Diese Spielsystemintegration hat keine Einstellmöglichkeiten",
"color": {
"name": "Farbe für {colorName}",
"hint": "Die Farbe mit der Felder in {colorName} Reichweite hervorgehoben werden",
"unreachable": {
"name": "unerreichbar",
"hint": "Die Farbe für Felder, die von der bewegten Spielfigur nicht erreicht werden können"
}
},
"speedProvider": {
"name": "Verwendete Spielsystemintegration",
"hint": "Wähle aus, welche Spielsystemintegration für die farbliche Hervorhebung der Felder zuständig sein soll. Drag Ruler stellt eine generische Spielsystemintegration zur Verfügung, die grundlegende Funktionalität bietet und bei korrekter konfiguration mit allen Spielsystemen funktionieren sollte. Weitere Spielsystemintegrationen können durch Module und Spielsysteme angeboten werden. Eine andere Spielsystemintegration als die generische auszuwählen kann eine bessere Inegration in das verwendete Spielsystem bieten. Die Optionen unterhalb sind davon abhäng, welche Spielsystemintegration hier ausgewählt wurde.",
"choices": {
"module": "Modul {name}",
"native": "Generisch",
"system": "System {name}"
}
}
},
"swapSpacebarRightClick": {
"name": "Leertaste und Rechtsklick tauschen",
"hint": "Die Funktionen der Leertaste und des Rechtsklicks sind während eine Spielfigur bewegt wird vertauscht. Wenn diese Option aktiviert wrid können mit Rechtsklick Wegpunkte gesetzt werden und mit der Leertaste werden sie wieder gelöscht."
}
}
}
}
+16
View File
@@ -1,5 +1,17 @@
{ {
"drag-ruler": { "drag-ruler": {
"dependencies": {
"ok": "OK",
"terrain-ruler": {
"title": "How to measure difficult terrain with Drag Ruler",
"text": "You have the {moduleName} module enabled. Drag Ruler is able to measure difficult terrain that was placed down using that module. To make this possible Drag Ruler utilizes the Terrain Ruler module. If Terrain Ruler is installed and activated Drag Ruler will automatically start to respect difficult terrain in it's measurements.",
"neverShowAgain": "Never show this again"
},
"socketlib": {
"title": "Socketlib Module Missing",
"text": "Drag Ruler requires the socketlib module to work properly. Please activate the socketlib module in this world."
}
},
"genericSpeedProvider": { "genericSpeedProvider": {
"settings": { "settings": {
"dashMultiplier": { "dashMultiplier": {
@@ -21,6 +33,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."
}, },
"enableMovementHistory": {
"name": "Enable movement history during combat",
"hint": "If enabled, Drag Ruler will remember the path a token took during it's turn in combat and will display it when you pick the token back up."
},
"showGMRulerToPlayers": { "showGMRulerToPlayers": {
"name": "Show GM ruler to players", "name": "Show GM ruler to players",
"hint": "If disabled the ruler of GMs will not be shown for non-GM players" "hint": "If disabled the ruler of GMs will not be shown for non-GM players"
+4
View File
@@ -21,6 +21,10 @@
"name": "プレイヤーキャラクターの移動速度を全員に公開する", "name": "プレイヤーキャラクターの移動速度を全員に公開する",
"hint": "有効にすると、アクターの観察者(Observer)権限を持たない人にも,移動範囲の色分けが表示されるようになります。" "hint": "有効にすると、アクターの観察者(Observer)権限を持たない人にも,移動範囲の色分けが表示されるようになります。"
}, },
"showGMRulerToPlayers": {
"name": "GMのルーラーをプレイヤーに見せる",
"hint": "無効の場合、GMのルーラーはプレイヤーに表示されません。"
},
"speedProviderSettings": { "speedProviderSettings": {
"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": "交換空格鍵和右鍵點擊在拖動過程中的功能。如果啟用,右鍵單擊將放置航點,空格鍵將刪除它們。"
}
}
}
}
+21 -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.4.0", "version": "1.5.1",
"minimumCoreVersion" : "0.7.9", "minimumCoreVersion" : "0.7.9",
"compatibleCoreVersion" : "0.7.9", "compatibleCoreVersion" : "0.7.9",
"authors": [ "authors": [
@@ -13,12 +13,18 @@
} }
], ],
"esmodules": [ "esmodules": [
"src/main.js" "src/main.js",
"src/socket.js"
], ],
"templates": [ "templates": [
"speed_provider_settings.html" "speed_provider_settings.html"
], ],
"languages": [ "languages": [
{
"lang": "de",
"name": "Deutsch",
"path": "lang/de.json"
},
{ {
"lang": "en", "lang": "en",
"name": "English", "name": "English",
@@ -28,10 +34,22 @@
"lang": "ja", "lang": "ja",
"name": "日本語", "name": "日本語",
"path": "lang/ja.json" "path": "lang/ja.json"
},
{
"lang": "zh-tw",
"name": "正體中文",
"path": "lang/zh-tw.json"
} }
], ],
"dependencies": [
{
"name": "socketlib",
"manifest": "https://raw.githubusercontent.com/manuelVo/foundryvtt-socketlib/master/module.json"
}
],
"socket": true,
"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.4.0.zip", "download": "https://github.com/manuelVo/foundryvtt-drag-ruler/archive/v1.5.1.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",
+12
View File
@@ -1,5 +1,8 @@
import {measureDistances} from "./compatibility.js";
import {getMovementHistory} from "./movement_tracking.js";
import {GenericSpeedProvider, SpeedProvider} from "./speed_provider.js" import {GenericSpeedProvider, SpeedProvider} from "./speed_provider.js"
import {settingsKey} from "./settings.js" import {settingsKey} from "./settings.js"
import {getTokenShape} from "./util.js";
export const availableSpeedProviders = {} export const availableSpeedProviders = {}
export let currentSpeedProvider = undefined export let currentSpeedProvider = undefined
@@ -104,6 +107,15 @@ export function getCostFromSpeedProvider(token, area) {
return currentSpeedProvider.getCostForStep(token, area); return currentSpeedProvider.getCostForStep(token, area);
} }
export function getMovedDistanceFromToken(token) {
const history = getMovementHistory(token);
const segments = Ruler.dragRulerGetRaysFromWaypoints(history, {x: token.x, y: token.y}).map(ray => {return {ray}});
const shape = getTokenShape(token);
const distances = measureDistances(segments, token, shape);
// Sum up the distances
return distances.reduce((acc, val) => acc + val, 0);
}
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)
+75 -3
View File
@@ -1,14 +1,86 @@
import {getCostFromSpeedProvider} from "./api.js";
import {getColorForDistance} from "./main.js" import {getColorForDistance} from "./main.js"
import {highlightTokenShape} from "./util.js" import {settingsKey} from "./settings.js";
import {getAreaFromPositionAndShape, highlightTokenShape} from "./util.js";
export function getHexSizeSupportTokenGridCenter(token) { export function getHexSizeSupportTokenGridCenter(token) {
const tokenCenterOffset = CONFIG.hexSizeSupport.getCenterOffset(token) const tokenCenterOffset = CONFIG.hexSizeSupport.getCenterOffset(token)
return {x: token.x + tokenCenterOffset.x, y: token.y + tokenCenterOffset.y} return {x: token.x + tokenCenterOffset.x, y: token.y + tokenCenterOffset.y}
} }
export function highlightMeasurementTerrainRuler(ray, startDistance, tokenShape=[{x: 0, y: 0}]) { export function highlightMeasurementTerrainRuler(ray, startDistance, tokenShape=[{x: 0, y: 0}], alpha=1) {
for (const space of ray.terrainRulerVisitedSpaces.reverse()) { for (const space of ray.terrainRulerVisitedSpaces.reverse()) {
const color = getColorForDistance.call(this, startDistance, space.distance) const color = getColorForDistance.call(this, startDistance, space.distance)
highlightTokenShape.call(this, space, tokenShape, color) highlightTokenShape.call(this, space, tokenShape, color, alpha)
} }
} }
export function measureDistances(segments, token, shape, gridSpaces=true, options={}) {
const opts = duplicate(options)
opts.gridSpaces = gridSpaces;
const terrainRulerAvailable = game.modules.get("terrain-ruler")?.active && canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS;
if (terrainRulerAvailable) {
const firstNewSegmentIndex = segments.findIndex(segment => !segment.ray.dragRulerVisitedSpaces);
const previousSegments = segments.slice(0, firstNewSegmentIndex);
const newSegments = segments.slice(firstNewSegmentIndex);
const distances = previousSegments.map(segment => segment.ray.dragRulerVisitedSpaces[segment.ray.dragRulerVisitedSpaces.length - 1].distance);
previousSegments.forEach(segment => segment.ray.terrainRulerVisitedSpaces = duplicate(segment.ray.dragRulerVisitedSpaces));
opts.costFunction = (x, y) => getCostFromSpeedProvider(token, getAreaFromPositionAndShape({x, y}, shape), {x, y});
if (previousSegments.length > 0)
opts.terrainRulerInitialState = previousSegments[previousSegments.length - 1];
return distances.concat(terrainRuler.measureDistances(newSegments, opts));
}
else {
return canvas.grid.measureDistances(segments, opts);
}
}
export function checkDependencies() {
if (!game.modules.get("socketlib")?.active) {
console.error("Drag Ruler | The `socketlib` module isn't enabled, but it's required for Drag Ruler to operate properly.");
if (game.user.isGM) {
new Dialog({
title: game.i18n.localize("drag-ruler.dependencies.socketlib.title"),
content: `<h2>${game.i18n.localize("drag-ruler.dependencies.socketlib.title")}</h2><p>${game.i18n.localize("drag-ruler.dependencies.socketlib.text")}</p>`,
buttons: {
ok: {
icon: '<i class="fas fa-check"></i>',
label: game.i18n.localize("drag-ruler.dependencies.ok")
}
},
}).render(true);
}
}
else if (!game.modules.get("terrain-ruler")?.active && game.user.isGM && !game.settings.get(settingsKey, "neverShowTerrainRulerHint")) {
const lastHint = game.settings.get(settingsKey, "lastTerrainRulerHintTime");
if (Date.now() - lastHint > 604800000) { // One week
let enabledTerrainModule;
if (game.modules.get("enhanced-terrain-layer")?.active) {
enabledTerrainModule = game.modules.get("enhanced-terrain-layer").data.title;
}
else if (game.modules.get("TerrainLayer")?.active) {
enabledTerrainModule = game.modules.get("TerrainLayer").data.title;
}
if (enabledTerrainModule) {
new Dialog({
title: game.i18n.localize("drag-ruler.dependencies.terrain-ruler.title"),
content: `<h2>${game.i18n.localize("drag-ruler.dependencies.terrain-ruler.title")}</h2><p>${game.i18n.format("drag-ruler.dependencies.terrain-ruler.text", {moduleName: enabledTerrainModule})}</p>`,
buttons: {
ok: {
icon: '<i class="fas fa-check"></i>',
label: game.i18n.localize("drag-ruler.dependencies.ok"),
callback: () => game.settings.set(settingsKey, "lastTerrainRulerHintTime", Date.now()),
},
neverShowAgain: {
icon: '<i class="fas fa-times"></i>',
label: game.i18n.localize("drag-ruler.dependencies.terrain-ruler.neverShowAgain"),
callback: () => game.settings.set(settingsKey, "neverShowTerrainRulerHint", true),
}
},
close: () => game.settings.set(settingsKey, "lastTerrainRulerHintTime", Date.now())
}).render(true);
}
}
}
}
+30 -20
View File
@@ -1,8 +1,8 @@
import { getCostFromSpeedProvider } from "./api.js"; import {highlightMeasurementTerrainRuler, measureDistances} from "./compatibility.js";
import {highlightMeasurementTerrainRuler} from "./compatibility.js";
import {getGridPositionFromPixels} from "./foundry_fixes.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"; import {trackRays} from "./movement_tracking.js"
import {applyTokenSizeOffset, 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) {
@@ -16,7 +16,7 @@ export async function moveTokens(draggedToken, selectedTokens) {
// Get the movement rays and check collision along each Ray // Get the movement rays and check collision along each Ray
// These rays are center-to-center for the purposes of collision checking // These rays are center-to-center for the purposes of collision checking
const rays = this._getRaysFromWaypoints(this.waypoints, this.destination); const rays = this.constructor.dragRulerGetRaysFromWaypoints(this.waypoints, this.destination);
if (!game.user.isGM) { if (!game.user.isGM) {
const hasCollision = selectedTokens.some(token => { const hasCollision = selectedTokens.some(token => {
const offset = calculateTokenOffset(token, draggedToken) const offset = calculateTokenOffset(token, draggedToken)
@@ -45,11 +45,13 @@ export async function moveTokens(draggedToken, selectedTokens) {
// This is a modified version code extracted from Ruler.moveToken from foundry 0.7.9 // This is a modified version code extracted from Ruler.moveToken from foundry 0.7.9
async function animateToken(token, rays, tokenOffset, wasPaused) { async function animateToken(token, rays, tokenOffset, wasPaused) {
const offsetRays = rays.map(ray => applyOffsetToRay(ray, tokenOffset)) const offsetRays = rays.filter(r => !r.isPrevious).map(ray => applyOffsetToRay(ray, tokenOffset));
trackRays(token, offsetRays);
// Determine offset relative to the Token top-left. // Determine offset relative to the Token top-left.
// This is important so we can position the token relative to the ruler origin for non-1x1 tokens. // This is important so we can position the token relative to the ruler origin for non-1x1 tokens.
const origin = [this.waypoints[0].x + tokenOffset.x, this.waypoints[0].y + tokenOffset.y] const firstWaypoint = this.waypoints.find(w => !w.isPrevious);
const origin = [firstWaypoint.x + tokenOffset.x, firstWaypoint.y + tokenOffset.y];
let dx, dy let dx, dy
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) { if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
dx = token.data.x - origin[0] dx = token.data.x - origin[0]
@@ -76,7 +78,9 @@ function calculateTokenOffset(tokenA, tokenB) {
} }
function applyOffsetToRay(ray, offset) { function applyOffsetToRay(ray, offset) {
return new Ray({x: ray.A.x + offset.x, y: ray.A.y + offset.y}, {x: ray.B.x + offset.x, y: ray.B.y + offset.y}) const newRay = new Ray({x: ray.A.x + offset.x, y: ray.A.y + offset.y}, {x: ray.B.x + offset.x, y: ray.B.y + offset.y});
newRay.isPrevious = ray.isPrevious;
return newRay;
} }
// This is a modified version of Ruler._onMouseMove from foundry 0.7.9 // This is a modified version of Ruler._onMouseMove from foundry 0.7.9
@@ -108,9 +112,14 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
if (snap) if (snap)
destination = getSnapPointForToken(destination.x, destination.y, this.draggedToken) destination = getSnapPointForToken(destination.x, destination.y, this.draggedToken)
const terrainRulerAvailable = game.modules.get("terrain-ruler")?.active && canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS;
const waypoints = this.waypoints.concat([destination]); const waypoints = this.waypoints.concat([destination]);
// Move the waypoints to the center of the grid if a size is used that measures from edge to edge // 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 centeredWaypoints = applyTokenSizeOffset(waypoints, this.draggedToken)
// Foundries native ruler requires the waypoints to sit in the dead center of the square to work properly
if (!terrainRulerAvailable)
centeredWaypoints.forEach(w => [w.x, w.y] = canvas.grid.getCenter(w.x, w.y));
const r = this.ruler; const r = this.ruler;
this.destination = destination; this.destination = destination;
@@ -125,6 +134,10 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
const label = this.labels.children[i]; const label = this.labels.children[i];
const ray = new Ray(origin, dest); const ray = new Ray(origin, dest);
const centeredRay = new Ray(centeredOrigin, centeredDest) const centeredRay = new Ray(centeredOrigin, centeredDest)
ray.isPrevious = Boolean(origin.isPrevious);
centeredRay.isPrevious = ray.isPrevious;
ray.dragRulerVisitedSpaces = origin.dragRulerVisitedSpaces;
centeredRay.dragRulerVisitedSpaces = ray.dragRulerVisitedSpaces;
if (ray.distance < 10) { if (ray.distance < 10) {
if (label) label.visible = false; if (label) label.visible = false;
continue; continue;
@@ -133,15 +146,11 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
centeredSegments.push({ray: centeredRay, label}) centeredSegments.push({ray: centeredRay, label})
} }
const shape = getTokenShape(this.draggedToken) const shape = getTokenShape(this.draggedToken)
// Compute measured distance // Compute measured distance
const terrainRulerAvailable = game.modules.get("terrain-ruler")?.active && canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS const distances = measureDistances(centeredSegments, this.draggedToken, shape, gridSpaces);
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()) {
@@ -168,8 +177,9 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
const { label, text, last } = cs; const { label, text, last } = cs;
// Draw line segment // Draw line segment
r.lineStyle(6, 0x000000, 0.5).moveTo(s.ray.A.x, s.ray.A.y).lineTo(s.ray.B.x, s.ray.B.y) const opacityMultiplier = s.ray.isPrevious ? 0.33 : 1;
.lineStyle(4, rulerColor, 0.25).moveTo(s.ray.A.x, s.ray.A.y).lineTo(s.ray.B.x, s.ray.B.y); r.lineStyle(6, 0x000000, 0.5 * opacityMultiplier).moveTo(s.ray.A.x, s.ray.A.y).lineTo(s.ray.B.x, s.ray.B.y)
.lineStyle(4, rulerColor, 0.25 * opacityMultiplier).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) {
@@ -182,9 +192,9 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
// Highlight grid positions // Highlight grid positions
if (terrainRulerAvailable) if (terrainRulerAvailable)
highlightMeasurementTerrainRuler.call(this, cs.ray, cs.startDistance, shape) highlightMeasurementTerrainRuler.call(this, cs.ray, cs.startDistance, shape, opacityMultiplier)
else else
highlightMeasurementNative.call(this, cs.ray, cs.startDistance, shape); highlightMeasurementNative.call(this, cs.ray, cs.startDistance, shape, opacityMultiplier);
} }
// Draw endpoints // Draw endpoints
@@ -196,7 +206,7 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
return segments; return segments;
} }
export function highlightMeasurementNative(ray, startDistance, tokenShape=[{x: 0, y: 0}]) { export function highlightMeasurementNative(ray, startDistance, tokenShape=[{x: 0, y: 0}], alpha=1) {
const spacer = canvas.scene.data.gridType === CONST.GRID_TYPES.SQUARE ? 1.41 : 1; 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 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); const tMax = Array.fromRange(nMax+1).map(t => t / nMax);
@@ -232,9 +242,9 @@ export function highlightMeasurementNative(ray, startDistance, tokenShape=[{x: 0
const color = dragRuler.getColorForDistance.call(this, startDistance, subDistance) const color = dragRuler.getColorForDistance.call(this, startDistance, subDistance)
const snapPoint = getSnapPointForToken(...canvas.grid.getTopLeft(x, y), this.draggedToken); const snapPoint = getSnapPointForToken(...canvas.grid.getTopLeft(x, y), this.draggedToken);
const [snapX, snapY] = getGridPositionFromPixels(snapPoint.x + 1, snapPoint.y + 1); 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, alpha);
} }
highlightTokenShape.call(this, {x: snapX, y: snapY}, tokenShape, color); highlightTokenShape.call(this, {x: snapX, y: snapY}, tokenShape, color, alpha);
} }
} }
+18 -99
View File
@@ -1,22 +1,25 @@
"use strict" "use strict"
import {currentSpeedProvider, getRangesFromSpeedProvider, getUnreachableColorFromSpeedProvider, initApi, registerModule, registerSystem} from "./api.js" import {currentSpeedProvider, getMovedDistanceFromToken, getRangesFromSpeedProvider, getUnreachableColorFromSpeedProvider, initApi, registerModule, registerSystem} from "./api.js"
import {getHexSizeSupportTokenGridCenter} from "./compatibility.js" import {checkDependencies, getHexSizeSupportTokenGridCenter} from "./compatibility.js";
import {measure, moveTokens, onMouseMove} from "./foundry_imports.js" import {moveTokens, onMouseMove} from "./foundry_imports.js"
import {performMigrations} from "./migration.js" import {performMigrations} from "./migration.js"
import {DragRulerRuler} from "./ruler.js";
import {getMovementHistory} from "./movement_tracking.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()
initApi() initApi()
hookTokenDragHandlers() hookTokenDragHandlers()
hookRulerFunctions()
hookKeyboardManagerFunctions() hookKeyboardManagerFunctions()
Ruler = DragRulerRuler;
window.dragRuler = { window.dragRuler = {
getColorForDistance, getColorForDistance,
getMovedDistanceFromToken,
registerModule, registerModule,
registerSystem, registerSystem,
} }
@@ -24,6 +27,7 @@ Hooks.once("init", () => {
Hooks.once("ready", () => { Hooks.once("ready", () => {
performMigrations() performMigrations()
checkDependencies();
Hooks.callAll("dragRuler.ready", SpeedProvider) Hooks.callAll("dragRuler.ready", SpeedProvider)
}) })
@@ -66,51 +70,6 @@ function hookTokenDragHandlers() {
} }
} }
function hookRulerFunctions() {
const originalMoveTokenHandler = Ruler.prototype.moveToken
Ruler.prototype.moveToken = function (event) {
const eventHandled = onRulerMoveToken.call(this, event)
if (!eventHandled)
return originalMoveTokenHandler.call(this, event)
return true
}
const originalToJSON = Ruler.prototype.toJSON
Ruler.prototype.toJSON = function () {
const json = originalToJSON.call(this)
if (this.draggedToken)
json["draggedToken"] = this.draggedToken.data._id
return json
}
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)
}
originalUpdate.call(this, data)
}
const originalMeasure = Ruler.prototype.measure
Ruler.prototype.measure = function (destination, options={}) {
if (this.isDragRuler) {
return measure.call(this, destination, options)
}
else {
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() {
const originalHandleKeys = KeyboardManager.prototype._handleKeys const originalHandleKeys = KeyboardManager.prototype._handleKeys
KeyboardManager.prototype._handleKeys = function (event, key, up) { KeyboardManager.prototype._handleKeys = function (event, key, up) {
@@ -132,10 +91,11 @@ function handleKeys(event, key, up) {
function onKeyX(up) { function onKeyX(up) {
if (up) if (up)
return false return false
if (!canvas.controls.ruler.isDragRuler) const ruler = canvas.controls.ruler;
if (!ruler.isDragRuler)
return false return false
deleteWaypoint() ruler.dragRulerDeleteWaypoint();
return true return true
} }
@@ -163,7 +123,9 @@ function onTokenLeftDragStart(event) {
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}
addWaypoint.call(ruler, tokenCenter, false) if (game.settings.get(settingsKey, "enableMovementHistory"))
ruler.dragRulerAddWaypointHistory(getMovementHistory(this));
ruler.dragRulerAddWaypoint(tokenCenter, false);
} }
function onTokenLeftDragMove(event) { function onTokenLeftDragMove(event) {
@@ -176,6 +138,7 @@ function onTokenDragLeftDrop(event) {
const ruler = canvas.controls.ruler const ruler = canvas.controls.ruler
if (!ruler.isDragRuler) if (!ruler.isDragRuler)
return false return false
onMouseMove.call(ruler, event);
const selectedTokens = canvas.tokens.controlled const selectedTokens = canvas.tokens.controlled
ruler._state = Ruler.STATES.MOVING ruler._state = Ruler.STATES.MOVING
moveTokens.call(ruler, ruler.draggedToken, selectedTokens) moveTokens.call(ruler, ruler.draggedToken, selectedTokens)
@@ -189,61 +152,17 @@ function onTokenDragLeftCancel(event) {
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")) {
if (ruler.waypoints.length > 1) ruler.dragRulerDeleteWaypoint(event);
event.preventDefault()
deleteWaypoint()
} }
else { else {
event.preventDefault() event.preventDefault()
const snap = !event.shiftKey const snap = !event.shiftKey
addWaypoint.call(ruler, ruler.destination, snap) ruler.dragRulerAddWaypoint(ruler.destination, snap);
} }
} }
return true return true
} }
function onRulerMoveToken(event) {
// This function is invoked by left clicking
if (!this.isDragRuler)
return false
if (!game.settings.get(settingsKey, "swapSpacebarRightClick")) {
const snap = !event.shiftKey
addWaypoint.call(this, this.destination, snap)
}
else
deleteWaypoint()
return true
}
function addWaypoint(point, snap=true) {
if (snap)
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));
}
function deleteWaypoint() {
const ruler = canvas.controls.ruler
if (ruler.waypoints.length > 1) {
const mousePosition = canvas.app.renderer.plugins.interaction.mouse.getLocalPosition(canvas.tokens)
const rulerOffset = ruler.rulerOffset
ruler._removeWaypoint({x: mousePosition.x + rulerOffset.x, y: mousePosition.y + rulerOffset.y})
game.user.broadcastActivity({ruler: ruler})
}
else {
const token = ruler.draggedToken
ruler._endMeasurement()
// Deactivate the drag workflow in mouse
token.mouseInteractionManager._deactivateDragEvents();
token.mouseInteractionManager.state = token.mouseInteractionManager.states.HOVER;
// This will cancel the current drag operation
// Pass in a fake event that hopefully is enough to allow other modules to function
token._onDragLeftCancel({preventDefault: () => {return}})
}
}
export function getColorForDistance(startDistance, subDistance=0) { export function getColorForDistance(startDistance, subDistance=0) {
if (!this.isDragRuler) if (!this.isDragRuler)
return this.color return this.color
+74
View File
@@ -0,0 +1,74 @@
import {measureDistances} from "./compatibility.js";
import {updateCombatantDragRulerFlags} from "./socket.js";
import {getTokenShape} from "./util.js";
function initTrackingFlag(combatant) {
const initialFlag = {passedWaypoints: [], trackedRound: 0};
let dragRulerFlag = combatant.flags?.dragRuler;
if (dragRulerFlag) {
if (isNaN(dragRulerFlag.trackedRound)) {
mergeObject(dragRulerFlag, initialFlag);
}
}
else {
combatant.flags.dragRuler = initialFlag;
}
}
function getInitializedCombatant(token, combat) {
const combatant = combat.getCombatantByToken(token.data._id);
if (!combatant)
return undefined;
initTrackingFlag(combatant);
return combatant;
}
export async function trackRays(token, rays) {
// Only track movement if the current token is participating in the active combat
const combat = game.combat;
if (!combat)
return;
if (!combat.started)
return;
const combatant = getInitializedCombatant(token, combat);
if (!combatant)
return;
// Check if we have entered a new round. If so, remove the currently stored path
if (combat.data.round > combatant.flags.dragRuler.trackedRound) {
combatant.flags.dragRuler.passedWaypoints = [];
combatant.flags.dragRuler.trackedRound = combat.data.round;
}
// Add the passed waypoints to the combatant
const terrainRulerAvailable = game.modules.get("terrain-ruler")?.active && canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS;
const dragRulerFlags = combatant.flags.dragRuler;
const waypoints = dragRulerFlags.passedWaypoints;
for (const ray of rays) {
// Ignore rays that have the same start and end coordinates
if (ray.A.x !== ray.B.x || ray.A.y !== ray.B.y) {
if (terrainRulerAvailable) {
measureDistances([{ray}], token, getTokenShape(token), true, {terrainRulerInitialState: dragRulerFlags.rulerState});
ray.A.dragRulerVisitedSpaces = ray.terrainRulerVisitedSpaces;
dragRulerFlags.rulerState = ray.terrainRulerFinalState;
}
waypoints.push(ray.A);
}
}
await updateCombatantDragRulerFlags(combat, combatant, dragRulerFlags);
}
export function getMovementHistory(token) {
const combat = game.combat;
if (!combat)
return [];
const combatant = combat.getCombatantByToken(token.data._id);
if (!combatant)
return [];
const dragRulerFlags = combatant.flags.dragRuler;
if (!dragRulerFlags)
return [];
if (combat.data.round > dragRulerFlags.trackedRound)
return [];
return dragRulerFlags.passedWaypoints ?? [];
}
+111
View File
@@ -0,0 +1,111 @@
import {measure} from "./foundry_imports.js"
import {settingsKey} from "./settings.js";
import {getSnapPointForToken} from "./util.js";
export class DragRulerRuler extends Ruler {
// Functions below are overridden versions of functions in Ruler
constructor(user, {color=null}={}) {
super(user, {color});
this.previousWaypoints = [];
this.previousLabels = this.addChild(new PIXI.Container());
}
clear() {
super.clear();
this.previousWaypoints = [];
this.previousLabels.removeChildren().forEach(c => c.destroy());
}
async moveToken(event) {
// This function is invoked by left clicking
if (!this.isDragRuler)
return await super.moveToken(event);
if (!game.settings.get(settingsKey, "swapSpacebarRightClick")) {
const snap = !event.shiftKey;
this.dragRulerAddWaypoint(this.destination, snap);
}
else {
this.dragRulerDeleteWaypoint();
}
}
toJSON() {
const json = super.toJSON();
if (this.draggedToken)
json["draggedToken"] = this.draggedToken.data._id;
return json;
}
update(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);
}
super.update(data);
}
measure(destination, options={}) {
if (this.isDragRuler) {
return measure.call(this, destination, options);
}
else {
return super.measure(destination, options);
}
}
_endMeasurement() {
super._endMeasurement();
this.draggedToken = null;
}
// The functions below aren't present in the orignal Ruler class and are added by Drag Ruler
dragRulerAddWaypoint(point, snap=true) {
if (snap)
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));
}
dragRulerAddWaypointHistory(waypoints) {
waypoints.forEach(waypoint => waypoint.isPrevious = true);
this.waypoints = this.waypoints.concat(waypoints);
for (const waypoint of waypoints) {
this.labels.addChild(new PreciseText("", CONFIG.canvasTextStyle));
}
}
dragRulerDeleteWaypoint(event={preventDefault: () => {return}}) {
if (this.waypoints.filter(w => !w.isPrevious).length > 1) {
event.preventDefault();
const mousePosition = canvas.app.renderer.plugins.interaction.mouse.getLocalPosition(canvas.tokens);
const rulerOffset = this.rulerOffset;
this._removeWaypoint({x: mousePosition.x + rulerOffset.x, y: mousePosition.y + rulerOffset.y});
game.user.broadcastActivity({ruler: this});
}
else {
const token = this.draggedToken;
this._endMeasurement();
// Deactivate the drag workflow in mouse
token.mouseInteractionManager._deactivateDragEvents();
token.mouseInteractionManager.state = token.mouseInteractionManager.states.HOVER;
// This will cancel the current drag operation
// Pass in a fake event that hopefully is enough to allow other modules to function
token._onDragLeftCancel(event);
}
}
static dragRulerGetRaysFromWaypoints(waypoints, destination) {
if ( destination )
waypoints = waypoints.concat([destination]);
return waypoints.slice(1).map((wp, i) => {
const ray = new Ray(waypoints[i], wp);
ray.isPrevious = Boolean(waypoints[i].isPrevious);
return ray;
});
}
}
+21
View File
@@ -38,6 +38,27 @@ export function registerSettings() {
default: true, 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, "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 // 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",
+17
View File
@@ -0,0 +1,17 @@
let socket;
Hooks.once("socketlib.ready", () => {
socket = socketlib.registerModule("drag-ruler");
socket.register("updateCombatantDragRulerFlags", _socketUpdateCombatantDragRulerFlags);
});
export async function updateCombatantDragRulerFlags(combat, combatant, flags) {
const combatId = combat.id;
const combatantId = combatant._id;
return socket.executeAsGM(_socketUpdateCombatantDragRulerFlags, combatId, combatantId, flags);
}
async function _socketUpdateCombatantDragRulerFlags(combatId, combatantId, flags) {
const combat = game.combats.get(combatId);
await combat.updateEmbeddedEntity("Combatant", {_id: combatantId, flags: {dragRuler: flags}}, {diff: false});
}
+1 -1
View File
@@ -67,7 +67,7 @@ export class SpeedProvider {
*/ */
getCostForStep(token, area) { getCostForStep(token, area) {
// Lookup the cost for each square occupied by the token // Lookup the cost for each square occupied by the token
const costs = area.map(space => canvas.terrain.costGrid[space.y]?.[space.x]?.multiple ?? 1) const costs = area.map(space => terrainRuler.getCost(space.x, space.y, {token}));
// Return the maximum of the costs // Return the maximum of the costs
return costs.reduce((max, current) => Math.max(max, current)) return costs.reduce((max, current) => Math.max(max, current))
} }
+4
View File
@@ -7,12 +7,16 @@ export function getDefaultSpeedAttribute() {
return "actor.data.data.mech.speed" return "actor.data.data.mech.speed"
case "pf1": case "pf1":
return "actor.data.data.attributes.speed.land.total" return "actor.data.data.attributes.speed.land.total"
case "swade":
return "actor.data.data.stats.speed.value"
} }
return "" return ""
} }
export function getDefaultDashMultiplier() { export function getDefaultDashMultiplier() {
switch (game.system.id) { switch (game.system.id) {
case "swade":
return 0
case "dnd5e": case "dnd5e":
case "lancer": case "lancer":
case "pf1": case "pf1":
+29 -6
View File
@@ -7,6 +7,9 @@ export function* zip(it1, it2) {
} }
export function getSnapPointForToken(x, y, token) { export function getSnapPointForToken(x, y, token) {
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
return new PIXI.Point(x, y);
}
if (canvas.grid.isHex && game.modules.get("hex-size-support")?.active && CONFIG.hexSizeSupport.getAltSnappingFlag(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) { if (token.getFlag("hex-size-support", "borderSize") % 2 === 0) {
const snapPoint = CONFIG.hexSizeSupport.findVertexSnapPoint(x, y, token, canvas.grid.grid) const snapPoint = CONFIG.hexSizeSupport.findVertexSnapPoint(x, y, token, canvas.grid.grid)
@@ -16,18 +19,34 @@ export function getSnapPointForToken(x, y, token) {
return new PIXI.Point(...canvas.grid.getCenter(x, y)) return new PIXI.Point(...canvas.grid.getCenter(x, y))
} }
} }
if (canvas.grid.isHex || token.data.width % 2 === 1) { // Tiny tokens can snap to the cells corners
if (!canvas.grid.isHex && token.data.width <= 0.5) {
const [topLeftX, topLeftY] = canvas.grid.getTopLeft(x, y);
const offsetX = x - topLeftX;
const offsetY = y - topLeftY;
const subGridWidth = Math.floor(canvas.grid.w / 2);
const subGridHeight = Math.floor(canvas.grid.h / 2);
const subGridPosX = Math.floor(offsetX / subGridWidth);
const subGridPosY = Math.floor(offsetY / subGridHeight);
return new PIXI.Point(topLeftX + (subGridPosX + 0.5) * subGridWidth, topLeftY + (subGridPosY + 0.5) * subGridHeight);
}
// Hex tokens, tokens with odd multipliers (1x1, 3x3, ...) and tokens smaller than 1x1 but bigger than 0.5x0.5 snap to the center of the grid cell
if (canvas.grid.isHex || Math.round(token.data.width) % 2 === 1 || token.data.width < 1) {
return new PIXI.Point(...canvas.grid.getCenter(x, y)) return new PIXI.Point(...canvas.grid.getCenter(x, y))
} }
// All remaining tokens (those with even or fractional multipliers on square grids) snap to the intersection points of the grid
const [snappedX, snappedY] = canvas.grid.getCenter(x - canvas.grid.w / 2, y - canvas.grid.h / 2) 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) return new PIXI.Point(snappedX + canvas.grid.w / 2, snappedY + canvas.grid.h / 2)
} }
export function highlightTokenShape(position, shape, color) { export function highlightTokenShape(position, shape, color, alpha) {
const layer = canvas.grid.highlightLayers[this.name];
if ( !layer )
return false;
const area = getAreaFromPositionAndShape(position, shape); const area = getAreaFromPositionAndShape(position, shape);
for (const space of area) { for (const space of area) {
const [x, y] = getPixelsFromGridPosition(space.x, space.y); const [x, y] = getPixelsFromGridPosition(space.x, space.y);
canvas.grid.highlightPosition(this.name, {x, y, color}) canvas.grid.grid.highlightGridPosition(layer, {x, y, color, alpha: 0.25 * alpha});
} }
} }
@@ -57,9 +76,10 @@ export function getAreaFromPositionAndShape(position, shape) {
} }
export function getTokenShape(token) { export function getTokenShape(token) {
if (token.scene.data.gridType === CONST.GRID_TYPES.GRIDLESS) if (token.scene.data.gridType === CONST.GRID_TYPES.GRIDLESS) {
throw new Error("getTokenShape cannot be called for tokens on gridless maps") return [{x: 0, y: 0}]
if (token.scene.data.gridType === CONST.GRID_TYPES.SQUARE) { }
else if (token.scene.data.gridType === CONST.GRID_TYPES.SQUARE) {
const topOffset = -Math.floor(token.data.height / 2) const topOffset = -Math.floor(token.data.height / 2)
const leftOffset = -Math.floor(token.data.width / 2) const leftOffset = -Math.floor(token.data.width / 2)
const shape = [] const shape = []
@@ -117,6 +137,7 @@ export function applyTokenSizeOffset(waypoints, token) {
const tokenSize = getTokenSize(token); const tokenSize = getTokenSize(token);
const waypointOffset = {x: 0, y: 0}; const waypointOffset = {x: 0, y: 0};
if (canvas.grid.isHex) { if (canvas.grid.isHex) {
if (game.modules.get("hex-size-support")?.active) {
const isAltOrientation = CONFIG.hexSizeSupport.getAltOrientationFlag(token); const isAltOrientation = CONFIG.hexSizeSupport.getAltOrientationFlag(token);
if (canvas.grid.grid.options.columns) { if (canvas.grid.grid.options.columns) {
if (tokenSize.w % 2 === 0) { if (tokenSize.w % 2 === 0) {
@@ -133,6 +154,8 @@ export function applyTokenSizeOffset(waypoints, token) {
} }
} }
} }
// If hex size support isn't active leave the waypoints like they are
}
else { else {
if (tokenSize.w % 2 === 0) { if (tokenSize.w % 2 === 0) {
waypointOffset.x = canvas.grid.w / 2; waypointOffset.x = canvas.grid.w / 2;