diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 30a53bc..929e5a6 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -3,29 +3,36 @@ // SPDX-License-Identifier: MIT module.exports = { - parser: '@typescript-eslint/parser', - parserOptions: { - ecmaVersion: 2020, sourceType: 'module', }, env: { browser: true, + es2022: true, + jquery: true, }, - extends: ['plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended'], + extends: ['eslint:recommended', 'plugin:prettier/recommended'], - plugins: ['@typescript-eslint'], + globals: { + Hooks: 'readonly', + Actor: 'readonly', + CONFIG: 'readonly', + ui: 'readonly', + Game: 'readonly', + game: 'readonly', + foundry: 'readonly', + Dialog: 'readonly', + twist: 'readonly', + }, rules: {}, overrides: [ { - files: ['./*.cjs', './*.js'], - rules: { - '@typescript-eslint/no-var-requires': 'off', - }, + files: ['./*.cjs', './*.js', './tools/**/*'], + env: { node: true, browser: false, jquery: false }, }, ], }; diff --git a/.gitignore b/.gitignore index a45e3fd..88bb62e 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,8 @@ npm-debug.log # Local configuration foundryconfig.json +/common +/client # Distribution files dist diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index cef2e42..3561dfa 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -30,15 +30,6 @@ lint: cache: <<: *global_cache -typecheck: - stage: check - before_script: - - yarn install --immutable - script: - - yarn typecheck - cache: - <<: *global_cache - reuse: stage: check image: diff --git a/.vscode/settings.json b/.vscode/settings.json index 62c4dfb..d2cf83e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,8 +5,6 @@ }, "eslint.nodePath": ".yarn/sdks", "prettier.prettierPath": ".yarn/sdks/prettier/index.js", - "typescript.tsdk": ".yarn/sdks/typescript/lib", - "typescript.enablePromptUseWorkspaceTsdk": true, "importSorter.generalConfiguration.sortOnBeforeSave": true, "importSorter.importStringConfiguration.maximumNumberOfImportExpressionsPerLine.type": "newLineEachExpressionAfterCountLimitExceptIfOnlyOne", "importSorter.importStringConfiguration.maximumNumberOfImportExpressionsPerLine.count": 120, diff --git a/.yarn/sdks/eslint/package.json b/.yarn/sdks/eslint/package.json index 6e9210f..a025cef 100644 --- a/.yarn/sdks/eslint/package.json +++ b/.yarn/sdks/eslint/package.json @@ -1,6 +1,6 @@ { "name": "eslint", - "version": "8.25.0-sdk", + "version": "8.28.0-sdk", "main": "./lib/api.js", "type": "commonjs" } diff --git a/.yarn/sdks/prettier/package.json b/.yarn/sdks/prettier/package.json index b61805c..97db755 100644 --- a/.yarn/sdks/prettier/package.json +++ b/.yarn/sdks/prettier/package.json @@ -1,6 +1,6 @@ { "name": "prettier", - "version": "2.7.1-sdk", + "version": "2.8.0-sdk", "main": "./index.js", "type": "commonjs" } diff --git a/.yarn/sdks/typescript/bin/tsc b/.yarn/sdks/typescript/bin/tsc deleted file mode 100755 index 454b950..0000000 --- a/.yarn/sdks/typescript/bin/tsc +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env node - -const {existsSync} = require(`fs`); -const {createRequire} = require(`module`); -const {resolve} = require(`path`); - -const relPnpApiPath = "../../../../.pnp.cjs"; - -const absPnpApiPath = resolve(__dirname, relPnpApiPath); -const absRequire = createRequire(absPnpApiPath); - -if (existsSync(absPnpApiPath)) { - if (!process.versions.pnp) { - // Setup the environment to be able to require typescript/bin/tsc - require(absPnpApiPath).setup(); - } -} - -// Defer to the real typescript/bin/tsc your application uses -module.exports = absRequire(`typescript/bin/tsc`); diff --git a/.yarn/sdks/typescript/bin/tsserver b/.yarn/sdks/typescript/bin/tsserver deleted file mode 100755 index d7a6056..0000000 --- a/.yarn/sdks/typescript/bin/tsserver +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env node - -const {existsSync} = require(`fs`); -const {createRequire} = require(`module`); -const {resolve} = require(`path`); - -const relPnpApiPath = "../../../../.pnp.cjs"; - -const absPnpApiPath = resolve(__dirname, relPnpApiPath); -const absRequire = createRequire(absPnpApiPath); - -if (existsSync(absPnpApiPath)) { - if (!process.versions.pnp) { - // Setup the environment to be able to require typescript/bin/tsserver - require(absPnpApiPath).setup(); - } -} - -// Defer to the real typescript/bin/tsserver your application uses -module.exports = absRequire(`typescript/bin/tsserver`); diff --git a/.yarn/sdks/typescript/lib/tsc.js b/.yarn/sdks/typescript/lib/tsc.js deleted file mode 100644 index 2f62fc9..0000000 --- a/.yarn/sdks/typescript/lib/tsc.js +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env node - -const {existsSync} = require(`fs`); -const {createRequire} = require(`module`); -const {resolve} = require(`path`); - -const relPnpApiPath = "../../../../.pnp.cjs"; - -const absPnpApiPath = resolve(__dirname, relPnpApiPath); -const absRequire = createRequire(absPnpApiPath); - -if (existsSync(absPnpApiPath)) { - if (!process.versions.pnp) { - // Setup the environment to be able to require typescript/lib/tsc.js - require(absPnpApiPath).setup(); - } -} - -// Defer to the real typescript/lib/tsc.js your application uses -module.exports = absRequire(`typescript/lib/tsc.js`); diff --git a/.yarn/sdks/typescript/lib/tsserver.js b/.yarn/sdks/typescript/lib/tsserver.js deleted file mode 100644 index 0fb2ac1..0000000 --- a/.yarn/sdks/typescript/lib/tsserver.js +++ /dev/null @@ -1,223 +0,0 @@ -#!/usr/bin/env node - -const {existsSync} = require(`fs`); -const {createRequire} = require(`module`); -const {resolve} = require(`path`); - -const relPnpApiPath = "../../../../.pnp.cjs"; - -const absPnpApiPath = resolve(__dirname, relPnpApiPath); -const absRequire = createRequire(absPnpApiPath); - -const moduleWrapper = tsserver => { - if (!process.versions.pnp) { - return tsserver; - } - - const {isAbsolute} = require(`path`); - const pnpApi = require(`pnpapi`); - - const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//); - const isPortal = str => str.startsWith("portal:/"); - const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`); - - const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => { - return `${locator.name}@${locator.reference}`; - })); - - // VSCode sends the zip paths to TS using the "zip://" prefix, that TS - // doesn't understand. This layer makes sure to remove the protocol - // before forwarding it to TS, and to add it back on all returned paths. - - function toEditorPath(str) { - // We add the `zip:` prefix to both `.zip/` paths and virtual paths - if (isAbsolute(str) && !str.match(/^\^?(zip:|\/zip\/)/) && (str.match(/\.zip\//) || isVirtual(str))) { - // We also take the opportunity to turn virtual paths into physical ones; - // this makes it much easier to work with workspaces that list peer - // dependencies, since otherwise Ctrl+Click would bring us to the virtual - // file instances instead of the real ones. - // - // We only do this to modules owned by the the dependency tree roots. - // This avoids breaking the resolution when jumping inside a vendor - // with peer dep (otherwise jumping into react-dom would show resolution - // errors on react). - // - const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str; - if (resolved) { - const locator = pnpApi.findPackageLocator(resolved); - if (locator && (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))) { - str = resolved; - } - } - - str = normalize(str); - - if (str.match(/\.zip\//)) { - switch (hostInfo) { - // Absolute VSCode `Uri.fsPath`s need to start with a slash. - // VSCode only adds it automatically for supported schemes, - // so we have to do it manually for the `zip` scheme. - // The path needs to start with a caret otherwise VSCode doesn't handle the protocol - // - // Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910 - // - // 2021-10-08: VSCode changed the format in 1.61. - // Before | ^zip:/c:/foo/bar.zip/package.json - // After | ^/zip//c:/foo/bar.zip/package.json - // - // 2022-04-06: VSCode changed the format in 1.66. - // Before | ^/zip//c:/foo/bar.zip/package.json - // After | ^/zip/c:/foo/bar.zip/package.json - // - // 2022-05-06: VSCode changed the format in 1.68 - // Before | ^/zip/c:/foo/bar.zip/package.json - // After | ^/zip//c:/foo/bar.zip/package.json - // - case `vscode <1.61`: { - str = `^zip:${str}`; - } break; - - case `vscode <1.66`: { - str = `^/zip/${str}`; - } break; - - case `vscode <1.68`: { - str = `^/zip${str}`; - } break; - - case `vscode`: { - str = `^/zip/${str}`; - } break; - - // To make "go to definition" work, - // We have to resolve the actual file system path from virtual path - // and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip) - case `coc-nvim`: { - str = normalize(resolved).replace(/\.zip\//, `.zip::`); - str = resolve(`zipfile:${str}`); - } break; - - // Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server) - // We have to resolve the actual file system path from virtual path, - // everything else is up to neovim - case `neovim`: { - str = normalize(resolved).replace(/\.zip\//, `.zip::`); - str = `zipfile://${str}`; - } break; - - default: { - str = `zip:${str}`; - } break; - } - } - } - - return str; - } - - function fromEditorPath(str) { - switch (hostInfo) { - case `coc-nvim`: { - str = str.replace(/\.zip::/, `.zip/`); - // The path for coc-nvim is in format of //zipfile://.yarn/... - // So in order to convert it back, we use .* to match all the thing - // before `zipfile:` - return process.platform === `win32` - ? str.replace(/^.*zipfile:\//, ``) - : str.replace(/^.*zipfile:/, ``); - } break; - - case `neovim`: { - str = str.replace(/\.zip::/, `.zip/`); - // The path for neovim is in format of zipfile:////.yarn/... - return str.replace(/^zipfile:\/\//, ``); - } break; - - case `vscode`: - default: { - return str.replace(/^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, process.platform === `win32` ? `` : `/`) - } break; - } - } - - // Force enable 'allowLocalPluginLoads' - // TypeScript tries to resolve plugins using a path relative to itself - // which doesn't work when using the global cache - // https://github.com/microsoft/TypeScript/blob/1b57a0395e0bff191581c9606aab92832001de62/src/server/project.ts#L2238 - // VSCode doesn't want to enable 'allowLocalPluginLoads' due to security concerns but - // TypeScript already does local loads and if this code is running the user trusts the workspace - // https://github.com/microsoft/vscode/issues/45856 - const ConfiguredProject = tsserver.server.ConfiguredProject; - const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype; - ConfiguredProject.prototype.enablePluginsWithOptions = function() { - this.projectService.allowLocalPluginLoads = true; - return originalEnablePluginsWithOptions.apply(this, arguments); - }; - - // And here is the point where we hijack the VSCode <-> TS communications - // by adding ourselves in the middle. We locate everything that looks - // like an absolute path of ours and normalize it. - - const Session = tsserver.server.Session; - const {onMessage: originalOnMessage, send: originalSend} = Session.prototype; - let hostInfo = `unknown`; - - Object.assign(Session.prototype, { - onMessage(/** @type {string | object} */ message) { - const isStringMessage = typeof message === 'string'; - const parsedMessage = isStringMessage ? JSON.parse(message) : message; - - if ( - parsedMessage != null && - typeof parsedMessage === `object` && - parsedMessage.arguments && - typeof parsedMessage.arguments.hostInfo === `string` - ) { - hostInfo = parsedMessage.arguments.hostInfo; - if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK) { - const [, major, minor] = (process.env.VSCODE_IPC_HOOK.match( - // The RegExp from https://semver.org/ but without the caret at the start - /(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ - ) ?? []).map(Number) - - if (major === 1) { - if (minor < 61) { - hostInfo += ` <1.61`; - } else if (minor < 66) { - hostInfo += ` <1.66`; - } else if (minor < 68) { - hostInfo += ` <1.68`; - } - } - } - } - - const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => { - return typeof value === 'string' ? fromEditorPath(value) : value; - }); - - return originalOnMessage.call( - this, - isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON) - ); - }, - - send(/** @type {any} */ msg) { - return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => { - return typeof value === `string` ? toEditorPath(value) : value; - }))); - } - }); - - return tsserver; -}; - -if (existsSync(absPnpApiPath)) { - if (!process.versions.pnp) { - // Setup the environment to be able to require typescript/lib/tsserver.js - require(absPnpApiPath).setup(); - } -} - -// Defer to the real typescript/lib/tsserver.js your application uses -module.exports = moduleWrapper(absRequire(`typescript/lib/tsserver.js`)); diff --git a/.yarn/sdks/typescript/lib/tsserverlibrary.js b/.yarn/sdks/typescript/lib/tsserverlibrary.js deleted file mode 100644 index e7033a8..0000000 --- a/.yarn/sdks/typescript/lib/tsserverlibrary.js +++ /dev/null @@ -1,223 +0,0 @@ -#!/usr/bin/env node - -const {existsSync} = require(`fs`); -const {createRequire} = require(`module`); -const {resolve} = require(`path`); - -const relPnpApiPath = "../../../../.pnp.cjs"; - -const absPnpApiPath = resolve(__dirname, relPnpApiPath); -const absRequire = createRequire(absPnpApiPath); - -const moduleWrapper = tsserver => { - if (!process.versions.pnp) { - return tsserver; - } - - const {isAbsolute} = require(`path`); - const pnpApi = require(`pnpapi`); - - const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//); - const isPortal = str => str.startsWith("portal:/"); - const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`); - - const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => { - return `${locator.name}@${locator.reference}`; - })); - - // VSCode sends the zip paths to TS using the "zip://" prefix, that TS - // doesn't understand. This layer makes sure to remove the protocol - // before forwarding it to TS, and to add it back on all returned paths. - - function toEditorPath(str) { - // We add the `zip:` prefix to both `.zip/` paths and virtual paths - if (isAbsolute(str) && !str.match(/^\^?(zip:|\/zip\/)/) && (str.match(/\.zip\//) || isVirtual(str))) { - // We also take the opportunity to turn virtual paths into physical ones; - // this makes it much easier to work with workspaces that list peer - // dependencies, since otherwise Ctrl+Click would bring us to the virtual - // file instances instead of the real ones. - // - // We only do this to modules owned by the the dependency tree roots. - // This avoids breaking the resolution when jumping inside a vendor - // with peer dep (otherwise jumping into react-dom would show resolution - // errors on react). - // - const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str; - if (resolved) { - const locator = pnpApi.findPackageLocator(resolved); - if (locator && (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))) { - str = resolved; - } - } - - str = normalize(str); - - if (str.match(/\.zip\//)) { - switch (hostInfo) { - // Absolute VSCode `Uri.fsPath`s need to start with a slash. - // VSCode only adds it automatically for supported schemes, - // so we have to do it manually for the `zip` scheme. - // The path needs to start with a caret otherwise VSCode doesn't handle the protocol - // - // Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910 - // - // 2021-10-08: VSCode changed the format in 1.61. - // Before | ^zip:/c:/foo/bar.zip/package.json - // After | ^/zip//c:/foo/bar.zip/package.json - // - // 2022-04-06: VSCode changed the format in 1.66. - // Before | ^/zip//c:/foo/bar.zip/package.json - // After | ^/zip/c:/foo/bar.zip/package.json - // - // 2022-05-06: VSCode changed the format in 1.68 - // Before | ^/zip/c:/foo/bar.zip/package.json - // After | ^/zip//c:/foo/bar.zip/package.json - // - case `vscode <1.61`: { - str = `^zip:${str}`; - } break; - - case `vscode <1.66`: { - str = `^/zip/${str}`; - } break; - - case `vscode <1.68`: { - str = `^/zip${str}`; - } break; - - case `vscode`: { - str = `^/zip/${str}`; - } break; - - // To make "go to definition" work, - // We have to resolve the actual file system path from virtual path - // and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip) - case `coc-nvim`: { - str = normalize(resolved).replace(/\.zip\//, `.zip::`); - str = resolve(`zipfile:${str}`); - } break; - - // Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server) - // We have to resolve the actual file system path from virtual path, - // everything else is up to neovim - case `neovim`: { - str = normalize(resolved).replace(/\.zip\//, `.zip::`); - str = `zipfile://${str}`; - } break; - - default: { - str = `zip:${str}`; - } break; - } - } - } - - return str; - } - - function fromEditorPath(str) { - switch (hostInfo) { - case `coc-nvim`: { - str = str.replace(/\.zip::/, `.zip/`); - // The path for coc-nvim is in format of //zipfile://.yarn/... - // So in order to convert it back, we use .* to match all the thing - // before `zipfile:` - return process.platform === `win32` - ? str.replace(/^.*zipfile:\//, ``) - : str.replace(/^.*zipfile:/, ``); - } break; - - case `neovim`: { - str = str.replace(/\.zip::/, `.zip/`); - // The path for neovim is in format of zipfile:////.yarn/... - return str.replace(/^zipfile:\/\//, ``); - } break; - - case `vscode`: - default: { - return str.replace(/^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, process.platform === `win32` ? `` : `/`) - } break; - } - } - - // Force enable 'allowLocalPluginLoads' - // TypeScript tries to resolve plugins using a path relative to itself - // which doesn't work when using the global cache - // https://github.com/microsoft/TypeScript/blob/1b57a0395e0bff191581c9606aab92832001de62/src/server/project.ts#L2238 - // VSCode doesn't want to enable 'allowLocalPluginLoads' due to security concerns but - // TypeScript already does local loads and if this code is running the user trusts the workspace - // https://github.com/microsoft/vscode/issues/45856 - const ConfiguredProject = tsserver.server.ConfiguredProject; - const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype; - ConfiguredProject.prototype.enablePluginsWithOptions = function() { - this.projectService.allowLocalPluginLoads = true; - return originalEnablePluginsWithOptions.apply(this, arguments); - }; - - // And here is the point where we hijack the VSCode <-> TS communications - // by adding ourselves in the middle. We locate everything that looks - // like an absolute path of ours and normalize it. - - const Session = tsserver.server.Session; - const {onMessage: originalOnMessage, send: originalSend} = Session.prototype; - let hostInfo = `unknown`; - - Object.assign(Session.prototype, { - onMessage(/** @type {string | object} */ message) { - const isStringMessage = typeof message === 'string'; - const parsedMessage = isStringMessage ? JSON.parse(message) : message; - - if ( - parsedMessage != null && - typeof parsedMessage === `object` && - parsedMessage.arguments && - typeof parsedMessage.arguments.hostInfo === `string` - ) { - hostInfo = parsedMessage.arguments.hostInfo; - if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK) { - const [, major, minor] = (process.env.VSCODE_IPC_HOOK.match( - // The RegExp from https://semver.org/ but without the caret at the start - /(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ - ) ?? []).map(Number) - - if (major === 1) { - if (minor < 61) { - hostInfo += ` <1.61`; - } else if (minor < 66) { - hostInfo += ` <1.66`; - } else if (minor < 68) { - hostInfo += ` <1.68`; - } - } - } - } - - const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => { - return typeof value === 'string' ? fromEditorPath(value) : value; - }); - - return originalOnMessage.call( - this, - isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON) - ); - }, - - send(/** @type {any} */ msg) { - return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => { - return typeof value === `string` ? toEditorPath(value) : value; - }))); - } - }); - - return tsserver; -}; - -if (existsSync(absPnpApiPath)) { - if (!process.versions.pnp) { - // Setup the environment to be able to require typescript/lib/tsserverlibrary.js - require(absPnpApiPath).setup(); - } -} - -// Defer to the real typescript/lib/tsserverlibrary.js your application uses -module.exports = moduleWrapper(absRequire(`typescript/lib/tsserverlibrary.js`)); diff --git a/.yarn/sdks/typescript/lib/typescript.js b/.yarn/sdks/typescript/lib/typescript.js deleted file mode 100644 index e14fa87..0000000 --- a/.yarn/sdks/typescript/lib/typescript.js +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env node - -const {existsSync} = require(`fs`); -const {createRequire} = require(`module`); -const {resolve} = require(`path`); - -const relPnpApiPath = "../../../../.pnp.cjs"; - -const absPnpApiPath = resolve(__dirname, relPnpApiPath); -const absRequire = createRequire(absPnpApiPath); - -if (existsSync(absPnpApiPath)) { - if (!process.versions.pnp) { - // Setup the environment to be able to require typescript/lib/typescript.js - require(absPnpApiPath).setup(); - } -} - -// Defer to the real typescript/lib/typescript.js your application uses -module.exports = absRequire(`typescript/lib/typescript.js`); diff --git a/.yarn/sdks/typescript/package.json b/.yarn/sdks/typescript/package.json deleted file mode 100644 index b117d6a..0000000 --- a/.yarn/sdks/typescript/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "typescript", - "version": "4.7.4-sdk", - "main": "./lib/typescript.js", - "type": "commonjs" -} diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 0000000..8b929d6 --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "module": "es2022", + "target": "ES2022" + }, + "exclude": ["node_modules", "dist"], + "include": ["src", "client", "common"] +} diff --git a/jsconfig.json.license b/jsconfig.json.license new file mode 100644 index 0000000..467ee14 --- /dev/null +++ b/jsconfig.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2022 Johannes Loher + +SPDX-License-Identifier: MIT diff --git a/module.json b/module.json index 1792189..52a0da3 100644 --- a/module.json +++ b/module.json @@ -1,5 +1,5 @@ { - "name": "tickwerk", + "id": "tickwerk", "title": "Tickwerk", "description": "A tick based combat system for Foundry Virtual Tabletop", "authors": [ @@ -16,8 +16,10 @@ "bugs": "https://git.f3l.de/dungeonslayers/tickwerk/-/issues", "changelog": "https://git.f3l.de/dungeonslayers/tickwerk/-/releases/1.2.1", "version": "1.2.1", - "minimumCoreVersion": "9", - "compatibleCoreVersion": "9", + "compatibility": { + "minimum": "10.290", + "verified": "10" + }, "esmodules": ["tickwerk.js"], "styles": ["styles/tickwerk.css"], "languages": [ diff --git a/package.json b/package.json index 612eb99..68ea59d 100644 --- a/package.json +++ b/package.json @@ -27,11 +27,9 @@ "clean": "run-p clean:files clean:link", "clean:files": "rimraf dist", "clean:link": "node ./tools/link-package.js --clean", - "lint": "eslint --ext .ts,.js,.cjs,.mjs .", - "lint:fix": "eslint --ext .ts,.js,.cjs,.mjs --fix .", - "format": "prettier --write \"./**/*.(ts|js|cjs|mjs|json|scss|yml)\"", - "typecheck": "tsc --noEmit", - "typecheck:watch": "tsc --noEmit --watch", + "lint": "eslint --ext .js,.cjs,.mjs .", + "lint:fix": "eslint --ext .js,.cjs,.mjs --fix .", + "format": "prettier --write \"./**/*.(js|cjs|mjs|json|scss|yml)\"", "bump-version": "node ./tools/bump-version.js", "postinstall": "husky install", "changelog": "conventional-changelog -p conventionalcommits -o CHANGELOG.md -r 2" @@ -40,20 +38,7 @@ "@commitlint/cli": "17.3.0", "@commitlint/config-conventional": "17.3.0", "@guanghechen/rollup-plugin-copy": "2.1.4", - "@league-of-foundry-developers/foundry-vtt-types": "9.280.0", - "@pixi/constants": "6.2.1", - "@pixi/core": "6.2.1", - "@pixi/display": "6.2.1", - "@pixi/graphics": "6.2.1", - "@pixi/math": "6.2.1", - "@pixi/runner": "6.2.1", - "@pixi/settings": "6.2.1", - "@pixi/utils": "6.2.1", - "@seald-io/nedb": "3.1.0", "@swc/core": "1.3.20", - "@types/fs-extra": "9.0.13", - "@typescript-eslint/eslint-plugin": "5.44.0", - "@typescript-eslint/parser": "5.44.0", "conventional-changelog-cli": "2.2.2", "conventional-changelog-conventionalcommits": "5.0.0", "eslint": "8.28.0", @@ -70,13 +55,10 @@ "rollup-plugin-swc3": "0.7.0", "sass": "1.56.1", "semver": "7.3.8", - "ts-node": "10.9.1", - "tslib": "2.4.1", - "typescript": "4.7.4", "yargs": "17.6.2" }, "lint-staged": { - "*.(ts|js|cjs|mjs)": "eslint --cache --fix", + "*.(js|cjs|mjs)": "eslint --cache --fix", "*.(json|scss|yml)": "prettier --write" }, "packageManager": "yarn@3.2.4" diff --git a/rollup.config.js b/rollup.config.js index 4f325df..256579a 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -24,7 +24,7 @@ const isProduction = process.env.NODE_ENV === 'production'; * @type {import('rollup').RollupOptions} */ const config = { - input: { [name]: `${sourceDirectory}/${name}.ts` }, + input: { [name]: `${sourceDirectory}/${name}.js` }, output: { dir: distDirectory, format: 'es', diff --git a/src/apps/sidebar/combat-tracker.ts b/src/apps/sidebar/combat-tracker.js similarity index 65% rename from src/apps/sidebar/combat-tracker.ts rename to src/apps/sidebar/combat-tracker.js index 3a8adbe..d7c02dc 100644 --- a/src/apps/sidebar/combat-tracker.ts +++ b/src/apps/sidebar/combat-tracker.js @@ -3,35 +3,43 @@ // SPDX-License-Identifier: MIT export const registerCombatTrackerFunctionality = () => { - CONFIG.ui.combat = CombatTrackerMixin(CONFIG.ui.combat as typeof CombatTracker); // TODO: improve upstream types + CONFIG.ui.combat = CombatTrackerMixin(CONFIG.ui.combat); }; -const CombatTrackerMixin = (BaseCombatTracker: typeof CombatTracker) => { +/** + * Enhance a combat tracker class with functionality for Tickwerk. + * @param {typeof CombatTracker} BaseCombatTracker The combat tracker class to enhance + * @returns The enhanced combat tracker class + */ +const CombatTrackerMixin = (BaseCombatTracker) => { return class TickwerkCombatTracker extends BaseCombatTracker { - static override get defaultOptions(): ApplicationOptions { + /** @override */ + static get defaultOptions() { return foundry.utils.mergeObject(super.defaultOptions, { template: 'modules/tickwerk/templates/combat-tracker.hbs', }); } - override async getData(options?: Partial): Promise { + /** @override */ + async getData(options) { const data = await super.getData(options); return { ...data, turns: data.turns.map((turn) => ({ ...turn, waiting: this.viewed?.combatants.get(turn.id)?.waiting })), - } as CombatTracker.Data; // TODO: Improve upstream types + }; } - override activateListeners(html: JQuery): void { + /** @override */ + activateListeners(html) { super.activateListeners(html); html.find('.combatant-control[data-control="toggleWaiting"]').on('click', this._onToggleWaiting.bind(this)); } /** * Handle clicks on the Combatant waiting control button. - * @param event The originating click event + * @param {JQuery.ClickEvent} event The originating click event */ - _onToggleWaiting(event: JQuery.ClickEvent) { + _onToggleWaiting(event) { event.preventDefault(); event.stopPropagation(); const button = event.currentTarget; diff --git a/src/constants.ts b/src/constants.js similarity index 64% rename from src/constants.ts rename to src/constants.js index 0a54f83..28b3e82 100644 --- a/src/constants.ts +++ b/src/constants.js @@ -2,4 +2,4 @@ // // SPDX-License-Identifier: MIT -export const packageId = 'tickwerk' as const; +export const packageId = 'tickwerk'; diff --git a/src/data/documents/active-effect.ts b/src/data/documents/active-effect.js similarity index 67% rename from src/data/documents/active-effect.ts rename to src/data/documents/active-effect.js index d11fe6d..b640a6b 100644 --- a/src/data/documents/active-effect.ts +++ b/src/data/documents/active-effect.js @@ -5,12 +5,12 @@ import { getGame } from '../../helpers'; export const registerActiveEffectFunctionality = () => { - Hooks.on>('createActiveEffect', onActiveEffectChanged); - Hooks.on>('updateActiveEffect', onActiveEffectChanged); - Hooks.on>('deleteActiveEffect', onActiveEffectChanged); + Hooks.on('createActiveEffect', onActiveEffectChanged); + Hooks.on('updateActiveEffect', onActiveEffectChanged); + Hooks.on('deleteActiveEffect', onActiveEffectChanged); }; -const onActiveEffectChanged = (activeEffect: ActiveEffect) => { +const onActiveEffectChanged = (activeEffect) => { const game = getGame(); const parent = activeEffect.parent; const actorId = parent?.id; diff --git a/src/data/documents/combat.ts b/src/data/documents/combat.js similarity index 51% rename from src/data/documents/combat.ts rename to src/data/documents/combat.js index 1564249..dfcfc3c 100644 --- a/src/data/documents/combat.ts +++ b/src/data/documents/combat.js @@ -4,62 +4,76 @@ import { packageId } from '../../constants'; -import type { TickwerkCombatant } from './combatant'; - export const registerCombatFunctionality = () => { CONFIG.Combat.documentClass = CombatMixin(CONFIG.Combat.documentClass); }; -const CombatMixin = (BaseCombat: typeof Combat) => { +/** + * Enhance a combat class with functionality for the Tickwerk. + * @param {typeof Combat} BaseCombat The combat class to enhance + * @returns A combat class, adapted for the Tickwerk + */ +const CombatMixin = (BaseCombat) => { return class TickwerkCombat extends BaseCombat { - override get combatant() { - return this.turns[0]; + /** @override */ + get combatant() { + return this.turns?.[0]; } - override get round() { - return this.tickValue; + /** @override */ + get nextCombatant() { + return this.turns?.[1]; } - override get started() { - return this.turns.length > 0 && (this.getFlag(packageId, 'started') ?? false); - } - - override get turn() { - return 0; + /** @override */ + get started() { + return (this.turns?.length ?? 0) > 0 && (this.getFlag(packageId, 'started') ?? false); } /** * The current tick value of the Combat encounter. + * @type {number} */ - get tickValue(): number { + get tickValue() { const tickValues = this.combatants .filter((combatant) => !combatant.isDefeated && !combatant.waiting) .map((combatant) => combatant.initiative) - .filter((tickValue): tickValue is number => tickValue !== null); + .filter((tickValue) => tickValue !== null); const tickValue = Math.min(...tickValues); return tickValue === Infinity ? 0 : tickValue; } - override async nextRound(): Promise { + /** @override */ + prepareDerivedData() { + super.prepareDerivedData(); + this.turn = this.started ? 0 : null; + } + + /** @override */ + async nextRound() { throw new Error('Not implemented!'); } - override async nextTurn() { + /** @override */ + async nextTurn() { await this.combatant?.advanceTicksDialog(); return this; } - override previousRound(): Promise { + /** @override */ + previousRound() { throw new Error('Not implemented!'); } - override previousTurn(): Promise { + /** @override */ + previousTurn() { throw new Error('Not implemented!'); } - override async resetAll() { + /** @override */ + async resetAll() { for (const c of this.combatants) { - c.data.update({ initiative: null }); + c.updateSource({ initiative: null }); } return this.update( { @@ -74,7 +88,8 @@ const CombatMixin = (BaseCombat: typeof Combat) => { ); } - override setupTurns(): this['turns'] { + /** @override */ + setupTurns() { const turns = this.combatants.contents.sort(this._sortCombatants); const c = turns[0]; @@ -82,12 +97,13 @@ const CombatMixin = (BaseCombat: typeof Combat) => { round: this.round, turn: 0, combatantId: c?.id ?? null, - tokenId: c?.data.tokenId ?? null, + tokenId: c?.tokenId ?? null, }; return (this.turns = turns); } - override async startCombat(): Promise { + /** @override */ + async startCombat() { const hasCombatantWithTickValue = this.combatants.find( (combatant) => !combatant.isDefeated && combatant.initiative !== null, ); @@ -95,10 +111,14 @@ const CombatMixin = (BaseCombat: typeof Combat) => { ui.notifications?.warn('TICKWERK.WarningCannotStartCombat', { localize: true }); return this; } - return this.setFlag(packageId, 'started', true); + this._playCombatSound('startEncounter'); + const updateData = { flags: { [packageId]: { started: true } } }; + Hooks.callAll('combatStart', this, updateData); + return this.update(updateData); } - protected override _sortCombatants(a: TickwerkCombatant, b: TickwerkCombatant): number { + /** @override */ + _sortCombatants(a, b) { const da = a.isDefeated ? 1 : 0; const db = b.isDefeated ? 1 : 0; const cd = da - db; @@ -121,15 +141,36 @@ const CombatMixin = (BaseCombat: typeof Combat) => { return (b.id ?? '') > (a.id ?? '') ? 1 : -1; } + + /** @override */ + async _preUpdate(changed, options, user) { + delete changed.round; + delete changed.turn; + options.tickwerk = { + combatantBeforeUpdate: this.combatant?.id, + nextCombatantBeforeUpdate: this.nextCombatant?.id, + }; + return super._preUpdate(changed, options, user); + } + + /** @override */ + _onUpdate(data, options, userId) { + super._onUpdate(data, options, userId); + if (game.user.character) { + const { combatantBeforeUpdate, nextCombatantBeforeUpdate } = options.tickwerk ?? {}; + if ( + combatantBeforeUpdate !== undefined && + combatantBeforeUpdate !== this.combatant && + this.combatant?.actorId === game.user.character._id + ) { + this._playCombatSound('yourTurn'); + } else if ( + nextCombatantBeforeUpdate !== undefined && + nextCombatantBeforeUpdate !== this.nextCombatant && + this.nextCombatant?.actorId === game.user.character._id + ) + this._playCombatSound('nextUp'); + } + } }; }; - -declare global { - interface FlagConfig { - Combat: { - tickwerk?: { - started?: boolean; - }; - }; - } -} diff --git a/src/data/documents/combatant.ts b/src/data/documents/combatant.js similarity index 62% rename from src/data/documents/combatant.ts rename to src/data/documents/combatant.js index d7470a6..c996194 100644 --- a/src/data/documents/combatant.ts +++ b/src/data/documents/combatant.js @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: MIT -import type { CombatantDataConstructorData } from '@league-of-foundry-developers/foundry-vtt-types/src/foundry/common/data/data.mjs/combatantData'; import { packageId } from '../../constants'; import { getGame } from '../../helpers'; @@ -10,39 +9,49 @@ export const registerCombatantFunctionality = () => { CONFIG.Combatant.documentClass = CombatantMixin(CONFIG.Combatant.documentClass); }; -const CombatantMixin = (BaseCombatant: typeof Combatant) => { +/** + * Enhance a co,batant class with functionality for the Tickwerk. + * @param {typeof Combatant} BaseCombatant The combat class to enhance + * @returns A combat class, adapted for the Tickwerk + */ +const CombatantMixin = (BaseCombatant) => { return class TickwerkCombatant extends BaseCombatant { /** * An temporary property to make changes to the initiative available to other instances in their `_pre…` methods. + * @type {number|null|undefined} */ - _newInitiative: number | null | undefined; + _newInitiative; /** * An temporary property to make changes to the tiebreaker available to other instances in their `_pre…` methods. + * @type {number|undefined} */ - _newTiebreaker: number | undefined; + _newTiebreaker; /*** * Is this combatant currently waiting? + * @type {boolean} */ - get waiting(): boolean { + get waiting() { return this.getFlag(packageId, 'waiting') ?? false; } /** * Toggle the waiting state of this combatant. + * @returns {Promise} The updated combatant */ - toggleWaiting(): Promise { - const update: Record = { [`flags.${packageId}.waiting`]: !this.waiting }; + toggleWaiting() { + const update = { [`flags.${packageId}.waiting`]: !this.waiting }; if (this.parent?.started && this.waiting) update.initiative = this.parent?.round; return this.update(update); } /** * Advance for the given number of ticks. - * @param ticks The number of ticks to advance for + * @param {number} ticks The number of ticks to advance for + * @returns {Promise} A promise that resolves once when the combatant has advanced */ - async advanceTicks(ticks: number): Promise { + async advanceTicks(ticks) { if (this.initiative === null) { ui.notifications?.warn('TICKWERK.WarningCannotAdvanceWithoutTickValue', { localize: true }); return; @@ -59,11 +68,15 @@ const CombatantMixin = (BaseCombatant: typeof Combatant) => { await this.update({ initiative: this.initiative + ticks }); const advanceTime = ticks * CONFIG.time.roundTime; if (advanceTime !== 0) { - await this.combat?.update(undefined, { diff: false, advanceTime } as DocumentModificationContext); + await this.combat?.update(undefined, { diff: false, advanceTime }); } } - async advanceTicksDialog(): Promise { + /** + * Show a dialog for advancing the combatant a certain number of ticks. + * @returns {Promise} A promise that resolves when the dialog has been confirmed and the combatant has advanced + */ + async advanceTicksDialog() { const game = getGame(); const id = foundry.utils.randomID(); @@ -76,14 +89,14 @@ const CombatantMixin = (BaseCombatant: typeof Combatant) => { title: game.i18n.localize('TICKWERK.AdvanceTicks'), content: form, yes: (html) => { - const ticks = html[0]?.querySelector('input[name="ticks"]')?.value; + const ticks = html[0]?.querySelector('input[name="ticks"]')?.value; const parsedTicks = ticks !== undefined ? parseInt(ticks) : undefined; - return Number.isSafeInteger(parsedTicks) ? parsedTicks : undefined; + return Number.isSafeInteger(parsedTicks) ? parsedTicks : NaN; }, rejectClose: false, }); - if (ticks === undefined) { + if (Number.isNaN(ticks)) { ui.notifications?.warn('TICKWERK.WarningInvalidNumberOfTicks', { localize: true }); return; } @@ -95,9 +108,9 @@ const CombatantMixin = (BaseCombatant: typeof Combatant) => { /** * Update tiebreaker data for a given creation or update. - * @param data The data of the creation / update + * @param {object} data The data of the creation / update */ - async #updateTiebreakerData(data: DeepPartial): Promise { + async #updateTiebreakerData(data) { if ('initiative' in data) { const combatantsWithSameTickValue = this.parent?.combatants.filter((combatant) => { @@ -106,7 +119,7 @@ const CombatantMixin = (BaseCombatant: typeof Combatant) => { return otherInitiative === data.initiative; }) ?? []; const tiebreaker = await this.#getTiebreaker(combatantsWithSameTickValue); - setProperty(data, `flags.${packageId}.tiebreaker`, tiebreaker); + foundry.utils.setProperty(data, `flags.${packageId}.tiebreaker`, tiebreaker); this._newInitiative = data.initiative; this._newTiebreaker = tiebreaker; } @@ -114,46 +127,48 @@ const CombatantMixin = (BaseCombatant: typeof Combatant) => { /** * Get a tiebreaker between this combatant and the given other combatants. - * @param combatants The other combatants among which to find a tiebreaker - * @returns A promise that resolves to the tiebreaker + * @param {TickwerkCombatant[]} combatants The other combatants among which to find a tiebreaker + * @returns {Promise} A promise that resolves to the tiebreaker */ - async #getTiebreaker(combatants: TickwerkCombatant[]): Promise { + async #getTiebreaker(combatants) { const getTiebreaker = CONFIG.tickwerk?.getTiebreaker ?? defaultGetTiebreaker; return getTiebreaker(this, combatants); } - override testUserPermission( - user: foundry.documents.BaseUser, - permission: keyof typeof foundry.CONST.DOCUMENT_PERMISSION_LEVELS | foundry.CONST.DOCUMENT_PERMISSION_LEVELS, - { exact }: { exact?: boolean | undefined } = {}, - ): boolean { + /** @override */ + testUserPermission(user, permission, { exact } = {}) { if (user.isGM) return true; return super.testUserPermission(user, permission, { exact }); } - protected override _getInitiativeFormula(): string { + /** @override */ + _getInitiativeFormula() { const getInitiativeFormula = CONFIG.tickwerk?.getInitiativeFormula; if (getInitiativeFormula) return getInitiativeFormula(this); return super._getInitiativeFormula(); } - protected override async _preCreate(...args: Parameters): Promise { + /** @override */ + async _preCreate(...args) { await super._preCreate(...args); await this.#updateTiebreakerData(args[0]); } - protected override async _preUpdate(...args: Parameters): Promise { + /** @override */ + async _preUpdate(...args) { await super._preUpdate(...args); await this.#updateTiebreakerData(args[0]); } - protected override _onCreate(...args: Parameters): void { + /** @override */ + _onCreate(...args) { super._onCreate(...args); this._newInitiative = undefined; this._newTiebreaker = undefined; } - protected override _onUpdate(...args: Parameters): void { + /** @override */ + _onUpdate(...args) { super._onUpdate(...args); this._newInitiative = undefined; this._newTiebreaker = undefined; @@ -161,7 +176,16 @@ const CombatantMixin = (BaseCombatant: typeof Combatant) => { }; }; -const defaultGetTiebreaker = async (combatant: TickwerkCombatant, combatants: TickwerkCombatant[]): Promise => { +/** + * A function to get a tiebreaker for a combatant + * @typedef {(combatant: TickwerkCombatant, combatants: TickwerkCombatant[]) => Promise} GetTiebreaker + */ + +/** + * Default implementation to get a tiebreaker for a combatant. + * @type {GetTiebreaker} + */ +const defaultGetTiebreaker = async (combatant, combatants) => { if (combatants.length === 0) return 0; const tiebreakers = combatants.map((combatant) => { return ( @@ -172,28 +196,3 @@ const defaultGetTiebreaker = async (combatant: TickwerkCombatant, combatants: Ti }); return Math.max(...tiebreakers) + 1; }; - -export type TickwerkCombatantConstructor = ReturnType; -export type TickwerkCombatant = InstanceType; - -declare global { - interface FlagConfig { - Combatant: { - tickwerk: { - tiebreaker?: number | undefined; - waiting?: boolean | undefined; - }; - }; - } - - interface DocumentClassConfig { - Combatant: TickwerkCombatantConstructor; - } - - interface CONFIG { - tickwerk?: { - getTiebreaker?: (combatant: TickwerkCombatant, combatants: TickwerkCombatant[]) => Promise; - getInitiativeFormula?: (combatant: TickwerkCombatant) => string; - }; - } -} diff --git a/src/helpers.ts b/src/helpers.js similarity index 83% rename from src/helpers.ts rename to src/helpers.js index 20c5adf..c1c1276 100644 --- a/src/helpers.ts +++ b/src/helpers.js @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: MIT -export const getGame = (): Game => { +export const getGame = () => { if (!(game instanceof Game)) { throw new Error('game is not initialized yet.'); } diff --git a/src/systems/ds4.ts b/src/systems/ds4.js similarity index 62% rename from src/systems/ds4.ts rename to src/systems/ds4.js index 0cffe69..c978e56 100644 --- a/src/systems/ds4.ts +++ b/src/systems/ds4.js @@ -5,7 +5,6 @@ import { packageId } from '../constants'; import { getGame } from '../helpers'; -import type { TickwerkCombatant } from '../data/documents/combatant'; export const registerDS4SpecificFunctionality = () => { if (CONFIG.tickwerk === undefined) CONFIG.tickwerk = {}; foundry.utils.mergeObject(CONFIG.tickwerk, { getTiebreaker, getInitiativeFormula }); @@ -14,20 +13,22 @@ export const registerDS4SpecificFunctionality = () => { Hooks.on('ds4.rollItem', onRollItem); }; -const getTiebreaker = async (combatant: TickwerkCombatant, combatants: TickwerkCombatant[]): Promise => { - const ds4combatant = combatant as DS4TickwerkCombatant; - const ds4combatants = combatants as DS4TickwerkCombatant[]; +/** @type {import("../data/documents/combatant").GetTiebreaker} */ +const getTiebreaker = async (combatant, combatants) => { if (combatants.length === 0) return 0; - const lowerBounds: number[] = []; - const upperBounds: number[] = []; - const equals: number[] = []; + /** @type {number[]} */ + const lowerBounds = []; + /** @type {number[]} */ + const upperBounds = []; + /** @type {number[]} */ + const equals = []; - for (const combatant of ds4combatants) { - const tiebreaker = combatant._newTiebreaker ?? combatant.getFlag(packageId, 'tiebreaker') ?? 0; - if (getInitiative(combatant) > getInitiative(ds4combatant)) { + for (const other of combatants) { + const tiebreaker = other._newTiebreaker ?? other.getFlag(packageId, 'tiebreaker') ?? 0; + if (getInitiative(other) > getInitiative(combatant)) { lowerBounds.push(tiebreaker); - } else if (getInitiative(combatant) < getInitiative(ds4combatant)) { + } else if (getInitiative(other) < getInitiative(combatant)) { upperBounds.push(tiebreaker); } else { equals.push(tiebreaker); @@ -62,37 +63,32 @@ const getTiebreaker = async (combatant: TickwerkCombatant, combatants: TickwerkC } }; -const getInitiativeFormula = (combatant: TickwerkCombatant) => { +/** + * Get the initiative formula for a combatant. + * @param {TickwerkCombatant} combatant The combatant for which to get the initiative formula + * @returns {string} The initiative formula + */ +const getInitiativeFormula = (combatant) => { const started = combatant.combat?.started ?? false; if (!started) return '-@combatValues.initiative.total'; const tickValue = combatant.combat?.round ?? 0; return `max(${tickValue} + 10 - @combatValues.initiative.total, ${tickValue})`; }; -type DS4TickwerkCombatant = TickwerkCombatant & { actor: (Actor & { data: { data: ActorData } }) | null }; - -const getInitiative = (combatant: DS4TickwerkCombatant): number => { - return combatant.actor?.data.data.combatValues.initiative.total ?? -Infinity; +/** + * Get the initiative for a combatant. + * @param {TickwerkCombatant} combatant The combatant for which to get the initiative + * @returns {number} + */ +const getInitiative = (combatant) => { + return combatant.actor?.system.combatValues.initiative.total ?? -Infinity; }; -interface ActorData { - combatValues: { - initiative: { - total: number; - }; - }; -} - -declare global { - // eslint-disable-next-line @typescript-eslint/no-namespace - namespace Hooks { - interface StaticCallbacks { - 'ds4.rollItem': (item: Item) => void; - } - } -} - -const onRollItem = (item: Item) => { +/** + * React to an item roll by prompting the user to advance ticks. + * @param {Item} item The item that has been rolled + */ +const onRollItem = (item) => { const game = getGame(); if (game.settings.get(packageId, 'ds4.reactToRollItemHook')) { if (['weapon', 'spell'].includes(item.type) && item.actor?.id) { diff --git a/src/systems/index.ts b/src/systems/index.js similarity index 100% rename from src/systems/index.ts rename to src/systems/index.js diff --git a/src/tickwerk.ts b/src/tickwerk.js similarity index 100% rename from src/tickwerk.ts rename to src/tickwerk.js diff --git a/template.json b/template.json deleted file mode 100644 index f37c11e..0000000 --- a/template.json +++ /dev/null @@ -1,225 +0,0 @@ -{ - "Actor": { - "types": ["character", "creature"], - "templates": { - "base": { - "attributes": { - "body": { - "base": 0, - "mod": 0 - }, - "mobility": { - "base": 0, - "mod": 0 - }, - "mind": { - "base": 0, - "mod": 0 - } - }, - "traits": { - "strength": { - "base": 0, - "mod": 0 - }, - "constitution": { - "base": 0, - "mod": 0 - }, - "agility": { - "base": 0, - "mod": 0 - }, - "dexterity": { - "base": 0, - "mod": 0 - }, - "intellect": { - "base": 0, - "mod": 0 - }, - "aura": { - "base": 0, - "mod": 0 - } - }, - "combatValues": { - "hitPoints": { - "mod": 0, - "value": 0 - }, - "defense": { - "mod": 0 - }, - "initiative": { - "mod": 0 - }, - "movement": { - "mod": 0 - }, - "meleeAttack": { - "mod": 0 - }, - "rangedAttack": { - "mod": 0 - }, - "spellcasting": { - "mod": 0 - }, - "targetedSpellcasting": { - "mod": 0 - } - } - } - }, - "creature": { - "templates": ["base"], - "baseInfo": { - "loot": "", - "foeFactor": 1, - "creatureType": "humanoid", - "sizeCategory": "normal", - "experiencePoints": 0, - "description": "" - } - }, - "character": { - "templates": ["base"], - "baseInfo": { - "race": "", - "class": "", - "heroClass": "", - "culture": "" - }, - "progression": { - "level": 0, - "experiencePoints": 0, - "talentPoints": { - "total": 0, - "used": 0 - }, - "progressPoints": { - "total": 0, - "used": 0 - } - }, - "profile": { - "biography": "", - "gender": "", - "birthday": "", - "birthplace": "", - "age": 0, - "height": 0, - "hairColor": "", - "weight": 0, - "eyeColor": "", - "specialCharacteristics": "" - }, - "currency": { - "gold": 0, - "silver": 0, - "copper": 0 - }, - "slayerPoints": { - "value": 0 - } - } - }, - "Item": { - "types": [ - "weapon", - "armor", - "shield", - "spell", - "equipment", - "loot", - "talent", - "racialAbility", - "language", - "alphabet", - "specialCreatureAbility" - ], - "templates": { - "base": { - "description": "" - }, - "physical": { - "quantity": 1, - "price": 0, - "availability": "unset", - "storageLocation": "-" - }, - "equipable": { - "equipped": false - }, - "protective": { - "armorValue": 0 - } - }, - "weapon": { - "templates": ["base", "physical", "equipable"], - "attackType": "melee", - "weaponBonus": 0, - "opponentDefense": 0 - }, - "armor": { - "templates": ["base", "physical", "equipable", "protective"], - "armorMaterialType": "cloth", - "armorType": "body" - }, - "shield": { - "templates": ["base", "physical", "equipable", "protective"] - }, - "spell": { - "templates": ["base", "equipable"], - "spellType": "spellcasting", - "bonus": "", - "spellCategory": "unset", - "maxDistance": { - "value": "", - "unit": "meter" - }, - "effectRadius": { - "value": "", - "unit": "meter" - }, - "duration": { - "value": "", - "unit": "custom" - }, - "cooldownDuration": "0r", - "minimumLevels": { - "healer": null, - "wizard": null, - "sorcerer": null - } - }, - "equipment": { - "templates": ["base", "physical", "equipable"] - }, - "loot": { - "templates": ["base", "physical"] - }, - "talent": { - "templates": ["base"], - "rank": { - "base": 0, - "max": 0, - "mod": 0 - } - }, - "racialAbility": { - "templates": ["base"] - }, - "language": { - "templates": ["base"] - }, - "alphabet": { - "templates": ["base"] - }, - "specialCreatureAbility": { - "templates": ["base"], - "experiencePoints": 0 - } - } -} diff --git a/template.json.license b/template.json.license deleted file mode 100644 index ff79d3f..0000000 --- a/template.json.license +++ /dev/null @@ -1,6 +0,0 @@ -SPDX-FileCopyrightText: 2021 Johannes Loher -SPDX-FileCopyrightText: 2021 Oliver Rümpelein -SPDX-FileCopyrightText: 2021 Gesina Schwalbe -SPDX-FileCopyrightText: 2021 Siegfried Krug - -SPDX-License-Identifier: MIT diff --git a/templates/combat-tracker.hbs b/templates/combat-tracker.hbs index 6c74ff3..9772de4 100644 --- a/templates/combat-tracker.hbs +++ b/templates/combat-tracker.hbs @@ -5,82 +5,89 @@ SPDX-FileCopyrightText: 2022 Johannes Loher SPDX-License-Identifier: MIT --}} -