(function() {
/**
* Helper for distributing deff troops
* Created by: Hermitowski
* Modified on: 30/03/2018 - version 2.0 - initial release
* Modified on: 25/10/2018 - version 2.1 - added strategy for selecting villages
* Modified on: 27/10/2018 - version 2.2 - added time range filter
* Modified on: 12/08/2019 - version 2.2 - refactor + integration with new map files api
* Modified on: 18/08/2019 - version 2.3 - added open commands in new tabs
* Modified on: 19/08/2019 - version 2.4 - split units option
* Modified on: 26/08/2019 - version 2.5 - more date formats, ratio of value 0 handling
* Modified on: 28/11/2019 - version 2.6 - notification of violation no_other_support constraint
* Modified on: 27/11/2020 - version 2.7 - mass support sending
*/
(async function (TribalWars) {
const start = Date.now();
const namespace = 'Hermitowski.Guard';
const i18n = {
SETTINGS_SAVED: 'Zapisano pomy\u{15B}lnie',
SETTINGS_RESETED: 'Przywr\u{F3}cono domy\u{15B}lne ustawienia',
CURRENTLY_SELECTED_GROUP: 'Obecnie wybrana grupa',
ERROR_MESSAGE: 'Komunikat o b\u{142}\u{119}dzie: ',
FORUM_THREAD: 'Link do w\u{105}tku na forum',
FORUM_THREAD_HREF: 'https://forum.plemiona.pl/index.php?threads/hermitowska-obstawa.124587/',
STRATEGY: {
TROOP_ASC: 'Ilo\u{15B}\u{107} wojsk rosn\u{105}co',
TROOP_DESC: 'Ilo\u{15B}\u{107} wojsk malej\u{105}co',
DIST_ASC: 'Odleg\u{142}o\u{15B}\u{107} rosn\u{105}co',
DIST_DESC: 'Odleg\u{142}o\u{15B}\u{107} malej\u{105}co',
RANDOM: 'Losowo'
},
ERROR: {
BLANK: 'Pole __1__ jest puste',
NAN: 'Pole __1__ nie reprezentuje liczby',
NEGATIVE_NUMBER: 'Pole __1__ jest ujemne',
BAD_FORMAT: 'Pole __1__ ma z\u{142}y format',
PAST_DATE: 'Podany punkt w czasie nale\u{17C}y do przesz\u{142}o\u{15B}ci',
MOBILE: 'Wersja mobilna nie jest wspierana',
NEW_WINDOW_BLOCKED: 'Wygl\u0105da na to, \u017Ce preferencje u\u017Cytkownika w przegl\u0105darce nie pozwalaj\u0105 otworzy\u0107 wi\u0119kszej ilo\u015Bci kart',
EMPTY_DEFF_SELECTION: 'Nie uda\u{142}o si\u0119 wybra\u0107 jednostek defensywnych',
INVALID_VILLAGE_INFO: 'Wydaje si\u0119, \u017Ce wioska docelowa nie istnieje',
NO_OTHER_SUPPORT_CONFLICT: 'Ustawienia \u{15B}wiata nie pozwalaj\u0105 na wysy\u{142}anie wspar\u{107} do graczy innych plemion',
EMPTY_GROUP: 'Wybrana grupa nie posiada wiosek'
},
UNITS: {
spear: 'Pikinier',
sword: 'Miecznik',
archer: '\u{141}ucznik',
spy: 'Zwiadowca',
heavy: 'Ci\u{119}\u{17C}ka kawaleria',
},
FIELDSET: {
input: 'Domy\u{15B}lne warto\u{15B}ci',
ratio: 'Przeliczniki',
safeguard: 'Rezerwa',
},
LABELS: {
target: 'Cel',
group: 'Grupa',
deff_count: 'Ilo\u{15B}\u{107} deffa',
spy_count: 'Ilo\u{15B}\u{107} zwiadu',
village_count: 'Ilo\u{15B}\u{107} wiosek',
minimal_deff_count: 'Minimalna ilo\u{15B}\u{107} deffa',
strategy: 'Strategia wybierania',
arrival_date: 'Data dotarcia przed',
split_units: 'Rozdziel jednostki',
generate: 'Generuj',
command: 'Rozkaz',
execute: 'Wykonaj',
save_settings: 'Zapisz',
reset_settings: 'Przywr\u{F3}\u{107} domy\u{15B}lne',
}
};
const Helper = {
beautify_number: function (number) {
let prefix = ['', 'K', 'M', 'G'];
for (let i = 0; i < prefix.length; i++) {
if (number >= 1000) {
number /= 1000;
} else {
if (i === 0)
return number.toFixed();
let fraction = 2;
if (number >= 10)
fraction = 1;
if (number >= 100)
fraction = 0;
return `${number.toFixed(fraction)}${prefix[i]}`;
}
}
return `${number.toFixed(0)}T`;
},
parse_date: function (date_string, replacement) {
let time_offset = 0;
const date_matches = date_string.match(/jutro|dzisiaj|\d+\.\d+(?:\.\d+)?/g);
if (date_matches) {
time_offset = date_string.indexOf(date_matches[0]) + date_matches[0].length;
}
const time_matches = date_string.slice(time_offset).match(/\d+(?::\d+)*/g);
if (!time_matches || time_matches.length > 1 || (date_matches && date_matches.length > 1)) {
throw i18n.ERROR.BAD_FORMAT.replace('__1__', replacement);
}
const today = new Date();
const time_parts = time_matches[0].split(':').map(x => Number(x));
const parts = {
year: today.getFullYear(),
month: today.getMonth(),
date: today.getDate(),
hours: time_parts[0],
minutes: time_parts[1] || 0,
seconds: time_parts[2] || 0,
};
if (date_matches) {
const date_match = date_matches[0];
switch (date_match) {
case "jutro": parts.date = today.getDate() + 1; break;
case "dzisiaj": break;
default:
const date_parts = date_match.split('.').map(x => Number(x));
if (date_parts.length === 3) {
if (date_parts[2] < 100) {
date_parts[2] += 2000;
}
parts.year = date_parts[2];
}
parts.month = date_parts[1] - 1;
parts.date = date_parts[0];
break;
}
}
const user_date = new Date(parts.year, parts.month, parts.date, parts.hours, parts.minutes, parts.seconds);
if (!date_matches && user_date.getTime() < Date.now()) {
user_date.setDate(user_date.getDate() + 1);
}
return user_date;
},
assert_non_negative_number: function (input, replacement) {
const empty_regex = new RegExp(/^\s*$/);
const value = input.value;
if (empty_regex.test(value)) {
input.focus();
throw i18n.ERROR.BLANK.replace('__1__', replacement);
}
const numeric_value = Number(value);
if (isNaN(numeric_value)) {
input.focus();
throw i18n.ERROR.NAN.replace('__1__', replacement);
}
if (numeric_value < 0) {
input.focus();
throw i18n.ERROR.NEGATIVE_NUMBER.replace('__1__', replacement);
}
},
get_id: function (control_name) {
return control_name
? `${namespace}.${control_name}`
: namespace;
},
get_control: function (control_name) {
const escaped_id = Helper.get_id(control_name).replace(/\./g, '\\.');
return document.querySelector(`#${escaped_id}`);
},
handle_error: function (error) {
if (typeof (error) === 'string') {
UI.ErrorMessage(error);
return;
}
const gui =
`
WTF - What a Terrible Failure
${i18n.ERROR_MESSAGE}
${i18n.FORUM_THREAD}
`;
Dialog.show(namespace, gui);
}
};
const Guard = {
add_command: function (troops_info, user_input, target_info, group_name) {
for (let i = 0; i < troops_info.villages.length; i++) {
const row = document.createElement('tr');
row.dataset['group'] = group_name;
if (i == troops_info.villages.length - 1) {
row.style.borderBottom = 'black solid 1px';
}
const village_cell = document.createElement('td');
const village_anchor = document.createElement('a');
village_anchor.href = TribalWars.buildURL('GET', 'info_village', { id: troops_info.villages[i].id });
village_anchor.innerText = troops_info.villages[i].name;
village_cell.append(village_anchor);
row.append(village_cell);
for (const unit of Guard.deff_units) {
const unit_cell = document.createElement('td');
unit_cell.textContent = troops_info.villages[i].units[unit];
if (troops_info.villages[i].units[unit] === 0) {
unit_cell.classList.add('hidden');
}
row.append(unit_cell);
}
if (i == 0) {
const payload = {
x: user_input.target[0],
y: user_input.target[1],
target: target_info[Guard._village_info.VILLAGE_ID],
call: {},
h: game_data.csrf,
};
for (const village of troops_info.villages) {
payload['call'][village.id] = {};
for (const unit of Guard.deff_units) {
if (village.units[unit] != 0) {
payload['call'][village.id][unit] = village.units[unit];
}
}
}
const place_cell = document.createElement('td');
place_cell.rowSpan = troops_info.villages.length;
place_cell.style.verticalAlign = 'top';
const place_button = document.createElement('button');
place_button.innerText = i18n.LABELS.execute;
place_button.dataset['payload'] = JSON.stringify(payload);
place_button.classList.add('btn');
place_button.style.margin = '2px';
place_button.addEventListener('click', (e) => {
const payload = JSON.parse(e.target.dataset['payload']);
let payload_formdata = `x=${payload.x}&y=${payload.y}&target=${payload.target}&h=${game_data.csrf}`;
for (const village_id in payload.call) {
const village_troops = payload.call[village_id];
for (const unit_name in village_troops) {
payload_formdata += `&call[${village_id}][${unit_name}]=${village_troops[unit_name]}`;
}
}
fetch(TribalWars.buildURL('GET', 'place', { mode: 'call', action: 'call' }), {
body: encodeURI(payload_formdata),
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
}
});
Guard.group_id2villages.delete(user_input.group_id);
const group_rows = Helper.get_control('output').querySelectorAll(`tr[data-group="${group_name}"]`);
for (let i = group_rows.length - 1; i >= 0; i--) {
group_rows[i].remove();
}
return false;
});
place_cell.append(place_button);
row.append(place_cell);
}
Helper.get_control('output').append(row);
}
},
create_main_panel: function () {
const options = [
{ name: 'target', controls: [{ type: 'input', attributes: { id: 'target', size: 10 } }] },
{ name: 'group', controls: [{ type: 'select', attributes: { id: 'group' } }] },
{ name: 'deff_count', controls: [{ type: 'input', attributes: { id: 'deff_count', size: 10 } }] },
{ name: 'spy_count', controls: [{ type: 'input', attributes: { id: 'spy_count', size: 10 } }] },
{ name: 'village_count', controls: [{ type: 'input', attributes: { id: 'village_count', size: 10 } }] },
{ name: 'minimal_deff_count', controls: [{ type: 'input', attributes: { id: 'minimal_deff_count', size: 10 } }] },
{ name: 'strategy', controls: [{ type: 'select', attributes: { id: 'strategy' } }] },
{ name: 'arrival_date', for: 'is_arrival_date_enabled', controls: [{ type: 'input', attributes: { id: 'is_arrival_date_enabled', type: 'checkbox' } }, { type: 'input', attributes: { id: 'arrival_date', size: 12 } }] },
{ name: 'split_units', controls: [{ type: 'input', attributes: { id: 'split_units', type: 'checkbox' } }] },
];
const create_option_label = function (option) {
const option_cell = document.createElement('th');
const option_label = document.createElement('label');
option_label.classList.add('center');
option_label.setAttribute('for', Helper.get_id(option.hasOwnProperty('for') ? option.for : option.name));
option_label.textContent = i18n.LABELS[option.name];
option_cell.append(option_label)
return option_cell;
}
const create_option_input = function (option) {
const option_cell = document.createElement('td');
const option_span = document.createElement('span');
option_span.style.display = 'flex';
for (const control_definition of option.controls) {
const option_control = document.createElement(control_definition.type);
const attributes = control_definition.attributes;
for (const attribute_name in attributes) {
const attribute_value = attribute_name === 'id'
? Helper.get_id(attributes.id)
: attributes[attribute_name];
option_control.setAttribute(attribute_name, attribute_value);
}
if (attributes.type === 'checkbox') {
option_control.style.display = 'block';
option_control.style.margin = 'auto';
}
option_control.disabled = true;
option_span.append(option_control);
}
option_cell.append(option_span);
return option_cell;
}
const option_labels_row = document.createElement('tr');
const option_inputs_row = document.createElement('tr');
for (const option of options) {
option_labels_row.append(create_option_label(option));
option_inputs_row.append(create_option_input(option));
}
const settings_cell = document.createElement('th');
const settings_image = document.createElement('img');
settings_image.setAttribute('id', Helper.get_id('settings'));
settings_image.setAttribute('src', `${image_base}icons/settings.png`);
settings_image.setAttribute('alt', 'settings');
settings_image.style.margin = 'auto';
settings_image.style.display = 'block';
settings_cell.append(settings_image);
option_labels_row.append(settings_cell);
const generate_cell = document.createElement('td');
const generate_button = document.createElement('button');
generate_button.setAttribute('id', Helper.get_id('generate'));
generate_button.textContent = i18n.LABELS.generate;
generate_button.style.marginTop = '2px';
generate_button.classList.add('btn');
generate_button.disabled = true;
generate_cell.append(generate_button);
option_inputs_row.append(generate_cell);
const panel = document.createElement('div');
const table = document.createElement('table');
panel.classList.add('vis', 'vis_item');
panel.style.margin = '5px';
panel.append(table);
table.style.width = '100%';
table.append(option_labels_row);
table.append(option_inputs_row);
return panel;
},
create_output_panel: function () {
const header = document.createElement('thead');
const create_unit_counter_cell = function (unit_name) {
const cell = document.createElement('th');
if (game_data.units.includes(unit_name)) {
const unit_image = document.createElement('img');
unit_image.setAttribute('src', `${image_base}unit/unit_${unit_name}.png`);
unit_image.setAttribute('alt', unit_name);
cell.append(unit_image);
}
const selected_counter = document.createElement('span');
selected_counter.setAttribute('id', Helper.get_id(`${unit_name}.selected`));
selected_counter.textContent = 0;
const all_counter = document.createElement('span');
all_counter.setAttribute('id', Helper.get_id(`${unit_name}.all`));
all_counter.textContent = 0;
cell.append(document.createTextNode('('));
cell.append(selected_counter);
cell.append(document.createTextNode('/'));
cell.append(all_counter);
cell.append(document.createTextNode(')'));
return cell;
}
const deff_cell = create_unit_counter_cell('deff');
const deff_image = document.createElement('img');
deff_image.setAttribute('src', `${image_base}face.png`);
deff_image.setAttribute('alt', 'face.png');
deff_cell.prepend(deff_image);
header.append(deff_cell);
for (const unit_name of Guard.deff_units) {
header.append(create_unit_counter_cell(unit_name));
}
const command_cell = document.createElement('th');
command_cell.append(document.createTextNode(i18n.LABELS.command));
header.append(command_cell);
const body = document.createElement('tbody');
body.setAttribute('id', Helper.get_id('output'));
const table = document.createElement('table');
table.style.width = '100%';
table.append(header);
table.append(body);
const panel = document.createElement('div');
panel.classList.add('vis', 'vis_item');
panel.style.overflowY = 'auto';
panel.style.height = '200px';
panel.style.margin = '5px';
panel.append(table);
return panel;
},
create_signature_span: function () {
const span = document.createElement('span');
span.style.display = 'flex';
span.style.float = 'left';
span.style.marginTop = '10px';
const a = document.createElement('a');
a.setAttribute('href', i18n.FORUM_THREAD_HREF);
a.textContent = i18n.FORUM_THREAD;
span.append(a);
return span;
},
create_bottom_panel: function () {
const panel = document.createElement('div');
panel.classList.add('vis_item');
panel.style.margin = '5px';
const panel_table = document.createElement('table');
panel_table.style.width = '100%';
const panel_tr = document.createElement('tr');
const panel_td = document.createElement('td');
panel_table.append(panel_tr);
panel_tr.append(panel_td);
panel_td.append(Guard.create_signature_span());
panel.append(panel_table)
return panel;
},
create_gui: function () {
const div = document.createElement('div');
div.style.padding = '0px';
div.style.margin = '0px 0px 5px 0px';
div.setAttribute('id', namespace);
div.classList.add('vis', 'vis_item');
div.append(Guard.create_main_panel());
div.append(Guard.create_output_panel());
div.append(Guard.create_bottom_panel());
document.querySelector('#contentContainer').prepend(div);
},
init_gui: async function () {
const url_params = new URLSearchParams(location.search);
const target = Helper.get_control('target');
target.value = url_params.get('target') || (game_data.screen === 'info_village'
? `${TWMap.pos[0]}|${TWMap.pos[1]}`
: `${game_data.village.x}|${game_data.village.y}`);
target.disabled = false;
const strategy = Helper.get_control('strategy');
for (const key in Guard.strategies) {
const option = document.createElement('option');
option.setAttribute('value', key);
option.text = Guard.strategies[key];
strategy.append(option);
}
strategy.value = Guard.settings.input.strategy;
strategy.disabled = false;
for (const option_name of ['deff_count', 'spy_count', 'minimal_deff_count', 'village_count']) {
const control = Helper.get_control(option_name);
control.value = url_params.get(option_name) || Guard.settings.input[option_name];
control.disabled = false;
}
const split_units = Helper.get_control('split_units');
split_units.checked = Guard.settings.input.split_units;
split_units.disabled = false;
const groups_info = await Guard.get_groups_info();
const group = Helper.get_control('group');
for (const group_info of groups_info.result) {
const option = document.createElement('option');
option.setAttribute('value', group_info.group_id);
option.text = group_info.name;
group.append(option);
}
group.value = Guard.settings.input.group === '-1'
? groups_info.group_id
: Guard.settings.input.group;
group.disabled = false;
await Guard.get_world_info();
let default_date = new Date();
if (Guard.world_info.config.night.active) {
let end_hour = Number(Guard.world_info.config.night.end_hour);
if (default_date.getHours() >= end_hour) {
default_date.setDate(default_date.getDate() + 1);
}
default_date.setHours(end_hour);
}
const arrival_date = Helper.get_control('arrival_date');
arrival_date.value = url_params.get('arrival_date') || `${default_date.getDate()}.${default_date.getMonth() + 1} ${default_date.getHours()}:00:00`;
Helper.get_control('generate').addEventListener('click', async () => {
try { await Guard.generate_commands(); } catch (ex) { Helper.handle_error(ex); }
});
Helper.get_control('settings').addEventListener('click', () => {
try { Guard.edit_settings(); } catch (ex) { Helper.handle_error(ex); }
});
const enable_arrival_date = Helper.get_control('is_arrival_date_enabled');
enable_arrival_date.addEventListener('change', event => {
Helper.get_control('arrival_date').disabled = !event.target.checked;
});
enable_arrival_date.disabled = false;
Helper.get_control('generate').disabled = false;
if (url_params.has('arrival_date')) {
enable_arrival_date.checked = true;
arrival_date.disabled = false;
}
},
get_groups_info: async function () {
let url = TribalWars.buildURL('GET', 'groups', { mode: 'overview', ajax: 'load_group_menu' });
const response = await fetch(url, { credentials: 'include' });
const text = await response.text();
const payload = JSON.parse(text);
payload.result = payload.result.filter(group => group.type !== 'separator');
payload.result.forEach(group => {
Guard.group_id2group_name[group.group_id] = group.name;
});
return payload;
},
get_world_info: async function () {
Guard.world_info = await get_world_info({ configs: ['config', 'unit_info'] });
},
fetch_map_chunk: async function (x_chunk, y_chunk) {
const map_key = `${x_chunk}_${y_chunk}`;
if (!Guard.coords2map_chunk.has(map_key)) {
const url_params = new URLSearchParams({ locale: game_data.locale, v: 2, [map_key]: 1 });
const response = await fetch(`map.php?${url_params}`);
const map_info = await response.json();
Guard.coords2map_chunk.set(map_key, map_info);
}
return Guard.coords2map_chunk.get(map_key);
},
fetch_village_info: async function (coords) {
const x_chunk = coords[0] - coords[0] % 20;
const y_chunk = coords[1] - coords[1] % 20;
const map_info = await Guard.fetch_map_chunk(x_chunk, y_chunk);
const village_info = (map_info[0].data.villages[coords[0] - x_chunk] || [])[coords[1] - y_chunk];
return village_info;
},
generate_commands: async function () {
let get_user_input = function () {
const user_input = {};
const numeric_fields = ['deff_count', 'spy_count', 'village_count', 'minimal_deff_count'];
for (const numeric_field of numeric_fields) {
const input = Helper.get_control(numeric_field);
Helper.assert_non_negative_number(input, i18n.LABELS[numeric_field]);
user_input[numeric_field] = Number(input.value);
}
const coords_regex = new RegExp(/^\s*\d{1,3}\|\d{1,3}\s*$/);
const target = Helper.get_control('target');
if (!coords_regex.test(target.value)) {
target.focus();
throw i18n.ERROR.BAD_FORMAT.replace('__1__', i18n.LABELS.target);
}
user_input.target = target.value.trim().split('|').map(x => Number(x));
user_input.strategy = Helper.get_control('strategy').value;
user_input.group_id = Helper.get_control('group').value;
user_input.split_units = Helper.get_control('split_units').checked;
user_input.travel_time = NaN;
if (Helper.get_control('is_arrival_date_enabled').checked) {
let arrival_date = Helper.parse_date(Helper.get_control('arrival_date').value, i18n.LABELS.arrival_date);
if (arrival_date.getTime() <= Date.now()) {
Helper.get_control('arrival_date').focus();
throw i18n.ERROR.PAST_DATE;
}
user_input.travel_time = (arrival_date.getTime() - Date.now()) / 60 / 1000;
}
return user_input;
};
let sort_by_deff_asc = function (lhs, rhs) {
return lhs.deff !== rhs.deff ? lhs.deff > rhs.deff ? 1 : -1 : 0;
};
let sort_by_deff_desc = function (lhs, rhs) {
return lhs.deff !== rhs.deff ? lhs.deff > rhs.deff ? -1 : 1 : 0;
};
let sort_by_spy_desc = function (lhs, rhs) {
return lhs.units.spy !== rhs.units.spy ? lhs.units.spy > rhs.units.spy ? -1 : 1 : 0;
};
let sort_by_spy_asc = function (lhs, rhs) {
return lhs.units.spy !== rhs.units.spy ? lhs.units.spy > rhs.units.spy ? 1 : -1 : 0;
};
let sort_by_distance_desc = function (lhs, rhs) {
return lhs.distance !== rhs.distance ? lhs.distance > rhs.distance ? -1 : 1 : 0;
};
let sort_by_distance_asc = function (lhs, rhs) {
return lhs.distance !== rhs.distance ? lhs.distance > rhs.distance ? 1 : -1 : 0;
};
let random_sort = function (villages) {
for (let i = villages.length - 1; i > 0; i--) {
let j = Math.floor(Math.random() * (i + 1));
let x = villages[i];
villages[i] = villages[j];
villages[j] = x;
}
return villages;
};
let get_troops_info = function (villages, user_input) {
const troops_info = {
villages: [],
all: { deff: 0 },
selected: { deff: 0 }
};
for (const unit_name of Guard.deff_units) {
troops_info.all[unit_name] = 0;
troops_info.selected[unit_name] = 0;
}
for (const village of villages) {
const village_troop_info = {
deff: 0,
name: village.name,
id: village.id,
coords: village.coords,
distance: Math.hypot(user_input.target[0] - village.coords[0], user_input.target[1] - village.coords[1]),
units: {}
};
if (village_troop_info.distance === 0) {
continue;
}
for (const unit_name of Guard.deff_units) {
const ratio = Guard.settings.ratio[unit_name] === undefined
? 0
: Number(Guard.settings.ratio[unit_name]);
village_troop_info.units[unit_name] = village.units[unit_name] === undefined
? 0
: Math.max(village.units[unit_name] - Number(Guard.settings.safeguard[unit_name]), 0);
if (!isNaN(user_input.travel_time) && Guard.world_info.unit_info.hasOwnProperty(unit_name)) {
if (Number(Guard.world_info.unit_info[unit_name].speed) * village_troop_info.distance > user_input.travel_time) {
village_troop_info.units[unit_name] = 0;
}
}
if (unit_name !== 'spy' && ratio === 0) {
village_troop_info.units[unit_name] = 0;
}
village_troop_info.deff += Number(village_troop_info.units[unit_name]) * ratio;
troops_info.all[unit_name] += village_troop_info.units[unit_name];
}
troops_info.all.deff += village_troop_info.deff;
troops_info.villages.push(village_troop_info);
}
return troops_info;
}
const preprocess = function (troops_info, user_input) {
troops_info.villages = troops_info.villages.filter(village => village.deff >= user_input.minimal_deff_count);
switch (user_input.strategy) {
case 'DIST_ASC': troops_info.villages.sort(sort_by_distance_asc); break;
case 'DIST_DESC': troops_info.villages.sort(sort_by_distance_desc); break;
case 'TROOP_ASC': troops_info.villages.sort(user_input.deff_count ? sort_by_deff_asc : sort_by_spy_asc); break;
case 'TROOP_DESC': troops_info.villages.sort(user_input.deff_count ? sort_by_deff_desc : sort_by_spy_desc); break;
default: random_sort(troops_info.villages); break;
}
troops_info.villages = troops_info.villages.slice(0, user_input.village_count);
}
let select_troops = function (troops_info, user_input) {
troops_info.villages.sort(sort_by_deff_desc);
for (let i = troops_info.villages.length; i > 0; i--) {
const village = troops_info.villages[i - 1];
const threshold = (user_input.deff_count - troops_info.selected.deff) / i;
const ratio = threshold < village.deff ? threshold / village.deff : 1.0;
for (const unit_name in Guard.default_settings.ratio) {
if (Guard.deff_units.includes(unit_name)) {
const selected_count = Math.min(Math.round(ratio * village.units[unit_name]), village.units[unit_name]);
troops_info.selected[unit_name] += selected_count;
troops_info.selected.deff += selected_count * Number(Guard.settings.ratio[unit_name]);
village.units[unit_name] = selected_count;
}
}
}
troops_info.villages.sort(sort_by_spy_desc);
for (let i = troops_info.villages.length; i > 0; i--) {
const village = troops_info.villages[i - 1];
const threshold = (user_input.spy_count - troops_info.selected.spy) / i;
const ratio = threshold < village.units.spy ? threshold / village.units.spy : 1.0;
const selected_count = Math.min(Math.round(ratio * village.units.spy), village.units.spy);
troops_info.selected.spy += selected_count;
village.units.spy = selected_count;
}
for (const unit_name in troops_info.all) {
Helper.get_control(`${unit_name}.all`).textContent = Helper.beautify_number(troops_info.all[unit_name])
Helper.get_control(`${unit_name}.selected`).textContent = Helper.beautify_number(troops_info.selected[unit_name]);
}
};
const user_input = get_user_input();
const target_info = await Guard.fetch_village_info(user_input.target);
const villages = await Guard.get_villages(user_input.group_id);
if (!target_info) {
throw i18n.ERROR.INVALID_VILLAGE_INFO;
}
if (Number(Guard.world_info.config.ally.no_other_support) !== 0) {
if (Number(game_data.player.ally) === 0 ||
Number(target_info[Guard._village_info.ALLY_ID]) !== Number(game_data.player.ally)) {
throw i18n.ERROR.NO_OTHER_SUPPORT_CONFLICT;
}
}
if (villages == null) {
throw i18n.ERROR.EMPTY_GROUP;
}
const current_commands = [...Helper.get_control('output').children];
for (let i = current_commands.length - 1; i >= 0; i--) {
current_commands[i].remove();
}
const generate_button = Helper.get_control('generate');
try {
generate_button.disabled = true;
const troops_info = get_troops_info(villages, user_input);
preprocess(troops_info, user_input);
select_troops(troops_info, user_input);
if (user_input.deff_count && !user_input.spy_count && !troops_info.selected.deff) {
throw i18n.ERROR.EMPTY_DEFF_SELECTION;
}
if (user_input.split_units) {
for (const speed_group of Guard.speed_groups) {
const snapshot = JSON.parse(JSON.stringify(troops_info));
let total = 0;
for (const unit_name of Guard.deff_units) {
snapshot.selected[unit_name] = 0;
for (const village of snapshot.villages) {
if (!speed_group.includes(unit_name)) {
village.units[unit_name] = 0;
} else {
snapshot.selected[unit_name] += village.units[unit_name];
}
}
total += snapshot.selected[unit_name];
}
if (total) {
Guard.add_command(snapshot, user_input, target_info, speed_group[0]);
}
}
}
else {
Guard.add_command(troops_info, user_input, target_info, 'all');
}
}
finally {
generate_button.disabled = false;
}
},
get_villages: async function (group_id) {
if (Guard.group_id2villages.has(group_id)) {
return Guard.group_id2villages.get(group_id);
}
let url = TribalWars.buildURL('GET', 'overview_villages', { mode: 'units', type: 'own_home', group: group_id, page: -1, });
const request = await fetch(url, { credentials: 'same-origin' });
const response = await request.text();
const requested_body = document.createElement('body');
requested_body.innerHTML = response;
const units_table = requested_body.querySelector('#units_table');
if (!units_table) {
return null;
}
const villages = [];
for (let i = 1; i < units_table.rows.length; i++) {
let row = units_table.rows[i];
let units = {};
const offset = 2;
for (let j = 0; j < game_data.units.length; j++) {
let unit_name = game_data.units[j];
units[unit_name] = Number(row.cells[offset + j].textContent);
}
let main_cell = row.cells[0];
let name = main_cell.textContent.trim();
let villagei18n = {
name: name,
coords: name.match(/\d+\|\d+/).pop().split('|').map(x => Number(x)),
id: main_cell.children[0].getAttribute('data-id'),
units: units
};
villages.push(villagei18n);
}
Guard.group_id2villages.set(group_id, villages);
return villages;
},
edit_settings: function () {
const default_settings = Guard.get_default_settings();
let add_unit_fieldset = function (branch) {
let fieldset = `';
return fieldset;
};
let add_setttings_input = function (id, value) {
return ` | `;
};
let add_settings_select = function (id, options) {
let html = ` | ';
return html;
};
let add_settings_checkbox = function (id, checked) {
return ` | `;
};
let add_input_fieldset = function () {
let fieldset = `';
return fieldset;
};
let save_settings = function () {
const settings = { ratio: {}, safeguard: {}, input: {} };
try {
for (const branch of ['ratio', 'safeguard']) {
for (const unit_name in default_settings[branch]) {
if (Guard.deff_units.includes(unit_name)) {
const user_input = Helper.get_control(`${branch}.${unit_name}`);
Helper.assert_non_negative_number(user_input, `${i18n.FIELDSET[branch]} - ${i18n.UNITS[unit_name]}`);
settings[branch][unit_name] = Number(user_input.value);
Guard.settings[branch][unit_name] = Number(user_input.value);
}
}
}
for (const key in default_settings.input) {
const user_input = Helper.get_control(`input.${key}`);
if (['strategy', 'group'].includes(key)) {
settings.input[key] = user_input.value;
} else if (key === 'split_units') {
settings.input[key] = user_input.checked;
} else {
Helper.assert_non_negative_number(user_input, i18n.LABELS[key]);
settings.input[key] = Number(user_input.value);
}
}
localStorage.setItem(namespace, JSON.stringify(settings));
UI.SuccessMessage(i18n.SETTINGS_SAVED);
document.querySelector('.popup_box_close').click();
} catch (ex) {
Helper.handle_error(ex);
}
};
let reset_settings = function () {
Guard.settings = JSON.parse(JSON.stringify(Guard.default_settings));
localStorage.removeItem(namespace);
UI.SuccessMessage(i18n.SETTINGS_RESETED);
document.querySelector('.popup_box_close').click();
};
let gui = '';
gui += add_unit_fieldset('ratio');
gui += add_unit_fieldset('safeguard');
gui += add_input_fieldset();
const reset_settings_id = Helper.get_id('reset_settings');
const save_settings_id = Helper.get_id('save_settings');
gui += `
`;
gui += `
`;
Dialog.show(Helper.get_id('settings_editor'), gui);
setTimeout(() => {
Helper.get_control('input.group').value = default_settings.input.group;
Helper.get_control('input.strategy').value = default_settings.input.strategy;
const reset_settings_button = Helper.get_control('reset_settings');
reset_settings_button.addEventListener('click', reset_settings);
reset_settings_button.disabled = false;
const save_settings_button = Helper.get_control('save_settings');
save_settings_button.addEventListener('click', save_settings);
save_settings_button.disabled = false;
});
},
_village_info: {
ALLY_ID: 11,
VILLAGE_ID: 0
},
coords2map_chunk: new Map(),
group_id2villages: new Map(),
group_id2group_name: {
'-1': i18n.CURRENTLY_SELECTED_GROUP
},
strategies: {
'TROOP_DESC': i18n.STRATEGY.TROOP_DESC,
'TROOP_ASC': i18n.STRATEGY.TROOP_ASC,
'DIST_DESC': i18n.STRATEGY.DIST_DESC,
'DIST_ASC': i18n.STRATEGY.DIST_ASC,
'RANDOM': i18n.STRATEGY.RANDOM
},
default_settings: {
ratio: {
spear: 1,
sword: 1,
archer: 1,
heavy: 6
},
safeguard: {
spear: 0,
sword: 0,
archer: 0,
spy: 0,
heavy: 0
},
input: {
deff_count: 0,
spy_count: 0,
village_count: 12,
minimal_deff_count: 0,
strategy: 'TROOP_DESC',
group: '-1',
split_units: false,
},
},
deff_units: [],
speed_groups: [['spear', 'archer'], ['sword'], ['spy', 'heavy']],
settings: {},
get_default_settings: function () {
let stored_settings = localStorage.getItem(namespace);
return stored_settings
? JSON.parse(stored_settings)
: JSON.parse(JSON.stringify(Guard.default_settings));
},
init_settings: function () {
Guard.settings = Guard.get_default_settings();
Guard.deff_units = Object.keys(Guard.default_settings.safeguard)
.filter(unit_name => game_data.units.includes(unit_name));
},
main: async function () {
if (mobile) {
throw i18n.ERROR.MOBILE;
}
let instance = Helper.get_control();
if (instance) {
instance.remove();
return;
}
Guard.init_settings();
Guard.create_gui();
$.ajax({
url: 'https://media.innogamescdn.com/com_DS_PL/skrypty/HermitowskiePlikiMapy.js?_=' + ~~(Date.now() / 9e6),
dataType: 'script',
cache: true
}).then(() => {
Guard.init_gui().catch(Helper.handle_error);
});
}
};
try { await Guard.main(); } catch (ex) { Helper.handle_error(ex); }
console.log(`${namespace} | Elapsed time: ${Date.now() - start} [ms]`);
})(TribalWars);
Darmowy hosting zapewnia PRV.PL