Created
April 3, 2024 17:31
-
-
Save AlexHuicu/c3023daec4a84fca8bdcecc6e121dc6a to your computer and use it in GitHub Desktop.
clear_barb_walls.js
This file contains hidden or 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: Clear Barbarian Walls | |
* Version: v1.5.1 | |
* Last Updated: 2023-10-25 | |
* Author: RedAlert | |
* Author URL: https://twscripts.dev/ | |
* Author Contact: redalert_tw (Discord) | |
* Approved: N/A | |
* Approved Date: 2021-04-21 | |
* Mod: JawJaw | |
*/ | |
/*-------------------------------------------------------------------------------------- | |
* This script can NOT be cloned and modified without permission from the script author. | |
--------------------------------------------------------------------------------------*/ | |
var scriptData = { | |
name: 'Clear Barbarian Walls', | |
version: 'v1.5.1', | |
author: 'RedAlert', | |
authorUrl: 'https://twscripts.dev/', | |
helpLink: | |
'https://forum.tribalwars.net/index.php?threads/clear-barbarian-walls.286971/', | |
}; | |
// User Input | |
if (typeof DEBUG !== 'boolean') DEBUG = false; // enable/disable debug mode | |
// Globals | |
var ALLOWED_GAME_SCREENS = ['map']; // list of game screens where script can be executed | |
var COORDS_REGEX = /[0-9]{1,3}\|[0-9]{1,3}/g; // regex for coordinates | |
if (typeof TWMap === 'undefined') TWMap = {}; | |
if ('TWMap' in window) mapOverlay = TWMap; | |
// Data Store Config | |
var STORAGE_KEY = 'RA_CBW_STORE'; // key for sessionStorage | |
var DEFAULT_STATE = { | |
MAX_BARBARIANS: 100, | |
MAX_FA_PAGES_TO_FETCH: 20, | |
}; | |
// Translations | |
var translations = { | |
en_DK: { | |
'Clear Barbarian Walls': 'Clear Barbarian Walls', | |
Help: 'Help', | |
'This script requires PA and FA to be active!': | |
'This script requires PA and FA to be active!', | |
'Redirecting...': 'Redirecting...', | |
'Fetching FA pages...': 'Fetching FA pages...', | |
'Finished fetching FA pages!': 'Finished fetching FA pages!', | |
Fetching: 'Fetching', | |
'No barbarian villages found fitting the criteria!': | |
'No barbarian villages found fitting the criteria!', | |
Type: 'Type', | |
Barbarian: 'Barbarian', | |
Report: 'Report', | |
Distance: 'Distance', | |
Wall: 'Wall', | |
'Last Attack Time': 'Last Attack Time', | |
Actions: 'Actions', | |
Attack: 'Attack', | |
'barbarian villages where found': 'barbarian villages where found', | |
'Showing the first': 'Showing the first', | |
'barbarian villages.': 'barbarian villages.', | |
Settings: 'Settings', | |
'Save Settings': 'Save Settings', | |
'Maximum villages to show on the table': | |
'Maximum villages to show on the table', | |
'Maximum FA Pages to fetch': 'Maximum FA Pages to fetch', | |
'Minimum Wall Level': 'Minimum Wall Level', | |
'Settings saved!': 'Settings saved!', | |
'Include reports with partial losses': | |
'Include reports with partial losses', | |
}, | |
}; | |
// Init Debug | |
initDebug(); | |
// Initialize script logic | |
async function initClearBarbarianWalls(store) { | |
const { MAX_BARBARIANS, MAX_FA_PAGES_TO_FETCH } = store; | |
const faURLs = await fetchFAPages(MAX_FA_PAGES_TO_FETCH); | |
// Show progress bar and notify user | |
startProgressBar(faURLs.length); | |
UI.SuccessMessage(tt('Fetching FA pages...')); | |
const faPages = []; | |
jQuery.fetchAll( | |
faURLs, | |
function (index, data) { | |
updateProgressBar(index, faURLs.length); | |
const { plunder_list } = data; | |
faPages.push(...plunder_list); | |
}, | |
function () { | |
const faTableRows = getFATableRows(faPages); | |
const barbarians = getFABarbarians(faTableRows); | |
const content = prepareContent(barbarians, MAX_BARBARIANS); | |
renderUI(content); | |
jQuery('#barbVillagesCount').text(barbarians.length); | |
updateMap(barbarians); | |
// event handlers | |
showSettingsPanel(store); | |
}, | |
function (error) { | |
UI.ErrorMessage('Error fetching FA pages!'); | |
console.error(`${scriptInfo()} Error:`, error); | |
} | |
); | |
} | |
// Update map to include barbarians | |
function updateMap(barbarians) { | |
const barbCoords = barbarians.map((barbarian) => barbarian.coord); | |
// Show wall level of barbarian villages on the Map | |
if (mapOverlay.mapHandler._spawnSector) { | |
//exists already, don't recreate | |
} else { | |
//doesn't exist yet | |
mapOverlay.mapHandler._spawnSector = mapOverlay.mapHandler.spawnSector; | |
} | |
TWMap.mapHandler.spawnSector = function (data, sector) { | |
// Override Map Sector Spawn | |
mapOverlay.mapHandler._spawnSector(data, sector); | |
var beginX = sector.x - data.x; | |
var endX = beginX + mapOverlay.mapSubSectorSize; | |
var beginY = sector.y - data.y; | |
var endY = beginY + mapOverlay.mapSubSectorSize; | |
for (var x in data.tiles) { | |
var x = parseInt(x, 10); | |
if (x < beginX || x >= endX) { | |
continue; | |
} | |
for (var y in data.tiles[x]) { | |
var y = parseInt(y, 10); | |
if (y < beginY || y >= endY) { | |
continue; | |
} | |
var xCoord = data.x + x; | |
var yCoord = data.y + y; | |
var v = mapOverlay.villages[xCoord * 1000 + yCoord]; | |
if (v) { | |
var vXY = '' + v.xy; | |
var vCoords = vXY.slice(0, 3) + '|' + vXY.slice(3, 6); | |
if (barbCoords.includes(vCoords)) { | |
const currentBarbarian = barbarians.find( | |
(obj) => obj.villageId == v.id | |
); | |
const eleDIV = $('<div></div>') | |
.css({ | |
border: '1px coral solid', | |
position: 'absolute', | |
backgroundColor: '#000', | |
color: '#fff', | |
width: '30px', | |
height: '15px', | |
marginTop: '20px', | |
marginLeft: '10px', | |
display: 'block', | |
zIndex: '10', | |
fontWeight: 'normal', | |
textAlign: 'center', | |
}) | |
.attr('id', 'dsm' + v.id) | |
.html(currentBarbarian.wall); | |
sector.appendElement( | |
eleDIV[0], | |
data.x + x - sector.x, | |
data.y + y - sector.y | |
); | |
} | |
} | |
} | |
} | |
}; | |
mapOverlay.reload(); | |
} | |
// Prepare content | |
function prepareContent(villages, maxBarbsToShow) { | |
if (villages.length) { | |
const barbsTable = buildBarbsTable(villages, maxBarbsToShow); | |
var content = ` | |
<div> | |
<p> | |
<b><span id="barbVillagesCount"></span> ${tt( | |
'barbarian villages where found' | |
)}</b><br> | |
<em>${tt('Showing the first')} ${maxBarbsToShow} ${tt( | |
'barbarian villages.' | |
)}</em> | |
</p> | |
</div> | |
<div class="ra-table-container"> | |
${barbsTable} | |
</div> | |
`; | |
return content; | |
} else { | |
return `<b>${tt( | |
'No barbarian villages found fitting the criteria!' | |
)}</b>`; | |
} | |
} | |
// Render UI | |
function renderUI(body) { | |
const content = ` | |
<div class="ra-clear-barbs-walls" id="raClearBarbWalls"> | |
<div class="ra-clear-barbs-walls-header"> | |
<h3>${tt(scriptData.name)}</h3> | |
<a href="javascript:void(0);" id="showSettingsPanel" class="btn-show-settings"> | |
<span class="icon header settings"></span> | |
</a> | |
</div> | |
<div class="ra-clear-barbs-walls-body"> | |
${body} | |
</div> | |
<div class="ra-clear-barbs-walls-footer"> | |
<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> | |
</div> | |
<style> | |
.ra-clear-barbs-walls { position: relative; display: block; width: 100%; height: auto; clear: both; margin: 10px 0 15px; border: 1px solid #603000; box-sizing: border-box; background: #f4e4bc; } | |
.ra-clear-barbs-walls * { box-sizing: border-box; } | |
.ra-clear-barbs-walls > div { padding: 10px; } | |
.ra-clear-barbs-walls .btn-confirm-yes { padding: 3px; } | |
.ra-clear-barbs-walls-header { display: flex; align-items: center; justify-content: space-between; background-color: #c1a264 !important; background-image: url(/graphic/screen/tableheader_bg3.png); background-repeat: repeat-x; } | |
.ra-clear-barbs-walls-header h3 { margin: 0; padding: 0; line-height: 1; } | |
.ra-clear-barbs-walls-body p { font-size: 14px; } | |
.ra-clear-barbs-walls-body label { display: block; font-weight: 600; margin-bottom: 6px; } | |
/* Table Styling */ | |
.ra-table-container { overflow-y: auto; overflow-x: hidden; height: auto; max-height: 312px;border: 1px solid #bc6e1f; } | |
.ra-table th { font-size: 14px; } | |
.ra-table th, | |
.ra-table td { padding: 3px; text-align: center; } | |
.ra-table td a { word-break: break-all; } | |
.ra-table a:focus { color: blue; } | |
.ra-table a.btn:focus { color: #fff; } | |
.ra-table tr:nth-of-type(2n) td { background-color: #f0e2be } | |
.ra-table tr:nth-of-type(2n+1) td { background-color: #fff5da; } | |
/* Popup */ | |
.ra-popup-content { width: 360px; } | |
.ra-popup-content * { box-sizing: border-box; } | |
.ra-popup-content input[type="text"] { padding: 3px; width: 100%; } | |
/* Helpers */ | |
.ra-mb15 { margin-bottom: 15px; } | |
/* Elements */ | |
.already-sent-command { opacity: 0.6; } | |
</style> | |
`; | |
if (jQuery('#raClearBarbWalls').length < 1) { | |
jQuery('#contentContainer').prepend(content); | |
} else { | |
jQuery('.ra-clear-barbs-walls-body').html(body); | |
} | |
} | |
// Action Handlers: Show Settings Panel | |
function showSettingsPanel(store) { | |
jQuery('#showSettingsPanel').on('click', function (e) { | |
e.preventDefault(); | |
const { MAX_BARBARIANS, MAX_FA_PAGES_TO_FETCH } = store; | |
const content = ` | |
<div class="ra-popup-content"> | |
<div class="ra-popup-header"> | |
<h3>${tt('Settings')}</h3> | |
</div> | |
<div class="ra-popup-body ra-mb15"> | |
<table class="ra-settings-table" width=100%"> | |
<tbody> | |
<tr> | |
<td width="80%"> | |
<label for="maxBarbVillages"> | |
${tt('Maximum villages to show on the table')} | |
</label> | |
</td> | |
<td width="30%"> | |
<input type="text" name="max_barb_villages" id="maxBarbVillages" value="${MAX_BARBARIANS}" /> | |
</td> | |
</tr> | |
<tr> | |
<td width="80%"> | |
<label for="maxFApages"> | |
${tt('Maximum FA Pages to fetch')} | |
</label> | |
</td> | |
<td width="30%"> | |
<input type="text" name="max_fa_pages" id="maxFApages" value="${MAX_FA_PAGES_TO_FETCH}" /> | |
</td> | |
</tr> | |
</tbody> | |
</table> | |
</div> | |
<div class="ra-popup-footer"> | |
<a href="javascript:void(0);" id="saveSettingsBtn" class="btn btn-confirm-yes"> | |
${tt('Save Settings')} | |
</a> | |
</div> | |
</div> | |
`; | |
Dialog.show('SettingsPanel', content); | |
saveSettings(); | |
}); | |
} | |
// Action Handlers: Save Settings | |
function saveSettings() { | |
jQuery('#saveSettingsBtn').on('click', function (e) { | |
e.preventDefault(); | |
const maxBarbVillages = jQuery('#maxBarbVillages').val(); | |
const maxFApages = jQuery('#maxFApages').val(); | |
const data = { | |
MAX_BARBARIANS: maxBarbVillages, | |
MAX_FA_PAGES_TO_FETCH: maxFApages, | |
}; | |
writeStorage(data, readStorage(DEFAULT_STATE)); | |
UI.SuccessMessage(tt('Settings saved!'), 1000); | |
// Update UI to reflect new settings | |
initClearBarbarianWalls(data); | |
}); | |
} | |
// Build barbs table | |
function buildBarbsTable(villages, maxBarbsToShow) { | |
villages = villages.slice(0, maxBarbsToShow); | |
let barbsTable = ` | |
<table class="ra-table" width="100%"> | |
<thead> | |
<tr> | |
<th> | |
# | |
</th> | |
<th> | |
${tt('Type')} | |
</th> | |
<th> | |
${tt('Barbarian')} | |
</th> | |
<th> | |
${tt('Report')} | |
</th> | |
<th> | |
${tt('Distance')} | |
</th> | |
<th> | |
${tt('Wall')} | |
</th> | |
<th> | |
${tt('Last Attack Time')} | |
</th> | |
<th> | |
${tt('Actions')} | |
</th> | |
</tr> | |
</thead> | |
<tbody> | |
`; | |
villages.forEach((village, index) => { | |
index++; // update index so it starts at 1 instead of 0 | |
const { villageId, coord, wall, reportId, reportTime, type, distance } = | |
village; | |
const unitsToSend = calculateUnitsToSend(wall); | |
const villageUrl = `${game_data.link_base_pure}info_village&id=${villageId}`; | |
const reportUrl = `${game_data.link_base_pure}report&mode=all&view=${reportId}`; | |
const commandUrl = `${game_data.link_base_pure}place&target=${villageId}${unitsToSend}&wall=${wall}`; | |
barbsTable += ` | |
<tr> | |
<td> | |
${index} | |
</td> | |
<td> | |
<img src="${type}"> | |
</td> | |
<td> | |
<a href="${villageUrl}" target="_blank" rel="noopener noreferrer"> | |
${coord} | |
</a> | |
</td> | |
<td> | |
<a href="${reportUrl}" target="_blank" rel="noopener noreferrer"> | |
<span class="icon header new_report"></span> | |
</a> | |
</td> | |
<td> | |
${distance} | |
</td> | |
<td> | |
${wall !== '?' ? wall : '<b style="color:red;">?</b>'} | |
</td> | |
<td> | |
${reportTime} | |
</td> | |
<td> | |
<a href="${commandUrl}" onClick="highlightOpenedCommands(this);" class="ra-clear-barb-wall-btn btn" target="_blank" rel="noopener noreferrer"> | |
${tt('Attack')} | |
</a> | |
</td> | |
</tr> | |
`; | |
}); | |
barbsTable += ` | |
</tbody> | |
</table> | |
`; | |
return barbsTable; | |
} | |
// Action Handler: Highlight Opened Commands | |
function highlightOpenedCommands(element) { | |
element.classList.add('btn-confirm-yes'); | |
element.classList.add('btn-already-sent'); | |
element.parentElement.parentElement.classList.add('already-sent-command'); | |
} | |
// Helper: Get FA pages URLs for AJAX | |
async function fetchFAPages(maxFAPagesToFetch) { | |
const faPageURLs = await jQuery | |
.get(game_data.link_base_pure + 'am_farm') | |
.then((response) => { | |
const htmlDoc = jQuery.parseHTML(response); | |
const plunderListNav = jQuery(htmlDoc).find( | |
'#plunder_list_nav:eq(0) a' | |
); | |
const firstFApage = | |
game_data.link_base_pure + | |
`am_farm&ajax=page_entries&Farm_page=0&class=&extended=1`; | |
// Getting amount of LA pages | |
const faPageURLs = [firstFApage]; | |
jQuery(plunderListNav).each(function (index) { | |
index++; | |
if (index <= maxFAPagesToFetch - 1) { | |
const currentPageNumber = parseInt( | |
getParameterByName( | |
'Farm_page', | |
window.location.origin + jQuery(this).attr('href') | |
) | |
); | |
faPageURLs.push( | |
game_data.link_base_pure + | |
`am_farm&ajax=page_entries&Farm_page=${currentPageNumber}&class=&extended=1&order=distance&dir=asc` | |
); | |
} | |
}); | |
return faPageURLs; | |
}) | |
.catch((error) => { | |
UI.ErrorMessage('Error fetching FA page!'); | |
console.error(`${scriptInfo()} Error:`, error); | |
}); | |
return faPageURLs; | |
} | |
// Helper: Get FA table rows for all pages | |
function getFATableRows(pages) { | |
let barbariansText = ''; | |
pages.forEach((page) => { | |
barbariansText += page; | |
}); | |
return jQuery.parseHTML(barbariansText); | |
} | |
// Helper: Get barbarian villages with wall bigger then 0 | |
function getFABarbarians(rows) { | |
let barbarians = []; | |
rows.forEach((row) => { | |
let shouldAdd = false; | |
let villageId = parseInt( | |
getParameterByName( | |
'target', | |
window.location.origin + | |
jQuery(row).find('td').last().find('a').attr('href') | |
) | |
); | |
let coord = jQuery(row) | |
.find('td:eq(3) a') | |
.text() | |
.match(COORDS_REGEX)[0]; | |
let wall = jQuery(row).find('td:eq(6)').text(); | |
let distance = jQuery(row).find('td:eq(7)').text().trim(); | |
let reportId = parseInt( | |
getParameterByName( | |
'view', | |
window.location.origin + | |
jQuery(row).find('td:eq(3) a').attr('href') | |
) | |
); | |
let reportTime = jQuery(row).find('td:eq(4)').text().trim(); | |
let type = jQuery(row).find('td:eq(1) img').attr('src'); | |
const isGreenReportWithUnknownWall = | |
wall === '?' && type.includes('green.png'); | |
if (parseInt(wall) > 0 || wall === '?') { | |
shouldAdd = true; | |
if (isGreenReportWithUnknownWall) { | |
// do not show green reports with unknown wall on the table | |
shouldAdd = false; | |
} | |
} | |
if (shouldAdd) { | |
barbarians.push({ | |
villageId: villageId, | |
coord: coord, | |
distance: distance, | |
wall: wall, | |
reportId: reportId, | |
reportTime: reportTime, | |
type: type, | |
}); | |
} | |
}); | |
return barbarians; | |
} | |
// Helper: Calculate units to send based on wall level | |
function calculateUnitsToSend(wall) { | |
let wallToUnitAmounts = { | |
1: '&axe=60&ram=4&spy=1', | |
2: '&axe=60&ram=7&spy=1', | |
3: '&axe=60&ram=10&spy=1', | |
4: '&axe=150&ram=15&spy=1', | |
5: '&axe=150&ram=20&spy=1', | |
6: '&axe=150&ram=25&spy=1', | |
7: '&axe=250&ram=30&spy=1', | |
8: '&axe=250&ram=38&spy=1', | |
9: '&axe=500&ram=46&spy=1', | |
}; | |
if (wallToUnitAmounts[wall] !== undefined) { | |
return wallToUnitAmounts[wall]; | |
} else { | |
return `&axe=500&ram=100&spy=1`; | |
} | |
} | |
// Helper: Make consecutive AJAX (GET) requests | |
$.fetchAll = function ( | |
urls, // array of URLs | |
onLoad, // called when any URL is loaded, params (index, data) | |
onDone, // called when all URLs successfully loaded, no params | |
onError // called when a URL load fails or if onLoad throws an exception, params (error) | |
) { | |
var numDone = 0; | |
var lastRequestTime = 0; | |
var minWaitTime = 250; // ms between requests | |
loadNext(); | |
function loadNext() { | |
if (numDone == urls.length) { | |
onDone(); | |
return; | |
} | |
let now = Date.now(); | |
let timeElapsed = now - lastRequestTime; | |
if (timeElapsed < minWaitTime) { | |
let timeRemaining = minWaitTime - timeElapsed; | |
setTimeout(loadNext, timeRemaining); | |
return; | |
} | |
lastRequestTime = now; | |
$.get(urls[numDone]) | |
.done((data) => { | |
try { | |
onLoad(numDone, data); | |
++numDone; | |
loadNext(); | |
} catch (e) { | |
onError(e); | |
} | |
}) | |
.fail((xhr) => { | |
onError(xhr); | |
}); | |
} | |
}; | |
// Helper: Progress bar UI | |
function startProgressBar(total) { | |
const width = jQuery('#contentContainer')[0].clientWidth; | |
const preloaderContent = ` | |
<div id="progressbar" class="progress-bar" style="margin-bottom:12px;"> | |
<span class="count label">0/${total}</span> | |
<div id="progress"> | |
<span class="count label" style="width: ${width}px;"> | |
0/${total} | |
</span> | |
</div> | |
</div> | |
`; | |
$('#contentContainer').eq(0).prepend(preloaderContent); | |
} | |
// Helper: Updates progress bar | |
function updateProgressBar(index, total) { | |
jQuery('#progress').css('width', `${((index + 1) / total) * 100}%`); | |
jQuery('.count').text(`${tt('Fetching')} ${index + 1}/${total}`); | |
if (index + 1 == total) { | |
UI.SuccessMessage(tt('Finished fetching FA pages!')); | |
jQuery('#progressbar').fadeOut(1000); | |
} | |
} | |
// Helper: Read client-side storage | |
function readStorage(defaultState) { | |
let storedState = sessionStorage.getItem(STORAGE_KEY); | |
if (!storedState) return defaultState; | |
if (typeof storedState === 'object') return defaultState; | |
storedState = JSON.parse(storedState); | |
return storedState; | |
} | |
// Helper: Write into client-side storage | |
function writeStorage(data, initialState) { | |
const dataToBeSaved = { | |
...initialState, | |
...data, | |
}; | |
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(dataToBeSaved)); | |
} | |
// Helper: Get parameter by name | |
function getParameterByName(name, url = window.location.href) { | |
return new URL(url).searchParams.get(name); | |
} | |
// 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) { | |
var gameLocale = game_data.locale; | |
if (translations[gameLocale] !== undefined) { | |
return translations[gameLocale][string]; | |
} else { | |
return translations['en_DK'][string]; | |
} | |
} | |
// Initialize Script | |
(function () { | |
if ( | |
game_data.features.FarmAssistent.active && | |
game_data.features.Premium.active | |
) { | |
const gameScreen = getParameterByName('screen'); | |
if (ALLOWED_GAME_SCREENS.includes(gameScreen)) { | |
const state = readStorage(DEFAULT_STATE); | |
initClearBarbarianWalls(state); | |
} else { | |
UI.InfoMessage(tt('Redirecting...')); | |
window.location.assign(game_data.link_base_pure + 'map'); | |
} | |
} else { | |
UI.ErrorMessage(tt('This script requires PA and FA to be active!')); | |
} | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment