darkness-dependent-vision/src/module/darkness-dependent-vision.js
2021-06-29 05:40:25 +02:00

165 lines
5.7 KiB
JavaScript

// SPDX-FileCopyrightText: 2021 Johannes Loher
//
// SPDX-License-Identifier: MIT
'use strict';
import logger from './logger';
import { libWrapper } from './shims/libWrapperShim';
import notifications from './notifications';
const packageName = 'darkness-dependent-vision';
Hooks.once('init', async () => {
logger.info(`Initializing ${packageName}`);
try {
libWrapper.register('darkness-dependent-vision', 'Token.prototype.dimRadius', getDimRadius, 'OVERRIDE');
} catch (e) {
notifications.warn(game.i18n.format('DDV.ErrorFailedToOverride', { target: 'Token.prototype.dimRadius' }), {
log: true,
});
}
try {
libWrapper.register('darkness-dependent-vision', 'Token.prototype.brightRadius', getBrightRadius, 'OVERRIDE');
} catch (e) {
notifications.warn(game.i18n.format('DDV.ErrorFailedToOverride', { target: 'Token.prototype.brightRadius' }), {
log: true,
});
}
libWrapper.register('darkness-dependent-vision', 'Token.prototype.updateSource', updateSource, 'WRAPPER');
});
/**
* Does this {@link Token} have dim vision, considering the darkness level of
* its containing {@link Scene}?
*/
function hasDimVision() {
const dimVisionDarknessLowerBound = this.document.getFlag(packageName, 'dimVisionDarknessLowerBound') ?? -1;
const dimVisionDarknessUpperBound = this.document.getFlag(packageName, 'dimVisionDarknessUpperBound') ?? 2;
return (
this.document.parent.data.darkness >= dimVisionDarknessLowerBound &&
this.document.parent.data.darkness < dimVisionDarknessUpperBound
);
}
/**
* Does this {@link Token} have bright vision, considering the darkness level of
* its containing {@link Scene}?
*/
function hasBrightVision() {
const brightVisionDarknessLowerBound = this.document.getFlag(packageName, 'brightVisionDarknessLowerBound') ?? -1;
const brightVisionDarknessUpperBound = this.document.getFlag(packageName, 'brightVisionDarknessUpperBound') ?? 2;
return (
this.document.parent.data.darkness >= brightVisionDarknessLowerBound &&
this.document.parent.data.darkness < brightVisionDarknessUpperBound
);
}
/**
* Get this {@link Token}'s dim vision distance of in grid units, considering
* the darkness level of its containing {@link Scene}.
*
* @returns {number} The the number of grid units that this {@link Token} has
* dim vision
*/
function getDimVision() {
return hasDimVision.call(this) ? this.data.dimSight : 0;
}
/**
* Get this {@link Token}'s bright vision distance in grid units, considering
* the darkness level of its containing {@link Scene}.
*
* @returns {number} The the number of grid units that this {@link Token} has
* bright vision
*/
function getBrightVision() {
return hasBrightVision.call(this) ? this.data.brightSight : 0;
}
/**
* Translate the token's sight distance in units into a radius in pixels.
* @return {number} The sight radius in pixels
*/
function getDimRadius() {
const dimSight = getDimVision.call(this);
let r = Math.abs(this.data.dimLight) > Math.abs(dimSight) ? this.data.dimLight : dimSight;
return this.getLightRadius(r);
}
/**
* Translate the token's bright light distance in units into a radius in pixels.
* @return {number} The bright radius in pixels
*/
function getBrightRadius() {
const brightSight = getBrightVision.call(this);
let r = Math.abs(this.data.brightLight) > Math.abs(brightSight) ? this.data.brightLight : brightSight;
return this.getLightRadius(r);
}
/**
* @typedef {({defer, deleted, noUpdateFog}?: {defer?: boolean, deleted?: boolean, noUpdateFog?: boolean}) => void} UpdateSourceFunction
*/
/**
* Update the light and vision source objects associated with this Token
* @param {UpdateSourceFunction} wrapped The function that is wrapped by this function and needs to be called next in the chain
* @param {boolean} [defer] Defer refreshing the SightLayer to manually call that refresh later.
* @param {boolean} [deleted] Indicate that this light source has been deleted.
* @param {boolean} [noUpdateFog] Never update the Fog exploration progress for this update.
*/
function updateSource(wrapped, { defer = false, deleted = false, noUpdateFog = false } = {}) {
wrapped({ defer, deleted, noUpdateFog });
// Prepare some common data
const origin = this.getSightOrigin();
const sourceId = this.sourceId;
const d = canvas.dimensions;
// Update vision source
const isVisionSource = this._isVisionSource();
if (isVisionSource && !deleted) {
const dimSight = getDimVision.call(this);
const brightSight = getBrightVision.call(this);
let dim = Math.min(this.getLightRadius(dimSight), d.maxR);
const bright = Math.min(this.getLightRadius(brightSight), d.maxR);
this.vision.initialize({
x: origin.x,
y: origin.y,
dim: dim,
bright: bright,
angle: this.data.sightAngle,
rotation: this.data.rotation,
});
canvas.sight.sources.set(sourceId, this.vision);
if (!defer) {
this.vision.drawLight();
canvas.sight.refresh({ noUpdateFog });
}
} else {
canvas.sight.sources.delete(sourceId);
if (isVisionSource && !defer) canvas.sight.refresh();
}
}
Hooks.on('updateScene', (scene, change) => {
if (change.darkness != null) {
scene.getEmbeddedCollection('Token').forEach((tokenDocument) => tokenDocument.object.updateSource());
}
});
Hooks.on('updateToken', (token, change) => {
const shouldUpdateSource = [
'dimVisionDarknessLowerBound',
'dimVisionDarknessUpperBound',
'brightVisionDarknessLowerBound',
'brightVisionDarknessUpperBound',
].some((flagKey) => `flags.darkness-dependent-vision.${flagKey}` in foundry.utils.flattenObject(change));
if (shouldUpdateSource) {
token.object.updateSource();
}
});