diff --git a/src/damage.ts b/src/damage.ts
new file mode 100644
index 0000000000..66439c5847
--- /dev/null
+++ b/src/damage.ts
@@ -0,0 +1,67 @@
+import { Element } from "kolmafia";
+import { $elements } from "./template-string.js";
+import { get as getModifier } from "./modifier.js";
+import { capitalize, sum } from "./utils.js";
+
+export const SUPER_EFFECTIVE_CHART = Object.freeze({
+ hot: $elements`Spooky, Cold`,
+ spooky: $elements`Cold, Sleaze`,
+ cold: $elements`Sleaze, Stench`,
+ sleaze: $elements`Stench, Hot`,
+ stench: $elements`Hot, Spooky`,
+} as const);
+
+function isConventionalElement(
+ name: string,
+): name is keyof typeof SUPER_EFFECTIVE_CHART {
+ return name in SUPER_EFFECTIVE_CHART;
+}
+
+/**
+ * Determines if one type deals super effective damage against the other
+ * @param element The type of the damage
+ * @param against The type of the monster
+ * @returns Whether `element` is super effective against `against`
+ */
+export function isSuperEffective(element: Element, against: Element): boolean {
+ const elementName = element.toString();
+
+ return (
+ isConventionalElement(elementName) &&
+ SUPER_EFFECTIVE_CHART[elementName].includes(against)
+ );
+}
+
+/**
+ * Determines the amount of expected elemental damage, taking into account immunity and super effectiveness.
+ * @param damage The base amount of damage
+ * @param damageType The elemental type of the damage
+ * @param monsterType The elemental type of the target
+ * @returns The expected amount of damage the target would take
+ */
+export function elementalDamage(
+ damage: number,
+ damageType: Element,
+ monsterType: Element,
+): number {
+ if (damage === 0) return 0;
+ if (damageType === monsterType) return 1;
+ if (isSuperEffective(damageType, monsterType)) return 2 * damage;
+ return damage;
+}
+
+export const TRADITIONAL_ELEMENTS = Object.freeze(
+ $elements`cold, hot, sleaze, spooky, stench`,
+);
+
+/**
+ * @returns The total amount of elemental damage you have from all sources
+ */
+export function totalElementalDamage(): number {
+ return sum(
+ TRADITIONAL_ELEMENTS.map((el) => el.toString()).filter(
+ isConventionalElement,
+ ),
+ (el) => getModifier(`${capitalize(el)} Damage`),
+ );
+}
diff --git a/src/utils.ts b/src/utils.ts
index 6d71e56d1d..2ba5c591f2 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -410,6 +410,9 @@ export type Range = Exclude<
Enumerate
>;
+export const capitalize = (str: T): Capitalize =>
+ (str.length ? `${str[0].toUpperCase()}${str.slice(1)}` : "") as Capitalize;
+
/**
* Translate mafia's multi-dimensional array prefs into a multi-dimensional array
* @param prop The property (or whatever) to process; not the property NAME, the value itself
@@ -436,3 +439,4 @@ export function multiSplit(
mappers.map((func, index) => func(tup[index])),
) as T[];
}
+