Spolier Alert

WARNING: Posts addressing individual campaigns contain spoilers, including: Lost Mine of Phandelver, Horde of the Dragon Queen, The Rise of Tiamat, Yawning Portal, Princes of the Apocalypse, and home-brew content.

Tuesday, October 12, 2021

Foundry Macros - Writing My First (with some understanding)

I've been fiddling with macros and javascript relative to Foundry VTT for a few days now.  It seems a good time to write my own.  By that I mean go through the steps following closely in spacemandev's footsteps in his YouTube video, FoundryVTT Macros 101.

 I'll try to note the interesting bits as I go through the process. 

Visual Studios Code

My first non-obvious task was to obtain a copy of Visual Studios Code, which is available at no cost for PC, Mac, and Linux environments at this site: code.visualstudio.comI basically did the obvious to install it and get it started. 

One rather obscure step though -- open a copy of foundry.js in a window of VSCode seems to be necessary to enable the yummy completion lists.  WARNING: Do not change that file.  I think the window containing foundry.js can be closed after it is loaded and things will still work.  I've not really verified that. 

The file can be found at (on a Mac): FoundryVTT.app/Contents/Resources/app/public/scripts/foundry.js.  Because I am aware of my own fat fingers, I made a copy of the file and put it in the folder I use for Foundry-related coding tasks.  I'll have to manually update that at some point, but I can't destroy my actual running system.

General Coding Process

My workflow will (at least initially) mimic Spacemandev.  That is I will write the code in VSCode so I can leverage the help it can provide and then do a cut'n'paste into Foundry to execute it. A couple of handy steps:

  • Toggle Developer Tools with opt-cmd-i
  • View the console by clicking the console tab
  • Clear the console of old messages by clicking the do not enter symbol (top left of console)

Interacting with the Console

The console can be used to view (and change) important pieces of information. 

It can be used to explore the defined data structures.  One commonly accessed is the game object that contains a swarm of important information. 

Typing game. will cause a drop-down completion list such as the one shown on the right to appear.  This is telling you about the next level of definition within game.

Typing game.actors and hitting enter will give a list of all the actors in the game world.  This can be used to view the actual data model and discover what the magical dot eliminated things actually are called and what they currently contain. 

Obtaining Roll Data (for Chat Macros)

A quick little script macro can be used to view the roll data associated with a token. This script macro:
console.log(token.actor.getRollData());
This allows a view into the data for the selected actor (note: the macro as written has no error handling, it fails if a token is not selected.  The partial screen capture here shows the model partially opened. 

This particular token has a 13 strength (value) and a strength mod of 1.

This can be used in a script macro to add that mod to a roll.  The script macro would read: 
/r d20 + @abilities.str.mod
This macro will roll a d20 and add the token's strength modifier to the result.  The funky @ symbol is a way to refer to the currently selected token in a chat macro. 

Setting Up Wrapper Code to Act on Each Selected Token

So far, I've been tracking spacemandev's video, now I want to branch out and create a macro that does something with each selected token and includes some error handling.  

My first dip was just creating a macro that can spit out the names of the tokens and their associated actor within the generic wrapper I posted in a preceding post.  That macro goes as follows:

console.log("Launching JGB's Macro to Affect selecetd tokens");
/************************************************************
* This simple macro will spit out the name of each selected
* token and its actor. Just as a demonstration.
************************************************************/
main()
async function main() { // async isn't required here, if it is, consider await
let tokens = canvas.tokens.controlled;
if (tokens.length > 0 ){
for (let token of tokens ){
let actor = token.actor
// The following code is intended to affect each token selected.
console.log(token.name); // The token's name
console.log(token.actor.name); // The token's actor's name
}
} else {
ui.notifications.error("No token selected.");
}
}

The macro works, in that it provides an error message if no tokens are selected and it prints names, tokens, and actors to the console. 

Stipping Trash from Prototype Token Name 

One of my "known issues" with the campaigns I have imported from Roll20 is that a large number of my generic monsters ended up with prototype tokens with names of the form:

 Name %%NUMBERED%%

That is a result of some Roll20 coding (hacking, I suppose) to implement a numbered token system.  Something that Foundry provides as part of the standard package.

Unfortunately, I brought in the baggage and now want to be able to get rid of it. If I can manage to just strip the extraneous " %%NUMBERED%%" out of the prototype of the selected token(s), this shouldn't be an issue. 

After considerable effort, we failed to find a way to update the prototype token, though we did manage to fix the on-screen token's name with the following code block:

/*******************************************************
* The following block of code affects selected tokens.
* In this case it is replaces Token Name with Actor Name
*******************************************************/
if (token.name.includes("%%NUMBERED%%")) {
console.log("Found a %%NUMBERED%%");
let goodName = token.actor.name.toString();
token.document.update({name: goodName});
/*******************************************************
* End of code block affecting each token
*******************************************************/

Actually fixing each bad prototype token can be done by:
  1. Open the Prototype's Token Dialog,
  2. Delete the extraneous %%NUMBERED%% from the Token Name field,
  3. Click the Update Token button.
Not a great result, but some manual effort will do the trick at the situation should not repeat for me. 

Adding in a Dialog to Modify Action Loop

With partial success under my belt, I want to try using a dialog to alter the actions performed to each selected token.  The objective of this exercise will be to apply healing to tokens, but not allow the token to exceed its maximum health.  

My starting point is a YouTube video posted by Cédric HautevilleFoundry System Dev - Part 8 : Dialog boxes and system settings.  As well as a much simplified dialog posted on Reddit. I found Cedric's example too intense and instead choose to build off of a different snippet.  Embedded below so I can be sure to find it again:

let d = new Dialog({
title: "Test Dialog",
content: "<p>You must choose either Option 1, or Option 2</p>",
buttons: {
one: {
icon: '<i class="fas fa-check"></i>',
label: "Option One",
callback: () => console.log("Chose One")
},
two: {
icon: '<i class="fas fa-times"></i>',
label: "Option Two",
callback: () => console.log("Chose Two")
}
},
default: "two",
render: html => console.log("Register interactivity in the rendered dialog"),
close: html => console.log("This always is logged no matter which option is chosen")
});
d.render(true);

The above has four possible results from user action:
  1. Select the first button 
  2. Select the second button
  3. Click the X on the dialog to close it
  4. Hit esc on the keyboard to close it. 
It also uses those nasty arrow functions, which actually do seem to work better than the style I have been using. 

Yet another example Macro for updating npc token hp from Reddit is linked here.  His fixed macro is buried down several posts in that thread.

My trick will be to figure out how to have it:
  1. Give the token's name, current and max health, in a text field, 
  2. Query the user for how much health to add and 
  3. Then to add the allowed amount of health up to the token's max health.











No comments:

Post a Comment