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

 
(12 révisions intermédiaires par le même utilisateur non affichées)
Ligne 28 : Ligne 28 :
 
   }
 
   }
 
   return copy;
 
   return copy;
 +
}
 +
 +
function compareNumbers(a, b) {
 +
  return b - a;
 
}
 
}
  
Ligne 34 : Ligne 38 :
 
}
 
}
  
function numberFormat(number, precision) {
+
function floorMultiplicationWithNegative(firstFactor, secondFactor) {
   return Math.round(number * 10 ** precision) / 10 ** precision;
+
  if (secondFactor < 0) {
 +
    return -floorMultiplication(firstFactor, -secondFactor);
 +
  } else {
 +
    return floorMultiplication(firstFactor, secondFactor);
 +
  }
 +
}
 +
 
 +
function numberDisplay(number, precision) {
 +
   return (Math.round(number * 10 ** precision) / 10 ** precision)
 +
    .toString()
 +
    .replace(".", ",");
 
}
 
}
  
Ligne 61 : Ligne 75 :
 
   for (var damages in damagesWeighted) {
 
   for (var damages in damagesWeighted) {
 
     if (firstIteration && minMaxDamages) {
 
     if (firstIteration && minMaxDamages) {
       damages = parseInt(damages)
+
       damages = parseInt(damages);
 
       if (damages < minMaxDamages.min) {
 
       if (damages < minMaxDamages.min) {
 
         minMaxDamages.min = damages;
 
         minMaxDamages.min = damages;
Ligne 75 : Ligne 89 :
 
     var secondCell = newRow.insertCell(1);
 
     var secondCell = newRow.insertCell(1);
 
     secondCell.textContent =
 
     secondCell.textContent =
       numberFormat(damagesWeighted[damages] * 100, 3)
+
       numberDisplay(damagesWeighted[damages] * 100, 3) + " %";
        .toString()
 
        .replace(".", ",") + " %";
 
 
   }
 
   }
  
Ligne 93 : Ligne 105 :
 
     tableResult.deleteRow(tableHeaderRowCount);
 
     tableResult.deleteRow(tableHeaderRowCount);
 
   }
 
   }
 +
}
 +
 +
function getMonsterName(monsterVnum) {
 +
  var monsterAttributes = monsterData[monsterVnum];
 +
  return monsterAttributes[monsterAttributes.length - 1];
 
}
 
}
  
Ligne 159 : Ligne 176 :
 
   var oldImage = weaponDisplay.firstChild;
 
   var oldImage = weaponDisplay.firstChild;
 
   var oldText = oldImage.nextElementSibling;
 
   var oldText = oldImage.nextElementSibling;
 +
  var weaponName = weaponData[weaponValue][0];
  
   newText.textContent = " " + weaponData[weaponValue][0] + " ";
+
   if (weaponValue == 0) {
 +
    newText.textContent = " " + weaponName + " ";
 +
  } else {
 +
    var weaponLink = document.createElement("a");
 +
    weaponLink.href = mw.util.getUrl(weaponName);
 +
    weaponLink.title = weaponName;
 +
    weaponLink.textContent = weaponName;
 +
 
 +
    newText.appendChild(document.createTextNode(" "));
 +
    newText.appendChild(weaponLink);
 +
    newText.appendChild(document.createTextNode(" "));
 +
  }
  
 
   weaponDisplay.replaceChild(newImage, oldImage);
 
   weaponDisplay.replaceChild(newImage, oldImage);
Ligne 211 : Ligne 240 :
 
}
 
}
  
function filterState(stateChoice, polymorphMonster) {
+
function filterState(selectedState, polymorphMonster) {
   if (stateChoice.value === "polymorph") {
+
   if (selectedState === "polymorph") {
 
     showElement(polymorphMonster.parentElement);
 
     showElement(polymorphMonster.parentElement);
 
   } else {
 
   } else {
Ligne 219 : Ligne 248 :
 
}
 
}
  
function filterPlayerRank(lowRankCheckbox, playerRankChoice) {
+
function filterCheckbox(checkbox, element) {
   if (lowRankCheckbox.checked) {
+
   if (checkbox.checked) {
     showElement(playerRankChoice.parentElement);
+
     showElement(element);
 
   } else {
 
   } else {
     hideElement(playerRankChoice.parentElement);
+
     hideElement(element);
 +
  }
 +
}
 +
 
 +
function filterSkills(selectedClass, skillElementsToFilter) {
 +
  for (var element of skillElementsToFilter) {
 +
    if (isValueInArray(selectedClass, element.dataset.class)) {
 +
      showElement(element);
 +
    } else {
 +
      hideElement(element);
 +
    }
 
   }
 
   }
 
}
 
}
  
function filterForm(characters) {
+
function filterAttackTypeSelection(attacker, attackTypeSelection) {
   characters.characterCreation.addEventListener("change", function (event) {
+
  var attackerClass = attacker.class;
 +
  var selectedOption =
 +
    attackTypeSelection.options[attackTypeSelection.selectedIndex];
 +
 
 +
  for (var option of attackTypeSelection.options) {
 +
    optionClass = option.dataset.class;
 +
 
 +
    if (optionClass) {
 +
      if (
 +
        optionClass === attackerClass &&
 +
        attacker[option.value] &&
 +
        attacker.state !== "polymorph"
 +
      ) {
 +
        showElement(option);
 +
      } else {
 +
        hideElement(option);
 +
 
 +
        if (selectedOption === option) {
 +
          attackTypeSelection.selectedIndex = 0;
 +
        }
 +
      }
 +
    }
 +
  }
 +
}
 +
 
 +
function filterForm(characters, battle) {
 +
   var characterCreation = characters.characterCreation;
 +
 
 +
  characterCreation.addEventListener("change", function (event) {
 
     var target = event.target;
 
     var target = event.target;
 +
    var targetName = target.name;
  
     switch (target.name) {
+
     switch (targetName) {
 
       case "race":
 
       case "race":
 
         var selectedRace = target.value;
 
         var selectedRace = target.value;
         filterClass(selectedRace, characters.classChoice);
+
        var classChoice = characterCreation.class;
         filterWeapon(
+
        var weapon = characterCreation.weapon;
          selectedRace,
+
 
          characters.weapon,
+
         filterClass(selectedRace, classChoice);
          characters.weaponCategory
+
         filterWeapon(selectedRace, weapon, characters.weaponCategory);
        );
 
  
 
         var newWeapon = getSelectedWeapon(characters.weaponCategory);
 
         var newWeapon = getSelectedWeapon(characters.weaponCategory);
         handleWeaponDisplay(
+
         handleWeaponDisplay(characters.weaponDisplay, newWeapon, weapon.value);
          characters.weaponDisplay,
+
         filterUpgrade(
          newWeapon,
 
          characters.weapon.value
 
        );
 
         filterUpgrade(
 
 
           selectedRace,
 
           selectedRace,
           characters.weaponUpgrade,
+
           characterCreation.weaponUpgrade,
           characters.weapon.value,
+
           weapon.value,
 
           characters.randomAttackValue,
 
           characters.randomAttackValue,
 
           characters.randomMagicAttackValue
 
           characters.randomMagicAttackValue
 
         );
 
         );
 +
        filterSkills(classChoice.value, characters.skillElementsToFilter);
 +
 +
        if (characterCreation.name.value === battle.attackerSelection.value) {
 +
          battle.resetAttackType = true;
 +
        }
 +
        break;
 +
      case "class":
 +
        filterSkills(target.value, characters.skillElementsToFilter);
 +
 +
        if (characterCreation.name.value === battle.attackerSelection.value) {
 +
          battle.resetAttackType = true;
 +
        }
 
         break;
 
         break;
 
       case "weapon":
 
       case "weapon":
Ligne 259 : Ligne 334 :
 
           characters.weaponDisplay,
 
           characters.weaponDisplay,
 
           target,
 
           target,
           characters.weapon.value
+
           characterCreation.weapon.value
 
         );
 
         );
 
         filterUpgrade(
 
         filterUpgrade(
           characters.race.value,
+
           characterCreation.race.value,
           characters.weaponUpgrade,
+
           characterCreation.weaponUpgrade,
 
           target.value,
 
           target.value,
 
           characters.randomAttackValue,
 
           characters.randomAttackValue,
Ligne 270 : Ligne 345 :
 
         break;
 
         break;
 
       case "state":
 
       case "state":
         filterState(target, characters.polymorphMonster);
+
         filterState(target.value, characterCreation.polymorphMonster);
 +
        if (characterCreation.name.value === battle.attackerSelection.value) {
 +
          battle.resetAttackType = true;
 +
        }
 
         break;
 
         break;
 
       case "lowRank":
 
       case "lowRank":
         filterPlayerRank(target, characters.playerRankChoice);
+
         filterCheckbox(target, characterCreation.playerRank.parentElement);
 
         break;
 
         break;
 +
      case "isBlessed":
 +
        filterCheckbox(target, characters.blessingCreation);
 +
        break;
 +
      case "onYohara":
 +
        filterCheckbox(target, characters.yoharaCreation);
 +
        break;
 +
      case "isMarried":
 +
        filterCheckbox(target, characters.marriageCreation);
 +
        break;
 +
    }
 +
 +
    if (targetName.startsWith("attackSkill")) {
 +
      battle.resetAttackType = true;
 
     }
 
     }
 
   });
 
   });
Ligne 359 : Ligne 450 :
 
   if (newCharacter) {
 
   if (newCharacter) {
 
     addBattleChoice(battle, characterDataObject.name);
 
     addBattleChoice(battle, characterDataObject.name);
 +
  }
 +
 +
  if (battle.resetAttackType) {
 +
    filterAttackTypeSelection(characterDataObject, battle.attackTypeSelection);
 +
    battle.resetAttackType = false;
 
   }
 
   }
 
}
 
}
  
function saveButtonGreen(characters, animation) {
+
function saveButtonGreen(saveButton) {
  if (animation) {
+
   saveButton.classList.remove("unsaved-character");
    characters.saveButton.classList.add("save-animation");
 
  } else {
 
    characters.saveButton.classList.remove("save-animation");
 
  }
 
   characters.saveButton.classList.remove("unsaved-button");
 
 
}
 
}
  
function saveButtonOrange(characters) {
+
function saveButtonOrange(saveButton) {
   characters.saveButton.classList.remove("save-animation");
+
   saveButton.classList.add("unsaved-character");
  characters.saveButton.classList.add("unsaved-button");
 
 
}
 
}
  
Ligne 386 : Ligne 476 :
 
         battle
 
         battle
 
       );
 
       );
       saveButtonGreen(characters, true);
+
       saveButtonGreen(characters.saveButton);
 
       characters.unsavedChanges = false;
 
       characters.unsavedChanges = false;
 +
    }
 +
  });
 +
 +
  document.addEventListener("keydown", function (event) {
 +
    if (event.ctrlKey && event.key === "s") {
 +
      event.preventDefault();
 +
      characters.saveButton.click();
 
     }
 
     }
 
   });
 
   });
Ligne 405 : Ligne 502 :
  
 
function uploadCharacter(
 
function uploadCharacter(
 +
  selectedFiles,
 
   characters,
 
   characters,
 
   characterTemplate,
 
   characterTemplate,
Ligne 410 : Ligne 508 :
 
   battle
 
   battle
 
) {
 
) {
   var fileInput = document.createElement("input");
+
   var selectFilesLength = selectedFiles.length;
  
   fileInput.type = "file";
+
   for (var fileIndex = 0; fileIndex < selectFilesLength; fileIndex++) {
  fileInput.accept = ".txt";
+
    var selectedFile = selectedFiles[fileIndex];
  fileInput.multiple = true;
 
  fileInput.click();
 
  
  fileInput.addEventListener("change", function (event) {
+
    if (selectedFile.type === "text/plain") {
    var selectedFiles = event.target.files;
+
      var reader = new FileReader();
    var selectFilesLength = selectedFiles.length;
+
      reader.onload = function (e) {
 +
        var fileContent = e.target.result;
 +
        try {
 +
          var characterDataObject = JSON.parse(fileContent);
 +
          var characterPseudo = characterDataObject.name;
  
    hideElement(characters.characterCreation);
+
          if (characterPseudo) {
 
+
            hideElement(characters.characterCreation);
    for (var fileIndex = 0; fileIndex < selectFilesLength; fileIndex++) {
+
            characterPseudo = validPseudo(characterPseudo);
      var selectedFile = selectedFiles[fileIndex];
+
            [characterDataObject, characterPseudo] = addUniquePseudo(
 
+
              characterDataObject,
      if (selectedFile.type === "text/plain") {
+
              Object.keys(characters.savedCharacters)
        var reader = new FileReader();
+
            );
        reader.onload = function (e) {
+
            var selectedCharacter = handleNewCharacter(
          var fileContent = e.target.result;
+
              characters,
          try {
+
              characterTemplate,
             var characterDataObject = JSON.parse(fileContent);
+
              charactersContainer,
            var characterPseudo = characterDataObject.name;
+
              battle,
 +
              characterPseudo
 +
             )[0];
  
             if (characterPseudo) {
+
             if (selectFilesLength === 1) {
               characterPseudo = validPseudo(characterPseudo);
+
               updateForm(
              [characterDataObject, characterPseudo] = addUniquePseudo(
 
 
                 characterDataObject,
 
                 characterDataObject,
                 Object.keys(characters.savedCharacters)
+
                 characters.characterCreation,
              );
 
              var selectedCharacter = handleNewCharacter(
 
 
                 characters,
 
                 characters,
                 characterTemplate,
+
                 selectedCharacter
                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") {
+
            saveCharacter(
              // delete the character
+
              characters.savedCharacters,
            }
+
              characters.characterCreation,
 +
              battle,
 +
              true,
 +
              characterDataObject
 +
            );
 +
           }
 +
        } catch (error) {
 +
          if (error.name === "TypeError") {
 +
            // delete the character
 
           }
 
           }
         };
+
         }
        reader.readAsText(selectedFile);
+
      };
      }
+
      reader.readAsText(selectedFile);
 
     }
 
     }
   });
+
   }
 
}
 
}
  
function deleteCharacter(characters, pseudo, element, battle) {
+
function handleUploadCharacter(
   battle.battleForm.reset();
+
  characters,
   delete characters.savedCharacters[pseudo];
+
  characterTemplate,
   element.remove();
+
  charactersContainer,
 +
   battle
 +
) {
 +
   var characterInput = characters.characterInput;
 +
   var dropZone = characters.dropZone;
  
   updateSavedCharacters(characters.savedCharacters);
+
   characterInput.accept = ".txt";
   removeBattleChoice(battle, pseudo);
+
  characterInput.multiple = true;
 +
   dropZone.setAttribute("tabindex", "0");
  
   if (
+
   dropZone.addEventListener("click", function () {
    !Object.keys(characters.savedCharacters).length ||
+
     characterInput.click();
    characters.characterCreation.name.value === pseudo
+
   });
  ) {
 
     saveButtonGreen(characters);
 
    characters.unsavedChanges = false;
 
    hideElement(characters.characterCreation);
 
   }
 
}
 
  
function deleteMonster(characters, monsterName, element, battle) {
+
  dropZone.addEventListener("dragover", function (event) {
  battle.battleForm.reset();
+
    event.preventDefault();
  characters.savedMonsters.splice(
+
     dropZone.classList.add("drop-zone--dragover");
     characters.savedMonsters.indexOf(monsterName),
+
   });
    1
 
  );
 
   element.remove();
 
  
   updateSavedMonsters(characters.savedMonsters);
+
   ["dragleave", "dragend"].forEach(function (type) {
  removeBattleChoice(battle, monsterName);
+
    dropZone.addEventListener(type, function () {
}
+
      dropZone.classList.remove("drop-zone--dragover");
 +
    });
 +
  });
  
function handleStyle(characters, selectedElement) {
+
  dropZone.addEventListener("drop", function (event) {
   var currentCharacter = characters.currentCharacter;
+
    event.preventDefault();
 +
    uploadCharacter(
 +
      event.dataTransfer.files,
 +
      characters,
 +
      characterTemplate,
 +
      charactersContainer,
 +
      battle
 +
    );
 +
    dropZone.classList.remove("drop-zone--dragover");
 +
   });
  
   if (currentCharacter) {
+
   characterInput.addEventListener("change", function (event) {
     currentCharacter.classList.remove("selected-character");
+
     uploadCharacter(
  }
+
      event.target.files,
 
+
      characters,
  selectedElement.classList.add("selected-character");
+
      characterTemplate,
   characters.currentCharacter = selectedElement;
+
      charactersContainer,
 +
      battle
 +
    );
 +
   });
 
}
 
}
  
function updateForm(formData, characterCreation, characters, selectedElement) {
+
function deleteCharacter(characters, pseudo, element, battle) {
   saveButtonGreen(characters);
+
   battle.battleForm.reset();
   showElement(characterCreation);
+
   delete characters.savedCharacters[pseudo];
   handleStyle(characters, selectedElement);
+
   element.remove();
  
   characterCreation.reset();
+
   updateSavedCharacters(characters.savedCharacters);
 +
  removeBattleChoice(battle, pseudo);
  
   for (var [name, value] of Object.entries(formData)) {
+
   if (
     var formElement = characterCreation[name];
+
    !Object.keys(characters.savedCharacters).length ||
 +
    characters.characterCreation.name.value === pseudo
 +
  ) {
 +
     saveButtonGreen(characters.saveButton);
 +
    characters.unsavedChanges = false;
 +
    hideElement(characters.characterCreation);
 +
    showElement(characters.characterCreation.previousElementSibling);
 +
  }
 +
}
  
    if (!formElement) {
+
function deleteMonster(characters, monsterVnum, element, battle) {
      continue;
+
  battle.battleForm.reset();
     }
+
  characters.savedMonsters.splice(
 +
    characters.savedMonsters.indexOf(monsterVnum),
 +
     1
 +
  );
  
    if (formElement.type === "checkbox") {
+
  if (element) {
      if (value === "on") {
+
    element.remove();
        formElement.checked = true;
 
      }
 
    } else {
 
      formElement.value = value;
 
    }
 
 
   }
 
   }
  
   var selectedRace = characters.race.value;
+
   updateSavedMonsters(characters.savedMonsters);
 +
  removeBattleChoice(battle, monsterVnum);
 +
}
  
  filterClass(selectedRace, characters.classChoice, true);
+
function handleStyle(characters, selectedElement) {
   filterWeapon(
+
  var currentCharacter = characters.currentCharacter;
     selectedRace,
+
 
    characters.weapon,
+
   if (currentCharacter) {
    characters.weaponCategory,
+
     currentCharacter.classList.remove("selected-character");
    true
+
   }
   );
 
  
   var newWeapon = getSelectedWeapon(characters.weaponCategory);
+
   selectedElement.classList.add("selected-character");
  handleWeaponDisplay(
+
   characters.currentCharacter = selectedElement;
    characters.weaponDisplay,
 
    newWeapon,
 
    characters.weapon.value
 
  );
 
  filterUpgrade(
 
    selectedRace,
 
    characters.weaponUpgrade,
 
    characters.weapon.value,
 
    characters.randomAttackValue,
 
    characters.randomMagicAttackValue,
 
    formData.upgrade
 
  );
 
  filterState(characters.stateChoice, characters.polymorphMonster);
 
   filterPlayerRank(characters.lowRankCheckbox, characters.playerRankChoice);
 
 
}
 
}
  
function handleClickOnCharacter(
+
function updateForm(formData, characterCreation, characters, selectedElement) {
  spanInput,
+
   saveButtonGreen(characters.saveButton);
  target,
+
   hideElement(characterCreation.previousElementSibling);
  characters,
+
   showElement(characterCreation);
   characterElement,
+
   handleStyle(characters, selectedElement);
   battle,
+
 
   edition
+
   characterCreation.reset();
) {
 
   var displayedPseudo = characters.characterCreation.name.value;
 
   var pseudo = spanInput.dataset.name;
 
  
   if (edition) {
+
   for (var [name, value] of Object.entries(formData)) {
    if (!characters.unsavedChanges) {
+
     var formElement = characterCreation[name];
      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) {
+
     if (!formElement) {
        updateForm(
+
       continue;
          characters.savedCharacters[pseudo],
 
          characters.characterCreation,
 
          characters,
 
          characterElement
 
        );
 
        characters.unsavedChanges = false;
 
      }
 
    }
 
  } else {
 
     if (target.tagName === "path") {
 
       target = target.parentElement;
 
 
     }
 
     }
  
     switch (target.dataset.icon) {
+
     if (formElement.type === "checkbox") {
       case "duplicate":
+
       if (value === "on") {
         if (!characters.unsavedChanges) {
+
         formElement.checked = true;
          addNewCharacter(
+
      }
            characters,
+
    } else {
            characters.newCharacterTemplate,
+
      formElement.value = value;
            characters.charactersContainer,
+
    }
            battle,
+
  }
            pseudo
+
  var selectedRace = characterCreation.race.value;
          );
+
  var classChoice = characterCreation.class;
        } else {
+
  var weapon = characterCreation.weapon;
          var result = confirm(
+
 
            "Voulez-vous continuer ? Les dernières modifications ne seront pas sauvegardées."
+
  filterClass(selectedRace, classChoice, true);
          );
+
  filterWeapon(selectedRace, weapon, characters.weaponCategory, true);
  
          if (result) {
+
  var newWeapon = getSelectedWeapon(characters.weaponCategory);
            addNewCharacter(
 
              characters,
 
              characters.newCharacterTemplate,
 
              characters.charactersContainer,
 
              battle,
 
              pseudo
 
            );
 
            saveButtonGreen(characters);
 
            characters.unsavedChanges = false;
 
          }
 
        }
 
        break;
 
  
      case "download":
+
  handleWeaponDisplay(characters.weaponDisplay, newWeapon, weapon.value);
        downloadCharacter(characters.savedCharacters[pseudo]);
+
  filterUpgrade(
        break;
+
    selectedRace,
 
+
    characterCreation.weaponUpgrade,
      case "delete":
+
    weapon.value,
        var result = confirm(
+
    characters.randomAttackValue,
          "Voulez-vous vraiment supprimer définitivement le personnage " +
+
    characters.randomMagicAttackValue,
            pseudo +
+
    formData.weaponUpgrade
            " ?"
+
  );
        );
+
  filterState(
        if (result) {
+
    characterCreation.state.value,
          deleteCharacter(characters, pseudo, characterElement, battle);
+
    characterCreation.polymorphMonster
        }
+
  );
        break;
+
  filterCheckbox(
    }
+
    characterCreation.lowRank,
   }
+
    characterCreation.playerRank.parentElement
 +
  );
 +
  filterCheckbox(characterCreation.onYohara, characters.yoharaCreation);
 +
  filterCheckbox(characterCreation.isBlessed, characters.blessingCreation);
 +
  filterCheckbox(characterCreation.isMarried, characters.marriageCreation);
 +
   filterSkills(classChoice.value, characters.skillElementsToFilter);
 
}
 
}
  
function handleNewCharacter(
+
function handleClickOnCharacter(
 +
  spanInput,
 +
  target,
 
   characters,
 
   characters,
   characterTemplate,
+
   characterElement,
  charactersContainer,
 
 
   battle,
 
   battle,
   pseudo
+
   edition
 
) {
 
) {
   var newCharacterTemplate = characterTemplate.cloneNode(true);
+
   var displayedPseudo = characters.characterCreation.name.value;
   var spanInput = newCharacterTemplate.querySelector("span.input");
+
   var pseudo = spanInput.dataset.name;
  
  newCharacterTemplate.setAttribute("tabindex", "0");
+
   if (edition) {
  charactersContainer.appendChild(newCharacterTemplate);
+
     if (!characters.unsavedChanges) {
 
+
       updateForm(
   if (pseudo) {
+
         characters.savedCharacters[pseudo],
    spanInput.textContent = pseudo;
+
         characters.characterCreation,
    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,
 
         characters,
         newCharacterTemplate,
+
         characterElement
        battle
 
 
       );
 
       );
 +
    } else if (displayedPseudo === pseudo) {
 +
      // pass
 
     } else {
 
     } else {
       handleClickOnCharacter(
+
       var result = confirm(
         spanInput,
+
         "Voulez-vous continuer ? Les dernières modifications ne seront pas sauvegardées."
         null,
+
      );
        characters,
+
 
        newCharacterTemplate,
+
      if (result) {
         battle,
+
         updateForm(
         true
+
          characters.savedCharacters[pseudo],
       );
+
          characters.characterCreation,
 +
          characters,
 +
          characterElement
 +
         );
 +
         characters.unsavedChanges = false;
 +
       }
 
     }
 
     }
   });
+
   } else {
 
+
     if (target.tagName === "path") {
  newCharacterTemplate.addEventListener("keydown", function (event) {
+
       target = target.parentElement;
     if (event.keyCode === 13) {
 
       event.target.click();
 
 
     }
 
     }
  });
 
  
  return [newCharacterTemplate, spanInput];
+
    switch (target.dataset.icon) {
}
+
      case "duplicate":
 
+
        if (!characters.unsavedChanges) {
function validPseudo(pseudo) {
+
          addNewCharacter(
  var newPseudo = pseudo.replace(/[^A-Za-z0-9]+/g, "");
+
            characters,
 +
            characters.newCharacterTemplate,
 +
            characters.charactersContainer,
 +
            battle,
 +
            pseudo
 +
          );
 +
        } else {
 +
          var result = confirm(
 +
            "Voulez-vous continuer ? Les dernières modifications ne seront pas sauvegardées."
 +
          );
  
  if (!newPseudo) {
+
          if (result) {
    return "Pseudo";
+
            addNewCharacter(
  }
+
              characters,
 +
              characters.newCharacterTemplate,
 +
              characters.charactersContainer,
 +
              battle,
 +
              pseudo
 +
            );
 +
            saveButtonGreen(characters.saveButton);
 +
            characters.unsavedChanges = false;
 +
          }
 +
        }
 +
        break;
  
  return newPseudo;
+
      case "download":
}
+
        downloadCharacter(characters.savedCharacters[pseudo]);
 +
        break;
  
function addNewCharacter(
+
      case "delete":
 +
        var result = confirm(
 +
          "Voulez-vous vraiment supprimer définitivement le personnage " +
 +
            pseudo +
 +
            " ?"
 +
        );
 +
        if (result) {
 +
          deleteCharacter(characters, pseudo, characterElement, battle);
 +
        }
 +
        break;
 +
    }
 +
  }
 +
}
 +
 
 +
function handleNewCharacter(
 
   characters,
 
   characters,
 
   characterTemplate,
 
   characterTemplate,
 
   charactersContainer,
 
   charactersContainer,
 
   battle,
 
   battle,
   pseudoToDuplicate
+
   pseudo
 
) {
 
) {
   function editAndSetCharacterPseudoInput(selectedCharacter, spanInput) {
+
   var newCharacterTemplate = characterTemplate.cloneNode(true);
    var maxPseudoLength = 15;
+
  var spanInput = newCharacterTemplate.querySelector("span.input");
  
    var selection = window.getSelection();
+
  newCharacterTemplate.setAttribute("tabindex", "0");
    var range = document.createRange();
+
  charactersContainer.appendChild(newCharacterTemplate);
  
    if (pseudoToDuplicate) {
+
  if (pseudo) {
      spanInput.textContent = pseudoToDuplicate;
+
    spanInput.textContent = pseudo;
     }
+
     spanInput.setAttribute("data-name", pseudo);
 +
  }
  
    spanInput.contentEditable = true;
+
  newCharacterTemplate.addEventListener("click", function (event) {
    spanInput.focus();
+
     var target = event.target;
    range.selectNodeContents(spanInput);
 
     selection.removeAllRanges();
 
    selection.addRange(range);
 
  
     function pseudoValidation() {
+
     if (target.tagName === "path" || target.tagName === "svg") {
      var characterPseudo = validPseudo(spanInput.textContent);
+
       handleClickOnCharacter(
      var characterDataObject = { name: characterPseudo };
+
         spanInput,
 
+
         target,
       if (pseudoToDuplicate) {
+
         characters,
         characterDataObject = copyObject(
+
         newCharacterTemplate,
          characters.savedCharacters[pseudoToDuplicate]
+
         battle
         );
 
         characterDataObject.name = characterPseudo;
 
      }
 
 
 
      [characterDataObject, characterPseudo] = addUniquePseudo(
 
         characterDataObject,
 
         Object.keys(characters.savedCharacters)
 
 
       );
 
       );
 
+
    } else {
       selection.removeAllRanges();
+
       handleClickOnCharacter(
      spanInput.contentEditable = false;
+
        spanInput,
      spanInput.textContent = characterPseudo;
+
         null,
      spanInput.setAttribute("data-name", characterPseudo);
+
         characters,
 
+
         newCharacterTemplate,
      updateForm(
+
         battle,
         characterDataObject,
 
         characters.characterCreation,
 
         characters,
 
        selectedCharacter
 
      );
 
      saveCharacter(
 
        characters.savedCharacters,
 
        characters.characterCreation,
 
         battle,
 
 
         true
 
         true
 
       );
 
       );
 
     }
 
     }
 +
  });
  
    function handleMaxLength(event) {
+
  newCharacterTemplate.addEventListener("keydown", function (event) {
      if (spanInput.textContent.length > maxPseudoLength) {
+
    if (event.keyCode === 13) {
        spanInput.textContent = spanInput.textContent.slice(0, maxPseudoLength);
+
      event.target.click();
        range.setStart(spanInput.childNodes[0], maxPseudoLength);
 
        selection.removeAllRanges();
 
        selection.addRange(range);
 
      }
 
 
     }
 
     }
 +
  });
 +
 +
  return [newCharacterTemplate, spanInput];
 +
}
  
    function handleBlur() {
+
function validPseudo(pseudo) {
      spanInput.removeEventListener("blur", handleBlur);
+
  var newPseudo = pseudo.replace(/[^A-Za-z0-9]+/g, "");
      spanInput.removeEventListener("input", handleMaxLength);
 
      pseudoValidation();
 
    }
 
  
    function handleKeyDown(event) {
+
  if (!newPseudo) {
      if (event.key === "Enter") {
+
    return "Pseudo";
        event.preventDefault();
+
  }
  
        spanInput.removeEventListener("keydown", handleKeyDown);
+
  return newPseudo;
        spanInput.removeEventListener("blur", handleBlur);
+
}
        spanInput.removeEventListener("input", handleMaxLength);
 
  
        pseudoValidation();
+
function addNewCharacter(
      }
+
  characters,
     }
+
  characterTemplate,
 +
  charactersContainer,
 +
  battle,
 +
  pseudoToDuplicate
 +
) {
 +
  function editAndSetCharacterPseudoInput(selectedCharacter, spanInput) {
 +
     var maxPseudoLength = 15;
  
     spanInput.addEventListener("input", handleMaxLength);
+
     var selection = window.getSelection();
     spanInput.addEventListener("keydown", handleKeyDown);
+
     var range = document.createRange();
    spanInput.addEventListener("blur", handleBlur);
 
  }
 
  
  hideElement(characters.characterCreation);
+
    if (pseudoToDuplicate) {
  var [selectedCharacter, spanInput] = handleNewCharacter(
+
      spanInput.textContent = pseudoToDuplicate;
    characters,
+
     }
     characterTemplate,
 
    charactersContainer,
 
    battle
 
  );
 
  
  editAndSetCharacterPseudoInput(selectedCharacter, spanInput);
+
    spanInput.contentEditable = true;
}
+
    spanInput.focus();
 +
    range.selectNodeContents(spanInput);
 +
    selection.removeAllRanges();
 +
    selection.addRange(range);
  
function handleFocus() {
+
    function pseudoValidation() {
  var tooltipLinks = document.querySelectorAll("div.tooltip a");
+
      var characterPseudo = validPseudo(spanInput.textContent);
  tooltipLinks.forEach(function (link) {
+
      var characterDataObject = { name: characterPseudo };
    link.setAttribute("tabindex", -1);
 
  });
 
}
 
  
function characterManagement(characters, battle) {
+
      if (pseudoToDuplicate) {
  var characterTemplate = characters.newCharacterTemplate;
+
        characterDataObject = copyObject(
  var charactersContainer = characters.charactersContainer;
+
          characters.savedCharacters[pseudoToDuplicate]
 +
        );
 +
        characterDataObject.name = characterPseudo;
 +
      }
  
  Object.keys(characters.savedCharacters).forEach(function (pseudo) {
+
      [characterDataObject, characterPseudo] = addUniquePseudo(
    handleNewCharacter(
+
        characterDataObject,
       characters,
+
        Object.keys(characters.savedCharacters)
       characterTemplate,
+
      );
       charactersContainer,
+
 
       battle,
+
       selection.removeAllRanges();
      pseudo
+
       spanInput.contentEditable = false;
    );
+
       spanInput.textContent = characterPseudo;
  });
+
       spanInput.setAttribute("data-name", characterPseudo);
  
  characters.addNewCharacterButton.addEventListener("click", function (event) {
+
      updateForm(
    if (!characters.unsavedChanges) {
+
        characterDataObject,
      addNewCharacter(
+
        characters.characterCreation,
 
         characters,
 
         characters,
         characterTemplate,
+
         selectedCharacter
        charactersContainer,
 
        battle
 
 
       );
 
       );
    } else {
+
       saveCharacter(
       var result = confirm(
+
         characters.savedCharacters,
         "Voulez-vous continuer ? Les dernières modifications ne seront pas sauvegardées."
+
        characters.characterCreation,
 +
        battle,
 +
        true
 
       );
 
       );
 +
    }
  
       if (result) {
+
    function handleMaxLength(event) {
         addNewCharacter(
+
       if (spanInput.textContent.length > maxPseudoLength) {
          characters,
+
         spanInput.textContent = spanInput.textContent.slice(0, maxPseudoLength);
          characterTemplate,
+
        range.setStart(spanInput.childNodes[0], maxPseudoLength);
          charactersContainer,
+
         selection.removeAllRanges();
          battle
+
         selection.addRange(range);
        );
 
         saveButtonGreen(characters);
 
         characters.unsavedChanges = false;
 
 
       }
 
       }
 
     }
 
     }
  });
 
  
  characters.uploadCharacter.addEventListener("click", function (event) {
+
    function handleBlur() {
    uploadCharacter(characters, characterTemplate, charactersContainer, battle);
+
      spanInput.removeEventListener("blur", handleBlur);
  });
+
      spanInput.removeEventListener("input", handleMaxLength);
 +
      pseudoValidation();
 +
    }
  
  characters.characterCreation.addEventListener("change", function () {
+
    function handleKeyDown(event) {
    saveButtonOrange(characters);
+
      if (event.key === "Enter") {
    characters.unsavedChanges = true;
+
        event.preventDefault();
  });
 
  
  filterForm(characters);
+
        spanInput.removeEventListener("keydown", handleKeyDown);
  characterCreationListener(characters, battle);
+
        spanInput.removeEventListener("blur", handleBlur);
  handleFocus();
+
        spanInput.removeEventListener("input", handleMaxLength);
  
  window.addEventListener("beforeunload", function (event) {
+
        pseudoValidation();
    if (characters.unsavedChanges) {
+
       }
      event.preventDefault();
 
       event.returnValue = "";
 
      return "";
 
 
     }
 
     }
 +
 +
    spanInput.addEventListener("input", handleMaxLength);
 +
    spanInput.addEventListener("keydown", handleKeyDown);
 +
    spanInput.addEventListener("blur", handleBlur);
 +
  }
 +
 +
  hideElement(characters.characterCreation);
 +
  var [selectedCharacter, spanInput] = handleNewCharacter(
 +
    characters,
 +
    characterTemplate,
 +
    charactersContainer,
 +
    battle
 +
  );
 +
 +
  editAndSetCharacterPseudoInput(selectedCharacter, spanInput);
 +
}
 +
 +
function handleFocus() {
 +
  var tooltipLinks = document.querySelectorAll("div.tooltip a");
 +
  tooltipLinks.forEach(function (link) {
 +
    link.setAttribute("tabindex", -1);
 
   });
 
   });
 
}
 
}
  
function handleNewMonster(
+
function characterManagement(characters, battle) {
  characters,
+
   var characterTemplate = characters.newCharacterTemplate;
  monsterTemplate,
+
   var charactersContainer = characters.charactersContainer;
  monstersContainer,
 
  battle,
 
  monsterName,
 
  monsterList
 
) {
 
   var newMonsterTemplate = monsterTemplate.cloneNode(true);
 
   var spanInput = newMonsterTemplate.querySelector("span.input");
 
  var deleteSvg = newMonsterTemplate.querySelector("svg");
 
  
   spanInput.textContent = monsterName;
+
   Object.keys(characters.savedCharacters).forEach(function (pseudo) {
  monstersContainer.appendChild(newMonsterTemplate);
+
     handleNewCharacter(
 
+
      characters,
  newMonsterTemplate.setAttribute("tabindex", "0");
+
      characterTemplate,
  newMonsterTemplate.setAttribute("data-name", monsterName);
+
      charactersContainer,
  monstersContainer.appendChild(newMonsterTemplate);
+
      battle,
 
+
       pseudo
  deleteSvg.addEventListener("click", function (event) {
 
     deleteMonster(characters, monsterName, newMonsterTemplate, battle);
 
    var inputMonster = monsterList.querySelector(
 
       "input[name='" + monsterName + "']"
 
 
     );
 
     );
    inputMonster.checked = false;
 
 
   });
 
   });
}
 
  
function monsterManagement(characters, battle) {
+
  characters.addNewCharacterButton.addEventListener("click", function (event) {
  function handleDropdown(searchMonster, monsterList) {
+
    if (!characters.unsavedChanges) {
    searchMonster.addEventListener("focus", function (event) {
+
       addNewCharacter(
       showElement(monsterList);
+
        characters,
     });
+
        characterTemplate,
 +
        charactersContainer,
 +
        battle
 +
      );
 +
     } else {
 +
      var result = confirm(
 +
        "Voulez-vous continuer ? Les dernières modifications ne seront pas sauvegardées."
 +
      );
  
    document.addEventListener("mousedown", function (event) {
+
      if (result) {
      var target = event.target;
+
        addNewCharacter(
      if (!monsterList.contains(target) && !searchMonster.contains(target)) {
+
          characters,
         hideElement(monsterList);
+
          characterTemplate,
 +
          charactersContainer,
 +
          battle
 +
        );
 +
        saveButtonGreen(characters.saveButton);
 +
         characters.unsavedChanges = false;
 
       }
 
       }
     });
+
     }
   }
+
   });
  
   function addMonsterNames(monsterList) {
+
   handleUploadCharacter(
     var monsterIndex = 0;
+
     characters,
 
+
    characterTemplate,
     for (var monsterName in monsterData) {
+
     charactersContainer,
      var li = document.createElement("li");
+
    battle
      var label = document.createElement("label");
+
  );
      var input = document.createElement("input");
 
      var textNode = document.createTextNode(monsterName);
 
  
      label.htmlFor = "monster" + monsterIndex;
+
  characters.characterCreation.addEventListener("change", function () {
      input.id = "monster" + monsterIndex;
+
    saveButtonOrange(characters.saveButton);
      input.type = "checkbox";
+
    characters.unsavedChanges = true;
 +
  });
  
      input.name = monsterName;
+
  filterForm(characters, battle);
 +
  characterCreationListener(characters, battle);
 +
  handleFocus();
  
      label.appendChild(input);
+
  window.addEventListener("beforeunload", function (event) {
      label.appendChild(textNode);
+
    if (characters.unsavedChanges) {
       li.appendChild(label);
+
       event.preventDefault();
       monsterList.appendChild(li);
+
       event.returnValue = "";
 
+
       return "";
       monsterIndex++;
 
 
     }
 
     }
   }
+
   });
 +
}
  
  function filterNames(searchMonster, monsterList) {
+
function handleNewMonster(
    var debounceTimer;
+
  characters,
 
+
  monsterTemplate,
    searchMonster.addEventListener("input", function (event) {
+
  monstersContainer,
      clearTimeout(debounceTimer);
+
  battle,
      debounceTimer = setTimeout(function () {
+
  monsterVnum,
        var value = toNormalForm(event.target.value);
+
  monsterList
        for (var element of monsterList.children) {
+
) {
          if (!isValueInArray(value, toNormalForm(element.textContent))) {
+
  var newMonsterTemplate = monsterTemplate.cloneNode(true);
            hideElement(element);
+
  var spanInput = newMonsterTemplate.querySelector("span.input");
          } else {
+
  var deleteSvg = newMonsterTemplate.querySelector("svg");
            showElement(element);
+
  var monsterName = getMonsterName(monsterVnum);
          }
 
        }
 
      }, 500);
 
    });
 
  }
 
  
   var monsterTemplate = characters.newMonsterTemplate;
+
   var link = document.createElement("a");
   var monstersContainer = characters.monstersContainer;
+
   link.href = mw.util.getUrl(monsterName);
   var monsterList = characters.monsterList;
+
   link.title = monsterName;
   var searchMonster = characters.searchMonster;
+
   link.textContent = monsterName;
  var monsterListForm = characters.monsterListForm;
 
  
   handleDropdown(searchMonster, monsterList);
+
   spanInput.appendChild(link);
   addMonsterNames(monsterList, characters.monsterListTemplate);
+
   monstersContainer.appendChild(newMonsterTemplate);
  filterNames(searchMonster, monsterList);
 
  
   characters.savedMonsters.forEach(function (monsterName) {
+
   newMonsterTemplate.setAttribute("tabindex", "0");
     handleNewMonster(
+
  newMonsterTemplate.setAttribute("data-name", monsterVnum);
      characters,
+
  monstersContainer.appendChild(newMonsterTemplate);
      monsterTemplate,
+
 
      monstersContainer,
+
  deleteSvg.addEventListener("click", function (event) {
      battle,
+
     deleteMonster(characters, monsterVnum, newMonsterTemplate, battle);
      monsterName,
 
      monsterList
 
    );
 
 
     var inputMonster = monsterList.querySelector(
 
     var inputMonster = monsterList.querySelector(
       "input[name='" + monsterName + "']"
+
       "input[name='" + monsterVnum + "']"
 
     );
 
     );
 
+
     inputMonster.checked = false;
     if (inputMonster) {
 
      inputMonster.checked = true;
 
    } else {
 
      deleteMonster(characters, monsterName, newMonsterTemplate, battle);
 
    }
 
 
   });
 
   });
 +
}
  
   monsterListForm.addEventListener("submit", function (event) {
+
function monsterManagement(characters, battle) {
    event.preventDefault();
+
   function handleDropdown(searchMonster, monsterList) {
  });
+
    searchMonster.addEventListener("focus", function (event) {
 +
      showElement(monsterList);
 +
    });
  
  monsterListForm.addEventListener("change", function (event) {
+
    document.addEventListener("mousedown", function (event) {
    var target = event.target;
+
      var target = event.target;
    var monsterName = target.name;
+
      if (!monsterList.contains(target) && !searchMonster.contains(target)) {
 +
        hideElement(monsterList);
 +
      }
 +
    });
 +
  }
  
    if (monsterName === "search-monster") {
+
  function addMonsterNames(monsterList) {
      return;
+
    var lastMonsterAttributeIndex = monsterData[101].length - 1;
    }
 
  
     if (target.checked) {
+
     for (var monsterVnum in monsterData) {
       handleNewMonster(
+
       var li = document.createElement("li");
        characters,
+
      var label = document.createElement("label");
        monsterTemplate,
+
      var input = document.createElement("input");
        monstersContainer,
+
      var textNode = document.createTextNode(
        battle,
+
         monsterData[monsterVnum][lastMonsterAttributeIndex]
        monsterName,
 
         monsterList
 
 
       );
 
       );
  
       characters.savedMonsters.push(monsterName);
+
       label.htmlFor = "monster" + monsterVnum;
       updateSavedMonsters(characters.savedMonsters);
+
      input.id = "monster" + monsterVnum;
       addBattleChoice(battle, monsterName, true);
+
      input.type = "checkbox";
    } else {
+
 
       var currentMonsterTemplate = monstersContainer.querySelector(
+
       input.name = monsterVnum;
        "[data-name='" + monsterName + "']"
+
 
       );
+
       label.appendChild(input);
       deleteMonster(characters, monsterName, currentMonsterTemplate, battle);
+
       label.appendChild(textNode);
 +
       li.appendChild(label);
 +
       monsterList.appendChild(li);
 
     }
 
     }
   });
+
   }
}
 
  
function removeBattleChoice(battle, name) {
+
  function filterNames(searchMonster, monsterList) {
  var battleSelects = [battle.attackerSelection, battle.victimSelection];
+
    var debounceTimer;
  
  battleSelects.forEach(function (battleSelect) {
+
    searchMonster.addEventListener("input", function (event) {
    for (
+
      clearTimeout(debounceTimer);
       var optionIndex = 0;
+
       debounceTimer = setTimeout(function () {
      optionIndex < battleSelect.options.length;
+
        var value = toNormalForm(event.target.value);
      optionIndex++
+
        for (var element of monsterList.children) {
    ) {
+
          if (!isValueInArray(value, toNormalForm(element.textContent))) {
      if (battleSelect.options[optionIndex].value === name) {
+
            hideElement(element);
        battleSelect.remove(optionIndex);
+
          } else {
         break;
+
            showElement(element);
       }
+
          }
     }
+
         }
  });
+
       }, 500);
}
+
     });
 +
  }
  
function addBattleChoice(battle, name, isMonster = false) {
+
  var monsterTemplate = characters.newMonsterTemplate;
   function createOption(text) {
+
   var monstersContainer = characters.monstersContainer;
    var option = document.createElement("option");
+
  var monsterList = characters.monsterList;
    option.textContent = text;
+
  var searchMonster = characters.searchMonster;
    option.value = text;
+
  var monsterListForm = characters.monsterListForm;
  
     if (!isMonster) {
+
  document
      option.classList.add("notranslate");
+
     .getElementById("monster-link")
    }
+
    .querySelector("a")
 +
    .setAttribute("target", "_blank");
  
    return option;
+
  handleDropdown(searchMonster, monsterList);
   }
+
   addMonsterNames(monsterList, characters.monsterListTemplate);
 +
  filterNames(searchMonster, monsterList);
  
   if (isMonster && monsterData[name][1]) {
+
   characters.savedMonsters.slice().forEach(function (monsterVnum) {
     // pass
+
     var inputMonster = monsterList.querySelector(
  } else {
+
      "input[name='" + monsterVnum + "']"
     battle.attackerSelection.appendChild(createOption(name));
+
     );
  }
 
  
  battle.victimSelection.appendChild(createOption(name));
+
    if (inputMonster) {
}
+
      handleNewMonster(
 +
        characters,
 +
        monsterTemplate,
 +
        monstersContainer,
 +
        battle,
 +
        monsterVnum,
 +
        monsterList
 +
      );
 +
      inputMonster.checked = true;
 +
    } else {
 +
      deleteMonster(characters, monsterVnum, null, battle);
 +
    }
 +
  });
  
function updateBattleChoice(characters, battle) {
+
  monsterListForm.addEventListener("submit", function (event) {
  var keys = Object.keys(characters.savedCharacters);
+
    event.preventDefault();
 +
  });
  
   for (var index = 0; index < keys.length; index++) {
+
   monsterListForm.addEventListener("change", function (event) {
     var pseudo = keys[index];
+
     var target = event.target;
     addBattleChoice(battle, pseudo);
+
     var monsterVnum = target.name;
  }
 
  
  characters.savedMonsters.forEach(function (monsterName) {
+
    if (monsterVnum === "search-monster") {
    addBattleChoice(battle, monsterName, true);
+
      return;
  });
+
    }
}
 
  
function isPC(character) {
+
    if (target.checked) {
  if (character.race === 0 || character.race === 1) {
+
      handleNewMonster(
    return false;
+
        characters,
  }
+
        monsterTemplate,
  return true;
+
        monstersContainer,
}
+
        battle,
 +
        monsterVnum,
 +
        monsterList
 +
      );
  
function isBoss(character) {
+
      characters.savedMonsters.push(monsterVnum);
  return character.race === 0 && character.rank >= 5;
+
      updateSavedMonsters(characters.savedMonsters);
}
+
      addBattleChoice(battle, monsterVnum, true);
 +
    } else {
 +
      var currentMonsterTemplate = monstersContainer.querySelector(
 +
        "[data-name='" + monsterVnum + "']"
 +
      );
 +
      deleteMonster(characters, monsterVnum, currentMonsterTemplate, battle);
 +
    }
 +
  });
  
function isStone(character) {
+
  addEventListener("storage", function (event) {
  return character.race === 1;
+
    if (event.key === "newMonsterCalculator") {
}
+
      var monsterVnum = Number(event.newValue);
  
function isMagicClass(character) {
+
      if (!monsterVnum) {
  return character.race === "shaman" || character.class === "black_magic";
+
        return;
}
+
      }
  
function isPolymorph(character) {
+
      var inputMonster = monsterList.querySelector(
  return character.state === "polymorph";
+
        "input[name='" + Math.abs(monsterVnum) + "']"
}
+
      );
  
function isRiding(character) {
+
      if (inputMonster) {
   return character.state === "horse";
+
        if (
 +
          (monsterVnum > 0 && !inputMonster.checked) ||
 +
          (monsterVnum < 0 && inputMonster.checked)
 +
        ) {
 +
          inputMonster.click();
 +
        }
 +
      }
 +
    }
 +
   });
 
}
 
}
  
function calcAttackFactor(attacker, victim) {
+
function removeBattleChoice(battle, name) {
   function calcCoeffK(dex, level) {
+
   var battleSelects = [battle.attackerSelection, battle.victimSelection];
    return Math.min(90, Math.floor((2 * dex + level) / 3));
 
  }
 
  
   var K1 = calcCoeffK(attacker.polymorphDex, attacker.level);
+
   battleSelects.forEach(function (battleSelect) {
   var K2 = calcCoeffK(victim.polymorphDex, attacker.level);
+
    for (
 +
      var optionIndex = 0;
 +
      optionIndex < battleSelect.options.length;
 +
      optionIndex++
 +
    ) {
 +
      if (battleSelect.options[optionIndex].value === name) {
 +
        battleSelect.remove(optionIndex);
 +
        break;
 +
      }
 +
    }
 +
   });
 +
}
  
   var AR = (K1 + 210) / 300;
+
function addBattleChoice(battle, name, isMonster = false) {
  var ER = (((2 * K2 + 5) / (K2 + 95)) * 3) / 10;
+
   function createOption(text, vnum) {
 +
    var option = document.createElement("option");
 +
    option.textContent = text;
 +
    option.value = vnum;
  
  return AR - ER;
+
    if (!isMonster) {
}
+
      option.classList.add("notranslate");
 +
    }
  
function calcMainAttackValue(attacker, attackerWeapon) {
+
    return option;
  var leadership = 0;
+
   }
   var rawWeaponAttackValue = 0;
 
  
   if (isPC(attacker)) {
+
   var vnum = name;
    var rawWeaponAttackValue = attackerWeapon[3][attacker.upgrade];
 
  
    if (!rawWeaponAttackValue) {
+
  if (isMonster) {
      rawWeaponAttackValue = 0;
+
    name = getMonsterName(name);
    }
+
  }
  
     leadership = attacker.leadership;
+
  if (isMonster && monsterData[vnum][1]) {
 +
     // pass
 +
  } else {
 +
    battle.attackerSelection.appendChild(createOption(name, vnum));
 
   }
 
   }
  
   return 2 * (attacker.level + rawWeaponAttackValue) + leadership;
+
   battle.victimSelection.appendChild(createOption(name, vnum));
 
}
 
}
  
function calcStatAttackValue(character) {
+
function updateBattleChoice(characters, battle) {
   switch (character.race) {
+
   var keys = Object.keys(characters.savedCharacters);
    case "warrior":
+
 
    case "sura":
+
  for (var index = 0; index < keys.length; index++) {
      return 2 * character.str;
+
     var pseudo = keys[index];
    case "ninja":
+
     addBattleChoice(battle, pseudo);
      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;
 
 
   }
 
   }
 +
 +
  characters.savedMonsters.forEach(function (monsterVnum) {
 +
    addBattleChoice(battle, monsterVnum, true);
 +
  });
 
}
 
}
  
function calcSecondaryAttackValue(attacker, attackerWeapon) {
+
function isPC(character) {
   var attackValueOther = 0;
+
   if (character.race === 0 || character.race === 1) {
 +
    return false;
 +
  }
 +
  return true;
 +
}
  
   var minAttackValue = 0;
+
function isBoss(character) {
  var maxAttackValue = 0;
+
   return character.race === 0 && character.rank >= 5;
 +
}
  
   var minAttackValueSlash = 0;
+
function isStone(character) {
  var maxAttackValueSlash = 0;
+
   return character.race === 1;
 +
}
  
  if (isPC(attacker)) {
+
function isMagicClass(character) {
    if (isValueInArray("serpent", attackerWeapon[0].toLowerCase())) {
+
  return character.race === "shaman" || character.class === "black_magic";
      var rawAttackValue = attackerWeapon[3][attacker.upgrade];
+
}
  
      minAttackValue = attacker.minAttackValueRandom - rawAttackValue;
+
function isPolymorph(character) {
      maxAttackValue = attacker.maxAttackValueRandom - rawAttackValue;
+
  return character.state === "polymorph";
 +
}
  
      minAttackValue = Math.max(0, minAttackValue);
+
function isRiding(character) {
      maxAttackValue = Math.max(minAttackValue, maxAttackValue);
+
  return character.state === "horse";
    } else {
+
}
      minAttackValue = attackerWeapon[2][2];
 
      maxAttackValue = attackerWeapon[2][3];
 
    }
 
  
    minAttackValueSlash = Math.min(
+
function isBow(weapon) {
      attacker.minAttackValueSlash,
+
  return weapon[1] === 2;
      attacker.maxAttackValueSlash
+
}
    );
 
    maxAttackValueSlash = Math.max(
 
      attacker.minAttackValueSlash,
 
      attacker.maxAttackValueSlash
 
    );
 
  
    attackValueOther = attacker.attackValue;
+
function calcAttackFactor(attacker, victim) {
   } else {
+
   function calcCoeffK(dex, level) {
     minAttackValue = attacker.minAttackValue;
+
     return Math.min(90, Math.floor((2 * dex + level) / 3));
    maxAttackValue = attacker.maxAttackValue;
 
 
   }
 
   }
  
   minAttackValue += attacker.minAttackValuePolymorph;
+
   var K1 = calcCoeffK(attacker.polymorphDex, attacker.level);
   maxAttackValue += attacker.maxAttackValuePolymorph;
+
   var K2 = calcCoeffK(victim.polymorphDex, attacker.level);
  
   attackValueOther += attacker.statAttackValue;
+
   var AR = (K1 + 210) / 300;
   attackValueOther += attacker.horseAttackValue;
+
   var ER = (((2 * K2 + 5) / (K2 + 95)) * 3) / 10;
  
   var weaponInterval = maxAttackValue - minAttackValue;
+
   return AR - ER;
  var slashInterval = maxAttackValueSlash - minAttackValueSlash;
+
}
  
   var totalCardinal = (weaponInterval + 1) * (slashInterval + 1) * 10000;
+
function calcMainAttackValue(attacker, attackerWeapon) {
   var minInterval = Math.min(weaponInterval, slashInterval) + 1;
+
   var leadership = 0;
 +
   var rawWeaponAttackValue = 0;
  
   minAttackValue += minAttackValueSlash;
+
   if (isPC(attacker)) {
  maxAttackValue += maxAttackValueSlash;
+
    var rawWeaponAttackValue = attackerWeapon[3][attacker.weaponUpgrade];
  
  return [
+
     if (!rawWeaponAttackValue) {
     minAttackValue,
+
      rawWeaponAttackValue = 0;
    maxAttackValue,
+
     }
    attackValueOther,
 
    minInterval,
 
     totalCardinal,
 
  ];
 
}
 
  
function getPolymorphPower(polymorphPoint, polymorphPowerTable) {
+
    leadership = attacker.leadership;
   return polymorphPowerTable[polymorphPoint];
+
   }
}
 
  
function getSkillPower(skillPoint, skillPowerTable) {
+
  return 2 * (attacker.level + rawWeaponAttackValue) + leadership;
  return skillPowerTable[skillPoint];
 
 
}
 
}
  
function getMarriageBonusValue(character, marriageTable, itemName) {
+
function calcStatAttackValue(character) {
   var index;
+
   switch (character.race) {
  var lovePoint = character.lovePoint;
+
    case "warrior":
 
+
    case "sura":
  if (lovePoint < 65) {
+
      return 2 * character.str;
     index = 0;
+
    case "ninja":
  } else if (lovePoint < 80) {
+
      return Math.floor((1 / 4) * (character.str + 7 * character.dex));
    index = 1;
+
     case "shaman":
  } else if (lovePoint < 100) {
+
      return Math.floor((1 / 3) * (5 * character.int + character.dex));
     index = 2;
+
     case "lycan":
  } else {
+
      return character.vit + 2 * character.dex;
    index = 3;
+
    default:
 +
      return 2 * character.str;
 
   }
 
   }
 +
}
  
   return marriageTable[itemName][index];
+
function calcSecondaryAttackValue(attacker, attackerWeapon) {
}
+
   var attackValueOther = 0;
  
function calcDamageWithPrimaryBonuses(damages, battleValues) {
+
   var minAttackValue = 0;
  damages = floorMultiplication(damages, battleValues.attackValueCoeff);
+
   var maxAttackValue = 0;
  damages += battleValues.attackValueMarriage;
 
  damages = floorMultiplication(
 
    damages,
 
    battleValues.monsterResistanceMarriageCoeff
 
  );
 
  damages = floorMultiplication(damages, battleValues.monsterResistanceCoeff);
 
  damages = floorMultiplication(damages, battleValues.typeBonusCoeff);
 
  damages +=
 
    floorMultiplication(damages, battleValues.raceBonusCoeff) -
 
    floorMultiplication(damages, battleValues.raceResistanceCoeff);
 
   damages = floorMultiplication(damages, battleValues.stoneBonusCoeff);
 
   damages = floorMultiplication(damages, battleValues.monsterBonusCoeff);
 
  
   var elementDamages = 0;
+
   var minAttackValueSlash = 0;
   for (var elementBonusCoeff of battleValues.elementBonusCoeff) {
+
   var maxAttackValueSlash = 0;
    elementDamages += floorMultiplication(damages, elementBonusCoeff);
 
  }
 
  damages += elementDamages;
 
  
   damages = floorMultiplication(damages, battleValues.damageMultiplier);
+
   if (isPC(attacker)) {
 +
    if (isValueInArray("serpent", attackerWeapon[0].toLowerCase())) {
 +
      var rawAttackValue = attackerWeapon[3][attacker.weaponUpgrade];
  
  return damages;
+
      minAttackValue = attacker.minAttackValueRandom - rawAttackValue;
}
+
      maxAttackValue = attacker.maxAttackValueRandom - rawAttackValue;
  
function calcDamageWithSecondaryBonuses(
+
      minAttackValue = Math.max(0, minAttackValue);
  damages,
+
      maxAttackValue = Math.max(minAttackValue, maxAttackValue);
  battleValues,
+
    } else {
  damagesType,
+
      minAttackValue = attackerWeapon[2][2];
  minPiercingDamages
+
      maxAttackValue = attackerWeapon[2][3];
) {
+
    }
  damages = floorMultiplication(damages, battleValues.weaponDefenseCoeff);
+
 
 +
    minAttackValueSlash = Math.min(
 +
      attacker.minAttackValueSlash,
 +
      attacker.maxAttackValueSlash
 +
    );
 +
    maxAttackValueSlash = Math.max(
 +
      attacker.minAttackValueSlash,
 +
      attacker.maxAttackValueSlash
 +
    );
  
  if (damagesType.criticalHit) {
+
     attackValueOther += attacker.attackValue;
     damages *= 2;
 
  }
 
  
  if (damagesType.piercingHit) {
+
    if (isBow(attackerWeapon) && !isPolymorph(attacker)) {
     damages += battleValues.defense + Math.min(0, minPiercingDamages);
+
      attackValueOther += 25;
     damages = floorMultiplication(damages, battleValues.extraPiercingHitCoeff);
+
     }
 +
  } else {
 +
    minAttackValue = attacker.minAttackValue;
 +
     maxAttackValue = attacker.maxAttackValue;
 
   }
 
   }
  
   damages = floorMultiplication(damages, battleValues.averageDamageCoeff);
+
   minAttackValue += attacker.minAttackValuePolymorph;
   damages = floorMultiplication(
+
   maxAttackValue += attacker.maxAttackValuePolymorph;
    damages,
 
    battleValues.averageDamageResistanceCoeff
 
  );
 
  damages = floorMultiplication(
 
    damages,
 
    battleValues.skillDamageResistanceCoeff
 
  );
 
  
   damages = floorMultiplication(damages, battleValues.rankBonusCoeff);
+
   attackValueOther += attacker.statAttackValue;
   damages = Math.max(0, damages + Math.floor(battleValues.defensePercent));
+
   attackValueOther += attacker.horseAttackValue;
   damages += Math.min(
+
 
    300,
+
  var weaponInterval = maxAttackValue - minAttackValue + 1;
    floorMultiplication(damages, battleValues.damageBonusCoeff)
+
   var slashInterval = maxAttackValueSlash - minAttackValueSlash + 1;
   );
+
 
   damages = floorMultiplication(damages, battleValues.empireMalusCoeff);
+
  var totalCardinal = weaponInterval * slashInterval * 10000;
 +
  var minInterval = Math.min(weaponInterval, slashInterval);
 +
 
 +
   minAttackValue += minAttackValueSlash;
 +
   maxAttackValue += maxAttackValueSlash;
  
   return damages;
+
   return [
 +
    minAttackValue,
 +
    maxAttackValue,
 +
    attackValueOther,
 +
    minInterval,
 +
    totalCardinal,
 +
  ];
 
}
 
}
  
function calcSkillDamageWithSecondaryBonuses(
+
function calcMagicAttackValue(attacker, attackerWeapon) {
  damages,
+
   var minMagicAttackValue = 0;
  battleValues,
+
   var maxMagicAttackValue = 0;
  damagesType,
 
  minPiercingDamages,
 
  skillFormula
 
) {
 
   damages = skillFormula(damages);
 
   damages = floorMultiplication(damages, battleValues.weaponDefenseCoeff);
 
  
   damages -= battleValues.defense;
+
   var minMagicAttackValueSlash = 0;
 +
  var maxMagicAttackValueSlash = 0;
  
   if (damagesType.criticalHit) {
+
   if (isValueInArray("serpent", attackerWeapon[0].toLowerCase())) {
     damages *= 2;
+
     minMagicAttackValue = attacker.minMagicAttackValueRandom;
  }
+
    maxMagicAttackValue = attacker.maxMagicAttackValueRandom;
  
   if (damagesType.piercingHit) {
+
    maxMagicAttackValue = Math.max(minMagicAttackValue, maxMagicAttackValue);
     damages += battleValues.defense + Math.min(0, minPiercingDamages);
+
   } else {
 +
    var rawWeaponAttackValue = attackerWeapon[3][attacker.weaponUpgrade];
 +
 
 +
    if (!rawWeaponAttackValue) {
 +
      rawWeaponAttackValue = 0;
 +
     }
 +
 
 +
    minMagicAttackValue = attackerWeapon[2][0] + rawWeaponAttackValue;
 +
    maxMagicAttackValue = attackerWeapon[2][1] + rawWeaponAttackValue;
 
   }
 
   }
  
   damages = floorMultiplication(damages, battleValues.skillDamageCoeff);
+
   minMagicAttackValueSlash = Math.min(
  damages = floorMultiplication(
+
     attacker.minMagicAttackValueSlash,
     damages,
+
     attacker.maxMagicAttackValueSlash
     battleValues.skillDamageResistanceCoeff
 
 
   );
 
   );
 
+
   maxMagicAttackValueSlash = Math.max(
   damages = floorMultiplication(damages, battleValues.rankBonusCoeff);
+
    attacker.minMagicAttackValueSlash,
  damages = Math.max(0, damages + Math.floor(battleValues.defensePercent));
+
     attacker.maxMagicAttackValueSlash
  damages += Math.min(
 
    300,
 
     floorMultiplication(damages, battleValues.damageBonusCoeff)
 
 
   );
 
   );
  damages = floorMultiplication(damages, battleValues.empireMalusCoeff);
 
  
   return damages;
+
   var weaponInterval = maxMagicAttackValue - minMagicAttackValue + 1;
}
+
  var slashInterval = maxMagicAttackValueSlash - minMagicAttackValueSlash + 1;
  
function computePolymorphPoint(attacker, victim, polymorphPowerTable) {
+
  var totalCardinal = weaponInterval * slashInterval * 10000;
  attacker.statAttackValue = 0;
+
  var minInterval = Math.min(weaponInterval, slashInterval);
  
   attacker.polymorphDex = attacker.dex;
+
   minMagicAttackValue += minMagicAttackValueSlash;
   victim.polymorphDex = victim.dex;
+
   maxMagicAttackValue += maxMagicAttackValueSlash;
  
   attacker.minAttackValuePolymorph = 0;
+
   return [minMagicAttackValue, maxMagicAttackValue, minInterval, totalCardinal];
  attacker.maxAttackValuePolymorph = 0;
+
}
  
  if (isPC(attacker) && isPolymorph(attacker) && polymorphPowerTable) {
+
function getPolymorphPower(polymorphPoint, polymorphPowerTable) {
    var polymorphPowerPct =
+
  return polymorphPowerTable[polymorphPoint];
      getPolymorphPower(attacker.polymorphPoint, polymorphPowerTable) / 100;
+
}
    var polymorphMonster = createMonster(attacker.polymorphMonster);
 
  
    var polymorphStr = floorMultiplication(
+
function getSkillPower(skillPoint, skillPowerTable) {
      polymorphPowerPct,
+
  return skillPowerTable[skillPoint];
      polymorphMonster.str
+
}
    );
 
  
    attacker.polymorphDex += floorMultiplication(
+
function getMarriageBonusValue(character, marriageTable, itemName) {
      polymorphPowerPct,
+
  var index;
      polymorphMonster.dex
+
  var lovePoint = character.lovePoint;
    );
 
  
     attacker.minAttackValuePolymorph = floorMultiplication(
+
  if (lovePoint < 65) {
      polymorphPowerPct,
+
     index = 0;
      polymorphMonster.minAttackValue
+
  } else if (lovePoint < 80) {
    );
+
    index = 1;
     attacker.maxAttackValuePolymorph = floorMultiplication(
+
  } else if (lovePoint < 100) {
      polymorphPowerPct,
+
     index = 2;
      polymorphMonster.maxAttackValue
+
  } else {
     );
+
     index = 3;
 +
  }
  
    if (attacker.weapon === "Fist") {
+
  return marriageTable[itemName][index];
      attacker.maxAttackValuePolymorph += 1;
+
}
    }
 
  
     attacker.attackValue = 0;
+
function calcDamageWithPrimaryBonuses(damages, battleValues) {
 +
  damages = floorMultiplication(
 +
    damages * battleValues.attackValueCoeff + battleValues.adjustCoeff,
 +
     1
 +
  );
 +
  damages += battleValues.attackValueMarriage;
 +
  damages = floorMultiplication(
 +
    damages,
 +
    battleValues.monsterResistanceMarriageCoeff
 +
  );
 +
  damages = floorMultiplication(damages, battleValues.monsterResistanceCoeff);
 +
  damages = floorMultiplication(damages, battleValues.typeBonusCoeff);
 +
  damages +=
 +
    floorMultiplication(damages, battleValues.raceBonusCoeff) -
 +
    floorMultiplication(damages, battleValues.raceResistanceCoeff);
 +
  damages = floorMultiplication(damages, battleValues.stoneBonusCoeff);
 +
  damages = floorMultiplication(damages, battleValues.monsterBonusCoeff);
  
    if (isMagicClass(attacker)) {
+
  var elementDamages = 0;
      attacker.statAttackValue = 2 * (polymorphStr + attacker.int);
+
  for (var elementBonusCoeff of battleValues.elementBonusCoeff) {
     } else {
+
     elementDamages += floorMultiplicationWithNegative(
      attacker.statAttackValue = 2 * (polymorphStr + attacker.str);
+
      damages,
    }
+
      elementBonusCoeff
  } else {
+
     );
     attacker.statAttackValue = calcStatAttackValue(attacker);
 
 
   }
 
   }
 +
  damages += elementDamages;
 +
 +
  damages = floorMultiplication(damages, battleValues.damageMultiplier);
 +
 +
  return damages;
 
}
 
}
  
function computeHorse(attacker) {
+
function calcDamageWithSecondaryBonuses(
   attacker.horseAttackValue = 0;
+
  damages,
 +
  battleValues,
 +
  damagesType,
 +
  minPiercingDamages,
 +
  damagesWithPrimaryBonuses
 +
) {
 +
   damages = floorMultiplication(damages, battleValues.magicResistanceCoeff);
 +
  damages = floorMultiplication(damages, battleValues.weaponDefenseCoeff);
 +
  damages = floorMultiplication(damages, battleValues.tigerStrengthCoeff);
 +
  damages = floorMultiplication(damages, battleValues.blessingBonusCoeff);
  
   if (isPC(attacker) && isRiding(attacker)) {
+
   if (damagesType.criticalHit) {
     var horseConstant = 30;
+
     damages *= 2;
 +
  }
  
    if (attacker.class === "weaponary") {
+
  if (damagesType.piercingHit) {
      horseConstant = 60;
+
    damages += battleValues.defense + Math.min(0, minPiercingDamages);
     }
+
     damages += floorMultiplication(
 
+
       damagesWithPrimaryBonuses,
    attacker.horseAttackValue = floorMultiplication(
+
       battleValues.extraPiercingHitCoeff
       2 * attacker.level + attacker.statAttackValue,
 
       attacker.horsePoint / horseConstant
 
 
     );
 
     );
 
   }
 
   }
}
 
  
function getRankBonus(attacker) {
+
  damages = floorMultiplication(damages, battleValues.averageDamageCoeff);
   if (attacker.lowRank === "on") {
+
   damages = floorMultiplication(
     switch (attacker.playerRank) {
+
    damages,
      case "aggressive":
+
    battleValues.averageDamageResistanceCoeff
        return 1;
+
  );
      case "fraudulent":
+
  damages = floorMultiplication(
        return 2;
+
    damages,
      case "malicious":
+
    battleValues.skillDamageResistanceCoeff
        return 3;
+
  );
      case "cruel":
+
 
        return 5;
+
  damages = floorMultiplication(damages, battleValues.rankBonusCoeff);
    }
+
  damages = Math.max(0, damages + battleValues.defensePercent);
   }
+
  damages += Math.min(
   return 0;
+
    300,
}
+
     floorMultiplication(damages, battleValues.damageBonusCoeff)
 +
  );
 +
  damages = floorMultiplication(damages, battleValues.empireMalusCoeff);
 +
  damages = floorMultiplication(damages, battleValues.sungMaStrBonusCoeff);
 +
  damages -= floorMultiplication(damages, battleValues.sungmaStrMalusCoeff);
 +
 
 +
   damages = floorMultiplication(damages, battleValues.whiteDragonElixirCoeff);
 +
   damages = floorMultiplication(damages, battleValues.steelDragonElixirCoeff);
  
function skillChanceReduction(value) {
+
   return damages;
   if (value <= 9) {
 
    return Math.floor(value / 2);
 
  }
 
  return 5 + Math.floor((value - 10) / 4);
 
 
}
 
}
  
function createPhysicalBattleValues(
+
function calcSkillDamageWithSecondaryBonuses(
   attacker,
+
   damages,
   victim,
+
   battleValues,
   mapping,
+
   damagesType,
   polymorphPowerTable,
+
   minPiercingDamages
  marriageTable
 
 
) {
 
) {
   var missPercentage = 0;
+
   damages = floorMultiplication(damages, battleValues.magicResistanceCoeff);
  var attackValuePercent = 0;
+
   damages = floorMultiplication(damages, battleValues.weaponDefenseCoeff);
  var attackMeleeMagic = 0;
+
 
  var attackValueMarriage = 0;
+
   damages -= battleValues.defense;
  var monsterResistanceMarriage = 0;
 
  var monsterResistance = 0;
 
  var typeBonus = 0;
 
  var raceBonus = 0;
 
  var raceResistance = 0;
 
  var stoneBonus = 0;
 
  var monsterBonus = 0;
 
  var elementBonus = [0, 0, 0, 0, 0, 0]; // fire, ice, lightning, earth, darkness, wind, order doesn't matter
 
  var defenseMarriage = 0;
 
  var damageMultiplier = 1;
 
  var weaponDefense = 0;
 
  var criticalHitPercentage = attacker.criticalHit;
 
  var criticalHitPercentageMarriage = 0;
 
  var piercingHitPercentage = attacker.piercingHit;
 
  var piercingHitPercentageMarriage = 0;
 
   var extraPiercingHitPercentage = Math.max(0, piercingHitPercentage - 100);
 
  var averageDamage = 0;
 
   var averageDamageResistance = 0;
 
  var skillDamageResistance = 0;
 
  var rankBonus = 0;
 
  var defensePercent = 0;
 
  var damageBonus = 0;
 
  var empireMalus = 0;
 
  
   computePolymorphPoint(attacker, victim, polymorphPowerTable);
+
   damages = floorMultiplication(damages, battleValues.skillWardCoeff);
   computeHorse(attacker);
+
  damages = floorMultiplication(damages, battleValues.skillBonusCoeff);
 +
  damages = floorMultiplication(damages, battleValues.skillBonusByBonusCoeff);
 +
   damages = floorMultiplication(damages, battleValues.tigerStrengthCoeff);
  
   if (isPC(attacker)) {
+
   if (damagesType.criticalHit) {
     attackValuePercent = attacker.attackValuePercent;
+
     damages *= 2;
    attackMeleeMagic = attacker.attackMeleeMagic;
+
  }
  
     var weaponType = 8;
+
  if (damagesType.piercingHit) {
 +
     damages +=
 +
      battleValues.piercingHitDefense + Math.min(0, minPiercingDamages);
 +
  }
  
    if (weaponData.hasOwnProperty(attacker.weapon)) {
+
  damages = floorMultiplication(damages, battleValues.skillDamageCoeff);
      weaponType = weaponData[attacker.weapon][1];
+
  damages = floorMultiplication(
    }
+
    damages,
 +
    battleValues.skillDamageResistanceCoeff
 +
  );
  
     var weaponDefenseName = mapping.defenseWeapon[weaponType];
+
  damages = floorMultiplication(damages, battleValues.rankBonusCoeff);
    var weaponDefenseBreakName = mapping.breakWeapon[weaponType];
+
  damages = Math.max(0, damages + battleValues.defensePercent);
 +
  damages += Math.min(
 +
     300,
 +
    floorMultiplication(damages, battleValues.damageBonusCoeff)
 +
  );
 +
  damages = floorMultiplication(damages, battleValues.empireMalusCoeff);
 +
  damages = floorMultiplication(damages, battleValues.sungMaStrBonusCoeff);
 +
  damages -= floorMultiplication(damages, battleValues.sungmaStrMalusCoeff);
  
    if (victim.hasOwnProperty(weaponDefenseName)) {
+
  damages = floorMultiplication(damages, battleValues.whiteDragonElixirCoeff);
      weaponDefense = victim[weaponDefenseName];
+
  damages = floorMultiplication(damages, battleValues.steelDragonElixirCoeff);
    }
 
  
    if (isPC(victim)) {
+
  return damages;
      if (weaponType === 2 && !isPolymorph(attacker)) {
+
}
        missPercentage = victim.arrowBlock;
 
      } else {
 
        missPercentage = victim.meleeBlock;
 
      }
 
  
      missPercentage +=
+
function computePolymorphPoint(attacker, victim, polymorphPowerTable) {
        victim.meleeArrowBlock -
+
  attacker.statAttackValue = 0;
        (missPercentage * victim.meleeArrowBlock) / 100;
 
  
      typeBonus = Math.max(
+
  attacker.polymorphDex = attacker.dex;
        0,
+
  victim.polymorphDex = victim.dex;
        Math.max(1, attacker.humanBonus) - victim.humanResistance
 
      );
 
      raceBonus = attacker[mapping.raceBonus[victim.race]];
 
      raceResistance = victim[mapping.raceResistance[attacker.race]];
 
  
      for (var index = 0; index <= 5; index++) {
+
  attacker.minAttackValuePolymorph = 0;
        elementBonus[index] =
+
  attacker.maxAttackValuePolymorph = 0;
          Math.max(
 
            0,
 
            attacker[mapping.elementBonus[index]] -
 
              victim[mapping.elementResistance[index]]
 
          ) / 1000;
 
      }
 
  
      if (attacker.hasOwnProperty(weaponDefenseBreakName)) {
+
  if (isPC(attacker) && isPolymorph(attacker) && polymorphPowerTable) {
        weaponDefense -= attacker[weaponDefenseBreakName];
+
    var polymorphPowerPct =
      }
+
      getPolymorphPower(attacker.polymorphPoint, polymorphPowerTable) / 100;
 +
    var polymorphMonster = createMonster(attacker.polymorphMonster);
  
      averageDamageResistance = victim.averageDamageResistance;
+
     var polymorphStr = floorMultiplication(
     } else {
+
      polymorphPowerPct,
      if (attacker.loveNecklace === "on") {
+
      polymorphMonster.str
        attackValueMarriage = getMarriageBonusValue(
+
    );
          attacker,
 
          marriageTable,
 
          "loveNecklace"
 
        );
 
      }
 
  
      if (attacker.loveEarrings === "on") {
+
    attacker.polymorphDex += floorMultiplication(
        criticalHitPercentageMarriage = getMarriageBonusValue(
+
      polymorphPowerPct,
          attacker,
+
      polymorphMonster.dex
          marriageTable,
+
    );
          "loveEarrings"
 
        );
 
      }
 
  
      if (attacker.harmonyEarrings === "on") {
+
    attacker.minAttackValuePolymorph = floorMultiplication(
        piercingHitPercentageMarriage = getMarriageBonusValue(
+
      polymorphPowerPct,
          attacker,
+
      polymorphMonster.minAttackValue
          marriageTable,
+
    );
          "harmonyEarrings"
+
    attacker.maxAttackValuePolymorph = floorMultiplication(
        );
+
      polymorphPowerPct,
      }
+
      polymorphMonster.maxAttackValue
 +
    );
  
      for (var index = 0; index <= 5; index++) {
+
    if (!attacker.weapon) {
        var elementBonusName = mapping.elementBonus[index];
+
      attacker.maxAttackValuePolymorph += 1;
        var elementResistanceName = mapping.elementResistance[index];
+
    }
  
        if (attacker[elementBonusName] && victim[elementBonusName]) {
+
    attacker.attackValue = 0;
          elementBonus[index] =
 
            (attacker[elementBonusName] - victim[elementResistanceName]) / 200;
 
        } else {
 
          elementBonus[index] = attacker[elementBonusName] / 2000;
 
        }
 
      }
 
  
       var victimType = victim.type;
+
    if (isMagicClass(attacker)) {
 +
       attacker.statAttackValue = 2 * (polymorphStr + attacker.int);
 +
    } else {
 +
      attacker.statAttackValue = 2 * (polymorphStr + attacker.str);
 +
    }
 +
  } else {
 +
    attacker.statAttackValue = calcStatAttackValue(attacker);
 +
  }
 +
}
  
      if (victimType !== -1) {
+
function computeHorse(attacker) {
        typeBonus = attacker[mapping.typeFlag[victimType]];
+
  attacker.horseAttackValue = 0;
      }
+
 
 +
  if (isPC(attacker) && isRiding(attacker)) {
 +
    var horseConstant = 30;
  
       monsterBonus = attacker.monsterBonus;
+
    if (attacker.class === "weaponary") {
 +
       horseConstant = 60;
 +
    }
  
       if (isStone(victim)) {
+
    attacker.horseAttackValue = floorMultiplication(
        stoneBonus = attacker.stoneBonus;
+
       2 * attacker.level + attacker.statAttackValue,
      }
+
      attacker.horsePoint / horseConstant
 +
    );
 +
  }
 +
}
  
      if (isBoss(victim)) {
+
function getRankBonus(attacker) {
        averageDamage += attacker.bossDamage;
+
  if (attacker.lowRank !== "on") {
      }
+
    return 0;
    }
+
  }
  
    averageDamage += attacker.averageDamage;
+
  switch (attacker.playerRank) {
     rankBonus = getRankBonus(attacker);
+
    case "aggressive":
     damageBonus = attacker.damageBonus;
+
      return 1;
 +
    case "fraudulent":
 +
      return 2;
 +
     case "malicious":
 +
      return 3;
 +
     case "cruel":
 +
      return 5;
 +
  }
  
    if (attacker.empireMalus === "on") {
+
  return 0;
      empireMalus = 10;
+
}
    }
+
 
   } else {
+
function calcElementCoeffPvP(elementBonus, mapping, attacker, victim) {
    if (isPC(victim)) {
+
  var minElementMalus = 0;
      if (victim.harmonyBracelet === "on") {
+
  var maxDifference = 0;
        monsterResistanceMarriage = getMarriageBonusValue(
+
  var savedElementDifferences = [];
          victim,
+
  var elementBonusIndex = 0;
          marriageTable,
+
 
          "harmonyBracelet"
+
   for (var index = 0; index < elementBonus.length; index++) {
        );
+
    if (!attacker[mapping.elementBonus[index]]) {
       }
+
      continue;
 +
    }
 +
 
 +
    var elementDifference =
 +
      attacker[mapping.elementBonus[index]] -
 +
       victim[mapping.elementResistance[index]];
  
      if (victim.harmonyNecklace === "on") {
+
    if (elementDifference >= 0) {
        defenseMarriage = getMarriageBonusValue(
+
      elementBonus[elementBonusIndex] = elementDifference / 1000;
          victim,
+
      minElementMalus -= elementDifference;
          marriageTable,
+
      maxDifference = Math.max(maxDifference, elementDifference);
          "harmonyNecklace"
+
      elementBonusIndex++;
        );
+
    } else {
      }
+
      savedElementDifferences.push(elementDifference);
 +
    }
 +
  }
  
      monsterResistance = victim.monsterResistance;
+
  if (!savedElementDifferences.length) {
 +
    return;
 +
  }
  
      for (var index = 0; index <= 5; index++) {
+
  minElementMalus += maxDifference;
        var elementBonusName = mapping.elementBonus[index];
+
  savedElementDifferences.sort(compareNumbers);
        var elementResistanceName = mapping.elementResistance[index];
 
  
        if (attacker[elementBonusName]) {
+
  for (var index = 0; index < savedElementDifferences.length; index++) {
          elementBonus[index] =
+
    var elementDifference = savedElementDifferences[index];
            (attacker[elementBonusName] - victim[elementResistanceName]) / 125;
 
        }
 
      }
 
  
      if (attacker.attack == 0) {
+
    elementBonus[elementBonusIndex + index] =
        missPercentage = victim.meleeBlock;
+
      Math.max(minElementMalus, elementDifference) / 1000;
        averageDamageResistance = victim.averageDamageResistance;
 
      } else if (attacker.attack == 1) {
 
        missPercentage = victim.arrowBlock;
 
        weaponDefense = victim.arrowDefense;
 
        averageDamageResistance = victim.averageDamageResistance;
 
      } else {
 
        missPercentage = victim.arrowBlock;
 
        skillDamageResistance = victim.skillDamageResistance;
 
      }
 
  
       missPercentage +=
+
    minElementMalus = Math.min(
        victim.meleeArrowBlock -
+
       0,
        (missPercentage * victim.meleeArrowBlock) / 100;
+
      Math.max(minElementMalus, minElementMalus - elementDifference)
    }
+
    );
 +
  }
 +
}
  
    typeBonus = 1;
+
function skillChanceReduction(value) {
     damageMultiplier = attacker.damageMultiplier;
+
  if (value <= 9) {
 +
     return Math.floor(value / 2);
 
   }
 
   }
 +
  return 5 + Math.floor((value - 10) / 4);
 +
}
  
  if (isPC(victim)) {
+
function magicResistanceToCoeff(magicResistance) {
    if (victim.biologist70 === "on") {
+
  if (magicResistance) {
      victim.defense = floorMultiplication(victim.defense, 1.1);
+
    return 2000 / (6 * magicResistance + 1000) - 1;
    }
+
  }
    criticalHitPercentage = Math.max(
+
  return 1;
      0,
+
}
      criticalHitPercentage - victim.criticalHitResistance
 
    );
 
    piercingHitPercentage = Math.max(
 
      0,
 
      piercingHitPercentage - victim.piercingHitResistance
 
    );
 
  
    if (isMagicClass(victim)) {
+
function createPhysicalBattleValues(
      defensePercent = (-2 * victim.magicDefense * victim.defensePercent) / 100;
+
  attacker,
    } else {
+
  attackerWeapon,
      defensePercent = (-2 * victim.defense * victim.defensePercent) / 100;
+
  victim,
    }
+
  mapping,
   }
+
  polymorphPowerTable,
 
+
  marriageTable,
   missPercentage = Math.min(100, missPercentage);
+
  skillPowerTable
 
+
) {
   var battleValues = {
+
  var missPercentage = 0;
    missPercentage: missPercentage,
+
  var attackValuePercent = 0;
    attackValueCoeff:
+
  var attackMeleeMagic = 0;
      1 + (attackValuePercent + Math.min(100, attackMeleeMagic)) / 100,
+
  var attackValueMarriage = 0;
    attackValueMarriage: attackValueMarriage,
+
   var monsterResistanceMarriage = 0;
    monsterResistanceMarriageCoeff: 1 - monsterResistanceMarriage / 100,
+
  var monsterResistance = 0;
    monsterResistanceCoeff: 1 - monsterResistance / 100,
+
   var typeBonus = 0;
    typeBonusCoeff: 1 + typeBonus / 100,
+
  var raceBonus = 0;
    raceBonusCoeff: raceBonus / 100,
+
   var raceResistance = 0;
    raceResistanceCoeff: raceResistance / 100,
+
  var stoneBonus = 0;
    monsterBonusCoeff: 1 + monsterBonus / 100,
+
  var monsterBonus = 0;
    stoneBonusCoeff: 1 + stoneBonus / 100,
+
  var elementBonus = [0, 0, 0, 0, 0, 0]; // fire, ice, lightning, earth, darkness, wind, order doesn't matter
    elementBonusCoeff: elementBonus,
+
  var defenseMarriage = 0;
    damageMultiplier: damageMultiplier,
+
  var damageMultiplier = 1;
    defense: victim.defense,
+
  var magicResistance = 0;
    defenseMarriage: defenseMarriage,
+
  var weaponDefense = 0;
    weaponDefenseCoeff: 1 - weaponDefense / 100,
+
  var tigerStrength = 0;
    extraPiercingHitCoeff: 1 + extraPiercingHitPercentage / 200,
+
  var blessingBonus = 0;
    averageDamageCoeff: 1 + averageDamage / 100,
+
  var criticalHitPercentage = attacker.criticalHit;
    averageDamageResistanceCoeff:
+
  var criticalHitPercentageMarriage = 0;
      1 - Math.min(99, averageDamageResistance) / 100,
+
  var piercingHitPercentage = attacker.piercingHit;
    skillDamageResistanceCoeff: 1 - Math.min(99, skillDamageResistance) / 100,
+
  var piercingHitPercentageMarriage = 0;
    rankBonusCoeff: 1 + rankBonus / 100,
+
  var extraPiercingHitPercentage = Math.max(0, piercingHitPercentage - 100);
    defensePercent: defensePercent,
+
  var averageDamage = 0;
    damageBonusCoeff: damageBonus / 100,
+
  var averageDamageResistance = 0;
    empireMalusCoeff: 1 - empireMalus / 100,
+
  var skillDamageResistance = 0;
   };
+
  var rankBonus = 0;
 +
  var defensePercent = 0;
 +
  var damageBonus = 0;
 +
  var empireMalus = 0;
 +
  var sungMaStrBonus = 0;
 +
  var sungmaStrMalus = 0;
 +
  var whiteDragonElixir = 0;
 +
  var steelDragonElixir = 0;
 +
 
 +
  computePolymorphPoint(attacker, victim, polymorphPowerTable);
 +
  computeHorse(attacker);
 +
 
 +
   if (isPC(attacker)) {
 +
    attackValuePercent = attacker.attackValuePercent;
 +
    attackMeleeMagic = attacker.attackMeleeMagic;
 +
 
 +
    var weaponType = attackerWeapon[1];
  
  criticalHitPercentage = Math.min(
+
    var weaponDefenseName = mapping.defenseWeapon[weaponType];
    criticalHitPercentage + criticalHitPercentageMarriage,
+
     var weaponDefenseBreakName = mapping.breakWeapon[weaponType];
     100
 
  );
 
  piercingHitPercentage = Math.min(
 
    piercingHitPercentage + piercingHitPercentageMarriage,
 
    100
 
  );
 
  
  battleValues.damagesTypeCombinaison = [
+
     if (victim.hasOwnProperty(weaponDefenseName)) {
     {
+
       weaponDefense = victim[weaponDefenseName];
      criticalHit: false,
+
     }
      piercingHit: false,
 
      weight:
 
        (100 - criticalHitPercentage) *
 
        (100 - piercingHitPercentage) *
 
        (100 - missPercentage),
 
      name: "Coup classique",
 
    },
 
    {
 
       criticalHit: true,
 
      piercingHit: false,
 
      weight:
 
        criticalHitPercentage *
 
        (100 - piercingHitPercentage) *
 
        (100 - missPercentage),
 
      name: "Coup critique",
 
     },
 
    {
 
      criticalHit: false,
 
      piercingHit: true,
 
      weight:
 
        (100 - criticalHitPercentage) *
 
        piercingHitPercentage *
 
        (100 - missPercentage),
 
      name: "Coup perçant",
 
    },
 
    {
 
      criticalHit: true,
 
      piercingHit: true,
 
      weight:
 
        criticalHitPercentage * piercingHitPercentage * (100 - missPercentage),
 
      name: "Coup critique + coup perçant",
 
    },
 
  ];
 
  
  return battleValues;
+
    if (attacker.whiteDragonElixir === "on") {
}
+
      whiteDragonElixir = 10;
 +
    }
  
function createSkillBattleValues(attacker, victim, mapping) {
+
    if (isPC(victim)) {
  var attackValuePercent = 0;
+
      if (weaponType === 2 && !isPolymorph(attacker)) {
  var attackMeleeMagic = 0;
+
        missPercentage = victim.arrowBlock;
  var attackValueMarriage = 0;
+
      } else {
  var monsterResistanceMarriage = 0;
+
        missPercentage = victim.meleeBlock;
  var monsterResistance = 0;
+
      }
  var typeBonus = 0;
 
  var raceBonus = 0;
 
  var raceResistance = 0;
 
  var stoneBonus = 0;
 
  var monsterBonus = 0;
 
  var elementBonus = [0, 0, 0, 0, 0, 0]; // fire, ice, lightning, earth, darkness, wind, order doesn't matter
 
  var damageMultiplier = 1;
 
  var weaponDefense = 0;
 
  var criticalHitPercentage = attacker.criticalHit;
 
  var piercingHitPercentage = attacker.piercingHit;
 
  var skillDamage = 0;
 
  var skillDamageResistance = 0;
 
  var rankBonus = 0;
 
  var defensePercent = 0;
 
  var damageBonus = 0;
 
  var empireMalus = 0;
 
  
  computePolymorphPoint(attacker, victim);
+
      missPercentage +=
  computeHorse(attacker);
+
        victim.meleeArrowBlock -
 +
        (missPercentage * victim.meleeArrowBlock) / 100;
  
  if (isPC(attacker)) {
+
       typeBonus = Math.max(1, attacker.humanBonus - victim.humanResistance);
    attackValuePercent = attacker.attackValuePercent;
+
       raceBonus = attacker[mapping.raceBonus[victim.race]];
    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 = Math.max(
 
        0,
 
        Math.max(1, attacker.humanBonus) - victim.humanResistance
 
      );
 
       raceBonus = attacker[mapping.raceBonus[victim.race]];
 
 
       raceResistance = victim[mapping.raceResistance[attacker.race]];
 
       raceResistance = victim[mapping.raceResistance[attacker.race]];
  
       for (var index = 0; index <= 5; index++) {
+
       calcElementCoeffPvP(elementBonus, mapping, attacker, victim);
        elementBonus[index] =
 
          Math.max(
 
            0,
 
            attacker[mapping.elementBonus[index]] -
 
              victim[mapping.elementResistance[index]]
 
          ) / 1000;
 
      }
 
  
 
       if (attacker.hasOwnProperty(weaponDefenseBreakName)) {
 
       if (attacker.hasOwnProperty(weaponDefenseBreakName)) {
 
         weaponDefense -= attacker[weaponDefenseBreakName];
 
         weaponDefense -= attacker[weaponDefenseBreakName];
 
       }
 
       }
 +
 +
      criticalHitPercentage = 0;
 +
      blessingBonus = calcBlessingBonus(skillPowerTable, victim);
 +
      averageDamageResistance = victim.averageDamageResistance;
 
     } else {
 
     } else {
       if (attacker.loveNecklace === "on") {
+
       if (attacker.isMarried === "on") {
        attackValueMarriage = getMarriageBonusValue(
+
        if (attacker.loveNecklace === "on") {
          attacker,
+
          attackValueMarriage = getMarriageBonusValue(
          marriageTable,
+
            attacker,
          "loveNecklace"
+
            marriageTable,
         );
+
            "loveNecklace"
      }
+
          );
 +
        }
 +
 
 +
         if (attacker.loveEarrings === "on") {
 +
          criticalHitPercentageMarriage = getMarriageBonusValue(
 +
            attacker,
 +
            marriageTable,
 +
            "loveEarrings"
 +
          );
 +
        }
  
      if (attacker.loveEarrings === "on") {
+
        if (attacker.harmonyEarrings === "on") {
        criticalHitPercentage += getMarriageBonusValue(
+
          piercingHitPercentageMarriage = getMarriageBonusValue(
          attacker,
+
            attacker,
          marriageTable,
+
            marriageTable,
          "loveEarrings"
+
            "harmonyEarrings"
        );
+
          );
 +
        }
 
       }
 
       }
  
       if (attacker.harmonyEarrings === "on") {
+
       if (attacker.tigerStrength === "on") {
         piercingHitPercentage += getMarriageBonusValue(
+
         tigerStrength = 40;
          attacker,
 
          marriageTable,
 
          "harmonyEarrings"
 
        );
 
 
       }
 
       }
  
       for (var index = 0; index <= 5; index++) {
+
       for (var index = 0; index < elementBonus.length; index++) {
 
         var elementBonusName = mapping.elementBonus[index];
 
         var elementBonusName = mapping.elementBonus[index];
 
         var elementResistanceName = mapping.elementResistance[index];
 
         var elementResistanceName = mapping.elementResistance[index];
Ligne 1 849 : Ligne 1 906 :
  
 
       if (isBoss(victim)) {
 
       if (isBoss(victim)) {
         skillDamage += attacker.skillBossDamage;
+
         averageDamage += attacker.bossDamage;
 +
      }
 +
 
 +
      if (attacker.onYohara === "on") {
 +
        var sungmaStrDifference = attacker.sungmaStr - attacker.sungmaStrMalus;
 +
 
 +
        if (sungmaStrDifference >= 0) {
 +
          sungMaStrBonus = sungmaStrDifference;
 +
        } else {
 +
          sungmaStrMalus = 0.5;
 +
        }
 
       }
 
       }
 
     }
 
     }
  
     skillDamage += attacker.skillDamage;
+
     averageDamage += attacker.averageDamage;
 
     rankBonus = getRankBonus(attacker);
 
     rankBonus = getRankBonus(attacker);
 
     damageBonus = attacker.damageBonus;
 
     damageBonus = attacker.damageBonus;
Ligne 1 862 : Ligne 1 929 :
 
   } else {
 
   } else {
 
     if (isPC(victim)) {
 
     if (isPC(victim)) {
       if (victim.harmonyBracelet === "on") {
+
       if (victim.isMarried === "on") {
        monsterResistanceMarriage = getMarriageBonusValue(
+
        if (victim.harmonyBracelet === "on") {
          victim,
+
          monsterResistanceMarriage = getMarriageBonusValue(
          marriageTable,
+
            victim,
          "harmonyBracelet"
+
            marriageTable,
         );
+
            "harmonyBracelet"
 +
          );
 +
        }
 +
 
 +
         if (victim.harmonyNecklace === "on") {
 +
          defenseMarriage = getMarriageBonusValue(
 +
            victim,
 +
            marriageTable,
 +
            "harmonyNecklace"
 +
          );
 +
        }
 
       }
 
       }
 +
 
       monsterResistance = victim.monsterResistance;
 
       monsterResistance = victim.monsterResistance;
  
       for (var index = 0; index <= 5; index++) {
+
       for (var index = 0; index < elementBonus.length; index++) {
 
         var elementBonusName = mapping.elementBonus[index];
 
         var elementBonusName = mapping.elementBonus[index];
 
         var elementResistanceName = mapping.elementResistance[index];
 
         var elementResistanceName = mapping.elementResistance[index];
Ligne 1 880 : Ligne 1 958 :
 
         }
 
         }
 
       }
 
       }
    }
 
  
    typeBonus = 1;
+
      if (attacker.attack == 0) {
    damageMultiplier = attacker.damageMultiplier;
+
        missPercentage = victim.meleeBlock;
  }
+
        averageDamageResistance = victim.averageDamageResistance;
 
+
        blessingBonus = calcBlessingBonus(skillPowerTable, victim);
  criticalHitPercentage = skillChanceReduction(criticalHitPercentage);
+
      } else if (attacker.attack == 1) {
  piercingHitPercentage = skillChanceReduction(piercingHitPercentage);
+
        missPercentage = victim.arrowBlock;
 +
        weaponDefense = victim.arrowDefense;
 +
        averageDamageResistance = victim.averageDamageResistance;
 +
        blessingBonus = calcBlessingBonus(skillPowerTable, victim);
 +
      } else {
 +
        missPercentage = victim.arrowBlock;
 +
        skillDamageResistance = victim.skillDamageResistance;
 +
        magicResistance = victim.magicResistance;
 +
      }
 +
 
 +
      missPercentage +=
 +
        victim.meleeArrowBlock -
 +
        (missPercentage * victim.meleeArrowBlock) / 100;
 +
    }
 +
 
 +
    typeBonus = 1;
 +
    damageMultiplier = attacker.damageMultiplier;
 +
  }
  
 
   if (isPC(victim)) {
 
   if (isPC(victim)) {
 +
    if (victim.biologist70 === "on") {
 +
      victim.defense = floorMultiplication(victim.defense, 1.1);
 +
    }
 
     criticalHitPercentage = Math.max(
 
     criticalHitPercentage = Math.max(
 
       0,
 
       0,
Ligne 1 898 : Ligne 1 995 :
 
       piercingHitPercentage - victim.piercingHitResistance
 
       piercingHitPercentage - victim.piercingHitResistance
 
     );
 
     );
    skillDamageResistance = victim.skillDamageResistance;
 
  
 
     if (isMagicClass(victim)) {
 
     if (isMagicClass(victim)) {
Ligne 1 904 : Ligne 2 000 :
 
     } else {
 
     } else {
 
       defensePercent = (-2 * victim.defense * victim.defensePercent) / 100;
 
       defensePercent = (-2 * victim.defense * victim.defensePercent) / 100;
 +
    }
 +
 +
    if (victim.steelDragonElixir === "on") {
 +
      steelDragonElixir = 10;
 
     }
 
     }
 
   }
 
   }
 +
 +
  missPercentage = Math.min(100, missPercentage);
  
 
   var battleValues = {
 
   var battleValues = {
 +
    missPercentage: missPercentage,
 +
    adjustCoeff: 0,
 
     attackValueCoeff:
 
     attackValueCoeff:
 
       1 + (attackValuePercent + Math.min(100, attackMeleeMagic)) / 100,
 
       1 + (attackValuePercent + Math.min(100, attackMeleeMagic)) / 100,
Ligne 1 921 : Ligne 2 025 :
 
     damageMultiplier: damageMultiplier,
 
     damageMultiplier: damageMultiplier,
 
     defense: victim.defense,
 
     defense: victim.defense,
 +
    defenseMarriage: defenseMarriage,
 +
    magicResistanceCoeff: magicResistanceToCoeff(magicResistance),
 
     weaponDefenseCoeff: 1 - weaponDefense / 100,
 
     weaponDefenseCoeff: 1 - weaponDefense / 100,
     skillDamageCoeff: 1 + skillDamage / 100,
+
     tigerStrengthCoeff: 1 + tigerStrength / 100,
 +
    blessingBonusCoeff: 1 - blessingBonus / 100,
 +
    extraPiercingHitCoeff: extraPiercingHitPercentage / 200,
 +
    averageDamageCoeff: 1 + averageDamage / 100,
 +
    averageDamageResistanceCoeff:
 +
      1 - Math.min(99, averageDamageResistance) / 100,
 
     skillDamageResistanceCoeff: 1 - Math.min(99, skillDamageResistance) / 100,
 
     skillDamageResistanceCoeff: 1 - Math.min(99, skillDamageResistance) / 100,
 
     rankBonusCoeff: 1 + rankBonus / 100,
 
     rankBonusCoeff: 1 + rankBonus / 100,
     defensePercent: defensePercent,
+
     defensePercent: Math.floor(defensePercent),
 
     damageBonusCoeff: damageBonus / 100,
 
     damageBonusCoeff: damageBonus / 100,
 
     empireMalusCoeff: 1 - empireMalus / 100,
 
     empireMalusCoeff: 1 - empireMalus / 100,
 +
    sungMaStrBonusCoeff: 1 + sungMaStrBonus / 10000,
 +
    sungmaStrMalusCoeff: sungmaStrMalus,
 +
    whiteDragonElixirCoeff: 1 + whiteDragonElixir / 100,
 +
    steelDragonElixirCoeff: 1 - steelDragonElixir / 100
 
   };
 
   };
  
   criticalHitPercentage = Math.min(criticalHitPercentage, 100);
+
   criticalHitPercentage = Math.min(
   piercingHitPercentage = Math.min(piercingHitPercentage, 100);
+
    criticalHitPercentage + criticalHitPercentageMarriage,
 +
    100
 +
  );
 +
   piercingHitPercentage = Math.min(
 +
    piercingHitPercentage + piercingHitPercentageMarriage,
 +
    100
 +
  );
  
 
   battleValues.damagesTypeCombinaison = [
 
   battleValues.damagesTypeCombinaison = [
Ligne 1 937 : Ligne 2 058 :
 
       criticalHit: false,
 
       criticalHit: false,
 
       piercingHit: false,
 
       piercingHit: false,
       weight: (100 - criticalHitPercentage) * (100 - piercingHitPercentage),
+
       weight:
 +
        (100 - criticalHitPercentage) *
 +
        (100 - piercingHitPercentage) *
 +
        (100 - missPercentage),
 
       name: "Coup classique",
 
       name: "Coup classique",
 
     },
 
     },
Ligne 1 943 : Ligne 2 067 :
 
       criticalHit: true,
 
       criticalHit: true,
 
       piercingHit: false,
 
       piercingHit: false,
       weight: criticalHitPercentage * (100 - piercingHitPercentage),
+
       weight:
 +
        criticalHitPercentage *
 +
        (100 - piercingHitPercentage) *
 +
        (100 - missPercentage),
 
       name: "Coup critique",
 
       name: "Coup critique",
 
     },
 
     },
Ligne 1 949 : Ligne 2 076 :
 
       criticalHit: false,
 
       criticalHit: false,
 
       piercingHit: true,
 
       piercingHit: true,
       weight: (100 - criticalHitPercentage) * piercingHitPercentage,
+
       weight:
 +
        (100 - criticalHitPercentage) *
 +
        piercingHitPercentage *
 +
        (100 - missPercentage),
 
       name: "Coup perçant",
 
       name: "Coup perçant",
 
     },
 
     },
Ligne 1 955 : Ligne 2 085 :
 
       criticalHit: true,
 
       criticalHit: true,
 
       piercingHit: true,
 
       piercingHit: true,
       weight: criticalHitPercentage * piercingHitPercentage,
+
       weight:
 +
        criticalHitPercentage * piercingHitPercentage * (100 - missPercentage),
 
       name: "Coup critique + coup perçant",
 
       name: "Coup critique + coup perçant",
 
     },
 
     },
Ligne 1 963 : Ligne 2 094 :
 
}
 
}
  
function calcPhysicalDamages(
+
function createSkillBattleValues(
 
   attacker,
 
   attacker,
 +
  attackerWeapon,
 
   victim,
 
   victim,
  tableResult,
 
 
   mapping,
 
   mapping,
   polymorphPowerTable,
+
   marriageTable,
   marriageTable
+
   magicSkill
 
) {
 
) {
   var attackerWeapon = null;
+
   var adjustCoeff = 0;
   var battleValues = createPhysicalBattleValues(
+
   var attackValuePercent = 0;
    attacker,
+
  var attackMeleeMagic = 0;
    victim,
+
  var attackValueMarriage = 0;
    mapping,
+
  var monsterResistanceMarriage = 0;
    polymorphPowerTable,
+
  var monsterResistance = 0;
    marriageTable
+
  var typeBonus = 0;
   );
+
  var raceBonus = 0;
 +
  var raceResistance = 0;
 +
  var stoneBonus = 0;
 +
  var monsterBonus = 0;
 +
  var elementBonus = [0, 0, 0, 0, 0, 0]; // fire, ice, lightning, earth, darkness, wind, order doesn't matter
 +
  var damageMultiplier = 1;
 +
  var useDamages = 1;
 +
  var defense = victim.defense;
 +
  var magicResistance = 0;
 +
  var weaponDefense = 0;
 +
  var tigerStrength = 0;
 +
  var criticalHitPercentage = attacker.criticalHit;
 +
  var piercingHitPercentage = attacker.piercingHit;
 +
  var skillDamage = 0;
 +
  var skillDamageResistance = 0;
 +
  var rankBonus = 0;
 +
  var defensePercent = 0;
 +
  var damageBonus = 0;
 +
  var empireMalus = 0;
 +
  var sungMaStrBonus = 0;
 +
  var sungmaStrMalus = 0;
 +
  var whiteDragonElixir = 0;
 +
   var steelDragonElixir = 0;
  
   var sumDamages = 0;
+
   computePolymorphPoint(attacker, victim);
  var minMaxDamages = { min: Infinity, max: 0 };
+
   computeHorse(attacker);
   clearTableResult(tableResult);
 
  
 
   if (isPC(attacker)) {
 
   if (isPC(attacker)) {
     attackerWeapon = weaponData[attacker.weapon];
+
     attackValuePercent = attacker.attackValuePercent;
  }
+
    attackMeleeMagic = attacker.attackMeleeMagic;
 +
 
 +
    var weaponType = attackerWeapon[1];
  
  var attackFactor = calcAttackFactor(attacker, victim);
+
    if (attacker.class === "archery") {
  var mainAttackValue = calcMainAttackValue(attacker, attackerWeapon);
+
      if (weaponType !== 2) {
  var [
+
        useDamages = 0;
    minAttackValue,
+
        weaponType = 2;
    maxAttackValue,
+
      }
    attackValueOther,
+
      defense = 0;
    minInterval,
+
     }
     totalCardinal,
 
  ] = calcSecondaryAttackValue(attacker, attackerWeapon);
 
  
  totalCardinal *= 100;
+
    var weaponDefenseName = mapping.defenseWeapon[weaponType];
 +
    var weaponDefenseBreakName = mapping.breakWeapon[weaponType];
  
  if (battleValues.missPercentage) {
+
    if (victim.hasOwnProperty(weaponDefenseName)) {
    addRowToTableResult(tableResult, "Miss");
+
      weaponDefense = victim[weaponDefenseName];
     addToTableResult(tableResult, { 0: battleValues.missPercentage / 100 });
+
     }
  }
 
  
  var lastWeightsLimit = maxAttackValue - minInterval + 1;
+
     if (attacker.whiteDragonElixir === "on") {
  var firstWeightLimit = minAttackValue + minInterval - 1;
+
       whiteDragonElixir = 10;
 
 
  for (var damagesType of battleValues.damagesTypeCombinaison) {
 
     if (!damagesType.weight) {
 
       continue;
 
 
     }
 
     }
  
     var damagesWeighted = {};
+
     if (isPC(victim)) {
    addRowToTableResult(tableResult, damagesType.name);
+
      typeBonus = Math.max(1, attacker.humanBonus - victim.humanResistance);
 +
      raceBonus = attacker[mapping.raceBonus[victim.race]];
 +
      raceResistance = victim[mapping.raceResistance[attacker.race]];
  
    for (
+
      calcElementCoeffPvP(elementBonus, mapping, attacker, victim);
      var attackValue = minAttackValue;
 
      attackValue <= maxAttackValue;
 
      attackValue++
 
    ) {
 
      var weight;
 
  
       if (attackValue > lastWeightsLimit) {
+
       if (attacker.hasOwnProperty(weaponDefenseBreakName)) {
        weight = maxAttackValue - attackValue + 1;
+
         weaponDefense -= attacker[weaponDefenseBreakName];
      } else if (attackValue < firstWeightLimit) {
 
         weight = attackValue - minAttackValue + 1;
 
      } else {
 
        weight = minInterval;
 
 
       }
 
       }
  
       var secondaryAttackValue = 2 * attackValue + attackValueOther;
+
       criticalHitPercentage = 0;
       var rawDamages =
+
    } else {
         mainAttackValue +
+
       if (attacker.isMarried === "on") {
        floorMultiplication(attackFactor, secondaryAttackValue);
+
         if (attacker.loveNecklace === "on") {
      var damagesWithPrimaryBonuses = calcDamageWithPrimaryBonuses(
+
          attackValueMarriage = getMarriageBonusValue(
        rawDamages,
+
            attacker,
        battleValues
+
            marriageTable,
      );
+
            "loveNecklace"
 
+
          );
      damagesWithPrimaryBonuses -=
+
         }
         battleValues.defense + battleValues.defenseMarriage;
 
  
      if (damagesWithPrimaryBonuses <= 2) {
+
        if (attacker.loveEarrings === "on") {
        for (var damages = 1; damages <= 5; damages++) {
+
           criticalHitPercentage += getMarriageBonusValue(
           var finalDamages = calcDamageWithSecondaryBonuses(
+
             attacker,
             damages,
+
             marriageTable,
            battleValues,
+
             "loveEarrings"
             damagesType,
 
             damagesWithPrimaryBonuses
 
 
           );
 
           );
 +
        }
  
           addKeyValue(
+
        if (attacker.harmonyEarrings === "on") {
             damagesWeighted,
+
           piercingHitPercentage += getMarriageBonusValue(
             finalDamages,
+
             attacker,
             (weight * damagesType.weight) / (5 * totalCardinal)
+
             marriageTable,
 +
             "harmonyEarrings"
 
           );
 
           );
          sumDamages += (finalDamages * weight * damagesType.weight) / 5;
 
 
         }
 
         }
       } else {
+
       }
        var finalDamages = calcDamageWithSecondaryBonuses(
 
          damagesWithPrimaryBonuses,
 
          battleValues,
 
          damagesType,
 
          damagesWithPrimaryBonuses
 
        );
 
  
        addKeyValue(
+
      if (attacker.tigerStrength === "on") {
          damagesWeighted,
+
         tigerStrength = 40;
          finalDamages,
 
          (weight * damagesType.weight) / (5 * totalCardinal)
 
         );
 
        sumDamages += finalDamages * weight * damagesType.weight;
 
 
       }
 
       }
    }
 
  
    addToTableResult(tableResult, damagesWeighted, minMaxDamages);
+
      for (var index = 0; index < elementBonus.length; index++) {
  }
+
        var elementBonusName = mapping.elementBonus[index];
 +
        var elementResistanceName = mapping.elementResistance[index];
  
  if (minMaxDamages.min === Infinity) {
+
        if (attacker[elementBonusName] && victim[elementBonusName]) {
    minMaxDamages.min = 0;
+
          elementBonus[index] =
  }
+
            (attacker[elementBonusName] - victim[elementResistanceName]) / 200;
 +
        } else {
 +
          elementBonus[index] = attacker[elementBonusName] / 2000;
 +
        }
 +
      }
  
  return [sumDamages / totalCardinal, minMaxDamages];
+
      var victimType = victim.type;
}
 
  
function getSkillFormula(skillId, lv, vit, str, int, dex, skillPower) {
+
       if (victimType !== -1) {
  switch (skillId) {
+
         typeBonus = attacker[mapping.typeFlag[victimType]];
    case 5:
+
       }
       return function (atk) {
 
        return floorMultiplication(
 
          2 * atk + (atk + dex * 3 + str * 7 + vit) * skillPower,
 
          1
 
        );
 
      };
 
    case 62:
 
      return function (atk) {
 
         return floorMultiplication(
 
          1.1 * atk +
 
            2 * lv +
 
            2 * int +
 
            (1.5 * atk + str + 12 * int) * skillPower,
 
          1
 
        );
 
       };
 
  }
 
}
 
  
function calcSkillDamages(
+
      monsterBonus = attacker.monsterBonus;
  attacker,
 
  victim,
 
  tableResult,
 
  mapping,
 
  skillPowerTable,
 
  skillId
 
) {
 
  var skillPower = getSkillPower(attacker["skill" + skillId], skillPowerTable);
 
  var skillFormula = getSkillFormula(
 
    skillId,
 
    attacker.level,
 
    attacker.vit,
 
    attacker.str,
 
    attacker.int,
 
    attacker.dex,
 
    skillPower
 
  );
 
  
  var attackerWeapon = null;
+
      if (isStone(victim)) {
  var battleValues = createSkillBattleValues(attacker, victim, mapping);
+
        stoneBonus = attacker.stoneBonus;
 +
      }
  
  var sumDamages = 0;
+
      if (isBoss(victim)) {
  var minMaxDamages = { min: Infinity, max: 0 };
+
        skillDamage += attacker.skillBossDamage;
  clearTableResult(tableResult);
+
      }
  
  if (isPC(attacker)) {
+
      if (attacker.onYohara === "on") {
    attackerWeapon = weaponData[attacker.weapon];
+
        var sungmaStrDifference = attacker.sungmaStr - attacker.sungmaStrMalus;
  }
 
  
  var attackFactor = calcAttackFactor(attacker, victim);
+
        if (sungmaStrDifference >= 0) {
  var mainAttackValue = calcMainAttackValue(attacker, attackerWeapon);
+
          sungMaStrBonus = sungmaStrDifference;
  var [
+
        } else {
    minAttackValue,
+
          sungmaStrMalus = 0.5;
    maxAttackValue,
+
        }
    attackValueOther,
+
      }
    minInterval,
+
     }
     totalCardinal,
 
  ] = calcSecondaryAttackValue(attacker, attackerWeapon);
 
  
  var lastWeightsLimit = maxAttackValue - minInterval + 1;
+
    skillDamage += attacker.skillDamage;
  var firstWeightLimit = minAttackValue + minInterval - 1;
+
    rankBonus = getRankBonus(attacker);
 +
    damageBonus = attacker.damageBonus;
  
  for (var damagesType of battleValues.damagesTypeCombinaison) {
+
     if (attacker.empireMalus === "on") {
     if (!damagesType.weight) {
+
       empireMalus = 10;
       continue;
 
 
     }
 
     }
 +
  } else {
 +
    if (isPC(victim)) {
 +
      if (victim.isMarried === "on" && victim.harmonyBracelet === "on") {
 +
        monsterResistanceMarriage = getMarriageBonusValue(
 +
          victim,
 +
          marriageTable,
 +
          "harmonyBracelet"
 +
        );
 +
      }
  
    var damagesWeighted = {};
+
      monsterResistance = victim.monsterResistance;
    addRowToTableResult(tableResult, damagesType.name);
 
  
    for (
+
      for (var index = 0; index < elementBonus.length; index++) {
      var attackValue = minAttackValue;
+
        var elementBonusName = mapping.elementBonus[index];
      attackValue <= maxAttackValue;
+
        var elementResistanceName = mapping.elementResistance[index];
      attackValue++
 
    ) {
 
      var weight;
 
  
      if (attackValue > lastWeightsLimit) {
+
        if (attacker[elementBonusName]) {
        weight = maxAttackValue - attackValue + 1;
+
          elementBonus[index] =
      } else if (attackValue < firstWeightLimit) {
+
            (attacker[elementBonusName] - victim[elementResistanceName]) / 125;
         weight = attackValue - minAttackValue + 1;
+
         }
      } else {
 
        weight = minInterval;
 
 
       }
 
       }
 +
    }
  
      var secondaryAttackValue = 2 * attackValue + attackValueOther;
+
    typeBonus = 1;
      var rawDamages =
+
    damageMultiplier = attacker.damageMultiplier;
        mainAttackValue +
+
  }
        floorMultiplication(attackFactor, secondaryAttackValue);
+
 
      var damagesWithPrimaryBonuses = calcDamageWithPrimaryBonuses(
+
  criticalHitPercentage = skillChanceReduction(criticalHitPercentage);
        rawDamages,
+
  piercingHitPercentage = skillChanceReduction(piercingHitPercentage);
        battleValues
 
      );
 
  
      if (damagesWithPrimaryBonuses <= 2) {
+
  if (isPC(victim)) {
        for (var damages = 1; damages <= 5; damages++) {
+
    criticalHitPercentage = Math.max(
          var finalDamages = calcSkillDamageWithSecondaryBonuses(
+
      0,
            damages,
+
      criticalHitPercentage - victim.criticalHitResistance
            battleValues,
+
    );
            damagesType,
+
    piercingHitPercentage = Math.max(
            damagesWithPrimaryBonuses,
+
      0,
            skillFormula
+
      piercingHitPercentage - victim.piercingHitResistance
          );
+
    );
 +
    skillDamageResistance = victim.skillDamageResistance;
 +
 
 +
    if (isMagicClass(victim)) {
 +
      defensePercent = (-2 * victim.magicDefense * victim.defensePercent) / 100;
 +
    } else {
 +
      defensePercent = (-2 * victim.defense * victim.defensePercent) / 100;
 +
    }
 +
 
 +
    if (victim.steelDragonElixir === "on") {
 +
      steelDragonElixir = 10;
 +
    }
 +
  }
  
          addKeyValue(
+
  if (magicSkill) {
            damagesWeighted,
+
    adjustCoeff = 0.5;
            finalDamages,
+
    attackValuePercent = attacker.attackMagic;
            (weight * damagesType.weight) / (5 * totalCardinal)
+
    attackValueMarriage = 0;
          );
+
    defense = 0;
          sumDamages += (finalDamages * weight * damagesType.weight) / 5;
+
    magicResistance = victim.magicResistance;
        }
+
     weaponDefense = 0;
      } else {
 
        var finalDamages = calcSkillDamageWithSecondaryBonuses(
 
          damagesWithPrimaryBonuses,
 
          battleValues,
 
          damagesType,
 
          damagesWithPrimaryBonuses,
 
          skillFormula
 
        );
 
 
 
        addKeyValue(
 
          damagesWeighted,
 
          finalDamages,
 
          (weight * damagesType.weight) / (5 * totalCardinal)
 
        );
 
        sumDamages += finalDamages * weight * damagesType.weight;
 
      }
 
     }
 
 
 
    addToTableResult(tableResult, damagesWeighted, minMaxDamages);
 
 
   }
 
   }
  
  if (minMaxDamages.min === Infinity) {
+
   var battleValues = {
    minMaxDamages.min = 0;
+
     weaponBonusCoeff: 1,
  }
+
     adjustCoeff: adjustCoeff,
 
+
     attackValueCoeff:
  return [
+
      1 + (attackValuePercent + Math.min(100, attackMeleeMagic)) / 100,
    sumDamages / totalCardinal,
+
     attackValueMarriage: attackValueMarriage,
    minMaxDamages,
+
     monsterResistanceMarriageCoeff: 1 - monsterResistanceMarriage / 100,
  ];
+
     monsterResistanceCoeff: 1 - monsterResistance / 100,
}
+
     typeBonusCoeff: 1 + typeBonus / 100,
 
+
     raceBonusCoeff: raceBonus / 100,
function createMonster(name) {
+
     raceResistanceCoeff: raceResistance / 100,
  var data = monsterData[name];
+
     monsterBonusCoeff: 1 + monsterBonus / 100,
 
+
     stoneBonusCoeff: 1 + stoneBonus / 100,
   var monster = {
+
     elementBonusCoeff: elementBonus,
     name: name,
+
     damageMultiplier: damageMultiplier,
    rank: data[0],
+
     useDamages: useDamages,
    race: data[1],
+
     defense: defense,
     attack: data[2],
+
     tigerStrengthCoeff: 1 + tigerStrength / 100,
     level: data[3],
+
     piercingHitDefense: victim.defense,
    type: data[4],
+
     magicResistanceCoeff: magicResistanceToCoeff(magicResistance),
    str: data[5],
+
     weaponDefenseCoeff: 1 - weaponDefense / 100,
     dex: data[6],
+
     skillDamageCoeff: 1 + skillDamage / 100,
     vit: data[7],
+
     skillDamageResistanceCoeff: 1 - Math.min(99, skillDamageResistance) / 100,
     int: data[8],
+
     rankBonusCoeff: 1 + rankBonus / 100,
     minAttackValue: data[9],
+
     defensePercent: Math.floor(defensePercent),
    maxAttackValue: data[10],
+
     damageBonusCoeff: damageBonus / 100,
    defense: data[11] + data[3] + data[7],
+
     empireMalusCoeff: 1 - empireMalus / 100,
     criticalHit: data[12],
+
     sungMaStrBonusCoeff: 1 + sungMaStrBonus / 10000,
     piercingHit: data[13],
+
     sungmaStrMalusCoeff: sungmaStrMalus,
     fistDefense: data[14],
+
     whiteDragonElixirCoeff: 1 + whiteDragonElixir / 100,
     swordDefense: data[15],
+
     steelDragonElixirCoeff: 1 - steelDragonElixir / 100
     twoHandedSwordDefense: data[16],
 
     daggerDefense: data[17],
 
     bellDefense: data[18],
 
     fanDefense: data[19],
 
     arrowDefense: data[20],
 
     clawDefense: data[21],
 
     fireResistance: data[22],
 
     lightningResistance: data[23],
 
     windResistance: data[25],
 
     lightningBonus: data[26],
 
    fireBonus: data[27],
 
     iceBonus: data[28],
 
     windBonus: data[29],
 
     earthBonus: data[30],
 
     darknessBonus: data[31],
 
     darknessResistance: data[32],
 
     iceResistance: data[33],
 
     earthResistance: data[34],
 
     damageMultiplier: data[35],
 
 
   };
 
   };
  
   return monster;
+
   criticalHitPercentage = Math.min(criticalHitPercentage, 100);
}
+
  piercingHitPercentage = Math.min(piercingHitPercentage, 100);
  
function createBattle(characters, battle) {
+
  battleValues.damagesTypeCombinaison = [
  function isPseudoSaved(pseudo) {
+
    {
    return characters.savedCharacters.hasOwnProperty(pseudo);
+
      criticalHit: false,
  }
+
      piercingHit: false,
 
+
      weight: (100 - criticalHitPercentage) * (100 - piercingHitPercentage),
  battle.battleForm.addEventListener("submit", function (event) {
+
      name: "Coup classique",
    event.preventDefault();
+
    },
 
+
    {
    var battleInfo = new FormData(event.target);
+
      criticalHit: true,
    var attackerName = battleInfo.get("attacker");
+
      piercingHit: false,
    var attackType = battleInfo.get("attackTypeSelection");
+
      weight: criticalHitPercentage * (100 - piercingHitPercentage),
    var victimName = battleInfo.get("victim");
+
      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 updateBattleValues(battleValues, skillInfo, attackerWeapon) {
 +
  var weaponBonus = 0;
 +
  var skillWard = 0;
 +
  var skillBonus = 0;
 +
  var skillBonusByBonus = 0;
  
    if (!attackerName && !attackType && !victimName) {
+
  if (skillInfo.hasOwnProperty("weaponBonus")) {
      return;
+
    var [weaponType, weaponBonusValue] = skillInfo.weaponBonus;
    }
 
  
     if (isPseudoSaved(attackerName)) {
+
     if (weaponType === attackerWeapon[1]) {
      var attacker = copyObject(characters.savedCharacters[attackerName]);
+
       weaponBonus = weaponBonusValue;
    } else {
 
       var attacker = createMonster(attackerName);
 
 
     }
 
     }
 +
  }
  
    if (isPseudoSaved(victimName)) {
+
  if (skillInfo.skillBonus) {
      var victim = copyObject(characters.savedCharacters[victimName]);
+
    skillBonus = skillInfo.skillBonus;
    } else {
+
  }
      var victim = createMonster(victimName);
 
    }
 
  
     var meanDamages, minMaxDamages;
+
  if (skillInfo.skillWard) {
 +
     skillWard = skillInfo.skillWard;
 +
  }
  
    if (attackType === "physical") {
+
  if (skillInfo.skillBonusByBonus) {
      [meanDamages, minMaxDamages] = calcPhysicalDamages(
+
     skillBonusByBonus = skillInfo.skillBonusByBonus;
        attacker,
+
  }
        victim,
 
        battle.tableResult,
 
        battle.mapping,
 
        battle.constants.polymorphPowerTable,
 
        battle.constants.marriageTable
 
      );
 
     } else if (attackType.startsWith("skill")) {
 
      var skillId = parseInt(attackType.split("-")[1]);
 
      [meanDamages, minMaxDamages] = calcSkillDamages(
 
        attacker,
 
        victim,
 
        battle.tableResult,
 
        battle.mapping,
 
        battle.constants.skillPowerTable,
 
        skillId
 
      );
 
    }
 
  
     battle.damageResult.textContent =
+
  if (skillInfo.removeWeaponReduction) {
      attacker.name +
+
     battleValues.weaponDefenseCoeff = 1;
      " inflige " +
+
  }
      numberFormat(meanDamages, 1) +
 
      " dégâts en moyenne à " +
 
      victim.name +
 
      " (minimum : " +
 
      minMaxDamages.min +
 
      ", maximum : " +
 
      minMaxDamages.max +
 
      ").";
 
  
    showElement(battle.tableContainer);
+
  battleValues.weaponBonusCoeff = 1 + weaponBonus / 100;
   });
+
   battleValues.skillWardCoeff = 1 - skillWard / 100;
 +
  battleValues.skillBonusCoeff = 1 + skillBonus / 100;
 +
  battleValues.skillBonusByBonusCoeff = 1 + skillBonusByBonus / 100;
 +
}
  
   battle.attackerSelection.addEventListener("change", function (event) {
+
function calcPhysicalDamages(
    var name = event.target.value;
+
  attacker,
     var attackTypeSelection = battle.attackTypeSelection;
+
   attackerWeapon,
 
+
  victim,
    if (isPseudoSaved(name)) {
+
  tableResult,
      //pass
+
  mapping,
    } else {
+
  constants
      var optionsLength = attackTypeSelection.options.length;
+
) {
 +
  var battleValues = createPhysicalBattleValues(
 +
    attacker,
 +
    attackerWeapon,
 +
    victim,
 +
    mapping,
 +
    constants.polymorphPowerTable,
 +
    constants.marriageTable,
 +
     constants.skillPowerTable
 +
  );
 +
 
 +
  var sumDamages = 0;
 +
  var minMaxDamages = { min: Infinity, max: 0 };
 +
  clearTableResult(tableResult);
  
      if (optionsLength <= 1) {
+
  var attackFactor = calcAttackFactor(attacker, victim);
        return;
+
  var mainAttackValue = calcMainAttackValue(attacker, attackerWeapon);
      }
+
  var [
 +
    minAttackValue,
 +
    maxAttackValue,
 +
    attackValueOther,
 +
    minInterval,
 +
    totalCardinal,
 +
  ] = calcSecondaryAttackValue(attacker, attackerWeapon);
  
      for (
+
  totalCardinal *= 100;
        var optionIndex = optionsLength - 1;
 
        optionIndex >= 2;
 
        optionIndex--
 
      ) {
 
        attackTypeSelection.remove(optionIndex);
 
      }
 
    }
 
  });
 
}
 
  
function createMapping() {
+
  if (battleValues.missPercentage) {
  mapping = {
+
     addRowToTableResult(tableResult, "Miss");
     typeFlag: [
+
    addToTableResult(tableResult, { 0: battleValues.missPercentage / 100 });
      "animalBonus", // 0
+
  }
      "humanBonus", // 1
+
 
      "orcBonus", // 2
+
  var lastWeightsLimit = maxAttackValue - minInterval + 1;
      "mysticBonus", // 3
+
  var firstWeightLimit = minAttackValue + minInterval - 1;
      "undeadBonus", // 4
+
 
      "insectBonus", // 5
+
  for (var damagesType of battleValues.damagesTypeCombinaison) {
       "desertBonus", // 6
+
    if (!damagesType.weight) {
       "devilBonus", // 7
+
       0;
     ],
+
       continue;
     raceBonus: {
+
     }
      warrior: "warriorBonus",
+
 
      sura: "suraBonus",
+
     var damagesWeighted = {};
       ninja: "ninjaBonus",
+
    addRowToTableResult(tableResult, damagesType.name);
       shaman: "shamanBonus",
+
 
       lycan: "lycanBonus",
+
    for (
     },
+
       var attackValue = minAttackValue;
    raceResistance: {
+
       attackValue <= maxAttackValue;
       warrior: "warriorResistance",
+
       attackValue++
       sura: "suraResistance",
+
     ) {
       ninja: "ninjaResistance",
+
       var weight;
      shaman: "shamanResistance",
+
 
       lycan: "lycanResistance",
+
       if (attackValue > lastWeightsLimit) {
    },
+
        weight = maxAttackValue - attackValue + 1;
    defenseWeapon: [
+
       } else if (attackValue < firstWeightLimit) {
       "swordDefense", // 0
+
        weight = attackValue - minAttackValue + 1;
      "daggerDefense", // 1
+
       } else {
       "arrowDefense", // 2
+
        weight = minInterval;
       "twoHandedSwordDefense", // 3
+
       }
       "bellDefense", // 4
+
 
      "clawDefense", // 5
+
       var secondaryAttackValue = 2 * attackValue + attackValueOther;
       "fanDefense", // 6
+
       var rawDamages =
      "swordDefense", // 7
+
        mainAttackValue +
       "fistDefense", // 8
+
        floorMultiplication(attackFactor, secondaryAttackValue);
    ],
+
       var damagesWithPrimaryBonuses = calcDamageWithPrimaryBonuses(
    breakWeapon: [
+
        rawDamages,
      "breakSwordDefense", // 0
+
        battleValues
      "breakDaggerDefense", // 1
+
       );
       "breakArrowDefense", // 2
+
 
      "breakTwoHandedSwordDefense", // 3
+
       var minPiercingDamages =
      "breakBellDefense", // 4
+
        damagesWithPrimaryBonuses -
      "breakClawDefense", // 5
+
        battleValues.defense +
      "breakFanDefense", // 6
+
        battleValues.defenseMarriage;
      "breakSwordDefense", // 7
+
 
    ],
+
       if (minPiercingDamages <= 2) {
    elementBonus: [
+
        for (var damages = 1; damages <= 5; damages++) {
      "fireBonus", // 0
+
          var finalDamages = calcDamageWithSecondaryBonuses(
      "iceBonus", // 1
+
            damages,
      "windBonus", // 2
+
            battleValues,
      "lightningBonus", // 3
+
            damagesType,
      "earthBonus", // 4
+
            minPiercingDamages,
      "darknessBonus", // 5
+
            damagesWithPrimaryBonuses
    ],
+
          );
    elementResistance: [
+
 
      "fireResistance", // 0
+
          addKeyValue(
       "iceResistance", // 1
+
            damagesWeighted,
      "windResistance", // 2
+
            finalDamages,
      "lightningResistance", // 3
+
            (weight * damagesType.weight) / (5 * totalCardinal)
      "earthResistance", // 4
+
          );
      "darknessResistance", // 5
+
          sumDamages += (finalDamages * weight * damagesType.weight) / 5;
    ],
+
        }
  };
+
       } else {
  return mapping;
+
        var finalDamages = calcDamageWithSecondaryBonuses(
}
+
          minPiercingDamages,
 +
          battleValues,
 +
          damagesType,
 +
          minPiercingDamages,
 +
          damagesWithPrimaryBonuses
 +
        );
  
function createConstants() {
+
        addKeyValue(
  var constants = {
+
          damagesWeighted,
    polymorphPowerTable: [
+
          finalDamages,
      10, 11, 11, 12, 13, 13, 14, 15, 16, 17, 18, 19, 20, 22, 23, 24, 26, 27,
+
          (weight * damagesType.weight) / totalCardinal
      29, 31, 33, 35, 37, 39, 41, 44, 46, 49, 52, 55, 59, 62, 66, 70, 74, 79,
+
        );
      84, 89, 94, 100, 0,
+
        sumDamages += finalDamages * weight * damagesType.weight;
    ],
+
       }
    skillPowerTable: [
+
     }
      0, 0.05, 0.06, 0.08, 0.1, 0.12, 0.14, 0.16, 0.18, 0.2, 0.22, 0.24, 0.26,
+
 
      0.28, 0.3, 0.32, 0.34, 0.36, 0.38, 0.4, 0.5, 0.52, 0.54, 0.56, 0.58, 0.6,
+
     addToTableResult(tableResult, damagesWeighted, minMaxDamages);
      0.63, 0.66, 0.69, 0.72, 0.82, 0.85, 0.88, 0.91, 0.94, 0.98, 1.02, 1.06,
+
  }
       1.1, 1.15, 1.25,
+
 
     ],
+
  if (minMaxDamages.min === Infinity) {
     marriageTable: {
+
     minMaxDamages.min = 0;
      harmonyEarrings: [4, 5, 6, 8],
+
   }
      loveEarrings: [4, 5, 6, 8],
+
 
      harmonyBracelet: [4, 5, 6, 8],
+
   return [sumDamages / totalCardinal, minMaxDamages];
      loveNecklace: [20, 25, 30, 40],
 
      harmonyNecklace: [12, 16, 20, 30],
 
     },
 
   };
 
   return constants;
 
 
}
 
}
  
function createDamageCalculatorInformation() {
+
function calcBlessingBonus(skillPowerTable, victim) {
   var characters = {
+
  if (victim.isBlessed !== "on") {
     unsavedChanges: false,
+
    return 0;
     savedCharacters: {},
+
  }
     currentCharacter: null,
+
 
     characterCreation: document.getElementById("character-creation"),
+
  var int = victim.intBlessing;
     addNewCharacterButton: document.getElementById("add-new-character"),
+
  var dex = victim.dexBlessing;
     uploadCharacter: document.getElementById("upload-character"),
+
  var skillPower = getSkillPower(victim["skillBlessing"], skillPowerTable);
    newCharacterTemplate: document.getElementById("new-character-template")
+
 
      .children[0],
+
  if (!skillPower) {
     charactersContainer: document.getElementById("characters-container"),
+
    return 0;
     newMonsterTemplate: document.getElementById("new-monster-template")
+
  }
 +
 
 +
  var blessingBonus = floorMultiplication(
 +
    ((int * 0.3 + 5) * (2 * skillPower + 0.5) + 0.3 * dex) / (skillPower + 2.3),
 +
    1
 +
  );
 +
 
 +
  if (victim.class === "dragon" && victim.blessingOnself === "on") {
 +
    blessingBonus = floorMultiplication(blessingBonus, 1.1);
 +
  }
 +
 
 +
  return blessingBonus;
 +
}
 +
 
 +
function getSkillFormula(
 +
  skillPowerTable,
 +
  skillId,
 +
  attacker,
 +
  attackFactor,
 +
  victim
 +
) {
 +
  var skillFormula;
 +
  var skillInfo = {};
 +
 
 +
  var attackerClass = attacker.class;
 +
  var lv = attacker.level;
 +
  var vit = attacker.vit;
 +
  var str = attacker.str;
 +
  var int = attacker.int;
 +
  var dex = attacker.dex;
 +
 
 +
  var skillPower = getSkillPower(
 +
    attacker["attackSkill" + skillId],
 +
    skillPowerTable
 +
  );
 +
 
 +
  var improvedBySkillBonus = false;
 +
  var improvedByBonus = false;
 +
 
 +
  if (attackerClass === "body") {
 +
    switch (skillId) {
 +
      // Triple lacération
 +
      case 1:
 +
        skillFormula = function (atk) {
 +
          return floorMultiplication(
 +
            1.1 * atk + (0.5 * atk + 1.5 * str) * skillPower,
 +
            1
 +
          );
 +
        };
 +
        improvedByBonus = true;
 +
        break;
 +
      // Moulinet à l'épée
 +
      case 2:
 +
        skillFormula = function (atk) {
 +
          return floorMultiplication(
 +
            3 * atk + (0.8 * atk + 5 * str + 3 * dex + vit) * skillPower,
 +
            1
 +
          );
 +
        };
 +
        improvedByBonus = true;
 +
        improvedBySkillBonus = true;
 +
        break;
 +
      // Accélération
 +
      case 5:
 +
        skillFormula = function (atk) {
 +
          return floorMultiplication(
 +
            2 * atk + (atk + dex * 3 + str * 7 + vit) * skillPower,
 +
            1
 +
          );
 +
        };
 +
        improvedByBonus = true;
 +
        break;
 +
      // Volonté de vivre
 +
      case 6:
 +
        skillFormula = function (atk) {
 +
          return floorMultiplication(
 +
            (3 * atk + (atk + 1.5 * str) * skillPower) * 1.07,
 +
            1
 +
          );
 +
        };
 +
        break;
 +
      case 9:
 +
        skillFormula = function (atk) {
 +
          return floorMultiplication(
 +
            3 * atk + (0.9 * atk + 500.5 + 5 * str + 3 * dex + lv) * skillPower,
 +
            1
 +
          );
 +
        };
 +
        break;
 +
    }
 +
  } else if (attackerClass === "mental") {
 +
    switch (skillId) {
 +
      // Attaque de l'esprit
 +
      case 1:
 +
        skillFormula = function (atk) {
 +
          return floorMultiplication(
 +
            2.3 * atk + (4 * atk + 4 * str + vit) * skillPower,
 +
            1
 +
          );
 +
        };
 +
        improvedByBonus = true;
 +
        improvedBySkillBonus = true;
 +
        break;
 +
      // Attaque de la paume
 +
      case 2:
 +
        skillFormula = function (atk) {
 +
          return floorMultiplication(
 +
            2.3 * atk + (3 * atk + 4 * str + 3 * vit) * skillPower,
 +
            1
 +
          );
 +
        };
 +
        improvedByBonus = true;
 +
        break;
 +
      // Charge
 +
      case 3:
 +
        skillFormula = function (atk) {
 +
          return floorMultiplication(
 +
            2 * atk + (2 * atk + 2 * dex + 2 * vit + 4 * str) * skillPower,
 +
            1
 +
          );
 +
        };
 +
        break;
 +
      // Coup d'épée
 +
      case 5:
 +
        skillFormula = function (atk) {
 +
          return floorMultiplication(
 +
            2 * atk + (atk + 3 * dex + 5 * str + vit) * skillPower,
 +
            1
 +
          );
 +
        };
 +
        improvedByBonus = true;
 +
        break;
 +
      // Orbe de l'épée
 +
      case 6:
 +
        skillFormula = function (atk) {
 +
          return floorMultiplication(
 +
            (2 * atk + (2 * atk + 2 * dex + 2 * vit + 4 * str) * skillPower) *
 +
              1.1,
 +
            1
 +
          );
 +
        };
 +
        break;
 +
      // Tremblement de terre
 +
      case 9:
 +
        skillFormula = function (atk) {
 +
          return floorMultiplication(
 +
            3 * atk + (0.9 * atk + 500.5 + 5 * str + 3 * dex + lv) * skillPower,
 +
            1
 +
          );
 +
        };
 +
        break;
 +
    }
 +
  } else if (attackerClass === "blade_fight") {
 +
    switch (skillId) {
 +
      // Embuscade
 +
      case 1:
 +
        skillFormula = function (atk) {
 +
          return floorMultiplication(
 +
            atk + (1.2 * atk + 600 + 4 * dex + 4 * str) * skillPower,
 +
            1
 +
          );
 +
        };
 +
        skillInfo.weaponBonus = [1, 50];
 +
        improvedByBonus = true;
 +
        improvedBySkillBonus = true;
 +
        break;
 +
      // Attaque rapide
 +
      case 2:
 +
        skillFormula = function (atk) {
 +
          return floorMultiplication(
 +
            atk + (1.6 * atk + 250 + 7 * dex + 7 * str) * skillPower,
 +
            1
 +
          );
 +
        };
 +
        skillInfo.weaponBonus = [1, 35];
 +
        improvedByBonus = true;
 +
        break;
 +
      // Dague filante
 +
      case 3:
 +
        skillFormula = function (atk) {
 +
          return floorMultiplication(
 +
            2 * atk + (0.5 * atk + 9 * dex + 7 * str) * skillPower,
 +
            1
 +
          );
 +
        };
 +
        improvedByBonus = true;
 +
        break;
 +
      // Brume empoisonnée
 +
      case 5:
 +
        skillFormula = function (atk) {
 +
          return floorMultiplication(
 +
            2 * lv + (atk + 3 * str + 18 * dex) * skillPower,
 +
            1
 +
          );
 +
        };
 +
        improvedByBonus = true;
 +
        break;
 +
      // Poison insidieux
 +
      case 6:
 +
        skillFormula = function (atk) {
 +
          return floorMultiplication(
 +
            (2 * lv + (atk + 3 * str + 18 * dex) * skillPower) * 1.1,
 +
            1
 +
          );
 +
        };
 +
        break;
 +
      // Étoiles brillantes
 +
      case 9:
 +
        skillFormula = function (atk) {
 +
          return floorMultiplication(
 +
            atk + (1.7 * atk + 500.5 + 6 * dex + 5 * lv) * skillPower,
 +
            1
 +
          );
 +
        };
 +
        break;
 +
    }
 +
  } else if (attackerClass === "archery") {
 +
    switch (skillId) {
 +
      // Tir à répétition
 +
      // case 1:
 +
      //  skillFormula = function (atk) {
 +
      //    return floorMultiplication(
 +
      //      atk + 0.2 * atk * Math.floor(2 + 6 * skillPower) + (0.8 * atk + 8 * dex * attackFactor + 2 * int) * skillPower,
 +
      //      1
 +
      //    );
 +
      //  };
 +
      //  improvedByBonus = true;
 +
      //  break;
 +
      // Pluie de flèches
 +
      case 2:
 +
        skillFormula = function (atk) {
 +
          return floorMultiplication(
 +
            atk + (1.7 * atk + 5 * dex + str) * skillPower,
 +
            1
 +
          );
 +
        };
 +
        improvedByBonus = true;
 +
        break;
 +
      // Flèche de feu
 +
      case 3:
 +
        skillFormula = function (atk) {
 +
          return floorMultiplication(
 +
            1.5 * atk + (2.6 * atk + 0.9 * int + 200) * skillPower,
 +
            1
 +
          );
 +
        };
 +
        improvedByBonus = true;
 +
        improvedBySkillBonus = true;
 +
        break;
 +
      // Foulée de plume
 +
      case 4:
 +
        skillFormula = function (atk) {
 +
          return floorMultiplication(
 +
            (3 * dex + 200 + 2 * str + 2 * int) * skillPower,
 +
            1
 +
          );
 +
        };
 +
        skillInfo.removeWeaponReduction = true;
 +
        break;
 +
      // Flèche empoisonnée
 +
      case 5:
 +
        skillFormula = function (atk) {
 +
          return floorMultiplication(
 +
            atk + (1.4 * atk + 150 + 7 * dex + 4 * str + 4 * int) * skillPower,
 +
            1
 +
          );
 +
        };
 +
        improvedByBonus = true;
 +
        break;
 +
      // Coup étincelant
 +
      case 6:
 +
        skillFormula = function (atk) {
 +
          return floorMultiplication(
 +
            (atk +
 +
              (1.2 * atk + 150 + 6 * dex + 3 * str + 3 * int) * skillPower) *
 +
              1.2,
 +
            1
 +
          );
 +
        };
 +
        improvedByBonus = true;
 +
        break;
 +
      // Tir tempête
 +
      case 9:
 +
        skillFormula = function (atk) {
 +
          return floorMultiplication(
 +
            1.9 * atk + (2.6 * atk + 500.5) * skillPower,
 +
            1
 +
          );
 +
        };
 +
        break;
 +
    }
 +
  } else if (attackerClass === "weaponary") {
 +
    switch (skillId) {
 +
      // Toucher brûlant
 +
      case 1:
 +
        skillFormula = function (atk) {
 +
          return floorMultiplication(
 +
            atk +
 +
              2 * lv +
 +
              2 * int +
 +
              (2 * atk + 4 * str + 14 * int) * skillPower,
 +
            1
 +
          );
 +
        };
 +
        improvedByBonus = true;
 +
        improvedBySkillBonus = true;
 +
        break;
 +
      // Tourbillon du dragon
 +
      case 2:
 +
        skillFormula = function (atk) {
 +
          return floorMultiplication(
 +
            1.1 * atk +
 +
              2 * lv +
 +
              2 * int +
 +
              (1.5 * atk + str + 12 * int) * skillPower,
 +
            1
 +
          );
 +
        };
 +
        improvedByBonus = true;
 +
        break;
 +
    }
 +
  } else if (attackerClass === "black_magic") {
 +
    switch (skillId) {
 +
      // Attaque des ténèbres
 +
      case 1:
 +
        skillFormula = function (mav) {
 +
          return floorMultiplication(
 +
            40 +
 +
              5 * lv +
 +
              2 * int +
 +
              (13 * int + 6 * mav + 75) * attackFactor * skillPower,
 +
            1
 +
          );
 +
        };
 +
        improvedByBonus = true;
 +
        improvedBySkillBonus = true;
 +
        break;
 +
      // Attaque de flammes
 +
      // case 2:
 +
      //  skillFormula = function (mav) {
 +
      //    return floorMultiplication(
 +
      //      5 * lv + 2 * int + (7 * int + 8 * mav + 4 * str + 2 * vit + 190) * skillPower,
 +
      //      1
 +
      //    );
 +
      //  };
 +
      //  improvedByBonus = true;
 +
      //  break;
 +
      // Esprit de flammes
 +
      case 3:
 +
        skillFormula = function (mav) {
 +
          return floorMultiplication(
 +
            30 +
 +
              2 * lv +
 +
              2 * int +
 +
              (7 * int + 6 * mav + 350) * attackFactor * skillPower,
 +
            1
 +
          );
 +
        };
 +
        break;
 +
      // Frappe de l'esprit
 +
      // case 5:
 +
      //  skillFormula = function (mav) {
 +
      //    return floorMultiplication(
 +
      //      40 + 2 * lv + 2 * int + (2 * vit + 2 * dex + 13 * int + 6 * mav + 190) * attackFactor * skillPower,
 +
      //      1
 +
      //    );
 +
      //  };
 +
      //  break;
 +
      // Orbe des ténèbres
 +
      case 6:
 +
        skillFormula = function (mav) {
 +
          return floorMultiplication(
 +
            120 +
 +
              6 * lv +
 +
              (5 * vit + 5 * dex + 29 * int + 9 * mav) *
 +
                attackFactor *
 +
                skillPower,
 +
            1
 +
          );
 +
        };
 +
        improvedByBonus = true;
 +
        break;
 +
    }
 +
  } else if (attackerClass === "dragon") {
 +
    switch (skillId) {
 +
      // Talisman volant
 +
      case 1:
 +
        skillFormula = function (mav) {
 +
          return floorMultiplication(
 +
            70 +
 +
              5 * lv +
 +
              (18 * int + 7 * str + 5 * mav + 50) * attackFactor * skillPower,
 +
            1
 +
          );
 +
        };
 +
        skillInfo.weaponBonus = [4, 10];
 +
        improvedByBonus = true;
 +
        break;
 +
      // Dragon chassant
 +
      case 2:
 +
        skillFormula = function (mav) {
 +
          return floorMultiplication(
 +
            60 +
 +
              5 * lv +
 +
              (16 * int + 6 * dex + 6 * mav + 120) * attackFactor * skillPower,
 +
            1
 +
          );
 +
        };
 +
        skillInfo.weaponBonus = [4, 10];
 +
        improvedByBonus = true;
 +
        improvedBySkillBonus = true;
 +
        break;
 +
      // Rugissement du dragon
 +
      case 3:
 +
        skillFormula = function (mav) {
 +
          return floorMultiplication(
 +
            70 +
 +
              3 * lv +
 +
              (20 * int + 3 * str + 10 * mav + 100) * attackFactor * skillPower,
 +
            1
 +
          );
 +
        };
 +
        skillInfo.weaponBonus = [4, 10];
 +
        improvedByBonus = true;
 +
        break;
 +
    }
 +
  } else if (attackerClass === "heal") {
 +
    switch (skillId) {
 +
      // Jet de foudre
 +
      case 1:
 +
        skillFormula = function (mav) {
 +
          return floorMultiplication(
 +
            60 +
 +
              5 * lv +
 +
              (8 * int + 2 * dex + 8 * mav + 10 * int) *
 +
                attackFactor *
 +
                skillPower,
 +
            1
 +
          );
 +
        };
 +
        skillInfo.weaponBonus = [6, 10];
 +
        improvedByBonus = true;
 +
        break;
 +
      // Invocation de foudre
 +
      case 2:
 +
        skillFormula = function (mav) {
 +
          return floorMultiplication(
 +
            40 +
 +
              4 * lv +
 +
              (13 * int + 2 * str + 10 * mav + 10.5 * int) *
 +
                attackFactor *
 +
                skillPower,
 +
            1
 +
          );
 +
        };
 +
        skillInfo.weaponBonus = [6, 10];
 +
        improvedByBonus = true;
 +
        improvedBySkillBonus = true;
 +
        break;
 +
      // Griffe de foudre
 +
      case 3:
 +
        skillFormula = function (mav) {
 +
          return floorMultiplication(
 +
            50 +
 +
              5 * lv +
 +
              (8 * int + 2 * str + 8 * mav + 400.5) * attackFactor * skillPower,
 +
            1
 +
          );
 +
        };
 +
        improvedByBonus = true;
 +
        break;
 +
    }
 +
  } else if (attackerClass === "lycan") {
 +
    switch (skillId) {
 +
      // Déchiqueter
 +
      // case 1:
 +
      //  skillFormula = function (atk) {
 +
      //    return floorMultiplication(
 +
      //      1.1 * atk + (0.3 * atk + 1.5 * str) * skillPower,
 +
      //      1
 +
      //    );
 +
      //  };
 +
      //  skillInfo.weaponBonus = [5, 54];
 +
      //  improvedByBonus = true;
 +
      //  break;
 +
      // Souffle de loup
 +
      case 2:
 +
        skillFormula = function (atk) {
 +
          return floorMultiplication(
 +
            2 * atk + (atk + 3 * dex + 5 * str + vit) * skillPower,
 +
            1
 +
          );
 +
        };
 +
        skillInfo.weaponBonus = [5, 35];
 +
        improvedByBonus = true;
 +
        improvedBySkillBonus = true;
 +
        break;
 +
      // Bond de loup
 +
      case 3:
 +
        skillFormula = function (atk) {
 +
          return floorMultiplication(
 +
            atk + (1.6 * atk + 200 + 7 * dex + 7 * str) * skillPower,
 +
            1
 +
          );
 +
        };
 +
        skillInfo.weaponBonus = [5, 35];
 +
        improvedByBonus = true;
 +
        break;
 +
      // Griffe de loup
 +
      case 4:
 +
        skillFormula = function (atk) {
 +
          return floorMultiplication(
 +
            3 * atk + (0.8 * atk + 6 * str + 2 * dex + vit) * skillPower,
 +
            1
 +
          );
 +
        };
 +
        improvedByBonus = true;
 +
        break;
 +
    }
 +
  }
 +
 
 +
  if (improvedBySkillBonus) {
 +
    skillInfo.skillBonus =
 +
      16 * getSkillPower(attacker.skillBonus, skillPowerTable);
 +
 
 +
    var skillWardChoice = victim.skillWardChoice;
 +
 
 +
    if (skillWardChoice && skillWardChoice === attackerClass) {
 +
      skillInfo.skillWard =
 +
        24 * getSkillPower(victim.skillWard, skillPowerTable);
 +
    }
 +
  }
 +
 
 +
  if (improvedByBonus) {
 +
    skillInfo.skillBonusByBonus = attacker["skillBonus" + skillId];
 +
  }
 +
 
 +
  return [skillFormula, skillInfo];
 +
}
 +
 
 +
function calcPhysicalSkillDamages(
 +
  attacker,
 +
  attackerWeapon,
 +
  victim,
 +
  tableResult,
 +
  mapping,
 +
  constants,
 +
  skillId
 +
) {
 +
  var battleValues = createSkillBattleValues(
 +
    attacker,
 +
    attackerWeapon,
 +
    victim,
 +
    mapping,
 +
    constants.marriageTable
 +
  );
 +
 
 +
  var sumDamages = 0;
 +
  var minMaxDamages = { min: Infinity, max: 0 };
 +
  clearTableResult(tableResult);
 +
 
 +
  var attackFactor = calcAttackFactor(attacker, victim);
 +
  var mainAttackValue = calcMainAttackValue(attacker, attackerWeapon);
 +
  var [
 +
    minAttackValue,
 +
    maxAttackValue,
 +
    attackValueOther,
 +
    minInterval,
 +
    totalCardinal,
 +
  ] = calcSecondaryAttackValue(attacker, attackerWeapon);
 +
 
 +
  var lastWeightsLimit = maxAttackValue - minInterval + 1;
 +
  var firstWeightLimit = minAttackValue + minInterval - 1;
 +
 
 +
  var [skillFormula, skillInfo] = getSkillFormula(
 +
    constants.skillPowerTable,
 +
    skillId,
 +
    attacker,
 +
    attackFactor,
 +
    victim
 +
  );
 +
 
 +
  updateBattleValues(battleValues, skillInfo, attackerWeapon);
 +
 
 +
  for (var damagesType of battleValues.damagesTypeCombinaison) {
 +
    if (!damagesType.weight) {
 +
      continue;
 +
    }
 +
 
 +
    var damagesWeighted = {};
 +
    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 = minInterval;
 +
      }
 +
 
 +
      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++) {
 +
          damages *= battleValues.useDamages;
 +
 
 +
          var damagesWithFormula = skillFormula(damages);
 +
 
 +
          damagesWithFormula = floorMultiplication(
 +
            damagesWithFormula,
 +
            battleValues.weaponBonusCoeff
 +
          );
 +
 
 +
          var finalDamages = calcSkillDamageWithSecondaryBonuses(
 +
            damagesWithFormula,
 +
            battleValues,
 +
            damagesType,
 +
            damagesWithPrimaryBonuses
 +
          );
 +
 
 +
          addKeyValue(
 +
            damagesWeighted,
 +
            finalDamages,
 +
            (weight * damagesType.weight) / (5 * totalCardinal)
 +
          );
 +
          sumDamages += (finalDamages * weight * damagesType.weight) / 5;
 +
        }
 +
      } else {
 +
        damagesWithPrimaryBonuses *= battleValues.useDamages;
 +
 
 +
        var damagesWithFormula = skillFormula(damagesWithPrimaryBonuses);
 +
 
 +
        damagesWithFormula = floorMultiplication(
 +
          damagesWithFormula,
 +
          battleValues.weaponBonusCoeff
 +
        );
 +
 
 +
        var finalDamages = calcSkillDamageWithSecondaryBonuses(
 +
          damagesWithFormula,
 +
          battleValues,
 +
          damagesType,
 +
          damagesWithPrimaryBonuses
 +
        );
 +
 
 +
        addKeyValue(
 +
          damagesWeighted,
 +
          finalDamages,
 +
          (weight * damagesType.weight) / totalCardinal
 +
        );
 +
        sumDamages += finalDamages * weight * damagesType.weight;
 +
      }
 +
    }
 +
 
 +
    addToTableResult(tableResult, damagesWeighted, minMaxDamages);
 +
  }
 +
 
 +
  if (minMaxDamages.min === Infinity) {
 +
    minMaxDamages.min = 0;
 +
  }
 +
 
 +
  return [sumDamages / totalCardinal, minMaxDamages];
 +
}
 +
 
 +
function calcMagicSkillDamages(
 +
  attacker,
 +
  attackerWeapon,
 +
  victim,
 +
  tableResult,
 +
  mapping,
 +
  constants,
 +
  skillId
 +
) {
 +
  var battleValues = createSkillBattleValues(
 +
    attacker,
 +
    attackerWeapon,
 +
    victim,
 +
    mapping,
 +
    constants.marriageTable,
 +
    true
 +
  );
 +
 
 +
  var sumDamages = 0;
 +
  var minMaxDamages = { min: Infinity, max: 0 };
 +
  clearTableResult(tableResult);
 +
 
 +
  var attackFactor = calcAttackFactor(attacker, victim);
 +
  var [minMagicAttackValue, maxMagicAttackValue, minInterval, totalCardinal] =
 +
    calcMagicAttackValue(attacker, attackerWeapon);
 +
 
 +
  var lastWeightsLimit = maxMagicAttackValue - minInterval + 1;
 +
  var firstWeightLimit = minMagicAttackValue + minInterval - 1;
 +
 
 +
  var [skillFormula, skillInfo] = getSkillFormula(
 +
    constants.skillPowerTable,
 +
    skillId,
 +
    attacker,
 +
    attackFactor,
 +
    victim
 +
  );
 +
 
 +
  updateBattleValues(battleValues, skillInfo, attackerWeapon);
 +
 
 +
  for (var damagesType of battleValues.damagesTypeCombinaison) {
 +
    if (!damagesType.weight) {
 +
      continue;
 +
    }
 +
 
 +
    var damagesWeighted = {};
 +
    addRowToTableResult(tableResult, damagesType.name);
 +
 
 +
    for (
 +
      var magicAttackValue = minMagicAttackValue;
 +
      magicAttackValue <= maxMagicAttackValue;
 +
      magicAttackValue++
 +
    ) {
 +
      var weight;
 +
 
 +
      if (magicAttackValue > lastWeightsLimit) {
 +
        weight = maxMagicAttackValue - magicAttackValue + 1;
 +
      } else if (magicAttackValue < firstWeightLimit) {
 +
        weight = magicAttackValue - minMagicAttackValue + 1;
 +
      } else {
 +
        weight = minInterval;
 +
      }
 +
 
 +
      var rawDamages = skillFormula(magicAttackValue);
 +
 
 +
      rawDamages = floorMultiplication(
 +
        rawDamages,
 +
        battleValues.weaponBonusCoeff
 +
      );
 +
 
 +
      var damagesWithPrimaryBonuses = calcDamageWithPrimaryBonuses(
 +
        rawDamages,
 +
        battleValues
 +
      );
 +
 
 +
      if (damagesWithPrimaryBonuses <= 2) {
 +
        for (var damages = 1; damages <= 5; damages++) {
 +
          var finalDamages = calcSkillDamageWithSecondaryBonuses(
 +
            damages,
 +
            battleValues,
 +
            damagesType,
 +
            damagesWithPrimaryBonuses,
 +
            skillFormula
 +
          );
 +
 
 +
          addKeyValue(
 +
            damagesWeighted,
 +
            finalDamages,
 +
            (weight * damagesType.weight) / (5 * totalCardinal)
 +
          );
 +
          sumDamages += (finalDamages * weight * damagesType.weight) / 5;
 +
        }
 +
      } else {
 +
        var finalDamages = calcSkillDamageWithSecondaryBonuses(
 +
          damagesWithPrimaryBonuses,
 +
          battleValues,
 +
          damagesType,
 +
          damagesWithPrimaryBonuses,
 +
          skillFormula
 +
        );
 +
 
 +
        addKeyValue(
 +
          damagesWeighted,
 +
          finalDamages,
 +
          (weight * damagesType.weight) / totalCardinal
 +
        );
 +
        sumDamages += finalDamages * weight * damagesType.weight;
 +
      }
 +
    }
 +
 
 +
    addToTableResult(tableResult, damagesWeighted, minMaxDamages);
 +
  }
 +
 
 +
  if (minMaxDamages.min === Infinity) {
 +
    minMaxDamages.min = 0;
 +
  }
 +
 
 +
  return [sumDamages / totalCardinal, minMaxDamages];
 +
}
 +
 
 +
function changeMonsterValues(monster, instance, attacker) {
 +
  switch (instance) {
 +
    case "SungMahiTower":
 +
      var sungMahiFloor = 1;
 +
      var sungMahiStep = 1;
 +
      var rawDefense = 120;
 +
 
 +
      if (isPC(attacker)) {
 +
        sungMahiFloor = attacker.sungMahiFloor;
 +
        sungMahiStep = attacker.sungMahiStep;
 +
      }
 +
 
 +
      if (monster.rank === 5) {
 +
        monster.level = 121;
 +
        monster.dex = 75;
 +
        rawDefense += 1;
 +
      } else if (monster.rank === 6) {
 +
        monster.level = 123;
 +
        monster.dex = 75;
 +
        rawDefense += 1;
 +
      } else {
 +
        monster.level = 120;
 +
        monster.dex = 68;
 +
      }
 +
      monster.vit = 100;
 +
      monster.rawDefense = rawDefense + (sungMahiStep - 1) * 6;
 +
      monster.fistDefense = 0;
 +
      monster.swordDefense = 0;
 +
      monster.twoHandedSwordDefense = 0;
 +
      monster.daggerDefense = 0;
 +
      monster.bellDefense = 0;
 +
      monster.fanDefense = 0;
 +
      monster.arrowDefense = 0;
 +
      monster.clawDefense = 0;
 +
      monster.magicResistance = 0;
 +
      monster.fireResistance = -20;
 +
  }
 +
}
 +
 
 +
function createMonster(monsterVnum, attacker) {
 +
  var monsterAttributes = monsterData[monsterVnum];
 +
 
 +
  var monster = {
 +
    name: monsterAttributes[36],
 +
    rank: monsterAttributes[0],
 +
    race: monsterAttributes[1],
 +
    attack: monsterAttributes[2],
 +
    level: monsterAttributes[3],
 +
    type: monsterAttributes[4],
 +
    str: monsterAttributes[5],
 +
    dex: monsterAttributes[6],
 +
    vit: monsterAttributes[7],
 +
    int: monsterAttributes[8],
 +
    minAttackValue: monsterAttributes[9],
 +
    maxAttackValue: monsterAttributes[10],
 +
    rawDefense: monsterAttributes[11],
 +
    criticalHit: monsterAttributes[12],
 +
    piercingHit: monsterAttributes[13],
 +
    fistDefense: monsterAttributes[14],
 +
    swordDefense: monsterAttributes[15],
 +
    twoHandedSwordDefense: monsterAttributes[16],
 +
    daggerDefense: monsterAttributes[17],
 +
    bellDefense: monsterAttributes[18],
 +
    fanDefense: monsterAttributes[19],
 +
    arrowDefense: monsterAttributes[20],
 +
    clawDefense: monsterAttributes[21],
 +
    fireResistance: monsterAttributes[22],
 +
    lightningResistance: monsterAttributes[23],
 +
    magicResistance: monsterAttributes[24],
 +
    windResistance: monsterAttributes[25],
 +
    lightningBonus: monsterAttributes[26],
 +
    fireBonus: monsterAttributes[27],
 +
    iceBonus: monsterAttributes[28],
 +
    windBonus: monsterAttributes[29],
 +
    earthBonus: monsterAttributes[30],
 +
    darknessBonus: monsterAttributes[31],
 +
    darknessResistance: monsterAttributes[32],
 +
    iceResistance: monsterAttributes[33],
 +
    earthResistance: monsterAttributes[34],
 +
    damageMultiplier: monsterAttributes[35],
 +
  };
 +
 
 +
  // monster.instance = 0;
 +
 
 +
  // if (attacker && monster.instance === 0) {
 +
  //  changeMonsterValues(monster, "SungMahiTower", attacker);
 +
  // }
 +
 
 +
  monster.defense = monster.rawDefense + monster.level + monster.vit;
 +
 
 +
  return monster;
 +
}
 +
 
 +
function addPotentialErrorInformation(
 +
  errorInformation,
 +
  attacker,
 +
  victim,
 +
  characters
 +
) {
 +
  for (var error of Object.values(errorInformation)) {
 +
    hideElement(error);
 +
  }
 +
 
 +
  if (isPC(attacker)) {
 +
    if (isRiding(attacker)) {
 +
      if (attacker.horsePoint === 0) {
 +
        showElement(errorInformation["horse-level"]);
 +
      }
 +
      showElement(errorInformation["horse-stat"]);
 +
    } else if (isPolymorph(attacker)) {
 +
      if (attacker.polymorphPoint === 0) {
 +
        showElement(errorInformation["polymorph-level"]);
 +
      }
 +
 
 +
      if (
 +
        (attacker.polymorphPoint <= 39 && attacker.attackValuePercent <= 199) ||
 +
        (attacker.polymorphPoint === 40 && attacker.attackValuePercent <= 299)
 +
      ) {
 +
        showElement(errorInformation["polymorph-bonus"]);
 +
      }
 +
    }
 +
  } else {
 +
    showElement(errorInformation["monster-attacker"]);
 +
  }
 +
 
 +
  if (isPC(victim)) {
 +
    if (isRiding(victim)) {
 +
      showElement(errorInformation["horse-stat"]);
 +
    } else if (isPolymorph(victim)) {
 +
      if (attacker.polymorphPoint === 0) {
 +
        showElement(errorInformation["polymorph-level"]);
 +
      }
 +
      showElement(errorInformation["polymorph-defense"]);
 +
    }
 +
  }
 +
 
 +
  if (characters.unsavedChanges) {
 +
    showElement(errorInformation["save"]);
 +
  }
 +
}
 +
 
 +
function createBattle(characters, battle) {
 +
  function isPseudoSaved(pseudo) {
 +
    return characters.savedCharacters.hasOwnProperty(pseudo);
 +
  }
 +
 
 +
  battle.battleForm.addEventListener("submit", function (event) {
 +
    event.preventDefault();
 +
 
 +
    // auto save
 +
    if (characters.unsavedChanges) {
 +
      characters.saveButton.click();
 +
    }
 +
 
 +
    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;
 +
    }
 +
 
 +
    var attackerWeapon = null;
 +
 
 +
    if (isPseudoSaved(attackerName)) {
 +
      var attacker = copyObject(characters.savedCharacters[attackerName]);
 +
 
 +
      if (weaponData.hasOwnProperty(attacker.weapon)) {
 +
        attackerWeapon = weaponData[attacker.weapon];
 +
      } else {
 +
        attackerWeapon = weaponData[0];
 +
      }
 +
    } else {
 +
      var attacker = createMonster(attackerName);
 +
    }
 +
 
 +
    if (isPseudoSaved(victimName)) {
 +
      var victim = copyObject(characters.savedCharacters[victimName]);
 +
    } else {
 +
      var victim = createMonster(victimName, attacker);
 +
    }
 +
 
 +
    var meanDamages, minMaxDamages;
 +
    var calcDamages;
 +
    var skillId = 0;
 +
 
 +
    if (attackType === "physical") {
 +
      calcDamages = calcPhysicalDamages;
 +
    } else if (attackType.startsWith("attackSkill")) {
 +
      skillId = parseInt(attackType.split("attackSkill")[1]);
 +
 
 +
      if (isMagicClass(attacker)) {
 +
        calcDamages = calcMagicSkillDamages;
 +
      } else {
 +
        calcDamages = calcPhysicalSkillDamages;
 +
      }
 +
    }
 +
 
 +
    [meanDamages, minMaxDamages] = calcDamages(
 +
      attacker,
 +
      attackerWeapon,
 +
      victim,
 +
      battle.tableResult,
 +
      battle.mapping,
 +
      battle.constants,
 +
      skillId
 +
    );
 +
 
 +
    battle.damageResult.textContent =
 +
      attacker.name +
 +
      " inflige " +
 +
      numberDisplay(meanDamages, 1) +
 +
      " dégâts en moyenne à " +
 +
      victim.name +
 +
      " (minimum : " +
 +
      minMaxDamages.min +
 +
      ", maximum : " +
 +
      minMaxDamages.max +
 +
      ").";
 +
 
 +
    addPotentialErrorInformation(
 +
      battle.errorInformation,
 +
      attacker,
 +
      victim,
 +
      characters
 +
    );
 +
    showElement(battle.tableContainer);
 +
  });
 +
 
 +
  battle.attackerSelection.addEventListener("change", function (event) {
 +
    var attackerName = event.target.value;
 +
    var attackTypeSelection = battle.attackTypeSelection;
 +
 
 +
    if (isPseudoSaved(attackerName)) {
 +
      var attacker = characters.savedCharacters[attackerName];
 +
      filterAttackTypeSelection(attacker, attackTypeSelection);
 +
    } else {
 +
      for (var option of attackTypeSelection.options) {
 +
        if (option.dataset.class) {
 +
          hideElement(option);
 +
        }
 +
      }
 +
 
 +
      if (attackTypeSelection.selectedIndex !== 1) {
 +
        attackTypeSelection.selectedIndex = 0;
 +
      }
 +
    }
 +
  });
 +
}
 +
 
 +
function createMapping() {
 +
  mapping = {
 +
    typeFlag: [
 +
      "animalBonus", // 0
 +
      "humanBonus", // 1
 +
      "orcBonus", // 2
 +
      "mysticBonus", // 3
 +
      "undeadBonus", // 4
 +
      "insectBonus", // 5
 +
      "desertBonus", // 6
 +
      "devilBonus", // 7
 +
    ],
 +
    raceBonus: {
 +
      warrior: "warriorBonus",
 +
      sura: "suraBonus",
 +
      ninja: "ninjaBonus",
 +
      shaman: "shamanBonus",
 +
      lycan: "lycanBonus",
 +
    },
 +
    raceResistance: {
 +
      warrior: "warriorResistance",
 +
      sura: "suraResistance",
 +
      ninja: "ninjaResistance",
 +
      shaman: "shamanResistance",
 +
      lycan: "lycanResistance",
 +
    },
 +
    defenseWeapon: [
 +
      "swordDefense", // 0
 +
      "daggerDefense", // 1
 +
      "arrowDefense", // 2
 +
      "twoHandedSwordDefense", // 3
 +
      "bellDefense", // 4
 +
      "clawDefense", // 5
 +
      "fanDefense", // 6
 +
      "swordDefense", // 7
 +
      "fistDefense", // 8
 +
    ],
 +
    breakWeapon: [
 +
      "breakSwordDefense", // 0
 +
      "breakDaggerDefense", // 1
 +
      "breakArrowDefense", // 2
 +
      "breakTwoHandedSwordDefense", // 3
 +
      "breakBellDefense", // 4
 +
      "breakClawDefense", // 5
 +
      "breakFanDefense", // 6
 +
      "breakSwordDefense", // 7
 +
    ],
 +
    elementBonus: [
 +
      "fireBonus", // 0
 +
      "iceBonus", // 1
 +
      "windBonus", // 2
 +
      "lightningBonus", // 3
 +
      "earthBonus", // 4
 +
      "darknessBonus", // 5
 +
    ],
 +
    elementResistance: [
 +
      "fireResistance", // 0
 +
      "iceResistance", // 1
 +
      "windResistance", // 2
 +
      "lightningResistance", // 3
 +
      "earthResistance", // 4
 +
      "darknessResistance", // 5
 +
    ],
 +
  };
 +
  return mapping;
 +
}
 +
 
 +
function createConstants() {
 +
  var constants = {
 +
    polymorphPowerTable: [
 +
      10, 11, 11, 12, 13, 13, 14, 15, 16, 17, 18, 19, 20, 22, 23, 24, 26, 27,
 +
      29, 31, 33, 35, 37, 39, 41, 44, 46, 49, 52, 55, 59, 62, 66, 70, 74, 79,
 +
      84, 89, 94, 100, 0,
 +
    ],
 +
    skillPowerTable: [
 +
      0, 0.05, 0.06, 0.08, 0.1, 0.12, 0.14, 0.16, 0.18, 0.2, 0.22, 0.24, 0.26,
 +
      0.28, 0.3, 0.32, 0.34, 0.36, 0.38, 0.4, 0.5, 0.52, 0.54, 0.56, 0.58, 0.6,
 +
      0.63, 0.66, 0.69, 0.72, 0.82, 0.85, 0.88, 0.91, 0.94, 0.98, 1.02, 1.06,
 +
      1.1, 1.15, 1.25,
 +
    ],
 +
    marriageTable: {
 +
      harmonyEarrings: [4, 5, 6, 8],
 +
      loveEarrings: [4, 5, 6, 8],
 +
      harmonyBracelet: [4, 5, 6, 8],
 +
      loveNecklace: [20, 25, 30, 40],
 +
      harmonyNecklace: [12, 16, 20, 30],
 +
    },
 +
  };
 +
  return constants;
 +
}
 +
 
 +
function createDamageCalculatorInformation() {
 +
   var characters = {
 +
     unsavedChanges: false,
 +
     savedCharacters: {},
 +
     currentCharacter: null,
 +
     characterCreation: document.getElementById("character-creation"),
 +
     addNewCharacterButton: document.getElementById("add-new-character"),
 +
     dropZone: document.getElementById("character-drop-zone"),
 +
     characterInput: document.getElementById("character-input"),
 +
     newCharacterTemplate: document.getElementById("new-character-template")
 
       .children[0],
 
       .children[0],
     monstersContainer: document.getElementById("monsters-container"),
+
    charactersContainer: document.getElementById("characters-container"),
     monsterListForm: document.getElementById("monster-list-form"),
+
    newMonsterTemplate: document.getElementById("new-monster-template")
     searchMonster: document.getElementById("search-monster"),
+
      .children[0],
     monsterList: document.getElementById("monster-list"),
+
     monstersContainer: document.getElementById("monsters-container"),
     saveButton: document.getElementById("save-button"),
+
     monsterListForm: document.getElementById("monster-list-form"),
     classChoice: document.getElementById("class-choice"),
+
     searchMonster: document.getElementById("search-monster"),
     stateChoice: document.getElementById("state-choice"),
+
     monsterList: document.getElementById("monster-list"),
     polymorphMonster: document.getElementById("polymorph-monster"),
+
     saveButton: document.getElementById("save-character"),
     weaponCategory: document.getElementById("weapon-category"),
+
     weaponCategory: document.getElementById("weapon-category"),
     weaponDisplay: document.getElementById("weapon-display"),
+
     weaponDisplay: document.getElementById("weapon-display"),
     weaponUpgrade: document.getElementById("upgrade-choice"),
+
     randomAttackValue: document.getElementById("random-attack-value"),
     randomAttackValue: document.getElementById("random-attack-value"),
+
     randomMagicAttackValue: document.getElementById(
     randomMagicAttackValue: document.getElementById(
+
      "random-magic-attack-value"
      "random-magic-attack-value"
+
    ),
     ),
+
     yoharaCreation: document.getElementById("yohara-creation"),
     lowRankCheckbox: document.getElementById("low-rank"),
+
     blessingCreation: document.getElementById("blessing-creation"),
     playerRankChoice: document.getElementById("player-rank"),
+
     marriageCreation: document.getElementById("marriage-creation"),
 +
  };
 +
 
 +
  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 skillContainer = document.getElementById("skill-container");
 +
  characters.skillElementsToFilter =
 +
    skillContainer.querySelectorAll("[data-class]");
 +
 
 +
  var mapping = createMapping();
 +
  var constants = createConstants();
 +
 
 +
  var battle = {
 +
    resetAttackType: false,
 +
    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"),
 +
     errorInformation: {},
 +
    tableContainer: document.getElementById("result-table-container"),
 +
     tableResult: document.getElementById("result-table").children[0],
 +
    mapping: mapping,
 +
    constants: constants,
 
   };
 
   };
  characters.race = characters.characterCreation.race;
 
  characters.weapon = characters.characterCreation.weapon;
 
 
  delete characters.newCharacterTemplate.dataset.click;
 
  
   var savedCharacters = getSavedCharacters();
+
   var errorElements = document
  var savedMonsters = getSavedMonsters();
+
    .getElementById("error-information")
 +
    .querySelectorAll("li[data-error]");
  
   for (var [pseudo, character] of Object.entries(savedCharacters)) {
+
   for (var index = 0; index < errorElements.length; index++) {
     characters.savedCharacters[pseudo] = character;
+
     var errorElement = errorElements[index];
 +
    battle.errorInformation[errorElement.dataset.error] = errorElement;
 
   }
 
   }
 
  characters.savedMonsters = savedMonsters;
 
 
  var mapping = createMapping();
 
  var constants = createConstants();
 
 
  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,
 
    constants: constants,
 
  };
 
  
 
   return [characters, battle];
 
   return [characters, battle];

Version actuelle datée du 18 mai 2024 à 20:23

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 compareNumbers(a, b) {
  return b - a;
}

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

function floorMultiplicationWithNegative(firstFactor, secondFactor) {
  if (secondFactor < 0) {
    return -floorMultiplication(firstFactor, -secondFactor);
  } else {
    return floorMultiplication(firstFactor, secondFactor);
  }
}

function numberDisplay(number, precision) {
  return (Math.round(number * 10 ** precision) / 10 ** precision)
    .toString()
    .replace(".", ",");
}

function addKeyValue(object, key, value) {
  if (object.hasOwnProperty(key)) {
    object[key] += value;
  } else {
    object[key] = value;
  }
}

function addRowToTableResult(tableResult, value) {
  var newRow = tableResult.insertRow(-1);
  var firstCell = newRow.insertCell(0);

  firstCell.textContent = value;
  firstCell.colSpan = 2;

  newRow.style.fontWeight = "bold";
}

function addToTableResult(tableResult, damagesWeighted, minMaxDamages) {
  var firstIteration = true;

  for (var damages in damagesWeighted) {
    if (firstIteration && minMaxDamages) {
      damages = parseInt(damages);
      if (damages < minMaxDamages.min) {
        minMaxDamages.min = damages;
      }
      firstIteration = false;
    }

    var newRow = tableResult.insertRow(-1);
    var firstCell = newRow.insertCell(0);

    firstCell.textContent = damages;

    var secondCell = newRow.insertCell(1);
    secondCell.textContent =
      numberDisplay(damagesWeighted[damages] * 100, 3) + " %";
  }

  damages = parseInt(damages);
  if (minMaxDamages && damages > minMaxDamages.max) {
    minMaxDamages.max = damages;
  }
}

function clearTableResult(tableResult) {
  var tableHeaderRowCount = 1;
  var rowCount = tableResult.rows.length;

  for (var rowIndex = tableHeaderRowCount; rowIndex < rowCount; rowIndex++) {
    tableResult.deleteRow(tableHeaderRowCount);
  }
}

function getMonsterName(monsterVnum) {
  var monsterAttributes = monsterData[monsterVnum];
  return monsterAttributes[monsterAttributes.length - 1];
}

function filterClass(selectedRace, classChoice, selectValueIsChanged = false) {
  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);
    }
  }
  if (selectedRace == "lycan") {
    hideElement(classChoice.parentElement);
  }
}

function filterWeapon(
  selectedRace,
  weapon,
  weaponCategory,
  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[selectedRace];

  if (!selectValueIsChanged) {
    var weaponType = weaponData[weapon.value][1];

    if (!isValueInArray(weaponType, allowedWeapons)) {
      weapon.value = 0;
    }
  }

  var children = weaponCategory.children;

  for (var index = 0; index < children.length; index++) {
    var child = children[index];

    if (isValueInArray(index, allowedWeapons)) {
      showElement(child);
    } else {
      hideElement(child);
    }
  }
}

function getSelectedWeapon(weaponCategory) {
  return weaponCategory.querySelector("input[type='radio']:checked");
}

function handleWeaponDisplay(weaponDisplay, newWeapon, weaponValue) {
  var newImage = newWeapon.nextElementSibling.cloneNode();
  var newText = document.createElement("span");
  var oldImage = weaponDisplay.firstChild;
  var oldText = oldImage.nextElementSibling;
  var weaponName = weaponData[weaponValue][0];

  if (weaponValue == 0) {
    newText.textContent = " " + weaponName + " ";
  } else {
    var weaponLink = document.createElement("a");
    weaponLink.href = mw.util.getUrl(weaponName);
    weaponLink.title = weaponName;
    weaponLink.textContent = weaponName;

    newText.appendChild(document.createTextNode(" "));
    newText.appendChild(weaponLink);
    newText.appendChild(document.createTextNode(" "));
  }

  weaponDisplay.replaceChild(newImage, oldImage);
  weaponDisplay.replaceChild(newText, oldText);
}

function filterUpgrade(
  selectedRace,
  weaponUpgrade,
  weaponValue,
  randomAttackValue,
  randomMagicAttackValue,
  currentUpgrade
) {
  var weapon = weaponData[weaponValue];

  if (isValueInArray("serpent", weapon[0].toLowerCase())) {
    showElement(randomAttackValue);

    if (selectedRace === "sura" || selectedRace === "shaman") {
      showElement(randomMagicAttackValue);
    }
  } else {
    hideElement(randomAttackValue);
    hideElement(randomMagicAttackValue);
  }

  var upgradeNumber = weapon[3].length;

  if (upgradeNumber <= 1) {
    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 filterState(selectedState, polymorphMonster) {
  if (selectedState === "polymorph") {
    showElement(polymorphMonster.parentElement);
  } else {
    hideElement(polymorphMonster.parentElement);
  }
}

function filterCheckbox(checkbox, element) {
  if (checkbox.checked) {
    showElement(element);
  } else {
    hideElement(element);
  }
}

function filterSkills(selectedClass, skillElementsToFilter) {
  for (var element of skillElementsToFilter) {
    if (isValueInArray(selectedClass, element.dataset.class)) {
      showElement(element);
    } else {
      hideElement(element);
    }
  }
}

function filterAttackTypeSelection(attacker, attackTypeSelection) {
  var attackerClass = attacker.class;
  var selectedOption =
    attackTypeSelection.options[attackTypeSelection.selectedIndex];

  for (var option of attackTypeSelection.options) {
    optionClass = option.dataset.class;

    if (optionClass) {
      if (
        optionClass === attackerClass &&
        attacker[option.value] &&
        attacker.state !== "polymorph"
      ) {
        showElement(option);
      } else {
        hideElement(option);

        if (selectedOption === option) {
          attackTypeSelection.selectedIndex = 0;
        }
      }
    }
  }
}

function filterForm(characters, battle) {
  var characterCreation = characters.characterCreation;

  characterCreation.addEventListener("change", function (event) {
    var target = event.target;
    var targetName = target.name;

    switch (targetName) {
      case "race":
        var selectedRace = target.value;
        var classChoice = characterCreation.class;
        var weapon = characterCreation.weapon;

        filterClass(selectedRace, classChoice);
        filterWeapon(selectedRace, weapon, characters.weaponCategory);

        var newWeapon = getSelectedWeapon(characters.weaponCategory);
        handleWeaponDisplay(characters.weaponDisplay, newWeapon, weapon.value);
        filterUpgrade(
          selectedRace,
          characterCreation.weaponUpgrade,
          weapon.value,
          characters.randomAttackValue,
          characters.randomMagicAttackValue
        );
        filterSkills(classChoice.value, characters.skillElementsToFilter);

        if (characterCreation.name.value === battle.attackerSelection.value) {
          battle.resetAttackType = true;
        }
        break;
      case "class":
        filterSkills(target.value, characters.skillElementsToFilter);

        if (characterCreation.name.value === battle.attackerSelection.value) {
          battle.resetAttackType = true;
        }
        break;
      case "weapon":
        handleWeaponDisplay(
          characters.weaponDisplay,
          target,
          characterCreation.weapon.value
        );
        filterUpgrade(
          characterCreation.race.value,
          characterCreation.weaponUpgrade,
          target.value,
          characters.randomAttackValue,
          characters.randomMagicAttackValue
        );
        break;
      case "state":
        filterState(target.value, characterCreation.polymorphMonster);
        if (characterCreation.name.value === battle.attackerSelection.value) {
          battle.resetAttackType = true;
        }
        break;
      case "lowRank":
        filterCheckbox(target, characterCreation.playerRank.parentElement);
        break;
      case "isBlessed":
        filterCheckbox(target, characters.blessingCreation);
        break;
      case "onYohara":
        filterCheckbox(target, characters.yoharaCreation);
        break;
      case "isMarried":
        filterCheckbox(target, characters.marriageCreation);
        break;
    }

    if (targetName.startsWith("attackSkill")) {
      battle.resetAttackType = true;
    }
  });
}

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

  if (battle.resetAttackType) {
    filterAttackTypeSelection(characterDataObject, battle.attackTypeSelection);
    battle.resetAttackType = false;
  }
}

function saveButtonGreen(saveButton) {
  saveButton.classList.remove("unsaved-character");
}

function saveButtonOrange(saveButton) {
  saveButton.classList.add("unsaved-character");
}

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

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

  document.addEventListener("keydown", function (event) {
    if (event.ctrlKey && event.key === "s") {
      event.preventDefault();
      characters.saveButton.click();
    }
  });
}

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(
  selectedFiles,
  characters,
  characterTemplate,
  charactersContainer,
  battle
) {
  var selectFilesLength = selectedFiles.length;

  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) {
            hideElement(characters.characterCreation);
            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 handleUploadCharacter(
  characters,
  characterTemplate,
  charactersContainer,
  battle
) {
  var characterInput = characters.characterInput;
  var dropZone = characters.dropZone;

  characterInput.accept = ".txt";
  characterInput.multiple = true;
  dropZone.setAttribute("tabindex", "0");

  dropZone.addEventListener("click", function () {
    characterInput.click();
  });

  dropZone.addEventListener("dragover", function (event) {
    event.preventDefault();
    dropZone.classList.add("drop-zone--dragover");
  });

  ["dragleave", "dragend"].forEach(function (type) {
    dropZone.addEventListener(type, function () {
      dropZone.classList.remove("drop-zone--dragover");
    });
  });

  dropZone.addEventListener("drop", function (event) {
    event.preventDefault();
    uploadCharacter(
      event.dataTransfer.files,
      characters,
      characterTemplate,
      charactersContainer,
      battle
    );
    dropZone.classList.remove("drop-zone--dragover");
  });

  characterInput.addEventListener("change", function (event) {
    uploadCharacter(
      event.target.files,
      characters,
      characterTemplate,
      charactersContainer,
      battle
    );
  });
}

function deleteCharacter(characters, pseudo, element, battle) {
  battle.battleForm.reset();
  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.saveButton);
    characters.unsavedChanges = false;
    hideElement(characters.characterCreation);
    showElement(characters.characterCreation.previousElementSibling);
  }
}

function deleteMonster(characters, monsterVnum, element, battle) {
  battle.battleForm.reset();
  characters.savedMonsters.splice(
    characters.savedMonsters.indexOf(monsterVnum),
    1
  );

  if (element) {
    element.remove();
  }

  updateSavedMonsters(characters.savedMonsters);
  removeBattleChoice(battle, monsterVnum);
}

function handleStyle(characters, selectedElement) {
  var currentCharacter = characters.currentCharacter;

  if (currentCharacter) {
    currentCharacter.classList.remove("selected-character");
  }

  selectedElement.classList.add("selected-character");
  characters.currentCharacter = selectedElement;
}

function updateForm(formData, characterCreation, characters, selectedElement) {
  saveButtonGreen(characters.saveButton);
  hideElement(characterCreation.previousElementSibling);
  showElement(characterCreation);
  handleStyle(characters, selectedElement);

  characterCreation.reset();

  for (var [name, value] of Object.entries(formData)) {
    var formElement = characterCreation[name];

    if (!formElement) {
      continue;
    }

    if (formElement.type === "checkbox") {
      if (value === "on") {
        formElement.checked = true;
      }
    } else {
      formElement.value = value;
    }
  }
  var selectedRace = characterCreation.race.value;
  var classChoice = characterCreation.class;
  var weapon = characterCreation.weapon;

  filterClass(selectedRace, classChoice, true);
  filterWeapon(selectedRace, weapon, characters.weaponCategory, true);

  var newWeapon = getSelectedWeapon(characters.weaponCategory);

  handleWeaponDisplay(characters.weaponDisplay, newWeapon, weapon.value);
  filterUpgrade(
    selectedRace,
    characterCreation.weaponUpgrade,
    weapon.value,
    characters.randomAttackValue,
    characters.randomMagicAttackValue,
    formData.weaponUpgrade
  );
  filterState(
    characterCreation.state.value,
    characterCreation.polymorphMonster
  );
  filterCheckbox(
    characterCreation.lowRank,
    characterCreation.playerRank.parentElement
  );
  filterCheckbox(characterCreation.onYohara, characters.yoharaCreation);
  filterCheckbox(characterCreation.isBlessed, characters.blessingCreation);
  filterCheckbox(characterCreation.isMarried, characters.marriageCreation);
  filterSkills(classChoice.value, characters.skillElementsToFilter);
}

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.saveButton);
            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, characterElement, battle);
        }
        break;
    }
  }
}

function handleNewCharacter(
  characters,
  characterTemplate,
  charactersContainer,
  battle,
  pseudo
) {
  var newCharacterTemplate = characterTemplate.cloneNode(true);
  var spanInput = newCharacterTemplate.querySelector("span.input");

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

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) {
    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] = handleNewCharacter(
    characters,
    characterTemplate,
    charactersContainer,
    battle
  );

  editAndSetCharacterPseudoInput(selectedCharacter, spanInput);
}

function handleFocus() {
  var tooltipLinks = document.querySelectorAll("div.tooltip a");
  tooltipLinks.forEach(function (link) {
    link.setAttribute("tabindex", -1);
  });
}

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.saveButton);
        characters.unsavedChanges = false;
      }
    }
  });

  handleUploadCharacter(
    characters,
    characterTemplate,
    charactersContainer,
    battle
  );

  characters.characterCreation.addEventListener("change", function () {
    saveButtonOrange(characters.saveButton);
    characters.unsavedChanges = true;
  });

  filterForm(characters, battle);
  characterCreationListener(characters, battle);
  handleFocus();

  window.addEventListener("beforeunload", function (event) {
    if (characters.unsavedChanges) {
      event.preventDefault();
      event.returnValue = "";
      return "";
    }
  });
}

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

  var link = document.createElement("a");
  link.href = mw.util.getUrl(monsterName);
  link.title = monsterName;
  link.textContent = monsterName;

  spanInput.appendChild(link);
  monstersContainer.appendChild(newMonsterTemplate);

  newMonsterTemplate.setAttribute("tabindex", "0");
  newMonsterTemplate.setAttribute("data-name", monsterVnum);
  monstersContainer.appendChild(newMonsterTemplate);

  deleteSvg.addEventListener("click", function (event) {
    deleteMonster(characters, monsterVnum, newMonsterTemplate, battle);
    var inputMonster = monsterList.querySelector(
      "input[name='" + monsterVnum + "']"
    );
    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 lastMonsterAttributeIndex = monsterData[101].length - 1;

    for (var monsterVnum in monsterData) {
      var li = document.createElement("li");
      var label = document.createElement("label");
      var input = document.createElement("input");
      var textNode = document.createTextNode(
        monsterData[monsterVnum][lastMonsterAttributeIndex]
      );

      label.htmlFor = "monster" + monsterVnum;
      input.id = "monster" + monsterVnum;
      input.type = "checkbox";

      input.name = monsterVnum;

      label.appendChild(input);
      label.appendChild(textNode);
      li.appendChild(label);
      monsterList.appendChild(li);
    }
  }

  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;

  document
    .getElementById("monster-link")
    .querySelector("a")
    .setAttribute("target", "_blank");

  handleDropdown(searchMonster, monsterList);
  addMonsterNames(monsterList, characters.monsterListTemplate);
  filterNames(searchMonster, monsterList);

  characters.savedMonsters.slice().forEach(function (monsterVnum) {
    var inputMonster = monsterList.querySelector(
      "input[name='" + monsterVnum + "']"
    );

    if (inputMonster) {
      handleNewMonster(
        characters,
        monsterTemplate,
        monstersContainer,
        battle,
        monsterVnum,
        monsterList
      );
      inputMonster.checked = true;
    } else {
      deleteMonster(characters, monsterVnum, null, battle);
    }
  });

  monsterListForm.addEventListener("submit", function (event) {
    event.preventDefault();
  });

  monsterListForm.addEventListener("change", function (event) {
    var target = event.target;
    var monsterVnum = target.name;

    if (monsterVnum === "search-monster") {
      return;
    }

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

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

  addEventListener("storage", function (event) {
    if (event.key === "newMonsterCalculator") {
      var monsterVnum = Number(event.newValue);

      if (!monsterVnum) {
        return;
      }

      var inputMonster = monsterList.querySelector(
        "input[name='" + Math.abs(monsterVnum) + "']"
      );

      if (inputMonster) {
        if (
          (monsterVnum > 0 && !inputMonster.checked) ||
          (monsterVnum < 0 && inputMonster.checked)
        ) {
          inputMonster.click();
        }
      }
    }
  });
}

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, vnum) {
    var option = document.createElement("option");
    option.textContent = text;
    option.value = vnum;

    if (!isMonster) {
      option.classList.add("notranslate");
    }

    return option;
  }

  var vnum = name;

  if (isMonster) {
    name = getMonsterName(name);
  }

  if (isMonster && monsterData[vnum][1]) {
    // pass
  } else {
    battle.attackerSelection.appendChild(createOption(name, vnum));
  }

  battle.victimSelection.appendChild(createOption(name, vnum));
}

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

  for (var index = 0; index < keys.length; index++) {
    var pseudo = keys[index];
    addBattleChoice(battle, pseudo);
  }

  characters.savedMonsters.forEach(function (monsterVnum) {
    addBattleChoice(battle, monsterVnum, true);
  });
}

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

function isBoss(character) {
  return character.race === 0 && character.rank >= 5;
}

function isStone(character) {
  return character.race === 1;
}

function isMagicClass(character) {
  return character.race === "shaman" || character.class === "black_magic";
}

function isPolymorph(character) {
  return character.state === "polymorph";
}

function isRiding(character) {
  return character.state === "horse";
}

function isBow(weapon) {
  return weapon[1] === 2;
}

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

  var K1 = calcCoeffK(attacker.polymorphDex, attacker.level);
  var K2 = calcCoeffK(victim.polymorphDex, 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.weaponUpgrade];

    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 attackValueOther = 0;

  var minAttackValue = 0;
  var maxAttackValue = 0;

  var minAttackValueSlash = 0;
  var maxAttackValueSlash = 0;

  if (isPC(attacker)) {
    if (isValueInArray("serpent", attackerWeapon[0].toLowerCase())) {
      var rawAttackValue = attackerWeapon[3][attacker.weaponUpgrade];

      minAttackValue = attacker.minAttackValueRandom - rawAttackValue;
      maxAttackValue = attacker.maxAttackValueRandom - rawAttackValue;

      minAttackValue = Math.max(0, minAttackValue);
      maxAttackValue = Math.max(minAttackValue, maxAttackValue);
    } else {
      minAttackValue = attackerWeapon[2][2];
      maxAttackValue = attackerWeapon[2][3];
    }

    minAttackValueSlash = Math.min(
      attacker.minAttackValueSlash,
      attacker.maxAttackValueSlash
    );
    maxAttackValueSlash = Math.max(
      attacker.minAttackValueSlash,
      attacker.maxAttackValueSlash
    );

    attackValueOther += attacker.attackValue;

    if (isBow(attackerWeapon) && !isPolymorph(attacker)) {
      attackValueOther += 25;
    }
  } else {
    minAttackValue = attacker.minAttackValue;
    maxAttackValue = attacker.maxAttackValue;
  }

  minAttackValue += attacker.minAttackValuePolymorph;
  maxAttackValue += attacker.maxAttackValuePolymorph;

  attackValueOther += attacker.statAttackValue;
  attackValueOther += attacker.horseAttackValue;

  var weaponInterval = maxAttackValue - minAttackValue + 1;
  var slashInterval = maxAttackValueSlash - minAttackValueSlash + 1;

  var totalCardinal = weaponInterval * slashInterval * 10000;
  var minInterval = Math.min(weaponInterval, slashInterval);

  minAttackValue += minAttackValueSlash;
  maxAttackValue += maxAttackValueSlash;

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

function calcMagicAttackValue(attacker, attackerWeapon) {
  var minMagicAttackValue = 0;
  var maxMagicAttackValue = 0;

  var minMagicAttackValueSlash = 0;
  var maxMagicAttackValueSlash = 0;

  if (isValueInArray("serpent", attackerWeapon[0].toLowerCase())) {
    minMagicAttackValue = attacker.minMagicAttackValueRandom;
    maxMagicAttackValue = attacker.maxMagicAttackValueRandom;

    maxMagicAttackValue = Math.max(minMagicAttackValue, maxMagicAttackValue);
  } else {
    var rawWeaponAttackValue = attackerWeapon[3][attacker.weaponUpgrade];

    if (!rawWeaponAttackValue) {
      rawWeaponAttackValue = 0;
    }

    minMagicAttackValue = attackerWeapon[2][0] + rawWeaponAttackValue;
    maxMagicAttackValue = attackerWeapon[2][1] + rawWeaponAttackValue;
  }

  minMagicAttackValueSlash = Math.min(
    attacker.minMagicAttackValueSlash,
    attacker.maxMagicAttackValueSlash
  );
  maxMagicAttackValueSlash = Math.max(
    attacker.minMagicAttackValueSlash,
    attacker.maxMagicAttackValueSlash
  );

  var weaponInterval = maxMagicAttackValue - minMagicAttackValue + 1;
  var slashInterval = maxMagicAttackValueSlash - minMagicAttackValueSlash + 1;

  var totalCardinal = weaponInterval * slashInterval * 10000;
  var minInterval = Math.min(weaponInterval, slashInterval);

  minMagicAttackValue += minMagicAttackValueSlash;
  maxMagicAttackValue += maxMagicAttackValueSlash;

  return [minMagicAttackValue, maxMagicAttackValue, minInterval, totalCardinal];
}

function getPolymorphPower(polymorphPoint, polymorphPowerTable) {
  return polymorphPowerTable[polymorphPoint];
}

function getSkillPower(skillPoint, skillPowerTable) {
  return skillPowerTable[skillPoint];
}

function getMarriageBonusValue(character, marriageTable, itemName) {
  var index;
  var lovePoint = character.lovePoint;

  if (lovePoint < 65) {
    index = 0;
  } else if (lovePoint < 80) {
    index = 1;
  } else if (lovePoint < 100) {
    index = 2;
  } else {
    index = 3;
  }

  return marriageTable[itemName][index];
}

function calcDamageWithPrimaryBonuses(damages, battleValues) {
  damages = floorMultiplication(
    damages * battleValues.attackValueCoeff + battleValues.adjustCoeff,
    1
  );
  damages += battleValues.attackValueMarriage;
  damages = floorMultiplication(
    damages,
    battleValues.monsterResistanceMarriageCoeff
  );
  damages = floorMultiplication(damages, battleValues.monsterResistanceCoeff);
  damages = floorMultiplication(damages, battleValues.typeBonusCoeff);
  damages +=
    floorMultiplication(damages, battleValues.raceBonusCoeff) -
    floorMultiplication(damages, battleValues.raceResistanceCoeff);
  damages = floorMultiplication(damages, battleValues.stoneBonusCoeff);
  damages = floorMultiplication(damages, battleValues.monsterBonusCoeff);

  var elementDamages = 0;
  for (var elementBonusCoeff of battleValues.elementBonusCoeff) {
    elementDamages += floorMultiplicationWithNegative(
      damages,
      elementBonusCoeff
    );
  }
  damages += elementDamages;

  damages = floorMultiplication(damages, battleValues.damageMultiplier);

  return damages;
}

function calcDamageWithSecondaryBonuses(
  damages,
  battleValues,
  damagesType,
  minPiercingDamages,
  damagesWithPrimaryBonuses
) {
  damages = floorMultiplication(damages, battleValues.magicResistanceCoeff);
  damages = floorMultiplication(damages, battleValues.weaponDefenseCoeff);
  damages = floorMultiplication(damages, battleValues.tigerStrengthCoeff);
  damages = floorMultiplication(damages, battleValues.blessingBonusCoeff);

  if (damagesType.criticalHit) {
    damages *= 2;
  }

  if (damagesType.piercingHit) {
    damages += battleValues.defense + Math.min(0, minPiercingDamages);
    damages += floorMultiplication(
      damagesWithPrimaryBonuses,
      battleValues.extraPiercingHitCoeff
    );
  }

  damages = floorMultiplication(damages, battleValues.averageDamageCoeff);
  damages = floorMultiplication(
    damages,
    battleValues.averageDamageResistanceCoeff
  );
  damages = floorMultiplication(
    damages,
    battleValues.skillDamageResistanceCoeff
  );

  damages = floorMultiplication(damages, battleValues.rankBonusCoeff);
  damages = Math.max(0, damages + battleValues.defensePercent);
  damages += Math.min(
    300,
    floorMultiplication(damages, battleValues.damageBonusCoeff)
  );
  damages = floorMultiplication(damages, battleValues.empireMalusCoeff);
  damages = floorMultiplication(damages, battleValues.sungMaStrBonusCoeff);
  damages -= floorMultiplication(damages, battleValues.sungmaStrMalusCoeff);

  damages = floorMultiplication(damages, battleValues.whiteDragonElixirCoeff);
  damages = floorMultiplication(damages, battleValues.steelDragonElixirCoeff);

  return damages;
}

function calcSkillDamageWithSecondaryBonuses(
  damages,
  battleValues,
  damagesType,
  minPiercingDamages
) {
  damages = floorMultiplication(damages, battleValues.magicResistanceCoeff);
  damages = floorMultiplication(damages, battleValues.weaponDefenseCoeff);

  damages -= battleValues.defense;

  damages = floorMultiplication(damages, battleValues.skillWardCoeff);
  damages = floorMultiplication(damages, battleValues.skillBonusCoeff);
  damages = floorMultiplication(damages, battleValues.skillBonusByBonusCoeff);
  damages = floorMultiplication(damages, battleValues.tigerStrengthCoeff);

  if (damagesType.criticalHit) {
    damages *= 2;
  }

  if (damagesType.piercingHit) {
    damages +=
      battleValues.piercingHitDefense + Math.min(0, minPiercingDamages);
  }

  damages = floorMultiplication(damages, battleValues.skillDamageCoeff);
  damages = floorMultiplication(
    damages,
    battleValues.skillDamageResistanceCoeff
  );

  damages = floorMultiplication(damages, battleValues.rankBonusCoeff);
  damages = Math.max(0, damages + battleValues.defensePercent);
  damages += Math.min(
    300,
    floorMultiplication(damages, battleValues.damageBonusCoeff)
  );
  damages = floorMultiplication(damages, battleValues.empireMalusCoeff);
  damages = floorMultiplication(damages, battleValues.sungMaStrBonusCoeff);
  damages -= floorMultiplication(damages, battleValues.sungmaStrMalusCoeff);

  damages = floorMultiplication(damages, battleValues.whiteDragonElixirCoeff);
  damages = floorMultiplication(damages, battleValues.steelDragonElixirCoeff);

  return damages;
}

function computePolymorphPoint(attacker, victim, polymorphPowerTable) {
  attacker.statAttackValue = 0;

  attacker.polymorphDex = attacker.dex;
  victim.polymorphDex = victim.dex;

  attacker.minAttackValuePolymorph = 0;
  attacker.maxAttackValuePolymorph = 0;

  if (isPC(attacker) && isPolymorph(attacker) && polymorphPowerTable) {
    var polymorphPowerPct =
      getPolymorphPower(attacker.polymorphPoint, polymorphPowerTable) / 100;
    var polymorphMonster = createMonster(attacker.polymorphMonster);

    var polymorphStr = floorMultiplication(
      polymorphPowerPct,
      polymorphMonster.str
    );

    attacker.polymorphDex += floorMultiplication(
      polymorphPowerPct,
      polymorphMonster.dex
    );

    attacker.minAttackValuePolymorph = floorMultiplication(
      polymorphPowerPct,
      polymorphMonster.minAttackValue
    );
    attacker.maxAttackValuePolymorph = floorMultiplication(
      polymorphPowerPct,
      polymorphMonster.maxAttackValue
    );

    if (!attacker.weapon) {
      attacker.maxAttackValuePolymorph += 1;
    }

    attacker.attackValue = 0;

    if (isMagicClass(attacker)) {
      attacker.statAttackValue = 2 * (polymorphStr + attacker.int);
    } else {
      attacker.statAttackValue = 2 * (polymorphStr + attacker.str);
    }
  } else {
    attacker.statAttackValue = calcStatAttackValue(attacker);
  }
}

function computeHorse(attacker) {
  attacker.horseAttackValue = 0;

  if (isPC(attacker) && isRiding(attacker)) {
    var horseConstant = 30;

    if (attacker.class === "weaponary") {
      horseConstant = 60;
    }

    attacker.horseAttackValue = floorMultiplication(
      2 * attacker.level + attacker.statAttackValue,
      attacker.horsePoint / horseConstant
    );
  }
}

function getRankBonus(attacker) {
  if (attacker.lowRank !== "on") {
    return 0;
  }

  switch (attacker.playerRank) {
    case "aggressive":
      return 1;
    case "fraudulent":
      return 2;
    case "malicious":
      return 3;
    case "cruel":
      return 5;
  }

  return 0;
}

function calcElementCoeffPvP(elementBonus, mapping, attacker, victim) {
  var minElementMalus = 0;
  var maxDifference = 0;
  var savedElementDifferences = [];
  var elementBonusIndex = 0;

  for (var index = 0; index < elementBonus.length; index++) {
    if (!attacker[mapping.elementBonus[index]]) {
      continue;
    }

    var elementDifference =
      attacker[mapping.elementBonus[index]] -
      victim[mapping.elementResistance[index]];

    if (elementDifference >= 0) {
      elementBonus[elementBonusIndex] = elementDifference / 1000;
      minElementMalus -= elementDifference;
      maxDifference = Math.max(maxDifference, elementDifference);
      elementBonusIndex++;
    } else {
      savedElementDifferences.push(elementDifference);
    }
  }

  if (!savedElementDifferences.length) {
    return;
  }

  minElementMalus += maxDifference;
  savedElementDifferences.sort(compareNumbers);

  for (var index = 0; index < savedElementDifferences.length; index++) {
    var elementDifference = savedElementDifferences[index];

    elementBonus[elementBonusIndex + index] =
      Math.max(minElementMalus, elementDifference) / 1000;

    minElementMalus = Math.min(
      0,
      Math.max(minElementMalus, minElementMalus - elementDifference)
    );
  }
}

function skillChanceReduction(value) {
  if (value <= 9) {
    return Math.floor(value / 2);
  }
  return 5 + Math.floor((value - 10) / 4);
}

function magicResistanceToCoeff(magicResistance) {
  if (magicResistance) {
    return 2000 / (6 * magicResistance + 1000) - 1;
  }
  return 1;
}

function createPhysicalBattleValues(
  attacker,
  attackerWeapon,
  victim,
  mapping,
  polymorphPowerTable,
  marriageTable,
  skillPowerTable
) {
  var missPercentage = 0;
  var attackValuePercent = 0;
  var attackMeleeMagic = 0;
  var attackValueMarriage = 0;
  var monsterResistanceMarriage = 0;
  var monsterResistance = 0;
  var typeBonus = 0;
  var raceBonus = 0;
  var raceResistance = 0;
  var stoneBonus = 0;
  var monsterBonus = 0;
  var elementBonus = [0, 0, 0, 0, 0, 0]; // fire, ice, lightning, earth, darkness, wind, order doesn't matter
  var defenseMarriage = 0;
  var damageMultiplier = 1;
  var magicResistance = 0;
  var weaponDefense = 0;
  var tigerStrength = 0;
  var blessingBonus = 0;
  var criticalHitPercentage = attacker.criticalHit;
  var criticalHitPercentageMarriage = 0;
  var piercingHitPercentage = attacker.piercingHit;
  var piercingHitPercentageMarriage = 0;
  var extraPiercingHitPercentage = Math.max(0, piercingHitPercentage - 100);
  var averageDamage = 0;
  var averageDamageResistance = 0;
  var skillDamageResistance = 0;
  var rankBonus = 0;
  var defensePercent = 0;
  var damageBonus = 0;
  var empireMalus = 0;
  var sungMaStrBonus = 0;
  var sungmaStrMalus = 0;
  var whiteDragonElixir = 0;
  var steelDragonElixir = 0;

  computePolymorphPoint(attacker, victim, polymorphPowerTable);
  computeHorse(attacker);

  if (isPC(attacker)) {
    attackValuePercent = attacker.attackValuePercent;
    attackMeleeMagic = attacker.attackMeleeMagic;

    var weaponType = attackerWeapon[1];

    var weaponDefenseName = mapping.defenseWeapon[weaponType];
    var weaponDefenseBreakName = mapping.breakWeapon[weaponType];

    if (victim.hasOwnProperty(weaponDefenseName)) {
      weaponDefense = victim[weaponDefenseName];
    }

    if (attacker.whiteDragonElixir === "on") {
      whiteDragonElixir = 10;
    }

    if (isPC(victim)) {
      if (weaponType === 2 && !isPolymorph(attacker)) {
        missPercentage = victim.arrowBlock;
      } else {
        missPercentage = victim.meleeBlock;
      }

      missPercentage +=
        victim.meleeArrowBlock -
        (missPercentage * victim.meleeArrowBlock) / 100;

      typeBonus = Math.max(1, attacker.humanBonus - victim.humanResistance);
      raceBonus = attacker[mapping.raceBonus[victim.race]];
      raceResistance = victim[mapping.raceResistance[attacker.race]];

      calcElementCoeffPvP(elementBonus, mapping, attacker, victim);

      if (attacker.hasOwnProperty(weaponDefenseBreakName)) {
        weaponDefense -= attacker[weaponDefenseBreakName];
      }

      criticalHitPercentage = 0;
      blessingBonus = calcBlessingBonus(skillPowerTable, victim);
      averageDamageResistance = victim.averageDamageResistance;
    } else {
      if (attacker.isMarried === "on") {
        if (attacker.loveNecklace === "on") {
          attackValueMarriage = getMarriageBonusValue(
            attacker,
            marriageTable,
            "loveNecklace"
          );
        }

        if (attacker.loveEarrings === "on") {
          criticalHitPercentageMarriage = getMarriageBonusValue(
            attacker,
            marriageTable,
            "loveEarrings"
          );
        }

        if (attacker.harmonyEarrings === "on") {
          piercingHitPercentageMarriage = getMarriageBonusValue(
            attacker,
            marriageTable,
            "harmonyEarrings"
          );
        }
      }

      if (attacker.tigerStrength === "on") {
        tigerStrength = 40;
      }

      for (var index = 0; index < elementBonus.length; index++) {
        var elementBonusName = mapping.elementBonus[index];
        var elementResistanceName = mapping.elementResistance[index];

        if (attacker[elementBonusName] && victim[elementBonusName]) {
          elementBonus[index] =
            (attacker[elementBonusName] - victim[elementResistanceName]) / 200;
        } else {
          elementBonus[index] = attacker[elementBonusName] / 2000;
        }
      }

      var victimType = victim.type;

      if (victimType !== -1) {
        typeBonus = attacker[mapping.typeFlag[victimType]];
      }

      monsterBonus = attacker.monsterBonus;

      if (isStone(victim)) {
        stoneBonus = attacker.stoneBonus;
      }

      if (isBoss(victim)) {
        averageDamage += attacker.bossDamage;
      }

      if (attacker.onYohara === "on") {
        var sungmaStrDifference = attacker.sungmaStr - attacker.sungmaStrMalus;

        if (sungmaStrDifference >= 0) {
          sungMaStrBonus = sungmaStrDifference;
        } else {
          sungmaStrMalus = 0.5;
        }
      }
    }

    averageDamage += attacker.averageDamage;
    rankBonus = getRankBonus(attacker);
    damageBonus = attacker.damageBonus;

    if (attacker.empireMalus === "on") {
      empireMalus = 10;
    }
  } else {
    if (isPC(victim)) {
      if (victim.isMarried === "on") {
        if (victim.harmonyBracelet === "on") {
          monsterResistanceMarriage = getMarriageBonusValue(
            victim,
            marriageTable,
            "harmonyBracelet"
          );
        }

        if (victim.harmonyNecklace === "on") {
          defenseMarriage = getMarriageBonusValue(
            victim,
            marriageTable,
            "harmonyNecklace"
          );
        }
      }

      monsterResistance = victim.monsterResistance;

      for (var index = 0; index < elementBonus.length; index++) {
        var elementBonusName = mapping.elementBonus[index];
        var elementResistanceName = mapping.elementResistance[index];

        if (attacker[elementBonusName]) {
          elementBonus[index] =
            (attacker[elementBonusName] - victim[elementResistanceName]) / 125;
        }
      }

      if (attacker.attack == 0) {
        missPercentage = victim.meleeBlock;
        averageDamageResistance = victim.averageDamageResistance;
        blessingBonus = calcBlessingBonus(skillPowerTable, victim);
      } else if (attacker.attack == 1) {
        missPercentage = victim.arrowBlock;
        weaponDefense = victim.arrowDefense;
        averageDamageResistance = victim.averageDamageResistance;
        blessingBonus = calcBlessingBonus(skillPowerTable, victim);
      } else {
        missPercentage = victim.arrowBlock;
        skillDamageResistance = victim.skillDamageResistance;
        magicResistance = victim.magicResistance;
      }

      missPercentage +=
        victim.meleeArrowBlock -
        (missPercentage * victim.meleeArrowBlock) / 100;
    }

    typeBonus = 1;
    damageMultiplier = attacker.damageMultiplier;
  }

  if (isPC(victim)) {
    if (victim.biologist70 === "on") {
      victim.defense = floorMultiplication(victim.defense, 1.1);
    }
    criticalHitPercentage = Math.max(
      0,
      criticalHitPercentage - victim.criticalHitResistance
    );
    piercingHitPercentage = Math.max(
      0,
      piercingHitPercentage - victim.piercingHitResistance
    );

    if (isMagicClass(victim)) {
      defensePercent = (-2 * victim.magicDefense * victim.defensePercent) / 100;
    } else {
      defensePercent = (-2 * victim.defense * victim.defensePercent) / 100;
    }

    if (victim.steelDragonElixir === "on") {
      steelDragonElixir = 10;
    }
  }

  missPercentage = Math.min(100, missPercentage);

  var battleValues = {
    missPercentage: missPercentage,
    adjustCoeff: 0,
    attackValueCoeff:
      1 + (attackValuePercent + Math.min(100, attackMeleeMagic)) / 100,
    attackValueMarriage: attackValueMarriage,
    monsterResistanceMarriageCoeff: 1 - monsterResistanceMarriage / 100,
    monsterResistanceCoeff: 1 - monsterResistance / 100,
    typeBonusCoeff: 1 + typeBonus / 100,
    raceBonusCoeff: raceBonus / 100,
    raceResistanceCoeff: raceResistance / 100,
    monsterBonusCoeff: 1 + monsterBonus / 100,
    stoneBonusCoeff: 1 + stoneBonus / 100,
    elementBonusCoeff: elementBonus,
    damageMultiplier: damageMultiplier,
    defense: victim.defense,
    defenseMarriage: defenseMarriage,
    magicResistanceCoeff: magicResistanceToCoeff(magicResistance),
    weaponDefenseCoeff: 1 - weaponDefense / 100,
    tigerStrengthCoeff: 1 + tigerStrength / 100,
    blessingBonusCoeff: 1 - blessingBonus / 100,
    extraPiercingHitCoeff: extraPiercingHitPercentage / 200,
    averageDamageCoeff: 1 + averageDamage / 100,
    averageDamageResistanceCoeff:
      1 - Math.min(99, averageDamageResistance) / 100,
    skillDamageResistanceCoeff: 1 - Math.min(99, skillDamageResistance) / 100,
    rankBonusCoeff: 1 + rankBonus / 100,
    defensePercent: Math.floor(defensePercent),
    damageBonusCoeff: damageBonus / 100,
    empireMalusCoeff: 1 - empireMalus / 100,
    sungMaStrBonusCoeff: 1 + sungMaStrBonus / 10000,
    sungmaStrMalusCoeff: sungmaStrMalus,
    whiteDragonElixirCoeff: 1 + whiteDragonElixir / 100,
    steelDragonElixirCoeff: 1 - steelDragonElixir / 100
  };

  criticalHitPercentage = Math.min(
    criticalHitPercentage + criticalHitPercentageMarriage,
    100
  );
  piercingHitPercentage = Math.min(
    piercingHitPercentage + piercingHitPercentageMarriage,
    100
  );

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

  return battleValues;
}

function createSkillBattleValues(
  attacker,
  attackerWeapon,
  victim,
  mapping,
  marriageTable,
  magicSkill
) {
  var adjustCoeff = 0;
  var attackValuePercent = 0;
  var attackMeleeMagic = 0;
  var attackValueMarriage = 0;
  var monsterResistanceMarriage = 0;
  var monsterResistance = 0;
  var typeBonus = 0;
  var raceBonus = 0;
  var raceResistance = 0;
  var stoneBonus = 0;
  var monsterBonus = 0;
  var elementBonus = [0, 0, 0, 0, 0, 0]; // fire, ice, lightning, earth, darkness, wind, order doesn't matter
  var damageMultiplier = 1;
  var useDamages = 1;
  var defense = victim.defense;
  var magicResistance = 0;
  var weaponDefense = 0;
  var tigerStrength = 0;
  var criticalHitPercentage = attacker.criticalHit;
  var piercingHitPercentage = attacker.piercingHit;
  var skillDamage = 0;
  var skillDamageResistance = 0;
  var rankBonus = 0;
  var defensePercent = 0;
  var damageBonus = 0;
  var empireMalus = 0;
  var sungMaStrBonus = 0;
  var sungmaStrMalus = 0;
  var whiteDragonElixir = 0;
  var steelDragonElixir = 0;

  computePolymorphPoint(attacker, victim);
  computeHorse(attacker);

  if (isPC(attacker)) {
    attackValuePercent = attacker.attackValuePercent;
    attackMeleeMagic = attacker.attackMeleeMagic;

    var weaponType = attackerWeapon[1];

    if (attacker.class === "archery") {
      if (weaponType !== 2) {
        useDamages = 0;
        weaponType = 2;
      }
      defense = 0;
    }

    var weaponDefenseName = mapping.defenseWeapon[weaponType];
    var weaponDefenseBreakName = mapping.breakWeapon[weaponType];

    if (victim.hasOwnProperty(weaponDefenseName)) {
      weaponDefense = victim[weaponDefenseName];
    }

    if (attacker.whiteDragonElixir === "on") {
      whiteDragonElixir = 10;
    }

    if (isPC(victim)) {
      typeBonus = Math.max(1, attacker.humanBonus - victim.humanResistance);
      raceBonus = attacker[mapping.raceBonus[victim.race]];
      raceResistance = victim[mapping.raceResistance[attacker.race]];

      calcElementCoeffPvP(elementBonus, mapping, attacker, victim);

      if (attacker.hasOwnProperty(weaponDefenseBreakName)) {
        weaponDefense -= attacker[weaponDefenseBreakName];
      }

      criticalHitPercentage = 0;
    } else {
      if (attacker.isMarried === "on") {
        if (attacker.loveNecklace === "on") {
          attackValueMarriage = getMarriageBonusValue(
            attacker,
            marriageTable,
            "loveNecklace"
          );
        }

        if (attacker.loveEarrings === "on") {
          criticalHitPercentage += getMarriageBonusValue(
            attacker,
            marriageTable,
            "loveEarrings"
          );
        }

        if (attacker.harmonyEarrings === "on") {
          piercingHitPercentage += getMarriageBonusValue(
            attacker,
            marriageTable,
            "harmonyEarrings"
          );
        }
      }

      if (attacker.tigerStrength === "on") {
        tigerStrength = 40;
      }

      for (var index = 0; index < elementBonus.length; index++) {
        var elementBonusName = mapping.elementBonus[index];
        var elementResistanceName = mapping.elementResistance[index];

        if (attacker[elementBonusName] && victim[elementBonusName]) {
          elementBonus[index] =
            (attacker[elementBonusName] - victim[elementResistanceName]) / 200;
        } else {
          elementBonus[index] = attacker[elementBonusName] / 2000;
        }
      }

      var victimType = victim.type;

      if (victimType !== -1) {
        typeBonus = attacker[mapping.typeFlag[victimType]];
      }

      monsterBonus = attacker.monsterBonus;

      if (isStone(victim)) {
        stoneBonus = attacker.stoneBonus;
      }

      if (isBoss(victim)) {
        skillDamage += attacker.skillBossDamage;
      }

      if (attacker.onYohara === "on") {
        var sungmaStrDifference = attacker.sungmaStr - attacker.sungmaStrMalus;

        if (sungmaStrDifference >= 0) {
          sungMaStrBonus = sungmaStrDifference;
        } else {
          sungmaStrMalus = 0.5;
        }
      }
    }

    skillDamage += attacker.skillDamage;
    rankBonus = getRankBonus(attacker);
    damageBonus = attacker.damageBonus;

    if (attacker.empireMalus === "on") {
      empireMalus = 10;
    }
  } else {
    if (isPC(victim)) {
      if (victim.isMarried === "on" && victim.harmonyBracelet === "on") {
        monsterResistanceMarriage = getMarriageBonusValue(
          victim,
          marriageTable,
          "harmonyBracelet"
        );
      }

      monsterResistance = victim.monsterResistance;

      for (var index = 0; index < elementBonus.length; index++) {
        var elementBonusName = mapping.elementBonus[index];
        var elementResistanceName = mapping.elementResistance[index];

        if (attacker[elementBonusName]) {
          elementBonus[index] =
            (attacker[elementBonusName] - victim[elementResistanceName]) / 125;
        }
      }
    }

    typeBonus = 1;
    damageMultiplier = attacker.damageMultiplier;
  }

  criticalHitPercentage = skillChanceReduction(criticalHitPercentage);
  piercingHitPercentage = skillChanceReduction(piercingHitPercentage);

  if (isPC(victim)) {
    criticalHitPercentage = Math.max(
      0,
      criticalHitPercentage - victim.criticalHitResistance
    );
    piercingHitPercentage = Math.max(
      0,
      piercingHitPercentage - victim.piercingHitResistance
    );
    skillDamageResistance = victim.skillDamageResistance;

    if (isMagicClass(victim)) {
      defensePercent = (-2 * victim.magicDefense * victim.defensePercent) / 100;
    } else {
      defensePercent = (-2 * victim.defense * victim.defensePercent) / 100;
    }

    if (victim.steelDragonElixir === "on") {
      steelDragonElixir = 10;
    }
  }

  if (magicSkill) {
    adjustCoeff = 0.5;
    attackValuePercent = attacker.attackMagic;
    attackValueMarriage = 0;
    defense = 0;
    magicResistance = victim.magicResistance;
    weaponDefense = 0;
  }

  var battleValues = {
    weaponBonusCoeff: 1,
    adjustCoeff: adjustCoeff,
    attackValueCoeff:
      1 + (attackValuePercent + Math.min(100, attackMeleeMagic)) / 100,
    attackValueMarriage: attackValueMarriage,
    monsterResistanceMarriageCoeff: 1 - monsterResistanceMarriage / 100,
    monsterResistanceCoeff: 1 - monsterResistance / 100,
    typeBonusCoeff: 1 + typeBonus / 100,
    raceBonusCoeff: raceBonus / 100,
    raceResistanceCoeff: raceResistance / 100,
    monsterBonusCoeff: 1 + monsterBonus / 100,
    stoneBonusCoeff: 1 + stoneBonus / 100,
    elementBonusCoeff: elementBonus,
    damageMultiplier: damageMultiplier,
    useDamages: useDamages,
    defense: defense,
    tigerStrengthCoeff: 1 + tigerStrength / 100,
    piercingHitDefense: victim.defense,
    magicResistanceCoeff: magicResistanceToCoeff(magicResistance),
    weaponDefenseCoeff: 1 - weaponDefense / 100,
    skillDamageCoeff: 1 + skillDamage / 100,
    skillDamageResistanceCoeff: 1 - Math.min(99, skillDamageResistance) / 100,
    rankBonusCoeff: 1 + rankBonus / 100,
    defensePercent: Math.floor(defensePercent),
    damageBonusCoeff: damageBonus / 100,
    empireMalusCoeff: 1 - empireMalus / 100,
    sungMaStrBonusCoeff: 1 + sungMaStrBonus / 10000,
    sungmaStrMalusCoeff: sungmaStrMalus,
    whiteDragonElixirCoeff: 1 + whiteDragonElixir / 100,
    steelDragonElixirCoeff: 1 - steelDragonElixir / 100
  };

  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 updateBattleValues(battleValues, skillInfo, attackerWeapon) {
  var weaponBonus = 0;
  var skillWard = 0;
  var skillBonus = 0;
  var skillBonusByBonus = 0;

  if (skillInfo.hasOwnProperty("weaponBonus")) {
    var [weaponType, weaponBonusValue] = skillInfo.weaponBonus;

    if (weaponType === attackerWeapon[1]) {
      weaponBonus = weaponBonusValue;
    }
  }

  if (skillInfo.skillBonus) {
    skillBonus = skillInfo.skillBonus;
  }

  if (skillInfo.skillWard) {
    skillWard = skillInfo.skillWard;
  }

  if (skillInfo.skillBonusByBonus) {
    skillBonusByBonus = skillInfo.skillBonusByBonus;
  }

  if (skillInfo.removeWeaponReduction) {
    battleValues.weaponDefenseCoeff = 1;
  }

  battleValues.weaponBonusCoeff = 1 + weaponBonus / 100;
  battleValues.skillWardCoeff = 1 - skillWard / 100;
  battleValues.skillBonusCoeff = 1 + skillBonus / 100;
  battleValues.skillBonusByBonusCoeff = 1 + skillBonusByBonus / 100;
}

function calcPhysicalDamages(
  attacker,
  attackerWeapon,
  victim,
  tableResult,
  mapping,
  constants
) {
  var battleValues = createPhysicalBattleValues(
    attacker,
    attackerWeapon,
    victim,
    mapping,
    constants.polymorphPowerTable,
    constants.marriageTable,
    constants.skillPowerTable
  );

  var sumDamages = 0;
  var minMaxDamages = { min: Infinity, max: 0 };
  clearTableResult(tableResult);

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

  totalCardinal *= 100;

  if (battleValues.missPercentage) {
    addRowToTableResult(tableResult, "Miss");
    addToTableResult(tableResult, { 0: battleValues.missPercentage / 100 });
  }

  var lastWeightsLimit = maxAttackValue - minInterval + 1;
  var firstWeightLimit = minAttackValue + minInterval - 1;

  for (var damagesType of battleValues.damagesTypeCombinaison) {
    if (!damagesType.weight) {
      0;
      continue;
    }

    var damagesWeighted = {};
    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 = minInterval;
      }

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

      var minPiercingDamages =
        damagesWithPrimaryBonuses -
        battleValues.defense +
        battleValues.defenseMarriage;

      if (minPiercingDamages <= 2) {
        for (var damages = 1; damages <= 5; damages++) {
          var finalDamages = calcDamageWithSecondaryBonuses(
            damages,
            battleValues,
            damagesType,
            minPiercingDamages,
            damagesWithPrimaryBonuses
          );

          addKeyValue(
            damagesWeighted,
            finalDamages,
            (weight * damagesType.weight) / (5 * totalCardinal)
          );
          sumDamages += (finalDamages * weight * damagesType.weight) / 5;
        }
      } else {
        var finalDamages = calcDamageWithSecondaryBonuses(
          minPiercingDamages,
          battleValues,
          damagesType,
          minPiercingDamages,
          damagesWithPrimaryBonuses
        );

        addKeyValue(
          damagesWeighted,
          finalDamages,
          (weight * damagesType.weight) / totalCardinal
        );
        sumDamages += finalDamages * weight * damagesType.weight;
      }
    }

    addToTableResult(tableResult, damagesWeighted, minMaxDamages);
  }

  if (minMaxDamages.min === Infinity) {
    minMaxDamages.min = 0;
  }

  return [sumDamages / totalCardinal, minMaxDamages];
}

function calcBlessingBonus(skillPowerTable, victim) {
  if (victim.isBlessed !== "on") {
    return 0;
  }

  var int = victim.intBlessing;
  var dex = victim.dexBlessing;
  var skillPower = getSkillPower(victim["skillBlessing"], skillPowerTable);

  if (!skillPower) {
    return 0;
  }

  var blessingBonus = floorMultiplication(
    ((int * 0.3 + 5) * (2 * skillPower + 0.5) + 0.3 * dex) / (skillPower + 2.3),
    1
  );

  if (victim.class === "dragon" && victim.blessingOnself === "on") {
    blessingBonus = floorMultiplication(blessingBonus, 1.1);
  }

  return blessingBonus;
}

function getSkillFormula(
  skillPowerTable,
  skillId,
  attacker,
  attackFactor,
  victim
) {
  var skillFormula;
  var skillInfo = {};

  var attackerClass = attacker.class;
  var lv = attacker.level;
  var vit = attacker.vit;
  var str = attacker.str;
  var int = attacker.int;
  var dex = attacker.dex;

  var skillPower = getSkillPower(
    attacker["attackSkill" + skillId],
    skillPowerTable
  );

  var improvedBySkillBonus = false;
  var improvedByBonus = false;

  if (attackerClass === "body") {
    switch (skillId) {
      // Triple lacération
      case 1:
        skillFormula = function (atk) {
          return floorMultiplication(
            1.1 * atk + (0.5 * atk + 1.5 * str) * skillPower,
            1
          );
        };
        improvedByBonus = true;
        break;
      // Moulinet à l'épée
      case 2:
        skillFormula = function (atk) {
          return floorMultiplication(
            3 * atk + (0.8 * atk + 5 * str + 3 * dex + vit) * skillPower,
            1
          );
        };
        improvedByBonus = true;
        improvedBySkillBonus = true;
        break;
      // Accélération
      case 5:
        skillFormula = function (atk) {
          return floorMultiplication(
            2 * atk + (atk + dex * 3 + str * 7 + vit) * skillPower,
            1
          );
        };
        improvedByBonus = true;
        break;
      // Volonté de vivre
      case 6:
        skillFormula = function (atk) {
          return floorMultiplication(
            (3 * atk + (atk + 1.5 * str) * skillPower) * 1.07,
            1
          );
        };
        break;
      case 9:
        skillFormula = function (atk) {
          return floorMultiplication(
            3 * atk + (0.9 * atk + 500.5 + 5 * str + 3 * dex + lv) * skillPower,
            1
          );
        };
        break;
    }
  } else if (attackerClass === "mental") {
    switch (skillId) {
      // Attaque de l'esprit
      case 1:
        skillFormula = function (atk) {
          return floorMultiplication(
            2.3 * atk + (4 * atk + 4 * str + vit) * skillPower,
            1
          );
        };
        improvedByBonus = true;
        improvedBySkillBonus = true;
        break;
      // Attaque de la paume
      case 2:
        skillFormula = function (atk) {
          return floorMultiplication(
            2.3 * atk + (3 * atk + 4 * str + 3 * vit) * skillPower,
            1
          );
        };
        improvedByBonus = true;
        break;
      // Charge
      case 3:
        skillFormula = function (atk) {
          return floorMultiplication(
            2 * atk + (2 * atk + 2 * dex + 2 * vit + 4 * str) * skillPower,
            1
          );
        };
        break;
      // Coup d'épée
      case 5:
        skillFormula = function (atk) {
          return floorMultiplication(
            2 * atk + (atk + 3 * dex + 5 * str + vit) * skillPower,
            1
          );
        };
        improvedByBonus = true;
        break;
      // Orbe de l'épée
      case 6:
        skillFormula = function (atk) {
          return floorMultiplication(
            (2 * atk + (2 * atk + 2 * dex + 2 * vit + 4 * str) * skillPower) *
              1.1,
            1
          );
        };
        break;
      // Tremblement de terre
      case 9:
        skillFormula = function (atk) {
          return floorMultiplication(
            3 * atk + (0.9 * atk + 500.5 + 5 * str + 3 * dex + lv) * skillPower,
            1
          );
        };
        break;
    }
  } else if (attackerClass === "blade_fight") {
    switch (skillId) {
      // Embuscade
      case 1:
        skillFormula = function (atk) {
          return floorMultiplication(
            atk + (1.2 * atk + 600 + 4 * dex + 4 * str) * skillPower,
            1
          );
        };
        skillInfo.weaponBonus = [1, 50];
        improvedByBonus = true;
        improvedBySkillBonus = true;
        break;
      // Attaque rapide
      case 2:
        skillFormula = function (atk) {
          return floorMultiplication(
            atk + (1.6 * atk + 250 + 7 * dex + 7 * str) * skillPower,
            1
          );
        };
        skillInfo.weaponBonus = [1, 35];
        improvedByBonus = true;
        break;
      // Dague filante
      case 3:
        skillFormula = function (atk) {
          return floorMultiplication(
            2 * atk + (0.5 * atk + 9 * dex + 7 * str) * skillPower,
            1
          );
        };
        improvedByBonus = true;
        break;
      // Brume empoisonnée
      case 5:
        skillFormula = function (atk) {
          return floorMultiplication(
            2 * lv + (atk + 3 * str + 18 * dex) * skillPower,
            1
          );
        };
        improvedByBonus = true;
        break;
      // Poison insidieux
      case 6:
        skillFormula = function (atk) {
          return floorMultiplication(
            (2 * lv + (atk + 3 * str + 18 * dex) * skillPower) * 1.1,
            1
          );
        };
        break;
      // Étoiles brillantes
      case 9:
        skillFormula = function (atk) {
          return floorMultiplication(
            atk + (1.7 * atk + 500.5 + 6 * dex + 5 * lv) * skillPower,
            1
          );
        };
        break;
    }
  } else if (attackerClass === "archery") {
    switch (skillId) {
      // Tir à répétition
      // case 1:
      //   skillFormula = function (atk) {
      //     return floorMultiplication(
      //       atk + 0.2 * atk * Math.floor(2 + 6 * skillPower) + (0.8 * atk + 8 * dex * attackFactor + 2 * int) * skillPower,
      //       1
      //     );
      //   };
      //   improvedByBonus = true;
      //   break;
      // Pluie de flèches
      case 2:
        skillFormula = function (atk) {
          return floorMultiplication(
            atk + (1.7 * atk + 5 * dex + str) * skillPower,
            1
          );
        };
        improvedByBonus = true;
        break;
      // Flèche de feu
      case 3:
        skillFormula = function (atk) {
          return floorMultiplication(
            1.5 * atk + (2.6 * atk + 0.9 * int + 200) * skillPower,
            1
          );
        };
        improvedByBonus = true;
        improvedBySkillBonus = true;
        break;
      // Foulée de plume
      case 4:
        skillFormula = function (atk) {
          return floorMultiplication(
            (3 * dex + 200 + 2 * str + 2 * int) * skillPower,
            1
          );
        };
        skillInfo.removeWeaponReduction = true;
        break;
      // Flèche empoisonnée
      case 5:
        skillFormula = function (atk) {
          return floorMultiplication(
            atk + (1.4 * atk + 150 + 7 * dex + 4 * str + 4 * int) * skillPower,
            1
          );
        };
        improvedByBonus = true;
        break;
      // Coup étincelant
      case 6:
        skillFormula = function (atk) {
          return floorMultiplication(
            (atk +
              (1.2 * atk + 150 + 6 * dex + 3 * str + 3 * int) * skillPower) *
              1.2,
            1
          );
        };
        improvedByBonus = true;
        break;
      // Tir tempête
      case 9:
        skillFormula = function (atk) {
          return floorMultiplication(
            1.9 * atk + (2.6 * atk + 500.5) * skillPower,
            1
          );
        };
        break;
    }
  } else if (attackerClass === "weaponary") {
    switch (skillId) {
      // Toucher brûlant
      case 1:
        skillFormula = function (atk) {
          return floorMultiplication(
            atk +
              2 * lv +
              2 * int +
              (2 * atk + 4 * str + 14 * int) * skillPower,
            1
          );
        };
        improvedByBonus = true;
        improvedBySkillBonus = true;
        break;
      // Tourbillon du dragon
      case 2:
        skillFormula = function (atk) {
          return floorMultiplication(
            1.1 * atk +
              2 * lv +
              2 * int +
              (1.5 * atk + str + 12 * int) * skillPower,
            1
          );
        };
        improvedByBonus = true;
        break;
    }
  } else if (attackerClass === "black_magic") {
    switch (skillId) {
      // Attaque des ténèbres
      case 1:
        skillFormula = function (mav) {
          return floorMultiplication(
            40 +
              5 * lv +
              2 * int +
              (13 * int + 6 * mav + 75) * attackFactor * skillPower,
            1
          );
        };
        improvedByBonus = true;
        improvedBySkillBonus = true;
        break;
      // Attaque de flammes
      // case 2:
      //   skillFormula = function (mav) {
      //     return floorMultiplication(
      //       5 * lv + 2 * int + (7 * int + 8 * mav + 4 * str + 2 * vit + 190) * skillPower,
      //       1
      //     );
      //   };
      //   improvedByBonus = true;
      //   break;
      // Esprit de flammes
      case 3:
        skillFormula = function (mav) {
          return floorMultiplication(
            30 +
              2 * lv +
              2 * int +
              (7 * int + 6 * mav + 350) * attackFactor * skillPower,
            1
          );
        };
        break;
      // Frappe de l'esprit
      // case 5:
      //   skillFormula = function (mav) {
      //     return floorMultiplication(
      //       40 + 2 * lv + 2 * int + (2 * vit + 2 * dex + 13 * int + 6 * mav + 190) * attackFactor * skillPower,
      //       1
      //     );
      //   };
      //   break;
      // Orbe des ténèbres
      case 6:
        skillFormula = function (mav) {
          return floorMultiplication(
            120 +
              6 * lv +
              (5 * vit + 5 * dex + 29 * int + 9 * mav) *
                attackFactor *
                skillPower,
            1
          );
        };
        improvedByBonus = true;
        break;
    }
  } else if (attackerClass === "dragon") {
    switch (skillId) {
      // Talisman volant
      case 1:
        skillFormula = function (mav) {
          return floorMultiplication(
            70 +
              5 * lv +
              (18 * int + 7 * str + 5 * mav + 50) * attackFactor * skillPower,
            1
          );
        };
        skillInfo.weaponBonus = [4, 10];
        improvedByBonus = true;
        break;
      // Dragon chassant
      case 2:
        skillFormula = function (mav) {
          return floorMultiplication(
            60 +
              5 * lv +
              (16 * int + 6 * dex + 6 * mav + 120) * attackFactor * skillPower,
            1
          );
        };
        skillInfo.weaponBonus = [4, 10];
        improvedByBonus = true;
        improvedBySkillBonus = true;
        break;
      // Rugissement du dragon
      case 3:
        skillFormula = function (mav) {
          return floorMultiplication(
            70 +
              3 * lv +
              (20 * int + 3 * str + 10 * mav + 100) * attackFactor * skillPower,
            1
          );
        };
        skillInfo.weaponBonus = [4, 10];
        improvedByBonus = true;
        break;
    }
  } else if (attackerClass === "heal") {
    switch (skillId) {
      // Jet de foudre
      case 1:
        skillFormula = function (mav) {
          return floorMultiplication(
            60 +
              5 * lv +
              (8 * int + 2 * dex + 8 * mav + 10 * int) *
                attackFactor *
                skillPower,
            1
          );
        };
        skillInfo.weaponBonus = [6, 10];
        improvedByBonus = true;
        break;
      // Invocation de foudre
      case 2:
        skillFormula = function (mav) {
          return floorMultiplication(
            40 +
              4 * lv +
              (13 * int + 2 * str + 10 * mav + 10.5 * int) *
                attackFactor *
                skillPower,
            1
          );
        };
        skillInfo.weaponBonus = [6, 10];
        improvedByBonus = true;
        improvedBySkillBonus = true;
        break;
      // Griffe de foudre
      case 3:
        skillFormula = function (mav) {
          return floorMultiplication(
            50 +
              5 * lv +
              (8 * int + 2 * str + 8 * mav + 400.5) * attackFactor * skillPower,
            1
          );
        };
        improvedByBonus = true;
        break;
    }
  } else if (attackerClass === "lycan") {
    switch (skillId) {
      // Déchiqueter
      // case 1:
      //   skillFormula = function (atk) {
      //     return floorMultiplication(
      //       1.1 * atk + (0.3 * atk + 1.5 * str) * skillPower,
      //       1
      //     );
      //   };
      //   skillInfo.weaponBonus = [5, 54];
      //   improvedByBonus = true;
      //   break;
      // Souffle de loup
      case 2:
        skillFormula = function (atk) {
          return floorMultiplication(
            2 * atk + (atk + 3 * dex + 5 * str + vit) * skillPower,
            1
          );
        };
        skillInfo.weaponBonus = [5, 35];
        improvedByBonus = true;
        improvedBySkillBonus = true;
        break;
      // Bond de loup
      case 3:
        skillFormula = function (atk) {
          return floorMultiplication(
            atk + (1.6 * atk + 200 + 7 * dex + 7 * str) * skillPower,
            1
          );
        };
        skillInfo.weaponBonus = [5, 35];
        improvedByBonus = true;
        break;
      // Griffe de loup
      case 4:
        skillFormula = function (atk) {
          return floorMultiplication(
            3 * atk + (0.8 * atk + 6 * str + 2 * dex + vit) * skillPower,
            1
          );
        };
        improvedByBonus = true;
        break;
    }
  }

  if (improvedBySkillBonus) {
    skillInfo.skillBonus =
      16 * getSkillPower(attacker.skillBonus, skillPowerTable);

    var skillWardChoice = victim.skillWardChoice;

    if (skillWardChoice && skillWardChoice === attackerClass) {
      skillInfo.skillWard =
        24 * getSkillPower(victim.skillWard, skillPowerTable);
    }
  }

  if (improvedByBonus) {
    skillInfo.skillBonusByBonus = attacker["skillBonus" + skillId];
  }

  return [skillFormula, skillInfo];
}

function calcPhysicalSkillDamages(
  attacker,
  attackerWeapon,
  victim,
  tableResult,
  mapping,
  constants,
  skillId
) {
  var battleValues = createSkillBattleValues(
    attacker,
    attackerWeapon,
    victim,
    mapping,
    constants.marriageTable
  );

  var sumDamages = 0;
  var minMaxDamages = { min: Infinity, max: 0 };
  clearTableResult(tableResult);

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

  var lastWeightsLimit = maxAttackValue - minInterval + 1;
  var firstWeightLimit = minAttackValue + minInterval - 1;

  var [skillFormula, skillInfo] = getSkillFormula(
    constants.skillPowerTable,
    skillId,
    attacker,
    attackFactor,
    victim
  );

  updateBattleValues(battleValues, skillInfo, attackerWeapon);

  for (var damagesType of battleValues.damagesTypeCombinaison) {
    if (!damagesType.weight) {
      continue;
    }

    var damagesWeighted = {};
    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 = minInterval;
      }

      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++) {
          damages *= battleValues.useDamages;

          var damagesWithFormula = skillFormula(damages);

          damagesWithFormula = floorMultiplication(
            damagesWithFormula,
            battleValues.weaponBonusCoeff
          );

          var finalDamages = calcSkillDamageWithSecondaryBonuses(
            damagesWithFormula,
            battleValues,
            damagesType,
            damagesWithPrimaryBonuses
          );

          addKeyValue(
            damagesWeighted,
            finalDamages,
            (weight * damagesType.weight) / (5 * totalCardinal)
          );
          sumDamages += (finalDamages * weight * damagesType.weight) / 5;
        }
      } else {
        damagesWithPrimaryBonuses *= battleValues.useDamages;

        var damagesWithFormula = skillFormula(damagesWithPrimaryBonuses);

        damagesWithFormula = floorMultiplication(
          damagesWithFormula,
          battleValues.weaponBonusCoeff
        );

        var finalDamages = calcSkillDamageWithSecondaryBonuses(
          damagesWithFormula,
          battleValues,
          damagesType,
          damagesWithPrimaryBonuses
        );

        addKeyValue(
          damagesWeighted,
          finalDamages,
          (weight * damagesType.weight) / totalCardinal
        );
        sumDamages += finalDamages * weight * damagesType.weight;
      }
    }

    addToTableResult(tableResult, damagesWeighted, minMaxDamages);
  }

  if (minMaxDamages.min === Infinity) {
    minMaxDamages.min = 0;
  }

  return [sumDamages / totalCardinal, minMaxDamages];
}

function calcMagicSkillDamages(
  attacker,
  attackerWeapon,
  victim,
  tableResult,
  mapping,
  constants,
  skillId
) {
  var battleValues = createSkillBattleValues(
    attacker,
    attackerWeapon,
    victim,
    mapping,
    constants.marriageTable,
    true
  );

  var sumDamages = 0;
  var minMaxDamages = { min: Infinity, max: 0 };
  clearTableResult(tableResult);

  var attackFactor = calcAttackFactor(attacker, victim);
  var [minMagicAttackValue, maxMagicAttackValue, minInterval, totalCardinal] =
    calcMagicAttackValue(attacker, attackerWeapon);

  var lastWeightsLimit = maxMagicAttackValue - minInterval + 1;
  var firstWeightLimit = minMagicAttackValue + minInterval - 1;

  var [skillFormula, skillInfo] = getSkillFormula(
    constants.skillPowerTable,
    skillId,
    attacker,
    attackFactor,
    victim
  );

  updateBattleValues(battleValues, skillInfo, attackerWeapon);

  for (var damagesType of battleValues.damagesTypeCombinaison) {
    if (!damagesType.weight) {
      continue;
    }

    var damagesWeighted = {};
    addRowToTableResult(tableResult, damagesType.name);

    for (
      var magicAttackValue = minMagicAttackValue;
      magicAttackValue <= maxMagicAttackValue;
      magicAttackValue++
    ) {
      var weight;

      if (magicAttackValue > lastWeightsLimit) {
        weight = maxMagicAttackValue - magicAttackValue + 1;
      } else if (magicAttackValue < firstWeightLimit) {
        weight = magicAttackValue - minMagicAttackValue + 1;
      } else {
        weight = minInterval;
      }

      var rawDamages = skillFormula(magicAttackValue);

      rawDamages = floorMultiplication(
        rawDamages,
        battleValues.weaponBonusCoeff
      );

      var damagesWithPrimaryBonuses = calcDamageWithPrimaryBonuses(
        rawDamages,
        battleValues
      );

      if (damagesWithPrimaryBonuses <= 2) {
        for (var damages = 1; damages <= 5; damages++) {
          var finalDamages = calcSkillDamageWithSecondaryBonuses(
            damages,
            battleValues,
            damagesType,
            damagesWithPrimaryBonuses,
            skillFormula
          );

          addKeyValue(
            damagesWeighted,
            finalDamages,
            (weight * damagesType.weight) / (5 * totalCardinal)
          );
          sumDamages += (finalDamages * weight * damagesType.weight) / 5;
        }
      } else {
        var finalDamages = calcSkillDamageWithSecondaryBonuses(
          damagesWithPrimaryBonuses,
          battleValues,
          damagesType,
          damagesWithPrimaryBonuses,
          skillFormula
        );

        addKeyValue(
          damagesWeighted,
          finalDamages,
          (weight * damagesType.weight) / totalCardinal
        );
        sumDamages += finalDamages * weight * damagesType.weight;
      }
    }

    addToTableResult(tableResult, damagesWeighted, minMaxDamages);
  }

  if (minMaxDamages.min === Infinity) {
    minMaxDamages.min = 0;
  }

  return [sumDamages / totalCardinal, minMaxDamages];
}

function changeMonsterValues(monster, instance, attacker) {
  switch (instance) {
    case "SungMahiTower":
      var sungMahiFloor = 1;
      var sungMahiStep = 1;
      var rawDefense = 120;

      if (isPC(attacker)) {
        sungMahiFloor = attacker.sungMahiFloor;
        sungMahiStep = attacker.sungMahiStep;
      }

      if (monster.rank === 5) {
        monster.level = 121;
        monster.dex = 75;
        rawDefense += 1;
      } else if (monster.rank === 6) {
        monster.level = 123;
        monster.dex = 75;
        rawDefense += 1;
      } else {
        monster.level = 120;
        monster.dex = 68;
      }
      monster.vit = 100;
      monster.rawDefense = rawDefense + (sungMahiStep - 1) * 6;
      monster.fistDefense = 0;
      monster.swordDefense = 0;
      monster.twoHandedSwordDefense = 0;
      monster.daggerDefense = 0;
      monster.bellDefense = 0;
      monster.fanDefense = 0;
      monster.arrowDefense = 0;
      monster.clawDefense = 0;
      monster.magicResistance = 0;
      monster.fireResistance = -20;
  }
}

function createMonster(monsterVnum, attacker) {
  var monsterAttributes = monsterData[monsterVnum];

  var monster = {
    name: monsterAttributes[36],
    rank: monsterAttributes[0],
    race: monsterAttributes[1],
    attack: monsterAttributes[2],
    level: monsterAttributes[3],
    type: monsterAttributes[4],
    str: monsterAttributes[5],
    dex: monsterAttributes[6],
    vit: monsterAttributes[7],
    int: monsterAttributes[8],
    minAttackValue: monsterAttributes[9],
    maxAttackValue: monsterAttributes[10],
    rawDefense: monsterAttributes[11],
    criticalHit: monsterAttributes[12],
    piercingHit: monsterAttributes[13],
    fistDefense: monsterAttributes[14],
    swordDefense: monsterAttributes[15],
    twoHandedSwordDefense: monsterAttributes[16],
    daggerDefense: monsterAttributes[17],
    bellDefense: monsterAttributes[18],
    fanDefense: monsterAttributes[19],
    arrowDefense: monsterAttributes[20],
    clawDefense: monsterAttributes[21],
    fireResistance: monsterAttributes[22],
    lightningResistance: monsterAttributes[23],
    magicResistance: monsterAttributes[24],
    windResistance: monsterAttributes[25],
    lightningBonus: monsterAttributes[26],
    fireBonus: monsterAttributes[27],
    iceBonus: monsterAttributes[28],
    windBonus: monsterAttributes[29],
    earthBonus: monsterAttributes[30],
    darknessBonus: monsterAttributes[31],
    darknessResistance: monsterAttributes[32],
    iceResistance: monsterAttributes[33],
    earthResistance: monsterAttributes[34],
    damageMultiplier: monsterAttributes[35],
  };

  // monster.instance = 0;

  // if (attacker && monster.instance === 0) {
  //   changeMonsterValues(monster, "SungMahiTower", attacker);
  // }

  monster.defense = monster.rawDefense + monster.level + monster.vit;

  return monster;
}

function addPotentialErrorInformation(
  errorInformation,
  attacker,
  victim,
  characters
) {
  for (var error of Object.values(errorInformation)) {
    hideElement(error);
  }

  if (isPC(attacker)) {
    if (isRiding(attacker)) {
      if (attacker.horsePoint === 0) {
        showElement(errorInformation["horse-level"]);
      }
      showElement(errorInformation["horse-stat"]);
    } else if (isPolymorph(attacker)) {
      if (attacker.polymorphPoint === 0) {
        showElement(errorInformation["polymorph-level"]);
      }

      if (
        (attacker.polymorphPoint <= 39 && attacker.attackValuePercent <= 199) ||
        (attacker.polymorphPoint === 40 && attacker.attackValuePercent <= 299)
      ) {
        showElement(errorInformation["polymorph-bonus"]);
      }
    }
  } else {
    showElement(errorInformation["monster-attacker"]);
  }

  if (isPC(victim)) {
    if (isRiding(victim)) {
      showElement(errorInformation["horse-stat"]);
    } else if (isPolymorph(victim)) {
      if (attacker.polymorphPoint === 0) {
        showElement(errorInformation["polymorph-level"]);
      }
      showElement(errorInformation["polymorph-defense"]);
    }
  }

  if (characters.unsavedChanges) {
    showElement(errorInformation["save"]);
  }
}

function createBattle(characters, battle) {
  function isPseudoSaved(pseudo) {
    return characters.savedCharacters.hasOwnProperty(pseudo);
  }

  battle.battleForm.addEventListener("submit", function (event) {
    event.preventDefault();

    // auto save
    if (characters.unsavedChanges) {
      characters.saveButton.click();
    }

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

    var attackerWeapon = null;

    if (isPseudoSaved(attackerName)) {
      var attacker = copyObject(characters.savedCharacters[attackerName]);

      if (weaponData.hasOwnProperty(attacker.weapon)) {
        attackerWeapon = weaponData[attacker.weapon];
      } else {
        attackerWeapon = weaponData[0];
      }
    } else {
      var attacker = createMonster(attackerName);
    }

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

    var meanDamages, minMaxDamages;
    var calcDamages;
    var skillId = 0;

    if (attackType === "physical") {
      calcDamages = calcPhysicalDamages;
    } else if (attackType.startsWith("attackSkill")) {
      skillId = parseInt(attackType.split("attackSkill")[1]);

      if (isMagicClass(attacker)) {
        calcDamages = calcMagicSkillDamages;
      } else {
        calcDamages = calcPhysicalSkillDamages;
      }
    }

    [meanDamages, minMaxDamages] = calcDamages(
      attacker,
      attackerWeapon,
      victim,
      battle.tableResult,
      battle.mapping,
      battle.constants,
      skillId
    );

    battle.damageResult.textContent =
      attacker.name +
      " inflige " +
      numberDisplay(meanDamages, 1) +
      " dégâts en moyenne à " +
      victim.name +
      " (minimum : " +
      minMaxDamages.min +
      ", maximum : " +
      minMaxDamages.max +
      ").";

    addPotentialErrorInformation(
      battle.errorInformation,
      attacker,
      victim,
      characters
    );
    showElement(battle.tableContainer);
  });

  battle.attackerSelection.addEventListener("change", function (event) {
    var attackerName = event.target.value;
    var attackTypeSelection = battle.attackTypeSelection;

    if (isPseudoSaved(attackerName)) {
      var attacker = characters.savedCharacters[attackerName];
      filterAttackTypeSelection(attacker, attackTypeSelection);
    } else {
      for (var option of attackTypeSelection.options) {
        if (option.dataset.class) {
          hideElement(option);
        }
      }

      if (attackTypeSelection.selectedIndex !== 1) {
        attackTypeSelection.selectedIndex = 0;
      }
    }
  });
}

function createMapping() {
  mapping = {
    typeFlag: [
      "animalBonus", // 0
      "humanBonus", // 1
      "orcBonus", // 2
      "mysticBonus", // 3
      "undeadBonus", // 4
      "insectBonus", // 5
      "desertBonus", // 6
      "devilBonus", // 7
    ],
    raceBonus: {
      warrior: "warriorBonus",
      sura: "suraBonus",
      ninja: "ninjaBonus",
      shaman: "shamanBonus",
      lycan: "lycanBonus",
    },
    raceResistance: {
      warrior: "warriorResistance",
      sura: "suraResistance",
      ninja: "ninjaResistance",
      shaman: "shamanResistance",
      lycan: "lycanResistance",
    },
    defenseWeapon: [
      "swordDefense", // 0
      "daggerDefense", // 1
      "arrowDefense", // 2
      "twoHandedSwordDefense", // 3
      "bellDefense", // 4
      "clawDefense", // 5
      "fanDefense", // 6
      "swordDefense", // 7
      "fistDefense", // 8
    ],
    breakWeapon: [
      "breakSwordDefense", // 0
      "breakDaggerDefense", // 1
      "breakArrowDefense", // 2
      "breakTwoHandedSwordDefense", // 3
      "breakBellDefense", // 4
      "breakClawDefense", // 5
      "breakFanDefense", // 6
      "breakSwordDefense", // 7
    ],
    elementBonus: [
      "fireBonus", // 0
      "iceBonus", // 1
      "windBonus", // 2
      "lightningBonus", // 3
      "earthBonus", // 4
      "darknessBonus", // 5
    ],
    elementResistance: [
      "fireResistance", // 0
      "iceResistance", // 1
      "windResistance", // 2
      "lightningResistance", // 3
      "earthResistance", // 4
      "darknessResistance", // 5
    ],
  };
  return mapping;
}

function createConstants() {
  var constants = {
    polymorphPowerTable: [
      10, 11, 11, 12, 13, 13, 14, 15, 16, 17, 18, 19, 20, 22, 23, 24, 26, 27,
      29, 31, 33, 35, 37, 39, 41, 44, 46, 49, 52, 55, 59, 62, 66, 70, 74, 79,
      84, 89, 94, 100, 0,
    ],
    skillPowerTable: [
      0, 0.05, 0.06, 0.08, 0.1, 0.12, 0.14, 0.16, 0.18, 0.2, 0.22, 0.24, 0.26,
      0.28, 0.3, 0.32, 0.34, 0.36, 0.38, 0.4, 0.5, 0.52, 0.54, 0.56, 0.58, 0.6,
      0.63, 0.66, 0.69, 0.72, 0.82, 0.85, 0.88, 0.91, 0.94, 0.98, 1.02, 1.06,
      1.1, 1.15, 1.25,
    ],
    marriageTable: {
      harmonyEarrings: [4, 5, 6, 8],
      loveEarrings: [4, 5, 6, 8],
      harmonyBracelet: [4, 5, 6, 8],
      loveNecklace: [20, 25, 30, 40],
      harmonyNecklace: [12, 16, 20, 30],
    },
  };
  return constants;
}

function createDamageCalculatorInformation() {
  var characters = {
    unsavedChanges: false,
    savedCharacters: {},
    currentCharacter: null,
    characterCreation: document.getElementById("character-creation"),
    addNewCharacterButton: document.getElementById("add-new-character"),
    dropZone: document.getElementById("character-drop-zone"),
    characterInput: document.getElementById("character-input"),
    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-character"),
    weaponCategory: document.getElementById("weapon-category"),
    weaponDisplay: document.getElementById("weapon-display"),
    randomAttackValue: document.getElementById("random-attack-value"),
    randomMagicAttackValue: document.getElementById(
      "random-magic-attack-value"
    ),
    yoharaCreation: document.getElementById("yohara-creation"),
    blessingCreation: document.getElementById("blessing-creation"),
    marriageCreation: document.getElementById("marriage-creation"),
  };

  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 skillContainer = document.getElementById("skill-container");
  characters.skillElementsToFilter =
    skillContainer.querySelectorAll("[data-class]");

  var mapping = createMapping();
  var constants = createConstants();

  var battle = {
    resetAttackType: false,
    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"),
    errorInformation: {},
    tableContainer: document.getElementById("result-table-container"),
    tableResult: document.getElementById("result-table").children[0],
    mapping: mapping,
    constants: constants,
  };

  var errorElements = document
    .getElementById("error-information")
    .querySelectorAll("li[data-error]");

  for (var index = 0; index < errorElements.length; index++) {
    var errorElement = errorElements[index];
    battle.errorInformation[errorElement.dataset.error] = errorElement;
  }

  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 loading() {
  var mainContainer = document.getElementById("hide-all");
  var loadingAnimation = document.getElementById("loading-animation");

  mainContainer.classList.remove("tabber-noactive");
  loadingAnimation.classList.add("tabber-noactive");
}

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

    loading();
  }

  loadScript(javascriptSource, main);
})();