// ==UserScript==
// @name ModernBot
// @author Sau1707
// @description A modern grepolis bot
// @version 1.18.3
// @match http://*.grepolis.com/game/*
// @match https://*.grepolis.com/game/*
// @updateURL https://github.com/Sau1707/ModernBot/blob/main/dist/merged.user.js
// @downloadURL https://github.com/Sau1707/ModernBot/blob/main/dist/merged.user.js
// @icon https://raw.githubusercontent.com/Sau1707/ModernBot/main/img/gear.png
// @require http://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js
// ==/UserScript==
var uw;
if (typeof unsafeWindow == 'undefined') {
uw = window;
} else {
uw = unsafeWindow;
}
var style = document.createElement("style");
style.textContent = `.auto_build_up_arrow{background:url(https://gpit.innogamescdn.com/images/game/academy/up.png) no-repeat -2px -2px;width:18px;height:18px;position:absolute;right:-2px;bottom:12px;transform:scale(.8);cursor:pointer}.auto_build_down_arrow{background:url(https://gpit.innogamescdn.com/images/game/academy/up.png) no-repeat -2px -2px;width:18px;height:18px;position:absolute;right:-2px;bottom:-3px;transform:scale(.8) rotate(180deg);cursor:pointer}.auto_build_box{background:url(https://gpit.innogamescdn.com/images/game/academy/tech_frame.png) no-repeat 0 0;width:58px;height:59px;position:relative;overflow:hidden;display:inline-block;vertical-align:middle}.auto_build_building{position:absolute;top:4px;left:4px;width:50px;height:50px;background:url(https://gpit.innogamescdn.com/images/game/main/buildings_sprite_50x50.png) no-repeat 0 0}.auto_trade_troop{position:absolute;top:4px;left:4px;width:50px;height:50px;background:url(https://gpit.innogamescdn.com/images/game/autogenerated/units/unit_icons_50x50_654368f.png) no-repeat 0 0}.auto_build_lvl{position:absolute;bottom:3px;left:3px;margin:0;font-weight:700;font-size:12px;color:#fff;text-shadow:0 0 2px #000,1px 1px 2px #000,0 2px 2px #000}#buildings_lvl_buttons{padding:5px;max-height:400px;user-select:none}#troops_lvl_buttons{padding:5px;max-height:400px;user-select:none}.progress_bar_auto{position:absolute;z-index:1;height:100%;left:0;top:0;background-image:url(https://gpit.innogamescdn.com/images/game/border/header.png);background-position:0 -1px;filter:brightness(100%) saturate(186%) hue-rotate(241deg)}.modern_bot_settings{z-index:10;position:absolute;top:52px!important;right:116px!important}.console_modernbot{width:100%;height:100%;background-color:#000;color:#fff;font-family:monospace;font-size:16px;padding:20px;box-sizing:border-box;overflow-y:scroll;display:flex;flex-direction:column-reverse}#MODERN_BOT_content{height:100%}.console_modernbot p{margin:1px}.population_icon{background:url(https://gpit.innogamescdn.com/images/game/autogenerated/layout/layout_095495a.png) no-repeat -697px -647px;width:25px;height:20px;position:absolute;right:2px}.population_icon p{text-align:end;position:absolute;right:30px;padding:0;margin:0;color:#000;font-weight:700}.split_content{width:100%;display:inline-flex;justify-content:space-between}@keyframes rotateForever{from{transform:rotate(0)}to{transform:rotate(360deg)}}.rotate-forever{animation:rotateForever 5s linear infinite;transform-origin:16px 15px;filter:hue-rotate(72deg) saturate(2.5)}.enabled .game_header{filter:brightness(100%) saturate(186%) hue-rotate(241deg)}`;
document.head.appendChild(style);
class ModernUtil {
/* CONSTANTS */
REQUIREMENTS = {
sword: {},
archer: { research: 'archer' },
hoplite: { research: 'hoplite' },
slinger: { research: 'slinger' },
catapult: { research: 'catapult' },
rider: { research: 'rider', building: 'barracks', level: 10 },
chariot: { research: 'chariot', building: 'barracks', level: 15 },
big_transporter: { building: 'docks', level: 1 },
small_transporter: { research: 'small_transporter', building: 'docks', level: 1 },
bireme: { research: 'bireme', building: 'docks', level: 1 },
attack_ship: { research: 'attack_ship', building: 'docks', level: 1 },
trireme: { research: 'trireme', building: 'docks', level: 1 },
colonize_ship: { research: 'colonize_ship', building: 'docks', level: 10 },
};
constructor(console, storage) {
this.console = console;
this.storage = storage;
}
/* Usage async this.sleep(ms) -> stop the code for ms */
sleep = (ms, stdDev) => {
// Check if a standard deviation is not provided
if (typeof stdDev === 'undefined') return new Promise(resolve => setTimeout(resolve, ms));
const mean = ms;
let u = 0, v = 0;
while (u === 0) u = Math.random(); // Converting [0,1) to (0,1)
while (v === 0) v = Math.random();
let num = Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v);
num = num * stdDev + mean; // Scale and translate to desired mean and stdDev
return new Promise(resolve => setTimeout(resolve, num));
};
/**
* Generate a list of town IDs that are located on large islands.
* A large island is defined as an island that has at least one town that is not on a small island.
* @returns {Array} - Array of town IDs.
*/
generateList = () => {
const townList = uw.MM.getOnlyCollectionByName('Town').models;
const islandsList = [];
const polisList = [];
for (const town of townList) {
const { island_id, id, on_small_island } = town.attributes;
if (on_small_island) continue; // Skip towns on small islands
if (!islandsList.includes(island_id)) {
islandsList.push(island_id);
polisList.push(id);
}
}
return polisList;
};
/**
* Returns HTML code for a button with a specified ID, text, function, and optional properties.
*
* @param {string} id - The ID for the button.
* @param {string} text - The text to display on the button.
* @param {Function} fn - The function to call when the button is clicked.
* @param {string} [props] - Optional properties to pass to the function.
* @returns {string} - The HTML code for the button.
*/
getButtonHtml(id, text, fn, props) {
const name = this.constructor.name.charAt(0).toLowerCase() + this.constructor.name.slice(1);
props = isNaN(parseInt(props)) ? `'${props}'` : props;
const click = `window.modernBot.${name}.${fn.name}(${props || ''})`;
return `
${text}
`;
}
/**
* Returns the HTML for a game title with a clickable header that toggles a function.
*
* @param {string} id - The ID for the HTML element.
* @param {string} text - The text to display in the title.
* @param {function} fn - The function to toggle.
* @param {string|number} props - The properties to pass to the function.
* @param {boolean} enable - Whether the title is enabled or not.
* @param {string} [desc='(click to toggle)'] - The description to display.
* @returns {string} The HTML for the game title.
*/
getTitleHtml(id, text, fn, props, enable, desc = '(click to toggle)') {
const name = this.constructor.name.charAt(0).toLowerCase() + this.constructor.name.slice(1);
props = isNaN(parseInt(props)) && props ? `"${props}"` : props;
const click = `window.modernBot.${name}.${fn.name}(${props || ''})`;
const filter = 'brightness(100%) saturate(186%) hue-rotate(241deg)';
return `
${text}
${desc}
`;
}
/**
* Calculates the total population of a collection of units.
*
* @param {Object} units - The collection of units to count population for.
* @returns {number} - The total population of all units in the collection.
*/
countPopulation(obj) {
const data = GameData.units;
let total = 0;
for (let key in obj) {
total += data[key].population * obj[key];
}
return total;
}
isActive(type) {
return uw.GameDataPremium.isAdvisorActivated(type);
}
/**
* const button = elements.createButton('id', 'text', fn);
* $('body').append(button);
* To disable/enable the button:
* button.addClass('disabled'); button.removeClass('disabled');
* $('#id').addClass('disabled'); $('#id').removeClass('disabled');
* NOTE: Even when the button is disabled, the click event will still be triggered.
*/
createButton = (id, text, fn) => {
const $button = $('', {
'id': id,
'class': 'button_new',
});
// Add the left and right divs to the button
$button.append($('', { 'class': 'left' }));
$button.append($('', { 'class': 'right' }));
$button.append($('', {
'class': 'caption js-caption',
'html': `${text} `
}));
// Add the click event to the button if a function is provided
if (fn) $(document).on('click', `#${id}`, fn);
return $button;
}
/**
* const title = elements.createTitle('id', 'text', fn, description);
* $('body').append(title);
* To disable/enable the title:
* title.addClass('disabled'); title.removeClass('disabled');
* $('#id').addClass('disabled'); $('#id').removeClass('disabled');
* NOTE: Even when the title is disabled, the click event will still be triggered.
*/
createTitle = (id, text, fn, desc = '(click to toggle)') => {
const $div = $('').addClass('game_header bold').attr('id', id).css({
cursor: 'pointer',
position: 'relative',
}).html(text);
const $span = $('').addClass('command_count');
const $descDiv = $('').css({
position: 'absolute',
right: '10px',
top: '4px',
fontSize: '10px'
}).text(desc);
$div.append($span).append($descDiv);
if (fn) $(document).on('click', `#${id}`, fn);
return $('')
.append('')
.append('')
.append('')
.append('')
.append('')
.append('')
.append('')
.append('')
.append($div);
}
createActivity = (background) => {
const $activity_wrap = $('');
const $activity = $('');
const $icon = $('').css({
"background": background,
"position": "absolute",
"top": "-1px",
"left": "-1px",
});
const $count = $('').text(0);
$icon.append($count);
$activity.append($icon);
$activity_wrap.append($activity);
return { $activity, $count };
}
createPopup = (left, width, height, $content) => {
const $box = $('').css({
"left": `${left}px`,
"position": "absolute",
"width": `${width}px`,
"height": `${height}px`,
"top": "29px",
"margin-left": "0px",
"display": "none",
});
// Make all the corners
const $corner_tl = $('');
const $corner_tr = $('');
const $corner_bl = $('');
const $corner_br = $('');
// Make all the borders
const $border_t = $('');
const $border_b = $('');
const $border_l = $('');
const $border_r = $('');
// Make the middle
const $middle = $('').css({
"left": "10px",
"right": "20px",
"top": "14px",
"bottom": "20px",
});
const $middle_content = $('').append($content);
$middle.append($middle_content);
$box.append($corner_tl, $corner_tr, $corner_bl, $corner_br, $border_t, $border_b, $border_l, $border_r, $middle);
return $box;
}
}
// TO BE FINISCHED
class About {
constructor() {
this.checkVersion();
}
settings = () => {};
checkVersion = async () => {
if (!GM_info) return;
/* Check that the version it's the current one */
const installedVersion = GM_info.script.version;
const file = await fetch('https://raw.githubusercontent.com/Sau1707/ModernBot/main/version.txt');
const lastVersion = await file.text();
if (lastVersion != installedVersion) {
console.log('Versions differents');
}
console.log(lastVersion, installedVersion);
};
}
class AntiRage extends ModernUtil {
GOODS_ICONS = {
athena: 'js-power-icon.animated_power_icon.animated_power_icon_45x45.power_icon45x45.power.strength_of_heroes',
zeus: 'js-power-icon.animated_power_icon.animated_power_icon_45x45.power_icon45x45.power.fair_wind',
artemis: 'js-power-icon.animated_power_icon.animated_power_icon_45x45.power_icon45x45.power.effort_of_the_huntress',
};
constructor(c, s) {
super(c, s);
this.loop_funct = null;
this.active_god_el = null;
let commandId;
const oldCreate = GPWindowMgr.Create;
GPWindowMgr.Create = function (type, title, params, id) {
if (type === GPWindowMgr.TYPE_ATK_COMMAND && id) commandId = id;
return oldCreate.apply(this, arguments);
};
/* Attach event to attack opening */
uw.$.Observer(uw.GameEvents.window.open).subscribe((e, data) => {
if (data.context != 'atk_command') return;
//const id = data.wnd.getID();
let max = 10;
const addSpell = () => {
let spellMenu = $('#command_info-god')[0];
if (!spellMenu) {
if (max > 0) {
max -= 1;
setTimeout(addSpell, 50);
}
return;
}
$(spellMenu).on('click', this.trigger);
this.command_id = commandId;
};
setTimeout(addSpell, 50);
});
}
handleGod = good => {
const godEl = $(`.god_mini.${good}.${good}`).eq(0);
if (!godEl.length) return;
const powerClassName = this.GOODS_ICONS[good];
godEl.css({
zIndex: 10,
cursor: 'pointer',
borderRadius: '100%',
outline: 'none',
boxShadow: '0px 0px 10px 5px rgba(255, 215, 0, 0.5)',
});
const powerEl = $(`.${powerClassName}`).eq(0);
if (!powerEl.length) return;
godEl.click(() => {
// deactivate the previously active god
if (this.active_god_el && this.active_god_el.get(0) === godEl.get(0)) {
clearInterval(this.loop_funct);
this.loop_funct = null;
this.setColor(this.active_god_el.get(0), false);
this.active_god_el = null;
return;
}
if (this.active_god_el && this.active_god_el.get(0) !== godEl.get(0)) {
clearInterval(this.loop_funct);
this.setColor(this.active_god_el.get(0), false);
}
this.loop_funct = setInterval(this.clicker, 1000, powerEl);
this.active_god_el = godEl;
this.setColor(godEl.get(0), true);
});
};
setColor = (elm, apply) => {
if (apply) {
elm.style.filter = 'brightness(100%) sepia(100%) hue-rotate(90deg) saturate(1500%) contrast(0.8)';
} else {
elm.style.filter = '';
}
};
trigger = () => {
setTimeout(() => {
this.handleGod('athena');
this.handleGod('zeus');
this.handleGod('artemis');
$('.js-god-box[data-god_id="zeus"]').find('.powers').append(`
Enchanted Rage
An Enchanted version of the normal rage
Made for who try to troll with the autoclick
Cast Purification and Rage at the same time
300 zeus + 200 artemis
`;
const default_popup = `
`;
const { artemis_favor, zeus_favor } = uw.ITowns.player_gods.attributes;
const enable = artemis_favor >= 200 && zeus_favor >= 300;
if (!enable) $('#enchanted_rage').css('filter', 'grayscale(1)');
// TODO: disable if not enable
$('#enchanted_rage').on({
click: () => {
if (!enable) return;
this.enchanted('zeus');
},
mouseenter: event => {
$('#popup_div_curtain').html(html);
const $popupDiv = $('#popup_div');
const offset = $popupDiv.offset();
const height = $popupDiv.outerHeight();
const width = $popupDiv.outerWidth();
const left = event.pageX + 10;
const top = event.pageY + 10;
if (left + width > $(window).width()) {
offset.left -= width;
} else {
offset.left = left;
}
if (top + height > $(window).height()) {
offset.top -= height;
} else {
offset.top = top;
}
$popupDiv.css({
left: offset.left + 'px',
top: offset.top + 'px',
display: 'block',
});
},
mousemove: event => {
const $popupDiv = $('#popup_div');
if ($popupDiv.is(':visible')) {
const offset = $popupDiv.offset();
const height = $popupDiv.outerHeight();
const width = $popupDiv.outerWidth();
const left = event.pageX + 10;
const top = event.pageY + 10;
if (left + width > $(window).width()) {
offset.left -= width;
} else {
offset.left = left;
}
if (top + height > $(window).height()) {
offset.top -= height;
} else {
offset.top = top;
}
$popupDiv.css({
left: offset.left + 'px',
top: offset.top + 'px',
});
}
},
mouseleave: () => {
$('#popup_div_curtain').html(default_popup);
},
});
}, 100);
};
clicker = el => {
let check = $('.js-power-icon.animated_power_icon.animated_power_icon_45x45.power_icon45x45.power').eq(0);
if (!check.length) {
clearInterval(this.loop_funct);
this.loop_funct = null;
this.active_god_el = null;
return;
}
el.click();
let delta_time = 500;
let rand = 500 + Math.floor(Math.random() * delta_time);
clearInterval(this.loop_funct);
this.loop_funct = setInterval(this.clicker, rand, el);
};
enchanted = async type => {
if (type === 'zeus') {
this.cast(this.command_id, 'cleanse');
//await this.sleep(1);
this.cast(this.command_id, 'transformation');
}
};
cast = (id, type) => {
let data = {
model_url: 'Commands',
action_name: 'cast',
arguments: {
id: id,
power_id: type,
},
};
uw.gpAjax.ajaxPost('frontend_bridge', 'execute', data);
};
}
/*
Vento favorevole
La voce di Zeus risuona nell'aria, il vento fa gonfiare le vele delle navi e frecce e dardi sibilanti vengono lanciati con precisione verso il nemico.
Le forze navali attaccanti ottengono un bonus del 10% alla loro forza durante il loro prossimo attacco.
250 favore
*/
class AutoBootcamp extends ModernUtil {
constructor(console, storage) {
super(console, storage);
// Create the buttons for the settings
this.$title = this.createTitle('auto_autobootcamp', 'Auto Bootcamp', this.toggle, '(click to toggle)');
this.$button_only_off = this.createButton('autobootcamp_off', 'Only off', this.triggerUseDef);
this.$button_off_def = this.createButton('autobootcamp_def', 'Off & Def', this.triggerUseDef);
this.$settings = this.createSettingsHtml();
// Save the state of the auto bootcamp
if (this.storage.load('ab_active', false)) this.toggle();
if (this.storage.load('bootcamp_use_def', false)) this.triggerUseDef();
// Attach the observer to the window open event
uw.$.Observer(GameEvents.window.open).subscribe("modernAttackSpot", this.updateWindow);
}
updateWindow = (event, handler) => {
if (!handler.attributes || handler.attributes.window_type !== 'attack_spot') return
const cid = handler.cid;
const $window = $(`#window_${cid}`);
// Add height to the window
$window.css('height', '660px');
// Wait for the content to be loaded
const interval = setInterval(() => {
const $content = $window.find('.window_content');
if ($content.length === 0) return;
clearInterval(interval);
$content.append(this.$settings);
}, 100);
}
// Add the settings to the window, keep this for backwards compatibility
settings = () => {
return ""
}
createSettingsHtml = () => {
// Create the settings box
const $div = $('')
$div.css({
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
padding: '10px',
});
// Add the buttons to the settings box
$div.append(this.$button_only_off);
$div.append(this.$button_off_def);
// Create the box
const $box = $('')
$box.addClass('game_border')
$box.css({
margin: '20px',
});
$box.append(this.$title)
$box.append($div);
return $box;
};
/* Update the settings title and buttons */
updateSettings = () => {
if (this.use_def) {
this.$button_only_off.addClass('disabled');
this.$button_off_def.removeClass('disabled');
} else {
this.$button_off_def.addClass('disabled');
this.$button_only_off.removeClass('disabled');
}
if (this.enable_auto_bootcamp) this.$title.addClass('enabled');
else this.$title.removeClass('enabled');
}
// Toggle the use of def units
triggerUseDef = () => {
this.use_def = !this.use_def;
this.storage.save('bootcamp_use_def', this.use_def);
this.updateSettings();
};
toggle = () => {
if (!this.enable_auto_bootcamp) {
this.enable_auto_bootcamp = setInterval(this.main, 4000);
} else {
clearInterval(this.enable_auto_bootcamp);
this.enable_auto_bootcamp = null;
}
this.storage.save('ab_active', !!this.enable_auto_bootcamp);
this.updateSettings();
};
attackBootcamp = () => {
let cooldown = uw.MM.getModelByNameAndPlayerId('PlayerAttackSpot').getCooldownDuration();
if (cooldown > 0) return false;
let { MovementsUnits } = uw.MM.getModels();
// Check if there is already an active attack
if (MovementsUnits != null) {
if (Object.keys(MovementsUnits).length > 0) {
var attack_list = Object.keys(MovementsUnits);
for (var i = 0; i < Object.keys(MovementsUnits).length; i++) {
if (MovementsUnits[attack_list[i]].attributes.destination_is_attack_spot) return false;
if (MovementsUnits[attack_list[i]].attributes.origin_is_attack_spot) return false;
}
}
}
// Get the units
var units = { ...uw.ITowns.towns[uw.Game.townId].units() };
delete units.militia;
// Remove naval units
for (let unit in units) {
if (uw.GameData.units[unit].is_naval) delete units[unit];
}
// Remove def units if the setting is off
if (!this.use_def) {
delete units.sword;
delete units.archer;
}
// If there are not enough units, return
// TODO: here check if the units are enough to attack
if (Object.keys(units).length === 0) return false;
// Send the attack
this.postAttackBootcamp(units);
return true;
};
rewardBootcamp = () => {
let model = uw.MM.getModelByNameAndPlayerId('PlayerAttackSpot');
// Stop if level is not found
if (typeof model.getLevel() == 'undefined') {
this.toggle();
return true;
}
// Check if there is a reward
let hasReward = model.hasReward();
if (!hasReward) return false;
// Check if the reward is instant
let reward = model.getReward();
if (reward.power_id.includes('instant') && !reward.power_id.includes('favor')) {
this.useBootcampReward();
return true;
}
// Check if the reward is stashable
if (reward.stashable) this.stashBootcampReward();
else this.useBootcampReward();
return true;
};
/* Main function, call in loop */
main = () => {
if (this.rewardBootcamp()) return;
if (this.attackBootcamp()) return;
};
/* Send post request to attack with the given units */
postAttackBootcamp = units => {
const data = {
model_url: `PlayerAttackSpot/${uw.Game.player_id}`,
action_name: 'attack',
arguments: units,
};
uw.gpAjax.ajaxPost('frontend_bridge', 'execute', data);
};
/* Send requesto to the server to use the reward */
useBootcampReward = () => {
const data = {
model_url: `PlayerAttackSpot/${uw.Game.player_id}`,
action_name: 'useReward',
arguments: {},
};
uw.gpAjax.ajaxPost('frontend_bridge', 'execute', data);
};
/* Send request to the server to stash the reward */
stashBootcampReward = () => {
const data = {
model_url: `PlayerAttackSpot/${uw.Game.player_id}`,
action_name: 'stashReward',
arguments: {},
};
uw.gpAjax.ajaxPost('frontend_bridge', 'execute', data, 0, {
error: this.useBootcampReward,
});
};
}
// var r = Math.round(e.building.points * Math.pow(e.building.points_factor, e.next_level)) - Math.round(e.building.points * Math.pow(e.building.points_factor, e.level))
class AutoBuild extends ModernUtil {
constructor(c, s) {
super(c, s);
/* Load settings, the polis in the settings are the active */
this.towns_buildings = this.storage.load('buildings', {});
/* Check if shift is pressed */
this.shiftHeld = false;
/* Active always, check if the towns are in the active list */
this.enable = setInterval(this.main, 20000);
/* Add listener that change the Senate look */
uw.$.Observer(GameEvents.window.open).subscribe("modernSenate", this.updateSenate);
}
settings = () => {
/* Apply event to shift */
requestAnimationFrame(() => {
uw.$('#buildings_lvl_buttons').on('mousedown', e => {
this.shiftHeld = e.shiftKey;
});
this.setPolisInSettings(uw.ITowns.getCurrentTown().id);
this.updateTitle();
uw.$.Observer(uw.GameEvents.town.town_switch).subscribe(() => {
this.setPolisInSettings(uw.ITowns.getCurrentTown().id);
this.updateTitle();
});
});
return `
Auto Build
(click to toggle)
`;
};
/* Update the senate view */
updateSenate = (event, handler) => {
if (handler.context !== "building_senate") return;
// Edit the width of the window to fit the new element
handler.wnd.setWidth(850)
// Compute the id of the window
const id = `gpwnd_${handler.wnd.getID()}`
// Loop until the element is found
const updateView = () => {
const interval = setInterval(() => {
const $window = $('#' + id);
const $mainTasks = $window.find('#main_tasks');
if (!$mainTasks.length) return;
$mainTasks.hide();
let $newElement = $('').append(this.settings());
$newElement.css({
position: $mainTasks.css('position'),
left: $mainTasks.css('left') - 20,
top: $mainTasks.css('top'),
});
$mainTasks.after($newElement);
// Center the techTree
const $techTree = $window.find('#techtree');
$techTree.css({
position: 'relative',
left: "40px",
});
// Edit the width of the
$window.css({
overflowY: 'visible',
});
clearInterval(interval);
}, 10);
// If the element is not found, stop the interval
setTimeout(() => {
clearInterval(interval);
}, 100);
}
// subscribe to set content event
const oldSetContent = handler.wnd.setContent2;
handler.wnd.setContent2 = (...params) => {
updateView();
oldSetContent(...params);
}
}
/* Given the town id, set the polis in the settings menu */
setPolisInSettings = town_id => {
let town = uw.ITowns.towns[town_id];
/* If the town is in the active list set*/
let town_buildings = this.towns_buildings?.[town_id] ?? { ...town.buildings()?.attributes } ?? {};
let buildings = { ...town.buildings().attributes };
const getBuildingHtml = (building, bg) => {
let color = 'lime';
if (buildings[building] > town_buildings[building]) color = 'red';
else if (buildings[building] < town_buildings[building]) color = 'orange';
return `
${town_buildings[building]}
`;
};
/* If the town is in a group, the the groups */
const groups =
`(${Object.values(uw.ITowns.getTownGroups())
.filter(group => group.id > 0 && group.id !== -1 && group.towns[town_id])
.map(group => group.name)
.join(', ')})` || '';
uw.$('[id="buildings_lvl_buttons"]').html(`
${getBuildingHtml('main', [450, 0])}
${getBuildingHtml('storage', [250, 50])}
${getBuildingHtml('farm', [150, 0])}
${getBuildingHtml('academy', [0, 0])}
${getBuildingHtml('temple', [300, 50])}
${getBuildingHtml('barracks', [50, 0])}
${getBuildingHtml('docks', [100, 0])}
${getBuildingHtml('market', [0, 50])}
${getBuildingHtml('hide', [200, 0])}
${getBuildingHtml('lumber', [400, 0])}
${getBuildingHtml('stoner', [200, 50])}
${getBuildingHtml('ironer', [250, 0])}
${getBuildingHtml('wall', [50, 100])}
`);
};
/* call with town_id, building type and level to be added */
editBuildingLevel = (town_id, name, d) => {
const town = uw.ITowns.getTown(town_id);
const { max_level, min_level } = uw.GameData.buildings[name];
const town_buildings = this.towns_buildings?.[town_id] ?? { ...town.buildings()?.attributes } ?? {};
const townBuildings = town.buildings().attributes;
const current_lvl = parseInt(uw.$(`#build_lvl_${name}`).text());
if (d) {
/* if shift is pressed, add or remove 10 */
d = this.shiftHeld ? d * 10 : d;
/* Check if bottom or top overflow */
town_buildings[name] = Math.min(Math.max(current_lvl + d, min_level), max_level);
} else {
if (town_buildings[name] == current_lvl) town_buildings[name] = Math.min(Math.max(50, min_level), max_level);
else town_buildings[name] = townBuildings[name];
}
const color = town_buildings[name] > townBuildings[name] ? 'orange' : town_buildings[name] < townBuildings[name] ? 'red' : 'lime';
uw.$(`#build_settings_${town_id} #build_lvl_${name}`).css('color', color).text(town_buildings[name]);
if (town_id.toString() in this.towns_buildings) {
this.towns_buildings[town_id] = town_buildings;
this.storage.save('buildings', this.towns_buildings);
}
};
isActive = town_id => {
let town = uw.ITowns.towns[town_id];
return !this.towns_buildings?.[town.id];
};
updateTitle = () => {
let town = uw.ITowns.getCurrentTown();
if (town.id.toString() in this.towns_buildings) {
uw.$('[id="auto_build_title"]').css('filter', 'brightness(100%) saturate(186%) hue-rotate(241deg)');
} else {
uw.$('[id="auto_build_title"]').css('filter', '');
}
};
/* Call to toggle on and off (trigger the current town) */
toggle = () => {
let town = uw.ITowns.getCurrentTown();
if (!(town.id.toString() in this.towns_buildings)) {
this.console.log(`${town.name}: Auto Build On`);
this.towns_buildings[town.id] = {};
let buildins = ['main', 'storage', 'farm', 'academy', 'temple', 'barracks', 'docks', 'market', 'hide', 'lumber', 'stoner', 'ironer', 'wall'];
buildins.forEach(e => {
let lvl = parseInt(uw.$(`#build_lvl_${e}`).text());
this.towns_buildings[town.id][e] = lvl;
});
this.storage.save('buildings', this.towns_buildings);
} else {
delete this.towns_buildings[town.id];
this.storage.save('buildings', this.towns_buildings);
this.console.log(`${town.name}: Auto Build Off`);
}
this.updateTitle();
};
/* Main loop for building */
main = async () => {
for (let town_id of Object.keys(this.towns_buildings)) {
/* If the town don't exists in list, remove it to prevent errors */
if (!uw.ITowns.towns[town_id]) {
delete this.towns_buildings[town_id];
this.storage.save('buildings', this.towns_buildings);
continue;
}
if (this.isFullQueue(town_id)) continue;
/* If town is done, remove from the list */
if (this.isDone(town_id)) {
delete this.towns_buildings[town_id];
this.storage.save('buildings', this.towns_buildings);
this.updateTitle();
const town = uw.ITowns.getTown(town_id);
this.console.log(`${town.name}: Auto Build Done`);
continue;
}
await this.getNextBuild(town_id);
}
};
/* Make post request to the server to buildup the building */
postBuild = async (type, town_id) => {
const town = uw.ITowns.getTown(town_id);
let { wood, stone, iron } = town.resources();
let { resources_for, population_for } = uw.MM.getModels().BuildingBuildData[town_id].attributes.building_data[type];
if (town.getAvailablePopulation() < population_for) return;
const m = 20;
if (wood < resources_for.wood + m || stone < resources_for.stone + m || iron < resources_for.iron + m) return;
let data = {
model_url: 'BuildingOrder',
action_name: 'buildUp',
arguments: { building_id: type },
town_id: town_id,
};
uw.gpAjax.ajaxPost('frontend_bridge', 'execute', data);
this.console.log(`${town.getName()}: buildUp ${type}`);
await this.sleep(500);
};
/* Make post request to tear building down */
postTearDown = async (type, town_id) => {
let data = {
model_url: 'BuildingOrder',
action_name: 'tearDown',
arguments: { building_id: type },
town_id: town_id,
};
uw.gpAjax.ajaxPost('frontend_bridge', 'execute', data);
await this.sleep(500);
};
/* return true if the quee is full */
isFullQueue = town_id => {
const town = uw.ITowns.getTown(town_id);
if (uw.GameDataPremium.isAdvisorActivated('curator') && town.buildingOrders().length >= 7) {
return true;
}
if (!uw.GameDataPremium.isAdvisorActivated('curator') && town.buildingOrders().length >= 2) {
return true;
}
return false;
};
/* return true if building match polis */
isDone = town_id => {
const town = uw.ITowns.getTown(town_id);
let buildings = town.getBuildings().attributes;
for (let build of Object.keys(this.towns_buildings[town_id])) {
if (this.towns_buildings[town_id][build] != buildings[build]) {
return false;
}
}
return true;
};
/* */
getNextBuild = async town_id => {
let town = ITowns.towns[town_id];
/* livello attuale */
let buildings = { ...town.getBuildings().attributes };
/* Add the the list the current building progress */
for (let order of town.buildingOrders().models) {
if (order.attributes.tear_down) {
buildings[order.attributes.building_type] -= 1;
} else {
buildings[order.attributes.building_type] += 1;
}
}
/* livello in cui deve arrivare */
let target = this.towns_buildings[town_id];
/* Check if the building is duable, if yes build it and return true, else false */
const check = async (build, level) => {
/* if the given is an array, randomically try all of the array */
if (Array.isArray(build)) {
build.sort(() => Math.random() - 0.5);
for (let el of build) {
if (await check(el, level)) return true;
}
return false;
}
if (target[build] <= buildings[build]) return false;
else if (buildings[build] < level) {
await this.postBuild(build, town_id);
return true;
}
return false;
};
const tearCheck = async build => {
if (Array.isArray(build)) {
build.sort(() => Math.random() - 0.5);
for (let el of build) {
if (await tearCheck(el)) return true;
}
return false;
}
if (target[build] < buildings[build]) {
await this.postTearDown(build, town_id);
return true;
}
return false;
};
/* IF the docks is not build yet, then follow the tutorial */
if (buildings.docks < 1) {
if (await check('lumber', 3)) return;
if (await check('stoner', 3)) return;
if (await check('farm', 4)) return;
if (await check('ironer', 3)) return;
if (await check('storage', 4)) return;
if (await check('temple', 3)) return;
if (await check('main', 5)) return;
if (await check('barracks', 5)) return;
if (await check('storage', 5)) return;
if (await check('stoner', 6)) return;
if (await check('lumber', 6)) return;
if (await check('ironer', 6)) return;
if (await check('main', 8)) return;
if (await check('farm', 8)) return;
if (await check('market', 6)) return;
if (await check('storage', 8)) return;
if (await check('academy', 7)) return;
if (await check('temple', 5)) return;
if (await check('farm', 12)) return;
if (await check('main', 15)) return;
if (await check('storage', 12)) return;
if (await check('main', 25)) return;
if (await check('hide', 10)) return;
}
/* Resouces */
// WALLS!
if (await check('farm', 15)) return;
if (await check(['storage', 'main'], 25)) return;
if (await check('market', 4)) return;
if (await check('hide', 10)) return;
if (await check(['lumber', 'stoner', 'ironer'], 15)) return;
if (await check(['academy', 'farm'], 36)) return;
if (await check(['docks', 'barracks'], 10)) return;
if (await check('wall', 25)) return;
// terme
if (await check(['docks', 'barracks', 'market'], 20)) return;
if (await check('farm', 45)) return;
if (await check(['docks', 'barracks', 'market'], 30)) return;
if (await check(['lumber', 'stoner', 'ironer'], 40)) return;
if (await check('temple', 30)) return;
if (await check('storage', 35)) return;
/* Demolish */
let lista = ['lumber', 'stoner', 'ironer', 'docks', 'barracks', 'market', 'temple', 'academy', 'farm', 'hide', 'storage', 'wall'];
if (await tearCheck(lista)) return;
if (await tearCheck('main')) return;
};
}
class AutoFarm extends ModernUtil {
constructor(c, s) {
super(c, s);
// Load the settings
this.timing = this.storage.load('af_level', 300000);
this.percent = this.storage.load('af_percent', 1);
this.active = this.storage.load('af_active', false);
this.gui = this.storage.load('af_gui', false);
// Create the elements for the new menu
const { $activity, $count } = this.createActivity("url(https://gpit.innogamescdn.com/images/game/premium_features/feature_icons_2.08.png) no-repeat 0 -240px");
this.$activity = $activity
this.$count = $count
this.$activity.on('click', this.toggle)
this.createDropdown();
this.updateButtons();
this.timer = 0;
this.lastTime = Date.now();
if (this.active) this.active = setInterval(this.main, 1000);
}
/* Create the dropdown menu */
createDropdown = () => {
this.$content = $("")
this.$title = $("Modern Farm
").css({ "text-align": "center", "margin": "2px", "font-weight": "bold", "font-size": "16px" })
this.$content.append(this.$title)
this.$duration = $("Duration:
").css({ "text-align": "left", "margin": "2px", "font-weight": "bold" })
this.$button5 = this.createButton("modern_farm_5", "5 min", this.toggleDuration)
this.$button10 = this.createButton("modern_farm_10", "10 min", this.toggleDuration)
this.$button20 = this.createButton("modern_farm_20", "20 min", this.toggleDuration)
this.$content.append(this.$duration, this.$button5, this.$button10, this.$button20)
this.$storage = $("Storage:
").css({ "text-align": "left", "margin": "2px", "font-weight": "bold" })
this.$button80 = this.createButton("modern_farm_80", "80%", this.toggleStorage).css({ "width": "70px" })
this.$button90 = this.createButton("modern_farm_90", "90%", this.toggleStorage).css({ "width": "80px" })
this.$button100 = this.createButton("modern_farm_100", "100%", this.toggleStorage).css({ "width": "80px" })
this.$content.append(this.$storage, this.$button80, this.$button90, this.$button100)
this.$gui = $("Gui:
").css({ "text-align": "left", "margin": "2px", "font-weight": "bold" })
this.$guiOn = this.createButton("modern_farm_gui_on", "ON", this.toggleGui)
this.$guiOff = this.createButton("modern_farm_gui_off", "OFF", this.toggleGui)
this.$content.append(this.$gui, this.$guiOn, this.$guiOff)
this.$popup = this.createPopup(423, 250, 170, this.$content)
this.dropdown_active = false
// Open and close the dropdown with the mouse
const close = () => {
if (!this.dropdown_active) this.$popup.hide()
this.dropdown_active = false
}
const open = () => {
if (this.dropdown_active) this.$popup.show()
}
this.$activity.on({
mouseenter: () => {
this.dropdown_active = true
setTimeout(open, 1000)
},
mouseleave: () => {
this.dropdown_active = false
setTimeout(close, 50)
}
})
this.$popup.on({
mouseenter: () => {
this.dropdown_active = true
},
mouseleave: () => {
this.dropdown_active = false
setTimeout(close, 50)
}
})
}
/* Update the buttons */
updateButtons = () => {
this.$button5.addClass('disabled')
this.$button10.addClass('disabled')
this.$button20.addClass('disabled')
this.$button80.addClass('disabled')
this.$button90.addClass('disabled')
this.$button100.addClass('disabled')
if (this.timing == 300000) this.$button5.removeClass('disabled')
if (this.timing == 600000) this.$button10.removeClass('disabled')
if (this.timing == 1200000) this.$button20.removeClass('disabled')
if (this.percent == 0.8) this.$button80.removeClass('disabled')
if (this.percent == 0.9) this.$button90.removeClass('disabled')
if (this.percent == 1) this.$button100.removeClass('disabled')
if (!this.active) {
this.$count.css('color', "red")
this.$count.text("")
}
this.$guiOn.addClass('disabled')
this.$guiOff.addClass('disabled')
if (this.gui) this.$guiOn.removeClass('disabled')
else this.$guiOff.removeClass('disabled')
}
toggleDuration = (event) => {
const { id } = event.currentTarget
// Update the timer
if (id == "modern_farm_5") this.timing = 300_000
if (id == "modern_farm_10") this.timing = 600_000
if (id == "modern_farm_20") this.timing = 1_200_000
// Save the settings and update the buttons
this.storage.save('af_level', this.timing);
this.updateButtons()
}
toggleStorage = (event) => {
const { id } = event.currentTarget
// Update the percent
if (id == "modern_farm_80") this.percent = 0.8
if (id == "modern_farm_90") this.percent = 0.9
if (id == "modern_farm_100") this.percent = 1
// Save the settings and update the buttons
this.storage.save('af_percent', this.percent);
this.updateButtons()
}
toggleGui = (event) => {
const { id } = event.currentTarget
// Update the gui
if (id == "modern_farm_gui_on") this.gui = true
if (id == "modern_farm_gui_off") this.gui = false
// Save the settings and update the buttons
this.storage.save('af_gui', this.gui);
this.updateButtons()
}
/* generate the list containing 1 polis per island */
generateList = () => {
const islands_list = new Set();
const polis_list = [];
let minResource = 0;
let min_percent = 0;
const { models: towns } = uw.MM.getOnlyCollectionByName('Town');
for (const town of towns) {
const { on_small_island, island_id, id } = town.attributes;
if (on_small_island || islands_list.has(island_id)) continue;
// Check the min percent for each town
const { wood, stone, iron, storage } = uw.ITowns.getTown(id).resources();
minResource = Math.min(wood, stone, iron);
min_percent = minResource / storage;
islands_list.add(island_id);
polis_list.push(town.id);
// if (min_percent < this.percent) continue;
}
return polis_list;
};
toggle = () => {
if (this.active) {
clearInterval(this.active);
this.active = null;
this.updateButtons();
}
else {
this.updateTimer();
this.active = setInterval(this.main, 1000);
}
// Save the settings
this.storage.save('af_active', !!this.active);
};
/* return the time before the next collection */
getNextCollection = () => {
const { models } = uw.MM.getCollections().FarmTownPlayerRelation[0];
const lootCounts = {};
for (const model of models) {
const { lootable_at } = model.attributes;
lootCounts[lootable_at] = (lootCounts[lootable_at] || 0) + 1;
}
let maxLootableTime = 0;
let maxValue = 0;
for (const lootableTime in lootCounts) {
const value = lootCounts[lootableTime];
if (value < maxValue) continue;
maxLootableTime = lootableTime;
maxValue = value;
}
const seconds = maxLootableTime - Math.floor(Date.now() / 1000);
return seconds > 0 ? seconds * 1000 : 0;
};
/* Call to update the timer */
updateTimer = () => {
const currentTime = Date.now();
this.timer -= currentTime - this.lastTime;
this.lastTime = currentTime;
// Update the count
const isCaptainActive = uw.GameDataPremium.isAdvisorActivated('captain');
this.$count.text(Math.round(Math.max(this.timer, 0) / 1000));
this.$count.css('color', isCaptainActive ? "#1aff1a" : "yellow");
};
claim = async () => {
const isCaptainActive = uw.GameDataPremium.isAdvisorActivated('captain');
const polis_list = this.generateList();
// If the captain is active, claim all the resources at once and fake the opening
if (isCaptainActive && !this.gui) {
await this.fakeOpening();
await this.sleep(Math.random() * 2000 + 1000); // random between 1 second and 3
await this.fakeSelectAll();
await this.sleep(Math.random() * 2000 + 1000);
if (this.timing <= 600_000) await this.claimMultiple(300, 600);
if (this.timing > 600_000) await this.claimMultiple(1200, 2400);
await this.fakeUpdate();
setTimeout(() => uw.WMap.removeFarmTownLootCooldownIconAndRefreshLootTimers(), 2000);
return;
}
if (isCaptainActive && this.gui) {
await this.fakeGuiUpdate();
return;
}
// If the captain is not active, claim the resources one by one, but limit the number of claims
let max = 60;
const { models: player_relation_models } = uw.MM.getOnlyCollectionByName('FarmTownPlayerRelation');
const { models: farm_town_models } = uw.MM.getOnlyCollectionByName('FarmTown');
const now = Math.floor(Date.now() / 1000);
for (let town_id of polis_list) {
let town = uw.ITowns.towns[town_id];
let x = town.getIslandCoordinateX();
let y = town.getIslandCoordinateY();
for (let farm_town of farm_town_models) {
if (farm_town.attributes.island_x != x) continue;
if (farm_town.attributes.island_y != y) continue;
for (let relation of player_relation_models) {
if (farm_town.attributes.id != relation.attributes.farm_town_id) continue;
if (relation.attributes.relation_status !== 1) continue;
if (relation.attributes.lootable_at !== null && now < relation.attributes.lootable_at) continue;
this.claimSingle(town_id, relation.attributes.farm_town_id, relation.id, Math.ceil(this.timing / 600_000));
await this.sleep(500);
if (!max) return;
else max -= 1;
}
}
}
setTimeout(() => uw.WMap.removeFarmTownLootCooldownIconAndRefreshLootTimers(), 2000);
};
/* Return the total resources of the polis in the list */
getTotalResources = () => {
const polis_list = this.generateList();
let total = {
wood: 0,
stone: 0,
iron: 0,
storage: 0,
};
for (let town_id of polis_list) {
const town = uw.ITowns.getTown(town_id);
const { wood, stone, iron, storage } = town.resources();
total.wood += wood;
total.stone += stone;
total.iron += iron;
total.storage += storage;
}
return total;
};
main = async () => {
// Check that the timer is not too high
const next_collection = this.getNextCollection();
if (next_collection && (this.timer > next_collection + 60 * 1_000 || this.timer < next_collection)) {
this.timer = next_collection + Math.floor(Math.random() * 20_000) + 10_000;
}
// Claim resources when timer has passed
if (this.timer < 1) {
// Generate the list of polis and claim resources
this.polis_list = this.generateList();
// Claim the resources, stop the interval and restart it
clearInterval(this.active);
this.active = null;
await this.claim();
this.active = setInterval(this.main, 1000);
// Set the new timer
const rand = Math.floor(Math.random() * 20_000) + 10_000;
this.timer = this.timing + rand;
if (this.timer < next_collection) this.timer = next_collection + rand;
}
// update the timer
this.updateTimer();
};
/* Claim resources from a single polis */
claimSingle = (town_id, farm_town_id, relation_id, option = 1) => {
const data = {
model_url: `FarmTownPlayerRelation/${relation_id}`,
action_name: 'claim',
arguments: {
farm_town_id: farm_town_id,
type: 'resources',
option: option,
},
town_id: town_id,
};
uw.gpAjax.ajaxPost('frontend_bridge', 'execute', data);
};
/* Claim resources from multiple polis */
claimMultiple = (base = 300, boost = 600) =>
new Promise((myResolve, myReject) => {
const polis_list = this.generateList();
let data = {
towns: polis_list,
time_option_base: base,
time_option_booty: boost,
claim_factor: 'normal',
};
uw.gpAjax.ajaxPost('farm_town_overviews', 'claim_loads_multiple', data, false, () => myResolve());
});
/* Pretend that the window it's opening */
fakeOpening = () =>
new Promise((myResolve, myReject) => {
uw.gpAjax.ajaxGet('farm_town_overviews', 'index', {}, false, async () => {
await this.sleep(10);
await this.fakeUpdate();
myResolve();
});
});
/* Fake the user selecting the list */
fakeSelectAll = () =>
new Promise((myResolve, myReject) => {
const data = {
town_ids: this.polislist,
};
uw.gpAjax.ajaxGet('farm_town_overviews', 'get_farm_towns_from_multiple_towns', data, false, () => myResolve());
});
/* Fake the window update*/
fakeUpdate = () =>
new Promise((myResolve, myReject) => {
const town = uw.ITowns.getCurrentTown();
const { attributes: booty } = town.getResearches();
const { attributes: trade_office } = town.getBuildings();
const data = {
island_x: town.getIslandCoordinateX(),
island_y: town.getIslandCoordinateY(),
current_town_id: town.id,
booty_researched: booty ? 1 : 0,
diplomacy_researched: '',
trade_office: trade_office ? 1 : 0,
};
uw.gpAjax.ajaxGet('farm_town_overviews', 'get_farm_towns_for_town', data, false, () => myResolve());
});
/* Fake the gui update */
fakeGuiUpdate = () =>
new Promise(async (myResolve, myReject) => {
// Open the farm town overview
$(".toolbar_button.premium .icon").trigger('mouseenter')
await this.sleep(1019.39, 127.54)
// Click on the farm town overview
$(".farm_town_overview a").trigger('click')
await this.sleep(1156.65, 165.62)
// Select all the polis
$(".checkbox.select_all").trigger("click")
await this.sleep(1036.20, 135.69)
// Claim the resources
$("#fto_claim_button").trigger("click")
await this.sleep(1036.20, 135.69)
// Confirm the claim if needed
const el = $(".confirmation .btn_confirm.button_new")
if (el.length) {
el.trigger("click")
await this.sleep(1036.20, 135.69)
}
// Close the window
$(".icon_right.icon_type_speed.ui-dialog-titlebar-close").trigger("click")
myResolve();
});
}
class AutoGratis extends ModernUtil {
constructor(c, s) {
super(c, s);
if (this.storage.load('enable_autogratis', false)) this.toggle();
}
settings = () => {
return `
Auto Gratis
(click to toggle)
Trigger to automatically press the
Gratis
button (try every 4 seconds)
`;
};
/* Call to trigger the Auto Gratis */
toggle = () => {
if (!this.autogratis) {
uw.$('#auto_gratis_title').css(
'filter',
'brightness(100%) saturate(186%) hue-rotate(241deg)',
);
this.autogratis = setInterval(this.main, 4000);
} else {
uw.$('#auto_gratis_title').css('filter', '');
clearInterval(this.autogratis);
this.autogratis = null;
}
this.storage.save('enable_autogratis', !!this.autogratis);
};
/* Main loop for the autogratis bot */
main = () => {
const el = uw.$('.type_building_queue.type_free').not('#dummy_free');
if (el.length) el.click();
const town = uw.ITowns.getCurrentTown();
for (let model of town.buildingOrders().models) {
if (model.attributes.building_time < 300) {
this.callGratis(town.id, model.id)
return;
}
}
};
/* Post request to call the gratis */
callGratis = (town_id, order_id) => {
const data = {
"model_url": `BuildingOrder/${order_id}`,
"action_name": "buyInstant",
"arguments": {
"order_id": order_id
},
"town_id": town_id
}
uw.gpAjax.ajaxPost('frontend_bridge', 'execute', data);
}
}
class AutoHide extends ModernUtil {
constructor(c, s) {
super(c, s);
this.activePolis = this.storage.load('autohide_active', 0);
setInterval(this.main, 5000)
const addButton = () => {
let box = $('.order_count');
if (box.length) {
let butt = $('', {
class: 'button_new',
id: 'autoCaveButton',
style: 'float: right; margin: 0px; left: 169px; position: absolute; top: 56px; width: 66px',
html: ' Auto '
});
box.prepend(butt);
this.updateSettings(uw.ITowns.getCurrentTown().id);
} else {
setTimeout(addButton, 100);
}
};
uw.$.Observer(uw.GameEvents.window.open).subscribe((e, i) => {
if (!i.attributes) return
if (i.attributes.window_type != "hide") return
setTimeout(addButton, 100);
})
uw.$.Observer(uw.GameEvents.town.town_switch).subscribe(() => {
this.updateSettings(uw.ITowns.getCurrentTown().id);
let cave = document.getElementsByClassName(
'js-window-main-container classic_window hide',
)[0];
if (!cave) return;
setTimeout(addButton, 1);
});
}
settings = () => {
requestAnimationFrame(() => {
this.updateSettings(uw.ITowns.getCurrentTown().id);
})
return `
Auto Hide
(click to toggle)
Check every 5 seconds, if there is more then 5000 iron store it in the hide
`;
};
toggle = (town_id) => {
let town = town_id ? uw.ITowns.towns[town_id] : uw.ITowns.getCurrentTown();
let hide = town.buildings().attributes.hide
if (this.activePolis == town.id) {
this.activePolis = 0
} else {
if (hide == 10) this.activePolis = town.id;
else uw.HumanMessage.error("Hide must be at level 10");
}
this.storage.save("autohide_active", this.activePolis)
this.updateSettings(town.id)
}
updateSettings = (town_id) => {
if (town_id == this.activePolis) {
$('#auto_cave_title').css({
'filter': 'brightness(100%) saturate(186%) hue-rotate(241deg)'
});
$('#autoCaveButton').css({
'filter': ' brightness(100%) sepia(100%) hue-rotate(90deg) saturate(1500%) contrast(0.8)'
});
} else {
$('#auto_cave_title, #autoCaveButton').css({
'filter': ''
});
}
}
main = () => {
if (this.activePolis == 0) return;
const town = uw.ITowns.towns[this.activePolis];
const { iron } = town.resources()
if (iron > 5000) {
this.storeIron(this.activePolis, iron)
}
}
storeIron = (town_id, count) => {
const data = {
"model_url": "BuildingHide",
"action_name": "storeIron",
"arguments": {
"iron_to_store": count
},
"town_id": town_id,
}
uw.gpAjax.ajaxPost('frontend_bridge', 'execute', data);
}
}
class AutoParty extends ModernUtil {
constructor(c, s) {
super(c, s);
this.active_types = this.storage.load('ap_types', { festival: false, procession: false });
this.single = this.storage.load('ap_single', true);
if (this.storage.load('ap_enable', false)) {
this.enable = setInterval(this.main, 30000);
}
}
// ${this.getButtonHtml('autoparty_lvl_1', 'Olympic', this.setRuralLevel, 1)}
settings = () => {
requestAnimationFrame(() => {
this.triggerType('festival', false);
this.triggerType('procession', false);
this.triggerSingle(this.single);
});
return `
${this.getTitleHtml('auto_party_title', 'Auto Party', this.toggle, '', this.enable)}
${this.getButtonHtml('autoparty_festival', 'Party', this.triggerType, 'festival')}
${this.getButtonHtml('autoparty_procession', 'Parade', this.triggerType, 'procession')}
${this.getButtonHtml('autoparty_single', 'Single', this.triggerSingle, 0)}
${this.getButtonHtml('autoparty_multiple', 'All', this.triggerSingle, 1)}
`;
};
triggerType = (type, swap = true) => {
if (swap) {
this.active_types[type] = !this.active_types[type];
this.storage.save('ap_types', this.active_types);
}
if (!this.active_types[type]) uw.$(`#autoparty_${type}`).addClass('disabled');
else uw.$(`#autoparty_${type}`).removeClass('disabled');
};
triggerSingle = type => {
type = !!type;
if (type) {
uw.$(`#autoparty_single`).addClass('disabled');
uw.$(`#autoparty_multiple`).removeClass('disabled');
} else {
uw.$(`#autoparty_multiple`).addClass('disabled');
uw.$(`#autoparty_single`).removeClass('disabled');
}
if (this.single != type) {
this.single = type;
this.storage.save('ap_single', this.single);
}
};
/* Call to toggle on/off */
toggle = () => {
if (!this.enable) {
uw.$('#auto_party_title').css('filter', 'brightness(100%) saturate(186%) hue-rotate(241deg)');
this.enable = setInterval(this.main, 30000);
} else {
uw.$('#auto_party_title').css('filter', '');
clearInterval(this.enable);
this.enable = null;
}
this.storage.save('ap_enable', !!this.enable);
};
/* Return list of town with active celebration */
getCelebrationsList = type => {
const celebrationModels = uw.MM.getModels().Celebration;
if (typeof celebrationModels === 'undefined') return [];
const triumphs = Object.values(celebrationModels)
.filter(celebration => celebration.attributes.celebration_type === type)
.map(triumph => triumph.attributes.town_id);
return triumphs;
};
checkParty = async () => {
let max = 10;
let party = this.getCelebrationsList('party');
if (this.single) {
for (let town_id in uw.ITowns.towns) {
if (party.includes(parseInt(town_id))) continue;
let town = uw.ITowns.towns[town_id];
if (town.getBuildings().attributes.academy < 30) continue;
let { wood, stone, iron } = town.resources();
if (wood < 15000 || stone < 18000 || iron < 15000) continue;
this.makeCelebration('party', town_id);
await this.sleep(750);
max -= 1;
/* Prevent that the promise it's to long */
if (max <= 0) return;
}
} else {
if (party.length > 1) return;
this.makeCelebration('party');
}
};
checkTriumph = async () => {
let max = 10;
let killpoints = uw.MM.getModelByNameAndPlayerId('PlayerKillpoints').attributes;
let available = killpoints.att + killpoints.def - killpoints.used;
if (available < 300) return;
let triumph = this.getCelebrationsList('triumph');
if (!this.single) {
// single and multiple are swapped...
for (let town_id in uw.ITowns.towns) {
if (triumph.includes(parseInt(town_id))) continue;
this.makeCelebration('triumph', town_id);
await this.sleep(500);
available -= 300;
if (available < 300) return;
max -= 1;
/* Prevent that the promise it's to long */
if (max <= 0) return;
}
} else {
if (triumph.length > 1) return;
this.makeCelebration('triumph');
}
};
main = async () => {
if (this.active_types['procession']) await this.checkTriumph();
if (this.active_types['festival']) await this.checkParty();
};
makeCelebration = (type, town_id) => {
if (typeof town_id === 'undefined') {
let data = {
celebration_type: type,
};
uw.gpAjax.ajaxPost('town_overviews', 'start_all_celebrations', data);
} else {
let data = {
celebration_type: type,
town_id: town_id,
};
uw.gpAjax.ajaxPost('building_place', 'start_celebration', data);
}
};
}
class AutoRuralLevel extends ModernUtil {
constructor(c, s) {
super(c, s);
this.rural_level = this.storage.load('enable_autorural_level', 1);
if (this.storage.load('enable_autorural_level_active')) {
this.enable = setInterval(this.main, 20000);
}
}
settings = () => {
requestAnimationFrame(() => {
this.setRuralLevel(this.rural_level);
});
return `
${this.getTitleHtml('auto_rural_level', 'Auto Rural level', this.toggle, '', this.enable)}
`;
};
/* generate the list containing 1 polis per island */
generateList = () => {
let islands_list = [];
let polis_list = [];
let town_list = uw.MM.getOnlyCollectionByName('Town').models;
for (let town of town_list) {
if (town.attributes.on_small_island) continue;
let { island_id, id } = town.attributes;
if (!islands_list.includes(island_id)) {
islands_list.push(island_id);
polis_list.push(id);
}
}
return polis_list;
};
setRuralLevel = n => {
uw.$('#rural_lvl_buttons .button_new').addClass('disabled');
uw.$(`#rural_lvl_${n}`).removeClass('disabled');
if (this.rural_level != n) {
this.rural_level = n;
this.storage.save('enable_autorural_level', this.rural_level);
}
};
toggle = () => {
if (!this.enable) {
uw.$('#auto_rural_level').css('filter', 'brightness(100%) saturate(186%) hue-rotate(241deg)');
this.enable = setInterval(this.main, 20000);
} else {
uw.$('#auto_rural_level').css('filter', '');
clearInterval(this.enable);
this.enable = null;
}
this.storage.save('enable_autorural_level_active', !!this.enable);
};
main = async () => {
let player_relation_models = uw.MM.getOnlyCollectionByName('FarmTownPlayerRelation').models;
let farm_town_models = uw.MM.getOnlyCollectionByName('FarmTown').models;
let killpoints = uw.MM.getModelByNameAndPlayerId('PlayerKillpoints').attributes;
/* Get array with all locked rurals */
const locked = player_relation_models.filter(model => model.attributes.relation_status === 0);
/* Get killpoints */
let available = killpoints.att + killpoints.def - killpoints.used;
let unlocked = player_relation_models.length - locked.length;
/* If some rurals still have to be unlocked */
if (locked.length > 0) {
/* The first 5 rurals have discount */
const discounts = [2, 8, 10, 30, 50, 100];
if (unlocked < discounts.length && available < discounts[unlocked]) return;
else if (available < 100) return;
let towns = this.generateList();
for (let town_id of towns) {
let town = uw.ITowns.towns[town_id];
let x = town.getIslandCoordinateX(),
y = town.getIslandCoordinateY();
for (let farmtown of farm_town_models) {
if (farmtown.attributes.island_x != x || farmtown.attributes.island_y != y) continue;
for (let relation of locked) {
if (farmtown.attributes.id != relation.attributes.farm_town_id) continue;
this.unlockRural(town_id, relation.attributes.farm_town_id, relation.id);
this.console.log(`Island ${farmtown.attributes.island_xy}: unlocked ${farmtown.attributes.name}`);
return;
}
}
}
} else {
/* else check each level once at the time */
let towns = this.generateList();
let expansion = false;
const levelCosts = [1, 5, 25, 50, 100];
for (let level = 1; level < this.rural_level; level++) {
if (available < levelCosts[level - 1]) return;
for (let town_id of towns) {
let town = uw.ITowns.towns[town_id];
let x = town.getIslandCoordinateX();
let y = town.getIslandCoordinateY();
for (let farmtown of farm_town_models) {
if (farmtown.attributes.island_x != x) continue;
if (farmtown.attributes.island_y != y) continue;
for (let relation of player_relation_models) {
if (farmtown.attributes.id != relation.attributes.farm_town_id) {
continue;
}
if (relation.attributes.expansion_at) {
expansion = true;
continue;
}
if (relation.attributes.expansion_stage > level) continue;
this.upgradeRural(town_id, relation.attributes.farm_town_id, relation.attributes.id);
this.console.log(`Island ${farmtown.attributes.island_xy}: upgraded ${farmtown.attributes.name}`);
return;
}
}
}
}
if (expansion) return;
}
/* Auto turn off when the level is reached */
this.toggle();
};
/*
Post requests
*/
unlockRural = (town_id, farm_town_id, relation_id) => {
let data = {
model_url: `FarmTownPlayerRelation/${relation_id}`,
action_name: 'unlock',
arguments: {
farm_town_id: farm_town_id,
},
town_id: town_id,
};
uw.gpAjax.ajaxPost('frontend_bridge', 'execute', data);
};
upgradeRural = (town_id, farm_town_id, relation_id) => {
let data = {
model_url: `FarmTownPlayerRelation/${relation_id}`,
action_name: 'upgrade',
arguments: {
farm_town_id: farm_town_id,
},
town_id: town_id,
};
uw.gpAjax.ajaxPost('frontend_bridge', 'execute', data);
};
}
class AutoRuralTrade extends ModernUtil {
constructor(c, s) {
super(c, s);
this.ratio = this.storage.load('rt_ratio', 5);
}
settings = () => {
requestAnimationFrame(() => {
this.setMinRatioLevel(this.ratio);
});
return `
Auto Trade resouces
(click to stop)
${this.getButtonHtml('min_rural_ratio_1', '0.25', this.setMinRatioLevel, 1)}
${this.getButtonHtml('min_rural_ratio_2', '0.5', this.setMinRatioLevel, 2)}
${this.getButtonHtml('min_rural_ratio_3', '0.75', this.setMinRatioLevel, 3)}
${this.getButtonHtml('min_rural_ratio_4', '1.0', this.setMinRatioLevel, 4)}
${this.getButtonHtml('min_rural_ratio_5', '1.25', this.setMinRatioLevel, 5)}
`;
};
setMinRatioLevel = n => {
uw.$('#min_rural_ratio .button_new').addClass('disabled');
uw.$(`#min_rural_ratio_${n}`).removeClass('disabled');
if (this.ratio != n) {
this.ratio = n;
this.storage.save('rt_ratio', n);
}
};
/* Trade with all rurals*/
main = async resouce => {
if (resouce) {
/* Set button disabled */
[1, 2, 3, 4].forEach(i => {
uw.$(`#autotrade_lvl_${i}`).addClass('disabled').css('cursor', 'auto');
});
this.trade_resouce = resouce;
/* Set the current trade to polis at index 0 */
this.total_trade = Object.keys(uw.ITowns.towns).length;
this.done_trade = 0;
/* Set the interval */
this.auto_trade_resouces_loop = setInterval(this.mainTradeLoop, 1500);
} else {
/* Clear the interval */
clearInterval(this.auto_trade_resouces_loop);
/* Re-enable buttons and set progress to 0 */
uw.$('#res_progress_bar').css('width', 0);
[1, 2, 3, 4].forEach(i => {
uw.$(`#autotrade_lvl_${i}`).removeClass('disabled').css('cursor', 'pointer');
});
}
};
tradeWithRural = async polis_id => {
let town = uw.ITowns.towns[polis_id];
if (!town) return;
if (town.getAvailableTradeCapacity() < 3000) return;
//if (this.check_for_hide && town.getBuildings().attributes.hide < 10) return;
let farm_town_models = uw.MM.getOnlyCollectionByName('FarmTown').models;
let player_relation_models = uw.MM.getOnlyCollectionByName('FarmTownPlayerRelation').models;
/* Create list with all the farmtown in current island polis */
let x = town.getIslandCoordinateX(),
y = town.getIslandCoordinateY();
let resources = town.resources();
for (const farmtown of farm_town_models) {
if (farmtown.attributes.island_x != x || farmtown.attributes.island_y != y) continue;
if (farmtown.attributes.resource_offer != this.trade_resouce) continue;
if (resources[farmtown.attributes.resource_demand] < 3000) continue;
for (const relation of player_relation_models) {
if (farmtown.attributes.id != relation.attributes.farm_town_id) continue;
if (relation.attributes.current_trade_ratio < this.min_rural_ratio * 0.25) continue;
if (town.getAvailableTradeCapacity() < 3000) continue;
this.tradeRuralPost(relation.attributes.farm_town_id, relation.attributes.id, town.getAvailableTradeCapacity(), town.id);
await this.sleep(750);
}
}
};
mainTradeLoop = async () => {
/* If last polis, then trigger to stop */
if (this.done_trade >= this.total_trade) {
this.main();
return;
}
/* perform trade with current index */
let towns = Object.keys(uw.ITowns.towns);
await this.tradeWithRural(towns[this.done_trade]);
/* update progress bar */
uw.$('#res_progress_bar').css('width', `${(this.done_trade / this.total_trade) * 100}%`);
this.done_trade += 1;
};
tradeRuralPost = (farm_town_id, relation_id, count, town_id) => {
if (count < 100) return;
const data = {
model_url: `FarmTownPlayerRelation/${relation_id}`,
action_name: 'trade',
arguments: { farm_town_id: farm_town_id, amount: count > 3000 ? 3000 : count },
town_id: town_id,
};
uw.gpAjax.ajaxPost('frontend_bridge', 'execute', data);
};
}
class AutoTrade extends ModernUtil {
constructor(c, s) {
super(c, s);
}
settings = () => {
return `
${this.getTitleHtml('auto_trade', 'Auto Trade', '', '', this.enable_auto_farming)}
${this.getButtonHtml('farming_lvl_1', '5 min', this.setAutoFarmLevel, 1)}
${this.getButtonHtml('farming_lvl_2', '10 min', this.setAutoFarmLevel, 2)}
${this.getButtonHtml('farming_lvl_3', '20 min', this.setAutoFarmLevel, 3)}
${this.getButtonHtml('farming_lvl_4', '40 min', this.setAutoFarmLevel, 4)}
`;
};
}
function autoTradeBot() {
const uw = unsafeWindow;
const unit_counnt = {
bireme: 2.9,
slinger: 28,
};
this.tradeUntilComplete = async (target = 'active', troop = 'bireme') => {
console.log(troop);
let ammount;
if (target === 'active') target = uw.ITowns.getCurrentTown().id;
do {
console.log('Trade Loop');
ammount = await this.trade(target, troop);
await sleep(30000);
} while (ammount > 0);
console.log('Tradeing Done');
};
this.trade = async function (target = 'active', troop = 'bireme') {
if (target === 'active') target = uw.ITowns.getCurrentTown().id;
let ammount = await calculateAmmount(target, troop);
let current_ammount;
do {
current_ammount = ammount;
for (let town of Object.values(uw.ITowns.towns)) {
if (town.id == target) continue;
if (uw.stopBot) break;
if (ammount <= 0) break;
ammount = await sendBalance(town.id, target, troop, ammount);
}
} while (current_ammount > ammount);
return ammount;
};
/* return all the trades */
async function getAllTrades() {
return new Promise(function (myResolve, myReject) {
uw.gpAjax.ajaxGet('town_overviews', 'trade_overview', {}, !0, e => {
myResolve(e.movements);
});
});
}
/* Return the ammount of toops duable with the current resouces */
function getCount(targtet_id, troop) {
let target_polis = uw.ITowns.towns[targtet_id];
if (!target_polis) return {};
let resources = target_polis.resources();
let wood = resources.wood / uw.GameData.units[troop].resources.wood;
let stone = resources.stone / uw.GameData.units[troop].resources.stone;
let iron = resources.iron / uw.GameData.units[troop].resources.iron;
let min = Math.min(wood, stone, iron);
return min;
}
/* Return the id of the polis from the name */
function getTradeTarget(html) {
const element = document.createElement('div');
element.innerHTML = html;
let name = element.textContent;
for (let town of Object.values(uw.ITowns.towns)) {
if (town.name == name) return town.id;
}
}
/* Return ammount of troops duable for resouces in a trade */
function getCountFromTrade(trade, troop) {
let wood = trade.res.wood / uw.GameData.units[troop].resources.wood;
let stone = trade.res.stone / uw.GameData.units[troop].resources.stone;
let iron = trade.res.iron / uw.GameData.units[troop].resources.iron;
let min = Math.min(wood, stone, iron);
return min;
}
/* Return ammount of resouces to be send */
async function calculateAmmount(targtet_id, troop) {
let target_polis = uw.ITowns.towns[targtet_id];
if (!target_polis) return {};
let current_count = {};
let discount = uw.GeneralModifications.getUnitBuildResourcesModification(targtet_id, uw.GameData.units[troop]);
let todo = parseInt(target_polis.getAvailablePopulation() / uw.GameData.units[troop].population) * discount;
let in_polis = getCount(targtet_id, troop);
/* If the polis has all the resouces -> no resouces has to be sent */
todo -= in_polis;
if (todo < 0) return 0;
let trade = uw.MM.getCollections().Trade[0].models;
let trades = await getAllTrades();
for (let trade of trades) {
if (getTradeTarget(trade.to.link) != targtet_id) continue;
todo -= getCountFromTrade(trade, troop);
}
return todo;
}
function getCountWithTrade(targtet_id, troop) {
let target_polis = uw.ITowns.towns[targtet_id];
if (!target_polis) return {};
let resources = target_polis.resources();
let wood = resources.wood / uw.GameData.units[troop].resources.wood;
let stone = resources.stone / uw.GameData.units[troop].resources.stone;
let iron = resources.iron / uw.GameData.units[troop].resources.iron;
let min_resouces = Math.min(wood, stone, iron); // min ammount
let trade = target_polis.getAvailableTradeCapacity();
let max_trade = trade / (uw.GameData.units[troop].resources.wood + uw.GameData.units[troop].resources.stone + uw.GameData.units[troop].resources.iron); // max tradable
if (max_trade < min_resouces) return max_trade;
else return min_resouces;
}
/* Set await and add promise */
function sendTradeRequest(from_id, target_id, troop, count) {
let data = {
id: target_id,
wood: uw.GameData.units[troop].resources.wood * count,
stone: uw.GameData.units[troop].resources.stone * count,
iron: uw.GameData.units[troop].resources.iron * count,
town_id: from_id,
nl_init: true,
};
return new Promise(function (myResolve, myReject) {
uw.gpAjax.ajaxPost('town_info', 'trade', data, !0, () => {
setTimeout(() => myResolve(), 500);
});
});
}
/* Send resouces from polis to target, balanced for that troop, return updated count*/
async function sendBalance(polis_id, target_id, troop, count) {
let troops_ammount = unit_counnt[troop];
if (!troops_ammount) return 0;
if (polis_id == target_id) return count;
let sender_polis = uw.ITowns.towns[polis_id];
let duable = getCount(polis_id, troop);
if (duable < troops_ammount) return count;
if (sender_polis.getAvailableTradeCapacity() < 500) return count;
let duable_with_trade = getCountWithTrade(polis_id, troop);
if (duable_with_trade < troops_ammount) return count;
await sendTradeRequest(polis_id, target_id, troop, troops_ammount);
return count - troops_ammount < 0 ? 0 : count - troops_ammount;
}
function sleep(time) {
return new Promise(function (myResolve, myReject) {
setTimeout(() => myResolve(), time);
});
}
}
class AutoTrain extends ModernUtil {
POWER_LIST = ['call_of_the_ocean', 'spartan_training', 'fertility_improvement'];
GROUND_ORDER = ['catapult', 'sword', 'archer', 'hoplite', 'slinger', 'rider', 'chariot'];
NAVAL_ORDER = ['small_transporter', 'bireme', 'trireme', 'attack_ship', 'big_transporter', 'demolition_ship', 'colonize_ship'];
SHIFT_LEVELS = {
catapult: [5, 5],
sword: [200, 50],
archer: [200, 50],
hoplite: [200, 50],
slinger: [200, 50],
rider: [100, 25],
chariot: [100, 25],
small_transporter: [10, 5],
bireme: [50, 10],
trireme: [50, 10],
attack_ship: [50, 10],
big_transporter: [50, 10],
demolition_ship: [50, 10],
colonize_ship: [5, 1],
};
constructor(c, s) {
super(c, s);
this.spell = this.storage.load('at_spell', false);
this.percentual = this.storage.load('at_per', 1);
this.city_troops = this.storage.load('troops', {});
this.shiftHeld = false;
this.interval = setInterval(this.main, 10000);
}
settings = () => {
requestAnimationFrame(() => {
this.setPolisInSettings(uw.ITowns.getCurrentTown().id);
this.updatePolisInSettings(uw.ITowns.getCurrentTown().id);
this.handlePercentual(this.percentual);
this.handleSpell(this.spell);
uw.$.Observer(uw.GameEvents.town.town_switch).subscribe(() => {
this.setPolisInSettings(uw.ITowns.getCurrentTown().id);
this.updatePolisInSettings(uw.ITowns.getCurrentTown().id);
});
uw.$('#troops_lvl_buttons').on('mousedown', e => {
this.shiftHeld = e.shiftKey;
});
});
return `
Settings
${this.getButtonHtml('train_passive', 'Passive', this.handleSpell, 0)}
${this.getButtonHtml('train_spell', 'Spell', this.handleSpell, 1)}
${this.getButtonHtml('train_percentuals_1', '80%', this.handlePercentual, 1)}
${this.getButtonHtml('train_percentuals_2', '90%', this.handlePercentual, 2)}
${this.getButtonHtml('train_percentuals_3', '100%', this.handlePercentual, 3)}
Auto Train
(click to reset)
`;
};
handleSpell = e => {
e = !!e;
if (this.spell != e) {
this.spell = e;
this.storage.save('at_spell', e);
}
if (e) {
$('#train_passive').addClass('disabled');
$('#train_spell').removeClass('disabled');
} else {
$('#train_passive').removeClass('disabled');
$('#train_spell').addClass('disabled');
}
};
handlePercentual = n => {
let box = $('#train_percentuals');
let buttons = box.find('.button_new');
buttons.addClass('disabled');
$(`#train_percentuals_${n}`).removeClass('disabled');
if (this.percentual != n) {
this.percentual = n;
this.storage.save('at_per', n);
}
};
getTotalPopulation = town_id => {
const town = uw.ITowns.towns[town_id];
const data = GameData.units;
const { models: orders } = town.getUnitOrdersCollection();
let used = 0;
for (let order of orders) {
used += data[order.attributes.unit_type].population * (order.attributes.units_left / order.attributes.count) * order.attributes.count;
}
let units = town.units();
for (let unit of Object.keys(units)) {
used += data[unit].population * units[unit];
}
let outher = town.unitsOuter();
for (let out of Object.keys(outher)) {
used += data[out].population * outher[out];
}
return town.getAvailablePopulation() + used;
};
setPolisInSettings = town_id => {
let town = uw.ITowns.towns[town_id];
let researches = town.researches().attributes;
let buildings = town.buildings().attributes;
const isGray = troop => {
if (!this.REQUIREMENTS.hasOwnProperty(troop)) {
return true; // Troop type not recognized
}
const { research, building, level } = this.REQUIREMENTS[troop];
if (research && !researches[research]) return true;
if (building && buildings[building] < level) return true;
return false;
};
const getTroopHtml = (troop, bg) => {
let gray = isGray(troop, researches, buildings);
let color = 'red';
if (gray) {
return `
`;
}
return `
0
`;
};
uw.$('#troops_lvl_buttons').html(`
${getTroopHtml('sword', [400, 0])}
${getTroopHtml('archer', [50, 100])}
${getTroopHtml('hoplite', [300, 50])}
${getTroopHtml('slinger', [250, 350])}
${getTroopHtml('rider', [50, 350])}
${getTroopHtml('chariot', [200, 100])}
${getTroopHtml('catapult', [150, 150])}
${getTroopHtml('big_transporter', [0, 150])}
${getTroopHtml('small_transporter', [300, 350])}
${getTroopHtml('bireme', [50, 150])}
${getTroopHtml('demolition_ship', [250, 0])}
${getTroopHtml('attack_ship', [150, 100])}
${getTroopHtml('trireme', [400, 250])}
${getTroopHtml('colonize_ship', [50, 200])}
`);
};
editTroopCount = (town_id, troop, count) => {
/* restart the interval to prevent spam*/
clearInterval(this.interval);
this.interval = setInterval(this.main, 10000);
const { units } = GameData;
const { city_troops } = this;
// Add the town to the city_troops object if it doesn't already exist
if (!city_troops.hasOwnProperty(town_id)) city_troops[town_id] = {};
if (count) {
// Modify count based on whether the shift key is held down
const index = count > 0 ? 0 : 1;
count = this.shiftHeld ? count * this.SHIFT_LEVELS[troop][index] : count;
} else {
count = 10000;
}
// Check if the troop count can be increased without exceeding population capacity
const total_pop = this.getTotalPopulation(town_id);
const used_pop = this.countPopulation(this.city_troops[town_id]);
const unit_pop = units[troop].population;
if (total_pop - used_pop < unit_pop * count) count = parseInt((total_pop - used_pop) / unit_pop);
// Update the troop count for the specified town and troop type
if (troop in city_troops[town_id]) city_troops[town_id][troop] += count;
else city_troops[town_id][troop] = count;
/* Clenaup */
if (city_troops[town_id][troop] <= 0) delete city_troops[town_id][troop];
if (uw.$.isEmptyObject(city_troops[town_id])) delete this.city_troops[town_id];
this.updatePolisInSettings(town_id);
this.storage.save('troops', this.city_troops);
};
updatePolisInSettings = town_id => {
const { units } = GameData;
const cityTroops = this.city_troops[town_id];
Object.keys(units).forEach(troop => {
const guiCount = cityTroops?.[troop] ?? 0;
const selector = `#troops_settings_${town_id} #troop_lvl_${troop}`;
if (guiCount > 0) uw.$(selector).css('color', 'orange').text(guiCount);
else uw.$(selector).css('color', '').text('-');
});
const isTownActive = this.city_troops[town_id];
uw.$('#auto_train_title').css('filter', isTownActive ? 'brightness(100%) saturate(186%) hue-rotate(241deg)' : '');
};
trigger = () => {
const town = uw.ITowns.getCurrentTown();
const town_id = town.getId();
if (this.city_troops[town_id]) {
delete this.city_troops[town_id];
[...this.NAVAL_ORDER, ...this.GROUND_ORDER].forEach(troop => {
const selector = `#troops_settings_${town_id} #troop_lvl_${troop}`;
uw.$(selector).css('color', '').text('-');
});
uw.$('#auto_train_title').css('filter', '');
this.storage.save('troops', this.city_troops);
}
};
/* return the count of the order type (naval or ground) */
getUnitOrdersCount = (type, town_id) => {
const town = uw.ITowns.getTown(town_id);
return town.getUnitOrdersCollection().where({ kind: type }).length;
};
getNextInList = (unitType, town_id) => {
const troops = this.city_troops[town_id];
if (!troops) return null;
const unitOrder = unitType === 'naval' ? this.NAVAL_ORDER : this.GROUND_ORDER;
for (const unit of unitOrder) {
if (troops[unit] && this.getTroopCount(unit, town_id) !== 0) return unit;
}
return null;
};
getTroopCount = (troop, town_id) => {
const town = uw.ITowns.getTown(town_id);
if (!this.city_troops[town_id] || !this.city_troops[town_id][troop]) return 0;
let count = this.city_troops[town_id][troop];
for (let order of town.getUnitOrdersCollection().models) {
if (order.attributes.unit_type === troop) count -= order.attributes.count;
}
let townUnits = town.units();
if (townUnits.hasOwnProperty(troop)) count -= townUnits[troop];
let outerUnits = town.unitsOuter();
if (outerUnits.hasOwnProperty(troop)) count -= outerUnits[troop];
//TODO: in viaggio
if (count < 0) return 0;
/* Get the duable ammount with the current resouces of the polis */
let resources = town.resources();
let discount = uw.GeneralModifications.getUnitBuildResourcesModification(town_id, uw.GameData.units[troop]);
let { wood, stone, iron } = uw.GameData.units[troop].resources;
let w = resources.wood / Math.round(wood * discount);
let s = resources.stone / Math.round(stone * discount);
let i = resources.iron / Math.round(iron * discount);
let current = parseInt(Math.min(w, s, i));
/* Check for free population */
let duable_with_pop = parseInt(resources.population / uw.GameData.units[troop].population); // for each troop
/* Get the max duable */
let w_max = resources.storage / (wood * discount);
let s_max = resources.storage / (stone * discount);
let i_max = resources.storage / (iron * discount);
let max = parseInt(Math.min(w_max, s_max, i_max) * 0.85); // 0.8 it's the full percentual -> 80%
max = max > duable_with_pop ? duable_with_pop : max;
if (max > count) {
return count > current ? -1 : count;
} else {
if (current >= max && current < duable_with_pop) return current;
if (current >= max && current > duable_with_pop) return duable_with_pop;
return -1;
}
};
/* Check the given town, for ground or land */
checkPolis = (type, town_id) => {
let order_count = this.getUnitOrdersCount(type, town_id);
if (order_count > 6) return 0;
let count = 1;
while (count >= 0) {
let next = this.getNextInList(type, town_id);
if (!next) return 0;
count = this.getTroopCount(next, town_id);
if (count < 0) return 0;
if (count === 0) continue;
this.buildPost(town_id, next, count);
return true;
}
};
/* Return list of town that have power active */
getPowerActive = () => {
const { fragments } = MM.getFirstTownAgnosticCollectionByName('CastedPowers');
let towns_list = [];
for (let town_id in this.city_troops) {
const { models } = fragments[town_id];
for (let power of models) {
let { attributes } = power;
if (this.POWER_LIST.includes(attributes.power_id)) {
towns_list.push(town_id);
break;
}
}
}
return towns_list;
};
/* Make build request to the server */
buildPost = (town_id, unit, count) => {
let data = {
unit_id: unit,
amount: count,
town_id: town_id,
};
uw.gpAjax.ajaxPost('building_barracks', 'build', data);
};
/* return the active towns */
getActiveList = () => {
if (!this.spell) return Object.keys(this.city_troops);
return this.getPowerActive();
};
/* Main function, call in the loop */
main = () => {
let town_list = this.getActiveList();
for (let town_id of town_list) {
if (town_id in uw.ITowns.towns) {
if (this.checkPolis('naval', town_id)) return;
if (this.checkPolis('ground', town_id)) return;
} else {
delete this.city_troops[town_id];
}
}
};
}
/*
botConsole.log(message);
ideas:
- add colors
*/
class BotConsole {
constructor() {
this.string = [];
this.updateSettings();
}
renderSettings = () => {
setTimeout(() => {
this.updateSettings();
let interval = setInterval(() => {
this.updateSettings();
if (!uw.$('#modern_console').length) clearInterval(interval);
}, 1000);
}, 100);
return ``;
};
log = (string) => {
const date = new Date();
const time = date.toLocaleTimeString();
this.string.push(`[${time}] ${string}`);
};
updateSettings = () => {
let console = uw.$('#modern_console');
this.string.forEach((e, i) => {
if (uw.$(`#log_id_${i}`).length) return;
console.prepend(`${e}
`);
});
};
}
class Compressor {
NUMBERS = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
SYMBOLS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-./:;<=>?@[]^_`{|}~';
ITEMS = {
// Buildings
academy: 'a',
barracks: 'b',
docks: 'd',
farm: 'f',
hide: 'h',
ironer: 'i',
lumber: 'l',
main: 'm',
market: 'k',
stoner: 'c',
storage: 's',
temple: 't',
wall: 'w',
// Troops
sword: 'A',
archer: 'B',
hoplite: 'C',
slinger: 'D',
rider: 'E',
chariot: 'F',
catapult: 'G',
big_transporter: 'H',
small_transporter: 'I',
bireme: 'L',
demolition_ship: 'M',
attack_ship: 'N',
trireme: 'O',
colonize_ship: 'P',
};
constructor() {
const swap = json => {
var ret = {};
for (var key in json) {
ret[json[key]] = key;
}
return ret;
};
this.ITEMS_REV = swap(this.ITEMS);
}
/* Pass a storage object, return it encoded */
encode(storage) {
for (let item in storage) {
if (typeof storage[item] !== 'object') continue;
if (item == 'buildings') {
for (let polis_id in storage[item]) {
let obj = storage[item][polis_id];
storage[item][polis_id] = this.encode_building(obj);
}
}
if (item == 'troops') {
for (let polis_id in storage[item]) {
let obj = storage[item][polis_id];
storage[item][polis_id] = this.encode_troops(obj);
}
}
}
return storage;
}
decode(storage) {
for (let item in storage) {
if (typeof storage[item] !== 'object') continue;
if (item == 'buildings') {
for (let polis_id in storage[item]) {
let str = storage[item][polis_id];
storage[item][polis_id] = this.decode_bulding(str);
}
}
if (item === 'troops') {
for (let polis_id in storage[item]) {
let str = storage[item][polis_id];
storage[item][polis_id] = this.decode_troops(str);
}
}
}
return storage;
}
compressNumber(num) {
let base = this.SYMBOLS.length;
let digits = [];
while (num > 0) {
digits.unshift(this.SYMBOLS[num % base]);
num = Math.floor(num / base);
}
if (digits.length == 1) {
digits.unshift('0');
}
return digits.slice(-2).join('');
}
decompressNumber(str) {
let base = this.SYMBOLS.length;
let digits = str.split('');
let num = 0;
for (let i = 0; i < digits.length; i++) {
num += this.SYMBOLS.indexOf(digits[i]) * Math.pow(base, digits.length - i - 1);
}
return num;
}
/* Give the object of building, return the encoded string */
encode_building(obj) {
let str = '';
for (let item in obj) {
str += this.ITEMS[item] + this.NUMBERS[obj[item]];
}
return str;
}
/* Give an encoded string with building, return the correspong object */
decode_bulding(str) {
let json_str = '{';
for (let item of str.match(/.{1,2}/g)) {
json_str += `"${this.ITEMS_REV[item[0]]}"` + ':' + this.NUMBERS.indexOf(item[1]) + ',';
}
json_str = json_str.replace(/,$/, '}');
return JSON.parse(json_str);
}
encode_troops(obj) {
let str = '';
for (let item in obj) {
str += this.ITEMS[item] + this.compressNumber(obj[item]);
}
return str;
}
decode_troops(str) {
let json_str = '{';
for (let item of str.match(/.{1,3}/g)) {
json_str += `"${this.ITEMS_REV[item[0]]}"` + ':' + this.decompressNumber(item.slice(-2)) + ',';
}
json_str = json_str.replace(/,$/, '}');
return JSON.parse(json_str);
}
}
/*
Create a new window
*/
class createGrepoWindow {
constructor({ id, title, size, tabs, start_tab, minimizable = true }) {
this.minimizable = minimizable;
this.width = size[0];
this.height = size[1];
this.title = title;
this.id = id;
this.tabs = tabs;
this.start_tab = start_tab;
/* Private methods */
const createWindowType = (name, title, width, height, minimizable) => {
function WndHandler(wndhandle) {
this.wnd = wndhandle;
}
Function.prototype.inherits.call(WndHandler, uw.WndHandlerDefault);
WndHandler.prototype.getDefaultWindowOptions = function () {
return {
position: ['center', 'center', 100, 100],
width: width,
height: height,
minimizable: minimizable,
title: title,
};
};
uw.GPWindowMgr.addWndType(name, `${name}_75624`, WndHandler, 1);
};
const getTabById = (id) => {
return this.tabs.filter((tab) => tab.id === id)[0];
};
this.activate = function () {
createWindowType(this.id, this.title, this.width, this.height, this.minimizable); //
uw.$(
`
`,
).appendTo('head');
};
this.deactivate = function () {
if (uw.Layout.wnd.getOpenFirst(uw.GPWindowMgr[`TYPE_${this.id}`])) {
uw.Layout.wnd.getOpenFirst(uw.GPWindowMgr[`TYPE_${this.id}`]).close();
}
uw.$(`#${this.id}_custom_window_style`).remove();
};
/* open the window */
this.openWindow = function () {
let wn = uw.Layout.wnd.getOpenFirst(uw.GPWindowMgr[`TYPE_${this.id}`]);
/* if open is called but window it's alreay open minimized, maximize that */
if (wn) {
if (wn.isMinimized()) {
wn.maximizeWindow();
}
return;
}
let content = ` `;
uw.Layout.wnd.Create(uw.GPWindowMgr[`TYPE_${this.id}`]).setContent(content);
/* Add and reder tabs */
this.tabs.forEach((e) => {
let html = `
${e.title}
`;
uw.$(html).appendTo(`#${this.id}`);
});
/* Add events to tabs */
let tabs = '';
this.tabs.forEach((e) => {
tabs += `#${this.id} #${e.id}, `;
});
tabs = tabs.slice(0, -2);
let self = this;
uw.$(tabs).click(function () {
self.renderTab(this.id);
});
/* render default tab*/
this.renderTab(this.tabs[this.start_tab].id);
};
this.closeWindow = function () {
uw.Layout.wnd.getOpenFirst(uw.GPWindowMgr[`TYPE_${this.id}`]).close();
};
/* Handle active tab */
this.renderTab = function (id) {
let tab = getTabById(id);
uw.$(`#${this.id}_content`).html(getTabById(id).render());
uw.$(`#${this.id} .active`).removeClass('active');
uw.$(`#${id}`).addClass('active');
getTabById(id).afterRender ? getTabById(id).afterRender() : '';
};
}
}
// TODO:
// - disable note notifiation
// - add logs in console
class ModernStorage extends Compressor {
constructor() {
super();
this.check_done = 0;
/* Add event to add the button in the notes */
uw.$.Observer(uw.GameEvents.window.open).subscribe((e, i) => {
if (!i.attributes) return;
if (i.attributes.window_type != 'notes') return;
setTimeout(this.addButton, 100);
});
uw.$.Observer(uw.GameEvents.window.tab.rendered).subscribe((e, i) => {
const { attributes } = i.window_model;
if (!attributes) return;
if (attributes.window_type !== 'notes') return;
requestAnimationFrame(this.addButton);
});
}
getStorage = () => {
const worldId = uw.Game.world_id;
const savedValue = localStorage.getItem(`${worldId}_modernBot`);
let storage = {};
if (savedValue !== null && savedValue !== undefined) {
try {
storage = JSON.parse(savedValue);
} catch (error) {
console.error(`Error parsing localStorage data: ${error}`);
}
}
return storage;
};
saveStorage = storage => {
try {
const worldId = uw.Game.world_id;
localStorage.setItem(`${worldId}_modernBot`, JSON.stringify(storage));
this.lastUpdateTime = Date.now();
return true;
} catch (error) {
console.error(`Error saving data to localStorage: ${error}`);
return false;
}
};
save = (key, content) => {
const storage = this.getStorage();
storage[key] = content;
return this.saveStorage(storage);
};
load = (key, defaultValue = null) => {
const storage = this.getStorage();
const savedValue = storage[key];
return savedValue !== undefined ? savedValue : defaultValue;
};
/* Call to save the setting to the given note id */
saveSettingsNote = note_id => {
const storage = JSON.stringify(this.encode(this.getStorage()));
const data = {
model_url: `PlayerNote/${note_id}`,
action_name: 'save',
arguments: {
id: note_id,
text: storage,
},
};
uw.gpAjax.ajaxPost('frontend_bridge', 'execute', data);
return storage;
};
/* Call to add the buttons */
addButton = () => {
this.check_done += 1;
if ($('#modern_storage_load').length) return;
const modern_settings_load = $('', {
class: 'button_new',
id: 'modern_storage_load',
style: 'position: absolute; bottom: 5px; left: 6px; ',
onclick: 'modernBot.storage.loadSettings()',
html: ' Load ',
});
const modern_settings_save = $('', {
class: 'button_new',
id: 'modern_storage_save',
style: 'position: absolute; bottom: 5px; left: 75px; ',
onclick: 'modernBot.storage.saveSettings()',
html: ' Save ',
});
const box = $('.notes_container');
if (box.length) {
$('.notes_container').append(modern_settings_load, modern_settings_save);
} else {
if (this.check_done > 10) {
this.check_done = 0;
return;
}
setTimeout(this.addButton, 100);
}
};
saveSettings = () => {
uw.ConfirmationWindowFactory.openSimpleConfirmation(
'ModernStorage',
'This operation will overwrite the current note with the local settings of the ModernBot',
() => {
// trigged when user press yes
const note = this.getActiveNote();
if (!note) return; // TODO: display an error
const content = this.saveSettingsNote(note.id);
$('.preview_box').text(content);
},
() => {}
);
};
loadSettings = () => {
// TODO: check that the current note has settimhs
uw.ConfirmationWindowFactory.openSimpleConfirmation(
'ModernStorage',
'This operation will load the settings of the current note and overwrite the local settings',
() => {
// Trigged when the user press yes
const note = this.getActiveNote();
const { text } = note.attributes;
let decoded;
try {
decoded = this.decode(JSON.parse(text));
} catch {
HumanMessage.error("This note don't contains the settings");
return;
}
this.saveStorage(decoded);
location.reload();
},
() => {}
);
};
/* Return the current active note */
getActiveNote() {
const noteClass = $('.tab.selected').attr('class');
if (!noteClass) return null;
const noteX = noteClass.match(/note(\d+)/)[1];
const note_index = parseInt(noteX) - 1;
const collection = MM.getOnlyCollectionByName('PlayerNote');
if (!collection) return null;
let { models } = collection;
return models[note_index];
}
}
/* Setup autofarm in the window object */
class ModernBot {
constructor() {
this.console = new BotConsole();
this.storage = new ModernStorage();
this.$ui = $("#ui_box");
// Create the quick menu and the divider element
this.$menu = this.createModernMenu();
const $divider = $('');
// Add AutoFarm to the new menu
this.autoFarm = new AutoFarm(this.console, this.storage);
this.$menu.append(this.autoFarm.$activity)
this.$ui.append(this.autoFarm.$popup)
//const $farm = this.createActivity("url(https://gpit.innogamescdn.com/images/game/premium_features/feature_icons_2.08.png) no-repeat 0 -240px");
// this.$menu.append($farm, $divider.clone());
this.autoGratis = new AutoGratis(this.console, this.storage);
this.autoRuralLevel = new AutoRuralLevel(this.console, this.storage);
this.autoBuild = new AutoBuild(this.console, this.storage);
this.autoRuralTrade = new AutoRuralTrade(this.console, this.storage);
this.autoBootcamp = new AutoBootcamp(this.console, this.storage);
this.autoParty = new AutoParty(this.console, this.storage);
this.autoTrain = new AutoTrain(this.console, this.storage);
this.autoHide = new AutoHide(this.console, this.storage);
this.antiRage = new AntiRage(this.console, this.storage);
this.autoTrade = new AutoTrade(this.console, this.storage);
this.settingsFactory = new createGrepoWindow({
id: 'MODERN_BOT',
title: 'ModernBot',
size: [845, 300],
tabs: [
{
title: 'Farm',
id: 'farm',
render: this.settingsFarm,
},
{
title: 'Build',
id: 'build',
render: this.settingsBuild,
},
{
title: 'Train',
id: 'train',
render: this.settingsTrain,
} /*
{
title: 'Trade',
id: 'trade',
render: this.settingsTrade,
},*/,
{
title: 'Mix',
id: 'mix',
render: this.settingsMix,
},
{
title: 'Console',
id: 'console',
render: this.console.renderSettings,
},
],
start_tab: 0,
});
this.setup();
}
settingsFarm = () => {
let html = '';
// html += this.autoFarm.settings();
html += this.autoRuralLevel.settings();
html += this.autoRuralTrade.settings();
return html;
};
settingsBuild = () => {
let html = '';
html += this.autoGratis.settings();
html += this.autoBuild.settings();
return html;
};
settingsMix = () => {
let html = '';
html += this.autoBootcamp.settings();
html += this.autoParty.settings();
html += this.autoHide.settings();
return html;
};
settingsTrain = () => {
let html = '';
html += this.autoTrain.settings();
return html;
};
settingsTrade = () => {
let html = ``;
html += this.autoTrade.settings();
return html;
};
setup = () => {
/* Activate */
this.settingsFactory.activate();
uw.$('.gods_area_buttons').append(" ");
/* Add event to polis list menu */
const editController = () => {
const townController = uw.layout_main_controller.sub_controllers.find(controller => controller.name === 'town_name_area');
if (!townController) {
setTimeout(editController, 2500);
return;
}
const oldRender = townController.controller.town_groups_list_view.render;
townController.controller.town_groups_list_view.render = function () {
oldRender.call(this);
const both = ``;
const build = ``;
const troop = ``;
const townIds = Object.keys(uw.modernBot.autoBuild.towns_buildings);
const troopsIds = uw.modernBot.autoTrain.getActiveList().map(entry => entry.toString());
uw.$('.town_group_town').each(function () {
const townId = parseInt(uw.$(this).attr('data-townid'));
const is_build = townIds.includes(townId.toString());
const id_troop = troopsIds.includes(townId.toString());
if (!id_troop && !is_build) return;
if (id_troop && !is_build) uw.$(this).prepend(troop);
else if (is_build && !id_troop) uw.$(this).prepend(build);
else uw.$(this).prepend(both);
});
};
};
setTimeout(editController, 2500);
};
/* New quick menu */
// Create the html of an activity in the new quick menu
createModernMenu = () => {
const $menu = $('');
$menu.css({
'position': 'absolute',
'top': '3px',
'left': '400px',
'z-index': '1000',
});
// Add left, middle, right
const $left = $('');
const $middle = $('');
const $right = $('');
$menu.append($left, $middle, $right);
$("#ui_box").prepend($menu);
return $middle
}
}
// Load the bot when the loader is ready
const loader = setInterval(() => {
if ($("#loader").length > 0) return;
uw.modernBot = new ModernBot();
clearInterval(loader);
}, 100);
`);
const html = `