Skip to content

Commit

Permalink
feat: created the system of item macro drop inspired in DND5e System
Browse files Browse the repository at this point in the history
This system is linked to the `item-card.html` template. Through chat hooks in the system's core file
(`ordemparanormal_fvtt.mjs`), actions are identified through buttons and listeners and the
respective functions are called through a switch.
  • Loading branch information
SouOWendel committed Nov 13, 2023
1 parent 07b5ff0 commit 4ddfeaf
Show file tree
Hide file tree
Showing 2 changed files with 146 additions and 13 deletions.
65 changes: 52 additions & 13 deletions module/documents/item.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -188,10 +188,31 @@ export class OrdemItem extends Item {
* Rely upon the d20Roll logic for the core implementation
*/
async rollAttack(options={}) {
if ( !this.system.formulas.attackFormula.formula ) throw new Error('This Item does not have a formula to roll!');
if ( !this.system.formulas.attackFormula.attr || !this.system.formulas.attackFormula.skill) throw new Error('This Item does not have a formula to roll!');

const attack = this.system.formulas.attackFormula;

const bonus = attack.bonus && '+' + attack.bonus;
let attr = attack.attr;
let skill = attack.skill;
let rollMode = 'kh';

for (const [i, attrParent] of Object.entries(this.parent.system.attributes)) {
console.log(i + attr + attrParent.value);
if (i == attr) {
if (attrParent.value == 0) {
attr = 2;
rollMode = 'kl';
} else attr = attrParent.value;
}
}

for (const [i, skillParent] of Object.entries(this.parent.system.skills)) {
if (i == skill) skill = '+' + skillParent.value;
}

const rollConfig = {
formula: this.system.formulas.attackFormula.formula,
formula: attr + 'd20' + rollMode + skill + bonus,
data: this.getRollData(),
chatMessage: true
};
Expand All @@ -201,7 +222,7 @@ export class OrdemItem extends Item {
* A hook event that fires before a formula is rolled for an Item.
* @function ordemparanormal.preRollFormula
* @memberof hookEvents
* @param {Item5e} item Item for which the roll is being performed.
* @param {OrdemItem} item Item for which the roll is being performed.
* @param {object} config Configuration data for the pending roll.
* @param {string} config.formula Formula that will be rolled.
* @param {object} config.data Data used when evaluating the roll.
Expand All @@ -226,8 +247,8 @@ export class OrdemItem extends Item {
* A hook event that fires after a formula has been rolled for an Item.
* @function ordemparanormal.rollFormula
* @memberof hookEvents
* @param {Item5e} item Item for which the roll was performed.
* @param {Roll} roll The resulting roll.
* @param {OrdemItem} item Item for which the roll was performed.
* @param {Roll} roll The resulting roll.
*/
Hooks.callAll('ordemparanormal.rollFormula', this, roll);

Expand All @@ -240,19 +261,28 @@ export class OrdemItem extends Item {
*/
async rollDamage(options={}) {
if ( !this.system.formulas.damageFormula.formula ) throw new Error('This Item does not have a formula to roll!');


const damage = this.system.formulas.damageFormula;
const bonus = damage.bonus && '+' + damage.bonus;
const formula = damage.formula;
let attr = damage.attr;

for (const [i, attrParent] of Object.entries(this.parent.system.attributes)) {
if (i == attr) attr = '+' + attrParent.value;
}

const rollConfig = {
formula: this.system.formulas.damageFormula.formula,
data: this.getRollData(),
chatMessage: true
formula: formula + attr + bonus,
data: this.getRollData(),
chatMessage: true
};
// if ( spellLevel ) rollConfig.data.item.level = spellLevel;
// if ( spellLevel ) rollConfig.data.item.level = spellLevel;

/**
* A hook event that fires before a formula is rolled for an Item.
* @function ordemparanormal.preRollFormula
* @memberof hookEvents
* @param {Item5e} item Item for which the roll is being performed.
* @param {OrdemItem} item Item for which the roll is being performed.
* @param {object} config Configuration data for the pending roll.
* @param {string} config.formula Formula that will be rolled.
* @param {object} config.data Data used when evaluating the roll.
Expand All @@ -277,14 +307,23 @@ export class OrdemItem extends Item {
* A hook event that fires after a formula has been rolled for an Item.
* @function ordemparanormal.rollFormula
* @memberof hookEvents
* @param {Item5e} item Item for which the roll was performed.
* @param {Roll} roll The resulting roll.
* @param {Ordemitem} item Item for which the roll was performed.
* @param {Roll} roll The resulting roll.
*/
Hooks.callAll('ordemparanormal.rollFormula', this, roll);

return roll;
}

/**
* Trigger an item usage, optionally creating a chat message with followup actions.
**/
async use(config={}, options={}) {
const item = this;
const is = item.system;
const as = item.actor.system;
}

/**
* Handle clickable rolls.
* @param {Event} event The originating click event
Expand Down
94 changes: 94 additions & 0 deletions module/documents/macro.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/**
* Attempt to create a macro from the dropped data. Will use an existing macro if one exists.
* @param {object} dropData The dropped data
* @param {number} slot The hotbar slot to use
*/
export async function createOPMacro(dropData, slot) {
const macroData = { type: 'script', scope: 'actor' };
switch ( dropData.type ) {
case 'Item': {
const itemData = await Item.implementation.fromDropData(dropData);
if ( !itemData ) return ui.notifications.warn(game.i18n.localize('MACRO.opUnownedWarn'));
foundry.utils.mergeObject(macroData, {
name: itemData.name,
img: itemData.img,
command: `ordemparanormal.documents.macro.rollItem('${itemData.name}')`,
flags: {'dnd5e.itemMacro': true}
});
break;
}
case 'ActiveEffect': {
const effectData = await ActiveEffect.implementation.fromDropData(dropData);
if ( !effectData ) return ui.notifications.warn(game.i18n.localize('MACRO.opUnownedWarn'));
foundry.utils.mergeObject(macroData, {
name: effectData.label,
img: effectData.icon,
command: `ordemparanormal.documents.macro.toggleEffect('${effectData.label}')`,
flags: {'dnd5e.effectMacro': true}
});
break;
}
default:
return;
}

// Assign the macro to the hotbar
const macro = game.macros.find(m => {
return (m.name === macroData.name) && (m.command === macroData.command) && m.isAuthor;
}) || await Macro.create(macroData);
game.user.assignHotbarMacro(macro, slot);
}

/* -------------------------------------------- */

/**
* Find a document of the specified name and type on an assigned or selected actor.
* @param {string} name Document name to locate.
* @param {string} documentType Type of embedded document (e.g. "Item" or "ActiveEffect").
* @returns {Document} Document if found, otherwise nothing.
*/
function getMacroTarget(name, documentType) {
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.opNoActorSelected'));

const collection = (documentType === 'Item') ? actor.items : actor.effects;
const nameKeyPath = (documentType === 'Item') ? 'name' : 'label';

// Find item in collection
const documents = collection.filter(i => foundry.utils.getProperty(i, nameKeyPath) === name);
const type = game.i18n.localize(`DOCUMENT.${documentType}`);
if ( documents.length === 0 ) {
return ui.notifications.warn(game.i18n.format('MACRO.opMissingTargetWarn', { actor: actor.name, type, name }));
}
if ( documents.length > 1 ) {
ui.notifications.warn(game.i18n.format('MACRO.opMultipleTargetsWarn', { actor: actor.name, type, name }));
}

return documents[0];
}

/* -------------------------------------------- */

/**
* Trigger an item to roll when a macro is clicked.
* @param {string} itemName Name of the item on the selected actor to trigger.
* @returns {Promise<ChatMessage|object>} Roll result.
*/
export function rollItem(itemName) {
return getMacroTarget(itemName, 'Item');
}

/* -------------------------------------------- */

/**
* Toggle an effect on and off when a macro is clicked.
* @param {string} effectName Name of the effect to be toggled.
* @returns {Promise<ActiveEffect>} The effect after it has been toggled.
*/
export function toggleEffect(effectName) {
const effect = getMacroTarget(effectName, 'ActiveEffect');
return effect?.update({disabled: !effect.disabled});
}

0 comments on commit 4ddfeaf

Please sign in to comment.