MediaWiki:SCalScript.js
From Istaria Lexica
Note: After saving, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
- Opera: Go to Menu → Settings (Opera → Preferences on a Mac) and then to Privacy & security → Clear browsing data → Cached images and files.
/** * Skeleton Key Calculator * Elteria Shadowhand */ let SCalStateCheck = setInterval(() => { 'use strict'; console.log('ready state:' + document.readyState); if(document.readyState === 'complete') { clearInterval(SCalStateCheck); let mainDivId = 'SCalMain'; // ID of the main content box. Can be a <div> or whatever. if (document.getElementById(mainDivId)) { let version = '1.4'; // This script's version let baseResources; // Collection of the needed base resources. To be filled. let refinedResources; // Collection of the needed refined resources. To be filled. let productsDetails; // Detailed dynamic information for each product. To be filled. let currentProductIdCount; // Dirty little helper for adding dynamic IDs to productsDetails // defaults let maxKeyCount = 5000; // Skeleton Key counter maximum value let maxSkillCount = 2000; // Skill input fields maximum value let cookieParameters = '; path=/; SameSite=Strict'; let jQueryUrlStandalone = 'jquery-3.6.0.js'; // url to the standalone jquery if mediawiki isn't present let cssUrlStandalone = 'styles.css'; // url to the stylesheet if mediawiki isn't present let cssFileMediaWiki = ' MediaWiki:SCalScript.css'; // name of the stylesheet article in mediawiki let hasMW; // Indicator if mediawiki is present let wikiUrl = 'https://www.istaria-lexica.de'; // used to link to the resource and product pages on a wiki let headline = 'The Calculator'; // Calculator's head line text // The dataset of resources // Note that the skill is NOT the actual skill you need to create the product! // It's the skill you would need to create its parent product. E.g. if you created a Spell Shard which originally // needs Spellcraft, the subcomponent (i.e. a Stone Brick, which would be Stoneworking) of it would be Spellcraft. // The min and max parameters behave the same. // the REAL needed skills are documented in the skillsOverview variable. let products = { 'Skeleton Key': { skill: 'Tinkering', minCount: 1, maxCount: 1, minSkill: 1100, maxSkill: 1425, type: 'SCalProduct', subs: { 'Enchanted Adamantium-Mithril Bar': { skill: 'Tinkering', minCount: 3, maxCount: 6, minSkill: 1100, maxSkill: 1425, type: 'SCalProduct', subs: { 'Gozar\'s Blessing': { skill: 'Tinkering', minCount: 2, maxCount: 4, minSkill: 1100, maxSkill: 1425, type: 'SCalProduct', subs: { 'Meltanis\' Prayer': { skill: 'Scribing', minCount: 1, maxCount: 3, minSkill: 1100, maxSkill: 1425, type: 'SCalProduct', subs: { 'Travertine Spell Shard': { skill: 'Spellcraft', minCount: 1, maxCount: 5, minSkill: 1100, maxSkill: 1425, type: 'SCalRefinedResource', subs: { 'Travertine Brick': { skill: 'Stoneworking', minCount: 2, maxCount: 5, minSkill: 1000, maxSkill: 1325, type: 'SCalRefinedResource', subs: { 'Travertine Slab': { skill: 'Stoneworking', minCount: 2, maxCount: 5, minSkill: 1000, maxSkill: 1325 } } } } }, 'Radiant Essence Orb': { skill: 'Spellcraft', minCount: 4, maxCount: 10, minSkill: 1100, maxSkill: 1425, type: 'SCalRefinedResource', subs: { 'Radiant Essence': { skill: 'Essence Shaping', minCount: 2, maxCount: 5, minSkill: 1000, maxSkill: 1325 } } } } }, 'Primal Burst III': { skill: 'Spellcraft', minCount: 1, maxCount: 3,minSkill: 930, maxSkill: 1130, type: 'SCalProduct', subs: { 'Marble Spell Shard': { skill: 'Spellcraft', minCount: 5, maxCount: 5, minSkill: 930, maxSkill: 1130, type: 'SCalRefinedResource', subs: { 'Marble Brick': { skill: 'Spellcraft', minCount: 2, maxCount: 5, minSkill: 800, maxSkill: 1100, type: 'SCalRefinedResource', subs: { 'Marble Slab': { skill: 'Stoneworking', minCount: 2, maxCount: 5, minSkill: 800, maxSkill: 1100 } } } } }, 'Shining Essence Orb': { skill: 'Spellcraft', minCount: 9, maxCount: 18, minSkill: 930, maxSkill: 1130, type: 'SCalRefinedResource', subs: { 'Shining Essence': { skill: 'Essence Shaping', minCount: 2, maxCount: 5, minSkill: 800, maxSkill: 1100 } } } } }, 'Energy Strike V': { skill: 'Spellcraft', minCount: 1, maxCount: 3, minSkill: 850, maxSkill: 1050, type: 'SCalProduct', subs: { 'Marble Spell Shard': { skill: 'Spellcraft', minCount: 4, maxCount: 4, minSkill: 850, maxSkill: 1050, type: 'SCalRefinedResource', subs: { 'Marble Brick': { skill: 'Spellcraft', minCount: 2, maxCount: 5, minSkill: 800, maxSkill: 1100, type: 'SCalRefinedResource', subs: { 'Marble Slab': { skill: 'Stoneworking', minCount: 2, maxCount: 5, minSkill: 800, maxSkill: 1100 } } } } }, 'Shining Essence Orb': { skill: 'Spellcraft', minCount: 7, maxCount: 14, minSkill: 800, maxSkill: 1100, type: 'SCalRefinedResource', subs: { 'Shining Essence': { skill: 'Essence Shaping', minCount: 2, maxCount: 5, minSkill: 800, maxSkill: 1100 } } } } }, 'Ice Bomb V': { skill: 'Spellcraft', minCount: 1, maxCount: 3, minSkill: 920, maxSkill: 1120, type: 'SCalProduct', subs: { 'Marble Spell Shard': { skill: 'Spellcraft', minCount: 5, maxCount: 5, minSkill: 800, maxSkill: 1100, type: 'SCalRefinedResource', subs: { 'Marble Brick': { skill: 'Stoneworking', minCount: 2, maxCount: 5, minSkill: 800, maxSkill: 1100, type: 'SCalRefinedResource', subs: { 'Marble Slab': { skill: 'Stoneworking', minCount: 2, maxCount: 5, minSkill: 800, maxSkill: 1100 } } } } }, 'Shining Essence Orb': { skill: 'Spellcraft', minCount: 9, maxCount: 18, minSkill: 920, maxSkill: 1120, type: 'SCalRefinedResource', subs: { 'Shining Essence': { skill: 'Essence Shaping', minCount: 2, maxCount: 5, minSkill: 800, maxSkill: 1100 } } } } }, 'Fiery Strike V': { skill: 'Spellcraft', minCount: 1, maxCount: 3, minSkill: 980, maxSkill: 1180, type: 'SCalProduct', subs: { 'Marble Spell Shard': { skill: 'Spellcraft', minCount: 6, maxCount: 6, minSkill: 800, maxSkill: 1100, type: 'SCalRefinedResource', subs: { 'Marble Brick': { skill: 'Stoneworking', minCount: 2, maxCount: 5, minSkill: 800, maxSkill: 1100, type: 'SCalRefinedResource', subs: { 'Marble Slab': { skill: 'Stoneworking', minCount: 2, maxCount: 5, minSkill: 800, maxSkill: 1100 } } } } }, 'Shining Essence Orb': { skill: 'Spellcraft', minCount: 11, maxCount: 22, minSkill: 980, maxSkill: 1180, type: 'SCalRefinedResource', subs: { 'Shining Essence': { skill: 'Essence Shaping', minCount: 2, maxCount: 5, minSkill: 800, maxSkill: 1100 } } } } }, 'Gold Papyrus Sheet': { skill: 'Scribing', minCount: 2, maxCount: 5, minSkill: 1100, maxSkill: 1425, type: 'SCalRefinedResource', subs: { 'Gold Papyrus Stem': { skill: 'Papermaking', minCount: 2, maxCount: 5, minSkill: 1000, maxSkill: 1325 } } } } }, 'Hardened Adamantium-Mithril Bar': { skill: 'Tinkering', minCount: 2, maxCount: 5, minSkill: 1100, maxSkill: 1425, type: 'SCalRefinedResource', subs: { 'Solution of Majorita': { skill: 'Tinkering', minCount: 2, maxCount: 5, minSkill: 1100, maxSkill: 1425, type: 'SCalRefinedResource', subs: { 'Thornwood Bowl': { skill: 'Alchemy', minCount: 1, maxCount: 3, minSkill: 1100, maxSkill: 1425, type: 'SCalRefinedResource', subs: { 'Thornwood Sap': { skill: 'Fletching', minCount: 3, maxCount: 6, minSkill: 1100, maxSkill: 1425 } , 'Thornwood Board': { skill: 'Fletching', minCount: 5, maxCount: 10, minSkill: 1100, maxSkill: 1425, type: 'SCalRefinedResource', subs: { 'Thornwood Log': { skill: 'Lumbering', minCount: 2, maxCount: 5, minSkill: 1000, maxSkill: 1325 } } } } }, 'Crystallized Travertine Brick': { skill: 'Alchemy', minCount: 5, maxCount: 10, minSkill: 1000, maxSkill: 1325, type: 'SCalRefinedResource', subs: { 'Unfocused Violet Azulyte Crystal': { skill: 'Stoneworking', minCount: 1, maxCount: 5, minSkill: 1000, maxSkill: 1325 } , 'Travertine Slab': { skill: 'Stoneworking', minCount: 1, maxCount: 5, minSkill: 1000, maxSkill: 1325 } } }, 'Purified Radiant Essence Orb': { skill: 'Alchemy', minCount: 1, maxCount: 4, minSkill: 1100, maxSkill: 1425, type: 'SCalRefinedResource', subs: { 'Radiant Essence Orb': { skill: 'Alchemy', minCount: 1, maxCount: 4, minSkill: 1100, maxSkill: 1325, type: 'SCalRefinedResource', subs: { 'Radiant Essence': { skill: 'Essence Shaping', minCount: 2, maxCount: 5, minSkill: 1000, maxSkill: 1325 } } } } }, 'Water': { skill: 'Alchemy', minCount: 15, maxCount: 30, minSkill: 1100, maxSkill: 1425 } } }, 'Adamantium-Mithril Bar': { skill: 'Tinkering', minCount: 1, maxCount: 1, minSkill: 1100, maxSkill: 1425, type: 'SCalRefinedResource', subs: { 'Mithril Ore': { skill: 'Smelting', minCount: 3, maxCount: 6, minSkill: 1100, maxSkill: 1425 } , 'Adamantium Ore': { skill: 'Smelting', minCount: 3, maxCount: 6, minSkill: 1100, maxSkill: 1425 } } } } } } }, 'Skeleton Key Mold': { skill: 'Tinkering', minCount: 1, maxCount: 1, minSkill: 1100, maxSkill: 1425, type: 'SCalProduct', subs: { 'Skeleton Key Pattern': { skill: 'Earthencraft', minCount: 1, maxCount: 1, minSkill: 1100, maxSkill: 1425 }, 'Porcelain Clay Chunk': { skill: 'Earthencraft', minCount: 2, maxCount: 5, minSkill: 1100, maxSkill: 1425 } } } } } }; // A list of the skills and their limits you need to be able to craft everything let skillsOverview = { 'Tinkering': { min: 1100, max: 1425 }, 'Scribing': { min: 1100, max: 1425 }, 'Spellcraft': { min: 1100, max: 1425 }, 'Essence Shaping': { min: 1000, max: 1325 }, 'Papermaking': { min: 1000, max: 1325 }, 'Alchemy': { min: 1100, max: 1425 }, 'Fletching': { min: 1100, max: 1425 }, 'Enchanting': { min: 1100, max: 1425 }, 'Stoneworking': { min: 1000, max: 1325 }, 'Lumbering': { min: 1000, max: 1325 }, 'Smelting': { min: 1100, max: 1425 }, 'Earthencraft': { min: 1100, max: 1425 } }; /////////////////////////////////////////////////////////////////// //FUNCTIONS: var printKeyCounter = function printKeyCounter() { $('#' + mainDivId).append('<center><label for="SCalCountInput">How many Skeleton Keys would you want to create?</label></center>'); $('#' + mainDivId).append('<center><input type="number" id="SCalCountInput" name="SCalCountInput" value="1"></center>'); $('#SCalCountInput').change(function(event) { var value = event.target.value; // check for a correct entry if(!isRealInteger(value)) value = 1; if(value <= 0) value = 1; if(value > maxKeyCount) value = maxKeyCount; event.target.value = value; setCookieByInteger(event.target.id, value); updateLists(); }); }; var printHeader = function printHeader() { var setSkillCookie = function(event) { setCookieByInteger(event.target.id, event.target.value); updateLists(); }; $('#' + mainDivId).append(`<h1 id="SCalH1">${headline}</h1>`); $('#' + mainDivId).append(`<center style="font-size: 0.8em; margin-bottom:30px">(v ${version})</center>`); $('#' + mainDivId).append('<center>Please enter your current skills in the fields below. Defaults to optimal skills.</center>'); $('#' + mainDivId).append('<center><input type="button" class="SCal" id="SCalMaxSkillsBtn" value="Reset to optimal skills"></center>'); $('#SCalMaxSkillsBtn').click(function() { if(confirm('This will reset all of the skills to optimal! Are you really sure? ')) { for (var v of Object.values(skillsOverview)) { $('#' + v.id).val(v.max); $('#' + v.id).change(); } } }); $('#' + mainDivId).append('<center><div id="SCalSkills" style="column-count:3; width:fit-content; text-align:left"></div></center>'); for (var [skillName, values] of Object.entries(skillsOverview).sort()) { var id = values.id; var skillLevel = getCookie(id); if(!isRealInteger(skillLevel)) skillLevel = values.max; // default to optimal value if cookie not correctly set $('#SCalSkills').append(`<input type="number" id="${id}" name="${id}" value="${skillLevel}"><label for="${id}"> ${skillName}</label><br />`); $('#' + id).change(setSkillCookie); $('#' + id).change(); } $('#' + mainDivId).append('<br /><center>A <font style="color: red"><b>red</b></font> box means there is an error.</center>'); $('#' + mainDivId).append('<center>A <font style="color: green"><b>green</b></font> box means you\'re optimal for creating the items.</center>'); $('#' + mainDivId).append('<div id="SCalErrorText"></div>'); $('#' + mainDivId).append('<br />'); $('#' + mainDivId).append('<hr />'); }; var printExportLink = function printExportLink(text, buttontext, id, mainId, mimetype, filename) { $(`#${id}`).remove(); $(`#${mainId}`).append(`<a id="${id}A"></a>`); $(`#${id}A`).append(`<input class="SCal" id="${id}" name="${id}" type="button" value="${buttontext}">`); $(`#${id}`).click(function() { var url = `data:${mimetype};charset=utf-8,${encodeURIComponent(text)}`; $(`#${id}A`).attr('href', url); $(`#${id}A`).attr('download', filename); }); }; // Creates a simple product-tree object, containing the amount of resources needed var recursiveCreateProductTree = function recursiveCreateProductTree(products) { var treeProducts = {}; for(var [product, values] of Object.entries(products)) { treeProducts[product] = { needed: values.needed }; if(values.hasOwnProperty('subs')) treeProducts[product].subs = recursiveCreateProductTree(values.subs); } return treeProducts; }; var updateExportEverythingLinks = function updateExportEverythingLinks() { var JSONeverything = JSON.stringify({ 'Product Tree': recursiveCreateProductTree(productsDetails), 'Base Resources': baseResources, 'Refined Resources': refinedResources, 'Skills': generateSkillsAsJSON() }); printExportLink(JSONeverything, 'Export everything as JSON', 'SCalExportEverythingAsJSON', 'SCalExportEverything', 'application/json', 'Skeleton Key - All.json'); var TEXTeverything = '--- Tree of needed Resources ---\n\n'; TEXTeverything += recursiveGenerateTreeAsTEXT(productsDetails, 0); TEXTeverything += '\n\n'; TEXTeverything += '--- Needed Base Resources ---\n\n'; TEXTeverything += generateResourcesAsTEXT(baseResources); TEXTeverything += '\n\n'; TEXTeverything += '--- Needed Refined Resources ---\n\n'; TEXTeverything += generateResourcesAsTEXT(refinedResources); TEXTeverything += '\n\n'; TEXTeverything += '--- Needed Skills ---\n\n'; TEXTeverything += generateSkillsAsTEXT(); printExportLink(TEXTeverything, 'Export everything as TEXT', 'SCalExportEverythingAsEXT', 'SCalExportEverything', 'text/plain', 'Skeleton Key - All.txt'); var XMLeverything = '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>\n'; XMLeverything += '<Resources>\n'; XMLeverything += ' <Tree>\n'; XMLeverything += recursiveGenerateTreeAsXML(productsDetails, 4); XMLeverything += ' </Tree>\n'; XMLeverything += generateResourcesAsXML(baseResources, 'BaseResources', 2); XMLeverything += generateResourcesAsXML(refinedResources, 'RefinedResources', 2); XMLeverything += generateSkillsAsXML(2); XMLeverything += '</Resources>\n'; printExportLink(XMLeverything, 'Export everything as XML', 'SCalExportEverythingAsXML', 'SCalExportEverything', 'application/xml', 'Skeleton Key - All.xml'); }; var generateSkillsAsJSON = function generateSkillsAsJSON() { var json = {}; for(var [skill, values] of Object.entries(skillsOverview).sort()) { json[skill] = { minimal: values.min, optimal: values.max }; } return json; }; var recursiveGenerateTreeAsTEXT = function recursiveGenerateTreeAsTEXT(productsDetails, indent) { var text = ''; for(var [product, values] of Object.entries(productsDetails)) { for(var i=0; i<indent; i++) { text += ' '; } text += '|- '; text += product + ' '; text += values.needed; text += '\n'; if(values.hasOwnProperty('subs')) { text += recursiveGenerateTreeAsTEXT(values.subs, indent + 3); } } return text; }; var recursiveGenerateTreeAsXML = function recursiveGenerateTreeAsXML(productsDetails, indent) { var xml = ''; for(var [product, value] of Object.entries(productsDetails)) { for(var i=0; i < indent; i++) { xml += ' '; } xml += `<item name="${product}" needed="${value.needed}">\n`; if(value.hasOwnProperty('subs')) xml += recursiveGenerateTreeAsXML(value.subs, indent + 2); for(var j=0; j < indent; j++) { xml += ' '; } xml +='</item>\n'; } return xml; }; var determineNeededDigitlengthResources = function determineNeededDigitlengthResources(resources) { var length = 0; for(var count of Object.values(resources)) { if(count.toString().length > length) length = count.toString().length; } return length; }; var determineNeededDigitlengthSkills = function determineNeededDigitlengthSkills() { var length = [9, 10]; for(var skill of Object.keys(skillsOverview)) { if(skillsOverview[skill].min.toString().length > length[0]) length[0] = skillsOverview[skill].min.toString().length; if(skillsOverview[skill].max.toString().length > length[1]) length[1] = skillsOverview[skill].max.toString().length; } return length; }; // calculates the effective needed amount of a resource var calculateNeededResource = function calculateNeededResource(minSkill, maxSkill, minCount, maxCount, skill) { var currentSkill = $('#' + skillsOverview[skill].id).val(); var percent = (currentSkill - minSkill) / (maxSkill - minSkill); var needEffective = maxCount - ((maxCount - minCount) * percent); needEffective = Math.ceil(needEffective); if (needEffective < minCount) needEffective = minCount; if (needEffective > maxCount) needEffective = maxCount; return needEffective; }; var recursiveCleanHaveCookies = function recursiveCleanHaveCookies(details) { for(var values of Object.values(details)) { var id = values.id + 'Have'; deleteCookie(id); if(values.hasOwnProperty('subs')) recursiveCleanHaveCookies(values.subs); } }; var updateTree = function updateTree() { printExportLink(JSON.stringify(recursiveCreateProductTree(productsDetails)), 'Export as JSON', 'SCalExportTreeAsJSON', 'SCalResourcesExportLinks', 'application/json', 'Skeleton Key - Tree.json'); printExportLink(recursiveGenerateTreeAsTEXT(productsDetails, 0), 'Export as TEXT', 'SCalExportTreeAsTEXT', 'SCalResourcesExportLinks', 'text/plain', 'Skeleton Key - Tree.txt'); var treeAsXML = '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>\n' + recursiveGenerateTreeAsXML(productsDetails, 0); printExportLink(treeAsXML, 'Export as XML', 'SCalExportTreeAsXML', 'SCalResourcesExportLinks', 'application/xml', 'Skeleton Key - Tree.xml'); $('#SCalResetTree').remove(); $('#SCalResourcesExportLinks').append('<input class="SCal" type="button" id="SCalResetTree" value="Reset all have-fields">'); $('#SCalResetTree').click(function() { if(confirm('This will reset all have-fields to 0. Are you really sure?')) { recursiveCleanHaveCookies(productsDetails); updateLists(); } }); recursiveUpdateTreeProduct(productsDetails); }; var printTree = function printTree() { $('#SCalResourcescol1').append('<h2>Tree of needed products</h2>'); $('#SCalResourcescol1').append('<div id="SCalResourcesExportLinks"></div>'); $('#SCalResourcescol1').append(recursivePrintTreeProduct(productsDetails)); recursiveAddHaveChangeEvents(productsDetails); }; var recursiveAddHaveChangeEvents = function recursiveAddHaveChangeEvents(products) { var setHaveCookie = function(event) { var have = event.target.value; if(!isRealInteger(have)) have = 0; if(have < 0) have = 0; setCookieByInteger(event.target.id, have); updateLists(); }; for(var values of Object.values(products)) { $(`#${values.id}Have`).change(setHaveCookie); if(values.hasOwnProperty('subs')) recursiveAddHaveChangeEvents(values.subs); } }; //Calulates the needed data for the lists and puts them into objects //for further processing var recursiveCalculateResourceData = function recursiveCalculateResourceData(products, multiplikator) { var details = {}; for(var [product, values] of Object.entries(products)) { currentProductIdCount += 1; var id = 'SCalItem' + currentProductIdCount; var needed = calculateNeededResource(values.minSkill, values.maxSkill, values.minCount, values.maxCount, values.skill); var needEffective = needed * multiplikator; var have = getCookie(id + 'Have'); if(!isRealInteger(have)) have = 0; if(have < 0) have = 0; if(have > 0) needEffective = needEffective - have; if(needEffective < 0) needEffective = 0; //set the input field ID, needed amount and resource type details[product] = { id: id, needed: needEffective, type: values.type }; // Add needed amount to base resources // we assume that a product without subs is a base resource if(!values.hasOwnProperty('subs')) { if(baseResources.hasOwnProperty(product)) { baseResources[product] = details[product].needed + baseResources[product]; } else { baseResources[product] = details[product].needed; } } // Add needed amount to to refined resources if(values.type == 'SCalRefinedResource') { if(refinedResources.hasOwnProperty(product)) { refinedResources[product] = details[product].needed + refinedResources[product]; } else { refinedResources[product] = details[product].needed; } } // recurse through the sub products if(values.hasOwnProperty('subs')) details[product].subs = recursiveCalculateResourceData(values.subs, details[product].needed); } return details; }; // Fills the lists with data var updateLists = function updateLists() { if(validateSkillInputs()) { initialize(); updateExportEverythingLinks(); updateTree(); updateResources(baseResources, 'BaseResources'); updateResources(refinedResources, 'RefinedResources'); } }; //reinitializes everything and calculates needed data var initialize = function initialize() { baseResources = {}; refinedResources = {}; productsDetails = {}; currentProductIdCount = 0; var keysWanted = getCookie('SCalCountInput'); // set the Skeleton Key count var keysNeeded = keysWanted; if(!isRealInteger(keysWanted)) keysNeeded = 1; if(keysWanted < 0) keysNeeded = 1; if(keysWanted > maxKeyCount) keysNeeded = maxKeyCount; if(keysWanted != keysNeeded) setCookie('SCalCountInput', keysNeeded); // cookie seems to be set wrong, we reset. $('#SCalCountInput').val(keysNeeded); productsDetails = recursiveCalculateResourceData(products, keysWanted); }; //creates the containers for the lists var printLists = function printLists() { initialize(); // Container for the 'Export Everything' features $('#' + mainDivId).append('<div id="SCalExportEverything" style="text-align: center"></div>'); // Containers for the lists $('#' + mainDivId).append('<center><table>' + ' <tr valign="top">' + ' <td id="SCalResourcescol1"></td>' + ' <td id="SCalResourcescol2"></td>' + ' </tr>' + '</table></center>' ); printTree(); printResources(baseResources, 'Overview of Base Resources', 'BaseResources'); printResources(refinedResources, 'Overview of Refined Resources', 'RefinedResources'); printNeededSkills(); if(hasMW) { mw.loader.using('jquery.tablesorter', function() { // add the tablesorter to tables $('table.sortable').tablesorter({ sortList: [{ 0: 'asc' }] }); }); } }; var getURL = function getURL(url) { if(hasMW) return mw.util.getUrl(url); return wikiUrl + '/' + url; }; var printNeededSkills = function printNeededSkills() { $('#SCalResourcescol2').append('<h2>Required skills</h2>'); printExportLink(JSON.stringify(generateSkillsAsJSON()), 'Export as JSON', 'SCalExportSkillsAsJSON', 'SCalResourcescol2', 'application/json', 'Skeleton Key - Skills.json'); printExportLink(generateSkillsAsTEXT(), 'Export as TEXT', 'SCalExportSkillsAsTEXT', 'SCalResourcescol2', 'text/plain', 'Skeleton Key - Skills.txt'); var skillsAsXML = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n' + generateSkillsAsXML(); printExportLink(skillsAsXML, 'Export as XML', 'SCalExportSkillsAsXML', 'SCalResourcescol2', 'application/xml', 'Skeleton Key - Skills.xml'); var tablecontent = '<table class="wikitable sortable">' + ' <thead>' + ' <tr>' + ' <th align="left">Skill</th>' + ' <th align="left">Minimal</th>' + ' <th align="left">Optimal</th>' + ' </tr>' + ' </thead>' + ' <tbody>'; for(var [skill, values] of Object.entries(skillsOverview).sort()) { tablecontent += ' <tr>' + ` <td><a href="${getURL(skill)}">${skill}</a></td>` + ` <td>${values.min}</td>` + ` <td>${values.max}</td>` + ' </tr>'; } tablecontent += ' </tbody>'; tablecontent += '</table>'; $('#SCalResourcescol2').append(tablecontent); }; // Tests if the provided value has a valid integer and sets a cookie for it var setCookieByInteger = function setCookieByInteger(id, value) { if(isRealInteger(value)) setCookie(id, value); }; var recursivePrintTreeProduct = function recursivePrintTreeProduct(products) { var html = ''; for(var [product, values] of Object.entries(products)) { var neededId = values.id + 'Needed'; var haveId = values.id + 'Have'; html += ' <ul>'; html += ' <li>'; if(product != 'Skeleton Key') { html += ` <span class="${values.type}"><a href="${getURL(product)}">${product}</a> (<span id="${neededId}"></span>) </span>` + ` <label for="${values.id}Have">have:</label><input class="SCalTreeHave" id="${haveId}" name="${haveId}" type="number">`; } else { html += ` <span class="${values.type}"><a href="${getURL(product)}">${product}</a> (<span id="${neededId}"></span>)</span>`; } if(values.hasOwnProperty('subs')) { html += recursivePrintTreeProduct(values.subs); } html += ' </li>'; html += ' </ul>'; } return html; }; var recursiveUpdateTreeProduct = function recursiveUpdateTreeProduct(products) { var greenClass = 'SCalGreenInput'; for(var [product, values] of Object.entries(products)) { var haveId = values.id + 'Have'; var neededId = values.id + 'Needed'; if(product != 'Skeleton Key') { $('#' + haveId).removeClass(greenClass); var have = getCookie(haveId); if(!isRealInteger(have)) have = 0; if(have < 0) have = 0; if(have > 0) $('#' + haveId).addClass(greenClass); $('#' + neededId).html(values.needed); $('#' + haveId).val(have); } else { $('#' + neededId).html(values.needed); } if(values.hasOwnProperty('subs')) { recursiveUpdateTreeProduct(values.subs); } } }; var printResources = function printResources(resources, headline, type) { var allDoneId = 'SCal' + type + 'AllDone'; var updateCookie = function() { if($(this).prop('checked')) { setCookie($(this).get(0).id, true); } else { deleteCookie($(this).get(0).id); } updateLists(); }; $('#SCalResourcescol2').append(`<h2>${headline}</h2>`); $('#SCalResourcescol2').append(`<div id="SCal${type}ExportLinks"></div>`); var tablecontent = '<table class="wikitable sortable">' + ' <thead>' + ' <tr>' + ' <th style="vertical-align: top">Resource</th>' + ' <th style="vertical-align: top">Need</th>' + ` <th style="vertical-align: top" class="unsortable"><span><input type="checkbox" id="${allDoneId}"></span></th>` + ' </tr>' + ' </thead>' + ' <tbody>'; for (var resource of Object.keys(resources).sort()) { var id = resource.replaceAll(' ','') + 'Need'; var doneId = id + 'Done'; tablecontent += '<tr>' + ` <td><a href="${getURL(resource)}">${resource}</a></td>` + ' <td id="' + id + '"></td>' + ` <td style="text-align: center"><input type="checkbox" id="${doneId}"></td>` + ' </tr>'; } tablecontent += ' </tbody>'; tablecontent += '</table>'; $('#SCalResourcescol2').append(tablecontent); $('#' + allDoneId).click(function() { // handle the 'all done' checkbox if($(this).prop('checked')) { setCookie($(this).get(0).id, true); for (var resource of Object.keys(resources)) { var doneId = resource.replaceAll(' ','') + 'NeedDone'; setCookie(doneId, true); } } else { deleteCookie($(this).get(0).id); for (var resource2 of Object.keys(resources)) { var doneId2 = resource2.replaceAll(' ','') + 'NeedDone'; deleteCookie(doneId2); } } updateLists(); }); for (var res of Object.keys(resources)) { // handle each resource 'done' checkbox var doneId2 = '#' + res.replaceAll(' ','') + 'NeedDone'; $(doneId2).change(updateCookie); } }; var updateResources = function updateResources(resources, type) { var allDoneId = `SCal${type}AllDone`; printExportLink(JSON.stringify(Object.fromEntries(Object.entries(resources).sort())), 'Export as JSON', `SCalExport${type}resourcesAsJSON`, `SCal${type}ExportLinks`, 'application/json', `Skeleton Key - ${type}.json`); printExportLink(generateResourcesAsTEXT(resources), 'Export as TEXT', `SCalExport${type}resourcesAsTEXT`, `SCal${type}ExportLinks`, 'text/plain', `Skeleton Key - ${type}.txt`); var resourceXML = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n' + generateResourcesAsXML(resources, type, 0); printExportLink(resourceXML, 'Export as XML', `SCalExport${type}resourcesAsXML`, `SCal${type}ExportLinks`, 'application/xml', `Skeleton Key - ${type}.xml`); var allDoneChecked = getCookie(allDoneId); // handle the 'All done' checkbox if(allDoneChecked === 'true') { $('#' + allDoneId).prop('checked', true); } else { $('#' + allDoneId).prop('checked', false); } for (var [resource, count] of Object.entries(resources)) { // handle the 'done' checkboxes and set the count accordingly var id = resource.replaceAll(' ', '') + 'Need'; var doneId = id + 'Done'; var checked = getCookie(doneId); if(checked === 'true') { $('#' + doneId).prop('checked', true); $('#' + id).html(0); } else { $('#' + doneId).prop('checked', false); $('#' + id).html(count); } } }; var generateResourcesAsTEXT = function generateResourcesAsTEXT(resources) { var text = ''; var length = determineNeededDigitlengthResources(resources); for(var [resource, count] of Object.entries(resources).sort()) { var indent = ''; for(var i = 0;i < length - count.toString().length; i++) { indent += ' '; } text += count + indent + ' ' + resource + '\n'; } return text; }; var generateSkillsAsXML = function generateSkillsAsXML() { var xml = '<Skills>\n'; for(var [skill, values] of Object.entries(skillsOverview).sort()) { xml += ` <skill minimal="${values.min}" optimal="${values.max}">${skill}</skill>\n`; } xml += '</Skills>'; return xml; }; var generateSkillsAsTEXT = function generateSkillsAsTEXT() { var length = determineNeededDigitlengthSkills(); var text = 'Minimal Optimal Skillname\n'; text += '--------------------------------------------------------\n'; for(var [skill, values] of Object.entries(skillsOverview).sort()) { var needIndent = ''; var optIndent = ''; for(var i = 0;i < length[0] - values.min.toString().length; i++) { needIndent += ' '; } for(var j = 0;j < length[1] - values.max.toString().length; j++) { optIndent += ' '; } text += values.min + needIndent + values.max + optIndent + skill + '\n'; } return text; }; var generateResourcesAsXML = function generateResourcesAsXML(resources, tag) { var xml = `<${tag}>\n`; for(var [resource, count] of Object.entries(resources).sort()) { xml += ` <item needed="${count}">${resource}</item>\n`; } xml += `</${tag}>\n`; return xml; }; var isRealInteger = function isRealInteger(number) { return (!isNaN(number) && parseInt(Number(number)) == number && !isNaN(parseInt(number, 10))); }; // check if the skill's input fields contain valid entries var validateSkillInputs = function validateSkillInputs() { var ready = true; $('#SCalErrorText').html(''); for (var [skill, values] of Object.entries(skillsOverview)) { var inputObj = $('#' + values.id); var skillCount = inputObj.val(); inputObj.removeClass('SCalErroreousInput'); inputObj.removeClass('SCalOptimalInput'); if (!isRealInteger(skillCount)) { $('#SCalErrorText').append(`<center>${skill}: this is not a number!</center>`); inputObj.addClass('SCalErroreousInput'); ready = false; } else { if (skillCount < 1 || skillCount > maxSkillCount) { $('#SCalErrorText').append(`<center>${skill}: please provide a number between 1 and ${maxSkillCount}</center>`); inputObj.addClass('SCalErroreousInput'); ready = false; } if(skillCount < values.min) { $('#SCalErrorText').append(`<center>Skill ${skill} is too low! (have:${skillCount}, need: ${values.min})</center>`); inputObj.addClass('SCalErroreousInput'); } if(!inputObj.hasClass('SCalErroreousInput')) { if(skillCount >= values.max) inputObj.addClass('SCalOptimalInput'); } } } return ready; }; var setCookie = function setCookie(name, value) { var date = new Date(3030,1,4); var expires = '; expires=' + date.toGMTString(); document.cookie = name + '=' + value + expires + cookieParameters; }; var getCookie = function getCookie(name) { var nameEQ = name + '='; var ca = document.cookie.split(';'); for(var i=0;i < ca.length;i++) { var c = ca[i]; while (c.charAt(0)==' ') c = c.substring(1,c.length); if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length,c.length); } return null; }; var deleteCookie = function deleteCookie(name) { var expires = '; expires=Thu, 01 Jan 1970 00:00:00 UTC'; document.cookie = name + '=' + expires + cookieParameters; }; var loadJQuery = function loadJQuery() { return new Promise( function( resolve, reject ) { var jquery = document.createElement('script'); jquery.src = jQueryUrlStandalone; jquery.type = 'text/javascript'; document.head.appendChild(jquery); jquery.onload = function () { resolve(); }; }); }; var loadCSS = function loadCSS() { return new Promise( function( resolve, reject ) { var css = document.createElement('link'); css.rel = 'stylesheet'; css.href = cssUrlStandalone; document.head.appendChild(css); css.onload = function () { resolve(); }; }); }; var startSCal = function startSCal() { printHeader(); printKeyCounter(); printLists(); updateLists(); }; // END OF FUNCTIONS /////////////////////////////////////////////////////////////////// // Let's start adding content // generate IDs for the tradeskills for(var [key, values] of Object.entries(skillsOverview)) { values.id = 'SCal' + key.replaceAll(/\s/g, '') + 'Input'; } hasMW = true; // Check for the mediawiki api. We assume that jquery is available if the api exists. if(typeof mw === 'undefined') hasMW = false; if(hasMW) { var url = getURL(cssFileMediaWiki); if(url.includes('?')) { // workaround for mediawiki htaccess shortlinks url += '&'; } else { url += '?'; } url += 'action=raw&ctype=text/css'; mw.loader.load(url, 'text/css'); startSCal(); } else { loadCSS().then(function() { // load standalone resources loadJQuery().then(function() { startSCal(); }); }); } } } }, 50);