// ==UserScript== // @name [LSS]FirstResponderReloaded // @namespace FirstRespond // @version 2.0.1 // @description Wählt das nächstgelegene FirstResponder-Fahrzeug aus (Original von JuMaHo und DrTraxx) // @author SaibotH // @license MIT // @homepage https://github.com/SaibotH-LSS/LSSFirstResponderReloaded // @homepageURL https://github.com/SaibotH-LSS/LSSFirstResponderReloaded // @supportURL https://github.com/SaibotH-LSS/LSSFirstResponderReloaded/issues // @updateURL https://raw.githubusercontent.com/SaibotH-LSS/LSSFirstResponderReloaded/main/LSSFirstResponderReloaded.user.js // @downloadURL https://raw.githubusercontent.com/SaibotH-LSS/LSSFirstResponderReloaded/main/LSSFirstResponderReloaded.user.js // @icon https://www.leitstellenspiel.de/favicon.ico // @match *.leitstellenspiel.de // @match *.leitstellenspiel.de/missions/* // @match *.leitstellenspiel.de/aaos/*/edit // @match *.leitstellenspiel.de/buildings/*/edit // @run-at document-idle // @grant GM_info // ==/UserScript== // Definition von globalen Variablen um Fehlermeldungen zu unterdrücken /* global $,I18n */ (async function() { 'use strict'; // ###################### // Funktionsdeklarationen // ###################### function versioning(version) { logging.data(version, "Versionsnummer an versioning() übergeben: "); var dataChanged = false; // Versionssprung von TBD auf 2.0.0 if (frrSettings.scriptVersion === "TBD") { // Alte Daten in neuen Speicher laden // firstResponder aus den alten Daten holen und anschließend löschen if (localStorage.firstResponder) { const oldData = JSON.parse(localStorage.getItem('firstResponder')); if (frrSettings[lang].vehicleTypes.settings.length === 0 && oldData.vehicleTypes[lang]) { frrSettings[lang].vehicleTypes.settings = oldData.vehicleTypes[lang]; logging.log("Alte Fahrzeugeinstellungen wurden übernommen!"); } if (frrSettings[lang].aaoId === "00000000" && oldData.aaoId[lang]) { frrSettings[lang].aaoId = oldData.aaoId[lang]; frrSettings[lang].general.fWoAao = false; //AAO ist vorhanden daher Nutzung mit AAO logging.log("Alte AAO wurde übernommen!"); } // alte Daten nach der Übernahme löschen. localStorage.removeItem('firstResponder'); logging.log("firstResponder wurde aus localStorage gelöscht!") } // fr_dispatchSetup aus den alten Daten holen und anschließend löschen if (localStorage.fr_dispatchSetup) { const oldData = JSON.parse(localStorage.getItem('fr_dispatchSetup')); // Dispatch IDs holen if (frrSettings[lang].allowedBuildingIds.length === 0) { frrSettings[lang].allowedBuildingIds = oldData.dispatchId; logging.log("Alte erlaubte Gebäude (Leitstellen) wurden übernommen!"); } // Additional buildings holen if (frrSettings[lang].addBuildingIds.length === 0) { frrSettings[lang].addBuildingIds = oldData.additionalBuildings; logging.log("Alte zusätzliche Wachen wurden übernommen!"); } // UseIt holen if (frrSettings[lang].general.fUseDispatch !== oldData.useIt) { frrSettings[lang].general.fUseDispatch = oldData.useIt; logging.log("Alte UseIt einstellung wurde übernommen!"); } // alte Daten nach der Übernahme löschen. localStorage.removeItem('fr_dispatchSetup'); logging.log("fr_dispatchSetup wurde aus localStorage gelöscht!"); } if (localStorage.aVehicleTypesNew) { localStorage.removeItem('aVehicleTypesNew'); logging.log("aVehicleTypesNew wurde aus localStorage gelöscht"); } frrSettings.scriptVersion = "2.0.0" dataChanged = true; logging.log("Versioning hat Version TBD zu 2.0.0 übersetzt") } // Versionssprung von 2.0.0 auf 2.0.1 if (frrSettings.scriptVersion === "2.0.0") frrSettings.scriptVersion = "2.0.1"; // !!!Letzter Schritt im Versioning!!! if (dataChanged) { localStorage.setItem('frrSettings', JSON.stringify(frrSettings)); logging.log("Versioning hat Daten in localStorage aktualisiert"); } } function getFirstResponder() { var retVal = {}; var fFirstResponderFound = false; // alle Checkboxen durchgehen ob es ein Fahrzeug gibt welches als First Responde erlaubt ist. $(".vehicle_checkbox").each(function() { retVal.vType = +$(this).attr("vehicle_type_id"); // Fahrzeug Typ ID retVal.vId = $(this).attr("value"); //Fahrzeug ID retVal.lstId = +$(this).attr("building_id").split("_")[1]; //Leitstellen ID retVal.buId = +$(this).attr("building_id").split("_")[0]; // Gebäude ID retVal.timeAttr = $("#vehicle_sort_" + retVal.vId).attr("timevalue"); if (frrSettings[lang].vehicleTypes.settings.includes(retVal.vType) && //Fahrzeugtyp wurde in den Einstellungen ausgewählt UND !this.checked && // Checkbox ist NICHT angewählt UND !this.disabled && // Checkbox ist NICHT deaktiviert UND (frrSettings[lang].general.fUseDispatch === false || frrSettings[lang].allowedBuildingIds.includes(retVal.lstId) || frrSettings[lang].addBuildingIds.includes(retVal.buId))) { // Gebäudesettings werden erfüllt. fFirstResponderFound = true; logging.data(retVal, "First Responder wurde gefunden. Daten aus getFirstResponder: "); return false; } }) // Fahrzeug zurückgeben wenn eines gefunden wurde. if (fFirstResponderFound === true) { return retVal; } else { logging.log("getFirstResponder hat kein passendes Fahrzeug gefunden"); return undefined; } } // Funktion für die Auswahl des First Responders, der Alarmierung und des Teilens des Einsatzes function frrAlert() { if (!fFrrDone) { const firstResponder = getFirstResponder() if (firstResponder) { let shareButton = $( ".alert_next_alliance" )[0]; $("#vehicle_checkbox_" + firstResponder.vId).click(); logging.log("First Responder wurde ausgewählt"); fFrrDone = true; if (shareButton && frrSettings[lang].general.fAutoShare) { logging.log("Sharebutton gefunden und draufgeklickt"); setTimeout(function() { $( ".alert_next_alliance" )[0].click(); },frrSettings[lang].general.alarmDelay * 1000); } else if (frrSettings[lang].general.fAutoAlert) { logging.log('Sharebutton nicht gefunden oder automatisches Teilen abgeschaltet. Alarmieren und nächster Einsatz geklickt. Autoshare: ' + frrSettings[lang].general.fAutoShare) setTimeout(function() { $( ".alert_next" )[0].click(); },frrSettings[lang].general.alarmDelay * 1000); } else { logging.log("Sharebutton nicht gefunden oder automatisches Teilen abgeschaltet UND automatisches Alarmieren abgeschaltet. Autoshare: " + frrSettings[lang].general.fAutoShare + " AutoAlert: " + frrSettings[lang].general.fAutoAlert); } } else { logging.error("frrAlert(): Es konnte kein First Responder alarmiert werden!") } } else { logging.log("frrAlert() wurde bereits ausgeführt!"); } } // Fügt die Zeit zum AAO Button hinzu function getAaoTime() { const firstResponder = getFirstResponder(); // Prüfen ob es einen First Responder gibt und das Zeitattribut vorhanden ist if (!firstResponder) { logging.log("Kein First Responder gefunden!"); return "no FR"; } if (firstResponder.timeAttr === undefined) { logging.log("Zeitattribut ist nicht vorhanden!"); return "N/A"; } //Zeit Formattieren und in das Textfeld schreiben var seconds = parseInt(firstResponder.timeAttr); var minutes = Math.floor(seconds / 60); var remainingSeconds = seconds % 60; var formattedMinutes = (minutes < 10) ? "0" + minutes : minutes; var formattedSeconds = (remainingSeconds < 10) ? "0" + remainingSeconds : remainingSeconds; var frrTime = formattedMinutes + ":" + formattedSeconds; logging.data(frrTime, "Zeit erstellt und zurückgegeben: "); return frrTime } // Loggingfunktion die nur ausgeführt wird wenn bLoggingOn true ist function logging() { // Info Funktion logging.info = function(message) { if (fLoggingOn || frrSettings[lang].general.fLoggingOn) console.info("FirstResponderReloaded Info: ",message); } // Normales Logging logging.log = function(message) { if (fLoggingOn || frrSettings[lang].general.fLoggingOn) console.log("FirstResponderReloaded Logging: ",message); } // Logging von Daten logging.data = function(data, message) { if (fLoggingOn || frrSettings[lang].general.fLoggingOn) console.log("FirstResponderReloaded Objektlogging: ", message, data); } // Warnung logging.warn = function(message) { if (fLoggingOn || frrSettings[lang].general.fLoggingOn) console.warn("FirstResponderReloaded Warnung: ",message); } // Error (WIRD IMMER GESCHRIEBEN) logging.error = function(message) { console.error("FirstResponderReloaded Error: ", message); } } // Funktion zum Hinzufügen von Prefixen zum Fahrzeugnamen. Priorität dient dazu gewisse Fahrzeuge z.B. dem Rettungsdienst zuzuweisen anstatt der Feuerwehr da das entsprechende Fahrzeug in beiden Wachen stationiert sein kann. function updateCaptionPrefix(vehicle) { const buildingMap = [ { prefix: "Feuer - ", buildings: [0, 18], priority: 6 }, // Feuerwache { prefix: "Rettung - ", buildings: [2, 5, 20], priority: 1 }, // Rettungsdienstwache { prefix: "Polizei - ", buildings: [6, 11, 13, 17, 19, 24], priority: 5 }, // Polizei { prefix: "THW - ", buildings: [9], priority: 4 }, // THW { prefix: "SEG - ", buildings: [12, 20], priority: 3 }, // SEG { prefix: "Wasser - ", buildings: [15], priority: 2 } // Wasserrettung ]; const possibleBuildings = vehicle.possibleBuildings; const caption = vehicle.caption; // Sortiere buildingMap nach Priorität const sortedBuildingMap = buildingMap.sort((a, b) => a.priority - b.priority); let prefixFound = false; // Flag, um zu überprüfen, ob ein Präfix gefunden wurde for (const entry of sortedBuildingMap) { if (possibleBuildings.some(building => entry.buildings.includes(building))) { // Wenn mindestens ein Gebäude dem aktuellen Präfix entspricht, // füge den Präfix zur Caption hinzu vehicle.caption = entry.prefix + caption; prefixFound = true; break; // Da nur ein Präfix hinzugefügt werden soll, brechen wir die Schleife ab. }; }; // Wenn kein Prefix gefunden wurde wird ZZZ als Prefix genutzt. if (!prefixFound) { logging.warn(`Kein Prefix für Fahrzeug gefunden! Fahrzeugname: ${caption}`); vehicle.caption = "ZZZ - " + caption; } }; // Holt die Fahrzeugdaten aus der LSSM API ab, verarbeitet diese (Präfix und Fahrzeugnamenliste) und legt diese im local Storage ab. async function fetchVehicles(lang) { logging.data(Object.keys(frrSettings[lang].vehicleTypes.data).length, "Länge der Daten: "); logging.data(frrSettings[lang].vehicleTypes.lastUpdate, "Zeitstempel der alten Daten: ") logging.data(new Date().getTime(), "Aktueller Zeitstempel: "); // Daten werden abgerufen und bearbeitet wenn noch keine vorhanden sind oder die Daten zu alt sind if (Object.keys(frrSettings[lang].vehicleTypes.data).length === 0 || frrSettings[lang].vehicleTypes.lastUpdate < (new Date().getTime() - 5 * 1000 * 60)) { logging.log("Daten werden abgefragt"); // Daten werden abgerufen und über try ... catch Fehler abgefangen. try { frrSettings[lang].vehicleTypes.data = await $.getJSON("https://api.lss-manager.de/" + lang + "/vehicles"); // Ruft die Daten ab. Wenn ein Error kommt wird der folgende Code nicht mehr bearbeitet. frrSettings[lang].vehicleTypes.lastUpdate = new Date().getTime(); // Setzt den Update Zeitstempel wenn die Daten erfolgreich abgerufen wurden. logging.data(frrSettings[lang].vehicleTypes.data, "Neue Daten für Prefixing: "); // Prefix hinzufügen Object.keys(frrSettings[lang].vehicleTypes.data).forEach(function(key) { const vehicle = frrSettings[lang].vehicleTypes.data[key]; updateCaptionPrefix(vehicle); }); logging.log("Päfix hinzugefügt!"); // Speichert die Fahrzeugnamen in ein Array und Sortiert es frrSettings[lang].vehicleTypes.captionList = []; for (const [vehicleId, vehicleData] of Object.entries(frrSettings[lang].vehicleTypes.data)) { frrSettings[lang].vehicleTypes.captionList.push(vehicleData.caption); } frrSettings[lang].vehicleTypes.captionList.sort((a, b) => a.toUpperCase() > b.toUpperCase() ? 1 : -1); localStorage.setItem("frrSettings", JSON.stringify(frrSettings)); // Neue Daten werden in localStorage gespeichert logging.data(frrSettings[lang].vehicleTypes.data, "Daten aus API erfolgreich ausgelesen. Daten: "); } catch(error) { if (error.readyState === 0 && error.statusText === "error") { logging.error("Fehler beim Abrufen der API: Netzwerkfehler oder CORS-Problem"); } else { logging.error(error, "Sonstiger Fehler beim Abrufen der API: "); } } } else logging.info("Daten sind noch aktuell!") } // Je nach Trigger werden die Namen oder die IDs eines Arrays oder eines Objekts (dataSet) die zu einem anderen Array passen (mapArray) als neues Array (retVal) ausgegeben function mapping(dataSet, mapArray, trigger) { logging.data(dataSet, "Mapping dataSet: "); logging.data(mapArray, "Mapping mapArray: "); logging.data(trigger, "Mapping trigger: "); if (trigger !== "caption" && trigger !== "id") { logging.error("Mapping: Ungültiger Trigger!"); return []; } const retVal = []; // Überprüfen, ob dataSet ein Array oder ein Objekt ist if (Array.isArray(dataSet)) { dataSet.forEach(obj => { if (trigger === "caption" && mapArray.includes(obj.id)) { retVal.push(obj.caption); } else if (trigger === "id" && mapArray.includes(obj.caption)) { retVal.push(obj.id); } }); } else if (typeof dataSet === 'object') { for (const id in dataSet) { const obj = dataSet[id]; if (trigger === "caption" && mapArray.includes(parseInt(id))) { retVal.push(obj.caption); } else if (trigger === "id" && mapArray.includes(obj.caption)) { retVal.push(parseInt(id)); } } } else { logging.error("Mapping: Ungültiger DataSet-Typ!"); } return retVal; } // Funktion zum Erstellen des frrSettings Objekt function createSettingsObject() { let newObject = { scriptVersion: "TBD", // Zuletzt verwendete Script Version [lang]:{ aaoId: "00000000", // Hier wird die eingestellte AAO Id gespeichert general: { // Hier werden allgemeine Parameter gespeichert fAutoAlert: false, // Automatisch alarmieren wenn FRR ausgeführt wird fAutoShare: false, // Automatisch alarmieren und teilen wenn FRR ausgeführt wird jsKeyCode: 86, // Javascript Code für den HotKey. Wird mit v-Taste vorbelegt. 65=a 86=v - nicht unbeding ASCII! Siehe hier: https://www.toptal.com/developers/keycode fLoggingOn: false, // Schalter für logging fUseDispatch: false, // nur Fahrzeuge bestimmter Leitstellen nutzen alarmDelay: 1, // Standardmäßiges Delay beim automatischen alarmieren fWoAao: true // Alarmierung mit/ohne AAO }, vehicleTypes: { lastUpdate: { }, // Hier kommt das Datum zum letzten Update rein. data: { }, // Hier die Daten aus der API captionList: [], // Hier die sortierte Liste mit den Namen der Fahrzeuge settings: []// Hier die erlaubten Fahrzeuge }, allowedBuildingIds: [], // Hier können die Einstellungen für Leitstellen/Zusätzlichen Gebäuden hinzugefügt werden addBuildingIds: [] // Hier können die Einstellungen für Wachen hinzugefügt werden } }; return newObject; } // FUnktion zum öffnen des Modals (Settings) function openFrrModal() { $("#frModalBody").html( `