From 4ef73fb48296bfdf890510f4829fd8f71252d84d Mon Sep 17 00:00:00 2001 From: samuel Date: Wed, 3 Dec 2025 22:57:31 +0100 Subject: [PATCH] wip --- images/clock.svg | 1 + src/kaplus.css | 24 ++++ src/kaplus.js | 343 ++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 350 insertions(+), 18 deletions(-) create mode 100644 images/clock.svg 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/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..cce433d 100644 --- a/src/kaplus.js +++ b/src/kaplus.js @@ -1,7 +1,6 @@ -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": ["NAZGUL"] +}; function num(s) { return parseInt(s.replace(".", "")); @@ -119,10 +118,10 @@ function customizeNavbar(layCastleElement) { shortcutContainers[2].classList.add("shortcut_container_right"); } -function countUpMs() { +function countUpMs(tomorrow, after_tomorrow, movingDuration) { let arrivalDate = new Date(new Date().getTime() + movingDuration * 1000); let prefix = ""; - if (arrivalDate >= afterTomorrow) { + if (arrivalDate >= after_tomorrow) { prefix = "le " + arrivalDate.getDate() + "." + arrivalDate.getMonth() + " "; } else if (arrivalDate > tomorrow) { prefix = "demain "; @@ -138,6 +137,34 @@ function countUpMs() { + Math.floor(arrivalDate.getMilliseconds() / 100); } +function countdownMS(formattedDate) { + let now = new Date(); + if (formattedDate.startsWith("à")) { + let start = new Date(); + let hms = formattedDate.replace(/^à (\d\d:\d\d:\d\d) heures$/, "$1").split(":"); + start.setHours(parseInt(hms[0])); + start.setMinutes(parseInt(hms[1])); + start.setSeconds(parseInt(hms[2])); + return start.getTime() - now.getTime(); + } + if (formattedDate.startsWith("demain")) { + let start = new Date(now.getTime() + 24*60*60*1000); + let hms = formattedDate.replace(/^demain à (\d\d:\d\d:\d\d) heures$/, "$1").split(":"); + start.setHours(parseInt(hms[0])); + start.setMinutes(parseInt(hms[1])); + start.setSeconds(parseInt(hms[2])); + return start.getTime() - now.getTime(); + } + let start = new Date(); + let hms = formattedDate.replace(/^le (\d\d)\.(\d\d) à (\d\d:\d\d:\d\d) heures$/, "$1:$2:$3"); + start.setDate(parseInt(hms[0])); + start.setMonth(parseInt(hms[1])); + start.setHours(parseInt(hms[0])); + start.setMinutes(parseInt(hms[1])); + start.setSeconds(parseInt(hms[2])); + return start.getTime() - now.getTime(); +} + function removeAdsBanner() { /* Remove iframe banner */ document.getElementById("banner_skyscraper").remove(); @@ -207,6 +234,256 @@ function showPlayerUnitPointsAndId() { playerPropertiesTbody.insertBefore(createKeyValueRow("Id:", playerId), playerPropertiesRows[2]); } +function showPlanerAutonomous() { + // Exit if no attack is planed + let mainContentPane = document.getElementsByClassName("contentpane")[1]; + let planTable = mainContentPane.getElementsByClassName("borderlist")[2]; + let planRows = planTable.getElementsByTagName("tr"); + + setInterval(function() { + for (let i = 0; i < planRows.length; i ++) { + let planCells = planRows[i].getElementsByTagName("td"); + if (planCells.length < 9) { + break; + } + let startCell = planCells[4]; + let endCell = planCells[5]; + let countdownCell = planCells[6]; + let countdownSpan = countdownCell.firstChild; + if (countdownSpan.textContent === "0:00:00") { + countdownSpan.textContent = "en retard"; + startCell.style.color = "red"; + endCell.style.color = "red"; + } + } + }, 1000); + + if (planRows.length < 2) { + return; + } + + // Get an existing target from plan table + let targetCell = planRows[1].getElementsByTagName("td")[2]; + let targetXY = targetCell.getElementsByTagName("a")[1].textContent.split("|"); + + // Get token by submitting a fake support + let urlParams = new URLSearchParams(window.location.search); + let villageId = urlParams.get("village"); + let xhr = new XMLHttpRequest(); + let troopToken = null; + xhr.addEventListener("readystatechange", function () { + if (xhr.readyState === xhr.DONE) { + let parser = new DOMParser(); + let doc = parser.parseFromString(xhr.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) { + alert("Il faut avoir au moins une unité dans le village actuel pour déterminer le jeton de sécurité!"); + } + + let xhrSend = new XMLHttpRequest(); + let formData = new FormData(); + formData.set(unit, "1"); + formData.set("send_x", targetXY[0]); + formData.set("send_y", targetXY[1]); + formData.set("support", "Envoyer du renfort"); + 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"); + troopToken = action.replace(/^.*(p=([a-z0-9]+)).*$/, "$2"); + } + }); + xhrSend.open("POST", "/?village=" + villageId + "&s=build_barracks&m=command&sub=send"); + xhrSend.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + let formParams = new URLSearchParams(formData); + xhrSend.send(formParams.toString()); + } + }) + xhr.open("GET", "/?village=" + villageId + "&s=build_barracks"); + xhr.send(); + + // Display autonomous cell for each planed attack + let planHeaderCells = planRows[0].getElementsByTagName("th"); + planHeaderCells[6].textContent = "Manuel"; + planRows[0].insertBefore(createCustomElement("th", null, "Autonome"), planHeaderCells[6]); + planRows[0].insertBefore(createCustomElement("th", null, null, {"width": "18px"}), planHeaderCells[0]); + for (let i = 1; i < planRows.length; i ++) { + let planCells = planRows[i].getElementsByTagName("td"); + + let autonomousMarkerCell = createCustomElement("td"); + planRows[i].insertBefore(autonomousMarkerCell, planCells[0]); + + let startCell = planCells[4]; + let timeCounterCell = planCells[6]; + let timeCounterSpan = timeCounterCell.getElementsByTagName("span")[0]; + timeCounterSpan.setAttribute("reload", "false"); + + let autonomousCell = createCustomElement("td"); + let autonomousForm = createCustomElement("form", null, null, {"white-space": "nowrap"}); + + let attackLink = planCells[7].getElementsByTagName("a")[0] + let attackParams = new URLSearchParams(attackLink.getAttribute("href").replace(/^\/\?(.*)$/, "$1")); + for (let [key, value] of attackParams) { + switch (key) { + case "s": case "m": + break; + default: + let autonomousHidden = createCustomInput("hidden", key, value); + autonomousForm.appendChild(autonomousHidden); + break; + } + } + + let autonomousOccurrencesBox = createCustomElement( + "div", + {"title": "Préciser le nombre d'occurrences souhaité"}, + null, + {"display": "inline-block", "border": "1px solid grey", "vertical-align": "text-bottom", "margin": "0 3px"}, + ); + let autonomousOccurrencesPrefix = createCustomElement("span", null, "x", {"margin": "0 3px", "color": "grey"}); + let autonomousOccurrencesInput = createCustomInput( + "number", + "occurrences", + "1", + {"class": "autonoumous occurrences"}, + {"display": "inline-block", "width": "35px", "border": "none"}, + ); + autonomousOccurrencesBox.appendChild(autonomousOccurrencesPrefix); + autonomousOccurrencesBox.appendChild(autonomousOccurrencesInput); + autonomousForm.appendChild(autonomousOccurrencesBox); + + let autonomousAttack = createCustomInput( + "image", + null, + null, + {"src": "/img/command/attack.png", "title": "Pogrammer une attaque autonome", "class": "autonomous attack"}, + {"display": "inline-block", "margin": "0 3px", "border": "none"}, + ); + autonomousForm.appendChild(autonomousAttack); + let autonomousSupport = createCustomInput( + "image", + null, + null, + { + "src": "/img/command/support.png", + "title": "Programmer un renfort autonome", + "class": "autonomous support", + }, + {"display": "inline-block", "margin": "0 3px", "border": "none"}, + ); + autonomousForm.appendChild(autonomousSupport); + let autonomousCancel = createCustomInput( + "image", + null, + null, + { + "src": "/img/ico_delete.png", + "title": "Annuler la programmation autonome", + "class": "autonomous cancel", + "disabled": "disabled", + }, + {"display": "inline-block", "margin": "0 3px", "border": "none"}, + ); + autonomousForm.appendChild(autonomousCancel); + + let timeoutID = null; + autonomousForm.addEventListener("submit", function (event) { + event.preventDefault(); + + // Handle cancel button + if (event.submitter.classList.contains("cancel")) { + if (timeoutID !== null) { + clearTimeout(timeoutID); + timeoutID = null; + autonomousMarkerCell.firstChild.remove(); + autonomousOccurrencesInput.removeAttribute("disabled"); + autonomousAttack.removeAttribute("disabled"); + autonomousSupport.removeAttribute("disabled"); + autonomousCancel.setAttribute("disabled", "disabled"); + } + return; + } + + // Exit now if too late + if (timeCounterSpan.textContent === "en retard" || timeCounterSpan.textContent === "0:00:00") { + alert("Impossible de programmer le lancement autonome car l'heure de départ est déjà dépassée!"); + return; + } + + // Exit now if already programed + if (timeoutID !== null) { + return; + } + + // Parse form data + let formData = new FormData(this); + let occurrences = parseInt(formData.get("occurrences").toString()) + formData.delete("occurrences"); + if (event.submitter.classList.contains("attack")) { + formData.set("attack", "Attaquer"); + } + + // Program autonomous attack or support + let ms = countdownMS(startCell.textContent); + console.log("setTimeout", ms); + timeoutID = setTimeout(function() { + for (let i = 0; i < occurrences; i ++) { + let xhrTimeout = new XMLHttpRequest(); + xhrTimeout.open("POST", "/?s=build_barracks&m=command&a=sendTroop&p=" + troopToken); + xhrTimeout.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + let formParams = new URLSearchParams(formData); + xhrTimeout.send(formParams.toString()); + } + autonomousMarkerCell.firstChild.classList.remove("blink"); + autonomousOccurrencesInput.setAttribute("disabled", "disabled"); + autonomousAttack.setAttribute("disabled", "disabled"); + autonomousSupport.setAttribute("disabled", "disabled"); + autonomousCancel.setAttribute("disabled", "disabled"); + }, ms); + + // Add blinking marker + if (event.submitter.classList.contains("attack")) { + let attackImg = createCustomElement( + "img", + { + "src": "/img/command/attack.png", + "title": "Attaque autonome programmée", + "class": "autonomous blink", + } + ) + autonomousMarkerCell.appendChild(attackImg); + } else { + let supportImg = createCustomElement( + "img", + { + "src": "/img/command/support.png", + "title": "Renfort autonome programmé", + "class": "autonomous blink", + } + ) + autonomousMarkerCell.appendChild(supportImg); + } + autonomousOccurrencesInput.setAttribute("disabled", "disabled"); + autonomousAttack.setAttribute("disabled", "disabled"); + autonomousSupport.setAttribute("disabled", "disabled"); + autonomousCancel.removeAttribute("disabled"); + }) + autonomousCell.appendChild(autonomousForm); + planRows[i].insertBefore(autonomousCell, planCells[7]); + } +} + function showVillageUnitPoints() { let settlementElt = document.getElementById("settlement"); if (settlementElt === null) { @@ -339,9 +616,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 +630,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() { @@ -384,7 +665,9 @@ function showOccurrencesInput() { } }) 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()); } }); } @@ -523,12 +806,31 @@ function showSecondsAndCalculator() { } function main() { + /* Exit immediately if not on game page */ let layCastleTopElements = document.getElementsByClassName("lay_castle_top"); if (layCastleTopElements.length === 0) { return; } + /* Exit immediately if not authorized */ + let xhrAlly = new XMLHttpRequest(); + let serverID = window.location.hostname.replace(/^(s\d+)-.*$/, "$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) { @@ -548,7 +850,6 @@ function main() { let urlParams = new URLSearchParams(window.location.search); let section = urlParams.get("s"); let module = urlParams.get("m"); - let sub = urlParams.get("sub"); /* Choose action according to section, module and sub */ switch (section) { @@ -571,6 +872,14 @@ function main() { } break; + case "tools": + switch (module) { + case "attack_planer": + showPlanerAutonomous(); + break; + } + break; + case "overview": showVillageUnitPoints(); // test: FAILED because settlement element has been removed break; @@ -578,14 +887,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; }