diff --git a/README.md b/README.md index f71c950..492d7a1 100644 --- a/README.md +++ b/README.md @@ -272,6 +272,10 @@ Adds a button to the Walls Menu to Shut all doors in the current scene. Also add Changes the functionality from closing ALL doors to closing ONLY opened doors. Doors that are currently locked remain locked, and are not closed. +### [Experimental] Integration of [Combat Range Overlay](https://github.com/Nazrax/fvtt-combat-range-overlay) + +[Here the documentation](./wiki/docs/combat-range-overlay.md) + # Build ## Install all packages @@ -367,6 +371,7 @@ Thanks to anyone who helps me with this code! I appreciate the user community's - [foundryvtt-stairways](https://gitlab.com/SWW13/foundryvtt-stairways) ty to [SWW13](https://gitlab.com/SWW13) - [foundryvtt-rangefinder](https://github.com/manuelVo/foundryvtt-rangefinder/tree/master) ty to [manuelVo](https://github.com/manuelVo) - [drag-ruler](https://github.com/manuelVo/foundryvtt-drag-ruler) ty to [manuelVo](https://github.com/manuelVo) +- [range-overlay](https://github.com/Nazrax/fvtt-combat-range-overlay) ty to [Nazrax](https://github.com/Nazrax/) A very big thanks to [manuelVo](https://github.com/manuelVo), because i was to stupid to understand thing like measurement of Foundry by myself. diff --git a/changelog.md b/changelog.md index 9037c88..284c7e5 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,9 @@ # CHANGELOG +### 2.1.27 + +- Bug fix integration with stairway + ### 2.1.26 - Bug fix diff --git a/package.json b/package.json index eb594a3..0831814 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "name": "foundryvtt-arms-reach", "title": "FoundryVTT Arms Reach", "description": "Allows the GM to limit the distance that a player can interacted with a placeable object like door, journal, stairway, token, ecc..", - "version": "2.1.26", + "version": "2.1.27", "scripts": { "package": "gulp package", "build": "gulp build && gulp link", diff --git a/src/lang/en.json b/src/lang/en.json index c1d37cb..66086c7 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -225,5 +225,7 @@ "foundryvtt-arms-reach.speed-attr-path-hint": "Advanced: Set this only if you're using a System that can't autodetect token Speed", "foundryvtt-arms-reach.info-button": "Basic Information", "foundryvtt-arms-reach.token-speed-warning-gm": "I can't autodetect token speed; please manually set your token's speed and/or set the Speed Attribute in the module settings (and maybe open an issue to add support for your System)", - "foundryvtt-arms-reach.token-speed-warning-player": "Please manually set your token's speed (and maybe ask your GM to configure the Speed Attribute in the module settings)" + "foundryvtt-arms-reach.token-speed-warning-player": "Please manually set your token's speed (and maybe ask your GM to configure the Speed Attribute in the module settings)", + "foundryvtt-arms-reach.ignore-difficult-terrain": "Ignore difficult terrain?", + "foundryvtt-arms-reach.ignore-difficult-terrain-hint": " " } diff --git a/src/module.json b/src/module.json index 53444a2..ffb50ed 100644 --- a/src/module.json +++ b/src/module.json @@ -2,7 +2,7 @@ "name": "foundryvtt-arms-reach", "title": "FoundryVTT Arms Reach", "description": "Allows the GM to limit the distance that a player can interacted with a placeable object like door, journal, stairway, token, ecc..", - "version": "2.1.26", + "version": "2.1.27", "author": "Psyny, p4535992", "type": "module", "socket": true, @@ -46,9 +46,9 @@ "manifestPlusVersion": "1.2.1", "url": "https://github.com/p4535992/foundryvtt-arms-reach", "manifest": "https://github.com/p4535992/foundryvtt-arms-reach/releases/latest/download/module.json", - "download": "https://github.com/p4535992/foundryvtt-arms-reach/releases/download/v2.1.26/module.zip", - "readme": "https://github.com/p4535992/foundryvtt-arms-reach/blob/v2.1.26/README.md", - "changelog": "https://github.com/p4535992/foundryvtt-arms-reach/blob/v2.1.26/changelog.md", + "download": "https://github.com/p4535992/foundryvtt-arms-reach/releases/download/v2.1.27/module.zip", + "readme": "https://github.com/p4535992/foundryvtt-arms-reach/blob/v2.1.27/README.md", + "changelog": "https://github.com/p4535992/foundryvtt-arms-reach/blob/v2.1.27/changelog.md", "bugs": "https://github.com/p4535992/foundryvtt-arms-reach/issues", "allowBugReporter": true, "dependencies": [ diff --git a/src/module/lib/lib.ts b/src/module/lib/lib.ts index c0c1815..5252f71 100644 --- a/src/module/lib/lib.ts +++ b/src/module/lib/lib.ts @@ -234,9 +234,12 @@ export function getElevationPlaceableObject(placeableObject: any): number { ? //@ts-ignore _levels.getTokenLOSheight(placeableObject) : base.elevation ?? - base.flags['levels']?.elevation ?? - base.flags['levels']?.rangeBottom ?? - base.flags['wallHeight']?.wallHeightBottom ?? + (base.flags + ? base.flags['levels']?.elevation ?? + base.flags['levels']?.rangeBottom ?? + base.flags['wallHeight']?.wallHeightBottom ?? + 0 + : 0) ?? 0; return base_elevation; } diff --git a/src/module/module.ts b/src/module/module.ts index 1153da0..1c39b66 100644 --- a/src/module/module.ts +++ b/src/module/module.ts @@ -24,7 +24,7 @@ import { registerSocket } from './socket'; import { Overlay } from './apps/range_overlay/overlay'; import { keyboard } from './apps/range_overlay/keyboard'; import { mouse } from './apps/range_overlay/mouse'; -import { toggleButton, TOGGLE_BUTTON, _toggleButtonClick } from './apps/range_overlay/controls'; +import { TOGGLE_BUTTON, _toggleButtonClick } from './apps/range_overlay/controls'; import { canvasTokensGet } from './apps/range_overlay/utility'; import { TokenInfo, updateLocation, updateMeasureFrom } from './apps/range_overlay/tokenInfo'; @@ -347,6 +347,9 @@ export const readyHooks = async () => { // [EXPERIMENTAL] Range Overlay Integration if (game.settings.get(CONSTANTS.MODULE_NAME, 'enableRangeOverlay')) { Hooks.on('getSceneControlButtons', (controls: SceneControl[]) => { + if (!game.settings.get(CONSTANTS.MODULE_NAME, 'enableRangeOverlay')) { + return; + } const tokenButton = controls.find((b) => b.name == 'token'); if (tokenButton) { @@ -410,6 +413,9 @@ export const readyHooks = async () => { // noinspection JSUnusedLocalSymbols Hooks.on('createCombatant', (combatant, options, someId) => { + if (!game.settings.get(CONSTANTS.MODULE_NAME, 'enableRangeOverlay')) { + return; + } const token = canvasTokensGet(combatant.token.id); updateMeasureFrom(token, undefined); API.combatRangeOverlay.instance.fullRefresh(); @@ -417,6 +423,9 @@ export const readyHooks = async () => { // noinspection JSUnusedLocalSymbols Hooks.on('deleteCombatant', (combatant, options, someId) => { + if (!game.settings.get(CONSTANTS.MODULE_NAME, 'enableRangeOverlay')) { + return; + } const token = canvasTokensGet(combatant.token.id); updateMeasureFrom(token, undefined); API.combatRangeOverlay.instance.fullRefresh(); @@ -424,6 +433,9 @@ export const readyHooks = async () => { // noinspection JSUnusedLocalSymbols Hooks.on('updateCombat', (combat, turnInfo, diff, someId) => { + if (!game.settings.get(CONSTANTS.MODULE_NAME, 'enableRangeOverlay')) { + return; + } if (combat?.previous?.tokenId) { const token = canvasTokensGet(combat.previous.tokenId); updateMeasureFrom(token, undefined); @@ -433,6 +445,9 @@ export const readyHooks = async () => { // noinspection JSUnusedLocalSymbols Hooks.on('updateToken', (tokenDocument, updateData, options, someId) => { + if (!game.settings.get(CONSTANTS.MODULE_NAME, 'enableRangeOverlay')) { + return; + } const tokenId = tokenDocument.id; const realToken = canvasTokensGet(tokenId); // Get the real token updateLocation(realToken, updateData); @@ -443,6 +458,9 @@ export const readyHooks = async () => { }); Hooks.on('controlToken', (token, boolFlag) => { + if (!game.settings.get(CONSTANTS.MODULE_NAME, 'enableRangeOverlay')) { + return; + } if (boolFlag && TokenInfo.current.speed === 0 && TokenInfo.current.getSpeedFromAttributes() === 0) { if (game.user?.isGM) { warn(i18n(`${CONSTANTS.MODULE_NAME}.token-speed-warning-gm`), true); diff --git a/src/module/settings.ts b/src/module/settings.ts index 8240796..5123d81 100644 --- a/src/module/settings.ts +++ b/src/module/settings.ts @@ -374,7 +374,7 @@ export const registerSettings = function () { name: i18n(`${CONSTANTS.MODULE_NAME}.settingNameRangeOverlayFeature`), hint: i18n(`${CONSTANTS.MODULE_NAME}.settingHintRangeOverlayFeature`), scope: 'world', - config: true, + config: false, default: false, type: Boolean, }); @@ -437,7 +437,7 @@ export const registerSettings = function () { name: `${CONSTANTS.MODULE_NAME}.movement-alpha`, hint: `${CONSTANTS.MODULE_NAME}.movement-alpha-hint`, scope: 'client', - config: true, + config: false, type: Number, default: 0.1, range: { @@ -454,7 +454,7 @@ export const registerSettings = function () { name: `${CONSTANTS.MODULE_NAME}.ic_visibility`, hint: `${CONSTANTS.MODULE_NAME}.ic_visibility-hint`, scope: 'client', - config: true, + config: false, type: String, default: `never`, choices: { @@ -471,7 +471,7 @@ export const registerSettings = function () { name: `${CONSTANTS.MODULE_NAME}.ooc_visibility`, hint: `${CONSTANTS.MODULE_NAME}.ooc_visibility-hint`, scope: 'client', - config: true, + config: false, type: String, default: `never`, choices: { @@ -488,7 +488,7 @@ export const registerSettings = function () { name: `${CONSTANTS.MODULE_NAME}.ranges`, hint: `${CONSTANTS.MODULE_NAME}.ranges-hint`, scope: 'client', - config: true, + config: false, type: String, default: '5', onChange: () => { @@ -500,7 +500,7 @@ export const registerSettings = function () { name: `${CONSTANTS.MODULE_NAME}.diagonals.name`, hint: `${CONSTANTS.MODULE_NAME}.diagonals.hint`, scope: 'world', - config: true, + config: false, type: String, default: 'fiveTenFive', choices: { @@ -518,7 +518,7 @@ export const registerSettings = function () { name: `${CONSTANTS.MODULE_NAME}.speed-attr-path`, hint: `${CONSTANTS.MODULE_NAME}.speed-attr-path-hint`, scope: 'world', - config: true, + config: false, type: String, default: '', }); diff --git a/wiki/docs/combat-range-overlay.md b/wiki/docs/combat-range-overlay.md new file mode 100644 index 0000000..caf29d2 --- /dev/null +++ b/wiki/docs/combat-range-overlay.md @@ -0,0 +1,100 @@ +## Summary + +This module is designed to quickly and efficiently answer questions such as "How far can I move this turn? What enemies can I reach in the fewest actions? How can I best navigate difficult terrain?" I wrote it because I (not to mention the rest of my group) was tired of my pulling out Rulers, Blasts, and other helpers to figure out "Can I do _this_? Hmm, no, but maybe if I do it _this_ way ... nope, that doesn't work either. What about ..." + +## Basic Usage + +Click ![the button](../images/the-button.png) to toggle the Overlay on and off. Once the Overlay is enabled, it should Just Work™ with little to no interaction from you. By default, it assumes your weapon has a range of 5 feet; shift-click the button to change it for your currently selected token. Normally the overlay will reread your position at the end of your combat turn; control-click the button to force the Overlay to reposition. Display preferences are available in the module's Settings page. + +## Compatibility +Maps: This module relies on square tiles; I have no idea what would happen if you tried to use it on a map with hex tiles, but I don't think it would go well. + +Systems: My table plays Pathfinder 2E, and that's all I've tested it with. +It turns out that every system stores its token/actor move speeds in a different spot, +so out of the box speed autodetection will only work with Pathfinder 1, Pathfinder 2E, DND3.5, +and DND5E. If you're playing with a different system, you'll need to do some +[extra configuration](#advanced-setting-the-speed-attribute-path). Also, I believe other systems +treat diagonals differently, so there's a (GM-only) setting telling the module how to count +diagonal movement. + +Modules: This module requires lib-wrapper and supports the Enhanced Terrain Layer. + +## Understanding the Overlay + +![legend](../images/legend.jpg) + +The overlay in this image assumes a movement speed of 15ft/action and a weapon range of 10ft. + +1. Tiles tinted blue can be reached in a single action. +2. Tiles tinted yellow can be reached in 2 actions. +3. Enemies circled in white can be attacked without moving. +4. Enemies circled in blue can be attacked in a single move. +5. Enemies circled in yellow can be attacked with 2 moves. +6. Enemies circled in red require 3 or more movements to attack. +7. All tokens (other than the selected token) in combat are annotated with their initiative order relative to the current token. +8. The selected token is annotated with the currently selected weapon range. + +![single target](../images/single-target.jpg) + +If a target is selected, tiles in your movement range _and_ in range of the target will be highlighted in white, and only tiles on the shortest path to the highlighted squares will remain tinted. +If multiple targets are selected, only tiles in range of _all_ targeted enemies will be highlighted. If there's no way to hit all targeted enemies at once, the Overlay will display a warning and act as if no enemies are targeted. + +## Sample Use-cases + +The Overlay is useful no matter what kind of character you're playing as: + +### Melee + +![](../images/melle1.jpg) +Suppose you're trying to decide between these two enemies. + +![](../images/melle2.jpg) +Both enemies are obstructed - one by difficult terrain, one by walls - so a straight ruler won't help you. + +![](../images/melle3.jpg) +You'll need to use waypoints to get the true movement distances. + +![](../images/melle4.jpg) +Or you can use the Overlay to instantly see how many movement actions it'll take to attack each enemy. + +### Archery + +![](../images/archery1.jpg) +You want to attack this enemy, and you'd like to get _just_ close enough to attack him without his being able to close the distance and attack you on his turn. + +![](../images/archery2.jpg) +You drop a Blast on his position and then move to a tile on the very edge of the Blast (of course, working with Blasts takes a lot of control palette switching, clicking, dragging, deleting ... it's kind of a pain). + +![](../images/archery3.jpg) +Or you can use the Overlay to see where you can move to that's inside your attack range and move to the position that's nearest you. + +### Magic + +![](../images/magic1.jpg) +You want to cast Electric Arc (a 2 action, 2 target, 30ft range spell) on these two enemies. Where can you hit them both from? Are they close enough for you to hit them both? Can you reach a good spot in only one action so you'll have the two remaining actions to cast the spell? + +![](../images/magic2.jpg) +You could drop _two_ Blasts and then measure your distance to the overlapping tiles (with waypoints, of course - moving straight through that difficult terrain would be too much). + +![](../images/magic3.jpg) +Or you can use the Overlay to see where you can attack them both from and how far away the good spots are. + +### Tactician + +![](../images/tactician1.jpg) +You're pretty sure you can kill any of these enemies on your turn, and you'd like to kill one that'll go before your teammate to reduce how many enemies there are to attack him (or you). Unfortunately, while the Combat Tracker shows initiative order it doesn't take positioning into account, and trying to figure out which entry in the Combat Tracker corresponds to combatant tokens can be a pain. + +![](../images/tactician2.jpg) +Or you can use the Overlay to see who's close to you _and_ going before your teammate. + +## Advanced: Setting the speed attribute path +If you're using an unsupported System, you'll need to set the speed attribute path in +the module settings. Here's how to do it: +1) Select a token +1) Open your browser's dev tools and switch to the Javascript console +1) Type in `canvas.tokens.controlled[0].actor.data` and press Enter +1) Expand the result, then keep expanding children until you find the movement speed. Take note of each child +you expand + * For instance, with Pathfinder 2E, you expand `data`, `attributes`, `speed`, and find the speed in `total` ![](../images/object_flags.png) +1) Join these names with periods to come up with your attribute path + * For Pathfinder 2E, this would be `data.attributes.speed.total` \ No newline at end of file diff --git a/wiki/images/archery1.jpg b/wiki/images/archery1.jpg new file mode 100644 index 0000000..9a6f276 Binary files /dev/null and b/wiki/images/archery1.jpg differ diff --git a/wiki/images/archery2.jpg b/wiki/images/archery2.jpg new file mode 100644 index 0000000..bc86e53 Binary files /dev/null and b/wiki/images/archery2.jpg differ diff --git a/wiki/images/archery3.jpg b/wiki/images/archery3.jpg new file mode 100644 index 0000000..3a08dcf Binary files /dev/null and b/wiki/images/archery3.jpg differ diff --git a/wiki/images/legend.jpg b/wiki/images/legend.jpg new file mode 100644 index 0000000..7bfae6b Binary files /dev/null and b/wiki/images/legend.jpg differ diff --git a/wiki/images/magic1.jpg b/wiki/images/magic1.jpg new file mode 100644 index 0000000..6112cfd Binary files /dev/null and b/wiki/images/magic1.jpg differ diff --git a/wiki/images/magic2.jpg b/wiki/images/magic2.jpg new file mode 100644 index 0000000..2efbba1 Binary files /dev/null and b/wiki/images/magic2.jpg differ diff --git a/wiki/images/magic3.jpg b/wiki/images/magic3.jpg new file mode 100644 index 0000000..0a18f5f Binary files /dev/null and b/wiki/images/magic3.jpg differ diff --git a/wiki/images/melle1.jpg b/wiki/images/melle1.jpg new file mode 100644 index 0000000..2cd5da1 Binary files /dev/null and b/wiki/images/melle1.jpg differ diff --git a/wiki/images/melle2.jpg b/wiki/images/melle2.jpg new file mode 100644 index 0000000..029a61b Binary files /dev/null and b/wiki/images/melle2.jpg differ diff --git a/wiki/images/melle3.jpg b/wiki/images/melle3.jpg new file mode 100644 index 0000000..aabe816 Binary files /dev/null and b/wiki/images/melle3.jpg differ diff --git a/wiki/images/melle4.jpg b/wiki/images/melle4.jpg new file mode 100644 index 0000000..11be902 Binary files /dev/null and b/wiki/images/melle4.jpg differ diff --git a/wiki/images/object_flags.png b/wiki/images/object_flags.png new file mode 100644 index 0000000..e82c43e Binary files /dev/null and b/wiki/images/object_flags.png differ diff --git a/wiki/images/single-target.jpg b/wiki/images/single-target.jpg new file mode 100644 index 0000000..c16d99d Binary files /dev/null and b/wiki/images/single-target.jpg differ diff --git a/wiki/images/tactician1.jpg b/wiki/images/tactician1.jpg new file mode 100644 index 0000000..fb2cbd1 Binary files /dev/null and b/wiki/images/tactician1.jpg differ diff --git a/wiki/images/tactician2.jpg b/wiki/images/tactician2.jpg new file mode 100644 index 0000000..f7b2c85 Binary files /dev/null and b/wiki/images/tactician2.jpg differ diff --git a/wiki/images/the-button.png b/wiki/images/the-button.png new file mode 100644 index 0000000..606d65f Binary files /dev/null and b/wiki/images/the-button.png differ