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

Ligne 6 : Ligne 6 :
 
function hideElement(element) {
 
function hideElement(element) {
 
   element.classList.add("tabber-noactive");
 
   element.classList.add("tabber-noactive");
 +
}
 +
 +
 +
function copyObject(object) {
 +
  var copy = {};
 +
  for (var key in object) {
 +
    copy[key] = object[key];
 +
  }
 +
  return copy;
 +
}
 +
 +
 +
function averageArray(arr) {
 +
 
 +
  var average = 0;
 +
  var arrLength = arr.length;
 +
 
 +
  for (var indexArray = 0; indexArray < arrLength; indexArray++) {
 +
    average += arr[indexArray];
 +
  }
 +
 
 +
  return average / arrLength;
 
}
 
}
  
Ligne 429 : Ligne 451 :
 
    
 
    
 
   return newPseudo;
 
   return newPseudo;
}
 
 
 
function copyObject(object) {
 
  var copy = {};
 
  for (var key in object) {
 
    copy[key] = object[key];
 
  }
 
  return copy;
 
 
}
 
}
  
Ligne 592 : Ligne 605 :
 
     addBattleChoice(battle, pseudo);
 
     addBattleChoice(battle, pseudo);
 
   });
 
   });
 +
}
 +
 +
 +
function isPC(character) {
 +
  if (character.race === "monster") {
 +
    return false;
 +
  }
 +
  return true;
 
}
 
}
  
Ligne 611 : Ligne 632 :
  
  
function calcMainAttackValue(character, characterWeapon) {
+
function calcMainAttackValue(attacker, attackerWeapon) {
 
    
 
    
   var rawWeaponAttackValue = characterWeapon[2][character.upgrade];
+
  var leadership = 0;
 +
   var rawWeaponAttackValue = 0;
 
    
 
    
   if (!rawWeaponAttackValue) {
+
   if (isPC(attacker)) {
    rawWeaponAttackValue = 0;
+
   
 +
    var rawWeaponAttackValue = attackerWeapon[2][attacker.upgrade];
 +
 
 +
    if (!rawWeaponAttackValue) {
 +
      rawWeaponAttackValue = 0;
 +
    }
 +
   
 +
    leadership = attacker.leadership;
 
   }
 
   }
 
+
 
   return 2 * (character.level + rawWeaponAttackValue) + character.leadership
+
   return 2 * (attacker.level + rawWeaponAttackValue) + leadership;
 
}
 
}
  
Ligne 641 : Ligne 670 :
  
  
function calcSecondaryAttackValue(character, characterWeapon) {
+
function calcWeightsDamages(weaponRange, slashRange) {
 +
  var weights = [];
 +
  var totalCardinal = (weaponRange + 1) * (slashRange + 1);
 +
  var minLength = Math.min(weaponRange, slashRange);
 +
 
 +
  for (var index = 0; index < minLength; index++) {
 +
    weights.push((index + 1) / totalCardinal);
 +
  }
 
    
 
    
   var statAttackValue = calcStatAttackValue(character);
+
  for (var index = 0; index <= Math.abs(weaponRange - slashRange); index++) {
 +
    weights.push((minLength + 1) / totalCardinal);
 +
  }
 +
 
 +
  for (var index = 0; index < minLength; index++) {
 +
    weights.push((minLength - index) / totalCardinal);
 +
  }
 +
 
 +
  return weights;
 +
}
 +
 
 +
 
 +
function calcSecondaryAttackValue(attacker, attackerWeapon) {
 +
 
 +
   var statAttackValue = calcStatAttackValue(attacker);
 
   var attackValues = [];
 
   var attackValues = [];
 +
  var minAttackValue = 0;
 +
  var maxAttackValue = 0;
 +
  var secondaryAttackValue = 0;
 
    
 
    
   if (character.weapon.indexOf("serpent") === -1) {
+
   if (isPC(attacker)) {
 +
   
 +
    if (attacker.weapon.indexOf("serpent") === -1) {
 +
 
 +
      minAttackValue = attackerWeapon[1][2];
 +
      maxAttackValue = attackerWeapon[1][3];
 +
 
 +
    } else {
 +
 
 +
      var rawAttackValue = attackerWeapon[2][attacker.upgrade];
 +
     
 +
      minAttackValue = Math.max(0, attacker.randomAttackValueMin - rawAttackValue);
 +
      maxAttackValue = Math.max(minAttackValue, attacker.randomAttackValueMax  - rawAttackValue);
 +
    }
 +
   
 +
    var weaponRange = maxAttackValue - minAttackValue;
 +
   
 +
    var slashMax = Math.max(attacker.slashMin, attacker.slashMax);
 +
    var slashRange = slashMax - attacker.slashMin;
 +
   
 +
    minAttackValue += attacker.slashMin;
 +
    maxAttackValue += slashMax;
 +
   
 +
    secondaryAttackValue += attacker.attackValue;
 
      
 
      
     var minWeaponAttackValue = characterWeapon[1][2];
+
     // var weightsDamages = calcWeightsDamages(weaponRange, slashRange);
    var maxWeaponAttackValue = characterWeapon[1][3];
 
 
      
 
      
 
   } else {
 
   } else {
 
      
 
      
     var rawAttackValue = characterWeapon[2][character.upgrade]
+
     minAttackValue = attacker.randomAttackValueMin;
    var minWeaponAttackValue = character.randomAttackValueMin - rawAttackValue;
+
     maxAttackValue = attacker.randomAttackValueMax;
     var maxWeaponAttackValue = Math.max(minWeaponAttackValue, character.randomAttackValueMax - rawAttackValue);
 
   
 
 
   }
 
   }
 
    
 
    
   minWeaponAttackValue += character.slashMin;
+
   secondaryAttackValue += statAttackValue;
  maxWeaponAttackValue += character.slashMax;
 
 
    
 
    
   var secondaryAttackValue = statAttackValue + character.attackValue
+
   for (var attackValue = minAttackValue; attackValue <= maxAttackValue; attackValue++) {
 +
    attackValues.push(2 * attackValue + secondaryAttackValue);
 +
  }
 
    
 
    
   for (var attackValue = minWeaponAttackValue; attackValue <= maxWeaponAttackValue; attackValue++) {
+
   return attackValues;
     attackValues.push(2 * attackValue + secondaryAttackValue);
+
}
 +
 
 +
 
 +
function calcDamageWithPrimaryBonuses(attacker, victim, damages) {
 +
 
 +
  if (isPC(attacker)) {
 +
     damages += parseInt(damages * (attacker.attackValuePercent + Math.min(100, attacker.attackMeleeMagic)) / 100);
 
   }
 
   }
 
    
 
    
   return attackValues
+
  damages -= victim.defense;
 +
 
 +
   return [damages];
 
}
 
}
  
  
function calcDamageWithBonuses(attacker, victim, attackFactor, mainAttackValue, secondaryAttackValue) {
+
function calcDamageWithSecondaryBonuses(attacker, victim, damages) {
   var damages = mainAttackValue + parseInt(attackFactor * secondaryAttackValue);
+
    
   damages -= victim.defense
+
  if (isPC(attacker)) {
   return damages
+
    damages += parseInt(damages * attacker.averageDamage / 100);
 +
   }
 +
 
 +
   return damages;
 
}
 
}
  
  
function calcBattleDamage(attacker, victim) {
+
function calcBattleDamages(attacker, victim) {
 
    
 
    
   var sumDamages = 0;
+
   var allDamages = [];
+
  var attackerWeapon = null;
   var attackerWeapon = weaponData[attacker.weapon];
+
 
 +
   if (isPC(attacker)) {
 +
    attackerWeapon = weaponData[attacker.weapon];
 +
  }
  
 
   var attackFactor = calcAttackFactor(attacker, victim);
 
   var attackFactor = calcAttackFactor(attacker, victim);
 
   var mainAttackValue = calcMainAttackValue(attacker, attackerWeapon);
 
   var mainAttackValue = calcMainAttackValue(attacker, attackerWeapon);
 
   var secondaryAttackValues = calcSecondaryAttackValue(attacker, attackerWeapon);
 
   var secondaryAttackValues = calcSecondaryAttackValue(attacker, attackerWeapon);
 +
 
   var damageLength = secondaryAttackValues.length;
 
   var damageLength = secondaryAttackValues.length;
 
    
 
    
 
   for (var damageIndex = 0; damageIndex < damageLength; damageIndex++) {
 
   for (var damageIndex = 0; damageIndex < damageLength; damageIndex++) {
     var secondaryAttackValue = secondaryAttackValues[damageIndex]
+
   
     sumDamages += calcDamageWithBonuses(attacker, victim, attackFactor, mainAttackValue, secondaryAttackValue);
+
     var secondaryAttackValue = secondaryAttackValues[damageIndex];
 +
     var rawDamages = mainAttackValue + parseInt(attackFactor * secondaryAttackValue);
 +
 
 +
    var damages = calcDamageWithPrimaryBonuses(attacker, victim, rawDamages);
 +
   
 +
    if (damages[0] <= 2) {
 +
      damages = [1, 2, 3, 4, 5];
 +
    }
 +
   
 +
    damages.forEach(function(dam) {
 +
      allDamages.push(calcDamageWithSecondaryBonuses(attacker, victim, dam));
 +
    });
 
   }
 
   }
 
+
 
   return sumDamages / damageLength
+
   return [averageArray(allDamages), allDamages[0], allDamages[damageLength - 1]];
 
}
 
}
  
Ligne 716 : Ligne 816 :
 
     "str": data[1],
 
     "str": data[1],
 
     "dex": data[2],
 
     "dex": data[2],
    "weapon": "Poings",
 
 
     "randomAttackValueMin": data[5],
 
     "randomAttackValueMin": data[5],
 
     "randomAttackValueMax": data[6],
 
     "randomAttackValueMax": data[6],
    "slashMin": 0,
 
    "slashMax": 0,
 
    "attackValue": 0,
 
    "leadership": 0,
 
 
     "defense": data[7] + data[0] + data[3]
 
     "defense": data[7] + data[0] + data[3]
 
   }
 
   }
Ligne 754 : Ligne 849 :
 
     }
 
     }
  
     var meanDamages = calcBattleDamage(attacker, victim);
+
     var [meanDamages, minDamages, maxDamages] = calcBattleDamages(attacker, victim);
  
     battle.damageResult.textContent = attacker.name + " inflige " + damageFormat(meanDamages, 1) + " dégâts en moyenne à " + victim.name + ".";
+
     battle.damageResult.textContent = attacker.name + " inflige " + damageFormat(meanDamages, 1) + " dégâts en moyenne à " + victim.name + " (minimum : " + minDamages + ", maximum : " + maxDamages + ").";
 
   })
 
   })
 
    
 
    

Version du 5 octobre 2023 à 05:54

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


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


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


function averageArray(arr) {
  
  var average = 0;
  var arrLength = arr.length;
  
  for (var indexArray = 0; indexArray < arrLength; indexArray++) {
    average += arr[indexArray];
  }
  
  return average / arrLength;
}


function addWeapon(weaponChoice) {
  
  for (var weapon in weaponData) {
    var option = document.createElement("option");
    option.textContent = weapon;
    option.value = weapon;
    
    if (weaponData[weapon][0].indexOf("warrior") === -1) {
      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 selectedRace = raceChoice.value;
  
  for (var option of weaponChoice.options) {
    if (weaponData[option.value][0].indexOf(selectedRace) !== -1) {
      if (!selectValueIsChanged) {
        weaponChoice.value = option.value;
        selectValueIsChanged = true;
      }
      showElement(option);
    } else {
      hideElement(option);
    }
  }
}


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

  if (weaponName.indexOf('serpent') !== -1) {
    showElement(randomAttackValue);
  } else {
    hideElement(randomAttackValue);
  }
    
  var upgradeNumber = weaponData[weaponName][2].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 getCharacter(pseudo) {
  return localStorage.getItem(pseudo);
}


function getSavedCharactersPseudo() {
  var savedCharactersPseudo = localStorage.getItem("savedCharactersPseudo");

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


function parseCharacter(characterString) {
  return JSON.parse(characterString);
}


function addUniquePseudo(characterDataObject, savedCharactersPseudo) {

  var characterPseudo = characterDataObject.name;
  var originalPseudo = characterPseudo;
  var count = 0;
  
  while (savedCharactersPseudo.indexOf(characterPseudo) !== -1) {
    characterPseudo = originalPseudo + count;
    count++;
  }
  
  characterDataObject.name = characterPseudo;
  return [characterDataObject, characterPseudo]
}


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


function updateSavedCharactersPseudo(charactersPseudo) {
  localStorage.setItem("savedCharactersPseudo", JSON.stringify(charactersPseudo));
}


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;
  localStorage.setItem(characterDataObject.name, JSON.stringify(characterDataObject));
  
  if (newCharacter) {
    updateSavedCharactersPseudo(Object.keys(savedCharacters));
    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));
              handleNewCharacter(characters, characterTemplate, charactersContainer, battle, characterPseudo);
              
              if (selectFilesLength === 1) {
                saveButtonGreen(characters);
                showElement(characters.characterCreation);
                updateForm(characterDataObject, characters.characterCreation, characters);
              }

              saveCharacter(characters.savedCharacters, characters.characterCreation, battle, true, characterDataObject);
            }
          } catch (error) {
            // pass
          }
        };
        reader.readAsText(selectedFile);
      }
    }
});
}


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



function updateForm(formData, characterCreation, characters) {
  
  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(target, characters, battle, edition) {
  
  var displayedPseudo = characters.characterCreation.name.value;
  
  if (edition) {
    
    var pseudo = target.querySelector("span.input").textContent;
    
    saveButtonGreen(characters);
    showElement(characters.characterCreation);

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

    } 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);
        saveButtonGreen(characters);
        characters.unsavedChanges = false;
      }
    }
  } else {
    
    if (target.tagName === "path") {
      target = target.parentElement;
    }
    
    var pseudo = target.parentElement.dataset.pseudo;
    
    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, target.parentElement.parentElement, 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;
    svgContainer.setAttribute("data-pseudo", pseudo);
  }
  
  newCharacterTemplate.addEventListener("click", function(event) {
    var currentTarget = event.currentTarget;
    var target = event.target;
    
    if (target.tagName === "path" || target.tagName === "svg") {
      handleClickOnCharacter(target, characters, battle);
    } else {
      handleClickOnCharacter(currentTarget, characters, battle, true);
    }
  });
  
  return [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(spanInput, svgContainer) {
  
    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));
      
      svgContainer.setAttribute("data-pseudo", characterPseudo);
      selection.removeAllRanges();
      spanInput.contentEditable = false;
      spanInput.textContent = characterPseudo;
      
      saveButtonGreen(characters);
      showElement(characters.characterCreation);
      updateForm(characterDataObject, characters.characterCreation, characters);
      saveCharacter(characters.savedCharacters, characters.characterCreation, battle, true);
    }

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

      if (event.key === "Enter") {
        
        event.preventDefault();
        
        spanInput.removeEventListener("keydown", handleKeyDown);
        spanInput.removeEventListener("blur", handleBlur);
        
        pseudoValidation();
      }
    }

    spanInput.addEventListener("keydown", handleKeyDown);
    spanInput.addEventListener("blur", handleBlur);
  }
  
  hideElement(characters.characterCreation);
  var [spanInput, svgContainer] = handleNewCharacter(characters, characterTemplate, charactersContainer, battle);

  editAndSetCharacterPseudoInput(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 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) {
  
  function createOption(text) {
    var option = document.createElement("option");
    option.textContent = text;
    option.value = text;
    return option;
  }
  
  battle.attackerSelection.appendChild(createOption(name));
  battle.victimSelection.appendChild(createOption(name));
}


function updateBattleChoice(savedCharacters, battle) {
  
  addBattleChoice(battle, "Chien errant");

  Object.keys(savedCharacters).forEach(function(pseudo) {
    addBattleChoice(battle, pseudo);
  });
}


function isPC(character) {
  if (character.race === "monster") {
    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[2][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 parseInt(1 / 4 * (character.str + 7 * character.dex))
    case 'shaman':
      return parseInt(1 / 3 * (5 * character.int + character.dex))
    case 'lycan':
      return character.vit + 2 * character.dex;
    default:
      return 2 * character.str
  }
}


function calcWeightsDamages(weaponRange, slashRange) {
  var weights = [];
  var totalCardinal = (weaponRange + 1) * (slashRange + 1);
  var minLength = Math.min(weaponRange, slashRange);
  
  for (var index = 0; index < minLength; index++) {
    weights.push((index + 1) / totalCardinal);
  }
  
  for (var index = 0; index <= Math.abs(weaponRange - slashRange); index++) {
    weights.push((minLength + 1) / totalCardinal);
  }
  
  for (var index = 0; index < minLength; index++) {
    weights.push((minLength - index) / totalCardinal);
  }

  return weights;
}


function calcSecondaryAttackValue(attacker, attackerWeapon) {
  
  var statAttackValue = calcStatAttackValue(attacker);
  var attackValues = [];
  var minAttackValue = 0;
  var maxAttackValue = 0;
  var secondaryAttackValue = 0;
  
  if (isPC(attacker)) {
    
    if (attacker.weapon.indexOf("serpent") === -1) {

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

    } else {

      var rawAttackValue = attackerWeapon[2][attacker.upgrade];
      
      minAttackValue = Math.max(0, attacker.randomAttackValueMin - rawAttackValue);
      maxAttackValue = Math.max(minAttackValue, attacker.randomAttackValueMax  - rawAttackValue);
    }
    
    var weaponRange = maxAttackValue - minAttackValue;
    
    var slashMax = Math.max(attacker.slashMin, attacker.slashMax);
    var slashRange = slashMax - attacker.slashMin;
    
    minAttackValue += attacker.slashMin;
    maxAttackValue += slashMax;
    
    secondaryAttackValue += attacker.attackValue;
    
    // var weightsDamages = calcWeightsDamages(weaponRange, slashRange);
    
  } else {
    
    minAttackValue = attacker.randomAttackValueMin;
    maxAttackValue = attacker.randomAttackValueMax;
  }
  
  secondaryAttackValue += statAttackValue;
  
  for (var attackValue = minAttackValue; attackValue <= maxAttackValue; attackValue++) {
    attackValues.push(2 * attackValue + secondaryAttackValue);
  }
  
  return attackValues;
}


function calcDamageWithPrimaryBonuses(attacker, victim, damages) {
  
  if (isPC(attacker)) {
    damages += parseInt(damages * (attacker.attackValuePercent + Math.min(100, attacker.attackMeleeMagic)) / 100);
  }
  
  damages -= victim.defense;
  
  return [damages];
}


function calcDamageWithSecondaryBonuses(attacker, victim, damages) {
  
  if (isPC(attacker)) {
    damages += parseInt(damages * attacker.averageDamage / 100);
  }
  
  return damages;
}


function calcBattleDamages(attacker, victim) {
  
  var allDamages = [];
  var attackerWeapon = null;
  
  if (isPC(attacker)) {
     attackerWeapon = weaponData[attacker.weapon];
  }

  var attackFactor = calcAttackFactor(attacker, victim);
  var mainAttackValue = calcMainAttackValue(attacker, attackerWeapon);
  var secondaryAttackValues = calcSecondaryAttackValue(attacker, attackerWeapon);

  var damageLength = secondaryAttackValues.length;
  
  for (var damageIndex = 0; damageIndex < damageLength; damageIndex++) {
    
    var secondaryAttackValue = secondaryAttackValues[damageIndex];
    var rawDamages = mainAttackValue + parseInt(attackFactor * secondaryAttackValue);

    var damages = calcDamageWithPrimaryBonuses(attacker, victim, rawDamages);
    
    if (damages[0] <= 2) {
      damages = [1, 2, 3, 4, 5];
    }
    
    damages.forEach(function(dam) {
      allDamages.push(calcDamageWithSecondaryBonuses(attacker, victim, dam));
    });
  }

  return [averageArray(allDamages), allDamages[0], allDamages[damageLength - 1]];
}


function damageFormat(damage, precision) {
  return Math.max(0, Math.round((damage) * 10**precision) / 10**precision);
}


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

  var monster = {
    "name": name,
    "level": data[0],
    "race": "monster",
    "vit": data[3],
    "int": data[4],
    "str": data[1],
    "dex": data[2],
    "randomAttackValueMin": data[5],
    "randomAttackValueMax": data[6],
    "defense": data[7] + data[0] + data[3]
  }
  
  return monster;
}


function createBattle(characters, battle) {
  
  battle.battleForm.addEventListener("submit", function(event) {
    
    event.preventDefault();
    
    var battleInfo = new FormData(event.target)
    var attackerName = battleInfo.get("attacker");
    var victimName = battleInfo.get("victim");
    
    if (attackerName === 'Chien errant') {
      var attacker = createMonster('Chien errant');
      
    } else {
      var attacker = characters.savedCharacters[attackerName];
    }
    
    if (victimName === 'Chien errant') {
      var victim = createMonster('Chien errant');
      
    } else {
      var victim = characters.savedCharacters[victimName];
    }

    var [meanDamages, minDamages, maxDamages] = calcBattleDamages(attacker, victim);

    battle.damageResult.textContent = attacker.name + " inflige " + damageFormat(meanDamages, 1) + " dégâts en moyenne à " + victim.name + " (minimum : " + minDamages + ", maximum : " + maxDamages + ").";
  })
  
}


function createDamageCalculatorInformation() {
  
  var characters = {
    unsavedChanges: false,
    savedCharacters: {},
    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"),
    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")
  }
  var savedCharactersPseudo = getSavedCharactersPseudo();

  savedCharactersPseudo.forEach(function(pseudo) {
    var characterData = getCharacter(pseudo);
    characterData = parseCharacter(characterData);
    characters.savedCharacters[pseudo] = characterData;
  });
  
  var battle = {
    battleForm: document.getElementById("create-battle"),
    attackerSelection: document.getElementById("attacker-selection"),
    victimSelection: document.getElementById("victim-selection"),
    damageResult: document.getElementById("result-damage")
  }

  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(){
  
  var src = "/index.php?title=Utilisateur:Ankhseram/Calculator.js&action=raw&ctype=text/javascript";
  
  function main() {
  
    var [characters, battle] = createDamageCalculatorInformation();

    characterManagement(characters, battle);

    updateBattleChoice(characters.savedCharacters, battle);
    createBattle(characters, battle);
  }
  
  loadScript(src, main);

})();