// ==UserScript==
// @name [HWM] Cost per battle calculator
// @namespace https://greasyfork.org/en/users/242258
// @description Adds cost per battle caluclator to item info page.
// @version 0.2
// @author Alex_2oo8
// @match https://www.heroeswm.ru/art_info.php*
// @require http://code.jquery.com/jquery-3.3.1.min.js
// ==/UserScript==
/* global $ */
$('<style type="text/css">.blacksmith-enabled .enable-link, .blacksmith-enabled .disabled-text, .blacksmith-disabled .disable-link { display: none; }</style>').appendTo('head');
let searchParams = new URLSearchParams(window.location.search);
var itemInfo = $(".wb");
var price = 1, durability = 1, repairPrice = -1;
var infoCells = $(".wblight", itemInfo);
for (var i = 0; i < infoCells.length; i++) {
var children = infoCells[i].childNodes;
for (var j = 0; j + 1 < children.length; j++) {
if (children[j].tagName == "B") {
if (children[j].innerText == "Стоимость:") {
price = parsePrice(children[j + 1]);
}
else if (children[j].innerText == "Прочность:") {
durability = parseInt(children[j + 1].textContent);
}
else if (children[j].innerText == "Стоимость ремонта:") {
repairPrice = parsePrice(children[j + 1]);
}
}
}
}
var blacksmithId = 0;
var blacksmithMap = { };
var optionId = 0;
var optionMap = { };
if (repairPrice != -1) {
var calcTable = $('<table align="center" width="100%" border="0" cellspacing="0" cellpadding="8" class="wb" style="margin-top: 16px;"><tr><td class="wbwhite" colspan="2"><b>Калькулятор стоимости боя</b></td></tr><tr><td class="wblight" style="width: 320px"></td><td class="wblight" style="vertical-align: top;"></td></tr></table>');
var controlPrice = $("<input type='number' min='1' value='" + price + "' style='width: 100px;'>");
var controlBattl = $("<input type='number' min='1' max='99' value='" + durability + "' style='width: 45px;'>");
var controlDurab = $("<input type='number' min='1' max='99' value='" + durability + "' style='width: 45px;'>");
var calcBtn = $("<button>Рассчитать</button>");
calcBtn.click(calc);
controlBattl.change(function() { var bat = parseInt(controlBattl.val()); if (bat > parseInt(controlDurab.val())) controlDurab.val(bat); });
controlDurab.change(function() { var dur = parseInt(controlDurab.val()); if (dur < parseInt(controlBattl.val())) controlBattl.val(dur); });
var controlTable = $('<table cellspacing="8" style="width: 100%;">');
var priceTr = $("<tr>");
priceTr.append($("<td>Цена покупки артефакта:</td>"));
priceTr.append($('<td style="width: 100px;">').append(controlPrice));
controlTable.append(priceTr);
var durabTr = $("<tr>");
durabTr.append($("<td>Начальная прочность артефакта:</td>"));
durabTr.append($("<td>").append(controlBattl).append($('<div style="display: inline-block; width: 10px; text-align: center;">/</div>')).append(controlDurab));
controlTable.append(durabTr);
controlTable.append('<tr><td colspan="2"></td></tr>');
controlTable.append('<tr><td colspan="2">Доступные кузнецы:</td></tr>');
var addBlacksmithTr = $("<tr>").append($('<td colspan="2" style="padding-left: 45px;">').append($('<a href="#">Добавить кузнеца</a></td>').click(function() { addBlacksmith(); })));
controlTable.append(addBlacksmithTr);
controlTable.append('<tr><td colspan="2"></td></tr>');
controlTable.append($("<tr>").append($('<td colspan="2" style="text-align: center;">').append(calcBtn)));
var saved = localStorage.getItem("CostPerBattle_blacksmith_list");
if (saved === null) {
addBlacksmith();
}
else {
saved = JSON.parse(saved);
for (var id in saved) {
addBlacksmith(saved[id].efficiency, saved[id].price, saved[id].enabled);
}
}
var artId = searchParams.get("id");
var optionTable = $('<table cellspacing="8" style="width: 100%;">');
optionTable.append('<tr><td>Сохраненные варианты:</td></tr>');
optionTable.append('<tr><td></td></tr>');
var addOptionTr = $('<tr><td></td></tr>');
optionTable.append(addOptionTr);
optionTable.append($("<tr>").append($('<td style="padding-left: 45px;">').append($('<a href="#">Добавить вариант</a></td>').click(function() { addOption(); }))));
saved = localStorage.getItem("CostPerBattle_item_options_" + artId);
if (saved !== null) {
saved = JSON.parse(saved);
for (var optId in saved) {
addOption(saved[optId].price, saved[optId].battles, saved[optId].durability, saved[optId].note);
}
}
var resultWrapper = $('<div style="display: none; position: relative; width: 100%; height: 100%;"></div>');
var resultContainer = $("<div>");
var closeResultBtn = $('<div style="position: absolute; right: -8px; top: -8px; border-left: 1px #5D413A solid; border-bottom: 1px #5D413A solid; width: 32px; height: 32px; text-align: center; line-height: 32px; font-size: 24px; cursor: pointer; user-select: none;">⨯</div>');
closeResultBtn.click(function() { resultWrapper.css("display", "none"); optionTable.css("display", ""); });
$(".wblight", calcTable).eq(0).append(controlTable);
$(".wblight", calcTable).eq(1).append(optionTable);
$(".wblight", calcTable).eq(1).append(resultWrapper.append(closeResultBtn).append(resultContainer));
itemInfo.after(calcTable);
}
function save() {
localStorage.setItem("CostPerBattle_blacksmith_list", JSON.stringify(blacksmithMap));
}
function addBlacksmith(eff, price, enabled) {
var id = blacksmithId++;
if (eff === undefined) eff = 90;
if (price === undefined) price = 101;
if (enabled === undefined) enabled = true;
var priceInput = $('<input type="number" min="1" max="199" value="' + price + '" style="margin: 0 10px; width: 55px;">');
var efficiencySelect = $('<select style="margin: 0 10px; width: 55px;">');
for (var i = 1; i <= 9; i++) {
var option = $('<option value="' + (i * 10) + '" ' + (i * 10 == eff ? "selected" : "") + '>' + (i * 10) + '%</option>');
efficiencySelect.append(option);
}
blacksmithMap[id] = { efficiency: eff, price: price, enabled: enabled };
save();
priceInput.change(function() { blacksmithMap[id].price = parseInt(priceInput.val()); save(); });
efficiencySelect.change(function() { blacksmithMap[id].efficiency = parseInt(efficiencySelect.val()); save(); });
var tr = $('<tr class="blacksmith-' + (enabled ? "enabled" : "disabled") + '">');
var removeLink = $('<a href="#" title="Удалить" style="margin: 0 10px;">(x)</a>');
removeLink.click(function() { delete blacksmithMap[id]; tr.remove(); save(); });
var disableLink = $('<a href="#" title="Исключить из расчета" class="disable-link">(-)</a>');
disableLink.click(function() { blacksmithMap[id].enabled = false; tr.toggleClass("blacksmith-enabled blacksmith-disabled"); save(); });
var enableLink = $('<a href="#" title="Включить в расчет" class="enable-link">(+)</a>');
enableLink.click(function() { blacksmithMap[id].enabled = true; tr.toggleClass("blacksmith-enabled blacksmith-disabled"); save(); });
var td = $('<td colspan="2">на</td>');
td.append(efficiencySelect);
td.append(document.createTextNode("за"));
td.append(priceInput);
td.append(disableLink);
td.append(enableLink);
td.append(removeLink);
td.append($('<span class="disabled-text">[Исключен]</span>'));
addBlacksmithTr.before(tr.append(td));
}
function saveOptions() {
localStorage.setItem("CostPerBattle_item_options_" + artId, JSON.stringify(optionMap));
}
function addOption(price, battles, durability, note) {
var id = optionId++;
if (price === undefined) price = parseInt(controlPrice.val());
if (battles === undefined) battles = parseInt(controlBattl.val());
if (durability === undefined) durability = parseInt(controlDurab.val());
if (note === undefined) note = "";
var priceInput = $("<input type='number' min='1' value='" + price + "' style='width: 100px; margin: 0 10px;'>");
var battlInput = $("<input type='number' min='1' max='99' value='" + battles + "' style='width: 45px; margin-left: 10px;'>");
var durabInput = $("<input type='number' min='1' max='99' value='" + durability + "' style='width: 45px;'>");
var notesInput = $("<input type='text' value='" + note + "' placeholder='Заметка' style='width: 79px; margin: 0 10px;'>");
optionMap[id] = { price: price, battles: battles, durability: durability, note: note };
saveOptions();
priceInput.change(function() { optionMap[id].price = parseInt(priceInput.val()); saveOptions(); });
battlInput.change(function() { optionMap[id].battles = parseInt(battlInput.val()); saveOptions(); });
durabInput.change(function() { optionMap[id].durability = parseInt(durabInput.val()); saveOptions(); });
notesInput.change(function() { optionMap[id].note = parseInt(notesInput.val()); saveOptions(); });
battlInput.change(function() { var bat = parseInt(battlInput.val()); if (bat > parseInt(durabInput.val())) { durabInput.val(bat); durabInput.change(); } });
durabInput.change(function() { var dur = parseInt(durabInput.val()); if (dur < parseInt(battlInput.val())) { battlInput.val(dur); battlInput.change(); } });
var tr = $('<tr>');
var removeLink = $('<a href="#" title="Удалить" style="margin: 0 10px;">(x)</a>');
removeLink.click(function() { delete optionMap[id]; tr.remove(); saveOptions(); });
var applyLink = $('<a href="#">Использовать</a>');
applyLink.click(function() {
controlPrice.val(priceInput.val());
controlBattl.val(battlInput.val());
controlDurab.val(durabInput.val());
controlPrice.change();
controlBattl.change();
controlDurab.change();
});
var td = $('<td>Цена:</td>');
td.append(priceInput);
td.append(document.createTextNode("Прочность:"));
td.append(battlInput);
td.append($('<div style="display: inline-block; width: 10px; text-align: center;">/</div>'));
td.append(durabInput);
td.append(notesInput);
td.append(applyLink);
td.append(removeLink);
addOptionTr.before(tr.append(td));
}
function solve(P) {
var dur = parseInt(controlDurab.val());
var opt = [], optCost = [], totalBattles = parseInt(controlBattl.val());
var S = totalBattles * P - parseInt(controlPrice.val());
while (dur > 1) {
var best = -1, bestEff, bestBattles, bestCost;
for (var id in blacksmithMap) {
if (blacksmithMap[id].enabled == false) continue;
var eff = blacksmithMap[id].efficiency;
var battles = Math.max(Math.floor(dur * eff / 100), 1);
var cost = Math.ceil(repairPrice * blacksmithMap[id].price / 100);
cost = Math.ceil(cost * 1.01);
var here = battles * P - cost;
if (here > best) {
best = here;
bestEff = eff;
bestBattles = battles;
bestCost = cost;
}
}
if (best < 0) break;
S += best;
opt.push(bestEff);
optCost.push(bestCost);
totalBattles += bestBattles;
dur--;
}
return { ok: S > 0, opt: opt, optCost: optCost, battles: totalBattles };
}
function calc() {
var L = 0, R = 1e6;
for (var it = 0; it < 100; it++) {
var C = (L + R) / 2;
var res = solve(C);
if (res.ok) R = C;
else L = C;
}
var best = solve(R), dur = parseInt(controlDurab.val());
var resultTable = $('<table cellspacing="8">');
resultTable.append($("<tr>").append("<td>Цена за бой:</td>").append($("<td>" + (Math.round(R * 100) / 100) + "</td>")));
resultTable.append($("<tr>").append("<td>Всего боев:</td>").append($("<td>" + best.battles + "</td>")));
resultTable.append($("<tr>").append("<td>Конечная прочность:</td>").append($("<td>0/" + (dur - best.opt.length) + "</td>")));
resultContainer.empty();
resultContainer.append(resultTable);
for (var i = 0, j, totalBattles = dur, totalPrice = parseInt(controlPrice.val()); i < best.opt.length; i = j) {
for (j = i; j < best.opt.length && best.opt[j] == best.opt[i]; j++) {
var battles = Math.max(Math.floor((dur - j) * best.opt[i] / 100), 1);
totalBattles += battles;
totalPrice += best.optCost[j];
}
resultContainer.append($("<p>С прочностью от 0/" + (dur - i) + " до " + battles + "/" + (dur - j) + " чинить на " + best.opt[i] + "%. Общее число боев: " + totalBattles + ". Средняя цена за бой: " + (Math.round(totalPrice / totalBattles * 100) / 100) + ".</p>"));
}
resultWrapper.css("display", "");
optionTable.css("display", "none");
}
function parsePrice(container) {
var resources = $("td", "#top_res_table");
var resourcePrice = [1, 180, 180, 360, 360, 360, 360];
var cells = $("td", container), price = 0;
for (var i = 0, j = 0; i + 1 < cells.length; i += 2) {
while ($("img", cells[i])[0].src != $("img", resources[2 * j])[0].src) j++;
var count = parseInt(cells[i + 1].innerText.replace(",", ""));
price += count * resourcePrice[j];
}
return price;
}