diff --git a/lang/en.json b/lang/en.json index 39f808e17c..b61a261d97 100644 --- a/lang/en.json +++ b/lang/en.json @@ -2119,6 +2119,13 @@ "SETTINGS.5eAutoCollapseCardN": "Collapse Item Cards in Chat", "SETTINGS.5eAutoSpellTemplateL": "When a spell is cast, defaults to begin the process to create the corresponding Measured Template if any (requires TRUSTED or higher player role)", "SETTINGS.5eAutoSpellTemplateN": "Always place Spell Template", +"SETTINGS.5eAttackRollVisibility": { + "Name": "Attack Result Visibility", + "Hint": "Control visibility of attack roll results in chat cards for players.", + "All": "Show results & target ACs", + "HideAC": "Show only results", + "None": "Hide all" +}, "SETTINGS.5eChallengeVisibility": { "Name": "Challenge Visibility", "Hint": "Control what roll DCs are visible to the players and whether successes/failures are highlighted.", diff --git a/module/documents/chat-message.mjs b/module/documents/chat-message.mjs index e17b27b554..93036ad32a 100644 --- a/module/documents/chat-message.mjs +++ b/module/documents/chat-message.mjs @@ -172,6 +172,7 @@ export default class ChatMessage5e extends ChatMessage { if ( !this.isContentVisible || !this.rolls.length ) return; const originatingMessage = game.messages.get(this.getFlag("dnd5e", "originatingMessage")) ?? this; const displayChallenge = originatingMessage?.shouldDisplayChallenge; + const displayAttackResult = game.user.isGM || (game.settings.get("dnd5e", "attackRollVisibility") !== "none"); /** * Create an icon to indicate success or failure. @@ -202,7 +203,9 @@ export default class ChatMessage5e extends ChatMessage { if ( !total ) continue; // Only attack rolls and death saves can crit or fumble. const canCrit = ["attack", "death"].includes(this.getFlag("dnd5e", "roll.type")); - if ( d.options.target && displayChallenge ) { + const isAttack = this.getFlag("dnd5e", "roll.type") === "attack"; + const showResult = isAttack ? displayAttackResult : displayChallenge; + if ( d.options.target && showResult ) { if ( d20Roll.total >= d.options.target ) total.classList.add("success"); else total.classList.add("failure"); } @@ -359,7 +362,9 @@ export default class ChatMessage5e extends ChatMessage { _enrichAttackTargets(html) { const attackRoll = this.rolls[0]; const targets = this.getFlag("dnd5e", "targets"); - if ( !game.user.isGM || !(attackRoll instanceof dnd5e.dice.D20Roll) || !targets?.length ) return; + const visibility = game.settings.get("dnd5e", "attackRollVisibility"); + const isVisible = game.user.isGM || (visibility !== "none"); + if ( !isVisible || !(attackRoll instanceof dnd5e.dice.D20Roll) || !targets?.length ) return; const tray = document.createElement("div"); tray.classList.add("dnd5e2"); tray.innerHTML = ` @@ -376,15 +381,18 @@ export default class ChatMessage5e extends ChatMessage { `; const evaluation = tray.querySelector("ul"); evaluation.innerHTML = targets.map(({ name, ac, uuid }) => { + if ( !game.user.isGM && (visibility !== "all") ) ac = ""; const isMiss = !attackRoll.isCritical && ((attackRoll.total < ac) || attackRoll.isFumble); return [`