├── .editorconfig ├── .eslintignore ├── .eslintrc.cjs ├── .github ├── FUNDING.yml └── workflows │ ├── checks.yml │ └── release.yml ├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── gulpfile.mjs ├── package-lock.json ├── package.json ├── src ├── languages │ ├── cn.json │ ├── de.json │ ├── en.json │ ├── es.json │ ├── fi.json │ ├── fr.json │ ├── pl.json │ └── pt-BR.json ├── main.js ├── module.json └── packs │ └── macros.db └── wiki └── advanced_macro_img.png /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | [*.yml] 11 | indent_size = 2 12 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | gulpfile.mjs 2 | .eslintrc.js 3 | .prettierrc.js 4 | dist 5 | jsconfig.json 6 | /dist 7 | /.pnp.js 8 | /.yarn/ 9 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Johannes Loher 2 | // SPDX-FileCopyrightText: 2022 David Archibald 3 | // 4 | // SPDX-License-Identifier: MIT 5 | 6 | module.exports = { 7 | parserOptions: { 8 | ecmaVersion: 13, 9 | extraFileExtensions: [".cjs", ".mjs"], 10 | sourceType: "module", 11 | }, 12 | 13 | env: { 14 | browser: true, 15 | es6: true, 16 | jquery: true, 17 | }, 18 | 19 | extends: ["eslint:recommended", "@typhonjs-fvtt/eslint-config-foundry.js/0.8.0"], 20 | 21 | plugins: [], 22 | 23 | rules: { 24 | "array-bracket-spacing": ["warn", "never"], 25 | "array-callback-return": "warn", 26 | "arrow-spacing": "warn", 27 | "brace-style": "warn", 28 | "comma-dangle": ["warn", "only-multiline"], 29 | "comma-style": "warn", 30 | "computed-property-spacing": "warn", 31 | "constructor-super": "error", 32 | "default-param-last": "warn", 33 | "dot-location": ["warn", "property"], 34 | "eol-last": ["error", "always"], 35 | eqeqeq: ["warn", "smart"], 36 | "func-call-spacing": "warn", 37 | "func-names": ["warn", "never"], 38 | "getter-return": "warn", 39 | indent: ["warn", "tab", { SwitchCase: 1 }], 40 | "lines-between-class-members": "warn", 41 | "new-parens": ["warn", "always"], 42 | "no-alert": "warn", 43 | "no-array-constructor": "warn", 44 | "no-class-assign": "warn", 45 | "no-compare-neg-zero": "warn", 46 | "no-cond-assign": "warn", 47 | "no-const-assign": "error", 48 | "no-constant-condition": "warn", 49 | "no-constructor-return": "warn", 50 | "no-delete-var": "warn", 51 | "no-dupe-args": "warn", 52 | "no-dupe-class-members": "warn", 53 | "no-dupe-keys": "warn", 54 | "no-duplicate-case": "warn", 55 | "no-duplicate-imports": ["warn", { includeExports: true }], 56 | "no-else-return": "warn", 57 | "no-empty": ["warn", { allowEmptyCatch: true }], 58 | "no-empty-character-class": "warn", 59 | "no-empty-pattern": "warn", 60 | "no-func-assign": "warn", 61 | "no-global-assign": "warn", 62 | "no-implicit-coercion": ["warn", { allow: ["!!"] }], 63 | "no-implied-eval": "warn", 64 | "no-import-assign": "warn", 65 | "no-invalid-regexp": "warn", 66 | "no-irregular-whitespace": "warn", 67 | "no-iterator": "warn", 68 | "no-lone-blocks": "warn", 69 | "no-lonely-if": "warn", 70 | "no-misleading-character-class": "warn", 71 | "no-mixed-operators": "warn", 72 | "no-multi-str": "warn", 73 | "no-multiple-empty-lines": ["warn", { max: 1 }], 74 | "no-new-func": "warn", 75 | "no-new-object": "warn", 76 | "no-new-symbol": "warn", 77 | "no-new-wrappers": "warn", 78 | "no-nonoctal-decimal-escape": "warn", 79 | "no-obj-calls": "warn", 80 | "no-octal": "warn", 81 | "no-octal-escape": "warn", 82 | "no-promise-executor-return": "warn", 83 | "no-proto": "warn", 84 | "no-regex-spaces": "warn", 85 | "no-script-url": "warn", 86 | "no-self-assign": "warn", 87 | "no-self-compare": "warn", 88 | "no-setter-return": "warn", 89 | "no-sequences": "warn", 90 | "no-template-curly-in-string": "warn", 91 | "no-this-before-super": "error", 92 | "no-unexpected-multiline": "warn", 93 | "no-unmodified-loop-condition": "warn", 94 | "no-unneeded-ternary": "warn", 95 | "no-unreachable": "warn", 96 | "no-unreachable-loop": "warn", 97 | "no-unsafe-negation": ["warn", { enforceForOrderingRelations: true }], 98 | "no-unsafe-optional-chaining": ["warn", { disallowArithmeticOperators: true }], 99 | "no-unused-expressions": "warn", 100 | "no-useless-backreference": "warn", 101 | "no-useless-call": "warn", 102 | "no-useless-catch": "warn", 103 | "no-useless-computed-key": ["warn", { enforceForClassMembers: true }], 104 | "no-useless-concat": "warn", 105 | "no-useless-constructor": "warn", 106 | "no-useless-rename": "warn", 107 | "no-useless-return": "warn", 108 | "no-var": "warn", 109 | "no-void": "warn", 110 | "no-whitespace-before-property": "warn", 111 | "prefer-numeric-literals": "warn", 112 | "prefer-object-spread": "warn", 113 | "prefer-regex-literals": "warn", 114 | "prefer-spread": "warn", 115 | "rest-spread-spacing": ["warn", "never"], 116 | "semi-spacing": "warn", 117 | "semi-style": ["warn", "last"], 118 | "space-unary-ops": ["warn", { words: true, nonwords: false }], 119 | "switch-colon-spacing": "warn", 120 | "symbol-description": "warn", 121 | "template-curly-spacing": ["warn", "never"], 122 | "unicode-bom": ["warn", "never"], 123 | "use-isnan": ["warn", { enforceForSwitchCase: true, enforceForIndexOf: true }], 124 | "valid-typeof": ["warn", { requireStringLiterals: true }], 125 | "wrap-iife": ["warn", "inside"], 126 | 127 | "arrow-parens": ["warn", "always"], 128 | "comma-spacing": "warn", 129 | "dot-notation": "warn", 130 | "key-spacing": "warn", 131 | "keyword-spacing": ["warn", { overrides: { catch: { before: true, after: false } } }], 132 | "max-len": [ 133 | "warn", 134 | { 135 | code: 120, 136 | ignoreComments: true, 137 | ignoreTrailingComments: true, 138 | ignoreUrls: true, 139 | ignoreStrings: true, 140 | ignoreTemplateLiterals: true, 141 | ignoreRegExpLiterals: true, 142 | }, 143 | ], 144 | "no-extra-boolean-cast": ["warn", { enforceForLogicalOperands: true }], 145 | "no-extra-semi": "warn", 146 | "no-multi-spaces": ["warn", { ignoreEOLComments: true }], 147 | "no-throw-literal": "error", 148 | "no-trailing-spaces": "warn", 149 | "no-useless-escape": "warn", 150 | "no-unused-vars": ["warn", { args: "none" }], 151 | "nonblock-statement-body-position": ["warn", "beside"], 152 | "one-var": ["warn", "never"], 153 | "operator-linebreak": [ 154 | "warn", 155 | "before", 156 | { 157 | overrides: { "=": "after", "+=": "after", "-=": "after" }, 158 | }, 159 | ], 160 | "prefer-template": "warn", 161 | "quote-props": ["warn", "as-needed", { keywords: false }], 162 | quotes: ["warn", "double", { avoidEscape: true, allowTemplateLiterals: false }], 163 | semi: "warn", 164 | "space-before-blocks": ["warn", "always"], 165 | "space-before-function-paren": [ 166 | "warn", 167 | { 168 | anonymous: "always", 169 | named: "never", 170 | asyncArrow: "always", 171 | }, 172 | ], 173 | "spaced-comment": "warn", 174 | }, 175 | 176 | globals: { 177 | TextEditor: false 178 | }, 179 | 180 | overrides: [ 181 | { 182 | files: ["./*.js", "./*.cjs", "./*.mjs"], 183 | env: { 184 | node: true, 185 | }, 186 | }, 187 | ], 188 | }; 189 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: mclemente 2 | custom: https://www.paypal.com/donate/?hosted_button_id=UVFV99HDGE5F8 3 | -------------------------------------------------------------------------------- /.github/workflows/checks.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2022 Johannes Loher 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | name: Checks 6 | 7 | on: 8 | - push 9 | - pull_request 10 | 11 | env: 12 | node_version: 20 13 | 14 | jobs: 15 | lint: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout code 19 | uses: actions/checkout@v4 20 | 21 | - name: Install node 22 | uses: actions/setup-node@v4 23 | with: 24 | node-version: ${{ env.node_version }} 25 | 26 | - name: Cache Node.js modules 27 | uses: actions/cache@v4 28 | with: 29 | path: .npm 30 | key: ${{ runner.OS }}-node-${{ hashFiles('**/package-lock.json') }} 31 | restore-keys: | 32 | ${{ runner.OS }}-node- 33 | ${{ runner.OS }}- 34 | 35 | - name: Install dependencies 36 | run: npm ci --cache .npm --prefer-offline 37 | 38 | - name: Lint 39 | run: npm run lint 40 | 41 | build: 42 | runs-on: ubuntu-latest 43 | steps: 44 | - name: Checkout code 45 | uses: actions/checkout@v4 46 | 47 | - name: Install node 48 | uses: actions/setup-node@v4 49 | with: 50 | node-version: ${{ env.node_version }} 51 | 52 | - name: Cache Node.js modules 53 | uses: actions/cache@v4 54 | with: 55 | path: .npm 56 | key: ${{ runner.OS }}-node-${{ hashFiles('**/package-lock.json') }} 57 | restore-keys: | 58 | ${{ runner.OS }}-node- 59 | ${{ runner.OS }}- 60 | 61 | - name: Install dependencies 62 | run: npm ci --cache .npm --prefer-offline 63 | 64 | - name: Build 65 | run: npm run build 66 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2022 Johannes Loher 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | name: Release 6 | 7 | on: 8 | release: 9 | types: [published] 10 | 11 | env: 12 | package_type: module 13 | node_version: 20 14 | 15 | jobs: 16 | lint: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Checkout code 20 | uses: actions/checkout@v4 21 | 22 | - name: Install node 23 | uses: actions/setup-node@v4 24 | with: 25 | node-version: ${{ env.node_version }} 26 | 27 | - name: Cache Node.js modules 28 | uses: actions/cache@v4 29 | with: 30 | path: .npm 31 | key: ${{ runner.OS }}-node-${{ hashFiles('**/package-lock.json') }} 32 | restore-keys: | 33 | ${{ runner.OS }}-node- 34 | ${{ runner.OS }}- 35 | 36 | - name: Install dependencies 37 | run: npm ci --cache .npm --prefer-offline 38 | 39 | - name: Lint 40 | run: npm run lint 41 | 42 | build: 43 | runs-on: ubuntu-latest 44 | steps: 45 | - name: Checkout code 46 | uses: actions/checkout@v4 47 | 48 | - name: Install node 49 | uses: actions/setup-node@v4 50 | with: 51 | node-version: ${{ env.node_version }} 52 | 53 | - name: Cache Node.js modules 54 | uses: actions/cache@v4 55 | with: 56 | path: .npm 57 | key: ${{ runner.OS }}-node-${{ hashFiles('**/package-lock.json') }} 58 | restore-keys: | 59 | ${{ runner.OS }}-node- 60 | ${{ runner.OS }}- 61 | 62 | - name: Install dependencies 63 | run: npm ci --cache .npm --prefer-offline 64 | 65 | - name: Extract tag version number 66 | id: get_version 67 | uses: battila7/get-version-action@v2 68 | 69 | - name: Substitute Manifest and Download Links For Versioned Ones 70 | id: sub_manifest_link_version 71 | uses: microsoft/variable-substitution@v1 72 | with: 73 | files: "src/${{ env.package_type }}.json" 74 | env: 75 | version: ${{ steps.get_version.outputs.version-without-v }} 76 | url: https://github.com/${{ github.repository }} 77 | manifest: https://github.com/${{ github.repository }}/releases/latest/download/${{ env.package_type }}.json 78 | download: https://github.com/${{ github.repository }}/releases/download/${{ github.event.release.tag_name }}/${{ env.package_type }}.zip 79 | 80 | - name: Build 81 | run: npm run build 82 | 83 | - name: Archive production artifacts 84 | uses: actions/upload-artifact@v4 85 | with: 86 | name: dist 87 | path: dist 88 | 89 | publish: 90 | needs: 91 | - lint 92 | - build 93 | runs-on: ubuntu-latest 94 | steps: 95 | - name: Checkout code 96 | uses: actions/checkout@v4 97 | 98 | - name: Download production artifacts for publication 99 | uses: actions/download-artifact@v4 100 | with: 101 | name: dist 102 | path: dist 103 | 104 | - name: Create zip file 105 | working-directory: ./dist 106 | run: zip -r ../${{ env.package_type }}.zip . 107 | 108 | - name: Create release 109 | id: create_version_release 110 | uses: ncipollo/release-action@v1 111 | with: 112 | allowUpdates: true 113 | name: ${{ github.event.release.name }} 114 | token: ${{ secrets.GITHUB_TOKEN }} 115 | artifacts: "./dist/${{ env.package_type }}.json, ./${{ env.package_type }}.zip" 116 | tag: ${{ github.event.release.tag_name }} 117 | body: ${{ github.event.release.body }} 118 | 119 | - name: Get Module ID 120 | id: moduleID 121 | uses: notiz-dev/github-action-json-property@release 122 | with: 123 | path: "./dist/${{ env.package_type }}.json" 124 | prop_path: "id" 125 | 126 | - name: Get mininum 127 | id: minimum 128 | uses: notiz-dev/github-action-json-property@release 129 | with: 130 | path: "./dist/${{ env.package_type }}.json" 131 | prop_path: "compatibility.minimum" 132 | 133 | - name: Get verified 134 | id: verified 135 | uses: notiz-dev/github-action-json-property@release 136 | with: 137 | path: "./dist/${{ env.package_type }}.json" 138 | prop_path: "compatibility.verified" 139 | 140 | - name: Extract tag version number 141 | id: get_version 142 | uses: battila7/get-version-action@v2 143 | 144 | - name: Foundry Release API 145 | uses: fjogeleit/http-request-action@v1 146 | with: 147 | url: "https://api.foundryvtt.com/_api/packages/release_version" 148 | method: "POST" 149 | customHeaders: '{"Content-Type": "application/json", "Authorization" : "${{ secrets.FOUNDRY_KEY }}"}' 150 | data: '{"id": "${{ steps.moduleID.outputs.prop }}", "release": {"version": "${{ steps.get_version.outputs.version-without-v }}", "manifest": "https://github.com/${{ github.repository }}/releases/download/${{ github.event.release.tag_name }}/${{ env.package_type }}.json", "notes": "https://github.com/${{ github.repository }}/releases/tag/${{ github.event.release.tag_name }}/", "compatibility" : {"minimum": "${{ steps.minimum.outputs.prop }}", "verified": "${{ steps.verified.outputs.prop }}"}}}' 151 | preventFailureOnNoResponse: true 152 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # Snowpack dependency directory (https://snowpack.dev/) 45 | web_modules/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | .parcel-cache 78 | 79 | # Next.js build output 80 | .next 81 | out 82 | 83 | # Nuxt.js build / generate output 84 | .nuxt 85 | 86 | # Gatsby files 87 | .cache/ 88 | # Comment in the public line in if your project uses Gatsby and not Next.js 89 | # https://nextjs.org/blog/next-9-1#public-directory-support 90 | # public 91 | 92 | # vuepress build output 93 | .vuepress/dist 94 | 95 | # Serverless directories 96 | .serverless/ 97 | 98 | # FuseBox cache 99 | .fusebox/ 100 | 101 | # DynamoDB Local files 102 | .dynamodb/ 103 | 104 | # TernJS port file 105 | .tern-port 106 | 107 | # Stores VSCode versions used for testing VSCode extensions 108 | .vscode-test 109 | 110 | # yarn v2 111 | .yarn/cache 112 | .yarn/unplugged 113 | .yarn/build-state.yml 114 | .yarn/install-state.gz 115 | .pnp.* 116 | 117 | # Foundry 118 | foundry.js 119 | /.vscode 120 | /dist 121 | /package 122 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "tabWidth": 4, 4 | "useTabs": true, 5 | "semi": true, 6 | "trailingComma": "all", 7 | "arrowParens": "always" 8 | } 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Matheus Clemente, 4535992 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![GitHub release](https://img.shields.io/github/release-date/mclemente/fvtt-advanced-macros) 2 | ![the latest version](https://img.shields.io/github/downloads/mclemente/fvtt-advanced-macros/latest/module.zip) 3 | 4 | [![ko-fi](https://img.shields.io/badge/ko--fi-Support%20Me-red?style=flat-square&logo=ko-fi)](https://ko-fi.com/mclemente) 5 | 6 | # Advanced Macros 7 | 8 | Check out the Macros compendium for some useful macros that showcase the advanced macros system as well as provide additional features. 9 | 10 | # Build 11 | 12 | See the [Build](./wiki/Build) instructions. 13 | 14 | ## Development and Contributing 15 | 16 | Advanced Macros is a free and open source project. You can contribute to the project by making a merge request or by creating a [Github issue](https://github.com/mclemente/fvtt-advanced-macros/issues). 17 | Translations are done on the Foundry Hub Weblate directly. Check the [Weblate](https://weblate.foundryvtt-hub.com/engage/advanced-macros/) page for contributing. 18 | 19 | 20 | Translation status 21 | 22 | 23 | # Attribution 24 | 25 | This work is licensed under the MIT license. 26 | 27 | This work contains code originally from [The Furnace](https://github.com/League-of-Foundry-Developers/fvtt-module-furnace) module, writen by KaKaRoTo. 28 | 29 | This work is licensed under Foundry Virtual Tabletop [Limited License Agreement for Module Development](https://foundryvtt.com/article/license/). 30 | -------------------------------------------------------------------------------- /gulpfile.mjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-useless-escape */ 2 | /* eslint-disable @typescript-eslint/no-var-requires */ 3 | 4 | import fs from "fs-extra"; 5 | import gulp from "gulp"; 6 | import path from "node:path"; 7 | import yargs from "yargs"; 8 | import { hideBin } from "yargs/helpers"; 9 | 10 | /********************/ 11 | /* CONFIGURATION */ 12 | /********************/ 13 | 14 | const packageId = "advanced-macros"; 15 | const sourceDirectory = "./src"; 16 | const distDirectory = "./dist"; 17 | const sourceFileExtension = "js"; 18 | const staticFiles = ["languages", "packs", "module.json"]; 19 | 20 | /********************/ 21 | /* BUILD */ 22 | /********************/ 23 | 24 | function buildCode() { 25 | return ( 26 | gulp 27 | .src(`${sourceDirectory}/**/*.${sourceFileExtension}`) 28 | .pipe(gulp.dest(`dist`)) 29 | ); 30 | } 31 | 32 | /** 33 | * Copy static files 34 | */ 35 | async function copyFiles() { 36 | for (const file of staticFiles) { 37 | if (fs.existsSync(`${sourceDirectory}/${file}`)) { 38 | await fs.copy(`${sourceDirectory}/${file}`, `${distDirectory}/${file}`); 39 | } 40 | } 41 | } 42 | /** 43 | * Watch for changes for each build step 44 | */ 45 | export function watch() { 46 | gulp.watch(`${sourceDirectory}/**/*.${sourceFileExtension}`, { ignoreInitial: false }, buildCode); 47 | gulp.watch( 48 | staticFiles.map((file) => `${sourceDirectory}/${file}`), 49 | { ignoreInitial: false }, 50 | copyFiles 51 | ); 52 | } 53 | 54 | export const build = gulp.series(clean, gulp.parallel(buildCode, copyFiles)); 55 | 56 | /********************/ 57 | /* CLEAN */ 58 | /********************/ 59 | 60 | /** 61 | * Remove built files from `dist` folder while ignoring source files 62 | */ 63 | export async function clean() { 64 | const files = [...staticFiles, "main.js"]; 65 | 66 | console.log(" ", "Files to clean:"); 67 | console.log(" ", files.join("\n ")); 68 | 69 | for (const filePath of files) { 70 | await fs.remove(`${distDirectory}/${filePath}`); 71 | } 72 | } 73 | 74 | /********************/ 75 | /* LINK */ 76 | /********************/ 77 | 78 | /** 79 | * Get the data paths of Foundry VTT based on what is configured in `foundryconfig.json` 80 | */ 81 | function getDataPaths() { 82 | const config = fs.readJSONSync("foundryconfig.json"); 83 | const dataPath = config?.dataPath; 84 | 85 | if (dataPath) { 86 | const dataPaths = Array.isArray(dataPath) ? dataPath : [dataPath]; 87 | 88 | return dataPaths.map((dataPath) => { 89 | if (typeof dataPath !== "string") { 90 | throw new Error( 91 | `Property dataPath in foundryconfig.json is expected to be a string or an array of strings, but found ${dataPath}` 92 | ); 93 | } 94 | if (!fs.existsSync(path.resolve(dataPath))) { 95 | throw new Error(`The dataPath ${dataPath} does not exist on the file system`); 96 | } 97 | return path.resolve(dataPath); 98 | }); 99 | } else { 100 | throw new Error("No dataPath defined in foundryconfig.json"); 101 | } 102 | } 103 | 104 | /** 105 | * Link build to User Data folder 106 | */ 107 | export async function link() { 108 | let destinationDirectory; 109 | if (fs.existsSync(path.resolve(sourceDirectory, "module.json"))) { 110 | destinationDirectory = "modules"; 111 | } else { 112 | throw new Error("Could not find module.json"); 113 | } 114 | 115 | const linkDirectories = getDataPaths().map((dataPath) => 116 | path.resolve(dataPath, "Data", destinationDirectory, packageId) 117 | ); 118 | 119 | const argv = yargs(hideBin(process.argv)).option("clean", { 120 | alias: "c", 121 | type: "boolean", 122 | default: false, 123 | }).argv; 124 | const clean = argv.c; 125 | 126 | for (const linkDirectory of linkDirectories) { 127 | if (clean) { 128 | console.log(`Removing build in ${linkDirectory}.`); 129 | 130 | await fs.remove(linkDirectory); 131 | } else if (!fs.existsSync(linkDirectory)) { 132 | console.log(`Linking dist to ${linkDirectory}.`); 133 | await fs.ensureDir(path.resolve(linkDirectory, "..")); 134 | await fs.symlink(path.resolve(distDirectory), linkDirectory); 135 | } else { 136 | console.log(`Skipped linking to ${linkDirectory}, as it already exists.`); 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "build": "gulp build", 4 | "build:watch": "gulp watch", 5 | "link-project": "gulp link", 6 | "clean": "gulp clean", 7 | "clean:link": "gulp link --clean", 8 | "lint": "eslint --ext .js,.cjs,.mjs .", 9 | "lint:fix": "eslint --ext .js,.cjs,.mjs --fix .", 10 | "postinstall": "husky install" 11 | }, 12 | "devDependencies": { 13 | "@rollup/plugin-node-resolve": "^15.2.1", 14 | "@rollup/stream": "^3.0.0", 15 | "@typhonjs-fvtt/eslint-config-foundry.js": "^0.8.0", 16 | "eslint": "^8.49.0", 17 | "fs-extra": "^11.1.1", 18 | "gulp": "^4.0.2", 19 | "husky": "^8.0.3", 20 | "lint-staged": "^14.0.1", 21 | "rollup": "^2.79.1", 22 | "yargs": "^17.7.2" 23 | }, 24 | "lint-staged": { 25 | "*.(js|cjs|mjs)": "eslint --fix", 26 | "*.(json|yml|css)": "prettier --write" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/languages/cn.json: -------------------------------------------------------------------------------- 1 | { 2 | "advanced-macros.MACROS.runAsGM": "以 GM 角色执行宏", 3 | "advanced-macros.MACROS.runAsGMTooltip": "此项将使玩家执行时也以 GM 用户身份执行此宏。
出于安全,仅允许 GM 创建并且没有其他用户具有拥有权限的宏应用" 4 | } 5 | -------------------------------------------------------------------------------- /src/languages/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "advanced-macros": { 3 | "MACROS": { 4 | "runForSpecificUser": "Ausführen für bestimmten Benutzer", 5 | "runForEveryone": "Alle", 6 | "runForEveryoneElse": "Alle anderen", 7 | "none": "Keine/r", 8 | "runAsWorldScriptTooltip": "Dadurch wird das Makro automatisch in der Welt ausgeführt, sobald sie geladen ist.\nAus Sicherheitsgründen gilt dies nur für Makros, die vom SL erstellt wurden und keine anderen Besitzer haben.", 9 | "runForSpecificUserTooltip": "Dies bewirkt, dass das Makro vom SL für einen bestimmten Spieler ausgeführt wird, der online ist.\nAus Sicherheitsgründen gilt dies nur für Makros, die vom SL erstellt wurden und keine anderen Besitzer haben.", 10 | "runAsWorldScript": "Welt-Script" 11 | }, 12 | "setting": { 13 | "legacySlashCommand": { 14 | "hint": "Ignoriert die Notwendigkeit, den Befehl /macro zu verwenden, um Makros im Chat aufzurufen. Warnung: Dies kann zu Konflikten mit Ihrem System oder anderen Modulen führen.", 15 | "name": "Chat-Slash-Kommando" 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/languages/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "advanced-macros": { 3 | "MACROS": { 4 | "runForSpecificUser": "Execute for Specific User", 5 | "runForEveryone": "Everyone", 6 | "runForEveryoneElse": "Everyone else", 7 | "WorldScript": "World Script", 8 | "runAsWorldScript": "Ready Hook", 9 | "runAsWorldScriptSetup": "Setup Hook" 10 | }, 11 | "setting": { 12 | "legacySlashCommand": { 13 | "name": "Chat Slash Command", 14 | "hint": "Ignores the need to use the /macro command to call macros on the chat. Warning: this might cause conflict with your system or other modules." 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/languages/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "advanced-macros": { 3 | "MACROS": { 4 | "none": "Nadie", 5 | "WorldScript": "Script de mundo", 6 | "runForSpecificUser": "Ejecutar para el usuario específico", 7 | "runForSpecificUserTooltip": "Esto hará que la macro sea ejecutada por el GM para un jugador concreto que esté conectado.\nPor motivos de seguridad, solo aplica a macros creadas por los GM que no tengan ningún otro propietario.", 8 | "runForEveryone": "Todo el mundo", 9 | "runForEveryoneElse": "Todos los demás", 10 | "runAsWorldScriptTooltip": "Esto hará que la macro sea ejecutada automáticamente una vez se haya cargado el mundo.\nPor motivos de seguridad, solo aplica a macros creadas por el GM sin otro propietario." 11 | }, 12 | "setting": { 13 | "legacySlashCommand": { 14 | "name": "Comando barra de chat", 15 | "hint": "Ignora la necesidad de usar el comando /macro para llamar macros en el chat. Aviso: esto podría causar conflicto con tu sistema u otros módulos." 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/languages/fi.json: -------------------------------------------------------------------------------- 1 | { 2 | "advanced-macros": { 3 | "setting": { 4 | "legacySlashCommand": { 5 | "name": "Keskustelun vinoviivakomento", 6 | "hint": "Välttää tarpeen käyttää /macro-komentoa makrojen suorittamiseen keskustelussa. Varoitus: tämä saattaa aiheuttaa ristiriitoja pelijärjestelmäsi tai muiden moduulien kanssa." 7 | } 8 | }, 9 | "MACROS": { 10 | "runForSpecificUser": "Suorita tietylle käyttäjälle", 11 | "runForSpecificUserTooltip": "Tämä saa PJ:n suorittamaan makron tietylle pelissä olevalle pelaajalle.\nTurvallisuussyistä tämä koskee vain PJ:n luomia makroja, joilla ei ole muita omistajia.", 12 | "runForEveryone": "Kaikille", 13 | "runForEveryoneElse": "Kaikille muille", 14 | "none": "Ei mitään", 15 | "WorldScript": "Maailmaskripti", 16 | "runAsWorldScriptTooltip": "Tämä makro suoritetaan automaattisesti, kun maailma on ladattu.\nTurvallisuussyistä tämä koskee vain PJ:n luomia makroja, joilla ei ole muita omistajia." 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/languages/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "advanced-macros.MACROS.runAsGM": "Exécuter la macro en tant que MJ ?", 3 | "advanced-macros.MACROS.runAsGMTooltip": "La macro sera exécutée par le MJ lorsque les joueurs l'exécuteront.\nPour des raisons de sécurité, ne s'applique qu'aux macros créées par le MJ et n'ayant aucun autre propriétaire.", 4 | "advanced-macros": { 5 | "ROLLTABLE": { 6 | "macroName": "{document} : {name}" 7 | }, 8 | "MACROS": { 9 | "responses": { 10 | "NoMacro": "Impossible de trouver la macro.", 11 | "NoUser": "Utilisateur non valide.", 12 | "NotScript": "Type de macro non valide.", 13 | "NoMacroPermission": "Vous n'êtes pas autorisé à utiliser des macros de script.", 14 | "NoRunAsGM": "Vous n'êtes pas autorisé à exécuter cette macro en tant que MJ.", 15 | "SyntaxError": "Il y a eu une erreur dans la syntaxe de votre message de discussion.", 16 | "MacroSyntaxError": "Il y a eu une erreur dans la syntaxe de votre macro. Consultez la console (F12) pour plus de détails.", 17 | "ExternalMacroSyntaxError": "Il y a eu une erreur dans la syntaxe de votre macro. Consultez la console (F12) du MJ {GM} pour plus de détails.", 18 | "TimeoutGM": "Le temps est écoulé en attendant d'élire un MJ pour exécuter la macro.", 19 | "TimeoutWaitGM": "Le délai d'attente pour l'exécution de la macro par le MJ a expiré.", 20 | "NoConnectedGM": "Il n'y a aucun MJ connecté pour exécuter la macro {macro} dans le contexte du MJ." 21 | }, 22 | "runForEveryone": "Tout le monde", 23 | "runForEveryoneTooltip": "Ainsi, la macro sera exécutée par le MJ pour tous les joueurs connectés.\nPour des raisons de sécurité, cette option ne s'applique qu'aux macros créées par le MJ et n'ayant aucun autre propriétaire.", 24 | "runForSpecificUser": "Exécuter pour un utilisateur spécifique", 25 | "runForSpecificUserTooltip": "Ainsi, la macro sera exécutée par le MJ pour un joueur spécifique connecté.\nPour des raisons de sécurité, ne s'applique qu'aux macros créées par le MJ et n'ayant aucun autre propriétaire.", 26 | "none": "Aucun", 27 | "runForEveryoneElse": "Tous les autres", 28 | "runAsWorldScriptTooltip": "La macro sera alors exécutée automatiquement pour le monde une fois qu'elle aura été chargée.\nPour des raisons de sécurité, ne s'applique qu'aux macros créées par le MJ et n'ayant pas d'autres propriétaires." 29 | }, 30 | "setting": { 31 | "reset.name": "Réinitialiser les paramètres par défaut", 32 | "reset.label": "Remettre les Réglages Par Défaut", 33 | "reset.hint": "Cela rétablira tous les réglages par défaut du système du jeu actif.", 34 | "debug.name": "Activer la recherche d'anomalies", 35 | "debug.label": "Imprime les messages d'anomalies dans la console", 36 | "debug.hint": " ", 37 | "disableDropHotbarRollTableBehavior": { 38 | "name": "Désactiver l'affichage du tableau déroulant sur la barre d'outils", 39 | "hint": "La fonction de création d'une macro dans la barre d'outils lors d'une dépose d'un tableau roulant peut entrer en conflit avec d'autres modules. Ce paramètre désactive cette fonctionnalité." 40 | }, 41 | "legacySlashCommand": { 42 | "name": "Commande Chat Slash", 43 | "hint": "Ignore la nécessité d'utiliser la commande /macro pour appeler des macros sur le Tchat. Attention : cela peut entraîner des conflits avec votre système ou d'autres modules." 44 | } 45 | }, 46 | "dialogs": { 47 | "resetsettings.title": "Réinitialiser les paramètres du module", 48 | "resetsettings.content": "Êtes-vous sûr de vouloir réinitialiser tous les paramètres du module aux valeurs par défaut du système actuel ? NE PEUT PAS ÊTRE ANNULÉ !", 49 | "resetsettings.confirm": "Réinitialiser les paramètres du module", 50 | "resetsettings.cancel": "Annuler" 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/languages/pl.json: -------------------------------------------------------------------------------- 1 | { 2 | "advanced-macros": { 3 | "ROLLTABLE": { 4 | "macroName": "{document}: {name}" 5 | }, 6 | "MACROS": { 7 | "responses": { 8 | "NoMacro": "Nie można znaleźć makro.", 9 | "NoUser": "Nieprawidłowy użytkownik.", 10 | "NotScript": "Nieprawidłowy typ makra.", 11 | "NoMacroPermission": "Nie masz uprawnień aby używać makr JavaScript.", 12 | "NoRunAsGM": "Nie masz uprawnień aby użyć tego makra jako GM.", 13 | "SyntaxError": "Był błąd w składni twojej wiadomości czatu.", 14 | "MacroSyntaxError": "Wystąpił błąd w twojej składni makra. Zobacz konsolę (F12), aby uzyskać szczegóły.", 15 | "ExternalMacroSyntaxError": "Wystąpił błąd w twojej składni makra. Zobacz konsolę (F12) GMa {GM}, aby uzyskać szczegóły.", 16 | "TimeoutGM": "Za długo zajęło wybieranie GMa który uruchomi makro.", 17 | "TimeoutWaitGM": "Za długo zajęło czekanie na uruchomienie makra przez GMa.", 18 | "NoConnectedGM": "Nie ma żadnych GMów aby uruchomić makro {macro} jako GM." 19 | }, 20 | "runAsGM": "Uruchom makro jako GM?", 21 | "runForEveryone": "Każdy", 22 | "runForEveryoneTooltip": "Makro zostanie uruchomione przez GMa dla wszystkich graczy. \nDla bezpieczeństwa, tylko dotyczy makra stworzone przez GMa i nie posiadające innych twórców.", 23 | "runForSpecificUser": "Wykonaj dla określonego użytkownika", 24 | "none": "Nic", 25 | "runAsGMTooltip": "Makro zostanie uruchomione przez GMa kiedy gracz włączy makro. \nDla bezpieczeństwa, tylko dotyczy makra stworzone przez GMa i nie posiadające innych twórców.", 26 | "runForSpecificUserTooltip": "Makro zostanie uruchomione przez GMa tylko dla wybranego zalogowanego gracza. \nDla bezpieczeństwa, tylko dotyczy makra stworzone przez GMa i nie posiadające innych twórców.", 27 | "runForEveryoneElse": "Wszyscy inni", 28 | "WorldScript": "Skrypt globalny", 29 | "runAsWorldScriptTooltip": "Spowoduje to automatyczne wykonanie makra na świecie po jego załadowaniu.\nZe względów bezpieczeństwa dotyczy tylko makr stworzonych przez GM bez innych właścicieli.", 30 | "runAsWorldScript": "Gotowy hak", 31 | "runAsWorldScriptSetup": "Ustawienia haka" 32 | }, 33 | "setting": { 34 | "disableDropHotbarRollTableBehavior": { 35 | "name": "Wyłączenie funkcji Drop RollTable na pasku narzędziowym", 36 | "hint": "Tworzenie makra kiedy się upuszcza tabelę na przybornik może być niekompatybilne z innymi modułami. To ustawienie wyłącza tą funkcję." 37 | }, 38 | "reset.name": "Przywrócenie ustawień domyślnych", 39 | "reset.label": "Przywrócenie Ustawień Domyślnych", 40 | "reset.hint": "Spowoduje to przywrócenie wszystkich ustawień domyślnych dla aktywnego systemu gry.", 41 | "debug.name": "Włącz debugowanie", 42 | "debug.label": "Drukuje komunikaty debugowe do konsoli", 43 | "debug.hint": " ", 44 | "legacySlashCommand": { 45 | "hint": "Ignoruje potrzebę używania polecenia /macro do wywoływania makr na czacie. Ostrzeżenie: może to spowodować konflikt z systemem lub innymi modułami.", 46 | "name": "Komenda ukośnika czatu" 47 | } 48 | }, 49 | "dialogs": { 50 | "resetsettings.title": "Resetowanie Ustawień Modułu", 51 | "resetsettings.confirm": "Resetowanie Ustawień Modułu", 52 | "resetsettings.cancel": "Anuluj", 53 | "resetsettings.content": "Czy na pewno chcesz zresetować wszystkie ustawienia modułu do domyślnych ustawień bieżącego systemu? TEGO NIE DA SIĘ COFNĄĆ!" 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/languages/pt-BR.json: -------------------------------------------------------------------------------- 1 | { 2 | "advanced-macros": { 3 | "MACROS": { 4 | "runForSpecificUser": "Executar para usuário específico", 5 | "runForSpecificUserTooltip": "Isto fará com que a macro seja executada pelo MJ para um usuário específico conectado.\nPor questões de segurança, só se aplica a macros criadas pelo MJ sem outros donos.", 6 | "runForEveryone": "Todos", 7 | "runForEveryoneElse": "Todos os outros", 8 | "none": "Ninguém", 9 | "WorldScript": "Script do Mundo", 10 | "runAsGM": "Como {gm}", 11 | "runAsWorldScriptTooltip": "Isto fará com que a macro seja executada automaticamente quando o Mundo for carregado.\nPor questões de segurança, só se aplica a macros criadas pelo MJ sem outros donos." 12 | }, 13 | "setting": { 14 | "legacySlashCommand": { 15 | "name": "Comando de Barra (/) no Chat", 16 | "hint": "Ignora a necessidade de usar o comando /macro para chamar macros no chat. Aviso: isso pode causar conflito com seu sistema ou outros módulos." 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | Hooks.once("init", () => { 2 | class AdvancedMacro extends CONFIG.Macro.documentClass { 3 | static metadata = Object.freeze(foundry.utils.mergeObject(super.metadata, { 4 | preserveOnImport: ["_id", "sort", "ownership", "author"] 5 | }, {inplace: false})); 6 | 7 | canUserExecute(user) { 8 | if (!this.testUserPermission(user, "LIMITED")) return false; 9 | return this.type === "script" ? user.can("MACRO_SCRIPT") || (this.canRunAsGM && !user.isGM) : true; 10 | } 11 | 12 | /** 13 | * Defines whether a Macro can run as a GM. 14 | * For security reasons, only macros authored by the GM, and not editable by users 15 | * can be run as GM 16 | */ 17 | get canRunAsGM() { 18 | const author = game.users.get(this.author?.id); 19 | const permissions = foundry.utils.deepClone(this.ownership) || {}; 20 | 21 | for (const user of game.users.contents) { 22 | if (user.isGM || user.id === author?.id) delete permissions[user.id]; 23 | } 24 | const highestPermissionLevel = Math.max(...Object.values(permissions)); 25 | return author?.isGM && highestPermissionLevel < CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER; 26 | } 27 | 28 | async execute(scope = {}, callFromSocket = false) { 29 | if (!this.canExecute) { 30 | return ui.notifications.warn(`You do not have permission to execute Macro "${this.name}".`); 31 | } 32 | switch (this.type) { 33 | case "chat": 34 | return super.execute(scope); 35 | case "script": { 36 | const queryData = { macro: this.id, scope }; 37 | const runFor = this.getFlag("advanced-macros", "runForSpecificUser"); 38 | const runQuery = (user) => user.query("advanced-macros.executeMacro", queryData, { timeout: 30000 }); 39 | if (callFromSocket || !runFor || runFor === "runAsWorldScript" || runFor === "runAsWorldScriptSetup" || !this.canRunAsGM) { 40 | return super.execute(scope); 41 | } else if (runFor === "GM") { 42 | if (game.users.activeGM?.isSelf) return super.execute(scope); 43 | return runQuery(game.users.activeGM); 44 | } else if (runFor === "runForEveryone") { 45 | return game.users.filter((u) => u.active).forEach(runQuery); 46 | } else if (runFor === "runForEveryoneElse") { 47 | return game.users.filter((u) => u.active && u.id !== game.user.id).forEach(runQuery); 48 | } else if (runFor) { 49 | return runQuery(game.users.find((u) => u.id === runFor)); 50 | } 51 | } 52 | } 53 | } 54 | } 55 | 56 | CONFIG.Macro.documentClass = AdvancedMacro; 57 | game.settings.register("advanced-macros", "legacySlashCommand", { 58 | name: "advanced-macros.setting.legacySlashCommand.name", 59 | hint: "advanced-macros.setting.legacySlashCommand.hint", 60 | scope: "world", 61 | config: true, 62 | default: false, 63 | type: Boolean, 64 | }); 65 | CONFIG.queries["advanced-macros.executeMacro"] = (queryData) => { 66 | const { macro, scope } = queryData; 67 | return game.macros.get(macro)?.execute(scope, true); 68 | }; 69 | }); 70 | 71 | Hooks.on("chatMessage", (chatLog, message, chatData) => { 72 | // Ignore messages starting with "<" or matching a macro pattern. 73 | if (message.trim().startsWith("<") || message.match(chatLog.constructor.MESSAGE_PATTERNS.macro)) return true; 74 | if (!game.settings.get("advanced-macros", "legacySlashCommand")) return; 75 | // If the message contains an invalid command and starts with a "/", try to process macros in it. 76 | let [command, match] = chatLog.constructor.parse(message); 77 | if (command === "invalid" && message.trim().startsWith("/")) { 78 | const messageArray = message.slice(1).split(" "); 79 | let macroName = messageArray[0]; 80 | let macro = game.macros.getName(macroName); 81 | for (const token of messageArray.slice(1)) { 82 | if (!macro) { 83 | macroName += ` ${token}`; 84 | macro = game.macros.getName(macroName); 85 | } 86 | if (macro) break; 87 | } 88 | if (macro) { 89 | [command, match] = chatLog.constructor.parse(`/macro ${message.slice(1)}`); 90 | chatLog._processMacroCommand(command, match); 91 | return false; 92 | } 93 | } 94 | return true; 95 | }); 96 | 97 | function runWorldScripts(key) { 98 | const worldScripts = game.macros.contents.filter( 99 | (macro) => macro.getFlag("advanced-macros", "runForSpecificUser") === key 100 | ); 101 | for (const macro of worldScripts) { 102 | try { 103 | macro.execute(); 104 | console.debug(`Advanced Macros | Executed "${macro.name}" world script (ID: ${macro.id})`); 105 | } catch(err) { 106 | console.error(`Advanced Macros | Error executing "${macro.name}" world script (ID: ${macro.id})`, err); 107 | } 108 | } 109 | } 110 | 111 | Hooks.once("setup", () => runWorldScripts("runAsWorldScriptSetup")); 112 | 113 | Hooks.once("ready", () => { 114 | Hooks.on("renderMacroConfig", (obj, html, data) => { 115 | if (!game.user.isGM) return; 116 | const macro = obj.document; 117 | // A re-render will cause the html object to be the internal element, which is the form itself. 118 | const typeSelect = html.querySelector("select[name=type]"); 119 | const typeGroup = typeSelect.closest(".form-group"); 120 | const options = [ 121 | { 122 | value: "GM", 123 | label: game.i18n.localize("USER.RoleGamemaster") 124 | }, 125 | ...["runForEveryone", "runForEveryoneElse"].map((run) => ({ 126 | value: run, 127 | label: game.i18n.localize(`advanced-macros.MACROS.${run}`), 128 | group: "DOCUMENT.Users" 129 | })), 130 | ...["runAsWorldScriptSetup", "runAsWorldScript"].map((run) => ({ 131 | value: run, 132 | label: game.i18n.localize(`advanced-macros.MACROS.${run}`), 133 | group: "advanced-macros.MACROS.WorldScript" 134 | })), 135 | ...game.users.players 136 | .map((user) => ({ 137 | value: user.id, 138 | label: user.name, 139 | group: "PLAYERS.Title", 140 | })), 141 | ]; 142 | 143 | const select = foundry.applications.fields.createSelectInput({ 144 | name: "flags.advanced-macros.runForSpecificUser", 145 | options, 146 | value: macro.getFlag("advanced-macros", "runForSpecificUser"), 147 | blank: "", 148 | labelAttr: "label", 149 | localize: true, 150 | disabled: !macro.canRunAsGM 151 | }); 152 | 153 | const specificOneDiv = $(` 154 |
155 | 156 |
${select.outerHTML}
157 |
158 | `); 159 | 160 | specificOneDiv.insertAfter(typeGroup); 161 | 162 | typeSelect.addEventListener("change", (event) => { 163 | if (event.target.value === "chat") specificOneDiv.hide(); 164 | else specificOneDiv.show(); 165 | }); 166 | }); 167 | runWorldScripts("runAsWorldScript"); 168 | }); 169 | -------------------------------------------------------------------------------- /src/module.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "advanced-macros", 3 | "title": "Advanced Macros", 4 | "description": "Call macros with arguments directly from chat and execute macros for specific users.", 5 | "authors": [ 6 | { 7 | "name": "mclemente", 8 | "url": "https://github.com/mclemente", 9 | "discord": "mclemente#5524", 10 | "github": "mclemente" 11 | } 12 | ], 13 | "languages": [ 14 | { 15 | "lang": "en", 16 | "name": "English", 17 | "path": "languages/en.json" 18 | }, 19 | { 20 | "lang": "fr", 21 | "name": "Français (French)", 22 | "path": "languages/fr.json" 23 | }, 24 | { 25 | "lang": "cn", 26 | "name": "中文 (Chinese)", 27 | "path": "languages/cn.json" 28 | }, 29 | { 30 | "lang": "pl", 31 | "name": "polski", 32 | "path": "languages/pl.json" 33 | }, 34 | { 35 | "lang": "pt-BR", 36 | "name": "Português (Brasil)", 37 | "path": "languages/pt-BR.json" 38 | }, 39 | { 40 | "lang": "fi", 41 | "name": "Suomi", 42 | "path": "languages/fi.json" 43 | }, 44 | { 45 | "lang": "es", 46 | "name": "Español", 47 | "path": "languages/es.json" 48 | }, 49 | { 50 | "lang": "de", 51 | "name": "Deutsch", 52 | "path": "languages/de.json" 53 | } 54 | ], 55 | "packs": [ 56 | { 57 | "name": "macros", 58 | "label": "Advanced Macros", 59 | "path": "packs/macros.db", 60 | "ownership": { 61 | "PLAYER": "LIMITED", 62 | "ASSISTANT": "OWNER" 63 | }, 64 | "type": "Macro" 65 | } 66 | ], 67 | "socket": true, 68 | "esmodules": ["main.js"], 69 | "compatibility": { 70 | "minimum": 13, 71 | "verified": 13 72 | }, 73 | "url": "This is auto replaced", 74 | "version": "This is auto replaced", 75 | "manifest": "This is auto replaced", 76 | "download": "This is auto replaced" 77 | } 78 | -------------------------------------------------------------------------------- /src/packs/macros.db: -------------------------------------------------------------------------------- 1 | {"_id":"3t22b7DeBqd8OKz6","name":"Canvas Always-on-top","permission":{"default":0,"PEJBB2AYKoE5ZuxD":3},"type":"script","flags":{"advanced-macros":{"runAsGM":false},"core":{"sourceId":"Macro.tHqyy0sqn6mHlV0m"}},"scope":"global","command":"const video = $(``)[0];\nvideo.srcObject = $(\"#board\")[0].captureStream();\nconst playVideo = () => {\n if (video.readyState < 2) setTimeout(playVideo, 0);\n else video.play();\n};\nplayVideo();","author":"PEJBB2AYKoE5ZuxD","img":"icons/svg/dice-target.svg","actorIds":[]} 2 | {"_id":"4aUMx41pvnZCJ52b","name":"teleport","permission":{"FRJk2q4bH9IdLZDf":3},"type":"script","sort":100001,"flags":{},"scope":"global","command":"// This macro only serves as an example. For a proper teleportation macro, check out the Dynamic Effects module.\n//\n// This macro requires the advanced macros of Advanced Macros\n// This macro depends on /move-token\n// Takes X and Y as arguments\n\nconst macro = game.macros.getName(\"move-token\");\nif (!macro) {\n ui.notifications.error(\"This macro depends on the 'move-token' macro, which could not be found.\");\n return;\n}\nmacro.execute([args[0], args[1]]);","author":"FRJk2q4bH9IdLZDf","img":"icons/svg/dice-target.svg","actorIds":[]} 3 | {"_id":"63bPkXo7BUsZEQJ0","name":"animate-pan","permission":{"default":0,"PEJBB2AYKoE5ZuxD":3},"type":"script","flags":{},"scope":"global","command":"/* Pan the camera to the X, Y and scale positions.\r\n * See the 'pan-camera' macro for more details.\r\n * The first argument is the duration in milliseconds for the panning animation\r\n * The second argument is X, third argument is Y and fourth argument is the zoom level\r\n * Example: /animate-pan 500 1500 1500 0.5\r\n */\r\n\r\ncanvas.animatePan({duration: args[0], x: args[1], y: args[2], scale: args[3]})","author":"PEJBB2AYKoE5ZuxD","img":"icons/svg/dice-target.svg","actorIds":[]} 4 | {"_id":"CRaAk3V9gF6ihk1P","name":"Measure Token Distances","permission":{"default":0,"PEJBB2AYKoE5ZuxD":3},"type":"script","flags":{"advanced-macros":{"runAsGM":false}},"scope":"global","command":"// This macro will measure the distance between the selected tokens\r\n// and every targetted tokens within a scene.\r\n// It will then output the measured distances to the chat\r\n// If called with an argument, determines who to whisper the message to,\r\n// otherwise sends it as a public message\r\n\r\nlet message = \"\"\r\nfor (let token of canvas.tokens.controlled) {\r\n let ruler = canvas.controls.ruler;\r\n for (let target of game.user.targets) {\r\n ruler.clear()\r\n ruler.waypoints.push(token.center)\r\n ruler.labels.addChild(new PIXI.Text(\"\"));\r\n ruler.measure(target.center);\r\n let distance = ruler.labels.children[0].text;\r\n message += `From '${token.name}' to '${target.name}' : ${distance}
`\r\n ruler.clear();\r\n }\r\n}\r\nif (message) {\r\n const whisper = args[0] ? ChatMessage.getWhisperIDs(args[0]) : undefined;\r\n ChatMessage.create({content: message, whisper});\r\n}","author":"PEJBB2AYKoE5ZuxD","img":"icons/svg/dice-target.svg","actorIds":[]} 5 | {"_id":"PoygoBaDDvbDCluL","name":"current-time","permission":{"default":0,"PEJBB2AYKoE5ZuxD":3},"type":"script","flags":{},"scope":"global","command":"// Returns the current time in format \"HH:MM\" (24 hour format)\r\nconst now = new Date();\r\nreturn `${("0" + now.getHours()).slice(-2)}:${("0" + now.getMinutes()).slice(-2)}`;","author":"PEJBB2AYKoE5ZuxD","img":"icons/svg/dice-target.svg","actorIds":[]} 6 | {"_id":"rKmHs6LsBtDjPDhK","name":"Set Token bars and nameplate","permission":{"bWT4RleWjkaYiYKA":3},"type":"script","sort":100001,"flags":{},"scope":"global","command":"/* This will set every token in scene to always display their\r\n * token bars and nameplate, and sets the first bar to represent \r\n * HP and removes the second token bar.\r\n*/\r\n\r\nconst tokens =canvas.tokens.placeables.map(token => {\r\n return {\r\n _id: token.id,\r\n \"bar1.attribute\": \"attributes.hp\",\r\n \"bar2.attribute\": \"\",\r\n \"displayName\": CONST.TOKEN_DISPLAY_MODES.ALWAYS,\r\n \"displayBars\": CONST.TOKEN_DISPLAY_MODES.ALWAYS\r\n };\r\n});\r\n\r\ncanvas.scene.updateEmbeddedDocuments('Token', tokens)","author":"bWT4RleWjkaYiYKA","img":"icons/svg/dice-target.svg","actorIds":[]} 7 | {"_id":"rMOgmYRkV0AZ1sjc","name":"move-token","permission":{"FRJk2q4bH9IdLZDf":3},"type":"script","sort":100001,"flags":{},"scope":"global","command":"/* This macro requires the advanced macros of Advanced Macros\n * This will move the selected token to the designated position\n * Takes X and Y as arguments for the position (in pixels)\n * A third, optional, argument, if set to true, will disable the movement animation\n * Example: /move-token 1000 1500 false\n */\n\nif (!token) return;\nconst x = args[0] ?? token.x;\nconst y = args[1] ?? token.y;\nawait token.document.update({x, y})","author":"FRJk2q4bH9IdLZDf","img":"icons/svg/dice-target.svg","actorIds":[]} 8 | {"_id":"wg8eFxpVcgewRvsu","name":"pan-camera","permission":{"FRJk2q4bH9IdLZDf":3},"type":"script","sort":100001,"flags":{},"scope":"global","command":"// Pan the canvas camera to a position X and Y, in pixels.\r\n// Can also set the zoom level using the third optional argument.\r\n// Example: /pan-camera 1500\r\n// Example: /pan-camera 2500 2000 0.3\r\n\r\ncanvas.pan({x: args[0], y: args[1], scale: args[2]})","author":"FRJk2q4bH9IdLZDf","img":"icons/svg/dice-target.svg","actorIds":[]} 9 | {"_id":"yUlSqozYscIEOGz1","name":"play-audio","permission":{"FRJk2q4bH9IdLZDf":3},"type":"script","sort":100001,"flags":{},"scope":"global","command":"/* This macro requires the advanced macros of Advanced Macros\r\n * This will play audio from a URL\r\n * Takes the URL of the audio file as its first argument\r\n * The second argument, if set to true, will play the audio for every other player too.\r\n * Example: /play-audio \"https://example.com/sound-effects/explosion.mp3\" true\r\n */\r\nconst url = args[0];\r\nconst push = args[1];\r\n\r\nAudioHelper.play({src: [url]}, push);","author":"FRJk2q4bH9IdLZDf","img":"icons/svg/dice-target.svg","actorIds":[]} -------------------------------------------------------------------------------- /wiki/advanced_macro_img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mclemente/fvtt-advanced-macros/479c3a719a67eddf5f363fa83aa6db0a03cafe67/wiki/advanced_macro_img.png --------------------------------------------------------------------------------