Initial commit

This commit is contained in:
Johannes Loher 2021-06-29 05:27:27 +02:00
commit 32ae12c6bd
38 changed files with 16736 additions and 0 deletions

13
.editorconfig Normal file
View file

@ -0,0 +1,13 @@
# SPDX-FileCopyrightText: 2021 Johannes Loher
#
# SPDX-License-Identifier: MIT
root = true
[*]
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
charset = utf-8
trim_trailing_whitespace = true

5
.eslintignore Normal file
View file

@ -0,0 +1,5 @@
# SPDX-FileCopyrightText: 2021 Johannes Loher
#
# SPDX-License-Identifier: MIT
dist

36
.eslintrc.js Normal file
View file

@ -0,0 +1,36 @@
// SPDX-FileCopyrightText: 2021 Johannes Loher
//
// SPDX-License-Identifier: MIT
module.exports = {
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
},
env: {
browser: true,
es2020: true,
},
extends: ['eslint:recommended', '@typhonjs-fvtt/eslint-config-foundry.js/0.7.9', 'plugin:prettier/recommended'],
plugins: [],
globals: {
foundry: false,
},
rules: {
// Specify any specific ESLint rules.
},
overrides: [
{
files: ['./*.js'],
env: {
node: true,
},
},
],
};

32
.gitignore vendored Normal file
View file

@ -0,0 +1,32 @@
# SPDX-FileCopyrightText: 2021 Johannes Loher
#
# SPDX-License-Identifier: MIT
# IDE
.idea/
.vs/
# Node Modules
node_modules/
npm-debug.log
# yarn2
.yarn/*
!.yarn/patches
!.yarn/releases
!.yarn/plugins
!.yarn/sdks
!.yarn/versions
.pnp.*
# Local configuration
foundryconfig.json
# Distribution files
dist
# ESLint
.eslintcache
# Junit results
junit.xml

90
.gitlab-ci.yml Normal file
View file

@ -0,0 +1,90 @@
# SPDX-FileCopyrightText: 2021 Johannes Loher
# SPDX-FileCopyrightText: 2021 Oliver Rümpelein
#
# SPDX-License-Identifier: MIT
image: node:lts
stages:
- test
- build
- release
before_script:
- yarn install --immutable
cache: &global_cache
paths:
- .yarn/cache
lint:
stage: test
script:
- yarn lint
cache:
<<: *global_cache
reuse:
stage: test
image:
name: fsfe/reuse:latest
entrypoint: [""]
before_script: []
script:
- reuse lint
build:
stage: build
script:
- yarn build
- mv dist darkness-dependent-vision
cache:
<<: *global_cache
artifacts:
paths:
- darkness-dependent-vision
expire_in: 1 week
.release-template: &release-template
stage: release
before_script:
- yarn install
- apt update
- apt install --yes jq
- REPOSITORY_URL=$(echo "${CI_REPOSITORY_URL}" | sed -e "s|gitlab-ci-token:.*@|${RELEASE_TOKEN}:${RELEASE_TOKEN_SECRET}@|g")
- git remote set-url origin $REPOSITORY_URL
- git config user.name $GITLAB_USER_LOGIN
- git config user.email $GITLAB_USER_EMAIL
- git branch -D ci-processing || true
- git checkout -b ci-processing
cache:
<<: *global_cache
script: |
yarn bump-version --release=${RELEASE_TYPE}
RELEASE_VERSION=$(jq -r '.version' < package.json)
git add package.json src/module.json
git --no-pager diff
git commit -m "release version ${RELEASE_VERSION}"
git tag -f latest
git tag -f ${RELEASE_VERSION}
git push origin ci-processing:${CI_BUILD_REF_NAME}
git push origin latest -f
git push origin ${RELEASE_VERSION}
only:
- master
when: manual
release-patch:
variables:
RELEASE_TYPE: patch
<<: *release-template
release-minor:
variables:
RELEASE_TYPE: minor
<<: *release-template
release-major:
variables:
RELEASE_TYPE: major
<<: *release-template

1
.husky/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
_

View file

@ -0,0 +1,3 @@
SPDX-FileCopyrightText: 2021 Johannes Loher
SPDX-License-Identifier: MIT

4
.husky/pre-commit Executable file
View file

@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
yarn run lint-staged

View file

@ -0,0 +1,3 @@
SPDX-FileCopyrightText: 2021 Johannes Loher
SPDX-License-Identifier: MIT

1
.nvmrc Normal file
View file

@ -0,0 +1 @@
lts/*

3
.nvmrc.license Normal file
View file

@ -0,0 +1,3 @@
SPDX-FileCopyrightText: 2021 Johannes Loher
SPDX-License-Identifier: MIT

6
.prettierignore Normal file
View file

@ -0,0 +1,6 @@
# SPDX-FileCopyrightText: 2021 Johannes Loher
#
# SPDX-License-Identifier: MIT
dist
package-lock.json

11
.prettierrc.js Normal file
View file

@ -0,0 +1,11 @@
// SPDX-FileCopyrightText: 2021 Johannes Loher
//
// SPDX-License-Identifier: MIT
module.exports = {
semi: true,
trailingComma: 'all',
singleQuote: true,
printWidth: 120,
tabWidth: 2,
};

8
.reuse/dep5 Normal file
View file

@ -0,0 +1,8 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: darkness-dependent-vision
Upstream-Contact: Johannes Loher <johannes.loher@fg4f.de>
Source: https://git.f3l.de/ghost/darkness-dependent-vision
Files: .yarn/**
Copyright: Copyright (c) 2016-present, Yarn Contributors. All rights reserved.
License: BSD-2-Clause

File diff suppressed because one or more lines are too long

55
.yarn/releases/yarn-2.4.2.cjs vendored Executable file

File diff suppressed because one or more lines are too long

9
.yarnrc.yml Normal file
View file

@ -0,0 +1,9 @@
# SPDX-FileCopyrightText: 2021 Johannes Loher
#
# SPDX-License-Identifier: MIT
plugins:
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
spec: "@yarnpkg/plugin-interactive-tools"
yarnPath: .yarn/releases/yarn-2.4.2.cjs

View file

@ -0,0 +1,9 @@
Copyright (c) <year> <owner> All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

9
LICENSES/MIT.txt Normal file
View file

@ -0,0 +1,9 @@
MIT License
Copyright (c) <year> <copyright holders>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

92
README.md Normal file
View file

@ -0,0 +1,92 @@
<!--
SPDX-FileCopyrightText: 2021 Johannes Loher
SPDX-License-Identifier: MIT
-->
# Darkness Dependent Vision
A module for [Foundry Virtual Tabletop] that provides the ability to make the
dim and bright vision of tokens depend on the scene's darkness level.
## Installation
To install and use the Darkness Dependent Vision module for Foundry Virtual
Tabletop, simply paste the following URL into the **Install Module** dialog on
the Setup menu of the application.
https://git.f3l.de/ghost/darkness-dependent-vision/-/raw/latest/src/module.json?inline=false
## Development
### Prerequisites
In order to build this module, recent versions of `node` and `yarn` are
required. Most likely using `npm` also works but only `yarn` is officially
supported. We recommend using the latest lts version of `node`, which is
`v14.17.1` at the time of writing. If you use `nvm` to manage your `node`
versions, you can simply run
```
nvm install
```
in the project's root directory.
You also need to install the the project's dependencies. To do so, run
```
yarn install
```
### Building
You can build the project by running
```
yarn build
```
Alternatively, you can run
```
yarn build:watch
```
to watch for changes and automatically build as necessary.
### Linking the built project to Foundry VTT
In order to provide a fluent development experience, it is recommended to link
the built module to your local Foundry VTT installation's data folder. In
order to do so, first add a file called `foundryconfig.json` to the project root
with the following content:
```
{
"dataPath": "/absolute/path/to/your/FoundryVTT/Data"
}
```
(if you are using Windows, make sure to use `\` as a path separator instead of
`/`)
Then run
```
yarn link-project
```
On Windows, creating symlinks requires administrator privileges so unfortunately
you need to run the above command in an administrator terminal for it to work.
## Licensing
This project is being developed under the terms of the
[LIMITED LICENSE AGREEMENT FOR MODULE DEVELOPMENT] for Foundry Virtual Tabletop.
This project is licensed under [MIT]. To get more detailed information about the
individual copyright holders, check the individual files.
[Foundry Virtual Tabletop]: http://foundryvtt.com
[MIT]: LICENSES/MIT.txt

222
gulpfile.js Normal file
View file

@ -0,0 +1,222 @@
// SPDX-FileCopyrightText: 2021 Johannes Loher
//
// SPDX-License-Identifier: MIT
const { rollup } = require('rollup');
const argv = require('yargs').argv;
const chalk = require('chalk');
const fs = require('fs-extra');
const gulp = require('gulp');
const path = require('path');
const rollupConfig = require('./rollup.config');
const semver = require('semver');
/********************/
/* CONFIGURATION */
/********************/
const name = path.basename(path.resolve('.'));
const sourceDirectory = './src';
const distDirectory = './dist';
const stylesDirectory = `${sourceDirectory}/styles`;
const stylesExtension = 'css';
const sourceFileExtension = 'js';
const staticFiles = ['assets', 'fonts', 'lang', 'packs', 'templates', 'module.json'];
const getDownloadURL = (version) =>
`https://git.f3l.de/ghost/darkness-dependent-vision/-/jobs/artifacts/${version}/download?job=build`;
/********************/
/* BUILD */
/********************/
/**
* Build the distributable JavaScript code
*/
async function buildCode() {
const build = await rollup({ input: rollupConfig.input, plugins: rollupConfig.plugins });
return build.write(rollupConfig.output);
}
/**
* Build style sheets
*/
function buildStyles() {
return gulp.src(`${stylesDirectory}/${name}.${stylesExtension}`).pipe(gulp.dest(`${distDirectory}/styles`));
}
/**
* Copy static files
*/
async function copyFiles() {
for (const file of staticFiles) {
if (fs.existsSync(`${sourceDirectory}/${file}`)) {
await fs.copy(`${sourceDirectory}/${file}`, `${distDirectory}/${file}`);
}
}
}
/**
* Watch for changes for each build step
*/
function buildWatch() {
gulp.watch(`${sourceDirectory}/**/*.${sourceFileExtension}`, { ignoreInitial: false }, buildCode);
gulp.watch(`${stylesDirectory}/**/*.${stylesExtension}`, { ignoreInitial: false }, buildStyles);
gulp.watch(
staticFiles.map((file) => `${sourceDirectory}/${file}`),
{ ignoreInitial: false },
copyFiles,
);
}
/********************/
/* CLEAN */
/********************/
/**
* Remove built files from `dist` folder while ignoring source files
*/
async function clean() {
const files = [...staticFiles, 'module'];
if (fs.existsSync(`${stylesDirectory}/${name}.${stylesExtension}`)) {
files.push('styles');
}
console.log(' ', chalk.yellow('Files to clean:'));
console.log(' ', chalk.blueBright(files.join('\n ')));
for (const filePath of files) {
await fs.remove(`${distDirectory}/${filePath}`);
}
}
/********************/
/* LINK */
/********************/
/**
* Get the data path of Foundry VTT based on what is configured in `foundryconfig.json`
*/
function getDataPath() {
const config = fs.readJSONSync('foundryconfig.json');
if (config?.dataPath) {
if (!fs.existsSync(path.resolve(config.dataPath))) {
throw new Error('User Data path invalid, no Data directory found');
}
return path.resolve(config.dataPath);
} else {
throw new Error('No User Data path defined in foundryconfig.json');
}
}
/**
* Link build to User Data folder
*/
async function linkUserData() {
let destinationDirectory;
if (fs.existsSync(path.resolve(sourceDirectory, 'module.json'))) {
destinationDirectory = 'modules';
} else {
throw new Error(`Could not find ${chalk.blueBright('module.json')}`);
}
const linkDirectory = path.resolve(getDataPath(), destinationDirectory, name);
if (argv.clean || argv.c) {
console.log(chalk.yellow(`Removing build in ${chalk.blueBright(linkDirectory)}.`));
await fs.remove(linkDirectory);
} else if (!fs.existsSync(linkDirectory)) {
console.log(chalk.green(`Linking dist to ${chalk.blueBright(linkDirectory)}.`));
await fs.ensureDir(path.resolve(linkDirectory, '..'));
await fs.symlink(path.resolve(distDirectory), linkDirectory);
}
}
/********************/
/* VERSIONING */
/********************/
/**
* Get the contents of the manifest file as object.
*/
function getManifest() {
const manifestPath = `${sourceDirectory}/module.json`;
if (fs.existsSync(manifestPath)) {
return {
file: fs.readJSONSync(manifestPath),
name: 'module.json',
};
}
}
/**
* Get the target version based on on the current version and the argument passed as release.
*/
function getTargetVersion(currentVersion, release) {
if (['major', 'premajor', 'minor', 'preminor', 'patch', 'prepatch', 'prerelease'].includes(release)) {
return semver.inc(currentVersion, release);
} else {
return semver.valid(release);
}
}
/**
* Update version and download URL.
*/
function bumpVersion(cb) {
const packageJson = fs.readJSONSync('package.json');
const packageLockJson = fs.existsSync('package-lock.json') ? fs.readJSONSync('package-lock.json') : undefined;
const manifest = getManifest();
if (!manifest) cb(Error(chalk.red('Manifest JSON not found')));
try {
const release = argv.release || argv.r;
const currentVersion = packageJson.version;
if (!release) {
return cb(Error('Missing release type'));
}
const targetVersion = getTargetVersion(currentVersion, release);
if (!targetVersion) {
return cb(new Error(chalk.red('Error: Incorrect version arguments')));
}
if (targetVersion === currentVersion) {
return cb(new Error(chalk.red('Error: Target version is identical to current version')));
}
console.log(`Updating version number to '${targetVersion}'`);
packageJson.version = targetVersion;
fs.writeJSONSync('package.json', packageJson, { spaces: 2 });
if (packageLockJson) {
packageLockJson.version = targetVersion;
fs.writeJSONSync('package-lock.json', packageLockJson, { spaces: 2 });
}
manifest.file.version = targetVersion;
manifest.file.download = getDownloadURL(targetVersion);
fs.writeJSONSync(`${sourceDirectory}/${manifest.name}`, manifest.file, { spaces: 2 });
return cb();
} catch (err) {
cb(err);
}
}
const execBuild = gulp.parallel(buildCode, buildStyles, copyFiles);
exports.build = gulp.series(clean, execBuild);
exports.watch = buildWatch;
exports.clean = clean;
exports.link = linkUserData;
exports.bumpVersion = bumpVersion;

10646
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,3 @@
SPDX-FileCopyrightText: 2021 Johannes Loher
SPDX-License-Identifier: MIT

52
package.json Normal file
View file

@ -0,0 +1,52 @@
{
"private": true,
"name": "darkness-dependent-vision",
"version": "0.0.1",
"description": "A module for Foundry Virtual Tabletop that provides the ability to make the dim and bright vision of tokens depend on the scene's darkness level.",
"license": "MIT",
"homepage": "https://git.f3l.de/ghost/darkness-dependent-vision",
"repository": {
"type": "git",
"url": "https://git.f3l.de/ghost/darkness-dependent-vision"
},
"bugs": {
"url": "https://git.f3l.de/ghost/darkness-dependent-vision/-/issues"
},
"contributors": [
{
"name": "Johannes Loher",
"email": "johannes.loher@fg4f.de"
}
],
"scripts": {
"build": "gulp build",
"build:watch": "gulp watch",
"link-project": "gulp link",
"clean": "gulp clean",
"clean:link": "gulp link --clean",
"bump-version": "gulp bumpVersion",
"lint": "eslint --ext .js .",
"lint:fix": "eslint --ext .js --fix .",
"format": "prettier --write \"./**/*.(js|json|css)\"",
"postinstall": "husky install"
},
"devDependencies": {
"@typhonjs-fvtt/eslint-config-foundry.js": "^0.8.0",
"chalk": "^4.1.1",
"eslint": "^7.29.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^3.4.0",
"fs-extra": "^10.0.0",
"gulp": "^4.0.2",
"husky": "^6.0.0",
"lint-staged": "^11.0.0",
"prettier": "^2.3.2",
"rollup": "^2.52.3",
"semver": "^7.3.5",
"yargs": "^17.0.1"
},
"lint-staged": {
"*.(js)": "eslint --fix",
"*.(json|css)": "prettier --write"
}
}

3
package.json.license Normal file
View file

@ -0,0 +1,3 @@
SPDX-FileCopyrightText: 2021 Johannes Loher
SPDX-License-Identifier: MIT

12
rollup.config.js Normal file
View file

@ -0,0 +1,12 @@
// SPDX-FileCopyrightText: 2021 Johannes Loher
//
// SPDX-License-Identifier: MIT
module.exports = {
input: 'src/module/darkness-dependent-vision.js',
output: {
dir: 'dist/module',
format: 'es',
sourcemap: true,
},
};

3
src/lang/en.json Normal file
View file

@ -0,0 +1,3 @@
{
"DDV.ErrorFailedToOverride": "Failed to override {target}, some things might not work correctly."
}

3
src/lang/en.json.license Normal file
View file

@ -0,0 +1,3 @@
SPDX-FileCopyrightText: 2021 Johannes Loher
SPDX-License-Identifier: MIT

34
src/module.json Normal file
View file

@ -0,0 +1,34 @@
{
"name": "darkness-dependent-vision",
"title": "Darkness Dependent Vision",
"description": "A module for Foundry Virtual Tabletop that provides the ability to make the dim and bright vision of tokens depend on the scene's darkness level.",
"version": "0.0.1",
"author": "Johannes Loher",
"authors": [
{
"name": "Johannes Loher",
"email": "johannes.loher@fg4f.de"
}
],
"minimumCoreVersion": "0.8.8",
"compatibleCoreVersion": "0.8.8",
"scripts": [],
"esmodules": ["module/darkness-dependent-vision.js"],
"styles": ["styles/darkness-dependent-vision.css"],
"languages": [
{
"lang": "en",
"name": "English",
"path": "lang/en.json"
}
],
"socket": false,
"url": "https://git.f3l.de/ghost/darkness-dependent-vision",
"manifest": "https://git.f3l.de/ghost/darkness-dependent-vision/-/raw/latest/src/module.json?inline=false",
"download": "https://git.f3l.de/ghost/darkness-dependent-vision/-/jobs/artifacts/0.0.1/download?job=build",
"license": "MIT",
"readme": "https://git.f3l.de/ghost/darkness-dependent-vision/-/blob/master/README.md",
"bugs": "https://git.f3l.de/ghost/darkness-dependent-vision/-/issues",
"changelog": "https://git.f3l.de/ghost/darkness-dependent-vision/-/releases",
"library": false
}

3
src/module.json.license Normal file
View file

@ -0,0 +1,3 @@
SPDX-FileCopyrightText: 2021 Johannes Loher
SPDX-License-Identifier: MIT

View file

@ -0,0 +1,164 @@
// 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();
}
});

35
src/module/logger.js Normal file
View file

@ -0,0 +1,35 @@
// SPDX-FileCopyrightText: 2021 Johannes Loher
//
// SPDX-License-Identifier: MIT
'use strict';
const loggingContext = 'DDV';
const loggingSeparator = '|';
/**
* @typedef {'debug' | 'info' | 'warning' | '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];
return (...data) => log(loggingContext, loggingSeparator, ...data);
}
const logger = Object.freeze({
debug: getLoggingFunction('debug'),
info: getLoggingFunction('info'),
warn: getLoggingFunction('warn'),
error: getLoggingFunction('error'),
getLoggingFunction,
});
export default logger;

View file

@ -0,0 +1,46 @@
// 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;

View file

@ -0,0 +1,138 @@
// Copyright © 2021 fvtt-lib-wrapper Rui Pinheiro
// 2021 Johannes Loher
//
// SPDX-License-Identifier: MIT
'use strict';
// A shim for the libWrapper library
export let libWrapper = undefined;
Hooks.once('init', () => {
// Check if the real module is already loaded - if so, use it
if (globalThis.libWrapper && !(globalThis.libWrapper.is_fallback ?? true)) {
libWrapper = globalThis.libWrapper;
return;
}
// Fallback implementation
libWrapper = class {
static get is_fallback() {
return true;
}
static register(package_id, target, fn, type = 'MIXED', { chain = undefined } = {}) {
const is_setter = target.endsWith('#set');
target = !is_setter ? target : target.slice(0, -4);
const split = target.split('.');
const fn_name = split.pop();
const root_nm = split.splice(0, 1)[0];
const _eval = eval; // The browser doesn't expose all global variables (e.g. 'Game') inside globalThis, but it does to an eval. We copy it to a variable to have it run in global scope.
const obj = split.reduce((x, y) => x[y], globalThis[root_nm] ?? _eval(root_nm));
let iObj = obj;
let descriptor = null;
while (iObj) {
descriptor = Object.getOwnPropertyDescriptor(iObj, fn_name);
if (descriptor) break;
iObj = Object.getPrototypeOf(iObj);
}
if (!descriptor || descriptor?.configurable === false)
throw `libWrapper Shim: '${target}' does not exist, could not be found, or has a non-configurable descriptor.`;
let original = null;
const wrapper =
chain ?? type != 'OVERRIDE'
? function () {
return fn.call(this, original.bind(this), ...arguments);
}
: function () {
return fn.apply(this, arguments);
};
if (!is_setter) {
if (descriptor.value) {
original = descriptor.value;
descriptor.value = wrapper;
} else {
original = descriptor.get;
descriptor.get = wrapper;
}
} else {
if (!descriptor.set) throw `libWrapper Shim: '${target}' does not have a setter`;
original = descriptor.set;
descriptor.set = wrapper;
}
descriptor.configurable = true;
Object.defineProperty(obj, fn_name, descriptor);
}
};
//************** USER CUSTOMIZABLE:
// Whether to warn GM that the fallback is being used
const WARN_FALLBACK = true;
// Set up the ready hook that shows the "libWrapper not installed" warning dialog
if (WARN_FALLBACK) {
//************** USER CUSTOMIZABLE:
// Package ID & Package Title - by default attempts to auto-detect, but you might want to hardcode your package ID and title here to avoid potential auto-detect issues
const [PACKAGE_ID, PACKAGE_TITLE] = (() => {
const match = (import.meta?.url ?? Error().stack)?.match(/\/(worlds|systems|modules)\/(.+)(?=\/)/i);
if (match?.length !== 3) return [null, null];
const dirs = match[2].split('/');
if (match[1] === 'worlds')
return dirs.find((n) => n && game.world.id === n) ? [game.world.id, game.world.title] : [null, null];
if (match[1] === 'systems')
return dirs.find((n) => n && game.system.id === n) ? [game.system.id, game.system.data.title] : [null, null];
const id = dirs.find((n) => n && game.modules.has(n));
return [id, game.modules.get(id)?.data?.title];
})();
if (!PACKAGE_ID || !PACKAGE_TITLE) {
console.error(
'libWrapper Shim: Could not auto-detect package ID and/or title. The libWrapper fallback warning dialog will be disabled.',
);
return;
}
Hooks.once('ready', () => {
//************** USER CUSTOMIZABLE:
// Title and message for the dialog shown when the real libWrapper is not installed.
const FALLBACK_MESSAGE_TITLE = PACKAGE_TITLE;
const FALLBACK_MESSAGE = `
<p><b>'${PACKAGE_TITLE}' depends on the 'libWrapper' module, which is not present.</b></p>
<p>A fallback implementation will be used, which increases the chance of compatibility issues with other modules.</p>
<small><p>'libWrapper' is a library which provides package developers with a simple way to modify core Foundry VTT code, while reducing the likelihood of conflict with other packages.</p>
<p>You can install it from the "Add-on Modules" tab in the <a href="javascript:game.shutDown()">Foundry VTT Setup</a>, from the <a href="https://foundryvtt.com/packages/lib-wrapper">Foundry VTT package repository</a>, or from <a href="https://github.com/ruipin/fvtt-lib-wrapper/">libWrapper's Github page</a>.</p></small>
`;
// Settings key used for the "Don't remind me again" setting
const DONT_REMIND_AGAIN_KEY = 'libwrapper-dont-remind-again';
// Dialog code
console.warn(`${PACKAGE_TITLE}: libWrapper not present, using fallback implementation.`);
game.settings.register(PACKAGE_ID, DONT_REMIND_AGAIN_KEY, {
name: '',
default: false,
type: Boolean,
scope: 'world',
config: false,
});
if (game.user.isGM && !game.settings.get(PACKAGE_ID, DONT_REMIND_AGAIN_KEY)) {
new Dialog({
title: FALLBACK_MESSAGE_TITLE,
content: FALLBACK_MESSAGE,
buttons: {
ok: { icon: '<i class="fas fa-check"></i>', label: 'Understood' },
dont_remind: {
icon: '<i class="fas fa-times"></i>',
label: "Don't remind me again",
callback: () => game.settings.set(PACKAGE_ID, DONT_REMIND_AGAIN_KEY, true),
},
},
}).render(true);
}
});
}
});

View file

View file

@ -0,0 +1,3 @@
SPDX-FileCopyrightText: 2021 Johannes Loher
SPDX-License-Identifier: MIT

4899
yarn.lock Normal file

File diff suppressed because it is too large Load diff

3
yarn.lock.license Normal file
View file

@ -0,0 +1,3 @@
SPDX-FileCopyrightText: 2021 Johannes Loher
SPDX-License-Identifier: MIT