Compare commits
54 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| 07d0c37fe9 | |||
| b8b97ac27a | |||
| 0e9215e8bb | |||
| dc4355e0de | |||
| cf7f84d9e4 | |||
| fe8a3b7b39 | |||
| be9535acd3 | |||
| 530a16ca5d | |||
| 3b9fbac80d | |||
| 0456fb0849 | |||
| 01d01f9887 | |||
| 9042b79967 | |||
| 4159d20e18 | |||
| 3897577756 | |||
| 5c29f401d6 | |||
| 1328d52f94 | |||
| eef05553c0 | |||
| 7ba89e4229 | |||
| d11d6385e2 | |||
| 4d2e4b7715 | |||
| 3cbe41e2be | |||
| 732802c579 | |||
| 8bbb11ddaa |
@@ -1,3 +1,91 @@
|
||||
## 1.9.1
|
||||
### Bugfixes
|
||||
- Fixed a bug that caused the ruler to misbehave or not show up at all if the speed provider isn't configured (this was a regression introduced in 1.9.0)
|
||||
|
||||
### Translation
|
||||
- Updated the spaish translation (thanks to Viriato139ac#342)
|
||||
|
||||
|
||||
## 1.9.0
|
||||
### New features
|
||||
- On Gridless scenes, tokens can now snap to their speed limits, to make full usage of a token's movement speed easier. This feature can be temporarily disabled by pressing Shift during drag and can be disabled completely in the settings.
|
||||
|
||||
### Bugfixes
|
||||
- Non-square tokens (e.g. 2x1) now work correctly on square grids
|
||||
- When modifying difficult terrain that a token has already moved over, this the movement history of the token won't change anymore (this was a regression introduced in 1.8.0)
|
||||
- Fixed a bug that prevented pausing/unpausing the game when no scene was active
|
||||
|
||||
### API
|
||||
- Added `dragRuler.getColorForDistanceAndToken` API endpoint that allows other modules to receive the highlight color for a specified distance with a given token.
|
||||
|
||||
|
||||
## 1.8.2
|
||||
### Compatibility
|
||||
- The generic speed provider defaults have been updated for lance 1.0 (thanks to BoltsJ!)
|
||||
- Eliminated a deprecation warning when both Drag Ruler and Hex Size Support are enabled (thanks Argonius-Angelus!)
|
||||
|
||||
|
||||
## 1.8.1
|
||||
### Bugfixes
|
||||
- Fixed a bug where the function that was bound to the spacebar key wouldn't work correctly when the "Toggle Snap To Grid" module was enabled
|
||||
|
||||
### Translation
|
||||
- Updated Spanish translation (thanks to Viriato139ac#342)
|
||||
|
||||
|
||||
## 1.8.0
|
||||
### New features
|
||||
- Pressing escape during a drag now cancels the drag
|
||||
- Undoing a movement via Ctrl+Z will now also remove that movement from Drag Ruler's movement history
|
||||
- Drag Ruler can now configured to stay disabled by default when a Token/Template is being dragged. In that case it will activate once the button to place a waypoint is being pressed.
|
||||
|
||||
### Bugfixes
|
||||
- Fixed a bug that caused the ruler to snap to grid when a waypoint was deleted while shift was being pressed (thanks to Michael Clavell!)
|
||||
- Fixed a bug that could leave behind a single waypoint on the canvas when canceling a dragging operation while moving the mouse
|
||||
|
||||
### Compatibility
|
||||
- Drag Ruler is now compatible with the "Toggle Snap To Grid" module (thanks to Michael Clavell!)
|
||||
|
||||
|
||||
## 1.7.7
|
||||
### Compatibility
|
||||
- Updated the default settings for the swade game system. The new default speed attribute points to a speed value that gets adjusted for wounds.
|
||||
|
||||
|
||||
## 1.7.6
|
||||
### Translation
|
||||
- Added Korean translation (thanks to KLO#1490)
|
||||
- Added Spanish translation (thanks to Viriato139ac#342)
|
||||
|
||||
### Compatibility
|
||||
- Drag Ruler's Generic Speed Provider is now awar of good default values for the "Call of Cthulhu 7th edition (Unofficial)" game system
|
||||
- Drag Ruler is now compatible with Foundry 0.8.8
|
||||
|
||||
## 1.7.5
|
||||
### Bugfixes
|
||||
- Decimal speeds (as often used in metric game systems) are no longer being rounded down (thanks to DarKDinDoN for diagnosing this bug)
|
||||
|
||||
|
||||
## 1.7.4
|
||||
### Bugfixes
|
||||
- Fixed a bug where the ruler would wrongly snap to the grid center for other players when dragging a measurement template
|
||||
|
||||
### Compatibility
|
||||
- Drag Ruler is now compatiblie with the "Monk's Active Tile Triggers" module
|
||||
- Drag Ruler's Generic Speed Provider is now aware of good default values for the D&D 3.5 game system
|
||||
- Drag Ruler is now compatible with Foundry 0.8.7
|
||||
|
||||
## 1.7.3
|
||||
### Compatibility
|
||||
- Drag Ruler is now compatible with Foundry 0.8.5
|
||||
|
||||
|
||||
## 1.7.2
|
||||
### Bugfixes
|
||||
- Fixed a bug that prevented waypoints for measurement templates from snapping to any other point than a grid cell corner (or grid cell center on hex)
|
||||
- Fixed a bug that could cause the ruler to not end up at the token's center (especially if the token is being moved very quickly and then stopped abruptly)
|
||||
|
||||
|
||||
## 1.7.1
|
||||
### Bugfixes
|
||||
- Fixed a bug that prevented players from moving their tokens ([#74](https://github.com/manuelVo/foundryvtt-drag-ruler/issues/74))
|
||||
|
||||
@@ -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,43 +1,52 @@
|
||||
[](https://ko-fi.com/staebchenfisch)
|
||||
|
||||
# Drag Ruler
|
||||
This module shows a ruler when you drag a token to infrom you how far you've dragged the token from it's start point. Additionally, if you're using a grid, the spaces the token will travel though will be colored depending on your tokens speed. If you're using a gridless map the ruler color will change to convey this information.
|
||||
This module shows a ruler when you drag a token or measurement template to inform you how far you've dragged it from its start point. Additionally, if you're using a grid, the spaces the token will travel though will be colored depending on your tokens speed. By default, three colors are being used: green for spaces that your token can reach by walking normally are colored green, spaces that can only be reached by dashing will be colored yellow and spaces that cannot be reached with the token's speed will be colored red. If you're using a gridless map the ruler color will change to convey this information.
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
|
||||
## Path color
|
||||

|
||||
## Supports Tokens of all sizes
|
||||
Terrain ruler has excellent support for tokens of all sizes. The Ruler will always originate from the token's center and will always highlight all the squares that tokens move over. If the [Hex Token Size Support](https://foundryvtt.com/packages/hex-size-support) is installed this is also true for large tokens on hex scenes.
|
||||
|
||||
The token has a speed of 30ft. All squares on the path within that range are colored in the players color. If the token is running it can cover double that range (this can be changed in the settings). All Squares on the path that can only be reached while running are colored in yellow. Squares on the path that the token cannot possibly reached at regular speeds are colored red. This coloring behavior can be tweaked in the settings and can be overridden by modules and game systems to provide more granular, game system specific control. For more information on how to do this see the [API](#api) section of this document.
|
||||

|
||||

|
||||
|
||||
|
||||
## Waypoints
|
||||
You can add waypoints to the path by pressing spacebar while you drag the token. To remove a placed waypoint press the right mouse button.
|
||||
## Difficult Terrain support
|
||||
**To use support for difficult terrain you must install the [Terrain Ruler](https://foundryvtt.com/packages/terrain-ruler) module**
|
||||
|
||||

|
||||
With the Terrain Ruler module installed, Drag Ruler is able to take difficult terrain that was placed down using the [Enhanced Terrain Layer](https://foundryvtt.com/packages/enhanced-terrain-layer) or [TerrainLayer](https://foundryvtt.com/packages/TerrainLayer) module into account.
|
||||
|
||||
## Difficult Terrain
|
||||
**To use support for difficult terrain you must install the [Terrain Ruler module](https://foundryvtt.com/packages/terrain-ruler)**
|
||||
|
||||
With the Terrain Ruler module installed, Drag Ruler is able to take difficult terrain that was placed with the [TerrainLayer module](https://foundryvtt.com/packages/TerrainLayer/) into account when measuring distances.
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
|
||||
## Why would I want to use this instead of ShowDragDistance?
|
||||
ShowDragDistance isn't maintained anymore. This means that it is at risk to stop working with every foundry update. In fact this process has already begun. As of Foundry Version 0.7.9 ShowDragDistance doesn't work anymore on gridless maps. Drag Ruler on the other hand is fully compatible with the current Foundry release and I'll continue updating it for future foundry releases for the forseeable future.
|
||||
## Movement history (optional)
|
||||
*This feature can be disabled in the settings if you don't like it*
|
||||
|
||||
In addition Drag Ruler provides more flexibility for game systems and modules via it's api to provide an experience that fits the rules of the game system that you are playing best.
|
||||
During combat, Drag Ruler will remember the path a token has taken during it's turn. When the token is being picked up again, Drag Ruler will continue measuring where it has left off. The path of the previous movement will be dipslayed in a faded color.
|
||||
|
||||

|
||||
|
||||
|
||||
## 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:
|
||||
- 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)
|
||||
- Lancer (via the module [Lancer Speed Provider](https://foundryvtt.com/packages/lancer-speed-provider))
|
||||
- Pathfinder 1 (starting with version 0.77.3)
|
||||
- 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)
|
||||
- Tagmar RPG (starting with version 1.1.4)
|
||||
- TheWitcherTRPG (starting with version 0.0.62)
|
||||
- Tormenta20 (starting with version 1.1.37)
|
||||
- Shadow of the Demon Lord (starting with version 1.7.15)
|
||||
- Wasteland Ventures (starting with version 0.1.0)
|
||||
@@ -50,6 +59,8 @@ Drag Ruler is available in the follwing languages:
|
||||
- English
|
||||
- German
|
||||
- Japanese (thanks to touge)
|
||||
- Korean (thanks to KLO#1490)
|
||||
- Spanish (thanks to Viriato139ac#342)
|
||||
|
||||
## API
|
||||
*Audience: This paragraph is intended for module and system devleopers that want to add more complex behavior to Drag Ruler. If you just want to use this plugins features skip this paragraph.*
|
||||
|
||||
@@ -34,6 +34,10 @@
|
||||
"name": "Geschwindigkeit von Spielercharakteren für jeden anzeigen",
|
||||
"hint": "Wenn diese Einstellung aktiviert ist wird die Färbung der hervorgehobenen Felder bei Spielercharakteren allen Spielern angezeigt, selbst wenn diese keinen Zugriff auf den Charakterbogen haben."
|
||||
},
|
||||
"autoStartMeasurement": {
|
||||
"name": "Automatisch mit Messen beginnen",
|
||||
"hint": "Wenn diese Option aktiviert ist beginnt Drag Ruler mit dem Messen sobald das Token bewegt wird. Wenn die Option deaktiviert ist bleibt Drag Ruler deaktiviert bis der Knopf zum platzieren eines Wegpunkts betätigt wird."
|
||||
},
|
||||
"enableMovementHistory": {
|
||||
"name": "Bewegungsverlauf während des Kampfes aktivieren",
|
||||
"hint": "Wenn 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."
|
||||
@@ -77,6 +81,10 @@
|
||||
"swapSpacebarRightClick": {
|
||||
"name": "Leertaste und Rechtsklick tauschen",
|
||||
"hint": "Die Funktionen der Leertaste und des Rechtsklicks sind, während eine Spielfigur bewegt wird, vertauscht. Wenn diese Option aktiviert wird können mit Rechtsklick Wegpunkte gesetzt werden und mit der Leertaste werden sie wieder gelöscht."
|
||||
},
|
||||
"useGridlessraster": {
|
||||
"name": "Geschwindigkeitsraster aktivieren",
|
||||
"hint": "Lässt Spielfiguren auf gitterlosen Szenen an deren Geschwindigkeitsgrenzen einrasten. Dies kann vorübergehend deaktiviert werden, indem während des ziehens die Umschalttaste gedrückt wird."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,10 @@
|
||||
"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."
|
||||
},
|
||||
"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": {
|
||||
"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."
|
||||
@@ -77,6 +81,10 @@
|
||||
"swapSpacebarRightClick": {
|
||||
"name": "Swap spacebar and right click",
|
||||
"hint": "Swaps the functions of spacebar and right click during dragging. If enabled right click will place waypoints and spacebar will delete them"
|
||||
},
|
||||
"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."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
{
|
||||
"drag-ruler": {
|
||||
"I18N": {
|
||||
"LANGUAGE": "Español",
|
||||
"MAINTAINERS": "@Viriato139ac#0342"
|
||||
},
|
||||
"dependencies": {
|
||||
"ok": "OK",
|
||||
"terrain-ruler": {
|
||||
"title": "Cómo medir a través de terreno difícil con Drag Ruler",
|
||||
"text": "You have the {moduleName} module enabled. Drag Ruler is able to measure difficult terrain that was placed down using that module. To make this possible Drag Ruler utilizes the Terrain Ruler module. If Terrain Ruler is installed and activated Drag Ruler will automatically start to respect difficult terrain in it's measurements.",
|
||||
"neverShowAgain": "No volver a mostrar"
|
||||
},
|
||||
"socketlib": {
|
||||
"title": "El módulo Socketlib no está instalado",
|
||||
"text": "Drag Ruler necesita para su funcionamiento correcto que instale socketlib. Por favor, active socketlib en este mundo"
|
||||
}
|
||||
},
|
||||
"resetMovementHistory": "Reiniciar historial de movimiento",
|
||||
"genericSpeedProvider": {
|
||||
"settings": {
|
||||
"dashMultiplier": {
|
||||
"name": "Multiplicador por correr",
|
||||
"hint": "Se puede usar para aplicar a los iconos una velocidad secundaria que use un color distinto en la medición de la ruta a seguir. Establece el valor a 0 para deshabilitar esta velocidad secundaria"
|
||||
},
|
||||
"speedAttribute": {
|
||||
"name": "Attributo de velocidad",
|
||||
"hint": "El atributo que define la velocidad de movimiento andando del icono. Se usa para los códigos de colores al medir la ruta a seguir"
|
||||
}
|
||||
},
|
||||
"speeds": {
|
||||
"walk": "caminar",
|
||||
"dash": "correr"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"alwaysShowSpeedForPCs": {
|
||||
"name": "Mostrar velocidad de los PJs a todo el mundo",
|
||||
"hint": "Si se habilita, se mostrará a todo el mundo los códigos de colores de las rutas de los PJs, incluso si no tienen permisos de observador para ese personaje"
|
||||
},
|
||||
"autoStartMeasurement": {
|
||||
"name": "Comenzar a medir automáticamente",
|
||||
"hint": "Si se habilita, Drag Ruler comenzará a medir en cuanto se comience a arrastrar un icono. Si se deshabilita, Drag Ruler permanecerá inactivo y comenzará a medir únicamente cuando se presione el botón configurado para añadir un nuevo punto de ruta"
|
||||
},
|
||||
"enableMovementHistory": {
|
||||
"name": "Habilitar historial de movimiento durante el combate",
|
||||
"hint": "Si se habilita, Drag Ruler recordará la ruta que ha seguido un icono en su turno y la mostrará al seleccionarlo de nuevo"
|
||||
},
|
||||
"showGMRulerToPlayers": {
|
||||
"name": "Mostrar regla del GM a los jugadores",
|
||||
"hint": "Si se deshabilita, no se mostrará la regla de medición de los GMs al resto de usuarios"
|
||||
},
|
||||
"speedProviderSettings": {
|
||||
"name": "Proveedor de configuración de velocidad",
|
||||
"hint": "El proveedor de ajustes de velocidad contiene todos los ajustes específicos de cada sistema",
|
||||
"button": "Proveedor de configuración de velocidad",
|
||||
"windowTitle": "Proveedor de configuración de velocidad",
|
||||
"headers": {
|
||||
"speedProvider": "Proveedor de velocidad",
|
||||
"speedProviderSettings": "Configuraciones específicas del proveedor de velocidad"
|
||||
},
|
||||
"activeProvider": {
|
||||
"name": "Proveedor de velocidad actualmente activo",
|
||||
"hint": "La configuración que se muestra más abajo depende del proveedor de velocidad activo. Si el GM selecciona un proveedor distinto, los ajustes disponibles puede que cambien"
|
||||
},
|
||||
"noSettings": "Este proveedor de velocidad no dispone de ninguna opción de configuración",
|
||||
"color": {
|
||||
"name": "Color para {colorName}",
|
||||
"hint": "Color que se usará para establecer el tono de las cuadrículas que se encuentran dentro del rango {colorName}",
|
||||
"unreachable": {
|
||||
"name": "inalcanzable",
|
||||
"hint": "Color que se usará para las cuadrículas más allá de la distancia máxima de movimiento del icono"
|
||||
}
|
||||
},
|
||||
"speedProvider": {
|
||||
"name": "Proveedor de configuración de velocidad",
|
||||
"hint": "Seleccione quien suministra la información de la velocidad de los iconos a la hora de colorear la ruta a seguir. Drag Ruler incluye un proveedor genérico que ofrece una funcionalidad básica y que probablemente funcione para casi todos los sistemas si se usa de forma correcta. Se pueden añadir otros proveedores de velocidad al instalar sistemas o módulos. Seleccionar un proveedor distinto al genérico asegurará una mejor integración con las reglas del sistema que está usando. Las opciones siguientes dependerán del proveedor que seleccione",
|
||||
"choices": {
|
||||
"module": "Módulo {name}",
|
||||
"native": "Genérico",
|
||||
"system": "Sistema {name}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"swapSpacebarRightClick": {
|
||||
"name": "Intercambiar barra espaciadora y clic-derecho",
|
||||
"hint": "Cambia el funcionamiento de la barra espaciadora y el clic-derecho del ratón mientras se arrastra un icono. Si está habilitado, clic-derecho establecerá puntos en la ruta y la barra espaciadora los borrará"
|
||||
},
|
||||
"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,83 @@
|
||||
{
|
||||
"drag-ruler": {
|
||||
"dependencies": {
|
||||
"ok": "OK",
|
||||
"terrain-ruler": {
|
||||
"title": "Drag Ruler를 사용한 험지 측정 방법",
|
||||
"text": "{moduleName} 모듈을 활성화했습니다. Drag Ruler는 해당 모듈을 사용하여 험지를 측정할 수 있습니다. 이렇게 하려면 Terrain Ruler 모듈을 활성화하십시오. Terrain Ruler 모듈을 설치하고 Drag Ruler를 활성화하면 자동적으로 험지를 측정합니다.",
|
||||
"neverShowAgain": "다시 표시하지 않음"
|
||||
},
|
||||
"socketlib": {
|
||||
"title": "Socketlib 모듈 비탐지",
|
||||
"text": "Drag Ruler는 Socketlib 모듈이 있어야 합니다. 이 월드에서 해당 모듈을 활성화하십시오."
|
||||
}
|
||||
},
|
||||
"resetMovementHistory": "이동 내역 리셋",
|
||||
"genericSpeedProvider": {
|
||||
"settings": {
|
||||
"dashMultiplier": {
|
||||
"name": "질주 승수",
|
||||
"hint": "측정된 경로에 색을 입힐 때 추가 속도를 제공하는 데 사용할 수 있다. 추가 속도를 비활성화하려면 이 값을 0으로 설정한다."
|
||||
},
|
||||
"speedAttribute": {
|
||||
"name": "속도 어트리뷰트",
|
||||
"hint": "토큰의 이동속도를 정의하는 속성이다. 측정 경로에 색을 입힐 때 사용한다."
|
||||
}
|
||||
},
|
||||
"speeds": {
|
||||
"walk": "보행",
|
||||
"dash": "질주"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"alwaysShowSpeedForPCs": {
|
||||
"name": "PC 속도를 모두에게 표시",
|
||||
"hint": "활성화시 플레이어 캐릭터에 대한 관찰자 권한이 없더라도 액터 속도에 따라 색상이 모든 유저에게 표시된다."
|
||||
},
|
||||
"enableMovementHistory": {
|
||||
"name": "전투 중 이동 기록 사용",
|
||||
"hint": "활성화시 Drag Ruler는 해당 턴에 토큰이 이동한 경로를 기억하고 다시 이동시킬 때 표시한다."
|
||||
},
|
||||
"showGMRulerToPlayers": {
|
||||
"name": "GM 측정자를 플레이어에게 표시",
|
||||
"hint": "비활성화 시 GM이 아닌 유저에게는 GM의 측정자가 표시되지 않는다."
|
||||
},
|
||||
"speedProviderSettings": {
|
||||
"name": "속도 공급자 설정",
|
||||
"hint": "속도 공급자 설정은 게임 시스템별 설정을 모두 포함한다.",
|
||||
"button": "속도 공급자 설정",
|
||||
"windowTitle": "속도 공급자 설정",
|
||||
"headers": {
|
||||
"speedProvider": "속도 공급자",
|
||||
"speedProviderSettings": "속도 공급자 관련 설정"
|
||||
},
|
||||
"activeProvider": {
|
||||
"name": "현재 활성화된 속도 공급자",
|
||||
"hint": "아래에 표시된 설정은 활성화된 속도 공급자를 보여준다. GM이 다른 속도 공급자를 선택하면 사용 가능한 설정이 변경될 수 있다."
|
||||
},
|
||||
"noSettings": "이 속도 공급자는 구성 옵션을 제공하지 않습니다.",
|
||||
"color": {
|
||||
"name": "{colorName} 색상",
|
||||
"hint": "{colorName} 범위 내에서의 칸의 색상을 지정하는데 사용할 색상.",
|
||||
"unreachable": {
|
||||
"name": "도달 불가",
|
||||
"hint": "드래그한 토큰으로 도달할 수 없는 공간의 색상"
|
||||
}
|
||||
},
|
||||
"speedProvider": {
|
||||
"name": "속도 설정 공급자",
|
||||
"hint": "색을 입힐 동안 사용할 토큰에 대한 속도 정보 제공자를 선택한다. Drag Ruler는 기본 기능을 제공하며 올바르게 구성된 경우 모든 게임 시스템에서 작동하는 일반적 속도 공급자를 제공한다. 게임 시스템과 설치된 모듈을 통해 더 많은 속도 공급자를 이용할 수 있다. 일반 속도 공급자와는 다른 속도 공급자를 선택하면 사용 중인 게임 시스템의 규칙에 더 잘 통합될 수 있다.아래 옵션은 여기에서 선택한 속도 공급자에 따라 다르다.",
|
||||
"choices": {
|
||||
"module": "모듈 {name}",
|
||||
"native": "포괄적",
|
||||
"system": "시스템 {name}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"swapSpacebarRightClick": {
|
||||
"name": "스페이스바와 우클릭 스왑",
|
||||
"hint": "드래그 도중 스페이스바의 기능을 우클릭 기능과 바꾼다. 활성화된 경우 드래그시 우클릭을 사용하면 경유지가 배치되고, 스페이스바가 해당 경유지를 삭제한다."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
-4
@@ -2,9 +2,9 @@
|
||||
"name": "drag-ruler",
|
||||
"title": "Drag Ruler",
|
||||
"description": "When dragging a token displays a ruler showing how far you've moved that token.",
|
||||
"version": "1.7.1",
|
||||
"minimumCoreVersion" : "0.7.9",
|
||||
"compatibleCoreVersion" : "0.7.9",
|
||||
"version": "1.9.1",
|
||||
"minimumCoreVersion" : "0.8.5",
|
||||
"compatibleCoreVersion" : "0.8.9",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Manuel Vögele",
|
||||
@@ -35,10 +35,20 @@
|
||||
"name": "日本語",
|
||||
"path": "lang/ja.json"
|
||||
},
|
||||
{
|
||||
"lang": "ko",
|
||||
"name": "한국어",
|
||||
"path": "lang/ko.json"
|
||||
},
|
||||
{
|
||||
"lang": "zh-tw",
|
||||
"name": "正體中文",
|
||||
"path": "lang/zh-tw.json"
|
||||
},
|
||||
{
|
||||
"lang": "es",
|
||||
"name": "Español",
|
||||
"path": "lang/es.json"
|
||||
}
|
||||
],
|
||||
"dependencies": [
|
||||
@@ -49,7 +59,7 @@
|
||||
],
|
||||
"socket": true,
|
||||
"url": "https://github.com/manuelVo/foundryvtt-drag-ruler",
|
||||
"download": "https://github.com/manuelVo/foundryvtt-drag-ruler/archive/v1.7.1.zip",
|
||||
"download": "https://github.com/manuelVo/foundryvtt-drag-ruler/archive/v1.9.1.zip",
|
||||
"manifest": "https://raw.githubusercontent.com/manuelVo/foundryvtt-drag-ruler/master/module.json",
|
||||
"readme": "https://github.com/manuelVo/foundryvtt-drag-ruler/blob/master/README.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 {
|
||||
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) {
|
||||
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) {
|
||||
const terrainRulerAvailable = game.modules.get("terrain-ruler")?.active;
|
||||
const history = getMovementHistory(token);
|
||||
const segments = Ruler.dragRulerGetRaysFromWaypoints(history, {x: token.x, y: token.y}).map(ray => {return {ray}});
|
||||
const shape = getTokenShape(token);
|
||||
const distances = measureDistances(segments, token, shape);
|
||||
const distances = measureDistances(segments, token, shape, {enableTerrainRuler: terrainRulerAvailable});
|
||||
// Sum up the distances
|
||||
return distances.reduce((acc, val) => acc + val, 0);
|
||||
}
|
||||
|
||||
+9
-11
@@ -1,5 +1,4 @@
|
||||
import {getCostFromSpeedProvider} from "./api.js";
|
||||
import {getColorForDistance} from "./main.js"
|
||||
import {settingsKey} from "./settings.js";
|
||||
import {getAreaFromPositionAndShape, highlightTokenShape} from "./util.js";
|
||||
|
||||
@@ -10,30 +9,32 @@ export function getHexSizeSupportTokenGridCenter(token) {
|
||||
|
||||
export function highlightMeasurementTerrainRuler(ray, startDistance, tokenShape=[{x: 0, y: 0}], alpha=1) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
export function measureDistances(segments, entity, shape, options={}) {
|
||||
const isToken = entity instanceof Token;
|
||||
const opts = duplicate(options)
|
||||
if (!opts.gridSpaces)
|
||||
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) {
|
||||
if (opts.enableTerrainRuler) {
|
||||
opts.gridSpaces = true;
|
||||
const firstNewSegmentIndex = segments.findIndex(segment => !segment.ray.dragRulerVisitedSpaces);
|
||||
const previousSegments = segments.slice(0, firstNewSegmentIndex);
|
||||
const newSegments = segments.slice(firstNewSegmentIndex);
|
||||
const distances = previousSegments.map(segment => segment.ray.dragRulerVisitedSpaces[segment.ray.dragRulerVisitedSpaces.length - 1].distance);
|
||||
previousSegments.forEach(segment => segment.ray.terrainRulerVisitedSpaces = duplicate(segment.ray.dragRulerVisitedSpaces));
|
||||
opts.costFunction = (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)
|
||||
opts.terrainRulerInitialState = previousSegments[previousSegments.length - 1].ray.dragRulerFinalState;
|
||||
return distances.concat(terrainRuler.measureDistances(newSegments, opts));
|
||||
}
|
||||
else {
|
||||
// If another module wants to enable grid measurements but disable grid highlighting,
|
||||
// manually set the *duplicate* option's gridSpaces value to true for the Foundry logic to work properly
|
||||
if(!opts.ignoreGrid) {
|
||||
opts.gridSpaces = true;
|
||||
}
|
||||
return canvas.grid.measureDistances(segments, opts);
|
||||
}
|
||||
}
|
||||
@@ -61,9 +62,6 @@ export function checkDependencies() {
|
||||
if (game.modules.get("enhanced-terrain-layer")?.active) {
|
||||
enabledTerrainModule = game.modules.get("enhanced-terrain-layer").data.title;
|
||||
}
|
||||
else if (game.modules.get("TerrainLayer")?.active) {
|
||||
enabledTerrainModule = game.modules.get("TerrainLayer").data.title;
|
||||
}
|
||||
if (enabledTerrainModule) {
|
||||
new Dialog({
|
||||
title: game.i18n.localize("drag-ruler.dependencies.terrain-ruler.title"),
|
||||
|
||||
+57
-21
@@ -1,10 +1,9 @@
|
||||
import {highlightMeasurementTerrainRuler, measureDistances} from "./compatibility.js";
|
||||
import {getGridPositionFromPixels} from "./foundry_fixes.js";
|
||||
import {Line} from "./geometry.js";
|
||||
import {getColorForDistance} from "./main.js"
|
||||
import {trackRays} from "./movement_tracking.js"
|
||||
import {recalculate} from "./socket.js";
|
||||
import {applyTokenSizeOffset, getSnapPointForMeasuredTemplate, getSnapPointForToken, getTokenShape, highlightTokenShape, zip} from "./util.js";
|
||||
import {applyTokenSizeOffset, getSnapPointForEntity, getSnapPointForToken, getTokenShape, highlightTokenShape, zip} from "./util.js";
|
||||
|
||||
// This is a modified version of Ruler.moveToken from foundry 0.7.9
|
||||
export async function moveEntities(draggedEntity, selectedEntities) {
|
||||
@@ -16,6 +15,9 @@ export async function moveEntities(draggedEntity, selectedEntities) {
|
||||
if (!this.visible || !this.destination) return false;
|
||||
if (!draggedEntity) return;
|
||||
|
||||
// Wait until all scheduled measurements are done
|
||||
await this.deferredMeasurementPromise;
|
||||
|
||||
// Get the movement rays and check collision along each Ray
|
||||
// These rays are center-to-center for the purposes of collision checking
|
||||
const rays = this.constructor.dragRulerGetRaysFromWaypoints(this.waypoints, this.destination);
|
||||
@@ -69,6 +71,10 @@ async function animateEntities(entities, draggedEntity, draggedRays, wasPaused)
|
||||
const isToken = draggedEntity instanceof Token;
|
||||
const animate = isToken && !game.keyboard.isDown("Alt");
|
||||
const startWaypoint = animate ? 0 : entityAnimationData[0].rays.length - 1;
|
||||
|
||||
// This is a flag of the "Monk's Active Tile Triggers" module that signals that the movement should be cancelled early
|
||||
this.cancelMovement = false;
|
||||
|
||||
for (let i = startWaypoint;i < entityAnimationData[0].rays.length; i++) {
|
||||
if (!wasPaused && game.paused) break;
|
||||
const entityPaths = entityAnimationData.map(({entity, rays, dx, dy}) => {
|
||||
@@ -80,9 +86,15 @@ async function animateEntities(entities, draggedEntity, draggedRays, wasPaused)
|
||||
const updates = entityPaths.map(({entity, path}) => {
|
||||
return {x: path.B.x, y: path.B.y, _id: entity.id};
|
||||
});
|
||||
await draggedEntity.scene.updateEmbeddedEntity(draggedEntity.constructor.embeddedName, updates, {animate});
|
||||
await draggedEntity.scene.updateEmbeddedDocuments(draggedEntity.constructor.embeddedName, updates, {animate});
|
||||
if (animate)
|
||||
await Promise.all(entityPaths.map(({entity, path}) => entity.animateMovement(path)));
|
||||
|
||||
// This is a flag of the "Monk's Active Tile Triggers" module that signals that the movement should be cancelled early
|
||||
if (this.cancelMovement) {
|
||||
entityAnimationData.forEach(ead => ead.rays = ead.rays.slice(0, i + 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isToken)
|
||||
trackRays(entities, entityAnimationData.map(({rays}) => rays)).then(() => recalculate(entities));
|
||||
@@ -103,8 +115,6 @@ export function onMouseMove(event) {
|
||||
if (this._state === Ruler.STATES.MOVING) return;
|
||||
|
||||
// Extract event data
|
||||
const mt = event._measureTime || 0;
|
||||
const originalEvent = event.data.originalEvent;
|
||||
const destination = {x: event.data.destination.x + this.rulerOffset.x, y: event.data.destination.y + this.rulerOffset.y}
|
||||
|
||||
// Hide any existing Token HUD
|
||||
@@ -112,33 +122,59 @@ export function onMouseMove(event) {
|
||||
delete event.data.hudState;
|
||||
|
||||
// Draw measurement updates
|
||||
if (Date.now() - mt > 50) {
|
||||
scheduleMeasurement.call(this, destination, event);
|
||||
}
|
||||
|
||||
function scheduleMeasurement(destination, event) {
|
||||
const measurementInterval = 50;
|
||||
const mt = event._measureTime || 0;
|
||||
const originalEvent = event.data.originalEvent;
|
||||
if (Date.now() - mt > measurementInterval) {
|
||||
this.measure(destination, {snap: !originalEvent.shiftKey});
|
||||
event._measureTime = Date.now();
|
||||
this._state = Ruler.STATES.MEASURING;
|
||||
cancelScheduledMeasurement.call(this);
|
||||
}
|
||||
else {
|
||||
this.deferredMeasurementData = {destination, event};
|
||||
if (!this.deferredMeasurementTimeout) {
|
||||
this.deferredMeasurementPromise = new Promise((resolve, reject) => this.deferredMeasurementResolve = resolve);
|
||||
this.deferredMeasurementTimeout = window.setTimeout(() => scheduleMeasurement.call(this, this.deferredMeasurementData.destination, this.deferredMeasurementData.event), measurementInterval);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function cancelScheduledMeasurement() {
|
||||
window.clearTimeout(this.deferredMeasurementTimeout);
|
||||
this.deferredMeasurementTimeout = undefined;
|
||||
this.deferredMeasurementResolve?.();
|
||||
}
|
||||
|
||||
// This is a modified version of Ruler.measure form foundry 0.7.9
|
||||
export function measure(destination, {gridSpaces=true, snap=false} = {}) {
|
||||
export function measure(destination, options={}) {
|
||||
const isToken = this.draggedEntity instanceof Token;
|
||||
if (isToken && !this.draggedEntity.isVisible)
|
||||
return []
|
||||
|
||||
if (snap) {
|
||||
if (isToken)
|
||||
destination = getSnapPointForToken(destination.x, destination.y, this.draggedEntity);
|
||||
else
|
||||
destination = getSnapPointForMeasuredTemplate(destination.x, destination.y);
|
||||
if (options.snap) {
|
||||
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(options.ignoreGrid === undefined) {
|
||||
options.ignoreGrid = false;
|
||||
}
|
||||
|
||||
options.enableTerrainRuler = isToken && game.modules.get("terrain-ruler")?.active;
|
||||
|
||||
const waypoints = this.waypoints.concat([destination]);
|
||||
// Move the waypoints to the center of the grid if a size is used that measures from edge to edge
|
||||
const centeredWaypoints = isToken ? applyTokenSizeOffset(waypoints, this.draggedEntity) : duplicate(waypoints);
|
||||
// Foundries native ruler requires the waypoints to sit in the dead center of the square to work properly
|
||||
if (!terrainRulerAvailable)
|
||||
if (!options.enableTerrainRuler && !options.ignoreGrid)
|
||||
centeredWaypoints.forEach(w => [w.x, w.y] = canvas.grid.getCenter(w.x, w.y));
|
||||
|
||||
const r = this.ruler;
|
||||
@@ -172,7 +208,7 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
|
||||
const shape = isToken ? getTokenShape(this.draggedEntity) : null;
|
||||
|
||||
// Compute measured distance
|
||||
const distances = measureDistances(centeredSegments, this.draggedEntity, shape, {gridSpaces});
|
||||
const distances = measureDistances(centeredSegments, this.draggedEntity, shape, options);
|
||||
|
||||
let totalDistance = 0;
|
||||
for (let [i, d] of distances.entries()) {
|
||||
@@ -191,8 +227,8 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
|
||||
// Draw measured path
|
||||
r.clear();
|
||||
let rulerColor
|
||||
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS)
|
||||
rulerColor = getColorForDistance.call(this, totalDistance)
|
||||
if (!options.gridSpaces || canvas.grid.type === CONST.GRID_TYPES.GRIDLESS)
|
||||
rulerColor = this.dragRulerGetColorForDistance(totalDistance);
|
||||
else
|
||||
rulerColor = this.color
|
||||
for (const [s, cs] of zip(segments.reverse(), centeredSegments.reverse())) {
|
||||
@@ -227,8 +263,8 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
|
||||
}
|
||||
|
||||
// Highlight grid positions
|
||||
if (isToken && canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS) {
|
||||
if (terrainRulerAvailable)
|
||||
if (isToken && canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS && options.gridSpaces) {
|
||||
if (options.enableTerrainRuler)
|
||||
highlightMeasurementTerrainRuler.call(this, cs.ray, cs.startDistance, shape, opacityMultiplier)
|
||||
else
|
||||
highlightMeasurementNative.call(this, cs.ray, cs.startDistance, shape, opacityMultiplier);
|
||||
@@ -264,7 +300,7 @@ export function highlightMeasurementNative(ray, startDistance, tokenShape=[{x: 0
|
||||
// Highlight the grid position
|
||||
let [xg, yg] = canvas.grid.grid.getPixelsFromGridPosition(x1, y1);
|
||||
const subDistance = canvas.grid.measureDistances([{ray: new Ray(ray.A, {x: xg, y: yg})}], {gridSpaces: true})[0]
|
||||
const color = dragRuler.getColorForDistance.call(this, startDistance, subDistance)
|
||||
const color = this.dragRulerGetColorForDistance(startDistance + subDistance);
|
||||
const snapPoint = getSnapPointForToken(...canvas.grid.getTopLeft(x, y), this.draggedEntity);
|
||||
const [snapX, snapY] = getGridPositionFromPixels(snapPoint.x + 1, snapPoint.y + 1);
|
||||
|
||||
@@ -277,7 +313,7 @@ export function highlightMeasurementNative(ray, startDistance, tokenShape=[{x: 0
|
||||
let [x1h, y1h] = canvas.grid.grid.getGridPositionFromPixels(x, y);
|
||||
let [xgh, ygh] = canvas.grid.grid.getPixelsFromGridPosition(x1h, y1h);
|
||||
const subDistance = canvas.grid.measureDistances([{ray: new Ray(ray.A, {x: xgh, y: ygh})}], {gridSpaces: true})[0]
|
||||
const color = dragRuler.getColorForDistance.call(this, startDistance, subDistance)
|
||||
const color = this.dragRulerGetColorForDistance(startDistance + subDistance);
|
||||
const snapPoint = getSnapPointForToken(...canvas.grid.getTopLeft(x, y), this.draggedEntity);
|
||||
const [snapX, snapY] = getGridPositionFromPixels(snapPoint.x + 1, snapPoint.y + 1);
|
||||
highlightTokenShape.call(this, {x: snapX, y: snapY}, tokenShape, color, alpha);
|
||||
|
||||
+170
-43
@@ -1,14 +1,15 @@
|
||||
"use strict"
|
||||
|
||||
import {currentSpeedProvider, getMovedDistanceFromToken, getRangesFromSpeedProvider, getUnreachableColorFromSpeedProvider, initApi, registerModule, registerSystem} from "./api.js"
|
||||
import {checkDependencies, getHexSizeSupportTokenGridCenter} from "./compatibility.js";
|
||||
import {currentSpeedProvider, getColorForDistanceAndToken, getMovedDistanceFromToken, getRangesFromSpeedProvider, initApi, registerModule, registerSystem} from "./api.js";
|
||||
import {checkDependencies, getHexSizeSupportTokenGridCenter, highlightMeasurementTerrainRuler} from "./compatibility.js";
|
||||
import {moveEntities, onMouseMove} from "./foundry_imports.js"
|
||||
import {performMigrations} from "./migration.js"
|
||||
import {DragRulerRuler} from "./ruler.js";
|
||||
import {getMovementHistory, resetMovementHistory} from "./movement_tracking.js";
|
||||
import {getMovementHistory, removeLastHistoryEntryIfAt, resetMovementHistory} from "./movement_tracking.js";
|
||||
import {registerSettings, settingsKey} from "./settings.js"
|
||||
import {recalculate} from "./socket.js";
|
||||
import {SpeedProvider} from "./speed_provider.js"
|
||||
import {isClose, setSnapParameterOnOptions} from "./util.js";
|
||||
|
||||
Hooks.once("init", () => {
|
||||
registerSettings()
|
||||
@@ -16,11 +17,12 @@ Hooks.once("init", () => {
|
||||
hookDragHandlers(Token);
|
||||
hookDragHandlers(MeasuredTemplate);
|
||||
hookKeyboardManagerFunctions()
|
||||
hookLayerFunctions();
|
||||
|
||||
Ruler = DragRulerRuler;
|
||||
|
||||
window.dragRuler = {
|
||||
getColorForDistance,
|
||||
getColorForDistanceAndToken,
|
||||
getMovedDistanceFromToken,
|
||||
registerModule,
|
||||
registerSystem,
|
||||
@@ -40,7 +42,7 @@ Hooks.on("canvasReady", () => {
|
||||
ruler.draggedEntity = null;
|
||||
Object.defineProperty(ruler, "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;
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -50,7 +52,7 @@ Hooks.on("getCombatTrackerEntryContext", function (html, menu) {
|
||||
const entry = {
|
||||
name: "drag-ruler.resetMovementHistory",
|
||||
icon: '<i class="fas fa-undo-alt"></i>',
|
||||
callback: li => resetMovementHistory(ui.combat.combat, li.data('combatant-id')),
|
||||
callback: li => resetMovementHistory(ui.combat.viewed, li.data('combatant-id')),
|
||||
};
|
||||
menu.splice(1, 0, entry);
|
||||
});
|
||||
@@ -64,6 +66,8 @@ function hookDragHandlers(entityType) {
|
||||
|
||||
const originalDragLeftMoveHandler = entityType.prototype._onDragLeftMove
|
||||
entityType.prototype._onDragLeftMove = function (event) {
|
||||
if (entityType === Token)
|
||||
applyGridlessSnapping.call(this, event);
|
||||
originalDragLeftMoveHandler.call(this, event)
|
||||
onEntityLeftDragMove.call(this, event)
|
||||
}
|
||||
@@ -92,12 +96,32 @@ function hookKeyboardManagerFunctions() {
|
||||
}
|
||||
}
|
||||
|
||||
function hookLayerFunctions() {
|
||||
const originalTokenLayerUndoHistory = TokenLayer.prototype.undoHistory;
|
||||
TokenLayer.prototype.undoHistory = function () {
|
||||
const historyEntry = this.history[this.history.length - 1];
|
||||
return originalTokenLayerUndoHistory.call(this).then((returnValue) => {
|
||||
if (historyEntry.type === "update") {
|
||||
for (const entry of historyEntry.data) {
|
||||
const token = canvas.tokens.get(entry._id);
|
||||
removeLastHistoryEntryIfAt(token, entry.x, entry.y);
|
||||
}
|
||||
}
|
||||
return returnValue;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function handleKeys(event, key, up) {
|
||||
if (event.repeat || this.hasFocus)
|
||||
return false
|
||||
|
||||
if (key.toLowerCase() === "x") return onKeyX(up)
|
||||
if (key.toLowerCase() === "shift") return onKeyShift(up)
|
||||
const lowercaseKey = key.toLowerCase();
|
||||
|
||||
if (lowercaseKey === "x") return onKeyX(up)
|
||||
if (lowercaseKey === "shift") return onKeyShift(up)
|
||||
if (lowercaseKey === "space") return onKeySpace(up);
|
||||
if (lowercaseKey === "escape") return onKeyEscape(up);
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -125,10 +149,39 @@ function onKeyShift(up) {
|
||||
ruler.measure(measurePosition, {snap: up})
|
||||
}
|
||||
|
||||
function onKeySpace(up) {
|
||||
const ruler = canvas.controls.ruler;
|
||||
// Ruler can end up being undefined here if no canvas is active
|
||||
if (!ruler?.draggedEntity)
|
||||
return false;
|
||||
|
||||
if (ruler._state !== Ruler.STATES.INACTIVE)
|
||||
return false;
|
||||
|
||||
const swapSpacebarRightClick = game.settings.get(settingsKey, "swapSpacebarRightClick");
|
||||
let options = {};
|
||||
setSnapParameterOnOptions(ruler, options);
|
||||
|
||||
if (!up) {
|
||||
if (swapSpacebarRightClick)
|
||||
ruler.dragRulerAbortDrag();
|
||||
else
|
||||
startDragRuler.call(ruler.draggedEntity, options);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function onKeyEscape(up) {
|
||||
const ruler = canvas.controls.ruler;
|
||||
if (!ruler.draggedEntity)
|
||||
return false;
|
||||
if (!up)
|
||||
ruler.dragRulerAbortDrag();
|
||||
return true;
|
||||
}
|
||||
|
||||
function onEntityLeftDragStart(event) {
|
||||
const isToken = this instanceof Token;
|
||||
if (isToken && !currentSpeedProvider.usesRuler(this))
|
||||
return
|
||||
const ruler = canvas.controls.ruler
|
||||
ruler.draggedEntity = this;
|
||||
let entityCenter;
|
||||
@@ -136,12 +189,33 @@ function onEntityLeftDragStart(event) {
|
||||
entityCenter = getHexSizeSupportTokenGridCenter(this);
|
||||
else
|
||||
entityCenter = this.center;
|
||||
ruler.rulerOffset = {x: entityCenter.x - event.data.origin.x, y: entityCenter.y - event.data.origin.y};
|
||||
if (game.settings.get(settingsKey, "autoStartMeasurement")) {
|
||||
let options = {};
|
||||
setSnapParameterOnOptions(ruler, options);
|
||||
startDragRuler.call(this, options, false);
|
||||
}
|
||||
}
|
||||
|
||||
function startDragRuler(options, measureImmediately=true) {
|
||||
const isToken = this instanceof Token;
|
||||
if (isToken && !currentSpeedProvider.usesRuler(this))
|
||||
return;
|
||||
const ruler = canvas.controls.ruler;
|
||||
ruler.clear();
|
||||
ruler._state = Ruler.STATES.STARTING;
|
||||
ruler.rulerOffset = {x: entityCenter.x - event.data.origin.x, y: entityCenter.y - event.data.origin.y};
|
||||
let entityCenter;
|
||||
if (isToken && canvas.grid.isHex && game.modules.get("hex-size-support")?.active && CONFIG.hexSizeSupport.getAltSnappingFlag(this))
|
||||
entityCenter = getHexSizeSupportTokenGridCenter(this);
|
||||
else
|
||||
entityCenter = this.center;
|
||||
if (isToken && game.settings.get(settingsKey, "enableMovementHistory"))
|
||||
ruler.dragRulerAddWaypointHistory(getMovementHistory(this));
|
||||
ruler.dragRulerAddWaypoint(entityCenter, false);
|
||||
ruler.dragRulerAddWaypoint(entityCenter, {snap: false});
|
||||
const mousePosition = canvas.app.renderer.plugins.interaction.mouse.getLocalPosition(canvas.tokens);
|
||||
const destination = {x: mousePosition.x + ruler.rulerOffset.x, y: mousePosition.y + ruler.rulerOffset.y};
|
||||
if (measureImmediately)
|
||||
ruler.measure(destination, options);
|
||||
}
|
||||
|
||||
function onEntityLeftDragMove(event) {
|
||||
@@ -152,9 +226,10 @@ function onEntityLeftDragMove(event) {
|
||||
|
||||
function onEntityDragLeftDrop(event) {
|
||||
const ruler = canvas.controls.ruler
|
||||
if (!ruler.isDragRuler)
|
||||
if (!ruler.isDragRuler) {
|
||||
ruler.draggedEntity = undefined;
|
||||
return false
|
||||
onMouseMove.call(ruler, event);
|
||||
}
|
||||
// When we're dragging a measured template no token will ever be selected,
|
||||
// resulting in only the dragged template to be moved as would be expected
|
||||
const selectedTokens = canvas.tokens.controlled
|
||||
@@ -169,43 +244,95 @@ function onEntityDragLeftDrop(event) {
|
||||
function onEntityDragLeftCancel(event) {
|
||||
// This function is invoked by right clicking
|
||||
const ruler = canvas.controls.ruler
|
||||
if (!ruler.isDragRuler || ruler._state === Ruler.STATES.MOVING)
|
||||
if (!ruler.draggedEntity || ruler._state === Ruler.STATES.MOVING)
|
||||
return false
|
||||
if (ruler._state === Ruler.STATES.MEASURING) {
|
||||
if (!game.settings.get(settingsKey, "swapSpacebarRightClick")) {
|
||||
ruler.dragRulerDeleteWaypoint(event);
|
||||
|
||||
const swapSpacebarRightClick = game.settings.get(settingsKey, "swapSpacebarRightClick");
|
||||
let options = {};
|
||||
setSnapParameterOnOptions(ruler, options);
|
||||
|
||||
if (ruler._state === Ruler.STATES.INACTIVE) {
|
||||
if (!swapSpacebarRightClick)
|
||||
return false;
|
||||
startDragRuler.call(this, options);
|
||||
event.preventDefault();
|
||||
}
|
||||
else if (ruler._state === Ruler.STATES.MEASURING) {
|
||||
if (!swapSpacebarRightClick) {
|
||||
ruler.dragRulerDeleteWaypoint(event, options);
|
||||
}
|
||||
else {
|
||||
event.preventDefault()
|
||||
const snap = !event.shiftKey
|
||||
ruler.dragRulerAddWaypoint(ruler.destination, snap);
|
||||
event.preventDefault();
|
||||
ruler.dragRulerAddWaypoint(ruler.destination, options);
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
export function getColorForDistance(startDistance, subDistance=0) {
|
||||
if (!this.isDragRuler)
|
||||
return this.color
|
||||
if (!this.draggedEntity.actor) {
|
||||
return this.color;
|
||||
function applyGridlessSnapping(event) {
|
||||
const ruler = canvas.controls.ruler;
|
||||
if (!game.settings.get(settingsKey, "useGridlessRaster"))
|
||||
return;
|
||||
if (!ruler.isDragRuler)
|
||||
return;
|
||||
if (game.keyboard.isDown("Shift"))
|
||||
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
|
||||
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
|
||||
else {
|
||||
let waypointDistance = 0;
|
||||
let origin = event.data.origin;
|
||||
if (ruler.waypoints.length > 1) {
|
||||
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
|
||||
}
|
||||
|
||||
+29
-15
@@ -1,22 +1,22 @@
|
||||
import {measureDistances} from "./compatibility.js";
|
||||
import {recalculate, updateCombatantDragRulerFlags} from "./socket.js";
|
||||
import {getTokenShape, zip} from "./util.js";
|
||||
import {getTokenShape, isClose, zip} from "./util.js";
|
||||
|
||||
function initTrackingFlag(combatant) {
|
||||
const initialFlag = {passedWaypoints: [], trackedRound: 0};
|
||||
let dragRulerFlag = combatant.flags?.dragRuler;
|
||||
let dragRulerFlag = combatant.data.flags.dragRuler;
|
||||
if (dragRulerFlag) {
|
||||
if (isNaN(dragRulerFlag.trackedRound)) {
|
||||
mergeObject(dragRulerFlag, initialFlag);
|
||||
}
|
||||
}
|
||||
else {
|
||||
combatant.flags.dragRuler = initialFlag;
|
||||
combatant.data.flags.dragRuler = initialFlag;
|
||||
}
|
||||
}
|
||||
|
||||
function getInitializedCombatant(token, combat) {
|
||||
const combatant = combat.getCombatantByToken(token.data._id);
|
||||
const combatant = combat.getCombatantByToken(token.id);
|
||||
if (!combatant)
|
||||
return undefined;
|
||||
initTrackingFlag(combatant);
|
||||
@@ -43,37 +43,37 @@ function calculateUpdate(combat, token, rays) {
|
||||
return;
|
||||
|
||||
// Check if we have entered a new round. If so, remove the currently stored path
|
||||
if (combat.data.round > combatant.flags.dragRuler.trackedRound) {
|
||||
combatant.flags.dragRuler.passedWaypoints = [];
|
||||
combatant.flags.dragRuler.trackedRound = combat.data.round;
|
||||
if (combat.data.round > combatant.data.flags.dragRuler.trackedRound) {
|
||||
combatant.data.flags.dragRuler.passedWaypoints = [];
|
||||
combatant.data.flags.dragRuler.trackedRound = combat.data.round;
|
||||
}
|
||||
|
||||
// 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 dragRulerFlags = combatant.flags.dragRuler;
|
||||
const terrainRulerAvailable = game.modules.get("terrain-ruler")?.active;
|
||||
const dragRulerFlags = combatant.data.flags.dragRuler;
|
||||
const waypoints = dragRulerFlags.passedWaypoints;
|
||||
for (const ray of rays) {
|
||||
// Ignore rays that have the same start and end coordinates
|
||||
if (ray.A.x !== ray.B.x || ray.A.y !== ray.B.y) {
|
||||
if (terrainRulerAvailable) {
|
||||
measureDistances([{ray}], token, getTokenShape(token), {terrainRulerInitialState: waypoints[waypoints.length - 1]?.dragRulerFinalState});
|
||||
measureDistances([{ray}], token, getTokenShape(token), {terrainRulerInitialState: waypoints[waypoints.length - 1]?.dragRulerFinalState, enableTerrainRuler: terrainRulerAvailable});
|
||||
ray.A.dragRulerVisitedSpaces = ray.terrainRulerVisitedSpaces;
|
||||
ray.A.dragRulerFinalState = ray.terrainRulerFinalState;
|
||||
}
|
||||
waypoints.push(ray.A);
|
||||
}
|
||||
}
|
||||
return {_id: combatant._id, dragRulerFlags};
|
||||
return {_id: combatant.id, dragRulerFlags};
|
||||
}
|
||||
|
||||
export function getMovementHistory(token) {
|
||||
const combat = game.combat;
|
||||
if (!combat)
|
||||
return [];
|
||||
const combatant = combat.getCombatantByToken(token.data._id);
|
||||
const combatant = combat.getCombatantByToken(token.id);
|
||||
if (!combatant)
|
||||
return [];
|
||||
const dragRulerFlags = combatant.flags.dragRuler;
|
||||
const dragRulerFlags = combatant.data.flags.dragRuler;
|
||||
if (!dragRulerFlags)
|
||||
return [];
|
||||
if (combat.data.round > dragRulerFlags.trackedRound)
|
||||
@@ -81,9 +81,23 @@ export function getMovementHistory(token) {
|
||||
return dragRulerFlags.passedWaypoints ?? [];
|
||||
}
|
||||
|
||||
export async function removeLastHistoryEntryIfAt(token, x, y) {
|
||||
const history = getMovementHistory(token);
|
||||
if (history.length === 0)
|
||||
return;
|
||||
const entry = history[history.length - 1];
|
||||
if (!isClose(x + token.w / 2, entry.x, 0.1) || !isClose(y + token.h / 2, entry.y, 0.1)) {
|
||||
return;
|
||||
}
|
||||
history.pop();
|
||||
const combat = game.combat;
|
||||
const combatant = combat.getCombatantByToken(token.id);
|
||||
await updateCombatantDragRulerFlags(combat, [{_id: combatant.id, dragRulerFlags: combatant.data.flags.dragRuler}]);
|
||||
}
|
||||
|
||||
export async function resetMovementHistory(combat, combatantId) {
|
||||
const combatant = combat.getCombatant(combatantId);
|
||||
const dragRulerFlags = combatant.flags.dragRuler;
|
||||
const combatant = combat.combatants.get(combatantId);
|
||||
const dragRulerFlags = combatant.data.flags.dragRuler;
|
||||
if (!dragRulerFlags)
|
||||
return;
|
||||
dragRulerFlags.passedWaypoints = null;
|
||||
|
||||
+64
-24
@@ -1,7 +1,8 @@
|
||||
import {measure} from "./foundry_imports.js"
|
||||
import {getColorForDistanceAndToken, getRangesFromSpeedProvider} from "./api.js";
|
||||
import {cancelScheduledMeasurement, measure} from "./foundry_imports.js"
|
||||
import {getMovementHistory} from "./movement_tracking.js";
|
||||
import {settingsKey} from "./settings.js";
|
||||
import {getSnapPointForToken} from "./util.js";
|
||||
import {getSnapPointForEntity, setSnapParameterOnOptions} from "./util.js";
|
||||
|
||||
export class DragRulerRuler extends Ruler {
|
||||
// Functions below are overridden versions of functions in Ruler
|
||||
@@ -16,25 +17,32 @@ export class DragRulerRuler extends Ruler {
|
||||
this.previousWaypoints = [];
|
||||
this.previousLabels.removeChildren().forEach(c => c.destroy());
|
||||
this.dragRulerRanges = undefined;
|
||||
cancelScheduledMeasurement.call(this);
|
||||
}
|
||||
|
||||
async moveToken(event) {
|
||||
// This function is invoked by left clicking
|
||||
if (!this.isDragRuler)
|
||||
return await super.moveToken(event);
|
||||
|
||||
let options = {};
|
||||
setSnapParameterOnOptions(this, options);
|
||||
|
||||
if (!game.settings.get(settingsKey, "swapSpacebarRightClick")) {
|
||||
const snap = !event.shiftKey;
|
||||
this.dragRulerAddWaypoint(this.destination, snap);
|
||||
this.dragRulerAddWaypoint(this.destination, options);
|
||||
}
|
||||
else {
|
||||
this.dragRulerDeleteWaypoint();
|
||||
this.dragRulerDeleteWaypoint(event, options);
|
||||
}
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
const json = super.toJSON();
|
||||
if (this.draggedEntity)
|
||||
json["draggedEntity"] = this.draggedEntity.data._id;
|
||||
if (this.draggedEntity) {
|
||||
const isToken = this.draggedEntity instanceof Token;
|
||||
json["draggedEntityIsToken"] = isToken;
|
||||
json["draggedEntity"] = this.draggedEntity.id;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
@@ -44,8 +52,12 @@ export class DragRulerRuler extends Ruler {
|
||||
return;
|
||||
|
||||
if (data.draggedEntity) {
|
||||
this.draggedEntity = canvas.tokens.get(data.draggedEntity);
|
||||
if (data.draggedEntityIsToken)
|
||||
this.draggedEntity = canvas.tokens.get(data.draggedEntity);
|
||||
else
|
||||
this.draggedEntity = canvas.templates.get(data.draggedEntity);
|
||||
}
|
||||
|
||||
super.update(data);
|
||||
}
|
||||
|
||||
@@ -64,9 +76,11 @@ export class DragRulerRuler extends Ruler {
|
||||
}
|
||||
|
||||
// The functions below aren't present in the orignal Ruler class and are added by Drag Ruler
|
||||
dragRulerAddWaypoint(point, snap=true) {
|
||||
if (snap)
|
||||
point = getSnapPointForToken(point.x, point.y, this.draggedEntity);
|
||||
dragRulerAddWaypoint(point, options={}) {
|
||||
options.snap = options.snap ?? true;
|
||||
if (options.snap) {
|
||||
point = getSnapPointForEntity(point.x, point.y, this.draggedEntity);
|
||||
}
|
||||
this.waypoints.push(new PIXI.Point(point.x, point.y));
|
||||
this.labels.addChild(new PreciseText("", CONFIG.canvasTextStyle));
|
||||
}
|
||||
@@ -84,28 +98,36 @@ export class DragRulerRuler extends Ruler {
|
||||
this.labels.removeChildren().forEach(c => c.destroy());
|
||||
}
|
||||
|
||||
dragRulerDeleteWaypoint(event={preventDefault: () => {return}}) {
|
||||
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;
|
||||
this._removeWaypoint({x: mousePosition.x + rulerOffset.x, y: mousePosition.y + rulerOffset.y});
|
||||
|
||||
// Options are not passed to _removeWaypoint in vanilla Foundry.
|
||||
// Send them in case other modules have overriden that behavior and accept an options parameter (Toggle Snap to Grid)
|
||||
this._removeWaypoint({x: mousePosition.x + rulerOffset.x, y: mousePosition.y + rulerOffset.y}, options);
|
||||
game.user.broadcastActivity({ruler: this});
|
||||
}
|
||||
else {
|
||||
const token = this.draggedEntity;
|
||||
this._endMeasurement();
|
||||
|
||||
// Deactivate the drag workflow in mouse
|
||||
token.mouseInteractionManager._deactivateDragEvents();
|
||||
token.mouseInteractionManager.state = token.mouseInteractionManager.states.HOVER;
|
||||
|
||||
// This will cancel the current drag operation
|
||||
// Pass in a fake event that hopefully is enough to allow other modules to function
|
||||
token._onDragLeftCancel(event);
|
||||
this.dragRulerAbortDrag(event);
|
||||
}
|
||||
}
|
||||
|
||||
dragRulerAbortDrag(event={preventDefault: () => {return}}) {
|
||||
const token = this.draggedEntity;
|
||||
this._endMeasurement();
|
||||
|
||||
// Deactivate the drag workflow in mouse
|
||||
token.mouseInteractionManager._deactivateDragEvents();
|
||||
token.mouseInteractionManager.state = token.mouseInteractionManager.states.HOVER;
|
||||
|
||||
// This will cancel the current drag operation
|
||||
// Pass in a fake event that hopefully is enough to allow other modules to function
|
||||
token._onDragLeftCancel(event);
|
||||
}
|
||||
|
||||
async dragRulerRecalculate(tokenIds) {
|
||||
if (this._state !== Ruler.STATES.MEASURING)
|
||||
return;
|
||||
@@ -116,7 +138,7 @@ export class DragRulerRuler extends Ruler {
|
||||
if (game.settings.get(settingsKey, "enableMovementHistory"))
|
||||
this.dragRulerAddWaypointHistory(getMovementHistory(this.draggedEntity));
|
||||
for (const waypoint of waypoints) {
|
||||
this.dragRulerAddWaypoint(waypoint, false);
|
||||
this.dragRulerAddWaypoint(waypoint, {snap: false});
|
||||
}
|
||||
this.measure(this.destination);
|
||||
game.user.broadcastActivity({ruler: this});
|
||||
@@ -131,4 +153,22 @@ export class DragRulerRuler extends Ruler {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,15 @@ export function registerSettings() {
|
||||
default: "fresh install"
|
||||
})
|
||||
|
||||
game.settings.register(settingsKey, "autoStartMeasurement", {
|
||||
name: "drag-ruler.settings.autoStartMeasurement.name",
|
||||
hint: "drag-ruler.settings.autoStartMeasurement.hint",
|
||||
scope: "client",
|
||||
config: true,
|
||||
type: Boolean,
|
||||
default: true,
|
||||
});
|
||||
|
||||
game.settings.register(settingsKey, "swapSpacebarRightClick", {
|
||||
name: "drag-ruler.settings.swapSpacebarRightClick.name",
|
||||
hint: "drag-ruler.settings.swapSpacebarRightClick.hint",
|
||||
@@ -20,6 +29,15 @@ export function registerSettings() {
|
||||
default: false,
|
||||
})
|
||||
|
||||
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", {
|
||||
name: "drag-ruler.settings.alwaysShowSpeedForPCs.name",
|
||||
hint: "drag-ruler.settings.alwaysShowSpeedForPCs.hint",
|
||||
|
||||
+4
-4
@@ -12,7 +12,7 @@ export function updateCombatantDragRulerFlags(combat, updates) {
|
||||
const combatId = combat.id;
|
||||
// TODO Check if canvas.tokens.get is still neccessary in future foundry versions
|
||||
return socket.executeAsGM(_socketUpdateCombatantDragRulerFlags, combatId, updates)
|
||||
.then(() => currentSpeedProvider.onMovementHistoryUpdate(updates.map(update => canvas.tokens.get(combat.getCombatant(update._id).token._id))));
|
||||
.then(() => currentSpeedProvider.onMovementHistoryUpdate(updates.map(update => canvas.tokens.get(combat.combatants.get(update._id).token.id))));
|
||||
}
|
||||
|
||||
async function _socketUpdateCombatantDragRulerFlags(combatId, updates) {
|
||||
@@ -20,10 +20,10 @@ async function _socketUpdateCombatantDragRulerFlags(combatId, updates) {
|
||||
const combat = game.combats.get(combatId);
|
||||
const requestedUpdates = updates.length;
|
||||
updates = updates.filter(update => {
|
||||
const actor = combat.getCombatant(update._id).actor;
|
||||
const actor = combat.combatants.get(update._id).actor;
|
||||
if (!actor)
|
||||
return false;
|
||||
return actor.hasPerm(user, "OWNER");
|
||||
return actor.testUserPermission(user, "OWNER");
|
||||
});
|
||||
if (updates.length !== requestedUpdates) {
|
||||
console.warn(`Some of the movement history updates requested by user '${game.users.get(this.socketdata.userId).name}' were not performed because the user lacks owner permissions for those tokens`);
|
||||
@@ -31,7 +31,7 @@ async function _socketUpdateCombatantDragRulerFlags(combatId, updates) {
|
||||
updates = updates.map(update => {
|
||||
return {_id: update._id, flags: {dragRuler: update.dragRulerFlags}};
|
||||
});
|
||||
await combat.updateEmbeddedEntity("Combatant", updates, {diff: false});
|
||||
await combat.updateEmbeddedDocuments("Combatant", updates, {diff: false});
|
||||
}
|
||||
|
||||
export function recalculate(tokens) {
|
||||
|
||||
@@ -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 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, ...)
|
||||
* 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)
|
||||
*/
|
||||
getCostForStep(token, area) {
|
||||
getCostForStep(token, area, options={}) {
|
||||
// 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 costs.reduce((max, current) => Math.max(max, current))
|
||||
}
|
||||
@@ -132,7 +137,7 @@ export class GenericSpeedProvider extends SpeedProvider {
|
||||
const speedAttribute = this.getSetting("speedAttribute")
|
||||
if (!speedAttribute)
|
||||
return []
|
||||
const tokenSpeed = parseInt(getProperty(token, speedAttribute));
|
||||
const tokenSpeed = parseFloat(getProperty(token, speedAttribute));
|
||||
if (tokenSpeed === undefined) {
|
||||
console.warn(`Drag Ruler (Generic Speed Provider) | The configured token speed attribute "${speedAttribute}" didn't return a speed value. To use colors based on drag distance set the setting to the correct value (or clear the box to disable this feature).`)
|
||||
return []
|
||||
|
||||
+9
-3
@@ -1,20 +1,23 @@
|
||||
|
||||
export function getDefaultSpeedAttribute() {
|
||||
switch (game.system.id) {
|
||||
case "CoC7":
|
||||
return "actor.data.data.attribs.mov.value";
|
||||
case "dcc":
|
||||
return "actor.data.data.attributes.speed.value";
|
||||
case "dnd5e":
|
||||
return "actor.data.data.attributes.movement.walk"
|
||||
case "lancer":
|
||||
return "actor.data.data.mech.speed"
|
||||
return "actor.data.data.derived.speed"
|
||||
case "pf1":
|
||||
return "actor.data.data.attributes.speed.land.total"
|
||||
case "D35E":
|
||||
return "actor.data.data.attributes.speed.land.total"
|
||||
case "sfrpg":
|
||||
return "actor.data.data.attributes.speed.value";
|
||||
case "shadowrun5e":
|
||||
return "actor.data.data.movement.walk.value";
|
||||
case "swade":
|
||||
return "actor.data.data.stats.speed.value"
|
||||
return "actor.data.data.stats.speed.adjusted";
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -27,9 +30,12 @@ export function getDefaultDashMultiplier() {
|
||||
case "dnd5e":
|
||||
case "lancer":
|
||||
case "pf1":
|
||||
case "D35E":
|
||||
case "sfrpg":
|
||||
case "shadowrun5e":
|
||||
return 2
|
||||
case "CoC7":
|
||||
return 5;
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
+68
-16
@@ -10,33 +10,61 @@ export function getSnapPointForToken(x, y, token) {
|
||||
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
|
||||
return new PIXI.Point(x, y);
|
||||
}
|
||||
if (canvas.grid.isHex && game.modules.get("hex-size-support")?.active && CONFIG.hexSizeSupport.getAltSnappingFlag(token)) {
|
||||
if (token.getFlag("hex-size-support", "borderSize") % 2 === 0) {
|
||||
const snapPoint = CONFIG.hexSizeSupport.findVertexSnapPoint(x, y, token, canvas.grid.grid)
|
||||
return new PIXI.Point(snapPoint.x, snapPoint.y)
|
||||
if (canvas.grid.isHex) {
|
||||
if (game.modules.get("hex-size-support")?.active && CONFIG.hexSizeSupport.getAltSnappingFlag(token)) {
|
||||
if (token.document.getFlag("hex-size-support", "borderSize") % 2 === 0) {
|
||||
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 {
|
||||
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
|
||||
if (!canvas.grid.isHex && token.data.width <= 0.5) {
|
||||
const [topLeftX, topLeftY] = canvas.grid.getTopLeft(x, y);
|
||||
if (token.data.width <= 0.5) {
|
||||
const offsetX = x - topLeftX;
|
||||
const offsetY = y - topLeftY;
|
||||
const subGridWidth = Math.floor(canvas.grid.w / 2);
|
||||
const subGridHeight = Math.floor(canvas.grid.h / 2);
|
||||
const subGridPosX = Math.floor(offsetX / subGridWidth);
|
||||
const subGridPosY = Math.floor(offsetY / subGridHeight);
|
||||
return new PIXI.Point(topLeftX + (subGridPosX + 0.5) * subGridWidth, topLeftY + (subGridPosY + 0.5) * subGridHeight);
|
||||
snapX = topLeftX + (subGridPosX + 0.5) * subGridWidth;
|
||||
}
|
||||
// Hex tokens, tokens with odd multipliers (1x1, 3x3, ...) and tokens smaller than 1x1 but bigger than 0.5x0.5 snap to the center of the grid cell
|
||||
if (canvas.grid.isHex || Math.round(token.data.width) % 2 === 1 || token.data.width < 1) {
|
||||
return new PIXI.Point(...canvas.grid.getCenter(x, y))
|
||||
// Tokens with odd multipliers (1x1, 3x3, ...) and tokens smaller than 1x1 but bigger than 0.5x0.5 snap to the center of the grid cell
|
||||
else if (Math.round(token.data.width) % 2 === 1 || token.data.width < 1) {
|
||||
snapX = centerX;
|
||||
}
|
||||
// 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)
|
||||
return new PIXI.Point(snappedX + canvas.grid.w / 2, snappedY + canvas.grid.h / 2)
|
||||
else {
|
||||
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) {
|
||||
@@ -62,6 +90,14 @@ export function getSnapPointForMeasuredTemplate(x, y) {
|
||||
return new PIXI.Point(snappedX, snappedY);
|
||||
}
|
||||
|
||||
export function getSnapPointForEntity(x, y, entity) {
|
||||
const isToken = entity instanceof Token;
|
||||
if (isToken)
|
||||
return getSnapPointForToken(x, y, entity);
|
||||
else
|
||||
return getSnapPointForMeasuredTemplate(x, y);
|
||||
}
|
||||
|
||||
export function highlightTokenShape(position, shape, color, alpha) {
|
||||
const layer = canvas.grid.highlightLayers[this.name];
|
||||
if ( !layer )
|
||||
@@ -190,3 +226,19 @@ export function applyTokenSizeOffset(waypoints, token) {
|
||||
|
||||
return waypoints.map(w => new PIXI.Point(w.x + waypointOffset.x, w.y + waypointOffset.y))
|
||||
}
|
||||
|
||||
export function setSnapParameterOnOptions(sourceObject, options) {
|
||||
// Allow outside modules to override snapping
|
||||
if (sourceObject.snapOverride?.active) {
|
||||
options.snapOverrideActive = true;
|
||||
options.snap = sourceObject.snapOverride.snap;
|
||||
sourceObject.snapOverride = undefined; // remove it to prevent any lingering data issues
|
||||
}
|
||||
else {
|
||||
options.snap = !game.keyboard._downKeys.has("Shift");
|
||||
}
|
||||
}
|
||||
|
||||
export function isClose(a, b, delta) {
|
||||
return Math.abs(a - b) <= delta;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user