Compare commits
73 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 334ccbc0f1 | |||
| 1242035744 | |||
| 49542a68e5 | |||
| 27a6235cc1 | |||
| c26b39c984 | |||
| e8ab77a62e | |||
| 7bed5abd0a | |||
| 4a96348659 | |||
| bed16de575 | |||
| 891bb1b4d8 | |||
| 43f26088b5 | |||
| 39a0787c79 | |||
| 3be898e49c | |||
| 817662bf30 | |||
| 56527ccf75 | |||
| 6bf8083f7a | |||
| b5ea1f2284 | |||
| 6d9870bedb | |||
| afbe5f9473 | |||
| bae0e43b7b | |||
| 1acc012d65 | |||
| 4671e6de51 | |||
| f0ef109658 | |||
| f0d1ef9d48 | |||
| d04ea9b0b7 | |||
| ba8ab9d473 | |||
| 2691720090 | |||
| 70b166d844 | |||
| 908600bfa3 | |||
| 6472c1d5bd | |||
| 9151b45874 | |||
| d732f7ca17 | |||
| 4be6730fd5 | |||
| 1faeda6f47 | |||
| 667259d5d6 | |||
| 4b68b82590 | |||
| 447995977b | |||
| 9e5b344b0d | |||
| 43c5df42d4 | |||
| d0ee47d551 | |||
| 27ffb2d8cb | |||
| b734257515 | |||
| ff5d46ebbe | |||
| 8f896d9b82 | |||
| f7683e8945 | |||
| 9787a41fd1 | |||
| 5d93c61f0c | |||
| 00e8a9781e | |||
| d2f2307e4d | |||
| 65912df989 | |||
| 9ae9dae427 | |||
| 8cd5d7f276 | |||
| 35624a37aa | |||
| c66ec46aed | |||
| dec422fa3a | |||
| 6ad1f4c522 | |||
| fb9a2ee60c | |||
| 57ed545e30 | |||
| 94fe223529 | |||
| 305a6116bc | |||
| 942bfa04e8 | |||
| b4ac65e478 | |||
| 0de01379e9 | |||
| f6eb5eff9c | |||
| fee0983841 | |||
| 3de044ea45 | |||
| e473fea121 | |||
| 0aee541eb3 | |||
| 40dde08ddd | |||
| 8234aa566d | |||
| 2862643ba8 | |||
| e3abdeab3a | |||
| 2ff4e98fe9 |
+106
-1
@@ -1,3 +1,108 @@
|
|||||||
|
## 1.6.4
|
||||||
|
### Bugfixes
|
||||||
|
- Fixed a bug where a bug in a Speed Provider could lead to the ruler getting stuck, leaving the token immovable
|
||||||
|
|
||||||
|
|
||||||
|
## 1.6.3
|
||||||
|
### Bugfixes
|
||||||
|
- If the movement history for a token is being updated (for example by a history reset by the gm) while a player is currently measuring a distance for that token the history change is now being reflected in the active measurement.
|
||||||
|
|
||||||
|
### Compatibility
|
||||||
|
- Drag Ruler's Generic SpeedProvider is now aware of good default values for the Savage Worlds Adventure Edition game system
|
||||||
|
|
||||||
|
|
||||||
|
## 1.6.2
|
||||||
|
### Bugfixes
|
||||||
|
- The reset movement history button now resets the movement history for all players, not just for the GM
|
||||||
|
|
||||||
|
|
||||||
|
## 1.6.1
|
||||||
|
### API
|
||||||
|
- Added `onMovementHistoryUpdate` callback to Speed Providers, that allows them to perform game systems specific improvements to the movement history
|
||||||
|
- Added `dragRuler.resetMovementHistory` that clears the stored movement history for a token.
|
||||||
|
|
||||||
|
|
||||||
|
## 1.6.0
|
||||||
|
### Performance
|
||||||
|
- Greatly increased the performance when playing on huge maps and when moving many tokens at once.
|
||||||
|
- Huge performance improvements for speed providers. (Technical details: `getRanges` is now being called way less frequently)
|
||||||
|
|
||||||
|
### New features
|
||||||
|
- GMs now have an option to reset the movement history for individual tokens in the right click menu of the combat tracker
|
||||||
|
- When releasing a dragged token while pressing Alt the token will be moved to the target location without an animation ([#3](https://github.com/manuelVo/foundryvtt-drag-ruler/issues/3))
|
||||||
|
|
||||||
|
### Bugfixes
|
||||||
|
- When starting to drag a new token while the previous one is still moving the ruler won't dissappear anymore when the previous token arrives at it's destination.
|
||||||
|
|
||||||
|
|
||||||
|
## 1.5.4
|
||||||
|
### Bugfixes
|
||||||
|
- Fixed a bug that prevented tokens from being moved when their movement history collides with a wall. ([#61](https://github.com/manuelVo/foundryvtt-drag-ruler/issues/61))
|
||||||
|
|
||||||
|
|
||||||
|
## 1.5.3
|
||||||
|
### Compatiblilty
|
||||||
|
- Drag Ruler's Generic Speed Provider is now able to work with game systems that put non-number characters behind the tokens movement speed (like `30ft.`). One example for such a game system is Dungeon Crawl Classics. ([#60](https://github.com/manuelVo/foundryvtt-drag-ruler/issues/60))
|
||||||
|
- The Generic Speed Provider now has good default settings for the Dungeon Crawl Classics (dcc) game system.
|
||||||
|
|
||||||
|
|
||||||
|
## 1.5.2
|
||||||
|
### Bugfixes
|
||||||
|
- Drag Ruler no longer prevents tokens that don't have an actor from being moved. ([#58](https://github.com/manuelVo/foundryvtt-drag-ruler/issues/58))
|
||||||
|
- Grid highlighting now also works for tokens that don't have an actor.
|
||||||
|
|
||||||
|
|
||||||
|
## 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 +131,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
|
||||||
|
|||||||
@@ -36,12 +36,17 @@ 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/))
|
||||||
|
- Tagmar RPG (starting with version 1.1.4)
|
||||||
- Tormenta20 (starting with version 1.1.37)
|
- Tormenta20 (starting with version 1.1.37)
|
||||||
|
- Shadow of the Demon Lord (starting with version 1.7.15)
|
||||||
|
- WWII:OWB (starting with version 1.0.4)
|
||||||
|
|
||||||
|
|
||||||
## 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
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
{
|
||||||
|
"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."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resetMovementHistory": "Bewegungsverlauf zurücksetzen",
|
||||||
|
"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."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,18 @@
|
|||||||
{
|
{
|
||||||
"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."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resetMovementHistory": "Reset Movement History",
|
||||||
"genericSpeedProvider": {
|
"genericSpeedProvider": {
|
||||||
"settings": {
|
"settings": {
|
||||||
"dashMultiplier": {
|
"dashMultiplier": {
|
||||||
@@ -21,6 +34,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"
|
||||||
|
|||||||
@@ -21,6 +21,10 @@
|
|||||||
"name": "プレイヤーキャラクターの移動速度を全員に公開する",
|
"name": "プレイヤーキャラクターの移動速度を全員に公開する",
|
||||||
"hint": "有効にすると、アクターの観察者(Observer)権限を持たない人にも,移動範囲の色分けが表示されるようになります。"
|
"hint": "有効にすると、アクターの観察者(Observer)権限を持たない人にも,移動範囲の色分けが表示されるようになります。"
|
||||||
},
|
},
|
||||||
|
"showGMRulerToPlayers": {
|
||||||
|
"name": "GMのルーラーをプレイヤーに見せる",
|
||||||
|
"hint": "無効の場合、GMのルーラーはプレイヤーに表示されません。"
|
||||||
|
},
|
||||||
"speedProviderSettings": {
|
"speedProviderSettings": {
|
||||||
"name": "移動速度の提供元",
|
"name": "移動速度の提供元",
|
||||||
"hint": "ゲームシステムに固有のすべての設定をここで行います。",
|
"hint": "ゲームシステムに固有のすべての設定をここで行います。",
|
||||||
|
|||||||
@@ -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
@@ -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.6.4",
|
||||||
"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.6.4.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",
|
||||||
|
|||||||
+18
@@ -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
|
||||||
@@ -98,10 +101,25 @@ export function getUnreachableColorFromSpeedProvider() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getCostFromSpeedProvider(token, area) {
|
export function getCostFromSpeedProvider(token, area) {
|
||||||
|
try {
|
||||||
if (currentSpeedProvider instanceof Function) {
|
if (currentSpeedProvider instanceof Function) {
|
||||||
return SpeedProvider.prototype.getCostForStep.call(undefined, token, area);
|
return SpeedProvider.prototype.getCostForStep.call(undefined, token, area);
|
||||||
}
|
}
|
||||||
return currentSpeedProvider.getCostForStep(token, area);
|
return currentSpeedProvider.getCostForStep(token, area);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
|||||||
+75
-3
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,13 +2,10 @@
|
|||||||
|
|
||||||
// https://gitlab.com/foundrynet/foundryvtt/-/issues/4705
|
// https://gitlab.com/foundrynet/foundryvtt/-/issues/4705
|
||||||
export function getPixelsFromGridPosition(xGrid, yGrid) {
|
export function getPixelsFromGridPosition(xGrid, yGrid) {
|
||||||
if (canvas.grid.isHex) {
|
if (canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS) {
|
||||||
return canvas.grid.grid.getPixelsFromGridPosition(yGrid, xGrid)
|
return canvas.grid.grid.getPixelsFromGridPosition(yGrid, xGrid)
|
||||||
}
|
}
|
||||||
const [x, y] = canvas.grid.grid.getPixelsFromGridPosition(xGrid, yGrid)
|
return 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
|
// Wrapper to fix a FoundryVTT bug that causes the return values of canvas.grid.grid.getPixelsFromGridPosition to be ordered inconsistently
|
||||||
|
|||||||
+66
-38
@@ -1,8 +1,9 @@
|
|||||||
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 {recalculate} from "./socket.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,11 +17,11 @@ 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)
|
||||||
const offsetRays = rays.map(ray => applyOffsetToRay(ray, offset))
|
const offsetRays = rays.filter(ray => !ray.isPrevious).map(ray => applyOffsetToRay(ray, offset))
|
||||||
return offsetRays.some(r => canvas.walls.checkCollision(r));
|
return offsetRays.some(r => canvas.walls.checkCollision(r));
|
||||||
})
|
})
|
||||||
if (hasCollision) {
|
if (hasCollision) {
|
||||||
@@ -33,42 +34,61 @@ export async function moveTokens(draggedToken, selectedTokens) {
|
|||||||
// Execute the movement path.
|
// Execute the movement path.
|
||||||
// Transform each center-to-center ray into a top-left to top-left ray using the prior token offsets.
|
// Transform each center-to-center ray into a top-left to top-left ray using the prior token offsets.
|
||||||
this._state = Ruler.STATES.MOVING;
|
this._state = Ruler.STATES.MOVING;
|
||||||
await Promise.all(selectedTokens.map(token => {
|
await animateTokens.call(this, selectedTokens, draggedToken, rays, wasPaused);
|
||||||
// Return the promise so we can wait for it outside the loop
|
|
||||||
const offset = calculateTokenOffset(token, draggedToken)
|
|
||||||
return animateToken.call(this, token, rays, offset, wasPaused)
|
|
||||||
}))
|
|
||||||
|
|
||||||
// Once all animations are complete we can clear the ruler
|
// Once all animations are complete we can clear the ruler
|
||||||
|
if (this.draggedToken?.id === draggedToken.id)
|
||||||
this._endMeasurement();
|
this._endMeasurement();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 animateTokens(tokens, draggedToken, draggedRays, wasPaused) {
|
||||||
const offsetRays = rays.map(ray => applyOffsetToRay(ray, tokenOffset))
|
const newRays = draggedRays.filter(r => !r.isPrevious);
|
||||||
|
const tokenAnimationData = tokens.map(token => {
|
||||||
|
const tokenOffset = calculateTokenOffset(token, draggedToken);
|
||||||
|
const offsetRays = newRays.map(ray => applyOffsetToRay(ray, tokenOffset));
|
||||||
|
|
||||||
// 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);
|
||||||
let dx, dy
|
const origin = [firstWaypoint.x + tokenOffset.x, firstWaypoint.y + tokenOffset.y];
|
||||||
|
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];
|
||||||
dy = token.data.y - origin[1]
|
dy = token.data.y - origin[1];
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
dx = token.data.x - origin[0]
|
dx = token.data.x - origin[0];
|
||||||
dy = token.data.y - origin[1]
|
dy = token.data.y - origin[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {token, rays: offsetRays, dx, dy};
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const {token, rays} of tokenAnimationData) {
|
||||||
token._noAnimate = true;
|
token._noAnimate = true;
|
||||||
for (let r of offsetRays) {
|
|
||||||
if (!wasPaused && game.paused) break;
|
|
||||||
const dest = [r.B.x, r.B.y];
|
|
||||||
const path = new Ray({ x: token.x, y: token.y }, { x: dest[0] + dx, y: dest[1] + dy });
|
|
||||||
await token.update(path.B);
|
|
||||||
await token.animateMovement(path);
|
|
||||||
}
|
}
|
||||||
|
const animate = !game.keyboard.isDown("Alt");
|
||||||
|
const startWaypoint = animate ? 0 : tokenAnimationData[0].rays.length - 1;
|
||||||
|
for (let i = startWaypoint;i < tokenAnimationData[0].rays.length; i++) {
|
||||||
|
if (!wasPaused && game.paused) break;
|
||||||
|
const tokenPaths = tokenAnimationData.map(({token, rays, dx, dy}) => {
|
||||||
|
const ray = rays[i];
|
||||||
|
const dest = [ray.B.x, ray.B.y];
|
||||||
|
const path = new Ray({x: token.x, y: token.y}, {x: dest[0] + dx, y: dest[1] + dy});
|
||||||
|
return {token, path};
|
||||||
|
});
|
||||||
|
const updates = tokenPaths.map(({token, path}) => {
|
||||||
|
return {x: path.B.x, y: path.B.y, _id: token.id};
|
||||||
|
});
|
||||||
|
await draggedToken.scene.updateEmbeddedEntity(draggedToken.constructor.embeddedName, updates, {animate});
|
||||||
|
if (animate)
|
||||||
|
await Promise.all(tokenPaths.map(({token, path}) => token.animateMovement(path)));
|
||||||
|
}
|
||||||
|
for (const {token} of tokenAnimationData) {
|
||||||
token._noAnimate = false;
|
token._noAnimate = false;
|
||||||
|
}
|
||||||
|
trackRays(tokens, tokenAnimationData.map(({rays}) => rays)).then(() => recalculate(tokens));
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculateTokenOffset(tokenA, tokenB) {
|
function calculateTokenOffset(tokenA, tokenB) {
|
||||||
@@ -76,7 +96,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 +130,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 +152,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 +164,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 +195,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 +210,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 +224,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 +260,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+36
-100
@@ -1,29 +1,36 @@
|
|||||||
"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, resetMovementHistory} from "./movement_tracking.js";
|
||||||
import {registerSettings, settingsKey} from "./settings.js"
|
import {registerSettings, settingsKey} from "./settings.js"
|
||||||
|
import {recalculate} from "./socket.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,
|
||||||
|
recalculate,
|
||||||
|
resetMovementHistory,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
Hooks.once("ready", () => {
|
Hooks.once("ready", () => {
|
||||||
performMigrations()
|
performMigrations()
|
||||||
|
checkDependencies();
|
||||||
Hooks.callAll("dragRuler.ready", SpeedProvider)
|
Hooks.callAll("dragRuler.ready", SpeedProvider)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -38,6 +45,15 @@ Hooks.on("canvasReady", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Hooks.on("getCombatTrackerEntryContext", function (html, menu) {
|
||||||
|
const entry = {
|
||||||
|
name: "drag-ruler.resetMovementHistory",
|
||||||
|
icon: '<i class="fas fa-undo-alt"></i>',
|
||||||
|
callback: li => resetMovementHistory(ui.combat.combat, li.data('combatant-id')),
|
||||||
|
};
|
||||||
|
menu.splice(1, 0, entry);
|
||||||
|
});
|
||||||
|
|
||||||
function hookTokenDragHandlers() {
|
function hookTokenDragHandlers() {
|
||||||
const originalDragLeftStartHandler = Token.prototype._onDragLeftStart
|
const originalDragLeftStartHandler = Token.prototype._onDragLeftStart
|
||||||
Token.prototype._onDragLeftStart = function(event) {
|
Token.prototype._onDragLeftStart = function(event) {
|
||||||
@@ -66,51 +82,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 +103,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 +135,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 +150,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,64 +164,23 @@ 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
|
||||||
|
if (!this.draggedToken.actor) {
|
||||||
|
return this.color;
|
||||||
|
}
|
||||||
// Don't apply colors if the current user doesn't have at least observer permissions
|
// Don't apply colors if the current user doesn't have at least observer permissions
|
||||||
if (this.draggedToken.actor.permission < 2) {
|
if (this.draggedToken.actor.permission < 2) {
|
||||||
// If this is a pc and alwaysShowSpeedForPCs is enabled we show the color anyway
|
// If this is a pc and alwaysShowSpeedForPCs is enabled we show the color anyway
|
||||||
@@ -254,7 +188,9 @@ export function getColorForDistance(startDistance, subDistance=0) {
|
|||||||
return this.color
|
return this.color
|
||||||
}
|
}
|
||||||
const distance = startDistance + subDistance
|
const distance = startDistance + subDistance
|
||||||
const ranges = getRangesFromSpeedProvider(this.draggedToken)
|
if (!this.dragRulerRanges)
|
||||||
|
this.dragRulerRanges = getRangesFromSpeedProvider(this.draggedToken)
|
||||||
|
const ranges = this.dragRulerRanges;
|
||||||
if (ranges.length === 0)
|
if (ranges.length === 0)
|
||||||
return this.color
|
return this.color
|
||||||
const currentRange = ranges.reduce((minRange, currentRange) => {
|
const currentRange = ranges.reduce((minRange, currentRange) => {
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import {measureDistances} from "./compatibility.js";
|
||||||
|
import {recalculate, updateCombatantDragRulerFlags} from "./socket.js";
|
||||||
|
import {getTokenShape, zip} 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(tokens, tokenRays) {
|
||||||
|
const combat = game.combat;
|
||||||
|
if (!combat)
|
||||||
|
return;
|
||||||
|
if (!combat.started)
|
||||||
|
return;
|
||||||
|
if (!(tokens instanceof Array)) {
|
||||||
|
tokens = [tokens];
|
||||||
|
tokenRays = [tokenRays];
|
||||||
|
}
|
||||||
|
const updates = Array.from(zip(tokens, tokenRays)).map(([token, rays]) => calculateUpdate(combat, token, rays)).filter(Boolean);
|
||||||
|
await updateCombatantDragRulerFlags(combat, updates);
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateUpdate(combat, token, rays) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {_id: combatant._id, 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 ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function resetMovementHistory(combat, combatantId) {
|
||||||
|
const combatant = combat.getCombatant(combatantId);
|
||||||
|
const dragRulerFlags = combatant.flags.dragRuler;
|
||||||
|
if (!dragRulerFlags)
|
||||||
|
return;
|
||||||
|
dragRulerFlags.passedWaypoints = null;
|
||||||
|
dragRulerFlags.trackedRound = null;
|
||||||
|
dragRulerFlags.rulerState = null;
|
||||||
|
await updateCombatantDragRulerFlags(combat, [{_id: combatantId, dragRulerFlags}]);
|
||||||
|
recalculate();
|
||||||
|
}
|
||||||
+134
@@ -0,0 +1,134 @@
|
|||||||
|
import {measure} from "./foundry_imports.js"
|
||||||
|
import {getMovementHistory} from "./movement_tracking.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());
|
||||||
|
this.dragRulerRanges = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dragRulerClearWaypoints() {
|
||||||
|
this.waypoints = [];
|
||||||
|
this.labels.removeChildren().forEach(c => c.destroy());
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async dragRulerRecalculate(tokenIds) {
|
||||||
|
if (this._state !== Ruler.STATES.MEASURING)
|
||||||
|
return;
|
||||||
|
if (tokenIds && !tokenIds.includes(this.draggedToken.id))
|
||||||
|
return;
|
||||||
|
const waypoints = this.waypoints.filter(waypoint => !waypoint.isPrevious);
|
||||||
|
this.dragRulerClearWaypoints();
|
||||||
|
if (game.settings.get(settingsKey, "enableMovementHistory"))
|
||||||
|
this.dragRulerAddWaypointHistory(getMovementHistory(this.draggedToken));
|
||||||
|
for (const waypoint of waypoints) {
|
||||||
|
this.dragRulerAddWaypoint(waypoint, false);
|
||||||
|
}
|
||||||
|
this.measure(this.destination);
|
||||||
|
game.user.broadcastActivity({ruler: this});
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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",
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import {currentSpeedProvider} from "./api.js";
|
||||||
|
|
||||||
|
let socket;
|
||||||
|
|
||||||
|
Hooks.once("socketlib.ready", () => {
|
||||||
|
socket = socketlib.registerModule("drag-ruler");
|
||||||
|
socket.register("updateCombatantDragRulerFlags", _socketUpdateCombatantDragRulerFlags);
|
||||||
|
socket.register("recalculate", _socketRecalculate);
|
||||||
|
});
|
||||||
|
|
||||||
|
export function updateCombatantDragRulerFlags(combat, updates) {
|
||||||
|
const combatId = combat.id;
|
||||||
|
// TODO Check if canvas.tokens.get is still neccessary in future foundry versions
|
||||||
|
return socket.executeAsGM(_socketUpdateCombatantDragRulerFlags, combatId, updates)
|
||||||
|
.then(() => currentSpeedProvider.onMovementHistoryUpdate(updates.map(update => canvas.tokens.get(combat.getCombatant(update._id).token._id))));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function _socketUpdateCombatantDragRulerFlags(combatId, updates) {
|
||||||
|
const user = game.users.get(this.socketdata.userId);
|
||||||
|
const combat = game.combats.get(combatId);
|
||||||
|
const requestedUpdates = updates.length;
|
||||||
|
updates = updates.filter(update => {
|
||||||
|
const actor = combat.getCombatant(update._id).actor;
|
||||||
|
if (!actor)
|
||||||
|
return false;
|
||||||
|
return actor.hasPerm(user, "OWNER");
|
||||||
|
});
|
||||||
|
if (updates.length !== requestedUpdates) {
|
||||||
|
console.warn(`Some of the movement history updates requested by user '${game.users.get(this.socketdata.userId).name}' were not performed because the user lacks owner permissions for those tokens`);
|
||||||
|
}
|
||||||
|
updates = updates.map(update => {
|
||||||
|
return {_id: update._id, flags: {dragRuler: update.dragRulerFlags}};
|
||||||
|
});
|
||||||
|
await combat.updateEmbeddedEntity("Combatant", updates, {diff: false});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function recalculate(tokens) {
|
||||||
|
socket.executeForEveryone(_socketRecalculate, tokens ? tokens.map(token => token.id) : undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _socketRecalculate(tokenIds) {
|
||||||
|
return canvas.controls.ruler.dragRulerRecalculate(tokenIds);
|
||||||
|
}
|
||||||
+10
-2
@@ -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))
|
||||||
}
|
}
|
||||||
@@ -84,6 +84,14 @@ export class SpeedProvider {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This hook is being called after Drag Ruler has updated the movement history for one or more tokens.
|
||||||
|
* It'll receive an array of tokens that have been updated.
|
||||||
|
* If your speed provider is storing any additional values that are relevant for the movement history, this function should
|
||||||
|
* await until those updates have completed inside foundry.
|
||||||
|
*/
|
||||||
|
async onMovementHistoryUpdate(tokens) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the value that is currently set for the setting registered with the provided settingId.
|
* Returns the value that is currently set for the setting registered with the provided settingId.
|
||||||
*
|
*
|
||||||
@@ -124,7 +132,7 @@ export class GenericSpeedProvider extends SpeedProvider {
|
|||||||
const speedAttribute = this.getSetting("speedAttribute")
|
const speedAttribute = this.getSetting("speedAttribute")
|
||||||
if (!speedAttribute)
|
if (!speedAttribute)
|
||||||
return []
|
return []
|
||||||
const tokenSpeed = getProperty(token, speedAttribute)
|
const tokenSpeed = parseInt(getProperty(token, speedAttribute));
|
||||||
if (tokenSpeed === undefined) {
|
if (tokenSpeed === undefined) {
|
||||||
console.warn(`Drag Ruler (Generic Speed Provider) | The configured token speed attribute "${speedAttribute}" didn't return a speed value. To use colors based on drag distance set the setting to the correct value (or clear the box to disable this feature).`)
|
console.warn(`Drag Ruler (Generic Speed Provider) | The configured token speed attribute "${speedAttribute}" didn't return a speed value. To use colors based on drag distance set the setting to the correct value (or clear the box to disable this feature).`)
|
||||||
return []
|
return []
|
||||||
|
|||||||
@@ -1,21 +1,31 @@
|
|||||||
|
|
||||||
export function getDefaultSpeedAttribute() {
|
export function getDefaultSpeedAttribute() {
|
||||||
switch (game.system.id) {
|
switch (game.system.id) {
|
||||||
|
case "dcc":
|
||||||
|
return "actor.data.data.attributes.speed.value";
|
||||||
case "dnd5e":
|
case "dnd5e":
|
||||||
return "actor.data.data.attributes.movement.walk"
|
return "actor.data.data.attributes.movement.walk"
|
||||||
case "lancer":
|
case "lancer":
|
||||||
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 "shadowrun5e":
|
||||||
|
return "actor.data.data.movement.walk.value";
|
||||||
|
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 "dcc":
|
||||||
case "dnd5e":
|
case "dnd5e":
|
||||||
case "lancer":
|
case "lancer":
|
||||||
case "pf1":
|
case "pf1":
|
||||||
|
case "shadowrun5e":
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
+29
-6
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user