diff --git a/README.md b/README.md index fad4421..e7f9679 100644 --- a/README.md +++ b/README.md @@ -21,13 +21,13 @@ baseMobXp = mobLevel^2 * ZEM - *A Froglok* — a level 3 mob in **Innothule Swamp**: ``` - baseMobXp = 3^2 * ZEM + baseMobXp = 3^2 * ZEM = 9 * ZEM ``` - *Lord Bob* — a level 65 mob in **Velk's Labyrinth** (Velketor's Labyrinth): ``` - baseMobXp = 65^2 * ZEM + baseMobXp = 65^2 * ZEM = 4,225 * ZEM ``` These are the base values before the group bonus, consider modifier, and 11% diff --git a/src/award.js b/src/award.js index 0022981..d4f0ed9 100644 --- a/src/award.js +++ b/src/award.js @@ -21,6 +21,8 @@ const PER_MOB_CAP = 0.11; * @property {number} share proportional share of the party total (0-1) * @property {number} xp XP this character receives for the kill (capped) * @property {boolean} capApplied true if the 11% per-mob cap clamped this slice + * @property {boolean} eligible false when the character is too far below the + * group's highest level to receive any XP */ /** @@ -54,6 +56,7 @@ export function awardXp(party, mobLevel, zem) { share: a.share, xp: capApplied ? cap : uncapped, capApplied, + eligible: a.eligible, }); }), ), diff --git a/src/kills.js b/src/kills.js index c3df5a3..887fa6e 100644 --- a/src/kills.js +++ b/src/kills.js @@ -15,6 +15,8 @@ import { awardXp } from "./award.js"; * @property {number} remaining XP still needed to reach the next level * @property {number} kills kills needed (Infinity if xpPerKill is 0) * @property {boolean} capApplied true if the 11% per-mob cap clamped xpPerKill + * @property {boolean} eligible false when the character is too far below the + * group's highest level to receive any XP */ /** @@ -50,6 +52,7 @@ export function killsToNextLevel(party, mobLevel, zem) { remaining, kills, capApplied: a.capApplied, + eligible: a.eligible, }); }); diff --git a/src/split.js b/src/split.js index f54baa0..33f4ede 100644 --- a/src/split.js +++ b/src/split.js @@ -26,6 +26,8 @@ import { groupXpEligibility } from "./eligibility.js"; * @typedef {Object} Allocation * @property {object} character the character this share belongs to * @property {number} share fraction of the XP pool (0-1); shares sum to 1 + * @property {boolean} eligible false when the character is too far below the + * group's highest level to receive any XP (groupXpEligibility check) */ /** @@ -46,15 +48,20 @@ export function splitXp(party) { } const { characters, penaltiesInEffect, maxLevel } = party; - const weights = characters.map((c) => { - if (!groupXpEligibility(c.level, maxLevel)) return 0; + const eligible = characters.map((c) => groupXpEligibility(c.level, maxLevel)); + const weights = characters.map((c, i) => { + if (!eligible[i]) return 0; return penaltiesInEffect ? c.xpToNextLevel : totalXpToLevel(c.level, 1); }); const total = weights.reduce((sum, w) => sum + w, 0); return Object.freeze( characters.map((c, i) => - Object.freeze({ character: c, share: weights[i] / total }), + Object.freeze({ + character: c, + share: weights[i] / total, + eligible: eligible[i], + }), ), ); } diff --git a/src/ui.js b/src/ui.js index e292316..dc27914 100644 --- a/src/ui.js +++ b/src/ui.js @@ -165,6 +165,13 @@ function refresh() { if (player.capApplied) { r.gk.appendChild(el("span", { class: "cap-flag" }, " *")); r.gk.title = "11% per-mob cap applied — excess XP is lost"; + } else if (!player.eligible) { + r.gk.appendChild(el("span", { class: "cap-flag" }, " †")); + r.gk.title = + "Character is too far below the highest party member to receive XP"; + } else if (player.xpPerKill === 0) { + r.gk.appendChild(el("span", { class: "cap-flag" }, " ‡")); + r.gk.title = "Mob cons green to the highest party member — no XP awarded"; } else { r.gk.title = ""; } @@ -177,12 +184,16 @@ function refresh() { r.xr.textContent = fmtNum(player.remaining) + " XP"; r.xc.textContent = fmtNum(player.character.xpToNextLevel) + " XP"; - r.kl.textContent = Number.isFinite(player.kills) - ? fmtNum(player.kills) - : dash; - r.tm.textContent = Number.isFinite(player.kills) - ? fmtMins(player.kills * state.enc.minutesPerKill) - : dash; + if (Number.isFinite(player.kills)) { + r.kl.textContent = fmtNum(player.kills); + r.tm.textContent = fmtMins(player.kills * state.enc.minutesPerKill); + } else if (!player.eligible) { + r.kl.textContent = "—†"; + r.tm.textContent = "—†"; + } else { + r.kl.textContent = "—‡"; + r.tm.textContent = "—‡"; + } }); // Totals row.