您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
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.
// ==UserScript== // @name * Personalzuweiser // @namespace bos-ernie.leitstellenspiel.de // @version 2.6.8 // @license BSD-3-Clause // @author BOS-Ernie, leeSalami // @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. // @match https://*.leitstellenspiel.de/vehicles/*/zuweisung // @match https://*.leitstellenspiel.de/buildings/* // @match https://*.meldkamerspel.com/vehicles/*/zuweisung // @match https://*.meldkamerspel.com/buildings/* // @exclude /new$/ // @exclude /personals$/ // @exclude /edit$/ // @exclude /move$/ // @require https://update.greasyfork.org/scripts/516844/API-Speicher.user.js // @icon https://www.google.com/s2/favicons?sz=64&domain=leitstellenspiel.de // @run-at document-idle // @grant unsafeWindow // ==/UserScript== (async function () { 'use strict'; // You can customize the hotkeys according to your needs. Helpful tool to find the codes: https://www.toptal.com/developers/keycode const ASSIGN_PERSONNEL_HOTKEY = 'KeyK'; const RESET_PERSONNEL_HOTKEY = 'KeyL'; const PREVIOUS_HOTKEY = 'ArrowLeft'; const NEXT_HOTKEY = 'ArrowRight'; const CUSTOM_VEHICLE_TRAINING = { 150: { 'no_training': true }, }; let running = false; if (document.getElementById('personal_table')) { document.addEventListener('keydown', (e) => { if (e.code === ASSIGN_PERSONNEL_HOTKEY) { assign(); } else if (e.code === RESET_PERSONNEL_HOTKEY) { reset(); } }); if (document.querySelector('.btn-group.pull-right:has(a[href^="/vehicles/"][href$="/zuweisung"])') !== null) { document.addEventListener('keydown', (e) => { if (e.code === PREVIOUS_HOTKEY) { const previousVehicleButton = getVehicleNavigationButton(1); if (previousVehicleButton !== null) { previousVehicleButton.click(); } } else if (e.code === NEXT_HOTKEY) { const nextVehicleButton = getVehicleNavigationButton(2); if (nextVehicleButton !== null) { nextVehicleButton.click(); } } }); } } else if (document.getElementById('building-navigation-container') !== null) { document.addEventListener('keydown', (e) => { if (e.code === PREVIOUS_HOTKEY) { const previousBuildingButton = getBuildingNavigationButton(1); if (previousBuildingButton !== null) { previousBuildingButton.click(); } } else if (e.code === NEXT_HOTKEY) { const nextBuildingButton = getBuildingNavigationButton(3); if (nextBuildingButton !== null) { nextBuildingButton.click(); } } }); return; } else { return; } updatePersonalCount(); const csrfToken = document.querySelector('meta[name=csrf-token]')?.content; if (!csrfToken) { return; } let db; const loadingText = I18n.t('common.loading'); addButtonGroup(); async function assign() { if (running) { return; } running = true; const assignedPersonsElement = getAssignedPersonsElement(); const numberOfAssignedPersonnel = parseInt(assignedPersonsElement.innerText, 10); const vehicleCapacity = parseInt(assignedPersonsElement.parentElement.firstElementChild.innerText, 10); let numberOfPersonnelToAssign = vehicleCapacity - numberOfAssignedPersonnel; const vehicleTypeId = await getVehicleTypeId(); if (numberOfPersonnelToAssign > 0 && vehicleTypeId !== null) { db = await openDb(); await updateVehicleTypes(db); const vehicleTraining = await getIdentifierByVehicleTypeId(vehicleTypeId); if (vehicleTraining === null) { await personnelAssigned(); return; } const trainingCount = Object.keys(vehicleTraining).length; if (trainingCount === 0) { await personnelAssigned(); 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) { await personnelAssigned(); return; } if ('no_training' in vehicleTraining) { if (typeof vehicleTraining['no_training'] === 'number') { numberOfPersonnelToAssign = Math.min(numberOfPersonnelToAssign, vehicleTraining['no_training']); } await assignPersonsWithoutTraining(numberOfPersonnelToAssign); } } await personnelAssigned(); } async function personnelAssigned() { const counterElement = document.getElementById('count_personal'); const vehicleCapacity = parseInt(counterElement.parentElement.firstElementChild.innerText, 10); if (parseInt(counterElement.innerText, 10) !== vehicleCapacity) { if (unsafeWindow.resetIncompleteVehicles) { await reset(); } document.dispatchEvent(new CustomEvent('personnel-assignment-incomplete')); } else { document.dispatchEvent(new CustomEvent('personnel-assignment-complete')); } document.dispatchEvent(new CustomEvent('personnel-assigned')); running = false; } function personnelReset() { document.dispatchEvent(new CustomEvent('personnel-reset')); running = false; } 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, 10); 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 aEducationLength = a.dataset.filterableBy.split(',').length - 1; const bEducationLength = b.dataset.filterableBy.split(',').length - 1; 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 (aEducationLength < bEducationLength) { return -1; } else if (aEducationLength > bEducationLength) { return 1; } else if ((aInVehicle === true && !bInVehicle) || (aInVehicle === undefined && bInVehicle === false)) { return -1; } else if (aInVehicle === bInVehicle) { return 0; } else { return 1; } }); } async function reset() { if (running) { return; } running = true; 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)); } personnelReset(); } 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.id = "assign_personnel"; 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.id = "reset_assigned_personnel"; 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} */ async function getVehicleTypeId() { const vehicleTypeId = document.getElementById('back_to_vehicle')?.getAttribute('vehicle_type_id'); if (vehicleTypeId) { return parseInt(vehicleTypeId, 10); } const vehicleId = getVehicleId(); try { const response = await fetch(`/api/v2/vehicles/${vehicleId}`); if (response.ok) { return (await response.json()).result.vehicle_type; } } catch (e) { return null; } return null; } function getVehicleNavigationButton(number) { return document.querySelector('.btn-group.pull-right:has(a[href^="/vehicles/"][href$="/zuweisung"]) > a[href^="/vehicles/"][href$="/zuweisung"]:nth-child(' + number + ')') } function getBuildingNavigationButton(number) { return document.querySelector('#building-navigation-container:has(a[href^="/buildings/"]) > a[href^="/buildings/"]:nth-child(' + number + ')') } /** * @return {{}|null} */ async function getIdentifierByVehicleTypeId(vehicleTypeId, allowTrailer = false, maxPersonnel = null) { if (vehicleTypeId in CUSTOM_VEHICLE_TRAINING) { return CUSTOM_VEHICLE_TRAINING[vehicleTypeId]; } const vehicle = await getData(db, 'vehicleTypes', vehicleTypeId); if (!vehicle || (allowTrailer === false && vehicle['isTrailer'] === true)) { return null; } if (maxPersonnel === null) { maxPersonnel = vehicle['maxPersonnel']; } if (!('training' in vehicle['staff'])) { const vehicles = await getAllData(db, 'vehicleTypes'); for (const vehicleId in vehicles) { if ('tractiveVehicles' in vehicles[vehicleId] && vehicles[vehicleId]['tractiveVehicles'].includes(vehicleTypeId) && 'training' in vehicles[vehicleId]['staff'] && vehicles[vehicleId]['tractiveVehicles'].length <= 5) { return getIdentifierByVehicleTypeId(parseInt(vehicleId, 10), true, maxPersonnel); } } return { 'no_training': true }; } const trainings = vehicle['staff']['training'][Object.keys(vehicle['staff']['training'])[0]]; const trainingKeys = Object.keys(trainings); const trainingsCount = trainingKeys.length; let training = {}; let personnelToTrain = 0; let trainingAtScene = 0; let isHelicopter = trainingKeys.some(key => key.includes('helicopter')); if ('trainingAtScene' in vehicle['staff']) { trainingAtScene = vehicle['staff']['trainingAtScene']; } for (let i = 0; i < trainingsCount; i++) { let trainingCount = trainingAtScene; if (trainingCount >= maxPersonnel || isHelicopter) { trainingCount = true; } else if (trainingCount === 0) { trainingCount = trainings[trainingKeys[i]][Object.keys(trainings[trainingKeys[i]])[0]]; } if (trainingCount !== true && trainingsCount === 1 && trainingCount < maxPersonnel && trainingKeys[i] !== 'notarzt') { trainingCount = true; } if (trainingCount === true) { personnelToTrain = maxPersonnel; } else { personnelToTrain += trainingCount; } training[trainingKeys[i]] = trainingCount; } if (personnelToTrain !== maxPersonnel) { training['no_training'] = maxPersonnel - personnelToTrain; } return training; } document.dispatchEvent(new CustomEvent('personnel-init')); unsafeWindow.personnelInit = true; })();