|
|
Line 1: |
Line 1: |
| -- Talent Calculator for MediaWiki | | -- Talent Calculator for MediaWiki |
| -- Configuration
| |
| local p = {} | | local p = {} |
|
| |
|
| -- Talent Tree Configuration | | -- [Keep all your existing code for talentTrees, generateTalents(), etc.] |
| local talentTrees = {
| |
| pvm = {
| |
| name = "PvM Talents",
| |
| tiers = 5,
| |
| color = "#c41e3a", -- Red theme for combat
| |
| talents_per_tier = 5,
| |
| tier_point_requirements = {0, 5, 10, 15, 20} -- Points needed in previous tiers
| |
| },
| |
| skilling = {
| |
| name = "Skilling Talents",
| |
| tiers = 5,
| |
| color = "#228b22", -- Green theme for skilling
| |
| talents_per_tier = 5,
| |
| tier_point_requirements = {0, 5, 10, 15, 20}
| |
| },
| |
| utility = {
| |
| name = "Utility Talents",
| |
| tiers = 5,
| |
| color = "#4169e1", -- Blue theme for utility
| |
| talents_per_tier = 5,
| |
| tier_point_requirements = {0, 5, 10, 15, 20}
| |
| }
| |
| }
| |
| | |
| -- Dummy talents data generator
| |
| local function generateTalents()
| |
| local talents = {}
| |
|
| |
| for treeId, tree in pairs(talentTrees) do
| |
| talents[treeId] = {}
| |
| for tier = 1, tree.tiers do
| |
| for talentNum = 1, tree.talents_per_tier do
| |
| local talentId = treeId .. "_" .. tier .. "_" .. talentNum
| |
| talents[treeId][talentId] = {
| |
| name = "Talent " .. tier .. "-" .. talentNum,
| |
| description = "This is a tier " .. tier .. " " .. treeId .. " talent.",
| |
| tier = tier,
| |
| maxPoints = math.min(tier * 2, 10), -- Max points scales with tier, capped at 10
| |
| cost = tier, -- Cost equal to tier number
| |
| icon = "talent_" .. treeId .. "_" .. tier .. "_" .. talentNum .. ".png"
| |
| }
| |
| end
| |
| end
| |
| end
| |
|
| |
| return talents
| |
| end
| |
| | |
| local talents = generateTalents()
| |
| | |
| -- Helper function to render a single talent tree
| |
| function renderTalentTree(treeId, tree)
| |
| local html = [[
| |
| <div class="talent-tree" style="border-color: ]] .. tree.color .. [[">
| |
| <div class="talent-tree-header" style="color: ]] .. tree.color .. [[">]] .. tree.name .. [[</div>
| |
| ]]
| |
| | |
| -- Render each tier
| |
| for tier = 1, tree.tiers do
| |
| html = html .. [[
| |
| <div class="talent-tier]] .. (tier > 1 and " talent-tier-disabled" or "") .. [[" data-tree="]] .. treeId .. [[" data-tier="]] .. tier .. [[">
| |
| <div class="talent-tier-header" style="background-color: ]] .. tree.color .. [[30">Tier ]] .. tier .. [[ (Requires ]] .. tree.tier_point_requirements[tier] .. [[ points)</div>
| |
| <div class="talents-container">
| |
| ]]
| |
| | |
| -- Add talents for this tier
| |
| for i = 1, tree.talents_per_tier do
| |
| local talentId = treeId .. "_" .. tier .. "_" .. i
| |
| local talent = talents[treeId][talentId]
| |
|
| |
| html = html .. [[
| |
| <div class="talent"
| |
| data-talent-id="]] .. talentId .. [["
| |
| data-tree-id="]] .. treeId .. [["
| |
| data-tier="]] .. tier .. [["
| |
| data-max-points="]] .. talent.maxPoints .. [[">
| |
| <div class="talent-icon">]] .. tier .. "-" .. i .. [[</div>
| |
| <div class="talent-points"><span id="]] .. talentId .. [[-points">0</span>/]] .. talent.maxPoints .. [[</div>
| |
| </div>
| |
| ]]
| |
| end
| |
|
| |
| html = html .. [[
| |
| </div>
| |
| </div>
| |
| ]]
| |
| end
| |
|
| |
| html = html .. [[
| |
| </div>
| |
| ]]
| |
| | |
| return html
| |
| end
| |
|
| |
|
| -- Main rendering function | | -- Main rendering function |
| function p.renderTalentCalculator(frame) | | function p.renderTalentCalculator(frame) |
| local talentState = {
| | -- [Keep the existing function content] |
| pvm = {},
| |
| skilling = {},
| |
| utility = {},
| |
| pointsSpent = {
| |
| pvm = 0,
| |
| skilling = 0,
| |
| utility = 0
| |
| },
| |
| totalPoints = 0
| |
| }
| |
|
| |
| -- Base HTML and CSS | |
| local html = [[
| |
| <style>
| |
| .talent-calculator {
| |
| display: flex;
| |
| flex-direction: column;
| |
| font-family: Arial, sans-serif;
| |
| max-width: 900px;
| |
| margin: 0 auto;
| |
| }
| |
|
| |
| .talent-trees {
| |
| display: flex;
| |
| flex-wrap: wrap;
| |
| gap: 20px;
| |
| justify-content: center;
| |
| }
| |
|
| |
| .talent-tree {
| |
| border: 1px solid #ccc;
| |
| border-radius: 8px;
| |
| padding: 15px;
| |
| width: 280px;
| |
| }
| |
|
| |
| .talent-tree-header {
| |
| text-align: center;
| |
| font-weight: bold;
| |
| font-size: 1.2em;
| |
| padding-bottom: 10px;
| |
| border-bottom: 1px solid #eee;
| |
| margin-bottom: 10px;
| |
| }
| |
|
| |
| .talent-tier {
| |
| margin-bottom: 15px;
| |
| padding: 5px;
| |
| border-radius: 5px;
| |
| background-color: rgba(0,0,0,0.05);
| |
| }
| |
|
| |
| .talent-tier-header {
| |
| font-weight: bold;
| |
| margin-bottom: 5px;
| |
| padding: 3px;
| |
| border-radius: 3px;
| |
| }
| |
|
| |
| .talent-tier-disabled {
| |
| opacity: 0.5;
| |
| pointer-events: none;
| |
| }
| |
|
| |
| .talents-container {
| |
| display: flex;
| |
| flex-wrap: wrap;
| |
| gap: 5px;
| |
| }
| |
|
| |
| .talent {
| |
| display: flex;
| |
| flex-direction: column;
| |
| align-items: center;
| |
| width: 50px;
| |
| cursor: pointer;
| |
| padding: 3px;
| |
| border-radius: 5px;
| |
| transition: background-color 0.2s;
| |
| }
| |
|
| |
| .talent:hover {
| |
| background-color: rgba(0,0,0,0.1);
| |
| }
| |
|
| |
| .talent-icon {
| |
| width: 40px;
| |
| height: 40px;
| |
| background-color: #eee;
| |
| border-radius: 5px;
| |
| display: flex;
| |
| justify-content: center;
| |
| align-items: center;
| |
| font-size: 0.8em;
| |
| margin-bottom: 3px;
| |
| }
| |
|
| |
| .talent-points {
| |
| font-size: 0.9em;
| |
| }
| |
| | | |
| .controls { | | -- Return just the JSON data and a div with ID |
| display: flex; | | return { |
| justify-content: space-between;
| | html = '<div id="talent-calculator-container">Loading talent calculator...</div>', |
| margin-top: 20px; | | talentsData = mw.text.jsonEncode(talents), |
| padding: 15px; | | treeConfig = mw.text.jsonEncode(talentTrees) |
| background-color: #f5f5f5;
| |
| border-radius: 8px;
| |
| } | | } |
|
| |
| .points-display {
| |
| font-weight: bold;
| |
| }
| |
|
| |
| .import-export {
| |
| display: flex;
| |
| gap: 10px;
| |
| }
| |
|
| |
| .button {
| |
| padding: 5px 10px;
| |
| border: none;
| |
| border-radius: 4px;
| |
| background-color: #4CAF50;
| |
| color: white;
| |
| cursor: pointer;
| |
| }
| |
|
| |
| .button:hover {
| |
| background-color: #45a049;
| |
| }
| |
|
| |
| .talent-tooltip {
| |
| position: absolute;
| |
| display: none;
| |
| background-color: #333;
| |
| color: #fff;
| |
| padding: 10px;
| |
| border-radius: 5px;
| |
| max-width: 200px;
| |
| z-index: 100;
| |
| }
| |
| </style>
| |
|
| |
| <div class="talent-calculator">
| |
| <div class="talent-trees">
| |
| ]]
| |
|
| |
| -- Render each talent tree
| |
| for treeId, tree in pairs(talentTrees) do
| |
| html = html .. renderTalentTree(treeId, tree)
| |
| end
| |
|
| |
| -- Add controls section
| |
| html = html .. [[
| |
| </div>
| |
|
| |
| <div class="controls">
| |
| <div class="points-display">
| |
| <span id="points-spent">Total Points Spent: 0/100</span>
| |
| <div>
| |
| <span id="points-pvm">PvM: 0 points</span> |
| |
| <span id="points-skilling">Skilling: 0 points</span> |
| |
| <span id="points-utility">Utility: 0 points</span>
| |
| </div>
| |
| </div>
| |
| <div class="import-export">
| |
| <button class="button" id="export-build">Export Build</button>
| |
| <button class="button" id="import-build">Import Build</button>
| |
| <button class="button" id="reset-build">Reset</button>
| |
| </div>
| |
| </div>
| |
|
| |
| <div class="talent-tooltip" id="talent-tooltip"></div>
| |
|
| |
| <div id="modal" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background-color:rgba(0,0,0,0.5); z-index:1000;">
| |
| <div style="position:relative; margin:10% auto; padding:20px; width:50%; background-color:white; border-radius:5px;">
| |
| <span id="close-modal" style="position:absolute; top:10px; right:15px; cursor:pointer; font-size:20px;">×</span>
| |
| <h3 id="modal-title">Import/Export Build</h3>
| |
| <p>Copy this code to share your build:</p>
| |
| <textarea id="build-code" style="width:100%; height:100px;"></textarea>
| |
| <button id="modal-button" class="button" style="margin-top:10px;">Copy to Clipboard</button>
| |
| </div>
| |
| </div>
| |
|
| |
| <script>
| |
| // Talent calculator functionality
| |
| (function() {
| |
| let talentState = {
| |
| pvm: {},
| |
| skilling: {},
| |
| utility: {},
| |
| pointsSpent: {
| |
| pvm: 0,
| |
| skilling: 0,
| |
| utility: 0
| |
| },
| |
| totalPoints: 0,
| |
| maxPoints: 100
| |
| };
| |
|
| |
| const treeRequirements = {
| |
| pvm: [0, 5, 10, 15, 20],
| |
| skilling: [0, 5, 10, 15, 20],
| |
| utility: [0, 5, 10, 15, 20]
| |
| };
| |
|
| |
| const talents = ]].. mw.text.jsonEncode(talents) .. [[;
| |
|
| |
| // Initialize talent state
| |
| Object.keys(talents).forEach(treeId => {
| |
| Object.keys(talents[treeId]).forEach(talentId => {
| |
| talentState[treeId][talentId] = 0;
| |
| });
| |
| });
| |
|
| |
| // Add event listeners to talents
| |
| document.querySelectorAll('.talent').forEach(talent => {
| |
| talent.addEventListener('click', handleTalentClick);
| |
| talent.addEventListener('mouseover', showTooltip);
| |
| talent.addEventListener('mouseout', hideTooltip);
| |
| });
| |
|
| |
| // Handle talent click
| |
| function handleTalentClick(event) {
| |
| const talentElement = event.currentTarget;
| |
| const talentId = talentElement.getAttribute('data-talent-id');
| |
| const treeId = talentElement.getAttribute('data-tree-id');
| |
| const tier = parseInt(talentElement.getAttribute('data-tier'));
| |
| const maxPoints = parseInt(talentElement.getAttribute('data-max-points'));
| |
| const rightClick = event.button === 2 || event.ctrlKey;
| |
|
| |
| // Check if tier is unlocked
| |
| if (!isTierUnlocked(treeId, tier)) return;
| |
|
| |
| // Add or remove points
| |
| if (rightClick) {
| |
| // Remove point
| |
| if (talentState[treeId][talentId] > 0) {
| |
| // Check if this would break tier requirements for talents in higher tiers
| |
| if (!canRemovePoint(treeId, tier)) {
| |
| alert("Cannot remove point - talents in higher tiers depend on this tier's total");
| |
| return;
| |
| }
| |
|
| |
| talentState[treeId][talentId]--;
| |
| talentState.pointsSpent[treeId]--;
| |
| talentState.totalPoints--;
| |
| }
| |
| } else {
| |
| // Add point
| |
| if (talentState[treeId][talentId] < maxPoints && talentState.totalPoints < talentState.maxPoints) {
| |
| talentState[treeId][talentId]++;
| |
| talentState.pointsSpent[treeId]++;
| |
| talentState.totalPoints++;
| |
| }
| |
| }
| |
|
| |
| // Update UI
| |
| updateTalentPoint(talentId, talentState[treeId][talentId]);
| |
| updatePointsDisplay();
| |
| updateTierAvailability();
| |
| }
| |
|
| |
| // Check if a tier is unlocked based on points spent in previous tiers
| |
| function isTierUnlocked(treeId, tier) {
| |
| if (tier === 1) return true;
| |
| return talentState.pointsSpent[treeId] >= treeRequirements[treeId][tier-1];
| |
| }
| |
|
| |
| // Check if removing a point will break tier requirements
| |
| function canRemovePoint(treeId, tier) {
| |
| // Check if any talents are allocated in higher tiers
| |
| for (let t = tier + 1; t <= 5; t++) {
| |
| // Find if any talents in this tier have points
| |
| const talentsInTier = Object.keys(talents[treeId]).filter(id =>
| |
| talents[treeId][id].tier === t && talentState[treeId][id] > 0
| |
| );
| |
|
| |
| if (talentsInTier.length > 0 &&
| |
| talentState.pointsSpent[treeId] - 1 < treeRequirements[treeId][t-1]) {
| |
| return false;
| |
| }
| |
| }
| |
| return true;
| |
| }
| |
|
| |
| // Update talent point display
| |
| function updateTalentPoint(talentId, points) {
| |
| const pointsElement = document.querySelector(`#${talentId}-points`);
| |
| if (pointsElement) {
| |
| pointsElement.textContent = points;
| |
| }
| |
| }
| |
|
| |
| // Update the overall points display
| |
| function updatePointsDisplay() {
| |
| document.getElementById('points-spent').textContent =
| |
| `Total Points Spent: ${talentState.totalPoints}/${talentState.maxPoints}`;
| |
| document.getElementById('points-pvm').textContent =
| |
| `PvM: ${talentState.pointsSpent.pvm} points`;
| |
| document.getElementById('points-skilling').textContent =
| |
| `Skilling: ${talentState.pointsSpent.skilling} points`;
| |
| document.getElementById('points-utility').textContent =
| |
| `Utility: ${talentState.pointsSpent.utility} points`;
| |
| }
| |
|
| |
| // Update tier availability based on points spent
| |
| function updateTierAvailability() {
| |
| Object.keys(talents).forEach(treeId => {
| |
| for (let tier = 2; tier <= 5; tier++) {
| |
| const tierElement = document.querySelector(`.talent-tier[data-tree="${treeId}"][data-tier="${tier}"]`);
| |
| if (isTierUnlocked(treeId, tier)) {
| |
| tierElement.classList.remove('talent-tier-disabled');
| |
| } else {
| |
| tierElement.classList.add('talent-tier-disabled');
| |
| }
| |
| }
| |
| });
| |
| }
| |
|
| |
| // Show tooltip on hover
| |
| function showTooltip(event) {
| |
| const talentElement = event.currentTarget;
| |
| const talentId = talentElement.getAttribute('data-talent-id');
| |
| const treeId = talentElement.getAttribute('data-tree-id');
| |
| const talent = talents[treeId][talentId];
| |
|
| |
| const tooltip = document.getElementById('talent-tooltip');
| |
| tooltip.innerHTML = `
| |
| <strong>${talent.name}</strong>
| |
| <div>${talent.description}</div>
| |
| <div>Tier: ${talent.tier}</div>
| |
| <div>Points: ${talentState[treeId][talentId]}/${talent.maxPoints}</div>
| |
| <div>Cost: ${talent.cost} per point</div>
| |
| `;
| |
|
| |
| tooltip.style.display = 'block';
| |
| tooltip.style.left = (event.pageX + 10) + 'px';
| |
| tooltip.style.top = (event.pageY + 10) + 'px';
| |
| }
| |
|
| |
| // Hide tooltip
| |
| function hideTooltip() {
| |
| document.getElementById('talent-tooltip').style.display = 'none';
| |
| }
| |
|
| |
| // Export build
| |
| document.getElementById('export-build').addEventListener('click', function() {
| |
| const modal = document.getElementById('modal');
| |
| const buildCode = document.getElementById('build-code');
| |
| const modalTitle = document.getElementById('modal-title');
| |
| const modalButton = document.getElementById('modal-button');
| |
|
| |
| modalTitle.textContent = 'Export Build';
| |
| modalButton.textContent = 'Copy to Clipboard';
| |
|
| |
| // Generate export code - simple base64 encoding of JSON state
| |
| const exportData = {
| |
| pvm: talentState.pvm,
| |
| skilling: talentState.skilling,
| |
| utility: talentState.utility
| |
| };
| |
|
| |
| const exportString = btoa(JSON.stringify(exportData));
| |
| buildCode.value = exportString;
| |
|
| |
| modal.style.display = 'block';
| |
| buildCode.select();
| |
|
| |
| modalButton.onclick = function() {
| |
| buildCode.select();
| |
| document.execCommand('copy');
| |
| alert('Build code copied to clipboard!');
| |
| };
| |
| });
| |
|
| |
| // Import build
| |
| document.getElementById('import-build').addEventListener('click', function() {
| |
| const modal = document.getElementById('modal');
| |
| const buildCode = document.getElementById('build-code');
| |
| const modalTitle = document.getElementById('modal-title');
| |
| const modalButton = document.getElementById('modal-button');
| |
|
| |
| modalTitle.textContent = 'Import Build';
| |
| modalButton.textContent = 'Import';
| |
| buildCode.value = '';
| |
|
| |
| modal.style.display = 'block';
| |
|
| |
| modalButton.onclick = function() {
| |
| try {
| |
| const importData = JSON.parse(atob(buildCode.value));
| |
|
| |
| // Reset current state
| |
| resetBuild();
| |
|
| |
| // Apply imported state
| |
| Object.keys(importData).forEach(treeId => {
| |
| Object.keys(importData[treeId]).forEach(talentId => {
| |
| const points = importData[treeId][talentId];
| |
| talentState[treeId][talentId] = points;
| |
| talentState.pointsSpent[treeId] += points;
| |
| talentState.totalPoints += points;
| |
| updateTalentPoint(talentId, points);
| |
| });
| |
| });
| |
|
| |
| updatePointsDisplay();
| |
| updateTierAvailability();
| |
| modal.style.display = 'none';
| |
| } catch (e) {
| |
| alert('Invalid build code. Please try again with a correct code.');
| |
| }
| |
| };
| |
| });
| |
|
| |
| // Reset build
| |
| document.getElementById('reset-build').addEventListener('click', resetBuild);
| |
|
| |
| function resetBuild() {
| |
| Object.keys(talents).forEach(treeId => {
| |
| Object.keys(talents[treeId]).forEach(talentId => {
| |
| talentState[treeId][talentId] = 0;
| |
| updateTalentPoint(talentId, 0);
| |
| });
| |
| talentState.pointsSpent[treeId] = 0;
| |
| });
| |
|
| |
| talentState.totalPoints = 0;
| |
| updatePointsDisplay();
| |
| updateTierAvailability();
| |
| }
| |
|
| |
| // Close modal
| |
| document.getElementById('close-modal').addEventListener('click', function() {
| |
| document.getElementById('modal').style.display = 'none';
| |
| });
| |
|
| |
| // Initialize tier availability
| |
| updateTierAvailability();
| |
|
| |
| // Prevent context menu on talents to use right-click for point removal
| |
| document.querySelectorAll('.talent').forEach(talent => {
| |
| talent.addEventListener('contextmenu', function(e) {
| |
| e.preventDefault();
| |
| handleTalentClick({...e, button: 2, currentTarget: talent});
| |
| return false;
| |
| });
| |
| });
| |
| })();
| |
| </script>
| |
| </div>
| |
| ]]
| |
|
| |
| return html
| |
| end | | end |
|
| |
|
| -- Modify the main function at the end of your script | | -- Modified main function |
| function p.main(frame) | | function p.main(frame) |
| local html = p.renderTalentCalculator(frame) | | local data = p.renderTalentCalculator(frame) |
| | | |
| -- Use this for MediaWiki 1.25+ (recommended) | | -- Create a template call to handle the HTML rendering |
| return frame:preprocess('<div class="talent-calculator-wrapper">' .. html .. '</div>') | | return frame:expandTemplate{ |
|
| | title = 'TalentCalculatorTemplate', |
| -- Alternative method
| | args = { |
| -- return mw.html.create('div')
| | html = data.html, |
| -- :css('width', '100%')
| | talentsData = data.talentsData, |
| -- :wikitext(html) | | treeConfig = data.treeConfig |
| | } |
| | } |
| end | | end |
|
| |
|
| |
|
| return p | | return p |