├── 5e-polymorph.js ├── 5e-roll-skill.js ├── 8.x-documents.js ├── LICENSE ├── README.md ├── audiohelper.js ├── chat-messages.js ├── compendiums.js ├── dialog.js ├── dice-rolls.js ├── distance.js ├── embeddedEntity-ownedItem.js ├── fetch-json.js ├── ffmpeg └── animated-gif-to-webm ├── files-and-downloads.js ├── flags.js ├── form-application.js ├── get-entities.js ├── handlebars-templates.js ├── image-popout.js ├── imagemagick ├── bulk-convert-to-webp.ps1 ├── bulk-flatten-with-background.ps1 ├── single-convert-to-webp └── single-flatten-with-background ├── macro-in-macro.js ├── macros ├── ddb-importer-monster-splitter ├── debug.js ├── heal-nearby.js ├── lights.js ├── list-modules.js ├── luck.js ├── replace-actors-in-journals.js ├── reset-fog-player.js ├── retrieve-lost-windows.js ├── reveal-fog-of-war.js ├── tile-quick-toggle.js └── toggle-active-character.js ├── mouse-position.js ├── notification-warning-error.js ├── position.js ├── powershell └── replace_special_chars.ps1 ├── ray-collision.js ├── roll-tables.js ├── settings.js ├── sockets.js ├── status-effects.js ├── tokens.js ├── update.js └── walls.js /5e-polymorph.js: -------------------------------------------------------------------------------- 1 | // Polymorph 2 | let form = game.actors.getName('Giant Spider'); 3 | canvas.tokens.controlled[0].actor.transformInto(form); 4 | 5 | // Wildshape 6 | let form = game.actors.getName('Giant Spider'); 7 | canvas.tokens.controlled[0].actor.transformInto(form, { 8 | keepMental: true, 9 | mergeSaves: true, 10 | mergeSkills: true, 11 | }); 12 | 13 | // Transform back (will not work on unliked tokens) 14 | canvas.tokens.controlled[0].actor.revertOriginalForm() 15 | -------------------------------------------------------------------------------- /5e-roll-skill.js: -------------------------------------------------------------------------------- 1 | // Make a skill (Athletics) check 2 | actor.rollSkill('ath'); 3 | 4 | // Skip advantage dialog and roll immediately 5 | actor.rollSkill('ath', { fastForward: true }); 6 | 7 | /* 8 | * Available params for 5e's rollSkill(): 9 | * 10 | * @param {Array} parts The dice roll component parts, excluding the initial d20 11 | * @param {Object} data Actor or item data against which to parse the roll 12 | * @param {Event|object} event The triggering event which initiated the roll 13 | * @param {string} rollMode A specific roll mode to apply as the default for the resulting roll 14 | * @param {string|null} template The HTML template used to render the roll dialog 15 | * @param {string|null} title The dice roll UI window title 16 | * @param {Object} speaker The ChatMessage speaker to pass when creating the chat 17 | * @param {string|null} flavor Flavor text to use in the posted chat message 18 | * @param {Boolean} fastForward Allow fast-forward advantage selection 19 | * @param {Function} onClose Callback for actions to take when the dialog form is closed 20 | * @param {Object} dialogOptions Modal dialog options 21 | * @param {boolean} advantage Apply advantage to the roll (unless otherwise specified) 22 | * @param {boolean} disadvantage Apply disadvantage to the roll (unless otherwise specified) 23 | * @param {number} critical The value of d20 result which represents a critical success 24 | * @param {number} fumble The value of d20 result which represents a critical failure 25 | * @param {number} targetValue Assign a target value against which the result of this roll should be compared 26 | * @param {boolean} elvenAccuracy Allow Elven Accuracy to modify this roll? 27 | * @param {boolean} halflingLucky Allow Halfling Luck to modify this roll? 28 | * @param {boolean} reliableTalent Allow Reliable Talent to modify this roll? 29 | * @param {boolean} chatMessage Automatically create a Chat Message for the result of this roll 30 | * @param {object} messageData Additional data which is applied to the created Chat Message, if any 31 | */ -------------------------------------------------------------------------------- /8.x-documents.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VanceCole/macros/673a4c1b068cfa8fc9e8273d4b006c07746c840c/8.x-documents.js -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Vance Cole 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 | # Macros & Code Examples 2 | A collection of foundry code examples and simple macros to learn from 3 | 4 | If there is something missing that you think would be helpful, please @vance#1935 on discord! 5 | 6 | ## To be added: 7 | - Array methods (.find, .filter, .map, etc) 8 | -------------------------------------------------------------------------------- /audiohelper.js: -------------------------------------------------------------------------------- 1 | let src = encodeURI("music/sfx/example.mp3"); 2 | AudioHelper.play({ 3 | src, 4 | volume: 1, 5 | autoplay: true, 6 | loop: false 7 | }, true); -------------------------------------------------------------------------------- /chat-messages.js: -------------------------------------------------------------------------------- 1 | // Send chat message 2 | ChatMessage.create({ content: 'Hello World!' }); 3 | 4 | // Send chat message under an alias 5 | ChatMessage.create({ content: "Blah blah blah", speaker: { alias: "Steve" } }); 6 | 7 | // Send chat message emote as a given actor 8 | let actr = game.actors.getName('Ancient Red Dragon'); 9 | let spkr = ChatMessage.getSpeaker({ actr }); 10 | ChatMessage.create({ 11 | speaker: spkr, 12 | content: "...turns his head toward Steve", 13 | type: CHAT_MESSAGE_TYPES.EMOTE 14 | }, 15 | { chatBubble: true }); 16 | 17 | // Whisper to player by id 18 | ChatMessage.create({ 19 | content: `Hello`, 20 | whisper: ["2UAYUrmMnLCEBiJm"] 21 | }); 22 | 23 | // Whisper players by name 24 | ChatMessage.create({ 25 | content: `Hello`, 26 | whisper: ChatMessage.getWhisperRecipients('Steve', 'Stella'), 27 | }); 28 | 29 | // Whisper GM(s) 30 | ChatMessage.create({ 31 | content: `Hello`, 32 | whisper: ChatMessage.getWhisperRecipients('GM'), 33 | }); 34 | -------------------------------------------------------------------------------- /compendiums.js: -------------------------------------------------------------------------------- 1 | // Get data of item in compendium 2 | let pack = game.packs.get('dnd5e.classes'); 3 | let content = await pack.getContent(); 4 | let data = content.find(i => i.name === 'Barbarian'); -------------------------------------------------------------------------------- /dialog.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Very simple dialog to display info 3 | */ 4 | let d = new Dialog({ 5 | title: 'Example', 6 | content: `Hello World!`, 7 | buttons: { 8 | ok: { 9 | icon: '', 10 | label: 'Ok', 11 | }, 12 | } 13 | }).render(true); 14 | 15 | /* ---------------------------------------------------- */ 16 | 17 | /** 18 | * Very simple dialog to prompt confirmation 19 | * after user clicks button, confirmation will be boolean for yes/no 20 | */ 21 | let confirmation = await Dialog.confirm({ 22 | title: 'Example Confirm', 23 | content: `

Are you sure?

`, 24 | }); 25 | 26 | /* ---------------------------------------------------- */ 27 | 28 | /** 29 | * Very simple dialog to request a user input 30 | * Requires 0.7x 31 | */ 32 | let myValue = await Dialog.prompt({ 33 | content: ``, 34 | callback: (html) => html.find('input').val() 35 | }) 36 | 37 | /* ---------------------------------------------------- */ 38 | 39 | /** 40 | * Example dialog that requests user input, then uses the value 41 | */ 42 | let d = new Dialog({ 43 | title: 'Example', 44 | content: ` 45 |
46 |
47 | 48 | 49 |
50 |
51 | 52 | 57 |
58 |
59 | 60 | 61 | 62 |
63 |
64 | 65 |
66 |
67 | `, 68 | buttons: { 69 | no: { 70 | icon: '', 71 | label: 'Cancel' 72 | }, 73 | yes: { 74 | icon: '', 75 | label: 'Yes', 76 | callback: (html) => { 77 | let input = html.find('[name="exampleInput"]').val(); 78 | let select = html.find('[name="exampleSelect"]').val(); 79 | let color = html.find('[name="exampleColor"]').val(); 80 | let text = html.find('[name="exampleText"]').val(); 81 | console.log(input, select, color, text); 82 | } 83 | }, 84 | }, 85 | default: 'yes', 86 | close: () => { 87 | console.log('Example Dialog Closed'); 88 | } 89 | }).render(true) 90 | 91 | /* ---------------------------------------------------- */ 92 | 93 | /** 94 | * Dialog that re-renders (stays open) when button is clicked 95 | * rather than closing 96 | */ 97 | let myContent = function (val) { 98 | return ` 99 |
100 |
101 | Attack information: 102 |
103 |
104 |
${val}
105 |
106 |
107 | ` 108 | } 109 | 110 | let myDialog = new Dialog({ 111 | title: `Example Dialog`, 112 | content: myContent('Default Value'), 113 | buttons: { 114 | update: { 115 | label: "Update", 116 | callback: () => { 117 | myValue = `Example random value: ${Math.random()*100}`; 118 | myDialog.data.content = myContent(myValue); 119 | myDialog.render(true); 120 | } 121 | } 122 | }, 123 | }, 124 | { 125 | id: 'test' 126 | } 127 | ).render(true); -------------------------------------------------------------------------------- /dice-rolls.js: -------------------------------------------------------------------------------- 1 | // Get result of a roll to use as a variable 2 | let r = new Roll('1d20').roll(); 3 | 4 | // Roll to chat message 5 | new Roll('1d20').toMessage(); 6 | 7 | /* 8 | * Note that toMessage is async 9 | * This means that if you need to use the result of the roll, you should either 10 | * 1) do so before calling toMessage() 11 | * 2) use await 12 | */ 13 | let r = new Roll('1d20').roll(); 14 | r.toMessage(); 15 | // or 16 | let r = await new Roll('1d20').toMessage(); 17 | 18 | // Roll with flavor text 19 | new Roll('1d20').toMessage({ flavor: 'Example Flavor' }); 20 | 21 | // Roll with preset roll modes 22 | new Roll('1d20').toMessage({ rollMode: 'roll' }); // Public Roll 23 | new Roll('1d20').toMessage({ rollMode: 'gmroll' }); // Private GM Roll 24 | new Roll('1d20').toMessage({ rollMode: 'blindroll' }); // Blind GM Roll 25 | new Roll('1d20').toMessage({ rollMode: 'selfroll' }); // Self Roll 26 | -------------------------------------------------------------------------------- /distance.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Get euclidean distance between two placeable objects 3 | */ 4 | let a = canvas.tokens.placeables[0]; 5 | let b = canvas.tokens.placeables[1]; 6 | let dist = canvas.grid.measureDistance(a, b); 7 | 8 | /** 9 | * Gets distance between two placeable objects 10 | * for example, tokens, using 5e grid rules 11 | * @param {Placeable} one 12 | * @param {Placeable} two 13 | */ 14 | function get5eGridDist(one, two) { 15 | let gs = canvas.grid.size; 16 | let d1 = Math.abs((one.x - two.x) / gs); 17 | let d2 = Math.abs((one.y - two.y) / gs); 18 | let dist = Math.max(d1, d2); 19 | dist = dist * canvas.scene.data.gridDistance; 20 | return dist; 21 | } 22 | let a = canvas.tokens.placeables[0]; 23 | let b = canvas.tokens.placeables[1]; 24 | let dist = get5eGridDist(a, b); 25 | 26 | 27 | /** 28 | * Gets distance between two placeable objects 29 | * for example, tokens, using 5e ALTERNATE grid rules (5-10-5 movement rule) 30 | * @param {Placeable} one 31 | * @param {Placeable} two 32 | */ 33 | function get5eGridDistAlt(one, two) { 34 | let gs = canvas.grid.size; 35 | let d1 = Math.abs((one.x - two.x) / gs); 36 | let d2 = Math.abs((one.y - two.y) / gs); 37 | let maxDim = Math.max(d1, d2); 38 | let minDim = Math.min(d1, d2); 39 | let dist = (maxDim + Math.floor(minDim / 2)) * canvas.scene.data.gridDistance; 40 | return dist; 41 | } 42 | let a = canvas.tokens.placeables[0]; 43 | let b = canvas.tokens.placeables[1]; 44 | let dist = get5eGridDistAlt(a, b); 45 | -------------------------------------------------------------------------------- /embeddedEntity-ownedItem.js: -------------------------------------------------------------------------------- 1 | /** 2 | * For dealing with entities embedded in another entity, for example 3 | * item owned by a character you use updateEmbeddedEntity() 4 | */ 5 | 6 | // Toggle equip / unequip of actor Steve's Warhammer 7 | let myActor = game.actors.getName('Steve'); 8 | let myItem = actor.data.items.find(i => i.name === 'Warhammer'); 9 | myActor.updateEmbeddedEntity('OwnedItem', { 10 | _id: myItem._id, 11 | 'data.equipped': !myItem.data.equipped 12 | }); 13 | 14 | // You can also use the shortcut method updateOwnedItem, f.x.: 15 | myActor.updateOwnedItem({ 16 | _id: myItem._id, 17 | 'data.equipped': !myItem.data.equipped 18 | }); 19 | 20 | // Example to change quantity of `Crossbow Bolts` owned by Steve by -1 21 | let myActor = game.actors.getName('Steve'); 22 | let myItem = actor.data.items.find(i => i.name === 'Crossbow Bolts'); 23 | myActor.updateOwnedItem({ 24 | _id: myItem._id, 25 | 'data.quantity': myItem.data.quantity-1 26 | }); 27 | 28 | /** 29 | * You can update multiple embeddedEntity in one db operation if required 30 | * Not only can you do this, you very much should especially if operation 31 | * may effect many items 32 | */ 33 | 34 | // Example to unequip all items of type "weapon" 35 | let myActor = game.actors.getName('Steve'); 36 | let myItems = actor.data.items.filter(i => i.type === 'weapon'); 37 | let updates = myItems.map((i) => { 38 | return { 39 | _id: i._id, 40 | 'data.equipped': false 41 | }; 42 | }); 43 | myActor.updateEmbeddedEntity('OwnedItem', updates); 44 | // myActor.updateOwnedItem(updates); // As before, you can use shortcut updateOwnedItem() instead 45 | -------------------------------------------------------------------------------- /fetch-json.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Load a json file via fetch 3 | */ 4 | fetch('/modules/myModule/module.json') 5 | .then(response => response.json()) 6 | .then(data => { 7 | // Do something with the file 8 | console.log(data) 9 | }); 10 | 11 | /* 12 | * Load a json file via fetch using await 13 | * (Await is only valid inside an async function) 14 | */ 15 | let response = await fetch('/modules/myModule/module.json'); 16 | let data = await response.json(); 17 | console.log(data); 18 | -------------------------------------------------------------------------------- /ffmpeg/animated-gif-to-webm: -------------------------------------------------------------------------------- 1 | ffmpeg -i myfile.gif -f webm -c:v libvpx -b:v 1M -auto-alt-ref 0 mynewfile.webm -------------------------------------------------------------------------------- /files-and-downloads.js: -------------------------------------------------------------------------------- 1 | // Download file to client 2 | saveDataToFile(JSON.stringify({ test: 'hello' }), "application/json", "test.json"); 3 | 4 | // Read file from client 5 | const input = $('') 6 | input.on("change", async () => { 7 | const file = input[0].files[0]; 8 | if (!file) return; 9 | let contents = await readTextFromFile(file) 10 | console.log(contents); 11 | }); 12 | input.trigger('click'); -------------------------------------------------------------------------------- /flags.js: -------------------------------------------------------------------------------- 1 | // Create module flag on a scene 2 | await canvas.scene.setFlag('myModule', 'myFlag', 'myValue'); 3 | 4 | // Retrieve module flag on a scene 5 | let x = canvas.scene.getFlag('myModule', 'myFlag'); 6 | 7 | // Unset a module flag on a scene 8 | await canvas.scene.unsetFlag('myModule', 'myFlag'); 9 | 10 | // Special syntax to unset a nested property 11 | await canvas.scene.setFlag('myModule', 'myFlag.some.nested.-=property', null); 12 | 13 | // React to changes to flag 14 | Hooks.on('updateScene', (scene, data) => { 15 | if (hasProperty(data, 'flags.myModule')) { 16 | console.log(data); 17 | } 18 | }); 19 | 20 | // Flags can be arrays, objects, numbers etc as well 21 | // Anything that can be stringified 22 | let data = { 23 | myString: 'hello', 24 | myNum: 42, 25 | myBool: true, 26 | myArray: ['a','b','c'] 27 | } 28 | await canvas.scene.setFlag('myModule', 'myFlag', data); 29 | -------------------------------------------------------------------------------- /form-application.js: -------------------------------------------------------------------------------- 1 | class MyFormApplication extends FormApplication { 2 | constructor(exampleOption) { 3 | super(); 4 | this.exampleOption = exampleOption; 5 | } 6 | 7 | static get defaultOptions() { 8 | return mergeObject(super.defaultOptions, { 9 | classes: ['form'], 10 | popOut: true, 11 | template: `myFormApplication.html`, 12 | id: 'my-form-application', 13 | title: 'My FormApplication', 14 | }); 15 | } 16 | 17 | getData() { 18 | // Return data to the template 19 | return { 20 | msg: this.exampleOption, 21 | color: 'red', 22 | }; 23 | } 24 | 25 | activateListeners(html) { 26 | super.activateListeners(html); 27 | } 28 | 29 | async _updateObject(event, formData) { 30 | console.log(formData.exampleInput); 31 | } 32 | } 33 | 34 | window.MyFormApplication = MyFormApplication; 35 | 36 | /** 37 | * To open your application 38 | */ 39 | 40 | new MyFormApplication('example').render(true); 41 | 42 | /** 43 | * myFormApplication.html 44 | */ 45 |
46 |
47 | 48 | 49 |
50 | 55 |
56 | -------------------------------------------------------------------------------- /get-entities.js: -------------------------------------------------------------------------------- 1 | // Get actor / item / scene / journal etc by ID 2 | let actor = game.actors.get("0HcZSUIUZ48WAPyv") 3 | let item = game.items.get("0HcZSUIUZ48WAPyv") 4 | let journal = game.journal.get("0HcZSUIUZ48WAPyv") 5 | let scene = game.scene.get("0HcZSUIUZ48WAPyv") 6 | 7 | // Get actor / item / scene / journal etc by name 8 | let actor = game.actors.getName("Steve") 9 | let item = game.items.getName("Steve's Item") 10 | let journal = game.journal.getName("Steve's Journal") 11 | let scene = game.scene.getName("Steve's House") 12 | 13 | // Get all actors which are Player Characters 14 | let pcs = game.actors.filter(actor => actor.isPC) 15 | 16 | // Get all actors which are npcs 17 | let npcs = game.actors.filter(actor => !actor.isPC) -------------------------------------------------------------------------------- /handlebars-templates.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Example using handlebars templates in a module 3 | */ 4 | 5 | // Preload and/or register handlebars templates and/or partials 6 | Hooks.once('init', () => { 7 | loadTemplates([ 8 | 'modules/mymodule/templates/my-template.html', 9 | 'modules/mymodule/templates/my-partial.html' 10 | ]); 11 | }); 12 | 13 | // Pass values to handlebars template and get rendered result 14 | let myGreeting = 'Hello'; 15 | let myUser = 'Steve'; 16 | const myHtml = await renderTemplate(`/path/to/my-handlebars.html`, { myGreeting, myUser }); 17 | 18 | 19 | // my-template.html 20 |
21 |

{{ myGreeting }}, {{ myUser }}

22 | {{> 'modules/mymodule/templates/my-partial.html' }} 23 |
24 | 25 | // my-partial.html 26 |

This is a partial, {{ myUser }}.

27 | 28 | // Result 29 |
30 |

Hello, Steve

31 |

This is a partial, Steve.

32 |
33 | 34 | /** 35 | * Example getting and inject html from handlebars template 36 | * to existing application 37 | */ 38 | // Whatever application hook, renderMeasuredTemplateConfig is just an example 39 | Hooks.on('renderMeasuredTemplateConfig', async (app, html) => { 40 | // Example value you want to pass to your handlebars template 41 | const myVar = 'Example value to be passed to handlebars'; 42 | // Path to module templates 43 | const tpl = '/modules/mymodule/templates/my-handlebars.html'; 44 | // Get the handlebars output 45 | const myHtml = await renderTemplate(tpl, { myVar }); 46 | 47 | // Find form elements and inject their counterparts 48 | // Slightly complicated example here to show how to find a form element when 49 | // it only has a name and not a class or ID, but any jQuery selector works 50 | const target = $(html).find('[name="fillColor"]').parent(); 51 | // Inject your handlebars template (can also use .before() of course) 52 | target.after(myHtml); 53 | }); -------------------------------------------------------------------------------- /image-popout.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Create and share an image popout 3 | */ 4 | let actor = game.actors.getName('Steve'); 5 | let ip = new ImagePopout(actor.data.img, { uuid: actor.uuid }); 6 | ip.render(true); // Display for self 7 | ip.shareImage(); // Display to all other players 8 | -------------------------------------------------------------------------------- /imagemagick/bulk-convert-to-webp.ps1: -------------------------------------------------------------------------------- 1 | # POWERSHELL SCRIPT 2 | # Convert all png/jpg/gif recursively from current directory to webp 3 | Get-ChildItem .\* -R -include *.png, *.jpg, *.jpeg, *.gif | 4 | Foreach-Object { 5 | magick convert -strip $_.FullName -quality 60 "$($_.FullName.split('.')[0]).webp" 6 | echo "Converting $_" 7 | } 8 | -------------------------------------------------------------------------------- /imagemagick/bulk-flatten-with-background.ps1: -------------------------------------------------------------------------------- 1 | # POWERSHELL SCRIPT 2 | # For transparent png/gifs, will composite onto black background 3 | # All gif/png in current directory 4 | # Can specify any color you want that imagemagick understands 5 | # - f.x. black, white, '#CCDDEE', ec 6 | Get-ChildItem .\* -R -include *.png, *.gif, *.webp | 7 | Foreach-Object { 8 | magick convert $_.FullName -background black -alpha remove -alpha off "$($_.FullName.split('.')[0].ToLower())_flat$($_.Extension)" 9 | } 10 | -------------------------------------------------------------------------------- /imagemagick/single-convert-to-webp: -------------------------------------------------------------------------------- 1 | magick convert myImage.png -strip -quality 60 myImage.webp -------------------------------------------------------------------------------- /imagemagick/single-flatten-with-background: -------------------------------------------------------------------------------- 1 | magick convert myImage.png -background black -alpha remove -alpha off myNewImage.png -------------------------------------------------------------------------------- /macro-in-macro.js: -------------------------------------------------------------------------------- 1 | game.macros.getName("myMacro").execute(); -------------------------------------------------------------------------------- /macros/ddb-importer-monster-splitter: -------------------------------------------------------------------------------- 1 | /* Splits DDB Importer monster compendium into smaller chunks */ 2 | 3 | const source = game.packs.get('world.ddb-dnd-monsters'); 4 | const dests = ['AB','CDE','FGH','IJKL','MNO','PQR','ST','UVW','XYZ']; 5 | const compendiums = {}; 6 | 7 | dests.forEach(async i => { 8 | const label = `${i.charAt(0)}-${i.charAt(i.length - 1)}`; 9 | compendiums[i] = await Compendium.create({ 10 | label: `DDB Monsters ${label}`, 11 | entity: 'Actor', 12 | }) 13 | }) 14 | 15 | console.log('Starting DDB Monster Split'); 16 | let c = await source.getContent(); 17 | for (entry of c) { 18 | const fc = entry.name.charAt(0).toUpperCase(); 19 | let dest = dests.find(d => d.split('').includes(fc.toUpperCase())); 20 | if (dest == null) dest = dests[0]; 21 | await compendiums[dest].createEntity(entry.data) 22 | } 23 | console.log('Finished DDB Monster Split'); 24 | -------------------------------------------------------------------------------- /macros/debug.js: -------------------------------------------------------------------------------- 1 | let report = {}; 2 | let output = ''; 3 | function formatBytes(a,b=2){if(0===a)return"0 Bytes";const c=0>b?0:b,d=Math.floor(Math.log(a)/Math.log(1024));return parseFloat((a/Math.pow(1024,d)).toFixed(c))+" "+["Bytes","KB","MB","GB","TB","PB","EB","ZB","YB"][d]} 4 | 5 | // Foundry Details 6 | report.System = { 7 | Foundry: game.data.version, 8 | System: `${game.system.id} version ${game.system.data.version}`, 9 | }; 10 | 11 | report.User = { 12 | Role: Object.keys(CONST.USER_ROLES)[game.user.role], 13 | } 14 | 15 | report.Scene = { 16 | Walls: canvas.walls.placeables.length, 17 | Lights: canvas.lighting.placeables.length, 18 | Tokens: canvas.tokens.placeables.length, 19 | Tiles: canvas.tiles.placeables.length, 20 | Sounds: canvas.sounds.placeables.length, 21 | Drawings: canvas.drawings.placeables.length, 22 | Notes: canvas.notes.placeables.length, 23 | Dimensions: `${canvas.dimensions.width} x ${canvas.dimensions.height}`, 24 | Background: `${canvas.background?.img?.texture?.width} x ${canvas.background?.img?.texture?.height}`, 25 | } 26 | 27 | report.Database = { 28 | Actors: game.actors.size, 29 | Items: game.items.size, 30 | Scenes: game.scenes.size, 31 | Journals: game.journal.size, 32 | Tables: game.tables.size, 33 | } 34 | 35 | // Module details 36 | let ct = 0; 37 | game.modules.forEach(m => { 38 | if (m.active) ct++; 39 | }); 40 | 41 | report.Modules = { 42 | Total: game.modules.size, 43 | Enabled: ct, 44 | } 45 | 46 | // Browser Details 47 | report.Browser = { 48 | Platform: navigator.platform, 49 | Vendor: navigator.vendor, 50 | Agent: navigator.userAgent, 51 | } 52 | 53 | // Browser Details 54 | let gl = canvas.app.renderer.gl; 55 | if (gl) { 56 | report.WebGL = { 57 | Context: gl.constructor.name, 58 | GL_Vendor: gl.getParameter(gl.VENDOR), 59 | Renderer: gl.getParameter(gl.RENDERER), 60 | WebGL_Version: gl.getParameter(gl.VERSION), 61 | MAX_TEXTURE_SIZE: gl.getParameter(gl.MAX_TEXTURE_SIZE), 62 | MAX_RENDERBUFFER: gl.getParameter(gl.MAX_RENDERBUFFER_SIZE), 63 | } 64 | } else { 65 | report.WebGL = { 66 | Context: 'FAILED TO GET WEBGL CONTEXT' 67 | } 68 | } 69 | 70 | // If chromium browser we can check memory stats 71 | if (performance?.memory) { 72 | report.Memory = { 73 | Heap_Limit: formatBytes(performance?.memory.jsHeapSizeLimit), 74 | Heap_Total: formatBytes(performance?.memory.totalJSHeapSize), 75 | Heap_Used: formatBytes(performance?.memory.usedJSHeapSize), 76 | } 77 | } 78 | 79 | for (const [k1, v1] of Object.entries(report)) { 80 | output += `${k1}:\n`; 81 | for (const [k2, v2] of Object.entries(v1)) { 82 | output += ` ${k2}: ${v2}\n`; 83 | } 84 | } 85 | 86 | let d = new Dialog({ 87 | title: `Debug Output`, 88 | content: ``, 89 | buttons: { 90 | copy: { 91 | label: `Copy to clipboard`, 92 | callback: () => { 93 | $("#debugmacro").select(); 94 | document.execCommand('copy'); 95 | } 96 | }, 97 | close: { 98 | icon: "", 99 | label: `Close` 100 | }, 101 | }, 102 | default: "close", 103 | close: () => {} 104 | }); 105 | 106 | d.options.width = 600; 107 | d.position.width = 600; 108 | d.render(true); 109 | -------------------------------------------------------------------------------- /macros/heal-nearby.js: -------------------------------------------------------------------------------- 1 | // Add 10 temp hp to tokens within 10 units of selected 2 | let thp = 10; 3 | let dist = 10; 4 | let sel = canvas.tokens.controlled[0]; 5 | canvas.tokens.placeables.forEach(token => { 6 | let d = canvas.grid.measureDistance(sel, token); 7 | if (d < dist && token.data.disposition === 1) { 8 | let curthp = token.actor.data.data.attributes.hp.temp; 9 | if (curthp < thp || curthp == "") { 10 | token.actor.update({"data.attributes.hp.temp" : thp}); 11 | console.log(`Adding ${thp} temp hp to ${token.name}`); 12 | } 13 | } 14 | }); 15 | -------------------------------------------------------------------------------- /macros/lights.js: -------------------------------------------------------------------------------- 1 | // Open a dialog for quickly changing token vision parameters of the controlled tokens. 2 | // This macro was written by @Sky#9453 3 | // https://github.com/Sky-Captain-13/foundry 4 | 5 | // Adds toggle for Has Vision 6 | // Removes furnace dependency 7 | // Minor performance improvement 8 | // - Vance 9 | 10 | (() => { 11 | 12 | if (canvas.tokens.controlled.length === 0) { 13 | return ui.notifications.error("Please select a token first"); 14 | } 15 | 16 | let applyChanges = false; 17 | new Dialog({ 18 | title: `Token Vision Configuration`, 19 | content: ` 20 |
21 |
22 | 23 | 34 |
35 |
36 | 37 | 48 |
49 |
50 | 51 | 52 |
53 |
54 | `, 55 | buttons: { 56 | yes: { 57 | icon: "", 58 | label: `Apply Changes`, 59 | callback: () => applyChanges = true 60 | }, 61 | no: { 62 | icon: "", 63 | label: `Cancel Changes` 64 | }, 65 | }, 66 | default: "yes", 67 | close: html => { 68 | if (applyChanges) { 69 | let visionType = html.find('[name="vision-type"]')[0].value; 70 | let lightSource = html.find('[name="light-source"]')[0].value; 71 | let vision = html.find('[name="vision"]').is(":checked"); 72 | for ( let token of canvas.tokens.controlled ) { 73 | let dimSight = 0; 74 | let brightSight = 0; 75 | let dimLight = 0; 76 | let brightLight = 0; 77 | let lightAngle = 360; 78 | let lockRotation = token.data.lockRotation; 79 | // Get Vision Type Values 80 | switch (visionType) { 81 | case "dim0": 82 | dimSight = 0; 83 | brightSight = 0; 84 | break; 85 | case "dim30": 86 | dimSight = 30; 87 | brightSight = 0; 88 | break; 89 | case "dim60": 90 | dimSight = 60; 91 | brightSight = 0; 92 | break; 93 | case "dim90": 94 | dimSight = 90; 95 | brightSight = 0; 96 | break; 97 | case "dim120": 98 | dimSight = 120; 99 | brightSight = 0; 100 | break; 101 | case "dim150": 102 | dimSight = 150; 103 | brightSight = 0; 104 | break; 105 | case "dim180": 106 | dimSight = 180; 107 | brightSight = 0; 108 | break; 109 | case "bright120": 110 | dimSight = 0; 111 | brightSight= 120; 112 | break; 113 | case "nochange": 114 | default: 115 | dimSight = token.data.dimSight; 116 | brightSight = token.data.brightSight; 117 | } 118 | // Get Light Source Values 119 | switch (lightSource) { 120 | case "none": 121 | dimLight = 0; 122 | brightLight = 0; 123 | break; 124 | case "candle": 125 | dimLight = 10; 126 | brightLight = 5; 127 | break; 128 | case "lamp": 129 | dimLight = 45; 130 | brightLight = 15; 131 | break; 132 | case "bullseye": 133 | dimLight = 120; 134 | brightLight = 60; 135 | lockRotation = false; 136 | lightAngle = 52.5; 137 | break; 138 | case "hooded-dim": 139 | dimLight = 5; 140 | brightLight = 0; 141 | break; 142 | case "hooded-bright": 143 | dimLight = 60; 144 | brightLight = 30; 145 | break; 146 | case "light": 147 | dimLight = 40; 148 | brightLight = 20; 149 | break; 150 | case "torch": 151 | dimLight = 40; 152 | brightLight = 20; 153 | break; 154 | case "nochange": 155 | default: 156 | dimLight = token.data.dimLight; 157 | brightLight = token.data.brightLight; 158 | lightAngle = token.data.lightAngle; 159 | lockRotation = token.data.lockRotation; 160 | } 161 | // Update Token 162 | token.update({ 163 | vision, 164 | dimSight, 165 | brightSight, 166 | dimLight, 167 | brightLight, 168 | lightAngle, 169 | lockRotation, 170 | }); 171 | } 172 | } 173 | } 174 | }).render(true); 175 | })(); 176 | -------------------------------------------------------------------------------- /macros/list-modules.js: -------------------------------------------------------------------------------- 1 | let mods = ''; 2 | game.modules.forEach(m => { 3 | let a = m.active ? 'Enabled' : 'Disabled'; 4 | mods = mods.concat(`${m.id}: ${a}\n`); 5 | }); 6 | 7 | let d = new Dialog({ 8 | title: `Enabled Mods`, 9 | content: ``, 10 | buttons: { 11 | copy: { 12 | label: `Copy to clipboard`, 13 | callback: () => { 14 | $("#modslist").select(); 15 | document.execCommand('copy'); 16 | } 17 | }, 18 | close: { 19 | icon: "", 20 | label: `Close` 21 | }, 22 | }, 23 | default: "close", 24 | close: () => {} 25 | }); 26 | 27 | d.render(true); 28 | -------------------------------------------------------------------------------- /macros/luck.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Luck Macro 3 | * Change 'secondary' to primary/secondary/tertiary 4 | * to match which is your luck resource 5 | */ 6 | let char = game.user.character; 7 | let data = char.data.data; 8 | let luck = data.resources.secondary.value; 9 | 10 | if(luck > 0) { 11 | data.resources.secondary.value--; 12 | char.update(data); 13 | new Roll('1d20').toMessage({ flavor: 'Lucky!' }); 14 | } else { 15 | ui.notifications.warn('You are out of luck.'); 16 | } 17 | -------------------------------------------------------------------------------- /macros/replace-actors-in-journals.js: -------------------------------------------------------------------------------- 1 | (async () => { 2 | let myfind = game.actors.getName('My NPC'); 3 | let myreplace = game.actors.getName('My PC'); 4 | if (myfind === null || myreplace === null) { 5 | ui.notifications.error('Could not find one of the actors'); 6 | return; 7 | } 8 | let regex = new RegExp(`\@Actor\\[${myfind.id}\\]{${myfind.name}}`, 'g'); 9 | game.journal.entries.forEach(async j => { 10 | let content = j.data.content; 11 | if (content.indexOf(myfind.id) === -1) return; 12 | let newcontent = content.replace(regex, `@Actor[${myreplace.id}]{${myreplace.name}}`); 13 | await j.update({'content': newcontent}); 14 | }); 15 | })(); 16 | -------------------------------------------------------------------------------- /macros/reset-fog-player.js: -------------------------------------------------------------------------------- 1 | // Requests individual user to reset fog exploration 2 | let uid = game.users.getName('Steve').id; 3 | SocketInterface.dispatch("modifyDocument", { 4 | type: "FogExploration", 5 | action: "delete", 6 | data: {user: uid, scene: canvas.scene.id}, 7 | options: {reset: true} 8 | }) 9 | -------------------------------------------------------------------------------- /macros/retrieve-lost-windows.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Macros to retrieve windows that ocassionaly get lost off screen 3 | */ 4 | 5 | // Popped out webcams 6 | $('.camera-view-popout').css({"left": "200px", "top": "200px"}); 7 | 8 | // Calendar module 9 | $('#calendar-time-container').css({"left": "200px", "top": "200px"}); -------------------------------------------------------------------------------- /macros/reveal-fog-of-war.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Reveals all fog of war to explored state 3 | * NOT THOROUGHLY TESTED, USE WITH CAUTION 4 | */ 5 | // Set desired level of opacity for revealed areas 6 | const opacity = 0x999999; 7 | 8 | // Get fog obj 9 | const fog = canvas.sight.fog; 10 | 11 | // Create a new render texture 12 | const revealed = PIXI.RenderTexture.create({ 13 | width: canvas.dimensions.width, 14 | height: canvas.dimensions.height, 15 | scale: 1, 16 | resolution: canvas.sight._fogResolution 17 | }); 18 | // Fill render texture with desired opacity 19 | const fill = new PIXI.Graphics(); 20 | fill.beginFill(opacity); 21 | fill.drawRect(0, 0, canvas.dimensions.width, canvas.dimensions.height); 22 | fill.endFill(); 23 | 24 | // Render fill to the texture 25 | canvas.app.renderer.render(fill, revealed); 26 | 27 | // Swap the staging texture to the rendered texture 28 | fog.rendered.texture.destroy(true); 29 | fog.rendered.texture = revealed; 30 | -------------------------------------------------------------------------------- /macros/tile-quick-toggle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Toggle visibility of tiles under mouse position 3 | */ 4 | 5 | /** ------------ */ 6 | 7 | /** 8 | * Check if token is in bounds of given tile 9 | * @param {Tile} tile An instance of Foundry Tile Placeable 10 | * @param {PIXI.IPointData} point - The Point to convert 11 | */ 12 | function inBounds(tile, point) { 13 | // Get local pos relative to tile 14 | const sprite = tile.tile.children[0]; 15 | const local = translatePoint(point, canvas.stage, tile.tile); 16 | // Check if in bounds of the tile 17 | if ( 18 | local.x > 0 19 | && local.y > 0 20 | && local.x < sprite.width 21 | && local.y < sprite.height 22 | ) { 23 | return true; 24 | } 25 | return false; 26 | } 27 | 28 | /** 29 | * Converts the coordinates of a Point from one context to another 30 | * 31 | * @static 32 | * @param {PIXI.IPointData} point - The Point to convert 33 | * @param {PIXI.Container} context1 - The context the point is currently in 34 | * @param {PIXI.Container} context2 - The context to translate the point to 35 | * @return {PIXI.Point} A Point representing the coordinates in the second context 36 | */ 37 | function translatePoint(point, context1, context2) { 38 | const pt = new PIXI.Container(); 39 | context1.addChild(pt); 40 | pt.position.set(point.x, point.y); 41 | const tp = context2.toLocal(new PIXI.Point(), pt); 42 | context1.removeChild(pt); 43 | return tp; 44 | } 45 | 46 | /** 47 | * Gets current mouse position relative to stage regardless of needing mouse event 48 | */ 49 | function getMouseStagePos() { 50 | const mouse = canvas.app.renderer.plugins.interaction.mouse; 51 | return mouse.getLocalPosition(canvas.app.stage); 52 | } 53 | 54 | 55 | /** 56 | * Return all tiles that exist at given point 57 | */ 58 | function getTilesAtPos(point) { 59 | return canvas.tiles.placeables.filter((tile) => { 60 | return inBounds(tile, point); 61 | }); 62 | } 63 | 64 | function main() { 65 | const pos = getMouseStagePos(); 66 | const tiles = getTilesAtPos(pos); 67 | const updates = tiles.map(t => { 68 | return { 69 | _id: t.id, 70 | hidden: !t.data.hidden, 71 | } 72 | }); 73 | canvas.tiles.updateMany(updates); 74 | } 75 | 76 | main(); 77 | -------------------------------------------------------------------------------- /macros/toggle-active-character.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Swaps between two actors as active character, 3 | * useful when 1 player is playing 2 characters 4 | */ 5 | const a = game.actors.getName(`Steve`); 6 | const b = game.actors.getName(`Janet`); 7 | if (!a || !b) ui.notifications.warn('Could not locate actors.'); 8 | else { 9 | if (game.user.data.character === a.id) game.user.update({character: b.id}); 10 | else game.user.update({character: a.id}); 11 | } 12 | -------------------------------------------------------------------------------- /mouse-position.js: -------------------------------------------------------------------------------- 1 | // Add listener to game board and report mouse click locations 2 | canvas.app.stage.addListener('pointerdown', event => { 3 | let pos = event.data.getLocalPosition(canvas.app.stage); 4 | console.log(`x: ${pos.x}, y: ${pos.y}`); 5 | }); 6 | 7 | /* 8 | * Example of getting mouse position when 9 | * macro is triggered by keyboard event 10 | * 11 | * Creates token of given actor at mouse coordinates 12 | */ 13 | let myActor = "Steve"; 14 | 15 | let mouse = canvas.app.renderer.plugins.interaction.mouse; 16 | let local = mouse.getLocalPosition(canvas.app.stage); 17 | 18 | let actor = game.actors.getName(myActor); 19 | canvas.tokens.dropActor(actor, { 20 | x: Math.round(local.x), 21 | y: Math.round(local.y) 22 | }); 23 | -------------------------------------------------------------------------------- /notification-warning-error.js: -------------------------------------------------------------------------------- 1 | // Display notification 2 | ui.notifications.notify('This is a notification'); 3 | 4 | // Display warning 5 | ui.notifications.warn('This is a warning'); 6 | 7 | // Display error 8 | ui.notifications.error('This is an error'); 9 | -------------------------------------------------------------------------------- /position.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Get canvas position given PIXI Container like object 3 | */ 4 | function getCanvasPos(context) { 5 | const { x, y } = context.toGlobal(new PIXI.Point()); 6 | const t = canvas.stage.worldTransform; 7 | let nx = (x - t.tx) / canvas.stage.scale.x; 8 | let ny = (y - t.ty) / canvas.stage.scale.y; 9 | return { x: nx, y: ny}; 10 | } 11 | 12 | /** 13 | * Converts the coordinates of a Point from one context to another 14 | * 15 | * @param {PIXI.IPointData} point - The Point to convert 16 | * @param {PIXI.Container} context1 - The context the point is currently in 17 | * @param {PIXI.Container} context2 - The context to translate the point to 18 | * @return {PIXI.Point} A Point representing the coordinates in the second context 19 | */ 20 | function translatePoint(point, context1, context2) { 21 | const pt = new PIXI.Container(); 22 | context1.addChild(pt); 23 | pt.position.set(point.x, point.y); 24 | const tp = context2.toLocal(new PIXI.Point(), pt); 25 | context1.removeChild(pt); 26 | return tp; 27 | } -------------------------------------------------------------------------------- /powershell/replace_special_chars.ps1: -------------------------------------------------------------------------------- 1 | Get-ChildItem -Recurse | 2 | Foreach-Object { 3 | $newName = $_.name -replace '[^A-Za-z0-9-_ \.\[\]]', '' 4 | if (-not ($_.name -eq $newname)){ 5 | Rename-Item -Path $_.fullname -newname ($newName) 6 | } 7 | } -------------------------------------------------------------------------------- /ray-collision.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Example provided by @cole 3 | * 4 | * Cast ray and detect wall collisions between 2 tokens 5 | */ 6 | const A = canvas.tokens.placeables.find(t => t.name === "Renee"); 7 | const B = canvas.tokens.placeables.find(t => t.name === "Brutus"); 8 | 9 | const ray = new Ray({ x: A.x, y: A.y }, { x: B.x, y: B.y }); 10 | const collisions = WallsLayer.getWallCollisionsForRay(ray, canvas.walls.blockVision); 11 | 12 | const collided = collisions.length > 0; 13 | 14 | console.log(collided) -------------------------------------------------------------------------------- /roll-tables.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Examples for using roll tables 3 | * Examples assume existence of a basic roll table 4 | * called "Months" that contains text entries 5 | */ 6 | 7 | // Roll table and output to chat 8 | game.tables.getName('Months').draw(); 9 | 10 | // Roll table and store result for later use 11 | let roll = game.tables.getName('Months').roll(); 12 | 13 | // Roll table and send custom chat with result 14 | let draw = game.tables.getName('Months').roll(); 15 | let month = draw.results[0].text; 16 | ChatMessage.create({ content: `The month is: ${month}`}); 17 | -------------------------------------------------------------------------------- /settings.js: -------------------------------------------------------------------------------- 1 | // Set core game settings 2 | await game.settings.set('core','rollMode','roll'); 3 | 4 | // Get core game setting 5 | game.settings.get('core','rollMode'); 6 | 7 | /* 8 | * Example to toggle a game setting 9 | * In this case, toggles Roll Mode between gm and normal 10 | */ 11 | if (game.settings.get('core','rollMode') == 'roll') { 12 | game.settings.set('core','rollMode','gmroll'); 13 | $('.roll-type-select select').val('gmroll'); 14 | } else { 15 | game.settings.set('core','rollMode','roll'); 16 | $('.roll-type-select select').val('roll'); 17 | } 18 | 19 | /* 20 | * Create a custom config setting 21 | */ 22 | await game.settings.register('myModule', 'mySetting', { 23 | name: 'My Setting', 24 | scope: 'world', // "world" = sync to db, "client" = local storage 25 | config: true, // false if you dont want it to show in module config 26 | type: Number, // Number, Boolean, String, 27 | default: 0, 28 | range: { 29 | min: 0, 30 | max: 100, 31 | step: 10 32 | }, 33 | onChange: value => { 34 | console.log(value) 35 | } 36 | }); 37 | 38 | // Get value of custom setting 39 | let x = game.settings.get('myModule','mySetting'); 40 | 41 | // Change existing custom setting 42 | await game.settings.set('myModule','mySetting','myValue'); 43 | -------------------------------------------------------------------------------- /sockets.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Example for using socket events in a module 3 | * 4 | * Note: you **MUST** set in your module manifest: 5 | * "socket": true 6 | * 7 | * Example assumes you have created a module called 'mymodule' 8 | */ 9 | 10 | // Create a socket event handler to listen to incomming sockets and dispatch to callbacks 11 | game.socket.on(`module.mymodule`, (data) => { 12 | if (data.operation === 'myEvent') handleMyEvent(data); 13 | if (data.operation === 'myOtherEvent') handleMyOtherEvent(data); 14 | }); 15 | 16 | // Emit a socket event 17 | game.socket.emit('module.mymodule', { 18 | operation: 'myEvent', 19 | user: game.user.id, 20 | content: 'Hello', 21 | }); 22 | 23 | function handleMyEvent(data) { 24 | console.log(`User [${data.user}] says: ${data.content}`); 25 | } 26 | function handleMyOtherEvent(data) { 27 | // do something 28 | } 29 | -------------------------------------------------------------------------------- /status-effects.js: -------------------------------------------------------------------------------- 1 | // Assign skull effect to selected tokens 2 | const effect = "icons/svg/skull.svg"; 3 | canvas.tokens.controlled.forEach(token => { 4 | token.toggleEffect(effect); 5 | }); 6 | 7 | // Assign skull effect to selected tokens as larger overlay version 8 | const effect = "icons/svg/skull.svg"; 9 | canvas.tokens.controlled.forEach(token => { 10 | token.toggleOverlay(effect); 11 | }); 12 | -------------------------------------------------------------------------------- /tokens.js: -------------------------------------------------------------------------------- 1 | // Get token from scene by name 2 | let token = canvas.scene.data.tokens.find(token => token.name = 'My Actor') 3 | 4 | // Get first controlled token 5 | let token = canvas.tokens.controlled[0]; 6 | 7 | // Get all controlled tokens and do something with each 8 | let tokens = canvas.tokens.controlled; 9 | tokens.forEach(token => { 10 | console.log(token.name); 11 | }); 12 | 13 | // Distance between 2 tokens 14 | let d = canvas.grid.measureDistance(token1, token2); 15 | 16 | // Select one random token from amongst all selected 17 | let tks = canvas.tokens.controlled; 18 | let r = Math.floor(Math.random()*tks.length); 19 | canvas.tokens.selectObjects(tks[r]); 20 | 21 | /* 22 | * Add token to scene 23 | * Options override (actor.data.token) or set properties 24 | */ 25 | let tk = game.actors.getName('MyActor').data.token; 26 | tk.x = 100; 27 | tk.y = 100; 28 | Token.create(tk); 29 | 30 | /* 31 | * Add multiple tokens to scene 32 | */ 33 | let tk = game.actors.getName('ActorOne').data.token; 34 | let tk2 = game.actors.getName('ActorTwo').data.token; 35 | tk.x = 100; 36 | tk.y = 100; 37 | tk2.x = 200; 38 | tk2.y = 200; 39 | Token.create([tk, tk2]); 40 | 41 | // Select all tokens in scene 42 | let tokens = canvas.tokens.placeables; 43 | canvas.tokens.selectObjects(tokens) 44 | 45 | // Select token by name 46 | let token = canvas.tokens.placeables.find(t => t.name === 'Azimuth'); 47 | token.control(); 48 | 49 | // Select all NPC tokens 50 | canvas.tokens.selectObjects(); // Drop existing selection 51 | let tokens = canvas.tokens.placeables.filter(t => !t.actor.isPC); // Find NPCs 52 | tokens.forEach(t => { t.control({ releaseOthers: false }); }); // Select them 53 | 54 | // Select token while retaining control of existing selection 55 | token.control({ releaseOthers: false }); 56 | -------------------------------------------------------------------------------- /update.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Using update() to update a single entity 3 | * this example toggles the hidden property of the 4 | * first selected unit 5 | */ 6 | let token = canvas.tokens.controlled[0]; 7 | token.update({ 8 | hidden: !token.data.hidden 9 | }) 10 | 11 | /** 12 | * Using updateMany() to update all tokens on a scene 13 | * this example sets the hidden property to false, 14 | * revealing any hidden units 15 | */ 16 | let updates = canvas.tokens.placeables.map(token => { 17 | return { 18 | _id: token.id, 19 | hidden: false 20 | } 21 | }); 22 | canvas.tokens.updateMany(updates); -------------------------------------------------------------------------------- /walls.js: -------------------------------------------------------------------------------- 1 | // Update selected walls to given values 2 | let updates = canvas.walls.controlled.map(w => { 3 | return { 4 | '_id': w.id, 5 | door: 0, 6 | dir: 0, 7 | ds: 0, 8 | move: 1, 9 | sense: 1, 10 | sound: 1, 11 | } 12 | }); 13 | canvas.walls.updateMany(updates) --------------------------------------------------------------------------------