Difference between revisions of "MediaWiki:SCalScript.js"

From Istaria Lexica

(Big script import)
(Big script import)
Line 1: Line 1:
 
/**
 
/**
  * Skeleton Key Calculator V 1.0
+
  * Skeleton Key Calculator
 
  * Elteria Shadowhand
 
  * Elteria Shadowhand
 
  */
 
  */
 
$(document).ready(function() {
 
$(document).ready(function() {
 +
  'use strict';
 
   if (document.getElementById('SCalMain')) {
 
   if (document.getElementById('SCalMain')) {
     'use strict';
+
     let version = '1.2'; // the script version
 
     let baseResources; // Collection of the needed base resources. To be filled.
 
     let baseResources; // Collection of the needed base resources. To be filled.
 
     let refinedResources; // Collection of the needed refined resources. To be filled.
 
     let refinedResources; // Collection of the needed refined resources. To be filled.
    let skillsOverview; // A list of all needed skills, their min/opt stats and an id
 
 
     let productsDetails = {}; // detailed dynamic information for each product
 
     let productsDetails = {}; // detailed dynamic information for each product
 +
    let currentProductIdCount; // Dirty little helper for adding dynamic IDs to the products
 +
 
     let maxKeyCount = 5000; // Skeleton Key counter maximum value
 
     let maxKeyCount = 5000; // Skeleton Key counter maximum value
 
     let maxSkillCount = 2000; // Skill input fields maximum value
 
     let maxSkillCount = 2000; // Skill input fields maximum value
Line 15: Line 17:
 
     // This invalidates all URLs and removes other mediawiki specific generated content, i.e. tablesorters and stylesheets
 
     // This invalidates all URLs and removes other mediawiki specific generated content, i.e. tablesorters and stylesheets
 
     let hasMW = true;  
 
     let hasMW = true;  
    let currentProductIdCount; // Dirty little helper for adding dynamic IDs to the products
 
 
     let cookieParameters = '; path=/; SameSite=Strict';
 
     let cookieParameters = '; path=/; SameSite=Strict';
  
     // The dataset
+
     // The dataset of resources
 
     // Note that the skill is NOT the actual skill you need to create the product!  
 
     // Note that the skill is NOT the actual skill you need to create the product!  
     // It is the skill you need to create it's parent product. So if you created a Spell Shard which needs Spellcraft,
+
     // It's the skill you would need to create its parent product. E.g. if you created a Spell Shard which originally
     // the subcomponent (i.e. a Stone Brick, which would be Stoneworking) of it would be Spellcraft. The min and max
+
     // needs Spellcraft, the subcomponent (i.e. a Stone Brick, which would be Stoneworking) of it would be Spellcraft.
    // parameters behave the same.
+
    // The min and max parameters behave the same.
 
     // the REAL needed skills are documented in the skillsOverview variable.
 
     // the REAL needed skills are documented in the skillsOverview variable.
 
     let products = {
 
     let products = {
Line 123: Line 124:
 
                       'Thornwood Bowl': { skill: 'Alchemy', minCount: 1, maxCount: 3, 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 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 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 }
 
                               'Thornwood Log': { skill: 'Lumbering', minCount: 2, maxCount: 5, minSkill: 1000, maxSkill: 1325 }
Line 132: Line 133:
 
                       }, 'Crystallized Travertine Brick': { skill: 'Alchemy', minCount: 5, maxCount: 10, minSkill: 1000, maxSkill: 1325, type: 'SCalRefinedResource', subs:
 
                       }, '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 }
+
                           '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 }
+
                           '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:
 
                       }, 'Purified Radiant Essence Orb': { skill: 'Alchemy', minCount: 1, maxCount: 4, minSkill: 1100, maxSkill: 1425, type: 'SCalRefinedResource', subs:
Line 147: Line 148:
 
                   }, 'Adamantium-Mithril Bar': { skill: 'Tinkering', minCount: 1, maxCount: 1, minSkill: 1100, maxSkill: 1425, type: 'SCalRefinedResource', subs:
 
                   }, '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 }
+
                       'Mithril Ore': { skill: 'Smelting', minCount: 3, maxCount: 6, minSkill: 1100, maxSkill: 1425 } ,
                       , 'Adamantium Ore': { skill: 'Smelting', minCount: 3, maxCount: 6, minSkill: 1100, maxSkill: 1425 }
+
                       'Adamantium Ore': { skill: 'Smelting', minCount: 3, maxCount: 6, minSkill: 1100, maxSkill: 1425 }
 
                     }
 
                     }
 
                   }
 
                   }
Line 156: Line 157:
 
           }, 'Skeleton Key Mold': { skill: 'Tinkering', minCount: 1, maxCount: 1, minSkill: 1100, maxSkill: 1425, type: 'SCalProduct', subs:
 
           }, '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 }
+
               '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 }
+
               'Porcelain Clay Chunk': { skill: 'Earthencraft', minCount: 2, maxCount: 5, minSkill: 1100, maxSkill: 1425 }
 
             }
 
             }
 
           }
 
           }
Line 164: Line 165:
 
     };
 
     };
  
     // List of the skills you need to have for crafting everything. Used for the input fields.
+
     // A list of the skills and their limits you need to be able to craft everything
     skillsOverview = {
+
     let skillsOverview = {
 
       'Tinkering': { min: 1100, max: 1425 },
 
       'Tinkering': { min: 1100, max: 1425 },
 
       'Scribing': { min: 1100, max: 1425 },
 
       'Scribing': { min: 1100, max: 1425 },
Line 180: Line 181:
 
     };
 
     };
  
     // css stylesheet
+
     ///////////////////////////////////////////////////////////////////
    if(hasMW) mw.loader.load(getURL('MediaWiki:SCalScript.css') + "?action=raw&ctype=text/css", "text/css");
 
   
 
    // dynamically generate IDs for skills
 
    for(var [key, values] of Object.entries(skillsOverview)) {
 
      values.id = 'SCal' + key.replace(/\s/, '') + 'Input';
 
    }
 
 
 
    // Let's start adding content
 
    // top part of the gui (static)
 
    printHeader();
 
    printKeyCounter();
 
 
 
    // finally create the lists and fill them.
 
    printLists();
 
    updateLists();
 
   
 
 
     //FUNCTIONS:
 
     //FUNCTIONS:
     function printKeyCounter() {
+
     var printKeyCounter = function printKeyCounter() {
 
       $('#SCalMain').append('<center><label for="SCalCountInput" id="SCalCountInputLbl">How many Skeleton Keys would you want to create?</label></center>');
 
       $('#SCalMain').append('<center><label for="SCalCountInput" id="SCalCountInputLbl">How many Skeleton Keys would you want to create?</label></center>');
 
       $('#SCalMain').append('<center><input type="number" id="SCalCountInput" name="SCalCountInput" value="1" size="6"></center>');
 
       $('#SCalMain').append('<center><input type="number" id="SCalCountInput" name="SCalCountInput" value="1" size="6"></center>');
Line 211: Line 196:
 
         updateLists();
 
         updateLists();
 
       });
 
       });
     }
+
     };
 
      
 
      
     function printHeader() {
+
     var printHeader = function printHeader() {
 +
      var setCookie = function(event) {
 +
            setCookieByInteger(event.target.id, event.target.value);
 +
            updateLists();
 +
        };
 
       $('#SCalMain').append('<h1 id="SCalH1">The Calculator</h1>');
 
       $('#SCalMain').append('<h1 id="SCalH1">The Calculator</h1>');
 +
      $('#SCalMain').append('<center style="font-size: 0.8em; margin-bottom:30px">(v ' + version + ')</center>');
 
       $('#SCalMain').append('<center>Please enter your current skills in the fields below. Defaults to optimal skills.</center>');
 
       $('#SCalMain').append('<center>Please enter your current skills in the fields below. Defaults to optimal skills.</center>');
  
Line 228: Line 218:
  
 
       $('#SCalMain').append('<center><div id="SCalSkills" style="column-count:3; width:fit-content; text-align:left"></div></center>');
 
       $('#SCalMain').append('<center><div id="SCalSkills" style="column-count:3; width:fit-content; text-align:left"></div></center>');
       for (var [key, value] of Object.entries(skillsOverview)) {
+
       for (var [skillName, values] of Object.entries(skillsOverview)) {
         var id = value.id;
+
         var id = values.id;
         var skill = getCookie(id);
+
         var skillLevel = getCookie(id);
         if(!isRealInteger(skill)) skill = value.max; // default to max value if cookie not correctly set
+
               
 +
         if(!isRealInteger(skillLevel)) skillLevel = values.max; // default to optimal value if cookie not correctly set
 
    
 
    
         $('#SCalSkills').append('<input type="number" id="' + id + '" name="' + id + '" size="6" value="' + skill + '"><label for="' + id + '">&nbsp;&nbsp;' + key + '</label><br />');
+
         $('#SCalSkills').append('<input type="number" id="' + id + '" name="' + id + '" size="6" value="' + skillLevel + '"><label for="' + id + '">&nbsp;&nbsp;' + skillName + '</label><br />');
 
          
 
          
         $('#' + id).change(function(event) {
+
         $('#' + id).change(setCookie);
            setCookieByInteger(event.target.id, event.target.value);
 
            updateLists();
 
        });
 
 
         $('#' + id).change();
 
         $('#' + id).change();
 
       }
 
       }
Line 247: Line 235:
 
       $('#SCalMain').append('<br />');
 
       $('#SCalMain').append('<br />');
 
       $('#SCalMain').append('<hr />');
 
       $('#SCalMain').append('<hr />');
     }
+
     };
  
     function printExportLink(text, buttontext, id, mainId, mimetype, filename) {
+
     var printExportLink = function printExportLink(text, buttontext, id, mainId, mimetype, filename) {
 
       $('#' + id).remove();
 
       $('#' + id).remove();
 
       $('#' + mainId).append('<a id="' + id + 'A"></a>');
 
       $('#' + mainId).append('<a id="' + id + 'A"></a>');
Line 258: Line 246:
 
         $('#' + id + 'A').attr('download', filename);
 
         $('#' + id + 'A').attr('download', filename);
 
       });
 
       });
     }
+
     };
  
 
     // Creates a simple products object, containing the needed resources
 
     // Creates a simple products object, containing the needed resources
     function recursiveCreateProductTree(productsDetails) {
+
     var recursiveCreateProductTree = function recursiveCreateProductTree(products) {
       var product = {};
+
       var treeProducts = {};
       for(var [key, values] of Object.entries(productsDetails)) {
+
       for(var [product, values] of Object.entries(products)) {
         product[key] = { needed: values.needed };  
+
         treeProducts[product] = { needed: values.needed };  
 
          
 
          
         if(values.hasOwnProperty('subs')) product[key].subs = recursiveCreateProductTree(values.subs);
+
         if(values.hasOwnProperty('subs')) treeProducts[product].subs = recursiveCreateProductTree(values.subs);
 
       }
 
       }
       return product;
+
       return treeProducts;
     }
+
     };
  
     function updateExportEverythingLinks() {
+
     var updateExportEverythingLinks = function updateExportEverythingLinks() {
 
       var JSONeverything = JSON.stringify({
 
       var JSONeverything = JSON.stringify({
 
         'Product Tree': recursiveCreateProductTree(productsDetails),
 
         'Product Tree': recursiveCreateProductTree(productsDetails),
Line 290: Line 278:
 
       TEXTeverything += '\n\n';
 
       TEXTeverything += '\n\n';
 
       TEXTeverything += '--- Needed Skills ---\n\n';
 
       TEXTeverything += '--- Needed Skills ---\n\n';
       TEXTeverything += generateSkillsAsTEXT(9,7);
+
       TEXTeverything += generateSkillsAsTEXT();
 
       printExportLink(TEXTeverything, 'Export everything as TEXT', 'SCalExportEverythingAsEXT', 'SCalExportEverything', 'text/plain', 'Skeleton Key - All.txt');
 
       printExportLink(TEXTeverything, 'Export everything as TEXT', 'SCalExportEverythingAsEXT', 'SCalExportEverything', 'text/plain', 'Skeleton Key - All.txt');
  
Line 297: Line 285:
 
       XMLeverything += recursiveGenerateTreeAsXML(productsDetails, 2);
 
       XMLeverything += recursiveGenerateTreeAsXML(productsDetails, 2);
 
       XMLeverything += '</ResourcesTree>\n';
 
       XMLeverything += '</ResourcesTree>\n';
       XMLeverything += generateResourcesAsXML(baseResources,'BaseResources', 0);
+
       XMLeverything += generateResourcesAsXML(baseResources, 'BaseResources', 0);
       XMLeverything += generateResourcesAsXML(refinedResources,'RefinedResources', 0);
+
       XMLeverything += generateResourcesAsXML(refinedResources, 'RefinedResources', 0);
 
       XMLeverything += generateSkillsAsXML(0);
 
       XMLeverything += generateSkillsAsXML(0);
 
       printExportLink(XMLeverything, 'Export everything as XML', 'SCalExportEverythingAsXML', 'SCalExportEverything', 'application/xml', 'Skeleton Key - All.xml');
 
       printExportLink(XMLeverything, 'Export everything as XML', 'SCalExportEverythingAsXML', 'SCalExportEverything', 'application/xml', 'Skeleton Key - All.xml');
     }
+
     };
  
     function generateSkillsAsJSON() {
+
     var generateSkillsAsJSON = function generateSkillsAsJSON() {
 
       var json = {};
 
       var json = {};
       for(var [key, values] of Object.entries(skillsOverview).sort()) {
+
       for(var [skill, values] of Object.entries(skillsOverview).sort()) {
         json[key] = {
+
         json[skill] = {
 
           minimal: values.min,
 
           minimal: values.min,
 
           optimal: values.max
 
           optimal: values.max
Line 312: Line 300:
 
       }
 
       }
 
       return json;
 
       return json;
     }
+
     };
 
      
 
      
     function recursiveGenerateTreeAsTEXT(productsDetails, indent) {
+
     var recursiveGenerateTreeAsTEXT = function recursiveGenerateTreeAsTEXT(productsDetails, indent) {
 
       var text = '';
 
       var text = '';
       for(var [key, value] of Object.entries(productsDetails)) {
+
       for(var [product, values] of Object.entries(productsDetails)) {
 
         for(var i=0; i<indent; i++) {
 
         for(var i=0; i<indent; i++) {
 
           text += ' ';
 
           text += ' ';
Line 322: Line 310:
 
          
 
          
 
         text += '|- ';
 
         text += '|- ';
         text += key + ' ';
+
         text += product + ' ';
         text += value.needed;
+
         text += values.needed;
 
         text += '\n';
 
         text += '\n';
  
         if(value.hasOwnProperty('subs')) {
+
         if(values.hasOwnProperty('subs')) {
           text += recursiveGenerateTreeAsTEXT(value.subs, indent+2);
+
           text += recursiveGenerateTreeAsTEXT(values.subs, indent + 3);
 
         }
 
         }
 
       }
 
       }
 
       return text;
 
       return text;
     }
+
     };
  
     function recursiveGenerateTreeAsXML(productsDetails, indent) {
+
     var recursiveGenerateTreeAsXML = function recursiveGenerateTreeAsXML(productsDetails, indent) {
 
       var xml = '';
 
       var xml = '';
       for(var [key, value] of Object.entries(productsDetails)) {
+
       for(var [product, value] of Object.entries(productsDetails)) {
         for(var i=0; i<indent; i++) {
+
         for(var i=0; i < indent; i++) {
 
           xml += ' ';
 
           xml += ' ';
 
         }
 
         }
 
          
 
          
         xml += '<item name="' + key +'" needed="' + value.needed + '">\n';
+
         xml += '<item name="' + product +'" needed="' + value.needed + '">\n';
  
         if(value.hasOwnProperty('subs')) xml += recursiveGenerateTreeAsXML(value.subs, indent+2);
+
         if(value.hasOwnProperty('subs')) xml += recursiveGenerateTreeAsXML(value.subs, indent + 2);
  
         for(var i=0; i<indent; i++) {
+
         for(var j=0; j < indent; j++) {
 
           xml += ' ';
 
           xml += ' ';
 
         }
 
         }
Line 351: Line 339:
 
       }
 
       }
 
       return xml;
 
       return xml;
     }
+
     };
  
     function determineNeededDigitlength(resources, length) {
+
     var determineNeededDigitlengthResources = function determineNeededDigitlengthResources(resources) {
       for(var value of Object.values(resources)) {
+
      var length = 0;
         if(value.toString().length > length) length = value.toString().length;
+
       for(var count of Object.values(resources)) {
 +
         if(count.toString().length > length) length = count.toString().length;
 
       }
 
       }
 
       return length;
 
       return length;
     }
+
     };
  
     function determineNeededDigitlengthSkills(length) {
+
     var determineNeededDigitlengthSkills = function determineNeededDigitlengthSkills() {
       for(var key of Object.keys(skillsOverview)) {
+
      var length = [9, 10];
         if(skillsOverview[key].min.toString().length > length) length[0] = skillsOverview[key].min.toString().length;
+
       for(var skill of Object.keys(skillsOverview)) {
         if(skillsOverview[key].max.toString().length > length) length[1] = skillsOverview[key].max.toString().length;
+
         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;
 
       return length;
     }
+
     };
  
     // calculated the effective needed resources for the given product
+
     // calculates the effective resources needed
     function calculateNeededResource(minSkill, maxSkill, minCount, maxCount, skill) {
+
     var calculateNeededResource = function calculateNeededResource(minSkill, maxSkill, minCount, maxCount, skill) {
 
         var currentSkill = $('#' + skillsOverview[skill].id).val();
 
         var currentSkill = $('#' + skillsOverview[skill].id).val();
 
          
 
          
Line 380: Line 370:
  
 
         return needEffective;
 
         return needEffective;
     }
+
     };
 
      
 
      
     function recursiveCleanHaveCookies(details) {
+
     var recursiveCleanHaveCookies = function recursiveCleanHaveCookies(details) {
 
       for(var values of Object.values(details)) {
 
       for(var values of Object.values(details)) {
 
         var id = values.id + 'Have';
 
         var id = values.id + 'Have';
Line 389: Line 379:
 
         if(values.hasOwnProperty('subs')) recursiveCleanHaveCookies(values.subs);
 
         if(values.hasOwnProperty('subs')) recursiveCleanHaveCookies(values.subs);
 
       }
 
       }
     }
+
     };
 
      
 
      
     function updateTree() {
+
     var updateTree = function updateTree() {
 
       printExportLink(JSON.stringify(recursiveCreateProductTree(productsDetails)), 'Export as JSON', 'SCalExportTreeAsJSON', 'SCalResourcesExportLinks', 'application/json', 'Skeleton Key - Tree.json');
 
       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');
 
       printExportLink(recursiveGenerateTreeAsTEXT(productsDetails, 0), 'Export as TEXT', 'SCalExportTreeAsTEXT', 'SCalResourcesExportLinks', 'text/plain', 'Skeleton Key - Tree.txt');
Line 407: Line 397:
  
 
       recursiveUpdateTreeProduct(productsDetails);
 
       recursiveUpdateTreeProduct(productsDetails);
     }
+
     };
 
      
 
      
     function printTree() {
+
     var printTree = function printTree() {
 
       $('#SCalResourcesrow1').append('<h2>Tree of needed products</h2>');
 
       $('#SCalResourcesrow1').append('<h2>Tree of needed products</h2>');
 
       $('#SCalResourcesrow1').append('<div id="SCalResourcesExportLinks"></div>');
 
       $('#SCalResourcesrow1').append('<div id="SCalResourcesExportLinks"></div>');
Line 415: Line 405:
 
       $('#SCalResourcesrow1').append(recursivePrintTreeProduct(productsDetails));
 
       $('#SCalResourcesrow1').append(recursivePrintTreeProduct(productsDetails));
 
       recursiveAddHaveChangeEvents(productsDetails);
 
       recursiveAddHaveChangeEvents(productsDetails);
     }
+
     };
  
     function recursiveAddHaveChangeEvents(details) {
+
     var recursiveAddHaveChangeEvents = function recursiveAddHaveChangeEvents(products) {
       for(var values of Object.values(details)) {
+
       var setCookie = function(event) {
        $('#' + values.id + 'Have').change(function(event) {
 
 
           var have = event.target.value;
 
           var have = event.target.value;
 
           if(!isRealInteger(have)) have = 0;
 
           if(!isRealInteger(have)) have = 0;
Line 425: Line 414:
 
           setCookieByInteger(event.target.id, have);
 
           setCookieByInteger(event.target.id, have);
 
           updateLists();
 
           updateLists();
         });
+
         };
 +
      for(var values of Object.values(products)) {
 +
        $('#' + values.id + 'Have').change(setCookie);
 
          
 
          
 
         if(values.hasOwnProperty('subs')) recursiveAddHaveChangeEvents(values.subs);
 
         if(values.hasOwnProperty('subs')) recursiveAddHaveChangeEvents(values.subs);
 
       }
 
       }
     }
+
     };
 
      
 
      
 
     //Calulates the needed data for the lists and puts them into objects
 
     //Calulates the needed data for the lists and puts them into objects
 
     //for further processing
 
     //for further processing
     function recursiveCalculateResourceData(products, previousNeeded) {
+
     var recursiveCalculateResourceData = function recursiveCalculateResourceData(products, multiplikator) {
 
       var details = {};
 
       var details = {};
       for(var [key, values] of Object.entries(products)) {
+
       for(var [product, values] of Object.entries(products)) {
 
         currentProductIdCount += 1;
 
         currentProductIdCount += 1;
 
         var id = 'SCalItem' + currentProductIdCount;
 
         var id = 'SCalItem' + currentProductIdCount;
 
         var needed = calculateNeededResource(values.minSkill, values.maxSkill, values.minCount, values.maxCount, values.skill);
 
         var needed = calculateNeededResource(values.minSkill, values.maxSkill, values.minCount, values.maxCount, values.skill);
         needEffective = needed * previousNeeded;
+
         var needEffective = needed * multiplikator;
 
         var have = getCookie(id + 'Have');
 
         var have = getCookie(id + 'Have');
 
         if(!isRealInteger(have)) have = 0;
 
         if(!isRealInteger(have)) have = 0;
Line 446: Line 437:
 
         if(needEffective < 0) needEffective = 0;
 
         if(needEffective < 0) needEffective = 0;
 
          
 
          
         //Process needed resources for this product
+
         //set the input field ID, needed amount and resource type
        //and set the input field ID and resource type
+
         details[product] = {  
         details[key] = {  
 
 
           id: id,
 
           id: id,
 
           needed: needEffective,
 
           needed: needEffective,
Line 454: Line 444:
 
         };
 
         };
 
          
 
          
         // Add to base resources
+
         // Add needed amount to base resources
 
         // we assume that a product without subs is a base resource
 
         // we assume that a product without subs is a base resource
 
         if(!values.hasOwnProperty('subs')) {  
 
         if(!values.hasOwnProperty('subs')) {  
           if(baseResources.hasOwnProperty(key)) {
+
           if(baseResources.hasOwnProperty(product)) {
             baseResources[key] = details[key].needed + baseResources[key];
+
             baseResources[product] = details[product].needed + baseResources[product];
 
           } else {
 
           } else {
             baseResources[key] = details[key].needed;
+
             baseResources[product] = details[product].needed;
 
           }
 
           }
 
         }
 
         }
  
         // Add to refined resources
+
         // Add needed amount to to refined resources
 
         if(values.type == 'SCalRefinedResource') {  
 
         if(values.type == 'SCalRefinedResource') {  
           if(refinedResources.hasOwnProperty(key)) {
+
           if(refinedResources.hasOwnProperty(product)) {
             refinedResources[key] = details[key].needed + refinedResources[key];
+
             refinedResources[product] = details[product].needed + refinedResources[product];
 
           } else {
 
           } else {
             refinedResources[key] = details[key].needed;
+
             refinedResources[product] = details[product].needed;
 
           }
 
           }
 
         }
 
         }
 
          
 
          
 
         // recurse through the sub products
 
         // recurse through the sub products
         if(values.hasOwnProperty('subs')) details[key].subs = recursiveCalculateResourceData(values.subs, details[key].needed);
+
         if(values.hasOwnProperty('subs')) details[product].subs = recursiveCalculateResourceData(values.subs, details[product].needed);
 
       }
 
       }
 
       return details;
 
       return details;
     }
+
     };
 
      
 
      
 
     // Fills the lists with data
 
     // Fills the lists with data
     function updateLists() {
+
     var updateLists = function updateLists() {
 
       if(validateSkillInputs()) {
 
       if(validateSkillInputs()) {
 
         initialize();
 
         initialize();
Line 488: Line 478:
 
         updateResources(refinedResources, 'Refined');
 
         updateResources(refinedResources, 'Refined');
 
       }       
 
       }       
     }
+
     };
 
      
 
      
     //reinitializes everything and calculates needed data
+
     //reinitializes everything and calculate needed data
     function initialize() {
+
     var initialize = function initialize() {
 
       baseResources = {};
 
       baseResources = {};
 
       refinedResources = {};
 
       refinedResources = {};
Line 502: Line 492:
 
        
 
        
 
       productsDetails = recursiveCalculateResourceData(products, keysWanted);
 
       productsDetails = recursiveCalculateResourceData(products, keysWanted);
     }
+
     };
  
 
     //creates the contents of the lists
 
     //creates the contents of the lists
     function printLists() {
+
     var printLists = function printLists() {
 
       initialize();
 
       initialize();
  
       // Container for the 'Export Everything' feature
+
       // Container for the 'Export Everything' features
 
       $('#SCalMain').append('<div id="SCalExportEverything" style="text-align: center"></div>');
 
       $('#SCalMain').append('<div id="SCalExportEverything" style="text-align: center"></div>');
 
        
 
        
 
       // Containers for the lists
 
       // Containers for the lists
       $('#SCalMain').append('<center><table>'
+
       $('#SCalMain').append('<center><table>' +
         + '<tr valign="top">'
+
         '<tr valign="top">' +
         + '<td id="SCalResourcesrow1"></td>'
+
         '<td id="SCalResourcesrow1"></td>' +
         + '<td id="SCalResourcesrow2"></td>'
+
         '<td id="SCalResourcesrow2"></td>' +
         + '</tr>'
+
         '</tr>' +
         + '</table></center>'
+
         '</table></center>'
 
       );
 
       );
  
Line 527: Line 517:
 
       if(hasMW) {
 
       if(hasMW) {
 
         mw.loader.using('jquery.tablesorter', function() { // add the tablesorter to tables
 
         mw.loader.using('jquery.tablesorter', function() { // add the tablesorter to tables
           $('table.sortable').tablesorter({ sortList: [{ 0: 'asc' }] })
+
           $('table.sortable').tablesorter({ sortList: [{ 0: 'asc' }] });
 
         });
 
         });
 
       }
 
       }
     }
+
     };
 
 
    // generates a simple object of skills and their min/max values
 
    function generateExportSkills() {
 
      var skills = {};
 
      for(var [key, value] of Object.entries(skillsOverview).sort()) {
 
        skills[key] = {
 
          'minimal skill': value.min,
 
          'optimal skill': value.max
 
        };
 
      }
 
      return skills;
 
    }
 
  
     function getURL(article) {
+
     var getURL = function getURL(article) {
 
       if(hasMW) return mw.util.getUrl(article);
 
       if(hasMW) return mw.util.getUrl(article);
 
       return '';
 
       return '';
     }
+
     };
  
     function printNeededSkills() {
+
     var printNeededSkills = function printNeededSkills() {
 
       $('#SCalResourcesrow2').append('<h2>Required skills</h2>');
 
       $('#SCalResourcesrow2').append('<h2>Required skills</h2>');
  
       printExportLink(JSON.stringify(generateExportSkills()), 'Export as JSON', 'SCalExportSkillsAsJSON', 'SCalResourcesrow2', 'application/json', 'Skeleton Key - Skills.json');
+
       printExportLink(JSON.stringify(generateSkillsAsJSON()), 'Export as JSON', 'SCalExportSkillsAsJSON', 'SCalResourcesrow2', 'application/json', 'Skeleton Key - Skills.json');
       printExportLink(generateSkillsAsTEXT(determineNeededDigitlengthSkills(9,7)), 'Export as TEXT', 'SCalExportSkillsAsTEXT', 'SCalResourcesrow2', 'text/plain', 'Skeleton Key - Skills.txt');
+
       printExportLink(generateSkillsAsTEXT(), 'Export as TEXT', 'SCalExportSkillsAsTEXT', 'SCalResourcesrow2', 'text/plain', 'Skeleton Key - Skills.txt');
 
       var skillsAsXML = '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>\n' + generateSkillsAsXML(0);
 
       var skillsAsXML = '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>\n' + generateSkillsAsXML(0);
 
       printExportLink(skillsAsXML, 'Export as XML', 'SCalExportSkillsAsXML', 'SCalResourcesrow2', 'application/xml', 'Skeleton Key - Skills.xml');
 
       printExportLink(skillsAsXML, 'Export as XML', 'SCalExportSkillsAsXML', 'SCalResourcesrow2', 'application/xml', 'Skeleton Key - Skills.xml');
  
       $('#SCalResourcesrow2').append('<table id="SCalNeededSkillsTable" class="wikitable sortable"><thead>'
+
       $('#SCalResourcesrow2').append('<table id="SCalNeededSkillsTable" class="wikitable sortable"><thead>' +
         + '<tr>'
+
         '<tr>' +
         + '<th align="left">Skill</th>'
+
         '<th align="left">Skill</th>' +
         + '<th align="left">Minimal</th>'
+
         '<th align="left">Minimal</th>' +
         + '<th align="left">Optimal</th>'
+
         '<th align="left">Optimal</th>' +
         + '</tr></thead>'
+
         '</tr></thead>'
 
       );
 
       );
  
 
       var tablecontent = '<tbody>';
 
       var tablecontent = '<tbody>';
  
       for(var [key, value] of Object.entries(skillsOverview).sort()) {
+
       for(var [skill, values] of Object.entries(skillsOverview).sort()) {
         tablecontent += '<tr>'
+
         tablecontent += '<tr>' +
           + '<td><a href="' + getURL(key) + '">' + key + '</a></td>'
+
           '<td><a href="' + getURL(skill) + '">' + skill + '</a></td>' +
           + '<td>' + value.min + '</td>'
+
           '<td>' + values.min + '</td>' +
           + '<td>' + value.max + '</td>'
+
           '<td>' + values.max + '</td>' +
           + '</tr>';
+
           '</tr>';
 
       }
 
       }
  
Line 578: Line 556:
 
       tablecontent += '</table>';
 
       tablecontent += '</table>';
 
       $('#SCalNeededSkillsTable').append(tablecontent);
 
       $('#SCalNeededSkillsTable').append(tablecontent);
     }
+
     };
 
      
 
      
 
     // Tests if the provided value has a valid integer and sets a cookie for it
 
     // Tests if the provided value has a valid integer and sets a cookie for it
     function setCookieByInteger(id, value) {
+
     var setCookieByInteger = function setCookieByInteger(id, value) {
 
       if(isRealInteger(value)) setCookie(id, value, new Date(3030,1,4));
 
       if(isRealInteger(value)) setCookie(id, value, new Date(3030,1,4));
     }
+
     };
  
     function recursivePrintTreeProduct(details) {
+
     var recursivePrintTreeProduct = function recursivePrintTreeProduct(products) {
 
       var html = '';
 
       var html = '';
       for(var [key, values] of Object.entries(details)) {
+
       for(var [product, values] of Object.entries(products)) {
 
         html += '  <ul>';
 
         html += '  <ul>';
 
         html += '    <li>';
 
         html += '    <li>';
  
         if(key != 'Skeleton Key') {
+
         if(product != 'Skeleton Key') {
           html += '      <span class="' + values.type + '"><a href="' + getURL(key) + '">' + key + '</a> (<span id="' + values.id + 'Needed"></span>)</span>'
+
           html += '      <span class="' + values.type + '"><a href="' + getURL(product) + '">' + product + '</a> (<span id="' + values.id + 'Needed"></span>)&nbsp;&nbsp;</span>' +
            + '      <label for="' + values.id + 'Have">have:</label><input class="SCalTreeHave" id="' + values.id + 'Have" name="' + values.id + 'Have" type="number" size="8">';
+
                  '      <label for="' + values.id + 'Have">have:</label><input class="SCalTreeHave" id="' + values.id + 'Have" name="' + values.id + 'Have" type="number" size="8">';
 
         } else {
 
         } else {
           html += '      <span class="' + values.type + '"><a href="' + getURL(key) + '">' + key + '</a> (<span id="' + values.id + 'Needed"></span>)</span>';
+
           html += '      <span class="' + values.type + '"><a href="' + getURL(product) + '">' + product + '</a> (<span id="' + values.id + 'Needed"></span>)</span>';
 
         }
 
         }
 
         if(values.hasOwnProperty('subs')) {
 
         if(values.hasOwnProperty('subs')) {
Line 604: Line 582:
 
       }
 
       }
 
       return html;
 
       return html;
     }
+
     };
  
     function recursiveUpdateTreeProduct(details) {
+
     var recursiveUpdateTreeProduct = function recursiveUpdateTreeProduct(products) {
       for(var [key, values] of Object.entries(details)) {
+
       for(var [product, values] of Object.entries(products)) {
         if(key != 'Skeleton Key') {
+
         if(product != 'Skeleton Key') {
 
           var greenClass = 'SCalGreenInput';
 
           var greenClass = 'SCalGreenInput';
 
           $('#' + values.id + 'Have').removeClass(greenClass);
 
           $('#' + values.id + 'Have').removeClass(greenClass);
Line 625: Line 603:
 
         }
 
         }
 
       }
 
       }
     }
+
     };
  
     function printResources(resources, headline, type) {
+
     var printResources = function printResources(resources, headline, type) {
 +
      var allDoneId = 'SCal' + type + 'AllDone';
 
       $('#SCalResourcesrow2').append('<h2>' + headline + '</h2>');
 
       $('#SCalResourcesrow2').append('<h2>' + headline + '</h2>');
 
       $('#SCalResourcesrow2').append('<div id="SCal' + type + 'ExportLinks"></div>');
 
       $('#SCalResourcesrow2').append('<div id="SCal' + type + 'ExportLinks"></div>');
 
        
 
        
       $('#SCalResourcesrow2').append('<table id="SCal' + type + 'ResourceTable" class="wikitable sortable"><thead>'
+
       $('#SCalResourcesrow2').append('<table id="SCal' + type + 'ResourceTable" class="wikitable sortable"><thead>' +
         + '<tr>'
+
         '<tr>' +
         + '<th align="left">Resource</th>'
+
         '<th style="vertical-align: top">Resource</th>' +
         + '<th align="left">Need</th>'
+
         '<th style="vertical-align: top">Need</th>' +
         + '</tr></thead>'
+
        '<th style="vertical-align: top" class="unsortable"><span><input type="checkbox" id="' + allDoneId + '"></span></th>' +
 +
         '</tr></thead>'
 
       );
 
       );
  
 
       var tablecontent = '<tbody>';
 
       var tablecontent = '<tbody>';
  
       for (var key of Object.keys(resources).sort()) {
+
       for (var resource of Object.keys(resources).sort()) {
         var id = key.replaceAll(' ','') + 'Need';
+
         var id = resource.replaceAll(' ','') + 'Need';
 +
        var doneId = id + 'Done';
 
          
 
          
         tablecontent += '<tr>'
+
         tablecontent += '<tr>' +
           + '<td><a href="' + getURL(key) + '">' + key + '</a></td>'
+
           '<td><a href="' + getURL(resource) + '">' + resource + '</a></td>' +
           + '<td id="' + id + '"></td>'
+
           '<td id="' + id + '"></td>' +
           + '</tr>';
+
           '<td style="text-align: center"><input type="checkbox" id="' + doneId + '"></td>' +
 +
          '</tr>';
 
       }
 
       }
  
Line 653: Line 635:
  
 
       $('#SCal' + type + 'ResourceTable').append(tablecontent);
 
       $('#SCal' + type + 'ResourceTable').append(tablecontent);
     }
+
     
 +
      $('#' + allDoneId).click(function() { // handle the 'all done' checkbox
 +
          if($(this).prop('checked')) {
 +
            setCookie($(this).get(0).id, true, new Date(3030,1,4));
 +
           
 +
            for (var resource of Object.keys(resources)) {
 +
              var doneId = resource.replaceAll(' ','') + 'NeedDone';
 +
              setCookie(doneId, true, new Date(3030,1,4));
 +
            }
 +
          } 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(function() {
 +
          if($(this).prop('checked')) {
 +
            setCookie($(this).get(0).id, true, new Date(3030,1,4));
 +
            updateLists();
 +
          } else {
 +
            deleteCookie($(this).get(0).id);
 +
            updateLists();
 +
          }
 +
        });
 +
      }
 +
     };
 
      
 
      
     function updateResources(resources, type) {
+
     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 + ' Resources.json');
 
       printExportLink(JSON.stringify(Object.fromEntries(Object.entries(resources).sort())), 'Export as JSON', 'SCalExport' + type + 'resourcesAsJSON', 'SCal' + type + 'ExportLinks', 'application/json', 'Skeleton Key - ' + type + ' Resources.json');
 
       printExportLink(generateResourcesAsTEXT(resources), 'Export as TEXT', 'SCalExport' + type + 'resourcesAsTEXT', 'SCal' + type + 'ExportLinks', 'text/plain', 'Skeleton Key - ' + type + ' Resources.txt');
 
       printExportLink(generateResourcesAsTEXT(resources), 'Export as TEXT', 'SCalExport' + type + 'resourcesAsTEXT', 'SCal' + type + 'ExportLinks', 'text/plain', 'Skeleton Key - ' + type + ' Resources.txt');
Line 661: Line 678:
 
       printExportLink(resourceXML, 'Export as XML', 'SCalExport' + type + 'resourcesAsXML', 'SCal' + type + 'ExportLinks', 'application/xml', 'Skeleton Key - ' + type + ' Resources.xml');
 
       printExportLink(resourceXML, 'Export as XML', 'SCalExport' + type + 'resourcesAsXML', 'SCal' + type + 'ExportLinks', 'application/xml', 'Skeleton Key - ' + type + ' Resources.xml');
 
        
 
        
       for (var [key, value] of Object.entries(resources)) {
+
       var allDoneChecked = getCookie(allDoneId); // handle the 'All done' checkbox
         var id = key.replaceAll(' ', '') + 'Need';
+
      if(allDoneChecked === 'true') {
         $('#' + id).html(value);
+
         $('#' + 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);
 +
        }
 +
      }
 +
     };
 
      
 
      
     function generateResourcesAsTEXT(resources) {
+
     var generateResourcesAsTEXT = function generateResourcesAsTEXT(resources) {
 
       var text = '';
 
       var text = '';
       var length = determineNeededDigitlength(resources, 0);
+
       var length = determineNeededDigitlengthResources(resources);
       for(var [key, count] of Object.entries(resources).sort()) {
+
       for(var [resource, count] of Object.entries(resources).sort()) {
 
         var indent = '';
 
         var indent = '';
 
         for(var i = 0;i < length - count.toString().length; i++) {
 
         for(var i = 0;i < length - count.toString().length; i++) {
Line 676: Line 709:
 
         }
 
         }
 
          
 
          
         text += count + indent + '  ' + key + '\n';
+
         text += count + indent + '  ' + resource + '\n';
 
       }
 
       }
 
       return text;
 
       return text;
     }
+
     };
 
      
 
      
     function generateSkillsAsXML(indent) {
+
     var generateSkillsAsXML = function generateSkillsAsXML(indent) {
       indentation = '';
+
       var indentation = '';
 
       for(var i = 0; i < indent; i++) {
 
       for(var i = 0; i < indent; i++) {
 
         indentation += ' ';
 
         indentation += ' ';
Line 688: Line 721:
 
        
 
        
 
       var xml = indentation + '<Skills>\n';
 
       var xml = indentation + '<Skills>\n';
       for(var [key, values] of Object.entries(skillsOverview).sort()) {
+
       for(var [skill, values] of Object.entries(skillsOverview).sort()) {
         xml += indentation + '  <skill minimal="' + values.min + '" optimal="' + values.max + '">' + key + '</item>\n';
+
         xml += indentation + '  <skill minimal="' + values.min + '" optimal="' + values.max + '">' + skill + '</item>\n';
 
       }
 
       }
       xml += indentation + '</Skills>'
+
       xml += indentation + '</Skills>';
 
       return xml;
 
       return xml;
     }
+
     };
 
      
 
      
     function generateSkillsAsTEXT(needLength, optLength) {
+
     var generateSkillsAsTEXT = function generateSkillsAsTEXT() {
 +
      var length = determineNeededDigitlengthSkills();
 
       var text = 'Minimal  Optimal  Skillname\n';
 
       var text = 'Minimal  Optimal  Skillname\n';
 
       text += '--------------------------------------------------------\n';
 
       text += '--------------------------------------------------------\n';
  
       for(var [key, values] of Object.entries(skillsOverview).sort()) {
+
       for(var [skill, values] of Object.entries(skillsOverview).sort()) {
 
         var needIndent = '';
 
         var needIndent = '';
 
         var optIndent = '';
 
         var optIndent = '';
         for(var i = 0;i < needLength - values.min.toString().length; i++) {
+
         for(var i = 0;i < length[0] - values.min.toString().length; i++) {
 
           needIndent += ' ';
 
           needIndent += ' ';
 
         }
 
         }
  
         for(var i = 0;i < optLength - values.max.toString().length; i++) {
+
         for(var j = 0;j < length[1] - values.max.toString().length; j++) {
 
           optIndent += ' ';
 
           optIndent += ' ';
 
         }
 
         }
 
          
 
          
         text += values.min + needIndent + values.max + optIndent + '  ' + key + '\n';
+
         text += values.min + needIndent + values.max + optIndent + skill + '\n';
 
       }
 
       }
 
       return text;
 
       return text;
     }
+
     };
  
     function generateResourcesAsXML(resources, tag, indent) {
+
     var generateResourcesAsXML = function generateResourcesAsXML(resources, tag, indent) {
 
       var indentation = '';
 
       var indentation = '';
 
       for(var i = 0; i < indent; i++) {
 
       for(var i = 0; i < indent; i++) {
Line 722: Line 756:
 
       var xml = indentation + '<' + tag + '>\n';
 
       var xml = indentation + '<' + tag + '>\n';
 
        
 
        
       for(var [key, count] of Object.entries(resources).sort()) {
+
       for(var [resource, count] of Object.entries(resources).sort()) {
         xml += indentation + '  <item needed="' + count + '">' + key + '</item>\n';
+
         xml += indentation + '  <item needed="' + count + '">' + resource + '</item>\n';
 
       }
 
       }
 
       xml += '</' + tag + '>\n';
 
       xml += '</' + tag + '>\n';
 +
     
 
       return xml;
 
       return xml;
     }
+
     };
  
     function isRealInteger(number) {
+
     var isRealInteger = function isRealInteger(number) {
 
       return (!isNaN(number) && parseInt(Number(number)) == number && !isNaN(parseInt(number, 10)));
 
       return (!isNaN(number) && parseInt(Number(number)) == number && !isNaN(parseInt(number, 10)));
     }
+
     };
 
      
 
      
 
     // check if the skill's input fields contain valid entries
 
     // check if the skill's input fields contain valid entries
     function validateSkillInputs() {
+
     var validateSkillInputs = function validateSkillInputs() {
 
       var ready = true;
 
       var ready = true;
 
       $('#SCalErrorText').html('');
 
       $('#SCalErrorText').html('');
Line 740: Line 775:
 
       for (var [skill, values] of Object.entries(skillsOverview)) {
 
       for (var [skill, values] of Object.entries(skillsOverview)) {
 
         var inputObj = $('#' + values.id);
 
         var inputObj = $('#' + values.id);
 +
        var skillCount = inputObj.val();
 
          
 
          
 
         inputObj.removeClass('SCalErroreousInput');
 
         inputObj.removeClass('SCalErroreousInput');
 
         inputObj.removeClass('SCalOptimalInput');
 
         inputObj.removeClass('SCalOptimalInput');
 
          
 
          
        number = inputObj.val();
+
         if (!isRealInteger(skillCount)) {
 
 
         if (!isRealInteger(number)) {
 
 
           $('#SCalErrorText').append('<center>' + skill + ': this is not a number!</center>');
 
           $('#SCalErrorText').append('<center>' + skill + ': this is not a number!</center>');
 
           inputObj.addClass('SCalErroreousInput');
 
           inputObj.addClass('SCalErroreousInput');
 
           ready = false;
 
           ready = false;
 
         } else {
 
         } else {
           if (number < 1 || number > maxSkillCount) {  
+
           if (skillCount < 1 || skillCount > maxSkillCount) {  
 
             $('#SCalErrorText').append('<center>' + skill + ': please provide a number between 1 and ' + maxSkillCount + '</center>');
 
             $('#SCalErrorText').append('<center>' + skill + ': please provide a number between 1 and ' + maxSkillCount + '</center>');
 
             inputObj.addClass('SCalErroreousInput');
 
             inputObj.addClass('SCalErroreousInput');
Line 757: Line 791:
 
           }
 
           }
 
            
 
            
           if(number < values.min) {
+
           if(skillCount < values.min) {
             $('#SCalErrorText').append('<center>' + skill + ': Skill ' + skill + ' is too low! (have:' + number + ', need: ' + values.min + ')</center>');
+
             $('#SCalErrorText').append('<center>' + skill + ': Skill ' + skill + ' is too low! (have:' + skillCount + ', need: ' + values.min + ')</center>');
 
             inputObj.addClass('SCalErroreousInput');
 
             inputObj.addClass('SCalErroreousInput');
 
           }
 
           }
 
           if(!inputObj.hasClass('SCalErroreousInput')) {
 
           if(!inputObj.hasClass('SCalErroreousInput')) {
             if(number >= values.max) inputObj.addClass('SCalOptimalInput');
+
             if(skillCount >= values.max) inputObj.addClass('SCalOptimalInput');
 
           }
 
           }
 
         }
 
         }
 
       }
 
       }
 
       return ready;
 
       return ready;
     }
+
     };
 
      
 
      
     function setCookie(name, value, date) {
+
     var setCookie = function setCookie(name, value, date) {
 
       var expires = '; expires=' + date.toGMTString();
 
       var expires = '; expires=' + date.toGMTString();
 
       var cookie = name + '=' + value + expires + cookieParameters;
 
       var cookie = name + '=' + value + expires + cookieParameters;
 
       document.cookie = cookie;
 
       document.cookie = cookie;
     }
+
     };
  
     function getCookie(name) {
+
     var getCookie = function getCookie(name) {
 
       var nameEQ = name + '=';
 
       var nameEQ = name + '=';
 
       var ca = document.cookie.split(';');
 
       var ca = document.cookie.split(';');
Line 781: Line 815:
 
         var c = ca[i];
 
         var c = ca[i];
 
         while (c.charAt(0)==' ') c = c.substring(1,c.length);
 
         while (c.charAt(0)==' ') c = c.substring(1,c.length);
         if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
+
         if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length,c.length);
 
       }
 
       }
 
       return null;
 
       return null;
     }
+
     };
 
      
 
      
     function deleteCookie(name) {
+
     var deleteCookie = function deleteCookie(name) {
 
       var expires = '; expires=Thu, 01 Jan 1970 00:00:00 UTC';
 
       var expires = '; expires=Thu, 01 Jan 1970 00:00:00 UTC';
 
       var cookie = name + '=' + expires + cookieParameters;
 
       var cookie = name + '=' + expires + cookieParameters;
 
       document.cookie = cookie;
 
       document.cookie = cookie;
 +
    };
 +
    // END OF FUNCTIONS
 +
    ///////////////////////////////////////////////////////////////////
 +
   
 +
    // get the css stylesheet
 +
    // (workaround for htaccess shortlink rules)
 +
    if(hasMW) {
 +
      var url = getURL('MediaWiki:SCalScript.css');
 +
      if(url !== '') {
 +
        if(url.includes('?')) {
 +
          url += '&';
 +
        } else {
 +
          url += '?';
 +
        }
 +
        url += 'action=raw&ctype=text/css';
 +
        mw.loader.load(url, 'text/css');
 +
      }
 
     }
 
     }
 +
   
 +
    // generate IDs for the tradeskills
 +
    for(var [key, values] of Object.entries(skillsOverview)) {
 +
      values.id = 'SCal' + key.replace(/\s/, '') + 'Input';
 +
    }
 +
 +
    // Let's start adding content
 +
    // top part of the gui (static)
 +
    printHeader();
 +
    printKeyCounter();
 +
    printLists();
 +
 +
    // fill the lists. (dynamic)
 +
    updateLists();
 
   }
 
   }
 
});
 
});

Revision as of 21:34, 1 July 2022

/**
 * Skeleton Key Calculator
 * Elteria Shadowhand
 */
$(document).ready(function() {
  'use strict';
  if (document.getElementById('SCalMain')) {
    let version = '1.2'; // the script 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
    let currentProductIdCount; // Dirty little helper for adding dynamic IDs to the products

    let maxKeyCount = 5000; // Skeleton Key counter maximum value
    let maxSkillCount = 2000; // Skill input fields maximum value
    // Set to false for the script to not use the MediaWiki API (for local execution outside of wikis)
    // This invalidates all URLs and removes other mediawiki specific generated content, i.e. tablesorters and stylesheets
    let hasMW = true; 
    let cookieParameters = '; path=/; SameSite=Strict';

    // 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() {
      $('#SCalMain').append('<center><label for="SCalCountInput" id="SCalCountInputLbl">How many Skeleton Keys would you want to create?</label></center>');
      $('#SCalMain').append('<center><input type="number" id="SCalCountInput" name="SCalCountInput" value="1" size="6"></center>');
      $('#SCalCountInput').change(function(event) {
        var value = event.target.value;
        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 setCookie = function(event) {
            setCookieByInteger(event.target.id, event.target.value);
            updateLists();
        };
      $('#SCalMain').append('<h1 id="SCalH1">The Calculator</h1>');
      $('#SCalMain').append('<center style="font-size: 0.8em; margin-bottom:30px">(v ' + version + ')</center>');
      $('#SCalMain').append('<center>Please enter your current skills in the fields below. Defaults to optimal skills.</center>');

      $('#SCalMain').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();
          }
        }
      });

      $('#SCalMain').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)) {
        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 + '" size="6" value="' + skillLevel + '"><label for="' + id + '">&nbsp;&nbsp;' + skillName + '</label><br />');
        
        $('#' + id).change(setCookie);
        $('#' + id).change();
      }
      
      $('#SCalMain').append('<br /><center>A <font style="color: red"><b>red</b></font> box means there is an error.</center>');
      $('#SCalMain').append('<center>A <font style="color: green"><b>green</b></font> box means you\'re optimal for creating the items.</center>');
      $('#SCalMain').append('<div id="SCalErrorText"></div>');
      $('#SCalMain').append('<br />');
      $('#SCalMain').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 products object, containing the needed resources
    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 += '<ResourcesTree>\n';
      XMLeverything += recursiveGenerateTreeAsXML(productsDetails, 2);
      XMLeverything += '</ResourcesTree>\n';
      XMLeverything += generateResourcesAsXML(baseResources, 'BaseResources', 0);
      XMLeverything += generateResourcesAsXML(refinedResources, 'RefinedResources', 0);
      XMLeverything += generateSkillsAsXML(0);
      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 resources needed
    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() {
      $('#SCalResourcesrow1').append('<h2>Tree of needed products</h2>');
      $('#SCalResourcesrow1').append('<div id="SCalResourcesExportLinks"></div>');
      
      $('#SCalResourcesrow1').append(recursivePrintTreeProduct(productsDetails));
      recursiveAddHaveChangeEvents(productsDetails);
    };

    var recursiveAddHaveChangeEvents = function recursiveAddHaveChangeEvents(products) {
      var setCookie = 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(setCookie);
        
        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, 'Base');
        updateResources(refinedResources, 'Refined');
      }      
    };
    
    //reinitializes everything and calculate needed data
    var initialize = function initialize() {
      baseResources = {};
      refinedResources = {};
      currentProductIdCount = 0;
      var keysWanted = getCookie('SCalCountInput');
      if(!isRealInteger(keysWanted)) keysWanted = 1;
      if(keysWanted < 0) keysWanted = 1;
      if(keysWanted > maxKeyCount) keysWanted = maxKeyCount;
      $('#SCalCountInput').val(keysWanted); // set the Skeleton Key count
      
      productsDetails = recursiveCalculateResourceData(products, keysWanted);
    };

    //creates the contents of the lists
    var printLists = function printLists() {
      initialize();

      // Container for the 'Export Everything' features
      $('#SCalMain').append('<div id="SCalExportEverything" style="text-align: center"></div>');
      
      // Containers for the lists
      $('#SCalMain').append('<center><table>' +
        '<tr valign="top">' +
        '<td id="SCalResourcesrow1"></td>' +
        '<td id="SCalResourcesrow2"></td>' +
        '</tr>' +
        '</table></center>'
      );

      printTree();
      printResources(baseResources, 'Overview of Base Resources', 'Base');
      printResources(refinedResources, 'Overview of Refined Resources', 'Refined');
      printNeededSkills();

      if(hasMW) {
        mw.loader.using('jquery.tablesorter', function() { // add the tablesorter to tables
          $('table.sortable').tablesorter({ sortList: [{ 0: 'asc' }] });
        });
      }
    };

    var getURL = function getURL(article) {
      if(hasMW) return mw.util.getUrl(article);
      return '';
    };

    var printNeededSkills = function printNeededSkills() {
      $('#SCalResourcesrow2').append('<h2>Required skills</h2>');

      printExportLink(JSON.stringify(generateSkillsAsJSON()), 'Export as JSON', 'SCalExportSkillsAsJSON', 'SCalResourcesrow2', 'application/json', 'Skeleton Key - Skills.json');
      printExportLink(generateSkillsAsTEXT(), 'Export as TEXT', 'SCalExportSkillsAsTEXT', 'SCalResourcesrow2', 'text/plain', 'Skeleton Key - Skills.txt');
      var skillsAsXML = '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>\n' + generateSkillsAsXML(0);
      printExportLink(skillsAsXML, 'Export as XML', 'SCalExportSkillsAsXML', 'SCalResourcesrow2', 'application/xml', 'Skeleton Key - Skills.xml');

      $('#SCalResourcesrow2').append('<table id="SCalNeededSkillsTable" class="wikitable sortable"><thead>' +
        '<tr>' +
        '<th align="left">Skill</th>' +
        '<th align="left">Minimal</th>' +
        '<th align="left">Optimal</th>' +
        '</tr></thead>'
      );

      var tablecontent = '<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>';
      $('#SCalNeededSkillsTable').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, new Date(3030,1,4));
    };

    var recursivePrintTreeProduct = function recursivePrintTreeProduct(products) {
      var html = '';
      for(var [product, values] of Object.entries(products)) {
        html += '  <ul>';
        html += '    <li>';

        if(product != 'Skeleton Key') {
          html += '      <span class="' + values.type + '"><a href="' + getURL(product) + '">' + product + '</a> (<span id="' + values.id + 'Needed"></span>)&nbsp;&nbsp;</span>' + 
                  '      <label for="' + values.id + 'Have">have:</label><input class="SCalTreeHave" id="' + values.id + 'Have" name="' + values.id + 'Have" type="number" size="8">';
        } else {
          html += '      <span class="' + values.type + '"><a href="' + getURL(product) + '">' + product + '</a> (<span id="' + values.id + 'Needed"></span>)</span>';
        }
        if(values.hasOwnProperty('subs')) {
          html += recursivePrintTreeProduct(values.subs);
        }
        html += '    </li>';
        html += '  </ul>';
      }
      return html;
    };

    var recursiveUpdateTreeProduct = function recursiveUpdateTreeProduct(products) {
      for(var [product, values] of Object.entries(products)) {
        if(product != 'Skeleton Key') {
          var greenClass = 'SCalGreenInput';
          $('#' + values.id + 'Have').removeClass(greenClass);

          var have = getCookie(values.id + 'Have');
          if(!isRealInteger(have)) have = 0;
          if(have < 0) have = 0;
          if(have > 0) $('#' + values.id + 'Have').addClass(greenClass);
          $('#' + values.id + 'Needed').html(values.needed);
          $('#' + values.id + 'Have').val(have);
        } else {
          $('#' + values.id + 'Needed').html(values.needed);
        }
        if(values.hasOwnProperty('subs')) {
          recursiveUpdateTreeProduct(values.subs);
        }
      }
    };

    var printResources = function printResources(resources, headline, type) {
      var allDoneId = 'SCal' + type + 'AllDone';
      $('#SCalResourcesrow2').append('<h2>' + headline + '</h2>');
      $('#SCalResourcesrow2').append('<div id="SCal' + type + 'ExportLinks"></div>');
      
      $('#SCalResourcesrow2').append('<table id="SCal' + type + 'ResourceTable" 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>'
      );

      var tablecontent = '<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>';

      $('#SCal' + type + 'ResourceTable').append(tablecontent);
      
      $('#' + allDoneId).click(function() { // handle the 'all done' checkbox
          if($(this).prop('checked')) {
            setCookie($(this).get(0).id, true, new Date(3030,1,4));
            
            for (var resource of Object.keys(resources)) {
              var doneId = resource.replaceAll(' ','') + 'NeedDone';
              setCookie(doneId, true, new Date(3030,1,4));
            }
          } 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(function() {
          if($(this).prop('checked')) {
            setCookie($(this).get(0).id, true, new Date(3030,1,4));
            updateLists();
          } else {
            deleteCookie($(this).get(0).id);
            updateLists();
          }
        });
      }
    };
    
    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 + ' Resources.json');
      printExportLink(generateResourcesAsTEXT(resources), 'Export as TEXT', 'SCalExport' + type + 'resourcesAsTEXT', 'SCal' + type + 'ExportLinks', 'text/plain', 'Skeleton Key - ' + type + ' Resources.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 + ' Resources.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(indent) {
      var indentation = '';
      for(var i = 0; i < indent; i++) {
        indentation += ' ';
      }
      
      var xml = indentation + '<Skills>\n';
      for(var [skill, values] of Object.entries(skillsOverview).sort()) {
        xml += indentation + '  <skill minimal="' + values.min + '" optimal="' + values.max + '">' + skill + '</item>\n';
      }
      xml += indentation + '</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, indent) {
      var indentation = '';
      for(var i = 0; i < indent; i++) {
        indentation += ' ';
      }
      var xml = indentation + '<' + tag + '>\n';
      
      for(var [resource, count] of Object.entries(resources).sort()) {
        xml += indentation + '  <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 ' + 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, date) {
      var expires = '; expires=' + date.toGMTString();
      var cookie = name + '=' + value + expires + cookieParameters;
      document.cookie = cookie;
    };

    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';
      var cookie = name + '=' + expires + cookieParameters;
      document.cookie = cookie;
    };
    // END OF FUNCTIONS
    ///////////////////////////////////////////////////////////////////
    
    // get the css stylesheet 
    // (workaround for htaccess shortlink rules)
    if(hasMW) {
      var url = getURL('MediaWiki:SCalScript.css');
      if(url !== '') {
        if(url.includes('?')) {
          url += '&';
        } else {
          url += '?';
        }
        url += 'action=raw&ctype=text/css';
        mw.loader.load(url, 'text/css');
      }
    }
    
    // generate IDs for the tradeskills
    for(var [key, values] of Object.entries(skillsOverview)) {
      values.id = 'SCal' + key.replace(/\s/, '') + 'Input';
    }

    // Let's start adding content
    // top part of the gui (static)
    printHeader();
    printKeyCounter();
    printLists();

    // fill the lists. (dynamic)
    updateLists();
  }
});