From 96059dc8453cd8a85511a67a2024f689d75b9845 Mon Sep 17 00:00:00 2001 From: sasquach45932 Date: Wed, 25 Mar 2026 20:13:01 +0100 Subject: [PATCH 01/13] Combat Tracker Fix (TurnOrder switch) --- src/module/actor/sheets/base-actor-sheet.js | 1 + src/module/combat/combat-tracker.js | 15 ++++++++++++--- src/module/combat/combat.js | 18 ++++++++++++++++++ src/styles/v2/_sheets.scss | 14 ++++++++++++++ .../actor/parts/character-sheet-sidemenu.hbs | 11 ++++++++--- 5 files changed, 53 insertions(+), 6 deletions(-) diff --git a/src/module/actor/sheets/base-actor-sheet.js b/src/module/actor/sheets/base-actor-sheet.js index 0ae3136f..2812f155 100644 --- a/src/module/actor/sheets/base-actor-sheet.js +++ b/src/module/actor/sheets/base-actor-sheet.js @@ -134,6 +134,7 @@ export default class DLBaseActorSheet extends HandlebarsApplicationMixin(ActorSh context.hideTurnMode = game.settings.get('demonlord', 'optionalRuleInitiativeMode') === 's' ? false : true context.hideFortune = game.settings.get('demonlord', 'fortuneHide') ? true : false context.isTraitMode2025 = game.settings.get('demonlord', 'optionalRuleTraitMode2025') + context.disableTurnSwitch = (game.combat?.turn === null) || game.user.isGM ? false : true //context.tabs = this._getTabs(options.parts) context.tabs = this._prepareTabs('primary') diff --git a/src/module/combat/combat-tracker.js b/src/module/combat/combat-tracker.js index 06a0957b..158b1307 100644 --- a/src/module/combat/combat-tracker.js +++ b/src/module/combat/combat-tracker.js @@ -238,6 +238,14 @@ calculateEncounterDifficulty(combatants) { } } + const trackerHeader = html.querySelector(".combat-tracker-header") + if (this.initiativeMethod === 's') { + if (game.combat?.turn === null) + trackerHeader.innerHTML = trackerHeader.innerHTML + `
${game.i18n.localize('DL.TurnChooseTurn')}
` + else + trackerHeader.innerHTML = trackerHeader.innerHTML + `
 
` + } + html.querySelectorAll('.combatant')?.forEach(el => { // For each combatant in the tracker, change the initiative selector const combId = el.getAttribute('data-combatant-id') @@ -246,6 +254,8 @@ calculateEncounterDifficulty(combatants) { const multipleCombatants = game.combat.getCombatantsByToken(combatant.token) + const title = (game.user.isGM || combatant.actor.isOwner) && game.combat?.turn === null ? i18n('DL.TurnChangeTurn') : '' + const style = (game.user.isGM || combatant.actor.isOwner) && game.combat?.turn === null ? 'font-weight: bold; cursor: pointer;' : 'font-weight: normal; cursor: auto; opacity: 0.5;' if (combatant.actor?.system.fastAndSlowTurn && multipleCombatants.length == 2) { // The combatant has a double initiative, so we display "Fast" and "Slow" @@ -261,7 +271,7 @@ calculateEncounterDifficulty(combatants) { // Change initiative by clicking on the name if (this.initiativeMethod === 's') el.getElementsByClassName('token-initiative')[0].innerHTML = - `${init}` + `${init}` } if (this.initiativeMethod === 'h' && game.user.isGM) @@ -346,8 +356,7 @@ calculateEncounterDifficulty(combatants) { const combId = li.dataset.combatantId const combatant = combatants.get(combId) if (!combatant) return - - if (game.user.isGM || combatant.actor.isOwner) { + if (game.user.isGM || (combatant.actor.isOwner && game.combat?.turn === null)) { await combatant.actor.update({'system.fastturn': !combatant.actor.system.fastturn}) const initChatMessage = await createInitChatMessage(combatant, {}) if (initChatMessage) ChatMessage.create(initChatMessage) diff --git a/src/module/combat/combat.js b/src/module/combat/combat.js index c9a2147b..46c4c575 100644 --- a/src/module/combat/combat.js +++ b/src/module/combat/combat.js @@ -256,10 +256,26 @@ async rollInitiativeGroup(ids, { formula = null, updateTurn = true, messageOptio }) } + async allowTurnOrderChangeInTurns(_updated) { + if (game.settings.get('demonlord', 'optionalRuleInitiativeMode') !== 's') return + if (game.combat.getFlag('demonlord', 'allowTurnOrderChange') == undefined && _updated.current.turn === 0) + { + await game.combat.update({turn : null}) + game.combat.setFlag('demonlord', 'allowTurnOrderChange', true) + } else if (_updated.current.turn > 0) game.combat.unsetFlag('demonlord', 'allowTurnOrderChange') + } + + async allowTurnOrderChangeInRounds() { + if (game.settings.get('demonlord', 'optionalRuleInitiativeMode') !== 's') return + await game.combat.update({turn : null}) + game.combat.setFlag('demonlord', 'allowTurnOrderChange', true) + } + /** @override */ async nextTurn() { const _updatedTurn = await super.nextTurn() await this._handleTurnEffects() + await this.allowTurnOrderChangeInTurns(_updatedTurn) return _updatedTurn } @@ -267,6 +283,7 @@ async rollInitiativeGroup(ids, { formula = null, updateTurn = true, messageOptio async previousTurn() { const _updatedTurn = await super.previousTurn() await this._handleTurnEffects() + await this.allowTurnOrderChangeInTurns(_updatedTurn) return _updatedTurn } @@ -282,6 +299,7 @@ async rollInitiativeGroup(ids, { formula = null, updateTurn = true, messageOptio } const _updatedRound = await super.nextRound() await this._handleTurnEffects() + await this.allowTurnOrderChangeInRounds() return _updatedRound } diff --git a/src/styles/v2/_sheets.scss b/src/styles/v2/_sheets.scss index d7e111d2..29f89c80 100644 --- a/src/styles/v2/_sheets.scss +++ b/src/styles/v2/_sheets.scss @@ -951,6 +951,13 @@ span.text-vs { &.off { opacity: 1; } + &.on_disabled { + opacity: 0; + } + + &.off_disabled { + opacity: 0.5; + } } .toggleInput:checked ~ .toggleText { @@ -961,5 +968,12 @@ span.text-vs { &.off { opacity: 0; } + &.on_disabled { + opacity: 0.5; + } + + &.off_disabled { + opacity: 0; + } } } diff --git a/src/templates/actor/parts/character-sheet-sidemenu.hbs b/src/templates/actor/parts/character-sheet-sidemenu.hbs index cab9e270..844714c8 100644 --- a/src/templates/actor/parts/character-sheet-sidemenu.hbs +++ b/src/templates/actor/parts/character-sheet-sidemenu.hbs @@ -156,10 +156,15 @@
From f0b19869f6adfc9016c03f40baed5f211213113a Mon Sep 17 00:00:00 2001 From: sasquach45932 Date: Wed, 25 Mar 2026 21:11:42 +0100 Subject: [PATCH 02/13] ChatCard tooltip CSS fix --- src/styles/components/_chat.scss | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/styles/components/_chat.scss b/src/styles/components/_chat.scss index 8ff5755d..e034e825 100644 --- a/src/styles/components/_chat.scss +++ b/src/styles/components/_chat.scss @@ -171,7 +171,7 @@ .tooltiptext { visibility: hidden; width: 260px; - background-color: rgba(0, 0, 0, 0.9); + background-color: rgba(0, 0, 0, 0.85); color: #fff; text-align: left; border: 1px solid rgba(0, 0, 0, 0.5); @@ -180,7 +180,7 @@ position: absolute; z-index: 1; left: 7px; - top: 24px; + top: -7px; } } @@ -243,7 +243,7 @@ .tooltiptext { display: block; width: 260px; - background-color: rgba(0, 0, 0, 0.5); + background-color: rgba(0, 0, 0, 0.85); color: #fff; text-align: left; border: 1px solid rgba(0, 0, 0, 0.5); @@ -252,7 +252,7 @@ position: relative; z-index: 1; left: -30px; - top: 10px; + top: -30px; } } From 73246ed5f177f1ce491363d5c2e812a10822bcc4 Mon Sep 17 00:00:00 2001 From: sasquach45932 Date: Fri, 27 Mar 2026 16:53:53 +0100 Subject: [PATCH 03/13] Look Out Creatures (Fear Roll) fixes --- .../ui/other-svg/dice-twenty-faces-twenty.svg | 1 + src/lang/en.json | 12 ++--- src/lang/pt-BR.json | 16 +++--- src/module/actor/actor.js | 54 ++++++++----------- src/module/dialog/roll-dialog.js | 14 ++--- 5 files changed, 45 insertions(+), 52 deletions(-) create mode 100644 src/assets/ui/other-svg/dice-twenty-faces-twenty.svg diff --git a/src/assets/ui/other-svg/dice-twenty-faces-twenty.svg b/src/assets/ui/other-svg/dice-twenty-faces-twenty.svg new file mode 100644 index 00000000..a9850857 --- /dev/null +++ b/src/assets/ui/other-svg/dice-twenty-faces-twenty.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/lang/en.json b/src/lang/en.json index 157fad7e..2652c18a 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -370,7 +370,7 @@ "DL.DialogWarningActorsNotSelected": "Actor(s) not selected", "DL.DialogWarningActorsNotTargeted": "Actor(s) not targeted", "DL.DialogWarningAfflictionFromEffect": "Affliction is applied from effect. Remove the effect to remove the affliction.", - "DL.DialogWarningAlreadyMadeWILLImmune": "You've already made a Will challenge roll against {creature} and you are immune to its {trait} trait.", + "DL.DialogWarningAlreadyMadeWILL": "You've already made a Will challenge roll against {creature}.", "DL.DialogWarningAlreadyMadeWILLFrightened": "You've already made a Will challenge roll against {creature} and you are already frightened.", "DL.DialogWarningBlindedChallengeFailer": "You're blinded and perception challenge rolls result in failure.", "DL.DialogWarningCreatureArmor": "You can't add armor to a creature. Change the Defense value manually.", @@ -405,6 +405,7 @@ "DL.EndRoundDelete": "Delete End of the Round", "DL.ExtraEffect": "Extra Effect", "DL.ExtraEffect20": "Extra Effect 20+", + "DL.FearRollAgainst": "Fear Roll against {creature}", "DL.FeatureAdd": "Add feature", "DL.FeatureDelete": "Delete feature", "DL.FeatureEdit": "Edit feature", @@ -422,14 +423,13 @@ "DL.GMnoteSave": "Save GM Note", "DL.HalfSpeed": "Half Speed", "DL.ImmuneAffliction": "Immune to Affliction", - "DL.ImmuneToTarget": "Immune to {creature}", "DL.ImmuneToHorrifyingOneMinute": "You become immune to {creature}'s horrifying trait for 1 minute.", "DL.ImmuneAttribute": "Immune Attribute", "DL.ImmuneCharacteristic": "Immune Characteristic", - "DL.ImmunityLastsUntilTheEndOfTheRound": "Immunity to target horrifying trait lasts until the end of the round.", - "DL.ImmunityLastsUntilTheEndOfNextRound": "Immunity to target horrifying trait lasts until the end of the next round.", - "DL.ImmunityLastsRounds": "Immunity to target horrifying trait lasts {rounds} more rounds.", - "DL.ImmunityLastsSeconds": "Immunity to target horrifying trait lasts {seconds} seconds.", + "DL.FearRollLastsUntilTheEndOfTheRound": "Fear Roll against target lasts until the end of the round.", + "DL.FearRollLastsUntilTheEndOfNextRound": "Fear Roll against target lasts until the end of the next round.", + "DL.FearRollLastsRounds": "Fear Roll against target lasts {rounds} more rounds.", + "DL.FearRollLastsSeconds": "Fear Roll against target lasts {seconds} seconds.", "DL.AfflictionImmunityEffectName": "{affliction} Immunity", "DL.IsDarkMagic": "Is Dark Magic", "DL.IsFrightening": "Is Frightening", diff --git a/src/lang/pt-BR.json b/src/lang/pt-BR.json index 71a1dd15..0967e5a4 100644 --- a/src/lang/pt-BR.json +++ b/src/lang/pt-BR.json @@ -265,10 +265,10 @@ "DL.CreatureEditMagic": "Editar Feitiço", "DL.CreatureEditSpecialActions": "Editar Ação Especial", "DL.CreatureEditSpecialAttacks": "Editar Ataque Especial", - "DL.CreatureFrightening": "Amedron.", + "DL.CreatureFrightening": "Amedrontador", "DL.CreatureFrighteningDescription": "Uma criatura sem o traço amedrontador ou horripilante deve fazer uma rolagem de desafio de Vontade ao ver pela primeira vez uma ou mais criaturas com esse traço. A rolagem é feita com 1 revés se houver quatro ou mais criaturas amedrontadoras ao mesmo tempo. Se fracassar, a criatura fica amedrontada por uma quantidade de rodadas igual a 1d3 + seu total de Insanidade ou ganha 1 ponto de Insanidade se já estiver amedrontada. Após fazer essa rolagem, seja qual for o resultado, a criatura não poderá mais ser afetada por esse traço da criatura, ou criaturas, que viu até completar um descanso.", "DL.CreatureHealth": "Vida", - "DL.CreatureHorrifying": "Horrip.", + "DL.CreatureHorrifying": "Horripilante", "DL.CreatureHorrifyingDescription": "Uma criatura sem o traço horripilante deve fazer uma rolagem de desafio de Vontade ao ver pela primeira vez uma ou mais criaturas com esse traço. A rolagem é feita com 1 revés se houver quatro ou mais criaturas horripilantes ao mesmo tempo. Se fracassar, a criatura ganha 1 ponto de Insanidade, ou 1d3 se já estiver amedrontada. Após fazer essa rolagem, seja qual for o resultado, a criatura não poderá mais ser afetada por esse traço da criatura, ou criaturas, que viu até completar um descanso. Independentemente do resultado da rolagem de desafio, criaturas que não possuem os traços amedrontadora ou horripilante fazem rolagens de ataque contra criaturas horripilantes com 1 revés.", "DL.CreatureInsanity": "Insanidade", "DL.CreatureIntellect": "Intelecto", @@ -370,7 +370,7 @@ "DL.DialogWarningActorsNotSelected": "Ator(es) não selecionado(s)", "DL.DialogWarningActorsNotTargeted": "Ator(es) não é(são) alvo(s)", "DL.DialogWarningAfflictionFromEffect": "A aflição é aplicada por efeito. Remova o efeito para remover a aflição.", - "DL.DialogWarningAlreadyMadeWILLImmune": "Você já fez uma rolagem de Vontade contra {creature} e está imune ao seu traço {trait}.", + "DL.DialogWarningAlreadyMadeWILL": "Você já fez uma rolagem de Vontade contra {creature}.", "DL.DialogWarningAlreadyMadeWILLFrightened": "Você já fez uma rolagem de Vontade contra {creature} e já está amedrontado.", "DL.DialogWarningBlindedChallengeFailer": "Você está cego e rolagens de desafio de Percepção resultam em fracasso.", "DL.DialogWarningCreatureArmor": "Você não pode adicionar armadura a uma criatura. Altere o valor de Defesa manualmente.", @@ -405,6 +405,11 @@ "DL.EndRoundDelete": "Excluir Final da Rodada", "DL.ExtraEffect": "Efeito Extra", "DL.ExtraEffect20": "Efeito Extra 20+", + "DL.FearRollAgainst": "Rolagem de Medo contra {creature}", + "DL.FearRollLastsUntilTheEndOfTheRound": "Rolagem de Medo contra o alvo irá durar até o fim da rodada.", + "DL.FearRollLastsUntilTheEndOfNextRound": "Rolagem de Medo contra o alvo irá durar até o final da próxima rodada.", + "DL.FearRollLastsRounds": "Rolagem de Medo contra o alvo irá durar mais {rounds} rodadas.", + "DL.FearRollLastsSeconds": "Rolagem de Medo contra o alvo irá durar {seconds} segundos.", "DL.FeatureAdd": "Adicionar Propriedade", "DL.FeatureDelete": "Excluir Propriedade", "DL.FeatureEdit": "Editar Propriedade", @@ -422,14 +427,9 @@ "DL.GMnoteSave": "Salvar Nota do MJ", "DL.HalfSpeed": "Metade da Velocidade", "DL.ImmuneAffliction": "Imune a aflição", - "DL.ImmuneToTarget": "Imune a {creature}", "DL.ImmuneToHorrifyingOneMinute": "Você se torna imune ao traço horripilante de {creature} por 1 minuto.", "DL.ImmuneAttribute": "Atributo Imune", "DL.ImmuneCharacteristic": "Característica Imune", - "DL.ImmunityLastsUntilTheEndOfTheRound": "Imunidade ao traço horripilante do alvo irá durar até o final da rodada.", - "DL.ImmunityLastsUntilTheEndOfNextRound": "Imunidade ao traço horripilante do alvo irá durar até o final da próxima rodada.", - "DL.ImmunityLastsRounds": "Imunidade ao traço horripilante do alvo irá durar mais {rounds} rodadas.", - "DL.ImmunityLastsSeconds": "Imunidade ao traço horripilante do alvo irá durar mais {seconds} segundos.", "DL.AfflictionImmunityEffectName": "Imunidade a {affliction}", "DL.IsDarkMagic": "É Magia das Trevas", "DL.IsFrightening": "É Amedrontador", diff --git a/src/module/actor/actor.js b/src/module/actor/actor.js index c667663d..52784984 100644 --- a/src/module/actor/actor.js +++ b/src/module/actor/actor.js @@ -475,9 +475,9 @@ export class DemonlordActor extends Actor { return result } - isImmuneToTarget(target) { + isFearRollCompleted(target) { let actor = this - const immuneArray = actor.appliedEffects.filter(x => x.name === game.i18n.format('DL.ImmuneToTarget', { + const immuneArray = actor.appliedEffects.filter(x => x.name === game.i18n.format('DL.FearRollAgainst', { creature: target.name })) let result = false @@ -503,17 +503,19 @@ getTargetAttackBane(target) { const attacker = this if (!target) return 0 if (!game.settings.get('demonlord', 'horrifyingBane') || attacker.isImmuneToAffliction('frightened')) return 0 - const optionalRuleBaneValue = game.settings.get('demonlord', 'optionalRuleBaneValue') ? 1 : 2 const ignoreLevelDependentBane = game.settings.get('demonlord', 'optionalRuleLevelDependentBane') && ((attacker.system?.level >= 3 && attacker.system?.level <= 6 && target?.system?.difficulty <= 25) || (attacker.system?.level >= 7 && target?.system?.difficulty <= 50)) ? false : true let baneValue = game.settings.get('demonlord', 'optionalRuleTraitMode2025') - ? (ignoreLevelDependentBane && !attacker.system.horrifying && !attacker.system.frightening && (target?.system.frightening || target?.system.horrifying) && !attacker.isImmuneToTarget(target) && 1) || 0 - : (ignoreLevelDependentBane && !attacker.system.horrifying && !attacker.system.frightening && target?.system.horrifying && !attacker.isImmuneToTarget(target) && 1) || 0 + ? (ignoreLevelDependentBane && !attacker.system.horrifying && !attacker.system.frightening && (target?.system.frightening || target?.system.horrifying) && 1) || 0 + : (ignoreLevelDependentBane && !attacker.system.horrifying && !attacker.system.frightening && target?.system.horrifying && 1) || 0 // Adjust bane if source of affliction can be seen, actor already has 1 (frightened), we need to add the difference - if (attacker.isFrightenedFrom(target)) baneValue += optionalRuleBaneValue + // 0 - no bane + // 1 - creature is horrifying, creature is frightening (only 2025 trtait mode) + // 2 - creature firhtened + if (attacker.isFrightenedFrom(target)) baneValue += 2 return baneValue } @@ -1090,7 +1092,8 @@ getTargetAttackBane(target) { } else ui.notifications.warn(game.i18n.localize('DL.DialogWarningActorImmuneFrightened')) } else { const frightenedEffect = actor.effects.find(e => e.statuses?.has('frightened')) - await frightenedEffect.update({ 'duration.rounds': newValue }) + // Only update effect duration if lasts longer than the current one + if ((frightenedEffect.duration.startTime + frightenedEffect.duration.rounds * 10) < (game.time.worldTime + newValue * 10)) await frightenedEffect.update({ 'duration.rounds': newValue }) if (!isStunned) await stunnedChallengeRoll() } if (actor.system.characteristics.insanity.value === actor.system.characteristics.insanity.max) await this.goingMad() @@ -1208,18 +1211,10 @@ getTargetAttackBane(target) { }), ) - const traitType = - targets[0].actor.system.frightening && targets[0].actor.system.horrifying ? - game.i18n.localize('DL.CreatureHorrifying').toLowerCase() : - targets[0].actor.system.frightening ? - game.i18n.localize('DL.CreatureFrightening').toLowerCase() : - game.i18n.localize('DL.CreatureHorrifying').toLowerCase() - - if (this.isImmuneToTarget(targets[0].actor)) + if (this.isFearRollCompleted(targets[0].actor)) return ui.notifications.warn( - game.i18n.format('DL.DialogWarningAlreadyMadeWILLImmune', { - creature: targets[0].actor.name, - trait: traitType + game.i18n.format('DL.DialogWarningAlreadyMadeWILL', { + creature: targets[0].actor.name }), ) } @@ -1227,7 +1222,7 @@ getTargetAttackBane(target) { const validTargetArray = targets.filter( target => (target.actor.system.horrifying || target.actor.system.frightening) && - !actor.isImmuneToTarget(target.actor) && + !actor.isFearRollCompleted(target.actor) && !ignoreTarget(target.actor), ) @@ -1239,9 +1234,9 @@ getTargetAttackBane(target) { for (const target of validTargetArray) { content += `• ${target.actor.name}
` - if (target.actor.system.horrifying && !actor.isImmuneToTarget(target.actor) && !ignoreTarget(target.actor)) + if (target.actor.system.horrifying && !actor.isFearRollCompleted(target.actor) && !ignoreTarget(target.actor)) isHorrifying = true - if (target.actor.system.frightening && !actor.isImmuneToTarget(target.actor) && !ignoreTarget(target.actor)) + if (target.actor.system.frightening && !actor.isFearRollCompleted(target.actor) && !ignoreTarget(target.actor)) isFrightening = true } @@ -1403,10 +1398,10 @@ getTargetAttackBane(target) { } const imuneToFrightenedEffect = new ActiveEffect({ - name: game.i18n.format('DL.ImmuneToTarget', { + name: game.i18n.format('DL.FearRollAgainst', { creature: target.actor.name }), - icon: 'systems/demonlord/assets/icons/effects/immune.svg', + icon: 'systems/demonlord/assets/ui/other-svg/dice-twenty-faces-twenty.svg', duration: { rounds: 1, }, @@ -1416,9 +1411,6 @@ getTargetAttackBane(target) { specialDuration: 'RestComplete', }, }, - description: game.i18n.format('DL.ImmuneToHorrifyingOneMinute', { - creature: target.actor.name - }), origin: target.actor.uuid }) @@ -1437,9 +1429,9 @@ getTargetAttackBane(target) { target: targets[0].actor.name }), ) - if (this.isImmuneToTarget(targets[0].actor)) + if (this.isFearRollCompleted(targets[0].actor)) return ui.notifications.warn( - game.i18n.format('DL.DialogWarningAlreadyMadeWILLImmune', { + game.i18n.format('DL.DialogWarningAlreadyMadeWILL', { creature: targets[0].actor.name, trait: game.i18n.localize('DL.CreatureHorrifying').toLowerCase() }), @@ -1459,7 +1451,7 @@ getTargetAttackBane(target) { !target.actor.system.horrifying || ignoreTarget(target.actor) || actor.isFrightenedFrom(target.actor) || - actor.isImmuneToTarget(target.actor) + actor.isFearRollCompleted(target.actor) ), ) if (validTargetArray.length === 0) return ui.notifications.warn(game.i18n.localize('DL.DialogWarningInvalidTarget')) @@ -1544,10 +1536,10 @@ getTargetAttackBane(target) { if (roll.total >= targetNumber) { const imuneToFrightenedEffect = new ActiveEffect({ - name: game.i18n.format('DL.ImmuneToTarget', { + name: game.i18n.format('DL.FearRollAgainst', { creature: target.actor.name }), - icon: 'systems/demonlord/assets/icons/effects/immune.svg', + icon: 'systems/demonlord/assets/ui/other-svg/dice-twenty-faces-twenty.svg', duration: { rounds: 6, }, diff --git a/src/module/dialog/roll-dialog.js b/src/module/dialog/roll-dialog.js index 36f72675..abf7e2a4 100644 --- a/src/module/dialog/roll-dialog.js +++ b/src/module/dialog/roll-dialog.js @@ -37,10 +37,10 @@ function prepareReminderHTML(text) if (game.settings.get('demonlord', 'launchDialogReminder')) { if (targets.length === 1 && (targets[0]?.actor.system.horrifying || targets[0]?.actor.system.frightening)) { if (actor.isFrightenedFrom(targets[0]?.actor)) content += `
${game.i18n.localize('DL.YouAreAttackingSoureceOfFrightenedAffliction')}
` - else if (actor.isImmuneToTarget(targets[0]?.actor)) { - if (!game.settings.get('demonlord', 'optionalRuleTraitMode2025')) content += prepareReminderHTML(game.i18n.localize('DL.YouCannotBeAffectedUntilYouCompleteARest')) - else { - const immuneArray = actor.appliedEffects.filter(x => x.name === game.i18n.format('DL.ImmuneToTarget', { + else if (actor.isFearRollCompleted(targets[0]?.actor)) { + if (game.settings.get('demonlord', 'optionalRuleTraitMode2025')) + { + const immuneArray = actor.appliedEffects.filter(x => x.name === game.i18n.format('DL.FearRollAgainst', { creature: targets[0].actor.name })) let effect @@ -49,12 +49,12 @@ if (game.settings.get('demonlord', 'launchDialogReminder')) { } if (game.combat) { const remainingRounds = calcEffectRemainingRounds(effect, game.combat.round) - const immuneText = (remainingRounds === 1) ? game.i18n.localize('DL.ImmunityLastsUntilTheEndOfNextRound') : (remainingRounds === 0) ? game.i18n.localize('DL.ImmunityLastsUntilTheEndOfTheRound') : game.i18n.format('DL.ImmunityLastsRounds', { + const immuneText = (remainingRounds === 1) ? game.i18n.localize('DL.FearRollLastsUntilTheEndOfNextRound') : (remainingRounds === 0) ? game.i18n.localize('DL.FearRollLastsUntilTheEndOfTheRound') : game.i18n.format('DL.FearRollLastsRounds', { rounds: remainingRounds }) content += prepareReminderHTML(immuneText) } else { - content += prepareReminderHTML(game.i18n.format('DL.ImmunityLastsSeconds', { + content += prepareReminderHTML(game.i18n.format('DL.FearRollLastsSeconds', { seconds: calcEffectRemainingSeconds(effect, game.time.worldTime) })) } @@ -62,7 +62,7 @@ if (game.settings.get('demonlord', 'launchDialogReminder')) { } const ignoreLevelDependentBane = (game.settings.get('demonlord', 'optionalRuleLevelDependentBane') && ((actor.system?.level >= 3 && actor.system?.level <= 6 && targets[0]?.actor.system?.difficulty <= 25) || (actor.system?.level >= 7 && targets[0]?.actor.system?.difficulty <= 50))) ? false : true - if (!actor.isFrightenedFrom(targets[0]?.actor) && !actor.isImmuneToTarget(targets[0]?.actor) && !actor.isImmuneToAffliction('frightened') && ignoreLevelDependentBane) { + if (!actor.isFrightenedFrom(targets[0]?.actor) && !actor.isFearRollCompleted(targets[0]?.actor) && !actor.isImmuneToAffliction('frightened') && ignoreLevelDependentBane) { if (game.settings.get('demonlord', 'optionalRuleTraitMode2025') && targets[0]?.actor.system.horrifying) content += prepareReminderHTML(game.i18n.localize('DL.YouHaventMadeWillChallengeRollAgainstTarget')) else if ( From 5c79ca13c7e84e8380888f73c994dec09196e2f0 Mon Sep 17 00:00:00 2001 From: sasquach45932 Date: Fri, 27 Mar 2026 17:02:14 +0100 Subject: [PATCH 04/13] Rename: Look Out Creatures! -> Fear Roll --- src/lang/en.json | 2 +- src/lang/pt-BR.json | 2 +- src/module/actor/actor.js | 4 ++-- src/templates/actor/parts/character-sheet-sidemenu.hbs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lang/en.json b/src/lang/en.json index 2652c18a..4d09298d 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -426,6 +426,7 @@ "DL.ImmuneToHorrifyingOneMinute": "You become immune to {creature}'s horrifying trait for 1 minute.", "DL.ImmuneAttribute": "Immune Attribute", "DL.ImmuneCharacteristic": "Immune Characteristic", + "DL.FearRoll": "Fear Roll", "DL.FearRollLastsUntilTheEndOfTheRound": "Fear Roll against target lasts until the end of the round.", "DL.FearRollLastsUntilTheEndOfNextRound": "Fear Roll against target lasts until the end of the next round.", "DL.FearRollLastsRounds": "Fear Roll against target lasts {rounds} more rounds.", @@ -467,7 +468,6 @@ "DL.LanguagesTitle": "Languages", "DL.LanguagesWrite": "Write", "DL.LanguagesWriteShort": "W", - "DL.LookOutCreatures": "Look out creatures!", "DL.MacroApplyAfflicationTitle": "Apply Afflictions", "DL.MacroCancel": "Cancel", "DL.MacroChallengeChoose": "Choose Attribute:", diff --git a/src/lang/pt-BR.json b/src/lang/pt-BR.json index 0967e5a4..b4987926 100644 --- a/src/lang/pt-BR.json +++ b/src/lang/pt-BR.json @@ -405,6 +405,7 @@ "DL.EndRoundDelete": "Excluir Final da Rodada", "DL.ExtraEffect": "Efeito Extra", "DL.ExtraEffect20": "Efeito Extra 20+", + "DL.FearRoll": "Rolagem de Medo", "DL.FearRollAgainst": "Rolagem de Medo contra {creature}", "DL.FearRollLastsUntilTheEndOfTheRound": "Rolagem de Medo contra o alvo irá durar até o fim da rodada.", "DL.FearRollLastsUntilTheEndOfNextRound": "Rolagem de Medo contra o alvo irá durar até o final da próxima rodada.", @@ -467,7 +468,6 @@ "DL.LanguagesTitle": "Idiomas", "DL.LanguagesWrite": "Escrever", "DL.LanguagesWriteShort": "E", - "DL.LookOutCreatures": "Cuidado, criaturas!", "DL.MacroApplyAfflicationTitle": "Aplicar Aflições", "DL.MacroCancel": "Cancelar", "DL.MacroChallengeChoose": "Selecionar Atributo:", diff --git a/src/module/actor/actor.js b/src/module/actor/actor.js index 52784984..3928098c 100644 --- a/src/module/actor/actor.js +++ b/src/module/actor/actor.js @@ -1251,7 +1251,7 @@ getTargetAttackBane(target) { const question = validTargetArray.length === 1 ? game.i18n.localize('DL.DialogDoYouSeeThisCreatureFirstTime') : game.i18n.localize('DL.DialogDoYouSeeTheseCreaturesFirstTime') const isLineOfSight = await foundry.applications.api.DialogV2.confirm({ window: { - title: game.i18n.localize('DL.LookOutCreatures'), + title: game.i18n.localize('DL.FearRoll'), }, content: question + content, position: { @@ -1464,7 +1464,7 @@ getTargetAttackBane(target) { const question = validTargetArray.length === 1 ? game.i18n.localize('DL.DialogDoYouStartYourTurnWithLOSCreature') : game.i18n.localize('DL.DialogDoYouStartYourTurnWithLOSCreatures') const isLineOfSight = await foundry.applications.api.DialogV2.confirm({ window: { - title: game.i18n.localize('DL.LookOutCreatures'), + title: game.i18n.localize('DL.FearRoll'), }, content: question + content, position: { diff --git a/src/templates/actor/parts/character-sheet-sidemenu.hbs b/src/templates/actor/parts/character-sheet-sidemenu.hbs index 844714c8..23a299e6 100644 --- a/src/templates/actor/parts/character-sheet-sidemenu.hbs +++ b/src/templates/actor/parts/character-sheet-sidemenu.hbs @@ -91,7 +91,7 @@
{{ifThen (gte ownership 2) system.characteristics.insanity.max '?'}}
{{ifThen (gte ownership 2) system.characteristics.insanity.value '?'}}
{{#if (gte ownership 2)}} - + {{/if}} {{else}}
From 503ee20fc241aa73682251138a7441aebde3ccbb Mon Sep 17 00:00:00 2001 From: sasquach45932 Date: Fri, 27 Mar 2026 17:07:32 +0100 Subject: [PATCH 05/13] ChatCard Effect messages fix --- src/module/chat/effect-messages.js | 61 ++++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 16 deletions(-) diff --git a/src/module/chat/effect-messages.js b/src/module/chat/effect-messages.js index 7df032b9..7bbfd77e 100644 --- a/src/module/chat/effect-messages.js +++ b/src/module/chat/effect-messages.js @@ -12,10 +12,11 @@ import { capitalize } from '../utils/utils' * @returns {Map} * @private */ -function _remapEffects(effects) { +function _remapEffects(effects, removeFrightenedEffect = false) { let m = new Map() // Active Auras module support effects = game.modules.get('ActiveAuras')?.active ? effects.filter(e => !e.flags?.ActiveAuras || foundry.utils.getProperty(e, `flags.ActiveAuras.isAura`) === undefined) : effects + effects = removeFrightenedEffect ? effects.filter(e => e.name != game.i18n.localize('DL.frightened')) : effects effects.forEach(effect => effect.changes.forEach(change => { const obj = { @@ -90,13 +91,16 @@ const changeListToMsgDefender = (m, keys, title, anonymize, f = plusify) => { * @returns {*} */ export function buildAttackEffectsMessage(attacker, defender, item, attackAttribute, defenseAttribute, inputBoons, plus20, inputModifier) { + const applyBane = attacker.getTargetAttackBane(defender) + const baneValue = applyBane <= 1 ? applyBane : 3 + // We remove Frightened Effect from ChatCard, because source is already frightened from target and later we add the correct effect name + const removeFrightenedEffect = baneValue === 3 && (defender?.system.frightening || defender?.system.horrifying) ? true : false const attackerEffects = Array.from(attacker.allApplicableEffects()).filter(effect => !effect.disabled) - let m = _remapEffects(attackerEffects) + let m = _remapEffects(attackerEffects, removeFrightenedEffect) const defenderEffects = defender ? Array.from(defender.allApplicableEffects()).filter(effect => !effect.disabled) : [] let d = _remapEffects(defenderEffects) let defenderBoonsArray = [`system.bonuses.defense.boons.${defenseAttribute}`,"system.bonuses.defense.boons.all"] - const applyHorrifyingBane = attacker.getTargetAttackBane(defender) let otherBoons = '' let modifiers = '' let inputBoonsMsg = inputBoons ? _toMsg(game.i18n.localize('DL.DialogInput'), plusify(inputBoons)) : '' @@ -154,12 +158,8 @@ export function buildAttackEffectsMessage(attacker, defender, item, attackAttrib let attributeText = 'DL.Attribute' + capitalize(attackAttribute) let attributeModMsg = attributeMod ? _toMsg(`${game.i18n.localize(attributeText)}`, plusify(attributeMod)) : '' - let revealHorrifyingBane = game.settings.get('demonlord', 'optionalRuleRevealHorrifyingBane') let creatureType - - if (!game.settings.get('demonlord', 'optionalRuleTraitMode2025')) - creatureType = game.i18n.localize('DL.CreatureHorrifying') - else + if (game.settings.get('demonlord', 'optionalRuleTraitMode2025')) creatureType = defender?.system.frightening && defender?.system.horrifying ? game.i18n.localize('DL.CreatureHorrifying') @@ -168,13 +168,42 @@ export function buildAttackEffectsMessage(attacker, defender, item, attackAttrib : defender?.system.horrifying ? game.i18n.localize('DL.CreatureHorrifying') : '' + else + creatureType = + defender?.system.frightening && defender?.system.horrifying + ? game.i18n.localize('DL.CreatureHorrifying') + '/' + game.i18n.localize('DL.CreatureFrightening') + : defender?.system.frightening + ? game.i18n.localize('DL.CreatureFrightening') + : defender?.system.horrifying + ? game.i18n.localize('DL.CreatureHorrifying') + : '' - const horrifyingText = applyHorrifyingBane > 1 ? game.i18n.localize('DL.CanSeeSoureOfAffliction') : `${game.i18n.localize(creatureType)} [${game.i18n.localize('DL.ActionTarget')}]` - let horrifyingHTMLPlayer = revealHorrifyingBane - ? '
' + _toMsg(horrifyingText, applyHorrifyingBane*-1) + '
' - : '
' + _toMsg(`${game.i18n.localize('DL.OtherUnknown')} [${game.i18n.localize('DL.ActionTarget')}]`, applyHorrifyingBane*-1) + '
' - - let horrifyingHTMLGM = '
' + _toMsg(horrifyingText, applyHorrifyingBane*-1) + '
' + const revealHorrifyingBane = game.settings.get('demonlord', 'optionalRuleRevealHorrifyingBane') + // We add the correct effect and its bane(s) to the chatcard. + const horrifyingTextGM = + baneValue > 1 + ? game.i18n.localize('DL.CanSeeSoureOfAffliction') + : `${game.i18n.localize(creatureType)} [${game.i18n.localize('DL.ActionTarget')}]` + let horrifyingHTMLGM + if (game.settings.get('demonlord', 'optionalRuleTraitMode2025')) + horrifyingHTMLGM = + baneValue > 1 + ? _toMsg(horrifyingTextGM, baneValue * -1) + + _toMsg(`${game.i18n.localize(creatureType)} [${game.i18n.localize('DL.ActionTarget')}]`, -1) + : _toMsg(horrifyingTextGM, baneValue * -1) + else if (creatureType !== game.i18n.localize('DL.CreatureFrightening')) + horrifyingHTMLGM = + baneValue > 1 + ? _toMsg(horrifyingTextGM, baneValue * -1) + + _toMsg(`${game.i18n.localize(creatureType)} [${game.i18n.localize('DL.ActionTarget')}]`, -1) + : _toMsg(horrifyingTextGM, baneValue * -1) + else horrifyingHTMLGM = _toMsg(horrifyingTextGM, baneValue * -1) + + horrifyingHTMLGM = '
' + horrifyingHTMLGM + '
' + const creatureTypeUnknown = game.i18n.localize('DL.OtherUnknown') + const horrifyingHTMLPlayer = revealHorrifyingBane + ? '
' + _toMsg(horrifyingTextGM, baneValue * -1) + '
' + : '
' + _toMsg(horrifyingTextGM, baneValue * -1).replace(creatureType, creatureTypeUnknown) + '
' let gmOnlyResult = changeListToMsgDefender(d, defenderBoonsArray, '', false) let playerOnlyResult = changeListToMsgDefender(d, defenderBoonsArray, '', true) @@ -185,8 +214,8 @@ export function buildAttackEffectsMessage(attacker, defender, item, attackAttrib (itemAttributePenalty ? _toMsg(itemAttributeRequirement, plusify(itemAttributePenalty)) : '') + changeListToMsg(m, [`system.bonuses.attack.boons.${attackAttribute}`, "system.bonuses.attack.boons.all"], '') + otherBoons + - (applyHorrifyingBane ? horrifyingHTMLPlayer : '') + - (applyHorrifyingBane ? horrifyingHTMLGM : '') + + (applyBane ? horrifyingHTMLPlayer : '') + + (applyBane ? horrifyingHTMLGM : '') + (playerOnlyMsg ? playerOnlyMsg : '') + (gmOnlyMsg ? gmOnlyMsg : '') boonsMsg = boonsMsg+inputBoonsMsg ? `  ${game.i18n.localize('DL.TalentAttackBoonsBanes')}
` + boonsMsg+inputBoonsMsg : '' From 9ce9fffdc3d04c6aae5d7c13378da26bf064cf99 Mon Sep 17 00:00:00 2001 From: sasquach45932 Date: Fri, 27 Mar 2026 17:12:39 +0100 Subject: [PATCH 06/13] Creature sidebar CSS fix --- src/styles/v2/_actors.scss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/styles/v2/_actors.scss b/src/styles/v2/_actors.scss index 7ed6dd22..58a609fb 100644 --- a/src/styles/v2/_actors.scss +++ b/src/styles/v2/_actors.scss @@ -154,6 +154,9 @@ aside { position: absolute; top: 40px; left: 0px; + text-overflow: ellipsis; + overflow: hidden; + width: 80px; } } @@ -164,6 +167,9 @@ aside { position: absolute; top: 40px; right: 0px; + text-overflow: ellipsis; + overflow: hidden; + width: 80px; } } From 2e64482bbc40bc11728ab52907df8fde4fb58a5e Mon Sep 17 00:00:00 2001 From: sasquach45932 Date: Fri, 27 Mar 2026 20:17:04 +0100 Subject: [PATCH 07/13] Actorsheet localization fixes --- src/module/utils/handlebars-helpers.js | 15 +++++++++++++++ src/templates/actor/tabs/combat.hbs | 2 +- src/templates/actor/tabs/magic.hbs | 4 ++-- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/module/utils/handlebars-helpers.js b/src/module/utils/handlebars-helpers.js index 048454d8..aef81ce6 100644 --- a/src/module/utils/handlebars-helpers.js +++ b/src/module/utils/handlebars-helpers.js @@ -120,6 +120,21 @@ export function registerHandlebarsHelpers() { else return game.i18n.localize(tooltip) }) + Handlebars.registerHelper('dlLocalize', function (groupName, str) { + let result + switch (groupName) { + case 'WeaponHands': + if (!str.length) result = '' + else result = i18n(`DL.WeaponHands${str.capitalize()}`) + break + case 'SpellType': + if (!str.length) result = '―' + else result = i18n(`DL.SpellType${str.capitalize()}`) + break + } + return result + }) + Handlebars.registerHelper('enrichHTMLUnrolled', async (x) => await TextEditor.enrichHTML(x, { unrolled: true })) Handlebars.registerHelper('lookupAttributeModifier', (attributeName, actorData) => actorData?.system?.attributes[attributeName.toLowerCase()]?.modifier diff --git a/src/templates/actor/tabs/combat.hbs b/src/templates/actor/tabs/combat.hbs index 41fe1d9f..a17f1d46 100644 --- a/src/templates/actor/tabs/combat.hbs +++ b/src/templates/actor/tabs/combat.hbs @@ -73,7 +73,7 @@ {{/if}} {{#if system.hands}}
- {{localize "DL.WeaponHands"}} {{system.hands}} + {{localize "DL.WeaponHands"}} {{dlLocalize "WeaponHands" system.hands}}
{{/if}} {{#if system.description}} diff --git a/src/templates/actor/tabs/magic.hbs b/src/templates/actor/tabs/magic.hbs index f25fb26b..05d20f41 100644 --- a/src/templates/actor/tabs/magic.hbs +++ b/src/templates/actor/tabs/magic.hbs @@ -32,7 +32,7 @@ -
{{defaultValue system.spelltype "―"}}
+
{{dlLocalize "SpellType" system.spelltype}}
{{system.rank}}
{{defaultValue (plusify system.action.boonsbanes) 0}} @@ -60,7 +60,7 @@ {{spell.name}}
- {{spellbook.tradition}} {{system.spelltype}} {{system.rank}} + {{spellbook.tradition}} {{dlLocalize "SpellType" system.spelltype}} {{system.rank}}

From e2b70463e2f3f41b3ce6ea7bc1b379b27ab4a250 Mon Sep 17 00:00:00 2001 From: sasquach45932 Date: Fri, 27 Mar 2026 20:34:54 +0100 Subject: [PATCH 08/13] Fear Roll ChatCard localization fix --- src/module/chat/roll-messages.js | 1 + src/templates/chat/text.hbs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/module/chat/roll-messages.js b/src/module/chat/roll-messages.js index 6cbd0c63..acfd9d60 100644 --- a/src/module/chat/roll-messages.js +++ b/src/module/chat/roll-messages.js @@ -610,6 +610,7 @@ export async function postCustomTextToChat(actor, roll, options, attribute = {}) break } + data['resultBoxClass'] = roll?.total ? roll.total >= targetNumber ? 'SUCCESS' : 'FAILURE' : '' data['actorInfo'] = buildActorInfo(actor) const rollMode = game.settings.get('core', 'rollMode') const chatData = getChatBaseData(actor, rollMode) diff --git a/src/templates/chat/text.hbs b/src/templates/chat/text.hbs index 3bbf11f5..027907b8 100644 --- a/src/templates/chat/text.hbs +++ b/src/templates/chat/text.hbs @@ -14,7 +14,7 @@
{{data.resultText}}
-
+
{{data.source}}
{{data.diceTotal}}
From 41ad862c0642559f9ee368e0f0893998426a7549 Mon Sep 17 00:00:00 2001 From: sasquach45932 Date: Sun, 5 Apr 2026 09:52:14 +0200 Subject: [PATCH 09/13] Creature Role difficulty increase fix --- src/lang/en.json | 2 ++ src/module/active-effects/item-effects.js | 23 ++++++++++++++++++++--- src/module/config.js | 2 ++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/lang/en.json b/src/lang/en.json index 4d09298d..f42eafba 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -373,10 +373,12 @@ "DL.DialogWarningAlreadyMadeWILL": "You've already made a Will challenge roll against {creature}.", "DL.DialogWarningAlreadyMadeWILLFrightened": "You've already made a Will challenge roll against {creature} and you are already frightened.", "DL.DialogWarningBlindedChallengeFailer": "You're blinded and perception challenge rolls result in failure.", + "DL.DialogWarningCannotIncreaseDifficulty": "Cannot increase difficulty by {steps} steps, because difficulty would be greater than maximum allowed {maxAllowed}.", "DL.DialogWarningCreatureArmor": "You can't add armor to a creature. Change the Defense value manually.", "DL.DialogWarningDazedFailer": "You're dazed and cannot use actions.", "DL.DialogWarningDefenselessFailer": "You're defenseless and cannot use actions, and challenge rolls using attributes result in failure.", "DL.DialogWarningInvalidTarget": "No frightening or horrifying creature targeted or you've already performed a Will challenge roll.", + "DL.DialogWarningInvalidCreatureDifficulty": "Invalid creature difficulty: {difficulty}.", "DL.DialogWarningStunnedFailer": "You're stunned and cannot use actions or move, and all your challenge rolls result in failure.", "DL.DialogWarningSurprisedFailer": "You're surprised and cannot use actions or move, and all your challenge rolls result in failure.", "DL.DialogWarningTargetNotHorrifying": "{target} not horrifying.", diff --git a/src/module/active-effects/item-effects.js b/src/module/active-effects/item-effects.js index 00a81f00..f50eaa8a 100644 --- a/src/module/active-effects/item-effects.js +++ b/src/module/active-effects/item-effects.js @@ -87,7 +87,7 @@ export class DLActiveEffects { effectDataList = DLActiveEffects.generateEffectDataFromArmor(doc, actor) break case 'creaturerole': - effectDataList = DLActiveEffects.generateEffectDataFromRole(doc) + effectDataList = DLActiveEffects.generateEffectDataFromRole(doc, actor, operation) break default: return await Promise.resolve(0) @@ -302,9 +302,26 @@ export class DLActiveEffects { /* -------------------------------------------- */ - static generateEffectDataFromRole(item) { + static bumpDifficulty(actor, steps, operation) { + const difficultyIndex = (operation === 'update') ? CONFIG.DL.difficultyScale.indexOf(actor.system.difficultyBase) : CONFIG.DL.difficultyScale.indexOf(actor.system.difficulty) + if (difficultyIndex === -1) { + ui.notifications.warn(game.i18n.format('DL.DialogWarningInvalidCreatureDifficulty', {difficulty: actor.system.difficulty})) + return null + } + + const calculatedDifficulty = CONFIG.DL.difficultyScale[difficultyIndex + parseInt(steps)] + if (calculatedDifficulty === undefined) { + ui.notifications.warn(game.i18n.format('DL.DialogWarningCannotIncreaseDifficulty', {steps: steps, maxAllowed: CONFIG.DL.difficultyScale[CONFIG.DL.difficultyScale.length-1]})) + return null + } + + return (operation === 'update') ? (calculatedDifficulty - actor.system.difficultyBase) : (calculatedDifficulty - actor.system.difficulty) + } + + static generateEffectDataFromRole(item, actor, operation) { const priority = 5 const data = item.system + const calculatedDifficulty = DLActiveEffects.bumpDifficulty(actor, data.characteristics.difficulty, operation) const effectData = { name: item.name, @@ -344,7 +361,7 @@ export class DLActiveEffects { // addEffect('system.characteristics.corruption.value', data.characteristics.corruption.value, priority), // addEffect('system.characteristics.insanity.value', data.characteristics.insanity.value, priority), - addEffect('system.difficulty', data.characteristics.difficulty, priority), + addEffect('system.difficulty', calculatedDifficulty, priority), overrideEffect('system.characteristics.size', data.characteristics.size, priority), overrideEffect('system.frightening', data.frightening, priority, true), overrideEffect('system.horrifying', data.horrifying, priority, true), diff --git a/src/module/config.js b/src/module/config.js index 9a7df087..2d8d383c 100644 --- a/src/module/config.js +++ b/src/module/config.js @@ -125,3 +125,5 @@ DL.defaultItemIcons = { creaturerole: 'icons/equipment/back/cape-layered-blue-accent.webp', relic: 'icons/commodities/treasure/sceptre-jeweled-gold.webp' } + +DL.difficultyScale = [1,5,10,25,50,100,250,500,750,1000,1500] \ No newline at end of file From 5dc462d134c68311ea52c5bd28e682394c866518 Mon Sep 17 00:00:00 2001 From: sasquach45932 Date: Sun, 5 Apr 2026 19:52:31 +0200 Subject: [PATCH 10/13] Item Macros --- src/lang/en.json | 2 + src/module/actor/actor.js | 46 +++++++++++++++- src/module/item-macro/ItemMacro.js | 66 +++++++++++++++++++++++ src/module/item-macro/ItemMacroConfig.js | 54 +++++++++++++++++++ src/module/item/item.js | 26 +++++++++ src/module/item/sheets/base-item-sheet.js | 18 +++++++ src/module/settings.js | 8 +++ src/module/utils/socket.js | 44 ++++++++++++--- 8 files changed, 256 insertions(+), 8 deletions(-) create mode 100644 src/module/item-macro/ItemMacro.js create mode 100644 src/module/item-macro/ItemMacroConfig.js diff --git a/src/lang/en.json b/src/lang/en.json index f42eafba..26607709 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -645,6 +645,8 @@ "DL.SettingDSNLabel": "Dice So Nice! Settings", "DL.SettingDamageScrollText": "Enable Scroll Text for Damage/Health", "DL.SettingDamageScrollTextHint": "When checked, scrolling text appears above tokens about damage/heal value.", + "DL.SettingEnableItemMacro": "Enable Item Macro", + "DL.SettingEnableItemMacroHint": "When checked item macros will be executed.", "DL.SettingEnableQuickDraw": "Enable one-click draw for rolltables.", "DL.SettingEnableQuickDrawHint": "Draw directly form sidebar, compendia or sheets (right-click).", "DL.SettingFinesseAutoSelect": "Finesse weapon attack attribute auto select", diff --git a/src/module/actor/actor.js b/src/module/actor/actor.js index 3928098c..4d7e913c 100644 --- a/src/module/actor/actor.js +++ b/src/module/actor/actor.js @@ -534,6 +534,7 @@ getTargetAttackBane(target) { // Get attacker attribute and defender attribute name let attackAttribute = item.system.action?.attack?.toLowerCase() const defenseAttribute = item.system.action?.against?.toLowerCase() + if (game.settings.get('demonlord', 'enableItemMacro')) await item.executeDLMacro({},{pass : 'preRollAttack', attackAttribute : attackAttribute, defenseAttribute: defenseAttribute, targetActorUuid: defender?.uuid}) if (game.settings.get('demonlord', 'finesseAutoSelect') && attackAttribute === '' && item.system.properties?.toLowerCase().includes('finesse')) { if (this.system.attributes.strength.value > this.system.attributes.agility.value) attackAttribute = 'strength' @@ -600,6 +601,12 @@ getTargetAttackBane(target) { hitTargets: hitTarget, attackRoll: attackRoll }) + + if (game.settings.get('demonlord', 'enableItemMacro')) { + const successfullHit = (defender && attackRoll?.total >= targetNumber) ? true : false + const plus20 = attackRoll?.total >= 20 && (targetNumber ? attackRoll?.total > targetNumber + (game.settings.get('demonlord', 'optionalRuleExceedsByFive') ? 5 : 4) : true) + await item.executeDLMacro({},{pass : 'postRollAttack', attackRoll : attackRoll, targetNumber: targetNumber, successfullHit: successfullHit, plus20: plus20, targetActorUuid: defender?.uuid}) + } } /** @@ -776,6 +783,7 @@ getTargetAttackBane(target) { const target = targets[0] let attackRoll = null + if (game.settings.get('demonlord', 'enableItemMacro')) await talent.executeDLMacro({},{pass : 'preRollTalent', targetActorUuid: target?.actor.uuid}) if (!talentData?.action?.attack) { await this.activateTalent(talent, true) } else { @@ -825,6 +833,7 @@ getTargetAttackBane(target) { }) postTalentToChat(this, talent, attackRoll, target?.actor, parseInt(inputBoons) || 0, parseInt(inputModifier) || 0) + if (game.settings.get('demonlord', 'enableItemMacro')) await talent.executeDLMacro({},{pass : 'postRollTalent', attackRoll : attackRoll, targetActorUuid: target?.actor.uuid}) return attackRoll } @@ -869,6 +878,8 @@ getTargetAttackBane(target) { const defenseAttribute = spellData?.action?.against?.toLowerCase() let attackRoll + const targetActorUuid = (target.length) ? target[0].actor.uuid : null + if (game.settings.get('demonlord', 'enableItemMacro')) await spell.executeDLMacro({},{pass : 'preRollSpell', attackAttribute: attackAttribute, defenseAttribute: defenseAttribute, targetActorUuid: targetActorUuid}) if (attackAttribute) { const attacker = this @@ -936,6 +947,26 @@ getTargetAttackBane(target) { concentrate['statuses'] = [concentrate.id] ActiveEffect.create(concentrate, {parent: this}) } + + if (game.settings.get('demonlord', 'enableItemMacro')) { + let successfullHit = false + let targetNumber + if (target.length) { + const defender = target[0].actor + targetNumber = + defenseAttribute === 'defense' + ? defender?.system.characteristics.defense + : defender?.system.attributes[defenseAttribute]?.value || '' + successfullHit = defender && attackRoll?.total >= targetNumber ? true : false + } + const plus20 = + attackRoll?.total >= 20 && + (targetNumber + ? attackRoll?.total > targetNumber + (game.settings.get('demonlord', 'optionalRuleExceedsByFive') ? 5 : 4) + : true) + await spell.executeDLMacro( {}, { pass: 'postRollSpell', attackRoll: attackRoll, successfullHit: successfullHit, plus20: plus20, targetActorUuid: targetActorUuid, }, ) + } + return attackRoll } @@ -990,11 +1021,13 @@ getTargetAttackBane(target) { let attackRoll = null if (!itemData?.action?.attack) { + if (game.settings.get('demonlord', 'enableItemMacro')) await item.executeDLMacro({},{pass : 'preRollItem', targetActorUuid: target?.actor.uuid}) postItemToChat(this, item, null, null, null) + if (game.settings.get('demonlord', 'enableItemMacro')) await item.executeDLMacro({},{pass : 'postRollItem', targetActorUuid: target?.actor.uuid}) return } else { const attackAttribute = itemData.action.attack.toLowerCase() - const defenseAttribute = itemData.action?.attack?.toLowerCase() + const defenseAttribute = itemData.action?.against?.toLowerCase() const attacker = this const modifiers = [ @@ -1019,6 +1052,7 @@ getTargetAttackBane(target) { const boonsReroll = parseInt(this.system.bonuses.rerollBoon1Dice) + if (game.settings.get('demonlord', 'enableItemMacro')) await item.executeDLMacro({},{pass : 'preRollItemAttack', attackAttribute : attackAttribute, defenseAttribute: defenseAttribute, targetActorUuid: target?.actor.uuid}) attackRoll = new Roll(this.rollFormula(modifiers, boons, boonsReroll), this.system) await attackRoll.evaluate() @@ -1026,6 +1060,16 @@ getTargetAttackBane(target) { const specialDuration = foundry.utils.getProperty(effect, `flags.${game.system.id}.specialDuration`) if (specialDuration === 'NextD20Roll' || specialDuration === 'NextAttackRoll') await effect?.delete() } + if (game.settings.get('demonlord', 'enableItemMacro')) { + const defender = target?.actor + const targetNumber = + defenseAttribute === 'defense' + ? defender?.system.characteristics.defense + : defender?.system.attributes[defenseAttribute]?.value || '' + const successfullHit = (target && attackRoll?.total >= targetNumber) ? true : false + const plus20 = attackRoll?.total >= 20 && (targetNumber ? attackRoll?.total > targetNumber + (game.settings.get('demonlord', 'optionalRuleExceedsByFive') ? 5 : 4) : true) + await item.executeDLMacro({},{pass : 'postRollAttack', attackRoll : attackRoll, targetNumber: targetNumber, successfullHit: successfullHit, plus20: plus20, targetActorUuid: target?.actor.uuid}) + } } postItemToChat(this, item, attackRoll, target?.actor, parseInt(inputBoons) || 0) return attackRoll diff --git a/src/module/item-macro/ItemMacro.js b/src/module/item-macro/ItemMacro.js new file mode 100644 index 00000000..4e635f93 --- /dev/null +++ b/src/module/item-macro/ItemMacro.js @@ -0,0 +1,66 @@ +/** + * Modified version of the awesome https://github.com/Foundry-Workshop/Item-Macro + * Big thanks to Forien & Kekilla0 + */ + +export class DLItemMacro extends Macro { + constructor(data, context) { + super(data, context) + + this.item = context.item + } + + #executeChat(speaker) { + return ui.chat.processMessage(this.command, { speaker }).catch(err => { + Hooks.onError('Macro#_executeChat', err, { + msg: 'There was an error in your chat message syntax.', + log: 'error', + notify: 'error', + command: this.command, + }) + }) + } + + async #executeScript(args = null) { + const item = this.item + const speaker = ChatMessage.getSpeaker({ actor: item.actor }) + const actor = item.actor ?? game.actors.get(speaker.actor) + + /* MMH@TODO Check the types returned by linked and unlinked */ + const token = canvas.tokens?.get(speaker.token) + const character = game.user.character + + //build script execution + const scriptFunction = Object.getPrototypeOf(async function () {}).constructor + const body = this.command + + if (game.user.isGM) { + const fn = new scriptFunction('item', 'speaker', 'actor', 'token', 'character', 'args', body) + + //attempt script execution + try { + return await fn.bind(this)(item, speaker, actor, token, character, args) + } catch (err) { + ui.notifications.error('DLItemMacro Execution failed') + } + } else { + game.socket.emit('system.demonlord', { + request: 'runMacro', + itemuuid : item.uuid, + speaker : speaker, + actoruuid: actor.uuid, + characteruuid: character.uuid, + args: args + }) + } + } + + execute(scope = {}, args = null) { + switch (this.type) { + case 'chat': + return this.#executeChat(scope.speaker) + case 'script': + return this.#executeScript(args) + } + } +} diff --git a/src/module/item-macro/ItemMacroConfig.js b/src/module/item-macro/ItemMacroConfig.js new file mode 100644 index 00000000..d7ea3d50 --- /dev/null +++ b/src/module/item-macro/ItemMacroConfig.js @@ -0,0 +1,54 @@ +/** + * Modified version of the awesome https://github.com/Foundry-Workshop/Item-Macro + * Big thanks to Forien & Kekilla0 + */ +import { DLItemMacro } from "./ItemMacro" +/** + * @extends {MacroConfig} + */ +export class DLItemMacroConfig extends foundry.applications.sheets.MacroConfig { + /** @override */ + static DEFAULT_OPTIONS = { + actions: { + execute: DLItemMacroConfig._onExecute + }, + } + + /** @override */ + // eslint-disable-next-line no-shadow + constructor({document, item}, ...args) { + super({document}, ...args) + this.item = item + } + + static async openConfig(item) { + const macro = new DLItemMacroConfig({document: item.getDLMacro(), item}) + macro.render(true) + } + + /** @override */ + async _processSubmitData(event, form, submitData) { + await this.updateMacro(submitData) + } + + static async _onExecute(event) { + await this.submit() + this.item.executeDLMacro(event) + } + + async updateMacro({command, type}) { + const item = this.item + + const newMacro = new DLItemMacro({ + name: item.name, + type, + scope: "global", + command, + author: game.user.id, + }, {item}) + + await item.setDLMacro(newMacro) + + this.object = newMacro + } +} \ No newline at end of file diff --git a/src/module/item/item.js b/src/module/item/item.js index fe5f9f34..45bec2fa 100644 --- a/src/module/item/item.js +++ b/src/module/item/item.js @@ -2,6 +2,7 @@ import {deleteActorNestedItems, PathLevel} from './nested-objects' import {DemonlordActor} from '../actor/actor' import { DLEndOfRound } from '../dialog/endofround' import { getChatBaseData } from '../chat/base-messages' +import { DLItemMacro } from '../item-macro/ItemMacro' export class DemonlordItem extends Item { /** @override */ @@ -215,4 +216,29 @@ export class DemonlordItem extends Item { return ancestry } + + getDLMacro() { + const hasMacro = this.hasDLMacro() + const flag = this.getFlag('demonlord', 'macro') + if (hasMacro) return new DLItemMacro(flag, { item: this }) + return new DLItemMacro({ img: this.img, name: this.name, scope: 'global', type: 'script' }, { item: this }) + } + + hasDLMacro() { + const flag = this.getFlag('demonlord', 'macro') + return !!flag?.command + } + + async setDLMacro(macro) { + if (macro instanceof DLItemMacro) { + const data = macro.toObject() + return await this.setFlag('demonlord', 'macro', data) + } + } + + executeDLMacro(scope = {}, args = null) { + if (!this.hasDLMacro()) return + return this.getDLMacro().execute(scope, args) + } + } diff --git a/src/module/item/sheets/base-item-sheet.js b/src/module/item/sheets/base-item-sheet.js index 230f1e5b..bfe113f2 100644 --- a/src/module/item/sheets/base-item-sheet.js +++ b/src/module/item/sheets/base-item-sheet.js @@ -18,6 +18,7 @@ import { DamageType } from '../nested-objects'; import { DLStatEditor } from '../../dialog/stat-editor' +import { DLItemMacroConfig } from '../../item-macro/ItemMacroConfig' const { TextEditor } = foundry.applications.ux //eslint-disable-line no-shadow @@ -722,6 +723,23 @@ export default class DLBaseItemSheet extends HandlebarsApplicationMixin(ItemShee // Autoselect text in inputs when focused e.querySelectorAll('input')?.forEach(el => el.addEventListener('focus', ev => ev.currentTarget.select())) + + if (game.user.isGM && game.settings.get('demonlord', 'enableItemMacro')) { + const hasMacro = this.document.hasDLMacro() + const macroLink = { + style: hasMacro ? 'color: darkorange;text-shadow: 0 0 8px darkorange; cursor: pointer;' : 'color: var(--button-text-color); text-shadow: 0 0 8px var(--button-text-color); cursor: pointer;', + icon: "fa-solid fa-code", + tooltip: hasMacro ? game.i18n.localize("MACRO.Edit") : game.i18n.localize("SIDEBAR.ACTIONS.CREATE.Macro") + } + + let macroLinkIndicator = `` + e.querySelector("macrolink")?.remove() + e.querySelector(".header-control")?.insertAdjacentHTML("beforebegin", macroLinkIndicator) + // eslint-disable-next-line no-unused-vars + e.querySelector("macrolink")?.addEventListener('click', async ev => { + DLItemMacroConfig.openConfig(this.document) + }) + } } /* -------------------------------------------- */ diff --git a/src/module/settings.js b/src/module/settings.js index 9dd94fbe..85274c44 100644 --- a/src/module/settings.js +++ b/src/module/settings.js @@ -979,4 +979,12 @@ export const registerSettings = function () { type: Boolean, config: true, }) + game.settings.register('demonlord', 'enableItemMacro', { + name: game.i18n.localize('DL.SettingEnableItemMacro'), + hint: game.i18n.localize('DL.SettingEnableItemMacroHint'), + default: false, + scope: 'world', + type: Boolean, + config: true, + }) } diff --git a/src/module/utils/socket.js b/src/module/utils/socket.js index 765b9ecb..92db0d02 100644 --- a/src/module/utils/socket.js +++ b/src/module/utils/socket.js @@ -1,22 +1,52 @@ /* global fromUuidSync */ +import { DLItemMacro } from '../item-macro/ItemMacro' export function activateSocketListener() { game.socket.on('system.demonlord', async (...[message]) => { - let actor = fromUuidSync(message.tokenuuid).actor // Execute it once if multiple GMs are connected. if (game.users.activeGM?.isSelf) { switch (message.request) { case 'createEffect': - await actor.createEmbeddedDocuments('ActiveEffect', [message.effectData]) + { + let actor = fromUuidSync(message.tokenuuid).actor + await actor.createEmbeddedDocuments('ActiveEffect', [message.effectData]) + } break case 'deleteEffect': - await actor.deleteEmbeddedDocuments('ActiveEffect', message.effectData) + { + let actor = fromUuidSync(message.tokenuuid).actor + await actor.deleteEmbeddedDocuments('ActiveEffect', message.effectData) + } break case 'increaseDamage': - await actor.increaseDamage(message.increment) - break - default: + { + let actor = fromUuidSync(message.tokenuuid).actor + await actor.increaseDamage(message.increment) + } break + case 'runMacro': { + const item = fromUuidSync(message.itemuuid) + const actor = fromUuidSync(message.actoruuid) + const character = fromUuidSync(message.characteruuid) + const token = canvas.tokens?.get(message.speaker.token) + const body = item.getDLMacro()?.command + const scriptFunction = Object.getPrototypeOf(async function () {}).constructor + const fn = new scriptFunction('item', 'speaker', 'actor', 'token', 'character', 'args', body) + //attempt script execution + try { + return await fn.bind(DLItemMacro)( + item, + message.speaker, + actor, + message.token, + character, + message.args, + body, + ) + } catch (err) { + ui.notifications.error('DLItemMacro Execution failed') + } + } } } }) -} \ No newline at end of file +} From 4a2dd1229efbfcd457b026a59d47377156df4526 Mon Sep 17 00:00:00 2001 From: sasquach45932 Date: Sun, 5 Apr 2026 19:58:05 +0200 Subject: [PATCH 11/13] ESLint fix --- src/module/utils/socket.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/module/utils/socket.js b/src/module/utils/socket.js index 92db0d02..c37a0c52 100644 --- a/src/module/utils/socket.js +++ b/src/module/utils/socket.js @@ -27,6 +27,7 @@ export function activateSocketListener() { const item = fromUuidSync(message.itemuuid) const actor = fromUuidSync(message.actoruuid) const character = fromUuidSync(message.characteruuid) + // eslint-disable-next-line no-unused-vars const token = canvas.tokens?.get(message.speaker.token) const body = item.getDLMacro()?.command const scriptFunction = Object.getPrototypeOf(async function () {}).constructor From 4d359251a5f4ede5320c1c8d678d6e155640dc31 Mon Sep 17 00:00:00 2001 From: sasquach45932 Date: Sun, 5 Apr 2026 21:32:35 +0200 Subject: [PATCH 12/13] Item Macros /2 --- src/module/actor/actor.js | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/module/actor/actor.js b/src/module/actor/actor.js index 4d7e913c..59958275 100644 --- a/src/module/actor/actor.js +++ b/src/module/actor/actor.js @@ -783,14 +783,15 @@ getTargetAttackBane(target) { const target = targets[0] let attackRoll = null - if (game.settings.get('demonlord', 'enableItemMacro')) await talent.executeDLMacro({},{pass : 'preRollTalent', targetActorUuid: target?.actor.uuid}) if (!talentData?.action?.attack) { + if (game.settings.get('demonlord', 'enableItemMacro')) await talent.executeDLMacro({},{pass : 'preRollTalent', targetActorUuid: target?.actor.uuid}) await this.activateTalent(talent, true) + if (game.settings.get('demonlord', 'enableItemMacro')) await talent.executeDLMacro({},{pass : 'postRollTalent', targetActorUuid: target?.actor.uuid}) } else { await this.activateTalent(talent, Boolean(talentData.action?.damageActive)) const attackAttribute = talentData.action.attack.toLowerCase() - const defenseAttribute = talentData.action?.attack?.toLowerCase() + const defenseAttribute = talentData.action?.against?.toLowerCase() const attacker = this const modifiers = [ @@ -814,7 +815,7 @@ getTargetAttackBane(target) { this.getTargetAttackBane(target.actor)) const boonsReroll = parseInt(this.system.bonuses.rerollBoon1Dice) - + if (game.settings.get('demonlord', 'enableItemMacro')) await talent.executeDLMacro({},{pass : 'preRollTalent', attackAttribute: attackAttribute, defenseAttribute: defenseAttribute, targetActorUuid: target?.actor.uuid}) attackRoll = new Roll(this.rollFormula(modifiers, boons, boonsReroll), this.system) await attackRoll.evaluate() @@ -823,6 +824,25 @@ getTargetAttackBane(target) { if (specialDuration === 'NextD20Roll' || specialDuration === 'NextAttackRoll') await effect?.delete() } + if (game.settings.get('demonlord', 'enableItemMacro')) { + let successfullHit = false + let targetNumber + if (targets.length) { + const defender = target.actor + targetNumber = + defenseAttribute === 'defense' + ? defender?.system.characteristics.defense + : defender?.system.attributes[defenseAttribute]?.value || '' + successfullHit = defender && attackRoll?.total >= targetNumber ? true : false + } + const plus20 = + attackRoll?.total >= 20 && + (targetNumber + ? attackRoll?.total > targetNumber + (game.settings.get('demonlord', 'optionalRuleExceedsByFive') ? 5 : 4) + : true) + + await talent.executeDLMacro({},{pass : 'postRollTalent', attackRoll: attackRoll, targetNumber: targetNumber, successfullHit: successfullHit, plus20: plus20, targetActorUuid: target?.actor.uuid}) + } } Hooks.call('DL.UseTalent', { @@ -833,7 +853,6 @@ getTargetAttackBane(target) { }) postTalentToChat(this, talent, attackRoll, target?.actor, parseInt(inputBoons) || 0, parseInt(inputModifier) || 0) - if (game.settings.get('demonlord', 'enableItemMacro')) await talent.executeDLMacro({},{pass : 'postRollTalent', attackRoll : attackRoll, targetActorUuid: target?.actor.uuid}) return attackRoll } @@ -1052,7 +1071,7 @@ getTargetAttackBane(target) { const boonsReroll = parseInt(this.system.bonuses.rerollBoon1Dice) - if (game.settings.get('demonlord', 'enableItemMacro')) await item.executeDLMacro({},{pass : 'preRollItemAttack', attackAttribute : attackAttribute, defenseAttribute: defenseAttribute, targetActorUuid: target?.actor.uuid}) + if (game.settings.get('demonlord', 'enableItemMacro')) await item.executeDLMacro({},{pass : 'preRollItem', attackAttribute : attackAttribute, defenseAttribute: defenseAttribute, targetActorUuid: target?.actor.uuid}) attackRoll = new Roll(this.rollFormula(modifiers, boons, boonsReroll), this.system) await attackRoll.evaluate() @@ -1068,7 +1087,7 @@ getTargetAttackBane(target) { : defender?.system.attributes[defenseAttribute]?.value || '' const successfullHit = (target && attackRoll?.total >= targetNumber) ? true : false const plus20 = attackRoll?.total >= 20 && (targetNumber ? attackRoll?.total > targetNumber + (game.settings.get('demonlord', 'optionalRuleExceedsByFive') ? 5 : 4) : true) - await item.executeDLMacro({},{pass : 'postRollAttack', attackRoll : attackRoll, targetNumber: targetNumber, successfullHit: successfullHit, plus20: plus20, targetActorUuid: target?.actor.uuid}) + await item.executeDLMacro({},{pass : 'preRollItem', attackRoll : attackRoll, targetNumber: targetNumber, successfullHit: successfullHit, plus20: plus20, targetActorUuid: target?.actor.uuid}) } } postItemToChat(this, item, attackRoll, target?.actor, parseInt(inputBoons) || 0) From 6a4d6f6718a95121c90e579a706f1a51e6514cc5 Mon Sep 17 00:00:00 2001 From: sasquach45932 Date: Wed, 8 Apr 2026 21:26:17 +0200 Subject: [PATCH 13/13] Implementation of Ferrer's recommendations --- src/module/actor/actor.js | 28 +++++++++++++---------- src/module/chat/effect-messages.js | 31 ++++++++++---------------- src/module/utils/handlebars-helpers.js | 2 +- src/templates/actor/tabs/combat.hbs | 2 +- src/templates/actor/tabs/magic.hbs | 4 ++-- 5 files changed, 32 insertions(+), 35 deletions(-) diff --git a/src/module/actor/actor.js b/src/module/actor/actor.js index 59958275..ff00843f 100644 --- a/src/module/actor/actor.js +++ b/src/module/actor/actor.js @@ -529,12 +529,13 @@ getTargetAttackBane(target) { const attacker = this const defender = token ? token.actor : null let defenderToken = [] + const itemMacroEnabled = game.settings.get('demonlord', 'enableItemMacro') if (token) defenderToken.push(token) // Get attacker attribute and defender attribute name let attackAttribute = item.system.action?.attack?.toLowerCase() const defenseAttribute = item.system.action?.against?.toLowerCase() - if (game.settings.get('demonlord', 'enableItemMacro')) await item.executeDLMacro({},{pass : 'preRollAttack', attackAttribute : attackAttribute, defenseAttribute: defenseAttribute, targetActorUuid: defender?.uuid}) + if (itemMacroEnabled) await item.executeDLMacro({},{pass : 'preRollAttack', attackAttribute : attackAttribute, defenseAttribute: defenseAttribute, targetActorUuid: defender?.uuid}) if (game.settings.get('demonlord', 'finesseAutoSelect') && attackAttribute === '' && item.system.properties?.toLowerCase().includes('finesse')) { if (this.system.attributes.strength.value > this.system.attributes.agility.value) attackAttribute = 'strength' @@ -602,7 +603,7 @@ getTargetAttackBane(target) { attackRoll: attackRoll }) - if (game.settings.get('demonlord', 'enableItemMacro')) { + if (itemMacroEnabled) { const successfullHit = (defender && attackRoll?.total >= targetNumber) ? true : false const plus20 = attackRoll?.total >= 20 && (targetNumber ? attackRoll?.total > targetNumber + (game.settings.get('demonlord', 'optionalRuleExceedsByFive') ? 5 : 4) : true) await item.executeDLMacro({},{pass : 'postRollAttack', attackRoll : attackRoll, targetNumber: targetNumber, successfullHit: successfullHit, plus20: plus20, targetActorUuid: defender?.uuid}) @@ -781,12 +782,13 @@ getTargetAttackBane(target) { const talentData = talent.system const targets = tokenManager.targets const target = targets[0] + const itemMacroEnabled = game.settings.get('demonlord', 'enableItemMacro') let attackRoll = null if (!talentData?.action?.attack) { - if (game.settings.get('demonlord', 'enableItemMacro')) await talent.executeDLMacro({},{pass : 'preRollTalent', targetActorUuid: target?.actor.uuid}) + if (itemMacroEnabled) await talent.executeDLMacro({},{pass : 'preRollTalent', targetActorUuid: target?.actor.uuid}) await this.activateTalent(talent, true) - if (game.settings.get('demonlord', 'enableItemMacro')) await talent.executeDLMacro({},{pass : 'postRollTalent', targetActorUuid: target?.actor.uuid}) + if (itemMacroEnabled) await talent.executeDLMacro({},{pass : 'postRollTalent', targetActorUuid: target?.actor.uuid}) } else { await this.activateTalent(talent, Boolean(talentData.action?.damageActive)) @@ -815,7 +817,7 @@ getTargetAttackBane(target) { this.getTargetAttackBane(target.actor)) const boonsReroll = parseInt(this.system.bonuses.rerollBoon1Dice) - if (game.settings.get('demonlord', 'enableItemMacro')) await talent.executeDLMacro({},{pass : 'preRollTalent', attackAttribute: attackAttribute, defenseAttribute: defenseAttribute, targetActorUuid: target?.actor.uuid}) + if (itemMacroEnabled) await talent.executeDLMacro({},{pass : 'preRollTalent', attackAttribute: attackAttribute, defenseAttribute: defenseAttribute, targetActorUuid: target?.actor.uuid}) attackRoll = new Roll(this.rollFormula(modifiers, boons, boonsReroll), this.system) await attackRoll.evaluate() @@ -824,7 +826,7 @@ getTargetAttackBane(target) { if (specialDuration === 'NextD20Roll' || specialDuration === 'NextAttackRoll') await effect?.delete() } - if (game.settings.get('demonlord', 'enableItemMacro')) { + if (itemMacroEnabled) { let successfullHit = false let targetNumber if (targets.length) { @@ -895,10 +897,11 @@ getTargetAttackBane(target) { const spellData = spell.system const attackAttribute = spellData?.action?.attack?.toLowerCase() const defenseAttribute = spellData?.action?.against?.toLowerCase() + const itemMacroEnabled = game.settings.get('demonlord', 'enableItemMacro') let attackRoll const targetActorUuid = (target.length) ? target[0].actor.uuid : null - if (game.settings.get('demonlord', 'enableItemMacro')) await spell.executeDLMacro({},{pass : 'preRollSpell', attackAttribute: attackAttribute, defenseAttribute: defenseAttribute, targetActorUuid: targetActorUuid}) + if (itemMacroEnabled) await spell.executeDLMacro({},{pass : 'preRollSpell', attackAttribute: attackAttribute, defenseAttribute: defenseAttribute, targetActorUuid: targetActorUuid}) if (attackAttribute) { const attacker = this @@ -967,7 +970,7 @@ getTargetAttackBane(target) { ActiveEffect.create(concentrate, {parent: this}) } - if (game.settings.get('demonlord', 'enableItemMacro')) { + if (itemMacroEnabled) { let successfullHit = false let targetNumber if (target.length) { @@ -1038,11 +1041,12 @@ getTargetAttackBane(target) { const targets = tokenManager.targets const target = targets[0] let attackRoll = null + const itemMacroEnabled = game.settings.get('demonlord', 'enableItemMacro') if (!itemData?.action?.attack) { - if (game.settings.get('demonlord', 'enableItemMacro')) await item.executeDLMacro({},{pass : 'preRollItem', targetActorUuid: target?.actor.uuid}) + if (itemMacroEnabled) await item.executeDLMacro({},{pass : 'preRollItem', targetActorUuid: target?.actor.uuid}) postItemToChat(this, item, null, null, null) - if (game.settings.get('demonlord', 'enableItemMacro')) await item.executeDLMacro({},{pass : 'postRollItem', targetActorUuid: target?.actor.uuid}) + if (itemMacroEnabled) await item.executeDLMacro({},{pass : 'postRollItem', targetActorUuid: target?.actor.uuid}) return } else { const attackAttribute = itemData.action.attack.toLowerCase() @@ -1071,7 +1075,7 @@ getTargetAttackBane(target) { const boonsReroll = parseInt(this.system.bonuses.rerollBoon1Dice) - if (game.settings.get('demonlord', 'enableItemMacro')) await item.executeDLMacro({},{pass : 'preRollItem', attackAttribute : attackAttribute, defenseAttribute: defenseAttribute, targetActorUuid: target?.actor.uuid}) + if (itemMacroEnabled) await item.executeDLMacro({},{pass : 'preRollItem', attackAttribute : attackAttribute, defenseAttribute: defenseAttribute, targetActorUuid: target?.actor.uuid}) attackRoll = new Roll(this.rollFormula(modifiers, boons, boonsReroll), this.system) await attackRoll.evaluate() @@ -1079,7 +1083,7 @@ getTargetAttackBane(target) { const specialDuration = foundry.utils.getProperty(effect, `flags.${game.system.id}.specialDuration`) if (specialDuration === 'NextD20Roll' || specialDuration === 'NextAttackRoll') await effect?.delete() } - if (game.settings.get('demonlord', 'enableItemMacro')) { + if (itemMacroEnabled) { const defender = target?.actor const targetNumber = defenseAttribute === 'defense' diff --git a/src/module/chat/effect-messages.js b/src/module/chat/effect-messages.js index 7bbfd77e..c19cafc6 100644 --- a/src/module/chat/effect-messages.js +++ b/src/module/chat/effect-messages.js @@ -158,25 +158,18 @@ export function buildAttackEffectsMessage(attacker, defender, item, attackAttrib let attributeText = 'DL.Attribute' + capitalize(attackAttribute) let attributeModMsg = attributeMod ? _toMsg(`${game.i18n.localize(attributeText)}`, plusify(attributeMod)) : '' - let creatureType - if (game.settings.get('demonlord', 'optionalRuleTraitMode2025')) - creatureType = - defender?.system.frightening && defender?.system.horrifying - ? game.i18n.localize('DL.CreatureHorrifying') - : defender?.system.frightening - ? game.i18n.localize('DL.CreatureFrightening') - : defender?.system.horrifying - ? game.i18n.localize('DL.CreatureHorrifying') - : '' - else - creatureType = - defender?.system.frightening && defender?.system.horrifying - ? game.i18n.localize('DL.CreatureHorrifying') + '/' + game.i18n.localize('DL.CreatureFrightening') - : defender?.system.frightening - ? game.i18n.localize('DL.CreatureFrightening') - : defender?.system.horrifying - ? game.i18n.localize('DL.CreatureHorrifying') - : '' + let creatureType = '' + const isFrightening = defender?.system.frightening + const isHorrifying = defender?.system.horrifying + const isTraitMode2025 = game.settings.get('demonlord', 'optionalRuleTraitMode2025') + + if ((isFrightening || isHorrifying) && isTraitMode2025) { + creatureType = isHorrifying ? game.i18n.localize('DL.CreatureHorrifying') : game.i18n.localize('DL.CreatureFrightening') + } else if (isFrightening && isHorrifying) { + creatureType = game.i18n.localize('DL.CreatureHorrifying') + '/' + game.i18n.localize('DL.CreatureFrightening') + } else if (isHorrifying) { + creatureType = game.i18n.localize('DL.CreatureHorrifying') + } const revealHorrifyingBane = game.settings.get('demonlord', 'optionalRuleRevealHorrifyingBane') // We add the correct effect and its bane(s) to the chatcard. diff --git a/src/module/utils/handlebars-helpers.js b/src/module/utils/handlebars-helpers.js index aef81ce6..75d1d93a 100644 --- a/src/module/utils/handlebars-helpers.js +++ b/src/module/utils/handlebars-helpers.js @@ -120,7 +120,7 @@ export function registerHandlebarsHelpers() { else return game.i18n.localize(tooltip) }) - Handlebars.registerHelper('dlLocalize', function (groupName, str) { + Handlebars.registerHelper('dLocalizeWithSuffix', function (groupName, str) { let result switch (groupName) { case 'WeaponHands': diff --git a/src/templates/actor/tabs/combat.hbs b/src/templates/actor/tabs/combat.hbs index a17f1d46..4d5336d7 100644 --- a/src/templates/actor/tabs/combat.hbs +++ b/src/templates/actor/tabs/combat.hbs @@ -73,7 +73,7 @@ {{/if}} {{#if system.hands}}
- {{localize "DL.WeaponHands"}} {{dlLocalize "WeaponHands" system.hands}} + {{localize "DL.WeaponHands"}} {{dLocalizeWithSuffix "WeaponHands" system.hands}}
{{/if}} {{#if system.description}} diff --git a/src/templates/actor/tabs/magic.hbs b/src/templates/actor/tabs/magic.hbs index 05d20f41..926f801b 100644 --- a/src/templates/actor/tabs/magic.hbs +++ b/src/templates/actor/tabs/magic.hbs @@ -32,7 +32,7 @@
-
{{dlLocalize "SpellType" system.spelltype}}
+
{{dLocalizeWithSuffix "SpellType" system.spelltype}}
{{system.rank}}
{{defaultValue (plusify system.action.boonsbanes) 0}} @@ -60,7 +60,7 @@ {{spell.name}}
- {{spellbook.tradition}} {{dlLocalize "SpellType" system.spelltype}} {{system.rank}} + {{spellbook.tradition}} {{dLocalizeWithSuffix "SpellType" system.spelltype}} {{system.rank}}