Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4a5b9bbb11 | |||
| d467fe5bcf | |||
| 1f770f7597 | |||
| fa62e1d9c4 | |||
| f18ce33779 | |||
| 34cead5cba | |||
| eeb6db76a6 | |||
| 9c44994fd2 | |||
| 23c1a903a0 | |||
| 1b3f563c4e | |||
| c4595f408f | |||
| fae1bb8fe0 | |||
| f203bea673 | |||
| 84c18a6221 | |||
| 0573adf0fe | |||
| 76c71329d6 | |||
| f918e148fe | |||
| 52d77ba0a4 | |||
| b06169f149 | |||
| c914eb23ae | |||
| c8a7c73ace | |||
| 76cff06b66 | |||
| ad005c1bb1 |
@@ -1,3 +1,46 @@
|
||||
## 1.11.0
|
||||
### New features
|
||||
- Drag Ruler now supports pathfinding. Pressing the assigned button will automatically calculate the shortest route to the point you're dragging your token to and add the necessary waypoints to the ruler.
|
||||
- This feature is only available for gridded maps
|
||||
- This feature can only be used if it's enabled by the GM in the module settings
|
||||
- The routing algorithm *does not* take difficult terrain into account
|
||||
|
||||
|
||||
## 1.10.3
|
||||
### Compatibility
|
||||
- This release contains changes required to be compatible with Foundry 9.245
|
||||
|
||||
|
||||
## 1.10.2
|
||||
### Bugfixes
|
||||
- Fixed a bug that could cause squares to be highlighted in the wrong color when using waypoints on a 5/10/5 gird
|
||||
- When using Token Drag Vision, the temporary vision is now correctly cleaned up when dropping the token (resolves a conflict with the "Perfect Vision" module)
|
||||
|
||||
|
||||
## 1.10.1
|
||||
### Bugfixes
|
||||
- Fixed a bug that caused keybindings to break if no scene is active
|
||||
|
||||
### Translation
|
||||
- Updated Spanish translation (thanks to Viriato139ac#342)
|
||||
|
||||
|
||||
## 1.10.0
|
||||
**BREAKING** Drag Ruler 1.10.0 and onward cannot update directly from Drag Ruler versions older than 1.3.0. If you've been using Drag Ruler 1.2.2 or earlier in your world make sure to update to any Version between (inclusive) Drag Ruler 1.3.0 and 1.9.1, launch your world and log in as GM at least once. After doing so you can safely update to v1.10.0 or newer versions. Updating directly from 1.2.2 or older to 1.10.0 or newer will cause Drag Ruler to forget your Speed Prover Settings.
|
||||
|
||||
### New features
|
||||
- Drag Ruler's key bindings can now be assigned to custom keys by the user
|
||||
- Measuring difficult terrain on gridded maps with euclidean grid rule is now supported (for this to work the `Terrain Ruler` module needs to be enabled)
|
||||
|
||||
### Compatibility
|
||||
- Drag Ruler now supports Foundry v9
|
||||
- Drag Ruler now utilizes libwrapper to increase interoperability with other modules
|
||||
|
||||
### Translation
|
||||
- Added french translation (thanks to Elfenduli)
|
||||
- Updated japanese translation (thanks to touge)
|
||||
|
||||
|
||||
## 1.9.1
|
||||
### Bugfixes
|
||||
- Fixed a bug that caused the ruler to misbehave or not show up at all if the speed provider isn't configured (this was a regression introduced in 1.9.0)
|
||||
|
||||
@@ -40,11 +40,13 @@ The game systems that offer Drag Ruler integration are:
|
||||
- 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))
|
||||
- Level Up: Advanced 5th Edition (Official) (via the module [A5E Drag Ruler Integration](https://foundryvtt.com/packages/a5edragruler))
|
||||
- Pathfinder 1 (starting with version 0.77.3)
|
||||
- Pathfinder 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)
|
||||
- TheWitcherTRPG (starting with version 0.0.62)
|
||||
- Tormenta20 (starting with version 1.1.37)
|
||||
|
||||
+31
-5
@@ -29,7 +29,28 @@
|
||||
"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."
|
||||
},
|
||||
"togglePathfinding": {
|
||||
"name": "Wegfindung umschalten",
|
||||
"hint": "Wenn diese Taste gedrückt gehalten wird, während ein Token gezogen wird, wird die Wegfindung vorübergehend aktiviert/deaktiviert"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"allowPathfinding": {
|
||||
"name": "Wegfindung aktivieren",
|
||||
"hint": "Aktiviert Drag Ruler's Wegfindungsfunktion. Bitte beachte, dass die Wegfindung Wege durch unerkundeten Nebel des Kriegs und Ätherische Wände berechnen kann. Dies kann deinen Spielern Geheimnisse lüften, von denen sie noch nicht erfahren sollten."
|
||||
},
|
||||
"alwaysShowSpeedForPCs": {
|
||||
"name": "Geschwindigkeit von Spielercharakteren für jeden anzeigen",
|
||||
"hint": "Wenn diese Einstellung aktiviert ist wird die Färbung der hervorgehobenen Felder bei Spielercharakteren allen Spielern angezeigt, selbst wenn diese keinen Zugriff auf den Charakterbogen haben."
|
||||
@@ -42,6 +63,15 @@
|
||||
"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."
|
||||
},
|
||||
"rightClickAction": {
|
||||
"name": "Aktion bei Rechtsklick",
|
||||
"hint": "Was soll getan werden, wenn die rechte Maustaste während des Ziehens gedrückt wird?",
|
||||
"choices": {
|
||||
"create": "Wegpunkt erstellen",
|
||||
"delete": "Wegpunkt löschen",
|
||||
"cancel": "Ziehen beenden"
|
||||
}
|
||||
},
|
||||
"showGMRulerToPlayers": {
|
||||
"name": "Lineal des Spielleiters bei Spielern anzeigen",
|
||||
"hint": "Wenn diese Option deaktiviert ist wird das Lineal des Spielleiters bei den anderen Spielern nicht angezeigt."
|
||||
@@ -78,13 +108,9 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"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."
|
||||
"hint": "Lässt Spielfiguren auf gitterlosen Szenen an deren Geschwindigkeitsgrenzen einrasten."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+35
-5
@@ -29,7 +29,28 @@
|
||||
"dash": "dash"
|
||||
}
|
||||
},
|
||||
"keybindings": {
|
||||
"cancelDrag": "Cancel dragging",
|
||||
"createWaypoint": "Create waypoint",
|
||||
"deleteWaypoint": "Delete waypoint",
|
||||
"disableSnap": {
|
||||
"name": "Disable snapping",
|
||||
"hint": "Snapping will be temporarily disabled while this key is being held down"
|
||||
},
|
||||
"moveWithoutAnimation": {
|
||||
"name": "Disable token animation",
|
||||
"hint": "When being held while dropping a token, the token will move to the target location without animating"
|
||||
},
|
||||
"togglePathfinding": {
|
||||
"name": "Toggle pathfinding",
|
||||
"hint": "When being held while dragging a token, the pathfinding functionality will be temporarily enabled/disabled"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"allowPathfinding": {
|
||||
"name": "Enable pathfinding feature",
|
||||
"hint": "Enables Drag Ruler's pathfinding functionality in this world. Be aware that pathfinding can route through unexplored fog of war and Ethereal Walls, which might reveal secrets to your players ahead of time."
|
||||
},
|
||||
"alwaysShowSpeedForPCs": {
|
||||
"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."
|
||||
@@ -38,10 +59,23 @@
|
||||
"name": "Automatically start measuring",
|
||||
"hint": "If enabled, Drag Ruler will start measuring as soon as the token is being dragged. If disabled, Drag Ruler will remain inactive and will only start measuring once the button to add a waypoint is being pressed."
|
||||
},
|
||||
"autoPathfinding": {
|
||||
"name": "Pathfinding by default",
|
||||
"hint": "If enabled, dragging a token will automatically use a pathfinding ruler"
|
||||
},
|
||||
"enableMovementHistory": {
|
||||
"name": "Enable movement history during combat",
|
||||
"hint": "If enabled, Drag Ruler will remember the path a token took during 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": {
|
||||
"name": "Show GM ruler to players",
|
||||
"hint": "If disabled the ruler of GMs will not be shown for non-GM players"
|
||||
@@ -78,13 +112,9 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"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."
|
||||
"hint": "On Gridless scenes, this makes tokens snap to the token's speed ranges."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+22
-4
@@ -33,6 +33,19 @@
|
||||
"dash": "correr"
|
||||
}
|
||||
},
|
||||
"keybindings": {
|
||||
"cancelDrag": "Cancelar arrastre",
|
||||
"createWaypoint": "Crear punto de ruta",
|
||||
"deleteWaypoint": "Borrar punto de ruta",
|
||||
"disableSnap": {
|
||||
"name": "Deshabilitar ajuste",
|
||||
"hint": "El ajuste a la rejilla se deshabilitará temporalmente mientras se presione esta tecla"
|
||||
},
|
||||
"moveWithoutAnimation": {
|
||||
"name": "Deshabilitar animación de icono",
|
||||
"hint": "Si al soltar un icono se presiona esta tecla, se deshabilitará la animación del icono al moverse al destino"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"alwaysShowSpeedForPCs": {
|
||||
"name": "Mostrar velocidad de los PJs a todo el mundo",
|
||||
@@ -46,6 +59,15 @@
|
||||
"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"
|
||||
},
|
||||
"rightClickAction": {
|
||||
"name": "Acción del botón derecho",
|
||||
"hint": "Qué acción se llevará a cabo cuando se haga clic derecho mientras se arrastra un icono",
|
||||
"choices": {
|
||||
"create": "Crear punto de ruta",
|
||||
"delete": "Borrar punto de ruta",
|
||||
"cancel": "Cancelar arrastre"
|
||||
}
|
||||
},
|
||||
"showGMRulerToPlayers": {
|
||||
"name": "Mostrar regla del GM a los jugadores",
|
||||
"hint": "Si se deshabilita, no se mostrará la regla de medición de los GMs al resto de usuarios"
|
||||
@@ -82,10 +104,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"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,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": {
|
||||
"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": {
|
||||
"settings": {
|
||||
"dashMultiplier": {
|
||||
@@ -21,6 +35,14 @@
|
||||
"name": "プレイヤーキャラクターの移動速度を全員に公開する",
|
||||
"hint": "有効にすると、アクターの観察者(Observer)権限を持たない人にも,移動範囲の色分けが表示されるようになります。"
|
||||
},
|
||||
"autoStartMeasurement": {
|
||||
"name": "自動計測",
|
||||
"hint": "有効化時、Drag Rulerはトークンがドラッグされた時点で計測を初めます。無効化の場合、ドラッグしてもDrag Rulerは起動せず、中間地点をおいたときにのみ起動します。"
|
||||
},
|
||||
"enableMovementHistory": {
|
||||
"name": "戦闘中の移動履歴を有効にする",
|
||||
"hint": "有効にすると、Drag Rulerは戦闘ターン中にコマが辿った経路を記憶するようになります。これはコマの再選択時に再表示されます。"
|
||||
},
|
||||
"showGMRulerToPlayers": {
|
||||
"name": "GMのルーラーをプレイヤーに見せる",
|
||||
"hint": "無効の場合、GMのルーラーはプレイヤーに表示されません。"
|
||||
@@ -57,9 +79,9 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"swapSpacebarRightClick": {
|
||||
"name": "スペースキーと右クリックを入れ替える",
|
||||
"hint": "ドラッグ中のスペースキーと右クリックの機能を入れ替えます。有効にすると右クリックがウェイポイントの配置、スペースキーが削除になります。"
|
||||
"useGridlessRaster": {
|
||||
"name": "移動速度制限",
|
||||
"hint": "グリッドの無いシーンでは、コマがドラッグできる範囲を移動速度ぶんまで制限します。Shiftキーを押すことで一時的にこの制限を無視できます。"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,10 +73,6 @@
|
||||
"system": "시스템 {name}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"swapSpacebarRightClick": {
|
||||
"name": "스페이스바와 우클릭 스왑",
|
||||
"hint": "드래그 도중 스페이스바의 기능을 우클릭 기능과 바꾼다. 활성화된 경우 드래그시 우클릭을 사용하면 경유지가 배치되고, 스페이스바가 해당 경유지를 삭제한다."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,10 +56,6 @@
|
||||
"system": "系統 {name}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"swapSpacebarRightClick": {
|
||||
"name": "交換空格鍵並鼠標右鍵",
|
||||
"hint": "交換空格鍵和右鍵點擊在拖動過程中的功能。如果啟用,右鍵單擊將放置航點,空格鍵將刪除它們。"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+15
-9
@@ -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.9.1",
|
||||
"minimumCoreVersion" : "0.8.5",
|
||||
"compatibleCoreVersion" : "0.8.9",
|
||||
"version": "1.11.0",
|
||||
"minimumCoreVersion" : "9.245",
|
||||
"compatibleCoreVersion" : "9",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Manuel Vögele",
|
||||
@@ -13,6 +13,7 @@
|
||||
}
|
||||
],
|
||||
"esmodules": [
|
||||
"src/libwrapper_shim.js",
|
||||
"src/main.js",
|
||||
"src/socket.js"
|
||||
],
|
||||
@@ -30,6 +31,16 @@
|
||||
"name": "English",
|
||||
"path": "lang/en.json"
|
||||
},
|
||||
{
|
||||
"lang": "es",
|
||||
"name": "Español",
|
||||
"path": "lang/es.json"
|
||||
},
|
||||
{
|
||||
"lang": "fr",
|
||||
"name": "Français",
|
||||
"path": "lang/fr.json"
|
||||
},
|
||||
{
|
||||
"lang": "ja",
|
||||
"name": "日本語",
|
||||
@@ -44,11 +55,6 @@
|
||||
"lang": "zh-tw",
|
||||
"name": "正體中文",
|
||||
"path": "lang/zh-tw.json"
|
||||
},
|
||||
{
|
||||
"lang": "es",
|
||||
"name": "Español",
|
||||
"path": "lang/es.json"
|
||||
}
|
||||
],
|
||||
"dependencies": [
|
||||
@@ -59,7 +65,7 @@
|
||||
],
|
||||
"socket": true,
|
||||
"url": "https://github.com/manuelVo/foundryvtt-drag-ruler",
|
||||
"download": "https://github.com/manuelVo/foundryvtt-drag-ruler/archive/v1.9.1.zip",
|
||||
"download": "https://github.com/manuelVo/foundryvtt-drag-ruler/archive/v1.11.0.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",
|
||||
|
||||
@@ -16,7 +16,10 @@ export function highlightMeasurementTerrainRuler(ray, startDistance, tokenShape=
|
||||
|
||||
export function measureDistances(segments, entity, shape, options={}) {
|
||||
const opts = duplicate(options)
|
||||
|
||||
if (canvas.grid.diagonalRule === "EUCL") {
|
||||
opts.ignoreGrid = true;
|
||||
opts.gridSpaes = false;
|
||||
}
|
||||
if (opts.enableTerrainRuler) {
|
||||
opts.gridSpaces = true;
|
||||
const firstNewSegmentIndex = segments.findIndex(segment => !segment.ray.dragRulerVisitedSpaces);
|
||||
|
||||
@@ -16,3 +16,21 @@ export function getGridPositionFromPixels(xPixel, yPixel) {
|
||||
return [y, x]
|
||||
return [x, y]
|
||||
}
|
||||
|
||||
export function getGridPositionFromPixelsObj(o) {
|
||||
const r = {};
|
||||
[r.x, r.y] = getGridPositionFromPixels(o.x, o.y);
|
||||
return r;
|
||||
}
|
||||
|
||||
export function getPixelsFromGridPositionObj(o) {
|
||||
const r = {};
|
||||
[r.x, r.y] = getPixelsFromGridPosition(o.x, o.y);
|
||||
return r;
|
||||
}
|
||||
|
||||
export function getCenterFromGridPositionObj(o) {
|
||||
const r = getPixelsFromGridPositionObj(o);
|
||||
[r.x, r.y] = canvas.grid.getCenter(r.x, r.y);
|
||||
return r;
|
||||
}
|
||||
|
||||
+56
-13
@@ -1,9 +1,12 @@
|
||||
import {highlightMeasurementTerrainRuler, measureDistances} from "./compatibility.js";
|
||||
import {getGridPositionFromPixels} from "./foundry_fixes.js";
|
||||
import {getCenterFromGridPositionObj, getGridPositionFromPixels, getGridPositionFromPixelsObj} from "./foundry_fixes.js";
|
||||
import {Line} from "./geometry.js";
|
||||
import {disableSnap, moveWithoutAnimation} from "./keybindings.js";
|
||||
import {trackRays} from "./movement_tracking.js"
|
||||
import {findPath, isPathfindingEnabled} from "./pathfinding.js";
|
||||
import {settingsKey} from "./settings.js";
|
||||
import {recalculate} from "./socket.js";
|
||||
import {applyTokenSizeOffset, getSnapPointForEntity, getSnapPointForToken, getTokenShape, highlightTokenShape, zip} from "./util.js";
|
||||
import {applyTokenSizeOffset, enumeratedZip, getSnapPointForEntity, getSnapPointForToken, getTokenShape, highlightTokenShape, sum} from "./util.js";
|
||||
|
||||
// This is a modified version of Ruler.moveToken from foundry 0.7.9
|
||||
export async function moveEntities(draggedEntity, selectedEntities) {
|
||||
@@ -69,7 +72,7 @@ async function animateEntities(entities, draggedEntity, draggedRays, wasPaused)
|
||||
});
|
||||
|
||||
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;
|
||||
|
||||
// This is a flag of the "Monk's Active Tile Triggers" module that signals that the movement should be cancelled early
|
||||
@@ -130,7 +133,7 @@ function scheduleMeasurement(destination, event) {
|
||||
const mt = event._measureTime || 0;
|
||||
const originalEvent = event.data.originalEvent;
|
||||
if (Date.now() - mt > measurementInterval) {
|
||||
this.measure(destination, {snap: !originalEvent.shiftKey});
|
||||
this.measure(destination, {snap: !disableSnap});
|
||||
event._measureTime = Date.now();
|
||||
this._state = Ruler.STATES.MEASURING;
|
||||
cancelScheduledMeasurement.call(this);
|
||||
@@ -152,18 +155,53 @@ export function cancelScheduledMeasurement() {
|
||||
|
||||
// This is a modified version of Ruler.measure form foundry 0.7.9
|
||||
export function measure(destination, options={}) {
|
||||
|
||||
const isToken = this.draggedEntity instanceof Token;
|
||||
if (isToken && !this.draggedEntity.isVisible)
|
||||
return []
|
||||
|
||||
options.snap = options.snap ?? !disableSnap;
|
||||
|
||||
if (options.snap) {
|
||||
destination = getSnapPointForEntity(destination.x, destination.y, this.draggedEntity);
|
||||
}
|
||||
|
||||
this.dragRulerRemovePathfindingWaypoints();
|
||||
|
||||
if (isToken && isPathfindingEnabled()) {
|
||||
let path = findPath(getGridPositionFromPixelsObj(this.waypoints[this.waypoints.length - 1]), getGridPositionFromPixelsObj(destination), this.waypoints);
|
||||
if (path) {
|
||||
path = path.map(point => getCenterFromGridPositionObj(point))
|
||||
|
||||
// If the token is snapped to the grid, the first point of the path is already handled by the ruler
|
||||
if (path[0].x === this.waypoints[this.waypoints.length - 1].x && path[0].y === this.waypoints[this.waypoints.length - 1].y)
|
||||
path = path.slice(1);
|
||||
|
||||
// If snapping is enabled, the last point of the path is already handled by the ruler
|
||||
if (options.snap)
|
||||
path = path.slice(0, path.length - 1);
|
||||
|
||||
for (const point of path) {
|
||||
point.isPathfinding = true;
|
||||
this.labels.addChild(new PreciseText("", CONFIG.canvasTextStyle));
|
||||
}
|
||||
this.waypoints = this.waypoints.concat(path);
|
||||
}
|
||||
else {
|
||||
// Don't show a path if the pathfinding yields no result to show the user that the destination is unreachable
|
||||
destination = this.waypoints[this.waypoints.length - 1];
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -231,7 +269,7 @@ export function measure(destination, options={}) {
|
||||
rulerColor = this.dragRulerGetColorForDistance(totalDistance);
|
||||
else
|
||||
rulerColor = this.color
|
||||
for (const [s, cs] of zip(segments.reverse(), centeredSegments.reverse())) {
|
||||
for (const [i, s, cs] of enumeratedZip([...segments].reverse(), [...centeredSegments].reverse())) {
|
||||
const { label, text, last } = cs;
|
||||
|
||||
// Draw line segment
|
||||
@@ -264,10 +302,13 @@ export function measure(destination, options={}) {
|
||||
|
||||
// Highlight grid positions
|
||||
if (isToken && canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS && options.gridSpaces) {
|
||||
if (options.enableTerrainRuler)
|
||||
if (options.enableTerrainRuler) {
|
||||
highlightMeasurementTerrainRuler.call(this, cs.ray, cs.startDistance, shape, opacityMultiplier)
|
||||
else
|
||||
highlightMeasurementNative.call(this, cs.ray, cs.startDistance, shape, opacityMultiplier);
|
||||
}
|
||||
else {
|
||||
const previousSegments = centeredSegments.slice(0, segments.length - 1 - i);
|
||||
highlightMeasurementNative.call(this, cs.ray, previousSegments, shape, opacityMultiplier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,7 +321,7 @@ export function measure(destination, options={}) {
|
||||
return segments;
|
||||
}
|
||||
|
||||
export function highlightMeasurementNative(ray, startDistance, tokenShape=[{x: 0, y: 0}], alpha=1) {
|
||||
export function highlightMeasurementNative(ray, previousSegments, tokenShape=[{x: 0, y: 0}], alpha=1) {
|
||||
const spacer = canvas.scene.data.gridType === CONST.GRID_TYPES.SQUARE ? 1.41 : 1;
|
||||
const nMax = Math.max(Math.floor(ray.distance / (spacer * Math.min(canvas.grid.w, canvas.grid.h))), 1);
|
||||
const tMax = Array.fromRange(nMax+1).map(t => t / nMax);
|
||||
@@ -299,8 +340,9 @@ 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 = this.dragRulerGetColorForDistance(startDistance + subDistance);
|
||||
const pathUntilSpace = previousSegments.concat([{ray: new Ray(ray.A, {x: xg, y: yg})}]);
|
||||
const distance = sum(canvas.grid.measureDistances(pathUntilSpace, {gridSpaces: true}));
|
||||
const color = this.dragRulerGetColorForDistance(distance);
|
||||
const snapPoint = getSnapPointForToken(...canvas.grid.getTopLeft(x, y), this.draggedEntity);
|
||||
const [snapX, snapY] = getGridPositionFromPixels(snapPoint.x + 1, snapPoint.y + 1);
|
||||
|
||||
@@ -312,8 +354,9 @@ export function highlightMeasurementNative(ray, startDistance, tokenShape=[{x: 0
|
||||
let {x, y} = ray.project(th);
|
||||
let [x1h, y1h] = canvas.grid.grid.getGridPositionFromPixels(x, y);
|
||||
let [xgh, ygh] = canvas.grid.grid.getPixelsFromGridPosition(x1h, y1h);
|
||||
const subDistance = canvas.grid.measureDistances([{ray: new Ray(ray.A, {x: xgh, y: ygh})}], {gridSpaces: true})[0]
|
||||
const color = this.dragRulerGetColorForDistance(startDistance + subDistance);
|
||||
const pathUntilSpace = previousSegments.concat([{ray: new Ray(ray.A, {x: xgh, y: ygh})}]);
|
||||
const distance = sum(canvas.grid.measureDistances(pathUntilSpace, {gridSpaces: true}));
|
||||
const color = this.dragRulerGetColorForDistance(distance);
|
||||
const snapPoint = getSnapPointForToken(...canvas.grid.getTopLeft(x, y), this.draggedEntity);
|
||||
const [snapX, snapY] = getGridPositionFromPixels(snapPoint.x + 1, snapPoint.y + 1);
|
||||
highlightTokenShape.call(this, {x: snapX, y: snapY}, tokenShape, color, alpha);
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
import {settingsKey} from "./settings.js";
|
||||
import {getMeasurePosition, setSnapParameterOnOptions} from "./util.js";
|
||||
|
||||
export let disableSnap = false;
|
||||
export let moveWithoutAnimation = false;
|
||||
export let togglePathfinding = false;
|
||||
|
||||
export function registerKeybindings() {
|
||||
game.keybindings.register(settingsKey, "cancelDrag", {
|
||||
name: "drag-ruler.keybindings.cancelDrag",
|
||||
onDown: cancelDrag,
|
||||
uneditable: [{
|
||||
key: "Escape",
|
||||
}],
|
||||
precedence: -1,
|
||||
});
|
||||
|
||||
game.keybindings.register(settingsKey, "createWaypoint", {
|
||||
name: "drag-ruler.keybindings.createWaypoint",
|
||||
onDown: handleCreateWaypoint,
|
||||
editable: [{
|
||||
key: "Space"
|
||||
}],
|
||||
precedence: -1,
|
||||
});
|
||||
|
||||
game.keybindings.register(settingsKey, "deleteWaypoint", {
|
||||
name: "drag-ruler.keybindings.deleteWaypoint",
|
||||
onDown: handleDeleteWaypoint,
|
||||
precedence: -1,
|
||||
});
|
||||
|
||||
game.keybindings.register(settingsKey, "disableSnap", {
|
||||
name: "drag-ruler.keybindings.disableSnap.name",
|
||||
hint: "drag-ruler.keybindings.disableSnap.hint",
|
||||
onDown: handleDisableSnap,
|
||||
onUp: handleDisableSnap,
|
||||
editable: [{
|
||||
key: "ShiftLeft",
|
||||
}],
|
||||
precedence: -1,
|
||||
});
|
||||
|
||||
game.keybindings.register(settingsKey, "moveWithoutAnimation", {
|
||||
name: "drag-ruler.keybindings.moveWithoutAnimation.name",
|
||||
hint: "drag-ruler.keybindings.moveWithoutAnimation.hint",
|
||||
onDown: handleMoveWithoutAnimation,
|
||||
onUp: handleMoveWithoutAnimation,
|
||||
editable: [{
|
||||
key: "AltLeft",
|
||||
}],
|
||||
precedence: -1,
|
||||
});
|
||||
|
||||
if (game.settings.get(settingsKey, "allowPathfinding")) {
|
||||
game.keybindings.register(settingsKey, "togglePathfinding", {
|
||||
name: "drag-ruler.keybindings.togglePathfinding.name",
|
||||
hint: "drag-ruler.keybindings.togglePathfinding.hint",
|
||||
onDown: handleTogglePathfinding,
|
||||
onUp: handleTogglePathfinding,
|
||||
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;
|
||||
}
|
||||
|
||||
function handleTogglePathfinding(event) {
|
||||
togglePathfinding = !event.up;
|
||||
|
||||
const ruler = canvas.controls.ruler;
|
||||
if (!ruler?.isDragRuler)
|
||||
return false;
|
||||
if (ruler._state !== Ruler.STATES.MEASURING)
|
||||
return false;
|
||||
|
||||
ruler.measure(getMeasurePosition(), {snap: !disableSnap});
|
||||
return false;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
+55
-149
@@ -1,25 +1,30 @@
|
||||
"use strict"
|
||||
|
||||
import {currentSpeedProvider, getColorForDistanceAndToken, getMovedDistanceFromToken, getRangesFromSpeedProvider, initApi, registerModule, registerSystem} from "./api.js";
|
||||
import {checkDependencies, getHexSizeSupportTokenGridCenter, highlightMeasurementTerrainRuler} from "./compatibility.js";
|
||||
import {getColorForDistanceAndToken, getMovedDistanceFromToken, getRangesFromSpeedProvider, initApi, registerModule, registerSystem} from "./api.js";
|
||||
import {checkDependencies, getHexSizeSupportTokenGridCenter} from "./compatibility.js";
|
||||
import {moveEntities, onMouseMove} from "./foundry_imports.js"
|
||||
import {disableSnap, registerKeybindings} from "./keybindings.js";
|
||||
import {libWrapper} from "./libwrapper_shim.js";
|
||||
import {performMigrations} from "./migration.js"
|
||||
import {DragRulerRuler} from "./ruler.js";
|
||||
import {getMovementHistory, removeLastHistoryEntryIfAt, resetMovementHistory} from "./movement_tracking.js";
|
||||
import {registerSettings, settingsKey} from "./settings.js"
|
||||
import {removeLastHistoryEntryIfAt, resetMovementHistory} from "./movement_tracking.js";
|
||||
import {extendRuler} from "./ruler.js";
|
||||
import {registerSettings, RightClickAction, settingsKey} from "./settings.js"
|
||||
import {recalculate} from "./socket.js";
|
||||
import {SpeedProvider} from "./speed_provider.js"
|
||||
import {isClose, setSnapParameterOnOptions} from "./util.js";
|
||||
import {setSnapParameterOnOptions} from "./util.js";
|
||||
|
||||
CONFIG.debug.dragRuler = false;
|
||||
export let debugGraphics = undefined;
|
||||
|
||||
Hooks.once("init", () => {
|
||||
registerSettings()
|
||||
registerKeybindings()
|
||||
initApi()
|
||||
hookDragHandlers(Token);
|
||||
hookDragHandlers(MeasuredTemplate);
|
||||
hookKeyboardManagerFunctions()
|
||||
hookLayerFunctions();
|
||||
libWrapper.register("drag-ruler", "TokenLayer.prototype.undoHistory", tokenLayerUndoHistory, "WRAPPER");
|
||||
|
||||
Ruler = DragRulerRuler;
|
||||
extendRuler();
|
||||
|
||||
window.dragRuler = {
|
||||
getColorForDistanceAndToken,
|
||||
@@ -35,6 +40,8 @@ Hooks.once("ready", () => {
|
||||
performMigrations()
|
||||
checkDependencies();
|
||||
Hooks.callAll("dragRuler.ready", SpeedProvider)
|
||||
if (CONFIG.debug.dragRuler)
|
||||
debugGraphics = canvas.controls.addChild(new PIXI.Container());
|
||||
})
|
||||
|
||||
Hooks.on("canvasReady", () => {
|
||||
@@ -57,50 +64,28 @@ Hooks.on("getCombatTrackerEntryContext", function (html, menu) {
|
||||
menu.splice(1, 0, entry);
|
||||
});
|
||||
|
||||
function forwardIfUnahndled(newFn) {
|
||||
return function(oldFn, ...args) {
|
||||
const eventHandled = newFn(...args);
|
||||
if (!eventHandled)
|
||||
oldFn(...args);
|
||||
};
|
||||
}
|
||||
|
||||
function hookDragHandlers(entityType) {
|
||||
const originalDragLeftStartHandler = entityType.prototype._onDragLeftStart
|
||||
entityType.prototype._onDragLeftStart = function(event) {
|
||||
originalDragLeftStartHandler.call(this, event)
|
||||
onEntityLeftDragStart.call(this, event)
|
||||
}
|
||||
|
||||
const originalDragLeftMoveHandler = entityType.prototype._onDragLeftMove
|
||||
entityType.prototype._onDragLeftMove = function (event) {
|
||||
const entityName = entityType.name
|
||||
libWrapper.register("drag-ruler", `${entityName}.prototype._onDragLeftStart`, onEntityLeftDragStart, "WRAPPER");
|
||||
if (entityType === Token)
|
||||
applyGridlessSnapping.call(this, event);
|
||||
originalDragLeftMoveHandler.call(this, event)
|
||||
onEntityLeftDragMove.call(this, event)
|
||||
libWrapper.register("drag-ruler", `${entityName}.prototype._onDragLeftMove`, onEntityLeftDragMoveSnap, "WRAPPER");
|
||||
else
|
||||
libWrapper.register("drag-ruler", `${entityName}.prototype._onDragLeftMove`, onEntityLeftDragMove, "WRAPPER");
|
||||
libWrapper.register("drag-ruler", `${entityName}.prototype._onDragLeftDrop`, forwardIfUnahndled(onEntityDragLeftDrop), "MIXED");
|
||||
libWrapper.register("drag-ruler", `${entityName}.prototype._onDragLeftCancel`, forwardIfUnahndled(onEntityDragLeftCancel), "MIXED");
|
||||
}
|
||||
|
||||
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() {
|
||||
const originalHandleKeys = KeyboardManager.prototype._handleKeys
|
||||
KeyboardManager.prototype._handleKeys = function (event, key, up) {
|
||||
const eventHandled = handleKeys.call(this, event, key, up)
|
||||
if (!eventHandled)
|
||||
originalHandleKeys.call(this, event, key, up)
|
||||
}
|
||||
}
|
||||
|
||||
function hookLayerFunctions() {
|
||||
const originalTokenLayerUndoHistory = TokenLayer.prototype.undoHistory;
|
||||
TokenLayer.prototype.undoHistory = function () {
|
||||
async function tokenLayerUndoHistory(wrapped) {
|
||||
const historyEntry = this.history[this.history.length - 1];
|
||||
return originalTokenLayerUndoHistory.call(this).then((returnValue) => {
|
||||
const returnValue = await wrapped();
|
||||
if (historyEntry.type === "update") {
|
||||
for (const entry of historyEntry.data) {
|
||||
const token = canvas.tokens.get(entry._id);
|
||||
@@ -108,79 +93,10 @@ function hookLayerFunctions() {
|
||||
}
|
||||
}
|
||||
return returnValue;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function handleKeys(event, key, up) {
|
||||
if (event.repeat || this.hasFocus)
|
||||
return false
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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 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) {
|
||||
function onEntityLeftDragStart(wrapped, event) {
|
||||
wrapped(event);
|
||||
const isToken = this instanceof Token;
|
||||
const ruler = canvas.controls.ruler
|
||||
ruler.draggedEntity = this;
|
||||
@@ -193,32 +109,17 @@ function onEntityLeftDragStart(event) {
|
||||
if (game.settings.get(settingsKey, "autoStartMeasurement")) {
|
||||
let options = {};
|
||||
setSnapParameterOnOptions(ruler, options);
|
||||
startDragRuler.call(this, options, false);
|
||||
ruler.dragRulerStart(options, false);
|
||||
}
|
||||
}
|
||||
|
||||
function startDragRuler(options, measureImmediately=true) {
|
||||
const isToken = this instanceof Token;
|
||||
if (isToken && !currentSpeedProvider.usesRuler(this))
|
||||
return;
|
||||
const ruler = canvas.controls.ruler;
|
||||
ruler.clear();
|
||||
ruler._state = Ruler.STATES.STARTING;
|
||||
let entityCenter;
|
||||
if (isToken && canvas.grid.isHex && game.modules.get("hex-size-support")?.active && CONFIG.hexSizeSupport.getAltSnappingFlag(this))
|
||||
entityCenter = getHexSizeSupportTokenGridCenter(this);
|
||||
else
|
||||
entityCenter = this.center;
|
||||
if (isToken && game.settings.get(settingsKey, "enableMovementHistory"))
|
||||
ruler.dragRulerAddWaypointHistory(getMovementHistory(this));
|
||||
ruler.dragRulerAddWaypoint(entityCenter, {snap: false});
|
||||
const mousePosition = canvas.app.renderer.plugins.interaction.mouse.getLocalPosition(canvas.tokens);
|
||||
const destination = {x: mousePosition.x + ruler.rulerOffset.x, y: mousePosition.y + ruler.rulerOffset.y};
|
||||
if (measureImmediately)
|
||||
ruler.measure(destination, options);
|
||||
function onEntityLeftDragMoveSnap(wrapped, event) {
|
||||
applyGridlessSnapping.call(this, event);
|
||||
onEntityLeftDragMove.call(this, wrapped, event);
|
||||
}
|
||||
|
||||
function onEntityLeftDragMove(event) {
|
||||
function onEntityLeftDragMove(wrapped, event) {
|
||||
wrapped(event);
|
||||
const ruler = canvas.controls.ruler
|
||||
if (ruler.isDragRuler)
|
||||
onMouseMove.call(ruler, event)
|
||||
@@ -247,26 +148,31 @@ function onEntityDragLeftCancel(event) {
|
||||
if (!ruler.draggedEntity || ruler._state === Ruler.STATES.MOVING)
|
||||
return false
|
||||
|
||||
const swapSpacebarRightClick = game.settings.get(settingsKey, "swapSpacebarRightClick");
|
||||
const rightClickAction = game.settings.get(settingsKey, "rightClickAction");
|
||||
let options = {};
|
||||
setSnapParameterOnOptions(ruler, options);
|
||||
|
||||
if (ruler._state === Ruler.STATES.INACTIVE) {
|
||||
if (!swapSpacebarRightClick)
|
||||
if (rightClickAction !== RightClickAction.CREATE_WAYPOINT)
|
||||
return false;
|
||||
startDragRuler.call(this, options);
|
||||
ruler.dragRulerStart(options);
|
||||
event.preventDefault();
|
||||
}
|
||||
else if (ruler._state === Ruler.STATES.MEASURING) {
|
||||
if (!swapSpacebarRightClick) {
|
||||
ruler.dragRulerDeleteWaypoint(event, options);
|
||||
}
|
||||
else {
|
||||
switch (rightClickAction) {
|
||||
case RightClickAction.CREATE_WAYPOINT:
|
||||
event.preventDefault();
|
||||
ruler.dragRulerAddWaypoint(ruler.destination, options);
|
||||
break;
|
||||
case RightClickAction.DELETE_WAYPOINT:
|
||||
ruler.dragRulerDeleteWaypoint(event, options);
|
||||
break;
|
||||
case RightClickAction.ABORT_DRAG:
|
||||
ruler.dragRulerAbortDrag();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true
|
||||
return true;
|
||||
}
|
||||
|
||||
function applyGridlessSnapping(event) {
|
||||
@@ -275,7 +181,7 @@ function applyGridlessSnapping(event) {
|
||||
return;
|
||||
if (!ruler.isDragRuler)
|
||||
return;
|
||||
if (game.keyboard.isDown("Shift"))
|
||||
if (disableSnap)
|
||||
return;
|
||||
if (canvas.grid.type !== CONST.GRID_TYPES.GRIDLESS)
|
||||
return;
|
||||
|
||||
+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() {
|
||||
if (!game.user.isGM)
|
||||
return
|
||||
export async function performMigrations() {
|
||||
if (game.user.isGM)
|
||||
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") {
|
||||
// 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)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
import {getCenterFromGridPositionObj, getGridPositionFromPixelsObj} from "./foundry_fixes.js";
|
||||
import {togglePathfinding} from "./keybindings.js";
|
||||
import {debugGraphics} from "./main.js";
|
||||
import {settingsKey} from "./settings.js";
|
||||
import {iterPairs} from "./util.js";
|
||||
|
||||
let cachedNodes = undefined;
|
||||
let use5105 = false;
|
||||
|
||||
export function isPathfindingEnabled() {
|
||||
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS)
|
||||
return false;
|
||||
if (!game.settings.get(settingsKey, "allowPathfinding"))
|
||||
return false;
|
||||
return game.settings.get(settingsKey, "autoPathfinding") != togglePathfinding;
|
||||
}
|
||||
|
||||
export function findPath(from, to, previousWaypoints) {
|
||||
const lastNode = calculatePath(from, to, previousWaypoints);
|
||||
if (!lastNode)
|
||||
return null;
|
||||
paintPathfindingDebug(lastNode);
|
||||
const path = [];
|
||||
let currentNode = lastNode;
|
||||
while (currentNode) {
|
||||
// TODO Check if the distance doesn't change
|
||||
if (path.length >= 2 && !canvas.walls.checkCollision(new Ray(getCenterFromGridPositionObj(currentNode.node), getCenterFromGridPositionObj(path[path.length - 2]))))
|
||||
// Replace last waypoint if the current waypoint leads to a valid path
|
||||
path[path.length - 1] = {x: currentNode.node.x, y: currentNode.node.y};
|
||||
else
|
||||
path.push({x: currentNode.node.x, y: currentNode.node.y});
|
||||
currentNode = currentNode.previous;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
export function wipePathfindingCache() {
|
||||
cachedNodes = undefined;
|
||||
}
|
||||
|
||||
function getNode(pos, initialize=true) {
|
||||
pos = {layer: 0, ...pos}; // Copy pos and set pos.layer to the default value if it's unset
|
||||
if (!cachedNodes)
|
||||
cachedNodes = new Array(2);
|
||||
if (!cachedNodes[pos.layer])
|
||||
cachedNodes[pos.layer] = new Array(Math.ceil(canvas.dimensions.height / canvas.grid.h));
|
||||
if (!cachedNodes[pos.layer][pos.y])
|
||||
cachedNodes[pos.layer][pos.y] = new Array(Math.ceil(canvas.dimensions.width / canvas.grid.w));
|
||||
if (!cachedNodes[pos.layer][pos.y][pos.x]) {
|
||||
cachedNodes[pos.layer][pos.y][pos.x] = pos;
|
||||
}
|
||||
|
||||
const node = cachedNodes[pos.layer][pos.y][pos.x];
|
||||
if (initialize && !node.edges) {
|
||||
node.edges = [];
|
||||
for (const neighborPos of canvas.grid.grid.getNeighbors(pos.y, pos.x).map(([y, x]) => {return {x, y};})) {
|
||||
if (neighborPos.x < 0 || neighborPos.y < 0)
|
||||
continue;
|
||||
// TODO Work with pixels instead of grid locations
|
||||
if (!canvas.walls.checkCollision(new Ray(getCenterFromGridPositionObj(pos), getCenterFromGridPositionObj(neighborPos)))) {
|
||||
const isDiagonal = node.x !== neighborPos.x && node.y !== neighborPos.y && canvas.grid.type === CONST.GRID_TYPES.SQUARE;
|
||||
let targetLayer = pos.layer;
|
||||
if (use5105 && isDiagonal)
|
||||
targetLayer = 1 - targetLayer;
|
||||
const neighbor = getNode({...neighborPos, layer: targetLayer}, false);
|
||||
|
||||
// TODO We currently assume a cost of one or two for all transitions. Change this for difficult terrain support
|
||||
let edgeCost = 1;
|
||||
if (isDiagonal) {
|
||||
// We charge 0.0001 more for edges to avoid unnecessary diagonal steps
|
||||
edgeCost = pos.layer === 1 && targetLayer === 0 ? 2 : 1.0001;
|
||||
}
|
||||
node.edges.push({target: neighbor, cost: edgeCost});
|
||||
}
|
||||
}
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
function calculatePath(from, to, previousWaypoints) {
|
||||
if (game.system.id === "pf2e")
|
||||
use5105 = true;
|
||||
if (canvas.grid.diagonalRule === "5105")
|
||||
use5105 = true;
|
||||
let startLayer = 0;
|
||||
if (use5105) {
|
||||
previousWaypoints = previousWaypoints.map(w => getGridPositionFromPixelsObj(w));
|
||||
startLayer = calcNoDiagonals(previousWaypoints) % 2;
|
||||
}
|
||||
const nextNodes = [{node: getNode({...to, layer: startLayer}), cost: 0, estimated: estimateCost(to, from), previous: null}];
|
||||
const previousNodes = new Set();
|
||||
while (nextNodes.length > 0) {
|
||||
// Sort by estimated cost, high to low
|
||||
// TODO Re-sorting every iteration is expensive. Think of something better
|
||||
nextNodes.sort((a, b) => b.estimated - a.estimated);
|
||||
// Get node with cheapest estimate
|
||||
const currentNode = nextNodes.pop();
|
||||
if (currentNode.node.x === from.x && currentNode.node.y === from.y)
|
||||
return currentNode;
|
||||
previousNodes.add(currentNode.node);
|
||||
for (const edge of currentNode.node.edges) {
|
||||
const neighborNode = getNode(edge.target);
|
||||
if (previousNodes.has(neighborNode))
|
||||
continue;
|
||||
const neighbor = {node: neighborNode, cost: currentNode.cost + edge.cost, estimated: currentNode.cost + edge.cost + estimateCost(neighborNode, from), previous: currentNode};
|
||||
const neighborIndex = nextNodes.findIndex(node => node.node === neighbor.node);
|
||||
if (neighborIndex >= 0) {
|
||||
// If the neighbor is cheaper to reach via the current route than through previously discovered routes, replace it
|
||||
if (nextNodes[neighborIndex].cost > neighbor.cost) {
|
||||
nextNodes[neighborIndex] = neighbor;
|
||||
}
|
||||
}
|
||||
else {
|
||||
nextNodes.push(neighbor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function calcNoDiagonals(waypoints) {
|
||||
let diagonals = 0;
|
||||
for (const [p1, p2] of iterPairs(waypoints)) {
|
||||
diagonals += Math.min(Math.abs(p1.x - p2.x), Math.abs(p1.y - p2.y));
|
||||
}
|
||||
return diagonals;
|
||||
}
|
||||
|
||||
function estimateCost(pos, target) {
|
||||
return Math.max(Math.abs(pos.x - target.x), Math.abs(pos.y - target.y));
|
||||
}
|
||||
|
||||
function paintPathfindingDebug(lastNode) {
|
||||
if (!CONFIG.debug.dragRuler)
|
||||
return;
|
||||
|
||||
debugGraphics.removeChildren();
|
||||
let currentNode = lastNode;
|
||||
while (currentNode) {
|
||||
let text = new PIXI.Text(currentNode.cost.toFixed(0));
|
||||
let pixels = getCenterFromGridPositionObj(currentNode.node);
|
||||
text.anchor.set(0.5, 1.0);
|
||||
text.x = pixels.x;
|
||||
text.y = pixels.y;
|
||||
debugGraphics.addChild(text);
|
||||
currentNode = currentNode.previous;
|
||||
}
|
||||
}
|
||||
+47
-16
@@ -1,10 +1,13 @@
|
||||
import {getColorForDistanceAndToken, getRangesFromSpeedProvider} from "./api.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 {wipePathfindingCache} from "./pathfinding.js";
|
||||
import {settingsKey} from "./settings.js";
|
||||
import {getSnapPointForEntity, setSnapParameterOnOptions} from "./util.js";
|
||||
import {getSnapPointForEntity} from "./util.js";
|
||||
|
||||
export class DragRulerRuler extends Ruler {
|
||||
export function extendRuler() {
|
||||
class DragRulerRuler extends Ruler {
|
||||
// Functions below are overridden versions of functions in Ruler
|
||||
constructor(user, {color=null}={}) {
|
||||
super(user, {color});
|
||||
@@ -21,27 +24,22 @@ export class DragRulerRuler extends Ruler {
|
||||
}
|
||||
|
||||
async moveToken(event) {
|
||||
// This function is invoked by left clicking
|
||||
// Disable moveToken if Drag Ruler is active
|
||||
if (!this.isDragRuler)
|
||||
return await super.moveToken(event);
|
||||
|
||||
let options = {};
|
||||
setSnapParameterOnOptions(this, options);
|
||||
|
||||
if (!game.settings.get(settingsKey, "swapSpacebarRightClick")) {
|
||||
this.dragRulerAddWaypoint(this.destination, options);
|
||||
}
|
||||
else {
|
||||
this.dragRulerDeleteWaypoint(event, options);
|
||||
}
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
const json = super.toJSON();
|
||||
if (this.draggedEntity) {
|
||||
const isToken = this.draggedEntity instanceof Token;
|
||||
json["draggedEntityIsToken"] = isToken;
|
||||
json["draggedEntity"] = this.draggedEntity.id;
|
||||
json.draggedEntityIsToken = isToken;
|
||||
json.draggedEntity = this.draggedEntity.id;
|
||||
json.waypoints = json.waypoints.map(old => {
|
||||
let w = duplicate(old);
|
||||
w.isPathfinding = undefined;
|
||||
return w;
|
||||
});
|
||||
}
|
||||
return json;
|
||||
}
|
||||
@@ -83,6 +81,7 @@ export class DragRulerRuler extends Ruler {
|
||||
}
|
||||
this.waypoints.push(new PIXI.Point(point.x, point.y));
|
||||
this.labels.addChild(new PreciseText("", CONFIG.canvasTextStyle));
|
||||
this.waypoints.filter(waypoint => waypoint.isPathfinding).forEach(waypoint => waypoint.isPathfinding = false);
|
||||
}
|
||||
|
||||
dragRulerAddWaypointHistory(waypoints) {
|
||||
@@ -99,6 +98,7 @@ export class DragRulerRuler extends Ruler {
|
||||
}
|
||||
|
||||
dragRulerDeleteWaypoint(event={preventDefault: () => {return}}, options={}) {
|
||||
this.dragRulerRemovePathfindingWaypoints();
|
||||
options.snap = options.snap ?? true;
|
||||
if (this.waypoints.filter(w => !w.isPrevious).length > 1) {
|
||||
event.preventDefault();
|
||||
@@ -115,6 +115,11 @@ export class DragRulerRuler extends Ruler {
|
||||
}
|
||||
}
|
||||
|
||||
dragRulerRemovePathfindingWaypoints() {
|
||||
this.waypoints.filter(waypoint => waypoint.isPathfinding).forEach(_ => this.labels.removeChild(this.labels.children.pop()));
|
||||
this.waypoints = this.waypoints.filter(waypoint => !waypoint.isPathfinding);
|
||||
}
|
||||
|
||||
dragRulerAbortDrag(event={preventDefault: () => {return}}) {
|
||||
const token = this.draggedEntity;
|
||||
this._endMeasurement();
|
||||
@@ -171,4 +176,30 @@ export class DragRulerRuler extends Ruler {
|
||||
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();
|
||||
wipePathfindingCache();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
Ruler = DragRulerRuler;
|
||||
}
|
||||
|
||||
+45
-9
@@ -3,6 +3,12 @@ import {SpeedProvider} from "./speed_provider.js"
|
||||
|
||||
export const settingsKey = "drag-ruler";
|
||||
|
||||
export const RightClickAction = Object.freeze({
|
||||
CREATE_WAYPOINT: 0,
|
||||
DELETE_WAYPOINT: 1,
|
||||
ABORT_DRAG:2,
|
||||
});
|
||||
|
||||
export function registerSettings() {
|
||||
game.settings.register(settingsKey, "dataVersion", {
|
||||
scope: "world",
|
||||
@@ -11,6 +17,26 @@ export function registerSettings() {
|
||||
default: "fresh install"
|
||||
})
|
||||
|
||||
game.settings.register(settingsKey, "clientDataVersion", {
|
||||
scope: "client",
|
||||
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",
|
||||
@@ -20,15 +46,6 @@ export function registerSettings() {
|
||||
default: true,
|
||||
});
|
||||
|
||||
game.settings.register(settingsKey, "swapSpacebarRightClick", {
|
||||
name: "drag-ruler.settings.swapSpacebarRightClick.name",
|
||||
hint: "drag-ruler.settings.swapSpacebarRightClick.hint",
|
||||
scope: "client",
|
||||
config: true,
|
||||
type: Boolean,
|
||||
default: false,
|
||||
})
|
||||
|
||||
game.settings.register(settingsKey, "useGridlessRaster", {
|
||||
name: "drag-ruler.settings.useGridlessRaster.name",
|
||||
hint: "drag-ruler.settings.useGridlessRaster.hint",
|
||||
@@ -65,6 +82,25 @@ export function registerSettings() {
|
||||
default: true,
|
||||
});
|
||||
|
||||
game.settings.register(settingsKey, "allowPathfinding", {
|
||||
name: "drag-ruler.settings.allowPathfinding.name",
|
||||
hint: "drag-ruler.settings.allowPathfinding.hint",
|
||||
scope: "world",
|
||||
config: true,
|
||||
type: Boolean,
|
||||
default: false,
|
||||
onChange: () => location.reload(),
|
||||
});
|
||||
|
||||
game.settings.register(settingsKey, "autoPathfinding", {
|
||||
name: "drag-ruler.settings.autoPathfinding.name",
|
||||
hint: "drag-ruler.settings.autoPathfinding.hint",
|
||||
scpoe: "client",
|
||||
config: true,
|
||||
type: Boolean,
|
||||
defualt: false,
|
||||
});
|
||||
|
||||
game.settings.register(settingsKey, "lastTerrainRulerHintTime", {
|
||||
config: false,
|
||||
type: Number,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
export function getDefaultSpeedAttribute() {
|
||||
switch (game.system.id) {
|
||||
case "CoC7":
|
||||
|
||||
+27
-1
@@ -1,4 +1,5 @@
|
||||
import {getPixelsFromGridPosition} from "./foundry_fixes.js"
|
||||
import { disableSnap } from "./keybindings.js";
|
||||
|
||||
export function* zip(it1, it2) {
|
||||
for (let i = 0;i < Math.min(it1.length, it2.length);i++) {
|
||||
@@ -6,6 +7,24 @@ export function* zip(it1, it2) {
|
||||
}
|
||||
}
|
||||
|
||||
export function* enumeratedZip(it1, it2) {
|
||||
let i = 0;
|
||||
for (const [v1, v2] of zip(it1, it2)) {
|
||||
yield [i, v1, v2];
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
export function* iterPairs(l) {
|
||||
for (let i = 1;i < l.length;i++) {
|
||||
yield [l[i - 1], l[i]];
|
||||
}
|
||||
}
|
||||
|
||||
export function sum(arr) {
|
||||
return arr.reduce((a, b) => a + b, 0);
|
||||
}
|
||||
|
||||
export function getSnapPointForToken(x, y, token) {
|
||||
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
|
||||
return new PIXI.Point(x, y);
|
||||
@@ -235,10 +254,17 @@ export function setSnapParameterOnOptions(sourceObject, options) {
|
||||
sourceObject.snapOverride = undefined; // remove it to prevent any lingering data issues
|
||||
}
|
||||
else {
|
||||
options.snap = !game.keyboard._downKeys.has("Shift");
|
||||
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