Skip to content

Helpful Foundry Macros

laialex501 edited this page Nov 30, 2020 · 4 revisions
  • Create Critical Injury Roll Table from Critical Injury Items (non-compendium Critical Injury Items with valid min/max values)
let critdamagevalues = game.items.filter(item => item.data.type === "criticalinjury");

let sorted = critdamagevalues.sort((a,b) => { return a.data.data.min < b.data.data.min ? -1 : a.data.data.min > b.data.data.min ? 1 : 0});

let rollresults = sorted.map(item => { return { type: 1, img: item.data.img, collection: "Item", weight: 1, range: [item.data.data.min, item.data.data.max], resultId: item.data._id, text: item.data.name }});

RollTable.create({ name: "Critical Injuries", results: rollresults, formula: "1d100" })
  • Create Critical Damage Roll Table from Critical Damage Items (non-compendium Critical Damage Items with valid min/max values)
let critdamagevalues = game.items.filter(item => item.data.type === "criticaldamage");

let sorted = critdamagevalues.sort((a,b) => { return a.data.data.min < b.data.data.min ? -1 : a.data.data.min > b.data.data.min ? 1 : 0});

let rollresults = sorted.map(item => { return { type: 1, img: item.data.img, collection: "Item", weight: 1, range: [item.data.data.min, item.data.data.max], resultId: item.data._id, text: item.data.name }});

RollTable.create({ name: "Critical Damage", results: rollresults, formula: "1d100" })
  • Roll Critical Hit From Existing Roll Table
const tables = game.tables.map(table => {
  return `<option value="${table.data._id}">${table.data.name}</option>`
})
let d = new Dialog({
  title: "Critical Roll",
  content: `<p>Select table and modifier</p>
    <div class="grid grid-2col">
      <div>Modifier: 
        <input name="modifier" class="modifier" style="width:50%" type="text" placeholder="0" data-dtype="String" />
      </div>
      <div>
        Table: <select class="crittable">${tables.join("")}</select>
      </div>
    </div>`,
  buttons: {
    one: {
      icon: '<i class="fas fa-check"></i>',
      label: "Roll Critical",
      callback: (html) => {
        let modifier;
        modifier = parseInt(html.find(".modifier").val(), 10);
        if(isNaN(modifier)) {
          modifier = 0;
        }
        const table = html.find(".crittable :selected").val();
        const critRoll = new Roll(`1d100 + ${modifier}`);
        const tableResult= game.tables.get(table).draw({roll: critRoll, displayChat: true});
      }
    },
    two: {
      icon: '<i class="fas fa-times"></i>',
      label: "Cancel",
      callback: () => console.log("Chose Two")
    }
  },
  default: "two",
  close: () => console.log("This always is logged no matter which option is chosen")
});
d.render(true);
  • RollItem code, but with some tweaks to automatically apply a boost die for aiming, a couple of ability upgrades for True Aim ranks, and a damage bonus for his Deadly Accuracy talent (which increases damage based on Ranged: Heavy ranks) (NOTE: It's worth noting though that the ability upgrades and the +1 boost are not dynamic in any way unlike the damage modifier, so there is a need to update the macro whenever ranks change (or to remove the aiming boost bonus which is situational)
const actor = game.actors.get("x8vKm1uIO4U1xp7q");
const actorSheet = actor.sheet.getData();

const item = actor.getOwnedItem("Kj5b3Fq9aI0S2GXv").data;

// ADD DAMAGE DUE TO DEADLY ACCURACY FROM RANGED HEAVY RANKS
item.data.damage.adjusted += actor.data.data.skills["Ranged: Heavy"].rank;

const skill = actor.data.data.skills[item.data.skill.value];
const characteristic = actor.data.data.characteristics[skill.characteristic];

const dicePool = new DicePoolFFG({
  ability: Math.max(characteristic.value, skill.rank),
  boost: skill.boost + 1,
  setback: skill.setback,
  force: skill.force,
  difficulty: 2, // default to average difficulty
});

dicePool.upgrade(Math.min(characteristic.value, skill.rank));

// THIS SHOULD APPLY THE 2 TRUE AIM UPGRADES
dicePool.upgrade(2);

game.ffg.DiceHelpers.displayRollDialog(actorSheet, dicePool, `${game.i18n.localize("SWFFG.Rolling")} ${skill.label}`, skill.label, item);
  • A modified version of the above macro that uses names instead of id's and does not create permanent changes to items when adjusting damage because of talents.
// Set config values
const actor_name = "<Actor_Name_Here>"; // Must exactly match actor name
const item_name = "<Item_Name_Here>"; // Must exactly match item name
const skill_to_use = "Ranged: Heavy";
const display_name = "Light Repeating Blaster"; // Can be anything
const aims = 2; 

// Acquire actor
const actor = game.actors.getName(actor_name);
const actorSheet = actor.sheet.getData();

// Acquire a copy of the item to allow temporary changes
let item = actor.data.items.find(n => n.name === item_name);
item = JSON.parse(JSON.stringify(item));

// Acquire skill and characteristic
const skill = actor.data.data.skills[skill_to_use];
const characteristic = actor.data.data.characteristics[skill.characteristic];

// Construct dice pool
const dicePool = new DicePoolFFG({
  ability: Math.max(characteristic.value, skill.rank),
  boost: skill.boost+aims,
  setback: skill.setback,
  force: skill.force,
  difficulty: 2, // default to average difficulty
});

// Use skill and characteristic to create dice pool
dicePool.upgrade(Math.min(characteristic.value, skill.rank));

// Check for talents that modify the roll or result
const talents = actorSheet.data.talentList;
talents.forEach((talent) => {
   if (talent.name === "True Aim") {
       // Upgrade once for every rank in true aim
      dicePool.upgrade(talent.rank);
   }
   if (talent.name === "Deadly Accuracy") {
       // Add damage from deadly accuracy due to ranged: heavy ranks
       item.data.damage.adjusted += actor.data.data.skills["Ranged: Heavy"].rank;
   }
});

// Display roll dialogue using given attack_name and item
game.ffg.DiceHelpers.displayRollDialog(actorSheet, dicePool, `${game.i18n.localize("SWFFG.Rolling")} ${display_name}`, display_name, item);
  • An advanced macro for playing audio upon clicking "Roll" in a roll dialog. Can be used to add sound effects to dice rolls. Extends the previous macro that uses names over id's and avoids unintended modifications to the item.
// Custom roll dialog that allows for audio
async function displayRollDialogWithAudio(data, dicePool, description, skillName, item, audio) {
    const id = randomID();
    
    const dicesymbols = {
      advantage: "<span class='dietype starwars advantage'>a</span>",
      success: "<span class='dietype starwars failure'>f</span>",
      threat: "<span class='dietype starwars success'>s</span>",
      failure: "<span class='dietype starwars threat'>t</span>",
    };
    
    const content = await renderTemplate("systems/starwarsffg/templates/roll-options.html", {
      dicePool,
      id,
      dicesymbols
    });

    new Dialog(
      {
        title: description || game.i18n.localize("SWFFG.RollingDefaultTitle"),
        content,
        buttons: {
          one: {
            icon: '<i class="fas fa-check"></i>',
            label: game.i18n.localize("SWFFG.ButtonRoll"),
            callback: () => {
              const container = document.getElementById(id);
              const finalPool = DicePoolFFG.fromContainer(container);

              const roll = new game.ffg.RollFFG(finalPool.renderDiceExpression(), item, finalPool);
              roll.toMessage({
                user: game.user._id,
                speaker: data,
                flavor: `${game.i18n.localize("SWFFG.Rolling")} ${skillName}...`,
              });
              AudioHelper.play(audio)
            },
          },
          two: {
            icon: '<i class="fas fa-times"></i>',
            label: game.i18n.localize("SWFFG.Cancel"),
          },
        },
      },
      {
        classes: ["dialog", "starwarsffg"],
      }
    ).render(true);
}

// Set config values
const actor_name = "<Actor_Name_Here>"; // Must exactly match actor name
const item_name = "<Item_Name_Here>"; // Must exactly match item name
const skill_to_use = "Ranged: Heavy";
const display_name = "Light Repeating Blaster"; // Can be anything
const aims = 2; 

// src must denote the filepath from foundrydata/data, volume is a value between 0 and 1, autoplay is true/false, loop is true/false
const audio = {src: "audio/Sound%20Effects/Melee/knife_tr_secondary_impact_flesh.mp3", volume: 0.4, autoplay: true, loop: false}

// Acquire actor
const actor = game.actors.getName(actor_name);
const actorSheet = actor.sheet.getData();

// Create copy of item so that changes are only temporary
const item = JSON.parse(JSON.stringify(actor.data.items.find(n => n.name === item_name)));

// Acquire skill and characteristic
const skill = actor.data.data.skills[skill_to_use];
const characteristic = actor.data.data.characteristics[skill.characteristic];

// Construct dice pool
const dicePool = new DicePoolFFG({
  ability: Math.max(characteristic.value, skill.rank),
  boost: skill.boost,
  setback: skill.setback,
  force: skill.force,
  advantage: 0, // default to zero automatic advantages
  difficulty: 2, // default to average difficulty
});

// Use skill and characteristic to create initial pool
dicePool.upgrade(Math.min(characteristic.value, skill.rank));

// Check for talents that modify the roll or result
const talents = actorSheet.data.talentList;
talents.forEach((talent) => {
   if (talent.name === "True Aim") {
       // Upgrade once for every rank in true aim
      dicePool.upgrade(talent.rank);
   }
   if (talent.name === "Deadly Accuracy") {
       // Add damage from deadly accuracy due to ranged: heavy ranks
       item.data.damage.adjusted += actor.data.data.skills["Ranged: Heavy"].rank;
   }
});

// Display roll dialogue using given attack_name and item
displayRollDialogWithAudio(actorSheet, dicePool, `${game.i18n.localize("SWFFG.Rolling")} ${display_name}`, display_name, item, audio);