diff --git a/lang/de.json b/lang/de.json index a41ef34..369d939 100644 --- a/lang/de.json +++ b/lang/de.json @@ -70,5 +70,13 @@ "ADR.Features.Single.slashing.Name": "Adaptive Resistenz: Hieb", "ADR.Features.Single.slashing.Description": "Das Wesen passt sich an Hiebschaden an, sobald dieser tatsächlich durch seine Abwehr dringt.", "ADR.Features.Single.thunder.Name": "Adaptive Resistenz: Donner", - "ADR.Features.Single.thunder.Description": "Das Wesen passt sich an Donnerschaden an, sobald dieser tatsächlich durch seine Abwehr dringt." + "ADR.Features.Single.thunder.Description": "Das Wesen passt sich an Donnerschaden an, sobald dieser tatsächlich durch seine Abwehr dringt.", + "ADR.Features.Set.NonProfane.Name": "Nicht-Profan", + "ADR.Features.Set.NonProfane.Description": "Das Wesen passt sich an Schaden an, der nicht aus einfacher körperlicher Gewalt entsteht: Säure, Kälte, Feuer, Energie, Blitz, nekrotisch, Gift, psychisch, gleißend und Donner.", + "ADR.Features.Set.All.Name": "Alle Schadensarten", + "ADR.Features.Set.All.Description": "Das Wesen passt sich an jede registrierte dnd5e-Schadensart an, sofern der Schaden tatsächlich durch seine Abwehr dringt.", + "ADR.Features.NonProfane.Name": "Adaptive Resistenz: Nicht-Profan", + "ADR.Features.NonProfane.Description": "Das Wesen passt sich an Schaden an, der nicht aus einfacher körperlicher Gewalt entsteht: Säure, Kälte, Feuer, Energie, Blitz, nekrotisch, Gift, psychisch, gleißend und Donner.", + "ADR.Features.All.Name": "Adaptive Resistenz: Alle Schadensarten", + "ADR.Features.All.Description": "Das Wesen passt sich an jede registrierte dnd5e-Schadensart an, sofern der Schaden tatsächlich durch seine Abwehr dringt." } diff --git a/lang/en.json b/lang/en.json index 12fba31..992b6a5 100644 --- a/lang/en.json +++ b/lang/en.json @@ -70,5 +70,13 @@ "ADR.Features.Single.slashing.Name": "Adaptive Resistance: Slashing", "ADR.Features.Single.slashing.Description": "The creature adapts to slashing damage once it truly pierces its defenses.", "ADR.Features.Single.thunder.Name": "Adaptive Resistance: Thunder", - "ADR.Features.Single.thunder.Description": "The creature adapts to thunder damage once it truly pierces its defenses." + "ADR.Features.Single.thunder.Description": "The creature adapts to thunder damage once it truly pierces its defenses.", + "ADR.Features.Set.NonProfane.Name": "Non-Profane", + "ADR.Features.Set.NonProfane.Description": "The creature adapts to damage that is not simple bodily violence: acid, cold, fire, force, lightning, necrotic, poison, psychic, radiant, and thunder.", + "ADR.Features.Set.All.Name": "All Damage", + "ADR.Features.Set.All.Description": "The creature adapts to any registered dnd5e damage type as long as the damage truly pierces its defenses.", + "ADR.Features.NonProfane.Name": "Adaptive Resistance: Non-Profane", + "ADR.Features.NonProfane.Description": "The creature adapts to damage that is not simple bodily violence: acid, cold, fire, force, lightning, necrotic, poison, psychic, radiant, and thunder.", + "ADR.Features.All.Name": "Adaptive Resistance: All Damage", + "ADR.Features.All.Description": "The creature adapts to any registered dnd5e damage type as long as the damage truly pierces its defenses." } diff --git a/scripts/constants.js b/scripts/constants.js index 9260bc4..9d0322b 100644 --- a/scripts/constants.js +++ b/scripts/constants.js @@ -4,6 +4,9 @@ export const PACK_ID = `${MODULE_ID}.${PACK_NAME}`; export const FEATURE_FLAG = "adaptiveResistanceFeature"; export const EFFECT_FLAG = "adaptiveResistanceEffect"; +export const PROFANE_DAMAGE_TYPES = Object.freeze(["bludgeoning", "piercing", "slashing"]); +export const PROFANE_BYPASSES = Object.freeze(["ada", "mgc", "sil"]); + export const ADAPTATION_TYPES = Object.freeze({ RESISTANCE: "resistance", IMMUNITY: "immunity" @@ -65,6 +68,18 @@ export const DAMAGE_SETS = Object.freeze({ descriptionKey: "ADR.Features.Set.Profane.Description", damageTypes: ["bludgeoning", "piercing", "slashing"] }, + nonProfane: { + id: "nonProfane", + nameKey: "ADR.Features.Set.NonProfane.Name", + descriptionKey: "ADR.Features.Set.NonProfane.Description", + damageTypes: ["acid", "cold", "fire", "force", "lightning", "necrotic", "poison", "psychic", "radiant", "thunder"] + }, + all: { + id: "all", + nameKey: "ADR.Features.Set.All.Name", + descriptionKey: "ADR.Features.Set.All.Description", + damageTypes: ["acid", "bludgeoning", "cold", "fire", "force", "lightning", "necrotic", "piercing", "poison", "psychic", "radiant", "slashing", "thunder"] + }, acid: { id: "acid", nameKey: "ADR.Features.Set.Single.acid.Name", diff --git a/scripts/effects.js b/scripts/effects.js index 0770f67..4673acf 100644 --- a/scripts/effects.js +++ b/scripts/effects.js @@ -1,4 +1,11 @@ -import { MODULE_ID, EFFECT_FLAG, ADAPTATION_CONFIG, ADAPTATION_TYPES } from "./constants.js"; +import { + MODULE_ID, + EFFECT_FLAG, + ADAPTATION_CONFIG, + ADAPTATION_TYPES, + PROFANE_DAMAGE_TYPES, + PROFANE_BYPASSES +} from "./constants.js"; import { getDamageTypeLabel } from "./utils.js"; export async function removeOldAdaptiveEffects(actor) { @@ -8,6 +15,32 @@ export async function removeOldAdaptiveEffects(actor) { await actor.deleteEmbeddedDocuments("ActiveEffect", oldEffects.map(effect => effect.id)); } +function getAdaptiveChanges(config, damageType) { + const changes = [ + { + key: config.traitKey, + mode: CONST.ACTIVE_EFFECT_MODES.ADD, + value: damageType, + priority: 20 + } + ]; + + if (PROFANE_DAMAGE_TYPES.includes(damageType)) { + const bypassKey = config.traitKey.replace(/\.value$/, ".bypasses"); + + for (const bypass of PROFANE_BYPASSES) { + changes.push({ + key: bypassKey, + mode: CONST.ACTIVE_EFFECT_MODES.ADD, + value: bypass, + priority: 20 + }); + } + } + + return changes; +} + export async function createAdaptiveEffect(actor, damageType, adaptationType = ADAPTATION_TYPES.RESISTANCE) { const config = ADAPTATION_CONFIG[adaptationType] ?? ADAPTATION_CONFIG[ADAPTATION_TYPES.RESISTANCE]; const label = getDamageTypeLabel(damageType); @@ -26,14 +59,7 @@ export async function createAdaptiveEffect(actor, damageType, adaptationType = A } } }, - changes: [ - { - key: config.traitKey, - mode: CONST.ACTIVE_EFFECT_MODES.ADD, - value: damageType, - priority: 20 - } - ] + changes: getAdaptiveChanges(config, damageType) } ]); } diff --git a/scripts/features.js b/scripts/features.js index bf8408c..67fbd84 100644 --- a/scripts/features.js +++ b/scripts/features.js @@ -37,6 +37,14 @@ const ENGLISH_SET_TEXT = Object.freeze({ name: "Profane", description: "The creature adapts to bodily violence from bludgeoning, piercing, and slashing harm. This follows the dnd5e damage types and does not reliably distinguish magical from nonmagical weapons." }, + nonProfane: { + name: "Non-Profane", + description: "The creature adapts to damage that is not simple bodily violence: acid, cold, fire, force, lightning, necrotic, poison, psychic, radiant, and thunder." + }, + all: { + name: "All Damage", + description: "The creature adapts to any registered dnd5e damage type as long as the damage truly pierces its defenses." + }, acid: { name: "Acid", description: "The creature adapts to acid damage once it truly pierces its defenses." diff --git a/scripts/hooks.js b/scripts/hooks.js index 5ccef55..9c12377 100644 --- a/scripts/hooks.js +++ b/scripts/hooks.js @@ -1,6 +1,7 @@ import { MODULE_ID } from "./constants.js"; import { actorAlreadyProtected, + actorVulnerableTo, getAdaptiveFeatureItems, getCandidatesForActor, getDominantDamageCandidate, @@ -32,6 +33,15 @@ Hooks.on("dnd5e.preCalculateDamage", (actor, damages, options = {}) => { return; } + const vulnerable = candidates.some(candidate => actorVulnerableTo(actor, candidate.type)); + if (vulnerable) { + storeDamageSelection(options, { + skip: true, + reason: "damage-has-vulnerability" + }); + return; + } + const candidate = getDominantDamageCandidate(candidates); if (!candidate?.type || !candidate?.adaptationType) return; diff --git a/scripts/utils.js b/scripts/utils.js index 03cfe0f..4ccdf5f 100644 --- a/scripts/utils.js +++ b/scripts/utils.js @@ -62,6 +62,14 @@ export function actorImmunityValues(actor) { return toArrayValue(actor?.system?.traits?.di?.value); } +export function actorVulnerabilityValues(actor) { + return toArrayValue(actor?.system?.traits?.dv?.value); +} + +export function actorVulnerableTo(actor, damageType) { + return actorVulnerabilityValues(actor).includes(damageType); +} + export function actorAlreadyResists(actor, damageType) { return actorResistanceValues(actor).includes(damageType); }