MediaWiki:ClassMasteryCalculator.js: Difference between revisions
From EQOA Sandstorm Wiki
Jump to navigationJump to search
RenodorDev (talk | contribs) No edit summary |
RenodorDev (talk | contribs) No edit summary |
||
| Line 1: | Line 1: | ||
mw.hook('wikipage.content').add(function () { | mw.hook('wikipage.content').add(function () { | ||
| Line 29: | Line 26: | ||
if (!app) return; | if (!app) return; | ||
var data = window.EQOA_CM_DATA; | var data = window.EQOA_CM_DATA || {}; | ||
var selectedMasteries = {}; | var selectedMasteries = {}; | ||
var activeCmTab = "General"; | var activeCmTab = "General"; | ||
app.innerHTML = | app.innerHTML = | ||
<div class="cm-calculator-box"> | '<div class="cm-calculator-box">' + | ||
<div class="character-builder-title">Class Mastery Calculator</div> | '<div class="character-builder-title">Class Mastery Calculator</div>' + | ||
<div class="character-builder-controls"> | '<div class="character-builder-controls">' + | ||
<label>Race</label> | '<label>Race</label>' + | ||
<select id="cm-race"></select> | '<select id="cm-race"></select>' + | ||
<label>Class</label> | '<label>Class</label>' + | ||
<select id="cm-class"></select> | '<select id="cm-class"></select>' + | ||
<label>Character Level</label> | '<label>Character Level</label>' + | ||
<input type="number" id="cm-level" min="1" max="60" value="60" style="width:70px;"> | '<input type="number" id="cm-level" min="1" max="60" value="60" style="width:70px;">' + | ||
<label>Transformation Path</label> | '<label>Transformation Path</label>' + | ||
<select id="cm-path"> | '<select id="cm-path">' + | ||
<option value="">None</option> | '<option value="">None</option>' + | ||
<option value="Werelion">Lionwere</option> | '<option value="Werelion">Lionwere</option>' + | ||
<option value="Werebear">Bearwere</option> | '<option value="Werebear">Bearwere</option>' + | ||
<option value="Werewolf">Wolfwere</option> | '<option value="Werewolf">Wolfwere</option>' + | ||
<option value="Wererat">Ratwere</option> | '<option value="Wererat">Ratwere</option>' + | ||
<option value="Weregator">Gatorwere</option> | '<option value="Weregator">Gatorwere</option>' + | ||
<option value="Vampire">Vampire</option> | '<option value="Vampire">Vampire</option>' + | ||
<option value="Were Hunter">Were-Hunter</option> | '<option value="Were Hunter">Were-Hunter</option>' + | ||
</select> | '</select>' + | ||
</div> | '</div>' + | ||
<div class="character-builder-section-title" style="margin-top:15px;">Point Summary</div> | '<div class="character-builder-section-title" style="margin-top:15px;">Point Summary</div>' + | ||
<table class="character-builder-stat-table"> | '<table class="character-builder-stat-table">' + | ||
<tr> | '<tr>' + | ||
<th>Available</th> | '<th>Available</th>' + | ||
<th>Spent</th> | '<th>Spent</th>' + | ||
<th>Remaining</th> | '<th>Remaining</th>' + | ||
</tr> | '</tr>' + | ||
<tr> | '<tr>' + | ||
<td id="cm-available">1500</td> | '<td id="cm-available">1500</td>' + | ||
<td id="cm-spent">0</td> | '<td id="cm-spent">0</td>' + | ||
<td id="cm-remaining">1500</td> | '<td id="cm-remaining">1500</td>' + | ||
</tr> | '</tr>' + | ||
</table> | '</table>' + | ||
<div class="character-builder-section-title" style="margin-top:18px;">Class Masteries</div> | '<div class="character-builder-section-title" style="margin-top:18px;">Class Masteries</div>' + | ||
<div class="cm-tabs"> | '<div class="cm-tabs">' + | ||
<button type="button" class="cm-tab-button cm-tab-active" data-tab="General">General</button> | '<button type="button" class="cm-tab-button cm-tab-active" data-tab="General">General</button>' + | ||
<button type="button" class="cm-tab-button" data-tab="Racial">Racial</button> | '<button type="button" class="cm-tab-button" data-tab="Racial">Racial</button>' + | ||
<button type="button" class="cm-tab-button" data-tab="Archetype">Archetype</button> | '<button type="button" class="cm-tab-button" data-tab="Archetype">Archetype</button>' + | ||
<button type="button" class="cm-tab-button" data-tab="Class">Class</button> | '<button type="button" class="cm-tab-button" data-tab="Class">Class</button>' + | ||
</div> | '</div>' + | ||
<div class="cm-main-layout"> | '<div class="cm-main-layout">' + | ||
<div class="cm-purchased-panel"> | '<div class="cm-purchased-panel">' + | ||
<div class="cm-panel-title">CM Purchased</div> | '<div class="cm-panel-title">CM Purchased</div>' + | ||
<div id="cm-purchased-list">None purchased.</div> | '<div id="cm-purchased-list">None purchased.</div>' + | ||
</div> | '</div>' + | ||
<div id="cm-mastery-panel" class="character-builder-warning-box"> | '<div id="cm-mastery-panel" class="character-builder-warning-box">' + | ||
Select a race and class to begin. | 'Select a race and class to begin.' + | ||
</div> | '</div>' + | ||
</div> | '</div>' + | ||
<div id="cm-tooltip" class="cm-tooltip" style="display:none;"></div> | '<div id="cm-tooltip" class="cm-tooltip" style="display:none;"></div>' + | ||
</div> | '</div>'; | ||
var raceSelect = document.getElementById('cm-race'); | var raceSelect = document.getElementById('cm-race'); | ||
| Line 104: | Line 100: | ||
var pathSelect = document.getElementById('cm-path'); | var pathSelect = document.getElementById('cm-path'); | ||
var cmTooltip = document.getElementById('cm-tooltip'); | var cmTooltip = document.getElementById('cm-tooltip'); | ||
function safeArray(value) { | |||
return Array.isArray(value) ? value : []; | |||
} | |||
function escapeHtml(value) { | |||
return String(value || '') | |||
.replace(/&/g, '&') | |||
.replace(/</g, '<') | |||
.replace(/>/g, '>') | |||
.replace(/"/g, '"') | |||
.replace(/'/g, '''); | |||
} | |||
function addOption(select, value, label) { | function addOption(select, value, label) { | ||
| Line 140: | Line 149: | ||
return map[value] || value; | return map[value] || value; | ||
} | |||
function findMasteryByName(name) { | |||
var masteries = safeArray(data.masteries); | |||
var i; | |||
for (i = 0; i < masteries.length; i++) { | |||
if (masteries[i].name === name) { | |||
return masteries[i]; | |||
} | |||
} | |||
return null; | |||
} | } | ||
| Line 145: | Line 167: | ||
raceSelect.innerHTML = ''; | raceSelect.innerHTML = ''; | ||
Object.keys(data.raceClasses).forEach(function (race) { | Object.keys(data.raceClasses || {}).forEach(function (race) { | ||
addOption(raceSelect, race, race); | addOption(raceSelect, race, race); | ||
}); | }); | ||
| Line 156: | Line 178: | ||
var race = raceSelect.value; | var race = raceSelect.value; | ||
var classList = data.raceClasses | var classList = safeArray((data.raceClasses || {})[race]); | ||
classList.forEach(function (classCode) { | classList.forEach(function (classCode) { | ||
| Line 162: | Line 184: | ||
classSelect, | classSelect, | ||
classCode, | classCode, | ||
classCode + ' - ' + (data.classes[classCode] || classCode) | classCode + ' - ' + ((data.classes || {})[classCode] || classCode) | ||
); | ); | ||
}); | }); | ||
| Line 209: | Line 231: | ||
return ( | return ( | ||
'<div class="cm-purchased-item" data-cm-name="' + name | '<div class="cm-purchased-item" data-cm-name="' + escapeHtml(name) + '">' + | ||
'<span>' + name + '</span>' + | '<span>' + escapeHtml(name) + '</span>' + | ||
'<span class="cm-purchased-cost">' + cm.points + '</span>' + | '<span class="cm-purchased-cost">' + escapeHtml(cm.points) + '</span>' + | ||
'</div>' | '</div>' | ||
); | ); | ||
| Line 260: | Line 282: | ||
statLines.push( | statLines.push( | ||
'<div class="cm-tooltip-stat">' + | '<div class="cm-tooltip-stat">' + | ||
key + ': ' + sign + value + | escapeHtml(key) + ': ' + sign + escapeHtml(value) + | ||
'</div>' | '</div>' | ||
); | ); | ||
| Line 266: | Line 288: | ||
}); | }); | ||
var spellAdds = (cm.spellsAdded | var spellAdds = safeArray(cm.spellsAdded).map(function (spell) { | ||
return '<div class="cm-tooltip-spell-add">+ ' + spell + '</div>'; | return '<div class="cm-tooltip-spell-add">+ ' + escapeHtml(spell) + '</div>'; | ||
}).join(''); | }).join(''); | ||
var spellRemoves = (cm.spellsRemoved | var spellRemoves = safeArray(cm.spellsRemoved).map(function (spell) { | ||
return '<div class="cm-tooltip-spell-remove">- ' + spell + '</div>'; | return '<div class="cm-tooltip-spell-remove">- ' + escapeHtml(spell) + '</div>'; | ||
}).join(''); | }).join(''); | ||
var reqHtml = ''; | var reqHtml = ''; | ||
if ((cm.requirements | if (safeArray(cm.requirements).length) { | ||
reqHtml = | reqHtml = | ||
'<div class="cm-tooltip-req">' + | '<div class="cm-tooltip-req">' + | ||
'Requires: ' + cm.requirements.join(', ') + | 'Requires: ' + escapeHtml(safeArray(cm.requirements).join(', ')) + | ||
'</div>'; | '</div>'; | ||
} | } | ||
return ( | return ( | ||
'<div class="cm-tooltip-title">' + cm.name + '</div>' + | '<div class="cm-tooltip-title">' + escapeHtml(cm.name) + '</div>' + | ||
'<div class="cm-tooltip-desc">' + cm.description + '</div>' + | '<div class="cm-tooltip-desc">' + escapeHtml(cm.description) + '</div>' + | ||
'<div class="cm-tooltip-meta">' + | '<div class="cm-tooltip-meta">' + | ||
'Level ' + cm.level + | 'Level ' + escapeHtml(cm.level) + | ||
' | Cost ' + cm.points + | ' | Cost ' + escapeHtml(cm.points) + | ||
(cm.cmSpentReq ? ' | CM Spent Req ' + cm.cmSpentReq : '') + | (cm.cmSpentReq ? ' | CM Spent Req ' + escapeHtml(cm.cmSpentReq) : '') + | ||
'</div>' + | '</div>' + | ||
reqHtml + | reqHtml + | ||
| Line 327: | Line 349: | ||
var selectedRace = raceSelect.value; | var selectedRace = raceSelect.value; | ||
var selectedRaceCode = data.raceCodes[selectedRace]; | var selectedRaceCode = (data.raceCodes || {})[selectedRace]; | ||
var selectedClass = classSelect.value; | var selectedClass = classSelect.value; | ||
var level = parseInt(levelInput.value, 10) || 1; | var level = parseInt(levelInput.value, 10) || 1; | ||
| Line 345: | Line 367: | ||
werePaths.indexOf(selectedPathNormalized) !== -1; | werePaths.indexOf(selectedPathNormalized) !== -1; | ||
var filtered = (data.masteries | var filtered = safeArray(data.masteries).filter(function (cm) { | ||
var cmRaces = safeArray(cm.races); | |||
var cmClasses = safeArray(cm.classes); | |||
var reqs = safeArray(cm.requirements); | |||
var raceOk = | var raceOk = | ||
! | !cmRaces.length || | ||
cmRaces.indexOf(selectedRaceCode) !== -1; | |||
var classOk = | var classOk = | ||
! | !cmClasses.length || | ||
cmClasses.indexOf(selectedClass) !== -1; | |||
var levelOk = | var levelOk = | ||
| Line 361: | Line 387: | ||
!cm.cmSpentReq || | !cm.cmSpentReq || | ||
spent >= cm.cmSpentReq; | spent >= cm.cmSpentReq; | ||
var reqOk = reqs.every(function (reqName) { | var reqOk = reqs.every(function (reqName) { | ||
| Line 384: | Line 408: | ||
} | } | ||
var notPurchased = | var notPurchased = !selectedMasteries[cm.name]; | ||
return raceOk && | return raceOk && | ||
| Line 402: | Line 425: | ||
} | } | ||
masteryPanel.innerHTML = filtered.map(function (cm) { | |||
return ( | |||
'<div class="cm-card cm-purchasable-card" data-cm-name="' + escapeHtml(cm.name) + '">' + | |||
'<div class="cm-card-title">' + escapeHtml(cm.name) + '</div>' + | |||
'<div class="cm-card cm-purchasable-card" data-cm-name="' + cm.name | '<div class="cm-card-desc">' + escapeHtml(cm.description) + '</div>' + | ||
'<div class="cm-card-title">' + cm.name + '</div>' + | |||
'<div class="cm-card-desc">' + cm.description + '</div>' + | |||
'<div class="cm-card-meta">' + | '<div class="cm-card-meta">' + | ||
'Level: ' + cm.level + | 'Level: ' + escapeHtml(cm.level) + | ||
' | Cost: ' + cm.points + | ' | Cost: ' + escapeHtml(cm.points) + | ||
(cm.cmSpentReq ? ' | CM Spent Req: ' + cm.cmSpentReq : '') + | (cm.cmSpentReq ? ' | CM Spent Req: ' + escapeHtml(cm.cmSpentReq) : '') + | ||
'</div>' + | '</div>' + | ||
'</div>'; | '</div>' | ||
}) | ); | ||
}).join(''); | |||
Array.prototype.forEach.call( | Array.prototype.forEach.call( | ||
| Line 423: | Line 443: | ||
function (card) { | function (card) { | ||
var cmName = card.getAttribute('data-cm-name'); | var cmName = card.getAttribute('data-cm-name'); | ||
var cm = findMasteryByName(cmName); | |||
var cm = ( | |||
card.addEventListener('mouseenter', function () { | card.addEventListener('mouseenter', function () { | ||
| Line 448: | Line 465: | ||
hideCmTooltip(); | hideCmTooltip(); | ||
updateSummary(); | updateSummary(); | ||
}); | }); | ||
| Line 487: | Line 503: | ||
}); | }); | ||
Revision as of 20:19, 14 May 2026
mw.hook('wikipage.content').add(function () {
function waitForCMData(attemptsLeft) {
if (window.EQOA_CM_DATA) {
initClassMasteryCalculator();
return;
}
if (attemptsLeft <= 0) {
var app = document.getElementById('cm-calculator-app');
if (app) {
app.innerHTML = '<div style="color:red;">EQOA_CM_DATA failed to load.</div>';
}
return;
}
setTimeout(function () {
waitForCMData(attemptsLeft - 1);
}, 100);
}
waitForCMData(30);
function initClassMasteryCalculator() {
var app = document.getElementById('cm-calculator-app');
if (!app) return;
var data = window.EQOA_CM_DATA || {};
var selectedMasteries = {};
var activeCmTab = "General";
app.innerHTML =
'<div class="cm-calculator-box">' +
'<div class="character-builder-title">Class Mastery Calculator</div>' +
'<div class="character-builder-controls">' +
'<label>Race</label>' +
'<select id="cm-race"></select>' +
'<label>Class</label>' +
'<select id="cm-class"></select>' +
'<label>Character Level</label>' +
'<input type="number" id="cm-level" min="1" max="60" value="60" style="width:70px;">' +
'<label>Transformation Path</label>' +
'<select id="cm-path">' +
'<option value="">None</option>' +
'<option value="Werelion">Lionwere</option>' +
'<option value="Werebear">Bearwere</option>' +
'<option value="Werewolf">Wolfwere</option>' +
'<option value="Wererat">Ratwere</option>' +
'<option value="Weregator">Gatorwere</option>' +
'<option value="Vampire">Vampire</option>' +
'<option value="Were Hunter">Were-Hunter</option>' +
'</select>' +
'</div>' +
'<div class="character-builder-section-title" style="margin-top:15px;">Point Summary</div>' +
'<table class="character-builder-stat-table">' +
'<tr>' +
'<th>Available</th>' +
'<th>Spent</th>' +
'<th>Remaining</th>' +
'</tr>' +
'<tr>' +
'<td id="cm-available">1500</td>' +
'<td id="cm-spent">0</td>' +
'<td id="cm-remaining">1500</td>' +
'</tr>' +
'</table>' +
'<div class="character-builder-section-title" style="margin-top:18px;">Class Masteries</div>' +
'<div class="cm-tabs">' +
'<button type="button" class="cm-tab-button cm-tab-active" data-tab="General">General</button>' +
'<button type="button" class="cm-tab-button" data-tab="Racial">Racial</button>' +
'<button type="button" class="cm-tab-button" data-tab="Archetype">Archetype</button>' +
'<button type="button" class="cm-tab-button" data-tab="Class">Class</button>' +
'</div>' +
'<div class="cm-main-layout">' +
'<div class="cm-purchased-panel">' +
'<div class="cm-panel-title">CM Purchased</div>' +
'<div id="cm-purchased-list">None purchased.</div>' +
'</div>' +
'<div id="cm-mastery-panel" class="character-builder-warning-box">' +
'Select a race and class to begin.' +
'</div>' +
'</div>' +
'<div id="cm-tooltip" class="cm-tooltip" style="display:none;"></div>' +
'</div>';
var raceSelect = document.getElementById('cm-race');
var classSelect = document.getElementById('cm-class');
var levelInput = document.getElementById('cm-level');
var pathSelect = document.getElementById('cm-path');
var cmTooltip = document.getElementById('cm-tooltip');
function safeArray(value) {
return Array.isArray(value) ? value : [];
}
function escapeHtml(value) {
return String(value || '')
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
function addOption(select, value, label) {
var option = document.createElement('option');
option.value = value;
option.textContent = label;
select.appendChild(option);
}
function normalizePath(value) {
value = String(value || "").toLowerCase().replace(/[\s-]/g, "");
var map = {
lionwere: "werelion",
werelion: "werelion",
bearwere: "werebear",
werebear: "werebear",
wolfwere: "werewolf",
werewolf: "werewolf",
ratwere: "wererat",
wererat: "wererat",
gatorwere: "weregator",
weregator: "weregator",
vampire: "vampire",
werehunter: "werehunter",
hunterwere: "werehunter",
werebeast: "werebeast"
};
return map[value] || value;
}
function findMasteryByName(name) {
var masteries = safeArray(data.masteries);
var i;
for (i = 0; i < masteries.length; i++) {
if (masteries[i].name === name) {
return masteries[i];
}
}
return null;
}
function loadRaces() {
raceSelect.innerHTML = '';
Object.keys(data.raceClasses || {}).forEach(function (race) {
addOption(raceSelect, race, race);
});
loadClasses();
}
function loadClasses() {
classSelect.innerHTML = '';
var race = raceSelect.value;
var classList = safeArray((data.raceClasses || {})[race]);
classList.forEach(function (classCode) {
addOption(
classSelect,
classCode,
classCode + ' - ' + ((data.classes || {})[classCode] || classCode)
);
});
updateSummary();
}
function getAvailablePoints() {
var level = parseInt(levelInput.value, 10);
if (isNaN(level) || level < 1) level = 1;
if (level > 60) level = 60;
levelInput.value = level;
return level * 25;
}
function getSpentPoints() {
var total = 0;
Object.keys(selectedMasteries).forEach(function (name) {
if (selectedMasteries[name]) {
total += selectedMasteries[name].points || 0;
}
});
return total;
}
function updatePurchasedList() {
var list = document.getElementById('cm-purchased-list');
if (!list) return;
var purchased = Object.keys(selectedMasteries).filter(function (name) {
return selectedMasteries[name];
});
if (!purchased.length) {
list.innerHTML = 'None purchased.';
return;
}
list.innerHTML = purchased.map(function (name) {
var cm = selectedMasteries[name];
return (
'<div class="cm-purchased-item" data-cm-name="' + escapeHtml(name) + '">' +
'<span>' + escapeHtml(name) + '</span>' +
'<span class="cm-purchased-cost">' + escapeHtml(cm.points) + '</span>' +
'</div>'
);
}).join('');
Array.prototype.forEach.call(
list.querySelectorAll('.cm-purchased-item'),
function (row) {
var cmName = row.getAttribute('data-cm-name');
var cm = selectedMasteries[cmName];
row.addEventListener('mouseenter', function () {
showCmTooltip(cm, row);
});
row.addEventListener('mouseleave', hideCmTooltip);
row.addEventListener('click', function () {
delete selectedMasteries[cmName];
hideCmTooltip();
updateSummary();
});
}
);
}
function updateSummary() {
var available = getAvailablePoints();
var spent = getSpentPoints();
var remaining = available - spent;
document.getElementById('cm-available').textContent = available;
document.getElementById('cm-spent').textContent = spent;
document.getElementById('cm-remaining').textContent = remaining;
updatePurchasedList();
renderMasteryList();
}
function buildCmTooltipHtml(cm) {
var statLines = [];
Object.keys(cm.stats || {}).forEach(function (key) {
var value = cm.stats[key];
if (value) {
var sign = value > 0 ? '+' : '';
statLines.push(
'<div class="cm-tooltip-stat">' +
escapeHtml(key) + ': ' + sign + escapeHtml(value) +
'</div>'
);
}
});
var spellAdds = safeArray(cm.spellsAdded).map(function (spell) {
return '<div class="cm-tooltip-spell-add">+ ' + escapeHtml(spell) + '</div>';
}).join('');
var spellRemoves = safeArray(cm.spellsRemoved).map(function (spell) {
return '<div class="cm-tooltip-spell-remove">- ' + escapeHtml(spell) + '</div>';
}).join('');
var reqHtml = '';
if (safeArray(cm.requirements).length) {
reqHtml =
'<div class="cm-tooltip-req">' +
'Requires: ' + escapeHtml(safeArray(cm.requirements).join(', ')) +
'</div>';
}
return (
'<div class="cm-tooltip-title">' + escapeHtml(cm.name) + '</div>' +
'<div class="cm-tooltip-desc">' + escapeHtml(cm.description) + '</div>' +
'<div class="cm-tooltip-meta">' +
'Level ' + escapeHtml(cm.level) +
' | Cost ' + escapeHtml(cm.points) +
(cm.cmSpentReq ? ' | CM Spent Req ' + escapeHtml(cm.cmSpentReq) : '') +
'</div>' +
reqHtml +
statLines.join('') +
spellAdds +
spellRemoves
);
}
function showCmTooltip(cm, card) {
if (!cmTooltip || !cm) return;
cmTooltip.innerHTML = buildCmTooltipHtml(cm);
cmTooltip.style.display = 'block';
var rect = card.getBoundingClientRect();
var tooltipWidth = 340;
var leftPos = rect.right + 12;
if (leftPos + tooltipWidth > window.innerWidth) {
leftPos = rect.left - tooltipWidth - 12;
}
cmTooltip.style.left = leftPos + 'px';
cmTooltip.style.top = Math.max(10, rect.top) + 'px';
}
function hideCmTooltip() {
if (cmTooltip) {
cmTooltip.style.display = 'none';
}
}
function renderMasteryList() {
var masteryPanel = document.getElementById('cm-mastery-panel');
if (!masteryPanel) return;
var selectedRace = raceSelect.value;
var selectedRaceCode = (data.raceCodes || {})[selectedRace];
var selectedClass = classSelect.value;
var level = parseInt(levelInput.value, 10) || 1;
var selectedPath = pathSelect ? pathSelect.value : "";
var selectedPathNormalized = normalizePath(selectedPath);
var spent = getSpentPoints();
var werePaths = [
"werelion",
"werebear",
"werewolf",
"wererat",
"weregator"
];
var selectedIsWere =
werePaths.indexOf(selectedPathNormalized) !== -1;
var filtered = safeArray(data.masteries).filter(function (cm) {
var cmRaces = safeArray(cm.races);
var cmClasses = safeArray(cm.classes);
var reqs = safeArray(cm.requirements);
var raceOk =
!cmRaces.length ||
cmRaces.indexOf(selectedRaceCode) !== -1;
var classOk =
!cmClasses.length ||
cmClasses.indexOf(selectedClass) !== -1;
var levelOk =
!cm.level ||
cm.level <= level;
var spentOk =
!cm.cmSpentReq ||
spent >= cm.cmSpentReq;
var reqOk = reqs.every(function (reqName) {
return selectedMasteries[reqName];
});
var tabOk = cm.classification === activeCmTab;
var cmPath = normalizePath(cm.lycanthropy);
var cmIsTransformation = cmPath !== "";
var pathOk = false;
if (!cmIsTransformation) {
pathOk = true;
} else if (!selectedPathNormalized) {
pathOk = false;
} else if (selectedIsWere && cmPath === "werebeast") {
pathOk = true;
} else {
pathOk = cmPath === selectedPathNormalized;
}
var notPurchased = !selectedMasteries[cm.name];
return raceOk &&
classOk &&
levelOk &&
spentOk &&
reqOk &&
tabOk &&
pathOk &&
notPurchased;
});
if (!filtered.length) {
masteryPanel.innerHTML = 'No Class Masteries found for this selection.';
return;
}
masteryPanel.innerHTML = filtered.map(function (cm) {
return (
'<div class="cm-card cm-purchasable-card" data-cm-name="' + escapeHtml(cm.name) + '">' +
'<div class="cm-card-title">' + escapeHtml(cm.name) + '</div>' +
'<div class="cm-card-desc">' + escapeHtml(cm.description) + '</div>' +
'<div class="cm-card-meta">' +
'Level: ' + escapeHtml(cm.level) +
' | Cost: ' + escapeHtml(cm.points) +
(cm.cmSpentReq ? ' | CM Spent Req: ' + escapeHtml(cm.cmSpentReq) : '') +
'</div>' +
'</div>'
);
}).join('');
Array.prototype.forEach.call(
masteryPanel.querySelectorAll('.cm-purchasable-card'),
function (card) {
var cmName = card.getAttribute('data-cm-name');
var cm = findMasteryByName(cmName);
card.addEventListener('mouseenter', function () {
showCmTooltip(cm, card);
});
card.addEventListener('mouseleave', hideCmTooltip);
card.addEventListener('click', function () {
if (!cm) return;
var available = getAvailablePoints();
var spent = getSpentPoints();
if (spent + cm.points > available) {
alert('Not enough CM points.');
return;
}
selectedMasteries[cm.name] = cm;
hideCmTooltip();
updateSummary();
});
}
);
}
raceSelect.addEventListener('change', loadClasses);
classSelect.addEventListener('change', updateSummary);
levelInput.addEventListener('input', updateSummary);
if (pathSelect) {
pathSelect.addEventListener('change', updateSummary);
}
Array.prototype.forEach.call(
document.querySelectorAll('.cm-tab-button'),
function (button) {
button.addEventListener('click', function () {
activeCmTab = button.getAttribute('data-tab');
Array.prototype.forEach.call(
document.querySelectorAll('.cm-tab-button'),
function (btn) {
btn.classList.remove('cm-tab-active');
}
);
button.classList.add('cm-tab-active');
updateSummary();
});
}
);
loadRaces();
}
});