Initial commit
This commit is contained in:
commit
32ae12c6bd
|
@ -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
|
|
@ -0,0 +1,5 @@
|
|||
# SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
dist
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
_
|
|
@ -0,0 +1,3 @@
|
|||
SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
|
||||
SPDX-License-Identifier: MIT
|
|
@ -0,0 +1,4 @@
|
|||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
yarn run lint-staged
|
|
@ -0,0 +1,3 @@
|
|||
SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
|
||||
SPDX-License-Identifier: MIT
|
|
@ -0,0 +1,3 @@
|
|||
SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
|
||||
SPDX-License-Identifier: MIT
|
|
@ -0,0 +1,6 @@
|
|||
# SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
dist
|
||||
package-lock.json
|
|
@ -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,
|
||||
};
|
|
@ -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
File diff suppressed because one or more lines are too long
|
@ -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
|
|
@ -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.
|
|
@ -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.
|
|
@ -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
|
|
@ -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;
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,3 @@
|
|||
SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
|
||||
SPDX-License-Identifier: MIT
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
|
||||
SPDX-License-Identifier: MIT
|
|
@ -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,
|
||||
},
|
||||
};
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"DDV.ErrorFailedToOverride": "Failed to override {target}, some things might not work correctly."
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
|
||||
SPDX-License-Identifier: MIT
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
|
||||
SPDX-License-Identifier: MIT
|
|
@ -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();
|
||||
}
|
||||
});
|
|
@ -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;
|
|
@ -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;
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
|
@ -0,0 +1,3 @@
|
|||
SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
|
||||
SPDX-License-Identifier: MIT
|
|
@ -0,0 +1,3 @@
|
|||
SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
|
||||
SPDX-License-Identifier: MIT
|
Loading…
Reference in New Issue