Compare commits

..

15 Commits

Author SHA1 Message Date
Manuel Vögele ebde56513d Release v1.1.1 2021-02-05 11:50:35 +01:00
Manuel Vögele 74c7d74c5a On gridless maps, always start measuring from the tokens center 2021-02-05 11:46:16 +01:00
Manuel Vögele f1542b7789 Start measuring immediately when the token is being dragged 2021-02-05 11:20:41 +01:00
Manuel Vögele 46edfa8ae6 Fix a bug where tokens wouldn't be moved to the corect end position on gridless maps 2021-02-05 00:54:09 +01:00
Manuel Vögele fe89a871c9 Release v1.1.0 2021-02-03 09:12:21 +01:00
Manuel Vögele 62c965b499 Clarify changelog 2021-02-03 08:27:58 +01:00
Manuel Vögele 4149f0e351 Fix #5: Handle installations where foundry.js has CRLF line endings gracefully 2021-02-03 00:52:59 +01:00
Manuel Vögele 5c43651925 Add option to show colors of PCs to anyone, regardless of permissions 2021-02-02 14:37:07 +01:00
Manuel Vögele 59f2e67717 Add ko-fi button to readme 2021-02-02 14:23:17 +01:00
Manuel Vögele 33cb9a2ba2 Only show range colors to players who have observer permissions for that token 2021-02-02 11:44:20 +01:00
Manuel Vögele c8a7352d5f Merge branch 'master' into develop 2021-02-02 11:04:18 +01:00
Manuel Vögele bbd2173e63 Release v1.0.1 2021-02-02 10:53:23 +01:00
Manuel Vögele 633563b672 Add custom implementation of Ruler.moveToken
This allows us to move multiple tokens at a time
and allows the gm to move tokens through walls again
2021-02-02 10:52:15 +01:00
Manuel Vögele 135a3091b0 Don't show the drag ruler to players who cannot se the dragged token
As a side effect this will now also show the coloring to other players
2021-02-01 21:42:38 +01:00
Manuel Vögele 6beae157e7 Fix typos in the example source code in the api documentation 2021-02-01 16:21:45 +01:00
7 changed files with 185 additions and 14 deletions
+20
View File
@@ -0,0 +1,20 @@
## v1.1.1
### Bugfixes
- Fixed a bug where tokens wouldn't be moved to the corect end position on gridless maps
- Ruler now appears immediately when the token is being dragged
- On gridless maps the ruler will always start measuring at the center of the token
- This change has no impact on the distance that is being measured
- In addition to the cosmetical aspect this also fixes a bug that allowed players to glitch through walls
## v1.1.0
### New features
- The drag ruler will now be colored for other players than the dragging player as well (only if they have at least observer permissions for that token)
- The drag ruler won't be shown to other players if they cannot see the dragged token
### Bugfixes
- Fixed a bug where Drag Ruler wouldn't work at all on some windows installations (specificially where the `foundry.js` has `CRLF` line endings)
## v1.0.1
### Bugfixes
- The GM can now move tokens through walls
- It is now possible to move multiple tokens with Drag Ruler enabled
+4 -2
View File
@@ -1,3 +1,5 @@
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/staebchenfisch)
# Drag Ruler # Drag Ruler
This module shows a ruler when you drag a token to infrom you how far you've dragged the token from it's start point. Additionally, if you're using a grid, the spaces the token will travel though will be colored depending on your tokens speed. This module shows a ruler when you drag a token to infrom you how far you've dragged the token from it's start point. Additionally, if you're using a grid, the spaces the token will travel though will be colored depending on your tokens speed.
@@ -49,7 +51,7 @@ Here is an example of how a Speed Provider might look like:
```javascript ```javascript
function mySpeedProvider(token, playerColor) { function mySpeedProvider(token, playerColor) {
const baseSpeed = token.actor.data.speed const baseSpeed = token.actor.data.speed
const ranges = [{range: baseSpeed, color: playerColor, range: baseSpeed * 2, color: 0xFFFF00}] const ranges = [{range: baseSpeed, color: playerColor}, {range: baseSpeed * 2, color: 0xFFFF00}]
if (!token.actor.data.isWearingArmor) { if (!token.actor.data.isWearingArmor) {
ranges.push({range: baseSpeed * 3, color: 0xFF8000}) ranges.push({range: baseSpeed * 3, color: 0xFF8000})
} }
@@ -69,7 +71,7 @@ Hooks.once("dragRuler.ready", () => {
function mySpeedProvider(token, playerColor) { function mySpeedProvider(token, playerColor) {
const baseSpeed = token.actor.data.speed const baseSpeed = token.actor.data.speed
const ranges = [{range: baseSpeed, color: playerColor, range: baseSpeed * 2, color: 0xFFFF00}] const ranges = [{range: baseSpeed, color: playerColor}, {range: baseSpeed * 2, color: 0xFFFF00}]
if (!token.actor.data.isWearingArmor) { if (!token.actor.data.isWearingArmor) {
ranges.push({range: baseSpeed * 3, color: 0xFF8000}) ranges.push({range: baseSpeed * 3, color: 0xFF8000})
} }
+4
View File
@@ -1,6 +1,10 @@
{ {
"drag-ruler": { "drag-ruler": {
"settings": { "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": { "dashMultiplier": {
"name": "Dash Multiplier", "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." "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."
+3 -2
View File
@@ -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.0.0", "version": "1.1.1",
"minimumCoreVersion" : "0.7.9", "minimumCoreVersion" : "0.7.9",
"compatibleCoreVersion" : "0.7.9", "compatibleCoreVersion" : "0.7.9",
"authors": [ "authors": [
@@ -23,8 +23,9 @@
} }
], ],
"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.0.0.zip", "download": "https://github.com/manuelVo/foundryvtt-drag-ruler/archive/v1.1.1.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",
"bugs": "https://github.com/manuelVo/foundryvtt-drag-ruler/issues" "bugs": "https://github.com/manuelVo/foundryvtt-drag-ruler/issues"
} }
+97
View File
@@ -0,0 +1,97 @@
// This is a modified version of Ruler.moveToken from foundry 0.7.9
export async function moveTokens(selectedTokens) {
let wasPaused = game.paused;
if (wasPaused && !game.user.isGM) {
ui.notifications.warn(game.i18n.localize("GAME.PausedWarning"));
return false;
}
if (!this.visible || !this.destination) return false;
const draggedToken = this._getMovementToken();
if (!draggedToken) return;
// Get the movement rays and check collision along each Ray
// These rays are center-to-center for the purposes of collision checking
const rays = this._getRaysFromWaypoints(this.waypoints, this.destination);
if (!game.user.isGM) {
const hasCollision = selectedTokens.some(token => {
const offset = calculateTokenOffset(token, draggedToken)
const offsetRays = rays.map(ray => applyOffsetToRay(ray, offset))
return offsetRays.some(r => canvas.walls.checkCollision(r));
})
if (hasCollision) {
ui.notifications.error(game.i18n.localize("ERROR.TokenCollide"));
this._endMeasurement();
return true;
}
}
// Execute the movement path.
// Transform each center-to-center ray into a top-left to top-left ray using the prior token offsets.
this._state = Ruler.STATES.MOVING;
await Promise.all(selectedTokens.map(token => {
// Return the promise so we can wait for it outside the loop
const offset = calculateTokenOffset(token, draggedToken)
return animateToken.call(this, token, rays, offset, wasPaused)
}))
// Once all animations are complete we can clear the ruler
this._endMeasurement();
}
// This is a modified version code extracted from Ruler.moveToken from foundry 0.7.9
async function animateToken(token, rays, tokenOffset, wasPaused) {
const offsetRays = rays.map(ray => applyOffsetToRay(ray, tokenOffset))
// Determine offset relative to the Token top-left.
// This is important so we can position the token relative to the ruler origin for non-1x1 tokens.
origin = canvas.grid.getTopLeft(this.waypoints[0].x + tokenOffset.x, this.waypoints[0].y + tokenOffset.y);
let dx, dy
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
dx = token.data.x - origin[0]
dy = token.data.y - origin[1]
}
else {
const s2 = canvas.dimensions.size / 2;
dx = Math.round((token.data.x - origin[0]) / s2) * s2;
dy = Math.round((token.data.y - origin[1]) / s2) * s2;
}
token._noAnimate = true;
for (let r of offsetRays) {
if (!wasPaused && game.paused) break;
const dest = canvas.grid.getTopLeft(r.B.x, r.B.y);
const path = new Ray({ x: token.x, y: token.y }, { x: dest[0] + dx, y: dest[1] + dy });
await token.update(path.B);
await token.animateMovement(path);
}
token._noAnimate = false;
}
function calculateTokenOffset(tokenA, tokenB) {
return {x: tokenA.data.x - tokenB.data.x, y: tokenA.data.y - tokenB.data.y}
}
function applyOffsetToRay(ray, offset) {
return new Ray({x: ray.A.x + offset.x, y: ray.A.y + offset.y}, {x: ray.B.x + offset.x, y: ray.B.y + offset.y})
}
// This is a modified version of Ruler._onMouseMove from foundry 0.7.9
export function onMouseMove(event) {
if (this._state === Ruler.STATES.MOVING) return;
// Extract event data
const mt = event._measureTime || 0;
const originalEvent = event.data.originalEvent;
const destination = {x: event.data.destination.x + this.rulerOffset.x, y: event.data.destination.y + this.rulerOffset.y}
// Hide any existing Token HUD
canvas.hud.token.clear();
delete event.data.hudState;
// Draw measurement updates
if (Date.now() - mt > 50) {
this.measure(destination, { gridSpaces: !originalEvent.shiftKey });
event._measureTime = Date.now();
this._state = Ruler.STATES.MEASURING;
}
}
+45 -7
View File
@@ -1,12 +1,13 @@
"use strict" "use strict"
import {availableSpeedProviders, currentSpeedProvider, registerModule, registerSystem, setCurrentSpeedProvider} from "./api.js" import {availableSpeedProviders, currentSpeedProvider, registerModule, registerSystem, setCurrentSpeedProvider} from "./api.js"
import {moveTokens, onMouseMove} from "./foundry_imports.js"
import {registerSettings, settingsKey} from "./settings.js" import {registerSettings, settingsKey} from "./settings.js"
Hooks.once("init", () => { Hooks.once("init", () => {
registerSettings() registerSettings()
hookTokenDragHandlers() hookTokenDragHandlers()
hookRulerHandlers() hookRulerFunctions()
patchRulerMeasure() patchRulerMeasure()
patchRulerHighlightMeasurement() patchRulerHighlightMeasurement()
@@ -26,13 +27,15 @@ Hooks.once("ready", () => {
}) })
Hooks.on("canvasReady", () => { Hooks.on("canvasReady", () => {
canvas.controls.ruler.draggedToken = null canvas.controls.rulers.children.forEach(ruler => {
Object.defineProperty(canvas.controls.ruler, "isDragRuler", { ruler.draggedToken = null
Object.defineProperty(ruler, "isDragRuler", {
get: function isDragRuler() { get: function isDragRuler() {
return Boolean(this.draggedToken) // If draggedToken is set this is a drag ruler return Boolean(this.draggedToken) // If draggedToken is set this is a drag ruler
} }
}) })
}) })
})
function hookTokenDragHandlers() { function hookTokenDragHandlers() {
const originalDragLeftStartHandler = Token.prototype._onDragLeftStart const originalDragLeftStartHandler = Token.prototype._onDragLeftStart
@@ -62,7 +65,7 @@ function hookTokenDragHandlers() {
} }
} }
function hookRulerHandlers() { function hookRulerFunctions() {
const originalMoveTokenHandler = Ruler.prototype.moveToken const originalMoveTokenHandler = Ruler.prototype.moveToken
Ruler.prototype.moveToken = function (event) { Ruler.prototype.moveToken = function (event) {
const eventHandled = onRulerMoveToken.call(this, event) const eventHandled = onRulerMoveToken.call(this, event)
@@ -70,23 +73,44 @@ function hookRulerHandlers() {
return originalMoveTokenHandler.call(this, event) return originalMoveTokenHandler.call(this, event)
return true return true
} }
const originalToJSON = Ruler.prototype.toJSON
Ruler.prototype.toJSON = function () {
const json = originalToJSON.call(this)
if (this.draggedToken)
json["draggedToken"] = this.draggedToken.data._id
return json
}
const originalUpdate = Ruler.prototype.update
Ruler.prototype.update = function (data) {
if (data.draggedToken) {
this.draggedToken = canvas.tokens.get(data.draggedToken)
}
originalUpdate.call(this, data)
}
} }
function onTokenLeftDragStart(event) { function onTokenLeftDragStart(event) {
canvas.controls.ruler._onDragStart(event)
canvas.controls.ruler.draggedToken = this canvas.controls.ruler.draggedToken = this
const tokenCenter = {x: this.x + canvas.grid.size / 2, y: this.y + canvas.grid.size / 2}
canvas.controls.ruler.clear();
canvas.controls.ruler._state = Ruler.STATES.STARTING;
canvas.controls.ruler.rulerOffset = {x: tokenCenter.x - event.data.origin.x, y: tokenCenter.y - event.data.origin.y}
canvas.controls.ruler._addWaypoint(tokenCenter);
} }
function onTokenLeftDragMove(event) { function onTokenLeftDragMove(event) {
if (canvas.controls.ruler.isDragRuler) if (canvas.controls.ruler.isDragRuler)
canvas.controls.ruler._onMouseMove(event) onMouseMove.call(canvas.controls.ruler, event)
} }
function onTokenDragLeftDrop(event) { function onTokenDragLeftDrop(event) {
if (!canvas.controls.ruler.isDragRuler) if (!canvas.controls.ruler.isDragRuler)
return false return false
canvas.controls.ruler.draggedToken = null canvas.controls.ruler.draggedToken = null
canvas.controls.ruler.moveToken(event) const selectedTokens = canvas.tokens.placeables.filter(token => token._controlled)
moveTokens.call(canvas.controls.ruler, selectedTokens)
return true return true
} }
@@ -123,10 +147,16 @@ function strInsertAfter(haystack, needle, strToInsert) {
// These patches were written with foundry-0.7.9.js as reference // These patches were written with foundry-0.7.9.js as reference
function patchRulerMeasure() { function patchRulerMeasure() {
let code = Ruler.prototype.measure.toString() let code = Ruler.prototype.measure.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) // 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 = 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, "for ( let [i, d] of distances.entries() ) {\n", "segments[i].startDistance = totalDistance\n")
code = strInsertAfter(code, "this._highlightMeasurement(ray", ", s.startDistance") code = strInsertAfter(code, "this._highlightMeasurement(ray", ", s.startDistance")
// Don't show ruler if the measured token is invisible
code = "if (this.isDragRuler && !this.draggedToken.isVisible) return [];" + code
Ruler.prototype.measure = new Function("destination", "{gridSpaces=true}={}", code) Ruler.prototype.measure = new Function("destination", "{gridSpaces=true}={}", code)
} }
@@ -148,6 +178,12 @@ function nativeSpeedProvider(token, playercolor) {
function getColorForDistance(startDistance, subDistance) { function getColorForDistance(startDistance, subDistance) {
if (!this.isDragRuler) if (!this.isDragRuler)
return this.color return this.color
// Don't apply colors if the current user doesn't have at least observer permissions
if (this.draggedToken.actor.permission < 2) {
// If this is a pc and alwaysShowSpeedForPCs is enabled we show the color anyway
if (!(this.draggedToken.actor.data.type === "character" && game.settings.get(settingsKey, "alwaysShowSpeedForPCs")))
return this.color
}
const distance = startDistance + subDistance const distance = startDistance + subDistance
const ranges = currentSpeedProvider(this.draggedToken, this.color) const ranges = currentSpeedProvider(this.draggedToken, this.color)
if (ranges.length === 0) if (ranges.length === 0)
@@ -163,6 +199,8 @@ function getColorForDistance(startDistance, subDistance) {
// These patches were written with foundry-0.7.9.js as reference // These patches were written with foundry-0.7.9.js as reference
function patchRulerHighlightMeasurement() { function patchRulerHighlightMeasurement() {
let code = Ruler.prototype._highlightMeasurement.toString() 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) // 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 = code.slice(code.indexOf("\n"), code.lastIndexOf("\n"))
+9
View File
@@ -4,6 +4,15 @@ import {getDefaultDashMultiplier, getDefaultSpeedAttribute} from "./systems.js"
export const settingsKey = "drag-ruler"; export const settingsKey = "drag-ruler";
export function registerSettings() { export function registerSettings() {
game.settings.register(settingsKey, "alwaysShowSpeedForPCs", {
name: "drag-ruler.settings.alwaysShowSpeedForPCs.name",
hint: "drag-ruler.settings.alwaysShowSpeedForPCs.hint",
scope: "world",
config: true,
type: Boolean,
default: true,
})
// 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", name: "drag-ruler.settings.speedProvider.name",