您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Wirbt benötigtes Personal für eine THW-Wache an.
// ==UserScript== // @name * THW Personalbeschaffer // @namespace bos-ernie.leitstellenspiel.de // @version 1.1.0 // @license BSD-3-Clause // @author BOS-Ernie edited by gonscher // @description Wirbt benötigtes Personal für eine THW-Wache an. // @match https://www.leitstellenspiel.de/buildings/* // @match https://polizei.leitstellenspiel.de/buildings/* // @icon https://www.google.com/s2/favicons?sz=64&domain=leitstellenspiel.de // @run-at document-idle // @grant none // @resource https://forum.leitstellenspiel.de/index.php?thread/26995-script-thw-personalbeschaffer/ // ==/UserScript== /* global $, loadedBuildings */ (function () { "use strict"; const minimalRemainingPersonnelInTHW = 50; // https://api.lss-manager.de/de_DE/schoolings const personnelSettingsInternal = [ { caption: "Ohne Ausbildung", key: null, numberOfRequiredPersonnel: 50, numberOfSelectedPersonnel: 0, }, { caption: "Zugtrupp", key: "thw_zugtrupp", numberOfRequiredPersonnel: 8, numberOfSelectedPersonnel: 0, }, { caption: "FG Räumen", key: "thw_raumen", numberOfRequiredPersonnel: 9, numberOfSelectedPersonnel: 0, }, { caption: "FG Bergungstaucher", key: "gw_taucher", numberOfRequiredPersonnel: 2, numberOfSelectedPersonnel: 0, }, { caption: "FG Rettungshundeführer", key: "thw_rescue_dogs", numberOfRequiredPersonnel: 10, numberOfSelectedPersonnel: 0, }, { caption: "FG Wasserschaden/Pumpen", key: "water_damage_pump", numberOfRequiredPersonnel: 10, numberOfSelectedPersonnel: 0, }, { caption: "FG SB", key: "heavy_rescue", numberOfRequiredPersonnel: 9, numberOfSelectedPersonnel: 0, }, { caption: "FG Elektr.Vers.", key: "thw_energy_supply", numberOfRequiredPersonnel: 3, numberOfSelectedPersonnel: 0, }, { caption: "Trupp Unbemannte Luftfahrtsysteme", key: "thw_drone", numberOfRequiredPersonnel: 4, numberOfSelectedPersonnel: 0, }, { caption: "FZ FüKom", key: "thw_command", numberOfRequiredPersonnel: 22, numberOfSelectedPersonnel: 0, }, ]; const personnelSettingsProxy = personnelSettingsInternal.map(setting => { return new Proxy(setting, { set: function (target, key, value) { target[key] = value; updateFooter(target.key, target.numberOfSelectedPersonnel); return true; }, }); }); function initPanelBodies() { const elements = document.getElementsByClassName("panel-body"); for (let i = 0; i < elements.length; i++) { elements[i].classList.add("hidden"); } } function removePanelHeadingClickEvent() { const elements = document.getElementsByClassName("personal-select-heading"); for (let i = 0; i < elements.length; i++) { elements[i].replaceWith(elements[i].cloneNode(true)); elements[i].addEventListener("click", panelHeadingClickEvent); } } function addFooter() { const wrapper = document.createElement("div"); wrapper.style = "display: flex; flex-wrap: wrap; flex-direction: row; column-gap: 15px"; const list = document.createElement("ul"); list.classList.add("list-inline"); list.style = "color: #fff;padding-top: 8px;"; for (let i = 0; i < personnelSettingsProxy.length; i++) { const setting = personnelSettingsProxy[i]; list.appendChild(createTotalSummaryElement(setting)); } wrapper.appendChild(list); const nav = document.querySelector(".navbar.navbar-default.navbar-fixed-bottom"); nav.children[0].children[0].insertAdjacentElement("afterend", wrapper); } function updateFooter(key, selectedPersonnel) { document.getElementById("number-of-selected-personnel-" + key).innerHTML = selectedPersonnel; const requiredPersonnel = personnelSettingsProxy.find(setting => setting.key === key).numberOfRequiredPersonnel; const labelClass = selectedPersonnel === requiredPersonnel ? "label-success" : "label-warning"; const spanPersonnel = document.getElementById("personnel-" + key); spanPersonnel.classList.remove("label-success", "label-warning"); spanPersonnel.classList.add(labelClass); } function addClickEventHandlerToCheckboxes() { const inputElements = document.getElementsByClassName("schooling_checkbox"); for (let i = 0; i < inputElements.length; i++) { inputElements[i].addEventListener("change", updateNumberOfSelectedPersonnel); } } function updateNumberOfSelectedPersonnel(event) { const attributes = Object.keys(event.target.attributes); let attributeIndex = attributes.find(key => event.target.attributes[key].value === "true"); let key = null; if (attributeIndex !== undefined) { key = event.target.attributes[attributeIndex].name; } const setting = personnelSettingsProxy.find(setting => setting.key === key); if (event.target.checked) { setting.numberOfSelectedPersonnel = setting.numberOfSelectedPersonnel + 1; } else { setting.numberOfSelectedPersonnel = setting.numberOfSelectedPersonnel - 1; } const buildingId = event.target.getAttribute("building_id"); const panelHeading = document.querySelector(".personal-select-heading[building_id='" + buildingId + "']"); } function addPersonnelSelector() { let elements = document.getElementsByClassName("panel-heading personal-select-heading"); for (let i = 0; i < elements.length; i++) { const element = elements[i]; const buildingId = element.getAttribute("building_id"); element.children[1].prepend(createPersonnelSelector(buildingId)); } } function createPersonnelSelector(buildingId) { const trashIcon = document.createElement("span"); trashIcon.classList.add("glyphicon", "glyphicon-trash"); const resetButton = document.createElement("button"); resetButton.classList.add("btn", "btn-xs", "btn-default", "personnel-reset-button"); resetButton.setAttribute("type", "button"); resetButton.setAttribute("data-building-id", buildingId); resetButton.addEventListener("click", resetPersonnelClick); resetButton.appendChild(trashIcon); const buttonGroup = document.createElement("div"); buttonGroup.classList.add("btn-group", "btn-group-xs"); buttonGroup.setAttribute("role", "group"); buttonGroup.appendChild(resetButton); buttonGroup.appendChild(createSelectButton(buildingId)); return buttonGroup; } function createSelectButton(buildingId) { const userIcon = document.createElement("span"); userIcon.classList.add("glyphicon", "glyphicon-user"); const button = document.createElement("button"); button.classList.add("btn", "btn-xs", "btn-default", "personnel-select-button"); button.setAttribute("id", "personnel-select-button-" + buildingId); button.setAttribute("type", "button"); button.setAttribute("data-building-id", buildingId); button.addEventListener("click", selectPersonnelClick); button.innerText = " Personal auswählen"; button.prepend(userIcon); return button; } function createTotalSummaryElement(setting) { const listItem = document.createElement("li"); const spanCaption = document.createElement("span"); spanCaption.innerHTML = setting.caption + ": "; const spanSelected = document.createElement("span"); spanSelected.setAttribute("id", "number-of-selected-personnel-" + setting.key); spanSelected.innerHTML = "0"; const spanRequired = document.createElement("span"); spanRequired.setAttribute("id", "number-of-required-personnel-" + setting.key); spanRequired.innerHTML = setting.numberOfRequiredPersonnel; const spanPersonnel = document.createElement("span"); spanPersonnel.setAttribute("id", "personnel-" + setting.key); spanPersonnel.classList.add("label", "label-warning"); spanPersonnel.appendChild(spanSelected); spanPersonnel.appendChild(document.createTextNode("/")); spanPersonnel.appendChild(spanRequired); listItem.appendChild(spanCaption); listItem.appendChild(spanPersonnel); return listItem; } async function selectPersonnelClick(event) { event.preventDefault(); const button = event.target.closest("button"); button.disabled = true; button.classList.remove("btn-default"); button.classList.add("btn-success"); const okIcon = document.createElement("span"); okIcon.classList.add("glyphicon", "glyphicon-ok"); button.replaceChild(okIcon, button.children[0]); const buildingId = button.dataset.buildingId; await selectPersonnel(buildingId); const panelBody = getPanelBody(buildingId); const numberOfSelectedPersonnel = panelBody.querySelectorAll("input:checked").length; button.innerHTML = button.innerHTML + " (" + numberOfSelectedPersonnel + ")"; } async function resetPersonnelClick(event) { event.preventDefault(); const resetButton = event.target.closest("button"); const buildingId = resetButton.dataset.buildingId; const selectButton = createSelectButton(buildingId); document.getElementById("personnel-select-button-" + buildingId).replaceWith(selectButton); await resetPersonnel(buildingId); } async function selectPersonnel(buildingId) { await panelHeadingClick(buildingId); for (let i = personnelSettingsProxy.length - 1; i >= 0; i--) { const setting = personnelSettingsProxy[i]; const panelBody = getPanelBody(buildingId); let inputElements = []; if (setting.key === null) { const schoolingCells = panelBody.querySelectorAll("td[id^='school_personal_education_']"); for (let j = 0; j < schoolingCells.length; j++) { const schoolingCell = schoolingCells[j]; // Personnel with anything but whitespace in schooling column, have a schooling and should not be selected if (schoolingCell.innerHTML.replace(/\s/g, "").length > 0) { continue; } inputElements.push(schoolingCell.parentElement.children[0].children[0]); } } else { inputElements = panelBody.querySelectorAll("input[" + setting.key + "='true']"); } inputElements = Array.from(inputElements).filter(function (element) { if (typeof element === "undefined") { return false; } return element.parentElement.parentElement.children[3].innerHTML.replace(/\s/g, "").length === 0; }); let j = inputElements.length - 1; while (setting.numberOfSelectedPersonnel < setting.numberOfRequiredPersonnel && j >= 0) { inputElements[j].click(); --j; if ( setting.key === null && inputElements.length - setting.numberOfSelectedPersonnel <= minimalRemainingPersonnelInTHW ) { break; } } } } function resetPersonnel(buildingId) { const panelBody = getPanelBody(buildingId); const inputElements = panelBody.querySelectorAll("input:checked"); for (let i = 0; i < inputElements.length; i++) { inputElements[i].click(); } } async function panelHeadingClickEvent(event) { // Skip redundant panelHeadingClick call which is handled by button click event if ( event.target.classList.contains("personnel-select-button") || event.target.classList.contains("glyphicon-trash") ) { return; } let buildingIdElement = event.target.outerHTML.match(/building_id="(\d+)"/); if (buildingIdElement === null) { buildingIdElement = event.target.parentElement.parentElement.parentElement.parentElement.outerHTML.match(/building_id="(\d+)"/); } await panelHeadingClick(buildingIdElement[1], true); } async function panelHeadingClick(buildingId, toggle = false) { const panelHeading = getPanelHeading(buildingId); const panelBody = getPanelBody(buildingId); const href = panelHeading.outerHTML.match(/href="([^"]+)"/)[1]; if (loadedBuildings.indexOf(href) > -1) { if (toggle) { togglePanelBody(panelBody); } return; } loadedBuildings.push(href); await $.get(href, function (data) { panelBody.innerHTML = data; }); const schoolingSelectAvailableButtons = panelBody.getElementsByClassName("schooling_select_available"); for (let i = 0; i < schoolingSelectAvailableButtons.length; i++) { schoolingSelectAvailableButtons[i].parentElement.remove(); } addClickEventHandlerToCheckboxes(); if (toggle) { showPanelBody(panelBody); } } function togglePanelBody(panelBody) { if (panelBody.classList.contains("hidden")) { panelBody.classList.remove("hidden"); } else { panelBody.classList.add("hidden"); } } function showPanelBody(panelBody) { if (panelBody.classList.contains("hidden")) { panelBody.classList.remove("hidden"); } } function getPanelHeading(buildingId) { return document.querySelector(".personal-select-heading[building_id='" + buildingId + "']"); } function getPanelBody(buildingId) { return document.querySelector(".panel-body[building_id='" + buildingId + "']"); } function main() { if (!window.location.href.match(/\/buildings\/\d+\/hire/)) { return; } const h1 = document.querySelector("h1[building_type]"); if (!h1 || h1.getAttribute("building_type") !== "9") { return; } initPanelBodies(); removePanelHeadingClickEvent(); addPersonnelSelector(); addFooter(); } main(); })();