From 2ce5bd4a19176b8ecb2f1920137eee0caecdf93b Mon Sep 17 00:00:00 2001 From: juanferrer Date: Tue, 2 Dec 2025 21:50:15 +0000 Subject: [PATCH 1/5] Missed commit --- src/module/data/common.js | 8 +++----- src/module/data/item/TalentDataModel.js | 1 + src/module/item/sheets/base-item-sheet.js | 5 ++--- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/module/data/common.js b/src/module/data/common.js index d832db9e..d898d16d 100644 --- a/src/module/data/common.js +++ b/src/module/data/common.js @@ -140,16 +140,14 @@ export function contents() { } export function levelItem(makeDataSchema) { - return new foundry.data.fields.ObjectField({ + return new foundry.data.fields.SchemaField({ system: makeDataSchema(), - description: new foundry.data.fields.SchemaField({ - value: makeStringField() - }), + description: makeStringField(), id: makeStringField(), name: makeStringField(), pack: makeStringField(), selected: makeBoolField(), - uuid: makeStringField(), + uuid: new foundry.data.fields.DocumentUUIDField(), img: makeStringField() }) } diff --git a/src/module/data/item/TalentDataModel.js b/src/module/data/item/TalentDataModel.js index 216cc37e..dba5e172 100644 --- a/src/module/data/item/TalentDataModel.js +++ b/src/module/data/item/TalentDataModel.js @@ -34,6 +34,7 @@ export default class TalentDataModel extends foundry.abstract.TypeDataModel { }), damage: makeStringField(), damagetype: makeStringField(), + extraeffect: makeStringField(), bonuses: new foundry.data.fields.SchemaField({ defenseactive: makeBoolField(true), defense: makeStringField(), diff --git a/src/module/item/sheets/base-item-sheet.js b/src/module/item/sheets/base-item-sheet.js index 9aa59448..3c333a54 100644 --- a/src/module/item/sheets/base-item-sheet.js +++ b/src/module/item/sheets/base-item-sheet.js @@ -15,7 +15,6 @@ import { createActorNestedItems, deleteActorNestedItems, PathLevel, - PathLevelItem, DamageType } from '../nested-objects'; import { DLStatEditor } from '../../dialog/stat-editor' @@ -1002,7 +1001,7 @@ export default class DLBaseItemSheet extends HandlebarsApplicationMixin(ItemShee } async _addItem(data, level, group) { - const levelItem = new PathLevelItem() + const levelItem = {} const itemData = foundry.utils.duplicate(this.document) const item = await getNestedItemData(data) if (!item || ['ancestry', 'path', 'creaturerole'].includes(item.type)) return @@ -1013,7 +1012,7 @@ export default class DLBaseItemSheet extends HandlebarsApplicationMixin(ItemShee levelItem.name = item.name levelItem.description = item.system.description levelItem.pack = data.pack ? data.pack : '' - levelItem.data = item + levelItem.system = item.system levelItem.img = item.img if (this.document.type === 'ancestry' || this.document.type === 'path') { From 74408d927596bfe0d707c090b20de0681639cb98 Mon Sep 17 00:00:00 2001 From: juanferrer Date: Mon, 22 Dec 2025 08:50:20 +0000 Subject: [PATCH 2/5] Add setting to show confirmation dialog when deleting a creature role --- src/lang/en.json | 11 +++++++---- src/lang/es.json | 3 +++ src/module/actor/sheets/creature-sheet.js | 12 +++++++++--- src/module/settings.js | 8 ++++++++ 4 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/lang/en.json b/src/lang/en.json index 6af53757..5b6ca5f8 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -174,9 +174,9 @@ "DL.AvailabilityR": "Rare", "DL.AvailabilityU": "Uncommon", "DL.BackgroundTitle": "Background", - "DL.BecomeFrightenedForRound" : "Your become frightened {round} round.", - "DL.BecomeFrightenedForRounds" : "Your become frightened {round} rounds.", - "DL.BecomeStunned" : "Your become stunned for {round} round.", + "DL.BecomeFrightenedForRound": "Your become frightened {round} round.", + "DL.BecomeFrightenedForRounds": "Your become frightened {round} rounds.", + "DL.BecomeStunned": "Your become stunned for {round} round.", "DL.ButtonEdit": "Edit", "DL.ButtonView": "View", "DL.ChallengeRequestRollText": "Request Challenge Roll", @@ -307,6 +307,7 @@ "DL.DialogDeleteSpellText": "The Spell will be permanently deleted.", "DL.DialogDeleteTraditionText": "The Tradition and all spells attached will be permanently deleted.", "DL.DialogDeleteTraitText": "The Trait will be permanently deleted.", + "DL.DialogDeleteCreatureRoleText": "The Creature Role will be permanently deleted.", "DL.DialogFortuneAwarded": "Awarded a Fortune point.", "DL.DialogFortuneExpended": "Expended a Fortune point.", "DL.DialogHealedFor": "Healed ", @@ -388,7 +389,7 @@ "DL.FeaturesTitle": "Features", "DL.FourOrMoreCreatures": "Four or more creatures at once", "DL.FourOrMoreCreaturesInSight": "Sees four or more creatures at once", - "DL.GainedInsanity" : "You gain {insanity} Insanity.", + "DL.GainedInsanity": "You gain {insanity} Insanity.", "DL.GMTools": "GM Tools", "DL.GMnote": "GM Notes", "DL.GMnoteEdit": "Edit GM Note", @@ -587,6 +588,8 @@ "DL.SettingConcentrationEffectHint": "Apply effect with duration when casting a spell which requires concentration.", "DL.SettingConfirmAncestryPathRemoval": "Confirm ancestry/path removal", "DL.SettingConfirmAncestryPathRemovalHint": "Show a confirmation dialog when attempting to remove an ancestry or path from a character of level 1 or higher.", + "DL.SettingConfirmCreatureRoleRemoval": "Confirm creature role removal", + "DL.SettingConfirmCreatureRoleRemovalHint": "Show a confirmation dialog when attempting to remove a creature role from a character of level 1 or higher.", "DL.SettingConvertIntoBadge": "Convert icons into badges.", "DL.SettingConvertIntoBadgeHint": "Surround ancestry and path icons with a badge background.", "DL.SettingDSN": "Dice So Nice! Configuration", diff --git a/src/lang/es.json b/src/lang/es.json index 4cca37fa..eade8aba 100644 --- a/src/lang/es.json +++ b/src/lang/es.json @@ -302,6 +302,7 @@ "DL.DialogDeleteSpellText": "El Hechizo será borrado permanentemente.", "DL.DialogDeleteTraditionText": "La Tradición y todos los conjuros vinculados serán borrados permanentemente.", "DL.DialogDeleteTraitText": "El Rasgo será borrado permanentemente.", + "DL.DialogDeleteCreatureRoleText": "El Rol de Criatura será borrado permanentemente.", "DL.DialogFortuneAwarded": "Punto de Fortuna otorgado.", "DL.DialogFortuneExpended": "Punto de Fortuna consumido.", "DL.DialogHealedFor": "Curado(s) ", @@ -569,6 +570,8 @@ "DL.SettingConcentrationEffectHint": "Aplicar efecto con la duración cuandose lance un hechizo que requiera concentración.", "DL.SettingConfirmAncestryPathRemoval": "Confirmar eliminación de linaje/senda", "DL.SettingConfirmAncestryPathRemovalHint": "Muestra un diálogo de confirmación al intentar borrar un linaje o una senda de un personaje de nivel 1 o mayor.", + "DL.SettingConfirmCreatureRoleRemoval": "Confirmar eliminación de rol de criatura", + "DL.SettingConfirmCreatureRoleRemovalHint": "Muestra un diálogo de confirmación al intentar borrar un rol de criatura de una criatura.", "DL.SettingConvertIntoBadge": "Convertir en insignia.", "DL.SettingConvertIntoBadgeHint": "Rodear los icono de linaje y senda con un fondo de insignia.", "DL.SettingDSN": "Configuración de Dice So Nice!", diff --git a/src/module/actor/sheets/creature-sheet.js b/src/module/actor/sheets/creature-sheet.js index cb5f1a9a..69ce6128 100644 --- a/src/module/actor/sheets/creature-sheet.js +++ b/src/module/actor/sheets/creature-sheet.js @@ -119,11 +119,17 @@ export default class DLCreatureSheet extends DLBaseActorSheet { /* -------------------------------------------- */ async onEditRole(ev) { - const div = $(ev.currentTarget) - const role = this.actor.getEmbeddedDocument('Item', div.data('itemId')) + const div = ev.target.closest('.role-edit') + const role = this.actor.getEmbeddedDocument('Item', div.dataset.itemId) if (ev.button == 0) role.sheet.render(true) - else if (ev.button == 2) await role.delete({ parent: this.actor }) + else if (ev.button == 2) { + if (game.settings.get('demonlord', 'confirmCreatureRoleRemoval')) { + await this.showDeleteDialog(game.i18n.localize('DL.DialogAreYouSure'), game.i18n.localize('DL.DialogDeleteCreatureRoleText'), div) + } else { + await role.delete({ parent: this.actor }) + } + } } async onEditRelic(ev) { diff --git a/src/module/settings.js b/src/module/settings.js index 1d7318d8..5619fdef 100644 --- a/src/module/settings.js +++ b/src/module/settings.js @@ -841,6 +841,14 @@ export const registerSettings = function () { type: Boolean, config: true, }) + game.settings.register('demonlord', 'confirmCreatureRoleRemoval', { + name: game.i18n.localize('DL.SettingConfirmCreatureRoleRemoval'), + hint: game.i18n.localize('DL.SettingConfirmCreatureRoleRemovalHint'), + default: true, + scope: 'world', + type: Boolean, + config: true, + }) game.settings.register('demonlord', 'fortuneAwardPrevented', { name: game.i18n.localize('DL.SettingFortuneAwardPrevented'), hint: game.i18n.localize('DL.SettingFortuneAwardPreventedHint'), From d41631ad77abca2a19f69783eb51e26695cc7f2f Mon Sep 17 00:00:00 2001 From: juanferrer Date: Mon, 22 Dec 2025 13:37:59 +0000 Subject: [PATCH 3/5] Handle creation/deletion of items embedded in actors that belong to ancestry/path levels --- src/module/actor/actor.js | 80 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/src/module/actor/actor.js b/src/module/actor/actor.js index baa1b5f6..71dd8d07 100644 --- a/src/module/actor/actor.js +++ b/src/module/actor/actor.js @@ -21,6 +21,7 @@ import { import {handleCreateAncestry, handleCreatePath, handleCreateRole, handleCreateRelic } from '../item/nested-objects' import {TokenManager} from '../pixi/token-manager' import {findAddEffect, findDeleteEffect} from "../demonlord"; +import { createActorNestedItems } from '../item/nested-objects' const tokenManager = new TokenManager() @@ -346,8 +347,85 @@ export class DemonlordActor extends Actor { // Don't need to update anything if the only change is the edit item state const isNameChange = documents.length === 1 && data[0].name !== undefined + const documentChanges = {} + + // Make a list of the necessary changes to the levels, which we will update afterwards + for (const sourceDocument of documents) { + if (sourceDocument.type === 'ancestry' || sourceDocument.type === 'path') { + // Property will be the level and value is an array of stuff + const addedItems = {} + let updateDocument = data.find(d => d._id === sourceDocument._id) + + if (!updateDocument.system.levels?.length) continue // Didn't change the levels, nothing to do + + const itemsFromThisDocument = this.items.filter(i => i.flags.demonlord?.parentItemId === sourceDocument._id) + + documentChanges[sourceDocument._id] = { + } + + // Now go through each level and search for the items + for (const level of updateDocument.system.levels.filter(l => parseInt(l.level) <= this.system.level)) { + addedItems[level.level] = { + languages: [], + spells: [], + talents: [], + talentspick: [], + talentsSelected: [] + } + + // Languages + for (const item of level.languages) { + const foundItem = itemsFromThisDocument.find(i => i.flags.demonlord?.levelRequired === level.level && i.flags.demonlord?.nestedItemId === item.id) + + if (!foundItem) { + addedItems[level.level].languages.push(item) + } else { + itemsFromThisDocument.splice(itemsFromThisDocument.indexOf(foundItem), 1) + } + } + + // Spells + for (const item of level.spells) { + const foundItem = itemsFromThisDocument.find(i => i.flags.demonlord?.levelRequired === level.level && i.flags.demonlord?.nestedItemId === item.id) + + if (!foundItem) { + addedItems[level.level].spells.push(item) + } else { + itemsFromThisDocument.splice(itemsFromThisDocument.indexOf(foundItem), 1) + } + } + + // Talents + for (const item of level.talents.concat(level.talentspick, level.talentsSelected)) { + const foundItem = itemsFromThisDocument.find(i => i.flags.demonlord?.levelRequired === level.level && i.flags.demonlord?.nestedItemId === item.id) + + if (!foundItem) { + addedItems[level.level].talents.push(item) + } else { + itemsFromThisDocument.splice(itemsFromThisDocument.indexOf(foundItem), 1) + } + } + + documentChanges[sourceDocument._id][level.level] = { + toRemove: itemsFromThisDocument, + toAdd: addedItems[level.level].languages.concat(addedItems[level.level].spells, addedItems[level.level].talents) + } + } + + this.deleteEmbeddedDocuments('Item', Object.values(documentChanges).map(o => Object.values(o)).flat()[0].toRemove.map(i => i.id)) + + for (const documentSource in documentChanges) { + for (const level in documentChanges[documentSource]) + await createActorNestedItems(this, documentChanges[documentSource][level].toAdd, documentSource, level) + } + } + } + if ((collection === 'items' || collection === 'effects') && userId === game.userId && !options.noEmbedEffects) await this._handleOnUpdateDescendant(documents, isNameChange).then(_ => this.sheet.render()) + + // Now process the changes to the levels + console.log(documentChanges) } async _handleOnUpdateDescendant(documents, isNameChange) { @@ -1059,7 +1137,7 @@ export class DemonlordActor extends Actor { }) postPlainTextToChat(actor,'frightenedForRounds', durationRoll) } else ui.notifications.warn(game.i18n.localize('DL.DialogWarningActorImmune')) - } else + } else { let durationRoll = new Roll(`1d1`) await durationRoll.evaluate() From 34b879efdfd5681bc9a7e14ac4fab6634a5b5d45 Mon Sep 17 00:00:00 2001 From: juanferrer Date: Tue, 23 Dec 2025 08:37:06 +0000 Subject: [PATCH 4/5] WIP: Allow right click on afflictions to toggle immunity --- src/module/actor/actor.js | 3 - src/module/actor/sheets/base-actor-sheet.js | 144 ++++++++++++-------- 2 files changed, 85 insertions(+), 62 deletions(-) diff --git a/src/module/actor/actor.js b/src/module/actor/actor.js index 71dd8d07..228f1054 100644 --- a/src/module/actor/actor.js +++ b/src/module/actor/actor.js @@ -423,9 +423,6 @@ export class DemonlordActor extends Actor { if ((collection === 'items' || collection === 'effects') && userId === game.userId && !options.noEmbedEffects) await this._handleOnUpdateDescendant(documents, isNameChange).then(_ => this.sheet.render()) - - // Now process the changes to the levels - console.log(documentChanges) } async _handleOnUpdateDescendant(documents, isNameChange) { diff --git a/src/module/actor/sheets/base-actor-sheet.js b/src/module/actor/sheets/base-actor-sheet.js index 925d1eb7..1e2c5675 100644 --- a/src/module/actor/sheets/base-actor-sheet.js +++ b/src/module/actor/sheets/base-actor-sheet.js @@ -11,6 +11,7 @@ import { buildDropdownListHover } from "../../utils/handlebars-helpers"; import { DLAfflictions } from '../../active-effects/afflictions' import launchRollDialog from '../../dialog/roll-dialog' import {TokenManager} from '../../pixi/token-manager' +import { capitalize } from '../../utils/utils' const tokenManager = new TokenManager() @@ -477,74 +478,99 @@ export default class DLBaseActorSheet extends HandlebarsApplicationMixin(ActorSh }) // Affliction checkboxes - e.querySelectorAll('[data-tab="afflictions"] .item-group-affliction.checkable')?.forEach(el => el.addEventListener('click', async ev => { + e.querySelectorAll('[data-tab="afflictions"] .item-group-affliction.checkable')?.forEach(el => el.addEventListener('mousedown', async ev => { const input = ev.target.parentElement.firstElementChild const checked = input.checked const afflictionId = input.dataset.name - if (checked) { - input.checked = false - const affliction = this.actor.effects.find(ef => ef?.statuses?.has(afflictionId)) - if (!affliction) return false - await affliction.delete() - } else { - input.checked = true - if (this.actor.isImmuneToAffliction(afflictionId)) { - ui.notifications.warn(game.i18n.localize('DL.DialogWarningActorImmune')); - return false; - } - const affliction = CONFIG.statusEffects.find(a => a.id === afflictionId) - if (!affliction) return false - affliction['statuses'] = [affliction.id] - await ActiveEffect.create(affliction, { parent: this.actor }) - const targets = tokenManager.targets - switch (afflictionId) { + if (ev.button == 0) { + if (checked) { + input.checked = false + const affliction = this.actor.effects.find(ef => ef?.statuses?.has(afflictionId)) + if (!affliction) return false + await affliction.delete() + } else { + input.checked = true + if (this.actor.isImmuneToAffliction(afflictionId)) { + ui.notifications.warn(game.i18n.localize('DL.DialogWarningActorImmune')); + return false; + } + const affliction = CONFIG.statusEffects.find(a => a.id === afflictionId) + if (!affliction) return false + affliction['statuses'] = [affliction.id] + await ActiveEffect.create(affliction, { parent: this.actor }) + const targets = tokenManager.targets + switch (afflictionId) { case 'help': { - const attribute = this.actor.system.attributes.intellect - if (!DLAfflictions.isActorBlocked(this.actor, 'challenge', attribute.key) && targets.length === 1) - launchRollDialog(this.actor.name + ' - ' + game.i18n.localize('DL.DialogChallengeRoll') + attribute.label, async (event, html) => { - let result = await this.actor.rollAttributeChallenge(attribute, html.form.elements.boonsbanes.value, html.form.elements.modifier.value) - if (result._total >= 10 || game.settings.get('demonlord', 'optionalRuleDieRollsMode') === 'b' && result._total >= 11) { - affliction['statuses'] = [affliction.id] - const effect = CONFIG.statusEffects.find(a => a.id === "helped") - effect['statuses'] = [effect.id] - if (game.user.isGM) { - await ActiveEffect.create(effect, { - parent: targets[0].actor - }) - } else { - game.socket.emit('system.demonlord', { - request: "createEffect", - tokenuuid: targets[0].document.uuid, - effectData: effect - }) - } - } - }) - break; + const attribute = this.actor.system.attributes.intellect + if (!DLAfflictions.isActorBlocked(this.actor, 'challenge', attribute.key) && targets.length === 1) + launchRollDialog(this.actor.name + ' - ' + game.i18n.localize('DL.DialogChallengeRoll') + attribute.label, async (event, html) => { + let result = await this.actor.rollAttributeChallenge(attribute, html.form.elements.boonsbanes.value, html.form.elements.modifier.value) + if (result._total >= 10 || game.settings.get('demonlord', 'optionalRuleDieRollsMode') === 'b' && result._total >= 11) { + affliction['statuses'] = [affliction.id] + const effect = CONFIG.statusEffects.find(a => a.id === "helped") + effect['statuses'] = [effect.id] + if (game.user.isGM) { + await ActiveEffect.create(effect, { + parent: targets[0].actor + }) + } else { + game.socket.emit('system.demonlord', { + request: "createEffect", + tokenuuid: targets[0].document.uuid, + effectData: effect + }) + } + } + }) + break; } case 'stabilize': { - const attribute = this.actor.system.attributes.intellect - const isIncapacitated = targets.length === 1 ? targets[0].actor.appliedEffects.find(ef => ef?.statuses?.has('incapacitated')) : false - if (!DLAfflictions.isActorBlocked(this.actor, 'challenge', attribute.key) && isIncapacitated) - launchRollDialog(this.actor.name + ' - ' + game.i18n.localize('DL.DialogChallengeRoll') + attribute.label, async (event, html) => { - let result = await this.actor.rollAttributeChallenge(attribute, html.form.elements.boonsbanes.value, html.form.elements.modifier.value) - if (result._total >= 10 || game.settings.get('demonlord', 'optionalRuleDieRollsMode') === 'b' && result._total >= 11) { - if (game.user.isGM) { - await targets[0].actor.increaseDamage(-1) - } else { - game.socket.emit('system.demonlord', { - request: "increaseDamage", - tokenuuid: targets[0].document.uuid, - increment: -1 - }) - } - } - }) - break; + const attribute = this.actor.system.attributes.intellect + const isIncapacitated = targets.length === 1 ? targets[0].actor.appliedEffects.find(ef => ef?.statuses?.has('incapacitated')) : false + if (!DLAfflictions.isActorBlocked(this.actor, 'challenge', attribute.key) && isIncapacitated) + launchRollDialog(this.actor.name + ' - ' + game.i18n.localize('DL.DialogChallengeRoll') + attribute.label, async (event, html) => { + let result = await this.actor.rollAttributeChallenge(attribute, html.form.elements.boonsbanes.value, html.form.elements.modifier.value) + if (result._total >= 10 || game.settings.get('demonlord', 'optionalRuleDieRollsMode') === 'b' && result._total >= 11) { + if (game.user.isGM) { + await targets[0].actor.increaseDamage(-1) + } else { + game.socket.emit('system.demonlord', { + request: "increaseDamage", + tokenuuid: targets[0].document.uuid, + increment: -1 + }) + } + } + }) + break; + } + } + } + return true + } else if (ev.button == 2) { + // Toggle immunity + const immuneEffects = this.actor.appliedEffects.filter(a => !a.disabled).filter(a => a.changes.some(c => c.key == 'system.bonuses.immune.affliction' && c.value == afflictionId)) + if (immuneEffects?.length) { + await immuneEffects[0].delete() + } else { + await this.actor.createEmbeddedDocuments('ActiveEffect', [ + { + name: capitalize(afflictionId) + ' Immunity', + icon: this.actor.img, + origin: this.actor.uuid, + transfer: false, + flags: { demonlord: { sourceType: this.actor.type } }, + changes: [ + { + key: 'system.bonuses.immune.affliction', + value: afflictionId, + mode: 2 + } + ] } + ]) } } - return true })) // Toggle accordion From 3008ba97dcc9f660bc6adc818ea124a030ab9090 Mon Sep 17 00:00:00 2001 From: juanferrer Date: Mon, 12 Jan 2026 11:46:39 +0000 Subject: [PATCH 5/5] Add indicator for affliction immunity and fix doble application of afflictions --- src/lang/en.json | 2 + src/lang/es.json | 4 +- src/module/actor/sheets/base-actor-sheet.js | 39 ++++++++++++++----- src/styles/v2/_sheets.scss | 13 +++++-- src/templates/actor/tabs/afflictions.hbs | 42 ++++++++++----------- 5 files changed, 65 insertions(+), 35 deletions(-) diff --git a/src/lang/en.json b/src/lang/en.json index 1fb4645f..84c6c424 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -357,6 +357,7 @@ "DL.DialogWarningActorDamageImmune": "Actor is immune to this type of damage.", "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.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.", "DL.DialogWarningDazedFailer": "You're dazed and cannot use actions.", @@ -402,6 +403,7 @@ "DL.ImmuneAffliction": "Immune to Affliction", "DL.ImmuneAttribute": "Immune Attribute", "DL.ImmuneCharacteristic": "Immune Characteristic", + "DL.AfflictionImmunityEffectName": "{affliction} Immunity", "DL.IsFrightening": "Is Frightening", "DL.IsHorrifying": "Is Horrifying", "DL.ItemAddItem": "Add item", diff --git a/src/lang/es.json b/src/lang/es.json index eade8aba..a7b2cfd6 100644 --- a/src/lang/es.json +++ b/src/lang/es.json @@ -345,10 +345,11 @@ "DL.DialogUseItemHealingPotion": "Poción de Curación", "DL.DialogUseItemHealingPotionText": "Cura # puntos de daño.", "DL.DialogUseTalent": "Usar Talento", - "DL.DialogWarningActorImmune": "Eres inmune a este estado.", + "DL.DialogWarningActorImmune": "Eres inmune a esta aflicción.", "DL.DialogWarningActorDamageImmune": "Actor es inmune a este tipo de daño.", "DL.DialogWarningActorsNotSelected": "Actor(es) no seleccionados", "DL.DialogWarningActorsNotTargeted": "Actor(es) no marcado(s) como Objetivo", + "DL.DialogWarningAfflictionFromEffect": "Esta aflicción fue aplicado por un efecto. Elimina el efecto para eliminar la aflicción.", "DL.DialogWarningBlindedChallengeFailer": "Estás cegado y todas tus pruebas de percepción fallan automáticamente.", "DL.DialogWarningCreatureArmor": "No puedes añadir una armadura a una criatura. Cambia el valor de Defensa manualmente.", "DL.DialogWarningDazedFailer": "Estás desorientado y no puedes realizar acciones.", @@ -390,6 +391,7 @@ "DL.ImmuneAffliction": "Inmune a Aflicción", "DL.ImmuneAttribute": "Atributo Inmune", "DL.ImmuneCharacteristic": "Característica Inmune", + "DL.AfflictionImmunityEffectName": "Inmunidad a {affliction}", "DL.IsFrightening": "Es Aterradora", "DL.IsHorrifying": "Es Horripilante", "DL.ItemAddItem": "Añadir Objeto", diff --git a/src/module/actor/sheets/base-actor-sheet.js b/src/module/actor/sheets/base-actor-sheet.js index 1e2c5675..299c934f 100644 --- a/src/module/actor/sheets/base-actor-sheet.js +++ b/src/module/actor/sheets/base-actor-sheet.js @@ -10,8 +10,7 @@ import tippy from "tippy.js"; import { buildDropdownListHover } from "../../utils/handlebars-helpers"; import { DLAfflictions } from '../../active-effects/afflictions' import launchRollDialog from '../../dialog/roll-dialog' -import {TokenManager} from '../../pixi/token-manager' -import { capitalize } from '../../utils/utils' +import { TokenManager } from '../../pixi/token-manager' const tokenManager = new TokenManager() @@ -130,6 +129,7 @@ export default class DLBaseActorSheet extends HandlebarsApplicationMixin(ActorSh context.generalEffects = prepareActiveEffectCategories(Array.from(this.document.allApplicableEffects()), true) context.effectsOverview = buildOverview(this.document) context.flags = this.document.flags + context.immunities = this.getImmunities() context.addCreatureInventoryTab = game.settings.get('demonlord', 'addCreatureInventoryTab') context.hideTurnMode = game.settings.get('demonlord', 'optionalRuleInitiativeMode') === 's' ? false : true context.hideFortune = game.settings.get('demonlord', 'fortuneHide') ? true : false @@ -479,21 +479,30 @@ export default class DLBaseActorSheet extends HandlebarsApplicationMixin(ActorSh // Affliction checkboxes e.querySelectorAll('[data-tab="afflictions"] .item-group-affliction.checkable')?.forEach(el => el.addEventListener('mousedown', async ev => { - const input = ev.target.parentElement.firstElementChild + const input = ev.target.closest('.checkable').querySelector('input') const checked = input.checked const afflictionId = input.dataset.name if (ev.button == 0) { if (checked) { - input.checked = false - const affliction = this.actor.effects.find(ef => ef?.statuses?.has(afflictionId)) - if (!affliction) return false - await affliction.delete() + + // Ignore if there's an active effect that sets this condition (except for the status effects) + const effect = this.actor.appliedEffects.find(ae => ae.statuses.size === 0 && ae.changes.some(c => c.key === 'system.maluses.affliction' && c.value === afflictionId)) + + if (effect) { + ui.notifications.warn(game.i18n.localize('DL.DialogWarningAfflictionFromEffect')) + } else { + input.checked = false + const affliction = this.actor.effects.find(ef => ef?.statuses?.has(afflictionId)) + if (!affliction) return false + await affliction.delete() + } } else { - input.checked = true if (this.actor.isImmuneToAffliction(afflictionId)) { ui.notifications.warn(game.i18n.localize('DL.DialogWarningActorImmune')); return false; } + + input.checked = true const affliction = CONFIG.statusEffects.find(a => a.id === afflictionId) if (!affliction) return false affliction['statuses'] = [affliction.id] @@ -549,13 +558,14 @@ export default class DLBaseActorSheet extends HandlebarsApplicationMixin(ActorSh return true } else if (ev.button == 2) { // Toggle immunity - const immuneEffects = this.actor.appliedEffects.filter(a => !a.disabled).filter(a => a.changes.some(c => c.key == 'system.bonuses.immune.affliction' && c.value == afflictionId)) + const effectName = game.i18n.format('DL.AfflictionImmunityEffectName', { affliction: game.i18n.localize(`DL.${afflictionId}`) }) + const immuneEffects = this.actor.appliedEffects.filter(a => !a.disabled).filter(a => a.name === effectName && a.changes.length === 1 && a.changes[0].key === 'system.bonuses.immune.affliction' && a.changes[0].value === afflictionId) if (immuneEffects?.length) { await immuneEffects[0].delete() } else { await this.actor.createEmbeddedDocuments('ActiveEffect', [ { - name: capitalize(afflictionId) + ' Immunity', + name: effectName, icon: this.actor.img, origin: this.actor.uuid, transfer: false, @@ -862,4 +872,13 @@ export default class DLBaseActorSheet extends HandlebarsApplicationMixin(ActorSh }) return incantation } + + getImmunities() { + const immunities = {} + + for (let immunityEffect of this.actor.appliedEffects.flatMap(e => e.changes).filter(c => c.key === 'system.bonuses.immune.affliction')) { + immunities[immunityEffect.value] = true + } + return immunities + } } diff --git a/src/styles/v2/_sheets.scss b/src/styles/v2/_sheets.scss index 0237330a..380fad1e 100644 --- a/src/styles/v2/_sheets.scss +++ b/src/styles/v2/_sheets.scss @@ -517,9 +517,9 @@ span.vertical-rule { } // Afflictions -$afflictions-demonlord: 'concentrate', 'defend', 'help', 'helped', 'prepare', 'reload', 'retreat', 'rush', 'stabilize', 'charmed', - 'compelled', 'defenseless', 'diseased', 'fatigued', 'horrified', 'grabbed', 'immobilized', 'impaired', 'surprised', - 'slowed', 'injured', 'incapacitated', 'disabled', 'dying', 'surrounded'; +$afflictions-demonlord: 'concentrate', 'defend', 'help', 'helped', 'prepare', 'reload', 'retreat', 'rush', 'stabilize', + 'charmed', 'compelled', 'defenseless', 'diseased', 'fatigued', 'horrified', 'grabbed', 'immobilized', 'impaired', + 'surprised', 'slowed', 'injured', 'incapacitated', 'disabled', 'dying', 'surrounded'; @each $affliction in $afflictions-demonlord { .icon-#{$affliction} { @include demonlord-icon('../assets/icons/effects/#{$affliction}.svg'); @@ -863,6 +863,13 @@ span.text-vs { } } +.checkable.locked::after { + content: '\f023'; // fa-lock + font-family: 'Font Awesome 6 Pro'; + font-weight: 900; + position: absolute; + right: 10px; +} /*******************/ /* Toggle */ /*******************/ diff --git a/src/templates/actor/tabs/afflictions.hbs b/src/templates/actor/tabs/afflictions.hbs index b7eff740..b1abd4b6 100644 --- a/src/templates/actor/tabs/afflictions.hbs +++ b/src/templates/actor/tabs/afflictions.hbs @@ -1,6 +1,6 @@
{{#*inline "dlAffliction"}} -
@@ -30,26 +30,26 @@
- {{> dlAffliction name="asleep" value=flags.demonlord.asleep label="DL.asleep" tooltip="DL.AfflictionsAsleep" }} - {{> dlAffliction name="blinded" value=flags.demonlord.blinded label="DL.blinded" tooltip="DL.AfflictionsBlinded" }} - {{> dlAffliction name="charmed" value=flags.demonlord.charmed label="DL.charmed" tooltip="DL.AfflictionsCharmed" }} - {{> dlAffliction name="compelled" value=flags.demonlord.compelled label="DL.compelled" tooltip="DL.AfflictionsCompelled" }} - {{> dlAffliction name="dazed" value=flags.demonlord.dazed label="DL.dazed" tooltip="DL.AfflictionsDazed" }} - {{> dlAffliction name="deafened" value=flags.demonlord.deafened label="DL.deafened" tooltip="DL.AfflictionsDeafened" }} - {{> dlAffliction name="defenseless" value=flags.demonlord.defenseless label="DL.defenseless" tooltip="DL.AfflictionsDefenseless"}} - {{> dlAffliction name="diseased" value=flags.demonlord.diseased label="DL.diseased" tooltip="DL.AfflictionsDiseased" }} - {{> dlAffliction name="fatigued" value=flags.demonlord.fatigued label="DL.fatigued" tooltip="DL.AfflictionsFatigued" }} - {{> dlAffliction name="frightened" value=flags.demonlord.frightened label="DL.frightened" tooltip="DL.AfflictionsFrightened" }} - {{> dlAffliction name="horrified" value=flags.demonlord.horrified label="DL.horrified" tooltip="DL.AfflictionsHorrified" }} - {{> dlAffliction name="grabbed" value=flags.demonlord.grabbed label="DL.grabbed" tooltip="DL.AfflictionsGrabbed" }} - {{> dlAffliction name="immobilized" value=flags.demonlord.immobilized label="DL.immobilized" tooltip="DL.AfflictionsImmobilized"}} - {{> dlAffliction name="impaired" value=flags.demonlord.impaired label="DL.impaired" tooltip="DL.AfflictionsImpaired" }} - {{> dlAffliction name="poisoned" value=flags.demonlord.poisoned label="DL.poisoned" tooltip="DL.AfflictionsPoisoned" }} - {{> dlAffliction name="prone" value=flags.demonlord.prone label="DL.prone" tooltip="DL.AfflictionsProne" }} - {{> dlAffliction name="slowed" value=flags.demonlord.slowed label="DL.slowed" tooltip="DL.AfflictionsSlowed" }} - {{> dlAffliction name="stunned" value=flags.demonlord.stunned label="DL.stunned" tooltip="DL.AfflictionsStunned" }} - {{> dlAffliction name="surprised" value=flags.demonlord.surprised label="DL.surprised" tooltip="DL.AfflictionsSurprised" }} - {{> dlAffliction name="unconscious" value=flags.demonlord.unconscious label="DL.unconscious" tooltip="DL.AfflictionsUnconscious"}} + {{> dlAffliction name="asleep" value=flags.demonlord.asleep immune=immunities.asleep label="DL.asleep" tooltip="DL.AfflictionsAsleep" }} + {{> dlAffliction name="blinded" value=flags.demonlord.blinded immune=immunities.blinded label="DL.blinded" tooltip="DL.AfflictionsBlinded" }} + {{> dlAffliction name="charmed" value=flags.demonlord.charmed immune=immunities.charmed label="DL.charmed" tooltip="DL.AfflictionsCharmed" }} + {{> dlAffliction name="compelled" value=flags.demonlord.compelled immune=immunities.compelled label="DL.compelled" tooltip="DL.AfflictionsCompelled" }} + {{> dlAffliction name="dazed" value=flags.demonlord.dazed immune=immunities.dazed label="DL.dazed" tooltip="DL.AfflictionsDazed" }} + {{> dlAffliction name="deafened" value=flags.demonlord.deafened immune=immunities.deafened label="DL.deafened" tooltip="DL.AfflictionsDeafened" }} + {{> dlAffliction name="defenseless" value=flags.demonlord.defenseless immune=immunities.defenseless label="DL.defenseless" tooltip="DL.AfflictionsDefenseless"}} + {{> dlAffliction name="diseased" value=flags.demonlord.diseased immune=immunities.diseased label="DL.diseased" tooltip="DL.AfflictionsDiseased" }} + {{> dlAffliction name="fatigued" value=flags.demonlord.fatigued immune=immunities.fatigued label="DL.fatigued" tooltip="DL.AfflictionsFatigued" }} + {{> dlAffliction name="frightened" value=flags.demonlord.frightened immune=immunities.frightened label="DL.frightened" tooltip="DL.AfflictionsFrightened" }} + {{> dlAffliction name="horrified" value=flags.demonlord.horrified immune=immunities.horrified label="DL.horrified" tooltip="DL.AfflictionsHorrified" }} + {{> dlAffliction name="grabbed" value=flags.demonlord.grabbed immune=immunities.grabbed label="DL.grabbed" tooltip="DL.AfflictionsGrabbed" }} + {{> dlAffliction name="immobilized" value=flags.demonlord.immobilized immune=immunities.immobilized label="DL.immobilized" tooltip="DL.AfflictionsImmobilized"}} + {{> dlAffliction name="impaired" value=flags.demonlord.impaired immune=immunities.impaired label="DL.impaired" tooltip="DL.AfflictionsImpaired" }} + {{> dlAffliction name="poisoned" value=flags.demonlord.poisoned immune=immunities.poisoned label="DL.poisoned" tooltip="DL.AfflictionsPoisoned" }} + {{> dlAffliction name="prone" value=flags.demonlord.prone immune=immunities.prone label="DL.prone" tooltip="DL.AfflictionsProne" }} + {{> dlAffliction name="slowed" value=flags.demonlord.slowed immune=immunities.slowed label="DL.slowed" tooltip="DL.AfflictionsSlowed" }} + {{> dlAffliction name="stunned" value=flags.demonlord.stunned immune=immunities.stunned label="DL.stunned" tooltip="DL.AfflictionsStunned" }} + {{> dlAffliction name="surprised" value=flags.demonlord.surprised immune=immunities.surprised label="DL.surprised" tooltip="DL.AfflictionsSurprised" }} + {{> dlAffliction name="unconscious" value=flags.demonlord.unconscious immune=immunities.unconscious label="DL.unconscious" tooltip="DL.AfflictionsUnconscious"}}