Not Updated for Foundry v10
This section of the system development tutorial has not yet been updated for Foundry v10+ versions. While the general concepts are still applicable, it's recommended that you review the equivalent section of the Boilerplate system used in the tutorial for differences (the system itself has been updated for v10).
https://gitlab.com/asacolips-projects/foundry-mods/boilerplate/-/tree/master
First, we need to update your game.mysystemname
object in your init hook. For the Boilerplate System, that looks like this:
game.boilerplate = {
BoilerplateActor,
BoilerplateItem,
rollItemMacro
};
The only change we've made from earlier is that we've added a new line for rollItemMacro
, which we'll define later.
Next we need to create (or modify) your ready hook in the main system JS file for your system. For the Boilerplate System, that's boilerplate.js
Hooks.once("ready", async function() {
// Wait to register hotbar drop hook on ready so that modules could register earlier if they want to
Hooks.on("hotbarDrop", (bar, data, slot) => createBoilerplateMacro(data, slot));
});
Next we'll need to add two new functions to both add the drop behavior and the roll function. Place the following after (not inside) your ready hook. Rename the createBoilerplateMacro
function both here and in the ready hook above to be more appropriate to your system, such as createMySystemNameMacro
/* -------------------------------------------- */
/* Hotbar Macros */
/* -------------------------------------------- */
/**
* Create a Macro from an Item drop.
* Get an existing item macro if one exists, otherwise create a new one.
* @param {Object} data The dropped data
* @param {number} slot The hotbar slot to use
* @returns {Promise}
*/
async function createBoilerplateMacro(data, slot) {
if (data.type !== "Item") return;
if (!("data" in data)) return ui.notifications.warn("You can only create macro buttons for owned Items");
const item = data.data;
// Create the macro command
const command = `game.boilerplate.rollItemMacro("${item.name}");`;
let macro = game.macros.entities.find(m => (m.name === item.name) && (m.command === command));
if (!macro) {
macro = await Macro.create({
name: item.name,
type: "script",
img: item.img,
command: command,
flags: { "boilerplate.itemMacro": true }
});
}
game.user.assignHotbarMacro(macro, slot);
return false;
}
/**
* Create a Macro from an Item drop.
* Get an existing item macro if one exists, otherwise create a new one.
* @param {string} itemName
* @return {Promise}
*/
function rollItemMacro(itemName) {
const speaker = ChatMessage.getSpeaker();
let actor;
if (speaker.token) actor = game.actors.tokens[speaker.token];
if (!actor) actor = game.actors.get(speaker.actor);
const item = actor ? actor.items.find(i => i.name === itemName) : null;
if (!item) return ui.notifications.warn(`Your controlled Actor does not have an item named ${itemName}`);
// Trigger the item roll
return item.roll();
}
The first function is used to create a new macro entity on drop and set its command to game.boilerplate.rollItemMacro(ITEMNAME)
, and the second function is the actual function that will trigger the roll on the item. Rename boilerplate
in that command to whatever your system's namespace is. This namespace is for the same object we created in the first step of this tutorial, so it should match that.
The last line of the rollItemMacro()
function is to call item.roll()
. So let's switch over to your Item
class and add that method.
roll()
to the Item classThe roll()
method is what we're calling when the macro is clicked, so you can put any logic into this. Here's an example from the Boilerplate System, but you should modify this to fit your needs.
/**
* Handle clickable rolls.
* @param {Event} event The originating click event
* @private
*/
async roll() {
// Basic template rendering data
const token = this.actor.token;
const item = this.data;
const actorData = this.actor ? this.actor.data.data : {};
const itemData = item.data;
// Define the roll formula.
let roll = new Roll('[email protected]', actorData);
let label = `Rolling ${item.name}`;
// Roll and send to chat.
roll.roll().toMessage({
speaker: ChatMessage.getSpeaker({ actor: this.actor }),
flavor: label
});
}
We're almost done! The last thing we have to do is make sure your character sheet has the proper drag events to be able to drag to the macrobar.
In your activeListeners()
method, add the following code.
// Drag events for macros.
if (this.actor.owner) {
let handler = ev => this._onDragStart(ev);
// Find all items on the character sheet.
html.find('li.item').each((i, li) => {
// Ignore for the header row.
if (li.classList.contains("item-header")) return;
// Add draggable attribute and dragstart listener.
li.setAttribute("draggable", true);
li.addEventListener("dragstart", handler, false);
});
}
If this is the actor's owner, we're going through all <li>
tags with the .item
class, ignoring those with the .item-header
class. You should update those two selectors to match what your system actually uses if they're different. Once we're inside the loop we add the draggable
attribute and dragstart
event to the list item to make it draggable for macrobar support. This will also allow you to do other things with the drag event, but that's outside of the scope of this part of the tutorial.