From 9bdd5f784f98f68e75d6ec36a90b2ff7c12bb5c4 Mon Sep 17 00:00:00 2001 From: samuel Date: Wed, 3 Dec 2025 22:57:31 +0100 Subject: [PATCH] Add autonomous attack planer --- CHANGELOG.md | 4 + images/cancel.svg | 1 + images/clock.svg | 1 + images/play.svg | 1 + manifest.json | 2 +- metadata.json | 4 +- src/kaplus.css | 24 + src/kaplus.js | 1083 +++++++++++++++++++++++++++++++++++++++++++-- 8 files changed, 1082 insertions(+), 38 deletions(-) create mode 100644 images/cancel.svg create mode 100644 images/clock.svg create mode 100644 images/play.svg diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d313b0..3de4e5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 1.12.4 (2026-12-07) + +- ajout du planificateur autonome d'attaques + ## 1.11.4 (2026-12-03) - correction du bug sur le calculateur diff --git a/images/cancel.svg b/images/cancel.svg new file mode 100644 index 0000000..2067162 --- /dev/null +++ b/images/cancel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/clock.svg b/images/clock.svg new file mode 100644 index 0000000..5f74ce2 --- /dev/null +++ b/images/clock.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/play.svg b/images/play.svg new file mode 100644 index 0000000..1cfff35 --- /dev/null +++ b/images/play.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/manifest.json b/manifest.json index 61b56f6..f5fe469 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "KAplus", - "version": "1.11.4", + "version": "1.12.1", "developer": { "name": "Samuel Campos", diff --git a/metadata.json b/metadata.json index bb6b108..0e806ca 100644 --- a/metadata.json +++ b/metadata.json @@ -5,8 +5,8 @@ "firefox" ], "release_notes": { - "fr": "- correction du bug sur le calculateur", - "en-US": "- fix bug on attack calculator" + "fr": "- ajout du planificateur autonome d'attaques", + "en-US": "- add autonomous attack planer" } } } \ No newline at end of file diff --git a/src/kaplus.css b/src/kaplus.css index bca460f..6884d0a 100644 --- a/src/kaplus.css +++ b/src/kaplus.css @@ -1,3 +1,17 @@ +@keyframes blink { + 0% { + opacity: 1; + } + + 50% { + opacity: 0; + } + + 100% { + opacity: 1; + } +} + #inner_footer { .inner_subcolumns { left: 0 !important; @@ -6,6 +20,16 @@ #content_wrapper > table { width: auto !important; + cursor: default; +} + +.borderlist { + .autonomous:disabled { + opacity: 0.3; + } + .autonomous.blink { + animation: blink 2s infinite; + } } .lay_castle_top { diff --git a/src/kaplus.js b/src/kaplus.js index 9907aaa..187baf2 100644 --- a/src/kaplus.js +++ b/src/kaplus.js @@ -1,7 +1,42 @@ -let tomorrow = new Date(new Date().getTime() + 24*60*60*1000); -tomorrow.setHours(0, 0, 0, 0); -let afterTomorrow = new Date(tomorrow.getTime() + 24*60*60*1000); -let movingDuration = 0; +const AUTHORIZED_ALLIANCES = { + "s58-fr": ["NAZGUL"] +}; + +const MOVING_FACTOR = { + "s56-fr": 3, + "s57-fr": 4, + "s58-fr": 1.98, +} + +const BUILDINGS = { + "main": "Château", + "stone": "Carrière", + "wood": "Scierie", + "iron": "Mine de minerai", + "storage": "Entrepôt", + "hide": "Cachette", + "farm": "Moulin", + "barracks": "Caserne", + "wall": "Remparts", + "stable": "Élevage d'ânes", + "snob": "Résidence", + "smith": "Orfèvrerie", + "statue": "Mémorial", +} + +const UNITS = { + "farmer": "Milice de paysans", + "sword": "Templier", + "spear": "Écuyer", + "axe": "Sauvage", + "bow": "Grand Arc", + "spy": "Éclaireur", + "light": "Croisé", + "heavy": "Chevalier Noir", + "ram": "Bélier", + "kata": "Trébuchet", + "snob": "Comte", +} function num(s) { return parseInt(s.replace(".", "")); @@ -73,6 +108,144 @@ function searchPoint(text) { return {x: point[0], y: point[1]} } +function selectVillage(selectRef, inputXRef, inputYRef) { + let select = document.getElementById(selectRef); + let inputX = document.getElementById(inputXRef); + let inputY = document.getElementById(inputYRef); + if (select.selectedIndex === 0) { + inputX.value = ""; + inputY.value = ""; + } else { + let xy = select.value.split("|"); + inputX.value = xy[0]; + inputY.value = xy[1]; + } +} + +function storeOrderToken(ownVillages, targetVillage) { + let orderToken = sessionStorage.getItem("orderToken"); + if (orderToken !== null) { + return; + } + + let breakLoop = false; + for (let villageId in ownVillages) { + if (breakLoop === true) { + return; + } + let xhrOrder = new XMLHttpRequest(); + xhrOrder.addEventListener("readystatechange", function () { + if (xhrOrder.readyState === xhrOrder.DONE) { + let parser = new DOMParser(); + let doc = parser.parseFromString(xhrOrder.responseText, "text/html"); + let quantityDivs = doc.getElementsByClassName("quantity"); + let unit = null; + for (let i = 0; i < quantityDivs.length; i ++) { + let quantityInput = quantityDivs[i].getElementsByTagName("input")[0]; + let quantityClick = quantityDivs[i].getElementsByClassName("click")[0]; + if (quantityClick.textContent !== "(0)") { + unit = quantityInput.getAttribute("name"); + break; + } + } + if (unit === null) { + return; + } + + let xhrSend = new XMLHttpRequest(); + let formData = new FormData(); + formData.set(unit, "1"); + formData.set("send_x", targetVillage.x); + formData.set("send_y", targetVillage.y); + formData.set("support", "Envoyer du renfort"); + let formParams = new URLSearchParams(formData); + xhrSend.addEventListener("readystatechange", function () { + if (xhrSend.readyState === xhrSend.DONE) { + let parser = new DOMParser(); + let doc = parser.parseFromString(xhrSend.responseText, "text/html"); + let form = doc.getElementsByTagName("form")[0]; + let action = form.getAttribute("action"); + let orderToken = action.replace(/^.*p=([a-z0-9]+).*$/, "$1"); + sessionStorage.setItem("orderToken", orderToken); + breakLoop = true; + } + }); + xhrSend.open("POST", "/?village=" + villageId + "&s=build_barracks&m=command&sub=send"); + xhrSend.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + xhrSend.send(formParams.toString()); + } + }); + xhrOrder.open("GET", "/?village=" + villageId + "&s=build_barracks"); + xhrOrder.send(); + } +} + +function storeTargetVillage(ownVillages) { + let targetVillage = sessionStorage.getItem("targetVillage"); + if (targetVillage !== null) { + storeOrderToken(ownVillages, JSON.parse(targetVillage)); + return; + } + + let xhr = new XMLHttpRequest(); + targetVillage = {}; + xhr.addEventListener("readystatechange", function () { + if (xhr.readyState === xhr.DONE) { + let parser = new DOMParser(); + let doc = parser.parseFromString(xhr.responseText, "text/html"); + let cells = doc.getElementsByClassName("occupied range"); + for (let i = 0; i < cells.length; i ++) { + let links = cells[i].getElementsByTagName("a"); + if (links.length === 0) { + continue; + } + let villageId = links[0].getAttribute("href").replace(/^.*id=(\d{4}).*$/, "$1"); + if (villageId in ownVillages) { + continue; + } + let villageXY = links[0].getAttribute("onmouseover").replace(/^.*(\d{3}\|\d{3}).*$/, "$1").split("|"); + targetVillage = {x: parseInt(villageXY[0]), y: parseInt(villageXY[1])}; + sessionStorage.setItem("targetVillage", JSON.stringify(targetVillage)); + storeOrderToken(ownVillages, targetVillage); + break; + } + } + }); + xhr.open("GET", "/?s=map"); + xhr.send(); +} + +function storeOwnVillages() { + let ownVillages = sessionStorage.getItem("ownVillages"); + if (ownVillages !== null) { + storeTargetVillage(JSON.parse(ownVillages)); + return; + } + + let xhr = new XMLHttpRequest(); + xhr.addEventListener("readystatechange", function () { + if (xhr.readyState === xhr.DONE) { + let parser = new DOMParser(); + let doc = parser.parseFromString(xhr.responseText, "text/html"); + let mainContentPane = doc.getElementsByClassName("contentpane")[1]; + let table = mainContentPane.getElementsByClassName("borderlist")[1]; + let rows = table.getElementsByTagName("tr"); + let ownVillages = {}; + for (let i = 1; i < rows.length; i ++) { + let cells = rows[i].getElementsByTagName("td"); + let link = cells[0].getElementsByTagName("a")[0]; + let villageId = link.getAttribute("href").replace(/^.*id=(\d{4}).*$/, "$1"); + let villageXY = cells[1].textContent.split("|"); + ownVillages[villageId] = {x: parseInt(villageXY[0]), y: parseInt(villageXY[1]), name: link.textContent}; + } + sessionStorage.setItem("ownVillages", JSON.stringify(ownVillages)); + storeTargetVillage(ownVillages); + } + }); + xhr.open("GET", "/?s=info_player"); + xhr.send(); +} + function shortcutElementReplace(elt, img, text) { // If more than one hyperlink element found, // then keep the last one but with href of the first one. @@ -119,7 +292,7 @@ function customizeNavbar(layCastleElement) { shortcutContainers[2].classList.add("shortcut_container_right"); } -function countUpMs() { +function countUpMs(tomorrow, afterTomorrow, movingDuration) { let arrivalDate = new Date(new Date().getTime() + movingDuration * 1000); let prefix = ""; if (arrivalDate >= afterTomorrow) { @@ -138,6 +311,49 @@ function countUpMs() { + Math.floor(arrivalDate.getMilliseconds() / 100); } +function convertTimestamp(timestamp) { + let now = new Date(); + let tomorrow = new Date(now.getTime() + 24*3600*1000); + tomorrow.setHours(0); + tomorrow.setMinutes(0); + tomorrow.setSeconds(0); + tomorrow.setMilliseconds(0); + let afterTomorrow = new Date(tomorrow.getTime() + 24*3600*1000); + let date = new Date(timestamp); + + let prefix = ""; + if (date >= afterTomorrow) { + prefix = "le " + date.getDate() + "." + date.getMonth() + " "; + } else if (date > tomorrow) { + prefix = "demain "; + } + return prefix + + "à " + + date.getHours().toString().padStart(2, "0") + + ":" + + date.getMinutes().toString().padStart(2, "0") + + ":" + + date.getSeconds().toString().padStart(2, "0") + + "." + + Math.floor(date.getMilliseconds() / 100); +} + +function convertDurationUntil(timestamp) { + let now = new Date(); + let duration = timestamp - now.getTime(); + let hours = Math.floor(duration / (3600*1000)); + duration -= hours * 3600 * 1000; + let minutes = Math.floor(duration / (60*1000)); + duration -= minutes * 60 * 1000; + let seconds = Math.floor(duration / 1000); + duration -= seconds * 1000; + let tenth = Math.floor(duration / 100); + return hours.toString().padStart(2, "0") + ":" + + minutes.toString().padStart(2, "0") + ":" + + seconds.toString().padStart(2, "0") + "." + + tenth.toString(); +} + function removeAdsBanner() { /* Remove iframe banner */ document.getElementById("banner_skyscraper").remove(); @@ -207,6 +423,777 @@ function showPlayerUnitPointsAndId() { playerPropertiesTbody.insertBefore(createKeyValueRow("Id:", playerId), playerPropertiesRows[2]); } +function showAutoPlanerMenu(village, module) { + let mainContentPane = document.getElementsByClassName("contentpane")[1]; + let navTable = mainContentPane.getElementsByTagName("table")[0]; + let navRow = navTable.getElementsByTagName("tr")[0]; + let navCells = navRow.getElementsByTagName("td"); + let separatorCell = createCustomElement("td"); + let separatorImg = createCustomElement("img"); + separatorCell.appendChild(separatorImg); + let autonomousCell = createCustomElement("td"); + let autonomousLink = createCustomElement( + "a", {"href": "/?village=" + village + "&s=tools&m=auto_planer"}, "Planificateur autonome d'attaques" + ); + let rightImg = navCells[6].getElementsByTagName("img")[0]; + switch (module) { + case "runtime_calculator": + separatorImg.setAttribute("src", "/img/tabs/menue_sn_center.png"); + autonomousCell.setAttribute("background", "/img/tabs/menue_n_back.png"); + rightImg.setAttribute("src", "/img/tabs/menue_n_right.png"); + break; + case "auto_planer": + separatorImg.setAttribute("src", "/img/tabs/menue_ns_center.png"); + autonomousCell.setAttribute("background", "/img/tabs/menue_s_back.png"); + rightImg.setAttribute("src", "/img/tabs/menue_s_right.png"); + break; + default: + separatorImg.setAttribute("src", "/img/tabs/menue_nn_center.png"); + autonomousCell.setAttribute("background", "/img/tabs/menue_n_back.png"); + rightImg.setAttribute("src", "/img/tabs/menue_n_right.png"); + break; + } + autonomousCell.appendChild(autonomousLink); + navRow.insertBefore(autonomousCell, navCells[6]); + navRow.insertBefore(separatorCell, navCells[6]); +} + +function insertAutoPlanedOrder(table, orderId, order) { + let row = createCustomElement( + "tr", + { + "data-id": orderId, + "data-time": order.start_time, + "data-order": JSON.stringify(order), + "data-status": "waiting", + } + ); + + let kindCell = createCustomElement("td"); + let kindImg = createCustomElement("img"); + switch (order.kind) { + case "attack": + kindImg.setAttribute("src", "/img/command/attack.png"); + kindImg.setAttribute("title", "Attaque"); + break; + case "support": + kindImg.setAttribute("src", "/img/command/support.png"); + kindImg.setAttribute("title", "Renfort"); + break + case "spying": + kindImg.setAttribute("src", "/img/units/unit_spy-slow.png"); + kindImg.setAttribute("title", "Espionnage"); + break; + } + kindCell.appendChild(kindImg); + row.appendChild(kindCell); + + let unitCounts = []; + for (let unit in UNITS) { + unitCounts.push(order[unit]); + } + let labelCell = createCustomElement( + "td", + {"title": "Programmé " + order.occurrences.toString() + " fois"}, + "(x" + order.occurrences + ") " + order.label, + ); + row.appendChild(labelCell); + + let headCell = createCustomElement( + "td", + { + "onmouseover": + "Tip(tipUnitDetails('/img', '" + Object.keys(UNITS).join(":") + "', '" + unitCounts.join(":") + "'))", + "onmouseout": "UnTip()", + }, + ); + let headImg = createCustomElement("img", {"src": "/img/units/unit_" + order.head + ".png"}); + headCell.appendChild(headImg); + row.appendChild(headCell); + + let originCell = createCustomElement("td"); + let originInfoLink = createCustomElement( + "a", + {"href": "/?s=info_village&id=" + order.start_id, "title": order.start_name}, + order.start_name, + ); + let originMapLink = createCustomElement( + "a", + {"href": "/?s=map&x=" + order.start_x + "&y=" + order.start_y}, + "(" + order.start_x + "|" + order.start_y + ")", + ) + originCell.appendChild(originInfoLink); + originCell.appendChild(originMapLink); + row.appendChild(originCell); + + let targetCell = createCustomElement("td"); + let targetInfoLink = createCustomElement( + "a", + {"href": "/?s=info_village&id=" + order.target_id, "title": order.target_name}, + order.target_name, + ); + let targetMapLink = createCustomElement( + "a", + {"href": "/?s=map&x=" + order.target_x + "&y=" + order.target_y}, + " (" + order.target_x + "|" + order.target_y + ")", + ) + targetCell.appendChild(targetInfoLink); + targetCell.appendChild(targetMapLink); + row.appendChild(targetCell); + + row.appendChild(createCustomElement("td", null, convertTimestamp(parseInt(order.start_time)))); + row.appendChild(createCustomElement("td", null, convertTimestamp(parseInt(order.target_time)))); + row.appendChild(createCustomElement("td", null, convertDurationUntil(parseInt(order.start_time)))); + + let playCell = createCustomElement("td"); + let playLink = createCustomElement( + "a", + { + "href": "/?village=" + order.start_id + "&s=build_barracks&m=command" + + "&send_x=" + order.target_x + "&send_y=" + order.target_y + "&farmer=" + order.farmer + + "&sword=" + order.sword + "&spear=" + order.spear + "&axe=" + order.axe + "&bow=" + order.bow + + "&spy=" + order.spy + "&light=" + order.light + "&heavy=" + order.heavy + "&ram=" + order.ram + + "&kata=" + order.kata + "&snob=" + order.snob, + "title": "Lancer manuellement", + }, + ); + let playImg = createCustomElement( + "img", {"src": chrome.runtime.getURL("/images/play.svg")}, null, {"width": "18px"} + ); + playLink.appendChild(playImg); + playCell.appendChild(playLink); + row.appendChild(playCell); + + let cancelCell = createCustomElement("td"); + let cancelImg = createCustomElement( + "img", + {"src": chrome.runtime.getURL("/images/cancel.svg"), "title": "Annuler"}, + null, + {"width": "18px", "cursor": "pointer"}, + ); + cancelImg.addEventListener("click", function() { + row.remove(); + let autoPlanedOrders = localStorage.getItem("autoPlanedOrders"); + if (autoPlanedOrders === null) { + return; + } + autoPlanedOrders = JSON.parse(autoPlanedOrders); + delete autoPlanedOrders[orderId]; + localStorage.setItem("autoPlanedOrders", JSON.stringify(autoPlanedOrders)); + }); + cancelCell.appendChild(cancelImg); + row.appendChild(cancelCell); + + // Insert row at right time place + let rows = table.getElementsByTagName("tr"); + for (let i = 1; i < rows.length; i ++) { + if (parseInt(rows[i].getAttribute("data-time")) > parseInt(order.start_time)) { + table.insertBefore(row, rows[i]); + return; + } + } + table.appendChild(row); +} + +function autoPlanerInterval() { + let now = new Date().getTime(); + let table = document.getElementById("autoPlanedOrders"); + let rows = table.getElementsByTagName("tr"); + let delay = 100; + for (let i = 1; i < rows.length; i ++) { + if (rows[i].getAttribute("data-status") !== "waiting") { + continue; + } + + let startTime = parseInt(rows[i].getAttribute("data-time")); + let duration = startTime - now; + if (duration <= 0) { + rows[i].setAttribute("data-status", "done"); + rows[i].style.opacity = "0.5"; + let order = JSON.parse(rows[i].getAttribute("data-order")); + let orderToken = sessionStorage.getItem("orderToken"); + let formData = new FormData(); + for (let unit in UNITS) { + formData.set(unit, order[unit]); + } + switch (order.kind) { + case "attack": + formData.set("attack", "Attaquer"); + formData.set("espy", ""); + if (order.kata !== "0") { + formData.set("kata_target", order.building); + } + break; + case "support": + formData.set("attack", ""); + formData.set("espy", ""); + break; + case "spying": + formData.set("attack", ""); + formData.set("espy", "Espionner"); + break; + } + formData.set("send_x", order.target_x); + formData.set("send_y", order.target_y); + formData.set("transport", "none"); + let formParams = new URLSearchParams(formData); + + for (let j = 0; j < parseInt(order.occurrences); j ++) { + let xhr = new XMLHttpRequest(); + xhr.open( + "POST", "/?village=" + order.start_id + "&s=build_barracks&m=command&a=sendTroop&p=" + orderToken + ); + xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + xhr.send(formParams.toString()); + + let autoPlanedOrders = localStorage.getItem("autoPlanedOrders"); + if (autoPlanedOrders === null) { + return; + } + autoPlanedOrders = JSON.parse(autoPlanedOrders); + delete autoPlanedOrders[rows[i].getAttribute("data-id")]; + localStorage.setItem("autoPlanedOrders", JSON.stringify(autoPlanedOrders)); + } + } else { + rows[i].getElementsByTagName("td")[7].textContent = convertDurationUntil(startTime); + } + } + setTimeout(autoPlanerInterval, delay); +} + +function handleAutoPlanerUnitChange() { + let buildingSelect = document.getElementById("autoPlanerBuilding"); + let unitInputs = document.getElementById("autoPlanerUnitRow").getElementsByTagName("input"); + let totalUnitCount = 0; + let spyCount = 0; + let kataCount = 0; + let attackButton = document.getElementById("autoPlanerSubmitAttack"); + let supportButton = document.getElementById("autoPlanerSubmitSupport"); + let spyingButton = document.getElementById("autoPlanerSubmitSpying"); + + for (let i = 0; i < unitInputs.length; i ++) { + let unitCount = 0; + if (unitInputs[i].value !== "") { + unitCount = parseInt(unitInputs[i].value); + } + if (unitInputs[i].getAttribute("name") === "spy") { + spyCount = unitCount; + } + if (unitInputs[i].getAttribute("name") === "kata") { + kataCount = unitCount; + } + totalUnitCount += unitCount; + } + + if (kataCount > 0) { + buildingSelect.removeAttribute("disabled"); + } else { + buildingSelect.setAttribute("disabled", "disabled"); + } + + if (spyCount > 0 && spyCount === totalUnitCount) { + spyingButton.removeAttribute("disabled"); + } else { + spyingButton.setAttribute("disabled", "disabled"); + } + + if (totalUnitCount > 0) { + attackButton.removeAttribute("disabled"); + supportButton.removeAttribute("disabled"); + } else { + attackButton.setAttribute("disabled", "disabled"); + supportButton.setAttribute("disabled", "disabled"); + } +} + +function handleAutoPlanerSubmit(event) { + event.preventDefault(); + + let autoPlanerError = document.getElementById("autoPlanerError"); + autoPlanerError.textContent = " "; + + let nowTime = Date.now(); + let formData = new FormData(this); + formData.set("kind", event.submitter.getAttribute("name")); + + // Check start village + let ownVillages = sessionStorage.getItem("ownVillages"); + if (ownVillages === null) { + autoPlanerError.textContent = "Impossible de récupérer la liste de vos propres villages, " + + "merci de recharger la page ou de vous reconnecter au jeu si le problème persiste!" + return; + } + ownVillages = JSON.parse(ownVillages); + for (let ownVillageId in ownVillages) { + let ownVillage = ownVillages[ownVillageId]; + if (ownVillage.x === parseInt(formData.get("start_x").toString()) && + ownVillage.y === parseInt(formData.get("start_y").toString())) { + formData.set("start_id", ownVillageId); + formData.set("start_name", ownVillage.name); + break; + } + } + if (! formData.has("start_id")) { + autoPlanerError.textContent = "Opération impossible car le village d'origine ne vous appartient pas!"; + return; + } + + // Check target time + let targetTime = Date.parse( + formData.get("year").toString() + "-" + + formData.get("month").toString().padStart(2, "0") + "-" + + formData.get("day").toString().padStart(2, "0") + "T" + + formData.get("hour").toString().padStart(2, "0") + ":" + + formData.get("minute").toString().padStart(2, "0") + ":" + + formData.get("second").toString().padStart(2, "0") + ) + parseInt(formData.get("tenth").toString()) * 100; + if (nowTime > targetTime) { + autoPlanerError.textContent = "Opération impossible car l'heure d'arrivée est dans le passé!"; + return; + } + formData.set("target_time", targetTime.toString()); + formData.delete("year"); + formData.delete("month"); + formData.delete("day"); + formData.delete("hour"); + formData.delete("minute"); + formData.delete("second"); + formData.delete("tenth"); + + // Replace empty unit values by 0 + for (let unit in UNITS) { + if (formData.get(unit) === "") { + formData.set(unit, "0"); + } + } + + // Calculate start time + let serverId = window.location.hostname.replace(/^([^.]+).*$/, "$1"); + let moving_factor = 1; + if (serverId in MOVING_FACTOR) { + moving_factor = MOVING_FACTOR[serverId]; + } + let durationSeconds = Math.sqrt( + Math.pow(parseInt(formData.get("target_x").toString()) - parseInt(formData.get("start_x").toString()), 2) + + Math.pow(parseInt(formData.get("target_y").toString()) - parseInt(formData.get("start_y").toString()), 2) + ) + if (formData.get("snob") !== "0") { + durationSeconds *= 2100 / moving_factor; + formData.set("head", "snob"); + } else if (formData.get("kata") !== "0") { + durationSeconds *= 1800 / moving_factor; + formData.set("head", "kata"); + } else if (formData.get("ram") !== "0") { + durationSeconds *= 1800 / moving_factor; + formData.set("head", "ram"); + } else if (formData.get("kind") === "spying") { + durationSeconds *= 1800 / moving_factor; + formData.set("head", "spy"); + } else if (formData.get("sword") !== "0") { + durationSeconds *= 1320 / moving_factor; + formData.set("head", "sword"); + } else if (formData.get("farmer") !== "0") { + durationSeconds *= 1200 / moving_factor; + formData.set("head", "farmer"); + } else if (formData.get("spear") !== "0") { + durationSeconds *= 1080 / moving_factor; + formData.set("head", "spear"); + } else if (formData.get("axe") !== "0") { + durationSeconds *= 1080 / moving_factor; + formData.set("head", "axe"); + } else if (formData.get("bow") !== "0") { + durationSeconds *= 1080 / moving_factor; + formData.set("head", "bow"); + } else if (formData.get("heavy") !== "0") { + durationSeconds *= 660 / moving_factor; + formData.set("head", "heavy"); + } else if (formData.get("light") !== "0") { + durationSeconds *= 600 / moving_factor; + formData.set("head", "light"); + } else if (formData.get("spy") !== "0") { + durationSeconds *= 540 / moving_factor; + formData.set("head", "spy"); + } else { + autoPlanerError.textContent = "Opération impossible car aucune unité n'a été sélectionnée!"; + return; + } + let duration = Math.round(durationSeconds) * 1000; + let startTime = targetTime - duration; + if (nowTime > startTime) { + autoPlanerError.textContent = "Opération impossible car l'heure de départ est dans le passé!"; + return; + } + formData.set("start_time", startTime.toString()); + + // Check target village + let xhr = new XMLHttpRequest(); + xhr.addEventListener("readystatechange", function () { + if (xhr.readyState === xhr.DONE) { + let parser = new DOMParser(); + let doc = parser.parseFromString(xhr.responseText, "text/html"); + let mapContainer = doc.getElementById("mapContainer"); + let map = mapContainer.getElementsByTagName("table")[1]; + let mapRows = map.getElementsByTagName("tr"); + let mapRow = mapRows[Math.floor(mapRows.length / 2) - 1]; + let mapCells = mapRow.getElementsByTagName("td"); + let mapCell = mapCells[Math.floor(mapCells.length / 2) + 1]; + let mapLinks = mapCell.getElementsByTagName("a"); + if (mapLinks.length === 0) { + autoPlanerError.textContent = "Opération impossible car le village cible n'existe pas!"; + return; + } + formData.set("target_id", mapLinks[0].getAttribute("href").replace(/^.*id=(\d{4}).*$/, "$1")); + let targetName = mapLinks[0].getAttribute("onmouseover").replace(/^.*'(.*) \(\d{3}\|\d{3}\).*$/, "$1"); + targetName = targetName.replace(/ <.*>/, ""); + formData.set("target_name", targetName); + + // Save attack in localStorage + let autoPlanedOrders = localStorage.getItem("autoPlanedOrders"); + if (autoPlanedOrders === null) { + autoPlanedOrders = {}; + } else { + autoPlanedOrders = JSON.parse(autoPlanedOrders); + } + let orderId = crypto.randomUUID(); + let order = Object.fromEntries(formData); + insertAutoPlanedOrder(document.getElementById("autoPlanedOrders"), orderId, order); + autoPlanedOrders[orderId] = order; + localStorage.setItem("autoPlanedOrders", JSON.stringify(autoPlanedOrders)); + } + }); + xhr.open( + "GET", + "/?s=map&x=" + formData.get("target_x").toString() + "&y=" + formData.get("target_y").toString(), + ); + xhr.send(); +} + +function showAutoPlanerPage(village) { + let mainContentPane = document.getElementsByClassName("contentpane")[1]; + for (let i = mainContentPane.childNodes.length - 1; i >= 5; i --) { + mainContentPane.childNodes[i].remove(); + } + + let autoPlanerForm = createCustomElement("form", {"name": "kingsage", "id": "autoPlanerForm"}); + let borderList = createCustomElement("table", {"class": "borderlist", "width": "820"}); + let titleRow = createCustomElement("tr"); + let titleCell = createCustomElement("th", {"colspan": "7"}, "Nouvel ordre autonome"); + titleRow.appendChild(titleCell); + borderList.appendChild(titleRow); + + let startRow = createCustomElement("tr"); + startRow.appendChild(createCustomElement("td", null, "Village originel:")); + startRow.appendChild(createCustomElement("td", null, "x:")); + let startXCell = createCustomElement("td"); + let startXInput = createCustomInput( + "number", + "start_x", + null, + {"id": "start_x", "required": "required", "min": "1", "max": "999"}, + {"width": "50px"}, + ); + startXCell.appendChild(startXInput); + startRow.appendChild(startXCell); + startRow.appendChild(createCustomElement("td", null, "y:")); + let startYCell = createCustomElement("td"); + let startYInput = createCustomInput( + "number", + "start_y", + null, + {"id": "start_y", "required": "required", "min": "1", "max": "999"}, + {"width": "50px"}, + ); + startYCell.appendChild(startYInput); + startRow.appendChild(startYCell); + let startClickCell = createCustomElement("td"); + let startClickSpan = createCustomElement( + "span", + { + "class": "click", + "onclick": "popup_mod('popup.php?s=targets&m=own_villages&build=attack_planer', 690, 400);return false;", + }, + ) + let startClickImg = createCustomElement("img", {"src": "/img/arrow_right_raquo.png"}); + let startClickText = document.createTextNode(" Propre"); + startClickSpan.appendChild(startClickImg); + startClickSpan.appendChild(startClickText); + startClickCell.appendChild(startClickSpan); + startRow.appendChild(startClickCell); + let startSelectCell = createCustomElement("td"); + let startSelect = createCustomElement("select", {"id": "ownlist"}, null, {"width": "300px"}); + startSelect.addEventListener("change", function () { selectVillage("ownlist", "start_x", "start_y") }); + startSelect.appendChild(createCustomElement("option", {"value": ""}, "Sélectionner village")); + startSelectCell.appendChild(startSelect); + startRow.appendChild(startSelectCell); + borderList.appendChild(startRow); + let startIntervalId = setInterval(function () { + let ownVillages = sessionStorage.getItem("ownVillages"); + if (ownVillages === null) { + return; + } + ownVillages = JSON.parse(ownVillages); + for (let ownVillageId in ownVillages) { + let ownVillage = ownVillages[ownVillageId]; + let startValue = ownVillage.x.toString() + "|" + ownVillage.y.toString() + let startOption = createCustomElement( + "option", {"value": startValue}, ownVillage.name + " (" + startValue + ")", + ); + startSelect.appendChild(startOption); + if (ownVillageId === village) { + startXInput.value = ownVillage.x; + startYInput.value = ownVillage.y; + startSelect.value = startValue; + } + } + clearInterval(startIntervalId); + }, 500); + + let targetRow = createCustomElement("tr"); + targetRow.appendChild(createCustomElement("td", null, "Village cible:")); + targetRow.appendChild(createCustomElement("td", null, "x:")); + let targetXCell = createCustomElement("td"); + let targetXInput = createCustomInput( + "number", + "target_x", + null, + {"id": "target_x", "required": "required", "min": "0", "max": "999"}, + {"width": "50px"}, + ); + targetXCell.appendChild(targetXInput); + targetRow.appendChild(targetXCell); + targetRow.appendChild(createCustomElement("td", null, "y:")); + let targetYCell = createCustomElement("td"); + let targetYInput = createCustomInput( + "number", + "target_y", + null, + {"id": "target_y", "required": "required", "min": "0", "max": "999"}, + {"width": "50px"}, + ); + targetYCell.appendChild(targetYInput); + targetRow.appendChild(targetYCell); + let targetClickCell = createCustomElement("td", {"colspan": "2"}); + let targetClickSpan = createCustomElement( + "span", + { + "class": "click", + "onclick": "popup_mod('popup.php?s=targets&m=favorites&build=attack_planer', 690, 400);return false;", + }, + ) + let targetClickImg = createCustomElement("img", {"src": "/img/arrow_right_raquo.png"}); + let targetClickText = document.createTextNode(" Favoris"); + targetClickSpan.appendChild(targetClickImg); + targetClickSpan.appendChild(targetClickText); + targetClickCell.appendChild(targetClickSpan); + targetRow.appendChild(targetClickCell); + borderList.appendChild(targetRow); + + let dateRow = createCustomElement("tr"); + dateRow.appendChild(createCustomElement("td", null, "Arrivée:")); + let dateCell = createCustomElement("td", {"colspan": "6"}); + let now = new Date(); + dateCell.appendChild(createCustomInput( + "number", + "day", + now.getDate().toString(), + {"required": "required", "min": "1", "max": "31"}, + {"width": "40px"}, + )); + dateCell.appendChild(document.createTextNode(". ")) + dateCell.appendChild(createCustomInput( + "number", + "month", + (now.getMonth() + 1).toString(), + {"required": "required", "min": "1", "max": "12"}, + {"width": "40px"}, + )); + dateCell.appendChild(document.createTextNode(". ")) + dateCell.appendChild(createCustomInput( + "number", + "year", + now.getFullYear().toString(), + {"required": "required", "min": "2025"}, + {"width": "50px"}, + )); + dateCell.appendChild(document.createTextNode(" - ")) + dateCell.appendChild(createCustomInput( + "number", + "hour", + now.getHours().toString(), + {"required": "required", "min": "0", "max": "23"}, + {"width": "40px"}, + )); + dateCell.appendChild(document.createTextNode(": ")) + dateCell.appendChild(createCustomInput( + "number", + "minute", + now.getMinutes().toString(), + {"required": "required", "min": "0", "max": "59"}, + {"width": "40px"}, + )); + dateCell.appendChild(document.createTextNode(": ")) + dateCell.appendChild(createCustomInput( + "number", + "second", + now.getSeconds().toString(), + {"required": "required", "min": "0", "max": "59"}, + {"width": "40px"}, + )); + dateCell.appendChild(document.createTextNode(". ")) + dateCell.appendChild(createCustomInput( + "number", + "tenth", + Math.floor(now.getMilliseconds()/100).toString(), + {"required": "required", "min": "0", "max": "9"}, + {"width": "40px"}, + )); + dateRow.appendChild(dateCell); + borderList.appendChild(dateRow); + + let unitBorderList = createCustomElement("table", {"class": "borderlist"}); + let unitTitleRow = createCustomElement("tr"); + for (let unit in UNITS) { + let unitTitleCell = createCustomElement("th", null, null, {"text-align": "center"}); + let unitTitleImg = createCustomElement( + "img", {"src": "/img/units/unit_"+unit+".png", "alt": UNITS[unit], "title": UNITS[unit]} + ); + unitTitleCell.appendChild(unitTitleImg); + unitTitleRow.appendChild(unitTitleCell); + } + + unitBorderList.appendChild(unitTitleRow); + let unitInputRow = createCustomElement("tr", {"id": "autoPlanerUnitRow"}); + for (let unit in UNITS) { + let unitInputCell = createCustomElement("td"); + let unitInput = createCustomInput( + "number", + unit, + null, + {"id": "autoPlanerUnit" + unit.charAt(0).toUpperCase() + unit.slice(1), "min": "0"}, {"width": "61px"}, + ); + unitInput.addEventListener("change", handleAutoPlanerUnitChange); + switch (unit) { + case "spy": case "light": case "heavy": case "ram": case "snob": + unitInput.style.width = "62px"; + break; + } + unitInputCell.appendChild(unitInput); + unitInputRow.appendChild(unitInputCell); + } + unitBorderList.appendChild(unitInputRow); + + let additionalBorderList = createCustomElement("table", {"class": "borderlist", "width": "820"}); + let additionalTitleRow = createCustomElement("tr"); + additionalTitleRow.appendChild(createCustomElement("th", {"colspan": "7"}, "Données additionnelles")); + additionalBorderList.appendChild(additionalTitleRow); + + let labelRow = createCustomElement("tr"); + labelRow.appendChild(createCustomElement("td", null, "Étiquette:")); + let labelCell = createCustomElement("td", {"colspan": "6"}); + let labelInput = createCustomInput( + "text", "label", null, {"maxlength": "10", "placeholder": "Optionnel"}, {"width": "100px"} + ); + labelCell.appendChild(labelInput); + labelRow.appendChild(labelCell); + additionalBorderList.appendChild(labelRow); + + let occurrencesRow = createCustomElement("tr"); + occurrencesRow.appendChild(createCustomElement("td", null, "Occurrences:")); + let occurrencesCell = createCustomElement("td", {"colspan": "6"}); + let occurrencesInput = createCustomInput( + "number", "occurrences", "1", {"required": "required", "min": "1"}, {"width": "50px"} + ); + occurrencesCell.appendChild(occurrencesInput); + occurrencesRow.appendChild(occurrencesCell); + additionalBorderList.appendChild(occurrencesRow); + + let buildingRow = createCustomElement("tr"); + buildingRow.appendChild(createCustomElement("td", null, "Bâtiment visé:")); + let buildingCell = createCustomElement("td", {"colspan": "6"}); + let buildingSelect = createCustomElement( + "select", {"name": "building", "disabled": "disabled", "id": "autoPlanerBuilding"}, null, {"width": "200px"} + ); + for (let building in BUILDINGS) { + buildingSelect.appendChild(createCustomElement("option", {"value": building}, BUILDINGS[building])); + } + buildingCell.appendChild(buildingSelect); + buildingRow.appendChild(buildingCell); + additionalBorderList.appendChild(buildingRow); + + autoPlanerForm.appendChild(createCustomElement( + "p", + null, + "!ATTENTION! Les programmations sur cette page sont sauvegardées sur ce navigateur uniquement. " + + "Afin que les troupes soient envoyées automatiquement, il faut garder cette page ouverte sur " + + "ce navigateur et rester connecté ici. Vous ne pourrez donc pas voir les programmations d'ici sur " + + "un autre navigateur ou un autre appareil. Cependant, les programmations sont persistantes sur " + + "votre navigateur, donc vous pouvez le fermer et le rouvrir, vous verrez toujours les programmations ici.", + {"font-weight": "bold"}, + )); + autoPlanerForm.appendChild(createCustomElement("br")); + autoPlanerForm.appendChild(createCustomElement("p", {"class": "error", "id": "autoPlanerError"}, " ")); + autoPlanerForm.appendChild(createCustomElement("br")); + autoPlanerForm.appendChild(borderList); + autoPlanerForm.appendChild(createCustomElement("br")); + autoPlanerForm.appendChild(unitBorderList); + autoPlanerForm.appendChild(createCustomElement("br")); + autoPlanerForm.appendChild(additionalBorderList); + autoPlanerForm.appendChild(createCustomElement("br")); + autoPlanerForm.appendChild(createCustomInput( + "submit", + "attack", + "> Programmer une nouvelle attaque", + {"id": "autoPlanerSubmitAttack", "disabled": "disabled"}, + {"font-weight": "bold", "width": "250px", "padding": "5px 0", "cursor": "pointer"} + )); + autoPlanerForm.appendChild(createCustomInput( + "submit", + "support", + "> Programmer un nouveau renfort", + {"id": "autoPlanerSubmitSupport", "disabled": "disabled"}, + {"font-weight": "bold", "width": "250px", "padding": "5px 0", "margin": "0 35px", "cursor": "pointer"} + )); + autoPlanerForm.appendChild(createCustomInput( + "submit", + "spying", + "> Programmer un nouvel espionnage", + {"id": "autoPlanerSubmitSpying", "disabled": "disabled"}, + {"font-weight": "bold", "width": "250px", "padding": "5px 0", "cursor": "pointer"} + )); + autoPlanerForm.addEventListener("submit", handleAutoPlanerSubmit); + mainContentPane.appendChild(autoPlanerForm); + + let ordersBorderList = createCustomElement( + "table", {"id": "autoPlanedOrders", "class": "borderlist"}, null, {"width": "820px"} + ); + let ordersTitleRow = createCustomElement("tr"); + ordersTitleRow.appendChild(createCustomElement("th", {"colspan": "3"})); + ordersTitleRow.appendChild(createCustomElement("th", null, "Village originel")); + ordersTitleRow.appendChild(createCustomElement("th", null, "Village cible")); + ordersTitleRow.appendChild(createCustomElement("th", null, "Heure du début")); + ordersTitleRow.appendChild(createCustomElement("th", null, "Heure d'arrivée")); + ordersTitleRow.appendChild(createCustomElement("th", null, "Décompte")); + ordersTitleRow.appendChild(createCustomElement("th", {"colspan": "2"})); + ordersBorderList.appendChild(ordersTitleRow); + + mainContentPane.appendChild(createCustomElement("br")); + mainContentPane.appendChild(createCustomElement("br")); + mainContentPane.appendChild(ordersBorderList); + + let autoPlanedOrders = localStorage.getItem("autoPlanedOrders"); + if (autoPlanedOrders === null) { + return; + } + autoPlanedOrders = JSON.parse(autoPlanedOrders); + if (autoPlanedOrders.length === 0) { + return; + } + for (let orderId in autoPlanedOrders) { + insertAutoPlanedOrder(ordersBorderList, orderId, autoPlanedOrders[orderId]); + } + autoPlanerInterval(); +} + function showVillageUnitPoints() { let settlementElt = document.getElementById("settlement"); if (settlementElt === null) { @@ -339,9 +1326,13 @@ function showBarrackSelectAllUnits() { } function showCountupTimeDecimals() { + let tomorrow = new Date(new Date().getTime() + 24*60*60*1000); + tomorrow.setHours(0, 0, 0, 0); + let after_tomorrow = new Date(tomorrow.getTime() + 24*60*60*1000); + let oldCell = document.getElementById("countup-time"); let hms = oldCell.parentElement.previousElementSibling.getElementsByTagName("td")[1].textContent.split(":"); - movingDuration = parseInt(hms[0]) * 3600 + parseInt(hms[1]) * 60 + parseInt(hms[2]) ; + let movingDuration = parseInt(hms[0]) * 3600 + parseInt(hms[1]) * 60 + parseInt(hms[2]) ; let newRow = createCustomElement("tr"); let newLeftCell = createCustomElement("td", null, "Arrivée:"); newRow.appendChild(newLeftCell); @@ -349,7 +1340,7 @@ function showCountupTimeDecimals() { newRow.appendChild(newRightCell); oldCell.parentElement.parentElement.insertBefore(newRow, oldCell.parentElement); oldCell.parentElement.style.display = "none"; - setInterval(countUpMs, 100); + setInterval(countUpMs, 100, tomorrow, after_tomorrow, movingDuration); } function showOccurrencesInput() { @@ -370,40 +1361,35 @@ function showOccurrencesInput() { form.addEventListener("submit", function (event) { event.preventDefault(); let formData = new FormData(this); - let attackCount = parseInt(formData.get("occurrences").toString()); + let occurrences = parseInt(formData.get("occurrences").toString()); formData.delete("occurrences"); let sent = 0; - for (let i = 0; i < attackCount; i ++) { + for (let i = 0; i < occurrences; i ++) { let xhr = new XMLHttpRequest(); xhr.addEventListener("readystatechange", function () { if (this.readyState === this.DONE) { sent ++; } - if (sent === attackCount) { + if (sent === occurrences) { window.location.replace(xhr.responseURL); } }) xhr.open(this.method, this.action, true); - xhr.send(formData); + xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + let formParams = new URLSearchParams(formData); + xhr.send(formParams.toString()); } }); } function fixSelectVillageBug() { - let select = document.getElementsByName("village_name")[0]; - select.removeAttribute("onchange"); - select.addEventListener("change", function () { - let sendX = document.getElementById("send_x"); - let sendY = document.getElementById("send_y"); - if (this.selectedIndex === 0) { - sendX.value = ""; - sendY.value = ""; - } else { - let xy = this.value.split("|"); - sendX.value = xy[0]; - sendY.value = xy[1]; - } - }); + let selects = document.getElementsByName("village_name"); + if (selects.length === 0) { + return; + } + selects[0].removeAttribute("onchange"); + selects[0].setAttribute("id", "ownlist"); + selects[0].addEventListener("change", function () { selectVillage("ownlist", "send_x", "send_y") }); } function showThousandInputs() { @@ -467,7 +1453,6 @@ function showSecondsAndCalculator() { if (rows[titleRowIndex].getElementsByTagName("th").length === 0) { titleRowIndex = 1; } - console.log(titleRowIndex) let headCell = createCustomElement("th", null, "Calcul", {"width": "45px"}); rows[titleRowIndex].appendChild(headCell); @@ -529,6 +1514,24 @@ function main() { return; } + /* Exit immediately if not authorized */ + let xhrAlly = new XMLHttpRequest(); + let serverID = window.location.hostname.replace(/^([^.]+).*$/, "$1"); + if (serverID in AUTHORIZED_ALLIANCES) { + xhrAlly.addEventListener("readystatechange", function () { + if (xhrAlly.readyState === xhrAlly.DONE) { + let parser = new DOMParser(); + let doc = parser.parseFromString(xhrAlly.responseText, "text/html"); + let h1 = doc.getElementsByTagName("h1")[0]; + if (!AUTHORIZED_ALLIANCES[serverID].includes(h1.textContent)) { + window.location.replace("about:blank"); + } + } + }); + } + xhrAlly.open("GET", "/?s=ally"); + xhrAlly.send(); + /* Exit immediately if extension has already been loaded */ let kaplus = document.getElementById("kaplus-marker"); if (kaplus) { @@ -544,11 +1547,14 @@ function main() { /* Customize navbar */ customizeNavbar(layCastleTopElements[0]); // test: OK + /* Store session storage values */ + storeOwnVillages(); + /* Parse url params and switch case */ let urlParams = new URLSearchParams(window.location.search); let section = urlParams.get("s"); let module = urlParams.get("m"); - let sub = urlParams.get("sub"); + let village = urlParams.get("village"); /* Choose action according to section, module and sub */ switch (section) { @@ -571,6 +1577,15 @@ function main() { } break; + case "tools": + showAutoPlanerMenu(village, module); + switch (module) { + case "auto_planer": + showAutoPlanerPage(village); + break; + } + break; + case "overview": showVillageUnitPoints(); // test: FAILED because settlement element has been removed break; @@ -578,14 +1593,12 @@ function main() { case "build_barracks": switch (module) { case "command": case null: - switch (sub) { - case null: - showBarrackSelectAllUnits(); // test: OK - break; - case "send": - showCountupTimeDecimals(); // test: OK - showOccurrencesInput(); // test: OK - break; + let sendCommandForm = document.getElementById("sendCommandForm"); + if (sendCommandForm !== null) { + showBarrackSelectAllUnits(); // test: OK + } else { + showCountupTimeDecimals(); // test: OK + showOccurrencesInput(); // test: OK } break; }