fusioneer / ConsulWar squad manager

// ==UserScript==
// @namespace     https://openuserjs.org/users/fusioneer
// @name          ConsulWar squad manager
// @description   Fleet squads management in relation to enemy fleet type
// @copyright     2017, fusioneer (https://openuserjs.org/users/fusioneer)
// @license       MIT
// @version       1.0.6
// @match         https://consulwar.ru/game/*
// @grant         none
// ==/UserScript==

// ==OpenUserJS==
// @author fusioneer
// ==/OpenUserJS==

/* RELEASE NOTES
  1.0.6
	+ planets list handling updated
  1.0.5
	+ game refactoring update
  1.0.4
	+ game refactoring update
  1.0.3
	+ game refactoring update
  1.0.2
	+ autoloading option
  1.0.1
	+ available presets highlighted in dropdown menu
	+ automatic switching to available preset when loaded composition isn't complete
	+ preset save confirmation
	* creation of named preset without saved default preset fixed
	* typo fix
*/

(function() {
	'use strict';
	function nullOrUndef(value)
	{
		return value === undefined || value === null;
	}

	const CONFIG_PRESETS = "cwfm.presets";
	const CONFIG_AUTOLOAD = "cwfm.autoload";

	const UNIT_TYPES = [
		"gammadrone",
		"wasp",
		"mirage",
		"frigate",
		"truckc",
		"cruiser",
		"battleship",
		"carrier",
		"dreadnought",
		"railgun",
		"reaper",
		"flagship",
	];
	Object.freeze(UNIT_TYPES);

	const UNIT_TYPE_IDS = {
		gammadrone: "Unit/Human/Space/Gammadrone",
		wasp: "Unit/Human/Space/Wasp",
		mirage: "Unit/Human/Space/Mirage",
		frigate: "Unit/Human/Space/Frigate",
		truckc: "Unit/Human/Space/TruckC",
		cruiser: "Unit/Human/Space/Cruiser",
		battleship: "Unit/Human/Space/Battleship",
		carrier: "Unit/Human/Space/Carrier",
		dreadnought: "Unit/Human/Space/Dreadnought",
		railgun: "Unit/Human/Space/Railgun",
		reaper: "Unit/Human/Space/Reaper",
		flagship: "Unit/Human/Space/Flagship",
	};
	Object.freeze(UNIT_TYPE_IDS);

	const UNIT_TYPE_NAMES = {
		gammadrone: "гаммадронов",
		wasp: "ос",
		mirage: "миражей",
		frigate: "фрегатов",
		truckc: "траков",
		cruiser: "крейсеров",
		battleship: "линкоров",
		carrier: "авианосцев",
		dreadnought: "дредноутов",
		railgun: "рейлганов",
		reaper: "пожинателей",
		flagship: "флагманов",
	};
	Object.freeze(UNIT_TYPE_NAMES);

	document.querySelector("head").insertAdjacentHTML('beforeend', '<link href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/vader/jquery-ui.min.css" rel="stylesheet" type="text/css">');

	Template.cosmosAttackMenu.onRendered(function() {
		var enemyTitle = document.querySelector(".attack-menu > .target .cw--SpacePlanetPopup__mission, .attack-menu > .target .cw--SpaceFleetPopup__mission");
		if (enemyTitle)
		{
			var squadsElement = document.querySelector(".attack-menu .squads");
			var code = `<div id="cwfm">
<div>
<select id="cwfm-presets" class="ui-corner-all" style="width: 150px;">
<option value="0">по умолчанию</option>
</select>
<div id="cwfm-load" class="ui-button ui-corner-all" style="width: 81px">Загрузить</div>
<label><input type="checkbox" id="cwfm-autoload" style="display: inline-block">Автозагрузка</label>
</div>
<div style="padding-top: 5px;">
<div id="cwfm-create" class="ui-button ui-corner-all">Создать новый</div>
<div id="cwfm-save" class="ui-button ui-corner-all">Сохранить</div>
<div id="cwfm-delete" class="ui-button ui-corner-all" style="color: #800;">Удалить</div>
</div>
</div>`;
			squadsElement.innerHTML = code;
			var selectElement = document.querySelector("#cwfm-presets");
			var createBtnElement = document.querySelector("#cwfm-create");
			var loadBtnElement = document.querySelector("#cwfm-load");
			var saveBtnElement = document.querySelector("#cwfm-save");
			var deleteBtnElement = document.querySelector("#cwfm-delete");
			var autoloadCheckElement = document.querySelector("#cwfm-autoload");
			autoloadCheckElement.checked = localStorage[CONFIG_AUTOLOAD] == "true";
			var matchResult = enemyTitle.innerText.match(/^(\D*) (\d+)/);
			var enemyType = matchResult[1];
			var enemyLevel = matchResult[2];
			var presets = nullOrUndef(localStorage[CONFIG_PRESETS]) || localStorage[CONFIG_PRESETS] === "" ? {} : JSON.parse(localStorage[CONFIG_PRESETS]);
			var currentPresetType = presets[enemyType];
			var currentPresets = currentPresetType === undefined ? [] : currentPresetType[enemyLevel];
			if (currentPresets === undefined)
				currentPresets = [];
			var planetElements = document.querySelectorAll(".attack-menu > .departure > .planets > a.cw--CosmosAttackMenu__planet");
			if (planetElements.length < 1) {
				console.error('Планеты не найдены');
			}

			const ensurePresetBranchExists = function()
			{
				if (currentPresetType === undefined)
					currentPresetType = presets[enemyType] = {};
				if (currentPresetType[enemyLevel] === undefined)
					currentPresets = currentPresetType[enemyLevel] = [];
			};

			const addPresetOption = function(preset)
			{
				var option = document.createElement('option');
				option.text = preset.name;
				selectElement.appendChild(option);
			};

			const readUnits = function()
			{
				var inputs = {}, maxima = {}, values = {};
				for (var i = 0; i < UNIT_TYPES.length; ++i)
				{
					var type = UNIT_TYPES[i];
					inputs[type] = document.querySelector(`.attack-menu > .departure .cw--UnitsReinforcement__unit:nth-child(${i + 1}) .cw--UnitsReinforcement__unitCount`);
					maxima[type] = parseInt(document.querySelector(`.attack-menu > .departure .cw--UnitsReinforcement__unit:nth-child(${i + 1}) .cw--UnitsReinforcement__unitReserve`).innerText);
					if (inputs[type].value > 0)
						values[type] = parseInt(inputs[type].value);
				}
				return {inputs: inputs, maxima: maxima, values: values};
			};

			const loadPreset = function(preset)
			{
				if (preset)
				{
					var neededUnits = preset.units;
					var formData = readUnits();
					var notEnough = false;
					for (var type in formData.inputs)
					{
						if (formData.maxima[type] < neededUnits[type])
						{
							Notifications.error("Недостаточно " + UNIT_TYPE_NAMES[type]);
							notEnough = true;
						}
						formData.inputs[type].value = neededUnits[type] ? neededUnits[type] : '';
						formData.inputs[type].dispatchEvent(new Event("input", { bubbles: true }));
					}
					if (notEnough)
						alert("Недостаточно войск");
				}
				else
					Notifications.error("Такой флот ещё не задан");
			};

			const presetAvailable = function(preset, formData)
			{
				if (!preset)
					return false;
				var neededUnits = preset.units;
				var notEnough = false;
				for (var type in formData.inputs)
				{
					if (formData.maxima[type] < neededUnits[type])
						return false;
				}
				return true;
			};

			const loadPresetWithFallback = function(preset)
			{
				var formData = readUnits();
				if (preset && !presetAvailable(preset, formData))
				{
					for (var i = 0; i < currentPresets.length; ++i)
					{
						if (presetAvailable(currentPresets[i], formData))
						{
							preset = currentPresets[i];
							selectElement.selectedIndex = i;
							Notifications.warn('Подобран состав от "' + preset.name + '"');
							break;
						}
					}
				}
				loadPreset(preset);
			};

			const recolorPresetOptions = function ()
			{
				var formData = readUnits();
				for (var i = 0; i < currentPresets.length; ++i)
					selectElement.options[i].style.backgroundColor = presetAvailable(currentPresets[i], formData) ? "" : "#ccc";
			};

			var i;
			for (i = 1; i < currentPresets.length; ++i)
				addPresetOption(currentPresets[i]);
			recolorPresetOptions();

			if (autoloadCheckElement.checked)
				loadPresetWithFallback(currentPresets[0]);

			//Control handlers
			for (i = 0; i < planetElements.length; ++i)
			{
				planetElements[i].addEventListener("click", function() {
					setTimeout(function () {
						recolorPresetOptions();
					}, 0);
				});
			}

			autoloadCheckElement.addEventListener("click", function() {
				localStorage[CONFIG_AUTOLOAD] = autoloadCheckElement.checked;
			});

			selectElement.addEventListener("change", function() {
				loadPreset(currentPresets[selectElement.selectedIndex]);
			});

			loadBtnElement.addEventListener("click", function() {
				loadPresetWithFallback(currentPresets[selectElement.selectedIndex]);
			});

			saveBtnElement.addEventListener("click", function() {
				if (nullOrUndef(currentPresets[selectElement.selectedIndex]) || confirm('Перезаписать флот "' + selectElement.options[selectElement.selectedIndex].innerText + '"?'))
				{
					ensurePresetBranchExists();
					if (nullOrUndef(currentPresets[selectElement.selectedIndex]))
						currentPresets[selectElement.selectedIndex] = {};
					currentPresets[selectElement.selectedIndex].units = readUnits().values;
					localStorage[CONFIG_PRESETS] = JSON.stringify(presets);
					recolorPresetOptions();
				}
			});

			createBtnElement.addEventListener("click", function() {
				var newName = prompt("Название флота", "Флот " + selectElement.options.length);
				if (newName !== null)
				{
					var newPreset = {name: newName, units: readUnits().values};
					addPresetOption(newPreset);
					ensurePresetBranchExists();
					if (currentPresets.length === 0)
						currentPresets.push(null);
					currentPresets.push(newPreset);
					selectElement.selectedIndex = selectElement.options.length - 1;
					localStorage[CONFIG_PRESETS] = JSON.stringify(presets);
					recolorPresetOptions();
				}
			});

			deleteBtnElement.addEventListener("click", function() {
				if (selectElement.selectedIndex > 0)
				{
					if (confirm('Удалить флот "' + selectElement.options[selectElement.selectedIndex].innerText + '"?'))
					{
						currentPresets.splice(selectElement.selectedIndex, 1);
						selectElement.removeChild(selectElement.options[selectElement.selectedIndex]);
						loadPreset(currentPresets[selectElement.selectedIndex]);
						localStorage[CONFIG_PRESETS] = JSON.stringify(presets);
					}
				}
			});
		}
	});
})();