Compare commits
177 Commits
v1.10.0
...
v1.14.2-test
| Author | SHA1 | Date | |
|---|---|---|---|
| 0372b6a3b9 | |||
| 3bd945b6e2 | |||
| 63b946051a | |||
| 179cc73fa1 | |||
| 9eed5d4245 | |||
| f31c2c0f8a | |||
| 72a646ab89 | |||
| e0c57ce78b | |||
| a21085ca5b | |||
| 21533a0075 | |||
| 8d67ca01c3 | |||
| 80ff8832fd | |||
| c787b18fcb | |||
| c03dbc09ff | |||
| 27a2b46a8a | |||
| 94a8e6f147 | |||
| ab1f5b4c9b | |||
| 1974e6e4a3 | |||
| cb40c8de50 | |||
| 6da3e65301 | |||
| a7d06eaed3 | |||
| 18cf63895c | |||
| 0e2ab35db7 | |||
| 0808f17ee3 | |||
| b649dacdb5 | |||
| c490550ed9 | |||
| f12dbf0e61 | |||
| f1fcc52867 | |||
| af03341638 | |||
| 79db620fc1 | |||
| 8bf075c2ce | |||
| f0c6ce1bcc | |||
| 7b392e5c9d | |||
| efe6eac5aa | |||
| 52b64c3016 | |||
| 48d0d17628 | |||
| 8101381cc4 | |||
| 844df150a6 | |||
| 47715e95f6 | |||
| 1dccbcb081 | |||
| 4c3d7ab42a | |||
| 61fc795f7b | |||
| f10fa049b9 | |||
| c844318836 | |||
| 9af692566c | |||
| d08416777b | |||
| 56f506bad2 | |||
| e1d54ed55d | |||
| 504c242e86 | |||
| a48c199863 | |||
| b7ace776d5 | |||
| cf98287afd | |||
| 7a3b547741 | |||
| 5329a4056c | |||
| a65ff4ddb4 | |||
| c2aa47985b | |||
| 4b5fea0763 | |||
| 26c3475db6 | |||
| 2cf97e2804 | |||
| 7dcb8b5c71 | |||
| a9c15d779b | |||
| 01b9941c16 | |||
| 7bd5ad3dad | |||
| 6b4965f507 | |||
| e57829936e | |||
| 1713380900 | |||
| 0896265f87 | |||
| 8a91c8843d | |||
| 4787ae5e12 | |||
| 6027735f4c | |||
| 84bce39043 | |||
| 46c504341c | |||
| ff9b187d90 | |||
| bd76c965a2 | |||
| ee9ac14346 | |||
| 7a00d6fcc2 | |||
| 4957188630 | |||
| 944ff96a26 | |||
| 58565b7011 | |||
| 25fc6258e7 | |||
| ac00aac85b | |||
| 8d5a6716e1 | |||
| c43b0bfd64 | |||
| c21e4c91d6 | |||
| 4b30d52da1 | |||
| 08c39b5683 | |||
| 2e2353e1d1 | |||
| cf74a11f18 | |||
| a1c10e088d | |||
| 626829e989 | |||
| b87e91b63c | |||
| ed10144cc6 | |||
| 5e82049686 | |||
| 71424cd284 | |||
| 445c03d29a | |||
| 5904efbdd5 | |||
| d325f8acee | |||
| 1577435a33 | |||
| 1b8b2328c6 | |||
| 0dba39b145 | |||
| c1b0e9d7fb | |||
| 9d227efcd1 | |||
| a3d942c287 | |||
| bdbd4fde18 | |||
| 3afb1f30b7 | |||
| 654f8e3111 | |||
| 2979e86201 | |||
| 8f81fd707e | |||
| 5f6e348bf0 | |||
| 82f2a885fb | |||
| d536ae8dfb | |||
| 5add0be393 | |||
| f77990420a | |||
| 44efabc656 | |||
| e84632c0d7 | |||
| 30bd3d33e0 | |||
| 6604e322bd | |||
| b68f3bbe29 | |||
| 793e87bc2f | |||
| df4c515fcd | |||
| 02dfd8db0d | |||
| 65911c11f0 | |||
| ddf89f9499 | |||
| 0dfdb23bfb | |||
| 3f2e9e1a3e | |||
| 846e39d946 | |||
| 26748ee234 | |||
| ebd0ee6ccf | |||
| 1d477094b8 | |||
| bbfce2bb77 | |||
| 54cee52eb2 | |||
| a967a03197 | |||
| 8d243ce919 | |||
| b227d5073b | |||
| 576db2dc5a | |||
| b031acaa6e | |||
| ee956abd78 | |||
| 1334703877 | |||
| d6cccf4466 | |||
| 074d2f5052 | |||
| e5b8f896f8 | |||
| 3de350d3e9 | |||
| 8f97081236 | |||
| 18253dc42b | |||
| fb702cd850 | |||
| ad3fdf4d18 | |||
| 58946096a6 | |||
| 22112ddfd5 | |||
| f59b0f9d2d | |||
| 443cfd4317 | |||
| fb3665d758 | |||
| 96cb690431 | |||
| fb7202d5f0 | |||
| fa581ddfc3 | |||
| 56e15be4ca | |||
| 1509307313 | |||
| 68885385ea | |||
| 6748617f6a | |||
| 9d5dccb504 | |||
| a252da620a | |||
| 41c8979925 | |||
| 39f9204fa7 | |||
| 10633e4e2a | |||
| f3e1492edd | |||
| 7ac1c828b6 | |||
| 1972498b05 | |||
| 4a5b9bbb11 | |||
| d467fe5bcf | |||
| 1f770f7597 | |||
| fa62e1d9c4 | |||
| f18ce33779 | |||
| 34cead5cba | |||
| eeb6db76a6 | |||
| 9c44994fd2 | |||
| 23c1a903a0 | |||
| 1b3f563c4e | |||
| c4595f408f |
@@ -0,0 +1,5 @@
|
|||||||
|
/foundry*.js
|
||||||
|
artifact/
|
||||||
|
wasm/
|
||||||
|
*.lock
|
||||||
|
.vscode/
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
*.md
|
||||||
|
*.html
|
||||||
|
*.json
|
||||||
|
foundry.js
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
printWidth: 100
|
||||||
|
trailingComma: "all"
|
||||||
|
bracketSpacing: false
|
||||||
|
arrowParens: "avoid"
|
||||||
+209
@@ -1,3 +1,212 @@
|
|||||||
|
## 1.14.2
|
||||||
|
### Bug fixes
|
||||||
|
- Fixed a bug where tokens would snap to inconsistent distances on gridless scenes
|
||||||
|
- Fixed a bug that caused the highlighted path on hex grids to be incorrect
|
||||||
|
|
||||||
|
|
||||||
|
## 1.14.1
|
||||||
|
### Bug fixes
|
||||||
|
- The functionality Drag Ruler on gridless has been restored
|
||||||
|
|
||||||
|
|
||||||
|
## 1.14.0
|
||||||
|
### New features
|
||||||
|
- Drag Ruler now supports hex tokens of size 5 (thanks KitCat!)
|
||||||
|
|
||||||
|
### Compatibility
|
||||||
|
- Drag Ruler has been updated to work with Foundry v12 (thanks TPNils and N0q!)
|
||||||
|
|
||||||
|
|
||||||
|
## 1.13.8
|
||||||
|
### Bugfixes
|
||||||
|
- `getMovedDistanceFromToken` no longer returns incorrect values on gridless maps
|
||||||
|
|
||||||
|
|
||||||
|
## 1.13.7
|
||||||
|
### Compatibility
|
||||||
|
- Drag Ruler's generic speed provider is now aware of good defaults for the Crucible game system
|
||||||
|
|
||||||
|
|
||||||
|
## 1.13.6
|
||||||
|
### Bugfixes
|
||||||
|
- Fixed a bug that caused no measurements to be shown next to the ruler
|
||||||
|
- Fixed a bug where diagonal paths would sometimes highlight squares that don't blong to the path on square maps
|
||||||
|
|
||||||
|
|
||||||
|
## 1.13.5
|
||||||
|
### Compatibility
|
||||||
|
- Drag Ruler is now compatible with Foundry VTT v11 (thanks to pkonshik for doing much of the porting work!)
|
||||||
|
- Drag Ruler's generic speed provider is now aware of good defaults for Warhammer Fantasy Roleplay 4th Edition
|
||||||
|
|
||||||
|
### Translations
|
||||||
|
- Updated Portugese (Brazil) translation (thanks eunaumtenhoid!)
|
||||||
|
|
||||||
|
|
||||||
|
## 1.13.4
|
||||||
|
### Translations
|
||||||
|
- New translation: Portuguese (Brazil) (thanks eunaumtenhoid!)
|
||||||
|
- Foundry should now be able to detect the Simplified Chenese translation properly
|
||||||
|
|
||||||
|
|
||||||
|
## 1.13.3
|
||||||
|
### Translations
|
||||||
|
- New translation: Chinese (Simplified) (thanks bnp800!)
|
||||||
|
|
||||||
|
|
||||||
|
## 1.13.2
|
||||||
|
### Bugfixes
|
||||||
|
- Fixed a bug that prevented pathfinding on hex to work when the hex size support module is not installed
|
||||||
|
|
||||||
|
## 1.13.1
|
||||||
|
### Bugfixes
|
||||||
|
- Fixed a bug that caused large hex tokens to not snap correctly
|
||||||
|
- Fixed a bug that prevented Drag Ruler from working on hex grids at all if the Hex Size Support module is enabled
|
||||||
|
- Fixed a bug that prevented Enhanced Terrain Layer from determining dragged moving token
|
||||||
|
- Fixed several deprecation warnings
|
||||||
|
|
||||||
|
### Translations
|
||||||
|
- Updated the english text for several UI items
|
||||||
|
- Updated the german translation
|
||||||
|
- Updated the french translation (thanks rectulo!)
|
||||||
|
|
||||||
|
|
||||||
|
## 1.13.0
|
||||||
|
### Breaking changes
|
||||||
|
- Drag Ruler's pathfinder has been extracted into a dedicated library module. If you'd like to continue to use Drag Ruler's pathfinding feature, please install [routinglib](https://foundryvtt.com/packages/routinglib) alongside Drag Ruler.
|
||||||
|
- Drag Ruler's API no longer supports the function `getCostForStep`. Instead, module authors are asked to use [Enhanced Terrain Layer's game system integration API](https://github.com/ironmonk88/enhanced-terrain-layer/blob/main/README.md#integrating-game-system-rules) to introduce game system specific terrain rules.
|
||||||
|
|
||||||
|
### New features
|
||||||
|
- Drag Ruler's pathfinding will now be running as a background task. This means that Foundry will no longer freeze while Drag Ruler is calculating a path.
|
||||||
|
- Drag Ruler's pathfinding will now take difficult terrain into account on griddes scenes
|
||||||
|
|
||||||
|
### Bug fixes
|
||||||
|
- Fixed a bug that would cause gridless snapping to snap slightly below the allowed range
|
||||||
|
|
||||||
|
### Compatibility
|
||||||
|
- Drag Ruler is now compatible with Foundry VTT v10
|
||||||
|
- Drag Ruler's compatibility with the Wall Height module is restored
|
||||||
|
|
||||||
|
|
||||||
|
## 1.12.8
|
||||||
|
### Bugfixes
|
||||||
|
- Fixed a bug that could cause grid cells to be highlighted in the wrong color
|
||||||
|
|
||||||
|
### Compatibility
|
||||||
|
- Fixed an interaction with the Wall Height module that could allow players to walk through walls
|
||||||
|
|
||||||
|
|
||||||
|
## 1.12.7
|
||||||
|
### Bugfixes
|
||||||
|
- Fixed a bug that caused measurement templates to only snap to the corners of the grid (this was a regression introduced in 1.12.5)
|
||||||
|
|
||||||
|
|
||||||
|
## 1.12.6
|
||||||
|
### Bugfixes
|
||||||
|
- Measured templates no longer snap to a virtual grid on gridless scenes (this was a regression introduced in 1.12.5)
|
||||||
|
|
||||||
|
|
||||||
|
## 1.12.5
|
||||||
|
### Compatibility
|
||||||
|
- Drag Ruler is now compatible with DF Template Enhancements
|
||||||
|
|
||||||
|
|
||||||
|
## 1.12.4
|
||||||
|
### Bugfixes
|
||||||
|
- Fixed a bug that could cause Drag Ruler to override the default ruler color on other player's clients
|
||||||
|
|
||||||
|
|
||||||
|
## 1.12.3
|
||||||
|
### Bugfixes
|
||||||
|
- Fixed a bug that could cause foundry to freeze indefinitely when trying to pathfind to an unreachalbe location (thanks to JDCalvert)
|
||||||
|
- Fixed a bug that caused the pathfinder to route through one-directional walls from the wrong direction (thanks to JDCalvert)
|
||||||
|
- Fixed a bug that could cause Drag Ruler to write errors into the JS console during regular usage
|
||||||
|
|
||||||
|
### Compatibility
|
||||||
|
- Drag Ruler's generic speed provider is now aware of good defaults for DnD 4th Edition
|
||||||
|
- Drag Ruler's pathfinder should now be compatible with the Wall Height and Levels modules (thanks to JDCalvert)
|
||||||
|
|
||||||
|
|
||||||
|
## 1.12.2
|
||||||
|
### Bugfixes
|
||||||
|
- Fixed a bug where the pathfinder on gridless scenes sometimes wasn't able to find a way around corners with specific angles
|
||||||
|
- Pathfinding will now be disabled when the hotkey to move tokens without animation is being pressed, to allow GMs to move their tokens through walls
|
||||||
|
|
||||||
|
|
||||||
|
## 1.12.1
|
||||||
|
### Hotfix
|
||||||
|
- Version 1.12.0 was incorrectly packaged, which caused it to fail to load
|
||||||
|
|
||||||
|
|
||||||
|
## 1.12.0
|
||||||
|
### New features
|
||||||
|
- Pathfinding is now supported on gridless scenes
|
||||||
|
|
||||||
|
|
||||||
|
## 1.11.5
|
||||||
|
### Bugfixes
|
||||||
|
- Fixed a bug that was causing Drag Ruler to spam useless warnings into the console (this was a regression introduced in 1.11.4)
|
||||||
|
|
||||||
|
|
||||||
|
## 1.11.4
|
||||||
|
### Bugfixes
|
||||||
|
- When changing the measurement mode via a keybinding (toggle snaping or toggle pathfinding) the updated ruler will now be sent to other players immediately
|
||||||
|
- Fixed a bug that incorrectly showed a ruler to be snapped to other players despite the ruler not being snapped
|
||||||
|
- Fixed a bug that could cause a token to move to an incorrect location if the token was being dragged and dropped very rapidly
|
||||||
|
- Drag Ruler's token movement animations can now be properly waited for (this improves the interaction with modules like sequencer)
|
||||||
|
|
||||||
|
### Translation
|
||||||
|
- Updated Spanish translation (thanks to Viriato139ac#342)
|
||||||
|
|
||||||
|
|
||||||
|
## 1.11.3
|
||||||
|
### Bugfixes
|
||||||
|
- The setting to automatically start pathfinding is now visible to players again (this was a regression introduced in 1.11.2)
|
||||||
|
- Fixed a bug that would show the measurements of other players as if they were using the pathfinder, even if they were not using it.
|
||||||
|
|
||||||
|
|
||||||
|
## 1.11.2
|
||||||
|
### Bugfixes
|
||||||
|
- Fixed a memory leak that could cause the rule to slow down after using the pathfinding functionality for a while
|
||||||
|
|
||||||
|
### Misc
|
||||||
|
- GMs are now always allowed to use the pathfinding tool. The setting now only prevents players from using it.
|
||||||
|
|
||||||
|
### Compatibility
|
||||||
|
- Drag Ruler's generic speed provider is now aware of good defaults for Dungeonslayers 4
|
||||||
|
|
||||||
|
|
||||||
|
## 1.11.1
|
||||||
|
### Bugfixes
|
||||||
|
- Fixed a bug that would cause the pathfinding algorithm to make tokens of size 2 and 4 to take an unnecessary step
|
||||||
|
|
||||||
|
|
||||||
|
## 1.11.0
|
||||||
|
### New features
|
||||||
|
- Drag Ruler now supports pathfinding. Pressing the assigned button will automatically calculate the shortest route to the point you're dragging your token to and add the necessary waypoints to the ruler.
|
||||||
|
- This feature is only available for gridded maps
|
||||||
|
- This feature can only be used if it's enabled by the GM in the module settings
|
||||||
|
- The routing algorithm *does not* take difficult terrain into account
|
||||||
|
|
||||||
|
|
||||||
|
## 1.10.3
|
||||||
|
### Compatibility
|
||||||
|
- This release contains changes required to be compatible with Foundry 9.245
|
||||||
|
|
||||||
|
|
||||||
|
## 1.10.2
|
||||||
|
### Bugfixes
|
||||||
|
- Fixed a bug that could cause squares to be highlighted in the wrong color when using waypoints on a 5/10/5 gird
|
||||||
|
- When using Token Drag Vision, the temporary vision is now correctly cleaned up when dropping the token (resolves a conflict with the "Perfect Vision" module)
|
||||||
|
|
||||||
|
|
||||||
|
## 1.10.1
|
||||||
|
### Bugfixes
|
||||||
|
- Fixed a bug that caused keybindings to break if no scene is active
|
||||||
|
|
||||||
|
### Translation
|
||||||
|
- Updated Spanish translation (thanks to Viriato139ac#342)
|
||||||
|
|
||||||
|
|
||||||
## 1.10.0
|
## 1.10.0
|
||||||
**BREAKING** Drag Ruler 1.10.0 and onward cannot update directly from Drag Ruler versions older than 1.3.0. If you've been using Drag Ruler 1.2.2 or earlier in your world make sure to update to any Version between (inclusive) Drag Ruler 1.3.0 and 1.9.1, launch your world and log in as GM at least once. After doing so you can safely update to v1.10.0 or newer versions. Updating directly from 1.2.2 or older to 1.10.0 or newer will cause Drag Ruler to forget your Speed Prover Settings.
|
**BREAKING** Drag Ruler 1.10.0 and onward cannot update directly from Drag Ruler versions older than 1.3.0. If you've been using Drag Ruler 1.2.2 or earlier in your world make sure to update to any Version between (inclusive) Drag Ruler 1.3.0 and 1.9.1, launch your world and log in as GM at least once. After doing so you can safely update to v1.10.0 or newer versions. Updating directly from 1.2.2 or older to 1.10.0 or newer will cause Drag Ruler to forget your Speed Prover Settings.
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,19 @@
|
|||||||
[](https://ko-fi.com/staebchenfisch)
|
[](https://ko-fi.com/staebchenfisch)
|
||||||
|
|
||||||
|
## Beware of bugs in v12 and onward
|
||||||
|
Foundry v12 has introduced significant changes to how the grid and the ruler are implemented. While Drag Ruler has now been updated to generally work with v12, quite a lot of bugs remain. This is espeically true for Hex grids, but some bugs affect all the grid types. Some of the issues require a large code rewrite to address them properly. Unfortunately this means that Drag Ruler will not provide the same quality experience (snappyness, bugfreeness) you're used to in v12 (and possibly onward). If you're planning to update to v12 and depend on Drag Ruler, proceed with caution.
|
||||||
|
|
||||||
|
|
||||||
# Drag Ruler
|
# Drag Ruler
|
||||||
|
|
||||||
This module shows a ruler when you drag a token or measurement template to inform you how far you've dragged it from its 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 yellow 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.
|
This module shows a ruler when you drag a token or measurement template to inform you how far you've dragged it from its 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 yellow 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.
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
## Supports Tokens of all sizes
|
## Supports Tokens of all sizes
|
||||||
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.
|
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.
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||
@@ -32,25 +36,41 @@ During combat, Drag Ruler will remember the path a token has taken during it's t
|
|||||||

|

|
||||||
|
|
||||||
|
|
||||||
|
## Pathfinding
|
||||||
|
**To use pathfinding you must install the [routinglib](https://foundryvtt.com/packages/routinglib) module**
|
||||||
|
|
||||||
|
When routinglib is installed, Drag Ruler can automatically place waypoints to walk around walls and terrain to reach the destination with the shortest possible movement. Pathfinding can be activated using a configurable key. Alternatively, Drag Ruler can be configured to always use pathfinding when a token is being dragged.
|
||||||
|
|
||||||
|
Pathfinding is restricted to GM users by default, since the pathfinding algorithm can create ways that lead through unexplored fog of war. If you want to allow your players to use Drag Ruler's pathfinding functionality, you need to enable the associated setting in Drag Ruler's module settings.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
## Game systems with Drag Ruler integration
|
## Game systems with Drag Ruler integration
|
||||||
Drag Ruler will work with all Foundry VTT game systems. However, some game systems offer a special integration via the [Drag Ruler API](#api), that allows Drag Ruler to take the rules of the game system into account when dispaying speeds (such as weight carried or conditions that apply to the character), offering a smoother experience. While some game systems offer this integration natively, for other game systems there are modules providing the integration. If the integration is provided via a module you need to install and activate both Drag Ruler and the integration module to benefit from the integration.
|
Drag Ruler will work with all Foundry VTT game systems. However, some game systems offer a special integration via the [Drag Ruler API](#api), that allows Drag Ruler to take the rules of the game system into account when dispaying speeds (such as weight carried or conditions that apply to the character), offering a smoother experience. While some game systems offer this integration natively, for other game systems there are modules providing the integration. If the integration is provided via a module you need to install and activate both Drag Ruler and the integration module to benefit from the integration.
|
||||||
|
|
||||||
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)
|
||||||
|
- DnD5e (via the module [DnD5e Drag Ruler Integration](https://foundryvtt.com/packages/elevation-drag-ruler))
|
||||||
- GURPS 4th Edition Game Aid (Unofficial) (starting with version 0.9.1)
|
- GURPS 4th Edition Game Aid (Unofficial) (starting with version 0.9.1)
|
||||||
|
- Hackmaster (starting with version 0.2.11)
|
||||||
- Ironclaw Second Edition (starting with version 0.2.2)
|
- Ironclaw Second Edition (starting with version 0.2.2)
|
||||||
- Lancer (via the module [Lancer Speed Provider](https://foundryvtt.com/packages/lancer-speed-provider))
|
- Lancer (via the module [Lancer Ruler Integration](https://foundryvtt.com/packages/lancer-speed-provider))
|
||||||
|
- Level Up: Advanced 5th Edition (Official) (via the module [A5E Drag Ruler Integration](https://foundryvtt.com/packages/a5edragruler))
|
||||||
- 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/))
|
||||||
- Shadowrun 5th Edition (via the module [Drag Ruler Integration for Shadowrun 5E](https://foundryvtt.com/packages/drag-ruler-integration-for-shadowrun-5e))
|
- Shadowrun 5th Edition (via the module [Drag Ruler Integration for Shadowrun 5E](https://foundryvtt.com/packages/drag-ruler-integration-for-shadowrun-5e))
|
||||||
|
- Shadow of the Demon Lord (starting with version 1.7.15)
|
||||||
- Starfinder (via the module [Starfinder Drag Ruler Integration](https://foundryvtt.com/packages/starfinder-drag-ruler))
|
- Starfinder (via the module [Starfinder Drag Ruler Integration](https://foundryvtt.com/packages/starfinder-drag-ruler))
|
||||||
- Stargate RPG (starting with version 1.6.0)
|
- Stargate RPG (starting with version 1.6.0)
|
||||||
- Symbaroum (via the module [Symbaroum drag ruler integration](https://foundryvtt.com/packages/symbaroum-drag-ruler-integration))
|
- Symbaroum (via the module [Symbaroum drag ruler integration](https://foundryvtt.com/packages/symbaroum-drag-ruler-integration))
|
||||||
- Tagmar RPG (starting with version 1.1.4)
|
- Tagmar RPG (starting with version 1.1.4)
|
||||||
- TheWitcherTRPG (starting with version 0.0.62)
|
- TheWitcherTRPG (starting with version 0.0.62)
|
||||||
- Tormenta20 (starting with version 1.1.37)
|
- Tormenta20 (starting with version 1.1.37)
|
||||||
|
- The Dark Eye 5 / Das Schwarze Auge 5 (via the module [TDE5/DSA5 Drag Ruler Integration](https://foundryvtt.com/packages/dsa5-drag-ruler))
|
||||||
- Shadow of the Demon Lord (starting with version 1.7.15)
|
- Shadow of the Demon Lord (starting with version 1.7.15)
|
||||||
- Wasteland Ventures (starting with version 0.1.0)
|
- Wasteland Ventures (starting with version 0.1.0)
|
||||||
|
- World of Darkness 20th ed (starting with version 3.3.0)
|
||||||
- WWII:OWB (starting with version 1.0.4)
|
- WWII:OWB (starting with version 1.0.4)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+29
-13
@@ -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 schwieriges 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": {
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
},
|
},
|
||||||
"speedAttribute": {
|
"speedAttribute": {
|
||||||
"name": "Geschwindigkeitsattribut",
|
"name": "Geschwindigkeitsattribut",
|
||||||
"hint": "Das Attribut, das die Gehgeschwindigkeit einer Spielfigur definiert. Diese Attribut wird während der Färbung des hervorgehobenen Pfads verwendet."
|
"hint": "Das Attribut, das die Gehgeschwindigkeit einer Spielfigur definiert. Dieses Attribut wird während der Färbung des hervorgehobenen Pfads verwendet."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"speeds": {
|
"speeds": {
|
||||||
@@ -35,25 +35,33 @@
|
|||||||
"deleteWaypoint": "Wegpunkt löschen",
|
"deleteWaypoint": "Wegpunkt löschen",
|
||||||
"disableSnap": {
|
"disableSnap": {
|
||||||
"name": "Raster deaktivieren",
|
"name": "Raster deaktivieren",
|
||||||
"hint": "Während diese Taste gedrückt ist wird das Raster temporär deaktiviert"
|
"hint": "Während diese Taste gedrückt ist, wird das Raster temporär deaktiviert."
|
||||||
},
|
},
|
||||||
"moveWithoutAnimation": {
|
"moveWithoutAnimation": {
|
||||||
"name": "Token animation deaktivieren",
|
"name": "Tokenanimation deaktivieren",
|
||||||
"hint": "Wenn diese Taste gedrückt wird, während ein Token fallen gelassen wird, bewegt es sich ohne Animation zum Zielort."
|
"hint": "Wenn diese Taste gedrückt wird, während ein Token fallen gelassen wird, bewegt es sich ohne Animation zum Zielort."
|
||||||
|
},
|
||||||
|
"togglePathfinding": {
|
||||||
|
"name": "Wegfindung umschalten",
|
||||||
|
"hint": "Wenn diese Taste gedrückt gehalten wird, während ein Token gezogen wird, wird die Wegfindung vorübergehend aktiviert/deaktiviert."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
"allowPathfinding": {
|
||||||
|
"name": "Wegfindung für Spieler erlauben",
|
||||||
|
"hint": "Erlaubt es Spielern die Wegfindung zu benutzen. Bitte beachte, dass die Wegfindung Wege durch unerkundeten Nebel des Kriegs und ätherische Wände berechnen kann. Dies kann deinen Spielern Geheimnisse lüften, von denen sie noch nicht erfahren sollten."
|
||||||
|
},
|
||||||
"alwaysShowSpeedForPCs": {
|
"alwaysShowSpeedForPCs": {
|
||||||
"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": {
|
"autoStartMeasurement": {
|
||||||
"name": "Automatisch mit Messen beginnen",
|
"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."
|
"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 diese 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."
|
||||||
},
|
},
|
||||||
"rightClickAction": {
|
"rightClickAction": {
|
||||||
"name": "Aktion bei Rechtsklick",
|
"name": "Aktion bei Rechtsklick",
|
||||||
@@ -66,7 +74,7 @@
|
|||||||
},
|
},
|
||||||
"showGMRulerToPlayers": {
|
"showGMRulerToPlayers": {
|
||||||
"name": "Lineal des Spielleiters 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": {
|
||||||
"name": "Einstellungen zur Spielsystemintegration",
|
"name": "Einstellungen zur Spielsystemintegration",
|
||||||
@@ -79,20 +87,20 @@
|
|||||||
},
|
},
|
||||||
"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 stehenden 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": {
|
||||||
"name": "Farbe für {colorName}",
|
"name": "Farbe für {colorName}",
|
||||||
"hint": "Die Farbe mit der Felder in {colorName} Reichweite hervorgehoben werden",
|
"hint": "Die Farbe mit der Felder in {colorName} Reichweite hervorgehoben werden.",
|
||||||
"unreachable": {
|
"unreachable": {
|
||||||
"name": "unerreichbar",
|
"name": "unerreichbar",
|
||||||
"hint": "Die Farbe für Felder, die von der bewegten Spielfigur nicht erreicht werden können"
|
"hint": "Die Farbe für Felder, die von der bewegten Spielfigur nicht erreicht werden können."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"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ängig, 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 Integration 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",
|
||||||
@@ -103,6 +111,14 @@
|
|||||||
"useGridlessraster": {
|
"useGridlessraster": {
|
||||||
"name": "Geschwindigkeitsraster aktivieren",
|
"name": "Geschwindigkeitsraster aktivieren",
|
||||||
"hint": "Lässt Spielfiguren auf gitterlosen Szenen an deren Geschwindigkeitsgrenzen einrasten."
|
"hint": "Lässt Spielfiguren auf gitterlosen Szenen an deren Geschwindigkeitsgrenzen einrasten."
|
||||||
|
},
|
||||||
|
"autoPathfinding": {
|
||||||
|
"name": "Wegfindung grundsätzlich aktivieren",
|
||||||
|
"hint": "Wenn diese Option aktiviert ist, wird beim Ziehen eines Tokens automatisch die Wegfindung verwendet."
|
||||||
|
},
|
||||||
|
"useGridlessRaster": {
|
||||||
|
"name": "Geschwindigkeitsbasiertes Raster verwenden",
|
||||||
|
"hint": "Dies lässt Spielfiguren auf gitterlosen Szenen an ihren Geschwindigkeitsgrenzen einrasten."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+24
-12
@@ -4,7 +4,7 @@
|
|||||||
"ok": "OK",
|
"ok": "OK",
|
||||||
"terrain-ruler": {
|
"terrain-ruler": {
|
||||||
"title": "How to measure difficult terrain with Drag Ruler",
|
"title": "How to measure difficult terrain with Drag Ruler",
|
||||||
"text": "You have the {moduleName} module enabled. Drag Ruler is able to measure difficult terrain that was placed down using that module. To make this possible Drag Ruler utilizes the Terrain Ruler module. If Terrain Ruler is installed and activated Drag Ruler will automatically start to respect difficult terrain in it's measurements.",
|
"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 its measurements.",
|
||||||
"neverShowAgain": "Never show this again"
|
"neverShowAgain": "Never show this again"
|
||||||
},
|
},
|
||||||
"socketlib": {
|
"socketlib": {
|
||||||
@@ -12,7 +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",
|
"resetMovementHistory": "Reset movement history",
|
||||||
"genericSpeedProvider": {
|
"genericSpeedProvider": {
|
||||||
"settings": {
|
"settings": {
|
||||||
"dashMultiplier": {
|
"dashMultiplier": {
|
||||||
@@ -35,29 +35,41 @@
|
|||||||
"deleteWaypoint": "Delete waypoint",
|
"deleteWaypoint": "Delete waypoint",
|
||||||
"disableSnap": {
|
"disableSnap": {
|
||||||
"name": "Disable snapping",
|
"name": "Disable snapping",
|
||||||
"hint": "Snapping will be temporarily disabled while this key is being held down"
|
"hint": "Snapping will be temporarily disabled while this key is being held down."
|
||||||
},
|
},
|
||||||
"moveWithoutAnimation": {
|
"moveWithoutAnimation": {
|
||||||
"name": "Disable token animation",
|
"name": "Disable token animation",
|
||||||
"hint": "When being held while dropping a token, the token will move to the target location without animating"
|
"hint": "When being held while dropping a token, the token will move to the target location without animating."
|
||||||
|
},
|
||||||
|
"togglePathfinding": {
|
||||||
|
"name": "Toggle pathfinding",
|
||||||
|
"hint": "When being held while dragging a token, the pathfinding functionality will be temporarily enabled/disabled."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
"allowPathfinding": {
|
||||||
|
"name": "Allow pathfinding for players",
|
||||||
|
"hint": "Allows players to use Drag Ruler's pathfinding functionality in this world. Be aware that pathfinding can route through unexplored fog of war and Ethereal Walls, which might reveal secrets to your players ahead of time."
|
||||||
|
},
|
||||||
"alwaysShowSpeedForPCs": {
|
"alwaysShowSpeedForPCs": {
|
||||||
"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 be shown to everyone, even if they don't have observer permission for the character sheet."
|
||||||
},
|
},
|
||||||
"autoStartMeasurement": {
|
"autoStartMeasurement": {
|
||||||
"name": "Automatically start measuring",
|
"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."
|
"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."
|
||||||
},
|
},
|
||||||
|
"autoPathfinding": {
|
||||||
|
"name": "Pathfinding by default",
|
||||||
|
"hint": "If enabled, dragging a token will automatically use a pathfinding ruler."
|
||||||
|
},
|
||||||
"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 its turn in combat and will display it when you pick the token back up."
|
||||||
},
|
},
|
||||||
"rightClickAction": {
|
"rightClickAction": {
|
||||||
"name": "Right click action",
|
"name": "Right click action",
|
||||||
"hint": "What action shall be performed when right clicking while dragging a token?",
|
"hint": "What action shall be performed when right-clicking while dragging a token?",
|
||||||
"choices": {
|
"choices": {
|
||||||
"create": "Create waypoint",
|
"create": "Create waypoint",
|
||||||
"delete": "Delete waypoint",
|
"delete": "Delete waypoint",
|
||||||
@@ -66,7 +78,7 @@
|
|||||||
},
|
},
|
||||||
"showGMRulerToPlayers": {
|
"showGMRulerToPlayers": {
|
||||||
"name": "Show GM ruler to players",
|
"name": "Show GM ruler to players",
|
||||||
"hint": "If disabled the ruler of GMs will not be shown for non-GM players"
|
"hint": "If disabled the ruler of GMs will not be shown for non-GM players."
|
||||||
},
|
},
|
||||||
"speedProviderSettings": {
|
"speedProviderSettings": {
|
||||||
"name": "Speed Provider Settings",
|
"name": "Speed Provider Settings",
|
||||||
@@ -78,21 +90,21 @@
|
|||||||
"speedProviderSettings": "Speed Provider specific settings"
|
"speedProviderSettings": "Speed Provider specific settings"
|
||||||
},
|
},
|
||||||
"activeProvider": {
|
"activeProvider": {
|
||||||
"name": "Currently active Speed Provider",
|
"name": "Active Speed Provider",
|
||||||
"hint": "The settings show below depend on the active speed provider. If the GM selects a different speed provider the available settings may change."
|
"hint": "The settings show below depend on the active speed provider. If the GM selects a different speed provider the available settings may change."
|
||||||
},
|
},
|
||||||
"noSettings": "This speed provider doesn't offer any configuration options.",
|
"noSettings": "This speed provider doesn't offer any configuration options.",
|
||||||
"color": {
|
"color": {
|
||||||
"name": "Color for {colorName}",
|
"name": "Color for {colorName}",
|
||||||
"hint": "The color that will be used to color squares that are within {colorName} range",
|
"hint": "The color that will be used to color squares that are within {colorName} range.",
|
||||||
"unreachable": {
|
"unreachable": {
|
||||||
"name": "unreachable",
|
"name": "unreachable",
|
||||||
"hint": "The color for spaces that aren't reachable by the dragged token"
|
"hint": "The color for spaces that aren't reachable by the dragged token."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"speedProvider": {
|
"speedProvider": {
|
||||||
"name": "Speed Settings Provider",
|
"name": "Speed Settings Provider",
|
||||||
"hint": "Select who provides speed information for tokens duing coloring. Drag Ruler offers a generic speed provider that provides basic functionality and should work for all game systems if configured correctly. More speed providers can be made available via game systems and installed modules. Selecting a different speed provider than the generic speed provider may offer a better integration into the rules of the game system you're using. The options below are dependent upon the speed provider selected here.",
|
"hint": "Select who provides speed information for tokens during coloring. Drag Ruler offers a generic speed provider that provides basic functionality and should work for all game systems if configured correctly. More speed providers can be made available via game systems and installed modules. Selecting a different speed provider than the generic speed provider may offer a better integration into the rules of the game system you're using. The options below are dependent upon the speed provider selected here.",
|
||||||
"choices": {
|
"choices": {
|
||||||
"module": "Module {name}",
|
"module": "Module {name}",
|
||||||
"native": "Generic",
|
"native": "Generic",
|
||||||
|
|||||||
+51
-17
@@ -8,24 +8,24 @@
|
|||||||
"ok": "OK",
|
"ok": "OK",
|
||||||
"terrain-ruler": {
|
"terrain-ruler": {
|
||||||
"title": "Cómo medir a través de terreno difícil con Drag 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.",
|
"text": "Tienes habilitado el módulo {moduleName}. Drag Ruler puede medir terrenos difíciles que se colocaron usando ese módulo. Para hacer esto posible, Drag Ruler utiliza el módulo Terrain Ruler. Si Terrain Ruler está instalado y activado, Drag Ruler automáticamente comenzará a respetar terrenos difíciles en sus medidas.",
|
||||||
"neverShowAgain": "No volver a mostrar"
|
"neverShowAgain": "No volver a mostrar"
|
||||||
},
|
},
|
||||||
"socketlib": {
|
"socketlib": {
|
||||||
"title": "El módulo Socketlib no está instalado",
|
"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"
|
"text": "Drag Ruler necesita para su funcionamiento correcto que instale socketlib. Por favor, active socketlib en este mundo."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"resetMovementHistory": "Reiniciar historial de movimiento",
|
"resetMovementHistory": "Reiniciar el historial de movimiento",
|
||||||
"genericSpeedProvider": {
|
"genericSpeedProvider": {
|
||||||
"settings": {
|
"settings": {
|
||||||
"dashMultiplier": {
|
"dashMultiplier": {
|
||||||
"name": "Multiplicador por correr",
|
"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"
|
"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": {
|
"speedAttribute": {
|
||||||
"name": "Attributo de velocidad",
|
"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"
|
"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": {
|
"speeds": {
|
||||||
@@ -33,26 +33,60 @@
|
|||||||
"dash": "correr"
|
"dash": "correr"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"keybindings": {
|
||||||
|
"cancelDrag": "Cancelar arrastre",
|
||||||
|
"createWaypoint": "Crear punto de ruta",
|
||||||
|
"deleteWaypoint": "Borrar punto de ruta",
|
||||||
|
"disableSnap": {
|
||||||
|
"name": "Deshabilitar ajuste",
|
||||||
|
"hint": "El ajuste a la rejilla se deshabilitará temporalmente mientras se presione esta tecla."
|
||||||
|
},
|
||||||
|
"moveWithoutAnimation": {
|
||||||
|
"name": "Deshabilitar animación de icono",
|
||||||
|
"hint": "Si al soltar un icono se presiona esta tecla, se deshabilitará la animación del icono al moverse al destino."
|
||||||
|
},
|
||||||
|
"togglePathfinding": {
|
||||||
|
"name": "Conmutar búsqueda de camino",
|
||||||
|
"hint": "Cuando se presione al arrastrar un icono, la funcionalidad de búsqueda de camino será temporalmente habilitada/deshabilitada."
|
||||||
|
}
|
||||||
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
"allowPathfinding": {
|
||||||
|
"name": "Permitir búsqueda de camino a los jugadores",
|
||||||
|
"hint": "Permite a los jugadores usar en este mundo la funcionalidad de búsqueda de camino. Tenga cuidado porque la ruta puede transcurrir por lugares con niebla de guerra o muros invisibles, lo que podrá revelar algunos secretos a los jugadores antes de tiempo."
|
||||||
|
},
|
||||||
"alwaysShowSpeedForPCs": {
|
"alwaysShowSpeedForPCs": {
|
||||||
"name": "Mostrar velocidad de los PJs a todo el mundo",
|
"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"
|
"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": {
|
"autoStartMeasurement": {
|
||||||
"name": "Comenzar a medir automáticamente",
|
"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"
|
"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."
|
||||||
|
},
|
||||||
|
"autoPathfinding": {
|
||||||
|
"name": "Búsqueda de camino por defecto",
|
||||||
|
"hint": "Si se habilita, al arrastrar un icono se usará automáticamente la regla de búsqueda de camino."
|
||||||
},
|
},
|
||||||
"enableMovementHistory": {
|
"enableMovementHistory": {
|
||||||
"name": "Habilitar historial de movimiento durante el combate",
|
"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"
|
"hint": "Si se habilita, Drag Ruler recordará la ruta que ha seguido un icono en su turno de combate y la mostrará al seleccionarlo de nuevo."
|
||||||
|
},
|
||||||
|
"rightClickAction": {
|
||||||
|
"name": "Acción del botón derecho",
|
||||||
|
"hint": "¿Qué acción se realizará al hacer clic derecho mientras se arrastra un icono?",
|
||||||
|
"choices": {
|
||||||
|
"create": "Crear punto de ruta",
|
||||||
|
"delete": "Borrar punto de ruta",
|
||||||
|
"cancel": "Cancelar arrastre"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"showGMRulerToPlayers": {
|
"showGMRulerToPlayers": {
|
||||||
"name": "Mostrar regla del GM a los jugadores",
|
"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"
|
"hint": "Si se deshabilita, no se mostrará la regla de medición de los GMs al resto de usuarios no-GMs."
|
||||||
},
|
},
|
||||||
"speedProviderSettings": {
|
"speedProviderSettings": {
|
||||||
"name": "Proveedor de configuración de velocidad",
|
"name": "Proveedor de configuración de velocidad",
|
||||||
"hint": "El proveedor de ajustes de velocidad contiene todos los ajustes específicos de cada sistema",
|
"hint": "El Proveedor de Ajustes de Velocidad contiene todos los ajustes específicos de cada sistema de juego.",
|
||||||
"button": "Proveedor de configuración de velocidad",
|
"button": "Proveedor de configuración de velocidad",
|
||||||
"windowTitle": "Proveedor de configuración de velocidad",
|
"windowTitle": "Proveedor de configuración de velocidad",
|
||||||
"headers": {
|
"headers": {
|
||||||
@@ -60,21 +94,21 @@
|
|||||||
"speedProviderSettings": "Configuraciones específicas del proveedor de velocidad"
|
"speedProviderSettings": "Configuraciones específicas del proveedor de velocidad"
|
||||||
},
|
},
|
||||||
"activeProvider": {
|
"activeProvider": {
|
||||||
"name": "Proveedor de velocidad actualmente activo",
|
"name": "Proveedor de Velocidad 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"
|
"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",
|
"noSettings": "Este proveedor de velocidad no dispone de ninguna opción de configuración.",
|
||||||
"color": {
|
"color": {
|
||||||
"name": "Color para {colorName}",
|
"name": "Color para {colorName}",
|
||||||
"hint": "Color que se usará para establecer el tono de las cuadrículas que se encuentran dentro del rango {colorName}",
|
"hint": "Color que se usará para establecer el tono de las cuadrículas que se encuentran dentro del rango {colorName}.",
|
||||||
"unreachable": {
|
"unreachable": {
|
||||||
"name": "inalcanzable",
|
"name": "inalcanzable",
|
||||||
"hint": "Color que se usará para las cuadrículas más allá de la distancia máxima de movimiento del icono"
|
"hint": "Color que se usará para las cuadrículas más allá de la distancia máxima de movimiento del icono."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"speedProvider": {
|
"speedProvider": {
|
||||||
"name": "Proveedor de configuración de velocidad",
|
"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",
|
"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": {
|
"choices": {
|
||||||
"module": "Módulo {name}",
|
"module": "Módulo {name}",
|
||||||
"native": "Genérico",
|
"native": "Genérico",
|
||||||
@@ -84,7 +118,7 @@
|
|||||||
},
|
},
|
||||||
"useGridlessRaster": {
|
"useGridlessRaster": {
|
||||||
"name": "Usar ajuste a la velocidad",
|
"name": "Usar ajuste a la velocidad",
|
||||||
"hint": "Cuando no se use rejilla en la escena, este ajuste hace que los iconos se ajusten a sus rangos de velocidad. Se puede deshabilitar temporalmente pulsando la tecla Mayús mientras se arrastra"
|
"hint": "Cuando no se use rejilla en la escena, este ajuste hace que los iconos se ajusten a sus rangos de velocidad."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+52
-16
@@ -4,31 +4,53 @@
|
|||||||
"ok": "OK",
|
"ok": "OK",
|
||||||
"terrain-ruler": {
|
"terrain-ruler": {
|
||||||
"title": "Comment mesurer un terrain difficile avec Drag Ruler",
|
"title": "Comment mesurer un terrain difficile avec Drag Ruler",
|
||||||
"text": "Vous avez le module {moduleName} activé. Drag Ruler est capable de mesurer un terrain difficile qui a été placé à l'aide de ce module. Pour rendre cela possible, Drag Ruler utilise le module Terrain Ruler. Si Terrain Ruler est installé et activé Drag Ruler commencera automatiquement à respecter le terrain difficile dans ses mesures.",
|
"text": "Vous avez le module {moduleName} activé. Drag Ruler est capable de mesurer un terrain difficile qui a été placé à l'aide de ce module. Pour rendre cela possible, Drag Ruler utilise le module Terrain Ruler. Si Terrain Ruler est installé et activé, Drag Ruler commencera automatiquement à respecter le terrain difficile lors de ses mesures.",
|
||||||
"neverShowAgain": "Ne plus jamais afficher cela"
|
"neverShowAgain": "Ne plus jamais afficher cela"
|
||||||
},
|
},
|
||||||
"socketlib": {
|
"socketlib": {
|
||||||
"title": "Module Socketlib manquant",
|
"title": "Module Socketlib manquant",
|
||||||
"text": "Drag Ruler nécessite que le module socketlib fonctionne correctement. Veuillez activer le module socketlib dans ce monde."
|
"text": "Drag Ruler nécessite que le module socketlib fonctionne correctement. Veuillez activer le module socketlib dans ce monde."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"resetMovementHistory": "Réinitialiser l'historique des mouvements",
|
"resetMovementHistory": "Réinitialiser l'historique des mouvements",
|
||||||
"genericSpeedProvider": {
|
"genericSpeedProvider": {
|
||||||
"settings": {
|
"settings": {
|
||||||
"dashMultiplier": {
|
"dashMultiplier": {
|
||||||
"name": "Multiplicateur de courses",
|
"name": "Multiplicateur de course",
|
||||||
"hint": "Cela peut être utilisé pour donner aux jetons une vitesse secondaire pendant la coloration du chemin mesuré. Réglez-le sur 0 pour désactiver la vitesse secondaire."
|
"hint": "Cela peut être utilisé pour donner aux jetons une vitesse secondaire pendant la coloration du chemin mesuré. Réglez-le sur 0 pour désactiver la vitesse secondaire."
|
||||||
},
|
},
|
||||||
"speedAttribute": {
|
"speedAttribute": {
|
||||||
"name": "Attribut de vitesse",
|
"name": "Attribut de vitesse",
|
||||||
"hint": "L'attribut qui définit la vitesse de marche d'un jeton. Il est utilisé lors de la coloration du chemin mesuré." }
|
"hint": "L'attribut qui définit la vitesse de marche d'un jeton. Il est utilisé lors de la coloration du chemin mesuré."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"speeds": {
|
"speeds": {
|
||||||
"walk": "marche",
|
"walk": "marche",
|
||||||
"dash": "course"
|
"dash": "course"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"keybindings": {
|
||||||
|
"cancelDrag": "Effacer glisser",
|
||||||
|
"createWaypoint": "Créer un point de passage",
|
||||||
|
"deleteWaypoint": "Effacer un point de passage",
|
||||||
|
"disableSnap": {
|
||||||
|
"name": "Désactiver l'interruption",
|
||||||
|
"hint": "L'interruption sera temporairement désactivée tant que cette clé est maintenue enfoncée."
|
||||||
|
},
|
||||||
|
"moveWithoutAnimation": {
|
||||||
|
"name": "Désactiver l'animation du jeton",
|
||||||
|
"hint": "Lorsqu'elle est maintenue lorsque vous déposer un jeton, le jeton se déplacera vers le lieu ciblé sans s'animer."
|
||||||
|
},
|
||||||
|
"togglePathfinding": {
|
||||||
|
"name": "Activer la recherche de l'itinéraire",
|
||||||
|
"hint": "Lorsqu'elle est maintenue en glissant un jeton, la fonction de recherche de l'itinéraire sera temporairement activée/désactivée."
|
||||||
|
}
|
||||||
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
"allowPathfinding": {
|
||||||
|
"name": "Autoriser la recherche de l'itinéraire pour les joueurs",
|
||||||
|
"hint": "Permet aux joueurs d'utiliser la fonction de recherche de l'itinéraire de Drag Ruler dans ce monde. Soyez conscient que la recherche de l'itinéraire peut conduire à travers le brouillard de guerre inexploré et les murs éthérés, ce qui pourrait révéler des secrets à vos joueurs avant l'heure."
|
||||||
|
},
|
||||||
"alwaysShowSpeedForPCs": {
|
"alwaysShowSpeedForPCs": {
|
||||||
"name": "Afficher la vitesse du PJ à tout le monde",
|
"name": "Afficher la vitesse du PJ à tout le monde",
|
||||||
"hint": "Si activé, la coloration basée sur la vitesse de l'acteur pour les personnages joueurs sera montrée à tout le monde, même s'ils n'ont pas la permission d'observateur pour la feuille de personnage."
|
"hint": "Si activé, la coloration basée sur la vitesse de l'acteur pour les personnages joueurs sera montrée à tout le monde, même s'ils n'ont pas la permission d'observateur pour la feuille de personnage."
|
||||||
@@ -37,13 +59,26 @@
|
|||||||
"name": "Démarrer automatiquement la mesure",
|
"name": "Démarrer automatiquement la mesure",
|
||||||
"hint": "Si activé, Drag Ruler commencera à mesurer dès que le jeton est glissé. S'il est désactivé, Drag Ruler restera inactif et ne commencera à mesurer qu'une fois que le bouton pour ajouter un waypoint est enfoncé."
|
"hint": "Si activé, Drag Ruler commencera à mesurer dès que le jeton est glissé. S'il est désactivé, Drag Ruler restera inactif et ne commencera à mesurer qu'une fois que le bouton pour ajouter un waypoint est enfoncé."
|
||||||
},
|
},
|
||||||
|
"autoPathfinding": {
|
||||||
|
"name": "Recherche d'itinéraire par défaut",
|
||||||
|
"hint": "Si activé, glisser un jeton utilisera automatiquement une recherche de l'itinéraire."
|
||||||
|
},
|
||||||
"enableMovementHistory": {
|
"enableMovementHistory": {
|
||||||
"name": "Activer l'historique des mouvements pendant le combat",
|
"name": "Activer l'historique des mouvements pendant le combat",
|
||||||
"hint": "Si activé, Drag Ruler se souviendra du chemin emprunté par un jeton pendant son tour de combat et l'affichera lorsque vous récupérerez le jeton."
|
"hint": "Si activé, Drag Ruler se souviendra du chemin emprunté par un jeton pendant son tour de combat et l'affichera lorsque vous récupérerez le jeton."
|
||||||
},
|
},
|
||||||
|
"rightClickAction": {
|
||||||
|
"name": "action de Clic droit",
|
||||||
|
"hint": "Quelle sera l'action accomplie lorsque vous ferez un clic droit en glissant un jeton ?",
|
||||||
|
"choices": {
|
||||||
|
"create": "Créer un point de passage",
|
||||||
|
"delete": "Effacer un point de passage",
|
||||||
|
"cancel": "Annuler le glisser"
|
||||||
|
}
|
||||||
|
},
|
||||||
"showGMRulerToPlayers": {
|
"showGMRulerToPlayers": {
|
||||||
"name": "Afficher la règle MJ aux joueurs",
|
"name": "Afficher la règle MJ aux joueurs",
|
||||||
"hint": "Si désactivé, la règle des MJ ne sera pas affichée pour les joueurs non-MJ"
|
"hint": "Si désactivé, la règle des MJ ne sera pas affichée pour les joueurs non MJ."
|
||||||
},
|
},
|
||||||
"speedProviderSettings": {
|
"speedProviderSettings": {
|
||||||
"name": "Paramètres du système de vitesse",
|
"name": "Paramètres du système de vitesse",
|
||||||
@@ -51,35 +86,36 @@
|
|||||||
"button": "Paramètres du système de vitesse",
|
"button": "Paramètres du système de vitesse",
|
||||||
"windowTitle": "Paramètres du système de vitesse",
|
"windowTitle": "Paramètres du système de vitesse",
|
||||||
"headers": {
|
"headers": {
|
||||||
"speedProvider": "système de vitesse",
|
"speedProvider": "Système de vitesse",
|
||||||
"speedProviderSettings": "Paramètres spécifiques au système de vitesse"
|
"speedProviderSettings": "Paramètres spécifiques au système de vitesse"
|
||||||
},
|
},
|
||||||
"activeProvider": {
|
"activeProvider": {
|
||||||
"name": "système de vitesse actuellement actif",
|
"name": "Système de vitesse actuellement actif",
|
||||||
"hint": "Les paramètres indiqués ci-dessous dépendent du système de vitesse actif. Si le MJ sélectionne un système de vitesse différent, les paramètres disponibles peuvent changer."
|
"hint": "Les paramètres indiqués ci-dessous dépendent du système de vitesse actif. Si le MJ sélectionne un système de vitesse différent, les paramètres disponibles peuvent changer."
|
||||||
},
|
},
|
||||||
"noSettings": "Ce système de vitesse n'offre aucune option de configuration.",
|
"noSettings": "Ce système de vitesse n'offre aucune option de configuration.",
|
||||||
"color": {
|
"color": {
|
||||||
"name": "Couleur pour {colorName}",
|
"name": "Couleur pour {colorName}",
|
||||||
"hint": "La couleur qui sera utilisée pour colorier les carrés qui sont dans la plage {colorName}",
|
"hint": "La couleur qui sera utilisée pour colorier les carrés qui sont dans la portée {colorName}.",
|
||||||
"unreachable": {
|
"unreachable": {
|
||||||
"name": "injoignable",
|
"name": "inaccessible",
|
||||||
"hint": "La couleur des espaces qui ne sont pas accessibles par le jeton déplacé"
|
"hint": "La couleur des cases qui ne sont pas accessibles pour le jeton déplacé."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"speedProvider": {
|
"speedProvider": {
|
||||||
"name": "système de paramètres de vitesse",
|
"name": "Système de paramètres de vitesse",
|
||||||
"indice": "Sélectionnez qui fournit les informations de vitesse pour les jetons lors de la coloration. Drag Ruler offre un système de vitesse générique qui fournit des fonctionnalités de base et devrait fonctionner pour tous les systèmes de jeu s'il est correctement configuré. Plus de systèmes de vitesse peuvent être mis à disposition via les systèmes de jeu et les modules installés . La sélection d'un autre système de vitesse que le système de vitesse générique peut offrir une meilleure intégration dans les règles du système de jeu que vous utilisez. Les options ci-dessous dépendent du système de vitesse sélectionné ici.",
|
"indice": "Sélectionnez qui fournit les informations de vitesse pour les jetons lors de la coloration. Drag Ruler offre un système de vitesse générique qui fournit des fonctionnalités de base et devrait fonctionner pour tous les systèmes de jeu s'il est correctement configuré. Plus de systèmes de vitesse peuvent être mis à disposition via les systèmes de jeu et les modules installés. La sélection d'un autre système de vitesse que le système de vitesse générique peut offrir une meilleure intégration dans les règles du système de jeu que vous utilisez. Les options ci-dessous dépendent du système de vitesse sélectionné ici.",
|
||||||
"choices": {
|
"choices": {
|
||||||
"module": "Module {name}",
|
"module": "Module {name}",
|
||||||
"native": "Générique",
|
"native": "Générique",
|
||||||
"system": "Systéme {name}"
|
"system": "Systéme {name}"
|
||||||
}
|
},
|
||||||
|
"hint": "Choisissez qui fournit les informations de vitesse pour les jetons lors de la coloration. Drag Ruler propose un fournisseur de vitesse générique qui offre une fonctionnalité de base qui devrait fonctionner pour tous les systèmes de jeu s'il est correctement configuré. D'autres fournisseurs de vitesse peuvent être mis à disposition par les systèmes de jeu et les modules installés. Le choix d'un fournisseur de vitesse différent du fournisseur de vitesse générique peut offrir une meilleure intégration des règles du système de jeu que vous utilisez. Les options ci-dessous dépendent du fournisseur de vitesse sélectionné ici."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"useGridlessRaster": {
|
"useGridlessRaster": {
|
||||||
"name": "Use speed based snapping",
|
"name": "Utilise l'interruption basée sur la vitesse",
|
||||||
"hint": "On Gridless scenes, this makes tokens snap to the token's speed ranges. This can be temporarily disabled by pressing Shift during drag."
|
"hint": "Sur des scènes sans grilles, cela permet aux jetons de s'interrompre à la portée de leur vitesse. Cela peut être temporairement désactivé en pressant Shift en faisant glisser."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+121
@@ -0,0 +1,121 @@
|
|||||||
|
{
|
||||||
|
"drag-ruler": {
|
||||||
|
"dependencies": {
|
||||||
|
"ok": "Ok",
|
||||||
|
"terrain-ruler": {
|
||||||
|
"neverShowAgain": "Nunca mostre isso de novo",
|
||||||
|
"title": "Como medir terrenos difíceis com o Drag Ruler",
|
||||||
|
"text": "Você tem o módulo {moduleName} habilitado. Drag Ruler é capaz de medir terreno difícil que foi colocado para baixo usando esse módulo. Para tornar isto possível, Drag Ruler utiliza o módulo Terrain Ruler. Se o Terrain Ruler for instalado e ativado, o Drag Ruler começará automaticamente a respeitar o terreno difícil em suas medições."
|
||||||
|
},
|
||||||
|
"socketlib": {
|
||||||
|
"title": "Módulo Socketlib ausente",
|
||||||
|
"text": "Drag Ruler requer que o módulo socketlib funcione corretamente. Por favor, ative o módulo socketlib neste mundo."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resetMovementHistory": "Redefinir histórico de movimento",
|
||||||
|
"genericSpeedProvider": {
|
||||||
|
"settings": {
|
||||||
|
"dashMultiplier": {
|
||||||
|
"name": "Traço Multiplicador",
|
||||||
|
"hint": "Isso pode ser usado para dar aos tokens uma velocidade secundária durante a coloração do caminho medido. Defina-o como 0 para desativar a velocidade secundária."
|
||||||
|
},
|
||||||
|
"speedAttribute": {
|
||||||
|
"name": "Atributo de velocidade",
|
||||||
|
"hint": "O atributo que define a velocidade de caminhada de um token. Isso é usado durante a coloração do caminho medido."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"speeds": {
|
||||||
|
"walk": "andar",
|
||||||
|
"dash": "traço"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"keybindings": {
|
||||||
|
"cancelDrag": "Cancelar o arrasto",
|
||||||
|
"disableSnap": {
|
||||||
|
"name": "Desativar encaixe",
|
||||||
|
"hint": "O encaixe será desativado temporariamente enquanto esta tecla estiver pressionada."
|
||||||
|
},
|
||||||
|
"createWaypoint": "Criar parada",
|
||||||
|
"deleteWaypoint": "Apagar parada",
|
||||||
|
"togglePathfinding": {
|
||||||
|
"name": "Alternar localização de caminhos",
|
||||||
|
"hint": "Ao ser segurado enquanto arrasta um token, a funcionalidade de localização de caminhos será temporariamente habilitada/desabilitada."
|
||||||
|
},
|
||||||
|
"moveWithoutAnimation": {
|
||||||
|
"name": "Desativar animação de token",
|
||||||
|
"hint": "Ao ser segurado ao soltar um token, o token se moverá para o local de destino sem animação."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"allowPathfinding": {
|
||||||
|
"name": "Permitir localização de caminhos para jogadores",
|
||||||
|
"hint": "Permite que os jogadores usem a funcionalidade de localização de caminhos do Drag Ruler neste mundo. Esteja ciente de que a localização de caminhos pode passar por uma névoa de guerra inexplorada e paredes etéreas, que podem revelar segredos para seus jogadores antes do tempo."
|
||||||
|
},
|
||||||
|
"alwaysShowSpeedForPCs": {
|
||||||
|
"name": "Mostre a velocidade do PJ para todos",
|
||||||
|
"hint": "Se ativado, a coloração baseada na velocidade do ator para os personagens dos jogadores será mostrada a todos, mesmo que eles não tenham permissão de observador para a ficha de personagem."
|
||||||
|
},
|
||||||
|
"autoStartMeasurement": {
|
||||||
|
"name": "Iniciar a medição automaticamente",
|
||||||
|
"hint": "Se ativado, Drag Ruler começará a medir assim que o token estiver sendo arrastado. Se desativado, o Drag Ruler permanecerá inativo e só começará a medir quando o botão para adicionar uma parada for pressionado."
|
||||||
|
},
|
||||||
|
"autoPathfinding": {
|
||||||
|
"hint": "Se ativado, arrastar um token usará automaticamente uma régua de localização de caminho.",
|
||||||
|
"name": "localização de caminho por padrão"
|
||||||
|
},
|
||||||
|
"enableMovementHistory": {
|
||||||
|
"name": "Habilitar histórico de movimento durante o combate",
|
||||||
|
"hint": "Se ativado, o Drag Ruler lembrará o caminho que um token percorreu durante seu turno em combate e o exibirá quando você pegar o token de volta."
|
||||||
|
},
|
||||||
|
"rightClickAction": {
|
||||||
|
"name": "Ação do botão direito",
|
||||||
|
"hint": "Que ação deve ser executada ao clicar com o botão direito do mouse enquanto arrasta um token?",
|
||||||
|
"choices": {
|
||||||
|
"create": "Criar parada",
|
||||||
|
"delete": "Apagar parada",
|
||||||
|
"cancel": "Cancelar arrasto"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"showGMRulerToPlayers": {
|
||||||
|
"name": "Mostrar régua do GM aos jogadores",
|
||||||
|
"hint": "Se desativado, a régua dos GMs não será exibido para jogadores não-GM."
|
||||||
|
},
|
||||||
|
"speedProviderSettings": {
|
||||||
|
"name": "Configurações do provedor de velocidade",
|
||||||
|
"button": "Configurações do provedor de velocidade",
|
||||||
|
"windowTitle": "Configurações do provedor de velocidade",
|
||||||
|
"headers": {
|
||||||
|
"speedProvider": "Provedor de velocidade",
|
||||||
|
"speedProviderSettings": "Configurações específicas do provedor de velocidade"
|
||||||
|
},
|
||||||
|
"activeProvider": {
|
||||||
|
"name": "Provedor de velocidade ativo",
|
||||||
|
"hint": "As configurações mostradas abaixo dependem do provedor de velocidade ativo. Se o GM selecionar um provedor de velocidade diferente, as configurações disponíveis podem mudar."
|
||||||
|
},
|
||||||
|
"noSettings": "Este provedor de velocidade não oferece nenhuma opção de configuração.",
|
||||||
|
"color": {
|
||||||
|
"name": "Cor para {colorName}",
|
||||||
|
"hint": "A cor que será usada para colorir os quadrados que estão dentro do intervalo {colorName}.",
|
||||||
|
"unreachable": {
|
||||||
|
"name": "inacessível",
|
||||||
|
"hint": "A cor dos espaços que não podem ser acessados pelo token arrastado."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"speedProvider": {
|
||||||
|
"name": "Provedor de configurações de velocidade",
|
||||||
|
"hint": "Selecione quem fornece informações de velocidade para tokens durante a coloração. Drag Ruler oferece um provedor de velocidade genérico que fornece funcionalidade básica e deve funcionar para todos os sistemas de jogo se configurado corretamente. Mais provedores de velocidade podem ser disponibilizados por meio de sistemas de jogos e módulos instalados. Selecionar um provedor de velocidade diferente do provedor de velocidade genérico pode oferecer uma melhor integração com as regras do sistema de jogo que você está usando. As opções abaixo dependem do provedor de velocidade selecionado aqui.",
|
||||||
|
"choices": {
|
||||||
|
"module": "Módulo {name}",
|
||||||
|
"native": "Genérico",
|
||||||
|
"system": "Sistema {name}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hint": "As configurações do provedor de velocidade contêm todas as configurações específicas do sistema de jogo."
|
||||||
|
},
|
||||||
|
"useGridlessRaster": {
|
||||||
|
"name": "Use o ajuste baseado em velocidade",
|
||||||
|
"hint": "Em cenas sem grid, isso faz com que os tokens se encaixem nas faixas de velocidade do token."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
{
|
||||||
|
"drag-ruler": {
|
||||||
|
"dependencies": {
|
||||||
|
"ok": "OK",
|
||||||
|
"terrain-ruler": {
|
||||||
|
"title": "Drag Ruler 如何测量困难地形",
|
||||||
|
"text": "你已启用 {moduleName}。Drag Ruler 可以使用此模组测量放置的困难地形。为此,Drag Ruler 将使用 Terrain Ruler 模组。如果已经安装并启用了 Terrain Ruler,Drag Ruler 将自动开始在测量中反映困难地形的结果。",
|
||||||
|
"neverShowAgain": "不再显示此条"
|
||||||
|
},
|
||||||
|
"socketlib": {
|
||||||
|
"title": "Socketlib 模组未安装/启用",
|
||||||
|
"text": "Drag Ruler 依赖于 socketlib 模组以正常运行。请在该世界中激活 socketlib 模组。"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resetMovementHistory": "重置移动历史",
|
||||||
|
"settings": {
|
||||||
|
"alwaysShowSpeedForPCs": {
|
||||||
|
"name": "向所有人展示 PC 的速度",
|
||||||
|
"hint": "启用后,基于 PC 速度的着色将会展示给所有人,即使并未对该角色有观察权限。"
|
||||||
|
},
|
||||||
|
"speedProviderSettings": {
|
||||||
|
"speedProvider": {
|
||||||
|
"choices": {
|
||||||
|
"native": "通用",
|
||||||
|
"system": "系统 {name}",
|
||||||
|
"module": "模组 {name}"
|
||||||
|
},
|
||||||
|
"name": "速度设置提供方",
|
||||||
|
"hint": "选择指示物移动着色时,速度信息的提供方。Drag Ruler 自带通用的速度提供方,提供基础功能并且在配置正确时,应对所有游戏系统都有效。游戏系统和安装的模组可能会提供可用的新速度提供方。选择其他速度提供方替换通用速度提供方可能会与正在使用的游戏系统的规则整合得更好。下列的选项依赖于此处选择的速度提供方。"
|
||||||
|
},
|
||||||
|
"name": "速度提供方设置",
|
||||||
|
"hint": "速度提供方设置内包含了所有游戏系统相关的设置。",
|
||||||
|
"button": "速度提供方设置",
|
||||||
|
"windowTitle": "速度提供方设置",
|
||||||
|
"headers": {
|
||||||
|
"speedProvider": "速度提供",
|
||||||
|
"speedProviderSettings": "速度提供方特定设置"
|
||||||
|
},
|
||||||
|
"activeProvider": {
|
||||||
|
"name": "激活的速度提供方",
|
||||||
|
"hint": "下方显示的此设置依赖于一个激活的速度提供方。如果 GM 选择了其他的速度提供方,可用的设置项可能会改变。"
|
||||||
|
},
|
||||||
|
"noSettings": "此速度提供方并不提供任何配置项。",
|
||||||
|
"color": {
|
||||||
|
"name": "颜色 {colorName}",
|
||||||
|
"hint": "将在 {colorName} 范围内的色彩格子内使用的颜色。",
|
||||||
|
"unreachable": {
|
||||||
|
"name": "不可抵达",
|
||||||
|
"hint": "拖拽指示物时显示的不可抵达的格子的颜色。"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"useGridlessRaster": {
|
||||||
|
"name": "使用基于速度的吸附",
|
||||||
|
"hint": "在无网格场景中,这使得指示物吸附至其速度范围。"
|
||||||
|
},
|
||||||
|
"allowPathfinding": {
|
||||||
|
"name": "允许玩家寻路",
|
||||||
|
"hint": "允许玩家在此世界中使用Drag Ruler的寻路功能。注意寻路可能经过未探索的战争迷雾和虚体墙,这可能提前向玩家暴露秘密。"
|
||||||
|
},
|
||||||
|
"autoStartMeasurement": {
|
||||||
|
"name": "自动测量",
|
||||||
|
"hint": "如果启用此选项,在你拖动棋子时标尺会自动显示,如果禁用,拖动棋子时将不会启用标尺,仅在按下显示路径按钮时显示。"
|
||||||
|
},
|
||||||
|
"autoPathfinding": {
|
||||||
|
"name": "默认寻路",
|
||||||
|
"hint": "若启用,拖动指示物会自动使用寻路标尺。"
|
||||||
|
},
|
||||||
|
"enableMovementHistory": {
|
||||||
|
"name": "在战斗时启用移动历史",
|
||||||
|
"hint": "启用后,Drag Ruler 将会记住指示物在战斗中其回合内行经的路径,并且会在拉起指示物时显示。"
|
||||||
|
},
|
||||||
|
"rightClickAction": {
|
||||||
|
"name": "右键点击动作",
|
||||||
|
"hint": "拖动指示物时点击右键应该执行什么动作?",
|
||||||
|
"choices": {
|
||||||
|
"create": "创建路径点",
|
||||||
|
"delete": "删除路径点",
|
||||||
|
"cancel": "取消拖动"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"showGMRulerToPlayers": {
|
||||||
|
"name": "向玩家展示 GM 尺子",
|
||||||
|
"hint": "禁用后,GM 的测量尺将不会向非 GM 玩家展示。"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"genericSpeedProvider": {
|
||||||
|
"settings": {
|
||||||
|
"dashMultiplier": {
|
||||||
|
"name": "疾跑倍数",
|
||||||
|
"hint": "此项可以赋予测量路径着色的指示物第二速度显示。将此项设置为 0 以禁用第二速度。"
|
||||||
|
},
|
||||||
|
"speedAttribute": {
|
||||||
|
"name": "速度字段",
|
||||||
|
"hint": "该字段定义了指示物的步行速度,用于测量路径的着色。"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"speeds": {
|
||||||
|
"walk": "步行",
|
||||||
|
"dash": "疾跑"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"keybindings": {
|
||||||
|
"cancelDrag": "取消拖动",
|
||||||
|
"createWaypoint": "创建路径点",
|
||||||
|
"deleteWaypoint": "删除路径点",
|
||||||
|
"disableSnap": {
|
||||||
|
"name": "禁用吸附",
|
||||||
|
"hint": "按下此键时吸附会被暂时禁用。"
|
||||||
|
},
|
||||||
|
"moveWithoutAnimation": {
|
||||||
|
"name": "禁用指示物动画",
|
||||||
|
"hint": "放置指示物时按住,则指示物将直接移动至目标地点。"
|
||||||
|
},
|
||||||
|
"togglePathfinding": {
|
||||||
|
"name": "寻路开关",
|
||||||
|
"hint": "拖动指示物时按住,寻路功能便会暂时启用/禁用。"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+32
-16
@@ -1,15 +1,18 @@
|
|||||||
{
|
{
|
||||||
"name": "drag-ruler",
|
"id": "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.10.0",
|
"version": "1.14.2",
|
||||||
"minimumCoreVersion" : "9.238",
|
"compatibility": {
|
||||||
"compatibleCoreVersion" : "9",
|
"minimum": "12",
|
||||||
|
"verified": "12",
|
||||||
|
"maximum": "13"
|
||||||
|
},
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
"name": "Manuel Vögele",
|
"name": "Manuel Vögele",
|
||||||
"email": "develop@manuel-voegele.de",
|
"email": "develop@manuel-voegele.de",
|
||||||
"discord": "Stäbchenfisch#5107"
|
"discord": "stabchenfisch"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"esmodules": [
|
"esmodules": [
|
||||||
@@ -55,20 +58,33 @@
|
|||||||
"lang": "zh-tw",
|
"lang": "zh-tw",
|
||||||
"name": "正體中文",
|
"name": "正體中文",
|
||||||
"path": "lang/zh-tw.json"
|
"path": "lang/zh-tw.json"
|
||||||
}
|
},
|
||||||
],
|
|
||||||
"dependencies": [
|
|
||||||
{
|
{
|
||||||
"name": "socketlib",
|
"lang": "cn",
|
||||||
"manifest": "https://raw.githubusercontent.com/manuelVo/foundryvtt-socketlib/master/module.json"
|
"name": "中文(简体)",
|
||||||
|
"path": "lang/zh_Hans.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"lang": "pt-BR",
|
||||||
|
"name": "Português (Brasil)",
|
||||||
|
"path": "lang/pt_BR.json"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"relationships": {
|
||||||
|
"requires": [
|
||||||
|
{
|
||||||
|
"id": "socketlib",
|
||||||
|
"type": "module",
|
||||||
|
"manifest": "https://gitea.crovaxon.de/crovaxon/foundryvtt-socketlib/raw/branch/develop/module.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"socket": true,
|
"socket": true,
|
||||||
"url": "https://github.com/manuelVo/foundryvtt-drag-ruler",
|
"url": "https://gitea.crovaxon.de/crovaxon/foundryvtt-drag-ruler",
|
||||||
"download": "https://github.com/manuelVo/foundryvtt-drag-ruler/archive/v1.10.0.zip",
|
"download": "https://gitea.crovaxon.de/crovaxon/foundryvtt-drag-ruler/archive/v1.14.2-test.zip",
|
||||||
"manifest": "https://raw.githubusercontent.com/manuelVo/foundryvtt-drag-ruler/master/module.json",
|
"manifest": "https://gitea.crovaxon.de/crovaxon/foundryvtt-drag-ruler/raw/branch/develop/module.json",
|
||||||
"readme": "https://github.com/manuelVo/foundryvtt-drag-ruler/blob/master/README.md",
|
"readme": "https://gitea.crovaxon.de/crovaxon/foundryvtt-drag-ruler/blob/master/README.md",
|
||||||
"changelog": "https://github.com/manuelVo/foundryvtt-drag-ruler/blob/master/CHANGELOG.md",
|
"changelog": "https://gitea.crovaxon.de/crovaxon/foundryvtt-drag-ruler/blob/master/CHANGELOG.md",
|
||||||
"bugs": "https://github.com/manuelVo/foundryvtt-drag-ruler/issues",
|
"bugs": "https://gitea.crovaxon.de/crovaxon/foundryvtt-drag-ruler/issues",
|
||||||
"allowBugReporter": true
|
"allowBugReporter": true
|
||||||
}
|
}
|
||||||
|
|||||||
+111
-90
@@ -1,181 +1,202 @@
|
|||||||
import {measureDistances} from "./compatibility.js";
|
import {measureDistances} from "./compatibility.js";
|
||||||
import {getMovementHistory} from "./movement_tracking.js";
|
import {getMovementHistory} from "./movement_tracking.js";
|
||||||
import {GenericSpeedProvider, SpeedProvider} from "./speed_provider.js"
|
import {GenericSpeedProvider, SpeedProvider} from "./speed_provider.js";
|
||||||
import {settingsKey} from "./settings.js"
|
import {settingsKey} from "./settings.js";
|
||||||
import {getTokenShape} from "./util.js";
|
import {getAreaFromPositionAndShape, getTokenShape} from "./util.js";
|
||||||
|
|
||||||
export const availableSpeedProviders = {}
|
export const availableSpeedProviders = {};
|
||||||
export let currentSpeedProvider = undefined
|
export let currentSpeedProvider = undefined;
|
||||||
|
|
||||||
function register(module, type, speedProvider) {
|
function register(module, type, speedProvider) {
|
||||||
const id = `${type}.${module.id}`
|
const id = `${type}.${module.id}`;
|
||||||
let providerInstance
|
let providerInstance;
|
||||||
if (speedProvider.prototype instanceof SpeedProvider) {
|
if (speedProvider.prototype instanceof SpeedProvider) {
|
||||||
providerInstance = new speedProvider(id)
|
providerInstance = new speedProvider(id);
|
||||||
|
} 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.usesRuler = () => true;
|
||||||
|
providerInstance = speedProvider;
|
||||||
}
|
}
|
||||||
else {
|
setupProvider(providerInstance);
|
||||||
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.usesRuler = () => true
|
|
||||||
providerInstance = speedProvider
|
|
||||||
}
|
|
||||||
setupProvider(providerInstance)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupProvider(speedProvider) {
|
function setupProvider(speedProvider) {
|
||||||
if (speedProvider instanceof SpeedProvider) {
|
if (speedProvider instanceof SpeedProvider) {
|
||||||
const unreachableColor = {id: "unreachable", default: speedProvider.defaultUnreachableColor, name: "drag-ruler.settings.speedProviderSettings.color.unreachable.name"}
|
const unreachableColor = {
|
||||||
|
id: "unreachable",
|
||||||
|
default: speedProvider.defaultUnreachableColor,
|
||||||
|
name: "drag-ruler.settings.speedProviderSettings.color.unreachable.name",
|
||||||
|
};
|
||||||
for (const color of speedProvider.colors.concat([unreachableColor])) {
|
for (const color of speedProvider.colors.concat([unreachableColor])) {
|
||||||
game.settings.register(settingsKey, `speedProviders.${speedProvider.id}.color.${color.id}`, {
|
game.settings.register(settingsKey, `speedProviders.${speedProvider.id}.color.${color.id}`, {
|
||||||
config: false,
|
config: false,
|
||||||
scope: "client",
|
scope: "client",
|
||||||
type: Number,
|
type: Number,
|
||||||
default: color.default,
|
default: color.default,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
for (const setting of speedProvider.settings) {
|
for (const setting of speedProvider.settings) {
|
||||||
setting.config = false
|
setting.config = false;
|
||||||
game.settings.register(settingsKey, `speedProviders.${speedProvider.id}.setting.${setting.id}`, setting)
|
game.settings.register(
|
||||||
|
settingsKey,
|
||||||
|
`speedProviders.${speedProvider.id}.setting.${setting.id}`,
|
||||||
|
setting,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
availableSpeedProviders[speedProvider.id] = speedProvider
|
availableSpeedProviders[speedProvider.id] = speedProvider;
|
||||||
game.settings.settings.get("drag-ruler.speedProvider").default = getDefaultSpeedProvider()
|
game.settings.settings.get("drag-ruler.speedProvider").default = getDefaultSpeedProvider();
|
||||||
updateSpeedProvider()
|
updateSpeedProvider();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDefaultSpeedProvider() {
|
export function getDefaultSpeedProvider() {
|
||||||
const providerIds = Object.keys(availableSpeedProviders)
|
const providerIds = Object.keys(availableSpeedProviders);
|
||||||
// Game systems take the highest precedence for the being the default
|
// Game systems take the highest precedence for the being the default
|
||||||
const gameSystem = providerIds.find(key => key.startsWith("system."))
|
const gameSystem = providerIds.find(key => key.startsWith("system."));
|
||||||
if (gameSystem)
|
if (gameSystem) return gameSystem;
|
||||||
return gameSystem
|
|
||||||
|
|
||||||
// If no game system is registered modules are next up.
|
// If no game system is registered modules are next up.
|
||||||
// For lack of a method to select the best module we're just falling back to taking the next best module
|
// For lack of a method to select the best module we're just falling back to taking the next best module
|
||||||
// settingKeys should always be sorted the same way so this should achive a stable default
|
// settingKeys should always be sorted the same way so this should achive a stable default
|
||||||
const module = providerIds.find(key => key.startsWith("module."))
|
const module = providerIds.find(key => key.startsWith("module."));
|
||||||
if (module)
|
if (module) return module;
|
||||||
return module
|
|
||||||
|
|
||||||
// If neither a game system or a module is found fall back to the native implementation
|
// If neither a game system or a module is found fall back to the native implementation
|
||||||
return providerIds[0]
|
return providerIds[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateSpeedProvider() {
|
export function updateSpeedProvider() {
|
||||||
// If the configured provider is registered use that one. If not use the default provider
|
// If the configured provider is registered use that one. If not use the default provider
|
||||||
const configuredProvider = game.settings.get("drag-ruler", "speedProvider")
|
const configuredProvider = game.settings.get("drag-ruler", "speedProvider");
|
||||||
currentSpeedProvider = availableSpeedProviders[configuredProvider] ?? availableSpeedProviders[game.settings.settings.get("drag-ruler.speedProvider").default]
|
currentSpeedProvider =
|
||||||
|
availableSpeedProviders[configuredProvider] ??
|
||||||
|
availableSpeedProviders[game.settings.settings.get("drag-ruler.speedProvider").default];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initApi() {
|
export function initApi() {
|
||||||
const genericSpeedProviderInstance = new GenericSpeedProvider("native")
|
const genericSpeedProviderInstance = new GenericSpeedProvider("native");
|
||||||
setupProvider(genericSpeedProviderInstance)
|
setupProvider(genericSpeedProviderInstance);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getRangesFromSpeedProvider(token) {
|
export function getRangesFromSpeedProvider(token) {
|
||||||
try {
|
try {
|
||||||
if (currentSpeedProvider instanceof Function)
|
if (currentSpeedProvider instanceof Function) return currentSpeedProvider(token, 0x00ff00);
|
||||||
return currentSpeedProvider(token, 0x00FF00)
|
const ranges = currentSpeedProvider.getRanges(token);
|
||||||
const ranges = currentSpeedProvider.getRanges(token)
|
|
||||||
for (const range of ranges) {
|
for (const range of ranges) {
|
||||||
range.color = game.settings.get(settingsKey, `speedProviders.${currentSpeedProvider.id}.color.${range.color}`)
|
range.color = game.settings.get(
|
||||||
|
settingsKey,
|
||||||
|
`speedProviders.${currentSpeedProvider.id}.color.${range.color}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return ranges
|
return ranges;
|
||||||
}
|
} catch (e) {
|
||||||
catch (e) {
|
console.error(e);
|
||||||
console.error(e)
|
return [];
|
||||||
return []
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getUnreachableColorFromSpeedProvider() {
|
export function getUnreachableColorFromSpeedProvider() {
|
||||||
if (currentSpeedProvider instanceof Function)
|
if (currentSpeedProvider instanceof Function) return 0xff0000;
|
||||||
return 0xFF0000
|
|
||||||
try {
|
try {
|
||||||
return game.settings.get(settingsKey, `speedProviders.${currentSpeedProvider.id}.color.unreachable`)
|
return game.settings.get(
|
||||||
}
|
settingsKey,
|
||||||
catch (e) {
|
`speedProviders.${currentSpeedProvider.id}.color.unreachable`,
|
||||||
console.error(e)
|
);
|
||||||
return 0xFF0000
|
} catch (e) {
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCostFromSpeedProvider(token, area, options) {
|
|
||||||
try {
|
|
||||||
if (currentSpeedProvider instanceof Function) {
|
|
||||||
return SpeedProvider.prototype.getCostForStep.call(undefined, token, area, options);
|
|
||||||
}
|
|
||||||
return currentSpeedProvider.getCostForStep(token, area, options);
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
console.error(e);
|
console.error(e);
|
||||||
return 1;
|
return 0xff0000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getColorForDistanceAndToken(distance, token, ranges=null) {
|
export function getColorForDistanceAndToken(distance, token, ranges = null) {
|
||||||
if (!ranges) {
|
if (!ranges) {
|
||||||
ranges = getRangesFromSpeedProvider(token);
|
ranges = getRangesFromSpeedProvider(token);
|
||||||
}
|
}
|
||||||
if (ranges.length === 0)
|
if (ranges.length === 0) return null;
|
||||||
return null;
|
const currentRange = ranges.reduce(
|
||||||
const currentRange = ranges.reduce((minRange, currentRange) => {
|
(minRange, currentRange) => {
|
||||||
if (distance <= currentRange.range && currentRange.range < minRange.range)
|
if (distance <= currentRange.range && currentRange.range < minRange.range)
|
||||||
return currentRange;
|
return currentRange;
|
||||||
return minRange;
|
return minRange;
|
||||||
}, {range: Infinity, color: getUnreachableColorFromSpeedProvider()});
|
},
|
||||||
|
{range: Infinity, color: getUnreachableColorFromSpeedProvider()},
|
||||||
|
);
|
||||||
return currentRange.color;
|
return currentRange.color;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMovedDistanceFromToken(token) {
|
export function getMovedDistanceFromToken(token) {
|
||||||
const terrainRulerAvailable = game.modules.get("terrain-ruler")?.active;
|
const terrainRulerAvailable = game.modules.get("terrain-ruler")?.active;
|
||||||
const history = getMovementHistory(token);
|
const history = getMovementHistory(token);
|
||||||
const segments = Ruler.dragRulerGetRaysFromWaypoints(history, {x: token.x, y: token.y}).map(ray => {return {ray}});
|
let tokenPos = {x: token.x, y: token.y};
|
||||||
|
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
|
||||||
|
tokenPos = token.center;
|
||||||
|
}
|
||||||
|
const segments = CONFIG.Canvas.rulerClass
|
||||||
|
.dragRulerGetRaysFromWaypoints(history, tokenPos)
|
||||||
|
.map(ray => {
|
||||||
|
return {ray};
|
||||||
|
});
|
||||||
const shape = getTokenShape(token);
|
const shape = getTokenShape(token);
|
||||||
const distances = measureDistances(segments, token, shape, {enableTerrainRuler: terrainRulerAvailable});
|
const distances = measureDistances(segments, token, shape, {
|
||||||
|
enableTerrainRuler: terrainRulerAvailable,
|
||||||
|
});
|
||||||
// Sum up the distances
|
// Sum up the distances
|
||||||
return distances.reduce((acc, val) => acc + val, 0);
|
return distances.reduce((acc, val) => acc + val, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function buildCostFunction(token, shape) {
|
||||||
|
return (x, y, costOptions = {}) => {
|
||||||
|
costOptions.token = token;
|
||||||
|
const area = getAreaFromPositionAndShape({x, y}, shape);
|
||||||
|
const costs = area.map(space => canvas.terrain.cost({x: space.x, y: space.y}, costOptions));
|
||||||
|
return costs.reduce((max, current) => Math.max(max, current));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function registerModule(moduleId, speedProvider) {
|
export function registerModule(moduleId, speedProvider) {
|
||||||
// Check if a module with the given id exists and is currently enabled
|
// Check if a module with the given id exists and is currently enabled
|
||||||
const module = game.modules.get(moduleId)
|
const module = game.modules.get(moduleId);
|
||||||
// If it doesn't the calling module did something wrong. Log a warning and ignore this module
|
// If it doesn't the calling module did something wrong. Log a warning and ignore this module
|
||||||
if (!module) {
|
if (!module) {
|
||||||
console.warn(
|
console.warn(
|
||||||
`Drag Ruler | A module tried to register with the id "${moduleId}". However no active module with this id was found.` +
|
`Drag Ruler | A module tried to register with the id "${moduleId}". However no active module with this id was found.` +
|
||||||
"This api registration call was ignored. " +
|
"This api registration call was ignored. " +
|
||||||
"If you are the author of that module please check that the id passed to `registerModule` matches the id in your manifest exactly." +
|
"If you are the author of that module please check that the id passed to `registerModule` matches the id in your manifest exactly." +
|
||||||
"If this call was made form a game system instead of a module please use `registerSystem` instead.")
|
"If this call was made form a game system instead of a module please use `registerSystem` instead.",
|
||||||
return
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
// Using Drag Ruler's id is not allowed
|
// Using Drag Ruler's id is not allowed
|
||||||
if (moduleId === "drag-ruler") {
|
if (moduleId === "drag-ruler") {
|
||||||
console.warn(
|
console.warn(
|
||||||
`Drag Ruler | A module tried to register with the id "${moduleId}", which is not allowed. This api registration call was ignored. ` +
|
`Drag Ruler | A module tried to register with the id "${moduleId}", which is not allowed. This api registration call was ignored. ` +
|
||||||
"If you're the author of the module please use the id of your own module as it's specified in your manifest to register to this api. " +
|
"If you're the author of the module please use the id of your own module as it's specified in your manifest to register to this api. " +
|
||||||
"If this call was made form a game system instead of a module please use `registerSystem` instead."
|
"If this call was made form a game system instead of a module please use `registerSystem` instead.",
|
||||||
)
|
);
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
register(module, "module", speedProvider)
|
register(module, "module", speedProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function registerSystem(systemId, speedProvider) {
|
export function registerSystem(systemId, speedProvider) {
|
||||||
const system = game.system
|
const system = game.system;
|
||||||
// If the current system id doesn't match the provided id something went wrong. Log a warning and ignore this module
|
// If the current system id doesn't match the provided id something went wrong. Log a warning and ignore this module
|
||||||
if (system.id != systemId) {
|
if (system.id != systemId) {
|
||||||
console.warn(
|
console.warn(
|
||||||
`Drag Ruler | A system tried to register with the id "${systemId}". However the active system has a different id.` +
|
`Drag Ruler | A system tried to register with the id "${systemId}". However the active system has a different id.` +
|
||||||
"This api registration call was ignored. " +
|
"This api registration call was ignored. " +
|
||||||
"If you are the author of that system please check that the id passed to `registerSystem` matches the id in your manifest exactly." +
|
"If you are the author of that system please check that the id passed to `registerSystem` matches the id in your manifest exactly." +
|
||||||
"If this call was made form a module instead of a game system please use `registerModule` instead.")
|
"If this call was made form a module instead of a game system please use `registerModule` instead.",
|
||||||
return
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
register(system, "system", speedProvider)
|
register(system, "system", speedProvider);
|
||||||
}
|
}
|
||||||
|
|||||||
+49
-30
@@ -1,41 +1,47 @@
|
|||||||
import {getCostFromSpeedProvider} from "./api.js";
|
import {buildCostFunction} from "./api.js";
|
||||||
import {settingsKey} from "./settings.js";
|
import {settingsKey} from "./settings.js";
|
||||||
import {getAreaFromPositionAndShape, highlightTokenShape} from "./util.js";
|
import {highlightTokenShape} from "./util.js";
|
||||||
|
|
||||||
export function getHexSizeSupportTokenGridCenter(token) {
|
export function highlightMeasurementTerrainRuler(
|
||||||
const tokenCenterOffset = CONFIG.hexSizeSupport.getCenterOffset(token)
|
ray,
|
||||||
return {x: token.x + tokenCenterOffset.x, y: token.y + tokenCenterOffset.y}
|
startDistance,
|
||||||
}
|
tokenShape = [{x: 0, y: 0}],
|
||||||
|
alpha = 1,
|
||||||
export function highlightMeasurementTerrainRuler(ray, startDistance, tokenShape=[{x: 0, y: 0}], alpha=1) {
|
) {
|
||||||
for (const space of ray.terrainRulerVisitedSpaces.reverse()) {
|
for (const space of ray.terrainRulerVisitedSpaces.reverse()) {
|
||||||
const color = this.dragRulerGetColorForDistance(startDistance + space.distance);
|
const color = this.dragRulerGetColorForDistance(startDistance + space.distance);
|
||||||
highlightTokenShape.call(this, space, tokenShape, color, alpha)
|
highlightTokenShape.call(this, space, tokenShape, color, alpha);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function measureDistances(segments, entity, shape, options={}) {
|
export function measureDistances(segments, entity, shape, options = {}) {
|
||||||
const opts = duplicate(options)
|
const opts = foundry.utils.duplicate(options);
|
||||||
if (canvas.grid.diagonalRule === "EUCL") {
|
if (canvas.grid.diagonalRule === "EUCL") {
|
||||||
opts.ignoreGrid = true;
|
opts.ignoreGrid = true;
|
||||||
opts.gridSpaes = false;
|
opts.gridSpaces = false;
|
||||||
}
|
}
|
||||||
if (opts.enableTerrainRuler) {
|
if (opts.enableTerrainRuler) {
|
||||||
opts.gridSpaces = true;
|
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(
|
||||||
previousSegments.forEach(segment => segment.ray.terrainRulerVisitedSpaces = duplicate(segment.ray.dragRulerVisitedSpaces));
|
segment =>
|
||||||
opts.costFunction = (x, y, costOptions={}) => { return getCostFromSpeedProvider(entity, getAreaFromPositionAndShape({x, y}, shape), costOptions); }
|
segment.ray.dragRulerVisitedSpaces[segment.ray.dragRulerVisitedSpaces.length - 1].distance,
|
||||||
|
);
|
||||||
|
previousSegments.forEach(
|
||||||
|
segment =>
|
||||||
|
(segment.ray.terrainRulerVisitedSpaces = foundry.utils.duplicate(segment.ray.dragRulerVisitedSpaces)),
|
||||||
|
);
|
||||||
|
opts.costFunction = buildCostFunction(entity, shape);
|
||||||
if (previousSegments.length > 0)
|
if (previousSegments.length > 0)
|
||||||
opts.terrainRulerInitialState = previousSegments[previousSegments.length - 1].ray.dragRulerFinalState;
|
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,
|
// 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
|
// manually set the *duplicate* option's gridSpaces value to true for the Foundry logic to work properly
|
||||||
if(!opts.ignoreGrid) {
|
if (!opts.ignoreGrid) {
|
||||||
opts.gridSpaces = true;
|
opts.gridSpaces = true;
|
||||||
}
|
}
|
||||||
return canvas.grid.measureDistances(segments, opts);
|
return canvas.grid.measureDistances(segments, opts);
|
||||||
@@ -44,23 +50,31 @@ export function measureDistances(segments, entity, shape, options={}) {
|
|||||||
|
|
||||||
export function checkDependencies() {
|
export function checkDependencies() {
|
||||||
if (!game.modules.get("socketlib")?.active) {
|
if (!game.modules.get("socketlib")?.active) {
|
||||||
console.error("Drag Ruler | The `socketlib` module isn't enabled, but it's required for Drag Ruler to operate properly.");
|
console.error(
|
||||||
|
"Drag Ruler | The `socketlib` module isn't enabled, but it's required for Drag Ruler to operate properly.",
|
||||||
|
);
|
||||||
if (game.user.isGM) {
|
if (game.user.isGM) {
|
||||||
new Dialog({
|
new Dialog({
|
||||||
title: game.i18n.localize("drag-ruler.dependencies.socketlib.title"),
|
title: game.i18n.localize("drag-ruler.dependencies.socketlib.title"),
|
||||||
content: `<h2>${game.i18n.localize("drag-ruler.dependencies.socketlib.title")}</h2><p>${game.i18n.localize("drag-ruler.dependencies.socketlib.text")}</p>`,
|
content: `<h2>${game.i18n.localize(
|
||||||
|
"drag-ruler.dependencies.socketlib.title",
|
||||||
|
)}</h2><p>${game.i18n.localize("drag-ruler.dependencies.socketlib.text")}</p>`,
|
||||||
buttons: {
|
buttons: {
|
||||||
ok: {
|
ok: {
|
||||||
icon: '<i class="fas fa-check"></i>',
|
icon: '<i class="fas fa-check"></i>',
|
||||||
label: game.i18n.localize("drag-ruler.dependencies.ok")
|
label: game.i18n.localize("drag-ruler.dependencies.ok"),
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
}).render(true);
|
}).render(true);
|
||||||
}
|
}
|
||||||
}
|
} else if (
|
||||||
else if (!game.modules.get("terrain-ruler")?.active && game.user.isGM && !game.settings.get(settingsKey, "neverShowTerrainRulerHint")) {
|
!game.modules.get("terrain-ruler")?.active &&
|
||||||
|
game.user.isGM &&
|
||||||
|
!game.settings.get(settingsKey, "neverShowTerrainRulerHint")
|
||||||
|
) {
|
||||||
const lastHint = game.settings.get(settingsKey, "lastTerrainRulerHintTime");
|
const lastHint = game.settings.get(settingsKey, "lastTerrainRulerHintTime");
|
||||||
if (Date.now() - lastHint > 604800000) { // One week
|
if (Date.now() - lastHint > 604800000) {
|
||||||
|
// One week
|
||||||
let enabledTerrainModule;
|
let enabledTerrainModule;
|
||||||
if (game.modules.get("enhanced-terrain-layer")?.active) {
|
if (game.modules.get("enhanced-terrain-layer")?.active) {
|
||||||
enabledTerrainModule = game.modules.get("enhanced-terrain-layer").data.title;
|
enabledTerrainModule = game.modules.get("enhanced-terrain-layer").data.title;
|
||||||
@@ -68,20 +82,25 @@ export function checkDependencies() {
|
|||||||
if (enabledTerrainModule) {
|
if (enabledTerrainModule) {
|
||||||
new Dialog({
|
new Dialog({
|
||||||
title: game.i18n.localize("drag-ruler.dependencies.terrain-ruler.title"),
|
title: game.i18n.localize("drag-ruler.dependencies.terrain-ruler.title"),
|
||||||
content: `<h2>${game.i18n.localize("drag-ruler.dependencies.terrain-ruler.title")}</h2><p>${game.i18n.format("drag-ruler.dependencies.terrain-ruler.text", {moduleName: enabledTerrainModule})}</p>`,
|
content: `<h2>${game.i18n.localize(
|
||||||
|
"drag-ruler.dependencies.terrain-ruler.title",
|
||||||
|
)}</h2><p>${game.i18n.format("drag-ruler.dependencies.terrain-ruler.text", {
|
||||||
|
moduleName: enabledTerrainModule,
|
||||||
|
})}</p>`,
|
||||||
buttons: {
|
buttons: {
|
||||||
ok: {
|
ok: {
|
||||||
icon: '<i class="fas fa-check"></i>',
|
icon: '<i class="fas fa-check"></i>',
|
||||||
label: game.i18n.localize("drag-ruler.dependencies.ok"),
|
label: game.i18n.localize("drag-ruler.dependencies.ok"),
|
||||||
callback: () => game.settings.set(settingsKey, "lastTerrainRulerHintTime", Date.now()),
|
callback: () =>
|
||||||
|
game.settings.set(settingsKey, "lastTerrainRulerHintTime", Date.now()),
|
||||||
},
|
},
|
||||||
neverShowAgain: {
|
neverShowAgain: {
|
||||||
icon: '<i class="fas fa-times"></i>',
|
icon: '<i class="fas fa-times"></i>',
|
||||||
label: game.i18n.localize("drag-ruler.dependencies.terrain-ruler.neverShowAgain"),
|
label: game.i18n.localize("drag-ruler.dependencies.terrain-ruler.neverShowAgain"),
|
||||||
callback: () => game.settings.set(settingsKey, "neverShowTerrainRulerHint", true),
|
callback: () => game.settings.set(settingsKey, "neverShowTerrainRulerHint", true),
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
close: () => game.settings.set(settingsKey, "lastTerrainRulerHintTime", Date.now())
|
close: () => game.settings.set(settingsKey, "lastTerrainRulerHintTime", Date.now()),
|
||||||
}).render(true);
|
}).render(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,138 @@
|
|||||||
|
/**
|
||||||
|
* A combination queue/set where the elements are ordered (in ascending order, according to the given priority function)
|
||||||
|
* and unique (according to the given elementMatcher).
|
||||||
|
*
|
||||||
|
* If an element is added to the set and an equivalent element already exists, the lower-priority one is discarded.
|
||||||
|
*/
|
||||||
|
export class PriorityQueueSet {
|
||||||
|
constructor(elementMatcher, priorityFunction) {
|
||||||
|
this.first = null;
|
||||||
|
this.elementMatcher = elementMatcher;
|
||||||
|
this.priorityFunction = priorityFunction;
|
||||||
|
}
|
||||||
|
|
||||||
|
pushWithPriority(value) {
|
||||||
|
const newNode = {value, priority: this.priorityFunction(value), next: null};
|
||||||
|
|
||||||
|
// If the queue is currently empty, we can just set this new node as the first and we're done
|
||||||
|
if (!this.first) {
|
||||||
|
this.first = newNode;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let inserted = false;
|
||||||
|
let previous;
|
||||||
|
let current = this.first;
|
||||||
|
|
||||||
|
// Loop through the existing elements
|
||||||
|
while (current) {
|
||||||
|
if (this.elementMatcher(current.value, value)) {
|
||||||
|
// We've found an equivalent element before one with a lower priority. This one has at least
|
||||||
|
// the same priority as the new one, so don't bother inserting
|
||||||
|
return;
|
||||||
|
} else if (newNode.priority <= current.priority) {
|
||||||
|
// We've found some element with lower priority than the new one, so insert the new one just before it
|
||||||
|
newNode.next = current;
|
||||||
|
if (previous) {
|
||||||
|
previous.next = newNode;
|
||||||
|
} else {
|
||||||
|
this.first = newNode;
|
||||||
|
}
|
||||||
|
inserted = true;
|
||||||
|
|
||||||
|
previous = current;
|
||||||
|
current = current.next;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
previous = current;
|
||||||
|
current = current.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inserted) {
|
||||||
|
// Go through the rest of the list and try to find an equivalent element to the new one.
|
||||||
|
// We know it has higher priority than the new one, so remove it.
|
||||||
|
while (current) {
|
||||||
|
if (this.elementMatcher(current.value, value)) {
|
||||||
|
if (previous) {
|
||||||
|
previous.next = current.next;
|
||||||
|
} else {
|
||||||
|
this.first = current.next;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
previous = current;
|
||||||
|
current = current.next;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// We reached the end of the queue without finding a lower-priority or existing element, so
|
||||||
|
// insert the new one at the end
|
||||||
|
previous.next = newNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hasNext() {
|
||||||
|
return !!this.first;
|
||||||
|
}
|
||||||
|
|
||||||
|
pop() {
|
||||||
|
const first = this.first;
|
||||||
|
this.first = first?.next;
|
||||||
|
return first?.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queue that will only ever accept elements with a given value once. Elements must have a "value" field, the
|
||||||
|
* JSON representation of which will be used as the key to match
|
||||||
|
*/
|
||||||
|
export class ProcessOnceQueue {
|
||||||
|
constructor() {
|
||||||
|
this.first = null;
|
||||||
|
this.last = null;
|
||||||
|
this.previouslyQueued = new Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove everything from the queue and forget all the previously-queued items
|
||||||
|
*/
|
||||||
|
reset() {
|
||||||
|
this.first = null;
|
||||||
|
this.last = null;
|
||||||
|
this.previouslyQueued.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
push(element) {
|
||||||
|
if (this.previouslyQueued.has(element)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.previouslyQueued.add(element);
|
||||||
|
|
||||||
|
const newNode = {
|
||||||
|
value: element,
|
||||||
|
next: null,
|
||||||
|
previous: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!this.first) {
|
||||||
|
this.first = newNode;
|
||||||
|
this.last = newNode;
|
||||||
|
} else {
|
||||||
|
this.last.next = newNode;
|
||||||
|
newNode.previous = this.last;
|
||||||
|
this.last = newNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pop() {
|
||||||
|
const node = this.first;
|
||||||
|
this.first = node?.next;
|
||||||
|
if (!node?.next) {
|
||||||
|
this.last = null;
|
||||||
|
}
|
||||||
|
return node?.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasNext() {
|
||||||
|
return !!this.first;
|
||||||
|
}
|
||||||
|
}
|
||||||
+23
-11
@@ -1,18 +1,30 @@
|
|||||||
// 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
|
||||||
|
|
||||||
// https://gitlab.com/foundrynet/foundryvtt/-/issues/4705
|
// This code could be phased out. The bug that caused the creation of these functions is now fixed, so this is only a wrapper function now
|
||||||
export function getPixelsFromGridPosition(xGrid, yGrid) {
|
export function getPixelsFromGridPosition(xGrid, yGrid) {
|
||||||
if (canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS) {
|
let coord = getPixelsFromGridPositionObj({x: xGrid, y: yGrid});
|
||||||
return canvas.grid.grid.getPixelsFromGridPosition(yGrid, xGrid)
|
return [coord.x, coord.y];
|
||||||
}
|
|
||||||
return canvas.grid.grid.getPixelsFromGridPosition(xGrid, yGrid)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrapper to fix a FoundryVTT bug that causes the return values of canvas.grid.grid.getPixelsFromGridPosition to be ordered inconsistently
|
// This code could be phased out. The bug that caused the creation of these functions is now fixed, so this is only a wrapper function now
|
||||||
// https://gitlab.com/foundrynet/foundryvtt/-/issues/4705
|
|
||||||
export function getGridPositionFromPixels(xPixel, yPixel) {
|
export function getGridPositionFromPixels(xPixel, yPixel) {
|
||||||
const [x, y] = canvas.grid.grid.getGridPositionFromPixels(xPixel, yPixel)
|
let coord = getGridPositionFromPixelsObj({x: xPixel, y: yPixel});
|
||||||
if (canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS)
|
return [coord.x, coord.y];
|
||||||
return [y, x]
|
}
|
||||||
return [x, y]
|
|
||||||
|
// This code could be phased out. The bug that caused the creation of these functions is now fixed, so this is only a wrapper function now
|
||||||
|
export function getGridPositionFromPixelsObj(o) {
|
||||||
|
const coord = canvas.grid.getOffset(o);
|
||||||
|
return {x: coord.j, y: coord.i};
|
||||||
|
}
|
||||||
|
|
||||||
|
// This code could be phased out. The bug that caused the creation of these functions is now fixed, so this is only a wrapper function now
|
||||||
|
export function getPixelsFromGridPositionObj(o) {
|
||||||
|
return canvas.grid.getTopLeftPoint({j: o.x, i: o.y});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCenterFromGridPositionObj(o) {
|
||||||
|
const r = getPixelsFromGridPositionObj(o);
|
||||||
|
[r.x, r.y] = canvas.grid.getCenter(r.x, r.y);
|
||||||
|
return r;
|
||||||
}
|
}
|
||||||
|
|||||||
+73
-201
@@ -1,10 +1,8 @@
|
|||||||
import {highlightMeasurementTerrainRuler, measureDistances} from "./compatibility.js";
|
|
||||||
import {getGridPositionFromPixels} from "./foundry_fixes.js";
|
import {getGridPositionFromPixels} from "./foundry_fixes.js";
|
||||||
import {Line} from "./geometry.js";
|
|
||||||
import {disableSnap, moveWithoutAnimation} from "./keybindings.js";
|
import {disableSnap, moveWithoutAnimation} from "./keybindings.js";
|
||||||
import {trackRays} from "./movement_tracking.js"
|
import {trackRays} from "./movement_tracking.js";
|
||||||
import {recalculate} from "./socket.js";
|
import {recalculate} from "./socket.js";
|
||||||
import {applyTokenSizeOffset, getSnapPointForEntity, getSnapPointForToken, getTokenShape, highlightTokenShape, zip} from "./util.js";
|
import {getSnapPointForToken, highlightTokenShape, sum} 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 moveEntities(draggedEntity, selectedEntities) {
|
export async function moveEntities(draggedEntity, selectedEntities) {
|
||||||
@@ -25,11 +23,20 @@ export async function moveEntities(draggedEntity, selectedEntities) {
|
|||||||
if (!game.user.isGM && draggedEntity instanceof Token) {
|
if (!game.user.isGM && draggedEntity instanceof Token) {
|
||||||
const hasCollision = selectedEntities.some(token => {
|
const hasCollision = selectedEntities.some(token => {
|
||||||
const offset = calculateEntityOffset(token, draggedEntity);
|
const offset = calculateEntityOffset(token, draggedEntity);
|
||||||
const offsetRays = rays.filter(ray => !ray.isPrevious).map(ray => applyOffsetToRay(ray, offset))
|
const offsetRays = rays
|
||||||
return offsetRays.some(r => canvas.walls.checkCollision(r));
|
.filter(ray => !ray.isPrevious)
|
||||||
})
|
.map(ray => applyOffsetToRay(ray, offset));
|
||||||
|
return offsetRays.some(r =>
|
||||||
|
token.checkCollision(r.B, {
|
||||||
|
origin: r.A,
|
||||||
|
mode: "any",
|
||||||
|
type: "move",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
if (hasCollision) {
|
if (hasCollision) {
|
||||||
ui.notifications.error(game.i18n.localize("ERROR.TokenCollide"));
|
ui.notifications.error(game.i18n.localize("RULER.MovementCollision"));
|
||||||
|
this._state = Ruler.STATES.MEASURING;
|
||||||
this._endMeasurement();
|
this._endMeasurement();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -41,8 +48,10 @@ export async function moveEntities(draggedEntity, selectedEntities) {
|
|||||||
await animateEntities.call(this, selectedEntities, draggedEntity, rays, wasPaused);
|
await animateEntities.call(this, selectedEntities, draggedEntity, rays, wasPaused);
|
||||||
|
|
||||||
// Once all animations are complete we can clear the ruler
|
// Once all animations are complete we can clear the ruler
|
||||||
if (this.draggedEntity?.id === draggedEntity.id)
|
if (this.draggedEntity?.id === draggedEntity.id) {
|
||||||
|
this._state = Ruler.STATES.MEASURING;
|
||||||
this._endMeasurement();
|
this._endMeasurement();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is a modified version code extracted from Ruler.moveToken from foundry 0.7.9
|
// This is a modified version code extracted from Ruler.moveToken from foundry 0.7.9
|
||||||
@@ -58,12 +67,11 @@ async function animateEntities(entities, draggedEntity, draggedRays, wasPaused)
|
|||||||
const origin = [firstWaypoint.x + entityOffset.x, firstWaypoint.y + entityOffset.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 = entity.data.x - origin[0];
|
dx = entity.x - origin[0];
|
||||||
dy = entity.data.y - origin[1];
|
dy = entity.y - origin[1];
|
||||||
}
|
} else {
|
||||||
else {
|
dx = entity.x - origin[0];
|
||||||
dx = entity.data.x - origin[0];
|
dy = entity.y - origin[1];
|
||||||
dy = entity.data.y - origin[1];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {entity, rays: offsetRays, dx, dy};
|
return {entity, rays: offsetRays, dx, dy};
|
||||||
@@ -76,7 +84,7 @@ async function animateEntities(entities, draggedEntity, draggedRays, wasPaused)
|
|||||||
// This is a flag of the "Monk's Active Tile Triggers" module that signals that the movement should be cancelled early
|
// This is a flag of the "Monk's Active Tile Triggers" module that signals that the movement should be cancelled early
|
||||||
this.cancelMovement = false;
|
this.cancelMovement = false;
|
||||||
|
|
||||||
for (let i = startWaypoint;i < entityAnimationData[0].rays.length; i++) {
|
for (let i = startWaypoint; i < entityAnimationData[0].rays.length; i++) {
|
||||||
if (!wasPaused && game.paused) break;
|
if (!wasPaused && game.paused) break;
|
||||||
const entityPaths = entityAnimationData.map(({entity, rays, dx, dy}) => {
|
const entityPaths = entityAnimationData.map(({entity, rays, dx, dy}) => {
|
||||||
const ray = rays[i];
|
const ray = rays[i];
|
||||||
@@ -87,26 +95,38 @@ async function animateEntities(entities, draggedEntity, draggedRays, wasPaused)
|
|||||||
const updates = entityPaths.map(({entity, path}) => {
|
const updates = entityPaths.map(({entity, path}) => {
|
||||||
return {x: path.B.x, y: path.B.y, _id: entity.id};
|
return {x: path.B.x, y: path.B.y, _id: entity.id};
|
||||||
});
|
});
|
||||||
await draggedEntity.scene.updateEmbeddedDocuments(draggedEntity.constructor.embeddedName, updates, {animate});
|
await draggedEntity.scene.updateEmbeddedDocuments(
|
||||||
|
draggedEntity.constructor.embeddedName,
|
||||||
|
updates,
|
||||||
|
{animate},
|
||||||
|
);
|
||||||
if (animate)
|
if (animate)
|
||||||
await Promise.all(entityPaths.map(({entity, path}) => entity.animateMovement(path)));
|
await Promise.all(
|
||||||
|
entityPaths.map(({entity}) => CanvasAnimation.getAnimation(entity.animationName)?.promise),
|
||||||
|
);
|
||||||
|
|
||||||
// This is a flag of the "Monk's Active Tile Triggers" module that signals that the movement should be cancelled early
|
// This is a flag of the "Monk's Active Tile Triggers" module that signals that the movement should be cancelled early
|
||||||
if (this.cancelMovement) {
|
if (this.cancelMovement) {
|
||||||
entityAnimationData.forEach(ead => ead.rays = ead.rays.slice(0, i + 1));
|
entityAnimationData.forEach(ead => (ead.rays = ead.rays.slice(0, i + 1)));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isToken)
|
if (isToken)
|
||||||
trackRays(entities, entityAnimationData.map(({rays}) => rays)).then(() => recalculate(entities));
|
trackRays(
|
||||||
|
entities,
|
||||||
|
entityAnimationData.map(({rays}) => rays),
|
||||||
|
).then(() => recalculate(entities));
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculateEntityOffset(entityA, entityB) {
|
function calculateEntityOffset(entityA, entityB) {
|
||||||
return {x: entityA.data.x - entityB.data.x, y: entityA.data.y - entityB.data.y};
|
return {x: entityA.x - entityB.x, y: entityA.y - entityB.y};
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyOffsetToRay(ray, offset) {
|
function applyOffsetToRay(ray, offset) {
|
||||||
const newRay = new Ray({x: ray.A.x + offset.x, y: ray.A.y + offset.y}, {x: ray.B.x + offset.x, y: ray.B.y + offset.y});
|
const newRay = new Ray(
|
||||||
|
{x: ray.A.x + offset.x, y: ray.A.y + offset.y},
|
||||||
|
{x: ray.B.x + offset.x, y: ray.B.y + offset.y},
|
||||||
|
);
|
||||||
newRay.isPrevious = ray.isPrevious;
|
newRay.isPrevious = ray.isPrevious;
|
||||||
return newRay;
|
return newRay;
|
||||||
}
|
}
|
||||||
@@ -116,7 +136,10 @@ 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 destination = {x: event.data.destination.x + this.rulerOffset.x, y: event.data.destination.y + this.rulerOffset.y}
|
const destination = {
|
||||||
|
x: event.interactionData.destination.x,
|
||||||
|
y: event.interactionData.destination.y,
|
||||||
|
};
|
||||||
|
|
||||||
// Hide any existing Token HUD
|
// Hide any existing Token HUD
|
||||||
canvas.hud.token.clear();
|
canvas.hud.token.clear();
|
||||||
@@ -129,18 +152,27 @@ export function onMouseMove(event) {
|
|||||||
function scheduleMeasurement(destination, event) {
|
function scheduleMeasurement(destination, event) {
|
||||||
const measurementInterval = 50;
|
const measurementInterval = 50;
|
||||||
const mt = event._measureTime || 0;
|
const mt = event._measureTime || 0;
|
||||||
const originalEvent = event.data.originalEvent;
|
const originalEvent = event.interactionData.originalEvent;
|
||||||
if (Date.now() - mt > measurementInterval) {
|
if (Date.now() - mt > measurementInterval) {
|
||||||
this.measure(destination, {snap: !disableSnap});
|
this.measure(destination, {snap: !disableSnap});
|
||||||
event._measureTime = Date.now();
|
event._measureTime = Date.now();
|
||||||
this._state = Ruler.STATES.MEASURING;
|
this._state = Ruler.STATES.MEASURING;
|
||||||
cancelScheduledMeasurement.call(this);
|
cancelScheduledMeasurement.call(this);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
this.deferredMeasurementData = {destination, event};
|
this.deferredMeasurementData = {destination, event};
|
||||||
if (!this.deferredMeasurementTimeout) {
|
if (!this.deferredMeasurementTimeout) {
|
||||||
this.deferredMeasurementPromise = new Promise((resolve, reject) => this.deferredMeasurementResolve = resolve);
|
this.deferredMeasurementPromise = new Promise(
|
||||||
this.deferredMeasurementTimeout = window.setTimeout(() => scheduleMeasurement.call(this, this.deferredMeasurementData.destination, this.deferredMeasurementData.event), measurementInterval);
|
(resolve, reject) => (this.deferredMeasurementResolve = resolve),
|
||||||
|
);
|
||||||
|
this.deferredMeasurementTimeout = window.setTimeout(
|
||||||
|
() =>
|
||||||
|
scheduleMeasurement.call(
|
||||||
|
this,
|
||||||
|
this.deferredMeasurementData.destination,
|
||||||
|
this.deferredMeasurementData.event,
|
||||||
|
),
|
||||||
|
measurementInterval,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -151,180 +183,20 @@ export function cancelScheduledMeasurement() {
|
|||||||
this.deferredMeasurementResolve?.();
|
this.deferredMeasurementResolve?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is a modified version of Ruler.measure form foundry 0.7.9
|
export function highlightMeasurementNative(
|
||||||
export function measure(destination, options={}) {
|
ray,
|
||||||
const isToken = this.draggedEntity instanceof Token;
|
previousSegments,
|
||||||
if (isToken && !this.draggedEntity.isVisible)
|
tokenShape = [{x: 0, y: 0}],
|
||||||
return []
|
alpha = 1,
|
||||||
|
) {
|
||||||
if (options.snap) {
|
for (const offset of canvas.grid.getDirectPath([ray.A, ray.B]).reverse()) {
|
||||||
destination = getSnapPointForEntity(destination.x, destination.y, this.draggedEntity);
|
const point = canvas.grid.getTopLeftPoint(offset);
|
||||||
}
|
const center = canvas.grid.getCenterPoint(offset);
|
||||||
|
const pathUntilSpace = previousSegments.concat([{ray: new Ray(ray.A, center)}]);
|
||||||
if(options.gridSpaces === undefined) {
|
const distance = sum(canvas.grid.measureDistances(pathUntilSpace, {gridSpaces: true}));
|
||||||
options.gridSpaces = canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS;
|
const color = this.dragRulerGetColorForDistance(distance);
|
||||||
}
|
const snapPoint = getSnapPointForToken(point.x + 1, point.y + 1, this.draggedEntity);
|
||||||
|
|
||||||
if (canvas.grid.diagonalRule === "EUCL") {
|
|
||||||
options.gridSpaces = false;
|
|
||||||
options.ignoreGrid = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(options.ignoreGrid === undefined) {
|
|
||||||
options.ignoreGrid = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
options.enableTerrainRuler = isToken && game.modules.get("terrain-ruler")?.active;
|
|
||||||
|
|
||||||
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
|
|
||||||
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
|
|
||||||
if (!options.enableTerrainRuler && !options.ignoreGrid)
|
|
||||||
centeredWaypoints.forEach(w => [w.x, w.y] = canvas.grid.getCenter(w.x, w.y));
|
|
||||||
|
|
||||||
const r = this.ruler;
|
|
||||||
this.destination = destination;
|
|
||||||
|
|
||||||
// Iterate over waypoints and construct segment rays
|
|
||||||
const segments = [];
|
|
||||||
const centeredSegments = []
|
|
||||||
for (let [i, dest] of waypoints.slice(1).entries()) {
|
|
||||||
const centeredDest = centeredWaypoints[i + 1]
|
|
||||||
const origin = waypoints[i];
|
|
||||||
const centeredOrigin = centeredWaypoints[i]
|
|
||||||
const label = this.labels.children[i];
|
|
||||||
const ray = new Ray(origin, dest);
|
|
||||||
const centeredRay = new Ray(centeredOrigin, centeredDest)
|
|
||||||
ray.isPrevious = Boolean(origin.isPrevious);
|
|
||||||
centeredRay.isPrevious = ray.isPrevious;
|
|
||||||
ray.dragRulerVisitedSpaces = origin.dragRulerVisitedSpaces;
|
|
||||||
centeredRay.dragRulerVisitedSpaces = ray.dragRulerVisitedSpaces;
|
|
||||||
ray.dragRulerFinalState = origin.dragRulerFinalState;
|
|
||||||
centeredRay.dragRulerFinalState = ray.dragRulerFinalState;
|
|
||||||
if (ray.distance < 10) {
|
|
||||||
if (label) label.visible = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
segments.push({ ray, label });
|
|
||||||
centeredSegments.push({ray: centeredRay, label})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const shape = isToken ? getTokenShape(this.draggedEntity) : null;
|
|
||||||
|
|
||||||
// Compute measured distance
|
|
||||||
const distances = measureDistances(centeredSegments, this.draggedEntity, shape, options);
|
|
||||||
|
|
||||||
let totalDistance = 0;
|
|
||||||
for (let [i, d] of distances.entries()) {
|
|
||||||
let s = centeredSegments[i];
|
|
||||||
s.startDistance = totalDistance
|
|
||||||
totalDistance += d;
|
|
||||||
s.last = i === (centeredSegments.length - 1);
|
|
||||||
s.distance = d;
|
|
||||||
s.text = this._getSegmentLabel(d, totalDistance, s.last);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear the grid highlight layer
|
|
||||||
const hlt = canvas.grid.highlightLayers[this.name] || canvas.grid.addHighlightLayer(this.name);
|
|
||||||
hlt.clear();
|
|
||||||
|
|
||||||
// Draw measured path
|
|
||||||
r.clear();
|
|
||||||
let rulerColor
|
|
||||||
if (!options.gridSpaces || canvas.grid.type === CONST.GRID_TYPES.GRIDLESS)
|
|
||||||
rulerColor = this.dragRulerGetColorForDistance(totalDistance);
|
|
||||||
else
|
|
||||||
rulerColor = this.color
|
|
||||||
for (const [s, cs] of zip(segments.reverse(), centeredSegments.reverse())) {
|
|
||||||
const { label, text, last } = cs;
|
|
||||||
|
|
||||||
// Draw line segment
|
|
||||||
const opacityMultiplier = s.ray.isPrevious ? 0.33 : 1;
|
|
||||||
r.lineStyle(6, 0x000000, 0.5 * opacityMultiplier).moveTo(s.ray.A.x, s.ray.A.y).lineTo(s.ray.B.x, s.ray.B.y)
|
|
||||||
.lineStyle(4, rulerColor, 0.25 * opacityMultiplier).moveTo(s.ray.A.x, s.ray.A.y).lineTo(s.ray.B.x, s.ray.B.y);
|
|
||||||
|
|
||||||
// Draw the distance label just after the endpoint of the segment
|
|
||||||
if (label) {
|
|
||||||
label.text = text;
|
|
||||||
label.alpha = last ? 1.0 : 0.5;
|
|
||||||
label.visible = true;
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Highlight grid positions
|
|
||||||
if (isToken && canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS && options.gridSpaces) {
|
|
||||||
if (options.enableTerrainRuler)
|
|
||||||
highlightMeasurementTerrainRuler.call(this, cs.ray, cs.startDistance, shape, opacityMultiplier)
|
|
||||||
else
|
|
||||||
highlightMeasurementNative.call(this, cs.ray, cs.startDistance, shape, opacityMultiplier);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw endpoints
|
|
||||||
for (let p of waypoints) {
|
|
||||||
r.lineStyle(2, 0x000000, 0.5).beginFill(rulerColor, 0.25).drawCircle(p.x, p.y, 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the measured segments
|
|
||||||
return segments;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function highlightMeasurementNative(ray, startDistance, tokenShape=[{x: 0, y: 0}], alpha=1) {
|
|
||||||
const spacer = canvas.scene.data.gridType === CONST.GRID_TYPES.SQUARE ? 1.41 : 1;
|
|
||||||
const nMax = Math.max(Math.floor(ray.distance / (spacer * Math.min(canvas.grid.w, canvas.grid.h))), 1);
|
|
||||||
const tMax = Array.fromRange(nMax+1).map(t => t / nMax);
|
|
||||||
|
|
||||||
// Track prior position
|
|
||||||
let prior = null;
|
|
||||||
|
|
||||||
// Iterate over ray portions
|
|
||||||
for ( let [i, t] of tMax.reverse().entries() ) {
|
|
||||||
let {x, y} = ray.project(t);
|
|
||||||
|
|
||||||
// Get grid position
|
|
||||||
let [x0, y0] = (i === 0) ? [null, null] : prior;
|
|
||||||
let [x1, y1] = canvas.grid.grid.getGridPositionFromPixels(x, y);
|
|
||||||
if ( x0 === x1 && y0 === y1 ) continue;
|
|
||||||
|
|
||||||
// Highlight the grid position
|
|
||||||
let [xg, yg] = canvas.grid.grid.getPixelsFromGridPosition(x1, y1);
|
|
||||||
const subDistance = canvas.grid.measureDistances([{ray: new Ray(ray.A, {x: xg, y: yg})}], {gridSpaces: true})[0]
|
|
||||||
const color = this.dragRulerGetColorForDistance(startDistance + subDistance);
|
|
||||||
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];
|
|
||||||
|
|
||||||
// If the positions are not neighbors, also highlight their halfway point
|
|
||||||
if (i > 0 && !canvas.grid.isNeighbor(x0, y0, x1, y1)) {
|
|
||||||
let th = tMax[i - 1] - (0.5 / nMax);
|
|
||||||
let {x, y} = ray.project(th);
|
|
||||||
let [x1h, y1h] = canvas.grid.grid.getGridPositionFromPixels(x, y);
|
|
||||||
let [xgh, ygh] = canvas.grid.grid.getPixelsFromGridPosition(x1h, y1h);
|
|
||||||
const subDistance = canvas.grid.measureDistances([{ray: new Ray(ray.A, {x: xgh, y: ygh})}], {gridSpaces: true})[0]
|
|
||||||
const color = this.dragRulerGetColorForDistance(startDistance + subDistance);
|
|
||||||
const snapPoint = getSnapPointForToken(...canvas.grid.getTopLeft(x, y), this.draggedEntity);
|
|
||||||
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);
|
highlightTokenShape.call(this, {x: snapX, y: snapY}, tokenShape, color, alpha);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
/**
|
||||||
|
* Functions taken directly from the hex-size-support module (https://github.com/Ourobor/Hex-Size-Support/releases/tag/1.1.0).
|
||||||
|
* Unless otherwise stated, these functions are taken as-is.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Altered version of this function.
|
||||||
|
* - Instead of taking a token as a parameter to retrieve the altOrientationFlag, receive the flag value directly
|
||||||
|
* - Instead of taking a grid parameter, get the grid value from the globas canvas
|
||||||
|
*/
|
||||||
|
export function findVertexSnapPoint(x, y, altOrientationFlag) {
|
||||||
|
const grid = canvas.grid;
|
||||||
|
if (grid.columnar) {
|
||||||
|
return findSnapPointCols(x, y, grid.sizeY, grid.sizeX, altOrientationFlag);
|
||||||
|
} else {
|
||||||
|
return findSnapPointRows(x, y, grid.sizeY, grid.sizeX, altOrientationFlag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function findSnapPointRows(x, y, h, w, alt) {
|
||||||
|
let xOffset = 0.0;
|
||||||
|
if (canvas.grid.grid.even) {
|
||||||
|
xOffset = -0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
let yOffset1 = 0.75;
|
||||||
|
let yOffset2 = 0.0;
|
||||||
|
if (alt) {
|
||||||
|
yOffset1 = 0.25;
|
||||||
|
yOffset2 = 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let row1 = calculateSnapPointsRows(x, y, h, w, 0.5 + xOffset, yOffset1);
|
||||||
|
let row2 = calculateSnapPointsRows(x, y, h, w, 1.0 + xOffset, yOffset2);
|
||||||
|
|
||||||
|
let dist1 = Math.pow(row1.x - x, 2) + Math.pow(row1.y - y, 2);
|
||||||
|
let dist2 = Math.pow(row2.x - x, 2) + Math.pow(row2.y - y, 2);
|
||||||
|
|
||||||
|
if (dist1 < dist2) {
|
||||||
|
return row1;
|
||||||
|
} else {
|
||||||
|
return row2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateSnapPointsRows(x, y, h, w, xOff, yOff) {
|
||||||
|
let c = Math.floor((x + (0.5 - xOff) * w) / w + 1);
|
||||||
|
let r = Math.floor((y + (0.75 - yOff) * h) / (1.5 * h) + 1);
|
||||||
|
|
||||||
|
let snapX = c * w - (1 - xOff) * w;
|
||||||
|
let snapY = r * h * 1.5 - (1.5 - yOff) * h;
|
||||||
|
|
||||||
|
return {x: snapX, y: snapY};
|
||||||
|
}
|
||||||
|
|
||||||
|
function findSnapPointCols(x, y, h, w, alt) {
|
||||||
|
let yOffset = 0.0;
|
||||||
|
if (canvas.grid.grid.even) {
|
||||||
|
yOffset = -0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
let xOffset1 = 0.75;
|
||||||
|
let xOffset2 = 0.0;
|
||||||
|
if (alt) {
|
||||||
|
xOffset1 = 0.25;
|
||||||
|
xOffset2 = 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let row1 = calculateSnapPointsCols(x, y, h, w, xOffset1, 0.5 + yOffset);
|
||||||
|
let row2 = calculateSnapPointsCols(x, y, h, w, xOffset2, 1.0 + yOffset);
|
||||||
|
|
||||||
|
let dist1 = Math.pow(row1.x - x, 2) + Math.pow(row1.y - y, 2);
|
||||||
|
let dist2 = Math.pow(row2.x - x, 2) + Math.pow(row2.y - y, 2);
|
||||||
|
|
||||||
|
if (dist1 < dist2) {
|
||||||
|
return row1;
|
||||||
|
} else {
|
||||||
|
return row2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateSnapPointsCols(x, y, h, w, xOff, yOff) {
|
||||||
|
let c = Math.floor((x + (0.75 - xOff) * w) / (1.5 * w) + 1);
|
||||||
|
let r = Math.floor((y + (0.5 - yOff) * h) / h + 1);
|
||||||
|
|
||||||
|
let snapX = c * w * 1.5 - (1.5 - xOff) * w;
|
||||||
|
let snapY = r * h - (1 - yOff) * h;
|
||||||
|
|
||||||
|
return {x: snapX, y: snapY};
|
||||||
|
}
|
||||||
+64
-24
@@ -1,25 +1,35 @@
|
|||||||
import {settingsKey} from "./settings.js";
|
import {settingsKey} from "./settings.js";
|
||||||
import {getMeasurePosition, setSnapParameterOnOptions} from "./util.js";
|
import {getMeasurePosition, setSnapParameterOnOptions} from "./util.js";
|
||||||
|
|
||||||
|
// Indicates whether the user is currently pressing the button to disable snapping
|
||||||
export let disableSnap = false;
|
export let disableSnap = false;
|
||||||
|
|
||||||
|
// Indicates whether the user is currently pressing the button to disable the movement animation
|
||||||
export let moveWithoutAnimation = false;
|
export let moveWithoutAnimation = false;
|
||||||
|
|
||||||
|
// Indicates whether the user is pressing the pathfinding toggle button
|
||||||
|
export let togglePathfinding = false;
|
||||||
|
|
||||||
export function registerKeybindings() {
|
export function registerKeybindings() {
|
||||||
game.keybindings.register(settingsKey, "cancelDrag", {
|
game.keybindings.register(settingsKey, "cancelDrag", {
|
||||||
name: "drag-ruler.keybindings.cancelDrag",
|
name: "drag-ruler.keybindings.cancelDrag",
|
||||||
onDown: cancelDrag,
|
onDown: cancelDrag,
|
||||||
uneditable: [{
|
uneditable: [
|
||||||
key: "Escape",
|
{
|
||||||
}],
|
key: "Escape",
|
||||||
|
},
|
||||||
|
],
|
||||||
precedence: -1,
|
precedence: -1,
|
||||||
});
|
});
|
||||||
|
|
||||||
game.keybindings.register(settingsKey, "createWaypoint", {
|
game.keybindings.register(settingsKey, "createWaypoint", {
|
||||||
name: "drag-ruler.keybindings.createWaypoint",
|
name: "drag-ruler.keybindings.createWaypoint",
|
||||||
onDown: handleCreateWaypoint,
|
onDown: handleCreateWaypoint,
|
||||||
editable: [{
|
editable: [
|
||||||
key: "Space"
|
{
|
||||||
}],
|
key: "Space",
|
||||||
|
},
|
||||||
|
],
|
||||||
precedence: -1,
|
precedence: -1,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -34,9 +44,11 @@ export function registerKeybindings() {
|
|||||||
hint: "drag-ruler.keybindings.disableSnap.hint",
|
hint: "drag-ruler.keybindings.disableSnap.hint",
|
||||||
onDown: handleDisableSnap,
|
onDown: handleDisableSnap,
|
||||||
onUp: handleDisableSnap,
|
onUp: handleDisableSnap,
|
||||||
editable: [{
|
editable: [
|
||||||
key: "ShiftLeft",
|
{
|
||||||
}],
|
key: "ShiftLeft",
|
||||||
|
},
|
||||||
|
],
|
||||||
precedence: -1,
|
precedence: -1,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -45,17 +57,29 @@ export function registerKeybindings() {
|
|||||||
hint: "drag-ruler.keybindings.moveWithoutAnimation.hint",
|
hint: "drag-ruler.keybindings.moveWithoutAnimation.hint",
|
||||||
onDown: handleMoveWithoutAnimation,
|
onDown: handleMoveWithoutAnimation,
|
||||||
onUp: handleMoveWithoutAnimation,
|
onUp: handleMoveWithoutAnimation,
|
||||||
editable: [{
|
editable: [
|
||||||
key: "AltLeft",
|
{
|
||||||
}],
|
key: "AltLeft",
|
||||||
|
},
|
||||||
|
],
|
||||||
precedence: -1,
|
precedence: -1,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (game.modules.get("routinglib")?.active) {
|
||||||
|
game.keybindings.register(settingsKey, "togglePathfinding", {
|
||||||
|
name: "drag-ruler.keybindings.togglePathfinding.name",
|
||||||
|
hint: "drag-ruler.keybindings.togglePathfinding.hint",
|
||||||
|
onDown: handleTogglePathfinding,
|
||||||
|
onUp: handleTogglePathfinding,
|
||||||
|
precedence: -1,
|
||||||
|
restricted: !game.settings.get(settingsKey, "allowPathfinding"),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDeleteWaypoint() {
|
function handleDeleteWaypoint() {
|
||||||
const ruler = canvas.controls.ruler;
|
const ruler = canvas.controls.ruler;
|
||||||
if (!ruler.draggedEntity)
|
if (!ruler?.draggedEntity) return false;
|
||||||
return false;
|
|
||||||
ruler.dragRulerDeleteWaypoint();
|
ruler.dragRulerDeleteWaypoint();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -64,16 +88,14 @@ function handleCreateWaypoint() {
|
|||||||
const ruler = canvas.controls.ruler;
|
const ruler = canvas.controls.ruler;
|
||||||
// .draggedEntity is used here because .isDragRuler only returns true once the ruler started measuring
|
// .draggedEntity is used here because .isDragRuler only returns true once the ruler started measuring
|
||||||
// Ruler can end up being undefined here if no canvas is active
|
// Ruler can end up being undefined here if no canvas is active
|
||||||
if (!ruler?.draggedEntity)
|
if (!ruler?.draggedEntity) return false;
|
||||||
return false;
|
|
||||||
|
|
||||||
let options = {};
|
let options = {};
|
||||||
setSnapParameterOnOptions(ruler, options);
|
setSnapParameterOnOptions(ruler, options);
|
||||||
|
|
||||||
if (ruler._state === Ruler.STATES.INACTIVE) {
|
if (ruler._state === Ruler.STATES.INACTIVE) {
|
||||||
ruler.dragRulerStart(options);
|
ruler.dragRulerStart(options);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
ruler.dragRulerAddWaypoint(getMeasurePosition(), options);
|
ruler.dragRulerAddWaypoint(getMeasurePosition(), options);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -81,8 +103,7 @@ function handleCreateWaypoint() {
|
|||||||
|
|
||||||
function cancelDrag() {
|
function cancelDrag() {
|
||||||
const ruler = canvas.controls.ruler;
|
const ruler = canvas.controls.ruler;
|
||||||
if (!ruler.draggedEntity)
|
if (!ruler?.draggedEntity) return false;
|
||||||
return false;
|
|
||||||
ruler.dragRulerAbortDrag();
|
ruler.dragRulerAbortDrag();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -91,15 +112,34 @@ function handleDisableSnap(event) {
|
|||||||
disableSnap = !event.up;
|
disableSnap = !event.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;
|
||||||
if (ruler._state !== Ruler.STATES.MEASURING)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
ruler.measure(getMeasurePosition(), {snap: !disableSnap});
|
ruler.measure(getMeasurePosition(), {snap: !disableSnap});
|
||||||
|
ruler.dragRulerSendState();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleMoveWithoutAnimation(event) {
|
function handleMoveWithoutAnimation(event) {
|
||||||
moveWithoutAnimation = !event.up;
|
moveWithoutAnimation = !event.up;
|
||||||
|
|
||||||
|
const ruler = canvas.controls.ruler;
|
||||||
|
if (!ruler?.isDragRuler) return false;
|
||||||
|
if (ruler._state !== Ruler.STATES.MEASURING) return false;
|
||||||
|
|
||||||
|
ruler.measure(getMeasurePosition(), {snap: !disableSnap});
|
||||||
|
ruler.dragRulerSendState();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTogglePathfinding(event) {
|
||||||
|
togglePathfinding = !event.up;
|
||||||
|
|
||||||
|
const ruler = canvas.controls.ruler;
|
||||||
|
if (!ruler?.isDragRuler) return false;
|
||||||
|
if (ruler._state !== Ruler.STATES.MEASURING) return false;
|
||||||
|
|
||||||
|
ruler.measure(getMeasurePosition(), {snap: !disableSnap});
|
||||||
|
ruler.dragRulerSendState();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
+48
-31
@@ -1,73 +1,90 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
// Copyright © 2021 fvtt-lib-wrapper Rui Pinheiro
|
// Copyright © 2021 fvtt-lib-wrapper Rui Pinheiro
|
||||||
|
|
||||||
|
"use strict";
|
||||||
'use strict';
|
|
||||||
|
|
||||||
// A shim for the libWrapper library
|
// A shim for the libWrapper library
|
||||||
export let libWrapper = undefined;
|
export let libWrapper = undefined;
|
||||||
|
|
||||||
export const VERSIONS = [1,11,0];
|
export const VERSIONS = [1, 11, 0];
|
||||||
export const TGT_SPLIT_RE = new RegExp("([^.[]+|\\[('([^'\\\\]|\\\\.)+?'|\"([^\"\\\\]|\\\\.)+?\")\\])", 'g');
|
export const TGT_SPLIT_RE = new RegExp(
|
||||||
export const TGT_CLEANUP_RE = new RegExp("(^\\['|'\\]$|^\\[\"|\"\\]$)", 'g');
|
"([^.[]+|\\[('([^'\\\\]|\\\\.)+?'|\"([^\"\\\\]|\\\\.)+?\")\\])",
|
||||||
|
"g",
|
||||||
|
);
|
||||||
|
export const TGT_CLEANUP_RE = new RegExp("(^\\['|'\\]$|^\\[\"|\"\\]$)", "g");
|
||||||
|
|
||||||
// Main shim code
|
// Main shim code
|
||||||
Hooks.once('init', () => {
|
Hooks.once("init", () => {
|
||||||
// Check if the real module is already loaded - if so, use it
|
// Check if the real module is already loaded - if so, use it
|
||||||
if(globalThis.libWrapper && !(globalThis.libWrapper.is_fallback ?? true)) {
|
if (globalThis.libWrapper && !(globalThis.libWrapper.is_fallback ?? true)) {
|
||||||
libWrapper = globalThis.libWrapper;
|
libWrapper = globalThis.libWrapper;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback implementation
|
// Fallback implementation
|
||||||
libWrapper = class {
|
libWrapper = class {
|
||||||
static get is_fallback() { return true };
|
static get is_fallback() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static get WRAPPER() { return 'WRAPPER' };
|
static get WRAPPER() {
|
||||||
static get MIXED() { return 'MIXED' };
|
return "WRAPPER";
|
||||||
static get OVERRIDE() { return 'OVERRIDE' };
|
}
|
||||||
|
static get MIXED() {
|
||||||
|
return "MIXED";
|
||||||
|
}
|
||||||
|
static get OVERRIDE() {
|
||||||
|
return "OVERRIDE";
|
||||||
|
}
|
||||||
|
|
||||||
static register(package_id, target, fn, type="MIXED", {chain=undefined}={}) {
|
static register(package_id, target, fn, type = "MIXED", {chain = undefined} = {}) {
|
||||||
const is_setter = target.endsWith('#set');
|
const is_setter = target.endsWith("#set");
|
||||||
target = !is_setter ? target : target.slice(0, -4);
|
target = !is_setter ? target : target.slice(0, -4);
|
||||||
const split = target.match(TGT_SPLIT_RE).map((x)=>x.replace(/\\(.)/g, '$1').replace(TGT_CLEANUP_RE,''));
|
const split = target
|
||||||
const root_nm = split.splice(0,1)[0];
|
.match(TGT_SPLIT_RE)
|
||||||
|
.map(x => x.replace(/\\(.)/g, "$1").replace(TGT_CLEANUP_RE, ""));
|
||||||
|
const root_nm = split.splice(0, 1)[0];
|
||||||
|
|
||||||
let obj, fn_name;
|
let obj, fn_name;
|
||||||
if(split.length == 0) {
|
if (split.length == 0) {
|
||||||
obj = globalThis;
|
obj = globalThis;
|
||||||
fn_name = root_nm;
|
fn_name = root_nm;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
const _eval = eval;
|
const _eval = eval;
|
||||||
fn_name = split.pop();
|
fn_name = split.pop();
|
||||||
obj = split.reduce((x,y)=>x[y], globalThis[root_nm] ?? _eval(root_nm));
|
obj = split.reduce((x, y) => x[y], globalThis[root_nm] ?? _eval(root_nm));
|
||||||
}
|
}
|
||||||
|
|
||||||
let iObj = obj;
|
let iObj = obj;
|
||||||
let descriptor = null;
|
let descriptor = null;
|
||||||
while(iObj) {
|
while (iObj) {
|
||||||
descriptor = Object.getOwnPropertyDescriptor(iObj, fn_name);
|
descriptor = Object.getOwnPropertyDescriptor(iObj, fn_name);
|
||||||
if(descriptor) break;
|
if (descriptor) break;
|
||||||
iObj = Object.getPrototypeOf(iObj);
|
iObj = Object.getPrototypeOf(iObj);
|
||||||
}
|
}
|
||||||
if(!descriptor || descriptor?.configurable === false) throw `libWrapper Shim: '${target}' does not exist, could not be found, or has a non-configurable descriptor.`;
|
if (!descriptor || descriptor?.configurable === false)
|
||||||
|
throw `libWrapper Shim: '${target}' does not exist, could not be found, or has a non-configurable descriptor.`;
|
||||||
|
|
||||||
let original = null;
|
let original = null;
|
||||||
const wrapper = (chain ?? (type.toUpperCase?.() != 'OVERRIDE' && type != 3)) ? function() { return fn.call(this, original.bind(this), ...arguments); } : function() { return fn.apply(this, arguments); };
|
const wrapper =
|
||||||
|
chain ?? (type.toUpperCase?.() != "OVERRIDE" && type != 3)
|
||||||
|
? function () {
|
||||||
|
return fn.call(this, original.bind(this), ...arguments);
|
||||||
|
}
|
||||||
|
: function () {
|
||||||
|
return fn.apply(this, arguments);
|
||||||
|
};
|
||||||
|
|
||||||
if(!is_setter) {
|
if (!is_setter) {
|
||||||
if(descriptor.value) {
|
if (descriptor.value) {
|
||||||
original = descriptor.value;
|
original = descriptor.value;
|
||||||
descriptor.value = wrapper;
|
descriptor.value = wrapper;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
original = descriptor.get;
|
original = descriptor.get;
|
||||||
descriptor.get = wrapper;
|
descriptor.get = wrapper;
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
if (!descriptor.set) throw `libWrapper Shim: '${target}' does not have a setter`;
|
||||||
if(!descriptor.set) throw `libWrapper Shim: '${target}' does not have a setter`;
|
|
||||||
original = descriptor.set;
|
original = descriptor.set;
|
||||||
descriptor.set = wrapper;
|
descriptor.set = wrapper;
|
||||||
}
|
}
|
||||||
@@ -75,5 +92,5 @@ Hooks.once('init', () => {
|
|||||||
descriptor.configurable = true;
|
descriptor.configurable = true;
|
||||||
Object.defineProperty(obj, fn_name, descriptor);
|
Object.defineProperty(obj, fn_name, descriptor);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
+115
-79
@@ -1,43 +1,60 @@
|
|||||||
"use strict"
|
"use strict";
|
||||||
|
|
||||||
import {getColorForDistanceAndToken, getMovedDistanceFromToken, getRangesFromSpeedProvider, initApi, registerModule, registerSystem} from "./api.js";
|
import {
|
||||||
import {checkDependencies, getHexSizeSupportTokenGridCenter} from "./compatibility.js";
|
getColorForDistanceAndToken,
|
||||||
import {moveEntities, onMouseMove} from "./foundry_imports.js"
|
getMovedDistanceFromToken,
|
||||||
|
getRangesFromSpeedProvider,
|
||||||
|
initApi,
|
||||||
|
registerModule,
|
||||||
|
registerSystem,
|
||||||
|
} from "./api.js";
|
||||||
|
import {checkDependencies} from "./compatibility.js";
|
||||||
|
import {moveEntities, onMouseMove} from "./foundry_imports.js";
|
||||||
import {disableSnap, registerKeybindings} from "./keybindings.js";
|
import {disableSnap, registerKeybindings} from "./keybindings.js";
|
||||||
import {libWrapper} from "./libwrapper_shim.js";
|
import {libWrapper} from "./libwrapper_shim.js";
|
||||||
import {performMigrations} from "./migration.js"
|
import {performMigrations} from "./migration.js";
|
||||||
import {removeLastHistoryEntryIfAt, resetMovementHistory} from "./movement_tracking.js";
|
import {removeLastHistoryEntryIfAt, resetMovementHistory} from "./movement_tracking.js";
|
||||||
import {extendRuler} from "./ruler.js";
|
import {extendRuler} from "./ruler.js";
|
||||||
import {registerSettings, RightClickAction, settingsKey} from "./settings.js"
|
import {registerSettings, RightClickAction, settingsKey} from "./settings.js";
|
||||||
import {recalculate} from "./socket.js";
|
import {recalculate} from "./socket.js";
|
||||||
import {SpeedProvider} from "./speed_provider.js"
|
import {SpeedProvider} from "./speed_provider.js";
|
||||||
import {setSnapParameterOnOptions} from "./util.js";
|
import {getEntityCenter, setSnapParameterOnOptions} from "./util.js";
|
||||||
|
|
||||||
|
CONFIG.debug.dragRuler = false;
|
||||||
|
export let debugGraphics = undefined;
|
||||||
|
|
||||||
Hooks.once("init", () => {
|
Hooks.once("init", () => {
|
||||||
registerSettings()
|
registerSettings();
|
||||||
registerKeybindings()
|
registerKeybindings();
|
||||||
initApi()
|
initApi();
|
||||||
hookDragHandlers(Token);
|
hookDragHandlers(Token);
|
||||||
hookDragHandlers(MeasuredTemplate);
|
hookDragHandlers(MeasuredTemplate);
|
||||||
libWrapper.register("drag-ruler", "TokenLayer.prototype.undoHistory", tokenLayerUndoHistory, "WRAPPER");
|
libWrapper.register(
|
||||||
|
"drag-ruler",
|
||||||
|
"TokenLayer.prototype.undoHistory",
|
||||||
|
tokenLayerUndoHistory,
|
||||||
|
"WRAPPER",
|
||||||
|
);
|
||||||
|
|
||||||
extendRuler();
|
extendRuler();
|
||||||
|
|
||||||
window.dragRuler = {
|
window.dragRuler = {
|
||||||
|
getRangesFromSpeedProvider,
|
||||||
getColorForDistanceAndToken,
|
getColorForDistanceAndToken,
|
||||||
getMovedDistanceFromToken,
|
getMovedDistanceFromToken,
|
||||||
registerModule,
|
registerModule,
|
||||||
registerSystem,
|
registerSystem,
|
||||||
recalculate,
|
recalculate,
|
||||||
resetMovementHistory,
|
resetMovementHistory,
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
|
|
||||||
Hooks.once("ready", () => {
|
Hooks.once("ready", () => {
|
||||||
performMigrations()
|
performMigrations();
|
||||||
checkDependencies();
|
checkDependencies();
|
||||||
Hooks.callAll("dragRuler.ready", SpeedProvider)
|
Hooks.callAll("dragRuler.ready", SpeedProvider);
|
||||||
})
|
if (CONFIG.debug.dragRuler) debugGraphics = canvas.controls.addChild(new PIXI.Container());
|
||||||
|
});
|
||||||
|
|
||||||
Hooks.on("canvasReady", () => {
|
Hooks.on("canvasReady", () => {
|
||||||
canvas.controls.rulers.children.forEach(ruler => {
|
canvas.controls.rulers.children.forEach(ruler => {
|
||||||
@@ -45,37 +62,61 @@ Hooks.on("canvasReady", () => {
|
|||||||
Object.defineProperty(ruler, "isDragRuler", {
|
Object.defineProperty(ruler, "isDragRuler", {
|
||||||
get: function isDragRuler() {
|
get: function isDragRuler() {
|
||||||
return Boolean(this.draggedEntity) && this._state !== Ruler.STATES.INACTIVE;
|
return Boolean(this.draggedEntity) && this._state !== Ruler.STATES.INACTIVE;
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
Hooks.on("getCombatTrackerEntryContext", function (html, menu) {
|
Hooks.on("getCombatTrackerEntryContext", function (html, menu) {
|
||||||
const entry = {
|
const entry = {
|
||||||
name: "drag-ruler.resetMovementHistory",
|
name: "drag-ruler.resetMovementHistory",
|
||||||
icon: '<i class="fas fa-undo-alt"></i>',
|
icon: '<i class="fas fa-undo-alt"></i>',
|
||||||
callback: li => resetMovementHistory(ui.combat.viewed, li.data('combatant-id')),
|
callback: li => resetMovementHistory(ui.combat.viewed, li.data("combatant-id")),
|
||||||
};
|
};
|
||||||
menu.splice(1, 0, entry);
|
menu.splice(1, 0, entry);
|
||||||
});
|
});
|
||||||
|
|
||||||
function forwardIfUnahndled(newFn) {
|
function forwardIfUnahndled(newFn) {
|
||||||
return function(oldFn, ...args) {
|
return function (oldFn, ...args) {
|
||||||
const eventHandled = newFn(...args);
|
const eventHandled = newFn(...args);
|
||||||
if (!eventHandled)
|
if (!eventHandled) oldFn(...args);
|
||||||
oldFn(...args);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function hookDragHandlers(entityType) {
|
function hookDragHandlers(entityType) {
|
||||||
const entityName = entityType.name
|
const entityName = entityType.name;
|
||||||
libWrapper.register("drag-ruler", `${entityName}.prototype._onDragLeftStart`, onEntityLeftDragStart, "WRAPPER");
|
libWrapper.register(
|
||||||
|
"drag-ruler",
|
||||||
|
`${entityName}.prototype._onDragLeftStart`,
|
||||||
|
onEntityLeftDragStart,
|
||||||
|
"WRAPPER",
|
||||||
|
);
|
||||||
if (entityType === Token)
|
if (entityType === Token)
|
||||||
libWrapper.register("drag-ruler", `${entityName}.prototype._onDragLeftMove`, onEntityLeftDragMoveSnap, "WRAPPER");
|
libWrapper.register(
|
||||||
|
"drag-ruler",
|
||||||
|
`${entityName}.prototype._onDragLeftMove`,
|
||||||
|
onEntityLeftDragMoveSnap,
|
||||||
|
"WRAPPER",
|
||||||
|
);
|
||||||
else
|
else
|
||||||
libWrapper.register("drag-ruler", `${entityName}.prototype._onDragLeftMove`, onEntityLeftDragMove, "WRAPPER");
|
libWrapper.register(
|
||||||
libWrapper.register("drag-ruler", `${entityName}.prototype._onDragLeftDrop`, forwardIfUnahndled(onEntityDragLeftDrop), "MIXED");
|
"drag-ruler",
|
||||||
libWrapper.register("drag-ruler", `${entityName}.prototype._onDragLeftCancel`, forwardIfUnahndled(onEntityDragLeftCancel), "MIXED");
|
`${entityName}.prototype._onDragLeftMove`,
|
||||||
|
onEntityLeftDragMove,
|
||||||
|
"WRAPPER",
|
||||||
|
);
|
||||||
|
libWrapper.register(
|
||||||
|
"drag-ruler",
|
||||||
|
`${entityName}.prototype._onDragLeftDrop`,
|
||||||
|
forwardIfUnahndled(onEntityDragLeftDrop),
|
||||||
|
"MIXED",
|
||||||
|
);
|
||||||
|
libWrapper.register(
|
||||||
|
"drag-ruler",
|
||||||
|
`${entityName}.prototype._onDragLeftCancel`,
|
||||||
|
forwardIfUnahndled(onEntityDragLeftCancel),
|
||||||
|
"MIXED",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function tokenLayerUndoHistory(wrapped) {
|
async function tokenLayerUndoHistory(wrapped) {
|
||||||
@@ -93,14 +134,9 @@ async function tokenLayerUndoHistory(wrapped) {
|
|||||||
function onEntityLeftDragStart(wrapped, event) {
|
function onEntityLeftDragStart(wrapped, event) {
|
||||||
wrapped(event);
|
wrapped(event);
|
||||||
const isToken = this instanceof Token;
|
const isToken = this instanceof Token;
|
||||||
const ruler = canvas.controls.ruler
|
const ruler = canvas.controls.ruler;
|
||||||
ruler.draggedEntity = this;
|
ruler.draggedEntity = this;
|
||||||
let entityCenter;
|
const entityCenter = getEntityCenter(this);
|
||||||
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")) {
|
if (game.settings.get(settingsKey, "autoStartMeasurement")) {
|
||||||
let options = {};
|
let options = {};
|
||||||
setSnapParameterOnOptions(ruler, options);
|
setSnapParameterOnOptions(ruler, options);
|
||||||
@@ -109,51 +145,48 @@ function onEntityLeftDragStart(wrapped, event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onEntityLeftDragMoveSnap(wrapped, event) {
|
function onEntityLeftDragMoveSnap(wrapped, event) {
|
||||||
applyGridlessSnapping.call(this, event);
|
applyGridlessSnapping.call(canvas.controls.ruler, event);
|
||||||
onEntityLeftDragMove.call(this, wrapped, event);
|
onEntityLeftDragMove.call(this, wrapped, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onEntityLeftDragMove(wrapped, event) {
|
function onEntityLeftDragMove(wrapped, event) {
|
||||||
wrapped(event);
|
wrapped(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 onEntityDragLeftDrop(event) {
|
function onEntityDragLeftDrop(event) {
|
||||||
const ruler = canvas.controls.ruler
|
const ruler = canvas.controls.ruler;
|
||||||
if (!ruler.isDragRuler) {
|
if (!ruler.isDragRuler) {
|
||||||
ruler.draggedEntity = undefined;
|
ruler.draggedEntity = undefined;
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
// When we're dragging a measured template no token will ever be selected,
|
// 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
|
// 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)
|
// This can happen if the user presses ESC during drag (maybe there are other ways too)
|
||||||
if (selectedTokens.length === 0)
|
if (selectedTokens.length === 0) selectedTokens.push(ruler.draggedEntity);
|
||||||
selectedTokens.push(ruler.draggedEntity);
|
// This can happen if the ruler is being dragged so rapidly that the drag move handler hasn't been called before dropping
|
||||||
ruler._state = Ruler.STATES.MOVING
|
if (ruler._state === Ruler.STATES.STARTING) onMouseMove.call(ruler, event);
|
||||||
|
ruler._state = Ruler.STATES.MOVING;
|
||||||
moveEntities.call(ruler, ruler.draggedEntity, selectedTokens);
|
moveEntities.call(ruler, ruler.draggedEntity, selectedTokens);
|
||||||
return true
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function onEntityDragLeftCancel(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.draggedEntity || ruler._state === Ruler.STATES.MOVING)
|
if (!ruler.draggedEntity || ruler._state === Ruler.STATES.MOVING) return false;
|
||||||
return false
|
|
||||||
|
|
||||||
const rightClickAction = game.settings.get(settingsKey, "rightClickAction");
|
const rightClickAction = game.settings.get(settingsKey, "rightClickAction");
|
||||||
let options = {};
|
let options = {};
|
||||||
setSnapParameterOnOptions(ruler, options);
|
setSnapParameterOnOptions(ruler, options);
|
||||||
|
|
||||||
if (ruler._state === Ruler.STATES.INACTIVE) {
|
if (ruler._state === Ruler.STATES.INACTIVE) {
|
||||||
if (rightClickAction !== RightClickAction.CREATE_WAYPOINT)
|
if (rightClickAction !== RightClickAction.CREATE_WAYPOINT) return false;
|
||||||
return false;
|
|
||||||
ruler.dragRulerStart(options);
|
ruler.dragRulerStart(options);
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
} else if (ruler._state === Ruler.STATES.MEASURING) {
|
||||||
else if (ruler._state === Ruler.STATES.MEASURING) {
|
|
||||||
switch (rightClickAction) {
|
switch (rightClickAction) {
|
||||||
case RightClickAction.CREATE_WAYPOINT:
|
case RightClickAction.CREATE_WAYPOINT:
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -172,24 +205,24 @@ function onEntityDragLeftCancel(event) {
|
|||||||
|
|
||||||
function applyGridlessSnapping(event) {
|
function applyGridlessSnapping(event) {
|
||||||
const ruler = canvas.controls.ruler;
|
const ruler = canvas.controls.ruler;
|
||||||
if (!game.settings.get(settingsKey, "useGridlessRaster"))
|
if (!game.settings.get(settingsKey, "useGridlessRaster")) return;
|
||||||
return;
|
if (!ruler.isDragRuler) return;
|
||||||
if (!ruler.isDragRuler)
|
if (disableSnap) return;
|
||||||
return;
|
if (canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS) return;
|
||||||
if (disableSnap)
|
|
||||||
return;
|
|
||||||
if (canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const rasterWidth = 35 / canvas.stage.scale.x;
|
const rasterWidth = 35 / canvas.stage.scale.x;
|
||||||
const tokenX = event.data.destination.x;
|
const tokenX = event.interactionData.destination.x;
|
||||||
const tokenY = event.data.destination.y;
|
const tokenY = event.interactionData.destination.y;
|
||||||
const destination = {x: tokenX + ruler.rulerOffset.x, y: tokenY + ruler.rulerOffset.y};
|
const destination = {x: tokenX, y: tokenY};
|
||||||
const ranges = getRangesFromSpeedProvider(ruler.draggedEntity);
|
const ranges = getRangesFromSpeedProvider(ruler.draggedEntity);
|
||||||
|
|
||||||
const terrainRulerAvailable = game.modules.get("terrain-ruler")?.active;
|
const terrainRulerAvailable = game.modules.get("terrain-ruler")?.active;
|
||||||
if (terrainRulerAvailable) {
|
if (terrainRulerAvailable) {
|
||||||
const segments = Ruler.dragRulerGetRaysFromWaypoints(ruler.waypoints, destination).map(ray => {return {ray}});
|
const segments = ruler.constructor
|
||||||
|
.dragRulerGetRaysFromWaypoints(ruler.waypoints, destination)
|
||||||
|
.map(ray => {
|
||||||
|
return {ray};
|
||||||
|
});
|
||||||
const pinpointDistances = new Map();
|
const pinpointDistances = new Map();
|
||||||
for (const range of ranges) {
|
for (const range of ranges) {
|
||||||
pinpointDistances.set(range.range, null);
|
pinpointDistances.set(range.range, null);
|
||||||
@@ -197,26 +230,29 @@ function applyGridlessSnapping(event) {
|
|||||||
terrainRuler.measureDistances(segments, {pinpointDistances});
|
terrainRuler.measureDistances(segments, {pinpointDistances});
|
||||||
const targetDistance = Array.from(pinpointDistances.entries())
|
const targetDistance = Array.from(pinpointDistances.entries())
|
||||||
.filter(([_key, val]) => val)
|
.filter(([_key, val]) => val)
|
||||||
.reduce((value, current) => value[0] > current[0] ? value : current, [0, null]);
|
.reduce((value, current) => (value[0] > current[0] ? value : current), [0, null]);
|
||||||
const rasterLocation = targetDistance[1];
|
const rasterLocation = targetDistance[1];
|
||||||
if (rasterLocation) {
|
if (rasterLocation) {
|
||||||
const deltaX = destination.x - rasterLocation.x;
|
const deltaX = destination.x - rasterLocation.x;
|
||||||
const deltaY = destination.y - rasterLocation.y;
|
const deltaY = destination.y - rasterLocation.y;
|
||||||
const rasterDistance = Math.hypot(deltaX, deltaY);
|
const rasterDistance = Math.hypot(deltaX, deltaY);
|
||||||
if (rasterDistance < rasterWidth) {
|
if (rasterDistance < rasterWidth) {
|
||||||
event.data.destination.x = rasterLocation.x - ruler.rulerOffset.x;
|
event.interactionData.destination.x = rasterLocation.x;
|
||||||
event.data.destination.y = rasterLocation.y - ruler.rulerOffset.y;
|
event.interactionData.destination.y = rasterLocation.y;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
let waypointDistance = 0;
|
let waypointDistance = 0;
|
||||||
let origin = event.data.origin;
|
let origin = this.draggedEntity.getCenterPoint();
|
||||||
if (ruler.waypoints.length > 1) {
|
if (ruler.waypoints.length > 1) {
|
||||||
const segments = Ruler.dragRulerGetRaysFromWaypoints(ruler.waypoints, destination).map(ray => {return {ray}});
|
const segments = ruler.constructor
|
||||||
|
.dragRulerGetRaysFromWaypoints(ruler.waypoints, destination)
|
||||||
|
.map(ray => {
|
||||||
|
return {ray};
|
||||||
|
});
|
||||||
origin = segments.pop().ray.A;
|
origin = segments.pop().ray.A;
|
||||||
waypointDistance = canvas.grid.measureDistances(segments).reduce((a, b) => a + b);
|
waypointDistance = canvas.grid.measureDistances(segments).reduce((a, b) => a + b);
|
||||||
origin = {x: origin.x - ruler.rulerOffset.x, y: origin.y - ruler.rulerOffset.y};
|
origin = {x: origin.x, y: origin.y};
|
||||||
}
|
}
|
||||||
|
|
||||||
const deltaX = tokenX - origin.x;
|
const deltaX = tokenX - origin.x;
|
||||||
@@ -226,13 +262,13 @@ function applyGridlessSnapping(event) {
|
|||||||
let targetDistance = ranges
|
let targetDistance = ranges
|
||||||
.map(range => range.range)
|
.map(range => range.range)
|
||||||
.map(range => range - waypointDistance)
|
.map(range => range - waypointDistance)
|
||||||
.map(range => range * canvas.dimensions.size / canvas.dimensions.distance)
|
.map(range => (range * canvas.dimensions.size) / canvas.dimensions.distance)
|
||||||
.filter(range => range < distance)
|
.filter(range => range < distance)
|
||||||
.reduce((a, b) => Math.max(a, b), 0);
|
.reduce((a, b) => Math.max(a, b), 0);
|
||||||
if (targetDistance) {
|
if (targetDistance) {
|
||||||
if (distance < targetDistance + rasterWidth) {
|
if (distance < targetDistance + rasterWidth) {
|
||||||
event.data.destination.x = origin.x + deltaX * targetDistance / distance;
|
event.interactionData.destination.x = origin.x + (deltaX * targetDistance) / distance;
|
||||||
event.data.destination.y = origin.y + deltaY * targetDistance / distance;
|
event.interactionData.destination.y = origin.y + (deltaY * targetDistance) / distance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-12
@@ -1,26 +1,24 @@
|
|||||||
import {RightClickAction, settingsKey} from "./settings.js"
|
import {RightClickAction, settingsKey} from "./settings.js";
|
||||||
|
|
||||||
const currentDataVersion = "1.10.0"
|
const currentDataVersion = "1.10.0";
|
||||||
|
|
||||||
export async function performMigrations() {
|
export async function performMigrations() {
|
||||||
if (game.user.isGM)
|
if (game.user.isGM) await performWorldMigraionts();
|
||||||
await performWorldMigraionts()
|
await performClientMigrations();
|
||||||
await performClientMigrations()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function performWorldMigraionts() {
|
async function performWorldMigraionts() {
|
||||||
let dataVersion = game.settings.get(settingsKey, "dataVersion");
|
let dataVersion = game.settings.get(settingsKey, "dataVersion");
|
||||||
|
|
||||||
if (dataVersion === currentDataVersion)
|
if (dataVersion === currentDataVersion) return;
|
||||||
return;
|
|
||||||
|
|
||||||
if (dataVersion === "fresh install") {
|
if (dataVersion === "fresh install") {
|
||||||
game.settings.set(settingsKey, "dataVersion", currentDataVersion)
|
game.settings.set(settingsKey, "dataVersion", currentDataVersion);
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dataVersion === "1.3.0") {
|
if (dataVersion === "1.3.0") {
|
||||||
dataVersion = "1.10.0"
|
dataVersion = "1.10.0";
|
||||||
}
|
}
|
||||||
|
|
||||||
game.settings.set(settingsKey, "dataVersion", dataVersion);
|
game.settings.set(settingsKey, "dataVersion", dataVersion);
|
||||||
@@ -31,13 +29,15 @@ async function performClientMigrations() {
|
|||||||
|
|
||||||
if (dataVersion === "fresh install") {
|
if (dataVersion === "fresh install") {
|
||||||
// Start of migration from unnamed version (< 1.10.0). TODO Remove in a future version
|
// Start of migration from unnamed version (< 1.10.0). TODO Remove in a future version
|
||||||
const swapSpacebarRightClick = game.settings.storage.get("client").getItem(`${settingsKey}.swapSpacebarRightClick`);
|
const swapSpacebarRightClick = game.settings.storage
|
||||||
|
.get("client")
|
||||||
|
.getItem(`${settingsKey}.swapSpacebarRightClick`);
|
||||||
if (swapSpacebarRightClick) {
|
if (swapSpacebarRightClick) {
|
||||||
game.settings.set(settingsKey, "rightClickAction", RightClickAction.CREATE_WAYPOINT);
|
game.settings.set(settingsKey, "rightClickAction", RightClickAction.CREATE_WAYPOINT);
|
||||||
await game.keybindings.set(settingsKey, "createWaypoint", []);
|
await game.keybindings.set(settingsKey, "createWaypoint", []);
|
||||||
await game.keybindings.set(settingsKey, "deleteWaypoint", [{key: "Space"}]);
|
await game.keybindings.set(settingsKey, "deleteWaypoint", [{key: "Space"}]);
|
||||||
}
|
}
|
||||||
// End of migration from unnamed version
|
// End of migration from unnamed version
|
||||||
game.settings.set(settingsKey, "clientDataVersion", currentDataVersion)
|
game.settings.set(settingsKey, "clientDataVersion", currentDataVersion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-33
@@ -4,59 +4,59 @@ 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.data.flags.dragRuler;
|
let dragRulerFlag = combatant.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.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(tokens, tokenRays) {
|
export async function trackRays(tokens, tokenRays) {
|
||||||
const combat = game.combat;
|
const combat = game.combat;
|
||||||
if (!combat)
|
if (!combat) return;
|
||||||
return;
|
if (!combat.started) return;
|
||||||
if (!combat.started)
|
|
||||||
return;
|
|
||||||
if (!(tokens instanceof Array)) {
|
if (!(tokens instanceof Array)) {
|
||||||
tokens = [tokens];
|
tokens = [tokens];
|
||||||
tokenRays = [tokenRays];
|
tokenRays = [tokenRays];
|
||||||
}
|
}
|
||||||
const updates = Array.from(zip(tokens, tokenRays)).map(([token, rays]) => calculateUpdate(combat, token, rays)).filter(Boolean);
|
const updates = Array.from(zip(tokens, tokenRays))
|
||||||
|
.map(([token, rays]) => calculateUpdate(combat, token, rays))
|
||||||
|
.filter(Boolean);
|
||||||
await updateCombatantDragRulerFlags(combat, updates);
|
await updateCombatantDragRulerFlags(combat, updates);
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculateUpdate(combat, token, rays) {
|
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.data.flags.dragRuler.trackedRound) {
|
if (combat.round > combatant.flags.dragRuler.trackedRound) {
|
||||||
combatant.data.flags.dragRuler.passedWaypoints = [];
|
combatant.flags.dragRuler.passedWaypoints = [];
|
||||||
combatant.data.flags.dragRuler.trackedRound = combat.data.round;
|
combatant.flags.dragRuler.trackedRound = combat.round;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the passed waypoints to the combatant
|
// Add the passed waypoints to the combatant
|
||||||
const terrainRulerAvailable = game.modules.get("terrain-ruler")?.active;
|
const terrainRulerAvailable = game.modules.get("terrain-ruler")?.active;
|
||||||
const dragRulerFlags = combatant.data.flags.dragRuler;
|
const dragRulerFlags = combatant.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), {terrainRulerInitialState: waypoints[waypoints.length - 1]?.dragRulerFinalState, enableTerrainRuler: terrainRulerAvailable});
|
measureDistances([{ray}], token, getTokenShape(token), {
|
||||||
|
terrainRulerInitialState: waypoints[waypoints.length - 1]?.dragRulerFinalState,
|
||||||
|
enableTerrainRuler: terrainRulerAvailable,
|
||||||
|
});
|
||||||
ray.A.dragRulerVisitedSpaces = ray.terrainRulerVisitedSpaces;
|
ray.A.dragRulerVisitedSpaces = ray.terrainRulerVisitedSpaces;
|
||||||
ray.A.dragRulerFinalState = ray.terrainRulerFinalState;
|
ray.A.dragRulerFinalState = ray.terrainRulerFinalState;
|
||||||
}
|
}
|
||||||
@@ -68,23 +68,18 @@ function calculateUpdate(combat, token, rays) {
|
|||||||
|
|
||||||
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.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) return [];
|
||||||
if (!dragRulerFlags)
|
if (combat.round > dragRulerFlags.trackedRound) return [];
|
||||||
return [];
|
|
||||||
if (combat.data.round > dragRulerFlags.trackedRound)
|
|
||||||
return [];
|
|
||||||
return dragRulerFlags.passedWaypoints ?? [];
|
return dragRulerFlags.passedWaypoints ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function removeLastHistoryEntryIfAt(token, x, y) {
|
export async function removeLastHistoryEntryIfAt(token, x, y) {
|
||||||
const history = getMovementHistory(token);
|
const history = getMovementHistory(token);
|
||||||
if (history.length === 0)
|
if (history.length === 0) return;
|
||||||
return;
|
|
||||||
const entry = history[history.length - 1];
|
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)) {
|
if (!isClose(x + token.w / 2, entry.x, 0.1) || !isClose(y + token.h / 2, entry.y, 0.1)) {
|
||||||
return;
|
return;
|
||||||
@@ -92,14 +87,15 @@ export async function removeLastHistoryEntryIfAt(token, x, y) {
|
|||||||
history.pop();
|
history.pop();
|
||||||
const combat = game.combat;
|
const combat = game.combat;
|
||||||
const combatant = combat.getCombatantByToken(token.id);
|
const combatant = combat.getCombatantByToken(token.id);
|
||||||
await updateCombatantDragRulerFlags(combat, [{_id: combatant.id, dragRulerFlags: combatant.data.flags.dragRuler}]);
|
await updateCombatantDragRulerFlags(combat, [
|
||||||
|
{_id: combatant.id, dragRulerFlags: combatant.flags.dragRuler},
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function resetMovementHistory(combat, combatantId) {
|
export async function resetMovementHistory(combat, combatantId) {
|
||||||
const combatant = combat.combatants.get(combatantId);
|
const combatant = combat.combatants.get(combatantId);
|
||||||
const dragRulerFlags = combatant.data.flags.dragRuler;
|
const dragRulerFlags = combatant.flags.dragRuler;
|
||||||
if (!dragRulerFlags)
|
if (!dragRulerFlags) return;
|
||||||
return;
|
|
||||||
dragRulerFlags.passedWaypoints = null;
|
dragRulerFlags.passedWaypoints = null;
|
||||||
dragRulerFlags.trackedRound = null;
|
dragRulerFlags.trackedRound = null;
|
||||||
dragRulerFlags.rulerState = null;
|
dragRulerFlags.rulerState = null;
|
||||||
|
|||||||
+377
-58
@@ -1,14 +1,28 @@
|
|||||||
import {currentSpeedProvider, getColorForDistanceAndToken, getRangesFromSpeedProvider} from "./api.js";
|
import {
|
||||||
import {getHexSizeSupportTokenGridCenter} from "./compatibility.js";
|
currentSpeedProvider,
|
||||||
import {cancelScheduledMeasurement, measure} from "./foundry_imports.js"
|
getColorForDistanceAndToken,
|
||||||
|
getRangesFromSpeedProvider,
|
||||||
|
} from "./api.js";
|
||||||
|
import {highlightMeasurementTerrainRuler, measureDistances} from "./compatibility.js";
|
||||||
|
import {getGridPositionFromPixelsObj, getPixelsFromGridPositionObj} from "./foundry_fixes.js";
|
||||||
|
import {cancelScheduledMeasurement, highlightMeasurementNative} from "./foundry_imports.js";
|
||||||
|
import {disableSnap} from "./keybindings.js";
|
||||||
import {getMovementHistory} from "./movement_tracking.js";
|
import {getMovementHistory} from "./movement_tracking.js";
|
||||||
import {settingsKey} from "./settings.js";
|
import {settingsKey} from "./settings.js";
|
||||||
import {getSnapPointForEntity} from "./util.js";
|
import {
|
||||||
|
applyTokenSizeOffset,
|
||||||
|
getSnapPointForEntity,
|
||||||
|
getSnapPointForTokenObj,
|
||||||
|
getEntityCenter,
|
||||||
|
getTokenShape,
|
||||||
|
isPathfindingEnabled,
|
||||||
|
} from "./util.js";
|
||||||
|
import {getPointer} from "./util.js";
|
||||||
|
|
||||||
export function extendRuler() {
|
export function extendRuler() {
|
||||||
class DragRulerRuler extends Ruler {
|
class DragRulerRuler extends CONFIG.Canvas.rulerClass {
|
||||||
// Functions below are overridden versions of functions in Ruler
|
// Functions below are overridden versions of functions in Ruler
|
||||||
constructor(user, {color=null}={}) {
|
constructor(user, {color = null} = {}) {
|
||||||
super(user, {color});
|
super(user, {color});
|
||||||
this.previousWaypoints = [];
|
this.previousWaypoints = [];
|
||||||
this.previousLabels = this.addChild(new PIXI.Container());
|
this.previousLabels = this.addChild(new PIXI.Container());
|
||||||
@@ -24,61 +38,335 @@ export function extendRuler() {
|
|||||||
|
|
||||||
async moveToken(event) {
|
async moveToken(event) {
|
||||||
// Disable moveToken if Drag Ruler is active
|
// Disable moveToken if Drag Ruler is active
|
||||||
if (!this.isDragRuler)
|
if (!this.isDragRuler) return await super.moveToken(event);
|
||||||
return await super.moveToken(event);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON() {
|
_getMeasurementData() {
|
||||||
const json = super.toJSON();
|
const json =
|
||||||
|
typeof super._getMeasurementData === "function"
|
||||||
|
? super._getMeasurementData()
|
||||||
|
: super.toJSON();
|
||||||
if (this.draggedEntity) {
|
if (this.draggedEntity) {
|
||||||
const isToken = this.draggedEntity instanceof Token;
|
const isToken = this.draggedEntity instanceof Token;
|
||||||
json["draggedEntityIsToken"] = isToken;
|
json.draggedEntityIsToken = isToken;
|
||||||
json["draggedEntity"] = this.draggedEntity.id;
|
json.draggedEntity = this.draggedEntity.id;
|
||||||
|
json.waypoints = json.waypoints.map(old => {
|
||||||
|
let w = foundry.utils.duplicate(old);
|
||||||
|
w.isPathfinding = undefined;
|
||||||
|
return w;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @deprecated since V12 */
|
||||||
|
toJSON() {
|
||||||
|
return this._getMeasurementData();
|
||||||
|
}
|
||||||
|
|
||||||
update(data) {
|
update(data) {
|
||||||
|
if (!data || data.state === Ruler.STATES.INACTIVE) return this.clear();
|
||||||
// Don't show a GMs drag ruler to non GM players
|
// Don't show a GMs drag ruler to non GM players
|
||||||
if (data.draggedEntity && 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.draggedEntity) {
|
if (data.draggedEntity) {
|
||||||
if (data.draggedEntityIsToken)
|
if (data.draggedEntityIsToken) this.draggedEntity = canvas.tokens.get(data.draggedEntity);
|
||||||
this.draggedEntity = canvas.tokens.get(data.draggedEntity);
|
else this.draggedEntity = canvas.templates.get(data.draggedEntity);
|
||||||
else
|
} else {
|
||||||
this.draggedEntity = canvas.templates.get(data.draggedEntity);
|
this.draggedEntity = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
super.update(data);
|
super.update(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
measure(destination, options={}) {
|
measure(destination, options = {}) {
|
||||||
if (this.isDragRuler) {
|
if (!this.isDragRuler) {
|
||||||
return measure.call(this, destination, options);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return super.measure(destination, options);
|
return super.measure(destination, options);
|
||||||
}
|
}
|
||||||
|
if (options.gridSpaces === undefined) {
|
||||||
|
options.gridSpaces = canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS;
|
||||||
|
}
|
||||||
|
this.dragRulerGridSpaces = options.gridSpaces;
|
||||||
|
const isToken = this.draggedEntity instanceof Token;
|
||||||
|
if (isToken && !this.draggedEntity.isVisible) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if (canvas.grid.diagonalRule === "EUCL") {
|
||||||
|
options.gridSpaces = false;
|
||||||
|
options.ignoreGrid = true;
|
||||||
|
}
|
||||||
|
if (options.ignoreGrid === undefined) {
|
||||||
|
options.ignoreGrid = false;
|
||||||
|
}
|
||||||
|
this.dragRulerIgnoreGrid = options.ignoreGrid;
|
||||||
|
// If this is the ruler of a remote user take the waypoints as they were transmitted and don't apply any additional snapping to them
|
||||||
|
if (this.user !== game.user) {
|
||||||
|
options.snap = false;
|
||||||
|
}
|
||||||
|
this.dragRulerSnap = options.snap ?? !disableSnap;
|
||||||
|
this.dragRulerEnableTerrainRuler = isToken && window.terrainRuler;
|
||||||
|
|
||||||
|
// Compute the measurement destination, segments, and distance
|
||||||
|
const d = this._getMeasurementDestination(destination);
|
||||||
|
if (this.destination && d.x === this.destination.x && d.y === this.destination.y) {
|
||||||
|
this.performPostPathfindingActions(options);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.destination = d;
|
||||||
|
|
||||||
|
// TODO Check if we can reuse the old path
|
||||||
|
this.dragRulerRemovePathfindingWaypoints();
|
||||||
|
if (this.pathfindingJob) {
|
||||||
|
routinglib.cancelPathfinding(this.pathfindingJob);
|
||||||
|
this.pathfindingJob = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isToken && isPathfindingEnabled.call(this)) {
|
||||||
|
// TODO Show a busy indicator
|
||||||
|
const from = getGridPositionFromPixelsObj(this.waypoints[this.waypoints.length - 1]);
|
||||||
|
const to = getGridPositionFromPixelsObj(destination);
|
||||||
|
|
||||||
|
const pathfindingJob = routinglib.calculatePath(from, to, {token: this.draggedEntity});
|
||||||
|
this.pathfindingJob = pathfindingJob;
|
||||||
|
return this.pathfindingJob.then(result => {
|
||||||
|
if (pathfindingJob === this.pathfindingJob) {
|
||||||
|
this.pathfindingJob = undefined;
|
||||||
|
this.addPathToWaypoints(result?.path);
|
||||||
|
return this.performPostPathfindingActions(options);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.performPostPathfindingActions(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
addPathToWaypoints(path) {
|
||||||
|
if (!path) {
|
||||||
|
// TODO Show an indicator informing that there is no path
|
||||||
|
|
||||||
|
// Don't show a path if the pathfinding yields no result to show the user that the destination is unreachable
|
||||||
|
this.destination = this.waypoints[this.waypoints.length - 1];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
path = path.map(point =>
|
||||||
|
getSnapPointForTokenObj(getPixelsFromGridPositionObj(point), this.draggedEntity),
|
||||||
|
);
|
||||||
|
|
||||||
|
// If the token is snapped to the grid, the first point of the path is already handled by the ruler
|
||||||
|
if (
|
||||||
|
path[0].x === this.waypoints[this.waypoints.length - 1].x &&
|
||||||
|
path[0].y === this.waypoints[this.waypoints.length - 1].y
|
||||||
|
) {
|
||||||
|
path = path.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If snapping is enabled, the last point of the path is already handled by the ruler
|
||||||
|
if (this.dragRulerSnap) {
|
||||||
|
path = path.slice(0, path.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const point of path) {
|
||||||
|
point.isPathfinding = true;
|
||||||
|
this.labels.addChild(new PreciseText("", CONFIG.canvasTextStyle));
|
||||||
|
}
|
||||||
|
this.waypoints = this.waypoints.concat(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
performPostPathfindingActions(options) {
|
||||||
|
// TODO Clear pathfinding busy indicator
|
||||||
|
this.segments = this._getMeasurementSegments();
|
||||||
|
this._computeDistance(options.gridSpaces);
|
||||||
|
|
||||||
|
// Draw the ruler graphic
|
||||||
|
this.ruler.clear();
|
||||||
|
this._drawMeasuredPath();
|
||||||
|
|
||||||
|
// Draw grid highlight
|
||||||
|
this.highlightLayer.clear();
|
||||||
|
const isToken = this.draggedEntity instanceof Token;
|
||||||
|
if (isToken && canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS && this.dragRulerGridSpaces) {
|
||||||
|
const shape = getTokenShape(this.draggedEntity);
|
||||||
|
if (!this.dragRulerEnableTerrainRuler) {
|
||||||
|
for (const [i, segment] of [...this.segments].reverse().entries()) {
|
||||||
|
const opacityMultiplier = segment.ray.isPrevious ? 0.33 : 1;
|
||||||
|
const previousSegments = this.segments.slice(0, this.segments.length - 1 - i);
|
||||||
|
highlightMeasurementNative.call(
|
||||||
|
this,
|
||||||
|
segment.ray,
|
||||||
|
previousSegments,
|
||||||
|
shape,
|
||||||
|
opacityMultiplier,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (const segment of [...this.segments].reverse()) {
|
||||||
|
const opacityMultiplier = segment.ray.isPrevious ? 0.33 : 1;
|
||||||
|
highlightMeasurementTerrainRuler.call(
|
||||||
|
this,
|
||||||
|
segment.ray,
|
||||||
|
segment.startDistance,
|
||||||
|
shape,
|
||||||
|
opacityMultiplier,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.dragRulerSendState();
|
||||||
|
return this.segments;
|
||||||
|
}
|
||||||
|
|
||||||
|
_getMeasurementDestination(destination) {
|
||||||
|
if (this.isDragRuler) {
|
||||||
|
if (this.dragRulerSnap) {
|
||||||
|
return getSnapPointForEntity(destination.x, destination.y, this.draggedEntity);
|
||||||
|
} else {
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return super._getMeasurementDestination(destination);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_getMeasurementSegments() {
|
||||||
|
if (this.isDragRuler) {
|
||||||
|
const unsnappedWaypoints = this.waypoints.concat([this.destination]);
|
||||||
|
const waypoints =
|
||||||
|
this.draggedEntity instanceof Token
|
||||||
|
? applyTokenSizeOffset(unsnappedWaypoints, this.draggedEntity)
|
||||||
|
: foundry.utils.duplicate(unsnappedWaypoints);
|
||||||
|
const unsnappedSegments = [];
|
||||||
|
const segments = [];
|
||||||
|
for (const [i, p1] of waypoints.entries()) {
|
||||||
|
if (i === 0) continue;
|
||||||
|
const unsnappedP1 = unsnappedWaypoints[i];
|
||||||
|
const p0 = waypoints[i - 1];
|
||||||
|
const unsnappedP0 = unsnappedWaypoints[i - 1];
|
||||||
|
const label = this.labels.children[i - 1];
|
||||||
|
const ray = new Ray(p0, p1);
|
||||||
|
const unsnappedRay = new Ray(unsnappedP0, unsnappedP1);
|
||||||
|
ray.isPrevious = Boolean(unsnappedP0.isPrevious);
|
||||||
|
unsnappedRay.isPrevious = ray.isPrevious;
|
||||||
|
ray.dragRulerVisitedSpaces = unsnappedP0.dragRulerVisitedSpaces;
|
||||||
|
unsnappedRay.dragRulerVisitedSpaces = ray.dragRulerVisitedSpaces;
|
||||||
|
ray.dragRulerFinalState = unsnappedP0.dragRulerFinalState;
|
||||||
|
unsnappedRay.dragRulerFinalState = ray.dragRulerFinalState;
|
||||||
|
if (ray.distance < 10) {
|
||||||
|
if (label) label.visible = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
segments.push({ray, label});
|
||||||
|
unsnappedSegments.push({ray: unsnappedRay, label});
|
||||||
|
}
|
||||||
|
this.dragRulerUnsnappedSegments = unsnappedSegments;
|
||||||
|
if (this.labels.children.length > segments.length) {
|
||||||
|
this.labels.removeChildren(segments.length).forEach(c => c.destroy());
|
||||||
|
}
|
||||||
|
return segments;
|
||||||
|
} else {
|
||||||
|
return super._getMeasurementSegments();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_computeDistance(gridSpaces) {
|
||||||
|
if (!this.isDragRuler) {
|
||||||
|
return super._computeDistance(gridSpaces);
|
||||||
|
}
|
||||||
|
const shape = this.draggedEntity ? getTokenShape(this.draggedEntity) : null;
|
||||||
|
const options = {
|
||||||
|
ignoreGrid: this.dragRulerIgnoreGrid,
|
||||||
|
gridSpaces,
|
||||||
|
enableTerrainRuler: this.dragRulerEnableTerrainRuler,
|
||||||
|
};
|
||||||
|
const distances = measureDistances(this.segments, this.draggedEntity, shape, options);
|
||||||
|
this.totalDistance = 0;
|
||||||
|
for (const [i, d] of distances.entries()) {
|
||||||
|
let s = this.segments[i];
|
||||||
|
s.startDistance = this.totalDistance;
|
||||||
|
this.totalDistance += d;
|
||||||
|
s.last = i === this.segments.length - 1;
|
||||||
|
s.distance = d;
|
||||||
|
s.text = this._getSegmentLabel(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [i, segment] of this.segments.entries()) {
|
||||||
|
const unsnappedSegment = this.dragRulerUnsnappedSegments[i];
|
||||||
|
unsnappedSegment.startDistance = segment.startDistance;
|
||||||
|
unsnappedSegment.last = segment.last;
|
||||||
|
unsnappedSegment.distance = segment.distance;
|
||||||
|
unsnappedSegment.text = segment.text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_drawMeasuredPath() {
|
||||||
|
if (!this.isDragRuler) {
|
||||||
|
return super._drawMeasuredPath();
|
||||||
|
}
|
||||||
|
let rulerColor = this.color;
|
||||||
|
if (!this.dragRulerGridSpaces || canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
|
||||||
|
const totalDistance = this.segments.reduce((total, current) => total + current.distance, 0);
|
||||||
|
rulerColor = this.dragRulerGetColorForDistance(totalDistance);
|
||||||
|
}
|
||||||
|
const r = this.ruler.beginFill(rulerColor, 0.25);
|
||||||
|
for (const segment of this.dragRulerUnsnappedSegments) {
|
||||||
|
const opacityMultiplier = segment.ray.isPrevious ? 0.33 : 1;
|
||||||
|
const {ray, distance, label, text, last} = segment;
|
||||||
|
if (distance === 0) continue;
|
||||||
|
|
||||||
|
// Draw Line
|
||||||
|
r.moveTo(ray.A.x, ray.A.y)
|
||||||
|
.lineStyle(6, 0x000000, 0.5 * opacityMultiplier)
|
||||||
|
.lineTo(ray.B.x, ray.B.y)
|
||||||
|
.lineStyle(4, rulerColor, 0.25 * opacityMultiplier)
|
||||||
|
.moveTo(ray.A.x, ray.A.y)
|
||||||
|
.lineTo(ray.B.x, ray.B.y);
|
||||||
|
|
||||||
|
// Draw Waypoints
|
||||||
|
r.lineStyle(2, 0x000000, 0.5).drawCircle(ray.A.x, ray.A.y, 8);
|
||||||
|
if (last) r.drawCircle(ray.B.x, ray.B.y, 8);
|
||||||
|
|
||||||
|
// Draw Label
|
||||||
|
if (label) {
|
||||||
|
label.text = text;
|
||||||
|
label.alpha = last ? 1.0 : 0.5;
|
||||||
|
label.visible = true;
|
||||||
|
let labelPosition = ray.project((ray.distance + 50) / ray.distance);
|
||||||
|
label.position.set(labelPosition.x, labelPosition.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.endFill();
|
||||||
}
|
}
|
||||||
|
|
||||||
_endMeasurement() {
|
_endMeasurement() {
|
||||||
super._endMeasurement();
|
super._endMeasurement();
|
||||||
this.draggedEntity = null;
|
this.draggedEntity = null;
|
||||||
|
if (this.pathfindingJob) {
|
||||||
|
routinglib.cancelPathfinding(this.pathfindingJob);
|
||||||
|
this.pathfindingJob = undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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, options={}) {
|
dragRulerAddWaypoint(point, options = {}) {
|
||||||
options.snap = options.snap ?? true;
|
options.snap = options.snap ?? true;
|
||||||
if (options.snap) {
|
if (options.snap) {
|
||||||
point = getSnapPointForEntity(point.x, point.y, this.draggedEntity);
|
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));
|
||||||
|
this.waypoints
|
||||||
|
.filter(waypoint => waypoint.isPathfinding)
|
||||||
|
.forEach(waypoint => (waypoint.isPathfinding = false));
|
||||||
}
|
}
|
||||||
|
|
||||||
dragRulerAddWaypointHistory(waypoints) {
|
dragRulerAddWaypointHistory(waypoints) {
|
||||||
waypoints.forEach(waypoint => waypoint.isPrevious = true);
|
waypoints.forEach(waypoint => (waypoint.isPrevious = true));
|
||||||
this.waypoints = this.waypoints.concat(waypoints);
|
this.waypoints = this.waypoints.concat(waypoints);
|
||||||
for (const waypoint of waypoints) {
|
for (const waypoint of waypoints) {
|
||||||
this.labels.addChild(new PreciseText("", CONFIG.canvasTextStyle));
|
this.labels.addChild(new PreciseText("", CONFIG.canvasTextStyle));
|
||||||
@@ -90,29 +378,50 @@ export function extendRuler() {
|
|||||||
this.labels.removeChildren().forEach(c => c.destroy());
|
this.labels.removeChildren().forEach(c => c.destroy());
|
||||||
}
|
}
|
||||||
|
|
||||||
dragRulerDeleteWaypoint(event={preventDefault: () => {return}}, options={}) {
|
dragRulerDeleteWaypoint(
|
||||||
|
event = {
|
||||||
|
preventDefault: () => {
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options = {},
|
||||||
|
) {
|
||||||
|
this.dragRulerRemovePathfindingWaypoints();
|
||||||
options.snap = options.snap ?? true;
|
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 = getPointer().getLocalPosition(canvas.tokens);
|
||||||
const rulerOffset = this.rulerOffset;
|
|
||||||
|
|
||||||
// Options are not passed to _removeWaypoint in vanilla Foundry.
|
// 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)
|
// 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);
|
this._removeWaypoint({x: mousePosition.x, y: mousePosition.y}, options);
|
||||||
game.user.broadcastActivity({ruler: this});
|
this.performPostPathfindingActions(options);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
this.dragRulerAbortDrag(event);
|
this.dragRulerAbortDrag(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dragRulerAbortDrag(event={preventDefault: () => {return}}) {
|
dragRulerRemovePathfindingWaypoints() {
|
||||||
|
this.waypoints
|
||||||
|
.filter(waypoint => waypoint.isPathfinding)
|
||||||
|
.forEach(_ =>
|
||||||
|
this.labels.removeChild(this.labels.children[this.labels.children.length - 1]).destroy(),
|
||||||
|
);
|
||||||
|
this.waypoints = this.waypoints.filter(waypoint => !waypoint.isPathfinding);
|
||||||
|
}
|
||||||
|
|
||||||
|
dragRulerAbortDrag(
|
||||||
|
event = {
|
||||||
|
preventDefault: () => {
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
) {
|
||||||
const token = this.draggedEntity;
|
const token = this.draggedEntity;
|
||||||
this._endMeasurement();
|
this._endMeasurement();
|
||||||
|
|
||||||
// Deactivate the drag workflow in mouse
|
// Deactivate the drag workflow in mouse
|
||||||
token.mouseInteractionManager._deactivateDragEvents();
|
token.mouseInteractionManager.cancel(event);
|
||||||
token.mouseInteractionManager.state = token.mouseInteractionManager.states.HOVER;
|
token.mouseInteractionManager.state = token.mouseInteractionManager.states.HOVER;
|
||||||
|
|
||||||
// This will cancel the current drag operation
|
// This will cancel the current drag operation
|
||||||
@@ -121,10 +430,8 @@ export function extendRuler() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async dragRulerRecalculate(tokenIds) {
|
async dragRulerRecalculate(tokenIds) {
|
||||||
if (this._state !== Ruler.STATES.MEASURING)
|
if (this._state !== Ruler.STATES.MEASURING) return;
|
||||||
return;
|
if (tokenIds && !tokenIds.includes(this.draggedEntity.id)) return;
|
||||||
if (tokenIds && !tokenIds.includes(this.draggedEntity.id))
|
|
||||||
return;
|
|
||||||
const waypoints = this.waypoints.filter(waypoint => !waypoint.isPrevious);
|
const waypoints = this.waypoints.filter(waypoint => !waypoint.isPrevious);
|
||||||
this.dragRulerClearWaypoints();
|
this.dragRulerClearWaypoints();
|
||||||
if (game.settings.get(settingsKey, "enableMovementHistory"))
|
if (game.settings.get(settingsKey, "enableMovementHistory"))
|
||||||
@@ -133,59 +440,71 @@ export function extendRuler() {
|
|||||||
this.dragRulerAddWaypoint(waypoint, {snap: false});
|
this.dragRulerAddWaypoint(waypoint, {snap: false});
|
||||||
}
|
}
|
||||||
this.measure(this.destination);
|
this.measure(this.destination);
|
||||||
game.user.broadcastActivity({ruler: this});
|
this.dragRulerSendState();
|
||||||
}
|
}
|
||||||
|
|
||||||
static dragRulerGetRaysFromWaypoints(waypoints, destination) {
|
static dragRulerGetRaysFromWaypoints(waypoints, destination) {
|
||||||
if ( destination )
|
if (destination) waypoints = waypoints.concat([destination]);
|
||||||
waypoints = waypoints.concat([destination]);
|
|
||||||
return waypoints.slice(1).map((wp, i) => {
|
return waypoints.slice(1).map((wp, i) => {
|
||||||
const ray = new Ray(waypoints[i], wp);
|
const ray = new Ray(waypoints[i], wp);
|
||||||
ray.isPrevious = Boolean(waypoints[i].isPrevious);
|
ray.isPrevious = Boolean(waypoints[i].isPrevious);
|
||||||
return ray;
|
return ray;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
dragRulerGetColorForDistance(distance) {
|
dragRulerGetColorForDistance(distance) {
|
||||||
if (!this.isDragRuler)
|
if (!this.isDragRuler) return this.color;
|
||||||
return this.color;
|
|
||||||
if (!this.draggedEntity.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.draggedEntity.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.draggedEntity.actor.data.type === "character" && game.settings.get(settingsKey, "alwaysShowSpeedForPCs")))
|
if (
|
||||||
|
!(
|
||||||
|
this.draggedEntity.actor.type === "character" &&
|
||||||
|
game.settings.get(settingsKey, "alwaysShowSpeedForPCs")
|
||||||
|
)
|
||||||
|
)
|
||||||
return this.color;
|
return this.color;
|
||||||
}
|
}
|
||||||
distance = Math.round(distance * 100) / 100;
|
distance = Math.round(distance * 100) / 100;
|
||||||
if (!this.dragRulerRanges)
|
if (!this.dragRulerRanges)
|
||||||
this.dragRulerRanges = getRangesFromSpeedProvider(this.draggedEntity);
|
this.dragRulerRanges = getRangesFromSpeedProvider(this.draggedEntity);
|
||||||
return getColorForDistanceAndToken(distance, this.draggedEntity, this.dragRulerRanges) ?? this.color;
|
return (
|
||||||
|
getColorForDistanceAndToken(distance, this.draggedEntity, this.dragRulerRanges) ??
|
||||||
|
this.color
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
dragRulerStart(options, measureImmediately=true) {
|
dragRulerStart(options, measureImmediately = true) {
|
||||||
const entity = this.draggedEntity;
|
const entity = this.draggedEntity;
|
||||||
const isToken = entity instanceof Token;
|
const isToken = entity instanceof Token;
|
||||||
if (isToken && !currentSpeedProvider.usesRuler(entity))
|
if (isToken && !currentSpeedProvider.usesRuler(entity)) return;
|
||||||
return;
|
|
||||||
const ruler = canvas.controls.ruler;
|
const ruler = canvas.controls.ruler;
|
||||||
ruler.clear();
|
ruler.clear();
|
||||||
ruler._state = Ruler.STATES.STARTING;
|
ruler._state = Ruler.STATES.STARTING;
|
||||||
let entityCenter;
|
const entityCenter = getEntityCenter(this.draggedEntity);
|
||||||
if (isToken && canvas.grid.isHex && game.modules.get("hex-size-support")?.active && CONFIG.hexSizeSupport.getAltSnappingFlag(entity))
|
|
||||||
entityCenter = getHexSizeSupportTokenGridCenter(entity);
|
|
||||||
else
|
|
||||||
entityCenter = entity.center;
|
|
||||||
if (isToken && game.settings.get(settingsKey, "enableMovementHistory"))
|
if (isToken && game.settings.get(settingsKey, "enableMovementHistory"))
|
||||||
ruler.dragRulerAddWaypointHistory(getMovementHistory(entity));
|
ruler.dragRulerAddWaypointHistory(getMovementHistory(entity));
|
||||||
ruler.dragRulerAddWaypoint(entityCenter, {snap: false});
|
ruler.dragRulerAddWaypoint(entityCenter, {snap: false});
|
||||||
const mousePosition = canvas.app.renderer.plugins.interaction.mouse.getLocalPosition(canvas.tokens);
|
const mousePosition = getPointer().getLocalPosition(canvas.tokens);
|
||||||
const destination = {x: mousePosition.x + ruler.rulerOffset.x, y: mousePosition.y + ruler.rulerOffset.y};
|
const destination = {
|
||||||
if (measureImmediately)
|
x: mousePosition.x,
|
||||||
ruler.measure(destination, options);
|
y: mousePosition.y,
|
||||||
|
};
|
||||||
|
if (measureImmediately) ruler.measure(destination, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
dragRulerSendState() {
|
||||||
|
if (this.user !== game.user) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
game.user.broadcastActivity({
|
||||||
|
ruler: this._getMeasurementData(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ruler = DragRulerRuler;
|
CONFIG.Canvas.rulerClass = DragRulerRuler;
|
||||||
}
|
}
|
||||||
|
|||||||
+129
-93
@@ -1,27 +1,37 @@
|
|||||||
import {availableSpeedProviders, currentSpeedProvider, getDefaultSpeedProvider, updateSpeedProvider} from "./api.js";
|
import {
|
||||||
import {SpeedProvider} from "./speed_provider.js"
|
availableSpeedProviders,
|
||||||
|
currentSpeedProvider,
|
||||||
|
getDefaultSpeedProvider,
|
||||||
|
updateSpeedProvider,
|
||||||
|
} from "./api.js";
|
||||||
|
import {SpeedProvider} from "./speed_provider.js";
|
||||||
|
import {early_isGM} from "./util.js";
|
||||||
|
|
||||||
export const settingsKey = "drag-ruler";
|
export const settingsKey = "drag-ruler";
|
||||||
|
|
||||||
export const RightClickAction = Object.freeze({
|
export const RightClickAction = Object.freeze({
|
||||||
CREATE_WAYPOINT: 0,
|
CREATE_WAYPOINT: 0,
|
||||||
DELETE_WAYPOINT: 1,
|
DELETE_WAYPOINT: 1,
|
||||||
ABORT_DRAG:2,
|
ABORT_DRAG: 2,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function delayedReload() {
|
||||||
|
window.setTimeout(() => location.reload(), 500);
|
||||||
|
}
|
||||||
|
|
||||||
export function registerSettings() {
|
export function registerSettings() {
|
||||||
game.settings.register(settingsKey, "dataVersion", {
|
game.settings.register(settingsKey, "dataVersion", {
|
||||||
scope: "world",
|
scope: "world",
|
||||||
config: false,
|
config: false,
|
||||||
type: String,
|
type: String,
|
||||||
default: "fresh install"
|
default: "fresh install",
|
||||||
})
|
});
|
||||||
|
|
||||||
game.settings.register(settingsKey, "clientDataVersion", {
|
game.settings.register(settingsKey, "clientDataVersion", {
|
||||||
scope: "client",
|
scope: "client",
|
||||||
config: false,
|
config: false,
|
||||||
type: String,
|
type: String,
|
||||||
default: "fresh install"
|
default: "fresh install",
|
||||||
});
|
});
|
||||||
|
|
||||||
game.settings.register(settingsKey, "rightClickAction", {
|
game.settings.register(settingsKey, "rightClickAction", {
|
||||||
@@ -62,7 +72,7 @@ export function registerSettings() {
|
|||||||
config: true,
|
config: true,
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true,
|
||||||
})
|
});
|
||||||
|
|
||||||
game.settings.register(settingsKey, "showGMRulerToPlayers", {
|
game.settings.register(settingsKey, "showGMRulerToPlayers", {
|
||||||
name: "drag-ruler.settings.showGMRulerToPlayers.name",
|
name: "drag-ruler.settings.showGMRulerToPlayers.name",
|
||||||
@@ -71,7 +81,7 @@ export function registerSettings() {
|
|||||||
config: true,
|
config: true,
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true,
|
||||||
})
|
});
|
||||||
|
|
||||||
game.settings.register(settingsKey, "enableMovementHistory", {
|
game.settings.register(settingsKey, "enableMovementHistory", {
|
||||||
name: "drag-ruler.settings.enableMovementHistory.name",
|
name: "drag-ruler.settings.enableMovementHistory.name",
|
||||||
@@ -82,6 +92,27 @@ export function registerSettings() {
|
|||||||
default: true,
|
default: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (game.modules.get("routinglib")?.active) {
|
||||||
|
game.settings.register(settingsKey, "allowPathfinding", {
|
||||||
|
name: "drag-ruler.settings.allowPathfinding.name",
|
||||||
|
hint: "drag-ruler.settings.allowPathfinding.hint",
|
||||||
|
scope: "world",
|
||||||
|
config: true,
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
onChange: delayedReload,
|
||||||
|
});
|
||||||
|
|
||||||
|
game.settings.register(settingsKey, "autoPathfinding", {
|
||||||
|
name: "drag-ruler.settings.autoPathfinding.name",
|
||||||
|
hint: "drag-ruler.settings.autoPathfinding.hint",
|
||||||
|
scope: "client",
|
||||||
|
config: early_isGM() || game.settings.get(settingsKey, "allowPathfinding"),
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
game.settings.register(settingsKey, "lastTerrainRulerHintTime", {
|
game.settings.register(settingsKey, "lastTerrainRulerHintTime", {
|
||||||
config: false,
|
config: false,
|
||||||
type: Number,
|
type: Number,
|
||||||
@@ -101,7 +132,7 @@ export function registerSettings() {
|
|||||||
type: String,
|
type: String,
|
||||||
default: getDefaultSpeedProvider(),
|
default: getDefaultSpeedProvider(),
|
||||||
onChange: updateSpeedProvider,
|
onChange: updateSpeedProvider,
|
||||||
})
|
});
|
||||||
|
|
||||||
game.settings.registerMenu(settingsKey, "speedProviderSettings", {
|
game.settings.registerMenu(settingsKey, "speedProviderSettings", {
|
||||||
name: "drag-ruler.settings.speedProviderSettings.name",
|
name: "drag-ruler.settings.speedProviderSettings.name",
|
||||||
@@ -110,7 +141,7 @@ export function registerSettings() {
|
|||||||
icon: "fas fa-tachometer-alt",
|
icon: "fas fa-tachometer-alt",
|
||||||
type: SpeedProviderSettings,
|
type: SpeedProviderSettings,
|
||||||
restricted: false,
|
restricted: false,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class SpeedProviderSettings extends FormApplication {
|
class SpeedProviderSettings extends FormApplication {
|
||||||
@@ -120,43 +151,44 @@ class SpeedProviderSettings extends FormApplication {
|
|||||||
title: game.i18n.localize("drag-ruler.settings.speedProviderSettings.windowTitle"),
|
title: game.i18n.localize("drag-ruler.settings.speedProviderSettings.windowTitle"),
|
||||||
template: "modules/drag-ruler/templates/speed_provider_settings.html",
|
template: "modules/drag-ruler/templates/speed_provider_settings.html",
|
||||||
width: 600,
|
width: 600,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getData(options={}) {
|
getData(options = {}) {
|
||||||
const data = {}
|
const data = {};
|
||||||
data.isGM = game.user.isGM
|
data.isGM = game.user.isGM;
|
||||||
const selectedProvider = currentSpeedProvider.id
|
const selectedProvider = currentSpeedProvider.id;
|
||||||
|
|
||||||
// Insert all speed providers into the template data
|
// Insert all speed providers into the template data
|
||||||
data.providers = Object.values(availableSpeedProviders).map(speedProvider => {
|
data.providers = Object.values(availableSpeedProviders).map(speedProvider => {
|
||||||
const provider = {}
|
const provider = {};
|
||||||
provider.id = speedProvider.id
|
provider.id = speedProvider.id;
|
||||||
provider.hasSettings = speedProvider instanceof SpeedProvider
|
provider.hasSettings = speedProvider instanceof SpeedProvider;
|
||||||
if (provider.hasSettings)
|
if (provider.hasSettings) provider.settings = enumerateProviderSettings(speedProvider);
|
||||||
provider.settings = enumerateProviderSettings(speedProvider)
|
let dotPosition = provider.id.indexOf(".");
|
||||||
let dotPosition = provider.id.indexOf(".")
|
if (dotPosition === -1) dotPosition = provider.id.length;
|
||||||
if (dotPosition === -1)
|
const type = provider.id.substring(0, dotPosition);
|
||||||
dotPosition = provider.id.length
|
const id = provider.id.substring(dotPosition + 1);
|
||||||
const type = provider.id.substring(0, dotPosition)
|
|
||||||
const id = provider.id.substring(dotPosition + 1)
|
|
||||||
if (type === "native") {
|
if (type === "native") {
|
||||||
provider.selectTitle = game.i18n.localize("drag-ruler.settings.speedProviderSettings.speedProvider.choices.native")
|
provider.selectTitle = game.i18n.localize(
|
||||||
}
|
"drag-ruler.settings.speedProviderSettings.speedProvider.choices.native",
|
||||||
else {
|
);
|
||||||
let name
|
} else {
|
||||||
|
let name;
|
||||||
if (type === "module") {
|
if (type === "module") {
|
||||||
name = game.modules.get(id).data.title
|
name = game.modules.get(id).title;
|
||||||
|
} else {
|
||||||
|
name = game.system.title;
|
||||||
}
|
}
|
||||||
else {
|
provider.selectTitle = game.i18n.format(
|
||||||
name = game.system.data.title
|
`drag-ruler.settings.speedProviderSettings.speedProvider.choices.${type}`,
|
||||||
}
|
{name},
|
||||||
provider.selectTitle = game.i18n.format(`drag-ruler.settings.speedProviderSettings.speedProvider.choices.${type}`, {name})
|
);
|
||||||
}
|
}
|
||||||
provider.isSelected = provider.id === selectedProvider
|
provider.isSelected = provider.id === selectedProvider;
|
||||||
return provider
|
return provider;
|
||||||
})
|
});
|
||||||
data.selectedProviderName = data.providers.find(provider => provider.isSelected).selectTitle
|
data.selectedProviderName = data.providers.find(provider => provider.isSelected).selectTitle;
|
||||||
|
|
||||||
data.providerSelection = {
|
data.providerSelection = {
|
||||||
id: "speedProvider",
|
id: "speedProvider",
|
||||||
@@ -164,122 +196,126 @@ class SpeedProviderSettings extends FormApplication {
|
|||||||
hint: game.i18n.localize("drag-ruler.settings.speedProviderSettings.speedProvider.hint"),
|
hint: game.i18n.localize("drag-ruler.settings.speedProviderSettings.speedProvider.hint"),
|
||||||
type: String,
|
type: String,
|
||||||
choices: data.providers.reduce((choices, provider) => {
|
choices: data.providers.reduce((choices, provider) => {
|
||||||
choices[provider.id] = provider.selectTitle
|
choices[provider.id] = provider.selectTitle;
|
||||||
return choices
|
return choices;
|
||||||
}, {}),
|
}, {}),
|
||||||
value: selectedProvider,
|
value: selectedProvider,
|
||||||
isCheckbox: false,
|
isCheckbox: false,
|
||||||
isSelect: true,
|
isSelect: true,
|
||||||
isRange: false,
|
isRange: false,
|
||||||
}
|
};
|
||||||
return data
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async _updateObject(event, formData) {
|
async _updateObject(event, formData) {
|
||||||
const selectedSpeedProvider = game.user.isGM ? formData.speedProvider : game.settings.get(settingsKey, "speedProvider")
|
const selectedSpeedProvider = game.user.isGM
|
||||||
|
? formData.speedProvider
|
||||||
|
: game.settings.get(settingsKey, "speedProvider");
|
||||||
for (let [key, value] of Object.entries(formData)) {
|
for (let [key, value] of Object.entries(formData)) {
|
||||||
// Check if this is color, convert the value to an integer
|
// Check if this is color, convert the value to an integer
|
||||||
const splitKey = key.split(".", 3)
|
const splitKey = key.split(".", 3);
|
||||||
if (splitKey[0] !== "native")
|
if (splitKey[0] !== "native") splitKey.shift();
|
||||||
splitKey.shift()
|
|
||||||
if (splitKey.length >= 2 && splitKey[1] == "color") {
|
if (splitKey.length >= 2 && splitKey[1] == "color") {
|
||||||
value = parseInt(value.substring(1), 16)
|
value = parseInt(value.substring(1), 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't change settings for speed providers that aren't currently active
|
// Don't change settings for speed providers that aren't currently active
|
||||||
if (key !== "speedProvider" && !key.startsWith(selectedSpeedProvider))
|
if (key !== "speedProvider" && !key.startsWith(selectedSpeedProvider)) continue;
|
||||||
continue
|
|
||||||
|
|
||||||
// Get the key for the current setting
|
// Get the key for the current setting
|
||||||
let setting
|
let setting;
|
||||||
if (key === "speedProvider")
|
if (key === "speedProvider") setting = "speedProvider";
|
||||||
setting = "speedProvider"
|
else setting = `speedProviders.${key}`;
|
||||||
else
|
|
||||||
setting = `speedProviders.${key}`
|
|
||||||
|
|
||||||
// Get the old setting value
|
// Get the old setting value
|
||||||
const oldValue = game.settings.get(settingsKey, setting)
|
const oldValue = game.settings.get(settingsKey, setting);
|
||||||
|
|
||||||
// Only update the setting if it has been changed (this leaves the default in place if it hasn't been touched)
|
// Only update the setting if it has been changed (this leaves the default in place if it hasn't been touched)
|
||||||
if (value !== oldValue)
|
if (value !== oldValue) game.settings.set(settingsKey, setting, value);
|
||||||
game.settings.set(settingsKey, setting, value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Activate the configured speed provider
|
// Activate the configured speed provider
|
||||||
updateSpeedProvider()
|
updateSpeedProvider();
|
||||||
}
|
}
|
||||||
|
|
||||||
activateListeners(html) {
|
activateListeners(html) {
|
||||||
super.activateListeners(html)
|
super.activateListeners(html);
|
||||||
html.find("select[name=speedProvider]").change(this.onSpeedProviderChange.bind(this))
|
html.find("select[name=speedProvider]").change(this.onSpeedProviderChange.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
onSpeedProviderChange(event) {
|
onSpeedProviderChange(event) {
|
||||||
// Hide all module settings
|
// Hide all module settings
|
||||||
document.querySelectorAll(".drag-ruler-provider-settings").forEach(element => element.style.display = "none")
|
document
|
||||||
|
.querySelectorAll(".drag-ruler-provider-settings")
|
||||||
|
.forEach(element => (element.style.display = "none"));
|
||||||
// Show the settings block for the currently selected module
|
// Show the settings block for the currently selected module
|
||||||
document.getElementById(`drag-ruler.provider.${event.currentTarget.value}`).style.display = ""
|
document.getElementById(`drag-ruler.provider.${event.currentTarget.value}`).style.display = "";
|
||||||
|
|
||||||
// Recalculate window height
|
// Recalculate window height
|
||||||
this.element[0].style.height = null
|
this.element[0].style.height = null;
|
||||||
this.position.height = undefined
|
this.position.height = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toDomHex(value) {
|
function toDomHex(value) {
|
||||||
const hex = value.toString(16)
|
const hex = value.toString(16);
|
||||||
return "#" + "0".repeat(Math.max(0, 6 - hex.length)) + hex
|
return "#" + "0".repeat(Math.max(0, 6 - hex.length)) + hex;
|
||||||
}
|
}
|
||||||
|
|
||||||
function enumerateProviderSettings(provider) {
|
function enumerateProviderSettings(provider) {
|
||||||
const colorSettings = []
|
const colorSettings = [];
|
||||||
const unreachableColor = {id: "unreachable", name: "drag-ruler.settings.speedProviderSettings.color.unreachable.name"}
|
const unreachableColor = {
|
||||||
|
id: "unreachable",
|
||||||
|
name: "drag-ruler.settings.speedProviderSettings.color.unreachable.name",
|
||||||
|
};
|
||||||
|
|
||||||
// Resolve settings for the colors
|
// Resolve settings for the colors
|
||||||
for (const color of provider.colors.concat([unreachableColor])) {
|
for (const color of provider.colors.concat([unreachableColor])) {
|
||||||
// Localize the name, if avaliable. If no name is available use the id as name
|
// Localize the name, if avaliable. If no name is available use the id as name
|
||||||
const colorName = color.name ? game.i18n.localize(color.name) : color.id
|
const colorName = color.name ? game.i18n.localize(color.name) : color.id;
|
||||||
let hint
|
let hint;
|
||||||
if (color === unreachableColor)
|
if (color === unreachableColor)
|
||||||
hint = game.i18n.localize("drag-ruler.settings.speedProviderSettings.color.unreachable.hint")
|
hint = game.i18n.localize("drag-ruler.settings.speedProviderSettings.color.unreachable.hint");
|
||||||
else
|
else
|
||||||
hint = game.i18n.format("drag-ruler.settings.speedProviderSettings.color.hint", {colorName})
|
hint = game.i18n.format("drag-ruler.settings.speedProviderSettings.color.hint", {colorName});
|
||||||
colorSettings.push({
|
colorSettings.push({
|
||||||
id: `${provider.id}.color.${color.id}`,
|
id: `${provider.id}.color.${color.id}`,
|
||||||
name: game.i18n.format("drag-ruler.settings.speedProviderSettings.color.name", {colorName}),
|
name: game.i18n.format("drag-ruler.settings.speedProviderSettings.color.name", {colorName}),
|
||||||
hint: hint,
|
hint: hint,
|
||||||
type: Number,
|
type: Number,
|
||||||
value: toDomHex(game.settings.get(settingsKey, `speedProviders.${provider.id}.color.${color.id}`)),
|
value: toDomHex(
|
||||||
|
game.settings.get(settingsKey, `speedProviders.${provider.id}.color.${color.id}`),
|
||||||
|
),
|
||||||
isCheckbox: false,
|
isCheckbox: false,
|
||||||
isSelect: false,
|
isSelect: false,
|
||||||
isRange: false,
|
isRange: false,
|
||||||
isColor: true,
|
isColor: true,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare regular settings
|
// Prepare regular settings
|
||||||
const settings = []
|
const settings = [];
|
||||||
for (const setting of provider.settings) {
|
for (const setting of provider.settings) {
|
||||||
try {
|
try {
|
||||||
if (setting.scope === "world" && !game.user.isGM)
|
if (setting.scope === "world" && !game.user.isGM) continue;
|
||||||
continue
|
const s = foundry.utils.duplicate(setting);
|
||||||
const s = duplicate(setting)
|
s.id = `${provider.id}.setting.${s.id}`;
|
||||||
s.id = `${provider.id}.setting.${s.id}`
|
s.name = game.i18n.localize(s.name);
|
||||||
s.name = game.i18n.localize(s.name)
|
s.hint = game.i18n.localize(s.hint);
|
||||||
s.hint = game.i18n.localize(s.hint)
|
s.value = provider.getSetting(setting.id);
|
||||||
s.value = provider.getSetting(setting.id)
|
s.type = setting.type instanceof Function ? setting.type.name : "String";
|
||||||
s.type = setting.type instanceof Function ? setting.type.name : "String"
|
s.isCheckbox = setting.type === Boolean;
|
||||||
s.isCheckbox = setting.type === Boolean
|
s.isSelect = s.choices !== undefined;
|
||||||
s.isSelect = s.choices !== undefined
|
s.isRange = setting.type === Number && s.range;
|
||||||
s.isRange = (setting.type === Number) && s.range
|
s.isColor = false;
|
||||||
s.isColor = false
|
settings.push(s);
|
||||||
settings.push(s)
|
} catch (e) {
|
||||||
}
|
console.warn(
|
||||||
catch (e) {
|
`Drag Ruler | The following error occured while rendering setting "${setting.id}" of module/system "${this.id}. It won't be displayed.`,
|
||||||
console.warn(`Drag Ruler | The following error occured while rendering setting "${setting.id}" of module/system "${this.id}. It won't be displayed.`)
|
);
|
||||||
console.error(e)
|
console.error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return settings.concat(colorSettings)
|
return settings.concat(colorSettings);
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-6
@@ -11,8 +11,13 @@ Hooks.once("socketlib.ready", () => {
|
|||||||
export function updateCombatantDragRulerFlags(combat, updates) {
|
export function updateCombatantDragRulerFlags(combat, updates) {
|
||||||
const combatId = combat.id;
|
const combatId = combat.id;
|
||||||
// TODO Check if canvas.tokens.get is still neccessary in future foundry versions
|
// TODO Check if canvas.tokens.get is still neccessary in future foundry versions
|
||||||
return socket.executeAsGM(_socketUpdateCombatantDragRulerFlags, combatId, updates)
|
return socket
|
||||||
.then(() => currentSpeedProvider.onMovementHistoryUpdate(updates.map(update => canvas.tokens.get(combat.combatants.get(update._id).token.id))));
|
.executeAsGM(_socketUpdateCombatantDragRulerFlags, combatId, updates)
|
||||||
|
.then(() =>
|
||||||
|
currentSpeedProvider.onMovementHistoryUpdate(
|
||||||
|
updates.map(update => canvas.tokens.get(combat.combatants.get(update._id).token.id)),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function _socketUpdateCombatantDragRulerFlags(combatId, updates) {
|
async function _socketUpdateCombatantDragRulerFlags(combatId, updates) {
|
||||||
@@ -21,12 +26,15 @@ async function _socketUpdateCombatantDragRulerFlags(combatId, updates) {
|
|||||||
const requestedUpdates = updates.length;
|
const requestedUpdates = updates.length;
|
||||||
updates = updates.filter(update => {
|
updates = updates.filter(update => {
|
||||||
const actor = combat.combatants.get(update._id).actor;
|
const actor = combat.combatants.get(update._id).actor;
|
||||||
if (!actor)
|
if (!actor) return false;
|
||||||
return false;
|
|
||||||
return actor.testUserPermission(user, "OWNER");
|
return actor.testUserPermission(user, "OWNER");
|
||||||
});
|
});
|
||||||
if (updates.length !== requestedUpdates) {
|
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`);
|
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 => {
|
updates = updates.map(update => {
|
||||||
return {_id: update._id, flags: {dragRuler: update.dragRulerFlags}};
|
return {_id: update._id, flags: {dragRuler: update.dragRulerFlags}};
|
||||||
@@ -39,5 +47,6 @@ export function recalculate(tokens) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function _socketRecalculate(tokenIds) {
|
function _socketRecalculate(tokenIds) {
|
||||||
return canvas.controls.ruler.dragRulerRecalculate(tokenIds);
|
const ruler = canvas.controls.ruler;
|
||||||
|
if (ruler.isDragRuler) ruler.dragRulerRecalculate(tokenIds);
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-50
@@ -1,5 +1,5 @@
|
|||||||
import {settingsKey} from "./settings.js"
|
import {settingsKey} from "./settings.js";
|
||||||
import {getDefaultDashMultiplier, getDefaultSpeedAttribute} from "./systems.js"
|
import {getDefaultDashMultiplier, getDefaultSpeedAttribute} from "./systems.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for all speed providers.
|
* Base class for all speed providers.
|
||||||
@@ -19,7 +19,7 @@ export class SpeedProvider {
|
|||||||
* Implementing this method is required for all speed providers
|
* Implementing this method is required for all speed providers
|
||||||
*/
|
*/
|
||||||
get colors() {
|
get colors() {
|
||||||
throw new Error("A SpeedProvider must implement the colors function")
|
throw new Error("A SpeedProvider must implement the colors function");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -31,7 +31,7 @@ export class SpeedProvider {
|
|||||||
* Implementing this method is required for all speed providers
|
* Implementing this method is required for all speed providers
|
||||||
*/
|
*/
|
||||||
getRanges(token) {
|
getRanges(token) {
|
||||||
throw new Error("A SpeedProvider must implement the getRanges function")
|
throw new Error("A SpeedProvider must implement the getRanges function");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -44,7 +44,7 @@ export class SpeedProvider {
|
|||||||
* Implementing this method is optional and only needs to be done if you want to provide custom provider settings
|
* Implementing this method is optional and only needs to be done if you want to provide custom provider settings
|
||||||
*/
|
*/
|
||||||
get settings() {
|
get settings() {
|
||||||
return []
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -53,28 +53,7 @@ export class SpeedProvider {
|
|||||||
* Implementing this method is optional and only needs to be done if you want to provide a custom default for that color.
|
* Implementing this method is optional and only needs to be done if you want to provide a custom default for that color.
|
||||||
*/
|
*/
|
||||||
get defaultUnreachableColor() {
|
get defaultUnreachableColor() {
|
||||||
return 0xFF0000
|
return 0xff0000;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the cost for a token to step into the specificed area.
|
|
||||||
* The area indicates the whole area that the token will occupy (for tokens larger than 1x1) the array will more than one entry.
|
|
||||||
* The return value should be an integer indicating a multiplicator by that the cost of that step should be increased.
|
|
||||||
* (1 is regular cost, 2 costs double, 3 costs triple, ...)
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* - options: An object used to configure Enhanced Terrain Layer'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 Enhanced Terrain Layer and Terrain Ruler modules are enabled.
|
|
||||||
*
|
|
||||||
* Implementing this method is optional and only needs to be done if you want to provide a custom cost function (for example to allow tokens to ignore difficult terrain)
|
|
||||||
*/
|
|
||||||
getCostForStep(token, area, options={}) {
|
|
||||||
// Lookup the cost for each square occupied by the token
|
|
||||||
options.token = token;
|
|
||||||
const costs = area.map(space => terrainRuler.getCost(space.x, space.y, options));
|
|
||||||
// Return the maximum of the costs
|
|
||||||
return costs.reduce((max, current) => Math.max(max, current))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -86,7 +65,7 @@ export class SpeedProvider {
|
|||||||
* Implementing this method is optional and only needs to be done if you want to disable Drag Ruler for some tokens.
|
* Implementing this method is optional and only needs to be done if you want to disable Drag Ruler for some tokens.
|
||||||
*/
|
*/
|
||||||
usesRuler(token) {
|
usesRuler(token) {
|
||||||
return true
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -104,13 +83,14 @@ export class SpeedProvider {
|
|||||||
*/
|
*/
|
||||||
getSetting(settingId) {
|
getSetting(settingId) {
|
||||||
try {
|
try {
|
||||||
return game.settings.get(settingsKey, `speedProviders.${this.id}.setting.${settingId}`)
|
return game.settings.get(settingsKey, `speedProviders.${this.id}.setting.${settingId}`);
|
||||||
}
|
} catch (e) {
|
||||||
catch (e) {
|
|
||||||
if (this.settings.some(setting => setting.id === settingId)) {
|
if (this.settings.some(setting => setting.id === settingId)) {
|
||||||
throw e
|
throw e;
|
||||||
}
|
}
|
||||||
throw new Error(`Drag Ruler | "${settingId}" is not a registered setting for "${this.id}". If you're the module/system developer, please add it to the return values of your Speed Providers "get settings()" function.`)
|
throw new Error(
|
||||||
|
`Drag Ruler | "${settingId}" is not a registered setting for "${this.id}". If you're the module/system developer, please add it to the return values of your Speed Providers "get settings()" function.`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,32 +100,34 @@ export class SpeedProvider {
|
|||||||
* This function should neither be called or overridden by speed provider implementations
|
* This function should neither be called or overridden by speed provider implementations
|
||||||
*/
|
*/
|
||||||
constructor(id) {
|
constructor(id) {
|
||||||
this.id = id
|
this.id = id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export class GenericSpeedProvider extends SpeedProvider {
|
export class GenericSpeedProvider extends SpeedProvider {
|
||||||
get colors() {
|
get colors() {
|
||||||
return [
|
return [
|
||||||
{id: "walk", default: 0x00FF00, name: "drag-ruler.genericSpeedProvider.speeds.walk"},
|
{id: "walk", default: 0x00ff00, name: "drag-ruler.genericSpeedProvider.speeds.walk"},
|
||||||
{id: "dash", default: 0xFFFF00, name: "drag-ruler.genericSpeedProvider.speeds.dash"}
|
{id: "dash", default: 0xffff00, name: "drag-ruler.genericSpeedProvider.speeds.dash"},
|
||||||
]
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
getRanges(token) {
|
getRanges(token) {
|
||||||
const speedAttribute = this.getSetting("speedAttribute")
|
const speedAttribute = this.getSetting("speedAttribute");
|
||||||
if (!speedAttribute)
|
if (!speedAttribute) return [];
|
||||||
return []
|
const tokenSpeed = parseFloat(foundry.utils.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(
|
||||||
return []
|
`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 [];
|
||||||
}
|
}
|
||||||
const dashMultiplier = this.getSetting("dashMultiplier")
|
const dashMultiplier = this.getSetting("dashMultiplier");
|
||||||
if (!dashMultiplier)
|
if (!dashMultiplier) return [{range: tokenSpeed, color: "walk"}];
|
||||||
return [{range: tokenSpeed, color: "walk"}]
|
return [
|
||||||
return [{range: tokenSpeed, color: "walk"}, {range: tokenSpeed * dashMultiplier, color: "dash"}]
|
{range: tokenSpeed, color: "walk"},
|
||||||
|
{range: tokenSpeed * dashMultiplier, color: "dash"},
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
get settings() {
|
get settings() {
|
||||||
@@ -167,7 +149,7 @@ export class GenericSpeedProvider extends SpeedProvider {
|
|||||||
config: true,
|
config: true,
|
||||||
type: Number,
|
type: Number,
|
||||||
default: getDefaultDashMultiplier(),
|
default: getDefaultDashMultiplier(),
|
||||||
}
|
},
|
||||||
]
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+33
-12
@@ -1,40 +1,61 @@
|
|||||||
export function getDefaultSpeedAttribute() {
|
export function getDefaultSpeedAttribute() {
|
||||||
switch (game.system.id) {
|
switch (game.system.id) {
|
||||||
case "CoC7":
|
case "CoC7":
|
||||||
return "actor.data.data.attribs.mov.value";
|
return "actor.system.attribs.mov.value";
|
||||||
case "dcc":
|
case "dcc":
|
||||||
return "actor.data.data.attributes.speed.value";
|
return "actor.system.attributes.speed.value";
|
||||||
|
case "dnd4e":
|
||||||
|
return "actor.system.movement.walk.value";
|
||||||
case "dnd5e":
|
case "dnd5e":
|
||||||
return "actor.data.data.attributes.movement.walk"
|
return "actor.system.attributes.movement.walk";
|
||||||
case "lancer":
|
case "lancer":
|
||||||
return "actor.data.data.derived.speed"
|
return "actor.system.speed";
|
||||||
case "pf1":
|
case "pf1":
|
||||||
case "D35E":
|
case "D35E":
|
||||||
return "actor.data.data.attributes.speed.land.total"
|
return "actor.system.attributes.speed.land.total";
|
||||||
case "sfrpg":
|
case "sfrpg":
|
||||||
return "actor.data.data.attributes.speed.value";
|
return "actor.system.attributes.speed.value";
|
||||||
case "shadowrun5e":
|
case "shadowrun5e":
|
||||||
return "actor.data.data.movement.walk.value";
|
return "actor.system.movement.walk.value";
|
||||||
case "swade":
|
case "swade":
|
||||||
return "actor.data.data.stats.speed.adjusted";
|
return "actor.system.stats.speed.adjusted";
|
||||||
|
case "ds4":
|
||||||
|
return "actor.system.combatValues.movement.total";
|
||||||
|
case "splittermond":
|
||||||
|
return "actor.derivedValues.speed.value";
|
||||||
|
case "wfrp4e":
|
||||||
|
return "actor.system.details.move.walk";
|
||||||
|
case "crucible":
|
||||||
|
return "actor.system.movement.stride";
|
||||||
|
case "dragonbane":
|
||||||
|
return "actor.system.movement.value";
|
||||||
}
|
}
|
||||||
return ""
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDefaultDashMultiplier() {
|
export function getDefaultDashMultiplier() {
|
||||||
switch (game.system.id) {
|
switch (game.system.id) {
|
||||||
case "swade":
|
case "swade":
|
||||||
return 0
|
return 0;
|
||||||
case "dcc":
|
case "dcc":
|
||||||
|
case "dnd4e":
|
||||||
case "dnd5e":
|
case "dnd5e":
|
||||||
case "lancer":
|
case "lancer":
|
||||||
case "pf1":
|
case "pf1":
|
||||||
case "D35E":
|
case "D35E":
|
||||||
case "sfrpg":
|
case "sfrpg":
|
||||||
case "shadowrun5e":
|
case "shadowrun5e":
|
||||||
return 2
|
case "ds4":
|
||||||
|
case "dragonbane":
|
||||||
|
return 2;
|
||||||
case "CoC7":
|
case "CoC7":
|
||||||
return 5;
|
return 5;
|
||||||
|
case "splittermond":
|
||||||
|
return 3;
|
||||||
|
case "wfrp4e":
|
||||||
|
return 2;
|
||||||
|
case "crucible":
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
return 0
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
+213
-136
@@ -1,112 +1,153 @@
|
|||||||
import {getPixelsFromGridPosition} from "./foundry_fixes.js"
|
import {getPixelsFromGridPosition} from "./foundry_fixes.js";
|
||||||
import { disableSnap } from "./keybindings.js";
|
import {findVertexSnapPoint} from "./hex_support.js";
|
||||||
|
import {disableSnap, moveWithoutAnimation, togglePathfinding} from "./keybindings.js";
|
||||||
|
import {settingsKey} from "./settings.js";
|
||||||
|
|
||||||
export function* zip(it1, it2) {
|
export function* zip(it1, it2) {
|
||||||
for (let i = 0;i < Math.min(it1.length, it2.length);i++) {
|
for (let i = 0; i < Math.min(it1.length, it2.length); i++) {
|
||||||
yield [it1[i], it2[i]]
|
yield [it1[i], it2[i]];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function* enumeratedZip(it1, it2) {
|
||||||
|
let i = 0;
|
||||||
|
for (const [v1, v2] of zip(it1, it2)) {
|
||||||
|
yield [i, v1, v2];
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* iterPairs(l) {
|
||||||
|
for (let i = 1; i < l.length; i++) {
|
||||||
|
yield [l[i - 1], l[i]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sum(arr) {
|
||||||
|
return arr.reduce((a, b) => a + b, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A copy of this function lives in the routinglib module
|
||||||
|
export function getHexTokenSize(token) {
|
||||||
|
const size = token.document.width;
|
||||||
|
if (token.document.height !== size) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getEntityCenter(token) {
|
||||||
|
if (token instanceof Token && isCanvasHex()) {
|
||||||
|
const center = token.center;
|
||||||
|
const size = getHexTokenSize(token);
|
||||||
|
if (size % 2 === 0) {
|
||||||
|
let offset;
|
||||||
|
if (canvas.grid.columnar) {
|
||||||
|
offset = canvas.grid.sizeX - canvas.grid.sizeY;
|
||||||
|
} else {
|
||||||
|
offset = canvas.grid.sizeY - canvas.grid.sizeX;
|
||||||
|
}
|
||||||
|
if (getAltOrientationFlagForToken(token, size)) {
|
||||||
|
offset *= -1;
|
||||||
|
}
|
||||||
|
if (canvas.grid.columnar) {
|
||||||
|
center.x -= offset;
|
||||||
|
return center;
|
||||||
|
} else {
|
||||||
|
center.y -= offset;
|
||||||
|
return center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return token.center;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A copy of this function lives in the routinglib module
|
||||||
|
export function getAltOrientationFlagForToken(token, size) {
|
||||||
|
const hexSizeSupport = game.modules.get("hex-size-support")?.api;
|
||||||
|
if (hexSizeSupport) {
|
||||||
|
return hexSizeSupport.isAltOrientation(token);
|
||||||
|
}
|
||||||
|
// In native foundry, tokens of size 2 are oriented like the "alt orientation" from hex-size-support
|
||||||
|
// Tokens of size 4 are oriented like alt orientation wasn't set
|
||||||
|
return size === 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A copy of this function lives in the librouting module
|
||||||
export function getSnapPointForToken(x, y, token) {
|
export function getSnapPointForToken(x, y, token) {
|
||||||
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
|
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
|
||||||
return new PIXI.Point(x, y);
|
return {x, y};
|
||||||
}
|
}
|
||||||
if (canvas.grid.isHex) {
|
|
||||||
if (game.modules.get("hex-size-support")?.active && CONFIG.hexSizeSupport.getAltSnappingFlag(token)) {
|
if (isCanvasHex()) {
|
||||||
if (token.document.getFlag("hex-size-support", "borderSize") % 2 === 0) {
|
const size = getHexTokenSize(token);
|
||||||
const snapPoint = CONFIG.hexSizeSupport.findVertexSnapPoint(x, y, token, canvas.grid.grid)
|
if (size % 2 === 0) {
|
||||||
return new PIXI.Point(snapPoint.x, snapPoint.y)
|
return findVertexSnapPoint(x, y, getAltOrientationFlagForToken(token, size));
|
||||||
}
|
|
||||||
else {
|
|
||||||
return new PIXI.Point(...canvas.grid.getCenter(x, y))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return new PIXI.Point(...canvas.grid.getCenter(x, y));
|
|
||||||
}
|
}
|
||||||
|
const [snapX, snapY] = canvas.grid.getCenter(x, y);
|
||||||
|
return {x: snapX, y: snapY};
|
||||||
}
|
}
|
||||||
|
|
||||||
const [topLeftX, topLeftY] = canvas.grid.getTopLeft(x, y);
|
const [topLeftX, topLeftY] = canvas.grid.getTopLeft(x, y);
|
||||||
let cellX, cellY;
|
let cell = {};
|
||||||
if (token.data.width % 2 === 0)
|
if (token.document.width % 2 === 0) cell.x = x - canvas.grid.sizeY / 2;
|
||||||
cellX = x - canvas.grid.h / 2;
|
else cell.x = x;
|
||||||
else
|
if (token.document.height % 2 === 0) cell.y = y - canvas.grid.sizeY / 2;
|
||||||
cellX = x;
|
else cell.y = y;
|
||||||
if (token.data.height % 2 === 0)
|
const center = canvas.grid.getCenterPoint(cell);
|
||||||
cellY = y - canvas.grid.h / 2;
|
|
||||||
else
|
|
||||||
cellY = y;
|
|
||||||
const [centerX, centerY] = canvas.grid.getCenter(cellX, cellY);
|
|
||||||
let snapX, snapY;
|
let snapX, snapY;
|
||||||
// Tiny tokens can snap to the cells corners
|
// Tiny tokens can snap to the cells corners
|
||||||
if (token.data.width <= 0.5) {
|
if (token.document.width <= 0.5) {
|
||||||
const offsetX = x - topLeftX;
|
const offsetX = x - topLeftX;
|
||||||
const subGridWidth = Math.floor(canvas.grid.w / 2);
|
const subGridWidth = Math.floor(canvas.grid.sizeX / 2);
|
||||||
const subGridPosX = Math.floor(offsetX / subGridWidth);
|
const subGridPosX = Math.floor(offsetX / subGridWidth);
|
||||||
snapX = topLeftX + (subGridPosX + 0.5) * subGridWidth;
|
snapX = topLeftX + (subGridPosX + 0.5) * subGridWidth;
|
||||||
}
|
}
|
||||||
// Tokens with odd multipliers (1x1, 3x3, ...) and tokens smaller than 1x1 but bigger than 0.5x0.5 snap to the center of the grid cell
|
// Tokens with odd multipliers (1x1, 3x3, ...) and tokens smaller than 1x1 but bigger than 0.5x0.5 snap to the center of the grid cell
|
||||||
else if (Math.round(token.data.width) % 2 === 1 || token.data.width < 1) {
|
else if (Math.round(token.document.width) % 2 === 1 || token.document.width < 1) {
|
||||||
snapX = centerX;
|
snapX = center.x;
|
||||||
}
|
}
|
||||||
// All remaining tokens (those with even or fractional multipliers on square grids) snap to the intersection points of the grid
|
// All remaining tokens (those with even or fractional multipliers on square grids) snap to the intersection points of the grid
|
||||||
else {
|
else {
|
||||||
snapX = centerX + canvas.grid.w / 2;
|
snapX = center.x + canvas.grid.sizeX / 2;
|
||||||
}
|
}
|
||||||
if (token.data.height <= 0.5) {
|
if (token.document.height <= 0.5) {
|
||||||
const offsetY = y - topLeftY;
|
const offsetY = y - topLeftY;
|
||||||
const subGridHeight = Math.floor(canvas.grid.h / 2);
|
const subGridHeight = Math.floor(canvas.grid.h / 2);
|
||||||
const subGridPosY = Math.floor(offsetY / subGridHeight);
|
const subGridPosY = Math.floor(offsetY / subGridHeight);
|
||||||
snapY = topLeftY + (subGridPosY + 0.5) * subGridHeight;
|
snapY = topLeftY + (subGridPosY + 0.5) * subGridHeight;
|
||||||
|
} else if (Math.round(token.document.height) % 2 === 1 || token.document.height < 1) {
|
||||||
|
snapY = center.y;
|
||||||
|
} else {
|
||||||
|
snapY = center.y + canvas.grid.sizeY / 2;
|
||||||
}
|
}
|
||||||
else if (Math.round(token.data.height) % 2 === 1 || token.data.height < 1) {
|
return {x: snapX, y: snapY};
|
||||||
snapY = centerY;
|
}
|
||||||
}
|
|
||||||
else {
|
export function getSnapPointForTokenObj(pos, token) {
|
||||||
snapY = centerY + canvas.grid.h / 2;
|
return getSnapPointForToken(pos.x, pos.y, token);
|
||||||
}
|
|
||||||
return new PIXI.Point(snapX, snapY);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSnapPointForMeasuredTemplate(x, y) {
|
export function getSnapPointForMeasuredTemplate(x, y) {
|
||||||
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
|
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
|
||||||
return new PIXI.Point(x, y);
|
return new PIXI.Point(x, y);
|
||||||
}
|
}
|
||||||
let subgridWidth, subgridHeight;
|
return canvas.grid.grid.getSnappedPosition(x, y, canvas.templates.gridPrecision);
|
||||||
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) {
|
export function getSnapPointForEntity(x, y, entity) {
|
||||||
const isToken = entity instanceof Token;
|
const isToken = entity instanceof Token;
|
||||||
if (isToken)
|
if (isToken) return getSnapPointForToken(x, y, entity);
|
||||||
return getSnapPointForToken(x, y, entity);
|
else return getSnapPointForMeasuredTemplate(x, y);
|
||||||
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) return false;
|
||||||
return false;
|
|
||||||
const area = getAreaFromPositionAndShape(position, shape);
|
const area = getAreaFromPositionAndShape(position, shape);
|
||||||
for (const space of area) {
|
for (const space of area) {
|
||||||
const [x, y] = getPixelsFromGridPosition(space.x, space.y);
|
const [x, y] = getPixelsFromGridPosition(space.x, space.y);
|
||||||
canvas.grid.grid.highlightGridPosition(layer, {x, y, color, alpha: 0.25 * alpha});
|
canvas.grid.highlightGridPosition(layer, {x, y, color, alpha: 0.25 * alpha});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,75 +155,92 @@ export function getAreaFromPositionAndShape(position, shape) {
|
|||||||
return shape.map(space => {
|
return shape.map(space => {
|
||||||
let x = position.x + space.x;
|
let x = position.x + space.x;
|
||||||
let y = position.y + space.y;
|
let y = position.y + space.y;
|
||||||
if (canvas.grid.isHex) {
|
if (isCanvasHex()) {
|
||||||
let shiftedRow;
|
let shiftedRow;
|
||||||
if (canvas.grid.grid.options.even)
|
if (canvas.grid?.even) shiftedRow = 1;
|
||||||
shiftedRow = 1
|
else shiftedRow = 0;
|
||||||
else
|
if (canvas.grid.grid.columnar) {
|
||||||
shiftedRow = 0
|
|
||||||
if (canvas.grid.grid.options.columns) {
|
|
||||||
if (space.x % 2 !== 0 && position.x % 2 !== shiftedRow) {
|
if (space.x % 2 !== 0 && position.x % 2 !== shiftedRow) {
|
||||||
y += 1;
|
y += 1;
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
if (space.y % 2 !== 0 && position.y % 2 !== shiftedRow) {
|
if (space.y % 2 !== 0 && position.y % 2 !== shiftedRow) {
|
||||||
x += 1;
|
x += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {x, y}
|
return {x, y};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A copy of this function lives in the routinglib module
|
||||||
export function getTokenShape(token) {
|
export function getTokenShape(token) {
|
||||||
if (token.scene.data.gridType === CONST.GRID_TYPES.GRIDLESS) {
|
let scene = canvas.scene;
|
||||||
return [{x: 0, y: 0}]
|
if (scene.grid.type === CONST.GRID_TYPES.GRIDLESS) {
|
||||||
}
|
return [{x: 0, y: 0}];
|
||||||
else if (token.scene.data.gridType === CONST.GRID_TYPES.SQUARE) {
|
} else if (scene.grid.type === CONST.GRID_TYPES.SQUARE) {
|
||||||
const topOffset = -Math.floor(token.data.height / 2)
|
const topOffset = -Math.floor(token.document.height / 2);
|
||||||
const leftOffset = -Math.floor(token.data.width / 2)
|
const leftOffset = -Math.floor(token.document.width / 2);
|
||||||
const shape = []
|
const shape = [];
|
||||||
for (let y = 0;y < token.data.height;y++) {
|
for (let y = 0; y < token.document.height; y++) {
|
||||||
for (let x = 0;x < token.data.width;x++) {
|
for (let x = 0; x < token.document.width; x++) {
|
||||||
shape.push({x: x + leftOffset, y: y + topOffset})
|
shape.push({x: x + leftOffset, y: y + topOffset});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return shape
|
return shape;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
// Hex grids
|
// Hex grids
|
||||||
if (game.modules.get("hex-size-support")?.active && CONFIG.hexSizeSupport.getAltSnappingFlag(token)) {
|
const size = getHexTokenSize(token);
|
||||||
const borderSize = token.data.flags["hex-size-support"].borderSize;
|
let shape = [{x: 0, y: 0}];
|
||||||
let shape = [{x: 0, y: 0}];
|
if (size >= 2)
|
||||||
if (borderSize >= 2)
|
shape = shape.concat([
|
||||||
shape = shape.concat([{x: 0, y: -1}, {x: -1, y: -1}]);
|
{x: 0, y: -1},
|
||||||
if (borderSize >= 3)
|
{x: -1, y: -1},
|
||||||
shape = shape.concat([{x: 0, y: 1}, {x: -1, y: 1}, {x: -1, y: 0}, {x: 1, y: 0}]);
|
]);
|
||||||
if (borderSize >= 4)
|
if (size >= 3)
|
||||||
shape = shape.concat([{x: -2, y: -1}, {x: 1, y: -1}, {x: -1, y: -2}, {x: 0, y: -2}, {x: 1, y: -2}])
|
shape = shape.concat([
|
||||||
|
{x: 0, y: 1},
|
||||||
|
{x: -1, y: 1},
|
||||||
|
{x: -1, y: 0},
|
||||||
|
{x: 1, y: 0},
|
||||||
|
]);
|
||||||
|
if (size >= 4)
|
||||||
|
shape = shape.concat([
|
||||||
|
{x: -2, y: -1},
|
||||||
|
{x: 1, y: -1},
|
||||||
|
{x: -1, y: -2},
|
||||||
|
{x: 0, y: -2},
|
||||||
|
{x: 1, y: -2},
|
||||||
|
]);
|
||||||
|
if (size >= 5)
|
||||||
|
shape = shape.concat([
|
||||||
|
{x: -2, y: 0},
|
||||||
|
{x: 1, y: 1},
|
||||||
|
{x: -1, y: 2},
|
||||||
|
{x: 0, y: 2},
|
||||||
|
{x: 1, y: 2},
|
||||||
|
{x: -2, y: 1},
|
||||||
|
{x: 2, y: 0},
|
||||||
|
]);
|
||||||
|
|
||||||
if (Boolean(CONFIG.hexSizeSupport.getAltOrientationFlag(token)) !== canvas.grid.grid.options.columns)
|
if (getAltOrientationFlagForToken(token, size)) {
|
||||||
shape.forEach(space => space.y *= -1);
|
shape.forEach(space => (space.y *= -1));
|
||||||
if (canvas.grid.grid.options.columns)
|
|
||||||
shape = shape.map(space => {return {x: space.y, y: space.x}});
|
|
||||||
return shape;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return [{x: 0, y: 0}];
|
|
||||||
}
|
}
|
||||||
|
if (canvas.grid.grid.columnar)
|
||||||
|
shape = shape.map(space => {
|
||||||
|
return {x: space.y, y: space.x};
|
||||||
|
});
|
||||||
|
return shape;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTokenSize(token) {
|
export function getTokenSize(token) {
|
||||||
let w, h;
|
let w, h;
|
||||||
const hexSizeSupportBorderSize = token.data.flags["hex-size-support"]?.borderSize;
|
if (isCanvasHex()) {
|
||||||
if (hexSizeSupportBorderSize > 0) {
|
w = h = getHexTokenSize(token);
|
||||||
w = h = hexSizeSupportBorderSize
|
} else {
|
||||||
}
|
w = token.document.width;
|
||||||
else {
|
h = token.document.height;
|
||||||
w = token.data.width
|
|
||||||
h = token.data.height
|
|
||||||
}
|
}
|
||||||
return {w, h};
|
return {w, h};
|
||||||
}
|
}
|
||||||
@@ -191,41 +249,35 @@ export function getTokenSize(token) {
|
|||||||
// This function applies an offset to to the waypoints that will move the ruler from the edge to the center of the cell
|
// This function applies an offset to to the waypoints that will move the ruler from the edge to the center of the cell
|
||||||
export function applyTokenSizeOffset(waypoints, token) {
|
export function applyTokenSizeOffset(waypoints, token) {
|
||||||
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
|
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
|
||||||
return waypoints
|
return waypoints;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tokenSize = getTokenSize(token);
|
const tokenSize = getTokenSize(token);
|
||||||
const waypointOffset = {x: 0, y: 0};
|
const waypointOffset = {x: 0, y: 0};
|
||||||
if (canvas.grid.isHex) {
|
if (isCanvasHex()) {
|
||||||
if (game.modules.get("hex-size-support")?.active) {
|
const isAltOrientation = getAltOrientationFlagForToken(token, getHexTokenSize(token));
|
||||||
const isAltOrientation = CONFIG.hexSizeSupport.getAltOrientationFlag(token);
|
if (canvas.grid.grid.columnar) {
|
||||||
if (canvas.grid.grid.options.columns) {
|
if (tokenSize.w % 2 === 0) {
|
||||||
if (tokenSize.w % 2 === 0) {
|
waypointOffset.x = canvas.grid.sizeX / 2;
|
||||||
waypointOffset.x = canvas.grid.w / 2;
|
if (isAltOrientation) waypointOffset.x *= -1;
|
||||||
if (!isAltOrientation)
|
|
||||||
waypointOffset.x *= -1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
} else {
|
||||||
if (tokenSize.h % 2 === 0) {
|
if (tokenSize.h % 2 === 0) {
|
||||||
waypointOffset.y = canvas.grid.h / 2;
|
waypointOffset.y = canvas.grid.sizeY / 2;
|
||||||
if (isAltOrientation)
|
if (isAltOrientation) waypointOffset.y *= -1;
|
||||||
waypointOffset.y *= -1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If hex size support isn't active leave the waypoints like they are
|
// If hex size support isn't active leave the waypoints like they are
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
if (tokenSize.w % 2 === 0) {
|
if (tokenSize.w % 2 === 0) {
|
||||||
waypointOffset.x = canvas.grid.w / 2;
|
waypointOffset.x = canvas.grid.sizeX / 2;
|
||||||
}
|
}
|
||||||
if (tokenSize.h % 2 === 0) {
|
if (tokenSize.h % 2 === 0) {
|
||||||
waypointOffset.y = canvas.grid.h / 2;
|
waypointOffset.y = canvas.grid.sizeY / 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
export function setSnapParameterOnOptions(sourceObject, options) {
|
||||||
@@ -234,8 +286,7 @@ export function setSnapParameterOnOptions(sourceObject, options) {
|
|||||||
options.snapOverrideActive = true;
|
options.snapOverrideActive = true;
|
||||||
options.snap = sourceObject.snapOverride.snap;
|
options.snap = sourceObject.snapOverride.snap;
|
||||||
sourceObject.snapOverride = undefined; // remove it to prevent any lingering data issues
|
sourceObject.snapOverride = undefined; // remove it to prevent any lingering data issues
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
options.snap = !disableSnap;
|
options.snap = !disableSnap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -244,9 +295,35 @@ export function isClose(a, b, delta) {
|
|||||||
return Math.abs(a - b) <= delta;
|
return Math.abs(a - b) <= delta;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getPointer() {
|
||||||
|
return canvas.app.renderer.events.pointer;
|
||||||
|
}
|
||||||
|
|
||||||
export function getMeasurePosition() {
|
export function getMeasurePosition() {
|
||||||
const mousePosition = canvas.app.renderer.plugins.interaction.mouse.getLocalPosition(canvas.tokens);
|
const mousePosition = getPointer().getLocalPosition(canvas.tokens);
|
||||||
const rulerOffset = canvas.controls.ruler.rulerOffset;
|
const measurePosition = {x: mousePosition.x, y: mousePosition.y};
|
||||||
const measurePosition = {x: mousePosition.x + rulerOffset.x, y: mousePosition.y + rulerOffset.y};
|
|
||||||
return measurePosition;
|
return measurePosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isGM function for use during loading when game.user isn't available yet
|
||||||
|
export function early_isGM() {
|
||||||
|
const level = game.data.users.find(u => u._id == game.data.userId).role;
|
||||||
|
const gmLevel = CONST.USER_ROLES.ASSISTANT;
|
||||||
|
return level >= gmLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isModuleActive(moduleName) {
|
||||||
|
return game.modules.get(moduleName)?.active;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isPathfindingEnabled() {
|
||||||
|
if (!window.routinglib) return false;
|
||||||
|
if (this.user !== game.user) return false;
|
||||||
|
if (!game.user.isGM && !game.settings.get(settingsKey, "allowPathfinding")) return false;
|
||||||
|
if (moveWithoutAnimation) return false;
|
||||||
|
return game.settings.get(settingsKey, "autoPathfinding") != togglePathfinding;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isCanvasHex() {
|
||||||
|
return canvas.grid.isHexagonal;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user