added non-profane and all damage resistance and immunity features and skip if damage has vulnerability

This commit is contained in:
Florian Zumpe 2026-05-21 12:32:04 +02:00
parent 54a201d4b1
commit 727606f480
7 changed files with 94 additions and 11 deletions

View File

@ -70,5 +70,13 @@
"ADR.Features.Single.slashing.Name": "Adaptive Resistenz: Hieb", "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.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.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."
} }

View File

@ -70,5 +70,13 @@
"ADR.Features.Single.slashing.Name": "Adaptive Resistance: Slashing", "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.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.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."
} }

View File

@ -4,6 +4,9 @@ export const PACK_ID = `${MODULE_ID}.${PACK_NAME}`;
export const FEATURE_FLAG = "adaptiveResistanceFeature"; export const FEATURE_FLAG = "adaptiveResistanceFeature";
export const EFFECT_FLAG = "adaptiveResistanceEffect"; 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({ export const ADAPTATION_TYPES = Object.freeze({
RESISTANCE: "resistance", RESISTANCE: "resistance",
IMMUNITY: "immunity" IMMUNITY: "immunity"
@ -65,6 +68,18 @@ export const DAMAGE_SETS = Object.freeze({
descriptionKey: "ADR.Features.Set.Profane.Description", descriptionKey: "ADR.Features.Set.Profane.Description",
damageTypes: ["bludgeoning", "piercing", "slashing"] 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: { acid: {
id: "acid", id: "acid",
nameKey: "ADR.Features.Set.Single.acid.Name", nameKey: "ADR.Features.Set.Single.acid.Name",

44
scripts/effects.js vendored
View File

@ -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"; import { getDamageTypeLabel } from "./utils.js";
export async function removeOldAdaptiveEffects(actor) { export async function removeOldAdaptiveEffects(actor) {
@ -8,6 +15,32 @@ export async function removeOldAdaptiveEffects(actor) {
await actor.deleteEmbeddedDocuments("ActiveEffect", oldEffects.map(effect => effect.id)); 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) { export async function createAdaptiveEffect(actor, damageType, adaptationType = ADAPTATION_TYPES.RESISTANCE) {
const config = ADAPTATION_CONFIG[adaptationType] ?? ADAPTATION_CONFIG[ADAPTATION_TYPES.RESISTANCE]; const config = ADAPTATION_CONFIG[adaptationType] ?? ADAPTATION_CONFIG[ADAPTATION_TYPES.RESISTANCE];
const label = getDamageTypeLabel(damageType); const label = getDamageTypeLabel(damageType);
@ -26,14 +59,7 @@ export async function createAdaptiveEffect(actor, damageType, adaptationType = A
} }
} }
}, },
changes: [ changes: getAdaptiveChanges(config, damageType)
{
key: config.traitKey,
mode: CONST.ACTIVE_EFFECT_MODES.ADD,
value: damageType,
priority: 20
}
]
} }
]); ]);
} }

View File

@ -37,6 +37,14 @@ const ENGLISH_SET_TEXT = Object.freeze({
name: "Profane", 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." 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: { acid: {
name: "Acid", name: "Acid",
description: "The creature adapts to acid damage once it truly pierces its defenses." description: "The creature adapts to acid damage once it truly pierces its defenses."

View File

@ -1,6 +1,7 @@
import { MODULE_ID } from "./constants.js"; import { MODULE_ID } from "./constants.js";
import { import {
actorAlreadyProtected, actorAlreadyProtected,
actorVulnerableTo,
getAdaptiveFeatureItems, getAdaptiveFeatureItems,
getCandidatesForActor, getCandidatesForActor,
getDominantDamageCandidate, getDominantDamageCandidate,
@ -32,6 +33,15 @@ Hooks.on("dnd5e.preCalculateDamage", (actor, damages, options = {}) => {
return; 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); const candidate = getDominantDamageCandidate(candidates);
if (!candidate?.type || !candidate?.adaptationType) return; if (!candidate?.type || !candidate?.adaptationType) return;

View File

@ -62,6 +62,14 @@ export function actorImmunityValues(actor) {
return toArrayValue(actor?.system?.traits?.di?.value); 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) { export function actorAlreadyResists(actor, damageType) {
return actorResistanceValues(actor).includes(damageType); return actorResistanceValues(actor).includes(damageType);
} }