Compare commits

..

19 Commits

Author SHA1 Message Date
Manuel Vögele 404e978377 Release v1.3.2 2021-03-02 08:30:41 +01:00
Manuel Vögele 4a56cdb0a4 Merge branch 'master' into develop 2021-03-02 08:30:03 +01:00
Manuel Vögele 48e6901b59 Add 15e9104 to the changelog 2021-03-02 08:28:57 +01:00
Brother Sharp 15e910424f update japanese localization (#32) 2021-03-02 08:24:04 +01:00
Manuel Vögele baba6e2986 Fix typo in english translation 2021-02-26 12:29:56 +01:00
Manuel Vögele 6f0a04c20f Add Tormenta20 to list of game systems with integration 2021-02-23 16:47:10 +01:00
Manuel Vögele 8522c9a811 Release v1.3.1 2021-02-22 05:34:08 +01:00
Manuel Vögele d5fd5e0d04 Dash multiplier 0 now works again 2021-02-22 05:29:00 +01:00
Manuel Vögele aabe9c75e8 Release v1.3.0 2021-02-21 12:48:26 +01:00
Manuel Vögele 0fb8aecfdd Refer to additional API capabilities in the readme 2021-02-21 12:41:12 +01:00
Manuel Vögele bb5fe94439 Allow speed providers to disable Drag Ruler on a per-token basis (resolves #24) 2021-02-21 12:13:14 +01:00
Manuel Vögele 6f6655009d Update readme to match the new api 2021-02-21 11:45:13 +01:00
Manuel Vögele 8110a1b78b Add setting migration 2021-02-18 00:38:55 +01:00
Manuel Vögele b84cdfd7c1 Fix a bug that prevented the Speed Provider Settings Dialog from being shown if an unavailable Speed Provider is configured 2021-02-18 00:13:15 +01:00
Manuel Vögele 75d59171d6 New API, allwing color configuration (resolves #7) and Speed Provider Settings 2021-02-17 16:25:17 +01:00
Manuel Vögele 6ea03579b9 Add Cipher System to the list of Systems with Drag Ruler integration 2021-02-16 18:03:47 +01:00
Manuel Vögele fd5b439ec2 Use foundries builin function to fetch selected tokens 2021-02-16 16:04:47 +01:00
Manuel Vögele 52118a6142 Mention b29a687 (defaults for lancer system) in changelog 2021-02-11 18:38:34 +01:00
Grygon b29a687fff Add Lancer to systems.js (#21) 2021-02-11 18:36:46 +01:00
12 changed files with 721 additions and 164 deletions
+27
View File
@@ -1,3 +1,30 @@
## 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)
+88 -45
View File
@@ -26,67 +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.
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 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
*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.
### 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.
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.
### 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
Hooks.once("dragRuler.ready", () => {
dragRuler.registerModule("id-of-your-module", mySpeedProvider)
})
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})
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"}
]
}
```
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).
+39 -12
View File
@@ -1,10 +1,7 @@
{
"drag-ruler": {
"genericSpeedProvider": {
"settings": {
"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."
},
"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."
@@ -12,19 +9,49 @@
"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": {
"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."
},
"speedProviderSettings": {
"name": "Speed Provider Settings",
"hint": "The Speed Provider Settings contain all the game system specific settings.",
"button": "Speed Provider Settings",
"windowTitle": "Speed Provider Settings",
"headers": {
"speedProvider": "Speed Provider",
"speedProviderSettings": "Speed Provider specific settings"
},
"activeProvider": {
"name": "Currently active Speed Provider",
"hint": "The settings show below depend on the active speed provider. If the GM selects a different speed provider the available settings may change."
},
"noSettings": "This speed provider doesn't offer any configuration options.",
"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. Using a game system or module may provide more flexible coloring than sticking to the options provided by Drag Ruler.",
"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",
"native": "Drag Ruler",
"system": "System"
"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": {
"name": "Swap spacebar and right click",
+43 -16
View File
@@ -1,30 +1,57 @@
{
"drag-ruler": {
"genericSpeedProvider": {
"settings": {
"dashMultiplier": {
"name": "早足の倍率",
"hint": "設定すると、移動経路の測定中に第二の色分けが追加されます。0にすると第二速度が無効になります。"
},
"speedAttribute": {
"name": "移動速度の属性値",
"hint": "コマの移動速度が定義された属性値を指定します。移動範囲表示の色分けに使用されます。"
}
},
"speeds": {
"walk": "歩き",
"dash": "早足"
}
},
"settings": {
"alwaysShowSpeedForPCs": {
"name": "プレイヤーキャラクターの移動速度を全員に公開する",
"hint": "有効にすると、アクターの観察者(Observer)権限を持たない人にも,移動範囲の色分けが表示されるようになります。"
},
"dashMultiplier": {
"name": "早足の倍率",
"hint": "有効にすると、移動範囲に二つ目の色分けが追加されます。0にすると、この機能は無効になります。"
"speedProviderSettings": {
"name": "移動速度の提供元",
"hint": "ゲームシステムに固有のすべての設定をここで行います。",
"button": "移動速度の提供元設定",
"windowTitle": "移動速度の提供元設定",
"headers": {
"speedProvider": "移動速度の提供元",
"speedProviderSettings": "提供元に固有の設定"
},
"speedAttribute": {
"name": "移動速度の属性値",
"hint": "コマの移動速度が定義された属性値を指定します。移動範囲表示の色分けに使用されます。"
"activeProvider": {
"name": "現在有効な提供元",
"hint": "以下の設定は、現在有効な移動速度の提供元によって変化します。GMが別の提供元を選択すると、利用可能な項目が変更となる場合があります。"
},
"speedProvider": {
"name": "移動速度設定の提供元",
"hint": "色分けに使用する速度情報の提供元を選択します。ゲームシステムやモジュールを選択すると、Drag Ruler自体が提供するものより柔軟な色分けが行える可能性があります。",
"choices": {
"module": "モジュール",
"native": "Drag Ruler",
"system": "ゲームシステム"
"noSettings": "この移動速度提供元には、設定オプションがありません。",
"color": {
"name": "{colorName}の色",
"hint": "{colorName}の範囲に使用するセルの色を指定します。",
"unreachable": {
"name": "到達不能域",
"hint": "ドラッグしたコマが到達できない範囲の色を指定します。"
}
},
"staticFirstColor": {
"name": "一つ目の色を固定する",
"hint": "一つ目の移動範囲の色に、プレイヤーの色でなく固定された色を使用します。"
"speedProvider": {
"name": "移動速度の提供元",
"hint": "色分けに使用する速度情報の提供元を選択します。Drag Rulerは基本機能を備えた汎用の提供元を備えており、正しく設定すればすべてのゲームシステムで動作するはずです。しかしゲームシステムやモジュールを介することで、より多くの提供元を利用することもできます。こうした提供元を利用すると、ゲームルールとの統合がさらに向上するかもしれません。以下のオプションは、ここで選択した提供元によって変化します。",
"choices": {
"module": "モジュール {name}",
"native": "汎用",
"system": "システム {name}"
}
}
},
"swapSpacebarRightClick": {
"name": "スペースキーと右クリックを入れ替える",
+5 -2
View File
@@ -2,7 +2,7 @@
"name": "drag-ruler",
"title": "Drag Ruler",
"description": "When dragging a token displays a ruler showing how far you've moved that token.",
"version": "1.2.2",
"version": "1.3.2",
"minimumCoreVersion" : "0.7.9",
"compatibleCoreVersion" : "0.7.9",
"authors": [
@@ -15,6 +15,9 @@
"esmodules": [
"src/main.js"
],
"templates": [
"speed_provider_settings.html"
],
"languages": [
{
"lang": "en",
@@ -28,7 +31,7 @@
}
],
"url": "https://github.com/manuelVo/foundryvtt-drag-ruler",
"download": "https://github.com/manuelVo/foundryvtt-drag-ruler/archive/v1.2.2.zip",
"download": "https://github.com/manuelVo/foundryvtt-drag-ruler/archive/v1.3.2.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",
+69 -16
View File
@@ -1,37 +1,61 @@
import {GenericSpeedProvider, SpeedProvider} from "./speed_provider.js"
import {settingsKey} from "./settings.js"
export const availableSpeedProviders = {}
export let currentSpeedProvider = undefined
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
providerSetting.config = true
const moduleName = module.data.title
const typeTitle = game.i18n.localize(`drag-ruler.settings.speedProvider.choices.${type}`)
providerSetting.choices[`${type}.${module.id}`] = `${typeTitle} ${moduleName}`
availableSpeedProviders[`${type}.${module.id}`] = speedProvider
providerSetting.default = getDefaultSpeedProvider()
function setupProvider(speedProvider) {
if (speedProvider instanceof SpeedProvider) {
const unreachableColor = {id: "unreachable", default: speedProvider.defaultUnreachableColor, name: "drag-ruler.settings.speedProviderSettings.color.unreachable.name"}
for (const color of speedProvider.colors.concat([unreachableColor])) {
game.settings.register(settingsKey, `speedProviders.${speedProvider.id}.color.${color.id}`, {
config: false,
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()
}
function getDefaultSpeedProvider() {
const providerSetting = game.settings.settings.get("drag-ruler.speedProvider")
const settingKeys = Object.keys(providerSetting.choices)
export function getDefaultSpeedProvider() {
const providerIds = Object.keys(availableSpeedProviders)
// 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)
return gameSystem
// 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
// 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)
return module
// 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() {
@@ -40,8 +64,37 @@ export function updateSpeedProvider() {
currentSpeedProvider = availableSpeedProviders[configuredProvider] ?? availableSpeedProviders[game.settings.settings.get("drag-ruler.speedProvider").default]
}
export function setCurrentSpeedProvider(newSpeedProvider) {
currentSpeedProvider = newSpeedProvider
export function initApi() {
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) {
+12 -26
View File
@@ -1,30 +1,30 @@
"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 {measure, moveTokens, onMouseMove} from "./foundry_imports.js"
import {performMigrations} from "./migration.js"
import {registerSettings, settingsKey} from "./settings.js"
import {SpeedProvider} from "./speed_provider.js"
Hooks.once("init", () => {
registerSettings()
initApi()
hookTokenDragHandlers()
hookRulerFunctions()
hookKeyboardManagerFunctions()
patchRulerHighlightMeasurement()
availableSpeedProviders["native"] = nativeSpeedProvider
setCurrentSpeedProvider(nativeSpeedProvider)
window.dragRuler = {
getColorForDistance,
registerModule,
registerSystem
registerSystem,
}
})
Hooks.once("ready", () => {
Hooks.callAll("dragRuler.ready")
performMigrations()
Hooks.callAll("dragRuler.ready", SpeedProvider)
})
Hooks.on("canvasReady", () => {
@@ -142,6 +142,8 @@ function onKeyShift(up) {
}
function onTokenLeftDragStart(event) {
if (!currentSpeedProvider.usesRuler(this))
return
const ruler = canvas.controls.ruler
ruler.draggedToken = this
let tokenCenter
@@ -165,7 +167,7 @@ function onTokenDragLeftDrop(event) {
const ruler = canvas.controls.ruler
if (!ruler.isDragRuler)
return false
const selectedTokens = canvas.tokens.placeables.filter(token => token._controlled)
const selectedTokens = canvas.tokens.controlled
moveTokens.call(ruler, ruler.draggedToken, selectedTokens)
ruler.draggedToken = null
return true
@@ -241,21 +243,6 @@ function strInsertAfter(haystack, needle, strToInsert) {
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) {
if (!this.isDragRuler)
return this.color
@@ -266,15 +253,14 @@ export function getColorForDistance(startDistance, subDistance=0) {
return this.color
}
const distance = startDistance + subDistance
const firstColor = game.settings.get(settingsKey, "staticFirstColor") ? 0x00FF00 : this.color
const ranges = currentSpeedProvider(this.draggedToken, firstColor)
const ranges = getRangesFromSpeedProvider(this.draggedToken)
if (ranges.length === 0)
return this.color
const currentRange = ranges.reduce((minRange, currentRange) => {
if (distance <= currentRange.range && currentRange.range < minRange.range)
return currentRange
return minRange
}, {range: Infinity, color: 0xFF0000})
}, {range: Infinity, color: getUnreachableColorFromSpeedProvider()})
return currentRange.color
}
+27
View File
@@ -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
}
}
+188 -34
View File
@@ -1,9 +1,16 @@
import {updateSpeedProvider} from "./api.js";
import {getDefaultDashMultiplier, getDefaultSpeedAttribute} from "./systems.js"
import {availableSpeedProviders, currentSpeedProvider, getDefaultSpeedProvider, updateSpeedProvider} from "./api.js";
import {SpeedProvider} from "./speed_provider.js"
export const settingsKey = "drag-ruler";
export function registerSettings() {
game.settings.register(settingsKey, "dataVersion", {
scope: "world",
config: false,
type: String,
default: "fresh install"
})
game.settings.register(settingsKey, "swapSpacebarRightClick", {
name: "drag-ruler.settings.swapSpacebarRightClick.name",
hint: "drag-ruler.settings.swapSpacebarRightClick.hint",
@@ -24,42 +31,189 @@ export function registerSettings() {
// This setting will be modified by the api if modules register to it
game.settings.register(settingsKey, "speedProvider", {
name: "drag-ruler.settings.speedProvider.name",
hint: "drag-ruler.settings.speedProvider.hint",
scope: "world",
config: false,
type: Object,
choices: {
"native": game.i18n.localize("drag-ruler.settings.speedProvider.choices.native")
},
default: "native",
type: String,
default: getDefaultSpeedProvider(),
onChange: updateSpeedProvider,
})
game.settings.register(settingsKey, "speedAttribute", {
name: "drag-ruler.settings.speedAttribute.name",
hint: "drag-ruler.settings.speedAttribute.hint",
scope: "world",
config: true,
type: String,
default: getDefaultSpeedAttribute(),
})
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,
game.settings.registerMenu(settingsKey, "speedProviderSettings", {
name: "drag-ruler.settings.speedProviderSettings.name",
hint: "drag-ruler.settings.speedProviderSettings.hint",
label: "drag-ruler.settings.speedProviderSettings.button",
icon: "fas fa-tachometer-alt",
type: SpeedProviderSettings,
restricted: 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) {
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(formData.speedProvider))
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)
}
+144
View File
@@ -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
View File
@@ -3,6 +3,8 @@ export function getDefaultSpeedAttribute() {
switch (game.system.id) {
case "dnd5e":
return "actor.data.data.attributes.movement.walk"
case "lancer":
return "actor.data.data.mech.speed"
case "pf1":
return "actor.data.data.attributes.speed.land.total"
}
@@ -12,6 +14,7 @@ export function getDefaultSpeedAttribute() {
export function getDefaultDashMultiplier() {
switch (game.system.id) {
case "dnd5e":
case "lancer":
case "pf1":
return 2
}
+63
View File
@@ -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>