Weist maximal mögliche Anzahl an Personal einem Fahrzeug zu. Originalskript von BOS-Ernie, angepasst und erweitert, zur Unterstützung der neusten Lehrgänge, durch leeSalami. Weitere Lehrgänge hinzugefügt von Manute1337.
// ==UserScript==
// @name * Personalzuweiser
// @namespace bos-ernie.leitstellenspiel.de
// @version 2.2.3
// @license BSD-3-Clause
// @author BOS-Ernie, leeSalami, Manute1337
// @description Weist maximal mögliche Anzahl an Personal einem Fahrzeug zu. Originalskript von BOS-Ernie, angepasst und erweitert, zur Unterstützung der neusten Lehrgänge, durch leeSalami. Weitere Lehrgänge hinzugefügt von Manute1337.
// @match https://*.leitstellenspiel.de/vehicles/*/zuweisung
// @icon https://www.google.com/s2/favicons?sz=64&domain=leitstellenspiel.de
// @run-at document-idle
// @grant none
// ==/UserScript==
(async function () {
'use strict';
updatePersonalCount();
const csrfToken = document.querySelector('meta[name=csrf-token]')?.content;
if (!csrfToken) {
return;
}
const loadingText = I18n.t('common.loading');
addButtonGroup();
async function assign() {
const assignedPersonsElement = getAssignedPersonsElement();
const numberOfAssignedPersonnel = parseInt(assignedPersonsElement.innerText);
const vehicleCapacity = parseInt(assignedPersonsElement.parentElement.firstElementChild.innerText);
let numberOfPersonnelToAssign = vehicleCapacity - numberOfAssignedPersonnel;
const vehicleTypeId = getVehicleTypeId();
if (numberOfPersonnelToAssign > 0 && vehicleTypeId !== null) {
const vehicleTraining = getIdentifierByVehicleTypeId(vehicleTypeId);
if (vehicleTraining === null) {
return;
}
const trainingCount = Object.keys(vehicleTraining).length;
if (trainingCount === 0) {
return;
}
if (trainingCount !== 1 || !('no_training' in vehicleTraining)) {
const rowsWithTraining = document.querySelectorAll('tbody tr:not([data-filterable-by="[]"]):has(a.btn-success):not(:has(span[data-education-key]))');
const sortedRowsWithTraining = sortRows(rowsWithTraining);
for (let i = 0, n = sortedRowsWithTraining.length; i < n; i++) {
let hasIdentifier = true;
let currentIdentifier = null;
for (const educationIdentifier in vehicleTraining) {
if (educationIdentifier === 'no_training') {
continue;
}
const rowHasTraining = sortedRowsWithTraining[i].dataset.filterableBy.includes('"' + educationIdentifier + '"');
if (vehicleTraining[educationIdentifier] === true && !rowHasTraining) {
hasIdentifier = false;
} else if (typeof vehicleTraining[educationIdentifier] === 'number') {
if (!rowHasTraining || vehicleTraining[educationIdentifier] <= 0) {
hasIdentifier = false;
} else {
hasIdentifier = true;
currentIdentifier = educationIdentifier;
break;
}
}
}
if (!hasIdentifier) {
continue;
}
if (await changeAssignment(sortedRowsWithTraining[i].querySelector('a.btn-success'))) {
numberOfPersonnelToAssign--;
if (currentIdentifier) {
vehicleTraining[currentIdentifier]--;
}
if (numberOfPersonnelToAssign === 0) {
break;
}
await new Promise(r => setTimeout(r, 5));
}
}
}
if (numberOfPersonnelToAssign === 0) {
return;
}
if ('no_training' in vehicleTraining) {
if (typeof vehicleTraining['no_training'] === 'number') {
numberOfPersonnelToAssign = Math.min(numberOfPersonnelToAssign, vehicleTraining['no_training']);
}
await assignPersonsWithoutTraining(numberOfPersonnelToAssign);
}
}
}
async function assignPersonsWithoutTraining(amount) {
const rowsWithoutTraining = document.querySelectorAll('tbody tr[data-filterable-by="[]"]:has(a.btn-success):not(:has(span[data-education-key]))');
const sortedRowsWithoutTraining = sortRows(rowsWithoutTraining);
for (let i = 0, n = sortedRowsWithoutTraining.length; i < n; i++) {
if (await changeAssignment(sortedRowsWithoutTraining[i].querySelector('a.btn-success'))) {
amount--;
if (amount === 0) {
break;
}
await new Promise(r => setTimeout(r, 5));
}
}
}
async function changeAssignment(button) {
if (button) {
const personalId = button.getAttribute('personal_id');
const personalElement = document.getElementById(`personal_${personalId}`);
personalElement.innerHTML = `<td colspan="4">${loadingText}</td>`;
try {
const response = await fetch(button.href, {
method: 'POST',
headers: {
'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
'x-csrf-token': csrfToken,
'x-requested-with': 'XMLHttpRequest',
},
});
if (!response.ok) {
return false;
}
personalElement.innerHTML = await response.text();
updatePersonalCount();
return true;
} catch (e) {
return false;
}
}
return false
}
function updatePersonalCount() {
const counterElement = document.getElementById('count_personal');
if (!counterElement) {
return;
}
const counter = document.querySelectorAll('.btn-assigned').length;
const vehicleCapacity = parseInt(counterElement.parentElement.firstElementChild.innerText);
counterElement.innerText = String(counter);
if (counter !== vehicleCapacity) {
counterElement.classList.remove('label-success');
counterElement.classList.add('label-warning');
} else {
counterElement.classList.remove('label-warning');
counterElement.classList.add('label-success');
}
}
function sortRows(rows) {
const vehicleId = getVehicleId();
return Array.from(rows)
.sort((a, b) => {
const aInVehicle = a.querySelector('td:nth-child(3) a')?.href?.endsWith('/' + vehicleId);
const bInVehicle = b.querySelector('td:nth-child(3) a')?.href?.endsWith('/' + vehicleId);
if ((aInVehicle === true && !bInVehicle) || (aInVehicle === undefined && bInVehicle === false)) {
return -1;
} else if (aInVehicle === bInVehicle) {
return 0;
} else {
return 1;
}
});
}
async function reset() {
const selectButtons = document.getElementsByClassName('btn btn-default btn-assigned');
// Since the click event removes the button from the DOM, only every second item would be clicked.
// To prevent this, the loop is executed backwards.
for (let i = selectButtons.length - 1; i >= 0; i--) {
await changeAssignment(selectButtons[i]);
await new Promise(r => setTimeout(r, 5));
}
}
function assignClickEvent(event) {
assign();
event.preventDefault();
}
function resetClickEvent(event) {
reset();
event.preventDefault();
}
function getAssignedPersonsElement() {
return document.getElementById("count_personal");
}
function addButtonGroup() {
const okIcon = document.createElement("span");
okIcon.className = "glyphicon glyphicon-ok";
const assignButton = document.createElement("button");
assignButton.type = "button";
assignButton.className = "btn btn-default";
assignButton.appendChild(okIcon);
assignButton.addEventListener("click", assignClickEvent);
const resetIcon = document.createElement("span");
resetIcon.className = "glyphicon glyphicon-trash";
const resetButton = document.createElement("button");
resetButton.type = "button";
resetButton.className = "btn btn-default";
resetButton.appendChild(resetIcon);
resetButton.addEventListener("click", resetClickEvent);
const buttonGroup = document.createElement("div");
buttonGroup.id = "vehicle-assigner-button-group";
buttonGroup.className = "btn-group";
buttonGroup.style.marginLeft = "5px";
buttonGroup.appendChild(assignButton);
buttonGroup.appendChild(resetButton);
// Append button group to element with class "vehicles-education-filter-box"
document.getElementsByClassName("vehicles-education-filter-box")[0].appendChild(buttonGroup);
}
function getVehicleId() {
return window.location.pathname.split("/")[2];
}
/**
* @return {number|null}
*/
function getVehicleTypeId() {
const vehicleId = getVehicleId();
const request = new XMLHttpRequest();
request.open("GET", `/api/v2/vehicles/${vehicleId}`, false);
request.send();
if (request.status === 200) {
const vehicle = JSON.parse(request.responseText);
return vehicle.result.vehicle_type;
}
return null;
}
/**
* @return {{}|null}
*/
function getIdentifierByVehicleTypeId(vehicleTypeId) {
switch (vehicleTypeId) {
case 0: //LF 20
return {'no_training': true};
case 1: //LF 10
return {'no_training': true};
case 2: //DLK 23
return {'no_training': true};
case 3: //ELW 1
return {'no_training': true};
case 4: //RW
return {'no_training': true};
case 5: //GW-A
return {'no_training': true};
case 6: //LF 8/6
return {'no_training': true};
case 7: //LF 20/16
return {'no_training': true};
case 8: //LF 10/6
return {'no_training': true};
case 9: //LF 16-TS
return {'no_training': true};
case 10: //GW-Öl
return {'no_training': true};
case 11: //GW-L2-Wasser
return {'no_training': true};
case 12: //GW-Messtechnik
return {'gw_messtechnik': true};
case 13: //SW 1000
return {'no_training': true};
case 14: //SW 2000
return {'no_training': true};
case 15: //SW 2000-Tr
return {'no_training': true};
case 16: //SW Kats
return {'no_training': true};
case 17: //TLF 2000
return {'no_training': true};
case 18: //TLF 3000
return {'no_training': true};
case 19: //TLF 8/8
return {'no_training': true};
case 20: //TLF 8/18
return {'no_training': true};
case 21: //TLF 16/24-Tr
return {'no_training': true};
case 22: //TLF 16/25
return {'no_training': true};
case 23: //TLF 16/45
return {'no_training': true};
case 24: //TLF 20/40
return {'no_training': true};
case 25: //TLF 20/40-SL
return {'no_training': true};
case 26: //TLF 16
return {'no_training': true};
case 27: //GW-Gefahrgut
return {'gw_gefahrgut': true};
case 28: //RTW
return {'no_training': true};
case 29: //NEF
return {'notarzt': true};
case 30: //HLF 20
return {'no_training': true};
case 31: //RTH
return {'notarzt': true};
case 32: //FuStW
return {'no_training': true};
case 33: //GW-Höhenrettung
return {'gw_hoehenrettung': true};
case 34: //ELW 2
return {'elw2': true};
case 35: //leBefKw
return {'police_einsatzleiter': true};
case 36: //MTW
return {'no_training': true};
case 37: //TSF-W
return {'no_training': true};
case 38: //KTW
return {'no_training': true};
case 39: //GKW
return {'no_training': true};
case 40: //MTW-TZ
return {'thw_zugtrupp': true};
case 41: //MzKW
return {'no_training': true};
case 42: //LKW K 9
return {'thw_raumen': true};
case 45: //MLW 5
return {'thw_raumen': true};
case 46: //WLF
return {'wechsellader': true};
case 50: //GruKw
return {'no_training': true};
case 51: //FüKw
return {'police_fukw': true};
case 52: //GefKw
return {'no_training': true};
case 53: //Dekon-P
return {'dekon_p': true};
case 55: //KdoW-LNA
return {'lna': true};
case 56: //KdoW-OrgL
return {'orgl': true};
case 57: //FwK
return {'fwk': true};
case 58: //KTW Typ B
return {'no_training': true};
case 59: //ELW 1 (SEG)
return {'seg_elw': true};
case 60: //GW-San
return {'seg_gw_san': true};
case 61: //Polizeihubschrauber
return {'polizeihubschrauber': true};
case 63: //GW-Taucher
return {'gw_taucher': true};
case 64: //GW-Wasserrettung
return {'gw_wasserrettung': true};
case 65: //LKW 7 Lkr 19 tm
return {'no_training': true};
case 69: //Tauchkraftwagen
return {'gw_taucher': true};
case 72: //WaWe 10
return {'police_wasserwerfer': true};
case 73: //GRTW
return {'notarzt': 1, 'no_training': 5};
case 74: //NAW
return {'notarzt': 1, 'no_training': 2};
case 75: //FLF
return {'arff': true};
case 76: //Rettungstreppe
return {'rettungstreppe': true};
case 79: //SEK - ZF
return {'police_sek': true};
case 80: //SEK - MTF
return {'police_sek': true};
case 81: //MEK - ZF
return {'police_mek': true};
case 82: //MEK - MTF
return {'police_mek': true};
case 83: //GW-Werkfeuerwehr
return {'werkfeuerwehr': true};
case 84: //ULF mit Löscharm
return {'werkfeuerwehr': true};
case 85: //TM 50
return {'werkfeuerwehr': true};
case 86: //Turbolöscher
return {'werkfeuerwehr': true};
case 87: //TLF 4000
return {'no_training': true};
case 88: //KLF
return {'no_training': true};
case 89: //MLF
return {'no_training': true};
case 90: //HLF 10
return {'no_training': true};
case 91: //Rettungshundefahrzeug
return {'seg_rescue_dogs': true};
case 93: //MTW-O
return {'thw_rescue_dogs': true};
case 94: //DHuFüKw
return {'k9': true};
case 95: //Polizeimotorrad
return {'police_motorcycle': true};
case 97: //ITW
return {'intensive_care': 2, 'notarzt': 1};
case 98: //Zivilstreifenwagen
return {'criminal_investigation': true};
case 99: //LKW 7 Lbw
return {'water_damage_pump': true};
case 100: //MLW 4
return {'water_damage_pump': true};
case 103: //FuStW (DGL)
return {'police_service_group_leader': true};
case 104: //GW-L1
return {'no_training': true};
case 105: //GW-L2
return {'no_training': true};
case 106: //MTF-L
return {'no_training': true};
case 107: //LF-L
return {'no_training': true};
case 109: //MzGW SB
return {'heavy_rescue': true};
case 114: //GW-Lüfter
return {'no_training': true};
case 121: //GTLF
return {'no_training': true};
case 122: //LKW 7 Lbw (FGr E)
return {'thw_energy_supply': true};
case 123: //LKW 7 Lbw (FGr WP)
return {'water_damage_pump': true};
case 124: //MTW-OV
return {'no_training': true};
case 125: //MTW-Tr UL
return {'thw_drone': true};
case 126: //MTF Drohne
return {'fire_drone': true};
case 127: //GW UAS
return {'seg_drone': true};
case 128: //ELW Drohne
return {'fire_drone': true};
case 129: //ELW2 Drohne
return {'fire_drone': true, 'elw2': true};
case 130: //GW-Bt
return {'care_service': 1, 'care_service_equipment': 2};
case 131: //Bt-Kombi
return {'care_service': true};
case 133: //Bt LKW
return {'care_service': 1, 'care_service_equipment': 2};
case 134: //Pferdetransporter klein
return {'police_horse': true};
case 135: //Pferdetransporter groß
return {'police_horse': true};
case 137: //Zugfahrzeug Pferdetransport
return {'police_horse': true};
case 140: //MTW-Verpflegung
return {"fire_care_service": true};
case 144: //FüKw (THW)
return {"thw_command": true};
case 145: //FüKomKW
return {"thw_command": true};
case 147: //FmKW
return {"thw_command": true};
case 148: //MTW Fgr K
return {"thw_command": true};
default:
return null;
}
}
})();