├── .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 | 
2 | 
3 |
4 | [](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 |
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 |