Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fae1bb8fe0 | |||
| f203bea673 | |||
| 84c18a6221 | |||
| 0573adf0fe | |||
| 76c71329d6 | |||
| f918e148fe | |||
| 52d77ba0a4 | |||
| b06169f149 | |||
| c914eb23ae | |||
| c8a7c73ace | |||
| 76cff06b66 | |||
| ad005c1bb1 | |||
| 40b882ec93 | |||
| 1c8ce20a45 | |||
| a16f97f9e7 | |||
| a3e0ea9832 | |||
| aa2b8b3928 | |||
| a164811bb6 | |||
| 752b8375ab | |||
| 32b38d8efe | |||
| c3a62e3646 | |||
| 39f7bab4b6 | |||
| a3be4ceb03 | |||
| b275e777db | |||
| 035cfb8969 | |||
| 90a25f467b | |||
| ab85b98a5c | |||
| 1669edf757 | |||
| d1b5f7c39b | |||
| 24f1e78092 | |||
| 854903a314 | |||
| 232eca74de | |||
| 9c7d4ead50 | |||
| a91c3c1b8c | |||
| 99d057bffc | |||
| 792d644803 | |||
| 63b13cbb34 | |||
| f1a153a2eb | |||
| 59c12d017b | |||
| 9552c25e6e | |||
| 461e1d0818 | |||
| e1265ad6fb | |||
| 93f0bf6369 |
@@ -1,3 +1,68 @@
|
|||||||
|
## 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
|
## 1.7.7
|
||||||
### Compatibility
|
### 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.
|
- Updated the default settings for the swade game system. The new default speed attribute points to a speed value that gets adjusted for wounds.
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
[](https://ko-fi.com/staebchenfisch)
|
[](https://ko-fi.com/staebchenfisch)
|
||||||
|
|
||||||
# Drag Ruler
|
# Drag Ruler
|
||||||
This module shows a ruler when you drag a token or measurement template to infrom you how far you've dragged it from it's start point. Additionally, if you're using a grid, the spaces the token will travel though will be colored depending on your tokens speed. By default three colors are being used: green for spaces that your token can reach by walking normally are colored green, spaces that can only be reached by dashing will be colored red and spaces that cannot be reached with the token's speed will be colored red. If you're using a gridless map the ruler color will change to convey this information.
|
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.
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||
@@ -33,14 +33,21 @@ During combat, Drag Ruler will remember the path a token has taken during it's t
|
|||||||
|
|
||||||
|
|
||||||
## 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)
|
||||||
|
- GURPS 4th Edition Game Aid (Unofficial) (starting with version 0.9.1)
|
||||||
- Ironclaw Second Edition (starting with version 0.2.2)
|
- Ironclaw Second Edition (starting with version 0.2.2)
|
||||||
|
- Lancer (via the module [Lancer Speed Provider](https://foundryvtt.com/packages/lancer-speed-provider))
|
||||||
- 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))
|
||||||
|
- 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)
|
- 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)
|
||||||
- Shadow of the Demon Lord (starting with version 1.7.15)
|
- Shadow of the Demon Lord (starting with version 1.7.15)
|
||||||
- Wasteland Ventures (starting with version 0.1.0)
|
- Wasteland Ventures (starting with version 0.1.0)
|
||||||
|
|||||||
+29
-3
@@ -29,15 +29,41 @@
|
|||||||
"dash": "sprinten"
|
"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": "Token animation deaktivieren",
|
||||||
|
"hint": "Wenn diese Taste gedrückt wird, während ein Token fallen gelassen wird, bewegt es sich ohne Animation zum Zielort."
|
||||||
|
}
|
||||||
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"alwaysShowSpeedForPCs": {
|
"alwaysShowSpeedForPCs": {
|
||||||
"name": "Geschwindigkeit von Spielercharakteren für jeden anzeigen",
|
"name": "Geschwindigkeit von Spielercharakteren für jeden anzeigen",
|
||||||
"hint": "Wenn diese Einstellung aktiviert ist wird die Färbung der hervorgehobenen Felder bei Spielercharakteren allen Spielern angezeigt, selbst wenn diese keinen Zugriff auf den Charakterbogen haben."
|
"hint": "Wenn diese Einstellung aktiviert ist wird die Färbung der hervorgehobenen Felder bei Spielercharakteren allen Spielern angezeigt, selbst wenn diese keinen Zugriff auf den Charakterbogen haben."
|
||||||
},
|
},
|
||||||
|
"autoStartMeasurement": {
|
||||||
|
"name": "Automatisch mit Messen beginnen",
|
||||||
|
"hint": "Wenn diese Option aktiviert ist beginnt Drag Ruler mit dem Messen sobald das Token bewegt wird. Wenn die Option deaktiviert ist bleibt Drag Ruler deaktiviert bis der Knopf zum platzieren eines Wegpunkts betätigt wird."
|
||||||
|
},
|
||||||
"enableMovementHistory": {
|
"enableMovementHistory": {
|
||||||
"name": "Bewegungsverlauf während des Kampfes aktivieren",
|
"name": "Bewegungsverlauf während des Kampfes aktivieren",
|
||||||
"hint": "Wenn diesen Option aktiviert ist merkt sich Drag Ruler die Bewegungen, die während einer Runde eines Kampfes gemacht wurden und zeigt diese an, wenn das Token weiterbewegt wird."
|
"hint": "Wenn diesen Option aktiviert ist merkt sich Drag Ruler die Bewegungen, die während einer Runde eines Kampfes gemacht wurden und zeigt diese an, wenn das Token weiterbewegt wird."
|
||||||
},
|
},
|
||||||
|
"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": {
|
"showGMRulerToPlayers": {
|
||||||
"name": "Lineal des Spielleiters bei Spielern anzeigen",
|
"name": "Lineal des Spielleiters bei Spielern anzeigen",
|
||||||
"hint": "Wenn diese Option deaktiviert ist wird das Lineal des Spielleiters bei den anderen Spielern nicht angezeigt."
|
"hint": "Wenn diese Option deaktiviert ist wird das Lineal des Spielleiters bei den anderen Spielern nicht angezeigt."
|
||||||
@@ -74,9 +100,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"swapSpacebarRightClick": {
|
"useGridlessraster": {
|
||||||
"name": "Leertaste und Rechtsklick tauschen",
|
"name": "Geschwindigkeitsraster aktivieren",
|
||||||
"hint": "Die Funktionen der Leertaste und des Rechtsklicks sind, während eine Spielfigur bewegt wird, vertauscht. Wenn diese Option aktiviert wird können mit Rechtsklick Wegpunkte gesetzt werden und mit der Leertaste werden sie wieder gelöscht."
|
"hint": "Lässt Spielfiguren auf gitterlosen Szenen an deren Geschwindigkeitsgrenzen einrasten."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-3
@@ -29,15 +29,41 @@
|
|||||||
"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"
|
||||||
|
}
|
||||||
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"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 shown to everyone, even if they don't have observer permission for the character sheet."
|
||||||
},
|
},
|
||||||
|
"autoStartMeasurement": {
|
||||||
|
"name": "Automatically start measuring",
|
||||||
|
"hint": "If enabled, Drag Ruler will start measuring as soon as the token is being dragged. If disabled, Drag Ruler will remain inactive and will only start measuring once the button to add a waypoint is being pressed."
|
||||||
|
},
|
||||||
"enableMovementHistory": {
|
"enableMovementHistory": {
|
||||||
"name": "Enable movement history during combat",
|
"name": "Enable movement history during combat",
|
||||||
"hint": "If enabled, Drag Ruler will remember the path a token took during it's turn in combat and will display it when you pick the token back up."
|
"hint": "If enabled, Drag Ruler will remember the path a token took during it's turn in combat and will display it when you pick the token back up."
|
||||||
},
|
},
|
||||||
|
"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": {
|
"showGMRulerToPlayers": {
|
||||||
"name": "Show GM ruler to players",
|
"name": "Show GM ruler to players",
|
||||||
"hint": "If disabled the ruler of GMs will not be shown for non-GM players"
|
"hint": "If disabled the ruler of GMs will not be shown for non-GM players"
|
||||||
@@ -74,9 +100,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."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+91
-87
@@ -1,87 +1,91 @@
|
|||||||
{
|
{
|
||||||
"drag-ruler": {
|
"drag-ruler": {
|
||||||
"I18N": {
|
"I18N": {
|
||||||
"LANGUAGE": "Español",
|
"LANGUAGE": "Español",
|
||||||
"MAINTAINERS": "@Viriato139ac#0342"
|
"MAINTAINERS": "@Viriato139ac#0342"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ok": "OK",
|
"ok": "OK",
|
||||||
"terrain-ruler": {
|
"terrain-ruler": {
|
||||||
"title": "Cómo medir a través de terreno difícil con Drag Ruler",
|
"title": "Cómo medir a través de terreno difícil con Drag Ruler",
|
||||||
"text": "You have the {moduleName} module enabled. Drag Ruler is able to measure difficult terrain that was placed down using that module. To make this possible Drag Ruler utilizes the Terrain Ruler module. If Terrain Ruler is installed and activated Drag Ruler will automatically start to respect difficult terrain in it's measurements.",
|
"text": "You have the {moduleName} module enabled. Drag Ruler is able to measure difficult terrain that was placed down using that module. To make this possible Drag Ruler utilizes the Terrain Ruler module. If Terrain Ruler is installed and activated Drag Ruler will automatically start to respect difficult terrain in it's measurements.",
|
||||||
"neverShowAgain": "No volver a mostrar"
|
"neverShowAgain": "No volver a mostrar"
|
||||||
},
|
},
|
||||||
"socketlib": {
|
"socketlib": {
|
||||||
"title": "El módulo Socketlib no está instalado",
|
"title": "El módulo Socketlib no está instalado",
|
||||||
"text": "Drag Ruler necesita para su funcionamiento correcto que instale socketlib. Por favor, active socketlib en este mundo"
|
"text": "Drag Ruler necesita para su funcionamiento correcto que instale socketlib. Por favor, active socketlib en este mundo"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"resetMovementHistory": "Reiniciar historial de movimiento",
|
"resetMovementHistory": "Reiniciar historial de movimiento",
|
||||||
"genericSpeedProvider": {
|
"genericSpeedProvider": {
|
||||||
"settings": {
|
"settings": {
|
||||||
"dashMultiplier": {
|
"dashMultiplier": {
|
||||||
"name": "Multiplicador por correr",
|
"name": "Multiplicador por correr",
|
||||||
"hint": "Se puede usar para aplicar a los iconos una velocidad secundaria que use un color distinto en la medición de la ruta a seguir. Establece el valor a 0 para deshabilitar esta velocidad secundaria"
|
"hint": "Se puede usar para aplicar a los iconos una velocidad secundaria que use un color distinto en la medición de la ruta a seguir. Establece el valor a 0 para deshabilitar esta velocidad secundaria"
|
||||||
},
|
},
|
||||||
"speedAttribute": {
|
"speedAttribute": {
|
||||||
"name": "Attributo de velocidad",
|
"name": "Attributo de velocidad",
|
||||||
"hint": "El atributo que define la velocidad de movimiento andando del icono. Se usa para los códigos de colores al medir la ruta a seguir"
|
"hint": "El atributo que define la velocidad de movimiento andando del icono. Se usa para los códigos de colores al medir la ruta a seguir"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"speeds": {
|
"speeds": {
|
||||||
"walk": "caminar",
|
"walk": "caminar",
|
||||||
"dash": "correr"
|
"dash": "correr"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"alwaysShowSpeedForPCs": {
|
"alwaysShowSpeedForPCs": {
|
||||||
"name": "Mostrar velocidad de los PJs a todo el mundo",
|
"name": "Mostrar velocidad de los PJs a todo el mundo",
|
||||||
"hint": "Si se habilita, se mostrará a todo el mundo los códigos de colores de las rutas de los PJs, incluso si no tienen permisos de observador para ese personaje"
|
"hint": "Si se habilita, se mostrará a todo el mundo los códigos de colores de las rutas de los PJs, incluso si no tienen permisos de observador para ese personaje"
|
||||||
},
|
},
|
||||||
"enableMovementHistory": {
|
"autoStartMeasurement": {
|
||||||
"name": "Habilitar historial de movimiento durante el combate",
|
"name": "Comenzar a medir automáticamente",
|
||||||
"hint": "Si se habilita, Drag Ruler recordará la ruta que ha seguido un icono en su turno y la mostrará al seleccionarlo de nuevo"
|
"hint": "Si se habilita, Drag Ruler 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"
|
||||||
},
|
},
|
||||||
"showGMRulerToPlayers": {
|
"enableMovementHistory": {
|
||||||
"name": "Mostrar regla del GM a los jugadores",
|
"name": "Habilitar historial de movimiento durante el combate",
|
||||||
"hint": "Si se deshabilita, no se mostrará la regla de medición de los GMs al resto de usuarios"
|
"hint": "Si se habilita, Drag Ruler recordará la ruta que ha seguido un icono en su turno y la mostrará al seleccionarlo de nuevo"
|
||||||
},
|
},
|
||||||
"speedProviderSettings": {
|
"showGMRulerToPlayers": {
|
||||||
"name": "Proveedor de configuración de velocidad",
|
"name": "Mostrar regla del GM a los jugadores",
|
||||||
"hint": "El proveedor de ajustes de velocidad contiene todos los ajustes específicos de cada sistema",
|
"hint": "Si se deshabilita, no se mostrará la regla de medición de los GMs al resto de usuarios"
|
||||||
"button": "Proveedor de configuración de velocidad",
|
},
|
||||||
"windowTitle": "Proveedor de configuración de velocidad",
|
"speedProviderSettings": {
|
||||||
"headers": {
|
"name": "Proveedor de configuración de velocidad",
|
||||||
"speedProvider": "Proveedor de velocidad",
|
"hint": "El proveedor de ajustes de velocidad contiene todos los ajustes específicos de cada sistema",
|
||||||
"speedProviderSettings": "Configuraciones específicas del proveedor de velocidad"
|
"button": "Proveedor de configuración de velocidad",
|
||||||
},
|
"windowTitle": "Proveedor de configuración de velocidad",
|
||||||
"activeProvider": {
|
"headers": {
|
||||||
"name": "Proveedor de velocidad actualmente activo",
|
"speedProvider": "Proveedor de velocidad",
|
||||||
"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"
|
"speedProviderSettings": "Configuraciones específicas del proveedor de velocidad"
|
||||||
},
|
},
|
||||||
"noSettings": "Este proveedor de velocidad no dispone de ninguna opción de configuración",
|
"activeProvider": {
|
||||||
"color": {
|
"name": "Proveedor de velocidad actualmente activo",
|
||||||
"name": "Color para {colorName}",
|
"hint": "La configuración que se muestra más abajo depende del proveedor de velocidad activo. Si el GM selecciona un proveedor distinto, los ajustes disponibles puede que cambien"
|
||||||
"hint": "Color que se usará para establecer el tono de las cuadrículas que se encuentran dentro del rango {colorName}",
|
},
|
||||||
"unreachable": {
|
"noSettings": "Este proveedor de velocidad no dispone de ninguna opción de configuración",
|
||||||
"name": "inalcanzable",
|
"color": {
|
||||||
"hint": "Color que se usará para las cuadrículas más allá de la distancia máxima de movimiento del icono"
|
"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": {
|
||||||
"speedProvider": {
|
"name": "inalcanzable",
|
||||||
"name": "Proveedor de configuración de velocidad",
|
"hint": "Color que se usará para las cuadrículas más allá de la distancia máxima de movimiento del icono"
|
||||||
"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}",
|
"speedProvider": {
|
||||||
"native": "Genérico",
|
"name": "Proveedor de configuración de velocidad",
|
||||||
"system": "Sistema {name}"
|
"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",
|
||||||
"swapSpacebarRightClick": {
|
"system": "Sistema {name}"
|
||||||
"name": "Intercambiar barra espaciadora y clic-derecho",
|
}
|
||||||
"hint": "Cambia el funcionamiento de la barra espaciadora y el clic-derecho del ratón mientras se arrastra un icono. Si está habilitado, clic-derecho establecerá puntos en la ruta y la barra espaciadora los borrará"
|
}
|
||||||
}
|
},
|
||||||
}
|
"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. Se puede deshabilitar temporalmente pulsando la tecla Mayús mientras se arrastra"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
{
|
||||||
|
"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 dans 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 courses",
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"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é."
|
||||||
|
},
|
||||||
|
"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."
|
||||||
|
},
|
||||||
|
"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 plage {colorName}",
|
||||||
|
"unreachable": {
|
||||||
|
"name": "injoignable",
|
||||||
|
"hint": "La couleur des espaces qui ne sont pas accessibles par 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}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"useGridlessRaster": {
|
||||||
|
"name": "Use speed based snapping",
|
||||||
|
"hint": "On Gridless scenes, this makes tokens snap to the token's speed ranges. This can be temporarily disabled by pressing Shift during drag."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+25
-3
@@ -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,14 @@
|
|||||||
"name": "プレイヤーキャラクターの移動速度を全員に公開する",
|
"name": "プレイヤーキャラクターの移動速度を全員に公開する",
|
||||||
"hint": "有効にすると、アクターの観察者(Observer)権限を持たない人にも,移動範囲の色分けが表示されるようになります。"
|
"hint": "有効にすると、アクターの観察者(Observer)権限を持たない人にも,移動範囲の色分けが表示されるようになります。"
|
||||||
},
|
},
|
||||||
|
"autoStartMeasurement": {
|
||||||
|
"name": "自動計測",
|
||||||
|
"hint": "有効化時、Drag Rulerはトークンがドラッグされた時点で計測を初めます。無効化の場合、ドラッグしてもDrag Rulerは起動せず、中間地点をおいたときにのみ起動します。"
|
||||||
|
},
|
||||||
|
"enableMovementHistory": {
|
||||||
|
"name": "戦闘中の移動履歴を有効にする",
|
||||||
|
"hint": "有効にすると、Drag Rulerは戦闘ターン中にコマが辿った経路を記憶するようになります。これはコマの再選択時に再表示されます。"
|
||||||
|
},
|
||||||
"showGMRulerToPlayers": {
|
"showGMRulerToPlayers": {
|
||||||
"name": "GMのルーラーをプレイヤーに見せる",
|
"name": "GMのルーラーをプレイヤーに見せる",
|
||||||
"hint": "無効の場合、GMのルーラーはプレイヤーに表示されません。"
|
"hint": "無効の場合、GMのルーラーはプレイヤーに表示されません。"
|
||||||
@@ -57,9 +79,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"swapSpacebarRightClick": {
|
"useGridlessRaster": {
|
||||||
"name": "スペースキーと右クリックを入れ替える",
|
"name": "移動速度制限",
|
||||||
"hint": "ドラッグ中のスペースキーと右クリックの機能を入れ替えます。有効にすると右クリックがウェイポイントの配置、スペースキーが削除になります。"
|
"hint": "グリッドの無いシーンでは、コマがドラッグできる範囲を移動速度ぶんまで制限します。Shiftキーを押すことで一時的にこの制限を無視できます。"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,10 +73,6 @@
|
|||||||
"system": "시스템 {name}"
|
"system": "시스템 {name}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"swapSpacebarRightClick": {
|
|
||||||
"name": "스페이스바와 우클릭 스왑",
|
|
||||||
"hint": "드래그 도중 스페이스바의 기능을 우클릭 기능과 바꾼다. 활성화된 경우 드래그시 우클릭을 사용하면 경유지가 배치되고, 스페이스바가 해당 경유지를 삭제한다."
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,10 +56,6 @@
|
|||||||
"system": "系統 {name}"
|
"system": "系統 {name}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"swapSpacebarRightClick": {
|
|
||||||
"name": "交換空格鍵並鼠標右鍵",
|
|
||||||
"hint": "交換空格鍵和右鍵點擊在拖動過程中的功能。如果啟用,右鍵單擊將放置航點,空格鍵將刪除它們。"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-9
@@ -2,9 +2,9 @@
|
|||||||
"name": "drag-ruler",
|
"name": "drag-ruler",
|
||||||
"title": "Drag Ruler",
|
"title": "Drag Ruler",
|
||||||
"description": "When dragging a token displays a ruler showing how far you've moved that token.",
|
"description": "When dragging a token displays a ruler showing how far you've moved that token.",
|
||||||
"version": "1.7.7",
|
"version": "1.10.0",
|
||||||
"minimumCoreVersion" : "0.8.5",
|
"minimumCoreVersion" : "9.238",
|
||||||
"compatibleCoreVersion" : "0.8.8",
|
"compatibleCoreVersion" : "9",
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
"name": "Manuel Vögele",
|
"name": "Manuel Vögele",
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"esmodules": [
|
"esmodules": [
|
||||||
|
"src/libwrapper_shim.js",
|
||||||
"src/main.js",
|
"src/main.js",
|
||||||
"src/socket.js"
|
"src/socket.js"
|
||||||
],
|
],
|
||||||
@@ -30,6 +31,16 @@
|
|||||||
"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": "日本語",
|
||||||
@@ -44,11 +55,6 @@
|
|||||||
"lang": "zh-tw",
|
"lang": "zh-tw",
|
||||||
"name": "正體中文",
|
"name": "正體中文",
|
||||||
"path": "lang/zh-tw.json"
|
"path": "lang/zh-tw.json"
|
||||||
},
|
|
||||||
{
|
|
||||||
"lang": "es",
|
|
||||||
"name": "Español",
|
|
||||||
"path": "lang/es.json"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
@@ -59,7 +65,7 @@
|
|||||||
],
|
],
|
||||||
"socket": true,
|
"socket": true,
|
||||||
"url": "https://github.com/manuelVo/foundryvtt-drag-ruler",
|
"url": "https://github.com/manuelVo/foundryvtt-drag-ruler",
|
||||||
"download": "https://github.com/manuelVo/foundryvtt-drag-ruler/archive/v1.7.7.zip",
|
"download": "https://github.com/manuelVo/foundryvtt-drag-ruler/archive/v1.10.0.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",
|
||||||
|
|||||||
+19
-4
@@ -103,12 +103,12 @@ export function getUnreachableColorFromSpeedProvider() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCostFromSpeedProvider(token, area) {
|
export function getCostFromSpeedProvider(token, area, options) {
|
||||||
try {
|
try {
|
||||||
if (currentSpeedProvider instanceof Function) {
|
if (currentSpeedProvider instanceof Function) {
|
||||||
return SpeedProvider.prototype.getCostForStep.call(undefined, token, area);
|
return SpeedProvider.prototype.getCostForStep.call(undefined, token, area, options);
|
||||||
}
|
}
|
||||||
return currentSpeedProvider.getCostForStep(token, area);
|
return currentSpeedProvider.getCostForStep(token, area, options);
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
@@ -116,11 +116,26 @@ export function getCostFromSpeedProvider(token, area) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
export function getMovedDistanceFromToken(token) {
|
||||||
|
const terrainRulerAvailable = game.modules.get("terrain-ruler")?.active;
|
||||||
const history = getMovementHistory(token);
|
const history = getMovementHistory(token);
|
||||||
const segments = Ruler.dragRulerGetRaysFromWaypoints(history, {x: token.x, y: token.y}).map(ray => {return {ray}});
|
const segments = Ruler.dragRulerGetRaysFromWaypoints(history, {x: token.x, y: token.y}).map(ray => {return {ray}});
|
||||||
const shape = getTokenShape(token);
|
const shape = getTokenShape(token);
|
||||||
const distances = measureDistances(segments, token, shape);
|
const distances = measureDistances(segments, token, shape, {enableTerrainRuler: terrainRulerAvailable});
|
||||||
// Sum up the distances
|
// Sum up the distances
|
||||||
return distances.reduce((acc, val) => acc + val, 0);
|
return distances.reduce((acc, val) => acc + val, 0);
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-11
@@ -1,5 +1,4 @@
|
|||||||
import {getCostFromSpeedProvider} from "./api.js";
|
import {getCostFromSpeedProvider} from "./api.js";
|
||||||
import {getColorForDistance} from "./main.js"
|
|
||||||
import {settingsKey} from "./settings.js";
|
import {settingsKey} from "./settings.js";
|
||||||
import {getAreaFromPositionAndShape, highlightTokenShape} from "./util.js";
|
import {getAreaFromPositionAndShape, highlightTokenShape} from "./util.js";
|
||||||
|
|
||||||
@@ -10,30 +9,35 @@ export function getHexSizeSupportTokenGridCenter(token) {
|
|||||||
|
|
||||||
export function highlightMeasurementTerrainRuler(ray, startDistance, tokenShape=[{x: 0, y: 0}], alpha=1) {
|
export function highlightMeasurementTerrainRuler(ray, startDistance, tokenShape=[{x: 0, y: 0}], alpha=1) {
|
||||||
for (const space of ray.terrainRulerVisitedSpaces.reverse()) {
|
for (const space of ray.terrainRulerVisitedSpaces.reverse()) {
|
||||||
const color = getColorForDistance.call(this, startDistance, space.distance)
|
const color = this.dragRulerGetColorForDistance(startDistance + space.distance);
|
||||||
highlightTokenShape.call(this, space, tokenShape, color, alpha)
|
highlightTokenShape.call(this, space, tokenShape, color, alpha)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function measureDistances(segments, entity, shape, options={}) {
|
export function measureDistances(segments, entity, shape, options={}) {
|
||||||
const isToken = entity instanceof Token;
|
|
||||||
const opts = duplicate(options)
|
const opts = duplicate(options)
|
||||||
if (!opts.gridSpaces)
|
if (canvas.grid.diagonalRule === "EUCL") {
|
||||||
|
opts.ignoreGrid = true;
|
||||||
|
opts.gridSpaes = false;
|
||||||
|
}
|
||||||
|
if (opts.enableTerrainRuler) {
|
||||||
opts.gridSpaces = true;
|
opts.gridSpaces = true;
|
||||||
const terrainRulerAvailable = isToken && game.modules.get("terrain-ruler")?.active && (!game.modules.get("TerrainLayer")?.active || canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS);
|
|
||||||
|
|
||||||
if (terrainRulerAvailable) {
|
|
||||||
const firstNewSegmentIndex = segments.findIndex(segment => !segment.ray.dragRulerVisitedSpaces);
|
const firstNewSegmentIndex = segments.findIndex(segment => !segment.ray.dragRulerVisitedSpaces);
|
||||||
const previousSegments = segments.slice(0, firstNewSegmentIndex);
|
const previousSegments = segments.slice(0, firstNewSegmentIndex);
|
||||||
const newSegments = segments.slice(firstNewSegmentIndex);
|
const newSegments = segments.slice(firstNewSegmentIndex);
|
||||||
const distances = previousSegments.map(segment => segment.ray.dragRulerVisitedSpaces[segment.ray.dragRulerVisitedSpaces.length - 1].distance);
|
const distances = previousSegments.map(segment => segment.ray.dragRulerVisitedSpaces[segment.ray.dragRulerVisitedSpaces.length - 1].distance);
|
||||||
previousSegments.forEach(segment => segment.ray.terrainRulerVisitedSpaces = duplicate(segment.ray.dragRulerVisitedSpaces));
|
previousSegments.forEach(segment => segment.ray.terrainRulerVisitedSpaces = duplicate(segment.ray.dragRulerVisitedSpaces));
|
||||||
opts.costFunction = (x, y) => getCostFromSpeedProvider(entity, getAreaFromPositionAndShape({x, y}, shape), {x, y});
|
opts.costFunction = (x, y, costOptions={}) => { return getCostFromSpeedProvider(entity, getAreaFromPositionAndShape({x, y}, shape), costOptions); }
|
||||||
if (previousSegments.length > 0)
|
if (previousSegments.length > 0)
|
||||||
opts.terrainRulerInitialState = previousSegments[previousSegments.length - 1].ray.dragRulerFinalState;
|
opts.terrainRulerInitialState = previousSegments[previousSegments.length - 1].ray.dragRulerFinalState;
|
||||||
return distances.concat(terrainRuler.measureDistances(newSegments, opts));
|
return distances.concat(terrainRuler.measureDistances(newSegments, opts));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
// If another module wants to enable grid measurements but disable grid highlighting,
|
||||||
|
// manually set the *duplicate* option's gridSpaces value to true for the Foundry logic to work properly
|
||||||
|
if(!opts.ignoreGrid) {
|
||||||
|
opts.gridSpaces = true;
|
||||||
|
}
|
||||||
return canvas.grid.measureDistances(segments, opts);
|
return canvas.grid.measureDistances(segments, opts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,9 +65,6 @@ export function checkDependencies() {
|
|||||||
if (game.modules.get("enhanced-terrain-layer")?.active) {
|
if (game.modules.get("enhanced-terrain-layer")?.active) {
|
||||||
enabledTerrainModule = game.modules.get("enhanced-terrain-layer").data.title;
|
enabledTerrainModule = game.modules.get("enhanced-terrain-layer").data.title;
|
||||||
}
|
}
|
||||||
else if (game.modules.get("TerrainLayer")?.active) {
|
|
||||||
enabledTerrainModule = game.modules.get("TerrainLayer").data.title;
|
|
||||||
}
|
|
||||||
if (enabledTerrainModule) {
|
if (enabledTerrainModule) {
|
||||||
new Dialog({
|
new Dialog({
|
||||||
title: game.i18n.localize("drag-ruler.dependencies.terrain-ruler.title"),
|
title: game.i18n.localize("drag-ruler.dependencies.terrain-ruler.title"),
|
||||||
|
|||||||
+34
-17
@@ -1,7 +1,7 @@
|
|||||||
import {highlightMeasurementTerrainRuler, measureDistances} from "./compatibility.js";
|
import {highlightMeasurementTerrainRuler, measureDistances} from "./compatibility.js";
|
||||||
import {getGridPositionFromPixels} from "./foundry_fixes.js";
|
import {getGridPositionFromPixels} from "./foundry_fixes.js";
|
||||||
import {Line} from "./geometry.js";
|
import {Line} from "./geometry.js";
|
||||||
import {getColorForDistance} from "./main.js"
|
import {disableSnap, moveWithoutAnimation} from "./keybindings.js";
|
||||||
import {trackRays} from "./movement_tracking.js"
|
import {trackRays} from "./movement_tracking.js"
|
||||||
import {recalculate} from "./socket.js";
|
import {recalculate} from "./socket.js";
|
||||||
import {applyTokenSizeOffset, getSnapPointForEntity, getSnapPointForToken, getTokenShape, highlightTokenShape, zip} from "./util.js";
|
import {applyTokenSizeOffset, getSnapPointForEntity, getSnapPointForToken, getTokenShape, highlightTokenShape, zip} from "./util.js";
|
||||||
@@ -70,7 +70,7 @@ async function animateEntities(entities, draggedEntity, draggedRays, wasPaused)
|
|||||||
});
|
});
|
||||||
|
|
||||||
const isToken = draggedEntity instanceof Token;
|
const isToken = draggedEntity instanceof Token;
|
||||||
const animate = isToken && !game.keyboard.isDown("Alt");
|
const animate = isToken && !moveWithoutAnimation;
|
||||||
const startWaypoint = animate ? 0 : entityAnimationData[0].rays.length - 1;
|
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 is a flag of the "Monk's Active Tile Triggers" module that signals that the movement should be cancelled early
|
||||||
@@ -131,12 +131,10 @@ function scheduleMeasurement(destination, event) {
|
|||||||
const mt = event._measureTime || 0;
|
const mt = event._measureTime || 0;
|
||||||
const originalEvent = event.data.originalEvent;
|
const originalEvent = event.data.originalEvent;
|
||||||
if (Date.now() - mt > measurementInterval) {
|
if (Date.now() - mt > measurementInterval) {
|
||||||
this.measure(destination, {snap: !originalEvent.shiftKey});
|
this.measure(destination, {snap: !disableSnap});
|
||||||
event._measureTime = Date.now();
|
event._measureTime = Date.now();
|
||||||
this._state = Ruler.STATES.MEASURING;
|
this._state = Ruler.STATES.MEASURING;
|
||||||
window.clearTimeout(this.deferredMeasurementTimeout);
|
cancelScheduledMeasurement.call(this);
|
||||||
this.deferredMeasurementTimeout = undefined;
|
|
||||||
this.deferredMeasurementResolve?.();
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.deferredMeasurementData = {destination, event};
|
this.deferredMeasurementData = {destination, event};
|
||||||
@@ -147,23 +145,42 @@ function scheduleMeasurement(destination, event) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function cancelScheduledMeasurement() {
|
||||||
|
window.clearTimeout(this.deferredMeasurementTimeout);
|
||||||
|
this.deferredMeasurementTimeout = undefined;
|
||||||
|
this.deferredMeasurementResolve?.();
|
||||||
|
}
|
||||||
|
|
||||||
// This is a modified version of Ruler.measure form foundry 0.7.9
|
// This is a modified version of Ruler.measure form foundry 0.7.9
|
||||||
export function measure(destination, {gridSpaces=true, snap=false} = {}) {
|
export function measure(destination, options={}) {
|
||||||
const isToken = this.draggedEntity instanceof Token;
|
const isToken = this.draggedEntity instanceof Token;
|
||||||
if (isToken && !this.draggedEntity.isVisible)
|
if (isToken && !this.draggedEntity.isVisible)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
if (snap) {
|
if (options.snap) {
|
||||||
destination = getSnapPointForEntity(destination.x, destination.y, this.draggedEntity);
|
destination = getSnapPointForEntity(destination.x, destination.y, this.draggedEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
const terrainRulerAvailable = isToken && game.modules.get("terrain-ruler")?.active && (!game.modules.get("TerrainLayer")?.active || canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS);
|
if(options.gridSpaces === undefined) {
|
||||||
|
options.gridSpaces = canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canvas.grid.diagonalRule === "EUCL") {
|
||||||
|
options.gridSpaces = false;
|
||||||
|
options.ignoreGrid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(options.ignoreGrid === undefined) {
|
||||||
|
options.ignoreGrid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
options.enableTerrainRuler = isToken && game.modules.get("terrain-ruler")?.active;
|
||||||
|
|
||||||
const waypoints = this.waypoints.concat([destination]);
|
const waypoints = this.waypoints.concat([destination]);
|
||||||
// Move the waypoints to the center of the grid if a size is used that measures from edge to edge
|
// Move the waypoints to the center of the grid if a size is used that measures from edge to edge
|
||||||
const centeredWaypoints = isToken ? applyTokenSizeOffset(waypoints, this.draggedEntity) : duplicate(waypoints);
|
const centeredWaypoints = isToken ? applyTokenSizeOffset(waypoints, this.draggedEntity) : duplicate(waypoints);
|
||||||
// Foundries native ruler requires the waypoints to sit in the dead center of the square to work properly
|
// Foundries native ruler requires the waypoints to sit in the dead center of the square to work properly
|
||||||
if (!terrainRulerAvailable)
|
if (!options.enableTerrainRuler && !options.ignoreGrid)
|
||||||
centeredWaypoints.forEach(w => [w.x, w.y] = canvas.grid.getCenter(w.x, w.y));
|
centeredWaypoints.forEach(w => [w.x, w.y] = canvas.grid.getCenter(w.x, w.y));
|
||||||
|
|
||||||
const r = this.ruler;
|
const r = this.ruler;
|
||||||
@@ -197,7 +214,7 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
|
|||||||
const shape = isToken ? getTokenShape(this.draggedEntity) : null;
|
const shape = isToken ? getTokenShape(this.draggedEntity) : null;
|
||||||
|
|
||||||
// Compute measured distance
|
// Compute measured distance
|
||||||
const distances = measureDistances(centeredSegments, this.draggedEntity, shape, {gridSpaces});
|
const distances = measureDistances(centeredSegments, this.draggedEntity, shape, options);
|
||||||
|
|
||||||
let totalDistance = 0;
|
let totalDistance = 0;
|
||||||
for (let [i, d] of distances.entries()) {
|
for (let [i, d] of distances.entries()) {
|
||||||
@@ -216,8 +233,8 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
|
|||||||
// Draw measured path
|
// Draw measured path
|
||||||
r.clear();
|
r.clear();
|
||||||
let rulerColor
|
let rulerColor
|
||||||
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS)
|
if (!options.gridSpaces || canvas.grid.type === CONST.GRID_TYPES.GRIDLESS)
|
||||||
rulerColor = getColorForDistance.call(this, totalDistance)
|
rulerColor = this.dragRulerGetColorForDistance(totalDistance);
|
||||||
else
|
else
|
||||||
rulerColor = this.color
|
rulerColor = this.color
|
||||||
for (const [s, cs] of zip(segments.reverse(), centeredSegments.reverse())) {
|
for (const [s, cs] of zip(segments.reverse(), centeredSegments.reverse())) {
|
||||||
@@ -252,8 +269,8 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Highlight grid positions
|
// Highlight grid positions
|
||||||
if (isToken && canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS) {
|
if (isToken && canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS && options.gridSpaces) {
|
||||||
if (terrainRulerAvailable)
|
if (options.enableTerrainRuler)
|
||||||
highlightMeasurementTerrainRuler.call(this, cs.ray, cs.startDistance, shape, opacityMultiplier)
|
highlightMeasurementTerrainRuler.call(this, cs.ray, cs.startDistance, shape, opacityMultiplier)
|
||||||
else
|
else
|
||||||
highlightMeasurementNative.call(this, cs.ray, cs.startDistance, shape, opacityMultiplier);
|
highlightMeasurementNative.call(this, cs.ray, cs.startDistance, shape, opacityMultiplier);
|
||||||
@@ -289,7 +306,7 @@ export function highlightMeasurementNative(ray, startDistance, tokenShape=[{x: 0
|
|||||||
// Highlight the grid position
|
// Highlight the grid position
|
||||||
let [xg, yg] = canvas.grid.grid.getPixelsFromGridPosition(x1, y1);
|
let [xg, yg] = canvas.grid.grid.getPixelsFromGridPosition(x1, y1);
|
||||||
const subDistance = canvas.grid.measureDistances([{ray: new Ray(ray.A, {x: xg, y: yg})}], {gridSpaces: true})[0]
|
const subDistance = canvas.grid.measureDistances([{ray: new Ray(ray.A, {x: xg, y: yg})}], {gridSpaces: true})[0]
|
||||||
const color = dragRuler.getColorForDistance.call(this, startDistance, subDistance)
|
const color = this.dragRulerGetColorForDistance(startDistance + subDistance);
|
||||||
const snapPoint = getSnapPointForToken(...canvas.grid.getTopLeft(x, y), this.draggedEntity);
|
const snapPoint = getSnapPointForToken(...canvas.grid.getTopLeft(x, y), this.draggedEntity);
|
||||||
const [snapX, snapY] = getGridPositionFromPixels(snapPoint.x + 1, snapPoint.y + 1);
|
const [snapX, snapY] = getGridPositionFromPixels(snapPoint.x + 1, snapPoint.y + 1);
|
||||||
|
|
||||||
@@ -302,7 +319,7 @@ export function highlightMeasurementNative(ray, startDistance, tokenShape=[{x: 0
|
|||||||
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 [xgh, ygh] = canvas.grid.grid.getPixelsFromGridPosition(x1h, y1h);
|
||||||
const subDistance = canvas.grid.measureDistances([{ray: new Ray(ray.A, {x: xgh, y: ygh})}], {gridSpaces: true})[0]
|
const subDistance = canvas.grid.measureDistances([{ray: new Ray(ray.A, {x: xgh, y: ygh})}], {gridSpaces: true})[0]
|
||||||
const color = dragRuler.getColorForDistance.call(this, startDistance, subDistance)
|
const color = this.dragRulerGetColorForDistance(startDistance + subDistance);
|
||||||
const snapPoint = getSnapPointForToken(...canvas.grid.getTopLeft(x, y), this.draggedEntity);
|
const snapPoint = getSnapPointForToken(...canvas.grid.getTopLeft(x, y), this.draggedEntity);
|
||||||
const [snapX, snapY] = getGridPositionFromPixels(snapPoint.x + 1, snapPoint.y + 1);
|
const [snapX, snapY] = getGridPositionFromPixels(snapPoint.x + 1, snapPoint.y + 1);
|
||||||
highlightTokenShape.call(this, {x: snapX, y: snapY}, tokenShape, color, alpha);
|
highlightTokenShape.call(this, {x: snapX, y: snapY}, tokenShape, color, alpha);
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
import {settingsKey} from "./settings.js";
|
||||||
|
import {getMeasurePosition, setSnapParameterOnOptions} from "./util.js";
|
||||||
|
|
||||||
|
export let disableSnap = false;
|
||||||
|
export let moveWithoutAnimation = 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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMoveWithoutAnimation(event) {
|
||||||
|
moveWithoutAnimation = !event.up;
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
+143
-114
@@ -1,26 +1,30 @@
|
|||||||
"use strict"
|
"use strict"
|
||||||
|
|
||||||
import {currentSpeedProvider, getMovedDistanceFromToken, getRangesFromSpeedProvider, getUnreachableColorFromSpeedProvider, initApi, registerModule, registerSystem} from "./api.js"
|
import {getColorForDistanceAndToken, getMovedDistanceFromToken, getRangesFromSpeedProvider, initApi, registerModule, registerSystem} from "./api.js";
|
||||||
import {checkDependencies, getHexSizeSupportTokenGridCenter} from "./compatibility.js";
|
import {checkDependencies, getHexSizeSupportTokenGridCenter} from "./compatibility.js";
|
||||||
import {moveEntities, onMouseMove} from "./foundry_imports.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 {performMigrations} from "./migration.js"
|
||||||
import {DragRulerRuler} from "./ruler.js";
|
import {removeLastHistoryEntryIfAt, resetMovementHistory} from "./movement_tracking.js";
|
||||||
import {getMovementHistory, resetMovementHistory} from "./movement_tracking.js";
|
import {extendRuler} from "./ruler.js";
|
||||||
import {registerSettings, settingsKey} from "./settings.js"
|
import {registerSettings, RightClickAction, settingsKey} from "./settings.js"
|
||||||
import {recalculate} from "./socket.js";
|
import {recalculate} from "./socket.js";
|
||||||
import {SpeedProvider} from "./speed_provider.js"
|
import {SpeedProvider} from "./speed_provider.js"
|
||||||
|
import {setSnapParameterOnOptions} from "./util.js";
|
||||||
|
|
||||||
Hooks.once("init", () => {
|
Hooks.once("init", () => {
|
||||||
registerSettings()
|
registerSettings()
|
||||||
|
registerKeybindings()
|
||||||
initApi()
|
initApi()
|
||||||
hookDragHandlers(Token);
|
hookDragHandlers(Token);
|
||||||
hookDragHandlers(MeasuredTemplate);
|
hookDragHandlers(MeasuredTemplate);
|
||||||
hookKeyboardManagerFunctions()
|
libWrapper.register("drag-ruler", "TokenLayer.prototype.undoHistory", tokenLayerUndoHistory, "WRAPPER");
|
||||||
|
|
||||||
Ruler = DragRulerRuler;
|
extendRuler();
|
||||||
|
|
||||||
window.dragRuler = {
|
window.dragRuler = {
|
||||||
getColorForDistance,
|
getColorForDistanceAndToken,
|
||||||
getMovedDistanceFromToken,
|
getMovedDistanceFromToken,
|
||||||
registerModule,
|
registerModule,
|
||||||
registerSystem,
|
registerSystem,
|
||||||
@@ -40,7 +44,7 @@ Hooks.on("canvasReady", () => {
|
|||||||
ruler.draggedEntity = null;
|
ruler.draggedEntity = null;
|
||||||
Object.defineProperty(ruler, "isDragRuler", {
|
Object.defineProperty(ruler, "isDragRuler", {
|
||||||
get: function isDragRuler() {
|
get: function isDragRuler() {
|
||||||
return Boolean(this.draggedEntity); // If draggedEntity is set this is a drag ruler
|
return Boolean(this.draggedEntity) && this._state !== Ruler.STATES.INACTIVE;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -55,80 +59,40 @@ Hooks.on("getCombatTrackerEntryContext", function (html, menu) {
|
|||||||
menu.splice(1, 0, entry);
|
menu.splice(1, 0, entry);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function forwardIfUnahndled(newFn) {
|
||||||
|
return function(oldFn, ...args) {
|
||||||
|
const eventHandled = newFn(...args);
|
||||||
|
if (!eventHandled)
|
||||||
|
oldFn(...args);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function hookDragHandlers(entityType) {
|
function hookDragHandlers(entityType) {
|
||||||
const originalDragLeftStartHandler = entityType.prototype._onDragLeftStart
|
const entityName = entityType.name
|
||||||
entityType.prototype._onDragLeftStart = function(event) {
|
libWrapper.register("drag-ruler", `${entityName}.prototype._onDragLeftStart`, onEntityLeftDragStart, "WRAPPER");
|
||||||
originalDragLeftStartHandler.call(this, event)
|
if (entityType === Token)
|
||||||
onEntityLeftDragStart.call(this, event)
|
libWrapper.register("drag-ruler", `${entityName}.prototype._onDragLeftMove`, onEntityLeftDragMoveSnap, "WRAPPER");
|
||||||
}
|
else
|
||||||
|
libWrapper.register("drag-ruler", `${entityName}.prototype._onDragLeftMove`, onEntityLeftDragMove, "WRAPPER");
|
||||||
const originalDragLeftMoveHandler = entityType.prototype._onDragLeftMove
|
libWrapper.register("drag-ruler", `${entityName}.prototype._onDragLeftDrop`, forwardIfUnahndled(onEntityDragLeftDrop), "MIXED");
|
||||||
entityType.prototype._onDragLeftMove = function (event) {
|
libWrapper.register("drag-ruler", `${entityName}.prototype._onDragLeftCancel`, forwardIfUnahndled(onEntityDragLeftCancel), "MIXED");
|
||||||
originalDragLeftMoveHandler.call(this, event)
|
|
||||||
onEntityLeftDragMove.call(this, event)
|
|
||||||
}
|
|
||||||
|
|
||||||
const originalDragLeftDropHandler = entityType.prototype._onDragLeftDrop
|
|
||||||
entityType.prototype._onDragLeftDrop = function (event) {
|
|
||||||
const eventHandled = onEntityDragLeftDrop.call(this, event)
|
|
||||||
if (!eventHandled)
|
|
||||||
originalDragLeftDropHandler.call(this, event)
|
|
||||||
}
|
|
||||||
|
|
||||||
const originalDragLeftCancelHandler = entityType.prototype._onDragLeftCancel
|
|
||||||
entityType.prototype._onDragLeftCancel = function (event) {
|
|
||||||
const eventHandled = onEntityDragLeftCancel.call(this, event)
|
|
||||||
if (!eventHandled)
|
|
||||||
originalDragLeftCancelHandler.call(this, event)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function hookKeyboardManagerFunctions() {
|
async function tokenLayerUndoHistory(wrapped) {
|
||||||
const originalHandleKeys = KeyboardManager.prototype._handleKeys
|
const historyEntry = this.history[this.history.length - 1];
|
||||||
KeyboardManager.prototype._handleKeys = function (event, key, up) {
|
const returnValue = await wrapped();
|
||||||
const eventHandled = handleKeys.call(this, event, key, up)
|
if (historyEntry.type === "update") {
|
||||||
if (!eventHandled)
|
for (const entry of historyEntry.data) {
|
||||||
originalHandleKeys.call(this, event, key, up)
|
const token = canvas.tokens.get(entry._id);
|
||||||
|
removeLastHistoryEntryIfAt(token, entry.x, entry.y);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return returnValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleKeys(event, key, up) {
|
function onEntityLeftDragStart(wrapped, event) {
|
||||||
if (event.repeat || this.hasFocus)
|
wrapped(event);
|
||||||
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
|
|
||||||
const ruler = canvas.controls.ruler;
|
|
||||||
if (!ruler.isDragRuler)
|
|
||||||
return false
|
|
||||||
|
|
||||||
ruler.dragRulerDeleteWaypoint();
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
function onKeyShift(up) {
|
|
||||||
const ruler = canvas.controls.ruler
|
|
||||||
if (!ruler.isDragRuler)
|
|
||||||
return false
|
|
||||||
if (ruler._state !== Ruler.STATES.MEASURING)
|
|
||||||
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 onEntityLeftDragStart(event) {
|
|
||||||
const isToken = this instanceof Token;
|
const isToken = this instanceof Token;
|
||||||
if (isToken && !currentSpeedProvider.usesRuler(this))
|
|
||||||
return
|
|
||||||
const ruler = canvas.controls.ruler
|
const ruler = canvas.controls.ruler
|
||||||
ruler.draggedEntity = this;
|
ruler.draggedEntity = this;
|
||||||
let entityCenter;
|
let entityCenter;
|
||||||
@@ -136,15 +100,21 @@ function onEntityLeftDragStart(event) {
|
|||||||
entityCenter = getHexSizeSupportTokenGridCenter(this);
|
entityCenter = getHexSizeSupportTokenGridCenter(this);
|
||||||
else
|
else
|
||||||
entityCenter = this.center;
|
entityCenter = this.center;
|
||||||
ruler.clear();
|
|
||||||
ruler._state = Ruler.STATES.STARTING;
|
|
||||||
ruler.rulerOffset = {x: entityCenter.x - event.data.origin.x, y: entityCenter.y - event.data.origin.y};
|
ruler.rulerOffset = {x: entityCenter.x - event.data.origin.x, y: entityCenter.y - event.data.origin.y};
|
||||||
if (isToken && game.settings.get(settingsKey, "enableMovementHistory"))
|
if (game.settings.get(settingsKey, "autoStartMeasurement")) {
|
||||||
ruler.dragRulerAddWaypointHistory(getMovementHistory(this));
|
let options = {};
|
||||||
ruler.dragRulerAddWaypoint(entityCenter, false);
|
setSnapParameterOnOptions(ruler, options);
|
||||||
|
ruler.dragRulerStart(options, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onEntityLeftDragMove(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
|
const ruler = canvas.controls.ruler
|
||||||
if (ruler.isDragRuler)
|
if (ruler.isDragRuler)
|
||||||
onMouseMove.call(ruler, event)
|
onMouseMove.call(ruler, event)
|
||||||
@@ -152,8 +122,10 @@ function onEntityLeftDragMove(event) {
|
|||||||
|
|
||||||
function onEntityDragLeftDrop(event) {
|
function onEntityDragLeftDrop(event) {
|
||||||
const ruler = canvas.controls.ruler
|
const ruler = canvas.controls.ruler
|
||||||
if (!ruler.isDragRuler)
|
if (!ruler.isDragRuler) {
|
||||||
|
ruler.draggedEntity = undefined;
|
||||||
return false
|
return false
|
||||||
|
}
|
||||||
// When we're dragging a measured template no token will ever be selected,
|
// When we're dragging a measured template no token will ever be selected,
|
||||||
// resulting in only the dragged template to be moved as would be expected
|
// resulting in only the dragged template to be moved as would be expected
|
||||||
const selectedTokens = canvas.tokens.controlled
|
const selectedTokens = canvas.tokens.controlled
|
||||||
@@ -168,43 +140,100 @@ function onEntityDragLeftDrop(event) {
|
|||||||
function onEntityDragLeftCancel(event) {
|
function onEntityDragLeftCancel(event) {
|
||||||
// This function is invoked by right clicking
|
// This function is invoked by right clicking
|
||||||
const ruler = canvas.controls.ruler
|
const ruler = canvas.controls.ruler
|
||||||
if (!ruler.isDragRuler || ruler._state === Ruler.STATES.MOVING)
|
if (!ruler.draggedEntity || ruler._state === Ruler.STATES.MOVING)
|
||||||
return false
|
return false
|
||||||
if (ruler._state === Ruler.STATES.MEASURING) {
|
|
||||||
if (!game.settings.get(settingsKey, "swapSpacebarRightClick")) {
|
const rightClickAction = game.settings.get(settingsKey, "rightClickAction");
|
||||||
ruler.dragRulerDeleteWaypoint(event);
|
let options = {};
|
||||||
}
|
setSnapParameterOnOptions(ruler, options);
|
||||||
else {
|
|
||||||
event.preventDefault()
|
if (ruler._state === Ruler.STATES.INACTIVE) {
|
||||||
const snap = !event.shiftKey
|
if (rightClickAction !== RightClickAction.CREATE_WAYPOINT)
|
||||||
ruler.dragRulerAddWaypoint(ruler.destination, snap);
|
return false;
|
||||||
|
ruler.dragRulerStart(options);
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
else if (ruler._state === Ruler.STATES.MEASURING) {
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getColorForDistance(startDistance, subDistance=0) {
|
function applyGridlessSnapping(event) {
|
||||||
if (!this.isDragRuler)
|
const ruler = canvas.controls.ruler;
|
||||||
return this.color
|
if (!game.settings.get(settingsKey, "useGridlessRaster"))
|
||||||
if (!this.draggedEntity.actor) {
|
return;
|
||||||
return this.color;
|
if (!ruler.isDragRuler)
|
||||||
|
return;
|
||||||
|
if (disableSnap)
|
||||||
|
return;
|
||||||
|
if (canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const rasterWidth = 35 / canvas.stage.scale.x;
|
||||||
|
const tokenX = event.data.destination.x;
|
||||||
|
const tokenY = event.data.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.dragRulerGetRaysFromWaypoints(ruler.waypoints, destination).map(ray => {return {ray}});
|
||||||
|
const pinpointDistances = new Map();
|
||||||
|
for (const range of ranges) {
|
||||||
|
pinpointDistances.set(range.range, null);
|
||||||
|
}
|
||||||
|
terrainRuler.measureDistances(segments, {pinpointDistances});
|
||||||
|
const targetDistance = Array.from(pinpointDistances.entries())
|
||||||
|
.filter(([_key, val]) => val)
|
||||||
|
.reduce((value, current) => value[0] > current[0] ? value : current, [0, null]);
|
||||||
|
const rasterLocation = targetDistance[1];
|
||||||
|
if (rasterLocation) {
|
||||||
|
const deltaX = destination.x - rasterLocation.x;
|
||||||
|
const deltaY = destination.y - rasterLocation.y;
|
||||||
|
const rasterDistance = Math.hypot(deltaX, deltaY);
|
||||||
|
if (rasterDistance < rasterWidth) {
|
||||||
|
event.data.destination.x = rasterLocation.x - ruler.rulerOffset.x;
|
||||||
|
event.data.destination.y = rasterLocation.y - ruler.rulerOffset.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Don't apply colors if the current user doesn't have at least observer permissions
|
else {
|
||||||
if (this.draggedEntity.actor.permission < 2) {
|
let waypointDistance = 0;
|
||||||
// If this is a pc and alwaysShowSpeedForPCs is enabled we show the color anyway
|
let origin = event.data.origin;
|
||||||
if (!(this.draggedEntity.actor.data.type === "character" && game.settings.get(settingsKey, "alwaysShowSpeedForPCs")))
|
if (ruler.waypoints.length > 1) {
|
||||||
return this.color
|
const segments = Ruler.dragRulerGetRaysFromWaypoints(ruler.waypoints, destination).map(ray => {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};
|
||||||
|
}
|
||||||
|
|
||||||
|
const deltaX = tokenX - origin.x;
|
||||||
|
const deltaY = tokenY - origin.y;
|
||||||
|
const distance = Math.hypot(deltaX, deltaY);
|
||||||
|
// targetRange will be the largest range that's still smaller than distance
|
||||||
|
let targetDistance = ranges
|
||||||
|
.map(range => range.range)
|
||||||
|
.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.data.destination.x = origin.x + deltaX * targetDistance / distance;
|
||||||
|
event.data.destination.y = origin.y + deltaY * targetDistance / distance;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const distance = startDistance + subDistance
|
|
||||||
if (!this.dragRulerRanges)
|
|
||||||
this.dragRulerRanges = getRangesFromSpeedProvider(this.draggedEntity);
|
|
||||||
const ranges = this.dragRulerRanges;
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|||||||
+35
-19
@@ -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)
|
||||||
return
|
await performWorldMigraionts()
|
||||||
|
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
|
|
||||||
let speedAttribute = game.settings.storage.get("world").get(`${settingsKey}.speedAttribute`)
|
|
||||||
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`)
|
|
||||||
const dashMultiplierDefault = game.settings.get(settingsKey, "speedProviders.native.setting.dashMultiplier")
|
|
||||||
if (dashMultiplier !== dashMultiplierDefault)
|
|
||||||
game.settings.set(settingsKey, "speedProviders.native.setting.dashMultiplier", dashMultiplier)
|
|
||||||
// End of unnamed version migration code
|
|
||||||
game.settings.set(settingsKey, "dataVersion", currentDataVersion)
|
game.settings.set(settingsKey, "dataVersion", currentDataVersion)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dataVersion === "1.3.0") {
|
||||||
|
dataVersion = "1.10.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
game.settings.set(settingsKey, "dataVersion", dataVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import {measureDistances} from "./compatibility.js";
|
import {measureDistances} from "./compatibility.js";
|
||||||
import {recalculate, updateCombatantDragRulerFlags} from "./socket.js";
|
import {recalculate, updateCombatantDragRulerFlags} from "./socket.js";
|
||||||
import {getTokenShape, zip} from "./util.js";
|
import {getTokenShape, isClose, zip} from "./util.js";
|
||||||
|
|
||||||
function initTrackingFlag(combatant) {
|
function initTrackingFlag(combatant) {
|
||||||
const initialFlag = {passedWaypoints: [], trackedRound: 0};
|
const initialFlag = {passedWaypoints: [], trackedRound: 0};
|
||||||
@@ -49,14 +49,14 @@ function calculateUpdate(combat, token, rays) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add the passed waypoints to the combatant
|
// Add the passed waypoints to the combatant
|
||||||
const terrainRulerAvailable = game.modules.get("terrain-ruler")?.active && (!game.modules.get("TerrainLayer")?.active || canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS);
|
const terrainRulerAvailable = game.modules.get("terrain-ruler")?.active;
|
||||||
const dragRulerFlags = combatant.data.flags.dragRuler;
|
const dragRulerFlags = combatant.data.flags.dragRuler;
|
||||||
const waypoints = dragRulerFlags.passedWaypoints;
|
const waypoints = dragRulerFlags.passedWaypoints;
|
||||||
for (const ray of rays) {
|
for (const ray of rays) {
|
||||||
// Ignore rays that have the same start and end coordinates
|
// Ignore rays that have the same start and end coordinates
|
||||||
if (ray.A.x !== ray.B.x || ray.A.y !== ray.B.y) {
|
if (ray.A.x !== ray.B.x || ray.A.y !== ray.B.y) {
|
||||||
if (terrainRulerAvailable) {
|
if (terrainRulerAvailable) {
|
||||||
measureDistances([{ray}], token, getTokenShape(token), {terrainRulerInitialState: waypoints[waypoints.length - 1]?.dragRulerFinalState});
|
measureDistances([{ray}], token, getTokenShape(token), {terrainRulerInitialState: waypoints[waypoints.length - 1]?.dragRulerFinalState, enableTerrainRuler: terrainRulerAvailable});
|
||||||
ray.A.dragRulerVisitedSpaces = ray.terrainRulerVisitedSpaces;
|
ray.A.dragRulerVisitedSpaces = ray.terrainRulerVisitedSpaces;
|
||||||
ray.A.dragRulerFinalState = ray.terrainRulerFinalState;
|
ray.A.dragRulerFinalState = ray.terrainRulerFinalState;
|
||||||
}
|
}
|
||||||
@@ -81,6 +81,20 @@ export function getMovementHistory(token) {
|
|||||||
return dragRulerFlags.passedWaypoints ?? [];
|
return dragRulerFlags.passedWaypoints ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function removeLastHistoryEntryIfAt(token, x, y) {
|
||||||
|
const history = getMovementHistory(token);
|
||||||
|
if (history.length === 0)
|
||||||
|
return;
|
||||||
|
const entry = history[history.length - 1];
|
||||||
|
if (!isClose(x + token.w / 2, entry.x, 0.1) || !isClose(y + token.h / 2, entry.y, 0.1)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
history.pop();
|
||||||
|
const combat = game.combat;
|
||||||
|
const combatant = combat.getCombatantByToken(token.id);
|
||||||
|
await updateCombatantDragRulerFlags(combat, [{_id: combatant.id, dragRulerFlags: combatant.data.flags.dragRuler}]);
|
||||||
|
}
|
||||||
|
|
||||||
export async function resetMovementHistory(combat, combatantId) {
|
export async function resetMovementHistory(combat, combatantId) {
|
||||||
const combatant = combat.combatants.get(combatantId);
|
const combatant = combat.combatants.get(combatantId);
|
||||||
const dragRulerFlags = combatant.data.flags.dragRuler;
|
const dragRulerFlags = combatant.data.flags.dragRuler;
|
||||||
|
|||||||
+155
-105
@@ -1,105 +1,113 @@
|
|||||||
import {measure} from "./foundry_imports.js"
|
import {currentSpeedProvider, getColorForDistanceAndToken, getRangesFromSpeedProvider} from "./api.js";
|
||||||
|
import {getHexSizeSupportTokenGridCenter} from "./compatibility.js";
|
||||||
|
import {cancelScheduledMeasurement, measure} from "./foundry_imports.js"
|
||||||
import {getMovementHistory} from "./movement_tracking.js";
|
import {getMovementHistory} from "./movement_tracking.js";
|
||||||
import {settingsKey} from "./settings.js";
|
import {settingsKey} from "./settings.js";
|
||||||
import {getSnapPointForEntity} from "./util.js";
|
import {getSnapPointForEntity} from "./util.js";
|
||||||
|
|
||||||
export class DragRulerRuler extends Ruler {
|
export function extendRuler() {
|
||||||
// Functions below are overridden versions of functions in Ruler
|
class DragRulerRuler extends Ruler {
|
||||||
constructor(user, {color=null}={}) {
|
// Functions below are overridden versions of functions in Ruler
|
||||||
super(user, {color});
|
constructor(user, {color=null}={}) {
|
||||||
this.previousWaypoints = [];
|
super(user, {color});
|
||||||
this.previousLabels = this.addChild(new PIXI.Container());
|
this.previousWaypoints = [];
|
||||||
}
|
this.previousLabels = this.addChild(new PIXI.Container());
|
||||||
|
|
||||||
clear() {
|
|
||||||
super.clear();
|
|
||||||
this.previousWaypoints = [];
|
|
||||||
this.previousLabels.removeChildren().forEach(c => c.destroy());
|
|
||||||
this.dragRulerRanges = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
async moveToken(event) {
|
|
||||||
// This function is invoked by left clicking
|
|
||||||
if (!this.isDragRuler)
|
|
||||||
return await super.moveToken(event);
|
|
||||||
if (!game.settings.get(settingsKey, "swapSpacebarRightClick")) {
|
|
||||||
const snap = !event.shiftKey;
|
|
||||||
this.dragRulerAddWaypoint(this.destination, snap);
|
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
this.dragRulerDeleteWaypoint();
|
clear() {
|
||||||
|
super.clear();
|
||||||
|
this.previousWaypoints = [];
|
||||||
|
this.previousLabels.removeChildren().forEach(c => c.destroy());
|
||||||
|
this.dragRulerRanges = undefined;
|
||||||
|
cancelScheduledMeasurement.call(this);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
toJSON() {
|
async moveToken(event) {
|
||||||
const json = super.toJSON();
|
// Disable moveToken if Drag Ruler is active
|
||||||
if (this.draggedEntity) {
|
if (!this.isDragRuler)
|
||||||
const isToken = this.draggedEntity instanceof Token;
|
return await super.moveToken(event);
|
||||||
json["draggedEntityIsToken"] = isToken;
|
|
||||||
json["draggedEntity"] = this.draggedEntity.id;
|
|
||||||
}
|
}
|
||||||
return json;
|
|
||||||
}
|
|
||||||
|
|
||||||
update(data) {
|
toJSON() {
|
||||||
// Don't show a GMs drag ruler to non GM players
|
const json = super.toJSON();
|
||||||
if (data.draggedEntity && this.user.isGM && !game.user.isGM && !game.settings.get(settingsKey, "showGMRulerToPlayers"))
|
if (this.draggedEntity) {
|
||||||
return;
|
const isToken = this.draggedEntity instanceof Token;
|
||||||
|
json["draggedEntityIsToken"] = isToken;
|
||||||
if (data.draggedEntity) {
|
json["draggedEntity"] = this.draggedEntity.id;
|
||||||
if (data.draggedEntityIsToken)
|
}
|
||||||
this.draggedEntity = canvas.tokens.get(data.draggedEntity);
|
return json;
|
||||||
else
|
|
||||||
this.draggedEntity = canvas.templates.get(data.draggedEntity);
|
|
||||||
}
|
}
|
||||||
super.update(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
measure(destination, options={}) {
|
update(data) {
|
||||||
if (this.isDragRuler) {
|
// Don't show a GMs drag ruler to non GM players
|
||||||
return measure.call(this, destination, options);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
super.update(data);
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
return super.measure(destination, options);
|
measure(destination, options={}) {
|
||||||
|
if (this.isDragRuler) {
|
||||||
|
return measure.call(this, destination, options);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return super.measure(destination, options);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
_endMeasurement() {
|
_endMeasurement() {
|
||||||
super._endMeasurement();
|
super._endMeasurement();
|
||||||
this.draggedEntity = null;
|
this.draggedEntity = null;
|
||||||
}
|
|
||||||
|
|
||||||
// The functions below aren't present in the orignal Ruler class and are added by Drag Ruler
|
|
||||||
dragRulerAddWaypoint(point, snap=true) {
|
|
||||||
if (snap) {
|
|
||||||
point = getSnapPointForEntity(point.x, point.y, this.draggedEntity);
|
|
||||||
}
|
}
|
||||||
this.waypoints.push(new PIXI.Point(point.x, point.y));
|
|
||||||
this.labels.addChild(new PreciseText("", CONFIG.canvasTextStyle));
|
|
||||||
}
|
|
||||||
|
|
||||||
dragRulerAddWaypointHistory(waypoints) {
|
// The functions below aren't present in the orignal Ruler class and are added by Drag Ruler
|
||||||
waypoints.forEach(waypoint => waypoint.isPrevious = true);
|
dragRulerAddWaypoint(point, options={}) {
|
||||||
this.waypoints = this.waypoints.concat(waypoints);
|
options.snap = options.snap ?? true;
|
||||||
for (const waypoint of waypoints) {
|
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.labels.addChild(new PreciseText("", CONFIG.canvasTextStyle));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
dragRulerClearWaypoints() {
|
dragRulerAddWaypointHistory(waypoints) {
|
||||||
this.waypoints = [];
|
waypoints.forEach(waypoint => waypoint.isPrevious = true);
|
||||||
this.labels.removeChildren().forEach(c => c.destroy());
|
this.waypoints = this.waypoints.concat(waypoints);
|
||||||
}
|
for (const waypoint of waypoints) {
|
||||||
|
this.labels.addChild(new PreciseText("", CONFIG.canvasTextStyle));
|
||||||
dragRulerDeleteWaypoint(event={preventDefault: () => {return}}) {
|
}
|
||||||
if (this.waypoints.filter(w => !w.isPrevious).length > 1) {
|
|
||||||
event.preventDefault();
|
|
||||||
const mousePosition = canvas.app.renderer.plugins.interaction.mouse.getLocalPosition(canvas.tokens);
|
|
||||||
const rulerOffset = this.rulerOffset;
|
|
||||||
this._removeWaypoint({x: mousePosition.x + rulerOffset.x, y: mousePosition.y + rulerOffset.y});
|
|
||||||
game.user.broadcastActivity({ruler: this});
|
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
|
dragRulerClearWaypoints() {
|
||||||
|
this.waypoints = [];
|
||||||
|
this.labels.removeChildren().forEach(c => c.destroy());
|
||||||
|
}
|
||||||
|
|
||||||
|
dragRulerDeleteWaypoint(event={preventDefault: () => {return}}, options={}) {
|
||||||
|
options.snap = options.snap ?? true;
|
||||||
|
if (this.waypoints.filter(w => !w.isPrevious).length > 1) {
|
||||||
|
event.preventDefault();
|
||||||
|
const mousePosition = canvas.app.renderer.plugins.interaction.mouse.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dragRulerAbortDrag(event={preventDefault: () => {return}}) {
|
||||||
const token = this.draggedEntity;
|
const token = this.draggedEntity;
|
||||||
this._endMeasurement();
|
this._endMeasurement();
|
||||||
|
|
||||||
@@ -111,31 +119,73 @@ export class DragRulerRuler extends Ruler {
|
|||||||
// Pass in a fake event that hopefully is enough to allow other modules to function
|
// Pass in a fake event that hopefully is enough to allow other modules to function
|
||||||
token._onDragLeftCancel(event);
|
token._onDragLeftCancel(event);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
async dragRulerRecalculate(tokenIds) {
|
async dragRulerRecalculate(tokenIds) {
|
||||||
if (this._state !== Ruler.STATES.MEASURING)
|
if (this._state !== Ruler.STATES.MEASURING)
|
||||||
return;
|
return;
|
||||||
if (tokenIds && !tokenIds.includes(this.draggedEntity.id))
|
if (tokenIds && !tokenIds.includes(this.draggedEntity.id))
|
||||||
return;
|
return;
|
||||||
const waypoints = this.waypoints.filter(waypoint => !waypoint.isPrevious);
|
const waypoints = this.waypoints.filter(waypoint => !waypoint.isPrevious);
|
||||||
this.dragRulerClearWaypoints();
|
this.dragRulerClearWaypoints();
|
||||||
if (game.settings.get(settingsKey, "enableMovementHistory"))
|
if (game.settings.get(settingsKey, "enableMovementHistory"))
|
||||||
this.dragRulerAddWaypointHistory(getMovementHistory(this.draggedEntity));
|
this.dragRulerAddWaypointHistory(getMovementHistory(this.draggedEntity));
|
||||||
for (const waypoint of waypoints) {
|
for (const waypoint of waypoints) {
|
||||||
this.dragRulerAddWaypoint(waypoint, false);
|
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.data.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;
|
||||||
|
let entityCenter;
|
||||||
|
if (isToken && canvas.grid.isHex && game.modules.get("hex-size-support")?.active && CONFIG.hexSizeSupport.getAltSnappingFlag(entity))
|
||||||
|
entityCenter = getHexSizeSupportTokenGridCenter(entity);
|
||||||
|
else
|
||||||
|
entityCenter = entity.center;
|
||||||
|
if (isToken && game.settings.get(settingsKey, "enableMovementHistory"))
|
||||||
|
ruler.dragRulerAddWaypointHistory(getMovementHistory(entity));
|
||||||
|
ruler.dragRulerAddWaypoint(entityCenter, {snap: false});
|
||||||
|
const mousePosition = canvas.app.renderer.plugins.interaction.mouse.getLocalPosition(canvas.tokens);
|
||||||
|
const destination = {x: mousePosition.x + ruler.rulerOffset.x, y: mousePosition.y + ruler.rulerOffset.y};
|
||||||
|
if (measureImmediately)
|
||||||
|
ruler.measure(destination, options);
|
||||||
}
|
}
|
||||||
this.measure(this.destination);
|
|
||||||
game.user.broadcastActivity({ruler: this});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static dragRulerGetRaysFromWaypoints(waypoints, destination) {
|
Ruler = DragRulerRuler;
|
||||||
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;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+40
-5
@@ -3,6 +3,12 @@ import {SpeedProvider} from "./speed_provider.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,
|
||||||
|
});
|
||||||
|
|
||||||
export function registerSettings() {
|
export function registerSettings() {
|
||||||
game.settings.register(settingsKey, "dataVersion", {
|
game.settings.register(settingsKey, "dataVersion", {
|
||||||
scope: "world",
|
scope: "world",
|
||||||
@@ -11,14 +17,43 @@ export function registerSettings() {
|
|||||||
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",
|
||||||
|
|||||||
@@ -61,13 +61,18 @@ export class SpeedProvider {
|
|||||||
* The area indicates the whole area that the token will occupy (for tokens larger than 1x1) the array will more than one entry.
|
* The area indicates the whole area that the token will occupy (for tokens larger than 1x1) the array will more than one entry.
|
||||||
* The return value should be an integer indicating a multiplicator by that the cost of that step should be increased.
|
* The return value should be an integer indicating a multiplicator by that the cost of that step should be increased.
|
||||||
* (1 is regular cost, 2 costs double, 3 costs triple, ...)
|
* (1 is regular cost, 2 costs double, 3 costs triple, ...)
|
||||||
* This function is only called if the TerrainLayer and TerrainRuler modules are enabled.
|
*
|
||||||
|
* Parameters:
|
||||||
|
* - options: An object used to configure Enhanced Terrain Layer's cost calculation. Ex: If options.ignoreGrid is set to true, then Euclidean measurement can be forced on a gridded map.
|
||||||
|
*
|
||||||
|
* This function is only called if the Enhanced Terrain Layer and Terrain Ruler modules are enabled.
|
||||||
*
|
*
|
||||||
* Implementing this method is optional and only needs to be done if you want to provide a custom cost function (for example to allow tokens to ignore difficult terrain)
|
* Implementing this method is optional and only needs to be done if you want to provide a custom cost function (for example to allow tokens to ignore difficult terrain)
|
||||||
*/
|
*/
|
||||||
getCostForStep(token, area) {
|
getCostForStep(token, area, options={}) {
|
||||||
// Lookup the cost for each square occupied by the token
|
// Lookup the cost for each square occupied by the token
|
||||||
const costs = area.map(space => terrainRuler.getCost(space.x, space.y, {token}));
|
options.token = token;
|
||||||
|
const costs = area.map(space => terrainRuler.getCost(space.x, space.y, options));
|
||||||
// Return the maximum of the costs
|
// Return the maximum of the costs
|
||||||
return costs.reduce((max, current) => Math.max(max, current))
|
return costs.reduce((max, current) => Math.max(max, current))
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-3
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
export function getDefaultSpeedAttribute() {
|
export function getDefaultSpeedAttribute() {
|
||||||
switch (game.system.id) {
|
switch (game.system.id) {
|
||||||
case "CoC7":
|
case "CoC7":
|
||||||
@@ -8,10 +7,10 @@ export function getDefaultSpeedAttribute() {
|
|||||||
case "dnd5e":
|
case "dnd5e":
|
||||||
return "actor.data.data.attributes.movement.walk"
|
return "actor.data.data.attributes.movement.walk"
|
||||||
case "lancer":
|
case "lancer":
|
||||||
return "actor.data.data.mech.speed"
|
return "actor.data.data.derived.speed"
|
||||||
case "pf1":
|
case "pf1":
|
||||||
case "D35E":
|
case "D35E":
|
||||||
return "actor.data.data.attributes.speed.land.total"
|
return "actor.data.data.attributes.speed.land.total"
|
||||||
case "sfrpg":
|
case "sfrpg":
|
||||||
return "actor.data.data.attributes.speed.value";
|
return "actor.data.data.attributes.speed.value";
|
||||||
case "shadowrun5e":
|
case "shadowrun5e":
|
||||||
|
|||||||
+68
-16
@@ -1,4 +1,5 @@
|
|||||||
import {getPixelsFromGridPosition} from "./foundry_fixes.js"
|
import {getPixelsFromGridPosition} from "./foundry_fixes.js"
|
||||||
|
import { disableSnap } from "./keybindings.js";
|
||||||
|
|
||||||
export function* zip(it1, it2) {
|
export function* zip(it1, it2) {
|
||||||
for (let i = 0;i < Math.min(it1.length, it2.length);i++) {
|
for (let i = 0;i < Math.min(it1.length, it2.length);i++) {
|
||||||
@@ -10,33 +11,61 @@ export function getSnapPointForToken(x, y, token) {
|
|||||||
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
|
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
|
||||||
return new PIXI.Point(x, y);
|
return new PIXI.Point(x, y);
|
||||||
}
|
}
|
||||||
if (canvas.grid.isHex && game.modules.get("hex-size-support")?.active && CONFIG.hexSizeSupport.getAltSnappingFlag(token)) {
|
if (canvas.grid.isHex) {
|
||||||
if (token.getFlag("hex-size-support", "borderSize") % 2 === 0) {
|
if (game.modules.get("hex-size-support")?.active && CONFIG.hexSizeSupport.getAltSnappingFlag(token)) {
|
||||||
const snapPoint = CONFIG.hexSizeSupport.findVertexSnapPoint(x, y, token, canvas.grid.grid)
|
if (token.document.getFlag("hex-size-support", "borderSize") % 2 === 0) {
|
||||||
return new PIXI.Point(snapPoint.x, snapPoint.y)
|
const snapPoint = CONFIG.hexSizeSupport.findVertexSnapPoint(x, y, token, canvas.grid.grid)
|
||||||
|
return new PIXI.Point(snapPoint.x, snapPoint.y)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return new PIXI.Point(...canvas.grid.getCenter(x, y))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return new PIXI.Point(...canvas.grid.getCenter(x, y))
|
return new PIXI.Point(...canvas.grid.getCenter(x, y));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const [topLeftX, topLeftY] = canvas.grid.getTopLeft(x, y);
|
||||||
|
let cellX, cellY;
|
||||||
|
if (token.data.width % 2 === 0)
|
||||||
|
cellX = x - canvas.grid.h / 2;
|
||||||
|
else
|
||||||
|
cellX = x;
|
||||||
|
if (token.data.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
|
// Tiny tokens can snap to the cells corners
|
||||||
if (!canvas.grid.isHex && token.data.width <= 0.5) {
|
if (token.data.width <= 0.5) {
|
||||||
const [topLeftX, topLeftY] = canvas.grid.getTopLeft(x, y);
|
|
||||||
const offsetX = x - topLeftX;
|
const offsetX = x - topLeftX;
|
||||||
const offsetY = y - topLeftY;
|
|
||||||
const subGridWidth = Math.floor(canvas.grid.w / 2);
|
const subGridWidth = Math.floor(canvas.grid.w / 2);
|
||||||
const subGridHeight = Math.floor(canvas.grid.h / 2);
|
|
||||||
const subGridPosX = Math.floor(offsetX / subGridWidth);
|
const subGridPosX = Math.floor(offsetX / subGridWidth);
|
||||||
const subGridPosY = Math.floor(offsetY / subGridHeight);
|
snapX = topLeftX + (subGridPosX + 0.5) * subGridWidth;
|
||||||
return new PIXI.Point(topLeftX + (subGridPosX + 0.5) * subGridWidth, topLeftY + (subGridPosY + 0.5) * subGridHeight);
|
|
||||||
}
|
}
|
||||||
// Hex tokens, tokens with odd multipliers (1x1, 3x3, ...) and tokens smaller than 1x1 but bigger than 0.5x0.5 snap to the center of the grid cell
|
// Tokens with odd multipliers (1x1, 3x3, ...) and tokens smaller than 1x1 but bigger than 0.5x0.5 snap to the center of the grid cell
|
||||||
if (canvas.grid.isHex || Math.round(token.data.width) % 2 === 1 || token.data.width < 1) {
|
else if (Math.round(token.data.width) % 2 === 1 || token.data.width < 1) {
|
||||||
return new PIXI.Point(...canvas.grid.getCenter(x, y))
|
snapX = centerX;
|
||||||
}
|
}
|
||||||
// All remaining tokens (those with even or fractional multipliers on square grids) snap to the intersection points of the grid
|
// All remaining tokens (those with even or fractional multipliers on square grids) snap to the intersection points of the grid
|
||||||
const [snappedX, snappedY] = canvas.grid.getCenter(x - canvas.grid.w / 2, y - canvas.grid.h / 2)
|
else {
|
||||||
return new PIXI.Point(snappedX + canvas.grid.w / 2, snappedY + canvas.grid.h / 2)
|
snapX = centerX + canvas.grid.w / 2;
|
||||||
|
}
|
||||||
|
if (token.data.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.data.height) % 2 === 1 || token.data.height < 1) {
|
||||||
|
snapY = centerY;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
snapY = centerY + canvas.grid.h / 2;
|
||||||
|
}
|
||||||
|
return new PIXI.Point(snapX, snapY);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSnapPointForMeasuredTemplate(x, y) {
|
export function getSnapPointForMeasuredTemplate(x, y) {
|
||||||
@@ -198,3 +227,26 @@ export function applyTokenSizeOffset(waypoints, token) {
|
|||||||
|
|
||||||
return waypoints.map(w => new PIXI.Point(w.x + waypointOffset.x, w.y + waypointOffset.y))
|
return waypoints.map(w => new PIXI.Point(w.x + waypointOffset.x, w.y + waypointOffset.y))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setSnapParameterOnOptions(sourceObject, options) {
|
||||||
|
// Allow outside modules to override snapping
|
||||||
|
if (sourceObject.snapOverride?.active) {
|
||||||
|
options.snapOverrideActive = true;
|
||||||
|
options.snap = sourceObject.snapOverride.snap;
|
||||||
|
sourceObject.snapOverride = undefined; // remove it to prevent any lingering data issues
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
options.snap = !disableSnap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isClose(a, b, delta) {
|
||||||
|
return Math.abs(a - b) <= delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getMeasurePosition() {
|
||||||
|
const mousePosition = canvas.app.renderer.plugins.interaction.mouse.getLocalPosition(canvas.tokens);
|
||||||
|
const rulerOffset = canvas.controls.ruler.rulerOffset;
|
||||||
|
const measurePosition = {x: mousePosition.x + rulerOffset.x, y: mousePosition.y + rulerOffset.y};
|
||||||
|
return measurePosition;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user