ENH: Chat card buttons
							parent
							
								
									07fff9b20a
								
							
						
					
					
						commit
						86bb4333e2
					
				|  | @ -29,6 +29,7 @@ export class OseDice { | |||
|           return details; | ||||
|         } | ||||
|         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") { | ||||
|       // SAVING THROWS
 | ||||
|  | @ -47,7 +48,7 @@ export class OseDice { | |||
|         details = `<div class='roll-result roll-fail'><b>Failure</b> (${sc})</div>`; | ||||
|       } | ||||
|     } else if (data.rollData.type == "Exploration") { | ||||
|       // Exploration Checks
 | ||||
|       // EXPLORATION CHECKS
 | ||||
|       let sc = data.data.exploration[data.rollData.stat]; | ||||
|       if (roll.total <= sc) { | ||||
|         details = `<div class='roll-result roll-success'><b>Success!</b> (${sc})</div>`; | ||||
|  |  | |||
|  | @ -13,6 +13,11 @@ export class OseItem extends Item { | |||
|     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) { | ||||
|     const data = duplicate(this.data.data); | ||||
|      | ||||
|  | @ -53,12 +58,32 @@ export class OseItem extends Item { | |||
|     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 | ||||
|    * @return {Promise} | ||||
|    */ | ||||
|   async roll({ configureDialog = true } = {}) { | ||||
|     console.log(this.data) | ||||
|     if (this.data.type == 'weapon') { | ||||
|       if (this.rollWeapon()) return; | ||||
|     } | ||||
|  | @ -101,4 +126,95 @@ export class OseItem extends Item { | |||
|     // Create the chat message
 | ||||
|     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; | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -62,4 +62,7 @@ Hooks.once("setup", function () { | |||
|       return obj; | ||||
|     }, {}); | ||||
|   } | ||||
| }); | ||||
| }); | ||||
| 
 | ||||
| 
 | ||||
| Hooks.on("renderChatLog", (app, html, data) => OseItem.chatListeners(html)); | ||||
|  | @ -12,7 +12,7 @@ | |||
|                 </div> | ||||
|                 <div class="attribute-bonuses"> | ||||
|                     {{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> | ||||
|                 </div> | ||||
|             </li> | ||||
|  |  | |||
|  | @ -1,11 +1,11 @@ | |||
| <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"> | ||||
|         <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> | ||||
|     </header> | ||||
| 
 | ||||
|     <div class="card-content"> | ||||
|     <div class="card-content" style="display:none;"> | ||||
|         {{{data.description}}} | ||||
|     </div> | ||||
| 
 | ||||
|  | @ -14,8 +14,11 @@ | |||
| 
 | ||||
|         {{#if hasDamage}} | ||||
|         <button data-action="damage"> | ||||
|             {{#if isHealing}}{{ localize "OSE.Healing" }} | ||||
|             {{else}}{{localize "OSE.Damage" }}{{/if}} | ||||
|             {{#if isHealing}} | ||||
|             {{ localize "OSE.Healing" }} | ||||
|             {{else}} | ||||
|             {{localize "OSE.Damage" }} | ||||
|             {{/if}} | ||||
|         </button> | ||||
|         {{/if}} | ||||
| 
 | ||||
|  | @ -25,8 +28,8 @@ | |||
|         </button> | ||||
|         {{/if}} | ||||
| 
 | ||||
|         {{#if data.formula}} | ||||
|         <button data-action="formula">{{ localize "OSE.OtherFormula"}}</button> | ||||
|         {{#if data.roll}} | ||||
|         <button data-action="formula">{{ localize "OSE.Roll"}}</button> | ||||
|         {{/if}} | ||||
|     </div> | ||||
| 
 | ||||
|  | @ -35,4 +38,4 @@ | |||
|         <span>{{this}}</span> | ||||
|         {{/each}} | ||||
|     </footer> | ||||
| </div> | ||||
| </div> | ||||
		Loading…
	
		Reference in New Issue