MediaWiki:Script/Calculator.js : Différence entre versions

Ligne 132 : Ligne 132 :
 
    
 
    
 
   var allowedWeaponsPerRace = {
 
   var allowedWeaponsPerRace = {
     'warrior': [0, 3, 8],
+
     "warrior": [0, 3, 8],
     'ninja': [0, 1, 2, 8],
+
     "ninja": [0, 1, 2, 8],
     'sura': [0, 7, 8],
+
     "sura": [0, 7, 8],
     'shaman': [4, 6, 8],
+
     "shaman": [4, 6, 8],
     'lycan': [5, 8]
+
     "lycan": [5, 8]
 
   }
 
   }
 
   var allowedWeapons = allowedWeaponsPerRace[raceChoice.value]
 
   var allowedWeapons = allowedWeaponsPerRace[raceChoice.value]
Ligne 484 : Ligne 484 :
 
     switch (target.dataset.icon) {
 
     switch (target.dataset.icon) {
  
       case 'duplicate':
+
       case "duplicate":
  
 
         if (!characters.unsavedChanges) {
 
         if (!characters.unsavedChanges) {
Ligne 500 : Ligne 500 :
 
         break;
 
         break;
  
       case 'download':
+
       case "download":
 
         downloadCharacter(characters.savedCharacters[pseudo]);
 
         downloadCharacter(characters.savedCharacters[pseudo]);
 
         break;
 
         break;
  
       case 'delete':
+
       case "delete":
 
         var result = confirm("Voulez-vous vraiment supprimer définitivement le personnage " + pseudo + " ?");
 
         var result = confirm("Voulez-vous vraiment supprimer définitivement le personnage " + pseudo + " ?");
 
         if (result) {
 
         if (result) {
Ligne 847 : Ligne 847 :
 
   }
 
   }
 
    
 
    
   battle.attackerSelection.appendChild(createOption(name));
+
   if (isMonster && monsterData[name][0]) {
 +
    // pass
 +
  } else {
 +
    battle.attackerSelection.appendChild(createOption(name));
 +
  }
 +
 
 
   battle.victimSelection.appendChild(createOption(name));
 
   battle.victimSelection.appendChild(createOption(name));
 
}
 
}
Ligne 853 : Ligne 858 :
  
 
function updateBattleChoice(characters, battle) {
 
function updateBattleChoice(characters, battle) {
 +
 
 +
  var keys = Object.keys(characters.savedCharacters);
 +
  var attackerSelection = battle.attackerSelection;
  
   Object.keys(characters.savedCharacters).forEach(function(pseudo) {
+
   for (var index = 0; index < keys.length; index++) {
 +
    var pseudo = keys[index];
 
     addBattleChoice(battle, pseudo);
 
     addBattleChoice(battle, pseudo);
   });
+
   }
 
    
 
    
 
   characters.savedMonsters.forEach(function(monsterName) {
 
   characters.savedMonsters.forEach(function(monsterName) {
 
     addBattleChoice(battle, monsterName, true);
 
     addBattleChoice(battle, monsterName, true);
 
   });
 
   });
 +
 +
  var observer = new MutationObserver(function(mutationsList, observer) {
 +
    battle.battleForm.reset();
 +
  });
 +
 +
  observer.observe(attackerSelection, {childList: true});
 
}
 
}
  
Ligne 1 242 : Ligne 1 257 :
  
 
function createBattle(characters, battle) {
 
function createBattle(characters, battle) {
 +
 
 +
  function isPseudoSaved(pseudo) {
 +
    return characters.savedCharacters.hasOwnProperty(pseudo);
 +
  }
 
    
 
    
 
   battle.battleForm.addEventListener("submit", function(event) {
 
   battle.battleForm.addEventListener("submit", function(event) {
Ligne 1 249 : Ligne 1 268 :
 
     var battleInfo = new FormData(event.target)
 
     var battleInfo = new FormData(event.target)
 
     var attackerName = battleInfo.get("attacker");
 
     var attackerName = battleInfo.get("attacker");
 +
    var attackType = battleInfo.get("attackTypeSelection");
 
     var victimName = battleInfo.get("victim");
 
     var victimName = battleInfo.get("victim");
 
      
 
      
     if (characters.savedCharacters.hasOwnProperty(attackerName)) {
+
     if (!attackerName && !attackType && !victimName) {
 +
      return;
 +
    }
 +
 
 +
    if (isPseudoSaved(attackerName)) {
 
       var attacker = characters.savedCharacters[attackerName];
 
       var attacker = characters.savedCharacters[attackerName];
 
     } else {
 
     } else {
Ligne 1 257 : Ligne 1 281 :
 
     }
 
     }
 
      
 
      
     if (characters.savedCharacters.hasOwnProperty(victimName)) {
+
     if (isPseudoSaved(victimName)) {
 
       var victim = characters.savedCharacters[victimName];
 
       var victim = characters.savedCharacters[victimName];
 
     } else {
 
     } else {
Ligne 1 269 : Ligne 1 293 :
 
     showElement(battle.tableContainer);
 
     showElement(battle.tableContainer);
 
   });
 
   });
    
+
 +
   battle.attackerSelection.addEventListener("change", function(event) {
 +
    var name = event.target.value;
 +
    var attackTypeSelection = battle.attackTypeSelection;
 +
   
 +
    if (isPseudoSaved(name)) {
 +
      //pass
 +
    } else {
 +
      var optionsLength = attackTypeSelection.options.length;
 +
     
 +
      if (optionsLength <= 1) {
 +
        return;
 +
      }
 +
     
 +
      for (var optionIndex = optionsLength - 1; optionIndex >= 2; optionIndex--) {
 +
        attackTypeSelection.remove(optionIndex);
 +
      }
 +
    }
 +
  });
 
}
 
}
  
Ligne 1 349 : Ligne 1 391 :
 
     battleForm: document.getElementById("create-battle"),
 
     battleForm: document.getElementById("create-battle"),
 
     attackerSelection: document.getElementById("attacker-selection"),
 
     attackerSelection: document.getElementById("attacker-selection"),
 +
    attackTypeSelection: document.getElementById("attack-type-selection"),
 
     victimSelection: document.getElementById("victim-selection"),
 
     victimSelection: document.getElementById("victim-selection"),
 
     damageResult: document.getElementById("result-damage"),
 
     damageResult: document.getElementById("result-damage"),

Version du 23 octobre 2023 à 06:50

function showElement(element) {
  element.classList.remove("tabber-noactive");
}


function hideElement(element) {
  element.classList.add("tabber-noactive");
}


function removeAccent(str) {
  return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase()
}


function toNormalForm(str) {
    return removeAccent(str).replace(/[^a-zA-Z0-9 ]/g, "");;
}


function isValueInArray(value, array) {
  return array.indexOf(value) !== -1;
}


function copyObject(object) {
  var copy = {};
  for (var key in object) {
    copy[key] = object[key];
  }
  return copy;
}


function floorMultiplication(firstFactor, secondFactor) {
  return Math.floor((firstFactor * secondFactor).toFixed(8));
}


function numberFormat(number, precision) {
  return Math.round((number) * 10**precision) / 10**precision;
}


function addRowToTableResult(tableResult, value) {
  
  var newRow = tableResult.insertRow(-1);
  var firstCell = newRow.insertCell(0);
  
  firstCell.textContent = value;
  firstCell.colSpan = 2;
  
  newRow.style.backgroundColor = "#1f0e02";
  newRow.style.color = "#f0d9a2";
  newRow.style.fontWeight = "bold";
}


function addToTableResult(tableResult, value1, value2) {
  
  var newRow = tableResult.insertRow(-1);

  var firstCell = newRow.insertCell(0);
  firstCell.textContent = value1;

  var secondCell = newRow.insertCell(1);
  secondCell.textContent = numberFormat(value2 * 100, 3) + " %";
}


function getMinDamages(tableResult) {
  return tableResult.rows[2].cells[0].textContent;
}


function clearTableResult(tableResult) {
  
  var tableHeaderRowCount = 1;
  var rowCount = tableResult.rows.length;
  
  for (var rowIndex = tableHeaderRowCount; rowIndex < rowCount; rowIndex++) {
      tableResult.deleteRow(tableHeaderRowCount);
  }
}


function addWeapon(weaponChoice) {
  
  for (var weapon in weaponData) {
    var option = document.createElement("option");
    option.textContent = weaponData[weapon][0];
    option.value = weapon;
    
    var weaponType = weaponData[weapon][1];
    
    if (weaponType !== 0 && weaponType !== 3) {
      hideElement(option);
    }
    weaponChoice.appendChild(option);
  }
}


function filterClass(raceChoice, classChoice, selectValueIsChanged = false) {
  
  var selectedRace = raceChoice.value;
  
  if (selectedRace == "lycan") {
      
    hideElement(classChoice.parentElement);

  } else {

    showElement(classChoice.parentElement);

    for (var option of classChoice.options) {
      if (option.getAttribute("data-race") === selectedRace) {
        if (!selectValueIsChanged) {
          classChoice.value = option.value;
          selectValueIsChanged = true;
        }
        showElement(option);
      } else {
        hideElement(option);
      }
    }
  }
}


function filterWeapon(raceChoice, weaponChoice, selectValueIsChanged = false) {
  
  var allowedWeaponsPerRace = {
    "warrior": [0, 3, 8],
    "ninja": [0, 1, 2, 8],
    "sura": [0, 7, 8],
    "shaman": [4, 6, 8],
    "lycan": [5, 8]
  }
  var allowedWeapons = allowedWeaponsPerRace[raceChoice.value]
  
  if (!selectValueIsChanged) {
    var weaponType = weaponData[weaponChoice.value][1];
    
    if (!isValueInArray(weaponType, allowedWeapons)) {
      weaponChoice.value = "Fist";
    }
  }

  for (var option of weaponChoice.options) {
    var weaponType = weaponData[option.value][1];
    
    if (isValueInArray(weaponType, allowedWeapons)) {
      showElement(option);
      
    } else {
      hideElement(option);
    }
  }
}


function filterUpgrade(weaponUpgrade, weaponChoice, randomAttackValue, currentUpgrade) {
  
  var weaponName = weaponChoice.value;

  if (isValueInArray("serpent", weaponName.toLowerCase())) {
    showElement(randomAttackValue);
    
  } else {
    hideElement(randomAttackValue);
  }
    
  var upgradeNumber = weaponData[weaponName][3].length;
  
  if (!upgradeNumber) {
    
    hideElement(weaponUpgrade.parentElement);
    
  } else {
    
    showElement(weaponUpgrade.parentElement);
    
    weaponUpgrade.innerHTML = "";

    for (var upgrade = 0; upgrade < upgradeNumber; upgrade++) {
   
      var option = document.createElement("option");
      option.value = upgrade;
      option.textContent = "+" + upgrade;
      weaponUpgrade.appendChild(option);
    }
    if (currentUpgrade === undefined) {
      option.selected = true;
    } else {
      weaponUpgrade.value = currentUpgrade;
      currentUpgrade = undefined;
    }
  }
}


function filterForm(characters) {
  
  addWeapon(characters.weaponChoice);
  
  characters.raceChoice.addEventListener("change", function(event) {
    
    filterClass(characters.raceChoice, characters.classChoice);
    filterWeapon(characters.raceChoice, characters.weaponChoice);
    
  });
  
  characters.weaponChoice.addEventListener("change", function(event) {
    
    filterUpgrade(characters.weaponUpgrade, characters.weaponChoice, characters.randomAttackValue);
    
  });
}


function getSavedCharacters() {
  var savedCharacters = localStorage.getItem("savedCharactersCalculator");

  if (savedCharacters) {
    return JSON.parse(savedCharacters);
  }
  return {};
}


function getSavedMonsters() {
  var savedMonsters = localStorage.getItem("savedMonstersCalculator");

  if (savedMonsters) {
    return JSON.parse(savedMonsters);
  }
  return [];
}


function addUniquePseudo(characterDataObject, savedCharactersPseudo) {

  var characterPseudo = characterDataObject.name;
  var originalPseudo = characterPseudo;
  var count = 0;
  
  var regex = /(.*)(\d)$/;
  var match = characterPseudo.match(regex);
  
  if (match) {
    originalPseudo = match[1];
    count = match[2];
  }
  
  while (isValueInArray(characterPseudo, savedCharactersPseudo)) {
    characterPseudo = originalPseudo + count;
    count++;
  }
  
  characterDataObject.name = characterPseudo;
  return [characterDataObject, characterPseudo]
}


function convertToNumber(value) {
  var valueNumber = Number(value);
  return isNaN(valueNumber) ? value : valueNumber;
}


function updateSavedCharacters(savedCharacters) {
  localStorage.setItem("savedCharactersCalculator", JSON.stringify(savedCharacters));
}


function updateSavedMonsters(savedMonsters) {
  localStorage.setItem("savedMonstersCalculator", JSON.stringify(savedMonsters));
}


function saveCharacter(savedCharacters, characterCreation, battle, newCharacter, characterDataObject) {
  
  if (!characterDataObject) {
    var characterData = new FormData(characterCreation);
    var characterDataObject = {};

    characterData.forEach(function(value, key) {
      characterDataObject[key] = convertToNumber(value);
    });
  }

  savedCharacters[characterDataObject.name] = characterDataObject;
  updateSavedCharacters(savedCharacters);
  
  if (newCharacter) {
    addBattleChoice(battle, characterDataObject.name);
  }
}


function saveButtonGreen(characters, animation) {
  if (animation) {
    characters.saveBar.style.animation = characters.saveButton.dataset.animation;
  } else {
    characters.saveBar.style.animation = "";
    characters.saveBar.style.backgroundColor = "lightgreen";
  }
}


function saveButtonOrange(characters) {
  characters.saveBar.style.animation = "";
  characters.saveBar.style.backgroundColor = "#ffdd40";
}


function characterCreationListener(characters, battle) {

  characters.characterCreation.addEventListener("submit", function(event) {
    event.preventDefault();

    if (characters.unsavedChanges) {
      saveCharacter(characters.savedCharacters, characters.characterCreation, battle);
      saveButtonGreen(characters, true);
      characters.unsavedChanges = false;
    }
  });
}


function downloadCharacter(character) {
  
  var content = JSON.stringify(character);
  var link = document.createElement("a");
  var blob = new Blob([content], {type: "text/plain"});
  var blobURL = URL.createObjectURL(blob);

  link.href = blobURL;
  link.download = character.name + ".txt";
  link.click();
  URL.revokeObjectURL(blobURL);
}


function uploadCharacter(characters, characterTemplate, charactersContainer, battle) {
  
  var fileInput = document.createElement("input");
  
  fileInput.type = "file";
  fileInput.accept = ".txt";
  fileInput.multiple = true;
  fileInput.click();
  
  fileInput.addEventListener("change", function(event) {
    var selectedFiles = event.target.files;
    var selectFilesLength = selectedFiles.length;
    
    hideElement(characters.characterCreation);

    for (var fileIndex = 0; fileIndex < selectFilesLength; fileIndex++) {
      var selectedFile = selectedFiles[fileIndex];
      
      if (selectedFile.type === "text/plain") {
        var reader = new FileReader();
        reader.onload = function(e) {
          var fileContent = e.target.result;
          try {
            var characterDataObject = JSON.parse(fileContent);
            var characterPseudo = characterDataObject.name;
            
            if (characterPseudo) {
              characterPseudo = validPseudo(characterPseudo);
              [characterDataObject, characterPseudo] = addUniquePseudo(characterDataObject, Object.keys(characters.savedCharacters));
              var selectedCharacter = handleNewCharacter(characters, characterTemplate, charactersContainer, battle, characterPseudo)[0];
              
              if (selectFilesLength === 1) {
                updateForm(characterDataObject, characters.characterCreation, characters, selectedCharacter);
              }
  
              saveCharacter(characters.savedCharacters, characters.characterCreation, battle, true, characterDataObject);
            }
          } catch (error) {
            if (error.name === "TypeError") {
              // delete the character
            }
          }
        };
        reader.readAsText(selectedFile);
      }
    }
});
}


function deleteCharacter(characters, pseudo, displayedPseudo, element, battle) {
  
  delete characters.savedCharacters[pseudo];
  
  element.remove();
  
  updateSavedCharacters(characters.savedCharacters);
  removeBattleChoice(battle, pseudo);
  
  if (!Object.keys(characters.savedCharacters).length || characters.characterCreation.name.value === pseudo) {
    saveButtonGreen(characters);
    characters.unsavedChanges = false;
    hideElement(characters.characterCreation);
  }
}


function deleteMonster(characters, monsterName, element, battle) {

  characters.savedMonsters.splice(characters.savedMonsters.indexOf(monsterName), 1);
  
  element.remove();
  
  updateSavedMonsters(characters.savedMonsters);
  removeBattleChoice(battle, monsterName);
}


function handleStyle(currentCharacter, selectedElement) {

  var currentCharacterElement = currentCharacter.element;

  if (currentCharacterElement) {
    currentCharacterElement.setAttribute("style", currentCharacter.normal);
  }
  
  selectedElement.setAttribute("style", currentCharacter.onClick);
  currentCharacter.element = selectedElement;
}


function updateForm(formData, characterCreation, characters, selectedElement) {
  
  saveButtonGreen(characters);
  showElement(characterCreation);
  handleStyle(characters.currentCharacter, selectedElement);
  
  characterCreation.reset();
  
  for (var [name, value] of Object.entries(formData)) {
    var formElement = characterCreation[name];
    if (formElement) {
      formElement.value = value;
    }
  }
  filterClass(characters.raceChoice, characters.classChoice, true);
  filterWeapon(characters.raceChoice, characters.weaponChoice, true);
  filterUpgrade(characters.weaponUpgrade, characters.weaponChoice, characters.randomAttackValue, formData.upgrade);
}


function handleClickOnCharacter(spanInput, target, characters, characterElement, battle, edition) {
  
  var displayedPseudo = characters.characterCreation.name.value;
  var pseudo = spanInput.dataset.name;

  if (edition) {

    if (!characters.unsavedChanges) {
      updateForm(characters.savedCharacters[pseudo], characters.characterCreation, characters, characterElement);

    } else if (displayedPseudo === pseudo) {
      // pass

    } else {
      var result = confirm("Voulez-vous continuer ? Les dernières modifications ne seront pas sauvegardées.");

      if (result) {
        updateForm(characters.savedCharacters[pseudo], characters.characterCreation, characters, characterElement);
        characters.unsavedChanges = false;
      }
    }
  } else {
    
    if (target.tagName === "path") {
      target = target.parentElement;
    }
    
    switch (target.dataset.icon) {

      case "duplicate":

        if (!characters.unsavedChanges) {
          addNewCharacter(characters, characters.newCharacterTemplate, characters.charactersContainer, battle, pseudo);

        } else {
          var result = confirm("Voulez-vous continuer ? Les dernières modifications ne seront pas sauvegardées.");

          if (result) {
            addNewCharacter(characters, characters.newCharacterTemplate, characters.charactersContainer, battle, pseudo);
            saveButtonGreen(characters);
            characters.unsavedChanges = false;
          }
        }
        break;

      case "download":
        downloadCharacter(characters.savedCharacters[pseudo]);
        break;

      case "delete":
        var result = confirm("Voulez-vous vraiment supprimer définitivement le personnage " + pseudo + " ?");
        if (result) {
          deleteCharacter(characters, pseudo, displayedPseudo, characterElement, battle);
        }
        break;
    }
  }
}


function handleNewCharacter(characters, characterTemplate, charactersContainer, battle, pseudo) {
  var newCharacterTemplate = characterTemplate.cloneNode(true);
  var spanInput = newCharacterTemplate.querySelector("span.input");
  var svgContainer = newCharacterTemplate.querySelector("div.svg-container");
  
  newCharacterTemplate.setAttribute("tabindex", "0");
  charactersContainer.appendChild(newCharacterTemplate);
  
  if (pseudo) {
    spanInput.textContent = pseudo;
    spanInput.setAttribute("data-name", pseudo);
  }
  
  newCharacterTemplate.addEventListener("click", function(event) {
    var target = event.target;
    
    if (target.tagName === "path" || target.tagName === "svg") {
      handleClickOnCharacter(spanInput, target, characters, newCharacterTemplate, battle);
    } else {
      handleClickOnCharacter(spanInput, null, characters, newCharacterTemplate, battle, true);
    }
  });
  
  newCharacterTemplate.addEventListener("keydown", function(event) {
    if (event.keyCode === 13) {
      event.target.click();
    }
  });
  
  return [newCharacterTemplate, spanInput, svgContainer];
}


function validPseudo(pseudo) {
  
  var newPseudo = pseudo.replace(/[^A-Za-z0-9]+/g, "");
  
  if (!newPseudo) {
    return "Pseudo"
  }
  
  return newPseudo;
}


function addNewCharacter(characters, characterTemplate, charactersContainer, battle, pseudoToDuplicate) {

  function editAndSetCharacterPseudoInput(selectedCharacter, spanInput, svgContainer) {
    
    var maxPseudoLength = 15;
    
    var selection = window.getSelection();
    var range = document.createRange();
    
    if (pseudoToDuplicate) {
      spanInput.textContent = pseudoToDuplicate;
    }
    
    spanInput.contentEditable = true;
    spanInput.focus();
    range.selectNodeContents(spanInput);
    selection.removeAllRanges();
    selection.addRange(range);
    
    function pseudoValidation() {
      
      var characterPseudo = validPseudo(spanInput.textContent);
      var characterDataObject = {name: characterPseudo};
      
      if (pseudoToDuplicate) {
        characterDataObject = copyObject(characters.savedCharacters[pseudoToDuplicate]);
        characterDataObject.name = characterPseudo;
      }
      
      [characterDataObject, characterPseudo] = addUniquePseudo(characterDataObject, Object.keys(characters.savedCharacters));
      
      selection.removeAllRanges();
      spanInput.contentEditable = false;
      spanInput.textContent = characterPseudo;
      spanInput.setAttribute("data-name", characterPseudo);
      
      updateForm(characterDataObject, characters.characterCreation, characters, selectedCharacter);
      saveCharacter(characters.savedCharacters, characters.characterCreation, battle, true);
    }
    
    function handleMaxLength(event) {
      if (spanInput.textContent.length > maxPseudoLength) {
        spanInput.textContent = spanInput.textContent.slice(0, maxPseudoLength);
        range.setStart(spanInput.childNodes[0], maxPseudoLength);
        selection.removeAllRanges();
        selection.addRange(range);
      }
    }

    function handleBlur() {
      spanInput.removeEventListener("blur", handleBlur);
      spanInput.removeEventListener("input", handleMaxLength);
      pseudoValidation();
    }
    
    function handleKeyDown(event) {

      if (event.key === "Enter") {
        
        event.preventDefault();
        
        spanInput.removeEventListener("keydown", handleKeyDown);
        spanInput.removeEventListener("blur", handleBlur);
        spanInput.removeEventListener("input", handleMaxLength);
        
        pseudoValidation();
      }
    }
    
    spanInput.addEventListener("input", handleMaxLength);
    spanInput.addEventListener("keydown", handleKeyDown);
    spanInput.addEventListener("blur", handleBlur);
  }
  
  hideElement(characters.characterCreation);
  var [selectedCharacter, spanInput, svgContainer] = handleNewCharacter(characters, characterTemplate, charactersContainer, battle);

  editAndSetCharacterPseudoInput(selectedCharacter, spanInput, svgContainer);
}


function characterManagement(characters, battle) {
  
  var characterTemplate = characters.newCharacterTemplate;
  var charactersContainer = characters.charactersContainer;
  
  Object.keys(characters.savedCharacters).forEach(function(pseudo) {
    handleNewCharacter(characters, characterTemplate, charactersContainer, battle, pseudo);
  });

  characters.addNewCharacterButton.addEventListener("click", function(event) {
    if (!characters.unsavedChanges) {
      addNewCharacter(characters, characterTemplate, charactersContainer, battle);
      
    } else {
      var result = confirm("Voulez-vous continuer ? Les dernières modifications ne seront pas sauvegardées.");
      
      if (result) {
        addNewCharacter(characters, characterTemplate, charactersContainer, battle);
        saveButtonGreen(characters);
        characters.unsavedChanges = false;
      }
    }
  });
  
  characters.uploadCharacter.addEventListener("click", function(event) {
    uploadCharacter(characters, characterTemplate, charactersContainer, battle);
  });
  
  characters.characterCreation.addEventListener("change", function() {
    saveButtonOrange(characters)
    characters.unsavedChanges = true;
  })
  
  filterForm(characters);
  characterCreationListener(characters, battle);
  
  window.addEventListener('beforeunload', function(event) {
    if (characters.unsavedChanges) {
      event.preventDefault();
      event.returnValue = '';
      return '';
    }
  });
}


function handleNewMonster(characters, monsterTemplate, monstersContainer, battle, monsterName, monsterList) {
  var newMonsterTemplate = monsterTemplate.cloneNode(true);
  var spanInput = newMonsterTemplate.querySelector("span.input");
  var deleteSvg = newMonsterTemplate.querySelector("svg");

  spanInput.textContent = monsterName;
  monstersContainer.appendChild(newMonsterTemplate);
  
  newMonsterTemplate.setAttribute("tabindex", "0");
  newMonsterTemplate.setAttribute("data-name", monsterName);
  monstersContainer.appendChild(newMonsterTemplate);
  
  deleteSvg.addEventListener("click", function(event) {
    deleteMonster(characters, monsterName, newMonsterTemplate, battle);
    var inputMonster = monsterList.querySelector("input[name='" + monsterName + "']");
    inputMonster.checked = false;
  });
}


function monsterManagement(characters, battle) {
  
  function handleDropdown(searchMonster, monsterList) {

    searchMonster.addEventListener("focus", function(event) {
      showElement(monsterList);
    });

    document.addEventListener("mousedown", function(event) {
      var target = event.target;
      if (!monsterList.contains(target) && !searchMonster.contains(target)) {
        hideElement(monsterList);
      }
    });
  }
  
  function addMonsterNames(monsterList) {
    
    var monsterIndex = 0;

    for (var monsterName in monsterData) {
      
      var li = document.createElement("li");
      var label = document.createElement("label");
      var input = document.createElement("input");
      var textNode = document.createTextNode(monsterName);
      
      label.htmlFor = "monster" + monsterIndex;
      input.id = "monster" + monsterIndex;
      input.type = "checkbox";
      
      input.name = monsterName;
      
      label.appendChild(input);
      label.appendChild(textNode);
      li.appendChild(label);
      monsterList.appendChild(li);
      
      monsterIndex++;
    }
  }
  
  function filterNames(searchMonster, monsterList) {
    
    var debounceTimer;

    searchMonster.addEventListener("input", function(event) {
      clearTimeout(debounceTimer);
      debounceTimer = setTimeout(function() {
        var value = toNormalForm(event.target.value);
        for (var element of monsterList.children) {
          if (!isValueInArray(value, toNormalForm(element.textContent))) {
            hideElement(element);
          } else {
            showElement(element);
          }
        }
      }, 500);
    });
  }
  
  var monsterTemplate = characters.newMonsterTemplate;
  var monstersContainer = characters.monstersContainer;
  var monsterList = characters.monsterList;
  var searchMonster = characters.searchMonster;
  var monsterListForm = characters.monsterListForm;
  
  handleDropdown(searchMonster, monsterList);
  addMonsterNames(monsterList, characters.monsterListTemplate);
  filterNames(searchMonster, monsterList);
  
  characters.savedMonsters.forEach(function(monsterName) {
    handleNewMonster(characters, monsterTemplate, monstersContainer, battle, monsterName, monsterList);
    var inputMonster = monsterList.querySelector("input[name='" + monsterName + "']");
    
    if (inputMonster) {
      inputMonster.checked = true;
    } else {
      deleteMonster(characters, monsterName, newMonsterTemplate, battle);
    }
  });
  
  monsterListForm.addEventListener("submit", function(event) {
    event.preventDefault();
  })
  
  monsterListForm.addEventListener("change", function(event) {
    
    var target = event.target;
    var monsterName = target.name;
    
    if (monsterName === "search-monster") {
      return;
    }

    if (target.checked) {
      
      handleNewMonster(characters, monsterTemplate, monstersContainer, battle, monsterName, monsterList);

      characters.savedMonsters.push(monsterName);
      updateSavedMonsters(characters.savedMonsters);
      addBattleChoice(battle, monsterName, true);
      
    } else {
      var currentMonsterTemplate = monstersContainer.querySelector("[data-name='" + monsterName + "']");
      deleteMonster(characters, monsterName, currentMonsterTemplate, battle);
    }
  });
}


function removeBattleChoice(battle, name) {
  
  var battleSelects = [battle.attackerSelection, battle.victimSelection];
  
  battleSelects.forEach(function(battleSelect) {
    for (var optionIndex = 0; optionIndex < battleSelect.options.length; optionIndex++) {
      if (battleSelect.options[optionIndex].value === name) {
        battleSelect.remove(optionIndex);
        break;
      }
    }
  });
}


function addBattleChoice(battle, name, isMonster = false) {
  
  function createOption(text) {
    var option = document.createElement("option");
    option.textContent = text;
    option.value = text;
    
    if (!isMonster) {
      option.classList.add("notranslate");
    }

    return option;
  }
  
  if (isMonster && monsterData[name][0]) {
    // pass
  } else {
    battle.attackerSelection.appendChild(createOption(name));
  }
  
  battle.victimSelection.appendChild(createOption(name));
}


function updateBattleChoice(characters, battle) {
  
  var keys = Object.keys(characters.savedCharacters);
  var attackerSelection = battle.attackerSelection;

  for (var index = 0; index < keys.length; index++) {
    var pseudo = keys[index];
    addBattleChoice(battle, pseudo);
  }
  
  characters.savedMonsters.forEach(function(monsterName) {
    addBattleChoice(battle, monsterName, true);
  });

  var observer = new MutationObserver(function(mutationsList, observer) {
    battle.battleForm.reset();
  });

  observer.observe(attackerSelection, {childList: true});
}


function isPC(character) {
  if (character.race === 0 || character.race === 1) {
    return false;
  }
  return true;
}


function calcAttackFactor(attacker, victim) {

  function calcCoeffK(dex, level) {
    return Math.min(90, Math.floor((2 * dex + level) / 3))
  }

  var K1 = calcCoeffK(attacker.dex, attacker.level);
  var K2 = calcCoeffK(victim.dex, attacker.level);
  
  var AR = (K1 + 210) / 300;
  var ER = (2 * K2 + 5) / (K2 + 95) * 3 / 10;

  return AR - ER
}


function calcMainAttackValue(attacker, attackerWeapon) {
  
  var leadership = 0;
  var rawWeaponAttackValue = 0;
  
  if (isPC(attacker)) {
    
    var rawWeaponAttackValue = attackerWeapon[3][attacker.upgrade];

    if (!rawWeaponAttackValue) {
      rawWeaponAttackValue = 0;
    }
    
    leadership = attacker.leadership;
  }
  
  return 2 * (attacker.level + rawWeaponAttackValue) + leadership;
}


function calcStatAttackValue(character) {
  
  switch (character.race) {
    case "warrior":
    case "sura":
      return 2 * character.str;
    case "ninja":
      return Math.floor(1 / 4 * (character.str + 7 * character.dex));
    case "shaman":
      return Math.floor(1 / 3 * (5 * character.int + character.dex));
    case "lycan":
      return character.vit + 2 * character.dex;
    default:
      return 2 * character.str;
  }
}


function calcSecondaryAttackValue(attacker, attackerWeapon) {
  
  var attackValues = [];
  var weights = [];
  
  var attackValueOther = 0;
  
  var minAttackValue = 0;
  var maxAttackValue = 0;
  
  var minAttackValueSlash = 0;
  var maxAttackValueSlash = 0;
  
  if (isPC(attacker)) {
    
    if (!isValueInArray("serpent", attacker.weapon.toLowerCase())) {

      minAttackValue += attackerWeapon[2][2];
      maxAttackValue += attackerWeapon[2][3];

    } else {

      var rawAttackValue = attackerWeapon[3][attacker.upgrade];
      
      minAttackValue += attacker.randomAttackValueMin - rawAttackValue;
      maxAttackValue += attacker.randomAttackValueMax  - rawAttackValue;
      
      minAttackValue = Math.max(0, minAttackValue);
      maxAttackValue = Math.max(minAttackValue, maxAttackValue);
    }
    
    minAttackValueSlash += Math.min(attacker.slashMin, attacker.slashMax);
    maxAttackValueSlash += Math.max(attacker.slashMin, attacker.slashMax);
    
    attackValueOther += attacker.attackValue;
    
  } else {
    
    minAttackValue += attacker.randomAttackValueMin;
    maxAttackValue += attacker.randomAttackValueMax;
  }
  
  attackValueOther += calcStatAttackValue(attacker);

  var weaponRange = maxAttackValue - minAttackValue;
  var slashRange = maxAttackValueSlash - minAttackValueSlash;
  
  var totalCardinal = (weaponRange + 1) * (slashRange + 1) * 10000;
  var minRange = Math.min(weaponRange, slashRange) + 1;
  
  minAttackValue += minAttackValueSlash;
  maxAttackValue += maxAttackValueSlash;

  return [minAttackValue, maxAttackValue, attackValueOther, minRange, totalCardinal];
}


function calcDamageWithPrimaryBonuses(damages, battleValues) {

  damages = floorMultiplication(damages, battleValues.attackValueCoeff);
  damages = floorMultiplication(damages, battleValues.typeBonusCoeff);
  damages = floorMultiplication(damages, battleValues.monsterBonusCoeff);
  damages = floorMultiplication(damages, battleValues.damageMultiplier);

  damages -= battleValues.defense;
  
  return damages;
}


function calcDamageWithSecondaryBonuses(damages, battleValues, damagesType, extraDamages) {
  
  damages = floorMultiplication(damages, battleValues.weaponDefenseCoeff);

  if (damagesType.criticalHit) {
    damages *= 2;
  }
  
  if (damagesType.piercingHit) {
    damages += battleValues.defense - Math.min(0, extraDamages);
    damages = floorMultiplication(damages, battleValues.extraPiercingHitCoeff);
  }
  
  damages = floorMultiplication(damages, battleValues.averageDamageCoeff);
  damages = floorMultiplication(damages, battleValues.averageDamageResistanceCoeff);
  damages += Math.floor(battleValues.defensePercent);

  return damages;
}


function createBattleValues(attacker, victim, mapping) {
  
  var attackValuePercent = 0;
  var attackMeleeMagic = 0;
  var typeBonus = 0;
  var monsterBonus = 0;
  var damageMultiplier = 1;
  var weaponDefense = 0;
  var criticalHitPercentage = attacker.criticalHit;
  var piercingHitPercentage = attacker.piercingHit;
  var averageDamage = 0;
  var averageDamageResistance = 0;
  var defensePercent = 0;
  
  if (isPC(attacker)) {
    
    attackValuePercent = attacker.attackValuePercent;
    attackMeleeMagic = attacker.attackMeleeMagic;
    
    var weaponType = 8;
    
    if (weaponData.hasOwnProperty(attacker.weapon))
      weaponType = weaponData[attacker.weapon][1];
    
    var weaponDefenseName = mapping.defenseWeapon[weaponType];
    var weaponDefenseBreakName = mapping.breakWeapon[weaponType]
    
    if (victim.hasOwnProperty(weaponDefenseName)) {
      weaponDefense = victim[weaponDefenseName];
    }
    
    if (isPC(victim)) {
      
      typeBonus = attacker.humanBonus;
      
      if (attacker.hasOwnProperty(weaponDefenseBreakName)) {
        weaponDefense -= attacker[weaponDefenseBreakName];
      }
    } else {
      
      var victimType = victim.type;
      
      if (victimType !== -1) {
        typeBonus = attacker[mapping.typeFlag[victimType]];
      }
      
      monsterBonus = attacker.monsterBonus;
    }
    
    averageDamage = attacker.averageDamage;
    
  } else {
    
    damageMultiplier = attacker.damageMultiplier;
  }
  
  if (isPC(victim)) {
    
    criticalHitPercentage -= victim.criticalHitResistance;
    piercingHitPercentage -= victim.piercingHitResistance;
    averageDamageResistance = victim.averageDamageResistance;
    
    if (victim.race === "shaman" || victim.class === "black_magic") {
      defensePercent = -2 * victim.magicDefense * victim.defensePercent / 100;

    } else {
      defensePercent = -2 * victim.defense * victim.defensePercent / 100;
    }
  }
  
  var battleValues = {
    attackValueCoeff: 1 + (attackValuePercent + Math.min(100, attackMeleeMagic)) / 100,
    typeBonusCoeff: 1 + typeBonus / 100,
    monsterBonusCoeff: 1 + monsterBonus / 100,
    damageMultiplier: damageMultiplier,
    defense: victim.defense,
    weaponDefenseCoeff: 1 - weaponDefense / 100,
    extraPiercingHitCoeff: 1 + Math.max(0, piercingHitPercentage - 100) / 200,
    averageDamageCoeff: 1 + averageDamage / 100,
    averageDamageResistanceCoeff: 1 - Math.min(99, averageDamageResistance) / 100,
    defensePercent: defensePercent
  }

  criticalHitPercentage = Math.min(criticalHitPercentage, 100);
  piercingHitPercentage = Math.min(piercingHitPercentage, 100);
  
  battleValues.damagesTypeCombinaison = [
    {criticalHit: false, piercingHit: false, weight: (100 - criticalHitPercentage) * (100 - piercingHitPercentage), name: "Coup classique"},
    {criticalHit: true, piercingHit: false, weight: criticalHitPercentage * (100 - piercingHitPercentage), name: "Coup critique"},
    {criticalHit: false, piercingHit: true, weight: (100 - criticalHitPercentage) * piercingHitPercentage, name: "Coup perçant"},
    {criticalHit: true, piercingHit: true, weight: criticalHitPercentage * piercingHitPercentage, name: "Coup critique + coup perçant"}
  ]

  return battleValues;
}


function handleFinalDamages(battleValues, damages, extraDamages, minDamages, maxDamages, damagesType, tableResult, probability) {
  
  var finalDamages = calcDamageWithSecondaryBonuses(damages, battleValues, damagesType, extraDamages);
  addToTableResult(tableResult, finalDamages, probability);
  
  if (finalDamages < minDamages) {
    minDamages = finalDamages;
  }

  if (finalDamages > maxDamages) {
    maxDamages = finalDamages;
  }
  
  return [finalDamages, minDamages, maxDamages];
}


function calcBattleDamages(attacker, victim, tableResult, mapping) {
  
  var primaryDamages = [];
  var weights = [];
  var attackerWeapon = null;
  var battleValues = createBattleValues(attacker, victim, mapping);
  
  var sumDamages = 0;
  var minDamages = 2**53 - 1;
  var saveDamages = 0;
  
  clearTableResult(tableResult);
  
  if (isPC(attacker)) {
     attackerWeapon = weaponData[attacker.weapon];
  }

  var attackFactor = calcAttackFactor(attacker, victim);
  var mainAttackValue = calcMainAttackValue(attacker, attackerWeapon);
  var [minAttackValue, maxAttackValue, attackValueOther, minRange, totalCardinal] = calcSecondaryAttackValue(attacker, attackerWeapon);

  var lastWeightsLimit = maxAttackValue - minRange + 1;
  var firstWeightLimit = minAttackValue + minRange - 1;
  
  for (var damagesType of battleValues.damagesTypeCombinaison) {

    if (!damagesType.weight) {
      continue;
    }
    
    addRowToTableResult(tableResult, damagesType.name);
    
    for (var attackValue = minAttackValue; attackValue <= maxAttackValue; attackValue++) {
      var weight;

      if (attackValue > lastWeightsLimit) {
        weight = maxAttackValue - attackValue + 1;

      } else if (attackValue < firstWeightLimit) {
        weight = attackValue - minAttackValue + 1;

      } else {
        weight = minRange;
      }

      var secondaryAttackValue = 2 * attackValue + attackValueOther;
      var rawDamages = mainAttackValue + floorMultiplication(attackFactor, secondaryAttackValue);
      var damagesWithPrimaryBonuses = calcDamageWithPrimaryBonuses(rawDamages, battleValues);

      if (damagesWithPrimaryBonuses <= 2) {
        
        for (var damages = 1; damages <= 5; damages++) {
          
          var finalDamages = calcDamageWithSecondaryBonuses(damages, battleValues, damagesType, damagesWithPrimaryBonuses);
          addToTableResult(tableResult, finalDamages, weight * damagesType.weight / (5 * totalCardinal));
          
          if (damages === 5) {
            saveDamages = finalDamages;
          }
          
          sumDamages += finalDamages * weight * damagesType.weight / 5;
        }
        
      } else {
        
        var finalDamages = calcDamageWithSecondaryBonuses(damagesWithPrimaryBonuses, battleValues, damagesType, 0);
        addToTableResult(tableResult, finalDamages, weight * damagesType.weight / totalCardinal);

        sumDamages += finalDamages * weight * damagesType.weight;
      }
    }
  }

  return [sumDamages / totalCardinal, getMinDamages(tableResult), Math.max(saveDamages, finalDamages)];
}


function createMonster(name) {
  
  var data = monsterData[name];

  var monster = {
    "name": name,
    "race": data[0],
    "level": data[1],
    "type": data[2],
    "str": data[3],
    "dex": data[4],
    "vit": data[5],
    "int": data[6],
    "randomAttackValueMin": data[7],
    "randomAttackValueMax": data[8],
    "defense": data[9] + data[1] + data[5],
    "criticalHit": data[10],
    "piercingHit": data[11],
    "fistDefense": data[12],
    "swordDefense": data[13],
    "twoHandedDefense": data[14],
    "daggerDefense": data[15],
    "bellDefense": data[16],
    "fanDefense": data[17],
    "bowDefense": data[18],
    "clawDefense": data[19],
    "damageMultiplier": data[33]
  }
  
  return monster;
}


function createBattle(characters, battle) {
  
  function isPseudoSaved(pseudo) {
    return characters.savedCharacters.hasOwnProperty(pseudo);
  }
  
  battle.battleForm.addEventListener("submit", function(event) {
    
    event.preventDefault();
    
    var battleInfo = new FormData(event.target)
    var attackerName = battleInfo.get("attacker");
    var attackType = battleInfo.get("attackTypeSelection");
    var victimName = battleInfo.get("victim");
    
    if (!attackerName && !attackType && !victimName) {
      return;
    }

    if (isPseudoSaved(attackerName)) {
      var attacker = characters.savedCharacters[attackerName];
    } else {
      var attacker = createMonster(attackerName);
    }
    
    if (isPseudoSaved(victimName)) {
      var victim = characters.savedCharacters[victimName];
    } else {
      var victim = createMonster(victimName);
    }

    var [meanDamages, minDamages, maxDamages] = calcBattleDamages(attacker, victim, battle.tableResult, battle.mapping);

    battle.damageResult.textContent = attacker.name + " inflige " + numberFormat(meanDamages, 1) + " dégâts en moyenne à " + victim.name + " (minimum : " + minDamages + ", maximum : " + maxDamages + ").";
    
    showElement(battle.tableContainer);
  });
 
  battle.attackerSelection.addEventListener("change", function(event) {
    var name = event.target.value;
    var attackTypeSelection = battle.attackTypeSelection;
    
    if (isPseudoSaved(name)) {
      //pass
    } else {
      var optionsLength = attackTypeSelection.options.length;
      
      if (optionsLength <= 1) {
        return;
      }
      
      for (var optionIndex = optionsLength - 1; optionIndex >= 2; optionIndex--) {
        attackTypeSelection.remove(optionIndex);
      }
    }
  });
}


function createDamageCalculatorInformation() {
  
  var characters = {
    unsavedChanges: false,
    savedCharacters: {},
    currentCharacter: {element: null},
    characterCreation: document.getElementById("character-creation"),
    addNewCharacterButton: document.getElementById("add-new-character"),
    uploadCharacter: document.getElementById("upload-character"),
    newCharacterTemplate: document.getElementById("new-character-template").children[0],
    charactersContainer: document.getElementById("characters-container"),
    newMonsterTemplate: document.getElementById("new-monster-template").children[0],
    monstersContainer: document.getElementById("monsters-container"),
    monsterListForm: document.getElementById("monster-list-form"),
    searchMonster: document.getElementById("search-monster"),
    monsterList: document.getElementById("monster-list"),
    saveButton: document.getElementById("save-button"),
    saveBar: document.getElementById("save-bar"),
    raceChoice: document.getElementById("race-choice"),
    classChoice: document.getElementById("class-choice"),
    weaponChoice: document.getElementById("weapon-choice"),
    weaponUpgrade: document.getElementById("upgrade-choice"),
    randomAttackValue: document.getElementById("random-attack-value")
  }

  characters.currentCharacter.normal = characters.newCharacterTemplate.getAttribute("style");
  characters.currentCharacter.onClick = characters.currentCharacter.normal + ' ' + characters.newCharacterTemplate.dataset.click;
  
  delete characters.newCharacterTemplate.dataset.click;

  var savedCharacters = getSavedCharacters();
  var savedMonsters = getSavedMonsters();

  for (var [pseudo, character] of Object.entries(savedCharacters)) {
    characters.savedCharacters[pseudo] = character;
  }
  
  characters.savedMonsters = savedMonsters;

  var mapping = {
    typeFlag: {
      0: "animalBonus",
      1: "humanBonus",
      2: "orcBonus",
      3: "mysticBonus",
      4: "undeadBonus",
      5: "insectBonus",
      6: "desertBonus",
      7: "devilBonus"
    },
    defenseWeapon: {
      0: "swordDefense",
      1: "daggerDefense",
      2: "bowDefense",
      3: "twoHandedSwordDefense",
      4: "bellDefense",
      5: "clawDefense",
      6: "fanDefense",
      7: "swordDefense",
      8: "fistDefense"
    },
    breakWeapon: {
      0: "breakSwordDefense",
      1: "breakDaggerDefense",
      2: "breakBowDefense",
      3: "breakTwoHandedSwordDefense",
      4: "breakBellDefense",
      5: "breakClawDefense",
      6: "breakFanDefense",
      7: "breakSwordDefense"
    }
  }
  
  var battle = {
    battleForm: document.getElementById("create-battle"),
    attackerSelection: document.getElementById("attacker-selection"),
    attackTypeSelection: document.getElementById("attack-type-selection"),
    victimSelection: document.getElementById("victim-selection"),
    damageResult: document.getElementById("result-damage"),
    tableContainer: document.getElementById("result-table-container"),
    tableResult: document.getElementById("result-table").children[0],
    mapping: mapping
  }

  return [characters, battle];
}


function loadScript(src, callback) {

  var script = document.createElement("script");
  script.src = src;
  
  function onComplete() {
    if (script.parentNode) {
      script.parentNode.removeChild(script);
    }
    callback();
  }
  
  document.head.appendChild(script);

  script.onload = onComplete;
  script.onerror = onComplete;
}


function loadStyle(src) {

  var link = document.createElement("link");
  link.href = src;
  link.rel = "stylesheet";
  
  document.head.appendChild(link);
}


(function(){
  
  var javascriptSource = "/index.php?title=Utilisateur:Ankhseram/Calculator.js&action=raw&ctype=text/javascript";
  var cssSource = "/index.php?title=Utilisateur:Ankhseram/Style.css&action=raw&ctype=text/css";
  
  loadStyle(cssSource);
  
  function main() {
  
    var [characters, battle] = createDamageCalculatorInformation();

    characterManagement(characters, battle);
    monsterManagement(characters, battle);

    updateBattleChoice(characters, battle);
    createBattle(characters, battle);
  }
  
  loadScript(javascriptSource, main);

})();