Last active
March 5, 2023 20:49
-
-
Save alexandrespmg/39bf7eafcbaa9558aa7ed48086e8ff46 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Script Name: Single Village Snipe | |
* Version: v2.1.3 | |
* Last Updated: 2023-02-26 | |
* Author: RedAlert | |
* Author URL: https://twscripts.dev/ | |
* Author Contact: RedAlert#9859 (Discord) | |
* Approved: N/A (approved after the script approval rules change) | |
* Approved Date: 2021-02-27 | |
* Mod: JawJaw | |
*/ | |
/*-------------------------------------------------------------------------------------- | |
* This script can NOT be cloned and modified without permission from the script author. | |
--------------------------------------------------------------------------------------*/ | |
var scriptData = { | |
prefix: 'singleVillageSnipe', | |
name: 'Single Village Snipe', | |
version: 'v2.1.3', | |
author: 'RedAlert', | |
authorUrl: 'https://twscripts.dev/', | |
helpLink: | |
'https://forum.tribalwars.net/index.php?threads/single-village-snipe.286731/', | |
}; | |
// User Input | |
if (typeof DEBUG !== 'boolean') DEBUG = true; | |
// Constants | |
var LS_PREFIX = 'raSingleVillageSnipe'; | |
var TIME_INTERVAL = 60 * 60 * 1000 * 24 * 1; // fetch data every 1 day | |
var GROUP_ID = localStorage.getItem(`${LS_PREFIX}_chosen_group`) ?? 0; | |
var LAST_UPDATED_TIME = localStorage.getItem(`${LS_PREFIX}_last_updated`) ?? 0; | |
// Globals | |
var unitInfo, | |
villages = [], | |
troopCounts = []; | |
// Translations | |
var translations = { | |
en_DK: { | |
'Single Village Snipe': 'Single Village Snipe', | |
Help: 'Help', | |
'This script can only be run on a single village screen!': | |
'This script can only be run on a single village screen!', | |
'Landing Time': 'Landing Time', | |
'Calculate Launch Times': 'Calculate Launch Times', | |
'Export as BB Code': 'Export as BB Code', | |
'Landing time was updated!': 'Landing time was updated!', | |
'Plan for:': 'Plan for:', | |
'Landing Time:': 'Landing Time:', | |
Unit: 'Unit', | |
From: 'From', | |
'Launch Time': 'Launch Time', | |
Command: 'Command', | |
Status: 'Status', | |
Send: 'Send', | |
'Error fetching village groups!': 'Error fetching village groups!', | |
'Choose Units to Snipe': 'Choose Units to Snipe', | |
Group: 'Group', | |
'No possible snipe options found!': 'No possible snipe options found!', | |
Distance: 'Distance', | |
'An error occured while fetching troop counts!': | |
'An error occured while fetching troop counts!', | |
'snipe attempts found': 'snipe attempts found', | |
'Nothing to export!': 'Nothing to export!', | |
'Target:': 'Target:', | |
'Send in': 'Send in', | |
'Destination Village': 'Destination Village', | |
Sigil: 'Sigil', | |
'Min. Amount': 'Min. Amount', | |
'Export Config': 'Export Config', | |
'Import Config': 'Import Config', | |
'Configuration imported successfully!': | |
'Configuration imported successfully!', | |
'Nothing to import!': 'Nothing to import!', | |
'There was an error fetching villages by group!': | |
'There was an error fetching villages by group!', | |
'Reset Chosen Group': 'Reset Chosen Group', | |
'Chosen group was reset!': 'Chosen group was reset!', | |
'There was an error!': 'There was an error!', | |
'Configuration has been copied!': 'Configuration has been copied!', | |
'BBCode have been copied!': 'BBCode have been copied!', | |
'This script requires Premium Account!': | |
'This script requires Premium Account!', | |
'Reset Script': 'Reset Script', | |
'Script configuration has been reset!': | |
'Script configuration has been reset!', | |
'Send in:': 'Send in:', | |
WB: 'WB', | |
'Copied Command successfully': 'Copied Command successfully', | |
'today at': 'today at', | |
'tomorrow at': 'tomorrow at', | |
'on': 'on', | |
}, | |
it_IT: { | |
'Single Village Snipe': 'Snipe Singolo Villaggio', | |
Help: 'Aiuto', | |
'This script can only be run on a single village screen!': | |
'Questo script può essere lanciato solo dalla panoramica del villaggio', | |
'Landing Time': 'Tempo di arrivo', | |
'Calculate Launch Times': 'Calcola tempi di lancio', | |
'Export as BB Code': 'Esporta in BB code', | |
'Landing time was updated!': 'Il tempo di arrivo è stato aggiornato!', | |
'Plan for:': 'Plan per:', | |
'Landing Time:': 'Tempo di arrivo:', | |
Unit: 'Unità ', | |
From: 'Da', | |
'Launch Time': 'Tempo di lancio', | |
Command: 'Comando', | |
Status: 'Stato', | |
Send: 'Invia', | |
'Error fetching village groups!': 'Errore nel recupero gruppo!', | |
'Choose Units to Snipe': `Scegli l'unità con cui ninjare`, | |
Group: 'Gruppo', | |
'No possible snipe options found!': 'Nessuna combinazione disponibile!', | |
Distance: 'Distanza', | |
'An error occured while fetching troop counts!': | |
'Errore nel recupero conteggio truppe!', | |
'snipe attempts found': 'Ninjata possibile trovata', | |
'Nothing to export!': `Non c'è niente da esportare!`, | |
'Target:': 'Target:', | |
'Send in': 'Lancia tra', | |
'Destination Village': 'Villaggio di destinazione', | |
Sigil: 'Sigillo', | |
'Min. Amount': 'Qnt. Minima', | |
'Export Config': 'Esporta configurazione', | |
'Import Config': 'Importa configurazione', | |
'Configuration imported successfully!': | |
'Configurazione importat con successo!', | |
'Nothing to import!': `Non c'è nulla da importare!`, | |
'There was an error fetching villages by group!': | |
'There was an error fetching villages by group!', | |
'Reset Chosen Group': 'Reset Chosen Group', | |
'Chosen group was reset!': 'Chosen group was reset!', | |
'There was an error!': `C'era un errore!`, | |
'Configuration has been copied!': 'Configuration has been copied!', | |
'BBCode have been copied!': 'BBCode have been copied!', | |
'This script requires Premium Account!': | |
'This script requires Premium Account!', | |
'Reset Script': 'Reset Script', | |
'Script configuration has been reset!': | |
'Script configuration has been reset!', | |
'Send in:': 'Send in:', | |
WB: 'WB', | |
'Copied Command successfully': 'Copied Command successfully', | |
'today at': 'today at', | |
'tomorrow at': 'tomorrow at', | |
'on': 'on', | |
}, | |
pt_PT: { | |
'Single Village Snipe': 'Aldeia Única Snipe', | |
Help: 'Ajuda', | |
'This script can only be run on a single village screen!': | |
'Este script só pode ser executado num único ecrã da aldeia!', | |
'Landing Time': 'Hora de chegada', | |
'Calculate Launch Times': 'Calcular os tempos de lançamento', | |
'Export as BB Code': ' Exportar como Código BB ', | |
'Landing time was updated!': 'A hora de aterragem foi atualizada!', | |
'Plan for:': 'Plano para:', | |
'Landing Time:': 'hora de aterragem:', | |
Unit: 'Unidade', | |
From: 'de', | |
'Launch Time': 'Hora do lançamento', | |
Command: 'Comando', | |
Status: 'Estado', | |
Send: 'Enviar', | |
'Error fetching village groups!': | |
'Erro a carregar os grupos de aldeias!', | |
'Choose Units to Snipe': 'Escolha unidades para Snipe', | |
Group: 'Grupo', | |
'No possible snipe options found!': | |
'Não foram encontradas possÃveis opções de snipe!', | |
Distance: 'Distância', | |
'An error occured while fetching troop counts!': | |
'Ocorreu um erro ao recolher as contagens das tropas!', | |
'snipe attempts found': 'tentativas de snipe encontradas', | |
'Nothing to export!': 'Nada para exportar!', | |
'Target:': ' Alvo:', | |
'Send in': 'Enviar em', | |
'Destination Village': 'Aldeia de Destino', | |
Sigil: 'Sigil', | |
'Min. Amount': 'Min. quantidade', | |
'Export Config': 'Exportar Config', | |
'Import Config': 'Importar Config', | |
'Configuration imported successfully!': | |
'Configuração importada com sucesso!', | |
'Nothing to import!': 'Nada para importar!', | |
'There was an error fetching villages by group!': | |
'There was an error fetching villages by group!', | |
'Reset Chosen Group': 'Reset Chosen Group', | |
'Chosen group was reset!': 'Chosen group was reset!', | |
'There was an error!': 'There was an error!', | |
'Configuration has been copied!': 'Configuration has been copied!', | |
'BBCode have been copied!': 'BBCode have been copied!', | |
'This script requires Premium Account!': | |
'This script requires Premium Account!', | |
'Reset Script': 'Reset Script', | |
'Script configuration has been reset!': | |
'Script configuration has been reset!', | |
'Send in:': 'Send in:', | |
WB: 'WB', | |
'Copied Command successfully': 'Copied Command successfully', | |
'today at': 'today at', | |
'tomorrow at': 'tomorrow at', | |
'on': 'on', | |
}, | |
pt_BR: { | |
'Single Village Snipe': 'Snip de Aldeia Única', | |
Help: 'Ajuda', | |
'This script can only be run on a single village screen!': | |
'Este script só pode ser executado em uma única tela de aldeia!', | |
'Landing Time': 'Hora de chegada', | |
'Calculate Launch Times': 'Calcular horários de lançamento', | |
'Export as BB Code': ' Exportar como código BB', | |
'Landing time was updated!': 'A hora de chegada foi atualizada!', | |
'Plan for:': 'Plano para:', | |
'Landing Time:': 'Chegada:', | |
Unit: 'Unidade', | |
From: 'Origem', | |
'Launch Time': 'Hora do lançamento', | |
Command: 'Comando', | |
Status: 'Estado', | |
Send: 'Enviar', | |
'Error fetching village groups!': | |
'Erro a carregar os grupos de aldeias!', | |
'Choose Units to Snipe': 'Escolha as unidades para o Snipe', | |
Group: 'Grupo', | |
'No possible snipe options found!': | |
'Nenhuma opção possível de ataque encontrado!', | |
Distance: 'Distância', | |
'An error occured while fetching troop counts!': | |
'Ocorreu um erro ao recolher as contagens das tropas!', | |
'snipe attempts found': 'tentativas de snipe encontradas', | |
'Nothing to export!': 'Nada para exportar!', | |
'Target:': ' Alvo:', | |
'Send in': 'Enviar em', | |
'Destination Village': 'Aldeia de Destino', | |
Sigil: 'Aflição', | |
'Min. Amount': 'Min. quantidade', | |
'Export Config': 'Exportar Config', | |
'Import Config': 'Importar Config', | |
'Configuration imported successfully!': | |
'Configuração importada com sucesso!', | |
'Nothing to import!': 'Nada para importar!', | |
'There was an error fetching villages by group!': | |
'Houve um erro ao importar as vilas por grupo!', | |
'Reset Chosen Group': 'Reiniciar grupo escolhido', | |
'Chosen group was reset!': 'Grupo escolhido foi reiniciado!', | |
'There was an error!': 'Houve um erro!', | |
'Configuration has been copied!': 'Configuração foi copiada!', | |
'BBCode have been copied!': 'BBCode foi copiado!', | |
'This script requires Premium Account!': | |
'Este script requer uma conta premium!', | |
'Reset Script': 'Reiniciar Script', | |
'Script configuration has been reset!': | |
'Configuração do Script foi reiniciada!', | |
'Send in:': 'Enviar Em:', | |
WB: 'WB', | |
'Copied Command successfully': 'Comando copiado com sucesso', | |
'today at': 'hoje às', | |
'tomorrow at': 'amanhã às', | |
'on': 'em', | |
}, | |
de_DE: { | |
'Single Village Snipe': 'Single Village Snipe', | |
Help: 'Hilfe', | |
'This script can only be run on a single village screen!': | |
'Das Skript kann nur auf der Dorfübersicht ausgeführt werden!', | |
'Landing Time': 'Ankunftszeit', | |
'Calculate Launch Times': 'Abschickzeiten berechnen', | |
'Export as BB Code': 'Als BB Code exportieren', | |
'Landing time was updated!': 'Ankunftszeit wurde aktualisiert!', | |
'Plan for:': 'Plan für:', | |
'Landing Time:': 'Ankunftszeit:', | |
Unit: 'Einheit', | |
From: 'Von', | |
'Launch Time': 'Abschickzeit', | |
Command: 'Kommand', | |
Status: 'Status', | |
Send: 'Abschicken', | |
'Error fetching village groups!': 'Fehler Dörfergruppen zu laden!', | |
'Choose Units to Snipe': 'Wähle Einheiten zum berechnen', | |
Group: 'Gruppe', | |
'No possible snipe options found!': 'Keine möglichen Befehle gefunden!', | |
Distance: 'Entfernung', | |
'An error occured while fetching troop counts!': | |
'Ein Fehler ist beim laden der Truppen Informationen aufgetreten!', | |
'snipe attempts found': 'möglichen Befehle gefunden', | |
'Nothing to export!': 'Keine Daten zum exportieren gefunden!', | |
'Target:': 'Ziel:', | |
'Send in': 'Abschicken in', | |
'Destination Village': 'Ziel Dorf', | |
Sigil: 'Faktor', | |
'Min. Amount': 'Min. Menge', | |
'Export Config': 'Konfiguration exportieren', | |
'Import Config': 'Konfiguration importieren', | |
'Configuration imported successfully!': | |
'Konfiguration erfolgreich importiert!', | |
'Nothing to import!': 'Keine Daten zum importieren!', | |
'There was an error fetching villages by group!': | |
'Fehler Dörfer bei Gruppen zu laden!', | |
'Reset Chosen Group': 'Gewählte Gruppe zurücksetzen', | |
'Chosen group was reset!': 'Gewählte Gruppe wurde zurückgesetzt!', | |
'There was an error!': 'Es gab einen Fehler!', | |
'Configuration has been copied!': 'Konfiguration wurde kopiert!', | |
'BBCode have been copied!': 'BBCode wurde kopiert!', | |
'This script requires Premium Account!': | |
'Dieses Skript benötigt einen Premium Account!', | |
'Reset Script': 'Skript zurücksetzen', | |
'Script configuration has been reset!': | |
'Skript Konfiguration wurde zurückgesetzt!', | |
'Send in:': 'Abschicken in:', | |
WB: 'WB', | |
'Copied Command successfully': 'Befehl erfolgreich kopiert.', | |
'today at': 'heute um', | |
'tomorrow at': 'morgen um', | |
'on': 'am', | |
}, | |
}; | |
// Init Debug | |
initDebug(); | |
// Init Count API | |
countAPI(); | |
if (LAST_UPDATED_TIME !== null) { | |
// Fetch unit info only when needed | |
if (Date.parse(new Date()) >= LAST_UPDATED_TIME + TIME_INTERVAL) { | |
fetchUnitInfo(); | |
} else { | |
unitInfo = JSON.parse(localStorage.getItem(`${LS_PREFIX}_unit_info`)); | |
} | |
} else { | |
fetchUnitInfo(); | |
} | |
// Initialize Single Village Snipe script | |
async function initVillageSnipe(groupId) { | |
// run on script load | |
villages = await fetchAllPlayerVillagesByGroup(groupId); | |
troopCounts = await fetchTroopsForCurrentGroup(groupId); | |
const groups = await fetchVillageGroups(); | |
const unitsTable = buildUnitsChoserTable(); | |
const content = prepareContent(groups, unitsTable); | |
renderUI(content); | |
if (DEBUG) { | |
console.debug(`${scriptInfo()} groupId: `, groupId); | |
console.debug(`${scriptInfo()} villages: `, villages); | |
console.debug(`${scriptInfo()} troopCounts: `, troopCounts); | |
} | |
// after script has been loaded events | |
setTimeout(function () { | |
// set the default destination village | |
let destinationVillage; | |
if (mobiledevice) { | |
destinationVillage = jQuery('.mobileKeyValue') | |
.eq(0) | |
.find('div') | |
.eq(0) | |
.text() | |
.match(/\d+\|\d+/)[0]; | |
} else { | |
destinationVillage = jQuery( | |
'#content_value table table td:eq(2)' | |
).text(); | |
} | |
if (`${LS_PREFIX}_${destinationVillage}` in localStorage) { | |
const savedConfig = JSON.parse( | |
localStorage.getItem(`${LS_PREFIX}_${destinationVillage}`) | |
); | |
const { landingTime, minAmount, sigil } = savedConfig; | |
jQuery('#raLandingTime').val(landingTime); | |
jQuery('#raMinAmount').val(minAmount); | |
jQuery('#raSigil').val(sigil); | |
} else { | |
// set the default landing time | |
const today = new Date().toLocaleString('en-GB').replace(',', ''); | |
jQuery('#raLandingTime').val(today); | |
} | |
jQuery('#raDestinationVillage').val(destinationVillage); | |
}, 100); | |
// scroll to element to focus user's attention | |
if (!mobiledevice) { | |
jQuery('html,body').animate( | |
{ | |
scrollTop: jQuery('#raSingleVillageSnipe').offset().top - 8, | |
}, | |
'slow' | |
); | |
} | |
// action handlers | |
calculateLaunchTimes(); | |
fillLandingTimeFromCommand(); | |
filterVillagesByChosenGroup(); | |
exportBBCode(); | |
exportConfig(); | |
importConfig(); | |
resetGroup(); | |
resetScriptHandler(); | |
} | |
// Helper: Prepare UI | |
function prepareContent(groups, unitsTable) { | |
const groupsFilter = renderGroupsFilter(groups); | |
return ` | |
<div class="ra-mb15"> | |
<div class="ra-grid"> | |
<div> | |
<label for="raDestinationVillage"> | |
${tt('Destination Village')} | |
</label> | |
<input id="raDestinationVillage" type="text" value=""> | |
</div> | |
<div> | |
<label for="raLandingTime"> | |
${tt('Landing Time')} (dd/mm/yyyy HH:mm:ss) | |
</label> | |
<input id="raLandingTime" type="text" value=""> | |
</div> | |
<div> | |
<label for="raLandingTime"> | |
${tt('Sigil')} | |
</label> | |
<input id="raSigil" type="text" value="0"> | |
</div> | |
<div> | |
<label>${tt('Min. Amount')}</label> | |
<input id="raMinAmount" type="text" value="50"> | |
</div> | |
<div> | |
<label>${tt('Group')}</label> | |
${groupsFilter} | |
</div> | |
</div> | |
</div> | |
<div class="ra-mb15"> | |
<label>${tt('Choose Units to Snipe')}</label> | |
${unitsTable} | |
</div> | |
<div class="ra-mb15"> | |
<a href="javascript:void(0);" id="calculateLaunchTimes" class="btn btn-confirm-yes"> | |
${tt('Calculate Launch Times')} | |
</a> | |
<a href="javascript:void(0);" id="exportBBCodeBtn" class="btn" data-snipe=""> | |
${tt('Export as BB Code')} | |
</a> | |
<a href="javascript:void(0);" id="exportConfig" class="btn"> | |
${tt('Export Config')} | |
</a> | |
<a href="javascript:void(0);" id="importConfig" class="btn"> | |
${tt('Import Config')} | |
</a> | |
<a href="javascript:void(0);" id="resetGroupBtn" class="btn"> | |
${tt('Reset Chosen Group')} | |
</a> | |
<a href="javascript:void(0);" id="resetScriptBtn" class="btn"> | |
${tt('Reset Script')} | |
</a> | |
</div> | |
<div style="display:none;" class="ra-mb15" id="raPossibleCombinations"> | |
<label><span id="possibleCombinationsCount">0</span> ${tt( | |
'snipe attempts found' | |
)}</label> | |
<div id="possibleCombinationsTable"></div> | |
</div> | |
`; | |
} | |
// Render UI | |
function renderUI(body) { | |
const content = ` | |
<div class="ra-single-village-snipe" id="raSingleVillageSnipe"> | |
<h2>${tt(scriptData.name)}</h2> | |
<div class="ra-single-village-snipe-data"> | |
${body} | |
</div> | |
<small> | |
<strong> | |
${tt(scriptData.name)} ${scriptData.version} | |
</strong> - | |
<a href="${scriptData.authorUrl | |
}" target="_blank" rel="noreferrer noopener"> | |
${scriptData.author} | |
</a> - | |
<a href="${scriptData.helpLink | |
}" target="_blank" rel="noreferrer noopener"> | |
${tt('Help')} | |
</a> | |
</small> | |
</div> | |
<style> | |
.ra-single-village-snipe { position: relative; display: block; width: auto; height: auto; clear: both; margin: 0 auto 15px; padding: 10px; border: 1px solid #603000; box-sizing: border-box; background: #f4e4bc; } | |
.ra-single-village-snipe * { box-sizing: border-box; } | |
.ra-single-village-snipe input[type="text"] { width: 100%; padding: 5px 10px; border: 1px solid #000; font-size: 16px; line-height: 1; } | |
.ra-single-village-snipe label { font-weight: 600 !important; margin-bottom: 5px; display: block; } | |
.ra-single-village-snipe select { width: 100%; padding: 5px 10px; border: 1px solid #000; font-size: 16px; line-height: 1; } | |
.ra-single-village-snipe .btn-confirm-yes { padding: 3px; } | |
${mobiledevice | |
? '.ra-single-village-snipe { margin: 5px; border-radius: 10px; } .ra-single-village-snipe h2 { margin: 0 0 10px 0; font-size: 18px; } .ra-single-village-snipe .ra-grid { grid-template-columns: 1fr } .ra-single-village-snipe .ra-grid > div { margin-bottom: 15px; } .ra-single-village-snipe .btn { margin-bottom: 8px; margin-right: 8px; } .ra-single-village-snipe select { height: auto; } .ra-single-village-snipe input[type="text"] { height: auto; } .ra-hide-on-mobile { display: none; }' | |
: '.ra-single-village-snipe .ra-grid { display: grid; grid-template-columns: 150px 1fr 100px 150px 150px; grid-gap: 0 20px; }' | |
} | |
/* Normal Table */ | |
.ra-table { border-collapse: separate !important; border-spacing: 2px !important; } | |
.ra-table label, | |
.ra-table input { cursor: pointer; margin: 0; } | |
.ra-table th { font-size: 14px; } | |
.ra-table th, | |
.ra-table td { padding: 4px; text-align: center; } | |
.ra-table td a { word-break: break-all; } | |
.ra-table tr:nth-of-type(2n+1) td { background-color: #fff5da; } | |
.ra-table a:focus:not(a.btn) { color: blue; } | |
/* Popup Content */ | |
.ra-popup-content { position: relative; display: block; width: 360px; } | |
.ra-popup-content * { box-sizing: border-box; } | |
.ra-popup-content label { font-weight: 600 !important; margin-bottom: 5px; display: block; } | |
.ra-popup-content textarea { width: 100%; height: 100px; resize: none; } | |
/* Helpers */ | |
.ra-mb15 { margin-bottom: 15px; } | |
.ra-mb30 { margin-bottom: 30px; } | |
.ra-chosen-command td { background-color: #ffe563 !important; } | |
.ra-text-left { text-align: left !important; } | |
.ra-text-center { text-align: center !important; } | |
.ra-unit-count { display: inline-block; margin-top: 3px; vertical-align: top; } | |
</style> | |
`; | |
if (jQuery('.ra-single-village-snipe').length < 1) { | |
if (mobiledevice) { | |
jQuery('#mobileContent').prepend(content); | |
} else { | |
jQuery('#contentContainer').prepend(content); | |
} | |
} else { | |
jQuery('.ra-single-village-snipe-data').html(body); | |
} | |
} | |
// Action Handler: Export Config | |
function exportConfig() { | |
jQuery('#exportConfig').on('click', function (e) { | |
const destinationVillage = jQuery('#raDestinationVillage').val(); | |
const landingTime = jQuery('#raLandingTime').val(); | |
const sigil = jQuery('#raSigil').val(); | |
const minAmount = jQuery('#raMinAmount').val(); | |
const data = { | |
destinationVillage: destinationVillage, | |
landingTime: landingTime, | |
sigil: sigil, | |
minAmount: minAmount, | |
}; | |
const content = ` | |
<div class="ra-popup-content"> | |
<textarea readonly id="exportConfigInput">${JSON.stringify(data)}</textarea> | |
</div> | |
`; | |
Dialog.show('content', content); | |
UI.SuccessMessage(tt('Configuration has been copied!')); | |
jQuery('#exportConfigInput').select(); | |
document.execCommand('copy'); | |
}); | |
} | |
// Action Handler: Export Config | |
function importConfig() { | |
jQuery('#importConfig').on('click', function (e) { | |
const content = ` | |
<div class="ra-popup-content"> | |
<textarea id="importConfigField"></textarea> | |
<a href="javascript:void(0);" id="importConfigBtn" class="btn">${tt( | |
'Import Config' | |
)}</a> | |
</div> | |
`; | |
Dialog.show('content', content); | |
jQuery('#importConfigBtn').on('click', function (e) { | |
e.preventDefault(); | |
const config = jQuery('#importConfigField').val(); | |
if (config.length) { | |
const data = JSON.parse(config); | |
const { destinationVillage, landingTime, minAmount, sigil } = | |
data; | |
jQuery('#raDestinationVillage').val(destinationVillage); | |
jQuery('#raLandingTime').val(landingTime); | |
jQuery('#raSigil').val(sigil); | |
jQuery('#raMinAmount').val(minAmount); | |
jQuery('#calculateLaunchTimes').trigger('click'); | |
UI.SuccessMessage(tt('Configuration imported successfully!')); | |
} else { | |
UI.ErrorMessage(tt('Nothing to import!')); | |
} | |
}); | |
}); | |
} | |
// Action Handler: Reset chosen group | |
function resetGroup() { | |
jQuery('#resetGroupBtn').on('click', function (e) { | |
e.preventDefault(); | |
localStorage.removeItem(`${LS_PREFIX}_chosen_group`); | |
UI.SuccessMessage(tt('Chosen group was reset!')); | |
initVillageSnipe(0); | |
}); | |
} | |
// Action Handler: Grab the "chosen" villages and calculate their launch times based on the unit type | |
function calculateLaunchTimes() { | |
jQuery('#calculateLaunchTimes').on('click', function (e) { | |
e.preventDefault(); | |
// collect user input and destination village | |
const landingTimeString = jQuery('#raLandingTime').val().trim(); | |
const destinationVillage = jQuery('#raDestinationVillage').val().trim(); | |
const minAmount = parseInt(jQuery('#raMinAmount').val().trim()); | |
const chosenUnits = []; | |
jQuery('.ra-unit-selector').each(function () { | |
if (jQuery(this).is(':checked')) { | |
chosenUnits.push(this.value); | |
} | |
}); | |
if (chosenUnits.length) { | |
localStorage.setItem( | |
`${LS_PREFIX}_chosen_units`, | |
JSON.stringify(chosenUnits) | |
); | |
} | |
handleSaveConfig(); | |
if (DEBUG) { | |
console.debug( | |
`${scriptInfo()} landingTimeString:`, | |
landingTimeString | |
); | |
console.debug( | |
`${scriptInfo()} destinationVillage:`, | |
destinationVillage | |
); | |
console.debug(`${scriptInfo()} minAmount:`, minAmount); | |
console.debug(`${scriptInfo()} chosenUnits:`, chosenUnits); | |
} | |
// helper variables | |
const landingTime = getLandingTime(landingTimeString); | |
const serverTime = getServerTime(); | |
const possibleSnipes = []; | |
const realSnipes = []; | |
villages.forEach((village) => { | |
const { id, name, coords } = village; | |
const distance = calculateDistance(coords, destinationVillage); | |
chosenUnits.forEach((unit) => { | |
const launchTime = getLaunchTime(unit, landingTime, distance); | |
if (launchTime > serverTime.getTime()) { | |
const formattedLaunchTime = formatDateTime(launchTime); | |
if (distance > 0) { | |
possibleSnipes.push({ | |
id: id, | |
name: name, | |
unit: unit, | |
coords: coords, | |
distance: distance, | |
launchTime: launchTime, | |
formattedLaunchTime: formattedLaunchTime, | |
}); | |
} | |
} | |
}); | |
}); | |
possibleSnipes.sort((a, b) => { | |
return a.launchTime - b.launchTime; | |
}); | |
// filter possible snipes to only show villages with available units | |
possibleSnipes.forEach((snipe) => { | |
const { id, unit } = snipe; | |
troopCounts.forEach((villageTroops) => { | |
if (!chosenUnits.includes('snob')) { | |
if ( | |
villageTroops.villageId === id && | |
villageTroops[unit] >= minAmount | |
) { | |
snipe = { | |
...snipe, | |
unitAmount: villageTroops[unit], | |
}; | |
realSnipes.push(snipe); | |
} | |
} else { | |
if ( | |
villageTroops.villageId === id && | |
villageTroops[unit] >= 1 | |
) { | |
snipe = { | |
...snipe, | |
unitAmount: villageTroops[unit], | |
}; | |
realSnipes.push(snipe); | |
} | |
} | |
}); | |
}); | |
if (DEBUG) { | |
console.debug(`${scriptInfo()} troopCounts:`, troopCounts); | |
console.debug(`${scriptInfo()} possibleSnipes:`, possibleSnipes); | |
console.debug(`${scriptInfo()} realSnipes:`, realSnipes); | |
} | |
if (realSnipes.length > 0) { | |
const snipeCombinationsTable = buildCombinationsTable( | |
realSnipes, | |
destinationVillage | |
); | |
jQuery('#raPossibleCombinations').show(); | |
jQuery('#possibleCombinationsCount').text(realSnipes.length); | |
jQuery('#possibleCombinationsTable').html(snipeCombinationsTable); | |
jQuery('#exportBBCodeBtn').attr( | |
'data-snipe', | |
JSON.stringify(realSnipes) | |
); | |
jQuery(window.TribalWars) | |
.off() | |
.on('global_tick', function () { | |
const remainingTime = jQuery( | |
'#possibleCombinationsTable .ra-table tbody tr:eq(0) span[data-endtime]' | |
) | |
.text() | |
.trim(); | |
if (remainingTime === '0:00:10') { | |
TribalWars.playSound('chat'); | |
} | |
document.title = tt('Send in:') + ' ' + remainingTime; | |
}); | |
Timing.tickHandlers.timers.handleTimerEnd = function (e) { | |
jQuery(this).closest('tr').remove(); | |
}; | |
Timing.tickHandlers.timers.init(); | |
} else { | |
UI.ErrorMessage(tt('No possible snipe options found!')); | |
jQuery('#raPossibleCombinations').hide(); | |
jQuery('#possibleCombinationsCount').text(0); | |
jQuery('#possibleCombinationsTable').html(''); | |
jQuery('#exportBBCodeBtn').attr('data-snipe', ''); | |
} | |
}); | |
} | |
// Action Handler: When a command is clicked fill landing time with the landing time of the command | |
function fillLandingTimeFromCommand() { | |
// add from "/game.php?screen=info_village&id=XXXX" screen | |
jQuery( | |
'#commands_outgoings table tbody tr.command-row, #commands_incomings table tbody tr.command-row' | |
).on('click', function () { | |
try { | |
jQuery( | |
'#commands_outgoings table tbody tr.command-row' | |
).removeClass('ra-chosen-command'); | |
jQuery(this).addClass('ra-chosen-command'); | |
const commandLandingTime = jQuery(this) | |
.find('td:eq(1)') | |
.text() | |
.trim(); | |
const landingTime = | |
getLandingTimeFromString(commandLandingTime); | |
jQuery('#raLandingTime').val(landingTime); | |
UI.SuccessMessage(tt('Landing time was updated!')); | |
} catch (error) { | |
UI.ErrorMessage(twSDK.tt('There was an error!')); | |
console.error(`${scriptInfo} Error: `, error); | |
} | |
}); | |
} | |
// Action Handler: Filter villages shown by selected group | |
function filterVillagesByChosenGroup() { | |
jQuery('#raGroupsFilter').on('change', function (e) { | |
e.preventDefault(); | |
if (DEBUG) { | |
console.debug( | |
`${scriptInfo()} selected group ID: `, | |
e.target.value | |
); | |
} | |
localStorage.setItem(`${LS_PREFIX}_chosen_group`, e.target.value); | |
initVillageSnipe(e.target.value); | |
}); | |
} | |
// Helper: Copy string to clipboard | |
function copyTextToClipboard(text) { | |
const textArea = document.createElement('textarea'); | |
textArea.style.position = 'fixed'; | |
textArea.style.top = 0; | |
textArea.style.left = 0; | |
textArea.style.width = '2em'; | |
textArea.style.height = '2em'; | |
textArea.style.padding = 0; | |
textArea.style.border = 'none'; | |
textArea.style.outline = 'none'; | |
textArea.style.boxShadow = 'none'; | |
textArea.style.background = 'transparent'; | |
textArea.value = text; | |
document.body.appendChild(textArea); | |
textArea.focus(); | |
textArea.select(); | |
try { | |
document.execCommand('copy'); | |
UI.SuccessMessage(tt('Copied Command successfully')); | |
} catch (err) { } | |
document.body.removeChild(textArea); | |
} | |
// Action Handler: Export snipe attempts list as BB Code | |
function exportBBCode() { | |
jQuery('#exportBBCodeBtn').on('click', function (e) { | |
e.preventDefault(); | |
const snipeAttempts = jQuery(this).attr('data-snipe'); | |
if (snipeAttempts) { | |
jQuery(this).addClass('btn-confirm-yes'); | |
const snipeAttemptsJSON = JSON.parse(snipeAttempts); | |
const bbCodeSnipes = getBBCodeExport(snipeAttemptsJSON); | |
const content = ` | |
<div class="ra-popup-content"> | |
<label for="exportBBCodeInput">${tt('Export as BB Code')}</label> | |
<textarea readonly id="exportBBCodeInput">${bbCodeSnipes.trim()}</textarea> | |
</div> | |
`; | |
Dialog.show('content', content); | |
UI.SuccessMessage(tt('BBCode have been copied!')); | |
$('#exportBBCodeInput').select(); | |
document.execCommand('copy'); | |
} else { | |
UI.ErrorMessage(tt('Nothing to export!')); | |
} | |
}); | |
} | |
// Action Handler: Reset script configuration handler | |
function resetScriptHandler() { | |
jQuery('#resetScriptBtn').on('click', function (e) { | |
e.preventDefault(); | |
const localStorageKeys = Object.keys(localStorage); | |
localStorageKeys.forEach((key) => { | |
if (key.startsWith(`${LS_PREFIX}_`)) { | |
localStorage.removeItem(key); | |
} | |
}); | |
UI.SuccessMessage(tt('Script configuration has been reset!')); | |
setTimeout(function () { | |
window.location.reload(); | |
}, 500); | |
}); | |
} | |
// Save configuration for village | |
function handleSaveConfig() { | |
const landingTime = jQuery('#raLandingTime').val().trim(); | |
const destinationVillage = jQuery('#raDestinationVillage').val().trim(); | |
const minAmount = parseInt(jQuery('#raMinAmount').val().trim()); | |
const sigil = parseInt(jQuery('#raSigil').val().trim()); | |
const chosenUnits = []; | |
jQuery('.ra-unit-selector').each(function () { | |
if (jQuery(this).is(':checked')) { | |
chosenUnits.push(this.value); | |
} | |
}); | |
if (chosenUnits.length) { | |
localStorage.setItem( | |
`${LS_PREFIX}_chosen_units`, | |
JSON.stringify(chosenUnits) | |
); | |
} | |
const data = { | |
landingTime: landingTime, | |
destinationVillage: destinationVillage, | |
sigil: sigil, | |
minAmount: minAmount, | |
chosenUnits: chosenUnits, | |
}; | |
localStorage.setItem( | |
`${LS_PREFIX}_${destinationVillage}`, | |
JSON.stringify(data) | |
); | |
} | |
// Prepare Units Selector | |
function buildUnitsChoserTable() { | |
const storedChosenUnits = JSON.parse( | |
localStorage.getItem(`${LS_PREFIX}_chosen_units`) | |
); | |
if (DEBUG) { | |
console.debug(`${scriptInfo()} storedChosenUnits:`, storedChosenUnits); | |
} | |
let unitsTable = ``; | |
let thUnits = ``; | |
let tableRow = ``; | |
if (storedChosenUnits !== null && storedChosenUnits !== undefined) { | |
game_data.units.forEach((unit) => { | |
if (unit !== 'spy' && unit !== 'militia') { | |
// automatically check defensive units | |
let checked = ''; | |
if (storedChosenUnits.includes(unit)) { | |
checked = `checked`; | |
} | |
thUnits += ` | |
<th class="ra-text-center"> | |
<label for="unit_${unit}"> | |
<img src="/graphic/unit/unit_${unit}.png"> | |
</label> | |
</th> | |
`; | |
tableRow += ` | |
<td class="ra-text-center"> | |
<input name="ra_chosen_units" type="checkbox" ${checked} id="unit_${unit}" class="ra-unit-selector" value="${unit}" /> | |
</td> | |
`; | |
} | |
}); | |
} else { | |
game_data.units.forEach((unit) => { | |
if (unit !== 'spy' && unit !== 'militia') { | |
// automatically check defensive units | |
let checked = ''; | |
if ( | |
unit === 'spear' || | |
unit === 'sword' || | |
unit === 'archer' || | |
unit === 'heavy' || | |
unit === 'catapult' | |
) { | |
checked = `checked`; | |
} | |
thUnits += ` | |
<th class="ra-text-center"> | |
<label for="unit_${unit}"> | |
<img src="/graphic/unit/unit_${unit}.png"> | |
</label> | |
</th> | |
`; | |
tableRow += ` | |
<td class="ra-text-center"> | |
<input name="ra_chosen_units" type="checkbox" ${checked} id="unit_${unit}" class="ra-unit-selector" value="${unit}" /> | |
</td> | |
`; | |
} | |
}); | |
} | |
unitsTable = ` | |
<table class="ra-table vis" width="100%" id="raUnitSelector"> | |
<thead> | |
<tr> | |
${thUnits} | |
</tr> | |
</thead> | |
<tbody> | |
<tr> | |
${tableRow} | |
</tr> | |
</tbody> | |
</table> | |
`; | |
return unitsTable; | |
} | |
// Render Combinations Table | |
function buildCombinationsTable(snipes, destinationVillage) { | |
let combinationsTable = ` | |
<table class="ra-table vis" width="100%"> | |
<thead> | |
<tr> | |
<th> | |
# | |
</th> | |
<th class="ra-text-left"> | |
${tt('From')} | |
</th> | |
<th> | |
${tt('Unit')} | |
</th> | |
<th class="ra-hide-on-mobile"> | |
${tt('Distance')} | |
</th> | |
<th> | |
${tt('Launch Time')} | |
</th> | |
<th> | |
${tt('Send in')} | |
</th> | |
<th> | |
${tt('Send')} | |
</th> | |
<th> | |
${tt('WB')} | |
</th> | |
</tr> | |
</thead> | |
<tbody> | |
`; | |
const serverTime = getServerTime().getTime(); | |
const arrivalTime = getLandingTime( | |
jQuery('#raLandingTime').val().trim() | |
).getTime(); | |
snipes.forEach((snipe, index) => { | |
const { | |
id, | |
name, | |
coords, | |
unit, | |
distance, | |
launchTime, | |
formattedLaunchTime, | |
unitAmount, | |
} = snipe; | |
const [toX, toY] = destinationVillage.split('|'); | |
const continent = getContinentByCoord(coords); | |
const timeTillLaunch = secondsToHms((launchTime - serverTime) / 1000); | |
let commandUrl = ''; | |
if (game_data.player.sitter > 0) { | |
commandUrl = `/game.php?t=${game_data.player.id}&village=${id}&screen=place&x=${toX}&y=${toY}&${unit}=${unitAmount}`; | |
} else { | |
commandUrl = `/game.php?village=${id}&screen=place&x=${toX}&y=${toY}&y=${toY}&${unit}=${unitAmount}`; | |
} | |
let attackType = 'snob'.includes(unit) | |
? 11 | |
: 'axelightramcatapultmarcher'.includes(unit) | |
? 8 | |
: 0; | |
let wbCommand = `${id}&${VillageInfo.village_id | |
}&${unit}&${arrivalTime}&${attackType}&false&false&${unit}=${btoa( | |
unitAmount | |
)}\\n`; | |
combinationsTable += ` | |
<tr> | |
<td> | |
${index + 1} | |
</td> | |
<td class="ra-text-left"> | |
<a href="${game_data.link_base_pure | |
}info_village&id=${id}" target="_blank" rel="noopener noreferrer"> | |
${name} (${coords}) K${continent} | |
</a> | |
</td> | |
<td> | |
<img src="/graphic/unit/unit_${unit}.png" /> <span class="ra-unit-count">${formatAsNumber( | |
unitAmount | |
)}</span> | |
</td> | |
<td class="ra-hide-on-mobile"> | |
${parseFloat(distance).toFixed(2)} | |
</td> | |
<td> | |
${formattedLaunchTime} | |
</td> | |
<td> | |
<span class="timer" data-endtime>${timeTillLaunch}</span> | |
</td> | |
<td> | |
<a href="${commandUrl}" target="_blank" rel="noopener noreferrer" class="btn"> | |
${tt('Send')} | |
</a> | |
</td> | |
<td> | |
<a target="_blank" rel="noopener noreferrer" class="btn" onclick="copyTextToClipboard('${wbCommand}');"> | |
${tt('WB')} | |
</a> | |
</td> | |
</tr> | |
`; | |
}); | |
combinationsTable += ` | |
</tbody> | |
</table> | |
`; | |
return combinationsTable; | |
} | |
// Helper: Convert Seconds to Hour:Minutes:Seconds | |
function secondsToHms(timestamp) { | |
const hours = Math.floor(timestamp / 60 / 60); | |
const minutes = Math.floor(timestamp / 60) - hours * 60; | |
const seconds = timestamp % 60; | |
const formatted = | |
hours.toString().padStart(2, '0') + | |
':' + | |
minutes.toString().padStart(2, '0') + | |
':' + | |
seconds.toString().padStart(2, '0'); | |
return formatted; | |
} | |
// Helper: Get BB Code export for snipe attempts | |
function getBBCodeExport(snipes) { | |
const landingTime = jQuery('#raLandingTime').val().trim(); | |
const destinationVillage = jQuery('#raDestinationVillage').val().trim(); | |
let bbCode = `[size=12][b]${tt( | |
'Target:' | |
)}[/b] ${destinationVillage}\n[b]${tt( | |
'Landing Time:' | |
)}[/b] ${landingTime}[/size]\n\n`; | |
bbCode += `[table][**]${tt('Unit')}[||]${tt('From')}[||]${tt( | |
'Launch Time' | |
)}[||]${tt('Command')}[||]${tt('Status')}[/**]\n`; | |
snipes.forEach((plan) => { | |
const { coords, formattedLaunchTime, id, unit, unitAmount } = plan; | |
const [toX, toY] = destinationVillage.split('|'); | |
let commandUrl = ''; | |
if (game_data.player.sitter > 0) { | |
commandUrl = `/game.php?t=${game_data.player.id}&village=${id}&screen=place&x=${toX}&y=${toY}&${unit}=${unitAmount}`; | |
} else { | |
commandUrl = `/game.php?village=${id}&screen=place&x=${toX}&y=${toY}&${unit}=${unitAmount}`; | |
} | |
bbCode += `[*][unit]${unit}[/unit] ${formatAsNumber( | |
unitAmount | |
)}[|] ${coords} [|]${formattedLaunchTime}[|][url=${window.location.origin | |
}${commandUrl}]${tt('Send')}[/url][|]\n`; | |
}); | |
bbCode += `[/table]`; | |
return bbCode; | |
} | |
// Helper: Process coordinate and extract coordinate continent | |
function getContinentByCoord(coord) { | |
if (!coord) return ''; | |
const coordParts = coord.split('|'); | |
return coordParts[1].charAt(0) + coordParts[0].charAt(0); | |
} | |
// Helper: Calculate distance between 2 villages | |
function calculateDistance(from, to) { | |
const [x1, y1] = from.split('|'); | |
const [x2, y2] = to.split('|'); | |
const deltaX = Math.abs(x1 - x2); | |
const deltaY = Math.abs(y1 - y2); | |
return Math.sqrt(deltaX * deltaX + deltaY * deltaY); | |
} | |
// Helper: Get launch time of command | |
function getLaunchTime(unit, landingTime, distance) { | |
const msPerSec = 1000; | |
const secsPerMin = 60; | |
const msPerMin = msPerSec * secsPerMin; | |
const sigilPercentage = +jQuery('#raSigil').val(); | |
const sigilRatio = 1 + sigilPercentage / 100; | |
const unitSpeed = unitInfo.config[unit].speed; | |
const unitTime = (distance * unitSpeed * msPerMin) / sigilRatio; | |
const launchTime = new Date(); | |
launchTime.setTime( | |
Math.round((landingTime - unitTime) / msPerSec) * msPerSec | |
); | |
return launchTime.getTime(); | |
} | |
// Helper: Get server time | |
function getServerTime() { | |
const serverTime = jQuery('#serverTime').text(); | |
const serverDate = jQuery('#serverDate').text(); | |
const [day, month, year] = serverDate.split('/'); | |
const serverTimeFormatted = | |
year + '-' + month + '-' + day + ' ' + serverTime; | |
const serverTimeObject = new Date(serverTimeFormatted); | |
return serverTimeObject; | |
} | |
// Helper: Format date and time | |
function formatDateTime(date) { | |
let currentDateTime = new Date(date); | |
var currentYear = currentDateTime.getFullYear(); | |
var currentMonth = currentDateTime.getMonth(); | |
var currentDate = currentDateTime.getDate(); | |
var currentHours = '' + currentDateTime.getHours(); | |
var currentMinutes = '' + currentDateTime.getMinutes(); | |
var currentSeconds = '' + currentDateTime.getSeconds(); | |
currentMonth = currentMonth + 1; | |
currentMonth = '' + currentMonth; | |
currentMonth = currentMonth.padStart(2, '0'); | |
currentHours = currentHours.padStart(2, '0'); | |
currentMinutes = currentMinutes.padStart(2, '0'); | |
currentSeconds = currentSeconds.padStart(2, '0'); | |
let formatted_date = | |
currentDate + | |
'/' + | |
currentMonth + | |
'/' + | |
currentYear + | |
' ' + | |
currentHours + | |
':' + | |
currentMinutes + | |
':' + | |
currentSeconds; | |
return formatted_date; | |
} | |
// Helper: Get landing time date object | |
function getLandingTime(landingTime) { | |
const [landingDay, landingHour] = landingTime.split(' '); | |
const [day, month, year] = landingDay.split('/'); | |
const landingTimeFormatted = | |
year + '-' + month + '-' + day + ' ' + landingHour; | |
const landingTimeObject = new Date(landingTimeFormatted); | |
return landingTimeObject; | |
} | |
// Helper: Render groups filter | |
function renderGroupsFilter(groups) { | |
const groupId = localStorage.getItem(`${LS_PREFIX}_chosen_group`) ?? 0; | |
let groupsFilter = ` | |
<select name="ra_groups_filter" id="raGroupsFilter"> | |
`; | |
for (const [_, group] of Object.entries(groups.result)) { | |
const { group_id, name } = group; | |
const isSelected = | |
parseInt(group_id) === parseInt(groupId) ? 'selected' : ''; | |
if (name !== undefined) { | |
groupsFilter += ` | |
<option value="${group_id}" ${isSelected}> | |
${name} | |
</option> | |
`; | |
} | |
} | |
groupsFilter += ` | |
</select> | |
`; | |
return groupsFilter; | |
} | |
// Helper: Fetch player villages by group | |
async function fetchAllPlayerVillagesByGroup(groupId) { | |
try { | |
let fetchVillagesUrl = ''; | |
if (game_data.player.sitter > 0) { | |
fetchVillagesUrl = | |
game_data.link_base_pure + | |
`groups&ajax=load_villages_from_group&t=${game_data.player.id}`; | |
} else { | |
fetchVillagesUrl = | |
game_data.link_base_pure + | |
'groups&ajax=load_villages_from_group'; | |
} | |
const villagesByGroup = await jQuery | |
.post({ | |
url: fetchVillagesUrl, | |
data: { | |
group_id: groupId, | |
}, | |
dataType: 'json', | |
headers: { | |
'TribalWars-Ajax': 1, | |
}, | |
}) | |
.then(({ response }) => { | |
const parser = new DOMParser(); | |
const htmlDoc = parser.parseFromString( | |
response.html, | |
'text/html' | |
); | |
const tableRows = jQuery(htmlDoc) | |
.find('#group_table > tbody > tr') | |
.not(':eq(0)'); | |
if (tableRows.length) { | |
let villagesList = []; | |
tableRows.each(function () { | |
const villageId = | |
jQuery(this) | |
.find('td:eq(0) a') | |
.attr('data-village-id') ?? | |
jQuery(this) | |
.find('td:eq(0) a') | |
.attr('href') | |
.match(/\d+/)[0]; | |
const villageName = jQuery(this) | |
.find('td:eq(0)') | |
.text() | |
.trim(); | |
const villageCoords = jQuery(this) | |
.find('td:eq(1)') | |
.text() | |
.trim(); | |
villagesList.push({ | |
id: parseInt(villageId), | |
name: villageName, | |
coords: villageCoords, | |
}); | |
}); | |
return villagesList; | |
} else { | |
return []; | |
} | |
}); | |
return villagesByGroup; | |
} catch (error) { | |
UI.ErrorMessage(tt('There was an error fetching villages by group!')); | |
console.error(`${scriptInfo()} Error:`, error); | |
return []; | |
} | |
} | |
// Helper: Fetch village groups | |
async function fetchVillageGroups() { | |
let fetchGroups = ''; | |
if (game_data.player.sitter > 0) { | |
fetchGroups = | |
game_data.link_base_pure + | |
`groups&mode=overview&ajax=load_group_menu&t=${game_data.player.id}`; | |
} else { | |
fetchGroups = | |
game_data.link_base_pure + | |
'groups&mode=overview&ajax=load_group_menu'; | |
} | |
const villageGroups = await jQuery | |
.get(fetchGroups) | |
.then((response) => response) | |
.catch((error) => { | |
UI.ErrorMessage('Error fetching village groups!'); | |
console.error(`${scriptInfo()} Error:`, error); | |
}); | |
return villageGroups; | |
} | |
// Helper: Fetch World Unit Info | |
function fetchUnitInfo() { | |
jQuery | |
.ajax({ | |
url: '/interface.php?func=get_unit_info', | |
}) | |
.done(function (response) { | |
unitInfo = xml2json($(response)); | |
localStorage.setItem( | |
`${LS_PREFIX}_unit_info`, | |
JSON.stringify(unitInfo) | |
); | |
localStorage.setItem( | |
`${LS_PREFIX}_last_updated`, | |
Date.parse(new Date()) | |
); | |
}); | |
} | |
// Helper: Fetch home troop counts for current group | |
async function fetchTroopsForCurrentGroup(groupId) { | |
const troopsForGroup = await jQuery | |
.get( | |
game_data.link_base_pure + | |
`overview_villages&mode=combined&group=${groupId}&page=-1` | |
) | |
.then(async (response) => { | |
const htmlDoc = jQuery.parseHTML(response); | |
const homeTroops = []; | |
if (mobiledevice) { | |
let table = jQuery(htmlDoc).find('#combined_table tr.nowrap'); | |
for (let i = 0; i < table.length; i++) { | |
let objTroops = {}; | |
let villageId = parseInt( | |
table[i] | |
.getElementsByClassName('quickedit-vn')[0] | |
.getAttribute('data-id') | |
); | |
let listTroops = Array.from( | |
table[i].getElementsByTagName('img') | |
) | |
.filter((e) => e.src.includes('unit')) | |
.map((e) => ({ | |
name: e.src | |
.split('unit_')[1] | |
.replace('@2x.png', ''), | |
value: parseInt( | |
e.parentElement.nextElementSibling.innerText | |
), | |
})); | |
listTroops.forEach((item) => { | |
objTroops[item.name] = item.value; | |
}); | |
objTroops.villageId = villageId; | |
homeTroops.push(objTroops); | |
} | |
} else { | |
const combinedTableRows = jQuery(htmlDoc).find( | |
'#combined_table tr.nowrap' | |
); | |
const combinedTableHead = jQuery(htmlDoc).find( | |
'#combined_table tr:eq(0) th' | |
); | |
const combinedTableHeader = []; | |
// collect possible buildings and troop types | |
jQuery(combinedTableHead).each(function () { | |
const thImage = jQuery(this).find('img').attr('src'); | |
if (thImage) { | |
let thImageFilename = thImage.split('/').pop(); | |
thImageFilename = thImageFilename.replace('.png', ''); | |
combinedTableHeader.push(thImageFilename); | |
} else { | |
combinedTableHeader.push(null); | |
} | |
}); | |
// collect possible troop types | |
combinedTableRows.each(function () { | |
let rowTroops = {}; | |
combinedTableHeader.forEach((tableHeader, index) => { | |
if (tableHeader) { | |
if (tableHeader.includes('unit_')) { | |
const villageId = jQuery(this) | |
.find('td:eq(1) span.quickedit-vn') | |
.attr('data-id'); | |
const unitType = tableHeader.replace( | |
'unit_', | |
'' | |
); | |
rowTroops = { | |
...rowTroops, | |
villageId: parseInt(villageId), | |
[unitType]: parseInt( | |
jQuery(this) | |
.find(`td:eq(${index})`) | |
.text() | |
), | |
}; | |
} | |
} | |
}); | |
homeTroops.push(rowTroops); | |
}); | |
} | |
return homeTroops; | |
}) | |
.catch((error) => { | |
UI.ErrorMessage( | |
tt('An error occured while fetching troop counts!') | |
); | |
console.error(`${scriptInfo()} Error:`, error); | |
}); | |
return troopsForGroup; | |
} | |
// Helper: Get landing time from a string that contains "today at" and "tomorrow at" | |
function getLandingTimeFromString(timeLand) { | |
let dateLand = ''; | |
let serverDate = document | |
.getElementById('serverDate') | |
.innerText.split('/'); | |
if (timeLand.includes(tt('today at'))) { | |
// today | |
dateLand = | |
serverDate[0] + | |
'/' + | |
serverDate[1] + | |
'/' + | |
serverDate[2] + | |
' ' + | |
timeLand.match(/\d+:\d+:\d+/)[0]; | |
} else if (timeLand.includes(tt('tomorrow at'))) { | |
// tomorrow | |
let tomorrowDate = new Date( | |
serverDate[1] + '/' + serverDate[0] + '/' + serverDate[2] | |
); | |
tomorrowDate.setDate(tomorrowDate.getDate() + 1); | |
dateLand = | |
('0' + tomorrowDate.getDate()).slice(-2) + | |
'/' + | |
('0' + (tomorrowDate.getMonth() + 1)).slice(-2) + | |
'/' + | |
tomorrowDate.getFullYear() + | |
' ' + | |
timeLand.match(/\d+:\d+:\d+/)[0]; | |
} else if (timeLand.includes(tt('on'))) { | |
// on | |
let on = timeLand.match(/\d+.\d+/)[0].split('.'); | |
dateLand = | |
on[0] + | |
'/' + | |
on[1] + | |
'/' + | |
serverDate[2] + | |
' ' + | |
timeLand.match(/\d+:\d+:\d+/)[0]; | |
} | |
return dateLand; | |
} | |
// Helper: Format as number | |
function formatAsNumber(number) { | |
return parseInt(number).toLocaleString('de'); | |
} | |
// Helper: XML to JSON converter | |
var xml2json = function ($xml) { | |
var data = {}; | |
$.each($xml.children(), function (i) { | |
var $this = $(this); | |
if ($this.children().length > 0) { | |
data[$this.prop('tagName')] = xml2json($this); | |
} else { | |
data[$this.prop('tagName')] = $.trim($this.text()); | |
} | |
}); | |
return data; | |
}; | |
// Helper: Get parameter by name | |
function getParameterByName(name, url = window.location.href) { | |
return new URL(url).searchParams.get(name); | |
} | |
// Helper: Count API | |
function countAPI() { | |
const { author, prefix } = scriptData; | |
jQuery.getJSON( | |
`https://api.countapi.xyz/hit/${author}/${prefix}`, | |
function ({ value }) { | |
console.debug( | |
`${scriptInfo()} This script has been run ${formatAsNumber( | |
parseInt(value) | |
)} times.` | |
); | |
} | |
); | |
} | |
// Helper: Generates script info | |
function scriptInfo() { | |
return `[${scriptData.name} ${scriptData.version}]`; | |
} | |
// Helper: Prints universal debug information | |
function initDebug() { | |
console.debug(`${scriptInfo()} It works 🚀!`); | |
console.debug(`${scriptInfo()} HELP:`, scriptData.helpLink); | |
if (DEBUG) { | |
console.debug(`${scriptInfo()} Market:`, game_data.market); | |
console.debug(`${scriptInfo()} World:`, game_data.world); | |
console.debug(`${scriptInfo()} Screen:`, game_data.screen); | |
console.debug(`${scriptInfo()} Game Version:`, game_data.majorVersion); | |
console.debug(`${scriptInfo()} Game Build:`, game_data.version); | |
console.debug(`${scriptInfo()} Locale:`, game_data.locale); | |
console.debug( | |
`${scriptInfo()} Premium:`, | |
game_data.features.Premium.active | |
); | |
} | |
} | |
// Helper: Text Translator | |
function tt(string) { | |
const gameLocale = game_data.locale; | |
if (translations[gameLocale] !== undefined) { | |
return translations[gameLocale][string]; | |
} else { | |
return translations['en_DK'][string]; | |
} | |
} | |
// Helper: Check authorization | |
async function checkAuth() { | |
const { world, player, market } = game_data; | |
const resonse = await fetch( | |
`https://twscripts.dev/api/auth-check/?world=${world}&ally=${player.ally}&player=${player.name}&market=${market}`, | |
{ | |
method: 'GET', | |
redirect: 'follow', | |
} | |
); | |
const { authorized, message } = await resonse.json(); | |
return authorized; | |
} | |
// Initialize Script | |
(async function () { | |
if (!game_data.features.Premium.active) { | |
UI.ErrorMessage(tt('This script requires Premium Account!')); | |
return; | |
} | |
const gameScreen = getParameterByName('screen'); | |
if (gameScreen === 'info_village') { | |
try { | |
if (await checkAuth()) { | |
initVillageSnipe(GROUP_ID); | |
} | |
} catch (error) { | |
UI.ErrorMessage(tt('There was an error!')); | |
console.error(`${scriptInfo()} Error:`, error); | |
} | |
} else { | |
UI.InfoMessage( | |
tt('This script can only be run on a single village screen!') | |
); | |
setTimeout(function () { | |
window.location.assign( | |
game_data.link_base_pure + | |
'info_village&id=' + | |
game_data.village.id | |
); | |
}, 500); | |
} | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment