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.

Sunday, October 10, 2021

Foundry Macros -- Getting a Start

The task I've taken on, migrating an existing Curse of Strahd campaign that has been running in Roll20 for about a year with a lot of amazing automation that Jon engineered for us places a fairly high bar.  

I need Foundry to pretty much be as good as our Roll20 environment across the board.  Foundry Macros appear to be much more powerful and robust than those offered in Roll20...but we have a couple of years of patching over warts and automating our game system.  

This posting focuses on my learning the Foundry macro ropes, largely indexing resources that I expect will make my journey easier. 

FoundryVTT Macros 101

My first surge of understanding resulted from watching a YouTube video posted by spacemandev. The YouTube video is here: FoundryVTT Macros 101.


Spacemandev posted the five macros he intended to highlight in the video, though he only actually covered one (turns out things go slower when explaining or if you don't understand). The source code is on https://github.com/spacemandev-git/fvtt-tutorials

His video uses Visual Studio as a development environment, which seemed really useful.  That software is currently offered at a very reasonable price of free on code.visualstudio.com. I have grabbed and installed it on my apple silicon mac.  I'll come back later and read their page on Getting Started.

Launching the Console

One key learning is how to open the console from the keyboard of a Mac: Option-Command-I (It's just F12 on a PC -- ok, PC implementation wins this one. Side note, the video gives the incorrect hotkey for the Mac.

The console can also be launched from the application menu (top of Mac screen),  FoundryVTT, Toggle Developer Tools.

There is an amazing wealth of options in those tools.  The console itself being the place the info/warning/error messages end up as well as informational bits created by commands like: console.log("Hello World").

Macros 102

Spacemandev posted a followup video that goes into how to roll results in a macro in another YouTube video: Macros 102.  


In this video, he delves into his attackMacro.js.  I've not watched the 50-minute long vid, yet, but I suspect it will be well worth the time. 

How to Act on One or More Selected Tokens

I ran across an effort by Talidor on Foundry Hub to post common macro tasks in a generic and documented manner.  He started the thread on Nov 2, 2011.  Unfortunately, it didn't go very far other than his seemingly excellent example that I will embed here for (at least) my future reference. 

(async () => {
    var tokens = canvas.tokens.controlled;
    if (tokens.length > 0 ){
        for (let token of tokens ){
             let actor = token.actor
             // some code which modifies the actor or token
        }
    } else {
        ui.notifications.error("No token selected.");
    }
})();

As I look at that snippet, several equations pop into my non-JavaScript brain.  

What's that async?

Apparently. the async more or less declares that this block can be executed asynchronously (out of order).  When ordering is important, use that await prefix to force execution before the following code.  That's trippy to me, I've never created code that could run out of order. 

What's up with var and let?

They both look like variable declarations with the assigned initial values.  I found a page that delves into these Javascript constructs on freecodecamp.  I need to read that page. I think the net is that var is an old implementation, the snippet likely could have just used let.

Here is a summary of the three declarations from Love2Dev.

  • var - has function level scoping and can change the value reference
  • let - has block level scoping and can change the value reference
  • const - has block level scoping but cannot change the value reference

What's that funky wrapper?

My watching of spacemandev's videos makes sense of the opening and closing lines of bizarre-looking characters.   They are what seems to be one of two schools of formatting.  Spacemandev advocates the following wrapper style"

main()
async function main(){
    // Code goes here.
 
The above is calling a function that, curiously, is defined after it's called, which is a-ok in Javascript he says.  Well, it sure looks better and doesn't fill me with to much confusion, so I've found a key style element for my future use. 

My Wrapper for the Task

Assuming I understand the above, I prefer the following as a generic code wrapper:
 
main() 
async function main() {
    let tokens = canvas.tokens.controlled;
    if (tokens.length > 0 ){
        for (let token of tokens ){
             let actor = token.actor
             // some code which modifies the actor or token
        }
    } else {
        ui.notifications.error("No token selected.");
    }
}

I'll be trying that out in the not too distant future. 

Some Handy Websites

I currently am using VSCode and started with the boilerplate provided by this tutorial: https://foundry-vtt-community.github.io/wiki/System-Development-Tutorial-Start-to-Finish/ Added the folder to VSCode's workspace and went from there.

Drink Health Potion Source Code

Below is the source of the Drink Health Potion scrapped from github so that I have it in one place. It's here for sure reference, but prettier on github's site

main()
async function main(){
  console.log("Hello World")
  //Is a token selected? If not, error
  console.log("Tokens: ", canvas.tokens.controlled)
  if(canvas.tokens.controlled.length == 0 || canvas.tokens.controlled.length > 1){
    ui.notifications.error("Please select a single token");
    return;
  }
  let actor = canvas.tokens.controlled[0].actor
  //Does the token have a health potion? Otherwise error
  console.log("Actor: ", actor);
  let healthpotion = actor.items.find(item => item.data.name == "Health Potion")
  if(healthpotion == null || healthpotion == undefined){
    ui.notifications.error("No Health Potions left");
    return;
  }
  
  //If token is max health if so, don't do anything
  if(actor.data.data.health.value == actor.data.data.health.max){
    ui.notifications.error("Actor already at max health");
    return;
  }
  //Subtract a health potion
  await healthpotion.update({"data.quantity": healthpotion.data.data.quantity - 1})
  if(healthpotion.data.data.quantity < 1){
    healthpotion.delete();
  }
  
  //Increase token health
  //// New Health is going to be grater the max health
  ////// If so, we want the new health to max 
  let newHealth = actor.data.data.health.value + healthpotion.data.data.attributes.hp_restore.value
  if(newHealth > actor.data.data.health.max){
    newHealth = actor.data.data.health.max
  }
  //update the actor health
  await actor.update({"data.health.value": newHealth});
  ui.notifications.info(`${actor.data.name} drank a health potion`)
}


No comments:

Post a Comment