Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0a7d8495c6 | |||
| 6e74582089 | |||
| 20e8713e30 | |||
| 544740b697 | |||
| 404e978377 | |||
| 4a56cdb0a4 | |||
| 48e6901b59 | |||
| 15e910424f | |||
| baba6e2986 | |||
| 6f0a04c20f | |||
| 8522c9a811 | |||
| d5fd5e0d04 | |||
| aabe9c75e8 | |||
| 0fb8aecfdd | |||
| bb5fe94439 | |||
| 6f6655009d | |||
| 8110a1b78b | |||
| b84cdfd7c1 | |||
| 75d59171d6 | |||
| 6ea03579b9 | |||
| fd5b439ec2 | |||
| 52118a6142 | |||
| b29a687fff | |||
| d232335e90 | |||
| b309464c76 | |||
| 9ce3cc5eb8 |
@@ -1,3 +1,43 @@
|
|||||||
|
## 1.3.4
|
||||||
|
### Module compatibility
|
||||||
|
- Increased compatiblility with other modules (namely Drag Ruler and Terrain Ruler are no longer incompatible)
|
||||||
|
|
||||||
|
## 1.3.3
|
||||||
|
### Bugfixes
|
||||||
|
- Speed Provider Settings are now being saved for non GM players
|
||||||
|
|
||||||
|
|
||||||
|
## 1.3.2
|
||||||
|
### Translation
|
||||||
|
- Updated japanese translation (thanks to togue)
|
||||||
|
|
||||||
|
|
||||||
|
## 1.3.1
|
||||||
|
### Bugfixes
|
||||||
|
- Fixed a bug where the coloring of ranges wouldn't work with the generic speed provider if the Dash Multiplier was set to 0
|
||||||
|
|
||||||
|
|
||||||
|
## 1.3.0
|
||||||
|
### New features
|
||||||
|
- The color used to indicate speed ranges is now configurable
|
||||||
|
- The settings dialog has been reworked
|
||||||
|
|
||||||
|
### System compatibility
|
||||||
|
- Drag Ruler's Generic SpeedProvider is now aware of good default values for the lancer game system (thanks to Grygon)
|
||||||
|
|
||||||
|
### API changes
|
||||||
|
This release introduces a new API that is incompatible with the old API. The new API offers more flexibility for users and Speed Providers alike and allows to add new features in the future without breaking compatibility again. The old API will continue to function, but to profit from any of the features below Speed Providers need to switch to the new API. For more details check out the API documentation.
|
||||||
|
|
||||||
|
The following things have changed with the new API:
|
||||||
|
- Colors used by speed providers can now be changed by the user via configuration
|
||||||
|
- Speed Providers can now offer settings to the user that will be integrated into Drag Ruler's settings menu
|
||||||
|
- Speed Providers can now conditionally disable Drag Ruler for some tokens
|
||||||
|
|
||||||
|
|
||||||
|
## 1.2.2
|
||||||
|
### Translation
|
||||||
|
- Added japanese translation (thanks to touge)
|
||||||
|
|
||||||
## 1.2.1
|
## 1.2.1
|
||||||
### Compatiblity
|
### Compatiblity
|
||||||
- Drag Ruler is now compatible with Hex Token Size Support. For compatibility Hex Token Size Support Version 0.5.4 or higher is required. Thanks to Ourobor for helping making this possible.
|
- Drag Ruler is now compatible with Hex Token Size Support. For compatibility Hex Token Size Support Version 0.5.4 or higher is required. Thanks to Ourobor for helping making this possible.
|
||||||
|
|||||||
@@ -26,63 +26,110 @@ In addition Drag Ruler provides more flexibility for game systems and modules vi
|
|||||||
Drag Ruler will work with all Foundry VTT game systems. However some game systems offer a special integration via the [Drag Ruler API](#api), that allows Drag Ruler to take the rules of the game system into account when dispaying speeds (such as weight carried or conditions that apply to the character), offering a smoother experience. While some game systems offer this integration natively, for other game systems there are modules providing the integration. If the integration is provided via a module you need to install and activate both Drag Ruler and the integration module to benefit from the integration.
|
Drag Ruler will work with all Foundry VTT game systems. However some game systems offer a special integration via the [Drag Ruler API](#api), that allows Drag Ruler to take the rules of the game system into account when dispaying speeds (such as weight carried or conditions that apply to the character), offering a smoother experience. While some game systems offer this integration natively, for other game systems there are modules providing the integration. If the integration is provided via a module you need to install and activate both Drag Ruler and the integration module to benefit from the integration.
|
||||||
|
|
||||||
The game systems that offer Drag Ruler integration are:
|
The game systems that offer Drag Ruler integration are:
|
||||||
|
- Cypher System (starting with version 1.13.0)
|
||||||
- Pathfinder 1 (starting with version 0.77.3)
|
- Pathfinder 1 (starting with version 0.77.3)
|
||||||
- Pathfinder 2e (via the module [PF2E Drag Ruler Integration](https://foundryvtt.com/packages/pf2e-dragruler/))
|
- Pathfinder 2e (via the module [PF2E Drag Ruler Integration](https://foundryvtt.com/packages/pf2e-dragruler/))
|
||||||
|
- Tormenta20 (starting with version 1.1.37)
|
||||||
|
|
||||||
|
|
||||||
|
## Translations
|
||||||
|
Drag Ruler is available in the follwing languages:
|
||||||
|
- English
|
||||||
|
- Japanese (thanks to touge)
|
||||||
|
|
||||||
## API
|
## API
|
||||||
*Audience: This paragraph is intended for module and system devleopers that want to add more complex behavior to Drag Ruler. If you just want to use this plugins features skip this paragraph.*
|
*Audience: This paragraph is intended for module and system devleopers that want to add more complex behavior to Drag Ruler. If you just want to use this plugins features skip this paragraph.*
|
||||||
|
|
||||||
The path coloring behavior of Drag Ruler can be altered by modules and systems to allow for for more complex coloring than provided by default. This allows specifying custom colors, using more different colors than offered by default and performing more calculations for determining the colors (for example a token may only be allowd to run if it isn't waring armor). Doing so is simple. This paragraph gives a short introduction on how to accomplish this, followed by a full example of how a call to the api might look like.
|
The path coloring behavior of Drag Ruler can be altered by modules and systems to allow for for more complex coloring than provided by default. This allows specifying custom colors, using more different colors than offered by default and performing more calculations for determining the colors (for example a token may only be allowd to run if it isn't waring armor). Doing so is simple. This paragraph will provide an example by showing an implementation of the api for a fictional game system, that contains everything you need to get started. Afterwards the code will be dissected into small parts and explained.
|
||||||
|
|
||||||
### Registering your Module/System
|
|
||||||
*This section is written from the perspective of a module developer. If you're writing a game system the process is exactly the same, except that you need to invoke `registerSystem` instead of `registerModule` and need to use the id of your system instead of a module id.*
|
|
||||||
|
|
||||||
The first thing you'll have to do is to make your module known to Drag Ruler. To do this, you have to call the `dragRuler.registerModule` function after the `dragRuler.ready` hook has fired. This might look like this:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
Hooks.once("dragRuler.ready", () => {
|
|
||||||
dragRuler.registerModule("id-of-your-module", mySpeedProvider)
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
The function `registerModule` takes two parameters: The first parameter must be the ID of your module as specified in your manifest. The second parameter is a reference to the Speed Provider of your module. What that Speed Provider looks like will be detailed in the paragraph [Writing the Speed Provider](#writing-the-speed-provider)
|
|
||||||
|
|
||||||
### Writing the Speed Provider
|
|
||||||
The Speed Provider is a function that calculates the speeds that a token can reach and assigns a color to each of the speeds. That function receives two arguments. The first argument is the token for which the speed should be calculated. The second argument is the color of the player that is dragging the token.
|
|
||||||
|
|
||||||
The function should contain an array of objects in the format `{range: Integer, color: Integer}`. The range indicates a distance that the token can cover with a certain speed and the color indicates which color should be used for that range. Spaces that cannot be reached with any of the speeds provided by this function will be colored in red (`0xFF0000`). The returned array doesn't need to be sorted in any particular order. However the array is not allowe to contain two objects with an identical range, as that results in undefined behavior.
|
|
||||||
|
|
||||||
The Speed Provider will be called once for each square that will be colored. For this reason it is advisable to not perform any expensive calculations in the Speed Provider.
|
|
||||||
|
|
||||||
Here is an example of how a Speed Provider might look like:
|
|
||||||
```javascript
|
|
||||||
function mySpeedProvider(token, playerColor) {
|
|
||||||
const baseSpeed = token.actor.data.speed
|
|
||||||
const ranges = [{range: baseSpeed, color: playerColor}, {range: baseSpeed * 2, color: 0xFFFF00}]
|
|
||||||
if (!token.actor.data.isWearingArmor) {
|
|
||||||
ranges.push({range: baseSpeed * 3, color: 0xFF8000})
|
|
||||||
}
|
|
||||||
return ranges
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
With this speed provider, the fields reachable with the token's base speed will be colored in the color of the player that is dragging the token. Each token is able to dash, which allows it to run twice it's base speed. Spaces that are reachable by dashing will be colored yellow (`0xFFFF00`). If the token isn't wearing any armor it is also allowed to sprint, allowing it to cover a distance of 3 times it's base speed. Those squares will be colored in orange (`0xFF8000`). All spaces further away from the token will be colored in red. Tokens wearing armor aren't allowed to sprint and for them all spaces that exceed 2 times their base speed will be colored red.
|
|
||||||
|
|
||||||
### Full example
|
### Full example
|
||||||
This is a full example of how using the api might look like. It's built from the examples above. For a explanation to what is going on refer to the previous chapters.
|
```javascript
|
||||||
|
Hooks.once("dragRuler.ready", (SpeedProvider) => {
|
||||||
|
class FictionalGameSystemSpeedProvider extends SpeedProvider {
|
||||||
|
get colors() {
|
||||||
|
return [
|
||||||
|
{id: "walk", default: 0x00FF00, name: "my-module-id.speeds.walk"},
|
||||||
|
{id: "dash", default: 0xFFFF00, name: "my-module-id.speeds.dash"},
|
||||||
|
{id: "run", default: 0xFF8000, name: "my-module-id.speeds.run"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
getRanges(token) {
|
||||||
|
const baseSpeed = token.actor.data.speed
|
||||||
|
|
||||||
|
// A character can always walk it's base speed and dash twice it's base speed
|
||||||
|
const ranges = [
|
||||||
|
{range: baseSpeed, color: "walk"},
|
||||||
|
{range: baseSpeed * 2, color: "dash"}
|
||||||
|
]
|
||||||
|
|
||||||
|
// Characters that aren't wearing armor are allowed to run with three times their speed
|
||||||
|
if (!token.actor.data.isWearingArmor) {
|
||||||
|
ranges.push({range: baseSpeed * 3, color: "dash"})
|
||||||
|
}
|
||||||
|
|
||||||
|
return ranges
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dragRuler.registerModule("my-module-id", FictionalGameSystemSpeedProvider)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Exmplanation of the code
|
||||||
|
```javascript
|
||||||
|
Hooks.once("dragRuler.ready", (SpeedProvider) => {
|
||||||
|
class FictionalGameSystemSpeedProvider extends SpeedProvider {
|
||||||
|
```
|
||||||
|
|
||||||
|
After Drag Ruler has initialized and is ready to receive API calls it will fire the `dragRuler.ready` event. This is the signal for your module/gamesystem that it can now register itself in Drag Ruler. To do this you'll need to implement a Speed Provider. The Hook will provide you with one Argument: The class `SpeedProvider`, which serves as base class for all speed providers. To implement a Speed Provider you create a subclass of `SpeedProvider`. Within that class you override functions of the base class to implement the functionality you need. The functions `colors` and `getRanges` must be overridden by all Speed Provider implementations. Overriding other functions of the Speed Provider is optional and can be done if you need additional functionality for your speed provider.
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
Hooks.once("dragRuler.ready", () => {
|
get colors() {
|
||||||
dragRuler.registerModule("id-of-your-module", mySpeedProvider)
|
return [
|
||||||
})
|
{id: "walk", default: 0x00FF00, name: "my-module-id.speeds.walk"},
|
||||||
|
{id: "dash", default: 0xFFFF00, name: "my-module-id.speeds.dash"},
|
||||||
function mySpeedProvider(token, playerColor) {
|
{id: "run", default: 0xFF8000, name: "my-module-id.speeds.run"}
|
||||||
const baseSpeed = token.actor.data.speed
|
]
|
||||||
const ranges = [{range: baseSpeed, color: playerColor}, {range: baseSpeed * 2, color: 0xFFFF00}]
|
}
|
||||||
if (!token.actor.data.isWearingArmor) {
|
|
||||||
ranges.push({range: baseSpeed * 3, color: 0xFF8000})
|
|
||||||
}
|
|
||||||
return ranges
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The getter `colors` is one of the two functions that must be overridden by all implementations of `SpeedProvider`. It must return an array of all colors, that may be used by this speed provider. Each color must be an object and has three attributes:
|
||||||
|
|
||||||
|
- *id*: A name for this color that identifies this color. It will be used in other functions within your speed provider to reference to this color. Ther must not be two colors with the same id.
|
||||||
|
- *default*: The default color value that should be used by this color.
|
||||||
|
- *name*: A human readable name for this color that will be used in the Speed Provider Settings dialog. Drag Ruler will try to internationalize this string. This field is optional, but it's highly recommended to use it.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
getRanges(token) {
|
||||||
|
const baseSpeed = token.actor.data.speed
|
||||||
|
|
||||||
|
// A character can always walk it's base speed and dash twice it's base speed
|
||||||
|
const ranges = [
|
||||||
|
{range: baseSpeed, color: "walk"},
|
||||||
|
{range: baseSpeed * 2, color: "dash"}
|
||||||
|
]
|
||||||
|
|
||||||
|
// Characters that aren't wearing armor are allowed to run with three times their speed
|
||||||
|
if (!token.actor.data.isWearingArmor) {
|
||||||
|
ranges.push({range: baseSpeed * 3, color: "dash"})
|
||||||
|
}
|
||||||
|
|
||||||
|
return ranges
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `getRanges` function is the second function that every Speed Provider must override. This function receives a token as a parameter and must return an array of all the ranges that this token can reach. Each range is represented by an object having these fields:
|
||||||
|
- *range*: The maximum distance a token is allowed to move within this range
|
||||||
|
- *color*: The id of the color that is used to represent this range. This id must match the id as defined in the `colors` getter.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
dragRuler.registerModule("my-module-id", FictionalGameSystemSpeedProvider)
|
||||||
|
```
|
||||||
|
|
||||||
|
This line registers the Speed Provider class that was just created with Drag Ruler. The paramter must be the id of the module you're writing. This id must exactly match the id specified in you manifest. As the second parameter the Speed Provider class that was just created is passed in.
|
||||||
|
|
||||||
|
If you're not writing a module but a game system use `dragRuler.registerSystem` instead of `dragRuler.registerModule.
|
||||||
|
|
||||||
|
### Additional capabilities of the API
|
||||||
|
In addition to the basic capabilities of the API presented in the example above, Drag Ruler's API offers more capabilities, like adding settings to your Speed Provider. To learn more about additional capabilities refer to the documentation of the `SpeedProvider` base class in [speed_provider.js](src/speed_provider.js).
|
||||||
|
|||||||
+46
-19
@@ -1,31 +1,58 @@
|
|||||||
{
|
{
|
||||||
"drag-ruler": {
|
"drag-ruler": {
|
||||||
|
"genericSpeedProvider": {
|
||||||
|
"settings": {
|
||||||
|
"dashMultiplier": {
|
||||||
|
"name": "Dash Multiplier",
|
||||||
|
"hint": "This can be used to give tokens a secondary speed during coloring of the measured path. Set it to 0 to disable the secondary speed."
|
||||||
|
},
|
||||||
|
"speedAttribute": {
|
||||||
|
"name": "Speed Attribute",
|
||||||
|
"hint": "The attribute that defines a token's walking speed. This is used during coloring of the measured path."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"speeds": {
|
||||||
|
"walk": "walk",
|
||||||
|
"dash": "dash"
|
||||||
|
}
|
||||||
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"alwaysShowSpeedForPCs": {
|
"alwaysShowSpeedForPCs": {
|
||||||
"name": "Show PC speed to everyone",
|
"name": "Show PC speed to everyone",
|
||||||
"hint": "If enabled the coloring based on actor speed for player characters will shown to everyone, even if they don't have observer permission for the character sheet."
|
"hint": "If enabled the coloring based on actor speed for player characters will shown to everyone, even if they don't have observer permission for the character sheet."
|
||||||
},
|
},
|
||||||
"dashMultiplier": {
|
"speedProviderSettings": {
|
||||||
"name": "Dash Multiplier",
|
"name": "Speed Provider Settings",
|
||||||
"hint": "This can be used to give tokens a secondary speed during coloring of the measured path. Set it to 0 to disable the secondary speed."
|
"hint": "The Speed Provider Settings contain all the game system specific settings.",
|
||||||
},
|
"button": "Speed Provider Settings",
|
||||||
"speedAttribute": {
|
"windowTitle": "Speed Provider Settings",
|
||||||
"name": "Speed Attribute",
|
"headers": {
|
||||||
"hint": "The attribute that defines a token's walking speed. This is used during coloring of the measured path."
|
"speedProvider": "Speed Provider",
|
||||||
},
|
"speedProviderSettings": "Speed Provider specific settings"
|
||||||
"speedProvider": {
|
},
|
||||||
"name": "Speed Settings Provider",
|
"activeProvider": {
|
||||||
"hint": "Select who provides speed information for tokens duing coloring. Using a game system or module may provide more flexible coloring than sticking to the options provided by Drag Ruler.",
|
"name": "Currently active Speed Provider",
|
||||||
"choices": {
|
"hint": "The settings show below depend on the active speed provider. If the GM selects a different speed provider the available settings may change."
|
||||||
"module": "Module",
|
},
|
||||||
"native": "Drag Ruler",
|
"noSettings": "This speed provider doesn't offer any configuration options.",
|
||||||
"system": "System"
|
"color": {
|
||||||
|
"name": "Color for {colorName}",
|
||||||
|
"hint": "The color that will be used to color squares that are within {colorName} range",
|
||||||
|
"unreachable": {
|
||||||
|
"name": "unreachable",
|
||||||
|
"hint": "The color for spaces that aren't reachable by the dragged token"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"speedProvider": {
|
||||||
|
"name": "Speed Settings Provider",
|
||||||
|
"hint": "Select who provides speed information for tokens duing coloring. Drag Ruler offers a generic speed provider that provides basic functionality and should work for all game systems if configured correctly. More speed providers can be made available via game systems and installed modules. Selecting a different speed provider than the generic speed provider may offer a better integration into the rules of the game system you're using. The options below are dependent upon the speed provider selected here.",
|
||||||
|
"choices": {
|
||||||
|
"module": "Module {name}",
|
||||||
|
"native": "Generic",
|
||||||
|
"system": "System {name}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"staticFirstColor": {
|
|
||||||
"name": "Static First Color",
|
|
||||||
"hint": "Use a static color for the first movement range instead of the player color"
|
|
||||||
},
|
|
||||||
"swapSpacebarRightClick": {
|
"swapSpacebarRightClick": {
|
||||||
"name": "Swap spacebar and right click",
|
"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"
|
"hint": "Swaps the functions of spacebar and right click during dragging. If enabled right click will place waypoints and spacebar will delete them"
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
{
|
||||||
|
"drag-ruler": {
|
||||||
|
"genericSpeedProvider": {
|
||||||
|
"settings": {
|
||||||
|
"dashMultiplier": {
|
||||||
|
"name": "早足の倍率",
|
||||||
|
"hint": "設定すると、移動経路の測定中に第二の色分けが追加されます。0にすると第二速度が無効になります。"
|
||||||
|
},
|
||||||
|
"speedAttribute": {
|
||||||
|
"name": "移動速度の属性値",
|
||||||
|
"hint": "コマの移動速度が定義された属性値を指定します。移動範囲表示の色分けに使用されます。"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"speeds": {
|
||||||
|
"walk": "歩き",
|
||||||
|
"dash": "早足"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"alwaysShowSpeedForPCs": {
|
||||||
|
"name": "プレイヤーキャラクターの移動速度を全員に公開する",
|
||||||
|
"hint": "有効にすると、アクターの観察者(Observer)権限を持たない人にも,移動範囲の色分けが表示されるようになります。"
|
||||||
|
},
|
||||||
|
"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": "ドラッグ中のスペースキーと右クリックの機能を入れ替えます。有効にすると右クリックがウェイポイントの配置、スペースキーが削除になります。"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
-2
@@ -2,7 +2,7 @@
|
|||||||
"name": "drag-ruler",
|
"name": "drag-ruler",
|
||||||
"title": "Drag Ruler",
|
"title": "Drag Ruler",
|
||||||
"description": "When dragging a token displays a ruler showing how far you've moved that token.",
|
"description": "When dragging a token displays a ruler showing how far you've moved that token.",
|
||||||
"version": "1.2.1",
|
"version": "1.3.4",
|
||||||
"minimumCoreVersion" : "0.7.9",
|
"minimumCoreVersion" : "0.7.9",
|
||||||
"compatibleCoreVersion" : "0.7.9",
|
"compatibleCoreVersion" : "0.7.9",
|
||||||
"authors": [
|
"authors": [
|
||||||
@@ -15,15 +15,23 @@
|
|||||||
"esmodules": [
|
"esmodules": [
|
||||||
"src/main.js"
|
"src/main.js"
|
||||||
],
|
],
|
||||||
|
"templates": [
|
||||||
|
"speed_provider_settings.html"
|
||||||
|
],
|
||||||
"languages": [
|
"languages": [
|
||||||
{
|
{
|
||||||
"lang": "en",
|
"lang": "en",
|
||||||
"name": "English",
|
"name": "English",
|
||||||
"path": "lang/en.json"
|
"path": "lang/en.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"lang": "ja",
|
||||||
|
"name": "日本語",
|
||||||
|
"path": "lang/ja.json"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"url": "https://github.com/manuelVo/foundryvtt-drag-ruler",
|
"url": "https://github.com/manuelVo/foundryvtt-drag-ruler",
|
||||||
"download": "https://github.com/manuelVo/foundryvtt-drag-ruler/archive/v1.2.1.zip",
|
"download": "https://github.com/manuelVo/foundryvtt-drag-ruler/archive/v1.3.4.zip",
|
||||||
"manifest": "https://raw.githubusercontent.com/manuelVo/foundryvtt-drag-ruler/master/module.json",
|
"manifest": "https://raw.githubusercontent.com/manuelVo/foundryvtt-drag-ruler/master/module.json",
|
||||||
"readme": "https://github.com/manuelVo/foundryvtt-drag-ruler/blob/master/README.md",
|
"readme": "https://github.com/manuelVo/foundryvtt-drag-ruler/blob/master/README.md",
|
||||||
"changelog": "https://github.com/manuelVo/foundryvtt-drag-ruler/blob/master/CHANGELOG.md",
|
"changelog": "https://github.com/manuelVo/foundryvtt-drag-ruler/blob/master/CHANGELOG.md",
|
||||||
|
|||||||
+69
-16
@@ -1,37 +1,61 @@
|
|||||||
|
import {GenericSpeedProvider, SpeedProvider} from "./speed_provider.js"
|
||||||
|
import {settingsKey} from "./settings.js"
|
||||||
|
|
||||||
export const availableSpeedProviders = {}
|
export const availableSpeedProviders = {}
|
||||||
export let currentSpeedProvider = undefined
|
export let currentSpeedProvider = undefined
|
||||||
|
|
||||||
function register(module, type, speedProvider) {
|
function register(module, type, speedProvider) {
|
||||||
const providerSetting = game.settings.settings.get("drag-ruler.speedProvider")
|
const id = `${type}.${module.id}`
|
||||||
|
let providerInstance
|
||||||
|
if (speedProvider.prototype instanceof SpeedProvider) {
|
||||||
|
providerInstance = new speedProvider(id)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
speedProvider.id = id
|
||||||
|
speedProvider.usesRuler = () => true
|
||||||
|
providerInstance = speedProvider
|
||||||
|
}
|
||||||
|
setupProvider(providerInstance)
|
||||||
|
}
|
||||||
|
|
||||||
// Add the registered module to the settings entry
|
function setupProvider(speedProvider) {
|
||||||
providerSetting.config = true
|
if (speedProvider instanceof SpeedProvider) {
|
||||||
const moduleName = module.data.title
|
const unreachableColor = {id: "unreachable", default: speedProvider.defaultUnreachableColor, name: "drag-ruler.settings.speedProviderSettings.color.unreachable.name"}
|
||||||
const typeTitle = game.i18n.localize(`drag-ruler.settings.speedProvider.choices.${type}`)
|
for (const color of speedProvider.colors.concat([unreachableColor])) {
|
||||||
providerSetting.choices[`${type}.${module.id}`] = `${typeTitle} ${moduleName}`
|
game.settings.register(settingsKey, `speedProviders.${speedProvider.id}.color.${color.id}`, {
|
||||||
availableSpeedProviders[`${type}.${module.id}`] = speedProvider
|
config: false,
|
||||||
providerSetting.default = getDefaultSpeedProvider()
|
scope: "client",
|
||||||
|
type: Number,
|
||||||
|
default: color.default,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for (const setting of speedProvider.settings) {
|
||||||
|
setting.config = false
|
||||||
|
game.settings.register(settingsKey, `speedProviders.${speedProvider.id}.setting.${setting.id}`, setting)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
availableSpeedProviders[speedProvider.id] = speedProvider
|
||||||
|
game.settings.settings.get("drag-ruler.speedProvider").default = getDefaultSpeedProvider()
|
||||||
updateSpeedProvider()
|
updateSpeedProvider()
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDefaultSpeedProvider() {
|
export function getDefaultSpeedProvider() {
|
||||||
const providerSetting = game.settings.settings.get("drag-ruler.speedProvider")
|
const providerIds = Object.keys(availableSpeedProviders)
|
||||||
const settingKeys = Object.keys(providerSetting.choices)
|
|
||||||
// Game systems take the highest precedence for the being the default
|
// Game systems take the highest precedence for the being the default
|
||||||
const gameSystem = settingKeys.find(key => key.startsWith("system."))
|
const gameSystem = providerIds.find(key => key.startsWith("system."))
|
||||||
if (gameSystem)
|
if (gameSystem)
|
||||||
return gameSystem
|
return gameSystem
|
||||||
|
|
||||||
// If no game system is registered modules are next up.
|
// If no game system is registered modules are next up.
|
||||||
// For lack of a method to select the best module we're just falling back to taking the next best module
|
// For lack of a method to select the best module we're just falling back to taking the next best module
|
||||||
// settingKeys should always be sorted the same way so this should achive a stable default
|
// settingKeys should always be sorted the same way so this should achive a stable default
|
||||||
const module = settingKeys.find(key => key.startsWith("module."))
|
const module = providerIds.find(key => key.startsWith("module."))
|
||||||
if (module)
|
if (module)
|
||||||
return module
|
return module
|
||||||
|
|
||||||
// If neither a game system or a module is found fall back to the native implementation
|
// If neither a game system or a module is found fall back to the native implementation
|
||||||
return settingKeys[0]
|
return providerIds[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateSpeedProvider() {
|
export function updateSpeedProvider() {
|
||||||
@@ -40,8 +64,37 @@ export function updateSpeedProvider() {
|
|||||||
currentSpeedProvider = availableSpeedProviders[configuredProvider] ?? availableSpeedProviders[game.settings.settings.get("drag-ruler.speedProvider").default]
|
currentSpeedProvider = availableSpeedProviders[configuredProvider] ?? availableSpeedProviders[game.settings.settings.get("drag-ruler.speedProvider").default]
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setCurrentSpeedProvider(newSpeedProvider) {
|
export function initApi() {
|
||||||
currentSpeedProvider = newSpeedProvider
|
const genericSpeedProviderInstance = new GenericSpeedProvider("native")
|
||||||
|
setupProvider(genericSpeedProviderInstance)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRangesFromSpeedProvider(token) {
|
||||||
|
try {
|
||||||
|
if (currentSpeedProvider instanceof Function)
|
||||||
|
return currentSpeedProvider(token, 0x00FF00)
|
||||||
|
const ranges = currentSpeedProvider.getRanges(token)
|
||||||
|
for (const range of ranges) {
|
||||||
|
range.color = game.settings.get(settingsKey, `speedProviders.${currentSpeedProvider.id}.color.${range.color}`)
|
||||||
|
}
|
||||||
|
return ranges
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getUnreachableColorFromSpeedProvider() {
|
||||||
|
if (currentSpeedProvider instanceof Function)
|
||||||
|
return 0xFF0000
|
||||||
|
try {
|
||||||
|
return game.settings.get(settingsKey, `speedProviders.${currentSpeedProvider.id}.color.unreachable`)
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
return 0xFF0000
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function registerModule(moduleId, speedProvider) {
|
export function registerModule(moduleId, speedProvider) {
|
||||||
|
|||||||
+41
-1
@@ -166,7 +166,7 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Highlight grid positions
|
// Highlight grid positions
|
||||||
this._highlightMeasurement(ray, s.startDistance);
|
highlightMeasurementNative.call(this, ray, s.startDistance);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw endpoints
|
// Draw endpoints
|
||||||
@@ -177,3 +177,43 @@ export function measure(destination, {gridSpaces=true, snap=false} = {}) {
|
|||||||
// Return the measured segments
|
// Return the measured segments
|
||||||
return segments;
|
return segments;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function highlightMeasurementNative(ray, startDistance) {
|
||||||
|
const spacer = canvas.scene.data.gridType === CONST.GRID_TYPES.SQUARE ? 1.41 : 1;
|
||||||
|
const nMax = Math.max(Math.floor(ray.distance / (spacer * Math.min(canvas.grid.w, canvas.grid.h))), 1);
|
||||||
|
const tMax = Array.fromRange(nMax+1).map(t => t / nMax);
|
||||||
|
|
||||||
|
// Track prior position
|
||||||
|
let prior = null;
|
||||||
|
|
||||||
|
// Iterate over ray portions
|
||||||
|
for ( let [i, t] of tMax.entries() ) {
|
||||||
|
let {x, y} = ray.project(t);
|
||||||
|
|
||||||
|
// Get grid position
|
||||||
|
let [x0, y0] = (i === 0) ? [null, null] : prior;
|
||||||
|
let [x1, y1] = canvas.grid.grid.getGridPositionFromPixels(x, y);
|
||||||
|
if ( x0 === x1 && y0 === y1 ) continue;
|
||||||
|
|
||||||
|
// Highlight the grid position
|
||||||
|
let [xg, yg] = canvas.grid.grid.getPixelsFromGridPosition(x1, y1);
|
||||||
|
let subDistance = canvas.grid.measureDistances([{ray: new Ray(ray.A, {x: xg, y: yg})}], {gridSpaces: true})[0]
|
||||||
|
let color = dragRuler.getColorForDistance.call(this, startDistance, subDistance)
|
||||||
|
canvas.grid.highlightPosition(this.name, {x: xg, y: yg, color: color});
|
||||||
|
|
||||||
|
// Skip the first one
|
||||||
|
prior = [x1, y1];
|
||||||
|
if ( i === 0 ) continue;
|
||||||
|
|
||||||
|
// If the positions are not neighbors, also highlight their halfway point
|
||||||
|
if ( !canvas.grid.isNeighbor(x0, y0, x1, y1) ) {
|
||||||
|
let th = tMax[i - 1] + (0.5 / nMax);
|
||||||
|
let {x, y} = ray.project(th);
|
||||||
|
let [x1h, y1h] = canvas.grid.grid.getGridPositionFromPixels(x, y);
|
||||||
|
let [xgh, ygh] = canvas.grid.grid.getPixelsFromGridPosition(x1h, y1h);
|
||||||
|
subDistance = canvas.grid.measureDistances([{ray: new Ray(ray.A, {x: xg, y: yg})}], {gridSpaces: true})[0]
|
||||||
|
color = dragRuler.getColorForDistance.call(this, startDistance, subDistance)
|
||||||
|
canvas.grid.highlightPosition(this.name, {x: xgh, y: ygh, color: color});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+12
-51
@@ -1,30 +1,29 @@
|
|||||||
"use strict"
|
"use strict"
|
||||||
|
|
||||||
import {availableSpeedProviders, currentSpeedProvider, registerModule, registerSystem, setCurrentSpeedProvider} from "./api.js"
|
import {currentSpeedProvider, getRangesFromSpeedProvider, getUnreachableColorFromSpeedProvider, initApi, registerModule, registerSystem} from "./api.js"
|
||||||
import {getHexSizeSupportTokenGridCenter} from "./compatibility.js"
|
import {getHexSizeSupportTokenGridCenter} from "./compatibility.js"
|
||||||
import {measure, moveTokens, onMouseMove} from "./foundry_imports.js"
|
import {measure, moveTokens, onMouseMove} from "./foundry_imports.js"
|
||||||
|
import {performMigrations} from "./migration.js"
|
||||||
import {registerSettings, settingsKey} from "./settings.js"
|
import {registerSettings, settingsKey} from "./settings.js"
|
||||||
|
import {SpeedProvider} from "./speed_provider.js"
|
||||||
|
|
||||||
Hooks.once("init", () => {
|
Hooks.once("init", () => {
|
||||||
registerSettings()
|
registerSettings()
|
||||||
|
initApi()
|
||||||
hookTokenDragHandlers()
|
hookTokenDragHandlers()
|
||||||
hookRulerFunctions()
|
hookRulerFunctions()
|
||||||
hookKeyboardManagerFunctions()
|
hookKeyboardManagerFunctions()
|
||||||
patchRulerHighlightMeasurement()
|
|
||||||
|
|
||||||
availableSpeedProviders["native"] = nativeSpeedProvider
|
|
||||||
setCurrentSpeedProvider(nativeSpeedProvider)
|
|
||||||
|
|
||||||
window.dragRuler = {
|
window.dragRuler = {
|
||||||
getColorForDistance,
|
getColorForDistance,
|
||||||
registerModule,
|
registerModule,
|
||||||
registerSystem
|
registerSystem,
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
Hooks.once("ready", () => {
|
Hooks.once("ready", () => {
|
||||||
Hooks.callAll("dragRuler.ready")
|
performMigrations()
|
||||||
|
Hooks.callAll("dragRuler.ready", SpeedProvider)
|
||||||
})
|
})
|
||||||
|
|
||||||
Hooks.on("canvasReady", () => {
|
Hooks.on("canvasReady", () => {
|
||||||
@@ -142,6 +141,8 @@ function onKeyShift(up) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onTokenLeftDragStart(event) {
|
function onTokenLeftDragStart(event) {
|
||||||
|
if (!currentSpeedProvider.usesRuler(this))
|
||||||
|
return
|
||||||
const ruler = canvas.controls.ruler
|
const ruler = canvas.controls.ruler
|
||||||
ruler.draggedToken = this
|
ruler.draggedToken = this
|
||||||
let tokenCenter
|
let tokenCenter
|
||||||
@@ -165,7 +166,7 @@ function onTokenDragLeftDrop(event) {
|
|||||||
const ruler = canvas.controls.ruler
|
const ruler = canvas.controls.ruler
|
||||||
if (!ruler.isDragRuler)
|
if (!ruler.isDragRuler)
|
||||||
return false
|
return false
|
||||||
const selectedTokens = canvas.tokens.placeables.filter(token => token._controlled)
|
const selectedTokens = canvas.tokens.controlled
|
||||||
moveTokens.call(ruler, ruler.draggedToken, selectedTokens)
|
moveTokens.call(ruler, ruler.draggedToken, selectedTokens)
|
||||||
ruler.draggedToken = null
|
ruler.draggedToken = null
|
||||||
return true
|
return true
|
||||||
@@ -236,26 +237,6 @@ function deleteWaypoint() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function strInsertAfter(haystack, needle, strToInsert) {
|
|
||||||
const pos = haystack.indexOf(needle) + needle.length
|
|
||||||
return haystack.slice(0, pos) + strToInsert + haystack.slice(pos)
|
|
||||||
}
|
|
||||||
|
|
||||||
function nativeSpeedProvider(token, playercolor) {
|
|
||||||
const speedAttribute = game.settings.get(settingsKey, "speedAttribute")
|
|
||||||
if (!speedAttribute)
|
|
||||||
return []
|
|
||||||
const tokenSpeed = getProperty(token, speedAttribute)
|
|
||||||
if (tokenSpeed === undefined) {
|
|
||||||
console.warn(`Drag Ruler | The configured token speed attribute "${speedAttribute}" didn't return a speed value. To use colors based on drag distance set the setting to the correct value (or clear the box to disable this feature).`)
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
const dashMultiplier = game.settings.get(settingsKey, "dashMultiplier")
|
|
||||||
if (!dashMultiplier)
|
|
||||||
return [{range: tokenSpeed, color: playercolor}]
|
|
||||||
return [{range: tokenSpeed, color: playercolor}, {range: tokenSpeed * dashMultiplier, color: 0xFFFF00}]
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getColorForDistance(startDistance, subDistance=0) {
|
export function getColorForDistance(startDistance, subDistance=0) {
|
||||||
if (!this.isDragRuler)
|
if (!this.isDragRuler)
|
||||||
return this.color
|
return this.color
|
||||||
@@ -266,33 +247,13 @@ export function getColorForDistance(startDistance, subDistance=0) {
|
|||||||
return this.color
|
return this.color
|
||||||
}
|
}
|
||||||
const distance = startDistance + subDistance
|
const distance = startDistance + subDistance
|
||||||
const firstColor = game.settings.get(settingsKey, "staticFirstColor") ? 0x00FF00 : this.color
|
const ranges = getRangesFromSpeedProvider(this.draggedToken)
|
||||||
const ranges = currentSpeedProvider(this.draggedToken, firstColor)
|
|
||||||
if (ranges.length === 0)
|
if (ranges.length === 0)
|
||||||
return this.color
|
return this.color
|
||||||
const currentRange = ranges.reduce((minRange, currentRange) => {
|
const currentRange = ranges.reduce((minRange, currentRange) => {
|
||||||
if (distance <= currentRange.range && currentRange.range < minRange.range)
|
if (distance <= currentRange.range && currentRange.range < minRange.range)
|
||||||
return currentRange
|
return currentRange
|
||||||
return minRange
|
return minRange
|
||||||
}, {range: Infinity, color: 0xFF0000})
|
}, {range: Infinity, color: getUnreachableColorFromSpeedProvider()})
|
||||||
return currentRange.color
|
return currentRange.color
|
||||||
}
|
}
|
||||||
|
|
||||||
// These patches were written with foundry-0.7.9.js as reference
|
|
||||||
function patchRulerHighlightMeasurement() {
|
|
||||||
let code = Ruler.prototype._highlightMeasurement.toString()
|
|
||||||
// Replace CRLF with LF in case foundry.js has CRLF for some reason
|
|
||||||
code = code.replace(/\r\n/g, "\n")
|
|
||||||
// Remove function signature and closing curly bracket (those are on the first and last line)
|
|
||||||
code = code.slice(code.indexOf("\n"), code.lastIndexOf("\n"))
|
|
||||||
|
|
||||||
const calcColorCode = `
|
|
||||||
let subDistance = canvas.grid.measureDistances([{ray: new Ray(ray.A, {x: xg, y: yg})}], {gridSpaces: true})[0]
|
|
||||||
let color = dragRuler.getColorForDistance.call(this, startDistance, subDistance)
|
|
||||||
`
|
|
||||||
|
|
||||||
code = strInsertAfter(code, "Position(x1, y1);\n", calcColorCode)
|
|
||||||
code = strInsertAfter(code, "Position(x1h, y1h);\n", calcColorCode.replace("x: xg, y: yg", "x: xgh, y: ygh"))
|
|
||||||
code = code.replace(/color: this\.color\}/g, "color}")
|
|
||||||
Ruler.prototype._highlightMeasurement = new Function("ray", "startDistance=undefined", code)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import {settingsKey} from "./settings.js"
|
||||||
|
|
||||||
|
const currentDataVersion = "1.3.0"
|
||||||
|
|
||||||
|
export function performMigrations() {
|
||||||
|
if (!game.user.isGM)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
+189
-34
@@ -1,9 +1,16 @@
|
|||||||
import {updateSpeedProvider} from "./api.js";
|
import {availableSpeedProviders, currentSpeedProvider, getDefaultSpeedProvider, updateSpeedProvider} from "./api.js";
|
||||||
import {getDefaultDashMultiplier, getDefaultSpeedAttribute} from "./systems.js"
|
import {SpeedProvider} from "./speed_provider.js"
|
||||||
|
|
||||||
export const settingsKey = "drag-ruler";
|
export const settingsKey = "drag-ruler";
|
||||||
|
|
||||||
export function registerSettings() {
|
export function registerSettings() {
|
||||||
|
game.settings.register(settingsKey, "dataVersion", {
|
||||||
|
scope: "world",
|
||||||
|
config: false,
|
||||||
|
type: String,
|
||||||
|
default: "fresh install"
|
||||||
|
})
|
||||||
|
|
||||||
game.settings.register(settingsKey, "swapSpacebarRightClick", {
|
game.settings.register(settingsKey, "swapSpacebarRightClick", {
|
||||||
name: "drag-ruler.settings.swapSpacebarRightClick.name",
|
name: "drag-ruler.settings.swapSpacebarRightClick.name",
|
||||||
hint: "drag-ruler.settings.swapSpacebarRightClick.hint",
|
hint: "drag-ruler.settings.swapSpacebarRightClick.hint",
|
||||||
@@ -24,42 +31,190 @@ export function registerSettings() {
|
|||||||
|
|
||||||
// This setting will be modified by the api if modules register to it
|
// This setting will be modified by the api if modules register to it
|
||||||
game.settings.register(settingsKey, "speedProvider", {
|
game.settings.register(settingsKey, "speedProvider", {
|
||||||
name: "drag-ruler.settings.speedProvider.name",
|
|
||||||
hint: "drag-ruler.settings.speedProvider.hint",
|
|
||||||
scope: "world",
|
scope: "world",
|
||||||
config: false,
|
config: false,
|
||||||
type: Object,
|
type: String,
|
||||||
choices: {
|
default: getDefaultSpeedProvider(),
|
||||||
"native": game.i18n.localize("drag-ruler.settings.speedProvider.choices.native")
|
|
||||||
},
|
|
||||||
default: "native",
|
|
||||||
onChange: updateSpeedProvider,
|
onChange: updateSpeedProvider,
|
||||||
})
|
})
|
||||||
|
|
||||||
game.settings.register(settingsKey, "speedAttribute", {
|
game.settings.registerMenu(settingsKey, "speedProviderSettings", {
|
||||||
name: "drag-ruler.settings.speedAttribute.name",
|
name: "drag-ruler.settings.speedProviderSettings.name",
|
||||||
hint: "drag-ruler.settings.speedAttribute.hint",
|
hint: "drag-ruler.settings.speedProviderSettings.hint",
|
||||||
scope: "world",
|
label: "drag-ruler.settings.speedProviderSettings.button",
|
||||||
config: true,
|
icon: "fas fa-tachometer-alt",
|
||||||
type: String,
|
type: SpeedProviderSettings,
|
||||||
default: getDefaultSpeedAttribute(),
|
restricted: false,
|
||||||
})
|
|
||||||
|
|
||||||
game.settings.register(settingsKey, "dashMultiplier", {
|
|
||||||
name: "drag-ruler.settings.dashMultiplier.name",
|
|
||||||
hint: "drag-ruler.settings.dashMultiplier.hint",
|
|
||||||
scope: "world",
|
|
||||||
config: true,
|
|
||||||
type: Number,
|
|
||||||
default: getDefaultDashMultiplier(),
|
|
||||||
})
|
|
||||||
|
|
||||||
game.settings.register(settingsKey, "staticFirstColor", {
|
|
||||||
name: "drag-ruler.settings.staticFirstColor.name",
|
|
||||||
hint: "drag-ruler.settings.staticFirstColor.hint",
|
|
||||||
scope: "world",
|
|
||||||
config: true,
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class SpeedProviderSettings extends FormApplication {
|
||||||
|
static get defaultOptions() {
|
||||||
|
return mergeObject(super.defaultOptions, {
|
||||||
|
id: "drag-ruler-speed-provider-settings",
|
||||||
|
title: game.i18n.localize("drag-ruler.settings.speedProviderSettings.windowTitle"),
|
||||||
|
template: "modules/drag-ruler/templates/speed_provider_settings.html",
|
||||||
|
width: 600,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
getData(options={}) {
|
||||||
|
const data = {}
|
||||||
|
data.isGM = game.user.isGM
|
||||||
|
const selectedProvider = currentSpeedProvider.id
|
||||||
|
|
||||||
|
// Insert all speed providers into the template data
|
||||||
|
data.providers = Object.values(availableSpeedProviders).map(speedProvider => {
|
||||||
|
const provider = {}
|
||||||
|
provider.id = speedProvider.id
|
||||||
|
provider.hasSettings = speedProvider instanceof SpeedProvider
|
||||||
|
if (provider.hasSettings)
|
||||||
|
provider.settings = enumerateProviderSettings(speedProvider)
|
||||||
|
let dotPosition = provider.id.indexOf(".")
|
||||||
|
if (dotPosition === -1)
|
||||||
|
dotPosition = provider.id.length
|
||||||
|
const type = provider.id.substring(0, dotPosition)
|
||||||
|
const id = provider.id.substring(dotPosition + 1)
|
||||||
|
if (type === "native") {
|
||||||
|
provider.selectTitle = game.i18n.localize("drag-ruler.settings.speedProviderSettings.speedProvider.choices.native")
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let name
|
||||||
|
if (type === "module") {
|
||||||
|
name = game.modules.get(id).data.title
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
name = game.system.data.title
|
||||||
|
}
|
||||||
|
provider.selectTitle = game.i18n.format(`drag-ruler.settings.speedProviderSettings.speedProvider.choices.${type}`, {name})
|
||||||
|
}
|
||||||
|
provider.isSelected = provider.id === selectedProvider
|
||||||
|
return provider
|
||||||
|
})
|
||||||
|
data.selectedProviderName = data.providers.find(provider => provider.isSelected).selectTitle
|
||||||
|
|
||||||
|
data.providerSelection = {
|
||||||
|
id: "speedProvider",
|
||||||
|
name: game.i18n.localize("drag-ruler.settings.speedProviderSettings.speedProvider.name"),
|
||||||
|
hint: game.i18n.localize("drag-ruler.settings.speedProviderSettings.speedProvider.hint"),
|
||||||
|
type: String,
|
||||||
|
choices: data.providers.reduce((choices, provider) => {
|
||||||
|
choices[provider.id] = provider.selectTitle
|
||||||
|
return choices
|
||||||
|
}, {}),
|
||||||
|
value: selectedProvider,
|
||||||
|
isCheckbox: false,
|
||||||
|
isSelect: true,
|
||||||
|
isRange: false,
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
async _updateObject(event, formData) {
|
||||||
|
const selectedSpeedProvider = game.user.isGM ? formData.speedProvider : game.settings.get(settingsKey, "speedProvider")
|
||||||
|
for (let [key, value] of Object.entries(formData)) {
|
||||||
|
// Check if this is color, convert the value to an integer
|
||||||
|
const splitKey = key.split(".", 3)
|
||||||
|
if (splitKey[0] !== "native")
|
||||||
|
splitKey.shift()
|
||||||
|
if (splitKey.length >= 2 && splitKey[1] == "color") {
|
||||||
|
value = parseInt(value.substring(1), 16)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't change settings for speed providers that aren't currently active
|
||||||
|
if (key !== "speedProvider" && !key.startsWith(selectedSpeedProvider))
|
||||||
|
continue
|
||||||
|
|
||||||
|
// Get the key for the current setting
|
||||||
|
let setting
|
||||||
|
if (key === "speedProvider")
|
||||||
|
setting = "speedProvider"
|
||||||
|
else
|
||||||
|
setting = `speedProviders.${key}`
|
||||||
|
|
||||||
|
// Get the old setting value
|
||||||
|
const oldValue = game.settings.get(settingsKey, setting)
|
||||||
|
|
||||||
|
// Only update the setting if it has been changed (this leaves the default in place if it hasn't been touched)
|
||||||
|
if (value !== oldValue)
|
||||||
|
game.settings.set(settingsKey, setting, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Activate the configured speed provider
|
||||||
|
updateSpeedProvider()
|
||||||
|
}
|
||||||
|
|
||||||
|
activateListeners(html) {
|
||||||
|
super.activateListeners(html)
|
||||||
|
html.find("select[name=speedProvider]").change(this.onSpeedProviderChange.bind(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
onSpeedProviderChange(event) {
|
||||||
|
// Hide all module settings
|
||||||
|
document.querySelectorAll(".drag-ruler-provider-settings").forEach(element => element.style.display = "none")
|
||||||
|
// Show the settings block for the currently selected module
|
||||||
|
document.getElementById(`drag-ruler.provider.${event.currentTarget.value}`).style.display = ""
|
||||||
|
|
||||||
|
// Recalculate window height
|
||||||
|
this.element[0].style.height = null
|
||||||
|
this.position.height = undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toDomHex(value) {
|
||||||
|
const hex = value.toString(16)
|
||||||
|
return "#" + "0".repeat(Math.max(0, 6 - hex.length)) + hex
|
||||||
|
}
|
||||||
|
|
||||||
|
function enumerateProviderSettings(provider) {
|
||||||
|
const colorSettings = []
|
||||||
|
const unreachableColor = {id: "unreachable", name: "drag-ruler.settings.speedProviderSettings.color.unreachable.name"}
|
||||||
|
|
||||||
|
// Resolve settings for the colors
|
||||||
|
for (const color of provider.colors.concat([unreachableColor])) {
|
||||||
|
// Localize the name, if avaliable. If no name is available use the id as name
|
||||||
|
const colorName = color.name ? game.i18n.localize(color.name) : color.id
|
||||||
|
let hint
|
||||||
|
if (color === unreachableColor)
|
||||||
|
hint = game.i18n.localize("drag-ruler.settings.speedProviderSettings.color.unreachable.hint")
|
||||||
|
else
|
||||||
|
hint = game.i18n.format("drag-ruler.settings.speedProviderSettings.color.hint", {colorName})
|
||||||
|
colorSettings.push({
|
||||||
|
id: `${provider.id}.color.${color.id}`,
|
||||||
|
name: game.i18n.format("drag-ruler.settings.speedProviderSettings.color.name", {colorName}),
|
||||||
|
hint: hint,
|
||||||
|
type: Number,
|
||||||
|
value: toDomHex(game.settings.get(settingsKey, `speedProviders.${provider.id}.color.${color.id}`)),
|
||||||
|
isCheckbox: false,
|
||||||
|
isSelect: false,
|
||||||
|
isRange: false,
|
||||||
|
isColor: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare regular settings
|
||||||
|
const settings = []
|
||||||
|
for (const setting of provider.settings) {
|
||||||
|
try {
|
||||||
|
if (setting.scope === "world" && !game.user.isGM)
|
||||||
|
continue
|
||||||
|
const s = duplicate(setting)
|
||||||
|
s.id = `${provider.id}.setting.${s.id}`
|
||||||
|
s.name = game.i18n.localize(s.name)
|
||||||
|
s.hint = game.i18n.localize(s.hint)
|
||||||
|
s.value = provider.getSetting(setting.id)
|
||||||
|
s.type = setting.type instanceof Function ? setting.type.name : "String"
|
||||||
|
s.isCheckbox = setting.type === Boolean
|
||||||
|
s.isSelect = s.choices !== undefined
|
||||||
|
s.isRange = (setting.type === Number) && s.range
|
||||||
|
s.isColor = false
|
||||||
|
settings.push(s)
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.warn(`Drag Ruler | The following error occured while rendering setting "${setting.id}" of module/system "${this.id}. It won't be displayed.`)
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return settings.concat(colorSettings)
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,144 @@
|
|||||||
|
import {settingsKey} from "./settings.js"
|
||||||
|
import {getDefaultDashMultiplier, getDefaultSpeedAttribute} from "./systems.js"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for all speed providers.
|
||||||
|
* If you want to offer a speed provider in your system/module you must derive this class.
|
||||||
|
* Each speed provider must at least implement
|
||||||
|
*/
|
||||||
|
export class SpeedProvider {
|
||||||
|
/**
|
||||||
|
* Returns an array of colors used by this speed provider. Each color corresponds to one speed that a token may have.
|
||||||
|
* Each color must be an object with the following properties:
|
||||||
|
* - id: A value that identfies the color. Must be unique for each color returned.
|
||||||
|
* - default: The color that is used to highlight that speed by default.
|
||||||
|
* - name: A user readable name for the speed represented by the color. This name is used in the color configuration dialog. Drag Ruler will attempt to localize this string using `game.i18n`
|
||||||
|
*
|
||||||
|
* Of these properties, id and defaultColor are required. name is optional, but it's recommended to set it
|
||||||
|
*
|
||||||
|
* Implementing this method is required for all speed providers
|
||||||
|
*/
|
||||||
|
get colors() {
|
||||||
|
throw new Error("A SpeedProvider must implement the colors function")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of speeds that the token passed in the arguments this token can reach.
|
||||||
|
* Each range is an object that with the following properties:
|
||||||
|
* - range: A number indicating the distance that the token can travel with this speed
|
||||||
|
* - color: The id (as defined in the `colors` getter) of the color that should be used to represent this range
|
||||||
|
*
|
||||||
|
* Implementing this method is required for all speed providers
|
||||||
|
*/
|
||||||
|
getRanges(token) {
|
||||||
|
throw new Error("A SpeedProvider must implement the getRanges function")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of configuration options for this module. The settings will be shown in the Speed Provider Settings of Drag Ruler.
|
||||||
|
* Each configuration option is an object that has the same attributes as a native foundry setting passed to `game.settings.register`,
|
||||||
|
* except for these exceptions:
|
||||||
|
* - id: A string that identifies the setting. Must be unique for each setting returned. This id will be used to fetch the setting.
|
||||||
|
* - config: This property is not supported by Drag Ruler module settings. Use foundries native settings instead if you need settings that don't show up in the configuration dialog.
|
||||||
|
*
|
||||||
|
* Implementing this method is optional and only needs to be done if you want to provide custom provider settings
|
||||||
|
*/
|
||||||
|
get settings() {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the default color for ranges that a token cannot reach.
|
||||||
|
*
|
||||||
|
* Implementing this method is optional and only needs to be done if you want to provide a custom default for that color.
|
||||||
|
*/
|
||||||
|
get defaultUnreachableColor() {
|
||||||
|
return 0xFF0000
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a boolean indicating whether this token will use a Ruler or not.
|
||||||
|
* If this is returns `false` for a token Drag Ruler will be disabled for that token. Dragging a token for which this function
|
||||||
|
* returns false will behave as if Drag Ruler wasn't installed.
|
||||||
|
* If usesRuler returns `false` it's guranteed that the `getRanges` function won't be called for that token.
|
||||||
|
*
|
||||||
|
* Implementing this method is optional and only needs to be done if you want to disable Drag Ruler for some tokens.
|
||||||
|
*/
|
||||||
|
usesRuler(token) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value that is currently set for the setting registered with the provided settingId.
|
||||||
|
*
|
||||||
|
* This function shouldn't be overridden by speed provider implementations. It can be called to fetch speed provider specific settings.
|
||||||
|
*/
|
||||||
|
getSetting(settingId) {
|
||||||
|
try {
|
||||||
|
return game.settings.get(settingsKey, `speedProviders.${this.id}.setting.${settingId}`)
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
if (this.settings.some(setting => setting.id === settingId)) {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
throw new Error(`Drag Ruler | "${settingId}" is not a registered setting for "${this.id}". If you're the module/system developer, please add it to the return values of your Speed Providers "get settings()" function.`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new instance of he speed provider
|
||||||
|
*
|
||||||
|
* This function should neither be called or overridden by speed provider implementations
|
||||||
|
*/
|
||||||
|
constructor(id) {
|
||||||
|
this.id = id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class GenericSpeedProvider extends SpeedProvider {
|
||||||
|
get colors() {
|
||||||
|
return [
|
||||||
|
{id: "walk", default: 0x00FF00, name: "drag-ruler.genericSpeedProvider.speeds.walk"},
|
||||||
|
{id: "dash", default: 0xFFFF00, name: "drag-ruler.genericSpeedProvider.speeds.dash"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
getRanges(token) {
|
||||||
|
const speedAttribute = this.getSetting("speedAttribute")
|
||||||
|
if (!speedAttribute)
|
||||||
|
return []
|
||||||
|
const tokenSpeed = 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 []
|
||||||
|
}
|
||||||
|
const dashMultiplier = this.getSetting("dashMultiplier")
|
||||||
|
if (!dashMultiplier)
|
||||||
|
return [{range: tokenSpeed, color: "walk"}]
|
||||||
|
return [{range: tokenSpeed, color: "walk"}, {range: tokenSpeed * dashMultiplier, color: "dash"}]
|
||||||
|
}
|
||||||
|
|
||||||
|
get settings() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: "speedAttribute",
|
||||||
|
name: "drag-ruler.genericSpeedProvider.settings.speedAttribute.name",
|
||||||
|
hint: "drag-ruler.genericSpeedProvider.settings.speedAttribute.hint",
|
||||||
|
scope: "world",
|
||||||
|
config: true,
|
||||||
|
type: String,
|
||||||
|
default: getDefaultSpeedAttribute(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "dashMultiplier",
|
||||||
|
name: "drag-ruler.genericSpeedProvider.settings.dashMultiplier.name",
|
||||||
|
hint: "drag-ruler.genericSpeedProvider.settings.dashMultiplier.hint",
|
||||||
|
scope: "world",
|
||||||
|
config: true,
|
||||||
|
type: Number,
|
||||||
|
default: getDefaultDashMultiplier(),
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,8 @@ export function getDefaultSpeedAttribute() {
|
|||||||
switch (game.system.id) {
|
switch (game.system.id) {
|
||||||
case "dnd5e":
|
case "dnd5e":
|
||||||
return "actor.data.data.attributes.movement.walk"
|
return "actor.data.data.attributes.movement.walk"
|
||||||
|
case "lancer":
|
||||||
|
return "actor.data.data.mech.speed"
|
||||||
case "pf1":
|
case "pf1":
|
||||||
return "actor.data.data.attributes.speed.land.total"
|
return "actor.data.data.attributes.speed.land.total"
|
||||||
}
|
}
|
||||||
@@ -12,6 +14,7 @@ export function getDefaultSpeedAttribute() {
|
|||||||
export function getDefaultDashMultiplier() {
|
export function getDefaultDashMultiplier() {
|
||||||
switch (game.system.id) {
|
switch (game.system.id) {
|
||||||
case "dnd5e":
|
case "dnd5e":
|
||||||
|
case "lancer":
|
||||||
case "pf1":
|
case "pf1":
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
{{! This partial is based on the foundry settings partial}}
|
||||||
|
{{#*inline "settingPartial"}}
|
||||||
|
<div class="form-group">
|
||||||
|
<label>{{this.name}}</label>
|
||||||
|
<div class="form-fields">
|
||||||
|
{{#if this.isCheckbox}}
|
||||||
|
<input type="checkbox" name="{{this.id}}" data-dtype="Boolean" {{checked this.value}} />
|
||||||
|
{{else if this.isSelect}}
|
||||||
|
<select name="{{this.id}}">
|
||||||
|
{{#select this.value}}
|
||||||
|
{{#each this.choices as |name k|}}
|
||||||
|
<option value="{{k}}">{{localize name}}</option>
|
||||||
|
{{/each}}
|
||||||
|
{{/select}}
|
||||||
|
</select>
|
||||||
|
{{else if this.isRange}}
|
||||||
|
<input type="range" name="{{this.id}}" data-dtype="Number" value="{{ this.value }}"
|
||||||
|
min="{{ this.range.min }}" max="{{ this.range.max }}" step="{{ this.range.step }}" />
|
||||||
|
<span class="range-value">{{this.value}}</span>
|
||||||
|
{{else if this.isColor}}
|
||||||
|
<input type="color" name="{{this.id}}" value="{{this.value}}" data-dtype="{{this.type}}" />
|
||||||
|
{{else}}
|
||||||
|
<input type="text" name="{{this.id}}" value="{{this.value}}" data-dtype="{{this.type}}" />
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
<p class="notes">{{this.hint}}</p>
|
||||||
|
</div>
|
||||||
|
{{/inline}}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<form class="flexcol" autocomplete="off">
|
||||||
|
<section class="content">
|
||||||
|
<div class="settings-list">
|
||||||
|
<h2 class="module-header">{{localize "drag-ruler.settings.speedProviderSettings.headers.speedProvider"}}</h2>
|
||||||
|
{{#if this.isGM}}
|
||||||
|
{{#with this.providerSelection}}
|
||||||
|
{{> settingPartial}}
|
||||||
|
{{/with}}
|
||||||
|
{{else}}
|
||||||
|
<div class="form-group"><label>{{localize "drag-ruler.settings.speedProviderSettings.activeProvider.name"}}</label><div class="form-fields" style="justify-content: flex-start;"><b>{{this.selectedProviderName}}</b></div></div>
|
||||||
|
<p class="notes">{{localize "drag-ruler.settings.speedProviderSettings.activeProvider.hint"}}</p>
|
||||||
|
{{/if}}
|
||||||
|
<h2 class="module-header">{{localize "drag-ruler.settings.speedProviderSettings.headers.speedProviderSettings"}}</h2>
|
||||||
|
{{#each this.providers}}
|
||||||
|
<div class="drag-ruler-provider-settings" id="drag-ruler.provider.{{this.id}}" {{#unless this.isSelected}}style="display:none"{{/unless}}>
|
||||||
|
{{#if this.hasSettings}}
|
||||||
|
{{#each settings}}
|
||||||
|
{{> settingPartial}}
|
||||||
|
{{/each}}
|
||||||
|
{{else}}
|
||||||
|
<p>{{localize "drag-ruler.settings.speedProviderSettings.noSettings"}}</p>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<footer class="sheet-footer flexrow">
|
||||||
|
<button type="submit" name="submit">
|
||||||
|
<i class="far fa-save"></i> {{localize 'SETTINGS.Save'}}
|
||||||
|
</button>
|
||||||
|
</footer>
|
||||||
|
</form>
|
||||||
Reference in New Issue
Block a user