Compare commits

..

87 Commits

Author SHA1 Message Date
Manuel Vögele 1669edf757 Release v1.8.2 2021-09-17 18:43:05 +02:00
Manuel Vögele d1b5f7c39b Verified to work with 0.8.9 2021-09-17 15:26:58 +02:00
Argonius-Angelus 24f1e78092 Updated getFlag behaviour (#114)
Updated getFlag behaviour to use Document#getFlag rather than the depreciated PlaceableObject#getFlag. Due to the min core version being marked as 0.8.5, this won't cause a 0.7 compatability issue.
2021-09-15 07:48:00 +02:00
Joe 854903a314 Add Lancer system integration info to README (#111)
Adds a link to the lancer-speed-provider module to the systems with integrations section.
2021-09-08 17:21:00 +02:00
Joe 232eca74de Update lancer settings to system v1.0 (#110) 2021-09-06 09:42:59 +02:00
Manuel Vögele 9c7d4ead50 Release v1.8.1 2021-08-03 10:41:56 +02:00
Manuel Vögele a91c3c1b8c setSnapParameterOnOptions was still using the old parameters here (fixes #102) 2021-08-03 10:17:13 +02:00
José E. Lozano 99d057bffc Updated Spanish (#103) 2021-08-03 10:01:28 +02:00
Manuel Vögele 792d644803 Release v1.8.0 2021-08-02 16:03:17 +02:00
Manuel Vögele 63b13cbb34 Abort the drag when pressing ESC (resolves #73) 2021-08-02 15:51:12 +02:00
Manuel Vögele f1a153a2eb Only execute "toLowerCase" once 2021-08-02 15:47:10 +02:00
Manuel Vögele 59c12d017b When Ctrl+Z undoes a movement, remove that movement form the movement history as well (resolves #64) 2021-08-02 15:43:22 +02:00
Manuel Vögele 9552c25e6e Add a setting that disables Drag Ruler by default and only activates it once the button to add a waypoint is being pressed 2021-08-02 15:23:49 +02:00
Manuel Vögele 461e1d0818 Cancel scheudled measurements then the ruler is being cleared 2021-07-30 12:40:32 +02:00
Michael Clavell e1265ad6fb Add support for the Toggle Snap To Grid module (#97) 2021-07-30 10:16:27 +02:00
Manuel Vögele 93f0bf6369 Create LICENSE 2021-07-28 17:57:15 +02:00
Manuel Vögele 07d0c37fe9 Release v1.7.7 2021-07-08 22:02:57 +02:00
Manuel Vögele b8b97ac27a Update generic speed provider default for swade (resolves #93) 2021-07-08 22:01:29 +02:00
Manuel Vögele 0e9215e8bb Release v1.7.6 2021-07-08 21:58:01 +02:00
Manuel Vögele dc4355e0de Add spanish translation to readme 2021-07-08 21:57:23 +02:00
José E. Lozano cf7f84d9e4 Add spanish translation (#94) 2021-07-08 21:22:01 +02:00
Manuel Vögele fe8a3b7b39 Verified to work with 0.8.8 2021-07-08 17:37:59 +02:00
Manuel Vögele be9535acd3 Add korean translation to manifest and readme 2021-07-08 17:32:17 +02:00
José E. Lozano 530a16ca5d CoC support (#92) 2021-07-08 17:27:34 +02:00
drdwing 3b9fbac80d Korean translation (#88)
by KLO (discord : KLO#1490)
2021-07-08 10:39:50 +02:00
Manuel Vögele 0456fb0849 Release v1.7.5 2021-06-22 23:20:36 +02:00
Manuel Vögele 01d01f9887 Movement speeds can be floats (fixes #85) 2021-06-22 23:14:53 +02:00
Manuel Vögele 9042b79967 Release v1.7.4 2021-06-18 13:58:54 +02:00
Manuel Vögele 4159d20e18 Verified to work with Foundry 0.8.7 2021-06-18 13:39:10 +02:00
Manuel Vögele 3897577756 Allow the "Monk's Active Tile Triggers" module to interrupt a token movement early 2021-06-18 13:37:26 +02:00
irbian 5c29f401d6 Support D&D 3.5 (#76) 2021-05-27 01:09:12 +02:00
Manuel Vögele 1328d52f94 Send the info whether the dragged entity is a token or a template to the other players (fixes a snapping issue in other players clients) 2021-05-26 16:17:23 +02:00
Manuel Vögele eef05553c0 Release v1.7.3 2021-05-22 22:00:46 +02:00
Manuel Vögele 7ba89e4229 Update code for Foundry 0.8.5 2021-05-22 21:57:59 +02:00
Manuel Vögele d11d6385e2 Release v1.7.2 2021-05-21 14:43:13 +02:00
Manuel Vögele 4d2e4b7715 Calculate the correct snappig for measured templates when placing a waypoint 2021-05-21 14:41:33 +02:00
Manuel Vögele 3cbe41e2be If a measurement is being skipped because of the ruler's rate limiting, schedule the measurement for later to ensure the ruler sticks to the token 2021-05-21 14:38:54 +02:00
Manuel Vögele 732802c579 Fix typo in readme 2021-05-20 11:41:19 +02:00
Manuel Vögele 8bbb11ddaa Update the readme to document all the features included in 1.7.0 2021-05-19 23:20:12 +02:00
Manuel Vögele e6cee3477a Release v1.7.1 2021-05-18 22:28:44 +02:00
Manuel Vögele 7cee1e7af0 Rename a function call that was forgotten during the the renaming from Token to Entity (fixes #74) 2021-05-18 22:25:19 +02:00
Manuel Vögele a7811d6894 Release v1.7.0 2021-05-18 15:28:19 +02:00
Manuel Vögele e637f8d28f Fix typo in changelog 2021-05-18 15:17:04 +02:00
Manuel Vögele acd2e74064 Implement measurement template support (resolves #13) 2021-05-18 14:11:24 +02:00
Manuel Vögele fd81833583 Rename Ruler.draggedToken to Ruler.draggedEntity in preparation of measurement template suppport 2021-05-18 13:03:42 +02:00
Manuel Vögele a413d44795 Don't re-measure when the shift key is being pressed while a token is following the currently active ruler 2021-05-18 13:03:04 +02:00
Manuel Vögele e63ee1988d Deprecate the old (prior to 1.3.0) Drag Ruler API 2021-05-18 12:57:55 +02:00
Manuel Vögele 4aab6eec95 Improve the positioning of labels aroud the ruler graphic 2021-05-18 12:57:51 +02:00
Manuel Vögele 38b7df0248 Don't set Token._noAnimate. It has no effect and will be removed in future foundry versions
https://gitlab.com/foundrynet/foundryvtt/-/issues/5094
2021-05-18 07:23:56 +02:00
Manuel Vögele bbd30be80e Add Wasteland Ventures to the list of game systems with Drag Ruler integration 2021-05-16 18:47:59 +02:00
Manuel Vögele 78c96f9059 Merge branch 'master' into develop 2021-05-16 18:43:59 +02:00
Manuel Vögele d1367aa82e Release v1.6.5 2021-05-15 20:05:50 +02:00
Manuel Vögele 9f70e5bc27 If not token is selected, use the dragged token as selected token (fixes #70) 2021-05-15 20:04:22 +02:00
Manuel Vögele d2876671cf Add default values for Starfinder to the Generic Speed Provider 2021-05-12 16:20:05 +02:00
Manuel Vögele 19abc9eedc Add ironclaw2e to list of game systems with Drag Ruler integration 2021-05-11 19:20:04 +02:00
Manuel Vögele 286aaf7ada Properly store terrain ruler's state in the movement history 2021-05-11 19:01:51 +02:00
Manuel Vögele 85e167af45 Allow difficult terrain measurements on girdless maps 2021-05-11 18:21:39 +02:00
Manuel Vögele 5f0849f12d Mention ba0012e in changelog 2021-05-11 01:47:15 +02:00
CarnVanBeck ba0012e875 spelling correction (#68) 2021-05-11 01:45:28 +02:00
Manuel Vögele 334ccbc0f1 Release v1.6.4 2021-05-10 09:13:32 +02:00
Manuel Vögele 1242035744 Catch all errors thrown by a Speed Provider's getCostForStep function 2021-05-10 09:11:29 +02:00
Manuel Vögele 49542a68e5 Simplify foundry fixes 2021-05-06 09:00:50 +02:00
Chris Sharp 27a6235cc1 Mention WWII:OWB Operation WhiteBox system support in the readme 2021-05-05 23:40:40 +02:00
Manuel Vögele c26b39c984 Release v1.6.3 2021-05-05 16:01:52 +02:00
Manuel Vögele e8ab77a62e Update active rulers when the movement history changes 2021-05-05 16:01:13 +02:00
Manuel Vögele 7bed5abd0a Add default settings for shadowrun 5e 2021-05-03 19:39:22 +02:00
Manuel Vögele 4a96348659 Add "Tagmar RPG" and "Shadow of the Demon Lord" to the List of game systems with Drag Ruler integration 2021-05-03 16:00:34 +02:00
Manuel Vögele bed16de575 Release v1.6.2 2021-05-03 10:53:53 +02:00
Manuel Vögele 891bb1b4d8 undefined cannot be transmitted via json, so we use null to reset the movement history instead 2021-05-03 10:52:57 +02:00
Manuel Vögele 43f26088b5 Release v1.6.1 2021-04-30 10:52:00 +02:00
Manuel Vögele 39a0787c79 Add documentation about what needs to be awaited in SpeedProvider.onMovementHistoryUpdate 2021-04-30 10:26:37 +02:00
Manuel Vögele 3be898e49c First move the token, then store it's path. This ensures that getMovedDistance always returns a valid result. 2021-04-30 09:53:53 +02:00
Manuel Vögele 817662bf30 Send a proper token to onMovementHistoryUpdate 2021-04-30 09:51:05 +02:00
Manuel Vögele 56527ccf75 Add a hook that's called when the movement history is updated to the API 2021-04-30 00:10:45 +02:00
Manuel Vögele 6bf8083f7a Add a function for resetting the movement history to the public api 2021-04-30 00:00:15 +02:00
Manuel Vögele b5ea1f2284 Release v1.6.0 2021-04-29 14:04:14 +02:00
Manuel Vögele 6d9870bedb Add reference to the github issue in changelog 2021-04-29 13:24:33 +02:00
Manuel Vögele afbe5f9473 Don't animate tokens when they are dropped with the alt key being pressed (resolves #3) 2021-04-29 13:18:35 +02:00
Manuel Vögele bae0e43b7b Document 70b166d8 in changelog 2021-04-29 12:46:25 +02:00
Manuel Vögele 1acc012d65 Cache the output of SpeedProvider.getRanges during a drag to increase speed provider performance 2021-04-29 12:40:17 +02:00
Manuel Vögele 4671e6de51 Only allow users to update the movement history for tokens they own 2021-04-29 00:20:24 +02:00
Manuel Vögele f0ef109658 Track the movement of all tokens that are moved simultaneously in one batch (huge performance bump if many tokens are moved) 2021-04-29 00:03:05 +02:00
Manuel Vögele f0d1ef9d48 There might not actually be a dragged token when trying to end the measurement 2021-04-28 23:25:22 +02:00
Manuel Vögele d04ea9b0b7 Remove leftover lines from a refactor that didn't do anything 2021-04-28 12:11:58 +02:00
Manuel Vögele ba8ab9d473 After moving a token only end the measurement if no new token is being dragged 2021-04-27 21:17:28 +02:00
Manuel Vögele 2691720090 Update all moved tokens at once (provides a huge performance bump) 2021-04-27 20:28:44 +02:00
Manuel Vögele 70b166d844 Add a context menu entry that the gm can use to reset the movement history 2021-04-26 11:09:56 +02:00
21 changed files with 964 additions and 206 deletions
+136
View File
@@ -1,3 +1,139 @@
## 1.8.2
### Compatibility
- The generic speed provider defaults have been updated for lance 1.0 (thanks to BoltsJ!)
- Eliminated a deprecation warning when both Drag Ruler and Hex Size Support are enabled (thanks Argonius-Angelus!)
## 1.8.1
### Bugfixes
- Fixed a bug where the function that was bound to the spacebar key wouldn't work correctly when the "Toggle Snap To Grid" module was enabled
### Translation
- Updated Spanish translation (thanks to Viriato139ac#342)
## 1.8.0
### New features
- Pressing escape during a drag now cancels the drag
- Undoing a movement via Ctrl+Z will now also remove that movement from Drag Ruler's movement history
- Drag Ruler can now configured to stay disabled by default when a Token/Template is being dragged. In that case it will activate once the button to place a waypoint is being pressed.
### Bugfixes
- Fixed a bug that caused the ruler to snap to grid when a waypoint was deleted while shift was being pressed (thanks to Michael Clavell!)
- Fixed a bug that could leave behind a single waypoint on the canvas when canceling a dragging operation while moving the mouse
### Compatibility
- Drag Ruler is now compatible with the "Toggle Snap To Grid" module (thanks to Michael Clavell!)
## 1.7.7
### Compatibility
- Updated the default settings for the swade game system. The new default speed attribute points to a speed value that gets adjusted for wounds.
## 1.7.6
### Translation
- Added Korean translation (thanks to KLO#1490)
- Added Spanish translation (thanks to Viriato139ac#342)
### Compatibility
- Drag Ruler's Generic Speed Provider is now awar of good default values for the "Call of Cthulhu 7th edition (Unofficial)" game system
- Drag Ruler is now compatible with Foundry 0.8.8
## 1.7.5
### Bugfixes
- Decimal speeds (as often used in metric game systems) are no longer being rounded down (thanks to DarKDinDoN for diagnosing this bug)
## 1.7.4
### Bugfixes
- Fixed a bug where the ruler would wrongly snap to the grid center for other players when dragging a measurement template
### Compatibility
- Drag Ruler is now compatiblie with the "Monk's Active Tile Triggers" module
- Drag Ruler's Generic Speed Provider is now aware of good default values for the D&D 3.5 game system
- Drag Ruler is now compatible with Foundry 0.8.7
## 1.7.3
### Compatibility
- Drag Ruler is now compatible with Foundry 0.8.5
## 1.7.2
### Bugfixes
- Fixed a bug that prevented waypoints for measurement templates from snapping to any other point than a grid cell corner (or grid cell center on hex)
- Fixed a bug that could cause the ruler to not end up at the token's center (especially if the token is being moved very quickly and then stopped abruptly)
## 1.7.1
### Bugfixes
- Fixed a bug that prevented players from moving their tokens ([#74](https://github.com/manuelVo/foundryvtt-drag-ruler/issues/74))
## 1.7.0
**BREAKING** This update is incompatible with previous Terrain Ruler versions. If you're using Terrain Ruler, make sure you update Terrain Ruler to at least version 1.3.0.
### New features
- A ruler will now be shown when dragging measurement templates over the map ([#13](https://github.com/manuelVo/foundryvtt-drag-ruler/issues/13))
- Drag Ruler can now measure difficult terrain on gridless maps (if the Terrain Ruler module is installed and enabled)
- Improved the positioning of the labels around the ruler. The labels should now never overlap with the waypoint.
### Bugfixes
- Fixed a bug that sometimes measured diagonals incorrectly with the 5/10/5 grid rule
- Fixed a bug that would cause the ruler to re-measure when the shift key is being pressed or released while a token is moving
### Compatibility
- Drag Ruler's Generic Speed Provider is now aware of good default values for the Starfinder game system
### Translation
- Corrected typos in the german translation (thanks to CarnVanBeck!)
### API
- The old API that Drag Ruler offered prior to version 1.3.0 is now deprecated. Speed Providers that still use this API will continue to work for now, but will generate a warning in the console about the deprecation. All modules and game systems offered on the FoundryVTT website have already updated to the new API. If you see the deprecation warning, please consider updating to the current version of the respective system/module you're using.
## 1.6.5
### Bugfixes
- Drag Ruler no longer gets stuck if the user presses ESC during drag ([#70](https://github.com/manuelVo/foundryvtt-drag-ruler/issues/70))
## 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 ## 1.5.4
### Bugfixes ### 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)) - 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))
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Manuel Vögele
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+27 -16
View File
@@ -1,32 +1,35 @@
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/staebchenfisch) [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/staebchenfisch)
# Drag Ruler # Drag Ruler
This module shows a ruler when you drag a token to infrom you how far you've dragged the token from it's start point. Additionally, if you're using a grid, the spaces the token will travel though will be colored depending on your tokens speed. If you're using a gridless map the ruler color will change to convey this information. This module shows a ruler when you drag a token or measurement template to infrom you how far you've dragged it from it's start point. Additionally, if you're using a grid, the spaces the token will travel though will be colored depending on your tokens speed. By default three colors are being used: green for spaces that your token can reach by walking normally are colored green, spaces that can only be reached by dashing will be colored red and spaces that cannot be reached with the token's speed will be colored red. If you're using a gridless map the ruler color will change to convey this information.
![Drag Ruler being used on a square grids](https://raw.githubusercontent.com/manuelVo/foundryvtt-drag-ruler/709774b25f7dd818a90591165f74b3e6dbc788cc/media/basic_square.webp)
![Drag Ruler being used on a gridless scene](https://raw.githubusercontent.com/manuelVo/foundryvtt-drag-ruler/709774b25f7dd818a90591165f74b3e6dbc788cc/media/basic_gridless.webp)
![Drag Ruler while dragging a measurement template](https://raw.githubusercontent.com/manuelVo/foundryvtt-drag-ruler/709774b25f7dd818a90591165f74b3e6dbc788cc/media/measurement_template.webp)
## Path color ## Supports Tokens of all sizes
![Drag Ruler demonstration](https://raw.githubusercontent.com/manuelVo/foundryvtt-drag-ruler/5177746fbb4edb28b6ba09137247d142af575c47/media/drag_ruler.webp) Terrain ruler has excellent support for tokens of all sizes. The Ruler will always originate from the token's center and will always highlight all the squares that tokens move over. If the [Hex Token Size Support](https://foundryvtt.com/packages/hex-size-support) is installed this is also true for large tokens on hex scenes.
The token has a speed of 30ft. All squares on the path within that range are colored in the players color. If the token is running it can cover double that range (this can be changed in the settings). All Squares on the path that can only be reached while running are colored in yellow. Squares on the path that the token cannot possibly reached at regular speeds are colored red. This coloring behavior can be tweaked in the settings and can be overridden by modules and game systems to provide more granular, game system specific control. For more information on how to do this see the [API](#api) section of this document. ![Drag Ruler being used with a large token on a square grid](https://raw.githubusercontent.com/manuelVo/foundryvtt-drag-ruler/709774b25f7dd818a90591165f74b3e6dbc788cc/media/large_token_square.webp)
![Drag Ruler being used with a large token on a hex grid](https://raw.githubusercontent.com/manuelVo/foundryvtt-drag-ruler/709774b25f7dd818a90591165f74b3e6dbc788cc/media/large_token_hex.webp)
## Waypoints ## Difficult Terrain support
You can add waypoints to the path by pressing spacebar while you drag the token. To remove a placed waypoint press the right mouse button. **To use support for difficult terrain you must install the [Terrain Ruler](https://foundryvtt.com/packages/terrain-ruler) module**
![Demonstration of Waypoints](https://raw.githubusercontent.com/manuelVo/foundryvtt-drag-ruler/5177746fbb4edb28b6ba09137247d142af575c47/media/waypoints.webp) With the Terrain Ruler module installed, Drag Ruler is able to take difficult terrain that was placed down using the [Enhanced Terrain Layer](https://foundryvtt.com/packages/enhanced-terrain-layer) or [TerrainLayer](https://foundryvtt.com/packages/TerrainLayer) module into account.
## Difficult Terrain ![Drag Ruler being used to measure distances over difficult terrain on square grid](https://raw.githubusercontent.com/manuelVo/foundryvtt-drag-ruler/709774b25f7dd818a90591165f74b3e6dbc788cc/media/difficult_terrain_square.webp)
**To use support for difficult terrain you must install the [Terrain Ruler module](https://foundryvtt.com/packages/terrain-ruler)** ![Drag Ruler being used to measure distances over difficult terrain on gridless scenes](https://raw.githubusercontent.com/manuelVo/foundryvtt-drag-ruler/709774b25f7dd818a90591165f74b3e6dbc788cc/media/difficult_terrain_gridless.webp)
With the Terrain Ruler module installed, Drag Ruler is able to take difficult terrain that was placed with the [TerrainLayer module](https://foundryvtt.com/packages/TerrainLayer/) into account when measuring distances.
![Demonstration of Difficult Terrain support](https://raw.githubusercontent.com/manuelVo/foundryvtt-drag-ruler/bb8ac5d1acb9d6374d06f7e9c8e2c2dd61192d56/media/terrain_layer.webp)
## Why would I want to use this instead of ShowDragDistance? ## Movement history (optional)
ShowDragDistance isn't maintained anymore. This means that it is at risk to stop working with every foundry update. In fact this process has already begun. As of Foundry Version 0.7.9 ShowDragDistance doesn't work anymore on gridless maps. Drag Ruler on the other hand is fully compatible with the current Foundry release and I'll continue updating it for future foundry releases for the forseeable future. *This feature can be disabled in the settings if you don't like it*
In addition Drag Ruler provides more flexibility for game systems and modules via it's api to provide an experience that fits the rules of the game system that you are playing best. During combat, Drag Ruler will remember the path a token has taken during it's turn. When the token is being picked up again, Drag Ruler will continue measuring where it has left off. The path of the previous movement will be dipslayed in a faded color.
![Demonstration of the Movement History](https://raw.githubusercontent.com/manuelVo/foundryvtt-drag-ruler/709774b25f7dd818a90591165f74b3e6dbc788cc/media/movement_history.webp)
## Game systems with Drag Ruler integration ## Game systems with Drag Ruler integration
@@ -34,9 +37,15 @@ Drag Ruler will work with all Foundry VTT game systems. However some game system
The game systems that offer Drag Ruler integration are: 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)
- Ironclaw Second Edition (starting with version 0.2.2)
- Lancer (via the module [Lancer Speed Provider](https://foundryvtt.com/packages/lancer-speed-provider))
- 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)
- Wasteland Ventures (starting with version 0.1.0)
- WWII:OWB (starting with version 1.0.4)
## Translations ## Translations
@@ -45,6 +54,8 @@ Drag Ruler is available in the follwing languages:
- English - English
- German - German
- Japanese (thanks to touge) - Japanese (thanks to touge)
- Korean (thanks to KLO#1490)
- Spanish (thanks to Viriato139ac#342)
## API ## API
*Audience: This paragraph is intended for module and system devleopers that want to add more complex behavior to Drag Ruler. If you just want to use this plugins features skip this paragraph.* *Audience: This paragraph is intended for module and system devleopers that want to add more complex behavior to Drag Ruler. If you just want to use this plugins features skip this paragraph.*
+10 -5
View File
@@ -4,7 +4,7 @@
"ok": "OK", "ok": "OK",
"terrain-ruler": { "terrain-ruler": {
"title": "Wie man schwieriges Gelände mit Drag Ruler misst", "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.", "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 schwieriges Gelände in den Messungen zu berücksichtigen.",
"neverShowAgain": "Zeige dies nie wieder an" "neverShowAgain": "Zeige dies nie wieder an"
}, },
"socketlib": { "socketlib": {
@@ -12,6 +12,7 @@
"text": "Drag Ruler benötigt das socketlib-Modul, um korrekt zu funktionieren. Bitte aktiviere das socketlib-Modul in dieser Welt." "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": { "genericSpeedProvider": {
"settings": { "settings": {
"dashMultiplier": { "dashMultiplier": {
@@ -33,12 +34,16 @@
"name": "Geschwindigkeit von Spielercharakteren für jeden anzeigen", "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." "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."
}, },
"autoStartMeasurement": {
"name": "Automatisch mit Messen beginnen",
"hint": "Wenn diese Option aktiviert ist beginnt Drag Ruler mit dem Messen sobald das Token bewegt wird. Wenn die Option deaktiviert ist bleibt Drag Ruler deaktiviert bis der Knopf zum platzieren eines Wegpunkts betätigt wird."
},
"enableMovementHistory": { "enableMovementHistory": {
"name": "Bewegungsverlauf während des Kampfes aktivieren", "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." "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": { "showGMRulerToPlayers": {
"name": "Lineal des Sipelleiters bei Spielern anzeigen", "name": "Lineal des Spielleiters bei Spielern anzeigen",
"hint": "Wenn diese Option deaktiviert ist wird das Lineal des Spielleiters bei den anderen Spielern nicht angezeigt." "hint": "Wenn diese Option deaktiviert ist wird das Lineal des Spielleiters bei den anderen Spielern nicht angezeigt."
}, },
"speedProviderSettings": { "speedProviderSettings": {
@@ -52,7 +57,7 @@
}, },
"activeProvider": { "activeProvider": {
"name": "Verwendete Spielsystemintegration", "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." "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 stehenden Einstellungen ändern."
}, },
"noSettings": "Diese Spielsystemintegration hat keine Einstellmöglichkeiten", "noSettings": "Diese Spielsystemintegration hat keine Einstellmöglichkeiten",
"color": { "color": {
@@ -65,7 +70,7 @@
}, },
"speedProvider": { "speedProvider": {
"name": "Verwendete Spielsystemintegration", "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.", "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ängig, welche Spielsystemintegration hier ausgewählt wurde.",
"choices": { "choices": {
"module": "Modul {name}", "module": "Modul {name}",
"native": "Generisch", "native": "Generisch",
@@ -75,7 +80,7 @@
}, },
"swapSpacebarRightClick": { "swapSpacebarRightClick": {
"name": "Leertaste und Rechtsklick tauschen", "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." "hint": "Die Funktionen der Leertaste und des Rechtsklicks sind, während eine Spielfigur bewegt wird, vertauscht. Wenn diese Option aktiviert wird können mit Rechtsklick Wegpunkte gesetzt werden und mit der Leertaste werden sie wieder gelöscht."
} }
} }
} }
+5
View File
@@ -12,6 +12,7 @@
"text": "Drag Ruler requires the socketlib module to work properly. Please activate the socketlib module in this world." "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": {
@@ -33,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."
}, },
"autoStartMeasurement": {
"name": "Automatically start measuring",
"hint": "If enabled, Drag Ruler will start measuring as soon as the token is being dragged. If disabled, Drag Ruler will remain inactive and will only start measuring once the button to add a waypoint is being pressed."
},
"enableMovementHistory": { "enableMovementHistory": {
"name": "Enable movement history during combat", "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." "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."
+91
View File
@@ -0,0 +1,91 @@
{
"drag-ruler": {
"I18N": {
"LANGUAGE": "Español",
"MAINTAINERS": "@Viriato139ac#0342"
},
"dependencies": {
"ok": "OK",
"terrain-ruler": {
"title": "Cómo medir a través de terreno difícil con 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": "No volver a mostrar"
},
"socketlib": {
"title": "El módulo Socketlib no está instalado",
"text": "Drag Ruler necesita para su funcionamiento correcto que instale socketlib. Por favor, active socketlib en este mundo"
}
},
"resetMovementHistory": "Reiniciar historial de movimiento",
"genericSpeedProvider": {
"settings": {
"dashMultiplier": {
"name": "Multiplicador por correr",
"hint": "Se puede usar para aplicar a los iconos una velocidad secundaria que use un color distinto en la medición de la ruta a seguir. Establece el valor a 0 para deshabilitar esta velocidad secundaria"
},
"speedAttribute": {
"name": "Attributo de velocidad",
"hint": "El atributo que define la velocidad de movimiento andando del icono. Se usa para los códigos de colores al medir la ruta a seguir"
}
},
"speeds": {
"walk": "caminar",
"dash": "correr"
}
},
"settings": {
"alwaysShowSpeedForPCs": {
"name": "Mostrar velocidad de los PJs a todo el mundo",
"hint": "Si se habilita, se mostrará a todo el mundo los códigos de colores de las rutas de los PJs, incluso si no tienen permisos de observador para ese personaje"
},
"autoStartMeasurement": {
"name": "Comenzar a medir automáticamente",
"hint": "Si se habilita, Drag Ruler comenzará a medir en cuanto se comience a arrastrar un icono. Si se deshabilita, Drag Ruler permanecerá inactivo y comenzará a medir únicamente cuando se presione el botón configurado para añadir un nuevo punto de ruta"
},
"enableMovementHistory": {
"name": "Habilitar historial de movimiento durante el combate",
"hint": "Si se habilita, Drag Ruler recordará la ruta que ha seguido un icono en su turno y la mostrará al seleccionarlo de nuevo"
},
"showGMRulerToPlayers": {
"name": "Mostrar regla del GM a los jugadores",
"hint": "Si se deshabilita, no se mostrará la regla de medición de los GMs al resto de usuarios"
},
"speedProviderSettings": {
"name": "Proveedor de configuración de velocidad",
"hint": "El proveedor de ajustes de velocidad contiene todos los ajustes específicos de cada sistema",
"button": "Proveedor de configuración de velocidad",
"windowTitle": "Proveedor de configuración de velocidad",
"headers": {
"speedProvider": "Proveedor de velocidad",
"speedProviderSettings": "Configuraciones específicas del proveedor de velocidad"
},
"activeProvider": {
"name": "Proveedor de velocidad actualmente activo",
"hint": "La configuración que se muestra más abajo depende del proveedor de velocidad activo. Si el GM selecciona un proveedor distinto, los ajustes disponibles puede que cambien"
},
"noSettings": "Este proveedor de velocidad no dispone de ninguna opción de configuración",
"color": {
"name": "Color para {colorName}",
"hint": "Color que se usará para establecer el tono de las cuadrículas que se encuentran dentro del rango {colorName}",
"unreachable": {
"name": "inalcanzable",
"hint": "Color que se usará para las cuadrículas más allá de la distancia máxima de movimiento del icono"
}
},
"speedProvider": {
"name": "Proveedor de configuración de velocidad",
"hint": "Seleccione quien suministra la información de la velocidad de los iconos a la hora de colorear la ruta a seguir. Drag Ruler incluye un proveedor genérico que ofrece una funcionalidad básica y que probablemente funcione para casi todos los sistemas si se usa de forma correcta. Se pueden añadir otros proveedores de velocidad al instalar sistemas o módulos. Seleccionar un proveedor distinto al genérico asegurará una mejor integración con las reglas del sistema que está usando. Las opciones siguientes dependerán del proveedor que seleccione",
"choices": {
"module": "Módulo {name}",
"native": "Genérico",
"system": "Sistema {name}"
}
}
},
"swapSpacebarRightClick": {
"name": "Intercambiar barra espaciadora y clic-derecho",
"hint": "Cambia el funcionamiento de la barra espaciadora y el clic-derecho del ratón mientras se arrastra un icono. Si está habilitado, clic-derecho establecerá puntos en la ruta y la barra espaciadora los borrará"
}
}
}
}
+83
View File
@@ -0,0 +1,83 @@
{
"drag-ruler": {
"dependencies": {
"ok": "OK",
"terrain-ruler": {
"title": "Drag Ruler를 사용한 험지 측정 방법",
"text": "{moduleName} 모듈을 활성화했습니다. Drag Ruler는 해당 모듈을 사용하여 험지를 측정할 수 있습니다. 이렇게 하려면 Terrain Ruler 모듈을 활성화하십시오. Terrain Ruler 모듈을 설치하고 Drag Ruler를 활성화하면 자동적으로 험지를 측정합니다.",
"neverShowAgain": "다시 표시하지 않음"
},
"socketlib": {
"title": "Socketlib 모듈 비탐지",
"text": "Drag Ruler는 Socketlib 모듈이 있어야 합니다. 이 월드에서 해당 모듈을 활성화하십시오."
}
},
"resetMovementHistory": "이동 내역 리셋",
"genericSpeedProvider": {
"settings": {
"dashMultiplier": {
"name": "질주 승수",
"hint": "측정된 경로에 색을 입힐 때 추가 속도를 제공하는 데 사용할 수 있다. 추가 속도를 비활성화하려면 이 값을 0으로 설정한다."
},
"speedAttribute": {
"name": "속도 어트리뷰트",
"hint": "토큰의 이동속도를 정의하는 속성이다. 측정 경로에 색을 입힐 때 사용한다."
}
},
"speeds": {
"walk": "보행",
"dash": "질주"
}
},
"settings": {
"alwaysShowSpeedForPCs": {
"name": "PC 속도를 모두에게 표시",
"hint": "활성화시 플레이어 캐릭터에 대한 관찰자 권한이 없더라도 액터 속도에 따라 색상이 모든 유저에게 표시된다."
},
"enableMovementHistory": {
"name": "전투 중 이동 기록 사용",
"hint": "활성화시 Drag Ruler는 해당 턴에 토큰이 이동한 경로를 기억하고 다시 이동시킬 때 표시한다."
},
"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": "색을 입힐 동안 사용할 토큰에 대한 속도 정보 제공자를 선택한다. Drag Ruler는 기본 기능을 제공하며 올바르게 구성된 경우 모든 게임 시스템에서 작동하는 일반적 속도 공급자를 제공한다. 게임 시스템과 설치된 모듈을 통해 더 많은 속도 공급자를 이용할 수 있다. 일반 속도 공급자와는 다른 속도 공급자를 선택하면 사용 중인 게임 시스템의 규칙에 더 잘 통합될 수 있다.아래 옵션은 여기에서 선택한 속도 공급자에 따라 다르다.",
"choices": {
"module": "모듈 {name}",
"native": "포괄적",
"system": "시스템 {name}"
}
}
},
"swapSpacebarRightClick": {
"name": "스페이스바와 우클릭 스왑",
"hint": "드래그 도중 스페이스바의 기능을 우클릭 기능과 바꾼다. 활성화된 경우 드래그시 우클릭을 사용하면 경유지가 배치되고, 스페이스바가 해당 경유지를 삭제한다."
}
}
}
}
+14 -4
View File
@@ -2,9 +2,9 @@
"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.5.4", "version": "1.8.2",
"minimumCoreVersion" : "0.7.9", "minimumCoreVersion" : "0.8.5",
"compatibleCoreVersion" : "0.7.9", "compatibleCoreVersion" : "0.8.9",
"authors": [ "authors": [
{ {
"name": "Manuel Vögele", "name": "Manuel Vögele",
@@ -35,10 +35,20 @@
"name": "日本語", "name": "日本語",
"path": "lang/ja.json" "path": "lang/ja.json"
}, },
{
"lang": "ko",
"name": "한국어",
"path": "lang/ko.json"
},
{ {
"lang": "zh-tw", "lang": "zh-tw",
"name": "正體中文", "name": "正體中文",
"path": "lang/zh-tw.json" "path": "lang/zh-tw.json"
},
{
"lang": "es",
"name": "Español",
"path": "lang/es.json"
} }
], ],
"dependencies": [ "dependencies": [
@@ -49,7 +59,7 @@
], ],
"socket": true, "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.5.4.zip", "download": "https://github.com/manuelVo/foundryvtt-drag-ruler/archive/v1.8.2.zip",
"manifest": "https://raw.githubusercontent.com/manuelVo/foundryvtt-drag-ruler/master/module.json", "manifest": "https://raw.githubusercontent.com/manuelVo/foundryvtt-drag-ruler/master/module.json",
"readme": "https://github.com/manuelVo/foundryvtt-drag-ruler/blob/master/README.md", "readme": "https://github.com/manuelVo/foundryvtt-drag-ruler/blob/master/README.md",
"changelog": "https://github.com/manuelVo/foundryvtt-drag-ruler/blob/master/CHANGELOG.md", "changelog": "https://github.com/manuelVo/foundryvtt-drag-ruler/blob/master/CHANGELOG.md",
+13 -4
View File
@@ -14,6 +14,9 @@ function register(module, type, speedProvider) {
providerInstance = new speedProvider(id) providerInstance = new speedProvider(id)
} }
else { else {
console.warn(`Drag Ruler | The ${type} '${module.id}' uses the old, deprecated version of the Drag Ruler API. ` +
"That old API will be removed in a future Drag Ruler version. " +
`Please update the ${type} ${module.id} to stay compatible with future Drag Ruler versions.`);
speedProvider.id = id speedProvider.id = id
speedProvider.usesRuler = () => true speedProvider.usesRuler = () => true
providerInstance = speedProvider providerInstance = speedProvider
@@ -100,11 +103,17 @@ export function getUnreachableColorFromSpeedProvider() {
} }
} }
export function getCostFromSpeedProvider(token, area) { export function getCostFromSpeedProvider(token, area, options) {
if (currentSpeedProvider instanceof Function) { try {
return SpeedProvider.prototype.getCostForStep.call(undefined, token, area); if (currentSpeedProvider instanceof Function) {
return SpeedProvider.prototype.getCostForStep.call(undefined, token, area, options);
}
return currentSpeedProvider.getCostForStep(token, area, options);
}
catch (e) {
console.error(e);
return 1;
} }
return currentSpeedProvider.getCostForStep(token, area);
} }
export function getMovedDistanceFromToken(token) { export function getMovedDistanceFromToken(token) {
+11 -7
View File
@@ -15,22 +15,27 @@ export function highlightMeasurementTerrainRuler(ray, startDistance, tokenShape=
} }
} }
export function measureDistances(segments, token, shape, gridSpaces=true, options={}) { export function measureDistances(segments, entity, shape, options={}) {
const opts = duplicate(options) const opts = duplicate(options)
opts.gridSpaces = gridSpaces;
const terrainRulerAvailable = game.modules.get("terrain-ruler")?.active && canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS; if (opts.enableTerrainRuler) {
if (terrainRulerAvailable) { opts.gridSpaces = true;
const firstNewSegmentIndex = segments.findIndex(segment => !segment.ray.dragRulerVisitedSpaces); const firstNewSegmentIndex = segments.findIndex(segment => !segment.ray.dragRulerVisitedSpaces);
const previousSegments = segments.slice(0, firstNewSegmentIndex); const previousSegments = segments.slice(0, firstNewSegmentIndex);
const newSegments = segments.slice(firstNewSegmentIndex); const newSegments = segments.slice(firstNewSegmentIndex);
const distances = previousSegments.map(segment => segment.ray.dragRulerVisitedSpaces[segment.ray.dragRulerVisitedSpaces.length - 1].distance); const distances = previousSegments.map(segment => segment.ray.dragRulerVisitedSpaces[segment.ray.dragRulerVisitedSpaces.length - 1].distance);
previousSegments.forEach(segment => segment.ray.terrainRulerVisitedSpaces = duplicate(segment.ray.dragRulerVisitedSpaces)); previousSegments.forEach(segment => segment.ray.terrainRulerVisitedSpaces = duplicate(segment.ray.dragRulerVisitedSpaces));
opts.costFunction = (x, y) => getCostFromSpeedProvider(token, getAreaFromPositionAndShape({x, y}, shape), {x, y}); opts.costFunction = (x, y, costOptions={}) => { return getCostFromSpeedProvider(entity, getAreaFromPositionAndShape({x, y}, shape), costOptions); }
if (previousSegments.length > 0) if (previousSegments.length > 0)
opts.terrainRulerInitialState = previousSegments[previousSegments.length - 1]; opts.terrainRulerInitialState = previousSegments[previousSegments.length - 1].ray.dragRulerFinalState;
return distances.concat(terrainRuler.measureDistances(newSegments, opts)); return distances.concat(terrainRuler.measureDistances(newSegments, opts));
} }
else { else {
// If another module wants to enable grid measurements but disable grid highlighting,
// manually set the *duplicate* option's gridSpaces value to true for the Foundry logic to work properly
if(!opts.ignoreGrid) {
opts.gridSpaces = true;
}
return canvas.grid.measureDistances(segments, opts); return canvas.grid.measureDistances(segments, opts);
} }
} }
@@ -83,4 +88,3 @@ export function checkDependencies() {
} }
} }
} }
+2 -5
View File
@@ -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
+132 -57
View File
@@ -1,25 +1,30 @@
import {highlightMeasurementTerrainRuler, measureDistances} from "./compatibility.js"; import {highlightMeasurementTerrainRuler, measureDistances} from "./compatibility.js";
import {getGridPositionFromPixels} from "./foundry_fixes.js"; import {getGridPositionFromPixels} from "./foundry_fixes.js";
import {Line} from "./geometry.js";
import {getColorForDistance} from "./main.js" import {getColorForDistance} from "./main.js"
import {trackRays} from "./movement_tracking.js" import {trackRays} from "./movement_tracking.js"
import {applyTokenSizeOffset, getSnapPointForToken, getTokenShape, highlightTokenShape, zip} from "./util.js"; import {recalculate} from "./socket.js";
import {applyTokenSizeOffset, getSnapPointForEntity, 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 moveEntities(draggedEntity, selectedEntities) {
let wasPaused = game.paused; let wasPaused = game.paused;
if (wasPaused && !game.user.isGM) { if (wasPaused && !game.user.isGM) {
ui.notifications.warn(game.i18n.localize("GAME.PausedWarning")); ui.notifications.warn(game.i18n.localize("GAME.PausedWarning"));
return false; return false;
} }
if (!this.visible || !this.destination) return false; if (!this.visible || !this.destination) return false;
if (!draggedToken) return; if (!draggedEntity) return;
// Wait until all scheduled measurements are done
await this.deferredMeasurementPromise;
// 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.constructor.dragRulerGetRaysFromWaypoints(this.waypoints, this.destination); const rays = this.constructor.dragRulerGetRaysFromWaypoints(this.waypoints, this.destination);
if (!game.user.isGM) { if (!game.user.isGM && draggedEntity instanceof Token) {
const hasCollision = selectedTokens.some(token => { const hasCollision = selectedEntities.some(token => {
const offset = calculateTokenOffset(token, draggedToken) const offset = calculateEntityOffset(token, draggedEntity);
const offsetRays = rays.filter(ray => !ray.isPrevious).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));
}) })
@@ -33,48 +38,71 @@ 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 animateEntities.call(this, selectedEntities, draggedEntity, 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
this._endMeasurement(); if (this.draggedEntity?.id === draggedEntity.id)
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 animateEntities(entities, draggedEntity, draggedRays, wasPaused) {
const offsetRays = rays.filter(r => !r.isPrevious).map(ray => applyOffsetToRay(ray, tokenOffset)); const newRays = draggedRays.filter(r => !r.isPrevious);
trackRays(token, offsetRays); const entityAnimationData = entities.map(entity => {
const entityOffset = calculateEntityOffset(entity, draggedEntity);
const offsetRays = newRays.map(ray => applyOffsetToRay(ray, entityOffset));
// 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 firstWaypoint = this.waypoints.find(w => !w.isPrevious); const firstWaypoint = this.waypoints.find(w => !w.isPrevious);
const origin = [firstWaypoint.x + tokenOffset.x, firstWaypoint.y + tokenOffset.y]; const origin = [firstWaypoint.x + entityOffset.x, firstWaypoint.y + entityOffset.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 = entity.data.x - origin[0];
dy = token.data.y - origin[1] dy = entity.data.y - origin[1];
} }
else { else {
dx = token.data.x - origin[0] dx = entity.data.x - origin[0];
dy = token.data.y - origin[1] dy = entity.data.y - origin[1];
} }
token._noAnimate = true; return {entity, rays: offsetRays, dx, dy};
for (let r of offsetRays) { });
const isToken = draggedEntity instanceof Token;
const animate = isToken && !game.keyboard.isDown("Alt");
const startWaypoint = animate ? 0 : entityAnimationData[0].rays.length - 1;
// This is a flag of the "Monk's Active Tile Triggers" module that signals that the movement should be cancelled early
this.cancelMovement = false;
for (let i = startWaypoint;i < entityAnimationData[0].rays.length; i++) {
if (!wasPaused && game.paused) break; if (!wasPaused && game.paused) break;
const dest = [r.B.x, r.B.y]; const entityPaths = entityAnimationData.map(({entity, rays, dx, dy}) => {
const path = new Ray({ x: token.x, y: token.y }, { x: dest[0] + dx, y: dest[1] + dy }); const ray = rays[i];
await token.update(path.B); const dest = [ray.B.x, ray.B.y];
await token.animateMovement(path); const path = new Ray({x: entity.x, y: entity.y}, {x: dest[0] + dx, y: dest[1] + dy});
return {entity, path};
});
const updates = entityPaths.map(({entity, path}) => {
return {x: path.B.x, y: path.B.y, _id: entity.id};
});
await draggedEntity.scene.updateEmbeddedDocuments(draggedEntity.constructor.embeddedName, updates, {animate});
if (animate)
await Promise.all(entityPaths.map(({entity, path}) => entity.animateMovement(path)));
// This is a flag of the "Monk's Active Tile Triggers" module that signals that the movement should be cancelled early
if (this.cancelMovement) {
entityAnimationData.forEach(ead => ead.rays = ead.rays.slice(0, i + 1));
break;
}
} }
token._noAnimate = false; if (isToken)
trackRays(entities, entityAnimationData.map(({rays}) => rays)).then(() => recalculate(entities));
} }
function calculateTokenOffset(tokenA, tokenB) { function calculateEntityOffset(entityA, entityB) {
return {x: tokenA.data.x - tokenB.data.x, y: tokenA.data.y - tokenB.data.y} return {x: entityA.data.x - entityB.data.x, y: entityA.data.y - entityB.data.y};
} }
function applyOffsetToRay(ray, offset) { function applyOffsetToRay(ray, offset) {
@@ -88,8 +116,6 @@ export function onMouseMove(event) {
if (this._state === Ruler.STATES.MOVING) return; if (this._state === Ruler.STATES.MOVING) return;
// Extract event data // Extract event data
const mt = event._measureTime || 0;
const originalEvent = event.data.originalEvent;
const destination = {x: event.data.destination.x + this.rulerOffset.x, y: event.data.destination.y + this.rulerOffset.y} const destination = {x: event.data.destination.x + this.rulerOffset.x, y: event.data.destination.y + this.rulerOffset.y}
// Hide any existing Token HUD // Hide any existing Token HUD
@@ -97,28 +123,59 @@ export function onMouseMove(event) {
delete event.data.hudState; delete event.data.hudState;
// Draw measurement updates // Draw measurement updates
if (Date.now() - mt > 50) { scheduleMeasurement.call(this, destination, event);
}
function scheduleMeasurement(destination, event) {
const measurementInterval = 50;
const mt = event._measureTime || 0;
const originalEvent = event.data.originalEvent;
if (Date.now() - mt > measurementInterval) {
this.measure(destination, {snap: !originalEvent.shiftKey}); this.measure(destination, {snap: !originalEvent.shiftKey});
event._measureTime = Date.now(); event._measureTime = Date.now();
this._state = Ruler.STATES.MEASURING; this._state = Ruler.STATES.MEASURING;
cancelScheduledMeasurement.call(this);
}
else {
this.deferredMeasurementData = {destination, event};
if (!this.deferredMeasurementTimeout) {
this.deferredMeasurementPromise = new Promise((resolve, reject) => this.deferredMeasurementResolve = resolve);
this.deferredMeasurementTimeout = window.setTimeout(() => scheduleMeasurement.call(this, this.deferredMeasurementData.destination, this.deferredMeasurementData.event), measurementInterval);
}
} }
} }
export function cancelScheduledMeasurement() {
window.clearTimeout(this.deferredMeasurementTimeout);
this.deferredMeasurementTimeout = undefined;
this.deferredMeasurementResolve?.();
}
// This is a modified version of Ruler.measure form foundry 0.7.9 // This is a modified version of Ruler.measure form foundry 0.7.9
export function measure(destination, {gridSpaces=true, snap=false} = {}) { export function measure(destination, options={}) {
if (this.isDragRuler && !this.draggedToken.isVisible) const isToken = this.draggedEntity instanceof Token;
if (isToken && !this.draggedEntity.isVisible)
return [] return []
if (snap) if (options.snap) {
destination = getSnapPointForToken(destination.x, destination.y, this.draggedToken) destination = getSnapPointForEntity(destination.x, destination.y, this.draggedEntity);
}
const terrainRulerAvailable = game.modules.get("terrain-ruler")?.active && canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS; if(options.gridSpaces === undefined) {
options.gridSpaces = canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS;
}
if(options.ignoreGrid === undefined) {
options.ignoreGrid = false;
}
options.enableTerrainRuler = isToken && game.modules.get("terrain-ruler")?.active && (!game.modules.get("TerrainLayer")?.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 = isToken ? applyTokenSizeOffset(waypoints, this.draggedEntity) : duplicate(waypoints);
// Foundries native ruler requires the waypoints to sit in the dead center of the square to work properly // Foundries native ruler requires the waypoints to sit in the dead center of the square to work properly
if (!terrainRulerAvailable) if (!options.enableTerrainRuler && !options.ignoreGrid)
centeredWaypoints.forEach(w => [w.x, w.y] = canvas.grid.getCenter(w.x, w.y)); centeredWaypoints.forEach(w => [w.x, w.y] = canvas.grid.getCenter(w.x, w.y));
const r = this.ruler; const r = this.ruler;
@@ -138,6 +195,8 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
centeredRay.isPrevious = ray.isPrevious; centeredRay.isPrevious = ray.isPrevious;
ray.dragRulerVisitedSpaces = origin.dragRulerVisitedSpaces; ray.dragRulerVisitedSpaces = origin.dragRulerVisitedSpaces;
centeredRay.dragRulerVisitedSpaces = ray.dragRulerVisitedSpaces; centeredRay.dragRulerVisitedSpaces = ray.dragRulerVisitedSpaces;
ray.dragRulerFinalState = origin.dragRulerFinalState;
centeredRay.dragRulerFinalState = ray.dragRulerFinalState;
if (ray.distance < 10) { if (ray.distance < 10) {
if (label) label.visible = false; if (label) label.visible = false;
continue; continue;
@@ -147,10 +206,10 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
} }
const shape = getTokenShape(this.draggedToken) const shape = isToken ? getTokenShape(this.draggedEntity) : null;
// Compute measured distance // Compute measured distance
const distances = measureDistances(centeredSegments, this.draggedToken, shape, gridSpaces); const distances = measureDistances(centeredSegments, this.draggedEntity, shape, options);
let totalDistance = 0; let totalDistance = 0;
for (let [i, d] of distances.entries()) { for (let [i, d] of distances.entries()) {
@@ -169,7 +228,7 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
// Draw measured path // Draw measured path
r.clear(); r.clear();
let rulerColor let rulerColor
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) if (!options.gridSpaces || canvas.grid.type === CONST.GRID_TYPES.GRIDLESS)
rulerColor = getColorForDistance.call(this, totalDistance) rulerColor = getColorForDistance.call(this, totalDistance)
else else
rulerColor = this.color rulerColor = this.color
@@ -186,15 +245,31 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
label.text = text; label.text = text;
label.alpha = last ? 1.0 : 0.5; label.alpha = last ? 1.0 : 0.5;
label.visible = true; label.visible = true;
let labelPosition = cs.ray.project((cs.ray.distance + 50) / cs.ray.distance); let labelPosition = {x: s.ray.x0, y: s.ray.y0};
labelPosition.x -= label.width / 2;
labelPosition.y -= label.height / 2;
const rayLine = Line.fromPoints(s.ray.A, s.ray.B);
const rayLabelXHitY = rayLine.calcY(labelPosition.x);
let innerDistance;
// If ray hits top or bottom side of label
if (rayLine.isVertical || rayLabelXHitY < labelPosition.y || rayLabelXHitY > labelPosition.y + label.height)
innerDistance = Math.abs((label.height / 2) / Math.sin(s.ray.angle));
// If ray hits left or right side of label
else
innerDistance = Math.abs((label.width / 2) / Math.cos(s.ray.angle));
labelPosition = s.ray.project((s.ray.distance + 50 + innerDistance) / s.ray.distance);
labelPosition.x -= label.width / 2;
labelPosition.y -= label.height / 2;
label.position.set(labelPosition.x, labelPosition.y); label.position.set(labelPosition.x, labelPosition.y);
} }
// Highlight grid positions // Highlight grid positions
if (terrainRulerAvailable) if (isToken && canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS && options.gridSpaces) {
highlightMeasurementTerrainRuler.call(this, cs.ray, cs.startDistance, shape, opacityMultiplier) if (options.enableTerrainRuler)
else highlightMeasurementTerrainRuler.call(this, cs.ray, cs.startDistance, shape, opacityMultiplier)
highlightMeasurementNative.call(this, cs.ray, cs.startDistance, shape, opacityMultiplier); else
highlightMeasurementNative.call(this, cs.ray, cs.startDistance, shape, opacityMultiplier);
}
} }
// Draw endpoints // Draw endpoints
@@ -227,7 +302,7 @@ export function highlightMeasurementNative(ray, startDistance, tokenShape=[{x: 0
let [xg, yg] = canvas.grid.grid.getPixelsFromGridPosition(x1, y1); let [xg, yg] = canvas.grid.grid.getPixelsFromGridPosition(x1, y1);
const subDistance = canvas.grid.measureDistances([{ray: new Ray(ray.A, {x: xg, y: yg})}], {gridSpaces: true})[0] const subDistance = canvas.grid.measureDistances([{ray: new Ray(ray.A, {x: xg, y: yg})}], {gridSpaces: true})[0]
const color = dragRuler.getColorForDistance.call(this, startDistance, subDistance) const 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.draggedEntity);
const [snapX, snapY] = getGridPositionFromPixels(snapPoint.x + 1, snapPoint.y + 1); const [snapX, snapY] = getGridPositionFromPixels(snapPoint.x + 1, snapPoint.y + 1);
prior = [x1, y1]; prior = [x1, y1];
@@ -240,7 +315,7 @@ export function highlightMeasurementNative(ray, startDistance, tokenShape=[{x: 0
let [xgh, ygh] = canvas.grid.grid.getPixelsFromGridPosition(x1h, y1h); let [xgh, ygh] = canvas.grid.grid.getPixelsFromGridPosition(x1h, y1h);
const subDistance = canvas.grid.measureDistances([{ray: new Ray(ray.A, {x: xgh, y: ygh})}], {gridSpaces: true})[0] const subDistance = canvas.grid.measureDistances([{ray: new Ray(ray.A, {x: xgh, y: ygh})}], {gridSpaces: true})[0]
const color = dragRuler.getColorForDistance.call(this, startDistance, subDistance) const 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.draggedEntity);
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, alpha); highlightTokenShape.call(this, {x: snapX, y: snapY}, tokenShape, color, alpha);
} }
+21
View File
@@ -0,0 +1,21 @@
export class Line {
constructor(m, b) {
this.m = m;
this.b = b;
}
static fromPoints(p1, p2) {
// Bring line into y=mx+b form
const m = (p1.y - p2.y) / (p1.x - p2.x);
const b = p1.y - m * p1.x;
return new Line(m, b);
}
get isVertical() {
return !isFinite(this.m);
}
calcY(x) {
return this.m * x + this.b;
}
}
+158 -53
View File
@@ -2,18 +2,22 @@
import {currentSpeedProvider, getMovedDistanceFromToken, getRangesFromSpeedProvider, getUnreachableColorFromSpeedProvider, initApi, registerModule, registerSystem} from "./api.js" import {currentSpeedProvider, getMovedDistanceFromToken, getRangesFromSpeedProvider, getUnreachableColorFromSpeedProvider, initApi, registerModule, registerSystem} from "./api.js"
import {checkDependencies, getHexSizeSupportTokenGridCenter} from "./compatibility.js"; import {checkDependencies, getHexSizeSupportTokenGridCenter} from "./compatibility.js";
import {moveTokens, onMouseMove} from "./foundry_imports.js" import {moveEntities, onMouseMove} from "./foundry_imports.js"
import {performMigrations} from "./migration.js" import {performMigrations} from "./migration.js"
import {DragRulerRuler} from "./ruler.js"; import {DragRulerRuler} from "./ruler.js";
import {getMovementHistory} from "./movement_tracking.js"; import {getMovementHistory, removeLastHistoryEntryIfAt, 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 {isClose, setSnapParameterOnOptions} from "./util.js";
Hooks.once("init", () => { Hooks.once("init", () => {
registerSettings() registerSettings()
initApi() initApi()
hookTokenDragHandlers() hookDragHandlers(Token);
hookDragHandlers(MeasuredTemplate);
hookKeyboardManagerFunctions() hookKeyboardManagerFunctions()
hookLayerFunctions();
Ruler = DragRulerRuler; Ruler = DragRulerRuler;
@@ -22,6 +26,8 @@ Hooks.once("init", () => {
getMovedDistanceFromToken, getMovedDistanceFromToken,
registerModule, registerModule,
registerSystem, registerSystem,
recalculate,
resetMovementHistory,
} }
}) })
@@ -33,38 +39,47 @@ Hooks.once("ready", () => {
Hooks.on("canvasReady", () => { Hooks.on("canvasReady", () => {
canvas.controls.rulers.children.forEach(ruler => { canvas.controls.rulers.children.forEach(ruler => {
ruler.draggedToken = null ruler.draggedEntity = null;
Object.defineProperty(ruler, "isDragRuler", { Object.defineProperty(ruler, "isDragRuler", {
get: function isDragRuler() { get: function isDragRuler() {
return Boolean(this.draggedToken) // If draggedToken is set this is a drag ruler return Boolean(this.draggedEntity) && this._state !== Ruler.STATES.INACTIVE;
} }
}) })
}) })
}) })
function hookTokenDragHandlers() { Hooks.on("getCombatTrackerEntryContext", function (html, menu) {
const originalDragLeftStartHandler = Token.prototype._onDragLeftStart const entry = {
Token.prototype._onDragLeftStart = function(event) { name: "drag-ruler.resetMovementHistory",
icon: '<i class="fas fa-undo-alt"></i>',
callback: li => resetMovementHistory(ui.combat.viewed, li.data('combatant-id')),
};
menu.splice(1, 0, entry);
});
function hookDragHandlers(entityType) {
const originalDragLeftStartHandler = entityType.prototype._onDragLeftStart
entityType.prototype._onDragLeftStart = function(event) {
originalDragLeftStartHandler.call(this, event) originalDragLeftStartHandler.call(this, event)
onTokenLeftDragStart.call(this, event) onEntityLeftDragStart.call(this, event)
} }
const originalDragLeftMoveHandler = Token.prototype._onDragLeftMove const originalDragLeftMoveHandler = entityType.prototype._onDragLeftMove
Token.prototype._onDragLeftMove = function (event) { entityType.prototype._onDragLeftMove = function (event) {
originalDragLeftMoveHandler.call(this, event) originalDragLeftMoveHandler.call(this, event)
onTokenLeftDragMove.call(this, event) onEntityLeftDragMove.call(this, event)
} }
const originalDragLeftDropHandler = Token.prototype._onDragLeftDrop const originalDragLeftDropHandler = entityType.prototype._onDragLeftDrop
Token.prototype._onDragLeftDrop = function (event) { entityType.prototype._onDragLeftDrop = function (event) {
const eventHandled = onTokenDragLeftDrop.call(this, event) const eventHandled = onEntityDragLeftDrop.call(this, event)
if (!eventHandled) if (!eventHandled)
originalDragLeftDropHandler.call(this, event) originalDragLeftDropHandler.call(this, event)
} }
const originalDragLeftCancelHandler = Token.prototype._onDragLeftCancel const originalDragLeftCancelHandler = entityType.prototype._onDragLeftCancel
Token.prototype._onDragLeftCancel = function (event) { entityType.prototype._onDragLeftCancel = function (event) {
const eventHandled = onTokenDragLeftCancel.call(this, event) const eventHandled = onEntityDragLeftCancel.call(this, event)
if (!eventHandled) if (!eventHandled)
originalDragLeftCancelHandler.call(this, event) originalDragLeftCancelHandler.call(this, event)
} }
@@ -79,12 +94,32 @@ function hookKeyboardManagerFunctions() {
} }
} }
function hookLayerFunctions() {
const originalTokenLayerUndoHistory = TokenLayer.prototype.undoHistory;
TokenLayer.prototype.undoHistory = function () {
const historyEntry = this.history[this.history.length - 1];
return originalTokenLayerUndoHistory.call(this).then((returnValue) => {
if (historyEntry.type === "update") {
for (const entry of historyEntry.data) {
const token = canvas.tokens.get(entry._id);
removeLastHistoryEntryIfAt(token, entry.x, entry.y);
}
}
return returnValue;
});
}
}
function handleKeys(event, key, up) { function handleKeys(event, key, up) {
if (event.repeat || this.hasFocus) if (event.repeat || this.hasFocus)
return false return false
if (key.toLowerCase() === "x") return onKeyX(up) const lowercaseKey = key.toLowerCase();
if (key.toLowerCase() === "shift") return onKeyShift(up)
if (lowercaseKey === "x") return onKeyX(up)
if (lowercaseKey === "shift") return onKeyShift(up)
if (lowercaseKey === "space") return onKeySpace(up);
if (lowercaseKey === "escape") return onKeyEscape(up);
return false return false
} }
@@ -103,6 +138,8 @@ function onKeyShift(up) {
const ruler = canvas.controls.ruler const ruler = canvas.controls.ruler
if (!ruler.isDragRuler) if (!ruler.isDragRuler)
return false return false
if (ruler._state !== Ruler.STATES.MEASURING)
return false;
const mousePosition = canvas.app.renderer.plugins.interaction.mouse.getLocalPosition(canvas.tokens) const mousePosition = canvas.app.renderer.plugins.interaction.mouse.getLocalPosition(canvas.tokens)
const rulerOffset = ruler.rulerOffset const rulerOffset = ruler.rulerOffset
@@ -110,54 +147,120 @@ function onKeyShift(up) {
ruler.measure(measurePosition, {snap: up}) ruler.measure(measurePosition, {snap: up})
} }
function onTokenLeftDragStart(event) { function onKeySpace(up) {
if (!currentSpeedProvider.usesRuler(this)) const ruler = canvas.controls.ruler;
return if (!ruler.draggedEntity)
const ruler = canvas.controls.ruler return false;
ruler.draggedToken = this
let tokenCenter if (ruler._state !== Ruler.STATES.INACTIVE)
if (canvas.grid.isHex && game.modules.get("hex-size-support")?.active && CONFIG.hexSizeSupport.getAltSnappingFlag(this)) return false;
tokenCenter = getHexSizeSupportTokenGridCenter(this)
else const swapSpacebarRightClick = game.settings.get(settingsKey, "swapSpacebarRightClick");
tokenCenter = this.center let options = {};
ruler.clear(); setSnapParameterOnOptions(ruler, options);
ruler._state = Ruler.STATES.STARTING;
ruler.rulerOffset = {x: tokenCenter.x - event.data.origin.x, y: tokenCenter.y - event.data.origin.y} if (!up) {
if (game.settings.get(settingsKey, "enableMovementHistory")) if (swapSpacebarRightClick)
ruler.dragRulerAddWaypointHistory(getMovementHistory(this)); ruler.dragRulerAbortDrag();
ruler.dragRulerAddWaypoint(tokenCenter, false); else
startDragRuler.call(ruler.draggedEntity, options);
}
return true;
} }
function onTokenLeftDragMove(event) { function onKeyEscape(up) {
const ruler = canvas.controls.ruler;
if (!ruler.draggedEntity)
return false;
if (!up)
ruler.dragRulerAbortDrag();
return true;
}
function onEntityLeftDragStart(event) {
const isToken = this instanceof Token;
const ruler = canvas.controls.ruler
ruler.draggedEntity = this;
let entityCenter;
if (isToken && canvas.grid.isHex && game.modules.get("hex-size-support")?.active && CONFIG.hexSizeSupport.getAltSnappingFlag(this))
entityCenter = getHexSizeSupportTokenGridCenter(this);
else
entityCenter = this.center;
ruler.rulerOffset = {x: entityCenter.x - event.data.origin.x, y: entityCenter.y - event.data.origin.y};
if (game.settings.get(settingsKey, "autoStartMeasurement")) {
let options = {};
setSnapParameterOnOptions(ruler, options);
startDragRuler.call(this, options, false);
}
}
function startDragRuler(options, measureImmediately=true) {
const isToken = this instanceof Token;
if (isToken && !currentSpeedProvider.usesRuler(this))
return;
const ruler = canvas.controls.ruler;
ruler.clear();
ruler._state = Ruler.STATES.STARTING;
let entityCenter;
if (isToken && canvas.grid.isHex && game.modules.get("hex-size-support")?.active && CONFIG.hexSizeSupport.getAltSnappingFlag(this))
entityCenter = getHexSizeSupportTokenGridCenter(this);
else
entityCenter = this.center;
if (isToken && game.settings.get(settingsKey, "enableMovementHistory"))
ruler.dragRulerAddWaypointHistory(getMovementHistory(this));
ruler.dragRulerAddWaypoint(entityCenter, {snap: false});
const mousePosition = canvas.app.renderer.plugins.interaction.mouse.getLocalPosition(canvas.tokens);
const destination = {x: mousePosition.x + ruler.rulerOffset.x, y: mousePosition.y + ruler.rulerOffset.y};
if (measureImmediately)
ruler.measure(destination, options);
}
function onEntityLeftDragMove(event) {
const ruler = canvas.controls.ruler const ruler = canvas.controls.ruler
if (ruler.isDragRuler) if (ruler.isDragRuler)
onMouseMove.call(ruler, event) onMouseMove.call(ruler, event)
} }
function onTokenDragLeftDrop(event) { function onEntityDragLeftDrop(event) {
const ruler = canvas.controls.ruler const ruler = canvas.controls.ruler
if (!ruler.isDragRuler) if (!ruler.isDragRuler) {
ruler.draggedEntity = undefined;
return false return false
onMouseMove.call(ruler, event); }
// When we're dragging a measured template no token will ever be selected,
// resulting in only the dragged template to be moved as would be expected
const selectedTokens = canvas.tokens.controlled const selectedTokens = canvas.tokens.controlled
// This can happen if the user presses ESC during drag (maybe there are other ways too)
if (selectedTokens.length === 0)
selectedTokens.push(ruler.draggedEntity);
ruler._state = Ruler.STATES.MOVING ruler._state = Ruler.STATES.MOVING
moveTokens.call(ruler, ruler.draggedToken, selectedTokens) moveEntities.call(ruler, ruler.draggedEntity, selectedTokens);
return true return true
} }
function onTokenDragLeftCancel(event) { function onEntityDragLeftCancel(event) {
// This function is invoked by right clicking // This function is invoked by right clicking
const ruler = canvas.controls.ruler const ruler = canvas.controls.ruler
if (!ruler.isDragRuler || ruler._state === Ruler.STATES.MOVING) if (!ruler.draggedEntity || ruler._state === Ruler.STATES.MOVING)
return false return false
if (ruler._state === Ruler.STATES.MEASURING) {
if (!game.settings.get(settingsKey, "swapSpacebarRightClick")) { const swapSpacebarRightClick = game.settings.get(settingsKey, "swapSpacebarRightClick");
ruler.dragRulerDeleteWaypoint(event); let options = {};
setSnapParameterOnOptions(ruler, options);
if (ruler._state === Ruler.STATES.INACTIVE) {
if (!swapSpacebarRightClick)
return false;
startDragRuler.call(this, options);
event.preventDefault();
}
else if (ruler._state === Ruler.STATES.MEASURING) {
if (!swapSpacebarRightClick) {
ruler.dragRulerDeleteWaypoint(event, options);
} }
else { else {
event.preventDefault() event.preventDefault();
const snap = !event.shiftKey ruler.dragRulerAddWaypoint(ruler.destination, options);
ruler.dragRulerAddWaypoint(ruler.destination, snap);
} }
} }
return true return true
@@ -166,17 +269,19 @@ function onTokenDragLeftCancel(event) {
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) { if (!this.draggedEntity.actor) {
return this.color; 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.draggedEntity.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
if (!(this.draggedToken.actor.data.type === "character" && game.settings.get(settingsKey, "alwaysShowSpeedForPCs"))) if (!(this.draggedEntity.actor.data.type === "character" && game.settings.get(settingsKey, "alwaysShowSpeedForPCs")))
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.draggedEntity);
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) => {
+51 -17
View File
@@ -1,74 +1,108 @@
import {measureDistances} from "./compatibility.js"; import {measureDistances} from "./compatibility.js";
import {updateCombatantDragRulerFlags} from "./socket.js"; import {recalculate, updateCombatantDragRulerFlags} from "./socket.js";
import {getTokenShape} from "./util.js"; import {getTokenShape, isClose, zip} from "./util.js";
function initTrackingFlag(combatant) { function initTrackingFlag(combatant) {
const initialFlag = {passedWaypoints: [], trackedRound: 0}; const initialFlag = {passedWaypoints: [], trackedRound: 0};
let dragRulerFlag = combatant.flags?.dragRuler; let dragRulerFlag = combatant.data.flags.dragRuler;
if (dragRulerFlag) { if (dragRulerFlag) {
if (isNaN(dragRulerFlag.trackedRound)) { if (isNaN(dragRulerFlag.trackedRound)) {
mergeObject(dragRulerFlag, initialFlag); mergeObject(dragRulerFlag, initialFlag);
} }
} }
else { else {
combatant.flags.dragRuler = initialFlag; combatant.data.flags.dragRuler = initialFlag;
} }
} }
function getInitializedCombatant(token, combat) { function getInitializedCombatant(token, combat) {
const combatant = combat.getCombatantByToken(token.data._id); const combatant = combat.getCombatantByToken(token.id);
if (!combatant) if (!combatant)
return undefined; return undefined;
initTrackingFlag(combatant); initTrackingFlag(combatant);
return combatant; return combatant;
} }
export async function trackRays(token, rays) { export async function trackRays(tokens, tokenRays) {
// Only track movement if the current token is participating in the active combat
const combat = game.combat; const combat = game.combat;
if (!combat) if (!combat)
return; return;
if (!combat.started) if (!combat.started)
return; 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); const combatant = getInitializedCombatant(token, combat);
if (!combatant) if (!combatant)
return; return;
// Check if we have entered a new round. If so, remove the currently stored path // Check if we have entered a new round. If so, remove the currently stored path
if (combat.data.round > combatant.flags.dragRuler.trackedRound) { if (combat.data.round > combatant.data.flags.dragRuler.trackedRound) {
combatant.flags.dragRuler.passedWaypoints = []; combatant.data.flags.dragRuler.passedWaypoints = [];
combatant.flags.dragRuler.trackedRound = combat.data.round; combatant.data.flags.dragRuler.trackedRound = combat.data.round;
} }
// Add the passed waypoints to the combatant // Add the passed waypoints to the combatant
const terrainRulerAvailable = game.modules.get("terrain-ruler")?.active && canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS; const terrainRulerAvailable = game.modules.get("terrain-ruler")?.active && (!game.modules.get("TerrainLayer")?.active || canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS);
const dragRulerFlags = combatant.flags.dragRuler; const dragRulerFlags = combatant.data.flags.dragRuler;
const waypoints = dragRulerFlags.passedWaypoints; const waypoints = dragRulerFlags.passedWaypoints;
for (const ray of rays) { for (const ray of rays) {
// Ignore rays that have the same start and end coordinates // Ignore rays that have the same start and end coordinates
if (ray.A.x !== ray.B.x || ray.A.y !== ray.B.y) { if (ray.A.x !== ray.B.x || ray.A.y !== ray.B.y) {
if (terrainRulerAvailable) { if (terrainRulerAvailable) {
measureDistances([{ray}], token, getTokenShape(token), true, {terrainRulerInitialState: dragRulerFlags.rulerState}); measureDistances([{ray}], token, getTokenShape(token), {terrainRulerInitialState: waypoints[waypoints.length - 1]?.dragRulerFinalState});
ray.A.dragRulerVisitedSpaces = ray.terrainRulerVisitedSpaces; ray.A.dragRulerVisitedSpaces = ray.terrainRulerVisitedSpaces;
dragRulerFlags.rulerState = ray.terrainRulerFinalState; ray.A.dragRulerFinalState = ray.terrainRulerFinalState;
} }
waypoints.push(ray.A); waypoints.push(ray.A);
} }
} }
await updateCombatantDragRulerFlags(combat, combatant, dragRulerFlags); return {_id: combatant.id, dragRulerFlags};
} }
export function getMovementHistory(token) { export function getMovementHistory(token) {
const combat = game.combat; const combat = game.combat;
if (!combat) if (!combat)
return []; return [];
const combatant = combat.getCombatantByToken(token.data._id); const combatant = combat.getCombatantByToken(token.id);
if (!combatant) if (!combatant)
return []; return [];
const dragRulerFlags = combatant.flags.dragRuler; const dragRulerFlags = combatant.data.flags.dragRuler;
if (!dragRulerFlags) if (!dragRulerFlags)
return []; return [];
if (combat.data.round > dragRulerFlags.trackedRound) if (combat.data.round > dragRulerFlags.trackedRound)
return []; return [];
return dragRulerFlags.passedWaypoints ?? []; return dragRulerFlags.passedWaypoints ?? [];
} }
export async function removeLastHistoryEntryIfAt(token, x, y) {
const history = getMovementHistory(token);
if (history.length === 0)
return;
const entry = history[history.length - 1];
if (!isClose(x + token.w / 2, entry.x, 0.1) || !isClose(y + token.h / 2, entry.y, 0.1)) {
return;
}
history.pop();
const combat = game.combat;
const combatant = combat.getCombatantByToken(token.id);
await updateCombatantDragRulerFlags(combat, [{_id: combatant.id, dragRulerFlags: combatant.data.flags.dragRuler}]);
}
export async function resetMovementHistory(combat, combatantId) {
const combatant = combat.combatants.get(combatantId);
const dragRulerFlags = combatant.data.flags.dragRuler;
if (!dragRulerFlags)
return;
dragRulerFlags.passedWaypoints = null;
dragRulerFlags.trackedRound = null;
dragRulerFlags.rulerState = null;
await updateCombatantDragRulerFlags(combat, [{_id: combatantId, dragRulerFlags}]);
recalculate();
}
+70 -26
View File
@@ -1,6 +1,7 @@
import {measure} from "./foundry_imports.js" import {cancelScheduledMeasurement, measure} from "./foundry_imports.js"
import {getMovementHistory} from "./movement_tracking.js";
import {settingsKey} from "./settings.js"; import {settingsKey} from "./settings.js";
import {getSnapPointForToken} from "./util.js"; import {getSnapPointForEntity, setSnapParameterOnOptions} from "./util.js";
export class DragRulerRuler extends Ruler { export class DragRulerRuler extends Ruler {
// Functions below are overridden versions of functions in Ruler // Functions below are overridden versions of functions in Ruler
@@ -14,36 +15,48 @@ export class DragRulerRuler extends Ruler {
super.clear(); super.clear();
this.previousWaypoints = []; this.previousWaypoints = [];
this.previousLabels.removeChildren().forEach(c => c.destroy()); this.previousLabels.removeChildren().forEach(c => c.destroy());
this.dragRulerRanges = undefined;
cancelScheduledMeasurement.call(this);
} }
async moveToken(event) { async moveToken(event) {
// This function is invoked by left clicking // This function is invoked by left clicking
if (!this.isDragRuler) if (!this.isDragRuler)
return await super.moveToken(event); return await super.moveToken(event);
let options = {};
setSnapParameterOnOptions(this, options);
if (!game.settings.get(settingsKey, "swapSpacebarRightClick")) { if (!game.settings.get(settingsKey, "swapSpacebarRightClick")) {
const snap = !event.shiftKey; this.dragRulerAddWaypoint(this.destination, options);
this.dragRulerAddWaypoint(this.destination, snap);
} }
else { else {
this.dragRulerDeleteWaypoint(); this.dragRulerDeleteWaypoint(event, options);
} }
} }
toJSON() { toJSON() {
const json = super.toJSON(); const json = super.toJSON();
if (this.draggedToken) if (this.draggedEntity) {
json["draggedToken"] = this.draggedToken.data._id; const isToken = this.draggedEntity instanceof Token;
json["draggedEntityIsToken"] = isToken;
json["draggedEntity"] = this.draggedEntity.id;
}
return json; return json;
} }
update(data) { update(data) {
// Don't show a GMs drag ruler to non GM players // 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")) if (data.draggedEntity && this.user.isGM && !game.user.isGM && !game.settings.get(settingsKey, "showGMRulerToPlayers"))
return; return;
if (data.draggedToken) { if (data.draggedEntity) {
this.draggedToken = canvas.tokens.get(data.draggedToken); if (data.draggedEntityIsToken)
this.draggedEntity = canvas.tokens.get(data.draggedEntity);
else
this.draggedEntity = canvas.templates.get(data.draggedEntity);
} }
super.update(data); super.update(data);
} }
@@ -58,13 +71,15 @@ export class DragRulerRuler extends Ruler {
_endMeasurement() { _endMeasurement() {
super._endMeasurement(); super._endMeasurement();
this.draggedToken = null; this.draggedEntity = null;
} }
// The functions below aren't present in the orignal Ruler class and are added by Drag Ruler // The functions below aren't present in the orignal Ruler class and are added by Drag Ruler
dragRulerAddWaypoint(point, snap=true) { dragRulerAddWaypoint(point, options={}) {
if (snap) options.snap = options.snap ?? true;
point = getSnapPointForToken(point.x, point.y, this.draggedToken); if (options.snap) {
point = getSnapPointForEntity(point.x, point.y, this.draggedEntity);
}
this.waypoints.push(new PIXI.Point(point.x, point.y)); this.waypoints.push(new PIXI.Point(point.x, point.y));
this.labels.addChild(new PreciseText("", CONFIG.canvasTextStyle)); this.labels.addChild(new PreciseText("", CONFIG.canvasTextStyle));
} }
@@ -77,28 +92,57 @@ export class DragRulerRuler extends Ruler {
} }
} }
dragRulerDeleteWaypoint(event={preventDefault: () => {return}}) { dragRulerClearWaypoints() {
this.waypoints = [];
this.labels.removeChildren().forEach(c => c.destroy());
}
dragRulerDeleteWaypoint(event={preventDefault: () => {return}}, options={}) {
options.snap = options.snap ?? true;
if (this.waypoints.filter(w => !w.isPrevious).length > 1) { if (this.waypoints.filter(w => !w.isPrevious).length > 1) {
event.preventDefault(); event.preventDefault();
const mousePosition = canvas.app.renderer.plugins.interaction.mouse.getLocalPosition(canvas.tokens); const mousePosition = canvas.app.renderer.plugins.interaction.mouse.getLocalPosition(canvas.tokens);
const rulerOffset = this.rulerOffset; const rulerOffset = this.rulerOffset;
this._removeWaypoint({x: mousePosition.x + rulerOffset.x, y: mousePosition.y + rulerOffset.y});
// Options are not passed to _removeWaypoint in vanilla Foundry.
// Send them in case other modules have overriden that behavior and accept an options parameter (Toggle Snap to Grid)
this._removeWaypoint({x: mousePosition.x + rulerOffset.x, y: mousePosition.y + rulerOffset.y}, options);
game.user.broadcastActivity({ruler: this}); game.user.broadcastActivity({ruler: this});
} }
else { else {
const token = this.draggedToken; this.dragRulerAbortDrag(event);
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);
} }
} }
dragRulerAbortDrag(event={preventDefault: () => {return}}) {
const token = this.draggedEntity;
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.draggedEntity.id))
return;
const waypoints = this.waypoints.filter(waypoint => !waypoint.isPrevious);
this.dragRulerClearWaypoints();
if (game.settings.get(settingsKey, "enableMovementHistory"))
this.dragRulerAddWaypointHistory(getMovementHistory(this.draggedEntity));
for (const waypoint of waypoints) {
this.dragRulerAddWaypoint(waypoint, {snap: false});
}
this.measure(this.destination);
game.user.broadcastActivity({ruler: this});
}
static dragRulerGetRaysFromWaypoints(waypoints, destination) { static dragRulerGetRaysFromWaypoints(waypoints, destination) {
if ( destination ) if ( destination )
waypoints = waypoints.concat([destination]); waypoints = waypoints.concat([destination]);
+9
View File
@@ -11,6 +11,15 @@ export function registerSettings() {
default: "fresh install" default: "fresh install"
}) })
game.settings.register(settingsKey, "autoStartMeasurement", {
name: "drag-ruler.settings.autoStartMeasurement.name",
hint: "drag-ruler.settings.autoStartMeasurement.hint",
scope: "client",
config: true,
type: Boolean,
default: true,
});
game.settings.register(settingsKey, "swapSpacebarRightClick", { game.settings.register(settingsKey, "swapSpacebarRightClick", {
name: "drag-ruler.settings.swapSpacebarRightClick.name", name: "drag-ruler.settings.swapSpacebarRightClick.name",
hint: "drag-ruler.settings.swapSpacebarRightClick.hint", hint: "drag-ruler.settings.swapSpacebarRightClick.hint",
+31 -5
View File
@@ -1,17 +1,43 @@
import {currentSpeedProvider} from "./api.js";
let socket; let socket;
Hooks.once("socketlib.ready", () => { Hooks.once("socketlib.ready", () => {
socket = socketlib.registerModule("drag-ruler"); socket = socketlib.registerModule("drag-ruler");
socket.register("updateCombatantDragRulerFlags", _socketUpdateCombatantDragRulerFlags); socket.register("updateCombatantDragRulerFlags", _socketUpdateCombatantDragRulerFlags);
socket.register("recalculate", _socketRecalculate);
}); });
export async function updateCombatantDragRulerFlags(combat, combatant, flags) { export function updateCombatantDragRulerFlags(combat, updates) {
const combatId = combat.id; const combatId = combat.id;
const combatantId = combatant._id; // TODO Check if canvas.tokens.get is still neccessary in future foundry versions
return socket.executeAsGM(_socketUpdateCombatantDragRulerFlags, combatId, combatantId, flags); return socket.executeAsGM(_socketUpdateCombatantDragRulerFlags, combatId, updates)
.then(() => currentSpeedProvider.onMovementHistoryUpdate(updates.map(update => canvas.tokens.get(combat.combatants.get(update._id).token.id))));
} }
async function _socketUpdateCombatantDragRulerFlags(combatId, combatantId, flags) { async function _socketUpdateCombatantDragRulerFlags(combatId, updates) {
const user = game.users.get(this.socketdata.userId);
const combat = game.combats.get(combatId); const combat = game.combats.get(combatId);
await combat.updateEmbeddedEntity("Combatant", {_id: combatantId, flags: {dragRuler: flags}}, {diff: false}); const requestedUpdates = updates.length;
updates = updates.filter(update => {
const actor = combat.combatants.get(update._id).actor;
if (!actor)
return false;
return actor.testUserPermission(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.updateEmbeddedDocuments("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);
} }
+16 -3
View File
@@ -61,13 +61,18 @@ export class SpeedProvider {
* The area indicates the whole area that the token will occupy (for tokens larger than 1x1) the array will more than one entry. * The area indicates the whole area that the token will occupy (for tokens larger than 1x1) the array will more than one entry.
* The return value should be an integer indicating a multiplicator by that the cost of that step should be increased. * The return value should be an integer indicating a multiplicator by that the cost of that step should be increased.
* (1 is regular cost, 2 costs double, 3 costs triple, ...) * (1 is regular cost, 2 costs double, 3 costs triple, ...)
*
* Parameters:
* - options: An object used to configure TerrainLayer's cost calculation. Ex: If options.ignoreGrid is set to true, then Euclidean measurement can be forced on a gridded map.
*
* This function is only called if the TerrainLayer and TerrainRuler modules are enabled. * This function is only called if the TerrainLayer and TerrainRuler modules are enabled.
* *
* Implementing this method is optional and only needs to be done if you want to provide a custom cost function (for example to allow tokens to ignore difficult terrain) * Implementing this method is optional and only needs to be done if you want to provide a custom cost function (for example to allow tokens to ignore difficult terrain)
*/ */
getCostForStep(token, area) { getCostForStep(token, area, options={}) {
// 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 => terrainRuler.getCost(space.x, space.y, {token})); options.token = token;
const costs = area.map(space => terrainRuler.getCost(space.x, space.y, options));
// 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 +89,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 +137,7 @@ export class GenericSpeedProvider extends SpeedProvider {
const speedAttribute = this.getSetting("speedAttribute") const speedAttribute = this.getSetting("speedAttribute")
if (!speedAttribute) if (!speedAttribute)
return [] return []
const tokenSpeed = parseInt(getProperty(token, speedAttribute)); const tokenSpeed = parseFloat(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 []
+14 -2
View File
@@ -1,16 +1,23 @@
export function getDefaultSpeedAttribute() { export function getDefaultSpeedAttribute() {
switch (game.system.id) { switch (game.system.id) {
case "CoC7":
return "actor.data.data.attribs.mov.value";
case "dcc": case "dcc":
return "actor.data.data.attributes.speed.value"; 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.derived.speed"
case "pf1": case "pf1":
case "D35E":
return "actor.data.data.attributes.speed.land.total" return "actor.data.data.attributes.speed.land.total"
case "sfrpg":
return "actor.data.data.attributes.speed.value";
case "shadowrun5e":
return "actor.data.data.movement.walk.value";
case "swade": case "swade":
return "actor.data.data.stats.speed.value" return "actor.data.data.stats.speed.adjusted";
} }
return "" return ""
} }
@@ -23,7 +30,12 @@ export function getDefaultDashMultiplier() {
case "dnd5e": case "dnd5e":
case "lancer": case "lancer":
case "pf1": case "pf1":
case "D35E":
case "sfrpg":
case "shadowrun5e":
return 2 return 2
case "CoC7":
return 5;
} }
return 0 return 0
} }
+48 -1
View File
@@ -11,7 +11,7 @@ export function getSnapPointForToken(x, y, token) {
return new PIXI.Point(x, y); 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.document.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)
return new PIXI.Point(snapPoint.x, snapPoint.y) return new PIXI.Point(snapPoint.x, snapPoint.y)
} }
@@ -39,6 +39,37 @@ export function getSnapPointForToken(x, y, token) {
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 getSnapPointForMeasuredTemplate(x, y) {
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
return new PIXI.Point(x, y);
}
let subgridWidth, subgridHeight;
if (canvas.grid.type === CONST.GRID_TYPES.SQUARE) {
subgridWidth = subgridHeight = canvas.dimensions.size / 2;
}
else {
if (canvas.grid.grid.columns) {
subgridWidth = canvas.grid.w / 4;
subgridHeight = canvas.grid.h / 2;
}
else {
subgridWidth = canvas.grid.w / 2;
subgridHeight = canvas.grid.h / 4;
}
}
const snappedX = Math.round(x / subgridWidth) * subgridWidth;
const snappedY = Math.round(y / subgridHeight) * subgridHeight;
return new PIXI.Point(snappedX, snappedY);
}
export function getSnapPointForEntity(x, y, entity) {
const isToken = entity instanceof Token;
if (isToken)
return getSnapPointForToken(x, y, entity);
else
return getSnapPointForMeasuredTemplate(x, y);
}
export function highlightTokenShape(position, shape, color, alpha) { export function highlightTokenShape(position, shape, color, alpha) {
const layer = canvas.grid.highlightLayers[this.name]; const layer = canvas.grid.highlightLayers[this.name];
if ( !layer ) if ( !layer )
@@ -167,3 +198,19 @@ export function applyTokenSizeOffset(waypoints, token) {
return waypoints.map(w => new PIXI.Point(w.x + waypointOffset.x, w.y + waypointOffset.y)) return waypoints.map(w => new PIXI.Point(w.x + waypointOffset.x, w.y + waypointOffset.y))
} }
export function setSnapParameterOnOptions(sourceObject, options) {
// Allow outside modules to override snapping
if (sourceObject.snapOverride?.active) {
options.snapOverrideActive = true;
options.snap = sourceObject.snapOverride.snap;
sourceObject.snapOverride = undefined; // remove it to prevent any lingering data issues
}
else {
options.snap = !game.keyboard._downKeys.has("Shift");
}
}
export function isClose(a, b, delta) {
return Math.abs(a - b) <= delta;
}