Skip to content

Commit

Permalink
update for dnd5e v3.0
Browse files Browse the repository at this point in the history
  • Loading branch information
Forien committed Feb 7, 2024
1 parent fd10c72 commit c46f24c
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 87 deletions.
17 changes: 16 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
# Changelog

## v1.X.X
## v1.11
### v1.11.0
#### General
* Changed `item.executeMacro()` method to be async
* Updated how Item Macro is built and called to allow retrieving its value
#### DnD5e
* Switched DnD 5e's support to `dnd5e.preUseItem` Hook
* Because of that, there no longer is distinction between using item from sheet, macro or any other way
* Because of that, using plain `item.use()` in Item Macro is now disabled as it can lead to uncontrolled infinite loops
* Since good practice dictates that all 3rd party modules, including custom sheets, should make use of `item.use()`, this means that all of them should now be supported by default
* `Character Sheet Hook` and `Right Click Override` settings for dnd5e are now removed in favor of single `Override default macro execution`
* Item Macros that you wish to still execute "standard" `item.use()` must now do it in one of two ways:
* Return `true` from macro, or
* Call `item.use({}, {skipItemMacro: true})` instead (passing `skipItemMacro = true` in `options` argument)


## v1.10
### v1.10.5
* Fixed the `Tidy 5e Sheet` stacking issue introduced in previous update

Expand Down
27 changes: 21 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,14 @@ You can execute the macro from the "item" class using the executeMacro(...args)

# Installation

_**Item Macro v1.11.0 onwards will not work with DnD 5e 2.4.**_
_If you do not use the newest version of the DnD 5e system, please do not update Item Macro and install v1.10.5 instead using [this manifest link](https://github.com/Foundry-Workshop/Item-Macro/releases/download/v1.10.5/module.json)._

1. Inside Foundry's Configuration and Setup screen, go to **Add-on Modules**
2. Click "Install Module"
3. Install module using one of the two approaches:
- Search for the Module and install using the Module Manager, or
- In the Manifest URL field paste: `https://github.com/Foundry-Workshop/Item-Macro/releases/latest/download/module.json`.
- Search for the Module and install using the Module Manager, or
- In the Manifest URL field paste: `https://github.com/Foundry-Workshop/Item-Macro/releases/latest/download/module.json`.

# Usage

Expand All @@ -33,13 +36,25 @@ Added context menu support allowing GM users to mass update item-macros on like

## Added Item Functionality

1. Item.hasMacro() => returns boolean on if the item has a macro command
2. Item.getMacro() => returns Macro instance, if the item has a macro command
3. Item.setMacro(Macro) => overwrites and saves given Macro to the Item
4. Item.executeMacro(...args) => executes Macro command, giving `item`, `speaker`, `actor`, `token`, `character`, and `event` constants. This is recognized as the macro itself. Pass an event as the first argument.
1. `Item.hasMacro()` => returns boolean on if the item has a macro command
2. `Item.getMacro()` => returns Macro instance, if the item has a macro command
3. `Item.setMacro(Macro)` => overwrites and saves given Macro to the Item
4. `Item.executeMacro(...args)` => executes Macro command, giving `item`, `speaker`, `actor`, `token`, `character`, and `event` constants. This is recognized as the macro itself. Pass an event as the first argument.

## Added System Functionality

### Dungeons & Dragons Fifth Edition

Starting from v1.11.0, Item Macro changed how it's supporting DnD 5e (3.0.0+). Here is the breakdown:
1. Item Macro now directly listens to `dnd5e.preUseItem` Hook
- Because of that, there no longer is distinction between using item from sheet, macro or any other way
- Because of that, using plain `item.use()` in Item Macro is now disabled as it can lead to uncontrolled infinite loops
- Since good practice dictates that all 3rd party modules, including custom sheets, should make use of `item.use()`, this means that all of them should now be supported by default
2. `Character Sheet Hook` and `Right Click Override` settings for dnd5e are now removed in favor of single `Override default macro execution`
3. Item Macros that you wish to still execute "standard" `item.use()` must now do it in one of two ways:
1. Return `true` from macro, or
2. Call `item.use({}, {skipItemMacro: true})` instead (passing `skipItemMacro = true` in `options` argument)

### Simple Worldbuilding

* Item names on actor sheets have been converted to rollable links that will execute the macro attached to the item when the "Enable Character Sheet Hook" is enabled.
Expand Down
4 changes: 3 additions & 1 deletion lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,7 @@
"error.editImage":"You cannot edit the icon for this macro.",
"error.macroExecution" : "There was an error in your macro syntax. See the console (F12) for details",

"itemacro.dnd5e.v9deprecation": "ITEM MACRO: The hotbar functionality has been disabled for v9."
"itemacro.dnd5e.v9deprecation": "ITEM MACRO: The hotbar functionality has been disabled for v9.",
"itemacro.dnd5e.systemDeprecationv3": "ITEM MACRO: New module versions no longer support DND5e below 3.0.0. Please update your system or install Item Macro v1.10.5.",
"itemacro.dnd5e.useFunctionWarning": "ITEM MACRO: usage of `item.use()` is no longer supported in Item Macros. Return `true` in macro in order to fall back to system's use method."
}
32 changes: 22 additions & 10 deletions scripts/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,18 +51,18 @@ export class helper{
}
}

Item.prototype.executeMacro = function(...args){
Item.prototype.executeMacro = async function(...args){
if(!this.hasMacro()) return;
const type = settings.isV10 ? this.getMacro()?.type : this.getMacro()?.data.type;
switch(type){
case "chat" :
//left open if chat macros ever become a thing you would want to do inside an item?
break;
case "script" :
return this._executeScript(...args);
return await this._executeScript(...args);
}
}
Item.prototype._executeScript = function(...args){
Item.prototype._executeScript = async function(...args){
//add variable to the evaluation of the script
const item = this;
const macro = item.getMacro();
Expand All @@ -76,18 +76,20 @@ export class helper{

logger.debug("Item | _executeScript | ", {macro, speaker, actor, token, character, item, event, args});

if (helper.systemValidation(macro) === false)
return;

//build script execution
const body = `(async ()=>{
${ macro.command ?? macro?.data?.command }
})();`;
const fn = Function("item", "speaker", "actor", "token", "character", "event", "args", body);
const scriptFunction = Object.getPrototypeOf(async function () {}).constructor;
const body = macro.command ?? macro?.data?.command;
const fn = new scriptFunction("item", "speaker", "actor", "token", "character", "event", "args", body)

logger.debug("Item | _executeScript | ", { body, fn });

//attempt script execution
try {
fn.call(macro, item, speaker, actor, token, character, event, args);
}catch(err){
return await fn(macro, item, speaker, actor, token, character, event, args);
} catch (err) {
ui.notifications.error(settings.i18n("error.macroExecution"));
logger.error(err);
}
Expand Down Expand Up @@ -196,7 +198,7 @@ export class helper{
static getSheetHooks() {
switch (game.system.id) {
case "dnd5e" :
if (settings.value("charsheet")) return dnd5e.sheetHooks();
if (settings.value("defaultmacro")) return dnd5e.sheetHooks();
break;
case "sfrpg" :
if(settings.value("charsheet")) return sfrpg.sheetHooks();
Expand Down Expand Up @@ -306,6 +308,16 @@ export class helper{
return new Promise((resolve)=> setTimeout(resolve, ms))
}

static systemValidation(macro) {
switch (game.system.id) {
case 'dnd5e':
return dnd5e.systemValidation(macro);
default:
}

return true;
}

static async postMessage() {
if (settings.value('welcome') === false) {
await ChatMessage.create({
Expand Down
4 changes: 4 additions & 0 deletions scripts/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ export class settings{
}
};

if (game.system.id === 'dnd5e') {
delete settingData.charsheet;
delete settingData.click;
}

Object.entries(settingData).forEach(([key, data])=> {
game.settings.register(
Expand Down
109 changes: 40 additions & 69 deletions scripts/systems/dnd5e.js
Original file line number Diff line number Diff line change
@@ -1,79 +1,50 @@
import { logger } from "../logger.js";
import { settings } from "../settings.js";
import {logger} from "../logger.js";
import {settings} from "../settings.js";

export function register_helper(){
logger.info(`Registering DND5E Helpers`);

/*
Override
*/
const itemMacroUseItem = function(name) {
let actor;
const speaker = ChatMessage.getSpeaker();
if ( speaker.token ) actor = game.actors.tokens[speaker.token];
actor ??= game.actors.get(speaker.actor);
if ( !actor ) return ui.notifications.warn(game.i18n.localize("MACRO.5eNoActorSelected"));
function preUseItem(item, config, options) {
if (options.skipItemMacro === true)
return true;

const collection = actor.items;
const nameKeyPath = "name";
if (!item.hasMacro())
return true;

// Find the item in collection
const documents = collection.filter(i => foundry.utils.getProperty(i, nameKeyPath) === name);
const type = game.i18n.localize(`DOCUMENT.Item`);
if ( documents.length === 0 ) {
return ui.notifications.warn(game.i18n.format("MACRO.5eMissingTargetWarn", { actor: actor.name, type, name }));
}
if ( documents.length > 1 ) {
ui.notifications.warn(game.i18n.format("MACRO.5eMultipleTargetsWarn", { actor: actor.name, type, name }));
}
if (!settings.value("defaultmacro"))
return true;

const item = documents[0];
// Trigger the item usage
if ( item.hasMacro() && settings.value("defaultmacro") ) {
return item.executeMacro();
}
return item.use();
item.executeMacro({config, options}).then((result) => {
if (result === true) {
options.skipItemMacro = true;
item.use(config, options);
}
});

if ( settings.isV10 ) {
dnd5e.documents = { ...dnd5e.documents };
dnd5e.documents.macro = { ...dnd5e.documents.macro };
dnd5e.documents.macro.rollItem = itemMacroUseItem;
}
else {
console.log(game.i18n.localize("itemacro.dnd5e.v9deprecation"));
}
return false;
}

export function register_helper() {
logger.info(`Registering DND5E Helpers`);

if (foundry.utils.isNewerVersion("3.0.0", game.system.version))
return ui.notifications.warn("itemacro.dnd5e.systemDeprecationv3", {localize: true, permanent: true});


Hooks.on("dnd5e.preUseItem", preUseItem)
}

export function systemValidation(macro) {
if (macro.command?.includes('.use()')) {
ui.notifications.warn("itemacro.dnd5e.useFunctionWarning", {localize: true, permanent: true});
return false;
}

return true;
}

export function sheetHooks()
{
const renderSheets = {
//Core
ActorSheet5eCharacter: ".item .item-image",
ActorSheet5eVehicle: ".item .item-image",
ActorSheet5eNPC: ".item .item-image",
//BetterNPC
BetterNPCActor5eSheet: ".item .rollable",
BetterNPCActor5eSheetDark: ".item .rollable",
ActorSheet5eCharacterDark: ".item .item-image",
//DarkSheet
DarkSheet: ".item .item-image",
ActorNPC5EDark: ".item .item-image",
DynamicActorSheet5e: ".item .item-image",
//DNDBeyond
DNDBeyondCharacterSheet5e: ".item .item-name .item-image",
//Tidy
//Tidy5eSheet: ".item .item-image",
//Tidy5eNPC: ".item .item-image",
//Monster Blocks
MonsterBlock5e: ".item .item-name",
};
const renderedSheets = {
Alt5eSheet : ".item .item-image",
Tidy5eSheet : ".item .item-image",
};
export function sheetHooks() {
const renderSheets = {};
const renderedSheets = {};

return { render : renderSheets, rendered : renderedSheets };
return {render: renderSheets, rendered: renderedSheets};
}

/**
Expand All @@ -89,7 +60,7 @@ export function applyTidy5eCompatibility() {
!settings.value("click") &&
item.hasMacro();
if (shouldExecuteMacro) {
item.executeMacro();
item.executeMacro({config, options});
return false;
}
});
Expand All @@ -103,7 +74,7 @@ export function applyTidy5eCompatibility() {
settings.value("click") &&
item.hasMacro();
if (shouldExecuteMacro) {
item.executeMacro();
item.executeMacro({options});
options.event.preventDefault();
options.event.stopPropagation();
}
Expand Down

0 comments on commit c46f24c

Please sign in to comment.