Initial commit
This commit is contained in:
@@ -0,0 +1,7 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
indent_style = tab
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"drag-ruler": {
|
||||||
|
"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."
|
||||||
|
},
|
||||||
|
"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.",
|
||||||
|
"choices": {
|
||||||
|
"module": "Module",
|
||||||
|
"native": "Drag Ruler",
|
||||||
|
"system": "System"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"name": "drag-ruler",
|
||||||
|
"title": "Drag Ruler",
|
||||||
|
"description": "When dragging a token displays a ruler showing how far you've moved that token.",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"minimumCoreVersion" : "0.7.9",
|
||||||
|
"compatibleCoreVersion" : "0.7.9",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Manuel Vögele",
|
||||||
|
"email": "develop@manuel-voegele.de",
|
||||||
|
"discord": "Stäbchenfisch#5107"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"esmodules": [
|
||||||
|
"src/main.js"
|
||||||
|
],
|
||||||
|
"languages": [
|
||||||
|
{
|
||||||
|
"lang": "en",
|
||||||
|
"name": "English",
|
||||||
|
"path": "lang/en.json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"url": "https://github.com/manuelVo/foundryvtt-drag-ruler",
|
||||||
|
"download": "https://github.com/manuelVo/foundryvtt-drag-ruler/archive/master.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",
|
||||||
|
"bugs": "https://github.com/manuelVo/foundryvtt-drag-ruler/issues"
|
||||||
|
}
|
||||||
+85
@@ -0,0 +1,85 @@
|
|||||||
|
export const availableSpeedProviders = {}
|
||||||
|
export let currentSpeedProvider = undefined
|
||||||
|
|
||||||
|
function register(module, type, speedProvider) {
|
||||||
|
const providerSetting = game.settings.settings.get("drag-ruler.speedProvider")
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
|
||||||
|
updateSpeedProvider()
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDefaultSpeedProvider() {
|
||||||
|
const providerSetting = game.settings.settings.get("drag-ruler.speedProvider")
|
||||||
|
const settingKeys = Object.keys(providerSetting.choices)
|
||||||
|
// Game systems take the highest precedence for the being the default
|
||||||
|
const gameSystem = settingKeys.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."))
|
||||||
|
if (module)
|
||||||
|
return module
|
||||||
|
|
||||||
|
// If neither a game system or a module is found fall back to the native implementation
|
||||||
|
return settingKeys[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateSpeedProvider() {
|
||||||
|
// If the configured provider is registered use that one. If not use the default provider
|
||||||
|
const configuredProvider = game.settings.get("drag-ruler", "speedProvider")
|
||||||
|
currentSpeedProvider = availableSpeedProviders[configuredProvider] ?? availableSpeedProviders[game.settings.settings.get("drag-ruler.speedProvider").default]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setCurrentSpeedProvider(newSpeedProvider) {
|
||||||
|
currentSpeedProvider = newSpeedProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
export function registerModule(moduleId, speedProvider) {
|
||||||
|
// Check if a module with the given id exists and is currently enabled
|
||||||
|
const module = game.modules.get(moduleId)
|
||||||
|
// If it doesn't the calling module did something wrong. Log a warning and ignore this module
|
||||||
|
if (!module) {
|
||||||
|
console.warn(
|
||||||
|
`Drag Ruler | A module tried to register with the id "${moduleId}". However no active module with this id was found.` +
|
||||||
|
"This api registration call was ignored. " +
|
||||||
|
"If you are the author of that module please check that the id passed to `registerModule` matches the id in your manifest exactly." +
|
||||||
|
"If this call was made form a game system instead of a module please use `registerSystem` instead.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Using Drag Ruler's id is not allowed
|
||||||
|
if (moduleId === "drag-ruler") {
|
||||||
|
console.warn(
|
||||||
|
`Drag Ruler | A module tried to register with the id "${moduleId}", which is not allowed. This api registration call was ignored. ` +
|
||||||
|
"If you're the author of the module please use the id of your own module as it's specified in your manifest to register to this api. " +
|
||||||
|
"If this call was made form a game system instead of a module please use `registerSystem` instead."
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
register(module, "module", speedProvider)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function registerSystem(systemId, speedProvider) {
|
||||||
|
const system = game.system
|
||||||
|
// If the current system id doesn't match the provided id something went wrong. Log a warning and ignore this module
|
||||||
|
if (system.id != systemId) {
|
||||||
|
console.warn(
|
||||||
|
`Drag Ruler | A system tried to register with the id "${systemId}". However the active system has a different id.` +
|
||||||
|
"This api registration call was ignored. " +
|
||||||
|
"If you are the author of that system please check that the id passed to `registerSystem` matches the id in your manifest exactly." +
|
||||||
|
"If this call was made form a module instead of a game system please use `registerModule` instead.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
register(system, "system", speedProvider)
|
||||||
|
}
|
||||||
+178
@@ -0,0 +1,178 @@
|
|||||||
|
"use strict"
|
||||||
|
|
||||||
|
import {availableSpeedProviders, currentSpeedProvider, registerModule, registerSystem, setCurrentSpeedProvider} from "./api.js"
|
||||||
|
import {registerSettings, settingsKey} from "./settings.js"
|
||||||
|
|
||||||
|
Hooks.once("init", () => {
|
||||||
|
registerSettings()
|
||||||
|
hookTokenDragHandlers()
|
||||||
|
hookRulerHandlers()
|
||||||
|
patchRulerMeasure()
|
||||||
|
patchRulerHighlightMeasurement()
|
||||||
|
|
||||||
|
availableSpeedProviders["native"] = nativeSpeedProvider
|
||||||
|
setCurrentSpeedProvider(nativeSpeedProvider)
|
||||||
|
|
||||||
|
window.dragRuler = {
|
||||||
|
getColorForDistance,
|
||||||
|
registerModule,
|
||||||
|
registerSystem
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
Hooks.once("ready", () => {
|
||||||
|
Hooks.callAll("dragRuler.ready")
|
||||||
|
})
|
||||||
|
|
||||||
|
Hooks.on("canvasReady", () => {
|
||||||
|
canvas.controls.ruler.draggedToken = null
|
||||||
|
Object.defineProperty(canvas.controls.ruler, "isDragRuler", {
|
||||||
|
get: function isDragRuler() {
|
||||||
|
return Boolean(this.draggedToken) // If draggedToken is set this is a drag ruler
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
function hookTokenDragHandlers() {
|
||||||
|
const originalDragLeftStartHandler = Token.prototype._onDragLeftStart
|
||||||
|
Token.prototype._onDragLeftStart = function(event) {
|
||||||
|
originalDragLeftStartHandler.call(this, event)
|
||||||
|
onTokenLeftDragStart.call(this, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
const originalDragLeftMoveHandler = Token.prototype._onDragLeftMove
|
||||||
|
Token.prototype._onDragLeftMove = function (event) {
|
||||||
|
originalDragLeftMoveHandler.call(this, event)
|
||||||
|
onTokenLeftDragMove.call(this, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
const originalDragLeftDropHandler = Token.prototype._onDragLeftDrop
|
||||||
|
Token.prototype._onDragLeftDrop = function (event) {
|
||||||
|
const eventHandled = onTokenDragLeftDrop.call(this, event)
|
||||||
|
if (!eventHandled)
|
||||||
|
originalDragLeftDropHandler.call(this, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
const originalDragLeftCancelHandler = Token.prototype._onDragLeftCancel
|
||||||
|
Token.prototype._onDragLeftCancel = function (event) {
|
||||||
|
const eventHandled = onTokenDragLeftCancel.call(this, event)
|
||||||
|
if (!eventHandled)
|
||||||
|
originalDragLeftCancelHandler.call(this, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hookRulerHandlers() {
|
||||||
|
const originalMoveTokenHandler = Ruler.prototype.moveToken
|
||||||
|
Ruler.prototype.moveToken = function (event) {
|
||||||
|
const eventHandled = onRulerMoveToken.call(this, event)
|
||||||
|
if (!eventHandled)
|
||||||
|
return originalMoveTokenHandler.call(this, event)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onTokenLeftDragStart(event) {
|
||||||
|
canvas.controls.ruler._onDragStart(event)
|
||||||
|
canvas.controls.ruler.draggedToken = this
|
||||||
|
}
|
||||||
|
|
||||||
|
function onTokenLeftDragMove(event) {
|
||||||
|
if (canvas.controls.ruler.isDragRuler)
|
||||||
|
canvas.controls.ruler._onMouseMove(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onTokenDragLeftDrop(event) {
|
||||||
|
if (!canvas.controls.ruler.isDragRuler)
|
||||||
|
return false
|
||||||
|
canvas.controls.ruler.draggedToken = null
|
||||||
|
canvas.controls.ruler.moveToken(event)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
function onTokenDragLeftCancel(event) {
|
||||||
|
if (!canvas.controls.ruler.isDragRuler)
|
||||||
|
return false
|
||||||
|
if (canvas.controls.ruler._state === Ruler.STATES.MEASURING) {
|
||||||
|
if (canvas.controls.ruler.waypoints.length > 1) {
|
||||||
|
canvas.controls.ruler._removeWaypoint(canvas.app.renderer.plugins.interaction.mouse.getLocalPosition(canvas.tokens), {snap: !event.shiftKey})
|
||||||
|
game.user.broadcastActivity({ruler: canvas.controls.ruler})
|
||||||
|
event.preventDefault()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
canvas.controls.ruler._endMeasurement()
|
||||||
|
canvas.controls.ruler.draggedToken = null
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
function onRulerMoveToken(event) {
|
||||||
|
if (!this.isDragRuler)
|
||||||
|
return false
|
||||||
|
this._addWaypoint(this.destination)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
function strInsertAfter(haystack, needle, strToInsert) {
|
||||||
|
const pos = haystack.indexOf(needle) + needle.length
|
||||||
|
return haystack.slice(0, pos) + strToInsert + haystack.slice(pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
// These patches were written with foundry-0.7.9.js as reference
|
||||||
|
function patchRulerMeasure() {
|
||||||
|
let code = Ruler.prototype.measure.toString()
|
||||||
|
// Remove function signature and closing curly bracket (those are on the first and last line)
|
||||||
|
code = code.slice(code.indexOf("\n"), code.lastIndexOf("\n"))
|
||||||
|
code = strInsertAfter(code, "for ( let [i, d] of distances.entries() ) {\n", "segments[i].startDistance = totalDistance\n")
|
||||||
|
code = strInsertAfter(code, "this._highlightMeasurement(ray", ", s.startDistance")
|
||||||
|
Ruler.prototype.measure = new Function("destination", "{gridSpaces=true}={}", code)
|
||||||
|
}
|
||||||
|
|
||||||
|
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}]
|
||||||
|
}
|
||||||
|
|
||||||
|
function getColorForDistance(startDistance, subDistance) {
|
||||||
|
if (!this.isDragRuler)
|
||||||
|
return this.color
|
||||||
|
const distance = startDistance + subDistance
|
||||||
|
const ranges = currentSpeedProvider(this.draggedToken, this.color)
|
||||||
|
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})
|
||||||
|
return currentRange.color
|
||||||
|
}
|
||||||
|
|
||||||
|
// These patches were written with foundry-0.7.9.js as reference
|
||||||
|
function patchRulerHighlightMeasurement() {
|
||||||
|
let code = Ruler.prototype._highlightMeasurement.toString()
|
||||||
|
// 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,38 @@
|
|||||||
|
import {updateSpeedProvider} from "./api.js";
|
||||||
|
import {getDefaultDashMultiplier, getDefaultSpeedAttribute} from "./systems.js"
|
||||||
|
|
||||||
|
export const settingsKey = "drag-ruler";
|
||||||
|
|
||||||
|
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",
|
||||||
|
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(),
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
|
||||||
|
export function getDefaultSpeedAttribute() {
|
||||||
|
switch (game.system.id) {
|
||||||
|
case "dnd5e":
|
||||||
|
return "actor.data.data.attributes.movement.walk"
|
||||||
|
case "pf1":
|
||||||
|
return "actor.data.data.attributes.speed.land.total"
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDefaultDashMultiplier() {
|
||||||
|
switch (game.system.id) {
|
||||||
|
case "dnd5e":
|
||||||
|
case "pf1":
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user