Skip to content

Commit

Permalink
Merge branch 'release/0.2.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
caewok committed Nov 2, 2022
2 parents c482f1a + bc81ec0 commit 30d1bc8
Show file tree
Hide file tree
Showing 8 changed files with 75 additions and 195 deletions.
5 changes: 5 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 0.2.0
Remove 3d visibility code. (Taken over by Alternative Token Visibility.)

Remove dependency on Wall Height. (Still highly recommended.)

## 0.1.6
Incorporate update from Perfect Vision 4.0.34; no longer need to force PV into debug mode.

Expand Down
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,21 @@ This module relies in part on the [Wall Height](https://foundryvtt.com/packages/
*This module is still in early development stages. Many things are likely to change, including the image download/upload format.*

# Thanks

Special thanks to:
- dev7355608 ([Perfect Vision](https://github.com/dev7355608/perfect-vision)) author for answering my many random PIXIjs questions.
- ironmonk88 ([Enhanced Terrain Layer](https://github.com/ironmonk88/enhanced-terrain-layer)) author, from whom I borrowed some of the control layout ideas and code.

# Module compatibility

## Required modules
- [Wall Height](https://foundryvtt.com/packages/wall-height/)
- [libWrapper](https://github.com/ruipin/fvtt-lib-wrapper)

## Recommended modules
- [Token Lean](https://foundryvtt.com/packages/token-lean). Token Lean is a great addition because it allows tokens to "peak" over the edge of a cliff. For v10, I have created a fork: https://github.com/caewok/token-lean/releases.

- [Wall Height](https://github.com/theripper93/wall-height). Wall Height is highly recommended to improve your experience with Elevated Vision. With Wall Height, you can set walls and lights to have defined heights. Elevated Vision will create shadows for elevated lights cast on lower walls, block lower-elevation lights from illuminating the higher elevation, and create shadows when elevated tokens look down at lower-elevation walls.
- [Token Lean](https://foundryvtt.com/packages/token-lean). Token Lean is a great addition because it allows tokens to "peak" over the edge of a cliff. Now updated for v10!


## Problematic modules
- Should be compatible with [Perfect Vision](https://foundryvtt.com/packages/perfect-vision) as of Elevated Vision v0.1.5. Please report any issues to the Elevated Vision git issue tracker.
Expand Down
7 changes: 1 addition & 6 deletions module.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"manifestPlusVersion": "1.0.0",
"compatibility": {
"minimum": "10.279",
"verified": "10.286"
"verified": "10.288"
},
"authors": [
{
Expand All @@ -23,11 +23,6 @@
"id": "lib-wrapper",
"type": "module",
"manifest": "https://raw.githubusercontent.com/ruipin/fvtt-lib-wrapper/master/module.json"
},
{
"id": "wall-height",
"type": "module",
"manifest": "https://github.com/theripper93/wall-height/blob/main/module.json"
}
]
},
Expand Down
7 changes: 5 additions & 2 deletions scripts/ElevationLayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ isEmpty,
PolygonVertex,
CONFIG,
Ray,
WallHeight,
PreciseText
*/
"use strict";
Expand Down Expand Up @@ -1225,7 +1224,11 @@ export class ElevationLayer extends InteractionLayer {
* Draw the wall lower and upper heights on the canvas.
*/
_drawWallRange(wall) {
const bounds = WallHeight.getWallBounds(wall);
// Fill in for WallHeight.getWallBounds
const bounds = {
top: wall.document.flags?.["wall-height"]?.top ?? Number.POSITIVE_INFINITY,
bottom: wall.document.flags?.["wall-height"]?.bottom ?? Number.NEGATIVE_INFINITY
}
if ( bounds.top === Infinity && bounds.bottom === -Infinity ) return;

const style = CONFIG.canvasTextStyle.clone();
Expand Down
43 changes: 42 additions & 1 deletion scripts/module.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import {
} from "./controls.js";

// Settings, to toggle whether to change elevation on token move
import { SETTINGS, getSetting, registerSettings } from "./settings.js";
import { SETTINGS, getSetting, setSetting, registerSettings } from "./settings.js";
import { tokenOnGround, tokenElevationAt } from "./tokens.js";

Hooks.once("init", function() {
Expand Down Expand Up @@ -70,6 +70,47 @@ Hooks.once("setup", async function() {
registerPatches();
});


Hooks.once("ready", async function() {
if ( !getSetting(SETTINGS.WELCOME_DIALOG.v020) ) {
Dialog.prompt({
title: 'Elevated Vision v0.2.0 Changes!',
content: `
<p>
As of version 0.2.0, Elevated Vision no longer adjusts token visibility. You can install one or more of the
following modules if you need more functionality regarding 3d token visibility:
<ul>
<li><a href="https://github.com/caewok/fvtt-token-visibility">Alternative Token Visibility</a></li>
<li><a href="https://github.com/dev7355608/perfect-vision">Perfect Vision</a></li>
<li><a href="https://github.com/theripper93/Levels">Levels</a></li>
</ul>
These modules should work together; please report bugs to the relevant git issue page!
</p>
<p>
Elevated Vision also no longer strictly requires, but still very strongly recommends, the <a href="https://foundryvtt.com/packages/wall-height/">Wall Height</a> module.
With Wall Height, you can set walls and lights to have defined heights. Elevated Vision will create shadows for elevated lights cast on lower walls,
block lower-elevation lights from illuminating the higher elevation, and create shadows when elevated tokens look down at lower-elevation walls.
</p>
<p>
Thus, with Wall Height but no other token visibility module installed, tokens in shadows
(caused by looking down on walls with defined height) will remain visible. Tokens otherwise behind walls
will be unseen, as expected by default Foundry. Basically, token visibility should be equivalent to
what you get using the Wall Height module alone; shadows are added but do not affect the visibility calculation.
</p>
<p>
<br>
<em>Clicking the button below will make this message no longer display when FoundryVTT loads. If you
want to keep seeing this message, please click the close button above.</em>
</p>
`,
rejectClose: false,
callback: () => setSetting(SETTINGS.WELCOME_DIALOG.v020, true)
});
}
});

Hooks.on("canvasReady", async function() {
// Set the elevation grid now that we know scene dimensions
if ( !canvas.elevation ) return;
Expand Down
16 changes: 1 addition & 15 deletions scripts/patching.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ Token,
libWrapper,
ClockwiseSweepPolygon,
GlobalLightSource,
game,
PerfectVision
games
*/

"use strict";
Expand All @@ -21,10 +20,6 @@ import { zValue } from "./util.js";
import { getSetting, SETTINGS } from "./settings.js";

import {
testVisibilityDetectionMode,
testVisibilityLightSource,
_testLOSDetectionMode,
_testRangeDetectionMode,
_refreshToken,
cloneToken
} from "./tokens.js";
Expand Down Expand Up @@ -226,7 +221,6 @@ function shaderPVAdditions() {
export function registerPatches() {
const use_shader = getSetting(SETTINGS.VISION_USE_SHADER);
const pv_present = game.modules.get("perfect-vision")?.active;
const levels_present = game.modules.get("levels")?.active;
const shader_choice = use_shader | (pv_present << 1);

// ----- Locating edges that create shadows in the LOS ----- //
Expand All @@ -240,14 +234,6 @@ export function registerPatches() {
libWrapper.register(MODULE_ID, "LightSource.prototype._updateIlluminationUniforms", _updateIlluminationUniformsLightSource, libWrapper.WRAPPER, {perf_mode: libWrapper.PERF_FAST});
libWrapper.register(MODULE_ID, "LightSource.prototype._createPolygon", _createPolygonLightSource, libWrapper.WRAPPER, {perf_mode: libWrapper.PERF_FAST});

// ----- Visibility testing ----- //
if ( !pv_present && !levels_present ) {
libWrapper.register(MODULE_ID, "LightSource.prototype.testVisibility", testVisibilityLightSource, libWrapper.MIXED, {perf_mode: libWrapper.PERF_FAST});
libWrapper.register(MODULE_ID, "DetectionMode.prototype.testVisibility", testVisibilityDetectionMode, libWrapper.WRAPPER, {perf_mode: libWrapper.PERF_FAST});
libWrapper.register(MODULE_ID, "DetectionMode.prototype._testRange", _testRangeDetectionMode, libWrapper.WRAPPER, {perf_mode: libWrapper.PERF_FAST});
libWrapper.register(MODULE_ID, "DetectionMode.prototype._testLOS", _testLOSDetectionMode, libWrapper.WRAPPER, {perf_mode: libWrapper.PERF_FAST});
}

// ----- Token animation and elevation change ---- //
libWrapper.register(MODULE_ID, "Token.prototype._refresh", _refreshToken, libWrapper.WRAPPER, {perf_mode: libWrapper.PERF_FAST});
libWrapper.register(MODULE_ID, "Token.prototype.clone", cloneToken, libWrapper.WRAPPER, {perf_mode: libWrapper.PERF_FAST});
Expand Down
12 changes: 11 additions & 1 deletion scripts/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import { MODULE_ID } from "./const.js";
export const SETTINGS = {
VISION_USE_SHADER: "vision-use-shader",
AUTO_ELEVATION: "auto-change-elevation",
AUTO_AVERAGING: "auto-change-elevation.averaging"
AUTO_AVERAGING: "auto-change-elevation.averaging",
WELCOME_DIALOG: {
v020: "welcome-dialog-v0-20"
}
};

export function getSetting(settingName) {
Expand Down Expand Up @@ -57,4 +60,11 @@ export function registerSettings() {
type: Boolean,
requiresReload: false
});

game.settings.register(MODULE_ID, SETTINGS.WELCOME_DIALOG.v020, {
scope: "world",
config: false,
default: false,
type: Boolean
});
}
172 changes: 4 additions & 168 deletions scripts/tokens.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
/* globals
Token,
CONFIG,
ClockwiseSweepPolygon,
canvas
*/
"use strict";

import { Point3d } from "./Point3d.js";
import { log } from "./util.js";
import { getSetting, SETTINGS } from "./settings.js";

Expand Down Expand Up @@ -124,7 +120,6 @@ export function tokenTileElevation(token, position = { x: token.x, y: token.y })
return null;
}


/**
* Determine token elevation for a give grid position.
* Will be either the tile elevation, if the token is on the tile, or the terrain elevation.
Expand All @@ -137,7 +132,10 @@ export function tokenTileElevation(token, position = { x: token.x, y: token.y })
* @param {boolean} [considerTiles] If false, skip testing tile elevations; return the underlying terrain elevation.
* @returns {number} Elevation in grid units.
*/
export function tokenElevationAt(token, position, { useAveraging = getSetting(SETTINGS.AUTO_AVERAGING), considerTiles = true } = {}) {
export function tokenElevationAt(token, position, {
useAveraging = getSetting(SETTINGS.AUTO_AVERAGING),
considerTiles = true } = {}) {

if ( considerTiles ) {
const tileE = tokenTileElevation(token, position);
if ( tileE !== null ) return tileE;
Expand All @@ -153,165 +151,3 @@ function averageElevationForToken(x, y, w, h) {
const tokenShape = canvas.elevation._tokenShape(x, y, w, h);
return canvas.elevation.averageElevationWithinShape(tokenShape);
}

/**
* Helper function to construct a test object for testVisiblity
* @param {number} x
* @param {number} y
* @param {number} z
* @returns {object} Object with { point, los }
* See CanvasVisibility.prototype.testVisibility
*/
function buildTestObject(x, y, z = 0) {
return { point: new Point3d(x, y, z), los: new Map() };
}

/**
* Add 3d points for testing visibility
* Try a single 3d center point for middle, top, bottom.
* Then add middle, top, bottom for all other points around the boundary.
* @param {object[]} tests Object array with { point, hasLOS, hasFOV }
* @param {object} object An optional reference to the object whose visibility is being tested
* @returns {object[]} Modified tests array
*/
function create3dTestPoints(tests, object) {
// We need a point that provides both LOS and FOV membership, and
// also, if in shadow, has line of sight to a vision source without intersecting a wall.
// Top and bottom of the token cube---the points to test along the token.
// Test the middle for better consistency with how offsets above also test center
const obj_top = object.topZ;
const obj_bottom = object.bottomZ;
const obj_center = (obj_top + obj_bottom) / 2;
const skip_top = obj_top === obj_bottom;

// Try a single 3d center point for middle, top, bottom.
// Then add middle, top, bottom for all other points around the boundary.
const t0 = tests.shift();
const tests3d = [];
tests3d.push(buildTestObject(t0.point.x, t0.point.y, obj_center));

tests.forEach(t => {
const { x, y } = t.point;

tests3d.push(buildTestObject(x, y, obj_center));
if ( skip_top ) return;

tests3d.push(
buildTestObject(x, y, obj_top),
buildTestObject(x, y, obj_bottom));
});

return tests3d;
}

/**
* Wrap LightSource.prototype.testVisibility
*/
export function testVisibilityLightSource(wrapper, {tests, object} = {}) {
if ( !object || !(object instanceof Token) ) return wrapper({tests, object});

tests = create3dTestPoints(tests, object);
const doc = object.document;
if ( (doc instanceof Token) && doc.hasStatusEffect(CONFIG.specialStatusEffects.INVISIBLE) ) return false;
return tests.some(test => {
const contains = testVisionSourceLOS(this, test.point);
if ( contains ) {
if ( this.data.vision ) test.hasLOS = true;
test.hasFOV = true;
return test.hasLOS;
}
return false;
});
}

/**
* Wrap DetectionMode.prototype.testVisibility
* Add additional 3d test points for token objects
*/
export function testVisibilityDetectionMode(wrapper, visionSource, mode, {object, tests} = {}) {
if ( object && object instanceof Token) tests = create3dTestPoints(tests, object);
return wrapper(visionSource, mode, {object, tests});
}

/**
* Wrap DetectionMode.prototype._testRange
* Use a 3-D range test if the test point is 3d.
*/
export function _testRangeDetectionMode(wrapper, visionSource, mode, target, test) {
const res2d = wrapper(visionSource, mode, target, test);
if ( !res2d || !Object.hasOwn(test.point, "z") ) return res2d;

const radius = visionSource.object.getLightRadius(mode.range);
const dx = test.point.x - visionSource.x;
const dy = test.point.y - visionSource.y;
const dz = test.point.z - visionSource.elevationZ;
return ((dx * dx) + (dy * dy) + (dz * dz)) <= (radius * radius);
}

/**
* Wrap DetectionMode.prototype._testLOS
* Tokens only.
*/
export function _testLOSDetectionMode(wrapper, visionSource, mode, target, test) {
const res2d = wrapper(visionSource, mode, target, test);

if ( !res2d || !Object.prototype.hasOwnProperty.call(test.point, "z") ) return res2d;
if ( !this.walls ) return true;

const hasLOS = testVisionSourceLOS(visionSource, test.point);
test.los.set(visionSource, hasLOS);

return hasLOS;
}

/**
* Wrap VisionMode.prototype.testNaturalVisibility
*/
// export function testNaturalVisibilityVisionMode(wrapper, {tests, object} = {}) {
// if ( !object || !(object instanceof Token) ) return wrapper({tests, object});
//
// tests = create3dTestPoints(tests, object);
//
// return tests.some(test => {
// if ( !test.hasFOV && testVisionSourceLOS(this, test.point) ) {
// test.hasFOV = test.hasLOS = true;
// return true;
// }
// if ( !test.hasLOS && testVisionSourceLOS(this, test.point)) test.hasLOS = true;
// return (test.hasFOV && test.hasLOS);
// });
// }

function testVisionSourceLOS(source, p) {
if ( !source.los.contains(p.x, p.y) ) return false;
if ( !source.los.shadows?.length ) return true;

const point_in_shadow = source.los.shadows.some(s => s.contains(p.x, p.y));
if ( !point_in_shadow ) return true;

return !ClockwiseSweepPolygon.testCollision3d(new Point3d(source.x, source.y, source.elevationZ), p, { type: "sight", mode: "any" });
}


// No longer used in v10
/**
* Wrap VisionSource.prototype.drawSight
*/
// export function drawSightVisionSource(wrapped) {
// log("drawSightVisionSource");
//
// const c = wrapped();
//
// const shadows = this.los.shadows;
// if ( !shadows || !shadows.length ) {
// log("drawSightVisionSource|no shadows");
// return c;
// }
//
// for ( const shadow of shadows ) {
// const g = c.addChild(new PIXI.LegacyGraphics());
// g.beginFill(0x000000, 1.0).drawShape(shadow).endFill();
// }
//
// return c;
// }

0 comments on commit 30d1bc8

Please sign in to comment.