Do some cleanup
This commit is contained in:
parent
47b5793087
commit
9f39c0856b
9 changed files with 223 additions and 242 deletions
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"DDV.ErrorFailedToOverride": "Failed to override {target}, some things might not work correctly.",
|
||||
"DDV.WarningLackingPermissionToConfigure": "You do not have permission to configure this Token!",
|
||||
"DDV.Title": "Darkness Dependent Vision Configuration",
|
||||
"DDV.TokenConfigHeaderButtonLabel": "DDV",
|
||||
|
|
7
src/module/config.js
Normal file
7
src/module/config.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
// SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
'use strict';
|
||||
|
||||
export const packageName = 'darkness-dependent-vision';
|
|
@ -4,8 +4,13 @@
|
|||
|
||||
'use strict';
|
||||
|
||||
import notifications from './notifications';
|
||||
|
||||
/**
|
||||
* An application that provides functionality to configure darkness dependent
|
||||
* vision for a {@link TokenDocument}.
|
||||
* @extends {FormApplication}
|
||||
* @remarks Most of the code of this class is heavily inspired by the
|
||||
* implementation of {@link TokenConfig}.
|
||||
*/
|
||||
export class DarknessDependentVisionConfig extends FormApplication {
|
||||
constructor(object, options) {
|
||||
super(object, options);
|
||||
|
@ -52,7 +57,6 @@ export class DarknessDependentVisionConfig extends FormApplication {
|
|||
async getData() {
|
||||
const data = this.isPrototype ? this.actor.data.token : this.token.data;
|
||||
return {
|
||||
cssClasses: [this.isPrototype ? 'prototype' : null].filter((c) => !!c).join(' '),
|
||||
object: data,
|
||||
};
|
||||
}
|
||||
|
@ -60,7 +64,7 @@ export class DarknessDependentVisionConfig extends FormApplication {
|
|||
async render(force, options) {
|
||||
const canConfigure = game.user.isGM || this.actor?.isOwner;
|
||||
if (!game.user.can('TOKEN_CONFIGURE') || !canConfigure) {
|
||||
notifications.warn(game.i18n.localize('DDV.WarningLackingPermissionToConfigure'));
|
||||
ui.notifications?.warn(game.i18n.localize('DDV.WarningLackingPermissionToConfigure'));
|
||||
return this;
|
||||
}
|
||||
return super.render(force, options);
|
||||
|
|
|
@ -4,186 +4,5 @@
|
|||
|
||||
'use strict';
|
||||
|
||||
import logger from './logger';
|
||||
import { libWrapper } from './shims/libWrapperShim';
|
||||
import notifications from './notifications';
|
||||
import { DarknessDependentVisionConfig } from './darkness-dependent-vision-config';
|
||||
|
||||
const packageName = 'darkness-dependent-vision';
|
||||
|
||||
Hooks.once('init', async () => {
|
||||
logger.info(`Initializing ${packageName}`);
|
||||
|
||||
try {
|
||||
libWrapper.register(packageName, 'CONFIG.Token.objectClass.prototype.dimRadius', getDimRadius, 'OVERRIDE');
|
||||
} catch (e) {
|
||||
notifications.warn(
|
||||
game.i18n.format('DDV.ErrorFailedToOverride', { target: 'CONFIG.Token.documentClass.prototype.dimRadius' }),
|
||||
{
|
||||
log: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
libWrapper.register(packageName, 'CONFIG.Token.objectClass.prototype.brightRadius', getBrightRadius, 'OVERRIDE');
|
||||
} catch (e) {
|
||||
notifications.warn(
|
||||
game.i18n.format('DDV.ErrorFailedToOverride', { target: 'CONFIG.Token.documentClass.prototype.brightRadius' }),
|
||||
{
|
||||
log: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
libWrapper.register(packageName, 'CONFIG.Token.objectClass.prototype.updateSource', updateSource, 'WRAPPER');
|
||||
|
||||
libWrapper.register(packageName, 'CONFIG.Token.sheetClass.prototype._getHeaderButtons', getHeaderButtons, 'WRAPPER');
|
||||
});
|
||||
|
||||
/**
|
||||
* Does this {@link Token} have dim vision, considering the darkness level of
|
||||
* its containing {@link Scene}?
|
||||
*/
|
||||
function hasDimVision() {
|
||||
const dimVisionDarknessMin = this.document.getFlag(packageName, 'dimVisionDarknessMin') ?? 0;
|
||||
const dimVisionDarknessMax = this.document.getFlag(packageName, 'dimVisionDarknessMax') ?? 1;
|
||||
const darkness = this.document.parent.data.darkness;
|
||||
return dimVisionDarknessMin <= darkness && darkness <= dimVisionDarknessMax;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this {@link Token} have bright vision, considering the darkness level of
|
||||
* its containing {@link Scene}?
|
||||
*/
|
||||
function hasBrightVision() {
|
||||
const brightVisionDarknessMin = this.document.getFlag(packageName, 'brightVisionDarknessMin') ?? 0;
|
||||
const brightVisionDarknessMax = this.document.getFlag(packageName, 'brightVisionDarknessMax') ?? 1;
|
||||
const darkness = this.document.parent.data.darkness;
|
||||
return brightVisionDarknessMin <= darkness && darkness <= brightVisionDarknessMax;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the light and vision source objects associated with this Token
|
||||
* @typedef {({defer, deleted, noUpdateFog}?: {defer?: boolean, deleted?: boolean, noUpdateFog?: boolean}) => void} UpdateSourceFunction
|
||||
* @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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the set of config buttons which should appear in the Application header.
|
||||
* Buttons should be returned as an Array of objects.
|
||||
* The header buttons which are added to the application can be modified by the getApplicationHeaderButtons hook.
|
||||
* @typedef {{label: string, class: string, icon: string, onclick: Function|null}} ApplicationHeaderButton
|
||||
* @param {() => ApplicationHeaderButton[]} wrapped The function that is wrapped by this function and needs to be called next in the chain
|
||||
* @fires Application#hook:getApplicationHeaderButtons
|
||||
* @returns {ApplicationHeaderButton[]}
|
||||
*/
|
||||
function getHeaderButtons(wrapped) {
|
||||
const buttons = wrapped();
|
||||
const button = {
|
||||
label: 'DDV.TokenConfigHeaderButtonLabel',
|
||||
class: 'configure-darkness-dependent-vision',
|
||||
icon: 'fas fa-eye',
|
||||
onclick: async () => {
|
||||
return new DarknessDependentVisionConfig(this.object).render(true);
|
||||
},
|
||||
};
|
||||
|
||||
return [button, ...buttons];
|
||||
}
|
||||
|
||||
Hooks.on('updateScene', (scene, change) => {
|
||||
if (change.darkness != null) {
|
||||
scene.getEmbeddedCollection('Token').forEach((tokenDocument) => tokenDocument.object.updateSource());
|
||||
}
|
||||
});
|
||||
|
||||
Hooks.on('updateToken', (token, change) => {
|
||||
const shouldUpdateSource = [
|
||||
'dimVisionDarknessMin',
|
||||
'dimVisionDarknessMax',
|
||||
'brightVisionDarknessMin',
|
||||
'brightVisionDarknessMax',
|
||||
].some((flagKey) => `flags.darkness-dependent-vision.${flagKey}` in foundry.utils.flattenObject(change));
|
||||
if (shouldUpdateSource) {
|
||||
token.object.updateSource();
|
||||
}
|
||||
});
|
||||
import registerForHooks from './hooks';
|
||||
registerForHooks();
|
||||
|
|
57
src/module/hooks.js
Normal file
57
src/module/hooks.js
Normal file
|
@ -0,0 +1,57 @@
|
|||
// SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
'use strict';
|
||||
|
||||
import { packageName } from './config';
|
||||
import logger from './logger';
|
||||
import { getBrightRadius, getDimRadius, updateSource } from './wrappers/token';
|
||||
import { getHeaderButtons } from './wrappers/token-config';
|
||||
import { libWrapper } from './shims/libWrapperShim';
|
||||
|
||||
export default function registerForHooks() {
|
||||
Hooks.once('init', onInit);
|
||||
Hooks.on('updateScene', onUpdateScene);
|
||||
Hooks.on('updateToken', onUpdateToken);
|
||||
}
|
||||
|
||||
function onInit() {
|
||||
logger.info(`Initializing ${packageName}`);
|
||||
|
||||
const dimRadiusTarget = 'CONFIG.Token.objectClass.prototype.dimRadius';
|
||||
try {
|
||||
libWrapper.register(packageName, dimRadiusTarget, getDimRadius, 'OVERRIDE');
|
||||
} catch (e) {
|
||||
logger.warn(`Failed to override ${dimRadiusTarget}, some things might not work correctly:`, e);
|
||||
}
|
||||
|
||||
const brightRadiusTarget = 'CONFIG.Token.objectClass.prototype.dimRadius';
|
||||
try {
|
||||
libWrapper.register(packageName, brightRadiusTarget, getBrightRadius, 'OVERRIDE');
|
||||
} catch (e) {
|
||||
logger.warn(`Failed to override ${brightRadiusTarget}, some things might not work correctly:`, e);
|
||||
}
|
||||
|
||||
libWrapper.register(packageName, 'CONFIG.Token.objectClass.prototype.updateSource', updateSource, 'WRAPPER');
|
||||
|
||||
libWrapper.register(packageName, 'CONFIG.Token.sheetClass.prototype._getHeaderButtons', getHeaderButtons, 'WRAPPER');
|
||||
}
|
||||
|
||||
function onUpdateScene(scene, change) {
|
||||
if (change.darkness != null) {
|
||||
scene.getEmbeddedCollection('Token').forEach((tokenDocument) => tokenDocument.object.updateSource());
|
||||
}
|
||||
}
|
||||
|
||||
function onUpdateToken(token, change) {
|
||||
const shouldUpdateSource = [
|
||||
'dimVisionDarknessMin',
|
||||
'dimVisionDarknessMax',
|
||||
'brightVisionDarknessMin',
|
||||
'brightVisionDarknessMax',
|
||||
].some((flagKey) => `flags.darkness-dependent-vision.${flagKey}` in foundry.utils.flattenObject(change));
|
||||
if (shouldUpdateSource) {
|
||||
token.object.updateSource();
|
||||
}
|
||||
}
|
|
@ -8,22 +8,20 @@ const loggingContext = 'DDV';
|
|||
const loggingSeparator = '|';
|
||||
|
||||
/**
|
||||
* @typedef {'debug' | 'info' | 'warning' | 'error'} LogLevel
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get a logging function for the requested log level.
|
||||
* @typedef {'debug' | 'info' | 'warn' | 'error'} LogLevel
|
||||
* @typedef {(...args: unknown[]) => void} LoggingFunction
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {LogLevel} [type = 'info'] - The log level of the requested logger
|
||||
* @returns {LoggingFunction}
|
||||
*/
|
||||
function getLoggingFunction(type = 'info') {
|
||||
const log = { debug: console.debug, info: console.info, warning: console.warn, error: console.error }[type];
|
||||
const log = console[type];
|
||||
return (...data) => log(loggingContext, loggingSeparator, ...data);
|
||||
}
|
||||
|
||||
/**
|
||||
* A singleton logger object.
|
||||
*/
|
||||
const logger = Object.freeze({
|
||||
debug: getLoggingFunction('debug'),
|
||||
info: getLoggingFunction('info'),
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
'use strict';
|
||||
|
||||
import logger from './logger';
|
||||
|
||||
const notificationPrefix = 'Darkness Dependent Vision:';
|
||||
|
||||
/**
|
||||
* @typedef {"info" | "warn" | "error"} NotificationType
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {(message: string, {permanent, log}?: {permanent?: boolean, log?: boolean}) => void} NotificationFunction
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {NotificationType} type The type of the notification function to get
|
||||
* @returns {NotificationFunction} A function that can be called to send a notification to the user
|
||||
*/
|
||||
function getNotificationFunction(type) {
|
||||
return (message, { permanent = false, log = false } = {}) => {
|
||||
ui.notifications[type](`${notificationPrefix} ${message}`, { permanent });
|
||||
if (log) {
|
||||
logger[type](message);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const notifications = {
|
||||
info: getNotificationFunction('info'),
|
||||
warn: getNotificationFunction('warn'),
|
||||
error: getNotificationFunction('error'),
|
||||
notify: (message, type, { permanent = false, log = false } = {}) => {
|
||||
ui.notifications.notify(`${notificationPrefix} ${message}`, type, { permanent });
|
||||
if (log) {
|
||||
logger.getLoggingFunction(type)(message);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
Object.freeze(notifications);
|
||||
|
||||
export default notifications;
|
30
src/module/wrappers/token-config.js
Normal file
30
src/module/wrappers/token-config.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
// SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
'use strict';
|
||||
|
||||
import { DarknessDependentVisionConfig } from '../darkness-dependent-vision-config';
|
||||
|
||||
/**
|
||||
* Specify the set of config buttons which should appear in the Application header.
|
||||
* Buttons should be returned as an Array of objects.
|
||||
* The header buttons which are added to the application can be modified by the getApplicationHeaderButtons hook.
|
||||
* @typedef {{label: string, class: string, icon: string, onclick: Function|null}} ApplicationHeaderButton
|
||||
* @param {() => ApplicationHeaderButton[]} wrapped The function that is wrapped by this function and needs to be called next in the chain
|
||||
* @fires Application#hook:getApplicationHeaderButtons
|
||||
* @returns {ApplicationHeaderButton[]}
|
||||
*/
|
||||
export function getHeaderButtons(wrapped) {
|
||||
const buttons = wrapped();
|
||||
const button = {
|
||||
label: 'DDV.TokenConfigHeaderButtonLabel',
|
||||
class: 'configure-darkness-dependent-vision',
|
||||
icon: 'fas fa-eye',
|
||||
onclick: async () => {
|
||||
return new DarknessDependentVisionConfig(this.object).render(true);
|
||||
},
|
||||
};
|
||||
|
||||
return [button, ...buttons];
|
||||
}
|
113
src/module/wrappers/token.js
Normal file
113
src/module/wrappers/token.js
Normal file
|
@ -0,0 +1,113 @@
|
|||
// SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
'use strict';
|
||||
|
||||
import { packageName } from '../config';
|
||||
|
||||
/**
|
||||
* Translate the token's sight distance in units into a radius in pixels.
|
||||
* @return {number} The sight radius in pixels
|
||||
*/
|
||||
export 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
|
||||
*/
|
||||
export 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the light and vision source objects associated with this Token
|
||||
* @typedef {({defer, deleted, noUpdateFog}?: {defer?: boolean, deleted?: boolean, noUpdateFog?: boolean}) => void} UpdateSourceFunction
|
||||
* @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.
|
||||
*/
|
||||
export 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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this {@link Token} have dim vision, considering the darkness level of
|
||||
* its containing {@link Scene}?
|
||||
*/
|
||||
function hasDimVision() {
|
||||
const dimVisionDarknessMin = this.document.getFlag(packageName, 'dimVisionDarknessMin') ?? 0;
|
||||
const dimVisionDarknessMax = this.document.getFlag(packageName, 'dimVisionDarknessMax') ?? 1;
|
||||
const darkness = this.document.parent.data.darkness;
|
||||
return dimVisionDarknessMin <= darkness && darkness <= dimVisionDarknessMax;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this {@link Token} have bright vision, considering the darkness level of
|
||||
* its containing {@link Scene}?
|
||||
*/
|
||||
function hasBrightVision() {
|
||||
const brightVisionDarknessMin = this.document.getFlag(packageName, 'brightVisionDarknessMin') ?? 0;
|
||||
const brightVisionDarknessMax = this.document.getFlag(packageName, 'brightVisionDarknessMax') ?? 1;
|
||||
const darkness = this.document.parent.data.darkness;
|
||||
return brightVisionDarknessMin <= darkness && darkness <= brightVisionDarknessMax;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
Loading…
Reference in a new issue