Compare commits

...

312 Commits

Author SHA1 Message Date
Manuel Vögele 52b64c3016 Release v1.13.6 2023-06-12 22:22:54 +02:00
Manuel Vögele 48d0d17628 Use canvas.scene.grid.type instead of cnavas.scene.gridType (which is undefined since v10) (fixes #272) 2023-06-12 22:21:19 +02:00
Manuel Vögele 8101381cc4 Remove redundant & broken code path in _computeDistance (fixes #280) 2023-06-12 21:40:04 +02:00
Manuel Vögele 844df150a6 Release v1.13.5 2023-06-12 19:52:33 +02:00
Manuel Vögele 47715e95f6 Replace newly private functions with public alternatives 2023-06-12 19:50:55 +02:00
Manuel Vögele 1dccbcb081 Reformat with prettier 2023-06-12 19:36:20 +02:00
Manuel Vögele 4c3d7ab42a Add .prettierignore 2023-06-12 19:26:44 +02:00
Manuel Vögele 61fc795f7b Correct a typo in a variable name (fixes #255) 2023-06-11 13:46:09 +02:00
Foundry Hub f10fa049b9 Translated using Weblate (Portuguese (Brazil)) (#268)
Currently translated at 100.0% (59 of 59 strings)

Translation: Drag Ruler/main
Translate-URL: https://weblate.foundryvtt-hub.com/projects/drag-ruler/main/pt_BR/

Co-authored-by: eunaumtenhoid <eunaumtenhoid@outlook.com>
2023-06-11 13:20:55 +02:00
Manuel Vögele c844318836 Update module.json to reflect v11 compatiblility 2023-06-11 13:20:11 +02:00
pkonshik 9af692566c Apply changes for v11 compatibility (#277) 2023-06-11 13:17:36 +02:00
Txus d08416777b Wfrp4e default values (#276) 2023-06-10 10:27:42 +02:00
Manuel Vögele 56f506bad2 Release v1.13.4 2023-03-14 16:27:36 +01:00
Manuel Vögele e1d54ed55d Use "cn" as language code for Simplified Chinese, as is done by foundry 2023-03-14 16:24:16 +01:00
eunaumtenhoid 504c242e86 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (59 of 59 strings)

Translation: Drag Ruler/main
Translate-URL: https://weblate.foundryvtt-hub.com/projects/drag-ruler/main/pt_BR/
2023-03-14 16:22:01 +01:00
eunaumtenhoid a48c199863 Added translation using Weblate (Portuguese (Brazil)) 2023-03-14 16:22:01 +01:00
Manuel Vögele b7ace776d5 Release v1.13.3 2023-02-21 10:02:05 +01:00
bnp800 cf98287afd Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (59 of 59 strings)

Translation: Drag Ruler/main
Translate-URL: https://weblate.foundryvtt-hub.com/projects/drag-ruler/main/zh_Hans/
2023-02-21 09:58:43 +01:00
bnp800 7a3b547741 Added translation using Weblate (Chinese (Simplified)) 2023-02-21 09:58:43 +01:00
Raul Castaño 5329a4056c Translated using Weblate (Spanish)
Currently translated at 100.0% (59 of 59 strings)

Translation: Drag Ruler/main
Translate-URL: https://weblate.foundryvtt-hub.com/projects/drag-ruler/main/es/
2022-11-18 14:17:56 +01:00
Manuel Vögele a65ff4ddb4 Release v1.13.2 2022-10-15 18:40:48 +02:00
Manuel Vögele c2aa47985b Take into account that the hex size support module may not be installed (fixes #241) 2022-10-15 18:39:44 +02:00
Manuel Vögele 4b5fea0763 Release v1.13.1 2022-10-15 17:54:15 +02:00
Manuel Vögele 26c3475db6 Repair regressions of a9c15d77 where square token highlighting would break 2022-10-15 16:48:07 +02:00
Manuel Vögele 2cf97e2804 Add comments about re-used code in routinglib 2022-10-15 16:37:47 +02:00
Manuel Vögele 7dcb8b5c71 Remove hex-size-support from readme, as it's no longer needed for hex support 2022-10-15 16:01:32 +02:00
Manuel Vögele a9c15d779b v10 compat: Make Drag Ruler play nice with Foundry v10's hex grid and the new hex-size-support module 2022-10-15 16:00:38 +02:00
Manuel Vögele 01b9941c16 Export getRangesFromSpeedProvider 2022-10-15 10:33:32 +02:00
vincent 7bd5ad3dad Translated using Weblate (French)
Currently translated at 100.0% (59 of 59 strings)

Translation: Drag Ruler/main
Translate-URL: https://weblate.foundryvtt-hub.com/projects/drag-ruler/main/fr/
2022-10-14 23:56:03 +02:00
Manuel Vögele 6b4965f507 Translated using Weblate (German)
Currently translated at 100.0% (59 of 59 strings)

Translation: Drag Ruler/main
Translate-URL: https://weblate.foundryvtt-hub.com/projects/drag-ruler/main/de/
2022-10-14 23:56:03 +02:00
Manuel Vögele e57829936e Translated using Weblate (English)
Currently translated at 100.0% (59 of 59 strings)

Translation: Drag Ruler/main
Translate-URL: https://weblate.foundryvtt-hub.com/projects/drag-ruler/main/en/
2022-10-14 23:56:03 +02:00
Manuel Vögele 1713380900 Make pathfinding demonstration loop 2022-10-14 22:21:24 +02:00
Manuel Vögele 0896265f87 Transmit the cost options to ETL properly (fixes #238) 2022-10-14 22:21:20 +02:00
Manuel Vögele 8a91c8843d v10 compat: Get rid of several deprecation warnings (fixes #237) 2022-10-14 11:17:27 +02:00
rectulo 4787ae5e12 Update fr.json (#234) 2022-10-14 09:19:25 +02:00
Manuel Vögele 6027735f4c Release v1.13.0 2022-10-13 10:33:53 +02:00
Manuel Vögele 84bce39043 Add documentation for the pathfinding feature 2022-10-13 10:28:25 +02:00
Manuel Vögele 46c504341c Remove getCostForStep SpedProvider function in favor or the Enhanced Terrain Layer API 2022-10-13 08:18:59 +02:00
Manuel Vögele ff9b187d90 v10 compat: Use the new way of checking a wall collision 2022-10-13 08:07:08 +02:00
n0q bd76c965a2 Update README.md (#232) 2022-10-13 07:45:17 +02:00
Rapunzel77 ee9ac14346 adding TDE5 support to the README.md (#231)
Co-authored-by: Feiertag, Nico <nfeiertag@gmx.net>
2022-10-11 22:38:45 +02:00
Manuel Vögele 7a00d6fcc2 Stop pathfinding if the measurement is canceled mid-pathfinding 2022-10-04 21:32:26 +02:00
Manuel Vögele 4957188630 If a previous pathfinding job is done, reset it to undefined 2022-10-04 10:25:55 +02:00
Manuel Vögele 944ff96a26 Add comment about identical an identical function in another module 2022-10-03 10:51:09 +02:00
Manuel Vögele 58565b7011 v10 compat: Update to new token animation name 2022-10-01 07:56:20 +02:00
Manuel Vögele 25fc6258e7 Cancel running pathfinding jobs before scheduling a new one 2022-09-30 22:47:51 +02:00
Manuel Vögele ac00aac85b Gracefully handle situations where no path exists 2022-09-30 22:36:48 +02:00
Manuel Vögele 8d5a6716e1 v10 compat: Migrate manifest 2022-09-30 22:07:55 +02:00
Manuel Vögele c43b0bfd64 Rename js/ to src/ since the rust component is now gone 2022-09-30 22:04:41 +02:00
Manuel Vögele c21e4c91d6 Move pathfinding into routinglib; call routinglib for pathfinding jobs 2022-09-30 21:52:48 +02:00
Manuel Vögele 4b30d52da1 Fix several issues with Terrain Ruler based measurements 2022-09-30 19:46:25 +02:00
Manuel Vögele 08c39b5683 Fix references to the Ruler class 2022-09-30 14:08:38 +02:00
Manuel Vögele 2e2353e1d1 v10 compat: migrate to new data paths 2022-09-30 14:03:59 +02:00
Manuel Vögele cf74a11f18 v10 compat: Split measure function into several subfunctions as suggested by Foundry v10 2022-09-30 14:03:47 +02:00
Manuel Vögele a1c10e088d Add documentation to keybinding globals 2022-09-30 14:00:34 +02:00
Manuel Vögele 626829e989 v10 compat: migrate to new data paths 2022-09-28 08:07:02 +02:00
Manuel Vögele b87e91b63c v10 compat: Reference CONFIG.Canvas.rulerClass instead of Ruler 2022-09-28 08:05:14 +02:00
Manuel Vögele ed10144cc6 Disable background caching in preparation for routinglib 2022-09-28 07:46:34 +02:00
Manuel Vögele 5e82049686 Reformat with prettier 2022-09-28 07:35:16 +02:00
gsterling 71424cd284 add splittermond with speed and dash multiplier (#223) 2022-09-28 07:31:20 +02:00
Manuel Vögele 445c03d29a Add difficult terrain support for pathfinding on gridded scenes 2022-05-10 08:39:19 +02:00
Manuel Vögele 5904efbdd5 Remove redundant first waypoint from path generated by pathfinder
This fixes a bug where gridless snapping would snap slightly below the target range
2022-05-07 08:12:30 +02:00
Manuel Vögele d325f8acee Don't attempt to start background caching on gridless scenes, as it's currently unsupported (fixes #196) 2022-05-06 20:22:42 +02:00
Jonathan Calvert 1577435a33 Fix issue caused by client setting moving from Levels to Wall Height (#195) 2022-05-06 20:16:52 +02:00
Cole Schultz 1b8b2328c6 Add the pathfinding radius setting (#194) 2022-04-29 10:41:05 +02:00
Manuel Vögele 0dba39b145 Merge branch 'master' into develop 2022-04-12 00:22:20 +02:00
Manuel Vögele c1b0e9d7fb Release v1.12.8 2022-04-12 00:13:34 +02:00
Manuel Vögele 9d227efcd1 Enusre that measurements to determine the highlight color are always done center to center (fixes #189) 2022-04-12 00:11:30 +02:00
Manuel Vögele a3d942c287 Apply wall height's bounds to the rays before doing colission checks (fixes #190) 2022-04-12 00:11:30 +02:00
Manuel Vögele bdbd4fde18 Merge branch 'master' into develop 2022-04-01 18:07:25 +02:00
Manuel Vögele 3afb1f30b7 Release v1.12.7 2022-04-01 18:06:46 +02:00
Manuel Vögele 654f8e3111 Fix a typo that caused measurement template snapping to be broken (fixes #185) 2022-04-01 18:04:25 +02:00
Jonathan Calvert 2979e86201 Improve Pathfinding: Add background caching (#175) 2022-03-09 18:40:43 +01:00
Manuel Vögele 8f81fd707e Merge branch 'master' into develop 2022-03-08 23:13:36 +01:00
Manuel Vögele 5f6e348bf0 Release v1.12.6 2022-03-08 23:12:17 +01:00
Manuel Vögele 82f2a885fb getSnappedPosition cannot be used on gridless to calculate the snap point for measured templates 2022-03-08 23:09:06 +01:00
Manuel Vögele d536ae8dfb Merge branch 'master' into develop 2022-03-08 22:07:43 +01:00
Manuel Vögele 5add0be393 Release v1.12.5 2022-03-08 22:06:14 +01:00
Manuel Vögele f77990420a Replace getSnapPointForMeasuredTemplate with foundry native function (fixes #181) 2022-03-08 20:17:25 +01:00
Manuel Vögele 44efabc656 Merge branch 'master' into develop 2022-03-08 12:50:16 +01:00
Manuel Vögele e84632c0d7 Release v1.12.4 2022-03-08 12:49:56 +01:00
Manuel Vögele 30bd3d33e0 If no dragged entity is being transmitted, unset it (fixes #179) 2022-03-08 12:48:17 +01:00
Manuel Vögele 6604e322bd Show diagnoal count in debug print 2022-03-08 11:49:27 +01:00
Manuel Vögele b68f3bbe29 Elevation also needs to be respected if only "wall-height" is enabled 2022-03-08 11:45:58 +01:00
Manuel Vögele 793e87bc2f Merge branch 'master' into develop 2022-03-08 09:56:03 +01:00
Manuel Vögele df4c515fcd Release v1.12.3 2022-03-07 16:53:48 +01:00
Manuel Vögele 02dfd8db0d Only reclaculate the ruler if it's a Drag Ruler (fixes #161) 2022-03-07 14:39:31 +01:00
Manuel Vögele 65911c11f0 Only reclaculate the ruler if it's a Drag Ruler (fixes #161) 2022-03-07 14:37:15 +01:00
Manuel Vögele ddf89f9499 Add support for the wall height module in the gridless pathfinder 2022-03-07 12:40:16 +01:00
Jonathan Calvert 0dfdb23bfb Levels Compatibility: Clear pathfinding cache after changing elevation (#173) 2022-03-07 12:39:16 +01:00
Jonathan Calvert 3f2e9e1a3e Fix one-way wall collision detection (fixes #167) (#172) 2022-03-07 12:38:30 +01:00
Jonathan Calvert 846e39d946 Pathfinding: Keep Multiple Caches (#177) 2022-03-05 21:36:56 +01:00
Manuel Vögele 26748ee234 Revert ebd0ee6ccf, which introduced a bug instead of fixing one 2022-03-04 08:40:04 +01:00
Manuel Vögele ebd0ee6ccf Fix the order of colission checks during pathfinding waypoint culling to make one-way-walls work properly 2022-03-02 22:33:42 +01:00
Jonathan Calvert 1d477094b8 Fix bug with diagonal movement (#174) 2022-03-01 14:28:01 +01:00
Jonathan Calvert bbfce2bb77 Pathfinding Improvements: Improve 5-10-5 Diagonal Handling (#170) 2022-02-28 23:08:56 +01:00
Manuel Vögele 54cee52eb2 Add support for the wall height module in the gridless pathfinder 2022-02-28 22:48:15 +01:00
Jonathan Calvert a967a03197 Levels Compatibility: Clear pathfinding cache after changing elevation (#173) 2022-02-28 21:17:54 +01:00
Jonathan Calvert 8d243ce919 Fix one-way wall collision detection (fixes #167) (#172) 2022-02-28 19:13:12 +01:00
Jonathan Calvert b227d5073b Pathfinding Improvements: Replace nextNodes array with PriorityQueueSet (#169) 2022-02-28 18:23:46 +01:00
Jonathan Calvert 576db2dc5a Pathfinding Improvements: Bound Algorithm to Canvas (#168) 2022-02-28 09:52:12 +01:00
EndlesNights b031acaa6e dnd 4e system support (#162) 2022-02-28 09:42:10 +01:00
Manuel Vögele ee956abd78 Update .gitignore 2022-02-17 11:15:40 +01:00
Manuel Vögele 1334703877 Release v1.12.2 2022-02-17 00:58:33 +01:00
Manuel Vögele d6cccf4466 Ensure pathfinding on gridless is possible by always generating a pathfinding point when a virtual wall is being inserted 2022-02-17 00:53:51 +01:00
Manuel Vögele 074d2f5052 Disable pathfinding when the hotkey for moving tokens without pathfinding is enabled (fixes #158) 2022-02-17 00:47:13 +01:00
Manuel Vögele e5b8f896f8 Release v1.12.1 2022-02-16 12:48:59 +01:00
Manuel Vögele 3de350d3e9 Remove wasm snippets from release builds 2022-02-16 12:47:23 +01:00
Manuel Vögele 8f97081236 Release v1.12.0 2022-02-16 11:57:33 +01:00
Manuel Vögele 18253dc42b Delay reload to make sure all settings are being applied 2022-02-16 11:46:46 +01:00
Manuel Vögele fb702cd850 Add support for gridless pathfinding 2022-02-15 17:56:08 +01:00
Manuel Vögele ad3fdf4d18 Release v1.11.5 2022-02-12 18:29:52 +01:00
Manuel Vögele 58946096a6 Remove debugging output 2022-02-12 18:25:50 +01:00
Manuel Vögele 22112ddfd5 Release v1.11.4 2022-02-09 10:58:02 +01:00
Manuel Vögele f59b0f9d2d When changing the measurement mode via a keybinding send the updated ruler state to other players immediately (fixes #152) 2022-02-09 10:51:19 +01:00
Manuel Vögele 443cfd4317 Don't apply snapping to rulers received from other players (fixes #150) 2022-02-09 10:41:01 +01:00
Manuel Vögele fb3665d758 If no measurement has been performed yet before dropping the token, schedule a measurement on drop (fixes #153) 2022-02-09 10:30:17 +01:00
Manuel Vögele 96cb690431 Use foundries new, improved way of wainting for animations when moving tokens (fixes #156) 2022-02-09 08:57:30 +01:00
José Lozano fb7202d5f0 Spanish updated (#155) 2022-02-07 10:07:07 +01:00
Manuel Vögele fa581ddfc3 Release v1.11.3 2022-02-03 18:23:35 +01:00
Manuel Vögele 56e15be4ca Don't perform pathfinding on other player's rulers 2022-02-03 17:59:35 +01:00
Manuel Vögele 1509307313 Show the setting for automatic pathfinding to players if they are allowed to use pathfinding 2022-02-03 17:59:09 +01:00
Manuel Vögele 68885385ea Formatting fix 2022-02-03 17:42:35 +01:00
Manuel Vögele 6748617f6a Add "DnD5e Drag Ruler Integration" and "Shadow of the Demon Lord" to list of Drag Ruler integraion 2022-02-01 20:57:44 +01:00
Manuel Vögele 9d5dccb504 Release v1.11.2 2022-02-01 11:35:46 +01:00
Manuel Vögele a252da620a Always allow the GM to use pathfinding 2022-02-01 11:33:33 +01:00
Johannes Loher 41c8979925 Add support for Dungeonslayers 4 (#146) 2022-02-01 10:32:54 +01:00
Manuel Vögele 39f9204fa7 Destroy labels that are no longer used 2022-02-01 02:58:55 +01:00
Manuel Vögele 10633e4e2a Add .gitignore 2022-01-30 14:28:02 +01:00
Manuel Vögele f3e1492edd Release v1.11.1 2022-01-30 10:55:10 +01:00
Manuel Vögele 7ac1c828b6 Perform pathfinding on grid-corner to grid-corner basis for tokens with size divisible by two (fixes #144) 2022-01-30 10:51:58 +01:00
Manuel Vögele 1972498b05 Avoid unnecessary diagonal calculation on hex grids 2022-01-30 10:16:19 +01:00
Manuel Vögele 4a5b9bbb11 Release v1.11.0 2022-01-30 00:22:43 +01:00
Manuel Vögele d467fe5bcf Add pathfinidng support for square and hex grids 2022-01-30 00:06:48 +01:00
Manuel Vögele 1f770f7597 Release v1.10.3 2022-01-27 14:00:29 +01:00
Manuel Vögele fa62e1d9c4 Resetting the token drag vision moved from token drop to token cancel in foundry 9.245 2022-01-27 12:47:39 +01:00
Manuel Vögele f18ce33779 Release v1.10.2 2022-01-17 21:24:15 +01:00
Manuel Vögele 34cead5cba Clean up vision of clones when dropping a token (fixes #140) 2022-01-17 13:42:48 +01:00
Manuel Vögele eeb6db76a6 Measure the full path to determine highlight color (fixes #142) 2022-01-17 12:50:05 +01:00
Manuel Vögele 9c44994fd2 Add "A5e Drag Ruler Integration" to the list of game system integrations 2022-01-14 12:58:38 +01:00
Manuel Vögele 23c1a903a0 Release v1.10.1 2022-01-07 15:12:24 +01:00
Manuel Vögele 1b3f563c4e Keybindings no longer break if no scene is active (fixes #113) 2022-01-07 15:10:17 +01:00
José Lozano c4595f408f Spanish updated (#141) 2022-01-07 15:07:54 +01:00
Manuel Vögele fae1bb8fe0 Release v1.10.0 2022-01-03 09:15:10 +01:00
Manuel Vögele f203bea673 Add Symbaroum drag ruler integration to the list of game system integrations 2022-01-03 08:54:05 +01:00
Manuel Vögele 84c18a6221 Organzie imports 2022-01-03 00:44:16 +01:00
Manuel Vögele 0573adf0fe Sort translations by language code 2022-01-03 00:42:02 +01:00
Manuel Vögele 76c71329d6 Add french translation (thanks Elfenduli) (provided by & closes #129) 2022-01-03 00:42:02 +01:00
Manuel Vögele f918e148fe Add support for difficult terrain measurements on euclidean gridded scenes (resolves #134) 2022-01-03 00:23:35 +01:00
Manuel Vögele 52d77ba0a4 Code changes for v9 compatibility (moves keybindings into the new foundry API) 2022-01-02 18:32:30 +01:00
Manuel Vögele b06169f149 Remove migration from unnamed version 2022-01-02 18:32:00 +01:00
Manuel Vögele c914eb23ae Port to libwrapper 2022-01-02 18:32:00 +01:00
Manuel Vögele c8a7c73ace Immediately override the Ruler after extension to increase interoperability with other modules 2022-01-02 18:32:00 +01:00
Manuel Vögele 76cff06b66 Update formatting of japanes localization 2022-01-02 18:31:29 +01:00
Brother Sharp ad005c1bb1 update ja.json (file provided by touge) 2022-01-02 09:03:28 +01:00
Manuel Vögele 40b882ec93 Release v1.9.1 2021-12-28 17:20:08 +01:00
Manuel Vögele 1c8ce20a45 Move fallback to default color to a location where it's accessible (fixes #137) 2021-12-28 16:17:52 +01:00
José E. Lozano a16f97f9e7 Spanish updated (#133) 2021-12-01 09:48:54 +01:00
Manuel Vögele a3e0ea9832 Typo 2021-11-24 16:30:13 +01:00
Manuel Vögele aa2b8b3928 Release v1.9.0 2021-11-24 09:53:02 +01:00
Manuel Vögele a164811bb6 Add hint that shift can disable gridless snapping to the setting 2021-11-24 09:52:31 +01:00
Manuel Vögele 752b8375ab Non-square tokens are now fully supported on square grids (fixes #116) 2021-11-24 09:14:27 +01:00
Manuel Vögele 32b38d8efe Make tokens snap to their speed ranges on gridless scenes (resolves #71) 2021-11-23 11:10:21 +01:00
Manuel Vögele c3a62e3646 Only switch to the next color once the distance is 0.01 above the allowed distance
The goal of this is to reduce color flickering noise due to floating number errors
2021-11-21 22:55:02 +01:00
Manuel Vögele 39f7bab4b6 Pass enableTerrainRuler option to all measureDistances calls
This fixes a regression introduced with e1265ad6fb
2021-11-21 20:58:18 +01:00
Manuel Vögele a3be4ceb03 Remove support for TerrainLayer (Enhanced Terrain Layer stays supported) 2021-11-21 16:52:55 +01:00
Manuel Vögele b275e777db Ignore space key presses if there is no active canvas (fixes #123) 2021-11-21 15:13:45 +01:00
Manuel Vögele 035cfb8969 Add API function to receive the color for a given distance and token 2021-11-17 12:16:36 +01:00
Manuel Vögele 90a25f467b Add "TheWitcherTRPG" to the game system integration list 2021-11-17 11:48:18 +01:00
Manuel Vögele ab85b98a5c Add several new game system integrations to the list 2021-10-17 20:38:01 +02:00
Manuel Vögele 1669edf757 Release v1.8.2 2021-09-17 18:43:05 +02:00
Manuel Vögele d1b5f7c39b Verified to work with 0.8.9 2021-09-17 15:26:58 +02:00
Argonius-Angelus 24f1e78092 Updated getFlag behaviour (#114)
Updated getFlag behaviour to use Document#getFlag rather than the depreciated PlaceableObject#getFlag. Due to the min core version being marked as 0.8.5, this won't cause a 0.7 compatability issue.
2021-09-15 07:48:00 +02:00
Joe 854903a314 Add Lancer system integration info to README (#111)
Adds a link to the lancer-speed-provider module to the systems with integrations section.
2021-09-08 17:21:00 +02:00
Joe 232eca74de Update lancer settings to system v1.0 (#110) 2021-09-06 09:42:59 +02:00
Manuel Vögele 9c7d4ead50 Release v1.8.1 2021-08-03 10:41:56 +02:00
Manuel Vögele a91c3c1b8c setSnapParameterOnOptions was still using the old parameters here (fixes #102) 2021-08-03 10:17:13 +02:00
José E. Lozano 99d057bffc Updated Spanish (#103) 2021-08-03 10:01:28 +02:00
Manuel Vögele 792d644803 Release v1.8.0 2021-08-02 16:03:17 +02:00
Manuel Vögele 63b13cbb34 Abort the drag when pressing ESC (resolves #73) 2021-08-02 15:51:12 +02:00
Manuel Vögele f1a153a2eb Only execute "toLowerCase" once 2021-08-02 15:47:10 +02:00
Manuel Vögele 59c12d017b When Ctrl+Z undoes a movement, remove that movement form the movement history as well (resolves #64) 2021-08-02 15:43:22 +02:00
Manuel Vögele 9552c25e6e Add a setting that disables Drag Ruler by default and only activates it once the button to add a waypoint is being pressed 2021-08-02 15:23:49 +02:00
Manuel Vögele 461e1d0818 Cancel scheudled measurements then the ruler is being cleared 2021-07-30 12:40:32 +02:00
Michael Clavell e1265ad6fb Add support for the Toggle Snap To Grid module (#97) 2021-07-30 10:16:27 +02:00
Manuel Vögele 93f0bf6369 Create LICENSE 2021-07-28 17:57:15 +02:00
Manuel Vögele 07d0c37fe9 Release v1.7.7 2021-07-08 22:02:57 +02:00
Manuel Vögele b8b97ac27a Update generic speed provider default for swade (resolves #93) 2021-07-08 22:01:29 +02:00
Manuel Vögele 0e9215e8bb Release v1.7.6 2021-07-08 21:58:01 +02:00
Manuel Vögele dc4355e0de Add spanish translation to readme 2021-07-08 21:57:23 +02:00
José E. Lozano cf7f84d9e4 Add spanish translation (#94) 2021-07-08 21:22:01 +02:00
Manuel Vögele fe8a3b7b39 Verified to work with 0.8.8 2021-07-08 17:37:59 +02:00
Manuel Vögele be9535acd3 Add korean translation to manifest and readme 2021-07-08 17:32:17 +02:00
José E. Lozano 530a16ca5d CoC support (#92) 2021-07-08 17:27:34 +02:00
drdwing 3b9fbac80d Korean translation (#88)
by KLO (discord : KLO#1490)
2021-07-08 10:39:50 +02:00
Manuel Vögele 0456fb0849 Release v1.7.5 2021-06-22 23:20:36 +02:00
Manuel Vögele 01d01f9887 Movement speeds can be floats (fixes #85) 2021-06-22 23:14:53 +02:00
Manuel Vögele 9042b79967 Release v1.7.4 2021-06-18 13:58:54 +02:00
Manuel Vögele 4159d20e18 Verified to work with Foundry 0.8.7 2021-06-18 13:39:10 +02:00
Manuel Vögele 3897577756 Allow the "Monk's Active Tile Triggers" module to interrupt a token movement early 2021-06-18 13:37:26 +02:00
irbian 5c29f401d6 Support D&D 3.5 (#76) 2021-05-27 01:09:12 +02:00
Manuel Vögele 1328d52f94 Send the info whether the dragged entity is a token or a template to the other players (fixes a snapping issue in other players clients) 2021-05-26 16:17:23 +02:00
Manuel Vögele eef05553c0 Release v1.7.3 2021-05-22 22:00:46 +02:00
Manuel Vögele 7ba89e4229 Update code for Foundry 0.8.5 2021-05-22 21:57:59 +02:00
Manuel Vögele d11d6385e2 Release v1.7.2 2021-05-21 14:43:13 +02:00
Manuel Vögele 4d2e4b7715 Calculate the correct snappig for measured templates when placing a waypoint 2021-05-21 14:41:33 +02:00
Manuel Vögele 3cbe41e2be If a measurement is being skipped because of the ruler's rate limiting, schedule the measurement for later to ensure the ruler sticks to the token 2021-05-21 14:38:54 +02:00
Manuel Vögele 732802c579 Fix typo in readme 2021-05-20 11:41:19 +02:00
Manuel Vögele 8bbb11ddaa Update the readme to document all the features included in 1.7.0 2021-05-19 23:20:12 +02:00
Manuel Vögele e6cee3477a Release v1.7.1 2021-05-18 22:28:44 +02:00
Manuel Vögele 7cee1e7af0 Rename a function call that was forgotten during the the renaming from Token to Entity (fixes #74) 2021-05-18 22:25:19 +02:00
Manuel Vögele a7811d6894 Release v1.7.0 2021-05-18 15:28:19 +02:00
Manuel Vögele e637f8d28f Fix typo in changelog 2021-05-18 15:17:04 +02:00
Manuel Vögele acd2e74064 Implement measurement template support (resolves #13) 2021-05-18 14:11:24 +02:00
Manuel Vögele fd81833583 Rename Ruler.draggedToken to Ruler.draggedEntity in preparation of measurement template suppport 2021-05-18 13:03:42 +02:00
Manuel Vögele a413d44795 Don't re-measure when the shift key is being pressed while a token is following the currently active ruler 2021-05-18 13:03:04 +02:00
Manuel Vögele e63ee1988d Deprecate the old (prior to 1.3.0) Drag Ruler API 2021-05-18 12:57:55 +02:00
Manuel Vögele 4aab6eec95 Improve the positioning of labels aroud the ruler graphic 2021-05-18 12:57:51 +02:00
Manuel Vögele 38b7df0248 Don't set Token._noAnimate. It has no effect and will be removed in future foundry versions
https://gitlab.com/foundrynet/foundryvtt/-/issues/5094
2021-05-18 07:23:56 +02:00
Manuel Vögele bbd30be80e Add Wasteland Ventures to the list of game systems with Drag Ruler integration 2021-05-16 18:47:59 +02:00
Manuel Vögele 78c96f9059 Merge branch 'master' into develop 2021-05-16 18:43:59 +02:00
Manuel Vögele d1367aa82e Release v1.6.5 2021-05-15 20:05:50 +02:00
Manuel Vögele 9f70e5bc27 If not token is selected, use the dragged token as selected token (fixes #70) 2021-05-15 20:04:22 +02:00
Manuel Vögele d2876671cf Add default values for Starfinder to the Generic Speed Provider 2021-05-12 16:20:05 +02:00
Manuel Vögele 19abc9eedc Add ironclaw2e to list of game systems with Drag Ruler integration 2021-05-11 19:20:04 +02:00
Manuel Vögele 286aaf7ada Properly store terrain ruler's state in the movement history 2021-05-11 19:01:51 +02:00
Manuel Vögele 85e167af45 Allow difficult terrain measurements on girdless maps 2021-05-11 18:21:39 +02:00
Manuel Vögele 5f0849f12d Mention ba0012e in changelog 2021-05-11 01:47:15 +02:00
CarnVanBeck ba0012e875 spelling correction (#68) 2021-05-11 01:45:28 +02:00
Manuel Vögele 334ccbc0f1 Release v1.6.4 2021-05-10 09:13:32 +02:00
Manuel Vögele 1242035744 Catch all errors thrown by a Speed Provider's getCostForStep function 2021-05-10 09:11:29 +02:00
Manuel Vögele 49542a68e5 Simplify foundry fixes 2021-05-06 09:00:50 +02:00
Chris Sharp 27a6235cc1 Mention WWII:OWB Operation WhiteBox system support in the readme 2021-05-05 23:40:40 +02:00
Manuel Vögele c26b39c984 Release v1.6.3 2021-05-05 16:01:52 +02:00
Manuel Vögele e8ab77a62e Update active rulers when the movement history changes 2021-05-05 16:01:13 +02:00
Manuel Vögele 7bed5abd0a Add default settings for shadowrun 5e 2021-05-03 19:39:22 +02:00
Manuel Vögele 4a96348659 Add "Tagmar RPG" and "Shadow of the Demon Lord" to the List of game systems with Drag Ruler integration 2021-05-03 16:00:34 +02:00
Manuel Vögele bed16de575 Release v1.6.2 2021-05-03 10:53:53 +02:00
Manuel Vögele 891bb1b4d8 undefined cannot be transmitted via json, so we use null to reset the movement history instead 2021-05-03 10:52:57 +02:00
Manuel Vögele 43f26088b5 Release v1.6.1 2021-04-30 10:52:00 +02:00
Manuel Vögele 39a0787c79 Add documentation about what needs to be awaited in SpeedProvider.onMovementHistoryUpdate 2021-04-30 10:26:37 +02:00
Manuel Vögele 3be898e49c First move the token, then store it's path. This ensures that getMovedDistance always returns a valid result. 2021-04-30 09:53:53 +02:00
Manuel Vögele 817662bf30 Send a proper token to onMovementHistoryUpdate 2021-04-30 09:51:05 +02:00
Manuel Vögele 56527ccf75 Add a hook that's called when the movement history is updated to the API 2021-04-30 00:10:45 +02:00
Manuel Vögele 6bf8083f7a Add a function for resetting the movement history to the public api 2021-04-30 00:00:15 +02:00
Manuel Vögele b5ea1f2284 Release v1.6.0 2021-04-29 14:04:14 +02:00
Manuel Vögele 6d9870bedb Add reference to the github issue in changelog 2021-04-29 13:24:33 +02:00
Manuel Vögele afbe5f9473 Don't animate tokens when they are dropped with the alt key being pressed (resolves #3) 2021-04-29 13:18:35 +02:00
Manuel Vögele bae0e43b7b Document 70b166d8 in changelog 2021-04-29 12:46:25 +02:00
Manuel Vögele 1acc012d65 Cache the output of SpeedProvider.getRanges during a drag to increase speed provider performance 2021-04-29 12:40:17 +02:00
Manuel Vögele 4671e6de51 Only allow users to update the movement history for tokens they own 2021-04-29 00:20:24 +02:00
Manuel Vögele f0ef109658 Track the movement of all tokens that are moved simultaneously in one batch (huge performance bump if many tokens are moved) 2021-04-29 00:03:05 +02:00
Manuel Vögele f0d1ef9d48 There might not actually be a dragged token when trying to end the measurement 2021-04-28 23:25:22 +02:00
Manuel Vögele d04ea9b0b7 Remove leftover lines from a refactor that didn't do anything 2021-04-28 12:11:58 +02:00
Manuel Vögele ba8ab9d473 After moving a token only end the measurement if no new token is being dragged 2021-04-27 21:17:28 +02:00
Manuel Vögele 2691720090 Update all moved tokens at once (provides a huge performance bump) 2021-04-27 20:28:44 +02:00
Manuel Vögele 70b166d844 Add a context menu entry that the gm can use to reset the movement history 2021-04-26 11:09:56 +02:00
Manuel Vögele 908600bfa3 Release v1.5.4 2021-04-21 10:08:50 +02:00
Manuel Vögele 6472c1d5bd Don't include the movement history in collission checks (fixes #61) 2021-04-21 09:05:50 +02:00
Manuel Vögele 9151b45874 Release v1.5.3 2021-04-20 17:03:13 +02:00
Manuel Vögele d732f7ca17 Add defaults for dcc system 2021-04-20 17:02:37 +02:00
Manuel Vögele 4be6730fd5 Use parseInt on the speed attribute to increase compatibility with game systems (resolves #60) 2021-04-20 17:00:28 +02:00
Manuel Vögele 1faeda6f47 Release v1.5.2 2021-04-19 11:23:26 +02:00
Manuel Vögele 667259d5d6 If a token has no actor, use the user's color for highlighting grid spaces (fixes #58) 2021-04-19 11:22:13 +02:00
Manuel Vögele 4b68b82590 Release v1.5.1 2021-04-15 17:19:32 +02:00
Manuel Vögele 447995977b Don't show the difficult terrain measurement hint when no terrain module is enabled 2021-04-15 17:17:38 +02:00
Manuel Vögele 9e5b344b0d Release v1.5.0 2021-04-14 22:51:05 +02:00
Manuel Vögele 43c5df42d4 If a terrain module is installed, suggest installing Terrain Ruler to the user 2021-04-14 22:49:18 +02:00
Manuel Vögele d0ee47d551 Fix typo that made regular rulers not work 2021-04-14 19:45:17 +02:00
Manuel Vögele 27ffb2d8cb Don't return any value in _socketUpdateCombatantDragRulerFlags to reduce the amount of network traffic 2021-04-14 17:32:40 +02:00
Manuel Vögele b734257515 Check if all required dependencies are present and show an error if this is not the case 2021-04-14 16:37:46 +02:00
Manuel Vögele ff5d46ebbe Add a setting to disable the movement history 2021-04-14 16:10:11 +02:00
Manuel Vögele 8f896d9b82 Fix a bug that caused Drag Ruler to measure euclidic distance when Terrain Ruler isn't active 2021-04-14 15:54:59 +02:00
Manuel Vögele f7683e8945 Update the movement tracking flags in the GM client, becauase the user isn't allowed to modify them 2021-04-14 15:54:59 +02:00
Manuel Vögele 9787a41fd1 If Terran Ruler is enabled, store the length of the traveled path so changes to difficult terrain aren't reflected in the tokens movement history 2021-04-12 18:45:57 +02:00
Manuel Vögele 5d93c61f0c Merge branch 'master' into develop 2021-04-11 14:31:44 +02:00
Manuel Vögele 00e8a9781e Release v1.4.6 2021-04-11 14:27:09 +02:00
Manuel Vögele d2f2307e4d Update the ruler before dropping the token to get a more precise target location if the token has been moved very quickly (fixes #51) 2021-04-11 14:24:23 +02:00
Manuel Vögele 65912df989 Add support for the Enhanced Terrain Layer module (resolves #50) 2021-04-10 15:02:35 +02:00
Manuel Vögele 9ae9dae427 Add c66ec46 to changelog 2021-04-10 15:00:48 +02:00
Manuel Vögele 8cd5d7f276 Migrate Drag Ruler to Terrain Ruler's new API 2021-04-10 14:43:45 +02:00
Manuel Vögele 35624a37aa Add api endpoint to determine the distance that a token has moved already 2021-04-10 14:25:01 +02:00
Manuel Vögele c66ec46aed Track the path taken by tokens in combat and display it when reactivating DragRuler in the same turn 2021-04-08 09:35:04 +02:00
Manuel Vögele dec422fa3a Fix typo in changelog 2021-04-01 22:57:13 +02:00
Manuel Vögele 6ad1f4c522 Merge branch 'master' into develop 2021-04-01 22:36:06 +02:00
Manuel Vögele fb9a2ee60c Release v1.4.5 2021-04-01 22:35:23 +02:00
Manuel Vögele 57ed545e30 Snap tiny tokens (0.5x0.5 or smaller) to the corners of the grid squares like it is done in vanilla foundry (fixes #49) 2021-04-01 22:25:21 +02:00
Manuel Vögele 94fe223529 Fix measurement errors when disabling token snapping while the Terrain Ruler module isn't enabled 2021-04-01 00:07:26 +02:00
Manuel Vögele 305a6116bc Refactor: Override ruler class instead of hooking each function individually 2021-03-31 17:19:12 +02:00
Manuel Vögele 942bfa04e8 Release v1.4.4 2021-03-25 01:57:41 +01:00
Manuel Vögele b4ac65e478 Fix snapping for tokens that are smaller than 1x1 (fixes #48) 2021-03-25 01:55:48 +01:00
Manuel Vögele 0de01379e9 Release v1.4.3 2021-03-24 12:35:40 +01:00
Manuel Vögele f6eb5eff9c Add german translation 2021-03-24 12:35:05 +01:00
Manuel Vögele fee0983841 Mention 3de044e in changelog 2021-03-24 12:08:10 +01:00
Brother Sharp 3de044ea45 Update japanese translation (#47) 2021-03-24 12:05:10 +01:00
Manuel Vögele e473fea121 Add defaults for swade game system to generic speed provider 2021-03-18 19:06:35 +01:00
Manuel Vögele 0aee541eb3 Release v1.4.2 2021-03-18 07:45:18 +01:00
Manuel Vögele 40dde08ddd Allow getTokenShape on gridless maps (fixes #44) 2021-03-18 07:41:30 +01:00
Manuel Vögele 8234aa566d Release v1.4.1 2021-03-17 09:26:46 +01:00
Manuel Vögele 2862643ba8 Add missing check for whether hex-size-support module is present (fixes #43) 2021-03-17 09:25:30 +01:00
Manuel Vögele e3abdeab3a Mention chinese translation in changelog 2021-03-16 09:27:51 +01:00
zeteticl 2ff4e98fe9 Add chinese localization (#39) 2021-03-16 09:02:32 +01:00
Manuel Vögele e0d0483722 Release v1.4.0 2021-03-15 20:52:52 +01:00
Manuel Vögele 4f1dec3089 Support for difficult terrain with tokens larger than 1x1 2021-03-15 20:52:52 +01:00
Manuel Vögele c4d089e8ff Always draw the ruler from the token's center for larger tokens (even on grids). Highlight all spaces occupied by larger tokens. 2021-03-15 20:17:09 +01:00
Manuel Vögele f8fe4ee971 Allow bug reporter 2021-03-15 18:05:14 +01:00
Manuel Vögele 378401b5bb Merge branch 'master' into develop 2021-03-10 11:55:22 +01:00
Manuel Vögele 0a54a47951 Release v1.3.5 2021-03-10 11:51:41 +01:00
Manuel Vögele 0b71504da3 Fix regression introduced in 6e745820 that made colors flicker during measurement 2021-03-10 11:50:55 +01:00
Manuel Vögele 5a7f5531f2 When multiple colors overlap on one square draw the one representing the furthest range with the highest priority 2021-03-09 14:51:55 +01:00
Manuel Vögele 862477c698 Add a setting that allows hiding the GM's drag ruler from non gm players (resolves #23) 2021-03-09 12:54:28 +01:00
Manuel Vögele 384de1a1b5 Add support for difficult terrain via Terrain Ruler module (resolves #1) 2021-03-09 10:08:51 +01:00
34 changed files with 3869 additions and 629 deletions
+5
View File
@@ -0,0 +1,5 @@
/foundry*.js
artifact/
wasm/
*.lock
.vscode/
+4
View File
@@ -0,0 +1,4 @@
*.md
*.html
*.json
foundry.js
+4
View File
@@ -0,0 +1,4 @@
printWidth: 100
trailingComma: "all"
bracketSpacing: false
arrowParens: "avoid"
+439 -1
View File
@@ -1,3 +1,441 @@
## 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
**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.
### New features
- Drag Ruler's key bindings can now be assigned to custom keys by the user
- Measuring difficult terrain on gridded maps with euclidean grid rule is now supported (for this to work the `Terrain Ruler` module needs to be enabled)
### Compatibility
- Drag Ruler now supports Foundry v9
- Drag Ruler now utilizes libwrapper to increase interoperability with other modules
### Translation
- Added french translation (thanks to Elfenduli)
- Updated japanese translation (thanks to touge)
## 1.9.1
### Bugfixes
- Fixed a bug that caused the ruler to misbehave or not show up at all if the speed provider isn't configured (this was a regression introduced in 1.9.0)
### Translation
- Updated the spaish translation (thanks to Viriato139ac#342)
## 1.9.0
### New features
- On Gridless scenes, tokens can now snap to their speed limits, to make full usage of a token's movement speed easier. This feature can be temporarily disabled by pressing Shift during drag and can be disabled completely in the settings.
### Bugfixes
- Non-square tokens (e.g. 2x1) now work correctly on square grids
- When modifying difficult terrain that a token has already moved over, this the movement history of the token won't change anymore (this was a regression introduced in 1.8.0)
- Fixed a bug that prevented pausing/unpausing the game when no scene was active
### API
- Added `dragRuler.getColorForDistanceAndToken` API endpoint that allows other modules to receive the highlight color for a specified distance with a given token.
## 1.8.2
### Compatibility
- The generic speed provider defaults have been updated for lance 1.0 (thanks to BoltsJ!)
- Eliminated a deprecation warning when both Drag Ruler and Hex Size Support are enabled (thanks Argonius-Angelus!)
## 1.8.1
### Bugfixes
- Fixed a bug where the function that was bound to the spacebar key wouldn't work correctly when the "Toggle Snap To Grid" module was enabled
### Translation
- Updated Spanish translation (thanks to Viriato139ac#342)
## 1.8.0
### New features
- Pressing escape during a drag now cancels the drag
- Undoing a movement via Ctrl+Z will now also remove that movement from Drag Ruler's movement history
- Drag Ruler can now configured to stay disabled by default when a Token/Template is being dragged. In that case it will activate once the button to place a waypoint is being pressed.
### Bugfixes
- Fixed a bug that caused the ruler to snap to grid when a waypoint was deleted while shift was being pressed (thanks to Michael Clavell!)
- Fixed a bug that could leave behind a single waypoint on the canvas when canceling a dragging operation while moving the mouse
### Compatibility
- Drag Ruler is now compatible with the "Toggle Snap To Grid" module (thanks to Michael Clavell!)
## 1.7.7
### Compatibility
- Updated the default settings for the swade game system. The new default speed attribute points to a speed value that gets adjusted for wounds.
## 1.7.6
### Translation
- Added Korean translation (thanks to KLO#1490)
- Added Spanish translation (thanks to Viriato139ac#342)
### Compatibility
- Drag Ruler's Generic Speed Provider is now awar of good default values for the "Call of Cthulhu 7th edition (Unofficial)" game system
- Drag Ruler is now compatible with Foundry 0.8.8
## 1.7.5
### Bugfixes
- Decimal speeds (as often used in metric game systems) are no longer being rounded down (thanks to DarKDinDoN for diagnosing this bug)
## 1.7.4
### Bugfixes
- Fixed a bug where the ruler would wrongly snap to the grid center for other players when dragging a measurement template
### Compatibility
- Drag Ruler is now compatiblie with the "Monk's Active Tile Triggers" module
- Drag Ruler's Generic Speed Provider is now aware of good default values for the D&D 3.5 game system
- Drag Ruler is now compatible with Foundry 0.8.7
## 1.7.3
### Compatibility
- Drag Ruler is now compatible with Foundry 0.8.5
## 1.7.2
### Bugfixes
- Fixed a bug that prevented waypoints for measurement templates from snapping to any other point than a grid cell corner (or grid cell center on hex)
- Fixed a bug that could cause the ruler to not end up at the token's center (especially if the token is being moved very quickly and then stopped abruptly)
## 1.7.1
### Bugfixes
- Fixed a bug that prevented players from moving their tokens ([#74](https://github.com/manuelVo/foundryvtt-drag-ruler/issues/74))
## 1.7.0
**BREAKING** This update is incompatible with previous Terrain Ruler versions. If you're using Terrain Ruler, make sure you update Terrain Ruler to at least version 1.3.0.
### New features
- A ruler will now be shown when dragging measurement templates over the map ([#13](https://github.com/manuelVo/foundryvtt-drag-ruler/issues/13))
- Drag Ruler can now measure difficult terrain on gridless maps (if the Terrain Ruler module is installed and enabled)
- Improved the positioning of the labels around the ruler. The labels should now never overlap with the waypoint.
### Bugfixes
- Fixed a bug that sometimes measured diagonals incorrectly with the 5/10/5 grid rule
- Fixed a bug that would cause the ruler to re-measure when the shift key is being pressed or released while a token is moving
### Compatibility
- Drag Ruler's Generic Speed Provider is now aware of good default values for the Starfinder game system
### Translation
- Corrected typos in the german translation (thanks to CarnVanBeck!)
### API
- The old API that Drag Ruler offered prior to version 1.3.0 is now deprecated. Speed Providers that still use this API will continue to work for now, but will generate a warning in the console about the deprecation. All modules and game systems offered on the FoundryVTT website have already updated to the new API. If you see the deprecation warning, please consider updating to the current version of the respective system/module you're using.
## 1.6.5
### Bugfixes
- Drag Ruler no longer gets stuck if the user presses ESC during drag ([#70](https://github.com/manuelVo/foundryvtt-drag-ruler/issues/70))
## 1.6.4
### Bugfixes
- Fixed a bug where a bug in a Speed Provider could lead to the ruler getting stuck, leaving the token immovable
## 1.6.3
### Bugfixes
- If the movement history for a token is being updated (for example by a history reset by the gm) while a player is currently measuring a distance for that token the history change is now being reflected in the active measurement.
### Compatibility
- Drag Ruler's Generic SpeedProvider is now aware of good default values for the Savage Worlds Adventure Edition game system
## 1.6.2
### Bugfixes
- The reset movement history button now resets the movement history for all players, not just for the GM
## 1.6.1
### API
- Added `onMovementHistoryUpdate` callback to Speed Providers, that allows them to perform game systems specific improvements to the movement history
- Added `dragRuler.resetMovementHistory` that clears the stored movement history for a token.
## 1.6.0
### Performance
- Greatly increased the performance when playing on huge maps and when moving many tokens at once.
- Huge performance improvements for speed providers. (Technical details: `getRanges` is now being called way less frequently)
### New features
- GMs now have an option to reset the movement history for individual tokens in the right click menu of the combat tracker
- When releasing a dragged token while pressing Alt the token will be moved to the target location without an animation ([#3](https://github.com/manuelVo/foundryvtt-drag-ruler/issues/3))
### Bugfixes
- When starting to drag a new token while the previous one is still moving the ruler won't dissappear anymore when the previous token arrives at it's destination.
## 1.5.4
### Bugfixes
- Fixed a bug that prevented tokens from being moved when their movement history collides with a wall. ([#61](https://github.com/manuelVo/foundryvtt-drag-ruler/issues/61))
## 1.5.3
### Compatiblilty
- Drag Ruler's Generic Speed Provider is now able to work with game systems that put non-number characters behind the tokens movement speed (like `30ft.`). One example for such a game system is Dungeon Crawl Classics. ([#60](https://github.com/manuelVo/foundryvtt-drag-ruler/issues/60))
- The Generic Speed Provider now has good default settings for the Dungeon Crawl Classics (dcc) game system.
## 1.5.2
### Bugfixes
- Drag Ruler no longer prevents tokens that don't have an actor from being moved. ([#58](https://github.com/manuelVo/foundryvtt-drag-ruler/issues/58))
- Grid highlighting now also works for tokens that don't have an actor.
## 1.5.1
### Bugfixes
- The hint that tells users how to enable difficult terrain measurement in Drag Ruler is no longer shown if no terrain layer module is installed.
## 1.5.0
### New features
- In combat Drag Ruler will now remember the path that was taken by a token during the turn. Picking the token up during the same turn will continue the previous measurement, taking steps that are already taken into account.
### Module compatibility
- Drag Ruler is now fully compatible with the `Enhanced Terrain Layer` module. ([#50](https://github.com/manuelVo/foundryvtt-drag-ruler/issues/50))
## 1.4.6
### Bugfixes
- Fixed a bug where a token would move to the incorrect location if it is was dragged and released very quickly ([#51](https://github.com/manuelVo/foundryvtt-drag-ruler/issues/51)) - thanks to Silver Raven for helping me track down this bug!
## 1.4.5
### Bugfixes
- Tiny tokens (0.5x0.5 or smaller) now snap to the coners of a square like they do in vanilla foundry ([#49](https://github.com/manuelVo/foundryvtt-drag-ruler/issues/49))
- Fixed a bug that could cause a meausred distance to be wrong when disabling token snapping using the shift key
- Fixed a bug where the highlighted path could have gaps when disabling token snapping using the shift key
## 1.4.4
### Bugfixes
- Fix snapping for tokens that are smaller than 1x1
## 1.4.3
### System compatibility
- Drag Ruler's Generic SpeedProvider is now aware of good default values for the Savage Worlds Adventure Edition game system
### Translation
- Added german translation
- Updated japanese translation (thanks to touge)
## 1.4.2
### Bugfixes
- Drag Ruler now works again on gridless maps
## 1.4.1
### Bugfixes
- Fixed a bug where Drag Ruler wouldn't work at all on hex grids when the Hex Token Size Support isn't installed
### Translation
- Added chinese translation (thanks to zeteticl)
## 1.4.0
### New features
- If the [Terrain Ruler module](https://foundryvtt.com/packages/terrain-ruler/) is installed and activated, Drag Ruler will now take difficult terrain that was placed with the [TerrainLayer module](https://foundryvtt.com/packages/TerrainLayer/) into account.
- The ruler will now always be drawn from the tokens center (even for tokens larger than 1x1) on all grid types (on Hex Grids this requires the Hex Token Size Support module).
- For tokens larger than 1x1 the highlighted path will now reflect the tokens size (on Hex Grids this requires the Hex Token Size Support module)
- The GM's Drag Ruler can now be hidden from non GM players via a setting.
- When multiple different colors apply to a single grid space because the path crosses itself the color representing ranges further away will take priortiy over colors representing closer ranges.
### API
- Speed providers can now selectively ignore difficult terrain and even implement their own cost functions, if the default Drag Ruler behavior doesn't fit the game system. This can be achieved by overriding the new, optional `SpeedProvider` function `getCostForStep`.
## 1.3.5
### Bugfixes
- Fixed a regression where spaces could suddenly change their color during measurement
## 1.3.4 ## 1.3.4
### Module compatibility ### Module compatibility
- Increased compatiblility with other modules (namely Drag Ruler and Terrain Ruler are no longer incompatible) - Increased compatiblility with other modules (namely Drag Ruler and Terrain Ruler are no longer incompatible)
@@ -9,7 +447,7 @@
## 1.3.2 ## 1.3.2
### Translation ### Translation
- Updated japanese translation (thanks to togue) - Updated japanese translation (thanks to touge)
## 1.3.1 ## 1.3.1
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Manuel Vögele
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+52 -11
View File
@@ -1,41 +1,82 @@
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/staebchenfisch) [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/staebchenfisch)
# Drag Ruler # Drag Ruler
This module shows a ruler when you drag a token to infrom you how far you've dragged the token from it's start point. Additionally, if you're using a grid, the spaces the token will travel though will be colored depending on your tokens speed. If you're using a gridless map the ruler color will change to convey this information. This module shows a ruler when you drag a token or measurement template to 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.
![Drag Ruler being used on a square grids](https://raw.githubusercontent.com/manuelVo/foundryvtt-drag-ruler/709774b25f7dd818a90591165f74b3e6dbc788cc/media/basic_square.webp)
![Drag Ruler being used on a gridless scene](https://raw.githubusercontent.com/manuelVo/foundryvtt-drag-ruler/709774b25f7dd818a90591165f74b3e6dbc788cc/media/basic_gridless.webp)
![Drag Ruler while dragging a measurement template](https://raw.githubusercontent.com/manuelVo/foundryvtt-drag-ruler/709774b25f7dd818a90591165f74b3e6dbc788cc/media/measurement_template.webp)
## Path color ## Supports Tokens of all sizes
![Drag Ruler demonstration](https://raw.githubusercontent.com/manuelVo/foundryvtt-drag-ruler/5177746fbb4edb28b6ba09137247d142af575c47/media/drag_ruler.webp) Terrain ruler has excellent support for tokens of all sizes. The Ruler will always originate from the token's center and will always highlight all the squares that tokens move over.
The token has a speed of 30ft. All squares on the path within that range are colored in the players color. If the token is running it can cover double that range (this can be changed in the settings). All Squares on the path that can only be reached while running are colored in yellow. Squares on the path that the token cannot possibly reached at regular speeds are colored red. This coloring behavior can be tweaked in the settings and can be overridden by modules and game systems to provide more granular, game system specific control. For more information on how to do this see the [API](#api) section of this document. ![Drag Ruler being used with a large token on a square grid](https://raw.githubusercontent.com/manuelVo/foundryvtt-drag-ruler/709774b25f7dd818a90591165f74b3e6dbc788cc/media/large_token_square.webp)
![Drag Ruler being used with a large token on a hex grid](https://raw.githubusercontent.com/manuelVo/foundryvtt-drag-ruler/709774b25f7dd818a90591165f74b3e6dbc788cc/media/large_token_hex.webp)
## Waypoints ## Difficult Terrain support
You can add waypoints to the path by pressing spacebar while you drag the token. To remove a placed waypoint press the right mouse button. **To use support for difficult terrain you must install the [Terrain Ruler](https://foundryvtt.com/packages/terrain-ruler) module**
![Demonstration of Waypoints](https://raw.githubusercontent.com/manuelVo/foundryvtt-drag-ruler/5177746fbb4edb28b6ba09137247d142af575c47/media/waypoints.webp) With the Terrain Ruler module installed, Drag Ruler is able to take difficult terrain that was placed down using the [Enhanced Terrain Layer](https://foundryvtt.com/packages/enhanced-terrain-layer) or [TerrainLayer](https://foundryvtt.com/packages/TerrainLayer) module into account.
![Drag Ruler being used to measure distances over difficult terrain on square grid](https://raw.githubusercontent.com/manuelVo/foundryvtt-drag-ruler/709774b25f7dd818a90591165f74b3e6dbc788cc/media/difficult_terrain_square.webp)
![Drag Ruler being used to measure distances over difficult terrain on gridless scenes](https://raw.githubusercontent.com/manuelVo/foundryvtt-drag-ruler/709774b25f7dd818a90591165f74b3e6dbc788cc/media/difficult_terrain_gridless.webp)
## Why would I want to use this instead of ShowDragDistance? ## Movement history (optional)
ShowDragDistance isn't maintained anymore. This means that it is at risk to stop working with every foundry update. In fact this process has already begun. As of Foundry Version 0.7.9 ShowDragDistance doesn't work anymore on gridless maps. Drag Ruler on the other hand is fully compatible with the current Foundry release and I'll continue updating it for future foundry releases for the forseeable future. *This feature can be disabled in the settings if you don't like it*
In addition Drag Ruler provides more flexibility for game systems and modules via it's api to provide an experience that fits the rules of the game system that you are playing best. During combat, Drag Ruler will remember the path a token has taken during it's turn. When the token is being picked up again, Drag Ruler will continue measuring where it has left off. The path of the previous movement will be dipslayed in a faded color.
![Demonstration of the Movement History](https://raw.githubusercontent.com/manuelVo/foundryvtt-drag-ruler/709774b25f7dd818a90591165f74b3e6dbc788cc/media/movement_history.webp)
## 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.
![Demonstration of pathfinding](https://raw.githubusercontent.com/manuelVo/foundryvtt-drag-ruler/a5de9bbf0a54a5bafaa53c786bed4379dc244253/media/pathfinding.webp)
## 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)
- Hackmaster (starting with version 0.2.11)
- Ironclaw Second Edition (starting with version 0.2.2)
- Lancer (via the module [Lancer Speed Provider](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))
- 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))
- Stargate RPG (starting with version 1.6.0)
- Symbaroum (via the module [Symbaroum drag ruler integration](https://foundryvtt.com/packages/symbaroum-drag-ruler-integration))
- Tagmar RPG (starting with version 1.1.4)
- 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)
- Wasteland Ventures (starting with version 0.1.0)
- WWII:OWB (starting with version 1.0.4)
## Translations ## Translations
Drag Ruler is available in the follwing languages: Drag Ruler is available in the follwing languages:
- Chinese (thanks to zeteticl)
- English - English
- German
- Japanese (thanks to touge) - Japanese (thanks to touge)
- Korean (thanks to KLO#1490)
- Spanish (thanks to Viriato139ac#342)
## API ## API
*Audience: This paragraph is intended for module and system devleopers that want to add more complex behavior to Drag Ruler. If you just want to use this plugins features skip this paragraph.* *Audience: This paragraph is intended for module and system devleopers that want to add more complex behavior to Drag Ruler. If you just want to use this plugins features skip this paragraph.*
+125
View File
@@ -0,0 +1,125 @@
{
"drag-ruler": {
"dependencies": {
"ok": "OK",
"terrain-ruler": {
"title": "Wie man schwieriges Gelände mit Drag Ruler misst",
"text": "Du hast das {moduleName} Modul installiert. Drag Ruler kann schwieriges Gelände, das mit diesem Modul platziert wurde in seinen Messungen berücksichtigen. Hierzu greift Drag Ruler auf die Funktionalität von Terrain Ruler zurück. Wenn Terrain Ruler installiert und aktiviert ist, wird Drag Ruler automatisch anfangen schwieriges Gelände in den Messungen zu berücksichtigen.",
"neverShowAgain": "Zeige dies nie wieder an"
},
"socketlib": {
"title": "Socketlib Modul fehlt",
"text": "Drag Ruler benötigt das socketlib-Modul, um korrekt zu funktionieren. Bitte aktiviere das socketlib-Modul in dieser Welt."
}
},
"resetMovementHistory": "Bewegungsverlauf zurücksetzen",
"genericSpeedProvider": {
"settings": {
"dashMultiplier": {
"name": "Sprint Multiplikator",
"hint": "Diese Einstellung kann genutzt werden, um eine zweite Geschwindigkeitsfarbe im gemessenen Pfad zu erhalten. Die zweite Farbe wird deaktiviert, wenn diese Einstellung auf 0 gesetzt ist."
},
"speedAttribute": {
"name": "Geschwindigkeitsattribut",
"hint": "Das Attribut, das die Gehgeschwindigkeit einer Spielfigur definiert. Dieses Attribut wird während der Färbung des hervorgehobenen Pfads verwendet."
}
},
"speeds": {
"walk": "gehen",
"dash": "sprinten"
}
},
"keybindings": {
"cancelDrag": "Ziehen beenden",
"createWaypoint": "Wegpunkt erstellen",
"deleteWaypoint": "Wegpunkt löschen",
"disableSnap": {
"name": "Raster deaktivieren",
"hint": "Während diese Taste gedrückt ist, wird das Raster temporär deaktiviert."
},
"moveWithoutAnimation": {
"name": "Tokenanimation deaktivieren",
"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": {
"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": {
"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."
},
"autoStartMeasurement": {
"name": "Automatisch mit Messen beginnen",
"hint": "Wenn diese Option aktiviert ist beginnt Drag Ruler mit dem Messen, sobald das Token bewegt wird. Wenn die Option deaktiviert ist, bleibt Drag Ruler deaktiviert bis der Knopf zum Platzieren eines Wegpunkts betätigt wird."
},
"enableMovementHistory": {
"name": "Bewegungsverlauf während des Kampfes aktivieren",
"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": {
"name": "Aktion bei Rechtsklick",
"hint": "Was soll getan werden, wenn die rechte Maustaste während des Ziehens gedrückt wird?",
"choices": {
"create": "Wegpunkt erstellen",
"delete": "Wegpunkt löschen",
"cancel": "Ziehen beenden"
}
},
"showGMRulerToPlayers": {
"name": "Lineal des Spielleiters bei Spielern anzeigen",
"hint": "Wenn diese Option deaktiviert ist, wird das Lineal des Spielleiters bei den anderen Spielern nicht angezeigt."
},
"speedProviderSettings": {
"name": "Einstellungen zur Spielsystemintegration",
"hint": "In diesem Menü befinden sich alle spielsystemspezifischen Einstellungen.",
"button": "Einstellungen zur Spielsystemintegration",
"windowTitle": "Einstellungen zur Spielsystemintegration",
"headers": {
"speedProvider": "Spielsystemintegration",
"speedProviderSettings": "Integrationsspezifische Einstellungen"
},
"activeProvider": {
"name": "Verwendete Spielsystemintegration",
"hint": "Die unten angezeigten Einstellungen hängen von der verwendeten Spielsystemintegration ab. Wenn der Spielleiter eine andere Spielsystemintegration auswählt, können sich die zur Verfügung stehenden Einstellungen ändern."
},
"noSettings": "Diese Spielsystemintegration hat keine Einstellmöglichkeiten.",
"color": {
"name": "Farbe für {colorName}",
"hint": "Die Farbe mit der Felder in {colorName} Reichweite hervorgehoben werden.",
"unreachable": {
"name": "unerreichbar",
"hint": "Die Farbe für Felder, die von der bewegten Spielfigur nicht erreicht werden können."
}
},
"speedProvider": {
"name": "Verwendete Spielsystemintegration",
"hint": "Wähle aus, welche Spielsystemintegration für die farbliche Hervorhebung der Felder zuständig sein soll. Drag Ruler stellt eine generische Spielsystemintegration zur Verfügung, die grundlegende Funktionalität bietet und bei korrekter Konfiguration mit allen Spielsystemen funktionieren sollte. Weitere Spielsystemintegrationen können durch Module und Spielsysteme angeboten werden. Eine andere Spielsystemintegration als die generische auszuwählen kann eine bessere Integration in das verwendete Spielsystem bieten. Die Optionen unterhalb sind davon abhängig, welche Spielsystemintegration hier ausgewählt wurde.",
"choices": {
"module": "Modul {name}",
"native": "Generisch",
"system": "System {name}"
}
}
},
"useGridlessraster": {
"name": "Geschwindigkeitsraster aktivieren",
"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."
}
}
}
}
+67 -8
View File
@@ -1,5 +1,18 @@
{ {
"drag-ruler": { "drag-ruler": {
"dependencies": {
"ok": "OK",
"terrain-ruler": {
"title": "How to measure difficult terrain with Drag Ruler",
"text": "You have the {moduleName} module enabled. Drag Ruler is able to measure difficult terrain that was placed down using that module. To make this possible Drag Ruler utilizes the Terrain Ruler module. If Terrain Ruler is installed and activated Drag Ruler will automatically start to respect difficult terrain in its measurements.",
"neverShowAgain": "Never show this again"
},
"socketlib": {
"title": "Socketlib Module Missing",
"text": "Drag Ruler requires the socketlib module to work properly. Please activate the socketlib module in this world."
}
},
"resetMovementHistory": "Reset movement history",
"genericSpeedProvider": { "genericSpeedProvider": {
"settings": { "settings": {
"dashMultiplier": { "dashMultiplier": {
@@ -16,10 +29,56 @@
"dash": "dash" "dash": "dash"
} }
}, },
"keybindings": {
"cancelDrag": "Cancel dragging",
"createWaypoint": "Create waypoint",
"deleteWaypoint": "Delete waypoint",
"disableSnap": {
"name": "Disable snapping",
"hint": "Snapping will be temporarily disabled while this key is being held down."
},
"moveWithoutAnimation": {
"name": "Disable token animation",
"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": {
"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."
},
"autoPathfinding": {
"name": "Pathfinding by default",
"hint": "If enabled, dragging a token will automatically use a pathfinding ruler."
},
"enableMovementHistory": {
"name": "Enable movement history during combat",
"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": {
"name": "Right click action",
"hint": "What action shall be performed when right-clicking while dragging a token?",
"choices": {
"create": "Create waypoint",
"delete": "Delete waypoint",
"cancel": "Cancel drag"
}
},
"showGMRulerToPlayers": {
"name": "Show GM ruler to players",
"hint": "If disabled the ruler of GMs will not be shown for non-GM players."
}, },
"speedProviderSettings": { "speedProviderSettings": {
"name": "Speed Provider Settings", "name": "Speed Provider Settings",
@@ -31,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",
@@ -53,9 +112,9 @@
} }
} }
}, },
"swapSpacebarRightClick": { "useGridlessRaster": {
"name": "Swap spacebar and right click", "name": "Use speed based snapping",
"hint": "Swaps the functions of spacebar and right click during dragging. If enabled right click will place waypoints and spacebar will delete them" "hint": "On Gridless scenes, this makes tokens snap to the token's speed ranges."
} }
} }
} }
+125
View File
@@ -0,0 +1,125 @@
{
"drag-ruler": {
"I18N": {
"LANGUAGE": "Español",
"MAINTAINERS": "@Viriato139ac#0342"
},
"dependencies": {
"ok": "OK",
"terrain-ruler": {
"title": "Cómo medir a través de terreno difícil con Drag Ruler",
"text": "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"
},
"socketlib": {
"title": "El módulo Socketlib no está instalado",
"text": "Drag Ruler necesita para su funcionamiento correcto que instale socketlib. Por favor, active socketlib en este mundo."
}
},
"resetMovementHistory": "Reiniciar el historial de movimiento",
"genericSpeedProvider": {
"settings": {
"dashMultiplier": {
"name": "Multiplicador por correr",
"hint": "Se puede usar para aplicar a los iconos una velocidad secundaria que use un color distinto en la medición de la ruta a seguir. Establece el valor a 0 para deshabilitar esta velocidad secundaria."
},
"speedAttribute": {
"name": "Attributo de velocidad",
"hint": "El atributo que define la velocidad de movimiento andando del icono. Se usa para los códigos de colores al medir la ruta a seguir."
}
},
"speeds": {
"walk": "caminar",
"dash": "correr"
}
},
"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": {
"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": {
"name": "Mostrar velocidad de los PJs a todo el mundo",
"hint": "Si se habilita, se mostrará a todo el mundo los códigos de colores de las rutas de los PJs, incluso si no tienen permisos de observador para ese personaje."
},
"autoStartMeasurement": {
"name": "Comenzar a medir automáticamente",
"hint": "Si se habilita, Drag Ruler comenzará a medir en cuanto se comience a arrastrar un icono. Si se deshabilita, Drag Ruler permanecerá inactivo y comenzará a medir únicamente cuando se presione el botón configurado para añadir un nuevo punto de ruta."
},
"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": {
"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 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": {
"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 no-GMs."
},
"speedProviderSettings": {
"name": "Proveedor de configuración de velocidad",
"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",
"windowTitle": "Proveedor de configuración de velocidad",
"headers": {
"speedProvider": "Proveedor de velocidad",
"speedProviderSettings": "Configuraciones específicas del proveedor de velocidad"
},
"activeProvider": {
"name": "Proveedor de Velocidad Activo",
"hint": "La configuración que se muestra más abajo depende del proveedor de velocidad activo. Si el GM selecciona un proveedor distinto, los ajustes disponibles puede que cambien."
},
"noSettings": "Este proveedor de velocidad no dispone de ninguna opción de configuración.",
"color": {
"name": "Color para {colorName}",
"hint": "Color que se usará para establecer el tono de las cuadrículas que se encuentran dentro del rango {colorName}.",
"unreachable": {
"name": "inalcanzable",
"hint": "Color que se usará para las cuadrículas más allá de la distancia máxima de movimiento del icono."
}
},
"speedProvider": {
"name": "Proveedor de configuración de velocidad",
"hint": "Seleccione quien suministra la información de la velocidad de los iconos a la hora de colorear la ruta a seguir. Drag Ruler incluye un proveedor genérico que ofrece una funcionalidad básica y que probablemente funcione para casi todos los sistemas si se usa de forma correcta. Se pueden añadir otros proveedores de velocidad al instalar sistemas o módulos. Seleccionar un proveedor distinto al genérico asegurará una mejor integración con las reglas del sistema que está usando. Las opciones siguientes dependerán del proveedor que seleccione.",
"choices": {
"module": "Módulo {name}",
"native": "Genérico",
"system": "Sistema {name}"
}
}
},
"useGridlessRaster": {
"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."
}
}
}
}
+122
View File
@@ -0,0 +1,122 @@
{
"drag-ruler": {
"dependencies": {
"ok": "OK",
"terrain-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 lors de ses mesures.",
"neverShowAgain": "Ne plus jamais afficher cela"
},
"socketlib": {
"title": "Module Socketlib manquant",
"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",
"genericSpeedProvider": {
"settings": {
"dashMultiplier": {
"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."
},
"speedAttribute": {
"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é."
}
},
"speeds": {
"walk": "marche",
"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": {
"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": {
"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."
},
"autoStartMeasurement": {
"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é."
},
"autoPathfinding": {
"name": "Recherche d'itinéraire par défaut",
"hint": "Si activé, glisser un jeton utilisera automatiquement une recherche de l'itinéraire."
},
"enableMovementHistory": {
"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."
},
"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": {
"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."
},
"speedProviderSettings": {
"name": "Paramètres du système de vitesse",
"hint": "Les paramètres du système de vitesse contiennent tous les paramètres spécifiques au système de jeu.",
"button": "Paramètres du système de vitesse",
"windowTitle": "Paramètres du système de vitesse",
"headers": {
"speedProvider": "Système de vitesse",
"speedProviderSettings": "Paramètres spécifiques au système de vitesse"
},
"activeProvider": {
"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."
},
"noSettings": "Ce système de vitesse n'offre aucune option de configuration.",
"color": {
"name": "Couleur pour {colorName}",
"hint": "La couleur qui sera utilisée pour colorier les carrés qui sont dans la portée {colorName}.",
"unreachable": {
"name": "inaccessible",
"hint": "La couleur des cases qui ne sont pas accessibles pour le jeton déplacé."
}
},
"speedProvider": {
"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.",
"choices": {
"module": "Module {name}",
"native": "Générique",
"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": {
"name": "Utilise l'interruption basée sur la vitesse",
"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."
}
}
}
}
+29 -3
View File
@@ -1,5 +1,19 @@
{ {
"Localization Provided By": "touge",
"drag-ruler": { "drag-ruler": {
"dependencies": {
"ok": "OK",
"terrain-ruler": {
"title": "Drag Rulerで移動困難地形を測定する",
"text": "「{moduleName}」モジュールが有効化されました。Drag Rulerは、同モジュールによって設定された移動困難地形を測定できます。これには「Terrain Ruler」モジュールが必要です。有効化すると、自動的に移動困難地形を考慮した測定を行います",
"neverShowAgain": "再表示しない"
},
"socketlib": {
"title": "「Socketlib」が見つかりません",
"text": "Drag Rulerの正常動作には「Socketlib」モジュールが必要です。このワールドで「Socketlib」を有効にしてください。"
}
},
"resetMovementHistory": "移動履歴のリセット",
"genericSpeedProvider": { "genericSpeedProvider": {
"settings": { "settings": {
"dashMultiplier": { "dashMultiplier": {
@@ -21,6 +35,18 @@
"name": "プレイヤーキャラクターの移動速度を全員に公開する", "name": "プレイヤーキャラクターの移動速度を全員に公開する",
"hint": "有効にすると、アクターの観察者(Observer)権限を持たない人にも,移動範囲の色分けが表示されるようになります。" "hint": "有効にすると、アクターの観察者(Observer)権限を持たない人にも,移動範囲の色分けが表示されるようになります。"
}, },
"autoStartMeasurement": {
"name": "自動計測",
"hint": "有効化時、Drag Rulerはトークンがドラッグされた時点で計測を初めます。無効化の場合、ドラッグしてもDrag Rulerは起動せず、中間地点をおいたときにのみ起動します。"
},
"enableMovementHistory": {
"name": "戦闘中の移動履歴を有効にする",
"hint": "有効にすると、Drag Rulerは戦闘ターン中にコマが辿った経路を記憶するようになります。これはコマの再選択時に再表示されます。"
},
"showGMRulerToPlayers": {
"name": "GMのルーラーをプレイヤーに見せる",
"hint": "無効の場合、GMのルーラーはプレイヤーに表示されません。"
},
"speedProviderSettings": { "speedProviderSettings": {
"name": "移動速度の提供元", "name": "移動速度の提供元",
"hint": "ゲームシステムに固有のすべての設定をここで行います。", "hint": "ゲームシステムに固有のすべての設定をここで行います。",
@@ -53,9 +79,9 @@
} }
} }
}, },
"swapSpacebarRightClick": { "useGridlessRaster": {
"name": "スペースキーと右クリックを入れ替える", "name": "移動速度制限",
"hint": "ドラッグ中のスペースキーと右クリックの機能を入れ替えます。有効にすると右クリックがウェイポイントの配置、スペースキーが削除になります。" "hint": "グリッドの無いシーンでは、コマがドラッグできる範囲を移動速度ぶんまで制限します。Shiftキーを押すことで一時的にこの制限を無視できます。"
} }
} }
} }
+79
View File
@@ -0,0 +1,79 @@
{
"drag-ruler": {
"dependencies": {
"ok": "OK",
"terrain-ruler": {
"title": "Drag Ruler를 사용한 험지 측정 방법",
"text": "{moduleName} 모듈을 활성화했습니다. Drag Ruler는 해당 모듈을 사용하여 험지를 측정할 수 있습니다. 이렇게 하려면 Terrain Ruler 모듈을 활성화하십시오. Terrain Ruler 모듈을 설치하고 Drag Ruler를 활성화하면 자동적으로 험지를 측정합니다.",
"neverShowAgain": "다시 표시하지 않음"
},
"socketlib": {
"title": "Socketlib 모듈 비탐지",
"text": "Drag Ruler는 Socketlib 모듈이 있어야 합니다. 이 월드에서 해당 모듈을 활성화하십시오."
}
},
"resetMovementHistory": "이동 내역 리셋",
"genericSpeedProvider": {
"settings": {
"dashMultiplier": {
"name": "질주 승수",
"hint": "측정된 경로에 색을 입힐 때 추가 속도를 제공하는 데 사용할 수 있다. 추가 속도를 비활성화하려면 이 값을 0으로 설정한다."
},
"speedAttribute": {
"name": "속도 어트리뷰트",
"hint": "토큰의 이동속도를 정의하는 속성이다. 측정 경로에 색을 입힐 때 사용한다."
}
},
"speeds": {
"walk": "보행",
"dash": "질주"
}
},
"settings": {
"alwaysShowSpeedForPCs": {
"name": "PC 속도를 모두에게 표시",
"hint": "활성화시 플레이어 캐릭터에 대한 관찰자 권한이 없더라도 액터 속도에 따라 색상이 모든 유저에게 표시된다."
},
"enableMovementHistory": {
"name": "전투 중 이동 기록 사용",
"hint": "활성화시 Drag Ruler는 해당 턴에 토큰이 이동한 경로를 기억하고 다시 이동시킬 때 표시한다."
},
"showGMRulerToPlayers": {
"name": "GM 측정자를 플레이어에게 표시",
"hint": "비활성화 시 GM이 아닌 유저에게는 GM의 측정자가 표시되지 않는다."
},
"speedProviderSettings": {
"name": "속도 공급자 설정",
"hint": "속도 공급자 설정은 게임 시스템별 설정을 모두 포함한다.",
"button": "속도 공급자 설정",
"windowTitle": "속도 공급자 설정",
"headers": {
"speedProvider": "속도 공급자",
"speedProviderSettings": "속도 공급자 관련 설정"
},
"activeProvider": {
"name": "현재 활성화된 속도 공급자",
"hint": "아래에 표시된 설정은 활성화된 속도 공급자를 보여준다. GM이 다른 속도 공급자를 선택하면 사용 가능한 설정이 변경될 수 있다."
},
"noSettings": "이 속도 공급자는 구성 옵션을 제공하지 않습니다.",
"color": {
"name": "{colorName} 색상",
"hint": "{colorName} 범위 내에서의 칸의 색상을 지정하는데 사용할 색상.",
"unreachable": {
"name": "도달 불가",
"hint": "드래그한 토큰으로 도달할 수 없는 공간의 색상"
}
},
"speedProvider": {
"name": "속도 설정 공급자",
"hint": "색을 입힐 동안 사용할 토큰에 대한 속도 정보 제공자를 선택한다. Drag Ruler는 기본 기능을 제공하며 올바르게 구성된 경우 모든 게임 시스템에서 작동하는 일반적 속도 공급자를 제공한다. 게임 시스템과 설치된 모듈을 통해 더 많은 속도 공급자를 이용할 수 있다. 일반 속도 공급자와는 다른 속도 공급자를 선택하면 사용 중인 게임 시스템의 규칙에 더 잘 통합될 수 있다.아래 옵션은 여기에서 선택한 속도 공급자에 따라 다르다.",
"choices": {
"module": "모듈 {name}",
"native": "포괄적",
"system": "시스템 {name}"
}
}
}
}
}
}
+121
View File
@@ -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."
}
}
}
}
+62
View File
@@ -0,0 +1,62 @@
{
"drag-ruler": {
"genericSpeedProvider": {
"settings": {
"dashMultiplier": {
"name": "快步 倍數",
"hint": "這可用於在對測量路徑進行著色的過程中為棋子賦予第二速度。將其設定為0可禁用第二速度。"
},
"speedAttribute": {
"name": "速度屬性",
"hint": "定義棋子的行走速度的屬性。在對測量路徑進行著色時使用。"
}
},
"speeds": {
"walk": "走路",
"dash": "快步"
}
},
"settings": {
"alwaysShowSpeedForPCs": {
"name": "向所有人顯示PC速度",
"hint": "如果啟用, 玩家的角色(actor)的速度將顯示給所有人,即使他們沒有角色表的觀察者(Observer)權限也是如此。"
},
"showGMRulerToPlayers": {
"name": "向玩家顯示GM標尺",
"hint": "如果禁用,則不為非GM玩家顯示GM的標尺"
},
"speedProviderSettings": {
"name": "速度提供者設定",
"hint": "速度提供者設定 包括 所有遊戲系統特定的設定。",
"button": "速度提供者設定",
"windowTitle": "速度提供者設定",
"headers": {
"speedProvider": "速度提供者設定",
"speedProviderSettings": "特定於速度提供者的設定"
},
"activeProvider": {
"name": "當前活動的速度提供者",
"hint": "下面顯示的設定 取決於活動的 速度提供者。如果GM選擇其他速度提供者,則可用的設定可能會更改。"
},
"noSettings": "該速度提供者不提供任何配置選項。",
"color": {
"name": "{colorName}的顏色",
"hint": "將用於為{colorName}範圍內的正方形上色的顏色",
"unreachable": {
"name": "無法到達",
"hint": "指定拖動棋子無法到達的範圍的顏色"
}
},
"speedProvider": {
"name": "速度設定的提供者",
"hint": "選擇在著色期間為棋子提供速度信息的人。DragRuler提供了一個通用速度提供程序, 該提供程序提供了基本功能, 並且如果配置正確,則可以在所有遊戲系統上使用。可以通過遊戲系統和已安裝的Mod來提供更多速度提供程序。選擇與 標準速度提供者 不同的速度提供者可以更好地集成到您所使用的遊戲系統規則中。以下選項取決於此處選擇的速度提供者。",
"choices": {
"module": "Mod {name}",
"native": "一般",
"system": "系統 {name}"
}
}
}
}
}
}
+121
View File
@@ -0,0 +1,121 @@
{
"drag-ruler": {
"dependencies": {
"ok": "OK",
"terrain-ruler": {
"title": "Drag Ruler 如何测量困难地形",
"text": "你已启用 {moduleName}。Drag Ruler 可以使用此模组测量放置的困难地形。为此,Drag Ruler 将使用 Terrain Ruler 模组。如果已经安装并启用了 Terrain RulerDrag 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": "拖动指示物时按住,寻路功能便会暂时启用/禁用。"
}
}
}
}
+57 -7
View File
@@ -1,10 +1,12 @@
{ {
"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.3.4", "version": "1.13.6",
"minimumCoreVersion" : "0.7.9", "compatibility": {
"compatibleCoreVersion" : "0.7.9", "minimum": "11",
"verified": "11"
},
"authors": [ "authors": [
{ {
"name": "Manuel Vögele", "name": "Manuel Vögele",
@@ -13,27 +15,75 @@
} }
], ],
"esmodules": [ "esmodules": [
"src/main.js" "src/libwrapper_shim.js",
"src/main.js",
"src/socket.js"
], ],
"templates": [ "templates": [
"speed_provider_settings.html" "speed_provider_settings.html"
], ],
"languages": [ "languages": [
{
"lang": "de",
"name": "Deutsch",
"path": "lang/de.json"
},
{ {
"lang": "en", "lang": "en",
"name": "English", "name": "English",
"path": "lang/en.json" "path": "lang/en.json"
}, },
{
"lang": "es",
"name": "Español",
"path": "lang/es.json"
},
{
"lang": "fr",
"name": "Français",
"path": "lang/fr.json"
},
{ {
"lang": "ja", "lang": "ja",
"name": "日本語", "name": "日本語",
"path": "lang/ja.json" "path": "lang/ja.json"
},
{
"lang": "ko",
"name": "한국어",
"path": "lang/ko.json"
},
{
"lang": "zh-tw",
"name": "正體中文",
"path": "lang/zh-tw.json"
},
{
"lang": "cn",
"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://raw.githubusercontent.com/manuelVo/foundryvtt-socketlib/master/module.json"
}
]
},
"socket": true,
"url": "https://github.com/manuelVo/foundryvtt-drag-ruler", "url": "https://github.com/manuelVo/foundryvtt-drag-ruler",
"download": "https://github.com/manuelVo/foundryvtt-drag-ruler/archive/v1.3.4.zip", "download": "https://github.com/manuelVo/foundryvtt-drag-ruler/archive/v1.13.6.zip",
"manifest": "https://raw.githubusercontent.com/manuelVo/foundryvtt-drag-ruler/master/module.json", "manifest": "https://raw.githubusercontent.com/manuelVo/foundryvtt-drag-ruler/master/module.json",
"readme": "https://github.com/manuelVo/foundryvtt-drag-ruler/blob/master/README.md", "readme": "https://github.com/manuelVo/foundryvtt-drag-ruler/blob/master/README.md",
"changelog": "https://github.com/manuelVo/foundryvtt-drag-ruler/blob/master/CHANGELOG.md", "changelog": "https://github.com/manuelVo/foundryvtt-drag-ruler/blob/master/CHANGELOG.md",
"bugs": "https://github.com/manuelVo/foundryvtt-drag-ruler/issues" "bugs": "https://github.com/manuelVo/foundryvtt-drag-ruler/issues",
"allowBugReporter": true
} }
+118 -58
View File
@@ -1,138 +1,198 @@
import {GenericSpeedProvider, SpeedProvider} from "./speed_provider.js" import {measureDistances} from "./compatibility.js";
import {settingsKey} from "./settings.js" import {getMovementHistory} from "./movement_tracking.js";
import {GenericSpeedProvider, SpeedProvider} from "./speed_provider.js";
import {settingsKey} from "./settings.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);
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,
`speedProviders.${currentSpeedProvider.id}.color.unreachable`,
);
} catch (e) {
console.error(e);
return 0xff0000;
} }
catch (e) { }
console.error(e)
return 0xFF0000 export function getColorForDistanceAndToken(distance, token, ranges = null) {
if (!ranges) {
ranges = getRangesFromSpeedProvider(token);
} }
if (ranges.length === 0) return null;
const currentRange = ranges.reduce(
(minRange, currentRange) => {
if (distance <= currentRange.range && currentRange.range < minRange.range)
return currentRange;
return minRange;
},
{range: Infinity, color: getUnreachableColorFromSpeedProvider()},
);
return currentRange.color;
}
export function getMovedDistanceFromToken(token) {
const terrainRulerAvailable = game.modules.get("terrain-ruler")?.active;
const history = getMovementHistory(token);
const segments = CONFIG.Canvas.rulerClass
.dragRulerGetRaysFromWaypoints(history, {x: token.x, y: token.y})
.map(ray => {
return {ray};
});
const shape = getTokenShape(token);
const distances = measureDistances(segments, token, shape, {
enableTerrainRuler: terrainRulerAvailable,
});
// Sum up the distances
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);
} }
+106 -17
View File
@@ -1,19 +1,108 @@
export function getHexSizeSupportTokenGridCenter(token) { import {buildCostFunction} from "./api.js";
const tokenCenterOffset = CONFIG.hexSizeSupport.getCenterOffset(token) import {settingsKey} from "./settings.js";
const tokenCenter = {x: token.x + tokenCenterOffset.x, y: token.y + tokenCenterOffset.y} import {highlightTokenShape} from "./util.js";
if (token.getFlag("hex-size-support", "borderSize") % 2 === 1)
return tokenCenter export function highlightMeasurementTerrainRuler(
if (canvas.grid.grid.columns) { ray,
let hexOffset = canvas.grid.w / 2 startDistance,
if (!CONFIG.hexSizeSupport.getAltOrientationFlag(token)) tokenShape = [{x: 0, y: 0}],
hexOffset *= -1 alpha = 1,
tokenCenter.x += hexOffset ) {
for (const space of ray.terrainRulerVisitedSpaces.reverse()) {
const color = this.dragRulerGetColorForDistance(startDistance + space.distance);
highlightTokenShape.call(this, space, tokenShape, color, alpha);
}
}
export function measureDistances(segments, entity, shape, options = {}) {
const opts = duplicate(options);
if (canvas.grid.diagonalRule === "EUCL") {
opts.ignoreGrid = true;
opts.gridSpaces = false;
}
if (opts.enableTerrainRuler) {
opts.gridSpaces = true;
const firstNewSegmentIndex = segments.findIndex(segment => !segment.ray.dragRulerVisitedSpaces);
const previousSegments = segments.slice(0, firstNewSegmentIndex);
const newSegments = segments.slice(firstNewSegmentIndex);
const distances = previousSegments.map(
segment =>
segment.ray.dragRulerVisitedSpaces[segment.ray.dragRulerVisitedSpaces.length - 1].distance,
);
previousSegments.forEach(
segment =>
(segment.ray.terrainRulerVisitedSpaces = duplicate(segment.ray.dragRulerVisitedSpaces)),
);
opts.costFunction = buildCostFunction(entity, shape);
if (previousSegments.length > 0)
opts.terrainRulerInitialState =
previousSegments[previousSegments.length - 1].ray.dragRulerFinalState;
return distances.concat(terrainRuler.measureDistances(newSegments, opts));
} else {
// If another module wants to enable grid measurements but disable grid highlighting,
// manually set the *duplicate* option's gridSpaces value to true for the Foundry logic to work properly
if (!opts.ignoreGrid) {
opts.gridSpaces = true;
}
return canvas.grid.measureDistances(segments, opts);
}
}
export function checkDependencies() {
if (!game.modules.get("socketlib")?.active) {
console.error(
"Drag Ruler | The `socketlib` module isn't enabled, but it's required for Drag Ruler to operate properly.",
);
if (game.user.isGM) {
new Dialog({
title: game.i18n.localize("drag-ruler.dependencies.socketlib.title"),
content: `<h2>${game.i18n.localize(
"drag-ruler.dependencies.socketlib.title",
)}</h2><p>${game.i18n.localize("drag-ruler.dependencies.socketlib.text")}</p>`,
buttons: {
ok: {
icon: '<i class="fas fa-check"></i>',
label: game.i18n.localize("drag-ruler.dependencies.ok"),
},
},
}).render(true);
}
} else if (
!game.modules.get("terrain-ruler")?.active &&
game.user.isGM &&
!game.settings.get(settingsKey, "neverShowTerrainRulerHint")
) {
const lastHint = game.settings.get(settingsKey, "lastTerrainRulerHintTime");
if (Date.now() - lastHint > 604800000) {
// One week
let enabledTerrainModule;
if (game.modules.get("enhanced-terrain-layer")?.active) {
enabledTerrainModule = game.modules.get("enhanced-terrain-layer").data.title;
}
if (enabledTerrainModule) {
new Dialog({
title: game.i18n.localize("drag-ruler.dependencies.terrain-ruler.title"),
content: `<h2>${game.i18n.localize(
"drag-ruler.dependencies.terrain-ruler.title",
)}</h2><p>${game.i18n.format("drag-ruler.dependencies.terrain-ruler.text", {
moduleName: enabledTerrainModule,
})}</p>`,
buttons: {
ok: {
icon: '<i class="fas fa-check"></i>',
label: game.i18n.localize("drag-ruler.dependencies.ok"),
callback: () =>
game.settings.set(settingsKey, "lastTerrainRulerHintTime", Date.now()),
},
neverShowAgain: {
icon: '<i class="fas fa-times"></i>',
label: game.i18n.localize("drag-ruler.dependencies.terrain-ruler.neverShowAgain"),
callback: () => game.settings.set(settingsKey, "neverShowTerrainRulerHint", true),
},
},
close: () => game.settings.set(settingsKey, "lastTerrainRulerHintTime", Date.now()),
}).render(true);
}
}
} }
else {
let hexOffset = canvas.grid.h / 2
if (CONFIG.hexSizeSupport.getAltOrientationFlag(token))
hexOffset *= -1
tokenCenter.y += hexOffset
}
return tokenCenter
} }
+138
View File
@@ -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;
}
}
+35
View File
@@ -0,0 +1,35 @@
// Wrapper to fix a FoundryVTT bug that causes the return values of canvas.grid.grid.getPixelsFromGridPosition to be ordered inconsistently
// https://gitlab.com/foundrynet/foundryvtt/-/issues/4705
export function getPixelsFromGridPosition(xGrid, yGrid) {
if (canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS) {
return canvas.grid.grid.getPixelsFromGridPosition(yGrid, xGrid);
}
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
// https://gitlab.com/foundrynet/foundryvtt/-/issues/4705
export function getGridPositionFromPixels(xPixel, yPixel) {
const [x, y] = canvas.grid.grid.getGridPositionFromPixels(xPixel, yPixel);
if (canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS) return [y, x];
return [x, y];
}
export function getGridPositionFromPixelsObj(o) {
const r = {};
[r.x, r.y] = getGridPositionFromPixels(o.x, o.y);
return r;
}
export function getPixelsFromGridPositionObj(o) {
const r = {};
[r.x, r.y] = getPixelsFromGridPosition(o.x, o.y);
return r;
}
export function getCenterFromGridPositionObj(o) {
const r = getPixelsFromGridPositionObj(o);
[r.x, r.y] = canvas.grid.getCenter(r.x, r.y);
return r;
}
+156 -140
View File
@@ -1,24 +1,39 @@
import {getColorForDistance} from "./main.js" import {getGridPositionFromPixels} from "./foundry_fixes.js";
import {disableSnap, moveWithoutAnimation} from "./keybindings.js";
import {trackRays} from "./movement_tracking.js";
import {recalculate} from "./socket.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 moveTokens(draggedToken, selectedTokens) { export async function moveEntities(draggedEntity, selectedEntities) {
let wasPaused = game.paused; let wasPaused = game.paused;
if (wasPaused && !game.user.isGM) { if (wasPaused && !game.user.isGM) {
ui.notifications.warn(game.i18n.localize("GAME.PausedWarning")); ui.notifications.warn(game.i18n.localize("GAME.PausedWarning"));
return false; return false;
} }
if (!this.visible || !this.destination) return false; if (!this.visible || !this.destination) return false;
if (!draggedToken) return; if (!draggedEntity) return;
// Wait until all scheduled measurements are done
await this.deferredMeasurementPromise;
// Get the movement rays and check collision along each Ray // Get the movement rays and check collision along each Ray
// These rays are center-to-center for the purposes of collision checking // These rays are center-to-center for the purposes of collision checking
const rays = this._getRaysFromWaypoints(this.waypoints, this.destination); const rays = this.constructor.dragRulerGetRaysFromWaypoints(this.waypoints, this.destination);
if (!game.user.isGM) { if (!game.user.isGM && draggedEntity instanceof Token) {
const hasCollision = selectedTokens.some(token => { const hasCollision = selectedEntities.some(token => {
const offset = calculateTokenOffset(token, draggedToken) const offset = calculateEntityOffset(token, draggedEntity);
const offsetRays = rays.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("ERROR.TokenCollide"));
this._endMeasurement(); this._endMeasurement();
@@ -29,50 +44,87 @@ export async function moveTokens(draggedToken, selectedTokens) {
// Execute the movement path. // Execute the movement path.
// Transform each center-to-center ray into a top-left to top-left ray using the prior token offsets. // Transform each center-to-center ray into a top-left to top-left ray using the prior token offsets.
this._state = Ruler.STATES.MOVING; this._state = Ruler.STATES.MOVING;
await Promise.all(selectedTokens.map(token => { await animateEntities.call(this, selectedEntities, draggedEntity, rays, wasPaused);
// Return the promise so we can wait for it outside the loop
const offset = calculateTokenOffset(token, draggedToken)
return animateToken.call(this, token, rays, offset, wasPaused)
}))
// Once all animations are complete we can clear the ruler // Once all animations are complete we can clear the ruler
this._endMeasurement(); if (this.draggedEntity?.id === draggedEntity.id) this._endMeasurement();
} }
// This is a modified version code extracted from Ruler.moveToken from foundry 0.7.9 // This is a modified version code extracted from Ruler.moveToken from foundry 0.7.9
async function animateToken(token, rays, tokenOffset, wasPaused) { async function animateEntities(entities, draggedEntity, draggedRays, wasPaused) {
const offsetRays = rays.map(ray => applyOffsetToRay(ray, tokenOffset)) const newRays = draggedRays.filter(r => !r.isPrevious);
const entityAnimationData = entities.map(entity => {
const entityOffset = calculateEntityOffset(entity, draggedEntity);
const offsetRays = newRays.map(ray => applyOffsetToRay(ray, entityOffset));
// Determine offset relative to the Token top-left. // Determine offset relative to the Token top-left.
// This is important so we can position the token relative to the ruler origin for non-1x1 tokens. // This is important so we can position the token relative to the ruler origin for non-1x1 tokens.
const origin = [this.waypoints[0].x + tokenOffset.x, this.waypoints[0].y + tokenOffset.y] const firstWaypoint = this.waypoints.find(w => !w.isPrevious);
let dx, dy const origin = [firstWaypoint.x + entityOffset.x, firstWaypoint.y + entityOffset.y];
let dx, dy;
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) { if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
dx = token.data.x - origin[0] dx = entity.x - origin[0];
dy = token.data.y - origin[1] dy = entity.y - origin[1];
} } else {
else { dx = entity.x - origin[0];
dx = token.data.x - origin[0] dy = entity.y - origin[1];
dy = token.data.y - origin[1]
} }
token._noAnimate = true; return {entity, rays: offsetRays, dx, dy};
for (let r of offsetRays) { });
const isToken = draggedEntity instanceof Token;
const animate = isToken && !moveWithoutAnimation;
const startWaypoint = animate ? 0 : entityAnimationData[0].rays.length - 1;
// This is a flag of the "Monk's Active Tile Triggers" module that signals that the movement should be cancelled early
this.cancelMovement = false;
for (let i = startWaypoint; i < entityAnimationData[0].rays.length; i++) {
if (!wasPaused && game.paused) break; if (!wasPaused && game.paused) break;
const dest = [r.B.x, r.B.y]; const entityPaths = entityAnimationData.map(({entity, rays, dx, dy}) => {
const path = new Ray({ x: token.x, y: token.y }, { x: dest[0] + dx, y: dest[1] + dy }); const ray = rays[i];
await token.update(path.B); const dest = [ray.B.x, ray.B.y];
await token.animateMovement(path); const path = new Ray({x: entity.x, y: entity.y}, {x: dest[0] + dx, y: dest[1] + dy});
return {entity, path};
});
const updates = entityPaths.map(({entity, path}) => {
return {x: path.B.x, y: path.B.y, _id: entity.id};
});
await draggedEntity.scene.updateEmbeddedDocuments(
draggedEntity.constructor.embeddedName,
updates,
{animate},
);
if (animate)
await Promise.all(
entityPaths.map(({entity}) => 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
if (this.cancelMovement) {
entityAnimationData.forEach(ead => (ead.rays = ead.rays.slice(0, i + 1)));
break;
} }
token._noAnimate = false; }
if (isToken)
trackRays(
entities,
entityAnimationData.map(({rays}) => rays),
).then(() => recalculate(entities));
} }
function calculateTokenOffset(tokenA, tokenB) { function calculateEntityOffset(entityA, entityB) {
return {x: tokenA.data.x - tokenB.data.x, y: tokenA.data.y - tokenB.data.y} return {x: entityA.x - entityB.x, y: entityA.y - entityB.y};
} }
function applyOffsetToRay(ray, offset) { function applyOffsetToRay(ray, offset) {
return new Ray({x: ray.A.x + offset.x, y: ray.A.y + offset.y}, {x: ray.B.x + offset.x, y: ray.B.y + offset.y}) const newRay = new Ray(
{x: ray.A.x + offset.x, y: ray.A.y + offset.y},
{x: ray.B.x + offset.x, y: ray.B.y + offset.y},
);
newRay.isPrevious = ray.isPrevious;
return newRay;
} }
// This is a modified version of Ruler._onMouseMove from foundry 0.7.9 // This is a modified version of Ruler._onMouseMove from foundry 0.7.9
@@ -80,140 +132,104 @@ export function onMouseMove(event) {
if (this._state === Ruler.STATES.MOVING) return; if (this._state === Ruler.STATES.MOVING) return;
// Extract event data // Extract event data
const mt = event._measureTime || 0; const destination = {
const originalEvent = event.data.originalEvent; x: event.interactionData.destination.x + this.rulerOffset.x,
const destination = {x: event.data.destination.x + this.rulerOffset.x, y: event.data.destination.y + this.rulerOffset.y} y: event.interactionData.destination.y + this.rulerOffset.y,
};
// Hide any existing Token HUD // Hide any existing Token HUD
canvas.hud.token.clear(); canvas.hud.token.clear();
delete event.data.hudState; delete event.data.hudState;
// Draw measurement updates // Draw measurement updates
if (Date.now() - mt > 50) { scheduleMeasurement.call(this, destination, event);
this.measure(destination, {snap: !originalEvent.shiftKey}); }
function scheduleMeasurement(destination, event) {
const measurementInterval = 50;
const mt = event._measureTime || 0;
const originalEvent = event.interactionData.originalEvent;
if (Date.now() - mt > measurementInterval) {
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);
} else {
this.deferredMeasurementData = {destination, event};
if (!this.deferredMeasurementTimeout) {
this.deferredMeasurementPromise = new Promise(
(resolve, reject) => (this.deferredMeasurementResolve = resolve),
);
this.deferredMeasurementTimeout = window.setTimeout(
() =>
scheduleMeasurement.call(
this,
this.deferredMeasurementData.destination,
this.deferredMeasurementData.event,
),
measurementInterval,
);
}
} }
} }
// This is a modified version of Ruler.measure form foundry 0.7.9 export function cancelScheduledMeasurement() {
export function measure(destination, {gridSpaces=true, snap=false} = {}) { window.clearTimeout(this.deferredMeasurementTimeout);
if (this.isDragRuler && !this.draggedToken.isVisible) this.deferredMeasurementTimeout = undefined;
return [] this.deferredMeasurementResolve?.();
if (snap)
destination = new PIXI.Point(...canvas.grid.getCenter(destination.x, destination.y));
const waypoints = this.waypoints.concat([destination]);
const centeredWaypoints = waypoints.map(w => new PIXI.Point(...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)
if (ray.distance < 10) {
if (label) label.visible = false;
continue;
}
segments.push({ ray, label });
centeredSegments.push({ray: centeredRay, label})
}
// Compute measured distance
const distances = canvas.grid.measureDistances(centeredSegments, { gridSpaces });
let totalDistance = 0;
for (let [i, d] of distances.entries()) {
let s = segments[i];
s.startDistance = totalDistance
totalDistance += d;
s.last = i === (segments.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 (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS)
rulerColor = getColorForDistance.call(this, totalDistance)
else
rulerColor = this.color
for (let s of segments) {
const { ray, label, text, last } = s;
// Draw line segment
r.lineStyle(6, 0x000000, 0.5).moveTo(ray.A.x, ray.A.y).lineTo(ray.B.x, ray.B.y)
.lineStyle(4, rulerColor, 0.25).moveTo(ray.A.x, ray.A.y).lineTo(ray.B.x, 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 = ray.project((ray.distance + 50) / ray.distance);
label.position.set(labelPosition.x, labelPosition.y);
}
// Highlight grid positions
highlightMeasurementNative.call(this, ray, s.startDistance);
}
// 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) { export function highlightMeasurementNative(
const spacer = canvas.scene.data.gridType === CONST.GRID_TYPES.SQUARE ? 1.41 : 1; ray,
const nMax = Math.max(Math.floor(ray.distance / (spacer * Math.min(canvas.grid.w, canvas.grid.h))), 1); previousSegments,
const tMax = Array.fromRange(nMax+1).map(t => t / nMax); tokenShape = [{x: 0, y: 0}],
alpha = 1,
) {
const spacer = canvas.scene.grid.type === 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 // Track prior position
let prior = null; let prior = null;
// Iterate over ray portions // Iterate over ray portions
for ( let [i, t] of tMax.entries() ) { for (let [i, t] of tMax.reverse().entries()) {
let {x, y} = ray.project(t); let {x, y} = ray.project(t);
// Get grid position // Get grid position
let [x0, y0] = (i === 0) ? [null, null] : prior; let [x0, y0] = i === 0 ? [null, null] : prior;
let [x1, y1] = canvas.grid.grid.getGridPositionFromPixels(x, y); let [x1, y1] = canvas.grid.grid.getGridPositionFromPixels(x, y);
if ( x0 === x1 && y0 === y1 ) continue; if (x0 === x1 && y0 === y1) continue;
// Highlight the grid position // Highlight the grid position
let [xg, yg] = canvas.grid.grid.getPixelsFromGridPosition(x1, y1); let [xgtl, ygtl] = canvas.grid.grid.getPixelsFromGridPosition(x1, y1);
let subDistance = canvas.grid.measureDistances([{ray: new Ray(ray.A, {x: xg, y: yg})}], {gridSpaces: true})[0] let [xg, yg] = canvas.grid.grid.getCenter(xgtl, ygtl);
let color = dragRuler.getColorForDistance.call(this, startDistance, subDistance) const pathUntilSpace = previousSegments.concat([{ray: new Ray(ray.A, {x: xg, y: yg})}]);
canvas.grid.highlightPosition(this.name, {x: xg, y: yg, color: color}); const distance = sum(canvas.grid.measureDistances(pathUntilSpace, {gridSpaces: true}));
const color = this.dragRulerGetColorForDistance(distance);
const snapPoint = getSnapPointForToken(...canvas.grid.getTopLeft(x, y), this.draggedEntity);
const [snapX, snapY] = getGridPositionFromPixels(snapPoint.x + 1, snapPoint.y + 1);
// Skip the first one
prior = [x1, y1]; prior = [x1, y1];
if ( i === 0 ) continue;
// If the positions are not neighbors, also highlight their halfway point // If the positions are not neighbors, also highlight their halfway point
if ( !canvas.grid.isNeighbor(x0, y0, x1, y1) ) { if (i > 0 && !canvas.grid.isNeighbor(x0, y0, x1, y1)) {
let th = tMax[i - 1] + (0.5 / nMax); let th = tMax[i - 1] - 0.5 / nMax;
let {x, y} = ray.project(th); let {x, y} = ray.project(th);
let [x1h, y1h] = canvas.grid.grid.getGridPositionFromPixels(x, y); let [x1h, y1h] = canvas.grid.grid.getGridPositionFromPixels(x, y);
let [xgh, ygh] = canvas.grid.grid.getPixelsFromGridPosition(x1h, y1h); let [xghtl, yghtl] = canvas.grid.grid.getPixelsFromGridPosition(x1h, y1h);
subDistance = canvas.grid.measureDistances([{ray: new Ray(ray.A, {x: xg, y: yg})}], {gridSpaces: true})[0] let [xgh, ygh] = canvas.grid.grid.getCenter(xghtl, yghtl);
color = dragRuler.getColorForDistance.call(this, startDistance, subDistance) const pathUntilSpace = previousSegments.concat([{ray: new Ray(ray.A, {x: xgh, y: ygh})}]);
canvas.grid.highlightPosition(this.name, {x: xgh, y: ygh, color: color}); const distance = sum(canvas.grid.measureDistances(pathUntilSpace, {gridSpaces: true}));
const color = this.dragRulerGetColorForDistance(distance);
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);
} }
} }
+21
View File
@@ -0,0 +1,21 @@
export class Line {
constructor(m, b) {
this.m = m;
this.b = b;
}
static fromPoints(p1, p2) {
// Bring line into y=mx+b form
const m = (p1.y - p2.y) / (p1.x - p2.x);
const b = p1.y - m * p1.x;
return new Line(m, b);
}
get isVertical() {
return !isFinite(this.m);
}
calcY(x) {
return this.m * x + this.b;
}
}
+90
View File
@@ -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.grid;
if (grid.columnar) {
return findSnapPointCols(x, y, grid.h, grid.w, altOrientationFlag);
} else {
return findSnapPointRows(x, y, grid.h, grid.w, 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};
}
+145
View File
@@ -0,0 +1,145 @@
import {settingsKey} from "./settings.js";
import {getMeasurePosition, setSnapParameterOnOptions} from "./util.js";
// Indicates whether the user is currently pressing the button to disable snapping
export let disableSnap = false;
// Indicates whether the user is currently pressing the button to disable the movement animation
export let moveWithoutAnimation = false;
// Indicates whether the user is pressing the pathfinding toggle button
export let togglePathfinding = false;
export function registerKeybindings() {
game.keybindings.register(settingsKey, "cancelDrag", {
name: "drag-ruler.keybindings.cancelDrag",
onDown: cancelDrag,
uneditable: [
{
key: "Escape",
},
],
precedence: -1,
});
game.keybindings.register(settingsKey, "createWaypoint", {
name: "drag-ruler.keybindings.createWaypoint",
onDown: handleCreateWaypoint,
editable: [
{
key: "Space",
},
],
precedence: -1,
});
game.keybindings.register(settingsKey, "deleteWaypoint", {
name: "drag-ruler.keybindings.deleteWaypoint",
onDown: handleDeleteWaypoint,
precedence: -1,
});
game.keybindings.register(settingsKey, "disableSnap", {
name: "drag-ruler.keybindings.disableSnap.name",
hint: "drag-ruler.keybindings.disableSnap.hint",
onDown: handleDisableSnap,
onUp: handleDisableSnap,
editable: [
{
key: "ShiftLeft",
},
],
precedence: -1,
});
game.keybindings.register(settingsKey, "moveWithoutAnimation", {
name: "drag-ruler.keybindings.moveWithoutAnimation.name",
hint: "drag-ruler.keybindings.moveWithoutAnimation.hint",
onDown: handleMoveWithoutAnimation,
onUp: handleMoveWithoutAnimation,
editable: [
{
key: "AltLeft",
},
],
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() {
const ruler = canvas.controls.ruler;
if (!ruler?.draggedEntity) return false;
ruler.dragRulerDeleteWaypoint();
return true;
}
function handleCreateWaypoint() {
const ruler = canvas.controls.ruler;
// .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
if (!ruler?.draggedEntity) return false;
let options = {};
setSnapParameterOnOptions(ruler, options);
if (ruler._state === Ruler.STATES.INACTIVE) {
ruler.dragRulerStart(options);
} else {
ruler.dragRulerAddWaypoint(getMeasurePosition(), options);
}
return true;
}
function cancelDrag() {
const ruler = canvas.controls.ruler;
if (!ruler?.draggedEntity) return false;
ruler.dragRulerAbortDrag();
return true;
}
function handleDisableSnap(event) {
disableSnap = !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 handleMoveWithoutAnimation(event) {
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;
}
+96
View File
@@ -0,0 +1,96 @@
// SPDX-License-Identifier: MIT
// Copyright © 2021 fvtt-lib-wrapper Rui Pinheiro
"use strict";
// A shim for the libWrapper library
export let libWrapper = undefined;
export const VERSIONS = [1, 11, 0];
export const TGT_SPLIT_RE = new RegExp(
"([^.[]+|\\[('([^'\\\\]|\\\\.)+?'|\"([^\"\\\\]|\\\\.)+?\")\\])",
"g",
);
export const TGT_CLEANUP_RE = new RegExp("(^\\['|'\\]$|^\\[\"|\"\\]$)", "g");
// Main shim code
Hooks.once("init", () => {
// Check if the real module is already loaded - if so, use it
if (globalThis.libWrapper && !(globalThis.libWrapper.is_fallback ?? true)) {
libWrapper = globalThis.libWrapper;
return;
}
// Fallback implementation
libWrapper = class {
static get is_fallback() {
return true;
}
static get WRAPPER() {
return "WRAPPER";
}
static get MIXED() {
return "MIXED";
}
static get OVERRIDE() {
return "OVERRIDE";
}
static register(package_id, target, fn, type = "MIXED", {chain = undefined} = {}) {
const is_setter = target.endsWith("#set");
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 root_nm = split.splice(0, 1)[0];
let obj, fn_name;
if (split.length == 0) {
obj = globalThis;
fn_name = root_nm;
} else {
const _eval = eval;
fn_name = split.pop();
obj = split.reduce((x, y) => x[y], globalThis[root_nm] ?? _eval(root_nm));
}
let iObj = obj;
let descriptor = null;
while (iObj) {
descriptor = Object.getOwnPropertyDescriptor(iObj, fn_name);
if (descriptor) break;
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.`;
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);
};
if (!is_setter) {
if (descriptor.value) {
original = descriptor.value;
descriptor.value = wrapper;
} else {
original = descriptor.get;
descriptor.get = wrapper;
}
} else {
if (!descriptor.set) throw `libWrapper Shim: '${target}' does not have a setter`;
original = descriptor.set;
descriptor.set = wrapper;
}
descriptor.configurable = true;
Object.defineProperty(obj, fn_name, descriptor);
}
};
});
+247 -227
View File
@@ -1,259 +1,279 @@
"use strict" "use strict";
import {currentSpeedProvider, getRangesFromSpeedProvider, getUnreachableColorFromSpeedProvider, initApi, registerModule, registerSystem} from "./api.js" import {
import {getHexSizeSupportTokenGridCenter} from "./compatibility.js" getColorForDistanceAndToken,
import {measure, moveTokens, onMouseMove} from "./foundry_imports.js" getMovedDistanceFromToken,
import {performMigrations} from "./migration.js" getRangesFromSpeedProvider,
import {registerSettings, settingsKey} from "./settings.js" initApi,
import {SpeedProvider} from "./speed_provider.js"
Hooks.once("init", () => {
registerSettings()
initApi()
hookTokenDragHandlers()
hookRulerFunctions()
hookKeyboardManagerFunctions()
window.dragRuler = {
getColorForDistance,
registerModule, registerModule,
registerSystem, registerSystem,
} } from "./api.js";
}) import {checkDependencies} from "./compatibility.js";
import {moveEntities, onMouseMove} from "./foundry_imports.js";
import {disableSnap, registerKeybindings} from "./keybindings.js";
import {libWrapper} from "./libwrapper_shim.js";
import {performMigrations} from "./migration.js";
import {removeLastHistoryEntryIfAt, resetMovementHistory} from "./movement_tracking.js";
import {extendRuler} from "./ruler.js";
import {registerSettings, RightClickAction, settingsKey} from "./settings.js";
import {recalculate} from "./socket.js";
import {SpeedProvider} from "./speed_provider.js";
import {getEntityCenter, setSnapParameterOnOptions} from "./util.js";
CONFIG.debug.dragRuler = false;
export let debugGraphics = undefined;
Hooks.once("init", () => {
registerSettings();
registerKeybindings();
initApi();
hookDragHandlers(Token);
hookDragHandlers(MeasuredTemplate);
libWrapper.register(
"drag-ruler",
"TokenLayer.prototype.undoHistory",
tokenLayerUndoHistory,
"WRAPPER",
);
extendRuler();
window.dragRuler = {
getRangesFromSpeedProvider,
getColorForDistanceAndToken,
getMovedDistanceFromToken,
registerModule,
registerSystem,
recalculate,
resetMovementHistory,
};
});
Hooks.once("ready", () => { Hooks.once("ready", () => {
performMigrations() performMigrations();
Hooks.callAll("dragRuler.ready", SpeedProvider) checkDependencies();
}) 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 => {
ruler.draggedToken = null ruler.draggedEntity = null;
Object.defineProperty(ruler, "isDragRuler", { Object.defineProperty(ruler, "isDragRuler", {
get: function isDragRuler() { get: function isDragRuler() {
return Boolean(this.draggedToken) // If draggedToken is set this is a drag ruler return Boolean(this.draggedEntity) && this._state !== Ruler.STATES.INACTIVE;
} },
}) });
}) });
}) });
function hookTokenDragHandlers() { Hooks.on("getCombatTrackerEntryContext", function (html, menu) {
const originalDragLeftStartHandler = Token.prototype._onDragLeftStart const entry = {
Token.prototype._onDragLeftStart = function(event) { name: "drag-ruler.resetMovementHistory",
originalDragLeftStartHandler.call(this, event) icon: '<i class="fas fa-undo-alt"></i>',
onTokenLeftDragStart.call(this, event) callback: li => resetMovementHistory(ui.combat.viewed, li.data("combatant-id")),
} };
menu.splice(1, 0, entry);
});
const originalDragLeftMoveHandler = Token.prototype._onDragLeftMove function forwardIfUnahndled(newFn) {
Token.prototype._onDragLeftMove = function (event) { return function (oldFn, ...args) {
originalDragLeftMoveHandler.call(this, event) const eventHandled = newFn(...args);
onTokenLeftDragMove.call(this, event) if (!eventHandled) oldFn(...args);
} };
const originalDragLeftDropHandler = Token.prototype._onDragLeftDrop
Token.prototype._onDragLeftDrop = function (event) {
const eventHandled = onTokenDragLeftDrop.call(this, event)
if (!eventHandled)
originalDragLeftDropHandler.call(this, event)
}
const originalDragLeftCancelHandler = Token.prototype._onDragLeftCancel
Token.prototype._onDragLeftCancel = function (event) {
const eventHandled = onTokenDragLeftCancel.call(this, event)
if (!eventHandled)
originalDragLeftCancelHandler.call(this, event)
}
} }
function hookRulerFunctions() { function hookDragHandlers(entityType) {
const originalMoveTokenHandler = Ruler.prototype.moveToken const entityName = entityType.name;
Ruler.prototype.moveToken = function (event) { libWrapper.register(
const eventHandled = onRulerMoveToken.call(this, event) "drag-ruler",
if (!eventHandled) `${entityName}.prototype._onDragLeftStart`,
return originalMoveTokenHandler.call(this, event) onEntityLeftDragStart,
return true "WRAPPER",
} );
if (entityType === Token)
const originalToJSON = Ruler.prototype.toJSON libWrapper.register(
Ruler.prototype.toJSON = function () { "drag-ruler",
const json = originalToJSON.call(this) `${entityName}.prototype._onDragLeftMove`,
if (this.draggedToken) onEntityLeftDragMoveSnap,
json["draggedToken"] = this.draggedToken.data._id "WRAPPER",
return json );
}
const originalUpdate = Ruler.prototype.update
Ruler.prototype.update = function (data) {
if (data.draggedToken) {
this.draggedToken = canvas.tokens.get(data.draggedToken)
}
originalUpdate.call(this, data)
}
const originalMeasure = Ruler.prototype.measure
Ruler.prototype.measure = function (destination, options={}) {
if (this.isDragRuler) {
return measure.call(this, destination, options)
}
else {
return originalMeasure.call(this, destination, options)
}
}
}
function hookKeyboardManagerFunctions() {
const originalHandleKeys = KeyboardManager.prototype._handleKeys
KeyboardManager.prototype._handleKeys = function (event, key, up) {
const eventHandled = handleKeys.call(this, event, key, up)
if (!eventHandled)
originalHandleKeys.call(this, event, key, up)
}
}
function handleKeys(event, key, up) {
if (event.repeat || this.hasFocus)
return false
if (key.toLowerCase() === "x") return onKeyX(up)
if (key.toLowerCase() === "shift") return onKeyShift(up)
return false
}
function onKeyX(up) {
if (up)
return false
if (!canvas.controls.ruler.isDragRuler)
return false
deleteWaypoint()
return true
}
function onKeyShift(up) {
const ruler = canvas.controls.ruler
if (!ruler.isDragRuler)
return false
const mousePosition = canvas.app.renderer.plugins.interaction.mouse.getLocalPosition(canvas.tokens)
const rulerOffset = ruler.rulerOffset
const measurePosition = {x: mousePosition.x + rulerOffset.x, y: mousePosition.y + rulerOffset.y}
ruler.measure(measurePosition, {snap: up})
}
function onTokenLeftDragStart(event) {
if (!currentSpeedProvider.usesRuler(this))
return
const ruler = canvas.controls.ruler
ruler.draggedToken = this
let tokenCenter
if (canvas.grid.isHex && game.modules.get("hex-size-support")?.active && CONFIG.hexSizeSupport.getAltSnappingFlag(this))
tokenCenter = getHexSizeSupportTokenGridCenter(this)
else else
tokenCenter = {x: this.x + canvas.grid.grid.w / 2, y: this.y + canvas.grid.grid.h / 2} libWrapper.register(
ruler.clear(); "drag-ruler",
ruler._state = Ruler.STATES.STARTING; `${entityName}.prototype._onDragLeftMove`,
ruler.rulerOffset = {x: tokenCenter.x - event.data.origin.x, y: tokenCenter.y - event.data.origin.y} onEntityLeftDragMove,
addWaypoint.call(ruler, tokenCenter, false) "WRAPPER",
);
libWrapper.register(
"drag-ruler",
`${entityName}.prototype._onDragLeftDrop`,
forwardIfUnahndled(onEntityDragLeftDrop),
"MIXED",
);
libWrapper.register(
"drag-ruler",
`${entityName}.prototype._onDragLeftCancel`,
forwardIfUnahndled(onEntityDragLeftCancel),
"MIXED",
);
} }
function onTokenLeftDragMove(event) { async function tokenLayerUndoHistory(wrapped) {
const ruler = canvas.controls.ruler const historyEntry = this.history[this.history.length - 1];
if (ruler.isDragRuler) const returnValue = await wrapped();
onMouseMove.call(ruler, event) if (historyEntry.type === "update") {
for (const entry of historyEntry.data) {
const token = canvas.tokens.get(entry._id);
removeLastHistoryEntryIfAt(token, entry.x, entry.y);
}
}
return returnValue;
} }
function onTokenDragLeftDrop(event) { function onEntityLeftDragStart(wrapped, event) {
const ruler = canvas.controls.ruler wrapped(event);
if (!ruler.isDragRuler) const isToken = this instanceof Token;
return false const ruler = canvas.controls.ruler;
const selectedTokens = canvas.tokens.controlled ruler.draggedEntity = this;
moveTokens.call(ruler, ruler.draggedToken, selectedTokens) const entityCenter = getEntityCenter(this);
ruler.draggedToken = null ruler.rulerOffset = {
return true x: entityCenter.x - event.interactionData.origin.x,
y: entityCenter.y - event.interactionData.origin.y,
};
if (game.settings.get(settingsKey, "autoStartMeasurement")) {
let options = {};
setSnapParameterOnOptions(ruler, options);
ruler.dragRulerStart(options, false);
}
} }
function onTokenDragLeftCancel(event) { function onEntityLeftDragMoveSnap(wrapped, event) {
applyGridlessSnapping.call(this, event);
onEntityLeftDragMove.call(this, wrapped, event);
}
function onEntityLeftDragMove(wrapped, event) {
wrapped(event);
const ruler = canvas.controls.ruler;
if (ruler.isDragRuler) onMouseMove.call(ruler, event);
}
function onEntityDragLeftDrop(event) {
const ruler = canvas.controls.ruler;
if (!ruler.isDragRuler) {
ruler.draggedEntity = undefined;
return false;
}
// When we're dragging a measured template no token will ever be selected,
// resulting in only the dragged template to be moved as would be expected
const selectedTokens = canvas.tokens.controlled;
// This can happen if the user presses ESC during drag (maybe there are other ways too)
if (selectedTokens.length === 0) 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
if (ruler._state === Ruler.STATES.STARTING) onMouseMove.call(ruler, event);
ruler._state = Ruler.STATES.MOVING;
moveEntities.call(ruler, ruler.draggedEntity, selectedTokens);
return true;
}
function onEntityDragLeftCancel(event) {
// This function is invoked by right clicking // This function is invoked by right clicking
const ruler = canvas.controls.ruler const ruler = canvas.controls.ruler;
if (!ruler.isDragRuler) if (!ruler.draggedEntity || ruler._state === Ruler.STATES.MOVING) return false;
return false
if (ruler._state === Ruler.STATES.MEASURING) { const rightClickAction = game.settings.get(settingsKey, "rightClickAction");
if (!game.settings.get(settingsKey, "swapSpacebarRightClick")) { let options = {};
if (ruler.waypoints.length > 1) setSnapParameterOnOptions(ruler, options);
event.preventDefault()
deleteWaypoint() if (ruler._state === Ruler.STATES.INACTIVE) {
} if (rightClickAction !== RightClickAction.CREATE_WAYPOINT) return false;
else { ruler.dragRulerStart(options);
event.preventDefault() event.preventDefault();
const snap = !event.shiftKey } else if (ruler._state === Ruler.STATES.MEASURING) {
addWaypoint.call(ruler, ruler.destination, snap) switch (rightClickAction) {
case RightClickAction.CREATE_WAYPOINT:
event.preventDefault();
ruler.dragRulerAddWaypoint(ruler.destination, options);
break;
case RightClickAction.DELETE_WAYPOINT:
ruler.dragRulerDeleteWaypoint(event, options);
break;
case RightClickAction.ABORT_DRAG:
ruler.dragRulerAbortDrag();
break;
} }
} }
return true return true;
} }
function onRulerMoveToken(event) { function applyGridlessSnapping(event) {
// This function is invoked by left clicking const ruler = canvas.controls.ruler;
if (!this.isDragRuler) if (!game.settings.get(settingsKey, "useGridlessRaster")) return;
return false if (!ruler.isDragRuler) return;
if (!game.settings.get(settingsKey, "swapSpacebarRightClick")) { if (disableSnap) return;
const snap = !event.shiftKey if (canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS) return;
addWaypoint.call(this, this.destination, snap)
const rasterWidth = 35 / canvas.stage.scale.x;
const tokenX = event.interactionData.destination.x;
const tokenY = event.interactionData.destination.y;
const destination = {x: tokenX + ruler.rulerOffset.x, y: tokenY + ruler.rulerOffset.y};
const ranges = getRangesFromSpeedProvider(ruler.draggedEntity);
const terrainRulerAvailable = game.modules.get("terrain-ruler")?.active;
if (terrainRulerAvailable) {
const segments = ruler.constructor
.dragRulerGetRaysFromWaypoints(ruler.waypoints, destination)
.map(ray => {
return {ray};
});
const pinpointDistances = new Map();
for (const range of ranges) {
pinpointDistances.set(range.range, null);
} }
else terrainRuler.measureDistances(segments, {pinpointDistances});
deleteWaypoint() const targetDistance = Array.from(pinpointDistances.entries())
return true .filter(([_key, val]) => val)
} .reduce((value, current) => (value[0] > current[0] ? value : current), [0, null]);
const rasterLocation = targetDistance[1];
function addWaypoint(point, snap=true) { if (rasterLocation) {
if (snap) const deltaX = destination.x - rasterLocation.x;
point = canvas.grid.getCenter(point.x, point.y); const deltaY = destination.y - rasterLocation.y;
else const rasterDistance = Math.hypot(deltaX, deltaY);
point = [point.x, point.y] if (rasterDistance < rasterWidth) {
this.waypoints.push(new PIXI.Point(point[0], point[1])); event.interactionData.destination.x = rasterLocation.x - ruler.rulerOffset.x;
this.labels.addChild(new PreciseText("", CONFIG.canvasTextStyle)); event.interactionData.destination.y = rasterLocation.y - ruler.rulerOffset.y;
} }
}
function deleteWaypoint() { } else {
const ruler = canvas.controls.ruler let waypointDistance = 0;
let origin = event.interactionData.origin;
if (ruler.waypoints.length > 1) { if (ruler.waypoints.length > 1) {
const mousePosition = canvas.app.renderer.plugins.interaction.mouse.getLocalPosition(canvas.tokens) const segments = ruler.constructor
const rulerOffset = ruler.rulerOffset .dragRulerGetRaysFromWaypoints(ruler.waypoints, destination)
ruler._removeWaypoint({x: mousePosition.x + rulerOffset.x, y: mousePosition.y + rulerOffset.y}) .map(ray => {
game.user.broadcastActivity({ruler: ruler}) return {ray};
});
origin = segments.pop().ray.A;
waypointDistance = canvas.grid.measureDistances(segments).reduce((a, b) => a + b);
origin = {x: origin.x - ruler.rulerOffset.x, y: origin.y - ruler.rulerOffset.y};
} }
else {
const token = ruler.draggedToken
ruler._endMeasurement()
ruler.draggedToken = null
// Deactivate the drag workflow in mouse const deltaX = tokenX - origin.x;
token.mouseInteractionManager._deactivateDragEvents(); const deltaY = tokenY - origin.y;
token.mouseInteractionManager.state = token.mouseInteractionManager.states.HOVER; const distance = Math.hypot(deltaX, deltaY);
// targetRange will be the largest range that's still smaller than distance
// This will cancel the current drag operation let targetDistance = ranges
// Pass in a fake event that hopefully is enough to allow other modules to function .map(range => range.range)
token._onDragLeftCancel({preventDefault: () => {return}}) .map(range => range - waypointDistance)
.map(range => (range * canvas.dimensions.size) / canvas.dimensions.distance)
.filter(range => range < distance)
.reduce((a, b) => Math.max(a, b), 0);
if (targetDistance) {
if (distance < targetDistance + rasterWidth) {
event.interactionData.destination.x = origin.x + (deltaX * targetDistance) / distance;
event.interactionData.destination.y = origin.y + (deltaY * targetDistance) / distance;
}
}
} }
} }
export function getColorForDistance(startDistance, subDistance=0) {
if (!this.isDragRuler)
return this.color
// Don't apply colors if the current user doesn't have at least observer permissions
if (this.draggedToken.actor.permission < 2) {
// If this is a pc and alwaysShowSpeedForPCs is enabled we show the color anyway
if (!(this.draggedToken.actor.data.type === "character" && game.settings.get(settingsKey, "alwaysShowSpeedForPCs")))
return this.color
}
const distance = startDistance + subDistance
const ranges = getRangesFromSpeedProvider(this.draggedToken)
if (ranges.length === 0)
return this.color
const currentRange = ranges.reduce((minRange, currentRange) => {
if (distance <= currentRange.range && currentRange.range < minRange.range)
return currentRange
return minRange
}, {range: Infinity, color: getUnreachableColorFromSpeedProvider()})
return currentRange.color
}
+36 -20
View File
@@ -1,27 +1,43 @@
import {settingsKey} from "./settings.js" import {RightClickAction, settingsKey} from "./settings.js";
const currentDataVersion = "1.3.0" const currentDataVersion = "1.10.0";
export function performMigrations() { export async function performMigrations() {
if (!game.user.isGM) if (game.user.isGM) await performWorldMigraionts();
return await performClientMigrations();
}
async function performWorldMigraionts() {
let dataVersion = game.settings.get(settingsKey, "dataVersion");
if (dataVersion === currentDataVersion) return;
let dataVersion = game.settings.get(settingsKey, "dataVersion")
if (dataVersion === "fresh install") { if (dataVersion === "fresh install") {
// Migration from unnamed version. TODO Remove this in a future version game.settings.set(settingsKey, "dataVersion", currentDataVersion);
let speedAttribute = game.settings.storage.get("world").get(`${settingsKey}.speedAttribute`) return;
if (speedAttribute) }
speedAttribute = speedAttribute.slice(1, speedAttribute.length - 1)
const speedAttributeDefault = game.settings.get(settingsKey, "speedProviders.native.setting.speedAttribute")
if (speedAttribute !== speedAttributeDefault)
game.settings.set(settingsKey, "speedProviders.native.setting.speedAttribute", speedAttribute)
let dashMultiplier = game.settings.storage.get("world").get(`${settingsKey}.dashMultiplier`) if (dataVersion === "1.3.0") {
const dashMultiplierDefault = game.settings.get(settingsKey, "speedProviders.native.setting.dashMultiplier") dataVersion = "1.10.0";
if (dashMultiplier !== dashMultiplierDefault) }
game.settings.set(settingsKey, "speedProviders.native.setting.dashMultiplier", dashMultiplier)
// End of unnamed version migration code game.settings.set(settingsKey, "dataVersion", dataVersion);
game.settings.set(settingsKey, "dataVersion", currentDataVersion) }
return
async function performClientMigrations() {
let dataVersion = game.settings.get(settingsKey, "clientDataVersion");
if (dataVersion === "fresh install") {
// 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`);
if (swapSpacebarRightClick) {
game.settings.set(settingsKey, "rightClickAction", RightClickAction.CREATE_WAYPOINT);
await game.keybindings.set(settingsKey, "createWaypoint", []);
await game.keybindings.set(settingsKey, "deleteWaypoint", [{key: "Space"}]);
}
// End of migration from unnamed version
game.settings.set(settingsKey, "clientDataVersion", currentDataVersion);
} }
} }
+104
View File
@@ -0,0 +1,104 @@
import {measureDistances} from "./compatibility.js";
import {recalculate, updateCombatantDragRulerFlags} from "./socket.js";
import {getTokenShape, isClose, zip} from "./util.js";
function initTrackingFlag(combatant) {
const initialFlag = {passedWaypoints: [], trackedRound: 0};
let dragRulerFlag = combatant.flags.dragRuler;
if (dragRulerFlag) {
if (isNaN(dragRulerFlag.trackedRound)) {
mergeObject(dragRulerFlag, initialFlag);
}
} else {
combatant.flags.dragRuler = initialFlag;
}
}
function getInitializedCombatant(token, combat) {
const combatant = combat.getCombatantByToken(token.id);
if (!combatant) return undefined;
initTrackingFlag(combatant);
return combatant;
}
export async function trackRays(tokens, tokenRays) {
const combat = game.combat;
if (!combat) return;
if (!combat.started) return;
if (!(tokens instanceof Array)) {
tokens = [tokens];
tokenRays = [tokenRays];
}
const updates = Array.from(zip(tokens, tokenRays))
.map(([token, rays]) => calculateUpdate(combat, token, rays))
.filter(Boolean);
await updateCombatantDragRulerFlags(combat, updates);
}
function calculateUpdate(combat, token, rays) {
const combatant = getInitializedCombatant(token, combat);
if (!combatant) return;
// Check if we have entered a new round. If so, remove the currently stored path
if (combat.round > combatant.flags.dragRuler.trackedRound) {
combatant.flags.dragRuler.passedWaypoints = [];
combatant.flags.dragRuler.trackedRound = combat.round;
}
// Add the passed waypoints to the combatant
const terrainRulerAvailable = game.modules.get("terrain-ruler")?.active;
const dragRulerFlags = combatant.flags.dragRuler;
const waypoints = dragRulerFlags.passedWaypoints;
for (const ray of rays) {
// Ignore rays that have the same start and end coordinates
if (ray.A.x !== ray.B.x || ray.A.y !== ray.B.y) {
if (terrainRulerAvailable) {
measureDistances([{ray}], token, getTokenShape(token), {
terrainRulerInitialState: waypoints[waypoints.length - 1]?.dragRulerFinalState,
enableTerrainRuler: terrainRulerAvailable,
});
ray.A.dragRulerVisitedSpaces = ray.terrainRulerVisitedSpaces;
ray.A.dragRulerFinalState = ray.terrainRulerFinalState;
}
waypoints.push(ray.A);
}
}
return {_id: combatant.id, dragRulerFlags};
}
export function getMovementHistory(token) {
const combat = game.combat;
if (!combat) return [];
const combatant = combat.getCombatantByToken(token.id);
if (!combatant) return [];
const dragRulerFlags = combatant.flags.dragRuler;
if (!dragRulerFlags) return [];
if (combat.round > dragRulerFlags.trackedRound) return [];
return dragRulerFlags.passedWaypoints ?? [];
}
export async function removeLastHistoryEntryIfAt(token, x, y) {
const history = getMovementHistory(token);
if (history.length === 0) return;
const entry = history[history.length - 1];
if (!isClose(x + token.w / 2, entry.x, 0.1) || !isClose(y + token.h / 2, entry.y, 0.1)) {
return;
}
history.pop();
const combat = game.combat;
const combatant = combat.getCombatantByToken(token.id);
await updateCombatantDragRulerFlags(combat, [
{_id: combatant.id, dragRulerFlags: combatant.flags.dragRuler},
]);
}
export async function resetMovementHistory(combat, combatantId) {
const combatant = combat.combatants.get(combatantId);
const dragRulerFlags = combatant.flags.dragRuler;
if (!dragRulerFlags) return;
dragRulerFlags.passedWaypoints = null;
dragRulerFlags.trackedRound = null;
dragRulerFlags.rulerState = null;
await updateCombatantDragRulerFlags(combat, [{_id: combatantId, dragRulerFlags}]);
recalculate();
}
+495
View File
@@ -0,0 +1,495 @@
import {
currentSpeedProvider,
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 {settingsKey} from "./settings.js";
import {
applyTokenSizeOffset,
getSnapPointForEntity,
getSnapPointForTokenObj,
getEntityCenter,
getTokenShape,
isPathfindingEnabled,
} from "./util.js";
import {getPointer} from "./util.js";
export function extendRuler() {
class DragRulerRuler extends CONFIG.Canvas.rulerClass {
// Functions below are overridden versions of functions in Ruler
constructor(user, {color = null} = {}) {
super(user, {color});
this.previousWaypoints = [];
this.previousLabels = this.addChild(new PIXI.Container());
}
clear() {
super.clear();
this.previousWaypoints = [];
this.previousLabels.removeChildren().forEach(c => c.destroy());
this.dragRulerRanges = undefined;
cancelScheduledMeasurement.call(this);
}
async moveToken(event) {
// Disable moveToken if Drag Ruler is active
if (!this.isDragRuler) return await super.moveToken(event);
}
toJSON() {
const json = super.toJSON();
if (this.draggedEntity) {
const isToken = this.draggedEntity instanceof Token;
json.draggedEntityIsToken = isToken;
json.draggedEntity = this.draggedEntity.id;
json.waypoints = json.waypoints.map(old => {
let w = duplicate(old);
w.isPathfinding = undefined;
return w;
});
}
return json;
}
update(data) {
// 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")
)
return;
if (data.draggedEntity) {
if (data.draggedEntityIsToken) this.draggedEntity = canvas.tokens.get(data.draggedEntity);
else this.draggedEntity = canvas.templates.get(data.draggedEntity);
} else {
this.draggedEntity = undefined;
}
super.update(data);
}
measure(destination, options = {}) {
if (!this.isDragRuler) {
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 (d.x === this.destination.x && d.y === this.destination.y) 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,
);
}
}
}
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)
: 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;
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);
let totalDistance = 0;
for (const [i, d] of distances.entries()) {
let s = this.segments[i];
s.startDistance = totalDistance;
totalDistance += d;
s.last = i === this.segments.length - 1;
s.distance = d;
s.text = this._getSegmentLabel(s, totalDistance);
}
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() {
super._endMeasurement();
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
dragRulerAddWaypoint(point, options = {}) {
options.snap = options.snap ?? true;
if (options.snap) {
point = getSnapPointForEntity(point.x, point.y, this.draggedEntity);
}
this.waypoints.push(new PIXI.Point(point.x, point.y));
this.labels.addChild(new PreciseText("", CONFIG.canvasTextStyle));
this.waypoints
.filter(waypoint => waypoint.isPathfinding)
.forEach(waypoint => (waypoint.isPathfinding = false));
}
dragRulerAddWaypointHistory(waypoints) {
waypoints.forEach(waypoint => (waypoint.isPrevious = true));
this.waypoints = this.waypoints.concat(waypoints);
for (const waypoint of waypoints) {
this.labels.addChild(new PreciseText("", CONFIG.canvasTextStyle));
}
}
dragRulerClearWaypoints() {
this.waypoints = [];
this.labels.removeChildren().forEach(c => c.destroy());
}
dragRulerDeleteWaypoint(
event = {
preventDefault: () => {
return;
},
},
options = {},
) {
this.dragRulerRemovePathfindingWaypoints();
options.snap = options.snap ?? true;
if (this.waypoints.filter(w => !w.isPrevious).length > 1) {
event.preventDefault();
const mousePosition = getPointer().getLocalPosition(canvas.tokens);
const rulerOffset = this.rulerOffset;
// Options are not passed to _removeWaypoint in vanilla Foundry.
// Send them in case other modules have overriden that behavior and accept an options parameter (Toggle Snap to Grid)
this._removeWaypoint(
{x: mousePosition.x + rulerOffset.x, y: mousePosition.y + rulerOffset.y},
options,
);
game.user.broadcastActivity({ruler: this});
} else {
this.dragRulerAbortDrag(event);
}
}
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;
this._endMeasurement();
// Deactivate the drag workflow in mouse
token.mouseInteractionManager.cancel(event);
token.mouseInteractionManager.state = token.mouseInteractionManager.states.HOVER;
// This will cancel the current drag operation
// Pass in a fake event that hopefully is enough to allow other modules to function
token._onDragLeftCancel(event);
}
async dragRulerRecalculate(tokenIds) {
if (this._state !== Ruler.STATES.MEASURING) return;
if (tokenIds && !tokenIds.includes(this.draggedEntity.id)) return;
const waypoints = this.waypoints.filter(waypoint => !waypoint.isPrevious);
this.dragRulerClearWaypoints();
if (game.settings.get(settingsKey, "enableMovementHistory"))
this.dragRulerAddWaypointHistory(getMovementHistory(this.draggedEntity));
for (const waypoint of waypoints) {
this.dragRulerAddWaypoint(waypoint, {snap: false});
}
this.measure(this.destination);
game.user.broadcastActivity({ruler: this});
}
static dragRulerGetRaysFromWaypoints(waypoints, destination) {
if (destination) waypoints = waypoints.concat([destination]);
return waypoints.slice(1).map((wp, i) => {
const ray = new Ray(waypoints[i], wp);
ray.isPrevious = Boolean(waypoints[i].isPrevious);
return ray;
});
}
dragRulerGetColorForDistance(distance) {
if (!this.isDragRuler) return this.color;
if (!this.draggedEntity.actor) {
return this.color;
}
// Don't apply colors if the current user doesn't have at least observer permissions
if (this.draggedEntity.actor.permission < 2) {
// If this is a pc and alwaysShowSpeedForPCs is enabled we show the color anyway
if (
!(
this.draggedEntity.actor.type === "character" &&
game.settings.get(settingsKey, "alwaysShowSpeedForPCs")
)
)
return this.color;
}
distance = Math.round(distance * 100) / 100;
if (!this.dragRulerRanges)
this.dragRulerRanges = getRangesFromSpeedProvider(this.draggedEntity);
return (
getColorForDistanceAndToken(distance, this.draggedEntity, this.dragRulerRanges) ??
this.color
);
}
dragRulerStart(options, measureImmediately = true) {
const entity = this.draggedEntity;
const isToken = entity instanceof Token;
if (isToken && !currentSpeedProvider.usesRuler(entity)) return;
const ruler = canvas.controls.ruler;
ruler.clear();
ruler._state = Ruler.STATES.STARTING;
const entityCenter = getEntityCenter(this.draggedEntity);
if (isToken && game.settings.get(settingsKey, "enableMovementHistory"))
ruler.dragRulerAddWaypointHistory(getMovementHistory(entity));
ruler.dragRulerAddWaypoint(entityCenter, {snap: false});
const mousePosition = getPointer().getLocalPosition(canvas.tokens);
const destination = {
x: mousePosition.x + ruler.rulerOffset.x,
y: mousePosition.y + ruler.rulerOffset.y,
};
if (measureImmediately) ruler.measure(destination, options);
}
dragRulerSendState() {
game.user.broadcastActivity({
ruler: this.toJSON(),
});
}
}
CONFIG.Canvas.rulerClass = DragRulerRuler;
}
+196 -95
View File
@@ -1,24 +1,69 @@
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({
CREATE_WAYPOINT: 0,
DELETE_WAYPOINT: 1,
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, "swapSpacebarRightClick", { game.settings.register(settingsKey, "clientDataVersion", {
name: "drag-ruler.settings.swapSpacebarRightClick.name", scope: "client",
hint: "drag-ruler.settings.swapSpacebarRightClick.hint", config: false,
type: String,
default: "fresh install",
});
game.settings.register(settingsKey, "rightClickAction", {
name: "drag-ruler.settings.rightClickAction.name",
hint: "drag-ruler.settings.rightClickAction.hint",
config: true,
type: Number,
default: RightClickAction.DELETE_WAYPOINT,
choices: {
0: "drag-ruler.settings.rightClickAction.choices.create",
1: "drag-ruler.settings.rightClickAction.choices.delete",
2: "drag-ruler.settings.rightClickAction.choices.cancel",
},
});
game.settings.register(settingsKey, "autoStartMeasurement", {
name: "drag-ruler.settings.autoStartMeasurement.name",
hint: "drag-ruler.settings.autoStartMeasurement.hint",
scope: "client", scope: "client",
config: true, config: true,
type: Boolean, type: Boolean,
default: false, default: true,
}) });
game.settings.register(settingsKey, "useGridlessRaster", {
name: "drag-ruler.settings.useGridlessRaster.name",
hint: "drag-ruler.settings.useGridlessRaster.hint",
scope: "client",
config: true,
type: Boolean,
default: true,
});
game.settings.register(settingsKey, "alwaysShowSpeedForPCs", { game.settings.register(settingsKey, "alwaysShowSpeedForPCs", {
name: "drag-ruler.settings.alwaysShowSpeedForPCs.name", name: "drag-ruler.settings.alwaysShowSpeedForPCs.name",
@@ -27,7 +72,58 @@ export function registerSettings() {
config: true, config: true,
type: Boolean, type: Boolean,
default: true, default: true,
}) });
game.settings.register(settingsKey, "showGMRulerToPlayers", {
name: "drag-ruler.settings.showGMRulerToPlayers.name",
hint: "drag-ruler.settings.showGMRulerToPlayers.hint",
scope: "world",
config: true,
type: Boolean,
default: true,
});
game.settings.register(settingsKey, "enableMovementHistory", {
name: "drag-ruler.settings.enableMovementHistory.name",
hint: "drag-ruler.settings.enableMovementHistory.hint",
scope: "world",
config: true,
type: Boolean,
default: true,
});
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", {
config: false,
type: Number,
default: 0,
});
game.settings.register(settingsKey, "neverShowTerrainRulerHint", {
config: false,
type: Boolean,
default: false,
});
// This setting will be modified by the api if modules register to it // This setting will be modified by the api if modules register to it
game.settings.register(settingsKey, "speedProvider", { game.settings.register(settingsKey, "speedProvider", {
@@ -36,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",
@@ -45,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 {
@@ -55,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;
} return provider;
provider.isSelected = provider.id === selectedProvider });
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",
@@ -99,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 = 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);
} }
+52
View File
@@ -0,0 +1,52 @@
import {currentSpeedProvider} from "./api.js";
let socket;
Hooks.once("socketlib.ready", () => {
socket = socketlib.registerModule("drag-ruler");
socket.register("updateCombatantDragRulerFlags", _socketUpdateCombatantDragRulerFlags);
socket.register("recalculate", _socketRecalculate);
});
export function updateCombatantDragRulerFlags(combat, updates) {
const combatId = combat.id;
// TODO Check if canvas.tokens.get is still neccessary in future foundry versions
return socket
.executeAsGM(_socketUpdateCombatantDragRulerFlags, combatId, updates)
.then(() =>
currentSpeedProvider.onMovementHistoryUpdate(
updates.map(update => canvas.tokens.get(combat.combatants.get(update._id).token.id)),
),
);
}
async function _socketUpdateCombatantDragRulerFlags(combatId, updates) {
const user = game.users.get(this.socketdata.userId);
const combat = game.combats.get(combatId);
const requestedUpdates = updates.length;
updates = updates.filter(update => {
const actor = combat.combatants.get(update._id).actor;
if (!actor) return false;
return actor.testUserPermission(user, "OWNER");
});
if (updates.length !== requestedUpdates) {
console.warn(
`Some of the movement history updates requested by user '${
game.users.get(this.socketdata.userId).name
}' were not performed because the user lacks owner permissions for those tokens`,
);
}
updates = updates.map(update => {
return {_id: update._id, flags: {dragRuler: update.dragRulerFlags}};
});
await combat.updateEmbeddedDocuments("Combatant", updates, {diff: false});
}
export function recalculate(tokens) {
socket.executeForEveryone(_socketRecalculate, tokens ? tokens.map(token => token.id) : undefined);
}
function _socketRecalculate(tokenIds) {
const ruler = canvas.controls.ruler;
if (ruler.isDragRuler) ruler.dragRulerRecalculate(tokenIds);
}
+40 -29
View File
@@ -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,7 +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;
} }
/** /**
@@ -65,9 +65,17 @@ 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;
} }
/**
* This hook is being called after Drag Ruler has updated the movement history for one or more tokens.
* It'll receive an array of tokens that have been updated.
* If your speed provider is storing any additional values that are relevant for the movement history, this function should
* await until those updates have completed inside foundry.
*/
async onMovementHistoryUpdate(tokens) {}
/** /**
* Returns the value that is currently set for the setting registered with the provided settingId. * Returns the value that is currently set for the setting registered with the provided settingId.
* *
@@ -75,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.`,
);
} }
} }
@@ -91,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(getProperty(token, speedAttribute));
const tokenSpeed = 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() {
@@ -138,7 +149,7 @@ export class GenericSpeedProvider extends SpeedProvider {
config: true, config: true,
type: Number, type: Number,
default: getDefaultDashMultiplier(), default: getDefaultDashMultiplier(),
} },
] ];
} }
} }
+39 -7
View File
@@ -1,22 +1,54 @@
export function getDefaultSpeedAttribute() { export function getDefaultSpeedAttribute() {
switch (game.system.id) { switch (game.system.id) {
case "CoC7":
return "actor.system.attribs.mov.value";
case "dcc":
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.mech.speed" return "actor.system.derived.speed";
case "pf1": case "pf1":
return "actor.data.data.attributes.speed.land.total" case "D35E":
return "actor.system.attributes.speed.land.total";
case "sfrpg":
return "actor.system.attributes.speed.value";
case "shadowrun5e":
return "actor.system.movement.walk.value";
case "swade":
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";
} }
return "" return "";
} }
export function getDefaultDashMultiplier() { export function getDefaultDashMultiplier() {
switch (game.system.id) { switch (game.system.id) {
case "swade":
return 0;
case "dcc":
case "dnd4e":
case "dnd5e": case "dnd5e":
case "lancer": case "lancer":
case "pf1": case "pf1":
return 2 case "D35E":
case "sfrpg":
case "shadowrun5e":
case "ds4":
return 2;
case "CoC7":
return 5;
case "splittermond":
return 3;
case "wfrp4e":
return 2;
} }
return 0 return 0;
} }
+316
View File
@@ -0,0 +1,316 @@
import {getPixelsFromGridPosition} from "./foundry_fixes.js";
import {findVertexSnapPoint} from "./hex_support.js";
import {disableSnap, moveWithoutAnimation, togglePathfinding} from "./keybindings.js";
import {settingsKey} from "./settings.js";
export function* zip(it1, it2) {
for (let i = 0; i < Math.min(it1.length, it2.length); i++) {
yield [it1[i], it2[i]];
}
}
export function* 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 && canvas.grid.isHex) {
const center = token.center;
const size = getHexTokenSize(token);
if (size % 2 === 0) {
let offset;
if (canvas.grid.grid.columnar) {
offset = canvas.grid.grid.w - canvas.grid.grid.h;
} else {
offset = canvas.grid.grid.h - canvas.grid.grid.w;
}
if (getAltOrientationFlagForToken(token, size)) {
offset *= -1;
}
if (canvas.grid.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) {
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
return {x, y};
}
if (canvas.grid.isHex) {
const size = getHexTokenSize(token);
if (size % 2 === 0) {
return findVertexSnapPoint(x, y, getAltOrientationFlagForToken(token, size));
}
const [snapX, snapY] = canvas.grid.getCenter(x, y);
return {x: snapX, y: snapY};
}
const [topLeftX, topLeftY] = canvas.grid.getTopLeft(x, y);
let cellX, cellY;
if (token.document.width % 2 === 0) cellX = x - canvas.grid.h / 2;
else cellX = x;
if (token.document.height % 2 === 0) cellY = y - canvas.grid.h / 2;
else cellY = y;
const [centerX, centerY] = canvas.grid.getCenter(cellX, cellY);
let snapX, snapY;
// Tiny tokens can snap to the cells corners
if (token.document.width <= 0.5) {
const offsetX = x - topLeftX;
const subGridWidth = Math.floor(canvas.grid.w / 2);
const subGridPosX = Math.floor(offsetX / 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
else if (Math.round(token.document.width) % 2 === 1 || token.document.width < 1) {
snapX = centerX;
}
// All remaining tokens (those with even or fractional multipliers on square grids) snap to the intersection points of the grid
else {
snapX = centerX + canvas.grid.w / 2;
}
if (token.document.height <= 0.5) {
const offsetY = y - topLeftY;
const subGridHeight = Math.floor(canvas.grid.h / 2);
const subGridPosY = Math.floor(offsetY / subGridHeight);
snapY = topLeftY + (subGridPosY + 0.5) * subGridHeight;
} else if (Math.round(token.document.height) % 2 === 1 || token.document.height < 1) {
snapY = centerY;
} else {
snapY = centerY + canvas.grid.h / 2;
}
return {x: snapX, y: snapY};
}
export function getSnapPointForTokenObj(pos, token) {
return getSnapPointForToken(pos.x, pos.y, token);
}
export function getSnapPointForMeasuredTemplate(x, y) {
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
return new PIXI.Point(x, y);
}
return canvas.grid.grid.getSnappedPosition(x, y, canvas.templates.gridPrecision);
}
export function getSnapPointForEntity(x, y, entity) {
const isToken = entity instanceof Token;
if (isToken) return getSnapPointForToken(x, y, entity);
else return getSnapPointForMeasuredTemplate(x, y);
}
export function highlightTokenShape(position, shape, color, alpha) {
const layer = canvas.grid.highlightLayers[this.name];
if (!layer) return false;
const area = getAreaFromPositionAndShape(position, shape);
for (const space of area) {
const [x, y] = getPixelsFromGridPosition(space.x, space.y);
canvas.grid.grid.highlightGridPosition(layer, {x, y, color, alpha: 0.25 * alpha});
}
}
export function getAreaFromPositionAndShape(position, shape) {
return shape.map(space => {
let x = position.x + space.x;
let y = position.y + space.y;
if (canvas.grid.isHex) {
let shiftedRow;
if (canvas.grid.grid.options.even) shiftedRow = 1;
else shiftedRow = 0;
if (canvas.grid.grid.columnar) {
if (space.x % 2 !== 0 && position.x % 2 !== shiftedRow) {
y += 1;
}
} else {
if (space.y % 2 !== 0 && position.y % 2 !== shiftedRow) {
x += 1;
}
}
}
return {x, y};
});
}
// A copy of this function lives in the routinglib module
export function getTokenShape(token) {
let scene = canvas.scene;
if (scene.grid.type === CONST.GRID_TYPES.GRIDLESS) {
return [{x: 0, y: 0}];
} else if (scene.grid.type === CONST.GRID_TYPES.SQUARE) {
const topOffset = -Math.floor(token.document.height / 2);
const leftOffset = -Math.floor(token.document.width / 2);
const shape = [];
for (let y = 0; y < token.document.height; y++) {
for (let x = 0; x < token.document.width; x++) {
shape.push({x: x + leftOffset, y: y + topOffset});
}
}
return shape;
} else {
// Hex grids
const size = getHexTokenSize(token);
let shape = [{x: 0, y: 0}];
if (size >= 2)
shape = shape.concat([
{x: 0, y: -1},
{x: -1, y: -1},
]);
if (size >= 3)
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 (getAltOrientationFlagForToken(token, size)) {
shape.forEach(space => (space.y *= -1));
}
if (canvas.grid.grid.columnar)
shape = shape.map(space => {
return {x: space.y, y: space.x};
});
return shape;
}
}
export function getTokenSize(token) {
let w, h;
if (canvas.grid.isHex) {
w = h = getHexTokenSize(token);
} else {
w = token.document.width;
h = token.document.height;
}
return {w, h};
}
// Tokens that have a size divisible by two (2x2, 4x4, 2x1) have their ruler at the edge of a cell.
// This function applies an offset to to the waypoints that will move the ruler from the edge to the center of the cell
export function applyTokenSizeOffset(waypoints, token) {
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
return waypoints;
}
const tokenSize = getTokenSize(token);
const waypointOffset = {x: 0, y: 0};
if (canvas.grid.isHex) {
const isAltOrientation = getAltOrientationFlagForToken(token, getHexTokenSize(token));
if (canvas.grid.grid.columnar) {
if (tokenSize.w % 2 === 0) {
waypointOffset.x = canvas.grid.w / 2;
if (isAltOrientation) waypointOffset.x *= -1;
}
} else {
if (tokenSize.h % 2 === 0) {
waypointOffset.y = canvas.grid.h / 2;
if (isAltOrientation) waypointOffset.y *= -1;
}
}
// If hex size support isn't active leave the waypoints like they are
} else {
if (tokenSize.w % 2 === 0) {
waypointOffset.x = canvas.grid.w / 2;
}
if (tokenSize.h % 2 === 0) {
waypointOffset.y = canvas.grid.h / 2;
}
}
return waypoints.map(w => new PIXI.Point(w.x + waypointOffset.x, w.y + waypointOffset.y));
}
export function setSnapParameterOnOptions(sourceObject, options) {
// Allow outside modules to override snapping
if (sourceObject.snapOverride?.active) {
options.snapOverrideActive = true;
options.snap = sourceObject.snapOverride.snap;
sourceObject.snapOverride = undefined; // remove it to prevent any lingering data issues
} else {
options.snap = !disableSnap;
}
}
export function isClose(a, b, delta) {
return Math.abs(a - b) <= delta;
}
export function getPointer() {
return canvas.app.renderer.plugins.interaction?.mouse ?? canvas.app.renderer.events.pointer;
}
export function getMeasurePosition() {
const mousePosition = getPointer().getLocalPosition(canvas.tokens);
const rulerOffset = canvas.controls.ruler.rulerOffset;
const measurePosition = {x: mousePosition.x + rulerOffset.x, y: mousePosition.y + rulerOffset.y};
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;
}