您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Improve the Conflict Of Nations UI experience.
// ==UserScript== // @name CONfetti // @namespace https://www.conflictnations.com/ // @version 0.9.1 // @description Improve the Conflict Of Nations UI experience. // @author Taviandir // @match https://www.conflictnations.com/* // @icon https://www.google.com/s2/favicons?domain=tampermonkey.net // @license MIT // @grant none // ==/UserScript== (function () { 'use strict'; if (!inIframe()) return; log('INIT'); let correctUrl = document.location.href.includes('con-client'); console.log('CORRECT URL?', { correctUrl, href: document.location.href }); if (!correctUrl) return; // Determine when it's loaded by observing changes to splash screen attributes. When it is finished loading, style="display: none;" is added to this element. var splashScreen = document.getElementById('splashScreenContainer'); var observer = new MutationObserver(function (mutations) { mutations.forEach(function (mutation) { if (mutation.type === 'attributes') { if (!__loaded) { log('INIT SCRIPT'); initExtensionPlay(); __loaded = true; log('INIT SCRIPT - DONE'); } } }); }); observer.observe(splashScreen, { attributes: true, //configure it to listen to attribute changes }); })(); var __loaded = false; function initExtensionPlay() { initEventWindow(); // hideGoldMarketing(); hideTutorialAdvisor(); initExtensionMenuRow(); initDiplomacyWindow(); } function initDiplomacyWindow() { var dipBtn = document.getElementById('func_btn_diplomacy'); dipBtn.addEventListener('click', onOpenDiplomacyWindow); } function onOpenDiplomacyWindow() { log('on diplomacy open'); setTimeout(() => { // delay setting to give it time to load the UI var messageTabEl = document.getElementById('func_tab_messages'); messageTabEl.addEventListener('click', onClickDiplomacyMessagesTab); }, 2000); } var __enabledCtrlEnterSend; function onClickDiplomacyMessagesTab() { // enable CTRL-Enter to send messages if (!__enabledCtrlEnterSend) { setTimeout(() => { var textAreaEl = document.getElementById('func_create_message_body'); if (textAreaEl) { textAreaEl.addEventListener('keydown', function (e) { if (e.ctrlKey && (e.keyCode == 10 || e.keyCode == 13)) { // Ctrl-Enter pressed document.getElementById('func_send_message').click(); } }); __enabledCtrlEnterSend = true; } }, 2000); } } /**************************************** MENU ROW FEATURES ****************************************/ function initExtensionMenuRow() { var refElement = document.getElementById('menuContainer'); var menuWrapper = document.createElement('div'); menuWrapper.id = 'ExtMenu'; menuWrapper.style = 'position: absolute; bottom: -80px; left: 0; width: 315px; z-index: 10; color: white; margin-left: 13px;'; var ulEl = document.createElement('ul'); ulEl.classList.add('mainmenu'); ulEl.style = 'display: grid; grid-template-columns: repeat(5, 1fr)'; menuWrapper.appendChild(ulEl); insertAfter(menuWrapper, refElement); // UNIT B-LVL BUTTON var ubLvlButtonEl = addButtonToMenu(ulEl); ubLvlButtonEl.style.fill = 'white'; var svgWrapper = document.createElement('div'); svgWrapper.innerHTML = _buildingIconSvg; svgWrapper.style = 'width: 100%; margin: 0.25rem'; ubLvlButtonEl.appendChild(svgWrapper); ubLvlButtonEl.addEventListener('click', onClickMenuUnitBuildingLevel); // NOTES BUTTON var notesButtonEl = addButtonToMenu(ulEl); notesButtonEl.innerText = 'NOTES'; notesButtonEl.addEventListener('click', onClickMenuItemNotes); } function addButtonToMenu(menuEl) { var liEl = document.createElement('li'); liEl.classList.add('con_button'); liEl.style = 'display: inline-flex; align-items: center; justify-content: center; font-weight: bold;'; menuEl.appendChild(liEl); return liEl; } function createPopupCloseButton() { var div = document.createElement('div'); div.innerHTML = `<div class="close_button_s"><div>x</div></div>`; var child = div.firstChild; child.parent = null; div.remove(); return child; } /*** UNIT BUILDING LVL MATRIX ***/ function onClickMenuUnitBuildingLevel() { log('UNIT BUILDING-LEVEL ITEM CLICKED'); var popupEl = document.createElement('div'); popupEl.id = 'ExtNotesUbLvl'; popupEl.style = 'min-width: 500px; background: #51666d; color: white; border: 1px solid #ccc; position: absolute; left: 2%; top: 25%; display: flex; flex-direction: column; padding: 0.25rem;'; popupEl.appendChild(initUbPopupStyles()); popupEl.appendChild(initUbLvlHeader()); var tableWrapper = document.createElement('div'); popupEl.appendChild(tableWrapper); tableWrapper.innerHTML = _unitBuildlingLevelTableTemplate; // inject data into table var data = parseUnitBuildingLevelsData(); var tableEl = tableWrapper.firstChild; for (let key in data) { let obj = data[key]; for (let lvl = 1; lvl <= 5; lvl++) { let arr = obj[lvl]; if (arr.length) { let tdHtml = ubArrayToHtml(arr); var cellId = getUbTableCellId(key, lvl); var tdMatch = tableEl.querySelectorAll('#' + cellId); if (tdMatch.length) { tdMatch[0].innerHTML = tdHtml; } } } } document.getElementById('s1914').appendChild(popupEl); } function initUbPopupStyles() { var styles = document.createElement('style'); styles.innerHTML = _unitBuildlingPopupCss; return styles; } function ubArrayToHtml(arr) { let html = ''; for (let x of arr) { html += '<div>' + x + '</div>'; } return html; } function getUbTableCellId(building, lvl) { let key = ''; if (building === 'Army Base') { key = 'army'; } else if (building === 'Air Base') { key = 'air'; } else if (building === 'Naval Base') { key = 'naval'; } return key + '_' + lvl; } function initUbLvlHeader() { var headerWrapper = document.createElement('div'); headerWrapper.style = 'display: flex; align-items: space-between'; var closeButton = createPopupCloseButton(); closeButton.addEventListener('click', onClickCloseUbLvlWindow); var headerEl = document.createElement('h1'); headerEl.innerText = 'Units by Building Levels'; headerEl.style = 'margin-bottom: 0.5rem'; headerWrapper.appendChild(headerEl); headerWrapper.appendChild(closeButton); return headerWrapper; } function onClickCloseUbLvlWindow() { document.getElementById('ExtNotesUbLvl').remove(); } function parseUnitBuildingLevelsData() { var rows = _unitBuildingLevelsData.split('\n'); let dict = {}; let currentObj = null; for (let row of rows) { let cols = row.split('\t'); // check for new category if (cols[0] != '') { currentObj = {}; initLevelsInBuildingObj(currentObj); var key = cols[0] + ' Base'; dict[key] = currentObj; } // add units to its level for (let lvl = 1; lvl <= 5; lvl++) { if (!!cols[lvl]) { currentObj[lvl].push(cols[lvl]); } } } return dict; } function initLevelsInBuildingObj(obj) { for (let i = 0; i < 5; i++) { obj[i + 1] = []; } } /*** NOTES ***/ function onClickMenuItemNotes() { log('NOTES MENU ITEM CLICKED'); var popupEl = document.createElement('div'); popupEl.id = 'ExtNotesPopup'; popupEl.style = 'min-width: 500px; background: #eee; color: black; border: 1px solid #ccc; position: absolute; left: 2%; top: 25%; display: flex; flex-direction: column; padding: 0.25rem;'; var headerEl = document.createElement('h1'); headerEl.innerText = 'Notes'; headerEl.style = 'margin-bottom: 0.5rem'; popupEl.appendChild(headerEl); var gameId = getGameId(); var textEl = document.createElement('textarea'); textEl.id = 'ExtNoteInput'; textEl.value = loadGameNote(gameId); textEl.setAttribute('rows', 10); popupEl.appendChild(textEl); // buttons container var buttonDiv = document.createElement('div'); buttonDiv.style = 'display: flex; margin-top: 0.5rem; justify-content: flex-end'; popupEl.appendChild(buttonDiv); // cancel button var cancelEl = document.createElement('button'); cancelEl.id = 'ExtNoteCancel'; cancelEl.innerText = 'Cancel'; cancelEl.className = 'con_button large_button uppercase'; cancelEl.style = 'margin-right: 0.5rem;'; cancelEl.addEventListener('click', onClickCancelNote); buttonDiv.appendChild(cancelEl); // save button var saveEl = document.createElement('button'); saveEl.id = 'ExtNoteSave'; saveEl.innerText = 'Save'; saveEl.className = 'con_button large_button uppercase'; saveEl.addEventListener('click', onClickSaveNote); buttonDiv.appendChild(saveEl); document.getElementById('s1914').appendChild(popupEl); } function onClickCancelNote() { closeNoteWindow(); } function onClickSaveNote() { // log('save click'); var textValue = document.getElementById('ExtNoteInput').value; log(textValue); saveGameNote(getGameId(), textValue); // log('NOTE SAVED'); closeNoteWindow(); } function closeNoteWindow() { document.getElementById('ExtNotesPopup').remove(); } function loadGameNote(gameId) { return localStorage.getItem('ext_note_' + gameId); } function saveGameNote(gameId, text) { localStorage.setItem('ext_note_' + gameId, text); } function hideTutorialAdvisor() { var element = document.getElementById('tutorialAdviceTextContainer'); if (element) { element.remove(); } } function hideGoldMarketing() { var element = document.getElementById('marketingPopupContainer'); let m = document.querySelectorAll('#marketingPopupContainer .func_close_button'); console.log('GOLD match', m); if (m.length) { m.click(); } } var _unreadEvents = 0; function initEventWindow() { var eventButton = document.getElementById('func_btn_events'); // extract the "Unread Events" value from the red circle var unreadEventsElem = document.getElementById('func_events_unread'); if (unreadEventsElem) { log('UNREAD EVENTS: ' + unreadEventsElem.innerText); if (unreadEventsElem.innerText !== '') { var unreadValue = parseInt(unreadEventsElem.innerText); if (+unreadValue) { _unreadEvents = +unreadValue; } console.log('unread as number', unreadValue, _unreadEvents); } } eventButton.addEventListener('click', (event) => { initOptionsInEventWindow(); markUnreadEvents(); addUnitTypeToResearchEvents(); enhanceAgentEvents(); }); } function initOptionsInEventWindow() { log('init event window!'); var eventContentElem = document.querySelector('#eventsContainer .content .overview'); if (eventContentElem) { // create a parent wrapper object for all filters let wrapper = document.createElement('div'); wrapper.id = 'confetti-event-filters'; wrapper.style = 'display: flex; padding: 1rem;'; eventContentElem.prepend(wrapper); addTypeFilterSelect(wrapper); addCountryFilterSelect(wrapper); } } function markUnreadEvents() { console.log('Mark Unread Events', _unreadEvents); let childrenOfUl = document.querySelector('#eventsContainer .content .overview ul').children; //console.log("children of ul", childrenOfUl, unreadEvents); for (var i = 0; i < _unreadEvents; i++) { var liElem = childrenOfUl[i]; // console.log("SET UNREAD: li elem", liElem); liElem.style.borderLeft = '4px solid yellow'; } } function enhanceAgentEvents() { log('Enhance Agent Events'); let childrenOfUl = document.querySelector('#eventsContainer .content .overview ul').children; //console.log("children of ul", childrenOfUl, unreadEvents); // console.log("AGENT childrenoful", childrenOfUl); for (var i = 0; i < childrenOfUl.length; i++) { var evEl = childrenOfUl[i]; var desc = evEl.querySelector('.event-description'); // console.log("event innertext", evEl, desc, desc.innerText); if (desc.innerText.indexOf('Our agent') >= 0) { // console.log("ENHANCE - our agent event", desc.innerText); var headerEl = evEl.querySelector('.event-time'); if (desc.innerText.indexOf('have intercepted') >= 0) { // this is one of the "done to us" } else if (desc.innerText.indexOf('has been captured') >= 0) { // mission failed headerEl.innerText = '👎 ' + headerEl.innerText; headerEl.style = 'color: yellow;'; } else if (desc.innerText.indexOf('sabotaged buildings') >= 0 || desc.innerText.indexOf('destroyed resources') >= 0) { // mission successful headerEl.innerText = '👍 ' + headerEl.innerText; headerEl.style = 'color: #0f0;'; } // TODO : agent missions done on us } } } function addUnitTypeToResearchEvents() { setTimeout(() => { // console.log('addUnitTypeToResearchEvents()'); var eventElems = document.querySelectorAll('#eventsContainer .content .overview ul li'); // console.log('RES event elems', eventElems); for (var i = 0; i < eventElems.length; i++) { var evEl = eventElems[i]; var desc = evEl.querySelector('.event-description'); var content = ''; if (desc) { content = desc.innerText; } else { continue; } if (content.includes('Research Completed')) { // parse out research name from event text let prefix = 'Research for '; let suffix = ' has been completed'; let idxStart = content.lastIndexOf(prefix) + prefix.length; let idxEnd = content.lastIndexOf(suffix); let researchName = content.substring(idxStart, idxEnd); let researchNameWithoutParanthesis = researchName.substring(0, researchName.indexOf(' (')); // find match let unitTypeMatch = tryMatchUnitType(researchNameWithoutParanthesis); // let unitTypeMatch = tryMatchUnitType('Benjamin Franklin Class'); console.log('Unit type match?', { unitTypeMatch, researchNameWithoutParanthesis, researchName }); if (unitTypeMatch) { // set the new innerText on the content div //var newContent = content.substring(0, idxStart) + researchName + ' [' + unitTypeMatch + ']' + content.substring(idxEnd); setNewResearchContent(content, idxStart, idxEnd, researchName, unitTypeMatch, desc); } else { // perhaps a soft upgrade, try to match for it instead var softUpgrades = parseSoftUnitUpgradesData(); var lvl = extractResearchUpgradeLevel(researchName); // console.log('research softs', { researchName, researchNameWithoutParanthesis, lvl }); if (lvl > 1) { var unitTypes = tryMatchSoftUpgrade(researchNameWithoutParanthesis, lvl); // console.log("soft upgr result", { unitTypes }); // if more than 3 hits, then dont write anything (too common upgrade, e.g. "Engine Upgrade (Lvl 2)" if (unitTypes.length && unitTypes.length <= 3) { var softMatchStr = unitTypes.join(' / '); setNewResearchContent(content, idxStart, idxEnd, researchName, softMatchStr, desc); } } } } } }, 1000); } function setNewResearchContent(content, idxStart, idxEnd, researchName, matchName, el) { var style = 'text-decoration: underline'; var newContent = content.substring(0, idxStart) + researchName + ' <span style="' + style + '">[ ' + matchName + ' ]</span>' + content.substring(idxEnd); // console.log("NEW RESEARCH CONTENT", newContent); el.innerHTML = newContent; } function extractResearchUpgradeLevel(researchName) { var x = /(\d\))$/.exec(researchName)[0].replace(')', ''); if (!isNaN(x)) { return parseInt(x); } else { return null; } } function tryMatchSoftUpgrade(researchName, lvl) { let data = parseSoftUnitUpgradesData(); var idx = lvl - 1; var matches = []; for (let key in data) { if (data.hasOwnProperty(key)) { try { if (data[key][idx] === researchName) { matches.push(key); } } catch (error) {} } } return matches; } function tryMatchUnitType(researchName) { let data = parseUnitDoctrineData(); for (let key in data) { if (data.hasOwnProperty(key)) { if (data[key].some((str) => str == researchName)) { // console.log("MATCH FOUND!", { type: key, needle: researchName }); return key; } } } return null; } // format: { [key: string]: string[] } // where key = unit type, and array = unit type names in the doctrines // e.g. { 'Attack Submarine': ['Los Angeles Class', ...] var _parsedUnitDoctrineData = null; function parseUnitDoctrineData() { if (_parsedUnitDoctrineData) { return _parsedUnitDoctrineData; } let rowSplit = _unitDoctrineData.split('\n'); var result = {}; for (let i = 0; i < rowSplit.length; i++) { let rawRow = rowSplit[i]; let cols = rawRow.split('\t'); let values = cols.filter((x) => !!x && x.length); let unitType = values.shift(); result[unitType] = values; } _parsedUnitDoctrineData = result; return _parsedUnitDoctrineData; } var _parsedSoftUpgradesData = null; function parseSoftUnitUpgradesData() { if (_parsedSoftUpgradesData) { return _parsedSoftUpgradesData; } let rowSplit = _unitSoftUpgradesData.split('\n'); var result = {}; for (let i = 0; i < rowSplit.length; i++) { let rawRow = rowSplit[i]; let cols = rawRow.split('\t'); // let values = cols.filter(x => !!x && x.length); let values = cols; let unitType = values.shift(); result[unitType] = values; } _parsedSoftUpgradesData = result; return _parsedSoftUpgradesData; } /****************************** EVENT FILTERS ******************************/ function onChangeFilters() { console.log('onChangeFilters()'); var typeFilterValue = getEventFilterTypeValue(); var countryFilterValue = getEventFilterCountryValue(); // console.log("FILTER VALUES", { typeFilterValue, countryFilterValue }); var eventElems = document.querySelectorAll('#eventsContainer .content .overview ul li'); // console.log('eventElems', { eventElems }); for (var i = 0; i < eventElems.length; i++) { var evEl = eventElems[i]; var typeOk = evalFilterType(evEl, typeFilterValue); var countryOk = evalFilterCountry(evEl, countryFilterValue); // console.log('EV FILTER RESULT', { evEl, typeOk, countryOk, typeFilterValue, countryFilterValue }); var show = typeOk && countryOk; if (show) { evEl.removeAttribute('hidden'); } else { evEl.setAttribute('hidden', ''); } } } function addTypeFilterSelect(elem) { log('addTypeFilterSelect()'); let wrapper = document.createElement('div'); wrapper.id = 'confetti-event-wrapper-type'; wrapper.style.marginRight = '1rem'; // add a label for type select let filterLabel = document.createElement('span'); filterLabel.innerText = 'Type: '; wrapper.appendChild(filterLabel); // add type select w/ options let filterSelect = document.createElement('select'); filterSelect.id = _eventFilterTypeId; wrapper.appendChild(filterSelect); filterSelect.style.padding = '0.5rem'; addOptionToParent('All', 'ALL', filterSelect); addOptionToParent('Combat', 'COM', filterSelect); addOptionToParent('Territories', 'TER', filterSelect); addOptionToParent('Agents', 'AGE', filterSelect); addOptionToParent('Research', 'RES', filterSelect); addOptionToParent('City Production', 'CIT', filterSelect); addOptionToParent('Diplomacy', 'DIP', filterSelect); filterSelect.addEventListener('change', onChangeFilters); elem.append(wrapper); } var _eventFilterTypeId = 'confetti-filter-type-select'; function getEventFilterTypeValue() { return document.getElementById('' + _eventFilterTypeId).value; } var _eventFilterCountryId = 'confetti-filter-country-select'; function getEventFilterCountryValue() { return document.getElementById('' + _eventFilterCountryId).value; } function evalFilterType(evEl, filter) { if (!filter || filter == '' || filter == 'ALL') return true; var desc = evEl.querySelector('.event-description'); var content = ''; // console.log('evalFilterType', { desc, filter }); if (desc) { content = desc.innerText; } else { return true; } var show = true; var keywordsToSearchFor; if (filter === 'COM') { keywordsToSearchFor = ['Enemy Defeated', 'Fighting.', 'Friendly Unit Lost', 'Civilian Casualties']; } else if (filter === 'TER') { keywordsToSearchFor = ['Province Entered', 'Territory Lost', 'Territory Conquered']; } else if (filter === 'AGE') { keywordsToSearchFor = ['Agent']; } else if (filter === 'RES') { keywordsToSearchFor = ['Research Completed']; } else if (filter === 'CIT') { keywordsToSearchFor = ['built in', 'mobilized']; } else if (filter === 'DIP') { keywordsToSearchFor = ['New Article Published', 'Message Received', 'Diplomatic Status Changed', 'the coalition']; } if (keywordsToSearchFor && keywordsToSearchFor.length) { show = keywordsToSearchFor.map((x) => content.includes(x)).some((match) => match === true); } return show; } function evalFilterCountry(evEl, filter) { if (!filter || filter == '') return true; var attr = evEl.getAttribute('data-country'); return attr === filter; } function addCountryFilterSelect(elem) { log('addCountryFilterSelect()'); var countries = detectCountriesInEvents(); // console.log("COUNTRIES", countries); let wrapper = document.createElement('div'); wrapper.id = 'confetti-event-wrapper-country'; wrapper.style.marginRight = '1rem'; // add a label for type select let filterLabel = document.createElement('span'); filterLabel.innerText = 'Country: '; wrapper.appendChild(filterLabel); // add type select w/ options let filterSelect = document.createElement('select'); filterSelect.id = _eventFilterCountryId; wrapper.appendChild(filterSelect); filterSelect.style.padding = '0.5rem'; // add options addOptionToParent('All', '', filterSelect); for (var i = 0; i < countries.length; i++) { var c = countries[i]; addOptionToParent(c.value, c.key, filterSelect); } filterSelect.addEventListener('change', onChangeFilters); elem.append(wrapper); } function detectCountriesInEvents() { // NOTE : THIS METHOD BOTH DETECTS COUNTRIES AND SETS [data-country] ATTR ON EVENT var eventElems = document.querySelectorAll('#eventsContainer .content .overview ul li'); // var filter = event.target.value; var countriesLower = []; for (var i = 0; i < eventElems.length; i++) { var evEl = eventElems[i]; var finds = evEl.querySelectorAll('.small_flag_container img'); if (finds.length === 0) continue; var el = finds[0]; var imgSrc = el.getAttribute('src'); var countryName = imgSrc.split('small_')[1].split('.png')[0]; // set data-country attr on event elem evEl.setAttribute('data-country', countryName); // add to list of possible countries, if not there already if (countriesLower.indexOf(countryName) === -1) { countriesLower.push(countryName); } } var result = countriesLower.map((s) => { if (_flagCountryNameDict[s]) { return { key: s, value: _flagCountryNameDict[s] }; } else { return { key: s, value: toUpperCaseFirst(s) }; } }); // finally, sort console.log('>>>>>> countries result', { result }); result.sort((a, b) => (a.value < b.value ? -1 : 1)); return result; } /************************ MISC METHODS *******************************/ function getGameId() { const urlParams = new URLSearchParams(window.location.search); return urlParams.get('gameID'); } function insertAfter(newNode, referenceNode) { referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling); } function addOptionToParent(displayName, value, parentElem) { let node = document.createElement('option'); node.value = value; node.innerText = displayName; parentElem.appendChild(node); } function log(msg) { if (typeof msg === 'string') { console.log('[CONfetti] ' + msg); } else { console.log('[CONfetti] ', msg); } } function inIframe() { try { return window.self !== window.top; } catch (e) { return true; } } function toUpperCaseFirst(str) { return str.charAt(0).toUpperCase() + str.slice(1); } /************************ STATIC DATA *******************************/ const _flagCountryNameDict = { thechosen: 'The Chosen', roguestate: 'Rogue State', }; const _unitDoctrineData = `Motorized Infantry Basic Infantry Advanced Infantry Modern Infantry Mechanized Infantry Basic Mechanized Advanced Mechanized Modern Mechanized Naval Infantry Basic Marines Advanced Marines Modern Marines Airborne Infantry Basic Airborne Advanced Airborne Modern Airborne Special Forces Basic Rangers Advanced Rangers Modern Rangers Basic SAS Advanced SAS Modern SAS Basic Spetsnaz Advanced Spetsnaz Modern Spetsnaz National Guard Basic National Guard Advanced National Guard Modern National Guard Combat Recon Vehicle M113 Recon M1117 RSTA LAV-25 Fox FV721 VEC-M1 Griffon VBMR BRDM-1 BRDM-2 BRDM-3 Armored Fighting Vehicle M551 Sheridan M2 Bradley M3 Bradley Scorpion FV Warrior Puma BMP-2 BMP-3 Dragon T-15 Amphibious Combat Vehicle LVTP-7 AAVP-7A1 ACV 1.1 Fuchs Piranha VCBI II BTR-80 BTR-90 Bumerang Main Battle Tank M1A1 Abrams M1A2 Abrams M1A3 Abrams Leopard 2 Challenger 2 Leopard 2A7+ T-80 T-90 T-14 Armata Tank Destroyer M56 Scorpion M901 ITV M1134 Stryker ATGM Kanonenjagdpanzer AMX-10 RC Centauro 2S25 Sprut-SD BMPT Terminator BMPT-72 Terminator 2 Towed Artillery M198 Howitzer M119 Howitzer M777 Howitzer FH70 TRF1 155 GH 52 APU D-30 Howitzer 2A36 Giatsint-B 2A Msta-B Mobile Artillery M110 Howitzer M109 Howitzer M1203 NLOS GCT 155mm AS-90 Braveheart Panzerhaubitzer 2000 2S3 Akatsiya 2S19 Msta-S 2S35 Koalitsiya-SV Multiple Rocket Launcher M270 MLRS M270A1 MLRS M142 HIMARS Teruel M270 B1 LRSVM Morava BM-21 Grad BM-30 Smerch 9A52-4 Tornado Mobile Anti-Air Vehicle M163 VADS M247 Sergeant York LAV-AD Air Defense Gepard Otomatic Marksman AZU-57-2 ZSU-23-4 Shilka 2K22 Tunguska Mobile SAM Launcher MIM-23 Hawk MIM-72 Chaparral AN/TWQ-1 Avenger Ozelot Crotale Stormer HVM 9K35 Strela-10 BUK M1 Pantsir-S1 Theater Defense System MIM-14 Nike MIM-104 Patriot THAAD Missile Defence Bloodhound MEADS SAMP/T S-125 Neva S-300 S-400 Triumf Mobile Radar LCM RADAR ELEC EQ-36 PATRIOT AN/MPQ-53 UNIMOG SCB MARS-L Ground Master 400 1L121-E KASTA Nebo-M Helicopter Gunship Kiowa UH-1Y Venom Armed Black Hawk Gazelle Super Puma NH-90 Mi-8 TVK Mi-24 Hind Mi-35M Attack Helicopter AH-1G Cobra AH-1Z Viper AH-64D Apache Longbow A129 Mangusta AW Apache AH64D Tiger Ka-50 Black Shark Ka-52 Alligator Mi-28 Havoc ASW Helicopter SH-3 Sea King SH-2 Super Seasprite MH-60R Seahawk AB 212ASW Panther AW159 Wildcat Ka-25 Mi-14 Haze Ka-27 Helix Air Superiority Fighter F-5 Tiger F-16A Fighting Falcon F-16V Viper J 35A Draken Mirage F1 Typhoon MiG-23 Flogger MiG-29 Fulcrum MiG-35 Super Fulcrum Naval Air Superiority Fighter F-4 Phantom II F-14A Tomcat F-14D Super Tomcat Étendard IVM Jaguar M Rafale M Yak-141 Su-33 Flanker D MiG-29K Stealth Air Superiority Fighter F-22 Raptor MBB Firefly Su-47 Berkut Strike Fighter F-111 Aardvark F-15 Strike Eagle F-15 Silent Eagle Mirage Delta 2000 Tornado JAS 39 Gripen Su-24 Fencer Su-27 Flanker Su-35 Super Flanker Naval Strike Fighter A-6 Intruder A-7 Corsair II F-18 Super Hornet Harrier Super Étendard Harrier II Plus Yak-38 Su-27K Su-35K Stealth Strike Fighter F-35 Lightning II F-117 Nighthawk Su-T50 PakFa UAV MQ1-Predator RQ-9 Global Hawk X-47B Super Heron MQ9-Reaper NEUROn ZOND II United 40 B5 MIG SKAT Naval Patrol Aircraft P-3 Orion CP-140 Aurora P-8 Poseidon Nimrod CN-235 CASA C295 Persuader Tu-142 Bear Il-38 Dolphin A-40 Albatros AWACS EC-121 Warning Star E-3 Sentry E-8 Joint STARS EC-121 Warning Star E-3 Sentry E-8 Joint STARS Tu-126 A-50 Mainstay A-100 Naval AWACS E-2 Hawkeye Bombardier Globaleye Tu-126XXXXX Heavy Bomber B-47 Stratojet B-52 Stratofortress B-1 Lancer Valiant Victor Vulcan Tu-95 Bear Tu-22M Backfire Tu-160 White Swan Stealth Bomber B-2 Spirit SR71 Blackbird Tu-PakDa Corvette Hamilton Class Cyclone Class Freedom Class LCS Descubierta Class Göteborg Class Braunschweig Class Albatros Class Steregushchiy Class Gremyashchiy Class Frigate Garcia Class Knox Class Perry Class Duke Class Bremen Class Horizon Class Krivak Class Neutrashimy Class Admiral Gorshkov Class Destroyer Farragut Class Spruance Class Arleigh Burke Class Hamburg Class Gloucester Class Daring Class Kashin Class Sovremennyy Class Lider Class Cruiser California Class Virginia Class Ticonderoga Class Tiger Class Vittorio Veneto Class Absalon Class Kresta II Class Kara Class Slava Class Aircraft Carrier Kitty Hawk Class Nimitz Class Gerald R. Ford Class Giuseppe Garibaldi Class Charles de Gaulle Class Queen Elizabeth Class Kiev Class Kuznetsov Class Ulyanovsk Class Attack Submarine Los Angeles Class Seawolf Class Virginia Class Swiftsure Class Rubis Class Astute Class Viktor Class Akula Class Yasen Class Ballistic Missile Submarine Benjamin Franklin Class Ohio Class Columbia Class Resolution Class Vanguard Class Triomphant Class Delta Class Typhoon Class Borey Class ICBM Minuteman III GBSD M51.1 M51.2 RT-2PM Topol RS-26 Rubezh Ballistic Missile Pershing I Pershing II Pershing III PGM-17 Thor SSBS S3 J-600T Scud SS-20 Saber 9K720 Iskander Cruise Missile Gryphon Tomahawk LRSO RBS-15 KEPD 350 Storm Shadow P-500 Bazalt Kh-55 3M-54 Klub`; const _unitSoftUpgradesData = `Motorized Infantry Engine Upgrade I Man Portable Air Defense Engine Upgrade II Personal Armor Mechanized Infantry Engine Upgrade NBC Protection Reinforced Armor Naval Infantry Engine Upgrade Portable Air Defense NBC Protection Airborne Infantry Jungle Warfare Training Rapid Deployment Training Woodland Warfare Training Advanced Ballistic Armor Special Forces Portable Air Defense Amphibious Warfare Training National Guard Personal Armor Rapid Deployment Training I Rapid Deployment Training II Streamlined Mobilization Combat Recon Vehicle Engine Upgrade Air Assault NBC Protection Reinforced Armor Armored Fighting Vehicle Ground-to-Air Armament Upgrade Reinforced Armor NBC Protection Urban Survival Kit Amphibious Combat Vehicle Main Battle Tank Reinforced Armor Engine Upgrade NBC Protection Urban Survival Kit Tank Destroyer Anti Personnel Ammunition Engine Upgrade Air Assault Reinforced Armor Towed Artillery Rocket Assisted Projectiles Air Assault Enhanced Optical Sights Extended Barrel Upgrade Mobile Artillery Rocket Assisted Projectiles Reinforced Armor NBC Protection Multiple Rocket Launcher Improved Rocket Range Engine Upgrade Mobile Anti-Air Vehicle Reinforced Armor Engine Upgrade Air Assault Ground-to-Air Armament Upgrade Mobile SAM Launcher Improved Missile Range Engine Upgrade Air Assault Theater Defense System Improved Missile Range Survivability Kit Stealth Locating System Mobile Radar Advanced Sensors Array Engine Upgrade Stealth Locating System Helicopter Gunship Bulletproofing Engine Upgrade AT Missile Pods Fuel Optimization Measures Attack Helicopter Bulletproofing Fuel Optimization Measures Engine Upgrade Streamlined Mobilization ASW Helicopter Fuel Optimization Measures Advanced Sensors Array Anti-Surface Warfare Kit Air Superiority Fighter Reinforced Airframe Engine Replacement Fuel Optimization Measures Streamlined Mobilization Naval Air Superiority Fighter Stealth Air Superiority Fighter Strike Fighter Reinforced Airframe Air-to-Air Armament Upgrade Fuel Optimization Measures Streamlined Mobilization Naval Strike Fighter Stealth Strike Fighter UAV Fuel Optimization Measures Engine Replacement Reinforced Airframe Naval Patrol Aircraft Advanced Sensor Array Cruise Missile Hardpoints AWACS Reinforced Airframe Advanced Sensor Array Stealth Locating System Naval AWACS Heavy Bomber Reinforced Airframe Fuel Optimization Measures Increased Missile Hardpoints Bunker Busting Ordnance Stealth Bomber Corvette Survivability Refit Streamlined Mobilization Engine Overhaul Air Defense Upgrade Frigate AA Envelope Expansion Point-Defense Upgrade Engine Overhaul Stealth Locating System Destroyer Engine Overhaul Air Defense Upgrade Survivability Refit Cruiser Survivability Refit Expanded Missile Magazine Aircraft Carrier Air Defense Upgrade Point-Defense Upgrade Attack Submarine Survivability Refit Nuclear Reactor Refit Expanded Missile Magazine Ballistic Missile Submarine Nuclear Reactor Refit Cruise Missile Launch System Improved Reloading System Expanded Missile Magazine ICBM Fuel Improvement Warhead Shielding Ballistic Missile Fuel Improvement Booster Upgrade Warhead Shielding Cruise Missile Booster Upgrade Fuel Improvement Warhead Shielding `; const _unitBuildingLevelsData = `Army Motorized Infantry Mechanized Infantry Special Forces Multiple Rocket Launcher Theater Defense System National Guard Naval Infantry Mobile Artillery Combat Recon Vehicle Airmobile Infantry Mobile SAM Launcher Mobile Anti-Air Vehicle Armored Fighting Vehicle Tank Commander Infantry Officer Amphibious Combat Vehicle Airborne Officer Main Battle Tank Tank Destroyer Towed Artillery Mobile Radar Air Helicopter Gunship Attack Helicopter Naval Strike Fighter AWACS Stealth Air Superiority Fighter Air Superiority Fighter ASW Helicopter Naval Patrol Aircraft Naval AWACS Stealth Strike Fighter UAV Naval Air Superiority Fighter Heavy Bomber Stealth Bomber Rotary Wing Officer Strike Fighter Naval Corvette Destroyer Cruiser Aircraft Carrier Frigate Attack Submarine Ballistic Missile Submarine Naval Officer Submarine Commander `; const _unitBuildlingPopupCss = ` thead.ext-ub-head { border-bottom: 1px solid #fff; } td.ext-ub-cell-category { border-right: 1px solid #fff; } #ExtUbTable tbody td { padding: 1rem; vertical-align: unset; } `; const _unitBuildlingLevelTableTemplate = `<table id="ExtUbTable"> <thead class="ext-ub-head"> <tr> <th>Building</th> <th>Level 1</th> <th>Level 2</th> <th>Level 3</th> <th>Level 4</th> <th>Level 5</th> </tr> </thead> <tbody> <tr> <td class="ext-ub-cell-category">Army Base</td> <td id="army_1"></td> <td id="army_2"></td> <td id="army_3"></td> <td id="army_4"></td> <td id="army_5"></td> </tr> <tr> <td class="ext-ub-cell-category">Air Base</td> <td id="air_1"></td> <td id="air_2"></td> <td id="air_3"></td> <td id="air_4"></td> <td id="air_5"></td> </tr> <tr> <td class="ext-ub-cell-category">Naval Base</td> <td id="naval_1"></td> <td id="naval_2"></td> <td id="naval_3"></td> <td id="naval_4"></td> <td id="naval_5"></td> </tr> </tbody> </table>`; // NOTE : temp until code for 'native' icons are in place const _buildingIconSvg = `<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 442 442" style="enable-background:new 0 0 442 442;" xml:space="preserve"> <g> <path d="M382,0H60c-5.523,0-10,4.477-10,10v422c0,5.523,4.477,10,10,10h322c5.523,0,10-4.477,10-10V10C392,4.477,387.523,0,382,0z M295,422h-55V279h55V422z M372,422h-57V269c0-5.523-4.477-10-10-10h-75c-5.523,0-10,4.477-10,10v153H70V20h302V422z"/> <path d="M103,128h50c5.523,0,10-4.477,10-10V53c0-5.523-4.477-10-10-10h-50c-5.523,0-10,4.477-10,10v65 C93,123.523,97.477,128,103,128z M113,63h30v45h-30V63z"/> <path d="M196,128h50c5.523,0,10-4.477,10-10V53c0-5.523-4.477-10-10-10h-50c-5.523,0-10,4.477-10,10v65 C186,123.523,190.477,128,196,128z M206,63h30v45h-30V63z"/> <path d="M289,128h50c5.523,0,10-4.477,10-10V53c0-5.523-4.477-10-10-10h-50c-5.523,0-10,4.477-10,10v65 C279,123.523,283.477,128,289,128z M299,63h30v45h-30V63z"/> <path d="M103,236h50c5.523,0,10-4.477,10-10v-65c0-5.523-4.477-10-10-10h-50c-5.523,0-10,4.477-10,10v65 C93,231.523,97.477,236,103,236z M113,171h30v45h-30V171z"/> <path d="M196,236h50c5.523,0,10-4.477,10-10v-65c0-5.523-4.477-10-10-10h-50c-5.523,0-10,4.477-10,10v65 C186,231.523,190.477,236,196,236z M206,171h30v45h-30V171z"/> <path d="M289,236h50c5.523,0,10-4.477,10-10v-65c0-5.523-4.477-10-10-10h-50c-5.523,0-10,4.477-10,10v65 C279,231.523,283.477,236,289,236z M299,171h30v45h-30V171z"/> <path d="M103,344h50c5.523,0,10-4.477,10-10v-65c0-5.523-4.477-10-10-10h-50c-5.523,0-10,4.477-10,10v65 C93,339.523,97.477,344,103,344z M113,279h30v45h-30V279z"/> </g></svg>`;