From c9ae97e96071347e6f3f43fd877a393e19d77f8f Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Thu, 29 Aug 2024 12:35:15 -0700 Subject: [PATCH 01/12] =?UTF-8?q?=F0=9F=8E=B8=20feat=20|Drawing|Add=20flat?= =?UTF-8?q?=20movement=20penalty=20config?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- languages/en.json | 3 +++ scripts/const.js | 1 + templates/drawing-config.html | 10 +++++++++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/languages/en.json b/languages/en.json index 7daa5d8..89ac169 100644 --- a/languages/en.json +++ b/languages/en.json @@ -121,6 +121,9 @@ "elevationruler.drawingconfig.movementPenalty.name": "Movement Bonus/Penalty", "elevationruler.drawingconfig.movementPenalty.hint": "Set to 1 for no penalty. Values greater than one penalize movement by that percent; values less than one effectively grant a bonus to movement. For example, set to 2 to double movement through this area. Movement under the drawing elevation will be ignored.", + "elevationruler.drawingconfig.flatMovementPenalty.name": "Use Flat Penalty", + "elevationruler.drawingconfig.flatMovementPenalty.hint": "If enabled, treats the penalty as a fixed amount. For example, set to 5 to add +5 to each square of movement. Negative values provide flat bonuses to movement.", + "elevationruler.clearMovement": "Clear Combatant Movement", "elevationruler.waypoint": "waypoint", "elevationruler.up": "up", diff --git a/scripts/const.js b/scripts/const.js index 8c13765..cd03a90 100644 --- a/scripts/const.js +++ b/scripts/const.js @@ -16,6 +16,7 @@ export const TEMPLATES = { export const FLAGS = { MOVEMENT_SELECTION: "selectedMovementType", MOVEMENT_PENALTY: "movementPenalty", + MOVEMENT_PENALTY_FLAT: "flatMovementPenalty", SCENE: { BACKGROUND_ELEVATION: "backgroundElevation" }, diff --git a/templates/drawing-config.html b/templates/drawing-config.html index 2eaef75..25f7b31 100644 --- a/templates/drawing-config.html +++ b/templates/drawing-config.html @@ -4,10 +4,18 @@
- {{rangePicker name="flags.elevationruler.movementPenalty" value=object.flags.elevationruler.movementPenalty step=0.1 min=0.1 max=10}} +

{{ localize "elevationruler.drawingconfig.movementPenalty.hint" }}

+
+ +
+ +
+

{{ localize "elevationruler.drawingconfig.flatMovementPenalty.hint" }}

+
+ \ No newline at end of file From ae7877ffc26dd6fd016f3fbfa4c8b59e1b384091 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Thu, 29 Aug 2024 13:41:49 -0700 Subject: [PATCH 02/12] WIP|MovePenalty|Add grid-based penalty --- languages/en.json | 5 +++++ scripts/measurement/MovePenalty.js | 12 +++++++++--- scripts/settings.js | 11 +++++++++++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/languages/en.json b/languages/en.json index 89ac169..780c415 100644 --- a/languages/en.json +++ b/languages/en.json @@ -112,6 +112,11 @@ "elevationruler.settings.scale-text.name": "Scale Ruler Text", "elevationruler.settings.scale-text.hint": "When enabled, ruler text will be scaled depending on `canvas.dimensions.size`. Further adjust scaling by using `CONFIG.elevationruler.textScale`.", + "elevationruler.settings.force-grid-penalties.name": "Use Grid for Movement Penalties", + "elevationruler.settings.force-grid-penalties.hint": "By default, movement penalties are applied proportionally to the move. If a drawing with a x2 move penalty covers 3/4 of a grid square, it will apply to 3/4 of the move through that square (i.e., 1.5x penalty). When enabled, the penalty applies to a move through the entire grid square if, and only if, the penalty applies at the center of the grid. For example, if the drawing does not cover the center of the grid square, no movement through that grid square will be penalized. + + When enabled, ruler text will be scaled depending on `canvas.dimensions.size`. Further adjust scaling by using `CONFIG.elevationruler.textScale`.", + "elevationruler.settings.customized-labels.name": "Customized Ruler Text", "elevationruler.settings.customized-labels.hint": "Customize the ruler text. Change styles using `CONFIG.elevationruler.labelStyles` and `CONFIG.elevationruler.labelIcons`.", diff --git a/scripts/measurement/MovePenalty.js b/scripts/measurement/MovePenalty.js index 7c46749..8552f9e 100644 --- a/scripts/measurement/MovePenalty.js +++ b/scripts/measurement/MovePenalty.js @@ -67,8 +67,9 @@ export class MovePenalty { if ( r.terrainmapper.hasTerrain ) this.regions.add(r); }); canvas.drawings.placeables.forEach(d => { - const penalty = d.document.getFlag(MODULE_ID, FLAGS.MOVEMENT_PENALTY); - if ( penalty && penalty !== 1 ) this.drawings.add(d) + const penalty = d.document.getFlag(MODULE_ID, FLAGS.MOVEMENT_PENALTY) ?? 1; + const useFlatPenalty = d.document.getFlag(MODULE_ID, FLAGS.MOVEMENT_PENALTY_FLAT); + if ( (!useFlatPenalty && penalty !== 1) || (useFlatPenalty && penalty !== 0) ) this.drawings.add(d) }); this.tokens.delete(moveToken); @@ -160,7 +161,7 @@ export class MovePenalty { * @param {GridCoordinates3d} endCoords * @returns {number} The number used to multiply the move speed along the segment. */ - movementPenaltyForSegment(startCoords, endCoords) { + movementPenaltyForSegment(startCoords, endCoords, forceGridPenalty) { const start = startCoords.center; const end = endCoords.center; const key = `${start.key}|${end.key}`; @@ -169,6 +170,11 @@ export class MovePenalty { const t0 = performance.now(); const cutawayIxs = this._cutawayIntersections(start, end); if ( !cutawayIxs.length ) return 1; + + // If forcing to grid, get the grid breaks and the center points. + forceGridPenalty ??= Settings.get(Settings.KEYS.FORCE_GRID_PENALTIES); + + const t1 = performance.now(); const avgMultiplier = this._penaltiesForIntersections(start, end, cutawayIxs); const t2 = performance.now(); diff --git a/scripts/settings.js b/scripts/settings.js index eea3ae7..b6d1785 100644 --- a/scripts/settings.js +++ b/scripts/settings.js @@ -50,6 +50,7 @@ const SETTINGS = { EUCLIDEAN_GRID_DISTANCE: "euclidean-grid-distance", AUTO_MOVEMENT_TYPE: "automatic-movement-type", COMBAT_HISTORY: "token-ruler-combat-history", + FORCE_GRID_PENALTIES: "force-grid-penalties" }, NO_MODS: "no-modules-message", @@ -323,6 +324,16 @@ export class Settings extends ModuleSettingsAbstract { } }); + register(KEYS.MEASURING.FORCE_GRID_PENALTIES, { + name: localize(`${KEYS.MEASURING.FORCE_GRID_PENALTIES}.name`), + hint: localize(`${KEYS.MEASURING.FORCE_GRID_PENALTIES}.hint`), + scope: "user", + config: true, + default: true, + type: Boolean, + requiresReload: false + }); + // ----- NOTE: Grid Terrain Measurement ----- // // register(KEYS.GRID_TERRAIN.ALGORITHM, { // name: localize(`${KEYS.GRID_TERRAIN.ALGORITHM}.name`), From 77e4ca6a6e81fcc4ad3e2b26e139da40a9de847c Mon Sep 17 00:00:00 2001 From: GregoryWarn <154914479+GregoryWarn@users.noreply.github.com> Date: Fri, 30 Aug 2024 10:21:44 +0200 Subject: [PATCH 03/12] v0.10.5 Updated italian localization --- languages/it.json | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/languages/it.json b/languages/it.json index 905d481..099dd85 100644 --- a/languages/it.json +++ b/languages/it.json @@ -58,6 +58,9 @@ "elevationruler.settings.token-ruler-combat-history.name": "Traccia movimenti di combattimento", "elevationruler.settings.token-ruler-combat-history.hint": "Per l'evidenziare la velocità dei token, somma tutte le mosse dei token durante il round quando abilitato.", + "elevationruler.settings.combine-prior-with-total.name": "Combina il movimento precedente con il movimento totale", + "elevationruler.settings.combine-prior-with-total.hint": "Quando è abilitato il tracciamento dei movimenti di combattimento, combina il movimento precedente del token nel round con il movimento totale. Altrimenti, posiziona il movimento precedente su una riga separata.", + "elevationruler.settings.token-speed-property.name": "Proprietà Camminare Token", "elevationruler.settings.token-speed-property.hint": "Per l'evidenziazione della velocità del token, questa è la proprietà che rappresenta la velocità di camminata del token.", @@ -88,6 +91,9 @@ "elevationruler.settings.pathfinding_limit_token_los.name": "Limita Trova Percorso alle Aree Esplorate", "elevationruler.settings.pathfinding_limit_token_los.hint": "Usando Trova Percorso, limita il raggio d'azione del percorso alle aree esplorate a meno che l'utente non sia il DM", + "elevationruler.settings.pathfinding_snap_to_grid.name": "Trova Percorso agganciato alla griglia", + "elevationruler.settings.pathfinding_snap_to_grid.hint": "Durante la ricerca del percorso su una mappa a griglia, aggancia il percorso ai centri della griglia a meno che ciò non provochi il blocco del percorso.", + "elevationruler.settings.grid-terrain-algorithm.name": "Misurazione Griglia Terreno", "elevationruler.settings.grid-terrain-algorithm.hint": "Quando sei su una griglia, come tenere conto delle penalità di movimento o dei bonus derivanti dal terreno e dai token? Centro: si applica se il terreno/token si sovrappone al centro della griglia; Area percentuale: si applica se il terreno/token copre almeno questa parte del quadrato/esagono della griglia; Euclideo: ripartizione proporzionale in base alla percentuale del segmento di linea all'interno del terreno/token tra questo quadrato/esagono della griglia e il precedente.", "elevationruler.settings.grid-terrain-choice-center-point": "Centro", @@ -100,11 +106,25 @@ "elevationruler.settings.automatic-movement-type.name": "Rilevamento automatico del tipo di movimento", "elevationruler.settings.automatic-movement-type.hint": "Rileva automaticamente il tipo di movimento in base alla posizione del token. Imposta l'effetto dello stato del token su 'volare' o 'scavare' per sovrascrivere.", + "elevationruler.settings.euclidean-grid-distance.name": "Preferisci la distanza euclidea", + "elevationruler.settings.euclidean-grid-distance.hint": "Per le scene a griglia, se viene scelto 'Esatto (√2)' per l'impostazione principale 'Diagonali griglia quadrata', ciò costringerà a misurare la distanza euclidea invece della distanza in unità esadecimali/quadrate. Per le griglie esagonali, viene influenzato solo il movimento di elevazione diagonale.", + + "elevationruler.settings.scale-text.name": "Testo Scala Righello", + "elevationruler.settings.scale-text.hint": "Se abilitato, il testo del righello verrà ridimensionato in base a 'canvas.dimensions.size'. Regola ulteriormente il ridimensionamento utilizzando `CONFIG.elevationruler.textScale`.", + + "elevationruler.settings.customized-labels.name": "Testo Righello Personalizzato", + "elevationruler.settings.customized-labels.hint": "Personalizza il testo del righello. Modifica gli stili utilizzando `CONFIG.elevationruler.labelStyles` e `CONFIG.elevationruler.labelIcons`.", + "elevationruler.controls.prefer-token-elevation.name": "Preferisci elevazione token", "elevationruler.controls.pathfinding-control.name": "Trova Percorso", "elevationruler.drawingconfig.movementPenalty.name": "Bonus/Penalità Movimento", "elevationruler.drawingconfig.movementPenalty.hint": "Impostato a 1 senza penalità. Valori maggiori di uno penalizzano il movimento di quella percentuale; valori inferiori a uno garantiscono effettivamente un bonus al movimento. Ad esempio, impostalo su 2 per raddoppiare il movimento in quest'area. Il movimento sotto l'elevazione del disegno verrà ignorato.", - "elevationruler.clearMovement": "Cancella Movimento Combattente" + "elevationruler.clearMovement": "Cancella Movimento Combattente", + "elevationruler.waypoint": "Punto di passaggio", + "elevationruler.up": "Su", + "elevationruler.down": "Giù", + "elevationruler.added": "Aggiunto", + "elevationruler.prior": "Precedente" } From 4db52fc31ddec0da97f2fef9d6e79729d47b9b85 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Fri, 30 Aug 2024 17:28:57 -0700 Subject: [PATCH 04/12] =?UTF-8?q?=F0=9F=92=A1=20refactor|Measuring|Split?= =?UTF-8?q?=20out=20grid-based=20vs=20proportional=20cost?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Measure either the total cost of a grid square or the cost proportional to a segment moving through various regions/tokens/drawings. --- languages/en.json | 9 +- scripts/Ruler.js | 3 +- scripts/measurement/Grid.js | 33 ++-- scripts/measurement/MovePenalty.js | 233 +++++++++++++++++++++-------- scripts/settings.js | 23 ++- 5 files changed, 211 insertions(+), 90 deletions(-) diff --git a/languages/en.json b/languages/en.json index 780c415..5421a4b 100644 --- a/languages/en.json +++ b/languages/en.json @@ -96,6 +96,7 @@ "elevationruler.settings.grid-terrain-algorithm.name": "Terrain Grid Measurement", "elevationruler.settings.grid-terrain-algorithm.hint": "When on a grid, how to account for movement penalties or bonuses from terrain and tokens? Center: apply if the terrain/token overlaps the grid center; Percent Area: apply if the terrain/token covers at least this much of the grid square/hex; Euclidean: prorate based on percent of line segment within the terrain/token between this grid square/hex and the previous.", + "elevationruler.settings.grid-terrain-choice-center-point": "Center Point", "elevationruler.settings.grid-terrain-choice-percent-area": "Percent Area", "elevationruler.settings.grid-terrain-choice-euclidean": "Euclidean", @@ -113,9 +114,7 @@ "elevationruler.settings.scale-text.hint": "When enabled, ruler text will be scaled depending on `canvas.dimensions.size`. Further adjust scaling by using `CONFIG.elevationruler.textScale`.", "elevationruler.settings.force-grid-penalties.name": "Use Grid for Movement Penalties", - "elevationruler.settings.force-grid-penalties.hint": "By default, movement penalties are applied proportionally to the move. If a drawing with a x2 move penalty covers 3/4 of a grid square, it will apply to 3/4 of the move through that square (i.e., 1.5x penalty). When enabled, the penalty applies to a move through the entire grid square if, and only if, the penalty applies at the center of the grid. For example, if the drawing does not cover the center of the grid square, no movement through that grid square will be penalized. - - When enabled, ruler text will be scaled depending on `canvas.dimensions.size`. Further adjust scaling by using `CONFIG.elevationruler.textScale`.", + "elevationruler.settings.force-grid-penalties.hint": "By default, movement penalties are applied proportionally to the move. If a drawing with a x2 move penalty covers 3/4 of a grid square, it will apply to 3/4 of the move through that square (i.e., 1.5x penalty). When enabled, the penalty applies to a move through the entire grid square if, and only if, the penalty applies at the center of the grid. For example, if the drawing does not cover the center of the grid square, no movement through that grid square will be penalized.", "elevationruler.settings.customized-labels.name": "Customized Ruler Text", "elevationruler.settings.customized-labels.hint": "Customize the ruler text. Change styles using `CONFIG.elevationruler.labelStyles` and `CONFIG.elevationruler.labelIcons`.", @@ -123,8 +122,8 @@ "elevationruler.controls.prefer-token-elevation.name": "Prefer Token Elevation", "elevationruler.controls.pathfinding-control.name": "Use Pathfinding", - "elevationruler.drawingconfig.movementPenalty.name": "Movement Bonus/Penalty", - "elevationruler.drawingconfig.movementPenalty.hint": "Set to 1 for no penalty. Values greater than one penalize movement by that percent; values less than one effectively grant a bonus to movement. For example, set to 2 to double movement through this area. Movement under the drawing elevation will be ignored.", + "elevationruler.drawingconfig.movementPenalty.name": "Movement Penalty Percent", + "elevationruler.drawingconfig.movementPenalty.hint": "Set to 1 for no penalty, less than one to grant a bonus, greater than 1 to impose a penalty multiplier. For example, set to 2 to double movement cost through this area. Movement under the drawing elevation will be ignored.", "elevationruler.drawingconfig.flatMovementPenalty.name": "Use Flat Penalty", "elevationruler.drawingconfig.flatMovementPenalty.hint": "If enabled, treats the penalty as a fixed amount. For example, set to 5 to add +5 to each square of movement. Negative values provide flat bonuses to movement.", diff --git a/scripts/Ruler.js b/scripts/Ruler.js index 606725f..51ea403 100644 --- a/scripts/Ruler.js +++ b/scripts/Ruler.js @@ -499,8 +499,7 @@ function _getCostFunction() { return (prevOffset, currOffset, offsetDistance) => { if ( !(prevOffset instanceof GridCoordinates3d) ) prevOffset = GridCoordinates3d.fromOffset(prevOffset); if ( !(currOffset instanceof GridCoordinates3d) ) currOffset = GridCoordinates3d.fromOffset(currOffset); - const penalty = movePenaltyInstance.movementPenaltyForSegment(prevOffset, currOffset); - return offsetDistance * penalty; + return movePenaltyInstance.movementCostForSegment(prevOffset, currOffset, offsetDistance); }; } diff --git a/scripts/measurement/Grid.js b/scripts/measurement/Grid.js index 9c13cdb..1b61856 100644 --- a/scripts/measurement/Grid.js +++ b/scripts/measurement/Grid.js @@ -302,6 +302,26 @@ function singleOffsetHexDistanceFn(numDiagonals = 0) { return fn; } +/** + * Get the function to measure the offset distance for a given distance with given previous diagonals. + * @param {number} [diagonals=0] + * @returns {function} + */ +export function getOffsetDistanceFn(diagonals = 0) { + let offsetDistanceFn; + switch ( canvas.grid.type ) { + case CONST.GRID_TYPES.GRIDLESS: + offsetDistanceFn = (a, b) => CONFIG.GeometryLib.utils.pixelsToGridUnits(Point3d.distanceBetween(a, b)); + break; + case CONST.GRID_TYPES.SQUARE: + offsetDistanceFn = singleOffsetSquareDistanceFn(diagonals); + break; + default: // All hex grids + offsetDistanceFn = singleOffsetHexDistanceFn(diagonals); + } + return offsetDistanceFn; +} + /** * Measure a path for a gridded scene. Handles hex and square grids. * @param {GridMeasurePathWaypoint[]} waypoints The waypoints the path must pass through @@ -321,20 +341,9 @@ function _measurePath(wrapped, waypoints, { cost }, result) { // Movement cost requires knowing the 3d positions. // Cannot combine the projected waypoints to measure all at once, b/c they would be misaligned. // Copy the waypoint so it can be manipulated. - let diagonals = 0; let start = waypoints[0]; - let offsetDistanceFn; cost ??= (prevOffset, currOffset, offsetDistance) => offsetDistance; - switch ( canvas.grid.type ) { - case CONST.GRID_TYPES.GRIDLESS: - offsetDistanceFn = (a, b) => CONFIG.GeometryLib.utils.pixelsToGridUnits(Point3d.distanceBetween(a, b)); - break; - case CONST.GRID_TYPES.SQUARE: - offsetDistanceFn = singleOffsetSquareDistanceFn(diagonals); - break; - default: // All hex grids - offsetDistanceFn = singleOffsetHexDistanceFn(diagonals); - } + const offsetDistanceFn = getOffsetDistanceFn(0); // Diagonals = 0. const altGridDistanceFn = GridCoordinates3d.alternatingGridDistanceFn(); for ( let i = 1, n = waypoints.length; i < n; i += 1 ) { const end = waypoints[i]; diff --git a/scripts/measurement/MovePenalty.js b/scripts/measurement/MovePenalty.js index 8552f9e..40c993b 100644 --- a/scripts/measurement/MovePenalty.js +++ b/scripts/measurement/MovePenalty.js @@ -11,6 +11,7 @@ import { MODULE_ID, FLAGS, MODULES_ACTIVE, SPEED, MOVEMENT_TYPES } from "../cons import { Settings } from "../settings.js"; import { movementType } from "../token_hud.js"; import { log, keyForValue } from "../util.js"; +import { getOffsetDistanceFn } from "./Grid.js"; /* Class to measure penalty, as percentage of distance, between two points. @@ -56,8 +57,9 @@ export class MovePenalty { */ constructor(moveToken, speedFn) { this.moveToken = moveToken; - this.speedFn = speedFn ?? (token => foundry.utils.getProperty(token, SPEED.ATTRIBUTES[keyForValue(MOVEMENT_TYPES, token.movementType)])); - this.localTokenClone = this.constructor._constructTokenClone(this.moveToken); + this.speedFn = speedFn ?? (token => + foundry.utils.getProperty(token, SPEED.ATTRIBUTES[keyForValue(MOVEMENT_TYPES, token.movementType)])); + this.#localTokenClone = this.constructor._constructTokenClone(this.moveToken); const tokenMultiplier = this.constructor.tokenMultiplier; const terrainAPI = this.constructor.terrainAPI; @@ -69,7 +71,7 @@ export class MovePenalty { canvas.drawings.placeables.forEach(d => { const penalty = d.document.getFlag(MODULE_ID, FLAGS.MOVEMENT_PENALTY) ?? 1; const useFlatPenalty = d.document.getFlag(MODULE_ID, FLAGS.MOVEMENT_PENALTY_FLAT); - if ( (!useFlatPenalty && penalty !== 1) || (useFlatPenalty && penalty !== 0) ) this.drawings.add(d) + if ( (!useFlatPenalty && penalty !== 1) || (useFlatPenalty && penalty !== 0) ) this.drawings.add(d); }); this.tokens.delete(moveToken); @@ -117,7 +119,7 @@ export class MovePenalty { * - @prop {TokenDocument} document * - @prop {Actor} actor */ - localTokenClone; + #localTokenClone; /** * Construct the local token clone. @@ -125,8 +127,8 @@ export class MovePenalty { * @returns {object} */ static _constructTokenClone(token) { - const actor = new CONFIG.Actor.documentClass(token.actor.toObject()) - const document = new CONFIG.Token.documentClass(token.document.toObject()) + const actor = new CONFIG.Actor.documentClass(token.actor.toObject()); + const document = new CONFIG.Token.documentClass(token.document.toObject()); const tClone = { document, actor, _original: token }; // Add the movementType and needed properties to calculate movement type. @@ -142,7 +144,7 @@ export class MovePenalty { }, elevationE: { get: function() { - return this.document.elevation + return this.document.elevation; } } }); @@ -156,40 +158,118 @@ export class MovePenalty { // ----- NOTE: Primary methods ----- // /** - * Determine the movement penalties along a start|end segment. - * @param {GridCoordinates3d} startCoords - * @param {GridCoordinates3d} endCoords - * @returns {number} The number used to multiply the move speed along the segment. + * Determine the movement cost for a segment. + * @param {GridCoordinates3d} startCoords Exact starting position + * @param {GridCoordinates3d} endCoords Exact ending position + * @param {number} costFreeDistance Measured distance of the segment (may be offset distance) + * @returns {number} The costFreeDistance + cost, in grid units. */ - movementPenaltyForSegment(startCoords, endCoords, forceGridPenalty) { - const start = startCoords.center; - const end = endCoords.center; - const key = `${start.key}|${end.key}`; + movementCostForSegment(startCoords, endCoords, costFreeDistance = 0, forceGridPenalty) { // eslint-disable-line default-param-last + forceGridPenalty ??= Settings.get(Settings.KEYS.FORCE_GRID_PENALTIES); + forceGridPenalty &&= !canvas.grid.isGridless; + + // Did we already test this segment? + const startKey = forceGridPenalty ? startCoords.center.key : startCoords.key; + const endKey = forceGridPenalty ? endCoords.center.key : endCoords.key; + const key = `${startKey}|${endKey}`; if ( this.#penaltyCache.has(key) ) return this.#penaltyCache.get(key); - const t0 = performance.now(); - const cutawayIxs = this._cutawayIntersections(start, end); - if ( !cutawayIxs.length ) return 1; + let res = costFreeDistance; + if ( forceGridPenalty ) { + // Cost is assigned to each grid square/hex + const isOneStep = Math.abs(endCoords.i - startCoords.i) < 2 + && Math.abs(endCoords.j - startCoords.j) < 2 + && Math.abs(endCoords.k - startCoords.k) < 2; + if ( isOneStep ) return this.movementCostForGridSpace(endCoords, costFreeDistance); + + // Unlikely scenario where endCoords are more than 1 step away from startCoords. + let totalCost = 0; + const path = canvas.grid.getDirectPath([startCoords, endCoords]); + const offsetDistanceFn = getOffsetDistanceFn(); + let prevOffset = path[0]; + for ( let i = 1, n = path.length; i < n; i += 1 ) { + const currOffset = path[i]; + const offsetDist = offsetDistanceFn(prevOffset, currOffset); + totalCost += (this.movementCostForGridSpace(endCoords, offsetDist) - offsetDist); + prevOffset = currOffset; + } + res = totalCost + costFreeDistance; + } else { + // Cost is proportional to the distance of the segment covered by each penalty-imposing token,region,drawing. + const multiplier = this.proportionalCostForSegment(startCoords, endCoords); + res = costFreeDistance * multiplier; + } + this.#penaltyCache.set(key, res); + return res; + } - // If forcing to grid, get the grid breaks and the center points. - forceGridPenalty ??= Settings.get(Settings.KEYS.FORCE_GRID_PENALTIES); + /** + * Determine the movement cost when in a specific grid space. + * Typically used with Settings.KEYS.FORCE_GRID_PENALTIES. + * @param {GridCoordinates3d} coords Exact starting position + * @param {number} costFreeDistance Measured distance of the step + * @returns {number} The additional cost, in grid units, plus the costFreeDistance. + */ + movementCostForGridSpace(coords, costFreeDistance = 0) { + // Determine what regions, tokens, drawings overlap the center point. + const centerPt = coords.center; + const regions = this.regions.filter(r => r.testPoint(centerPt, centerPt.elevation)); + const tokens = this.tokens.filter(t => t.constrainedTokenBorder.contains(centerPt.x, centerPt.y) + && centerPt.elevation.between(t.bottomE, t.topE)); + const drawings = this.drawings.filter(d => d.contains(centerPt.x, centerPt.y) + && d.elevationE >= centerPt.elevation); + + // Track all speed multipliers and flat penalties for the grid space. + let flatPenalty = 0; + let currentMultiplier = 1; + + // Drawings + drawings.forEach(d => { + const penalty = d.document.getFlag(MODULE_ID, FLAGS.MOVEMENT_PENALTY); + if ( d.document.getFlag(MODULE_ID, FLAGS.MOVEMENT_PENALTY_FLAT) ) flatPenalty += penalty; + else currentMultiplier *= penalty; + }); - const t1 = performance.now(); - const avgMultiplier = this._penaltiesForIntersections(start, end, cutawayIxs); - const t2 = performance.now(); - if ( CONFIG[MODULE_ID].debug ) { - console.group(`${MODULE_ID}|movementPenaltyForSegment`); - console.debug(`${startCoords.x},${startCoords.y},${startCoords.z}(${startCoords.i},${startCoords.j},${startCoords.k}) --> ${endCoords.x},${endCoords.y},${endCoords.z}(${endCoords.i},${endCoords.j},${endCoords.k})`); - console.table({ - _cutawayIntersections: (t1 - t0).toNearest(.01), - penaltiesForIntersections: (t2 - t1).toNearest(.01), - total: (t2 - t0).toNearest(.01) - }); - console.groupEnd(`${MODULE_ID}|movementPenaltyForSegment`); - } - this.#penaltyCache.set(key, 1 / avgMultiplier); - return 1 / avgMultiplier; + // Tokens + const tokenMultiplier = this.tokenMultiplier; + const useTokenFlat = this.useFlatTokenMultiplier; + if ( useTokenFlat ) flatPenalty += (tokenMultiplier * tokens.length); + else currentMultiplier *= (tokenMultiplier * tokens.length); + + // Regions + const testRegions = this.constructor.terrainAPI && regions.length; + const tClone = testRegions ? this.#initializeTokenClone() : this.moveToken; + const startingSpeed = this.speedFn(tClone) || 1; + regions.forEach(r => this.#addTerrainsToToken(tClone, r)); + + const speedInGrid = ((this.speedFn(tClone) || 1) * currentMultiplier); + const gridMult = startingSpeed / speedInGrid; + return (flatPenalty + (gridMult * costFreeDistance)); + + /* Example + Token has speed 30 and moves 10 grid units. + Assume speed is halved plus a +5 flat penalty. + 30 / 15 = 2 * 10 = 20 grid units + 5 penalty. + So instead of moving 10 units, it is as though the token moved 25. + */ + } + + + /** + * Determine the movement penalties along a start|end segment. + * By default, the penalty is apportioned based on the exact intersections of the penalty + * region to the segment. If `forceGridPenalty=true`, then the penalty is assigned per grid space. + * + * @param {GridCoordinates3d} startCoords Exact starting position + * @param {GridCoordinates3d} endCoords Exact ending position + * @returns {number} The number used to multiply the move speed along the segment. + */ + proportionalCostForSegment(startCoords, endCoords) { + // Intersections for each region, token, drawing. + const cutawayIxs = this._cutawayIntersections(startCoords, endCoords); + if ( !cutawayIxs.length ) return 1; + return this._penaltiesForIntersections(startCoords, endCoords, cutawayIxs); } // ----- NOTE: Secondary methods ----- // @@ -210,11 +290,12 @@ export class MovePenalty { */ _cutawayIntersections(start, end) { const cutawayIxs = []; - const terrainAPI = this.constructor.terrainAPI; - for ( const region of this.pathRegions ) { - const ixs = region.terrainmapper._cutawayIntersections(start, end); - ixs.forEach(ix => ix.region = region); - cutawayIxs.push(...ixs); + if ( this.constructor.terrainAPI ) { + for ( const region of this.pathRegions ) { + const ixs = region.terrainmapper._cutawayIntersections(start, end); + ixs.forEach(ix => ix.region = region); + cutawayIxs.push(...ixs); + } } for ( const token of this.pathTokens ) { const ixs = this.constructor.tokenCutawayIntersections(start, end, token); @@ -234,24 +315,18 @@ export class MovePenalty { * @param {Point3d} start * @param {Point3d} end * @param {CutawayIntersection[]} cutawayIxs - * @returns {CutawayIntersection[]} Intersections with penalties and intersections converted to distance for x axis. + * @returns {number} The penalty multiplier for the given start --> end */ _penaltiesForIntersections(start, end, cutawayIxs) { if ( !cutawayIxs.length ) return 1; - // Set up the token clone to add and subtract terrains. - const tokenMultiplier = this.constructor.tokenMultiplier; - let tClone = this.moveToken; + // Tokens + const tokenMultiplier = this.tokenMultiplier; + const useTokenFlat = this.useFlatTokenMultiplier; + + // Regions const testRegions = this.constructor.terrainAPI && this.pathRegions; - if ( testRegions ) { - tClone = this.localTokenClone; - const Terrain = CONFIG.terrainmapper.Terrain; - const tokenTerrains = Terrain.allOnToken(tClone); - if ( tokenTerrains.length ) { - CONFIG.terrainmapper.Terrain.removeFromTokenLocally(tClone, tokenTerrains, { refresh: false }); - tClone.actor._initialize(); // This is slow; we really need something more specific to active effects. - } - } + const tClone = testRegions ? this.#initializeTokenClone() : this.moveToken; const startingSpeed = this.speedFn(tClone) || 1; // Traverse each intersection, determining the speed multiplier from starting speed @@ -260,40 +335,55 @@ export class MovePenalty { let totalDistance = 0; let totalTime = 0; let currentMultiplier = 1; + let currentFlat = 0; const start2d = convertToDistance(to2d(start, start, end)); const end2d = convertToDistance(to2d(end, start, end)); let prevIx = start2d; - //const changePts = []; cutawayIxs = cutawayIxs.map(ix => convertToDistance(shallowCopyCutawayIntersection(ix))); // Avoid modifying the originals. cutawayIxs.push(end2d); cutawayIxs.sort((a, b) => a.x - b.x); for ( const ix of cutawayIxs ) { // Must invert the multiplier to apply them as penalties. So a 2x penalty is 1/2 times speed. const multFn = ix.movingInto ? x => 1 / x : x => x; + const addFn = ix.movingInto ? x => x : x => -x; const terrainFn = ix.movingInto ? this.#addTerrainsToToken.bind(this) : this.#removeTerrainsFromToken.bind(this); // Handle all intersections at the same point. if ( ix.almostEqual(prevIx) ) { - if ( ix.token ) currentMultiplier *= multFn(tokenMultiplier); - if ( ix.drawing ) currentMultiplier *= multFn(ix.drawing.document.getFlag(MODULE_ID, FLAGS.MOVEMENT_PENALTY)); + if ( ix.token ) { + if ( useTokenFlat ) currentFlat += addFn(tokenMultiplier); + else currentMultiplier *= multFn(tokenMultiplier); + } + if ( ix.drawing ) { + const penalty = ix.drawing.document.getFlag(MODULE_ID, FLAGS.MOVEMENT_PENALTY); + if ( ix.drawing.document.getFlag(MODULE_ID, FLAGS.MOVEMENT_PENALTY_FLAT) ) currentFlat += addFn(penalty); + else currentMultiplier *= multFn(penalty); + } if ( ix.region ) terrainFn(tClone, ix.region); continue; } // Now we have prevIx --> ix. + prevIx.flat = currentFlat; prevIx.multiplier = currentMultiplier; prevIx.dist = PIXI.Point.distanceBetween(prevIx, ix); - prevIx.tokenSpeed = (this.speedFn(tClone) || 1) * prevIx.multiplier; + prevIx.tokenSpeed = ((this.speedFn(tClone) || 1) * prevIx.multiplier) + prevIx.flat; totalDistance += prevIx.dist; totalTime += (prevIx.dist / prevIx.tokenSpeed); - //changePts.push(prevIx); prevIx = ix; if ( ix.almostEqual(end2d) ) break; // Account for the changes due to ix. - if ( ix.token ) currentMultiplier *= multFn(tokenMultiplier); - if ( ix.drawing ) currentMultiplier *= multFn(ix.drawing.document.getFlag(MODULE_ID, FLAGS.MOVEMENT_PENALTY)); + if ( ix.token ) { + if ( useTokenFlat ) currentFlat += addFn(tokenMultiplier); + else currentMultiplier *= multFn(tokenMultiplier); + } + if ( ix.drawing ) { + const penalty = ix.drawing.document.getFlag(MODULE_ID, FLAGS.MOVEMENT_PENALTY); + if ( ix.drawing.document.getFlag(MODULE_ID, FLAGS.MOVEMENT_PENALTY_FLAT) ) currentFlat += addFn(penalty); + else currentMultiplier *= multFn(penalty); + } if ( ix.region ) terrainFn(tClone, ix.region); } @@ -337,10 +427,28 @@ export class MovePenalty { log(`#removeTerrainsFromToken|\tremoveLocally: ${(t1 - t0).toNearest(0.01)} ms\tinitialize: ${(t2 - t1).toNearest(0.01)} ms`); } + /** + * Initialize the token clone for testing movement penalty through regions. + * @returns {object} Token like object + */ + #initializeTokenClone() { + const tClone = this.#localTokenClone; + const Terrain = CONFIG.terrainmapper.Terrain; + const tokenTerrains = Terrain.allOnToken(tClone); + if ( tokenTerrains.length ) { + CONFIG.terrainmapper.Terrain.removeFromTokenLocally(tClone, tokenTerrains, { refresh: false }); + tClone.actor._initialize(); // This is slow; we really need something more specific to active effects. + } + return tClone; + } + // ----- NOTE: Static getters ----- // /** @type {number} */ - static get tokenMultiplier() { return Settings.get(Settings.KEYS.TOKEN_RULER.TOKEN_MULTIPLIER); } + static get tokenMultiplier() { return Settings.get(Settings.KEYS.MEASURING.TOKEN_MULTIPLIER); } + + /** @type {boolean} */ + static get useFlatTokenMultiplier() { return Settings.get(Settings.KEYS.MEASURING.TOKEN_MULTIPLIER_FLAT); } /** @type {object|undefined} */ static get terrainAPI() { return MODULES_ACTIVE.API?.TERRAIN_MAPPER; } @@ -379,7 +487,6 @@ export class MovePenalty { } } - /** * Duplicate pertinent parts of a CutawayIntersection. * @param {CutawayIntersection} ix @@ -391,8 +498,6 @@ function shallowCopyCutawayIntersection(ix) { return newIx; } - - /** * A function that returns the cost for a given move between grid/gridless spaces. * In square and hexagonal grids the grid spaces are always adjacent unless teleported. diff --git a/scripts/settings.js b/scripts/settings.js index b6d1785..ba3d600 100644 --- a/scripts/settings.js +++ b/scripts/settings.js @@ -50,14 +50,16 @@ const SETTINGS = { EUCLIDEAN_GRID_DISTANCE: "euclidean-grid-distance", AUTO_MOVEMENT_TYPE: "automatic-movement-type", COMBAT_HISTORY: "token-ruler-combat-history", - FORCE_GRID_PENALTIES: "force-grid-penalties" + FORCE_GRID_PENALTIES: "force-grid-penalties", + TOKEN_MULTIPLIER: "token-terrain-multiplier", + TOKEN_MULTIPLIER_FLAT: "token-terrain-multiplier" }, NO_MODS: "no-modules-message", TOKEN_RULER: { ENABLED: "enable-token-ruler", HIDE_GM: "hide-gm-ruler", - TOKEN_MULTIPLIER: "token-terrain-multiplier" + }, SPEED_HIGHLIGHTING: { @@ -310,20 +312,27 @@ export class Settings extends ModuleSettingsAbstract { type: Number }); - register(KEYS.TOKEN_RULER.TOKEN_MULTIPLIER, { - name: localize(`${KEYS.TOKEN_RULER.TOKEN_MULTIPLIER}.name`), - hint: localize(`${KEYS.TOKEN_RULER.TOKEN_MULTIPLIER}.hint`), + register(KEYS.MEASURING.TOKEN_MULTIPLIER, { + name: localize(`${KEYS.MEASURING.TOKEN_MULTIPLIER}.name`), + hint: localize(`${KEYS.MEASURING.TOKEN_MULTIPLIER}.hint`), scope: "world", config: true, default: 1, type: Number, range: { - max: 10, - min: 0, step: 0.1 } }); + register(KEYS.MEASURING.TOKEN_MULTIPLIER, { + name: localize(`${KEYS.MEASURING.TOKEN_MULTIPLIER_FLAT}.name`), + hint: localize(`${KEYS.MEASURING.TOKEN_MULTIPLIER_FLAT}.hint`), + scope: "world", + config: true, + default: true, + type: Boolean + }); + register(KEYS.MEASURING.FORCE_GRID_PENALTIES, { name: localize(`${KEYS.MEASURING.FORCE_GRID_PENALTIES}.name`), hint: localize(`${KEYS.MEASURING.FORCE_GRID_PENALTIES}.hint`), From 35fd350aa1503f3bcd2e748863c9ababbc380b36 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Fri, 30 Aug 2024 17:40:45 -0700 Subject: [PATCH 05/12] =?UTF-8?q?=F0=9F=92=84=20style|Eslint|fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/CombatTracker.js | 14 ++--- scripts/ModuleSettingsAbstract.js | 10 ++-- scripts/Patcher.js | 10 +++- scripts/Ruler.js | 3 +- scripts/Token.js | 9 +-- scripts/const.js | 4 +- scripts/measurement/Grid.js | 66 +++++++++++---------- scripts/module.js | 4 +- scripts/patching.js | 10 ++-- scripts/segment_labels_highlighting.js | 9 +-- scripts/segments.js | 10 +++- scripts/settings.js | 80 +++++++++++++------------- scripts/system_attributes.js | 2 +- scripts/terrain_elevation.js | 29 +++++----- scripts/token_hud.js | 1 + scripts/token_speed.js | 7 ++- scripts/util.js | 10 ++-- 17 files changed, 146 insertions(+), 132 deletions(-) diff --git a/scripts/CombatTracker.js b/scripts/CombatTracker.js index 733ff0a..0aa484f 100644 --- a/scripts/CombatTracker.js +++ b/scripts/CombatTracker.js @@ -1,5 +1,6 @@ /* globals -game +game, +ui */ /* eslint no-unused-vars: ["error", { "argsIgnorePattern": "^_" }] */ @@ -10,7 +11,7 @@ import { TEMPLATES, MODULE_ID, FLAGS } from "./const.js"; export const PATCHES = {}; PATCHES.BASIC = {}; -import { injectConfiguration, renderTemplateSync } from "./util.js"; +import { renderTemplateSync } from "./util.js"; // ----- NOTE: Hooks ----- // @@ -31,15 +32,8 @@ function renderCombatTracker(app, html, data) { if ( !dividers.length ) return; const myHtml = renderTemplateSync(TEMPLATES.COMBAT_TRACKER, data); - // const aElem = document.createElement("a"); - // aElem.innerHTML = myHtml; dividers[0].insertAdjacentHTML("beforebegin", myHtml); - - // const npcButton = Object.values(combatButtons).findIndex(b => b.dataset.control === "rollNPC"); - // const findString = ".combat-button[data-control='rollNPC']"; - // await injectConfiguration(app, html, data, template, findString); - - html.find(`.${MODULE_ID}`).click(ev => clearMovement.call(app, ev)); + html.find(`.${MODULE_ID}`).click(ev => clearMovement.call(app, ev)); } PATCHES.BASIC.HOOKS = { renderCombatTracker }; diff --git a/scripts/ModuleSettingsAbstract.js b/scripts/ModuleSettingsAbstract.js index 3a5a8e0..4cc7f0c 100644 --- a/scripts/ModuleSettingsAbstract.js +++ b/scripts/ModuleSettingsAbstract.js @@ -59,11 +59,11 @@ export class ModuleSettingsAbstract { const cached = this.cache.get(key); if ( typeof cached !== "undefined" ) { // For debugging, can confirm against what the value should be. -// const origValue = game.settings.get(MODULE_ID, key); -// if ( origValue !== cached ) { -// console.debug(`Settings cache fail: ${origValue} !== ${cached} for key ${key}`); -// return origValue; -// } + // const origValue = game.settings.get(MODULE_ID, key); + // if ( origValue !== cached ) { + // console.debug(`Settings cache fail: ${origValue} !== ${cached} for key ${key}`); + // return origValue; + // } return cached; diff --git a/scripts/Patcher.js b/scripts/Patcher.js index 41fc3ff..d68e6c5 100644 --- a/scripts/Patcher.js +++ b/scripts/Patcher.js @@ -197,7 +197,7 @@ export class Patcher { Object.defineProperty(cl, name, descriptor); const prototypeName = cl.constructor?.name; - const id = `${prototypeName ?? cl.name }.${prototypeName ? "prototype." : ""}${name}`; + const id = `${prototypeName ?? cl.name}.${prototypeName ? "prototype." : ""}${name}`; return { id, args: { cl, name } }; } @@ -401,7 +401,9 @@ export class MethodPatch extends AbstractPatch { else if ( this.config.isSetter ) this.prevMethod = this.prevMethod?.set; else this.prevMethod = this.prevMethod?.value; - this.regId = Patcher.addClassMethod(this.#cl, this.target, this.patchFn, { getter: this.config.isGetter, setter: this.config.isSetter }); + this.regId = Patcher.addClassMethod(this.#cl, this.target, this.patchFn, { + getter: this.config.isGetter, setter: this.config.isSetter + }); } /** @@ -413,7 +415,9 @@ export class MethodPatch extends AbstractPatch { // Add back the original, if any. if ( this.prevMethod ) { - Patcher.addClassMethod(this.#cl, this.target, this.prevMethod, { getter: this.config.isGetter, setter: this.config.isSetter }); + Patcher.addClassMethod(this.#cl, this.target, this.prevMethod, { + getter: this.config.isGetter, setter: this.config.isSetter + }); this.prevMethod = undefined; } this.regId = undefined; diff --git a/scripts/Ruler.js b/scripts/Ruler.js index 51ea403..4fa3abc 100644 --- a/scripts/Ruler.js +++ b/scripts/Ruler.js @@ -2,6 +2,7 @@ canvas, CONFIG, CONST, +foundry, game, PIXI, Ruler, @@ -276,7 +277,7 @@ function _getMeasurementDestination(wrapped, point, {snap=true}={}) { const origin = token.getCenterPoint(); const delta = origPoint.subtract(origin, PIXI.Point._tmp); let position = PIXI.Point._tmp2.copyFrom(token.document).add(delta, PIXI.Point._tmp2); - const tlSnapped = token._preview.getSnappedPosition(position); + const tlSnapped = token._preview.getSnappedPosition(position); return token.getCenterPoint(tlSnapped); } diff --git a/scripts/Token.js b/scripts/Token.js index accd369..b7c7ac2 100644 --- a/scripts/Token.js +++ b/scripts/Token.js @@ -1,6 +1,7 @@ /* globals canvas, CanvasAnimation, +CONFIG, foundry, game, Ruler @@ -96,7 +97,7 @@ function updateToken(document, changed, _options, _userId) { const tokenHistory = token[MODULE_ID].measurementHistory ??= []; const gridUnitsToPixels = CONFIG.GeometryLib.utils.gridUnitsToPixels; const origin = token.getCenterPoint(document); - const dest = token.getCenterPoint({ x: changed.x ?? document.x, y: changed.y ?? document.y}) + const dest = token.getCenterPoint({ x: changed.x ?? document.x, y: changed.y ?? document.y}); origin.z = gridUnitsToPixels(document.elevation); origin.teleport = false; origin.cost = 0; @@ -129,7 +130,7 @@ function _onDragLeftCancel(wrapped, event) { // Add waypoint on right click const ruler = canvas.controls.ruler; - if ( event.button === 2 && ruler._isTokenRuler && ruler.active && ruler.state === Ruler.STATES.MEASURING ) { + if ( event.button === 2 && ruler._isTokenRuler && ruler.active && ruler.state === Ruler.STATES.MEASURING ) { log("Token#_onDragLeftMove|Token ruler active"); event.preventDefault(); if ( event.ctrlKey ) ruler._removeWaypoint(event.interactionData.origin, {snap: !event.shiftKey}); @@ -187,7 +188,7 @@ async function _onDragLeftDrop(wrapped, event) { return false; } - // ruler._state = Ruler.STATES.MOVING; // Do NOT set state to MOVING here in v12, as it will break the canvas. + // NO: ruler._state = Ruler.STATES.MOVING; // Do NOT set state to MOVING here in v12, as it will break the canvas. ruler._onMoveKeyDown(event); // Movement is async here but not awaited in _onMoveKeyDown. } @@ -259,4 +260,4 @@ function noStartEase(easing) { function noEndEase(easing) { if ( typeof easing === "string" ) easing = CanvasAnimation[easing]; return pt => (pt > 0.5) ? pt : easing(pt); -} \ No newline at end of file +} diff --git a/scripts/const.js b/scripts/const.js index cd03a90..2b3cc5c 100644 --- a/scripts/const.js +++ b/scripts/const.js @@ -48,7 +48,9 @@ export const MOVEMENT_TYPES = { * @param {number} groundElev Ground elevation in grid units * @returns {MOVEMENT_TYPE} */ - forCurrentElevation: function movementTypeForCurrentElevation(currElev, groundElev = 0) { return Math.sign(currElev - groundElev) + 1; } + forCurrentElevation: function(currElev, groundElev = 0) { + return Math.sign(currElev - groundElev) + 1; + } }; diff --git a/scripts/measurement/Grid.js b/scripts/measurement/Grid.js index 1b61856..57067f1 100644 --- a/scripts/measurement/Grid.js +++ b/scripts/measurement/Grid.js @@ -1,5 +1,6 @@ /* globals canvas, +CONFIG, CONST */ /* eslint no-unused-vars: ["error", { "argsIgnorePattern": "^_" }] */ @@ -81,7 +82,7 @@ function directPath3dSquare(start, end, path2d) { prevOffset.i = path2d[0].i; prevOffset.j = path2d[0].j; - // currOffset will be modified in the loop; set to end to get elevation steps now. + // The currOffset will be modified in the loop; set to end to get elevation steps now. const currOffset = GridCoordinates3d.fromObject(end); // Do 1 elevation move for each 2d diagonal move. Spread out over the diagonal steps. @@ -99,12 +100,14 @@ function directPath3dSquare(start, end, path2d) { // Do 1 elevation move for each 2d non-diagonal move. Spread out over the non-diagonal steps. const num2dStraight = num2dMoves - num2dDiagonal; - let diagonalElevationStepsRemaining = Math.min(elevationStepsRemaining - doubleDiagonalElevationStepsRemaining, num2dStraight); + let diagonalElevationStepsRemaining = Math.min(elevationStepsRemaining + - doubleDiagonalElevationStepsRemaining, num2dStraight); let diagonalElevationStep = 0; const doDiagonalElevationStepMod = Math.ceil(num2dStraight / (diagonalElevationStepsRemaining + 1)); // Rest are all additional elevation-only moves. Spread out evenly. - let additionalElevationStepsRemaining = Math.max(0, elevationStepsRemaining - diagonalElevationStepsRemaining - diagonalElevationStepsRemaining); + let additionalElevationStepsRemaining = Math.max(0, + elevationStepsRemaining - diagonalElevationStepsRemaining - diagonalElevationStepsRemaining); const doAdditionalElevationStepMod = Math.ceil(num2dMoves / (additionalElevationStepsRemaining + 1)); currOffset.k = prevOffset.k; // Begin with the starting elevation, incrementing periodically in the loop. @@ -113,9 +116,13 @@ function directPath3dSquare(start, end, path2d) { currOffset.setOffset2d(path2d[i]); const is2dDiagonal = (currOffset.i !== prevOffset.i) && (currOffset.j !== prevOffset.j); - const doDoubleDiagonalElevationStep = is2dDiagonal && doubleDiagonalElevationStepsRemaining > 0 && ((doubleDiagonalElevationStep + 1) % doDoubleDiagonalElevationStepMod) === 0; - const doDiagonalElevationStep = !is2dDiagonal && diagonalElevationStepsRemaining > 0 && ((diagonalElevationStep + 1) % doDiagonalElevationStepMod) === 0; - const doAdditionalElevationSteps = additionalElevationStepsRemaining > 0 && ((i + 1) % doAdditionalElevationStepMod) === 0; + const doDoubleDiagonalElevationStep = is2dDiagonal + && doubleDiagonalElevationStepsRemaining > 0 + && ((doubleDiagonalElevationStep + 1) % doDoubleDiagonalElevationStepMod) === 0; + const doDiagonalElevationStep = !is2dDiagonal && diagonalElevationStepsRemaining > 0 + && ((diagonalElevationStep + 1) % doDiagonalElevationStepMod) === 0; + const doAdditionalElevationSteps = additionalElevationStepsRemaining > 0 + && ((i + 1) % doAdditionalElevationStepMod) === 0; // Either double or normal diagonals are the same but have separate tracking. if ( doDoubleDiagonalElevationStep ) { @@ -130,7 +137,7 @@ function directPath3dSquare(start, end, path2d) { path3d.push(currOffset.clone()); if ( doAdditionalElevationSteps ) { - let elevationSteps = Math.ceil(additionalElevationStepsRemaining / stepsRemaining); + let elevationSteps = Math.ceil(additionalElevationStepsRemaining / stepsRemaining); while ( elevationSteps > 0 ) { currOffset.k += 1; elevationSteps -= 1; @@ -143,9 +150,6 @@ function directPath3dSquare(start, end, path2d) { return path3d; } - - - /** * Construct a function to determine the offset cost for this canvas for a single 3d move on a square grid. * @param {number} numDiagonals @@ -161,7 +165,7 @@ function singleOffsetSquareDistanceFn(numDiagonals = 0) { if ( canvas.grid.diagonals === D.ALTERNATING_1 || canvas.grid.diagonals === D.ALTERNATING_2 ) { const kFn = canvas.grid.diagonals === D.ALTERNATING_1 ? () => nDiag & 1 ? 2 : 1 - : () => nDiag & 1 ? 1 : 2; + : () => nDiag & 1 ? 1 : 2; fn = (prevOffset, currOffset) => { const isElevationMove = prevOffset.k !== currOffset.k; const isStraight2dMove = (prevOffset.i === currOffset.i) ^ (prevOffset.j === currOffset.j); @@ -171,16 +175,16 @@ function singleOffsetSquareDistanceFn(numDiagonals = 0) { const d2 = isDiagonal2dMove && isElevationMove; if ( d1 || d2 ) nDiag++; const k = kFn(); - return (s + k * d1 + k * d2) * canvas.grid.distance; + return (s + (k * d1) + (k * d2)) * canvas.grid.distance; }; } else { let k = 1; let k2 = 1; switch ( canvas.grid.diagonals ) { - case D.EQUIDISTANT: k = 1; k2 = 1; break; - case D.EXACT: k = Math.SQRT2; k2 = Math.SQRT3; break; - case D.APPROXIMATE: k = 1.5; k2 = 1.75; break; - case D.RECTILINEAR: k = 2; k2 = 3; break; + case D.EQUIDISTANT: k = 1; k2 = 1; break; + case D.EXACT: k = Math.SQRT2; k2 = Math.SQRT3; break; + case D.APPROXIMATE: k = 1.5; k2 = 1.75; break; + case D.RECTILINEAR: k = 2; k2 = 3; break; } fn = (prevOffset, currOffset) => { const isElevationMove = prevOffset.k !== currOffset.k; @@ -189,11 +193,11 @@ function singleOffsetSquareDistanceFn(numDiagonals = 0) { const s = isStraight2dMove || (!isDiagonal2dMove && isElevationMove); const d1 = isDiagonal2dMove && !isElevationMove; const d2 = isDiagonal2dMove && isElevationMove; - return (s + k * d1 + k2 * d2) * canvas.grid.distance; + return (s + (k * d1) + (k2 * d2)) * canvas.grid.distance; }; } Object.defineProperty(fn, "diagonals", { - get : () => nDiag + get: () => nDiag }); return fn; } @@ -222,7 +226,7 @@ function directPath3dHex(start, end, path2d) { startOffset.i = path2d[0].i; startOffset.j = path2d[0].j; - // currOffset will be modified in the loop; set to end to get elevation steps now. + // The currOffset will be modified in the loop; set to end to get elevation steps now. const currOffset = GridCoordinates3d.fromObject(end); const path3d = [startOffset.clone()]; @@ -233,8 +237,9 @@ function directPath3dHex(start, end, path2d) { currOffset.setOffset2d(path2d[i]); const doElevationStep = ((i + 1) % doElevationStepMod) === 0; - let elevationSteps = doElevationStep && (elevationStepsRemaining > 0) ? Math.ceil(elevationStepsRemaining / stepsRemaining) : 0; - elevationStepsRemaining -= elevationSteps + let elevationSteps = doElevationStep + && (elevationStepsRemaining > 0) ? Math.ceil(elevationStepsRemaining / stepsRemaining) : 0; + elevationStepsRemaining -= elevationSteps; // Apply the first elevation step as a diagonal upwards move in combination with the canvas 2d move. if ( elevationSteps ) { @@ -269,7 +274,7 @@ function singleOffsetHexDistanceFn(numDiagonals = 0) { if ( canvas.grid.diagonals === D.ALTERNATING_1 || canvas.grid.diagonals === D.ALTERNATING_2 ) { const kFn = canvas.grid.diagonals === D.ALTERNATING_1 ? () => nDiag & 1 ? 2 : 1 - : () => nDiag & 1 ? 1 : 2; + : () => nDiag & 1 ? 1 : 2; fn = (prevOffset, currOffset) => { // For hex moves, no diagonal 2d. Just diagonal if both elevating and moving in 2d. const isElevationMove = prevOffset.k !== currOffset.k; @@ -278,26 +283,26 @@ function singleOffsetHexDistanceFn(numDiagonals = 0) { const d = !s; nDiag += d; const k = kFn(); - return (s + k * d) * canvas.grid.distance; + return (s + (k * d)) * canvas.grid.distance; }; } else { let k = 1; switch ( canvas.grid.diagonals ) { - case D.EQUIDISTANT: k = 1; break; - case D.EXACT: k = Math.SQRT2; break; - case D.APPROXIMATE: k = 1.5; break; - case D.RECTILINEAR: k = 2; break; + case D.EQUIDISTANT: k = 1; break; + case D.EXACT: k = Math.SQRT2; break; + case D.APPROXIMATE: k = 1.5; break; + case D.RECTILINEAR: k = 2; break; } fn = (prevOffset, currOffset) => { const isElevationMove = prevOffset.k !== currOffset.k; const is2dMove = prevOffset.i !== currOffset.i || prevOffset.j !== currOffset.j; const s = isElevationMove ^ is2dMove; const d = !s; - return (s + k * d) * canvas.grid.distance; + return (s + (k * d)) * canvas.grid.distance; }; } Object.defineProperty(fn, "diagonals", { - get : () => nDiag + get: () => nDiag }); return fn; } @@ -337,7 +342,8 @@ function _measurePath(wrapped, waypoints, { cost }, result) { result.segments.forEach(segment => initializeResultObject(segment)); // For each waypoint, project from 3d if the waypoint is a 3d class. - // The projected point can be used to determine distance but not movement cost because the passed coordinates will be incorrect. + // The projected point can be used to determine distance but not movement cost + // because the passed coordinates will be incorrect. // Movement cost requires knowing the 3d positions. // Cannot combine the projected waypoints to measure all at once, b/c they would be misaligned. // Copy the waypoint so it can be manipulated. diff --git a/scripts/module.js b/scripts/module.js index 2cf4a24..0ba38fc 100644 --- a/scripts/module.js +++ b/scripts/module.js @@ -105,7 +105,7 @@ Hooks.once("init", function() { * Settings related to the ruler text labels. */ labeling: { - /** + /** * Ruler label styles */ styles: { @@ -170,7 +170,7 @@ Hooks.once("init", function() { Settings }; - loadTemplates(Object.values(TEMPLATES)).then(_value => log(`Templates loaded.`)); + loadTemplates(Object.values(TEMPLATES)).then(_value => log("Templates loaded.")); }); // Setup is after init; before ready. diff --git a/scripts/patching.js b/scripts/patching.js index b860270..cfe73dd 100644 --- a/scripts/patching.js +++ b/scripts/patching.js @@ -32,12 +32,12 @@ const PATCHES = { ClientKeybindings: PATCHES_ClientKeybindings, ClientSettings: PATCHES_ClientSettings, CombatTracker: PATCHES_CombatTracker, - ["foundry.canvas.edges.CanvasEdges"]: PATCHES_CanvasEdges, + "foundry.canvas.edges.CanvasEdges": PATCHES_CanvasEdges, DrawingConfig: PATCHES_DrawingConfig, - ["foundry.grid.GridlessGrid"]: PATCHES_GridlessGrid, - ["foundry.grid.HexagonalGrid"]: PATCHES_HexagonalGrid, - ["foundry.grid.SquareGrid"]: PATCHES_SquareGrid, - ["CONFIG.Canvas.rulerClass"]: PATCHES_Ruler, + "foundry.grid.GridlessGrid": PATCHES_GridlessGrid, + "foundry.grid.HexagonalGrid": PATCHES_HexagonalGrid, + "foundry.grid.SquareGrid": PATCHES_SquareGrid, + "CONFIG.Canvas.rulerClass": PATCHES_Ruler, Token: mergeObject(mergeObject(PATCHES_Token, PATCHES_TokenPF), PATCHES_TokenHUD), Wall: PATCHES_Wall }; diff --git a/scripts/segment_labels_highlighting.js b/scripts/segment_labels_highlighting.js index 0d414cc..aa7b3f3 100644 --- a/scripts/segment_labels_highlighting.js +++ b/scripts/segment_labels_highlighting.js @@ -93,7 +93,7 @@ export function segmentElevationLabel(ruler, segment) { if ( displayTotalChange ) { const segmentArrow = (elevationDelta > 0) ? "↑" :"↓"; let totalChange = `[${segmentArrow}${Math.abs(roundMultiple(elevationDelta))}`; - totalChange += (units ? ` ${units}]` : `]`); + totalChange += (units ? ` ${units}]` : "]"); labelParts.push(totalChange); } segment.label.style.align = segment.last ? "center" : "right"; @@ -240,7 +240,8 @@ export function customizedTextLabel(ruler, segment, origLabel = "") { descriptor: game.i18n.localize(`${MODULE_ID}.added`) }; - // Align so that the icon is left justified and the value is right justified. This aligns the units label or descriptor. + // Align so that the icon is left justified and the value is right justified. + // This aligns the units label or descriptor. alignLeftAndRight(childLabels); // Build the string for each. @@ -313,7 +314,7 @@ function getDefaultLabel(segment) { function alignChildTextLeft(parent, child, priorChildren = []) { parent.anchor = { x: 0.5, y: 0.5 }; - child.anchor = { x: 0.5, y: 0.5 } + child.anchor = { x: 0.5, y: 0.5 }; /* Align relative to center of parent and child. -----•----- 11 @@ -337,7 +338,7 @@ function alignChildTextLeft(parent, child, priorChildren = []) { */ const SPACER = "\u200A"; // See https://unicode-explorer.com/articles/space-characters. function alignLeftAndRight(childLabels) { - const labelStyles = CONFIG[MODULE_ID].labeling.styles; + const labelStyles = CONFIG[MODULE_ID].labeling.styles; let targetWidth = 0; Object.entries(childLabels).forEach(([name, obj]) => { obj.iconValueStr = `${obj.icon} ${roundMultiple(obj.value)}`; diff --git a/scripts/segments.js b/scripts/segments.js index ba6e09c..7a313de 100644 --- a/scripts/segments.js +++ b/scripts/segments.js @@ -94,7 +94,12 @@ export function constructPathfindingSegments(segments, segmentMap) { for ( let i = 1; i < nPoints; i += 1 ) { const currPt = pathPoints[i]; currPt.z ??= A.z; - const newSegment = { ray: new Ray3d(prevPt, currPt), waypoint: {}, history: segment.history, teleport: segment.teleport }; + const newSegment = { + ray: new Ray3d(prevPt, currPt), + waypoint: {}, + history: segment.history, + teleport: segment.teleport + }; newSegment.waypoint.idx = segment.waypoint.idx; newSegments.push(newSegment); prevPt = currPt; @@ -126,7 +131,6 @@ export function elevateSegments(ruler, segments) { // Add destination as the fi const nHistory = ruler.history.length; for ( let i = 0, n = segments.length; i < n; i += 1 ) { const segment = segments[i]; - // segment.first = i === 0; segment.waypoint = { idx: Math.max(i - nHistory, -1) }; } @@ -137,7 +141,7 @@ export function elevateSegments(ruler, segments) { // Add destination as the fi _userElevationIncrements: 0, _forceToGround: Settings.FORCE_TO_GROUND, elevation: ruler.destinationElevation - } + }; const waypoints = [...ruler.waypoints, destWaypoint]; for ( const segment of segments ) { if ( segment.history ) { diff --git a/scripts/settings.js b/scripts/settings.js index ba3d600..fe79773 100644 --- a/scripts/settings.js +++ b/scripts/settings.js @@ -73,15 +73,15 @@ const SETTINGS = { } }, -// GRID_TERRAIN: { -// ALGORITHM: "grid-terrain-algorithm", -// CHOICES: { -// CENTER: "grid-terrain-choice-center-point", -// PERCENT: "grid-terrain-choice-percent-area", -// EUCLIDEAN: "grid-terrain-choice-euclidean" -// }, -// AREA_THRESHOLD: "grid-terrain-area-threshold" -// }, + // GRID_TERRAIN: { + // ALGORITHM: "grid-terrain-algorithm", + // CHOICES: { + // CENTER: "grid-terrain-choice-center-point", + // PERCENT: "grid-terrain-choice-percent-area", + // EUCLIDEAN: "grid-terrain-choice-euclidean" + // }, + // AREA_THRESHOLD: "grid-terrain-area-threshold" + // }, }; @@ -344,33 +344,33 @@ export class Settings extends ModuleSettingsAbstract { }); // ----- NOTE: Grid Terrain Measurement ----- // -// register(KEYS.GRID_TERRAIN.ALGORITHM, { -// name: localize(`${KEYS.GRID_TERRAIN.ALGORITHM}.name`), -// hint: localize(`${KEYS.GRID_TERRAIN.ALGORITHM}.hint`), -// scope: "world", -// config: true, -// default: KEYS.GRID_TERRAIN.CHOICES.CENTER, -// type: String, -// choices: { -// [KEYS.GRID_TERRAIN.CHOICES.CENTER]: localize(`${KEYS.GRID_TERRAIN.CHOICES.CENTER}`), -// [KEYS.GRID_TERRAIN.CHOICES.PERCENT]: localize(`${KEYS.GRID_TERRAIN.CHOICES.PERCENT}`), -// [KEYS.GRID_TERRAIN.CHOICES.EUCLIDEAN]: localize(`${KEYS.GRID_TERRAIN.CHOICES.EUCLIDEAN}`) -// } -// }); -// -// register(KEYS.GRID_TERRAIN.AREA_THRESHOLD, { -// name: localize(`${KEYS.GRID_TERRAIN.AREA_THRESHOLD}.name`), -// hint: localize(`${KEYS.GRID_TERRAIN.AREA_THRESHOLD}.hint`), -// scope: "world", -// config: true, -// default: 0.5, -// type: Number, -// range: { -// min: 0.1, -// max: 1, -// step: 0.1 -// } -// }); + // register(KEYS.GRID_TERRAIN.ALGORITHM, { + // name: localize(`${KEYS.GRID_TERRAIN.ALGORITHM}.name`), + // hint: localize(`${KEYS.GRID_TERRAIN.ALGORITHM}.hint`), + // scope: "world", + // config: true, + // default: KEYS.GRID_TERRAIN.CHOICES.CENTER, + // type: String, + // choices: { + // [KEYS.GRID_TERRAIN.CHOICES.CENTER]: localize(`${KEYS.GRID_TERRAIN.CHOICES.CENTER}`), + // [KEYS.GRID_TERRAIN.CHOICES.PERCENT]: localize(`${KEYS.GRID_TERRAIN.CHOICES.PERCENT}`), + // [KEYS.GRID_TERRAIN.CHOICES.EUCLIDEAN]: localize(`${KEYS.GRID_TERRAIN.CHOICES.EUCLIDEAN}`) + // } + // }); + // + // register(KEYS.GRID_TERRAIN.AREA_THRESHOLD, { + // name: localize(`${KEYS.GRID_TERRAIN.AREA_THRESHOLD}.name`), + // hint: localize(`${KEYS.GRID_TERRAIN.AREA_THRESHOLD}.hint`), + // scope: "world", + // config: true, + // default: 0.5, + // type: Number, + // range: { + // min: 0.1, + // max: 1, + // step: 0.1 + // } + // }); } static registerKeybindings() { @@ -425,7 +425,7 @@ export class Settings extends ModuleSettingsAbstract { { key: "Equal" } ], onDown: context => { - if ( canvas.controls?.ruler._isTokenRuler ) toggleTokenRulerWaypoint(context, true); + if ( canvas.controls?.ruler._isTokenRuler ) toggleTokenRulerWaypoint(context, true); }, precedence: CONST.KEYBINDING_PRECEDENCE.NORMAL }); @@ -467,7 +467,7 @@ export class Settings extends ModuleSettingsAbstract { editable: [ { key: "KeyG" } ], - onDown: _context => { // eslint-disable-line no-unused-vars + onDown: _context => { const ruler = canvas.controls.ruler; if ( !ruler.active ) return; this.FORCE_TO_GROUND = !this.FORCE_TO_GROUND; @@ -511,7 +511,7 @@ export class Settings extends ModuleSettingsAbstract { const t2 = performance.now(); console.group(`${MODULE_ID}|Initialized scene graph and pathfinding.`); - console.debug(`${MODULE_ID}|Constructed scene graph in ${t1 - t0} ms.`) + console.debug(`${MODULE_ID}|Constructed scene graph in ${t1 - t0} ms.`); console.debug(`${MODULE_ID}|Tracked ${SCENE_GRAPH.wallIds.size} walls.`); console.debug(`Tracked ${SCENE_GRAPH.tokenIds.size} tokens.`); console.debug(`Located ${SCENE_GRAPH.edges.size} distinct edges.`); @@ -543,7 +543,7 @@ export class Settings extends ModuleSettingsAbstract { Pathfinder.dirty = true; const res = SCENE_GRAPH._checkInternalConsistency(); if ( !res.allConsistent ) { - log(`WallTracer|setTokenBlocksPathfinding ${document.id} resulted in inconsistent graph.`, SCENE_GRAPH, res); + log("WallTracer|setTokenBlocksPathfinding resulted in inconsistent graph.", SCENE_GRAPH, res); SCENE_GRAPH._reset(); } } diff --git a/scripts/system_attributes.js b/scripts/system_attributes.js index ec3f9e2..3f52c4b 100644 --- a/scripts/system_attributes.js +++ b/scripts/system_attributes.js @@ -37,7 +37,7 @@ const MaximumSpeedCategory = { name: "Maximum", color: Color.from(0xff0000), multiplier: Number.POSITIVE_INFINITY -} +}; Hooks.once("init", function() { // Set the default speed parameters for the given system. diff --git a/scripts/terrain_elevation.js b/scripts/terrain_elevation.js index 2db488a..094124d 100644 --- a/scripts/terrain_elevation.js +++ b/scripts/terrain_elevation.js @@ -18,7 +18,8 @@ Path endpoint is adjusted such that it goes up/down. Elevation increments here are relative to ground position. To avoid recursion and infinite loops: - Get the ground at the destination based on starting elevation ± user increments. - Measure a path from start to destination at the ending elevation. May or may not be at the end elevation. -- Each waypoint has a set elevation, based on its measured path. Only the destination waypoint can change, and only it is measured. +- Each waypoint has a set elevation, based on its measured path. + Only the destination waypoint can change, and only it is measured. Ruler Foundry default display: @@ -76,13 +77,8 @@ segment 0: • moveDistance • numDiagonal • numPrevDiagonalf - - */ - - - /* When dragging tokens Origin: each token elevation Other: terrain elevation for each token @@ -185,13 +181,16 @@ export function elevationFromWaypoint(waypoint, location, token) { // For normal ruler, if hovering over a token, use that token's elevation. // Use the maximum token elevation unless terrain is above us (e.g., tile above). - if ( !Settings.FORCE_TO_GROUND ) maxTokenE = maxTokenElevationAtLocation(location, terrainE > waypoint.elevation ? terrainE : undefined); + if ( !Settings.FORCE_TO_GROUND ) { + maxTokenE = maxTokenElevationAtLocation(location, terrainE > waypoint.elevation ? terrainE : undefined); + } if ( maxTokenE ) locationElevation = maxTokenE; // If the starting elevation is on the ground or force-to-ground is enabled, use the ground elevation. else locationElevation = elevationAtLocation(location, { startE: waypoint.elevation, - forceToGround: Settings.FORCE_TO_GROUND || waypoint.elevation.almostEqual(terrainElevationAtLocation(waypoint, waypoint.elevation)) + forceToGround: Settings.FORCE_TO_GROUND + || waypoint.elevation.almostEqual(terrainElevationAtLocation(waypoint, waypoint.elevation)) }); } else locationElevation = tokenElevationForMovement(waypoint, location, { token, @@ -205,7 +204,8 @@ export function elevationFromWaypoint(waypoint, location, token) { * @param {Point} location Location for which elevation is desired * @param {number} startE Elevation at the starting point * @param {object} [opts] - * @param {boolean} [opts.forceToGround=false] If true, override the end elevation with nearest ground to that 3d point. + * @param {boolean} [opts.forceToGround=false] If true, override the end elevation with + * nearest ground to that 3d point * @returns {number} The destination elevation, in grid units */ function elevationAtLocation(location, { startE = 0, forceToGround = false } ) { @@ -218,7 +218,8 @@ function elevationAtLocation(location, { startE = 0, forceToGround = false } ) { * @param {RegionMovementWaypoint} start Start location with elevation property * @param {Point} location Desired end location * @param {object} [opts] - * @param {boolean} [opts.forceToGround=false] If true, override the end elevation with nearest ground to that 3d point. + * @param {boolean} [opts.forceToGround=false] If true, override the end elevation + * with nearest ground to that 3d point * @returns {number} The destination elevation, in grid units */ function tokenElevationForMovement(start, location, opts = {}) { @@ -310,7 +311,7 @@ function retrieveVisibleTokens() { * @returns {Number|undefined} Point elevation or null if module not active or no region at location. */ function TMElevationAtPoint(location, startingElevation = Number.POSITIVE_INFINITY) { - const api = MODULES_ACTIVE.API.TERRAIN_MAPPER + const api = MODULES_ACTIVE.API.TERRAIN_MAPPER; if ( !api || !api.ElevationHandler ) return undefined; const waypoint = { ...location, elevation: startingElevation }; const res = api.ElevationHandler.nearestGroundElevation(waypoint); @@ -326,7 +327,7 @@ function TMElevationAtPoint(location, startingElevation = Number.POSITIVE_INFINI * @returns {number|undefined} Elevation, in grid units */ function TMElevationForMovement(start, end, opts) { - const api = MODULES_ACTIVE.API.TERRAIN_MAPPER + const api = MODULES_ACTIVE.API.TERRAIN_MAPPER; if ( !api || !api.ElevationHandler ) return undefined; return TMPathForMovement(start, end, opts).at(-1)?.elevation; } @@ -342,7 +343,7 @@ function TMElevationForMovement(start, end, opts) { function TMPathForMovement(start, end, opts) { start.elevation ??= CONFIG.GeometryLib.utils.pixelsToGridUnits(start.z); end.elevation ??= CONFIG.GeometryLib.utils.pixelsToGridUnits(end.z); - const api = MODULES_ACTIVE.API.TERRAIN_MAPPER + const api = MODULES_ACTIVE.API.TERRAIN_MAPPER; if ( !api || !api.ElevationHandler ) return [start, end]; return api.ElevationHandler.constructPath(start, end, opts); } @@ -387,7 +388,7 @@ export function LevelsElevationAtPoint(p, startingElevation = 0) { */ function levelsTilesAtPoint({x, y}) { const bounds = new PIXI.Rectangle(x, y, 1, 1); - const collisionTest = (o, rect) => { // eslint-disable-line no-unused-vars + const collisionTest = (o, rect) => { // The object o constains n (Quadtree node), r (rect), t (object to test) const flags = o.t.document?.flags?.levels; if ( !flags ) return false; diff --git a/scripts/token_hud.js b/scripts/token_hud.js index 7ced485..3ffcaa0 100644 --- a/scripts/token_hud.js +++ b/scripts/token_hud.js @@ -1,5 +1,6 @@ /* globals CONFIG, +document, game */ "use strict"; diff --git a/scripts/token_speed.js b/scripts/token_speed.js index 424b127..47cdb10 100644 --- a/scripts/token_speed.js +++ b/scripts/token_speed.js @@ -1,5 +1,6 @@ /* globals canvas, +CONFIG, CONST, game, PIXI @@ -54,7 +55,7 @@ export function tokenSpeedSegmentSplitter(ruler, token) { } const processed = []; - const unprocessed = [segment] + const unprocessed = [segment]; while ( (segment = unprocessed.pop()) ) { // Skip speed categories that do not provide a distance larger than the last. while ( speedCategory && maxDistance <= minDistance ) { @@ -160,7 +161,7 @@ function targetSplitForSegment(targetCost, a, b, numPrevDiagonal = 0) { // Use pixel (integer) steps so the points are at integer bounds. const MAX_ITER = 100; let bestDist = 0; - let step = Math.floor(totalDist) + let step = Math.floor(totalDist); let iter = 0; while ( step > 1 && iter < MAX_ITER) { iter += 1; @@ -221,7 +222,7 @@ function _splitSegmentAt(segment, breakpoint, numPrevDiagonal = 0) { s0.cumulativeDistance = segment.cumulativeDistance - s1.distance; s0.cumulativeOffsetDistance = segment.cumulativeOffsetDistance - s1.offsetDistance; - // s1 waypoint should equal the segment waypoint. + // The s1 waypoint should equal the segment waypoint. s0.waypoint.distance = segment.waypoint.distance - s1.distance; s0.waypoint.offsetDistance = segment.waypoint.offsetDistance - s1.offsetDistance; s0.waypoint.cost = segment.waypoint.cost - s1.cost; diff --git a/scripts/util.js b/scripts/util.js index 72c63ae..5251c2d 100644 --- a/scripts/util.js +++ b/scripts/util.js @@ -2,6 +2,7 @@ canvas CONFIG, CONST, +Handlebars, PIXI, renderTemplate */ @@ -13,7 +14,7 @@ import { Settings } from "./settings.js"; export function log(...args) { try { if ( CONFIG[MODULE_ID].debug ) console.debug(MODULE_ID, "|", ...args); - } catch(e) { // eslint-disable-line no-unused-vars + } catch(e) { // Empty } } @@ -23,7 +24,7 @@ export function log(...args) { * @param {number} n * @returns {boolean} */ -export function isEven(n) { return ~n & 1; } +export function isEven(n) { return ~n & 1; } /** * Is this number odd? @@ -240,7 +241,6 @@ export function * iterateGridUnderLine(origin, destination, { reverse = false } const offset = canvas.grid.getOffset({x, y}); const r1 = offset.i; const c1 = offset.j; - // const [r1, c1] = canvas.grid.grid.getGridPositionFromPixels(x, y); if ( r0 === r1 && c0 === c1 ) continue; // Skip the first one @@ -250,8 +250,6 @@ export function * iterateGridUnderLine(origin, destination, { reverse = false } const {x: xh, y: yh} = origin.projectToward(destination, th); const hOffset = canvas.grid.getOffset({ x: xh, y: yh }); yield [hOffset.i, hOffset.j]; - - // yield canvas.grid.grid.getGridPositionFromPixels(xh, yh); // [rh, ch] } // After so the halfway point is done first. @@ -285,7 +283,7 @@ export function renderTemplateSync(path, data) { * @param {number} num The number to round * @returns {number} The rounded number */ -export function roundMultiple (num) { +export function roundMultiple(num) { const multiple = Settings.get(Settings.KEYS.LABELING.ROUND_TO_MULTIPLE); if (multiple) return num.toNearest(multiple); return num; From 751393de4c05ab8b8f2a9d844834d2079e7293e2 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Sat, 31 Aug 2024 10:05:04 -0700 Subject: [PATCH 06/12] =?UTF-8?q?=F0=9F=90=9B=20fix|MovePenalty|Incorrect?= =?UTF-8?q?=20measurement=20for=20proportional?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- languages/en.json | 4 ++-- scripts/measurement/MovePenalty.js | 33 ++++++++++++++++++------------ 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/languages/en.json b/languages/en.json index 5421a4b..bdd6fd1 100644 --- a/languages/en.json +++ b/languages/en.json @@ -114,7 +114,7 @@ "elevationruler.settings.scale-text.hint": "When enabled, ruler text will be scaled depending on `canvas.dimensions.size`. Further adjust scaling by using `CONFIG.elevationruler.textScale`.", "elevationruler.settings.force-grid-penalties.name": "Use Grid for Movement Penalties", - "elevationruler.settings.force-grid-penalties.hint": "By default, movement penalties are applied proportionally to the move. If a drawing with a x2 move penalty covers 3/4 of a grid square, it will apply to 3/4 of the move through that square (i.e., 1.5x penalty). When enabled, the penalty applies to a move through the entire grid square if, and only if, the penalty applies at the center of the grid. For example, if the drawing does not cover the center of the grid square, no movement through that grid square will be penalized.", + "elevationruler.settings.force-grid-penalties.hint": "When enabled, move penalties only apply if the token/drawing/region covers the center of the hex/square and penalties are applied per-square or per-hex on gridded maps. When disabled, movement penalties are applied proportionally to the move. For example, if a drawing with a x2 move penalty covers 3/4 of a grid square, it will apply to 3/4 of the move through that square (i.e., 1.5x penalty).", "elevationruler.settings.customized-labels.name": "Customized Ruler Text", "elevationruler.settings.customized-labels.hint": "Customize the ruler text. Change styles using `CONFIG.elevationruler.labelStyles` and `CONFIG.elevationruler.labelIcons`.", @@ -126,7 +126,7 @@ "elevationruler.drawingconfig.movementPenalty.hint": "Set to 1 for no penalty, less than one to grant a bonus, greater than 1 to impose a penalty multiplier. For example, set to 2 to double movement cost through this area. Movement under the drawing elevation will be ignored.", "elevationruler.drawingconfig.flatMovementPenalty.name": "Use Flat Penalty", - "elevationruler.drawingconfig.flatMovementPenalty.hint": "If enabled, treats the penalty as a fixed amount. For example, set to 5 to add +5 to each square of movement. Negative values provide flat bonuses to movement.", + "elevationruler.drawingconfig.flatMovementPenalty.hint": "If enabled, treats the penalty as a fixed amount of additional distance. For example, set to 5 to add +5 to each square of movement. Negative values provide flat bonuses to movement.", "elevationruler.clearMovement": "Clear Combatant Movement", "elevationruler.waypoint": "waypoint", diff --git a/scripts/measurement/MovePenalty.js b/scripts/measurement/MovePenalty.js index 40c993b..4310c6a 100644 --- a/scripts/measurement/MovePenalty.js +++ b/scripts/measurement/MovePenalty.js @@ -165,7 +165,7 @@ export class MovePenalty { * @returns {number} The costFreeDistance + cost, in grid units. */ movementCostForSegment(startCoords, endCoords, costFreeDistance = 0, forceGridPenalty) { // eslint-disable-line default-param-last - forceGridPenalty ??= Settings.get(Settings.KEYS.FORCE_GRID_PENALTIES); + forceGridPenalty ??= Settings.get(Settings.KEYS.MEASURING.FORCE_GRID_PENALTIES); forceGridPenalty &&= !canvas.grid.isGridless; // Did we already test this segment? @@ -214,11 +214,11 @@ export class MovePenalty { movementCostForGridSpace(coords, costFreeDistance = 0) { // Determine what regions, tokens, drawings overlap the center point. const centerPt = coords.center; - const regions = this.regions.filter(r => r.testPoint(centerPt, centerPt.elevation)); - const tokens = this.tokens.filter(t => t.constrainedTokenBorder.contains(centerPt.x, centerPt.y) + const regions = [...this.regions].filter(r => r.testPoint(centerPt, centerPt.elevation)); + const tokens = [...this.tokens].filter(t => t.constrainedTokenBorder.contains(centerPt.x, centerPt.y) && centerPt.elevation.between(t.bottomE, t.topE)); - const drawings = this.drawings.filter(d => d.contains(centerPt.x, centerPt.y) - && d.elevationE >= centerPt.elevation); + const drawings = [...this.drawings].filter(d => d.bounds.contains(centerPt.x, centerPt.y) + && d.elevationE <= centerPt.elevation); // Track all speed multipliers and flat penalties for the grid space. let flatPenalty = 0; @@ -232,8 +232,8 @@ export class MovePenalty { }); // Tokens - const tokenMultiplier = this.tokenMultiplier; - const useTokenFlat = this.useFlatTokenMultiplier; + const tokenMultiplier = this.constructor.tokenMultiplier; + const useTokenFlat = this.constructor.useFlatTokenMultiplier; if ( useTokenFlat ) flatPenalty += (tokenMultiplier * tokens.length); else currentMultiplier *= (tokenMultiplier * tokens.length); @@ -321,8 +321,8 @@ export class MovePenalty { if ( !cutawayIxs.length ) return 1; // Tokens - const tokenMultiplier = this.tokenMultiplier; - const useTokenFlat = this.useFlatTokenMultiplier; + const tokenMultiplier = this.constructor.tokenMultiplier; + const useTokenFlat = this.constructor.useFlatTokenMultiplier; // Regions const testRegions = this.constructor.terrainAPI && this.pathRegions; @@ -333,6 +333,7 @@ export class MovePenalty { // and calculating total time and distance. x meters / y meters/second = x/y seconds const { to2d, convertToDistance } = CONFIG.GeometryLib.utils.cutaway; let totalDistance = 0; + let totalUnmodifiedDistance = 0; let totalTime = 0; let currentMultiplier = 1; let currentFlat = 0; @@ -366,8 +367,14 @@ export class MovePenalty { // Now we have prevIx --> ix. prevIx.flat = currentFlat; prevIx.multiplier = currentMultiplier; - prevIx.dist = PIXI.Point.distanceBetween(prevIx, ix); - prevIx.tokenSpeed = ((this.speedFn(tClone) || 1) * prevIx.multiplier) + prevIx.flat; + prevIx.dist = CONFIG.GeometryLib.utils.pixelsToGridUnits(PIXI.Point.distanceBetween(prevIx, ix)); + totalUnmodifiedDistance += prevIx.dist; + + // Speed is adjusted when moving through regions with a multiplier. + prevIx.tokenSpeed = ((this.speedFn(tClone) || 1) * prevIx.multiplier); + + // Flat adds extra distance to the grid square. Diagonal is longer, so will have larger penalty. + prevIx.dist += (prevIx.dist * currentFlat / canvas.grid.distance); totalDistance += prevIx.dist; totalTime += (prevIx.dist / prevIx.tokenSpeed); prevIx = ix; @@ -388,9 +395,9 @@ export class MovePenalty { } // Determine the ratio compared to a set speed - const totalDefaultTime = totalDistance / startingSpeed; + const totalDefaultTime = totalUnmodifiedDistance / startingSpeed; const avgMultiplier = (totalDefaultTime / totalTime) || 0; - return avgMultiplier; + return 1 / avgMultiplier; } /** From 431876427da3f1aae898361ac69c799c116d8c4b Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Sat, 31 Aug 2024 10:17:52 -0700 Subject: [PATCH 07/12] =?UTF-8?q?=F0=9F=90=9B=20fix|Ruler=20labels|Movemen?= =?UTF-8?q?t=20cost=20displaying=20as=20distance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/Ruler.js | 4 +++- scripts/segment_labels_highlighting.js | 12 +++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/scripts/Ruler.js b/scripts/Ruler.js index 4fa3abc..ac96a5b 100644 --- a/scripts/Ruler.js +++ b/scripts/Ruler.js @@ -519,9 +519,11 @@ function _getSegmentLabel(wrapped, segment) { } // Force distance to be between waypoints instead of (possibly pathfinding) segments. + // Use cost instead of straight distance for the label. const origSegmentDistance = segment.distance; const origTotalDistance = this.totalDistance; - segment.distance = roundMultiple(segment.waypoint.distance); + segment.distance = roundMultiple(segment.waypoint.cost); + this.totalDistance = this.totalCost; const origLabel = wrapped(segment); segment.distance = origSegmentDistance; this.totalDistance = origTotalDistance; diff --git a/scripts/segment_labels_highlighting.js b/scripts/segment_labels_highlighting.js index aa7b3f3..a4bf59f 100644 --- a/scripts/segment_labels_highlighting.js +++ b/scripts/segment_labels_highlighting.js @@ -137,13 +137,15 @@ function elevationForRulerLabel(ruler, segment) { export function segmentTerrainLabel(s) { if ( s.waypoint.cost.almostEqual(s.waypoint.offsetDistance) ) return ""; const units = (canvas.scene.grid.units) ? ` ${canvas.scene.grid.units}` : ""; - const moveDistance = roundMultiple(s.waypoint.cost); + const addedCost = roundMultiple(s.waypoint.cost - s.waypoint.offsetDistance); + const symbol = addedCost > 0 ? "+" : "-"; + if ( CONFIG[MODULE_ID].SPEED.useFontAwesome ) { const style = s.label.style; if ( !style.fontFamily.includes("fontAwesome") ) style.fontFamily += ",fontAwesome"; - return `\n${CONFIG[MODULE_ID].SPEED.terrainSymbol} ${moveDistance}${units}`; + return `\n${CONFIG[MODULE_ID].SPEED.terrainSymbol} ${symbol}${Math.abs(addedCost)}${units}`; } - return `\n${CONFIG[MODULE_ID].SPEED.terrainSymbol} ${moveDistance}${units}`; + return `\n${CONFIG[MODULE_ID].SPEED.terrainSymbol} ${symbol}${Math.abs(addedCost)}${units}`; } @@ -202,7 +204,7 @@ export function customizedTextLabel(ruler, segment, origLabel = "") { const childLabels = {}; // (1) Total Distance - let totalDistLabel = segment.last ? `${roundMultiple(ruler.totalDistance)}` : `${labelIcons.waypoint} ${roundMultiple(segment.waypoint.distance)}`; + let totalDistLabel = segment.last ? `${roundMultiple(ruler.totalCost)}` : `${labelIcons.waypoint} ${roundMultiple(segment.waypoint.cost)}`; // (2) Extra text // Strip out any custom text from the original label. @@ -212,7 +214,7 @@ export function customizedTextLabel(ruler, segment, origLabel = "") { // (3) Waypoint if ( segment.last && segment.waypoint.idx > 0 ) childLabels.waypoint = { icon: `${labelIcons.waypoint}`, - value: segment.waypoint.distance, + value: segment.waypoint.cost, descriptor: game.i18n.localize(`${MODULE_ID}.waypoint`) }; From cb118d31aeede347fbd708f46fb2b1506380f876 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Sat, 31 Aug 2024 10:32:31 -0700 Subject: [PATCH 08/12] =?UTF-8?q?=F0=9F=90=9B=20fix|Pathfinding|Wall=20blo?= =?UTF-8?q?cking=20when=20equal=20to=20elevation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Don't block if elevation exactly equal to top or bottom --- scripts/pathfinding/WallTracer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/pathfinding/WallTracer.js b/scripts/pathfinding/WallTracer.js index e91d7f0..1b67ecd 100644 --- a/scripts/pathfinding/WallTracer.js +++ b/scripts/pathfinding/WallTracer.js @@ -360,7 +360,7 @@ export class WallTracerEdge extends GraphEdge { * Tested "live" and not cached so door or wall orientation changes need not be tracked. * @param {Wall} wall Wall to test * @param {Point} origin Measure wall blocking from perspective of this origin point. - * @param {number} [elevation=0] Elevation of the point or origin to test. + * @param {number} [elevation=0] Elevation of the point or origin to test, in pixel units. * @returns {boolean} */ static wallBlocks(wall, origin, elevation = 0) { @@ -379,7 +379,7 @@ export class WallTracerEdge extends GraphEdge { && side === wall.document.dir ) return false; // Test for wall height. - if ( !elevation.between(wall.bottomZ, wall.topZ) ) return false; + if ( !elevation.between(wall.bottomZ, wall.topZ, false) ) return false; return true; } From 05e1d054e7b6d1c707dfde5cbc0353803e57ba79 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Sat, 31 Aug 2024 11:16:00 -0700 Subject: [PATCH 09/12] =?UTF-8?q?=F0=9F=90=9B=20fix|libGeometry|Default=20?= =?UTF-8?q?to=20core=20diagonals=20setting=20for=20hex=20grid?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/geometry | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/geometry b/scripts/geometry index ca4d31f..15bae42 160000 --- a/scripts/geometry +++ b/scripts/geometry @@ -1 +1 @@ -Subproject commit ca4d31f0266d32f52927fd7e752b2fd4477eb156 +Subproject commit 15bae42613c94f943c66eb51fe42b70a4a8f3266 From 3952f67f6c9aa0dbecabe54ec9de87dd47c3cd2f Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Sat, 31 Aug 2024 12:17:39 -0700 Subject: [PATCH 10/12] =?UTF-8?q?=F0=9F=90=9B=20fix=20|Measure|Hex=20grid?= =?UTF-8?q?=20NA=20distance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue #188 --- scripts/measurement/Grid.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/scripts/measurement/Grid.js b/scripts/measurement/Grid.js index 57067f1..932d206 100644 --- a/scripts/measurement/Grid.js +++ b/scripts/measurement/Grid.js @@ -1,13 +1,15 @@ /* globals canvas, CONFIG, -CONST +CONST, +game */ /* eslint no-unused-vars: ["error", { "argsIgnorePattern": "^_" }] */ "use strict"; import { GridCoordinates3d } from "../geometry/3d/GridCoordinates3d.js"; import { Point3d } from "../geometry/3d/Point3d.js"; +import { Settings } from "../settings.js"; /** * Modify Grid classes to measure in 3d. @@ -351,6 +353,9 @@ function _measurePath(wrapped, waypoints, { cost }, result) { cost ??= (prevOffset, currOffset, offsetDistance) => offsetDistance; const offsetDistanceFn = getOffsetDistanceFn(0); // Diagonals = 0. const altGridDistanceFn = GridCoordinates3d.alternatingGridDistanceFn(); + let diagonals = canvas.grid.diagonals ?? game.settings.get("core", "gridDiagonals"); + const D = GridCoordinates3d.GRID_DIAGONALS; + if ( diagonals === D.EXACT && Settings.get(Settings.KEYS.MEASURING.EUCLIDEAN_GRID_DISTANCE) ) diagonals = D.EUCLIDEAN; for ( let i = 1, n = waypoints.length; i < n; i += 1 ) { const end = waypoints[i]; const path3d = canvas.grid.getDirectPath([start, end]); @@ -360,7 +365,7 @@ function _measurePath(wrapped, waypoints, { cost }, result) { const prevDiagonals = offsetDistanceFn.diagonals; for ( let j = 1, n = path3d.length; j < n; j += 1 ) { const currPathPt = path3d[j]; - const dist = GridCoordinates3d.gridDistanceBetween(prevPathPt, currPathPt, altGridDistanceFn); + const dist = GridCoordinates3d.gridDistanceBetween(prevPathPt, currPathPt, { altGridDistanceFn, diagonals }); const offsetDistance = offsetDistanceFn(prevPathPt, currPathPt); segment.distance += dist; segment.offsetDistance += offsetDistance; From 36eadf612c0ca312cf9fdb7fc0c5e1c6af85bbd4 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Sat, 31 Aug 2024 12:29:07 -0700 Subject: [PATCH 11/12] =?UTF-8?q?=F0=9F=90=9B=20fix|Settings|Flat=20penalt?= =?UTF-8?q?y=20for=20tokens=20not=20appearing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- languages/en.json | 9 ++++++--- scripts/settings.js | 9 +++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/languages/en.json b/languages/en.json index bdd6fd1..ee922d1 100644 --- a/languages/en.json +++ b/languages/en.json @@ -76,8 +76,11 @@ "elevationruler.settings.round-to-multiple.name": "Round Distance to Multiple", "elevationruler.settings.round-to-multiple.hint": "Round the distance displayed to the nearest multiple of the number entered. Set to 0 to disable rounding.", - "elevationruler.settings.token-terrain-multiplier.name": "Token as Terrain Multiplier", - "elevationruler.settings.token-terrain-multiplier.hint": "Multiplier to use to calculate movement speed when moving through other tokens. Set to 1 to ignore. Values less than 1 treat token spaces as faster than normal; values greater than 1 penalize movement through token spaces.", + "elevationruler.settings.token-terrain-multiplier.name": "Token Penalty Percent", + "elevationruler.settings.token-terrain-multiplier.hint": "Penalize movement through other tokens. Set to 1 for no penalty, less than one to grant a bonus, greater than 1 to impose a penalty multiplier. For example, set to 2 to double movement cost through a token.", + + "elevationruler.settings.token-terrain-multiplier-flat.name": "Flat Token Penalty", + "elevationruler.settings.token-terrain-multiplier-flat.hint": "If enabled, treats the token move penalty as a fixed amount of additional distance. For example, set to 5 to add +5 to each square of movement. Negative values provide a flat bonus.", "elevationruler.settings.pathfinding_enable.name": "Use Pathfinding", "elevationruler.settings.pathfinding_enable.hint": "When enabled, adds a pathfinding togglee to the Token controls that will cause the ruler to map a path around walls and tokens, depending on settings. Disable this if pathfinding is causing compatibility issues. Disabling may also result in a small performance increase.", @@ -126,7 +129,7 @@ "elevationruler.drawingconfig.movementPenalty.hint": "Set to 1 for no penalty, less than one to grant a bonus, greater than 1 to impose a penalty multiplier. For example, set to 2 to double movement cost through this area. Movement under the drawing elevation will be ignored.", "elevationruler.drawingconfig.flatMovementPenalty.name": "Use Flat Penalty", - "elevationruler.drawingconfig.flatMovementPenalty.hint": "If enabled, treats the penalty as a fixed amount of additional distance. For example, set to 5 to add +5 to each square of movement. Negative values provide flat bonuses to movement.", + "elevationruler.drawingconfig.flatMovementPenalty.hint": "If enabled, treats the penalty as a fixed amount of additional distance. For example, set to 5 to add +5 to each square of movement. Negative values provide a flat bonus.", "elevationruler.clearMovement": "Clear Combatant Movement", "elevationruler.waypoint": "waypoint", diff --git a/scripts/settings.js b/scripts/settings.js index fe79773..2076fb7 100644 --- a/scripts/settings.js +++ b/scripts/settings.js @@ -52,7 +52,7 @@ const SETTINGS = { COMBAT_HISTORY: "token-ruler-combat-history", FORCE_GRID_PENALTIES: "force-grid-penalties", TOKEN_MULTIPLIER: "token-terrain-multiplier", - TOKEN_MULTIPLIER_FLAT: "token-terrain-multiplier" + TOKEN_MULTIPLIER_FLAT: "token-terrain-multiplier-flat" }, NO_MODS: "no-modules-message", @@ -318,13 +318,10 @@ export class Settings extends ModuleSettingsAbstract { scope: "world", config: true, default: 1, - type: Number, - range: { - step: 0.1 - } + type: Number }); - register(KEYS.MEASURING.TOKEN_MULTIPLIER, { + register(KEYS.MEASURING.TOKEN_MULTIPLIER_FLAT, { name: localize(`${KEYS.MEASURING.TOKEN_MULTIPLIER_FLAT}.name`), hint: localize(`${KEYS.MEASURING.TOKEN_MULTIPLIER_FLAT}.hint`), scope: "world", From 43acc4d53375a3c7fe556d207558ca86c97112d9 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Sat, 31 Aug 2024 12:38:37 -0700 Subject: [PATCH 12/12] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20docs|Changelog|Add?= =?UTF-8?q?=200.10.6=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Changelog.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Changelog.md b/Changelog.md index a244f09..784adcb 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,14 @@ +# 0.10.6 +## New Features +Setting to apply movement penalties per-grid-space. Defaults to enabled. For gridded scenes, this will (1) count regions/tokens/drawings as imposing movement penalty only if the region/token/drawing overlaps the center point of the grid space and (2) impose the penalty at a grid-space level. If disabled, this will proportionally apply the penalty based on the precise movement path. Closes #181. + +Setting to add (or subtract) a flat amount when moving into a grid space with a token penalty. E.g. +5 per grid square. Add parallel setting to drawing configuration. (May add a similar toggle to regions in Terrain Mapper if this cannot be easily handled using active effects.) Closes #125. + +## Bug fixes and other updates +Correct NaN distance when using hex grids. Closes #188. +Don't treat walls as blocking if the top elevation equals the token elevation. Closes #189. +Update Italian localization. Thanks @GregoryWarn! + # 0.10.5 Fix for `_fromPoint3d` not a function. Closes #186.