Difference between revisions of "MediaWiki:SCalScript.js"

From Istaria Lexica

(Big script import)
(Big script import)
Line 10: Line 10:
 
     let productsSkills = {}; // A list of all product's skills needed, containing their names and their min/opt values.
 
     let productsSkills = {}; // A list of all product's skills needed, containing their names and their min/opt values.
 
     let skillsOverview; // A list of all needed skills, their min/opt stats and an id  
 
     let skillsOverview; // A list of all needed skills, their min/opt stats and an id  
 +
    let resourcesTree = {}; // A tree of resources and needed counts. to be filled.
 
     let initialKeyCount = 10;
 
     let initialKeyCount = 10;
 
     let maxKeyCount = 5000;
 
     let maxKeyCount = 5000;
 
     let maxSkillCount = 2000;
 
     let maxSkillCount = 2000;
   
 
 
     // the dataset
 
     // the dataset
 
     // 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.  
Line 183: Line 183:
 
     printSkillInputs();
 
     printSkillInputs();
  
 +
    $('#SCalMain').append('<div id="SCalExportEverything" style="text-align: center"></div>');
 +
   
 +
    // Details of the crafting
 
     $('#SCalMain').append('<center><table>'
 
     $('#SCalMain').append('<center><table>'
 
       + '<tr valign="top">'
 
       + '<tr valign="top">'
Line 197: Line 200:
 
       printLists();
 
       printLists();
 
     }
 
     }
 +
 +
    // The 'print everything' feature
 +
    resourcesTree = recursiveGenerateTree(products);
 +
    printPrintEverythingLinks();
  
 
     //FUNCTIONS:
 
     //FUNCTIONS:
Line 212: Line 219:
 
       $('#SCalMain').append('<br /><center>A <font style="color: red"><b>red</b></font> box means there is an error.</center>');
 
       $('#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('<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('<br />');
 
       $('#SCalMain').append('<center><input type="button" id="SCalCountBtn" value="Calculate now!"></center>');
 
       $('#SCalMain').append('<center><input type="button" id="SCalCountBtn" value="Calculate now!"></center>');
      $('#SCalMain').append('<div id="SCalErrorText"></div>');
 
 
       $('#SCalMain').append('<hr />');
 
       $('#SCalMain').append('<hr />');
  
Line 247: Line 254:
 
       });
 
       });
 
     }
 
     }
 +
 +
    function printPrintEverythingLinks() {
 +
      $('#SCalExportEverything').append('<a id="SCalExportEverythingAsJSONA"></a>');
 +
      $('#SCalExportEverythingAsJSONA').append('<input id="SCalExportEverythingAsJSON" name="SCalExportEverythingAsJSON" type="button" value="Export everything as JSON">');
 +
      $('#SCalExportEverythingAsJSON').click(function() {
 +
        var JSONeverything = {
 +
          'Product Tree': resourcesTree,
 +
          'Base Resources': baseResources,
 +
          'Refined Resources': refinedResources,
 +
          'Skills': generateSkillsAsJSON()
 +
        };
 +
       
 +
        var url = 'data:text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(JSONeverything));
 +
        $('#SCalExportEverythingAsJSONA').attr('href', url);
 +
        $('#SCalExportEverythingAsJSONA').attr('download', 'Skeleton Key - All.json');
 +
      });
 +
      $('#SCalExportEverything').append('<a id="SCalExportEverythingAsTEXTA"></a>');
 +
      $('#SCalExportEverythingAsTEXTA').append('<input id="SCalExportEverythingAsTEXT" name="SCalExportEverythingAsTEXT" type="button" value="Export everything as TEXT">');
 +
      $('#SCalExportEverythingAsTEXT').click(function() {
 +
        var text = '--- Tree of needed Resources ---\n\n';
 +
        text += recursiveGenerateTreeAsTEXT(resourcesTree, 0);
 +
        text += '\n\n';
 +
        text += '--- Needed Base Resources ---\n\n';
 +
        text += generateResourcesAsTEXT(baseResources);
 +
        text += '\n\n';
 +
        text += '--- Needed Refined Resources ---\n\n';
 +
        text += generateResourcesAsTEXT(refinedResources);
 +
        text += '\n\n';
 +
        text += '--- Needed Skills ---\n\n';
 +
        text += generateSkillsAsTEXT([9,7]);
 +
       
 +
        var url = 'data:text/plain;charset=utf-8,' + encodeURIComponent(text);
 +
        $('#SCalExportEverythingAsTEXTA').attr('href', url);
 +
        $('#SCalExportEverythingAsTEXTA').attr('download', 'Skeleton Key - All.txt');
 +
      });
 +
      $('#SCalExportEverything').append('<a id="SCalExportEverythingAsXMLA"></a>');
 +
      $('#SCalExportEverythingAsXMLA').append('<input id="SCalExportEverythingAsXML" name="SCalExportEverythingAsXML" type="button" value="Export everything as XML">');
 +
      $('#SCalExportEverythingAsXML').click(function() {
 +
        var text = '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>\n';
 +
        text += '<ResourcesTree>\n';
 +
        text += recursiveGenerateTreeAsXML(resourcesTree, 2);
 +
        text += '</ResourcesTree>\n';
 +
        text += generateResourcesAsXML(baseResources,'BaseResources', 0);
 +
        text += generateResourcesAsXML(refinedResources,'RefinedResources', 0);
 +
        text += generateSkillsAsXML(0);
 +
        var url = 'data:application/xml;charset=utf-8,' + encodeURIComponent(text);
 +
 +
        $('#SCalExportEverythingAsXMLA').attr('href', url);
 +
        $('#SCalExportEverythingAsXMLA').attr('download', 'Skeleton Key - All.xml');
 +
      });
 +
    }
 +
   
 +
    function generateSkillsAsJSON() {
 +
      var json = {};
 +
      for(var [key, values] of Object.entries(skillsOverview).sort()) {
 +
        json[key] = {
 +
          minimal: values.min,
 +
          optimal: values.max
 +
        }
 +
      }
 +
      return json;
 +
    }
 +
   
 +
    function recursiveGenerateTree(product) {
 +
      var tree = {};
 +
      for(var [key, value] of Object.entries(product)) {
 +
        tree[key] = { needed: value.needed };
 +
       
 +
        if(value.hasOwnProperty('subs')) {
 +
          tree[key].subs = recursiveGenerateTree(value.subs);
 +
        }
 +
      }
 +
      return tree;
 +
    }
 +
 +
    function recursiveGenerateTreeAsTEXT(tree, indent) {
 +
      var text = '';
 +
      for(var [key, value] of Object.entries(tree)) {
 +
        for(var i=0; i<indent; i++) {
 +
          text += ' ';
 +
        }
 +
       
 +
        text += '|- ';
 +
        text += key + ' ';
 +
        text += value.needed;
 +
        text += '\n';
 +
 +
        if(value.hasOwnProperty('subs')) {
 +
          text += recursiveGenerateTreeAsTEXT(value.subs, indent+2);
 +
        }
 +
      }
 +
      return text;
 +
    }
 +
 +
    function recursiveGenerateTreeAsXML(tree, indent) {
 +
      var xml = '';
 +
      for(var [key, value] of Object.entries(tree)) {
 +
        for(var i=0; i<indent; i++) {
 +
          xml += ' ';
 +
        }
 +
       
 +
        xml += '<item name="' + key +'" needed="' + value.needed + '">\n';
 +
 +
        if(value.hasOwnProperty('subs')) {
 +
          xml += recursiveGenerateTreeAsXML(value.subs, indent+2);
 +
        }
 +
 +
        for(var i=0; i<indent; i++) {
 +
          xml += ' ';
 +
        }
 +
       
 +
        xml +='</item>\n';
 +
      }
 +
      return xml;
 +
    }
 +
 +
    function determineNeededDigitlength(resources, length) {
 +
      for(var value of Object.values(resources)) {
 +
        if(value.toString().length > length) length = value.toString().length;
 +
      }
 +
      return length;
 +
    }
 +
 +
    function determineNeededDigitlengthSkills(length) {
 +
      for(var key of Object.keys(skillsOverview)) {
 +
        if(skillsOverview[key].min.toString().length > length) length[0] = skillsOverview[key].min.toString().length;
 +
        if(skillsOverview[key].max.toString().length > length) length[1] = skillsOverview[key].max.toString().length;
 +
      }
 +
      return length;
 +
    }
 +
  
 
     function printLists() {
 
     function printLists() {
Line 255: Line 393:
 
       recursiveCalculateNeededResources(products, parseInt($('#SCalCountInput').val()));
 
       recursiveCalculateNeededResources(products, parseInt($('#SCalCountInput').val()));
 
       $('#SCalResources').html('');
 
       $('#SCalResources').html('');
 +
 +
     
 
       $('#SCalResources').append('<h2>Tree of needed products</h2>');
 
       $('#SCalResources').append('<h2>Tree of needed products</h2>');
 +
      $('#SCalResources').append('<a id="SCalExportTreeAsJSONA"></a>');
 +
      $('#SCalExportTreeAsJSONA').append('<input id="SCalExportTreeAsJSON" name="SCalExportTreeAsJSON" type="button" value="Export as JSON">');
 +
      $('#SCalExportTreeAsJSON').click(function() {
 +
        var url = 'data:text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(resourcesTree));
 +
        $('#SCalExportTreeAsJSONA').attr('href', url);
 +
        $('#SCalExportTreeAsJSONA').attr('download', 'Skeleton Key - Tree.json');
 +
      });
 +
      $('#SCalResources').append('<a id="SCalExportTreeAsTEXTA"></a>');
 +
      $('#SCalExportTreeAsTEXTA').append('<input id="SCalExportTreeAsTEXT" name="SCalExportTreeAsTEXT" type="button" value="Export as TEXT">');
 +
      $('#SCalExportTreeAsTEXT').click(function() {
 +
        var url = 'data:text/plain;charset=utf-8,' + encodeURIComponent(recursiveGenerateTreeAsTEXT(resourcesTree, 0));
 +
        $('#SCalExportTreeAsTEXTA').attr('href', url);
 +
        $('#SCalExportTreeAsTEXTA').attr('download', 'Skeleton Key - Tree.txt');
 +
      });
 +
      $('#SCalResources').append('<a id="SCalExportTreeAsXMLA"></a>');
 +
      $('#SCalExportTreeAsXMLA').append('<input id="SCalExportTreeAsXML" name="SCalExportTreeAsXML" type="button" value="Export as XML">');
 +
      $('#SCalExportTreeAsXML').click(function() {
 +
        var url = 'data:application/xml;charset=utf-8,' + encodeURIComponent(
 +
          '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>\n'
 +
          + recursiveGenerateTreeAsXML(resourcesTree, 0));
 +
        $('#SCalExportTreeAsXMLA').attr('href', url);
 +
        $('#SCalExportTreeAsXMLA').attr('download', 'Skeleton Key - Tree.xml');
 +
      });
 +
     
 
       var html = recursivePrintTreeProduct(products);
 
       var html = recursivePrintTreeProduct(products);
 
       $('#SCalResources').append(html);
 
       $('#SCalResources').append(html);
Line 263: Line 427:
 
       printNeededSkills();
 
       printNeededSkills();
  
      mw.loader.using('jquery.tablesorter', function() {
+
//      mw.loader.using('jquery.tablesorter', function() {
        $('table.sortable').tablesorter({ sortList: [{ 0: 'asc' }] })
+
//        $('table.sortable').tablesorter({ sortList: [{ 0: 'asc' }] })
       });
+
//      });
 +
    }
 +
 
 +
    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 printNeededSkills() {
 
     function printNeededSkills() {
 
       $('#SCalResourcesrow2').append('<h2>Required skills</h2>');
 
       $('#SCalResourcesrow2').append('<h2>Required skills</h2>');
 +
 +
      $('#SCalResourcesrow2').append('<a id="SCalExportSkillsAsJSONA"></a>');
 +
      $('#SCalExportSkillsAsJSONA').append('<input id="SCalExportSkillsAsJSON" name="SCalExportSkillsAsJSON" type="button" value="Export as JSON">');
 +
      $('#SCalExportSkillsAsJSON').click(function() {
 +
        var url = 'data:text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(generateExportSkills()));
 +
        $('#SCalExportSkillsAsJSONA').attr('href', url);
 +
        $('#SCalExportSkillsAsJSONA').attr('download', 'Skeleton Key - Skills.json');
 +
      });
 +
 +
      $('#SCalResourcesrow2').append('<a id="SCalExportSkillsAsTEXTA"></a>');
 +
      $('#SCalExportSkillsAsTEXTA').append('<input id="SCalExportSkillsAsTEXT" name="SCalExportSkillsAsTEXT" type="button" value="Export as TEXT">');
 +
      $('#SCalExportSkillsAsTEXT').click(function() {
 +
        var text = generateSkillsAsTEXT(determineNeededDigitlengthSkills([9,7]));
 +
        var url = 'data:text/plain;charset=utf-8,' + encodeURIComponent(text);
 +
        $('#SCalExportSkillsAsTEXTA').attr('href', url);
 +
        $('#SCalExportSkillsAsTEXTA').attr('download', 'Skeleton Key - Skills.txt');
 +
      });
 +
 +
      $('#SCalResourcesrow2').append('<a id="SCalExportSkillsAsXMLA"></a>');
 +
      $('#SCalExportSkillsAsXMLA').append('<input id="SCalExportSkillsAsXML" name="SCalExportSkillsAsXML" type="button" value="Export as XML">');
 +
      $('#SCalExportSkillsAsXML').click(function() {
 +
        var text = '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>\n'
 +
          + generateSkillsAsXML(0);
 +
        var url = 'data:application/xml;charset=utf-8,' + encodeURIComponent(text);
 +
        $('#SCalExportSkillsAsXMLA').attr('href', url);
 +
        $('#SCalExportSkillsAsXMLA').attr('download', 'Skeleton Key - Skills.xml');
 +
      });
 +
 +
 
       $('#SCalResourcesrow2').append('<table id="SCalNeededSkillsTable" class="wikitable sortable"><thead>'
 
       $('#SCalResourcesrow2').append('<table id="SCalNeededSkillsTable" class="wikitable sortable"><thead>'
 
         + '<tr>'
 
         + '<tr>'
Line 282: Line 486:
 
       for(var [key, value] of Object.entries(skillsOverview).sort()) {
 
       for(var [key, value] of Object.entries(skillsOverview).sort()) {
 
         tablecontent += '<tr>'
 
         tablecontent += '<tr>'
           + '<td><a href="' + mw.util.getUrl(key) + '">' + key + '</a></td>'
+
           + '<td><a href="' /*+ mw.util.getUrl(key)*/ + '">' + key + '</a></td>'
 
           + '<td>' + value.min + '</td>'
 
           + '<td>' + value.min + '</td>'
 
           + '<td>' + value.max + '</td>'
 
           + '<td>' + value.max + '</td>'
Line 361: Line 565:
 
         html += '<ul>';
 
         html += '<ul>';
 
         html += '<li>';
 
         html += '<li>';
         html += '<span class="' + value.type + '"><a href="' + mw.util.getUrl(key) + '">' + key + '</a>: ' + value.needed + '</span>';
+
         html += '<span class="' + value.type + '"><a href="' /*+ mw.util.getUrl(key)*/ + '">' + key + '</a>: ' + value.needed + '</span>';
 
         if(value.hasOwnProperty('subs')) {
 
         if(value.hasOwnProperty('subs')) {
 
           html += recursivePrintTreeProduct(value.subs);
 
           html += recursivePrintTreeProduct(value.subs);
Line 372: Line 576:
  
 
     // Adds needed resources to the dataset and counts up the base and refined resources
 
     // Adds needed resources to the dataset and counts up the base and refined resources
     function recursiveCalculateNeededResources(products, previousCount) {
+
     function recursiveCalculateNeededResources(product, previousCount) {
       for(var [key, values] of Object.entries(products)) {
+
       for(var [key, values] of Object.entries(product)) {
 
         var currentSkill = $('#' + skillsOverview[values.skill].id).val();
 
         var currentSkill = $('#' + skillsOverview[values.skill].id).val();
 
          
 
          
Line 405: Line 609:
 
          
 
          
 
         values.needed = needEffective;
 
         values.needed = needEffective;
         products[key] = values;
+
         product[key] = values;
  
 
         if(values.hasOwnProperty('subs')) {
 
         if(values.hasOwnProperty('subs')) {
Line 411: Line 615:
 
         }
 
         }
 
       }
 
       }
       return products;
+
       return product;
 
     }
 
     }
  
 
     function printRefinedResources() {
 
     function printRefinedResources() {
 
       $('#SCalResourcesrow2').append('<h2>Overview of refined resources</h2>');
 
       $('#SCalResourcesrow2').append('<h2>Overview of refined resources</h2>');
 +
     
 +
      $('#SCalResourcesrow2').append('<a id="SCalExportRefinedresourcesAsJSONA"></a>');
 +
      $('#SCalExportRefinedresourcesAsJSONA').append('<input id="SCalExportRefinedresourceAsJSON" name="SCalExportRefinedresourceAsJSON" type="button" value="Export as JSON">');
 +
      $('#SCalExportRefinedresourceAsJSON').click(function() {
 +
        var url = 'data:text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(Object.fromEntries(Object.entries(refinedResources).sort())));
 +
        $('#SCalExportRefinedresourcesAsJSONA').attr('href', url);
 +
        $('#SCalExportRefinedresourcesAsJSONA').attr('download', 'Skeleton Key - Refined Resources.json');
 +
      });
 +
 +
      $('#SCalResourcesrow2').append('<a id="SCalExportRefinedresourcesAsTEXTA"></a>');
 +
      $('#SCalExportRefinedresourcesAsTEXTA').append('<input id="SCalExportRefinedresourceAsTEXT" name="SCalExportRefinedresourceAsTEXT" type="button" value="Export as TEXT">');
 +
      $('#SCalExportRefinedresourceAsTEXT').click(function() {
 +
        var url = 'data:application/xml;charset=utf-8,' + encodeURIComponent(generateResourcesAsTEXT(baseResources));
 +
        $('#SCalExportRefinedresourcesAsTEXTA').attr('href', url);
 +
        $('#SCalExportRefinedresourcesAsTEXTA').attr('download', 'Skeleton Key - Refined Resouces.txt');
 +
      });
 +
 +
      $('#SCalResourcesrow2').append('<a id="SCalExportRefinedresourcesAsXMLA"></a>');
 +
      $('#SCalExportRefinedresourcesAsXMLA').append('<input id="SCalExportRefinedresourceAsXML" name="SCalExportRefinedresourceAsXML" type="button" value="Export as XML">');
 +
      $('#SCalExportRefinedresourceAsXML').click(function() {
 +
        var url = 'data:application/xml;charset=utf-8,' + encodeURIComponent(
 +
          '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>\n'
 +
          + generateResourcesAsXML(refinedResources,'RefinedResources', 0));
 +
        $('#SCalExportRefinedresourcesAsXMLA').attr('href', url);
 +
        $('#SCalExportRefinedresourcesAsXMLA').attr('download', 'Skeleton Key - Refined Resources.xml');
 +
      });
 +
 
       $('#SCalResourcesrow2').append('<table id="SCalRefinedResourceTable" class="wikitable sortable"><thead>'
 
       $('#SCalResourcesrow2').append('<table id="SCalRefinedResourceTable" class="wikitable sortable"><thead>'
 
         + '<tr>'
 
         + '<tr>'
Line 427: Line 658:
 
       for(var [key, value] of Object.entries(refinedResources).sort()) {
 
       for(var [key, value] of Object.entries(refinedResources).sort()) {
 
         tablecontent += '<tr>'
 
         tablecontent += '<tr>'
           + '<td><a href="' + mw.util.getUrl(key) + '">' + key + '</a></td>'
+
           + '<td><a href="' /*)+ mw.util.getUrl(key)*/ + '">' + key + '</a></td>'
 
           + '<td>' + value + '</td>'
 
           + '<td>' + value + '</td>'
 
           + '</tr>';
 
           + '</tr>';
Line 440: Line 671:
 
       $('#SCalResourcesrow2').html('');
 
       $('#SCalResourcesrow2').html('');
 
       $('#SCalResourcesrow2').append('<h2>Overview of base resources</h2>');
 
       $('#SCalResourcesrow2').append('<h2>Overview of base resources</h2>');
 +
     
 +
      $('#SCalResourcesrow2').append('<a id="SCalExportBaseresourcesAsJSONA"></a>');
 +
      $('#SCalExportBaseresourcesAsJSONA').append('<input id="SCalExportBaseresourceAsJSON" name="SCalExportBaseresourceAsJSON" type="button" value="Export as JSON">');
 +
      $('#SCalExportBaseresourceAsJSON').click(function() {
 +
        var url = 'data:text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(Object.fromEntries(Object.entries(baseResources).sort())));
 +
        $('#SCalExportBaseresourcesAsJSONA').attr('href', url);
 +
        $('#SCalExportBaseresourcesAsJSONA').attr('download', 'Skeleton Key - Base Resources.json');
 +
      });
 +
 +
      $('#SCalResourcesrow2').append('<a id="SCalExportBaseresourcesAsTEXTA"></a>');
 +
      $('#SCalExportBaseresourcesAsTEXTA').append('<input id="SCalExportBaseresourceAsTEXT" name="SCalExportBaseresourceAsTEXT" type="button" value="Export as TEXT">');
 +
      $('#SCalExportBaseresourceAsTEXT').click(function() {
 +
        var url = 'data:application/xml;charset=utf-8,' + encodeURIComponent(generateResourcesAsTEXT(baseResources));
 +
        $('#SCalExportBaseresourcesAsTEXTA').attr('href', url);
 +
        $('#SCalExportBaseresourcesAsTEXTA').attr('download', 'Skeleton Key - Base Resouces.txt');
 +
      });
 +
 +
      $('#SCalResourcesrow2').append('<a id="SCalExportBaseresourcesAsXMLA"></a>');
 +
      $('#SCalExportBaseresourcesAsXMLA').append('<input id="SCalExportBaseresourceAsXML" name="SCalExportBaseresourceAsXML" type="button" value="Export as XML">');
 +
      $('#SCalExportBaseresourceAsXML').click(function() {
 +
        var url = 'data:application/xml;charset=utf-8,' + encodeURIComponent(
 +
          '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>\n'
 +
          + generateResourcesAsXML(baseResources,'BaseResources', 0));
 +
        $('#SCalExportBaseresourcesAsXMLA').attr('href', url);
 +
        $('#SCalExportBaseresourcesAsXMLA').attr('download', 'Skeleton Key - Base Resources.xml');
 +
      });
 +
     
 
       $('#SCalResourcesrow2').append('<table id="SCalBaseResourceTable" class="wikitable sortable"><thead>'
 
       $('#SCalResourcesrow2').append('<table id="SCalBaseResourceTable" class="wikitable sortable"><thead>'
 
         + '<tr>'
 
         + '<tr>'
Line 451: Line 709:
 
       for (var [key, value] of Object.entries(baseResources).sort()) {
 
       for (var [key, value] of Object.entries(baseResources).sort()) {
 
         tablecontent += '<tr>'
 
         tablecontent += '<tr>'
           + '<td><a href="' + mw.util.getUrl(key) + '">' + key + '</a></td>'
+
           + '<td><a href="' /*+ mw.util.getUrl(key)*/ + '">' + key + '</a></td>'
 
           + '<td>' + value + '</td>'
 
           + '<td>' + value + '</td>'
 
           + '</tr>';
 
           + '</tr>';
Line 460: Line 718:
  
 
       $('#SCalBaseResourceTable').append(tablecontent);
 
       $('#SCalBaseResourceTable').append(tablecontent);
 +
    }
 +
 +
    function generateResourcesAsTEXT(resources) {
 +
      var text = '';
 +
      var length = determineNeededDigitlength(resources, 0);
 +
      for(var [key, count] of Object.entries(resources).sort()) {
 +
        var indent = '';
 +
        for(var i = 0;i < length - count.toString().length; i++) {
 +
          indent += ' ';
 +
        }
 +
       
 +
        text += count + indent + '  ' + key + '\n';
 +
      }
 +
      return text;
 +
    }
 +
   
 +
    function generateSkillsAsXML(indent) {
 +
      indentation = '';
 +
      for(var i = 0; i < indent; i++) {
 +
        indentation += ' ';
 +
      }
 +
     
 +
      var xml = indentation + '<Skills>\n';
 +
      for(var [key, values] of Object.entries(skillsOverview).sort()) {
 +
        xml += indentation + '  <skill minimal="' + values.min + '" optimal="' + values.max + '">' + key + '</item>\n';
 +
      }
 +
      xml += indentation + '</Skills>'
 +
      return xml;
 +
    }
 +
   
 +
    function generateSkillsAsTEXT(lengths) {
 +
      var text = 'Minimal  Optimal  Skillname\n';
 +
      text += '--------------------------------------------------------\n';
 +
 +
      for(var [key, values] of Object.entries(skillsOverview).sort()) {
 +
        var needIndent = '';
 +
        var optIndent = '';
 +
        for(var i = 0;i < lengths[0] - values.min.toString().length; i++) {
 +
          needIndent += ' ';
 +
        }
 +
 +
        for(var i = 0;i < lengths[1] - values.max.toString().length; i++) {
 +
          optIndent += ' ';
 +
        }
 +
       
 +
        text += values.min + needIndent + values.max + optIndent + '  ' + key + '\n';
 +
      }
 +
      return text;
 +
    }
 +
 +
    function generateResourcesAsXML(resources, tag, indent) {
 +
      var indentation = '';
 +
      for(var i = 0; i < indent; i++) {
 +
        indentation += ' ';
 +
      }
 +
      var xml = indentation + '<' + tag + '>\n';
 +
     
 +
      for(var [key, count] of Object.entries(resources).sort()) {
 +
        xml += indentation + '  <item needed="' + count + '">' + key + '</item>\n';
 +
      }
 +
      xml += '</' + tag + '>\n';
 +
      return xml;
 
     }
 
     }
  

Revision as of 15:48, 22 June 2022

/**
 * Skeleton Key Calculator V 0.5
 * Elteria Shadowhand
 */
$(document).ready(function() {
  if (document.getElementById('SCalMain')) {
    'use strict';
    let baseResources; // Collection of the needed base resources. To be filled.
    let refinedResources; // Collection of the needed refined resources. To be filled.
    let productsSkills = {}; // A list of all product's skills needed, containing their names and their min/opt values.
    let skillsOverview; // A list of all needed skills, their min/opt stats and an id 
    let resourcesTree = {}; // A tree of resources and needed counts. to be filled.
    let initialKeyCount = 10;
    let maxKeyCount = 5000;
    let maxSkillCount = 2000;
    // the dataset
    // 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, 
    // the subcomponent (i.e. a Stone Brick, which would be Stoneworking) of it would be Spellcraft. The minXXX and maxXXX
    // 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 }
            }
          }
        }
      }
    };

    // List of the skills you need to have for crafting everything, only used for the input fields
    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 },
    };
    
    for(var [key, values] of Object.entries(skillsOverview)) {
      values.id = 'SCal' + key.replace(/\s/, '') + 'Input';
    }

    printHeader();
    printSkillInputs();

    $('#SCalMain').append('<div id="SCalExportEverything" style="text-align: center"></div>');
    
    // Details of the crafting
    $('#SCalMain').append('<center><table>'
      + '<tr valign="top">'
      + '<td id="SCalResourcesrow1"></td>'
      + '<td id="SCalResourcesrow2"></td>'
      + '</tr>'
      + '</table></center>'
    );

    $('#SCalResourcesrow1').append('<div id="SCalResources"></div>');

    recursiveDetermineProductsSkills(products);
    if(validateInputs()) {
      printLists();
    }

    // The 'print everything' feature
    resourcesTree = recursiveGenerateTree(products);
    printPrintEverythingLinks();

    //FUNCTIONS:
    function printHeader() {
      var keyCount = getCookie('SCalCountInput');
      if(!isRealInteger(keyCount)) keyCount = initialKeyCount;
      $('#SCalMain').append('<h1 id="SCalH1">The Calculator</h1>');
      $('#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="' + keyCount + '" size="6"></center>');
      $('#SCalMain').append('<br />');
      $('#SCalMain').append('<hr />');
      $('#SCalMain').append('<center>Please enter your current skills in the fields below. Defaults to optimal skills.</center>');
      $('#SCalMain').append('<center><input type="button" id="SCalMaxSkillsBtn" value="Reset to optimal skills"></center>');
      $('#SCalMain').append('<center><div id="SCalSkills" style="column-count:3; width:fit-content; text-align:left"></div></center>');
      $('#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('<center><input type="button" id="SCalCountBtn" value="Calculate now!"></center>');
      $('#SCalMain').append('<hr />');

      $('#SCalMaxSkillsBtn').click(function() {
        if(confirm('This will reset all of the skills! Are you really sure? ')) {
          for (var v of Object.values(skillsOverview)) {
            $('#' + v.id).val(v.max);
            $('#' + v.id).change();
          };
        }
      });

      $('#SCalCountBtn').click(function(event) {
        if(validateInputs()) { 
          printLists();
          checkIntegerAndSetCookieById(event.target.id);
        }
      });

      $('#SCalCountInput').keypress(function(event) {
        if (event.key === 'Enter' && validateInputs()) {
          printLists();
          checkIntegerAndSetCookieById(event.target.id);
        }
      });

      $('#SCalCountInput').change(function(event) {
        if(validateInputs()) {
          printLists();
          checkIntegerAndSetCookieById(event.target.id);
        }
      });
    }

    function printPrintEverythingLinks() {
      $('#SCalExportEverything').append('<a id="SCalExportEverythingAsJSONA"></a>');
      $('#SCalExportEverythingAsJSONA').append('<input id="SCalExportEverythingAsJSON" name="SCalExportEverythingAsJSON" type="button" value="Export everything as JSON">');
      $('#SCalExportEverythingAsJSON').click(function() {
        var JSONeverything = {
          'Product Tree': resourcesTree,
          'Base Resources': baseResources,
          'Refined Resources': refinedResources,
          'Skills': generateSkillsAsJSON()
        }; 
        
        var url = 'data:text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(JSONeverything));
        $('#SCalExportEverythingAsJSONA').attr('href', url);
        $('#SCalExportEverythingAsJSONA').attr('download', 'Skeleton Key - All.json');
      });
      $('#SCalExportEverything').append('<a id="SCalExportEverythingAsTEXTA"></a>');
      $('#SCalExportEverythingAsTEXTA').append('<input id="SCalExportEverythingAsTEXT" name="SCalExportEverythingAsTEXT" type="button" value="Export everything as TEXT">');
      $('#SCalExportEverythingAsTEXT').click(function() {
        var text = '--- Tree of needed Resources ---\n\n';
        text += recursiveGenerateTreeAsTEXT(resourcesTree, 0);
        text += '\n\n';
        text += '--- Needed Base Resources ---\n\n';
        text += generateResourcesAsTEXT(baseResources);
        text += '\n\n';
        text += '--- Needed Refined Resources ---\n\n';
        text += generateResourcesAsTEXT(refinedResources);
        text += '\n\n';
        text += '--- Needed Skills ---\n\n';
        text += generateSkillsAsTEXT([9,7]);
        
        var url = 'data:text/plain;charset=utf-8,' + encodeURIComponent(text);
        $('#SCalExportEverythingAsTEXTA').attr('href', url);
        $('#SCalExportEverythingAsTEXTA').attr('download', 'Skeleton Key - All.txt');
      });
      $('#SCalExportEverything').append('<a id="SCalExportEverythingAsXMLA"></a>');
      $('#SCalExportEverythingAsXMLA').append('<input id="SCalExportEverythingAsXML" name="SCalExportEverythingAsXML" type="button" value="Export everything as XML">');
      $('#SCalExportEverythingAsXML').click(function() {
        var text = '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>\n';
        text += '<ResourcesTree>\n';
        text += recursiveGenerateTreeAsXML(resourcesTree, 2);
        text += '</ResourcesTree>\n';
        text += generateResourcesAsXML(baseResources,'BaseResources', 0);
        text += generateResourcesAsXML(refinedResources,'RefinedResources', 0);
        text += generateSkillsAsXML(0);
        var url = 'data:application/xml;charset=utf-8,' + encodeURIComponent(text);

        $('#SCalExportEverythingAsXMLA').attr('href', url);
        $('#SCalExportEverythingAsXMLA').attr('download', 'Skeleton Key - All.xml');
      });
    }
    
    function generateSkillsAsJSON() {
      var json = {};
      for(var [key, values] of Object.entries(skillsOverview).sort()) {
        json[key] = {
          minimal: values.min,
          optimal: values.max
        }
      }
      return json;
    }
    
    function recursiveGenerateTree(product) {
      var tree = {};
      for(var [key, value] of Object.entries(product)) {
        tree[key] = { needed: value.needed };
        
        if(value.hasOwnProperty('subs')) {
          tree[key].subs = recursiveGenerateTree(value.subs);
        }
      }
      return tree;
    }

    function recursiveGenerateTreeAsTEXT(tree, indent) {
      var text = '';
      for(var [key, value] of Object.entries(tree)) {
        for(var i=0; i<indent; i++) {
          text += ' ';
        }
        
        text += '|- ';
        text += key + ' ';
        text += value.needed;
        text += '\n';

        if(value.hasOwnProperty('subs')) {
          text += recursiveGenerateTreeAsTEXT(value.subs, indent+2);
        }
      }
      return text;
    }

    function recursiveGenerateTreeAsXML(tree, indent) {
      var xml = '';
      for(var [key, value] of Object.entries(tree)) {
        for(var i=0; i<indent; i++) {
          xml += ' ';
        }
        
        xml += '<item name="' + key +'" needed="' + value.needed + '">\n';

        if(value.hasOwnProperty('subs')) {
          xml += recursiveGenerateTreeAsXML(value.subs, indent+2);
        }

        for(var i=0; i<indent; i++) {
          xml += ' ';
        }
        
        xml +='</item>\n';
      }
      return xml;
    }

    function determineNeededDigitlength(resources, length) {
      for(var value of Object.values(resources)) {
        if(value.toString().length > length) length = value.toString().length;
      }
      return length;
    }

    function determineNeededDigitlengthSkills(length) {
      for(var key of Object.keys(skillsOverview)) {
        if(skillsOverview[key].min.toString().length > length) length[0] = skillsOverview[key].min.toString().length;
        if(skillsOverview[key].max.toString().length > length) length[1] = skillsOverview[key].max.toString().length;
      }
      return length;
    }


    function printLists() {
      // (re-)initialize to be updated lists
      baseResources = {};
      refinedResources = {};
      
      recursiveCalculateNeededResources(products, parseInt($('#SCalCountInput').val()));
      $('#SCalResources').html('');

      
      $('#SCalResources').append('<h2>Tree of needed products</h2>');
      $('#SCalResources').append('<a id="SCalExportTreeAsJSONA"></a>');
      $('#SCalExportTreeAsJSONA').append('<input id="SCalExportTreeAsJSON" name="SCalExportTreeAsJSON" type="button" value="Export as JSON">');
      $('#SCalExportTreeAsJSON').click(function() {
        var url = 'data:text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(resourcesTree));
        $('#SCalExportTreeAsJSONA').attr('href', url);
        $('#SCalExportTreeAsJSONA').attr('download', 'Skeleton Key - Tree.json');
      });
      $('#SCalResources').append('<a id="SCalExportTreeAsTEXTA"></a>');
      $('#SCalExportTreeAsTEXTA').append('<input id="SCalExportTreeAsTEXT" name="SCalExportTreeAsTEXT" type="button" value="Export as TEXT">');
      $('#SCalExportTreeAsTEXT').click(function() {
        var url = 'data:text/plain;charset=utf-8,' + encodeURIComponent(recursiveGenerateTreeAsTEXT(resourcesTree, 0));
        $('#SCalExportTreeAsTEXTA').attr('href', url);
        $('#SCalExportTreeAsTEXTA').attr('download', 'Skeleton Key - Tree.txt');
      });
      $('#SCalResources').append('<a id="SCalExportTreeAsXMLA"></a>');
      $('#SCalExportTreeAsXMLA').append('<input id="SCalExportTreeAsXML" name="SCalExportTreeAsXML" type="button" value="Export as XML">');
      $('#SCalExportTreeAsXML').click(function() {
        var url = 'data:application/xml;charset=utf-8,' + encodeURIComponent(
          '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>\n'
          + recursiveGenerateTreeAsXML(resourcesTree, 0));
        $('#SCalExportTreeAsXMLA').attr('href', url);
        $('#SCalExportTreeAsXMLA').attr('download', 'Skeleton Key - Tree.xml');
      });
      
      var html = recursivePrintTreeProduct(products);
      $('#SCalResources').append(html);

      printBaseResources();
      printRefinedResources();
      printNeededSkills();

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

    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 printNeededSkills() {
      $('#SCalResourcesrow2').append('<h2>Required skills</h2>');

      $('#SCalResourcesrow2').append('<a id="SCalExportSkillsAsJSONA"></a>');
      $('#SCalExportSkillsAsJSONA').append('<input id="SCalExportSkillsAsJSON" name="SCalExportSkillsAsJSON" type="button" value="Export as JSON">');
      $('#SCalExportSkillsAsJSON').click(function() {
        var url = 'data:text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(generateExportSkills()));
        $('#SCalExportSkillsAsJSONA').attr('href', url);
        $('#SCalExportSkillsAsJSONA').attr('download', 'Skeleton Key - Skills.json');
      });

      $('#SCalResourcesrow2').append('<a id="SCalExportSkillsAsTEXTA"></a>');
      $('#SCalExportSkillsAsTEXTA').append('<input id="SCalExportSkillsAsTEXT" name="SCalExportSkillsAsTEXT" type="button" value="Export as TEXT">');
      $('#SCalExportSkillsAsTEXT').click(function() {
        var text = generateSkillsAsTEXT(determineNeededDigitlengthSkills([9,7]));
        var url = 'data:text/plain;charset=utf-8,' + encodeURIComponent(text);
        $('#SCalExportSkillsAsTEXTA').attr('href', url);
        $('#SCalExportSkillsAsTEXTA').attr('download', 'Skeleton Key - Skills.txt');
      });

      $('#SCalResourcesrow2').append('<a id="SCalExportSkillsAsXMLA"></a>');
      $('#SCalExportSkillsAsXMLA').append('<input id="SCalExportSkillsAsXML" name="SCalExportSkillsAsXML" type="button" value="Export as XML">');
      $('#SCalExportSkillsAsXML').click(function() {
        var text = '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>\n'
          + generateSkillsAsXML(0);
        var url = 'data:application/xml;charset=utf-8,' + encodeURIComponent(text);
        $('#SCalExportSkillsAsXMLA').attr('href', url);
        $('#SCalExportSkillsAsXMLA').attr('download', '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 [key, value] of Object.entries(skillsOverview).sort()) {
        tablecontent += '<tr>'
          + '<td><a href="' /*+ mw.util.getUrl(key)*/ + '">' + key + '</a></td>'
          + '<td>' + value.min + '</td>'
          + '<td>' + value.max + '</td>'
          + '</tr>';
      }

      tablecontent += '</tbody>';
      tablecontent += '</table>';
      $('#SCalNeededSkillsTable').append(tablecontent);
    }
    
    function printSkillInputs() {
      for (var [key, value] of Object.entries(skillsOverview)) {
        var id = value.id;
        var skill = getCookie(id);
        if(!isRealInteger(skill)) { // default to max value if cookie not correctly set
          skill = value.max;
        } 

        $('#SCalSkills').append('<input type="number" id="' + id + '" name="' + id + '" size="6"><label for="' + id + '">&nbsp;&nbsp;' + key + '</label><br />');
        
        $('#' + id).keypress(function(event) {
          if (event.key === 'Enter' && validateInputs()) {
            printLists();
            checkIntegerAndSetCookieById(event.target.id);
          }
        });
        
        $('#' + id).change(function(event) {
          if(validateInputs()) {
            printLists();
            checkIntegerAndSetCookieById(event.target.id);
          }
        });
        
        $('#' + id).val(skill);
      }
    }

    // Tests if the provided input field has a valid integer and sets a cookie for it
    function checkIntegerAndSetCookieById(id) {
      hashedId = '#' + id;
      if(isRealInteger($(hashedId).val())) 
        setCookie(id, $(hashedId).val(), new Date(3030,1,4));
    }

    // Adds skillnames, ids, minskill and maxskill to a skills list
    function recursiveDetermineProductsSkills(currentproducts) {
      for (var product of Object.values(currentproducts)) {
        var skillName = product.skill;
        var newSkills = {};
        
        // compare min and max skills and set accordingly
        newSkills.minSkill = product.minSkill;
        newSkills.maxSkill = product.maxSkill;
        
        if(productsSkills.hasOwnProperty(skillName)) {
          if(productsSkills[skillName].minSkill < newSkills.minSkill) {
            newSkills.minSkill = productsSkills[skillName].minSkill;
          }
          
          if(productsSkills[skillName].maxSkill > newSkills.maxSkill) {
            newSkills.maxSkill = productsSkills[skillName].maxSkill;
          }
        }
        
        productsSkills[skillName] = newSkills;
        
        if(product.hasOwnProperty('subs')) {
          recursiveDetermineProductsSkills(product.subs);
        }
      }
    }
    
    function recursivePrintTreeProduct(product) {
      var html = '';
      for(var [key, value] of Object.entries(product)) {
        html += '<ul>';
        html += '<li>';
        html += '<span class="' + value.type + '"><a href="' /*+ mw.util.getUrl(key)*/ + '">' + key + '</a>: ' + value.needed + '</span>';
        if(value.hasOwnProperty('subs')) {
          html += recursivePrintTreeProduct(value.subs);
        }
        html += '</ul>';
        html += '</li>'
      }
      return html;
    }

    // Adds needed resources to the dataset and counts up the base and refined resources
    function recursiveCalculateNeededResources(product, previousCount) {
      for(var [key, values] of Object.entries(product)) {
        var currentSkill = $('#' + skillsOverview[values.skill].id).val();
        
        var percent = (currentSkill - values.minSkill) / (values.maxSkill - values.minSkill);
        var needEffective = values.maxCount - ((values.maxCount - values.minCount) * percent);
        needEffective = Math.ceil(needEffective);
  
        if (needEffective < values.minCount) needEffective = values.minCount;
        if (needEffective > values.maxCount) needEffective = values.maxCount;
        
        needEffective *= previousCount;

        // Process base resources
        // we assume that a product without subs is a base resource
        if(!values.hasOwnProperty('subs')) { 
          if(baseResources.hasOwnProperty(key)) {
            baseResources[key] = needEffective + baseResources[key];
          } else {
            baseResources[key] = needEffective;
          }
        }

        // Process refined resources
        if(values.type == 'SCalRefinedResource') { 
          if(refinedResources.hasOwnProperty(key)) {
            refinedResources[key] = needEffective + refinedResources[key];
          } else {
            refinedResources[key] = needEffective;
          }
        }
        
        values.needed = needEffective;
        product[key] = values;

        if(values.hasOwnProperty('subs')) {
          values.subs = recursiveCalculateNeededResources(values.subs, needEffective);
        }
      }
      return product;
    }

    function printRefinedResources() {
      $('#SCalResourcesrow2').append('<h2>Overview of refined resources</h2>');
      
      $('#SCalResourcesrow2').append('<a id="SCalExportRefinedresourcesAsJSONA"></a>');
      $('#SCalExportRefinedresourcesAsJSONA').append('<input id="SCalExportRefinedresourceAsJSON" name="SCalExportRefinedresourceAsJSON" type="button" value="Export as JSON">');
      $('#SCalExportRefinedresourceAsJSON').click(function() {
        var url = 'data:text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(Object.fromEntries(Object.entries(refinedResources).sort())));
        $('#SCalExportRefinedresourcesAsJSONA').attr('href', url);
        $('#SCalExportRefinedresourcesAsJSONA').attr('download', 'Skeleton Key - Refined Resources.json');
      });

      $('#SCalResourcesrow2').append('<a id="SCalExportRefinedresourcesAsTEXTA"></a>');
      $('#SCalExportRefinedresourcesAsTEXTA').append('<input id="SCalExportRefinedresourceAsTEXT" name="SCalExportRefinedresourceAsTEXT" type="button" value="Export as TEXT">');
      $('#SCalExportRefinedresourceAsTEXT').click(function() {
        var url = 'data:application/xml;charset=utf-8,' + encodeURIComponent(generateResourcesAsTEXT(baseResources));
        $('#SCalExportRefinedresourcesAsTEXTA').attr('href', url);
        $('#SCalExportRefinedresourcesAsTEXTA').attr('download', 'Skeleton Key - Refined Resouces.txt');
      });

      $('#SCalResourcesrow2').append('<a id="SCalExportRefinedresourcesAsXMLA"></a>');
      $('#SCalExportRefinedresourcesAsXMLA').append('<input id="SCalExportRefinedresourceAsXML" name="SCalExportRefinedresourceAsXML" type="button" value="Export as XML">');
      $('#SCalExportRefinedresourceAsXML').click(function() {
        var url = 'data:application/xml;charset=utf-8,' + encodeURIComponent(
          '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>\n'
          + generateResourcesAsXML(refinedResources,'RefinedResources', 0));
        $('#SCalExportRefinedresourcesAsXMLA').attr('href', url);
        $('#SCalExportRefinedresourcesAsXMLA').attr('download', 'Skeleton Key - Refined Resources.xml');
      });

      $('#SCalResourcesrow2').append('<table id="SCalRefinedResourceTable" class="wikitable sortable"><thead>'
        + '<tr>'
        + '<th align="left">Resource</th>'
        + '<th align="left">Need</th>'
        + '</tr></thead>'
      );

      var tablecontent = '<tbody>';

      for(var [key, value] of Object.entries(refinedResources).sort()) {
        tablecontent += '<tr>'
          + '<td><a href="' /*)+ mw.util.getUrl(key)*/ + '">' + key + '</a></td>'
          + '<td>' + value + '</td>'
          + '</tr>';
      }

      tablecontent += '</tbody>';
      tablecontent += '</table>';
      $('#SCalRefinedResourceTable').append(tablecontent);
    }

    function printBaseResources() {
      $('#SCalResourcesrow2').html('');
      $('#SCalResourcesrow2').append('<h2>Overview of base resources</h2>');
      
      $('#SCalResourcesrow2').append('<a id="SCalExportBaseresourcesAsJSONA"></a>');
      $('#SCalExportBaseresourcesAsJSONA').append('<input id="SCalExportBaseresourceAsJSON" name="SCalExportBaseresourceAsJSON" type="button" value="Export as JSON">');
      $('#SCalExportBaseresourceAsJSON').click(function() {
        var url = 'data:text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(Object.fromEntries(Object.entries(baseResources).sort())));
        $('#SCalExportBaseresourcesAsJSONA').attr('href', url);
        $('#SCalExportBaseresourcesAsJSONA').attr('download', 'Skeleton Key - Base Resources.json');
      });

      $('#SCalResourcesrow2').append('<a id="SCalExportBaseresourcesAsTEXTA"></a>');
      $('#SCalExportBaseresourcesAsTEXTA').append('<input id="SCalExportBaseresourceAsTEXT" name="SCalExportBaseresourceAsTEXT" type="button" value="Export as TEXT">');
      $('#SCalExportBaseresourceAsTEXT').click(function() {
        var url = 'data:application/xml;charset=utf-8,' + encodeURIComponent(generateResourcesAsTEXT(baseResources));
        $('#SCalExportBaseresourcesAsTEXTA').attr('href', url);
        $('#SCalExportBaseresourcesAsTEXTA').attr('download', 'Skeleton Key - Base Resouces.txt');
      });

      $('#SCalResourcesrow2').append('<a id="SCalExportBaseresourcesAsXMLA"></a>');
      $('#SCalExportBaseresourcesAsXMLA').append('<input id="SCalExportBaseresourceAsXML" name="SCalExportBaseresourceAsXML" type="button" value="Export as XML">');
      $('#SCalExportBaseresourceAsXML').click(function() {
        var url = 'data:application/xml;charset=utf-8,' + encodeURIComponent(
          '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>\n'
          + generateResourcesAsXML(baseResources,'BaseResources', 0));
        $('#SCalExportBaseresourcesAsXMLA').attr('href', url);
        $('#SCalExportBaseresourcesAsXMLA').attr('download', 'Skeleton Key - Base Resources.xml');
      });
      
      $('#SCalResourcesrow2').append('<table id="SCalBaseResourceTable" class="wikitable sortable"><thead>'
        + '<tr>'
        + '<th align="left">Resource</th>'
        + '<th align="left">Need</th>'
        + '</tr></thead>'
      );

      var tablecontent = '<tbody>';

      for (var [key, value] of Object.entries(baseResources).sort()) {
        tablecontent += '<tr>'
          + '<td><a href="' /*+ mw.util.getUrl(key)*/ + '">' + key + '</a></td>'
          + '<td>' + value + '</td>'
          + '</tr>';
      }

      tablecontent += '</tbody>';
      tablecontent += '</table>';

      $('#SCalBaseResourceTable').append(tablecontent);
    }

    function generateResourcesAsTEXT(resources) {
      var text = '';
      var length = determineNeededDigitlength(resources, 0);
      for(var [key, count] of Object.entries(resources).sort()) {
        var indent = '';
        for(var i = 0;i < length - count.toString().length; i++) {
          indent += ' ';
        }
        
        text += count + indent + '   ' + key + '\n';
      }
      return text;
    }
    
    function generateSkillsAsXML(indent) {
      indentation = '';
      for(var i = 0; i < indent; i++) {
        indentation += ' ';
      }
      
      var xml = indentation + '<Skills>\n';
      for(var [key, values] of Object.entries(skillsOverview).sort()) {
        xml += indentation + '  <skill minimal="' + values.min + '" optimal="' + values.max + '">' + key + '</item>\n';
      }
      xml += indentation + '</Skills>'
      return xml;
    }
    
    function generateSkillsAsTEXT(lengths) {
      var text = 'Minimal  Optimal   Skillname\n';
      text += '--------------------------------------------------------\n';

      for(var [key, values] of Object.entries(skillsOverview).sort()) {
        var needIndent = '';
        var optIndent = '';
        for(var i = 0;i < lengths[0] - values.min.toString().length; i++) {
          needIndent += ' ';
        }

        for(var i = 0;i < lengths[1] - values.max.toString().length; i++) {
          optIndent += ' ';
        }
        
        text += values.min + needIndent + values.max + optIndent + '   ' + key + '\n';
      }
      return text;
    }

    function generateResourcesAsXML(resources, tag, indent) {
      var indentation = '';
      for(var i = 0; i < indent; i++) {
        indentation += ' ';
      }
      var xml = indentation + '<' + tag + '>\n';
      
      for(var [key, count] of Object.entries(resources).sort()) {
        xml += indentation + '  <item needed="' + count + '">' + key + '</item>\n';
      }
      xml += '</' + tag + '>\n';
      return xml;
    }

    function isRealInteger(number) {
      return (!isNaN(number) && parseInt(Number(number)) == number && !isNaN(parseInt(number, 10)));
    }
    
    // check if the input fields contain valid entries
    function validateInputs() {
      var ready = true;
      //reset the error textfield
      $('#SCalErrorText').html('');

      // check the keycounter value
      $('#SCalCountInput').removeClass('SCalErroreousInput');
      var number = $('#SCalCountInput').val();
      if (!isRealInteger(number)) {
        $('#SCalErrorText').append('<center>Amount of keys: this was not a number!</center>');
        $('#SCalCountInput').addClass('SCalErroreousInput');
        ready = false;
      } else {
        if (number < 1 || number > maxKeyCount) {
          $('#SCalErrorText').append('<center>Amount of keys: please provide a number between 1 and ' + maxKeyCount + '</center>');
          $('#SCalCountInput').addClass('SCalErroreousInput');
          ready = false;
        }
      }

      //check the skills counters
      for (var [skill, values] of Object.entries(skillsOverview)) {
        var inputObj = $('#' + values.id);
        
        inputObj.removeClass('SCalErroreousInput');
        inputObj.removeClass('SCalOptimalInput');
        
        number = inputObj.val();

        if (!isRealInteger(number)) {
          $('#SCalErrorText').append('<center>' + skill + ': this was not a number!</center>');
          inputObj.addClass('SCalErroreousInput');
          ready = false;
        } else {
          if (number < 1 || number > maxSkillCount) { // exceeds the number range
            $('#SCalErrorText').append('<center>' + skill + ': please provide a number between 1 and ' + maxSkillCount + '</center>');
            inputObj.addClass('SCalErroreousInput');
            ready = false;
          }
          
          if(number < values.min) {
            $('#SCalErrorText').append('<center>Skill ' + skill + ' is too low (have:' + number + ', need: ' + values.min + ')</center>');
            inputObj.addClass('SCalErroreousInput');
          }
          if(!inputObj.hasClass('SCalErroreousInput')) {
            if(number >= values.max) inputObj.addClass('SCalOptimalInput');
          }
        }
      }
      return ready;
    }
    
    function setCookie(name, value, date) {
      var expires = '; expires=' + date.toGMTString();
      var cookie = name + '=' + value + expires + '; path=/; SameSite=Strict';
      document.cookie = cookie;
    }

    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;
    }
  }
});