ENH: Chat card buttons

master
U~man 2020-07-04 13:41:17 +02:00
parent 07fff9b20a
commit 86bb4333e2
5 changed files with 135 additions and 12 deletions

View File

@ -29,6 +29,7 @@ export class OseDice {
return details; return details;
} }
details = `<div class='roll-result'><b>Hits AC ${Math.clamped(thac - roll.total,-3,9)}</b> (${thac})</div>`; details = `<div class='roll-result'><b>Hits AC ${Math.clamped(thac - roll.total,-3,9)}</b> (${thac})</div>`;
// ADD DAMAGE ROLL
} }
} else if (data.rollData.type == "Save") { } else if (data.rollData.type == "Save") {
// SAVING THROWS // SAVING THROWS
@ -47,7 +48,7 @@ export class OseDice {
details = `<div class='roll-result roll-fail'><b>Failure</b> (${sc})</div>`; details = `<div class='roll-result roll-fail'><b>Failure</b> (${sc})</div>`;
} }
} else if (data.rollData.type == "Exploration") { } else if (data.rollData.type == "Exploration") {
// Exploration Checks // EXPLORATION CHECKS
let sc = data.data.exploration[data.rollData.stat]; let sc = data.data.exploration[data.rollData.stat];
if (roll.total <= sc) { if (roll.total <= sc) {
details = `<div class='roll-result roll-success'><b>Success!</b> (${sc})</div>`; details = `<div class='roll-result roll-success'><b>Success!</b> (${sc})</div>`;

View File

@ -13,6 +13,11 @@ export class OseItem extends Item {
super.prepareData(); super.prepareData();
} }
static chatListeners(html) {
html.on('click', '.card-buttons button', this._onChatCardAction.bind(this));
html.on('click', '.item-name', this._onChatCardToggleContent.bind(this));
}
getChatData(htmlOptions) { getChatData(htmlOptions) {
const data = duplicate(this.data.data); const data = duplicate(this.data.data);
@ -53,12 +58,32 @@ export class OseItem extends Item {
return false; return false;
} }
async rollFormula(options={}) {
if ( !this.data.data.roll ) {
throw new Error("This Item does not have a formula to roll!");
}
// Define Roll Data
const rollData = {
item: this.data.data
};
const title = `${this.name} - Roll`;
// Invoke the roll and submit it to chat
const roll = new Roll(rollData.item.roll, rollData).roll();
roll.toMessage({
speaker: ChatMessage.getSpeaker({actor: this.actor}),
flavor: this.data.data.chatFlavor || title,
rollMode: game.settings.get("core", "rollMode")
});
return roll;
}
/** /**
* Roll the item to Chat, creating a chat card which contains follow up attack or damage roll options * Roll the item to Chat, creating a chat card which contains follow up attack or damage roll options
* @return {Promise} * @return {Promise}
*/ */
async roll({ configureDialog = true } = {}) { async roll({ configureDialog = true } = {}) {
console.log(this.data)
if (this.data.type == 'weapon') { if (this.data.type == 'weapon') {
if (this.rollWeapon()) return; if (this.rollWeapon()) return;
} }
@ -101,4 +126,95 @@ export class OseItem extends Item {
// Create the chat message // Create the chat message
return ChatMessage.create(chatData); return ChatMessage.create(chatData);
} }
/**
* Handle toggling the visibility of chat card content when the name is clicked
* @param {Event} event The originating click event
* @private
*/
static _onChatCardToggleContent(event) {
event.preventDefault();
const header = event.currentTarget;
const card = header.closest(".chat-card");
const content = card.querySelector(".card-content");
content.style.display = content.style.display === "none" ? "block" : "none";
}
static async _onChatCardAction(event) {
event.preventDefault();
// Extract card data
const button = event.currentTarget;
button.disabled = true;
const card = button.closest(".chat-card");
const messageId = card.closest(".message").dataset.messageId;
const message = game.messages.get(messageId);
const action = button.dataset.action;
// Validate permission to proceed with the roll
const isTargetted = action === "save";
if ( !( isTargetted || game.user.isGM || message.isAuthor ) ) return;
// Get the Actor from a synthetic Token
const actor = this._getChatCardActor(card);
if ( !actor ) return;
// Get the Item
const item = actor.getOwnedItem(card.dataset.itemId);
if ( !item ) {
return ui.notifications.error(`The requested item ${card.dataset.itemId} no longer exists on Actor ${actor.name}`)
}
// Get card targets
let targets = [];
if ( isTargetted ) {
targets = this._getChatCardTargets(card);
if ( !targets.length ) {
ui.notifications.warn(`You must have one or more controlled Tokens in order to use this option.`);
return button.disabled = false;
}
}
// Attack and Damage Rolls
else if ( action === "damage" ) await item.rollDamage({event});
else if ( action === "formula" ) await item.rollFormula({event});
// Saving Throws for card targets
else if ( action === "save" ) {
for ( let t of targets ) {
await t.rollAbilitySave(button.dataset.ability, {event});
}
}
// Re-enable the button
button.disabled = false;
}
static _getChatCardActor(card) {
// Case 1 - a synthetic actor from a Token
const tokenKey = card.dataset.tokenId;
if (tokenKey) {
const [sceneId, tokenId] = tokenKey.split(".");
const scene = game.scenes.get(sceneId);
if (!scene) return null;
const tokenData = scene.getEmbeddedEntity("Token", tokenId);
if (!tokenData) return null;
const token = new Token(tokenData);
return token.actor;
}
// Case 2 - use Actor ID directory
const actorId = card.dataset.actorId;
return game.actors.get(actorId) || null;
}
static _getChatCardTargets(card) {
const character = game.user.character;
const controlled = canvas.tokens.controlled;
const targets = controlled.reduce((arr, t) => t.actor ? arr.concat([t.actor]) : arr, []);
if ( character && (controlled.length === 0) ) targets.push(character);
return targets;
}
} }

View File

@ -62,4 +62,7 @@ Hooks.once("setup", function () {
return obj; return obj;
}, {}); }, {});
} }
}); });
Hooks.on("renderChatLog", (app, html, data) => OseItem.chatListeners(html));

View File

@ -12,7 +12,7 @@
</div> </div>
<div class="attribute-bonuses"> <div class="attribute-bonuses">
{{localize 'OSE.Melee'}} ({{mods.str}})<br/> {{localize 'OSE.Melee'}} ({{mods.str}})<br/>
{{localize 'OSE.exploration.OpenDoor.long'}} {{localize 'OSE.exploration.od.long'}}
<span class="attribute-mod"><a><i class="fas fa-minus"></i></a></span> <span class="attribute-mod"><a><i class="fas fa-minus"></i></a></span>
</div> </div>
</li> </li>

View File

@ -1,11 +1,11 @@
<div class="ose chat-card item-card" data-actor-id="{{actor._id}}" data-item-id="{{item._id}}" <div class="ose chat-card item-card" data-actor-id="{{actor._id}}" data-item-id="{{item._id}}"
{{#if tokenId}}data-token-id="{{tokenId}}"{{/if}}> {{#if tokenId}}data-token-id="{{tokenId}}" {{/if}}>
<header class="card-header flexrow"> <header class="card-header flexrow">
<img src="{{item.img}}" title="{{item.name}}" width="36" height="36"/> <img src="{{item.img}}" title="{{item.name}}" width="36" height="36" />
<h3 class="item-name">{{item.name}}</h3> <h3 class="item-name">{{item.name}}</h3>
</header> </header>
<div class="card-content"> <div class="card-content" style="display:none;">
{{{data.description}}} {{{data.description}}}
</div> </div>
@ -14,8 +14,11 @@
{{#if hasDamage}} {{#if hasDamage}}
<button data-action="damage"> <button data-action="damage">
{{#if isHealing}}{{ localize "OSE.Healing" }} {{#if isHealing}}
{{else}}{{localize "OSE.Damage" }}{{/if}} {{ localize "OSE.Healing" }}
{{else}}
{{localize "OSE.Damage" }}
{{/if}}
</button> </button>
{{/if}} {{/if}}
@ -25,8 +28,8 @@
</button> </button>
{{/if}} {{/if}}
{{#if data.formula}} {{#if data.roll}}
<button data-action="formula">{{ localize "OSE.OtherFormula"}}</button> <button data-action="formula">{{ localize "OSE.Roll"}}</button>
{{/if}} {{/if}}
</div> </div>
@ -35,4 +38,4 @@
<span>{{this}}</span> <span>{{this}}</span>
{{/each}} {{/each}}
</footer> </footer>
</div> </div>