Nome do script

Copy the code



// ==UserScript==
// @name         ModernBot PT-BR JoeMan
// @author       Sau1707
// @description  A modern grepolis bot
// @version      1.18.5
// @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:#164d06;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}`;
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 },
        manticore: { research: 'manticore', god: 'zeus', building: 'temple', level: 15 }, // tentar acrescentar as manticore na lista
    };

    constructor(console, storage) {
        this.console = console;
        this.storage = storage;
    }
    /* Usage async this.sleep(ms) -> stop the code for ms */
    sleep = ms => {
        return new Promise(resolve => setTimeout(resolve, ms));
    };

    /**
     * Gere uma lista de IDs de cidades localizadas em ilhas grandes.
     * Uma ilha grande é definida como uma ilha que possui pelo menos uma cidade que não está em uma ilha pequena..
     * @returns {Array} -Matriz de IDs de cidade.
     */
    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; // Evite cidades em pequenas ilhas

            if (!islandsList.includes(island_id)) {
                islandsList.push(island_id);
                polisList.push(id);
            }
        }

        return polisList;
    };

    /**
     * Retorna o código HTML para um botão com um ID, texto, função e propriedades opcionais especificados.
     *
     * @param {string} id - O ID do botão.
     * @param {string} text - O texto a ser exibido no botão.
     * @param {Function} fn - A função a ser chamada quando o botão é clicado.
     * @param {string} [props] - Propriedades opcionais para passar para a função.
     * @returns {string} - O código HTML do botão.
     */
    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 `
      <div id="${id}" style="cursor: pointer" class="button_new" onclick="${click}">
        <div class="left"></div>
        <div class="right"></div>
        <div class="caption js-caption"> ${text} <div class="effect js-effect"></div></div>
      </div>`;
    }

    /**
     * Retorna o HTML de um título de jogo com um cabeçalho clicável que alterna uma função.
     *
     * @param {string} id - O ID do elemento HTML.
     * @param {string} text - O texto a ser exibido no título.
     * @param {function} fn - A função para alternar.
     * @param {string|number} props - As propriedades a serem passadas para a função.
     * @param {boolean} enable - Se o título está habilitado ou não.
     * @param {string} [desc='(clique para alterna)'] - A descrição a ser exibida.
     * @returns {string} O HTML do título do jogo.
     */
    getTitleHtml(id, text, fn, props, enable, desc = '<svg style="width: 20px;margin-top: -3px;" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512" xml:space="preserve" fill="#000000"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <circle style="fill:#5ac827;" cx="256" cy="256" r="245.801"></circle> <polygon style="fill:#FFF;" points="195.825,391.629 376.351,256 195.825,120.371 "></polygon> <g> <path style="fill:#4D4D4D;" d="M256,512c-68.381,0-132.667-26.628-181.019-74.98C26.628,388.667,0,324.38,0,256 S26.628,123.333,74.981,74.98C123.333,26.628,187.619,0,256,0s132.667,26.628,181.019,74.98C485.372,123.333,512,187.62,512,256 s-26.628,132.667-74.981,181.02C388.667,485.372,324.381,512,256,512z M256,20.398C126.089,20.398,20.398,126.089,20.398,256 S126.089,491.602,256,491.602S491.602,385.911,491.602,256S385.911,20.398,256,20.398z"></path> <path style="fill:#4D4D4D;" d="M195.824,401.828c-1.553,0-3.115-0.355-4.557-1.075c-3.458-1.727-5.641-5.26-5.641-9.124V120.371 c0-3.864,2.185-7.397,5.641-9.124c3.458-1.726,7.593-1.351,10.685,0.97l180.526,135.629c2.564,1.927,4.073,4.948,4.073,8.154 s-1.508,6.228-4.073,8.154L201.951,399.783C200.15,401.137,197.994,401.828,195.824,401.828z M206.024,140.791v230.418L359.371,256 L206.024,140.791z"></path> <path style="fill:#4D4D4D;" d="M256,473.243c-5.632,0-10.199-4.566-10.199-10.199c0-5.633,4.567-10.199,10.199-10.199 c52.815,0,102.404-20.633,139.633-58.1c3.973-3.996,10.429-4.015,14.425-0.045c3.995,3.971,4.016,10.428,0.046,14.424 C369.016,450.471,314.287,473.243,256,473.243z"></path> <path style="fill:#4D4D4D;" d="M430.396,377.825c-1.886,0-3.793-0.522-5.498-1.617c-4.741-3.041-6.118-9.351-3.076-14.092 c1.514-2.36,2.998-4.788,4.411-7.216c2.834-4.867,9.077-6.516,13.945-3.684c4.868,2.833,6.518,9.077,3.684,13.945 c-1.56,2.681-3.201,5.363-4.873,7.97C437.043,376.168,433.754,377.825,430.396,377.825z"></path> </g> </g></svg>') {
        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 `
        <div class="game_border_top"></div>
        <div class="game_border_bottom"></div>
        <div class="game_border_left"></div>
        <div class="game_border_right"></div>
        <div class="game_border_corner corner1"></div>
        <div class="game_border_corner corner2"></div>
        <div class="game_border_corner corner3"></div>
        <div class="game_border_corner corner4"></div>
        <div id="${id}" style="cursor: pointer; filter: ${enable ? filter : ''}" class="game_header bold" onclick="${click}">
            ${text}
            <span class="command_count"></span>
            <div style="position: absolute; right: 10px; top: 4px; font-size: 10px;"> ${desc} </div>
        </div>`;
    }

    /**
     * Calcula a população total de uma coleção de unidades.
     *
     * @param {Object} unidades - A coleção de unidades para contar a população.
     * @returns {number} - A população total de todas as unidades da coleção.
     */
    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);
    }
}

// A SER FINALIZADO
class About {
	constructor() {
		this.checkVersion();
	}

	settings = () => {};

	checkVersion = async () => {
		if (!GM_info) return;

		/* Verifique se a versão é a atual */
		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(() => {
			// desativar o deus anteriormente ativo

			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(`
            <div id="enchanted_rage" class="js-power-icon animated_power_icon animated_power_icon_45x45 power_icon45x45 power transformation" style="filter: brightness(70%) sepia(104%) hue-rotate(14deg) saturate(1642%) contrast(0.8)">
                <div class="extend_spell">
                    <div class="gold"></div>
                </div>
                <div class="js-caption"></div>
            </div>
            `);

			const html = `
            <table class="popup" id="popup_div" cellpadding="0" cellspacing="0" style="display: block; left: 243px; top: 461px; opacity: 1; position: absolute; z-index: 6001; width: auto; max-width: 400px;">
                <tbody>
                    <tr class="popup_top">
                        <td class="popup_top_left"></td>
                        <td class="popup_top_middle"></td>
                        <td class="popup_top_right"></td>
                    </tr>
                    <tr>
                        <td class="popup_middle_left">&nbsp;</td>
                        <td class="popup_middle_middle" id="popup_content" style="width: auto;">
                            <div class="temple_power_popup ">
                                <div class="temple_power_popup_image power_icon86x86 transformation" style="filter: brightness(70%) sepia(104%) hue-rotate(14deg) saturate(1642%) contrast(0.8)"></div>
                                <div class="temple_power_popup_info">
                                    <h4>Fúria Encantamento</h4>
                                    <p> Uma versão Encantada da raiva normal </p>
                                    <p> feita para quem tenta trollar com o autoclick </p>
                                    <p><b> Lance Purificação e Fúria ao mesmo tempo </b></p>
                                    <div class="favor_cost_info">
                                        <div class="resource_icon favor"></div>
                                        <span>300 zeus + 200 artemis</span>
                                    </div>
                                </div>
                            </div>
                        </td>
                        <td class="popup_middle_right">&nbsp;</td>
                    </tr>
                    <tr class="popup_bottom">
                        <td class="popup_bottom_left"></td>
                        <td class="popup_bottom_middle"></td>
                        <td class="popup_bottom_right"></td>
                    </tr>
                </tbody>
            </table>`;

			const default_popup = `
            <table class="popup" id="popup_div" cellpadding="0" cellspacing="0" style="display: none; opacity: 0;">
	    	    <tbody><tr class="popup_top">
	    	    	<td class="popup_top_left"></td>
	    	    	<td class="popup_top_middle"></td>
	    	    	<td class="popup_top_right"></td>
	    	    </tr>
	    	    <tr>
	    	    	<td class="popup_middle_left">&nbsp;</td>
	    	    	<td class="popup_middle_middle" id="popup_content"></td>
	    	    	<td class="popup_middle_right">&nbsp;</td>
	    	    </tr>
	    	    <tr class="popup_bottom">
	    	    	<td class="popup_bottom_left"></td>
	    	    	<td class="popup_bottom_middle"></td>
	    	    	<td class="popup_bottom_right"></td>
	    	    </tr>
 	            </tbody>
            </table>`;

			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: desabilitar se não habilitar
			$('#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);
	};
}

/*

'<div id="popup_div_curtain">'+
    '<table class="popup" id="popup_div" cellpadding="0" cellspacing="0" style="display: block; left: 243px; top: 461px; opacity: 1; position: absolute; z-index: 6001; width: auto; max-width: 400px;">'+
        '<tbody><tr class="popup_top">'+
            '<td class="popup_top_left"></td>'+
            '<td class="popup_top_middle"></td>'+
            '<td class="popup_top_right"></td>'+
        '</tr>'+
        '<tr>'+
            '<td class="popup_middle_left">&nbsp;</td>'+
            '<td class="popup_middle_middle" id="popup_content" style="width: auto;"><div>'+

'<div class="temple_power_popup ">'+

    '<div class="temple_power_popup_image power_icon86x86 fair_wind"></div>'+

    '<div class="temple_power_popup_info">'+
        '<h4>Vento favorevole</h4>'+
        '<p>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.</p>'+

            '<p><b>Le forze navali attaccanti ottengono un bonus del 10% alla loro forza durante il loro prossimo attacco.</b></p>'+
                    '<div class="favor_cost_info">'+
                        '<div class="resource_icon favor"></div>'+
                        '<span>250 favore</span>'+
                    '</div>'+
    '</div>'+
'</div>'+
'</div></td>'+
            '<td class="popup_middle_right">&nbsp;</td>'+
        '</tr>'+
        '<tr class="popup_bottom">'+
            '<td class="popup_bottom_left"></td>'+
            '<td class="popup_bottom_middle"></td>'+
            '<td class="popup_bottom_right"></td>'+
        '</tr>'+
      '</tbody></table>'+
'</div>'+
*/


class AutoBootcamp extends ModernUtil {
	constructor(c, s) {
		super(c, s);

		if (this.storage.load('ab_active', false)) this.toggle();
		if (this.storage.load('bootcamp_use_def', false)) this.triggerUseDef();
	}

	settings = () => {
		requestAnimationFrame(() => {
			if (this.use_def) {
				uw.$('#autobootcamp_off').addClass('disabled');
				uw.$('#autobootcamp_def').removeClass('disabled');
			} else {
				uw.$('#autobootcamp_def').addClass('disabled');
				uw.$('#autobootcamp_off').removeClass('disabled');
			}
		});

		return `
        <div class="game_border" style="margin-bottom: 20px">
            ${this.getTitleHtml('auto_autobootcamp', 'Auto ataque aldeia (so no inicio do jogo quando 1 cidade)', this.toggle, '', this.enable_auto_bootcamp)}

        <div id="autobootcamp_lvl_buttons" style="padding: 5px; display: inline-flex;">
            <!-- temp -->
            <div style="margin-right: 40px">
                ${this.getButtonHtml('autobootcamp_off', 'Apenas Off', this.triggerUseDef)}
                ${this.getButtonHtml('autobootcamp_def', 'Off & Defe', this.triggerUseDef)}
            </div>
        </div >
    </div>
        `;
	};

	triggerUseDef = () => {
		this.use_def = !this.use_def;
		if (this.use_def) {
			uw.$('#autobootcamp_off').addClass('disabled');
			uw.$('#autobootcamp_def').removeClass('disabled');
		} else {
			uw.$('#autobootcamp_def').addClass('disabled');
			uw.$('#autobootcamp_off').removeClass('disabled');
		}
		this.storage.save('bootcamp_use_def', this.use_def);
	};

	toggle = () => {
		if (!this.enable_auto_bootcamp) {
			uw.$('#auto_autobootcamp').css('filter', 'brightness(100%) saturate(186%) hue-rotate(241deg)');
			this.enable_auto_bootcamp = setInterval(this.main, 4000);
			this.console.log('Auto ataque aldeia -> On');
		} else {
			uw.$('#auto_autobootcamp').css('filter', '');
			clearInterval(this.enable_auto_bootcamp);
			this.enable_auto_bootcamp = null;
			this.console.log('Auto ataque aldeia -> Off');
		}
		this.storage.save('ab_active', !!this.enable_auto_bootcamp);
	};

	attackBootcamp = () => {
		let cooldown = uw.MM.getModelByNameAndPlayerId('PlayerAttackSpot').getCooldownDuration();
		if (cooldown > 0) return false;

		let { MovementsUnits } = uw.MM.getModels();

		/* Verifique se já não existe um ataque ativo */
		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;
					}
				}
			}
		}

		var units = { ...uw.ITowns.towns[uw.Game.townId].units() };

		delete units.militia;
		for (let unit in units) {
			if (uw.GameData.units[unit].is_naval) delete units[unit];
		}

		if (!this.use_def) {
			delete units.sword;
			delete units.archer;
		}

		/* Pare se não houver mais unidades disponíveis */
		if (Object.keys(units).length === 0) {
			this.toggle();
			return;
		}

		this.postAttackBootcamp(units);
		return true;
	};

	rewardBootcamp = () => {
		let model = uw.MM.getModelByNameAndPlayerId('PlayerAttackSpot');

		/* Pare se o nível não for encontrado */
		if (typeof model.getLevel() == 'undefined') {
			this.console.log('Auto ataque aldeia nao encontrado');
			this.toggle();
			return true;
		}

		let hasReward = model.hasReward();
		if (!hasReward) return false;

		let reward = model.getReward();
		if (reward.power_id.includes('instant') && !reward.power_id.includes('favor')) {
			this.useBootcampReward();
			return true;
		}

		if (reward.stashable) this.stashBootcampReward();
		else this.useBootcampReward();

		return true;
	};

	/* Função principal, chamada em loop */
	main = () => {
		if (this.rewardBootcamp()) return;
		if (this.attackBootcamp()) return;
	};

	/* Enviar solicitação de postagem para atacar com as unidades fornecidas */
	postAttackBootcamp = units => {
		var data = {
			model_url: `PlayerAttackSpot/${uw.Game.player_id}`,
			action_name: 'attack',
			arguments: units,
		};
		uw.gpAjax.ajaxPost('frontend_bridge', 'execute', data);
	};

	/* Envie solicitação ao servidor para usar a recompensa */
	useBootcampReward = () => {
		var data = {
			model_url: `PlayerAttackSpot/${uw.Game.player_id}`,
			action_name: 'useReward',
			arguments: {},
		};
		uw.gpAjax.ajaxPost('frontend_bridge', 'execute', data);
	};

	/* Envie uma solicitação ao servidor para armazenar a recompensa */
	stashBootcampReward = () => {
		var data = {
			model_url: `PlayerAttackSpot/${uw.Game.player_id}`,
			action_name: 'stashReward',
			arguments: {},
		};
		uw.gpAjax.ajaxPost('frontend_bridge', 'execute', data, 0, {
			error: this.useBootcampReward,
		});
	};
}

/*
    Ideas:
    - mostrar o status atual
        - feito
        - recursos faltando (por que digitar)
        - população faltando
        - filacheio
    - mostrar ponto final
    - edifícios especiais
*/

// 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);

		/* Carregar configurações, as polis nas configurações são as ativas */
		this.towns_buildings = this.storage.load('buildings', {});

		/* Verifique se shift está pressionado */
		this.shiftHeld = false;

		/* Ativo sempre, verifique se as cidades estão na lista ativa */
		this.enable = setInterval(this.main, 20000);

    }

	settings = () => {
		/* Aplicar evento ao turno */
		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 `
        <div class="game_border" style="margin-bottom: 20px">
            <div class="game_border_top"></div>
            <div class="game_border_bottom"></div>
            <div class="game_border_left"></div>
            <div class="game_border_right"></div>
            <div class="game_border_corner corner1"></div>
            <div class="game_border_corner corner2"></div>
            <div class="game_border_corner corner3"></div>
            <div class="game_border_corner corner4"></div>
            <div id="auto_build_title" style="cursor: pointer; filter: ${this.enable ? 'brightness(100%) saturate(186%) hue-rotate(241deg)' : ''}" class="game_header bold" onclick="window.modernBot.autoBuild.toggle()"> Fila de construcao Automatica <span class="command_count"></span>
                <div style="position: absolute; right: 10px; top: 4px; font-size: 10px;"> <svg style="width: 20px;margin-top: -3px;" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512" xml:space="preserve" fill="#000000"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <circle style="fill:#5ac827;" cx="256" cy="256" r="245.801"></circle> <polygon style="fill:#FFF;" points="195.825,391.629 376.351,256 195.825,120.371 "></polygon> <g> <path style="fill:#4D4D4D;" d="M256,512c-68.381,0-132.667-26.628-181.019-74.98C26.628,388.667,0,324.38,0,256 S26.628,123.333,74.981,74.98C123.333,26.628,187.619,0,256,0s132.667,26.628,181.019,74.98C485.372,123.333,512,187.62,512,256 s-26.628,132.667-74.981,181.02C388.667,485.372,324.381,512,256,512z M256,20.398C126.089,20.398,20.398,126.089,20.398,256 S126.089,491.602,256,491.602S491.602,385.911,491.602,256S385.911,20.398,256,20.398z"></path> <path style="fill:#4D4D4D;" d="M195.824,401.828c-1.553,0-3.115-0.355-4.557-1.075c-3.458-1.727-5.641-5.26-5.641-9.124V120.371 c0-3.864,2.185-7.397,5.641-9.124c3.458-1.726,7.593-1.351,10.685,0.97l180.526,135.629c2.564,1.927,4.073,4.948,4.073,8.154 s-1.508,6.228-4.073,8.154L201.951,399.783C200.15,401.137,197.994,401.828,195.824,401.828z M206.024,140.791v230.418L359.371,256 L206.024,140.791z"></path> <path style="fill:#4D4D4D;" d="M256,473.243c-5.632,0-10.199-4.566-10.199-10.199c0-5.633,4.567-10.199,10.199-10.199 c52.815,0,102.404-20.633,139.633-58.1c3.973-3.996,10.429-4.015,14.425-0.045c3.995,3.971,4.016,10.428,0.046,14.424 C369.016,450.471,314.287,473.243,256,473.243z"></path> <path style="fill:#4D4D4D;" d="M430.396,377.825c-1.886,0-3.793-0.522-5.498-1.617c-4.741-3.041-6.118-9.351-3.076-14.092 c1.514-2.36,2.998-4.788,4.411-7.216c2.834-4.867,9.077-6.516,13.945-3.684c4.868,2.833,6.518,9.077,3.684,13.945 c-1.56,2.681-3.201,5.363-4.873,7.97C437.043,376.168,433.754,377.825,430.396,377.825z"></path> </g> </g></svg> </div>
            </div>
            <div id="buildings_lvl_buttons"></div>
        </div> `;
	};

	/* Dado o ID da cidade, defina a polis no menu de configurações */
	setPolisInSettings = town_id => {
		let town = uw.ITowns.towns[town_id];

		/* Se a cidade estiver na lista ativa definida*/
		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 `
                <div class="auto_build_box" onclick="window.modernBot.autoBuild.editBuildingLevel(${town_id}, '${building}', 0)" style="cursor: pointer">
                <div class="item_icon auto_build_building" style="background-position: -${bg[0]}px -${bg[1]}px;">
                    <div class="auto_build_up_arrow" onclick="event.stopPropagation(); window.modernBot.autoBuild.editBuildingLevel(${town_id}, '${building}', 1)" ></div>
                    <div class="auto_build_down_arrow" onclick="event.stopPropagation(); window.modernBot.autoBuild.editBuildingLevel(${town_id}, '${building}', -1)"></div>
                    <p style="color: ${color}" id="build_lvl_${building}" class="auto_build_lvl"> ${town_buildings[building]} <p>
                </div>
            </div>`;
		};

		/* Se a cidade estiver em grupo, os grupos */
		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.$('#buildings_lvl_buttons').html(`
        <div id="build_settings_${town_id}">
            <div style="width: 600px; margin-bottom: 3px; display: inline-flex">
            <a class="gp_town_link" href="${town.getLinkFragment()}">${town.getName()}</a>
            <p style="font-weight: bold; margin: 0px 5px"> [${town.getPoints()} pts] </p>
            <p style="font-weight: bold; margin: 0px 5px"> ${groups} </p>
            </div>
            <div style="width: 100%; display: inline-flex; gap: 6px;">
                ${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])}
            </div>
        </div>
        <div id="textea" style="margin-top: 12px;"><span id="arm" style="padding-right: 7px;padding-left: 5px;color: #37290d;float: left;font-weight: 700;line-height: 20px;font-size: 11px;text-align: left;margin-top: -16px;">Senado</span><span id="arm" style="padding-left: 2px;color: #37290d;float: left;font-weight: 700;line-height: 20px;font-size: 11px;text-align: left;margin-top: -16px;">Armazem</span><span id="arm" style="padding-left: 11px;color: #37290d;float: left;font-weight: 700;line-height: 20px;font-size: 11px;text-align: left;margin-top: -16px;">Quinta</span>
<span id="arm" style="padding-left: 12px;color: #37290d;float: left;font-weight: 700;line-height: 20px;font-size: 11px;text-align: left;margin-top: -16px;">Academia</span>
<span id="arm" style="color: #37290d;float: left;font-weight: 700;line-height: 20px;font-size: 11px;text-align: left;padding-left: 8px;margin-top: -16px;">Templo</span><span id="arm" style="padding-left: 12px;color: #37290d;float: left;font-weight: 700;line-height: 20px;font-size: 11px;text-align: left;margin-top: -16px;">Quartelo</span><span id="arm" style="padding-left: 18px;color: #37290d;float: left;font-weight: 700;line-height: 20px;font-size: 11px;text-align: left;margin-top: -16px;">Porto</span><span id="arm" style="color: #37290d;float: left;font-weight: 700;line-height: 20px;font-size: 11px;text-align: left;padding-left: 19px;margin-top: -16px;">Mercado</span><span id="arm" style="padding-left: 18px;color: #37290d;float: left;font-weight: 700;line-height: 20px;font-size: 11px;text-align: left;margin-top: -16px;">Gruta</span><span id="arm" style="padding-left: 18px;color: #37290d;float: left;font-weight: 700;line-height: 20px;font-size: 11px;text-align: left;margin-top: -16px;">Seracao</span><span id="arm" style="padding-left: 13px;color: #37290d;float: left;font-weight: 700;line-height: 20px;font-size: 11px;text-align: left;margin-top: -16px;">Pedreira</span><span id="arm" style="padding-left: 17px;color: #37290d;float: left;font-weight: 700;line-height: 20px;font-size: 11px;text-align: left;margin-top: -16px;">Mina p</span><span id="arm" style="padding-left: 14px;color: #37290d;float: left;font-weight: 700;line-height: 20px;font-size: 11px;text-align: left;margin-top: -16px;">Muralha</span></div>
        `);
	};

	/* ligue com town_id, tipo de construção e nível a ser adicionado */
	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) {
			/* se shift for pressionado, adicione ou remova 10 */
			d = this.shiftHeld ? d * 10 : d;

			/* Verifique se há transbordamento inferior ou superior */
			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.$('#auto_build_title').css('filter', 'brightness(100%) saturate(186%) hue-rotate(241deg)');
		} else {
			uw.$('#auto_build_title').css('filter', '');
		}
	};

	/* Ligue para ligar e desligar (acionar a cidade atual) */
	toggle = () => {
		let town = uw.ITowns.getCurrentTown();

		if (!(town.id.toString() in this.towns_buildings)) {
			this.console.log(`${town.name}: Fila de construcao auto 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.console.log(`${town.name}: Fila de construcao auto Off`);
		}

		this.updateTitle();
	};

	/* Loop principal para construção */
	main = async () => {
		for (let town_id of Object.keys(this.towns_buildings)) {
			/* Se a cidade não existir na lista, remova-a para evitar erros*/
			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;

			/* Se a cidade estiver pronta, remova da lista */
			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}: Fila de construcao auto Done`);
				continue;
			}
			await this.getNextBuild(town_id);
		}
	};

	/* Faça uma solicitação posterior ao servidor para construir o edifício */
	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);
	};

	/* Faça uma solicitação de postagem para demolir o prédio */
	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);
	};

	/* retorne verdadeiro se a fila estiver cheia */
	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;
	};

	/* retorne verdadeiro se estiver construindo uma polis de correspondência */
	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];

		/* nivelo atuale */
		let buildings = { ...town.getBuildings().attributes };

		/* Adicione a lista do progresso atual da construção */
		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;
			}
		}
		/* nível que precisa atingir */
		let target = this.towns_buildings[town_id];

		/* Verifique se o edifício é duável, se sim, construa-o e retorne verdadeiro, caso contrário, falso  */
		const check = async (build, level) => {
			/* se o dado for um array, tente aleatoriamente todo o 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;
		};

		/* SE as docas ainda não estiverem construídas, siga o 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;
	};
}

/*
    TODO:
    - Autotrade: o conserto rural não pertence a você, matéria-prima que você possui + log no console
    - AutoRuralLevel: ainda por implementar
    - AutoFarm: verifique a hora de começar
*/
class AutoFarm extends ModernUtil {
	BUTTONHTML =
		'<div class="divider"id="autofarm_timer_divider" ></div><div onclick="window.modernBot.autoFarm.toggle()" class="activity" id="autofarm_timer" style="filter: brightness(110%) sepia(100%) hue-rotate(100deg) saturate(1500%) contrast(0.8); background: url(https://i.ibb.co/gm8NDFS/backgound-timer.png); height: 26px; width: 40px"><p id="autofarm_timer_p" style="z-index: 6; top: -8px; position: relative; font-weight: bold;"></p></div>';

	YELLOW = 'brightness(294%) sepia(100%) hue-rotate(15deg) saturate(1000%) contrast(0.8)';
	GREEN = 'brightness(110%) sepia(100%) hue-rotate(100deg) saturate(1500%) contrast(0.8)';

	TIMINGS = {
		1: 300000,
		2: 600000,
		3: 1200000,
		4: 2400000,
	};

	constructor(c, s) {
		super(c, s);

		this.delta_time = 10000;
		this.timing = this.storage.load('af_level', 1);
		this.percentual = this.storage.load('af_percentuals', 3);
		if (this.storage.load('af', false)) {
			this.lastTime = Date.now();
			this.timer = 0; // TODO: verifique se é realmente 0
			this.active = setInterval(this.main, 1000);
		}

		this.polislist = this.generateList();
	}

	settings = () => {
		requestAnimationFrame(() => {
			this.setAutoFarmLevel(this.timing);
			this.setAutoFarmPercentual(this.percentual);
		});

		return `
            <div class="game_border" style="margin-bottom: 20px">
                ${this.getTitleHtml('auto_farm', 'Coletar recursos automaticamente nas aldeias', this.toggle, '', this.enable_auto_farming)}
                <div class="split_content">
                <div id="farming_lvl_buttons" style="padding: 5px;">
                    ${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)}
                </div>
                <div id="rural_lvl_percentuals" style="padding: 5px">
                    ${this.getButtonHtml('percentuals_1', '80%', this.setAutoFarmPercentual, 1)}
                    ${this.getButtonHtml('percentuals_2', '90%', this.setAutoFarmPercentual, 2)}
                    ${this.getButtonHtml('percentuals_3', '100%', this.setAutoFarmPercentual, 3)}
                </div>
                </div>
            </div>
        `;
	};

	/* gerar a lista contendo 1 polis por ilha */
	generateList = () => {
		const islandsList = new Set();
		const polisList = [];

		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) continue;
			if (islandsList.has(island_id)) continue;
			islandsList.add(island_id);
			polisList.push(id);
		}

		return polisList;
	};

	toggle = () => {
		if (this.active) {
			uw.$('#autofarm_timer').remove();
			uw.$('#autofarm_timer_divider').remove();
			uw.$('#auto_farm').css('filter', '');
			clearInterval(this.active);
			this.active = null;
		} else {
			uw.$('#auto_farm').css('filter', 'brightness(100%) saturate(186%) hue-rotate(241deg)');
			this.lastTime = Date.now();
			this.timer = 0; // TODO: verifique se é realmente 0
			this.active = setInterval(this.main, 1000);
		}
		this.storage.save('af', !!this.active);
	};

	setAutoFarmLevel = n => {
		uw.$('#farming_lvl_buttons .button_new').addClass('disabled');
		uw.$(`#farming_lvl_${n}`).removeClass('disabled');
		if (this.timing != n) {
			this.timing = n;
			this.storage.save('af_level', n);
			const rand = Math.floor(Math.random() * this.delta_time);
			this.timer = this.TIMINGS[this.timing] + rand;
		}
	};

	setAutoFarmPercentual = n => {
		const box = uw.$('#rural_lvl_percentuals');
		box.find('.button_new').addClass('disabled');
		uw.$(`#percentuals_${n}`).removeClass('disabled');
		if (this.percentual != n) {
			this.percentual = n;
			this.storage.save('af_percentuals', n);
		}
	};

	/* retornar o tempo antes da próxima coleta */
	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;
	};

	/* Ligue para atualizar o cronômetro */
	updateTimer = () => {
		const currentTime = Date.now();
		this.timer -= currentTime - this.lastTime;
		this.lastTime = currentTime;

		/* Adicionar temporizador de não estar lá */
		const timerDisplay = uw.$('#autofarm_timer_p');
		if (!timerDisplay.length) uw.$('.tb_activities, .toolbar_activities').find('.middle').append(this.BUTTONHTML);
		else {
			timerDisplay.html(Math.round(Math.max(this.timer, 0) / 1000));
		}

		const isCaptainActive = uw.GameDataPremium.isAdvisorActivated('captain');
		uw.$('#autofarm_timer').css('filter', isCaptainActive ? this.GREEN : this.YELLOW);
	};

	claim = async () => {
		const isCaptainActive = uw.GameDataPremium.isAdvisorActivated('captain');
		if (isCaptainActive) {
			await this.fakeOpening();
			await this.sleep(Math.random() * 2000 + 1000); // aleatório entre 1 segundo e 3
			await this.fakeSelectAll();
			await this.sleep(Math.random() * 2000 + 1000);
			if (this.timing <= 2) await this.claimMultiple(300, 600);
			if (this.timing > 2) await this.claimMultiple(1200, 2400);
			await this.fakeUpdate();
		} else {
			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);
			let max = 60;
			for (let town_id of this.polislist) {
				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.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 / 2));
						await this.sleep(500);
						if (!max) return;
						else max -= 1;
					}
				}
			}
		}

		setTimeout(() => uw.WMap.removeFarmTownLootCooldownIconAndRefreshLootTimers(), 2000);
	};

	/* Retornar o total de recursos da polis na lista */
	getTotalResources = () => {
		let total = {
			wood: 0,
			stone: 0,
			iron: 0,
			storage: 0,
		};

		for (let town_id of this.polislist) {
			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 () => {
		/* Reclamar que o tempo dos recursos já passou */
		if (this.timer < 1) {
			/* Verifique se o percentual atingiu */
			const { wood, stone, iron, storage } = this.getTotalResources();
			const minResource = Math.min(wood, stone, iron);
			const min_percentual = minResource / storage;

			/* Se o percentual máximo for atingido, pare e aguarde 30 segundos*/
			if (this.percentual == 3 && min_percentual > 0.99) {
				this.timer = 30000;
				requestAnimationFrame(this.updateTimer);
				return;
			}
			if (this.percentual == 2 && min_percentual > 0.9) {
				this.timer = 30000;
				requestAnimationFrame(this.updateTimer);
				return;
			}
			if (this.percentual == 1 && min_percentual > 0.8) {
				this.timer = 30000;
				requestAnimationFrame(this.updateTimer);
				return;
			}

			const rand = Math.floor(Math.random() * this.delta_time);
			this.timer = this.TIMINGS[this.timing] + rand;

			await this.claim();
		}

		/* atualizar o cronômetro */
		this.updateTimer();
	};

	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);
	};

	claimMultiple = (base = 300, boost = 600) =>
		new Promise((myResolve, myReject) => {
			let data = {
				towns: this.polislist,
				time_option_base: base,
				time_option_booty: boost,
				claim_factor: 'normal',
			};
			uw.gpAjax.ajaxPost('farm_town_overviews', 'claim_loads_multiple', data, false, () => myResolve());
		});

	/* Finja que a janela está abrindo */
	fakeOpening = () =>
		new Promise((myResolve, myReject) => {
			uw.gpAjax.ajaxGet('farm_town_overviews', 'index', {}, false, async () => {
				await this.sleep(10);
				await this.fakeUpdate();
				myResolve();
			});
		});

	/* Falsifique o usuário selecionando a lista */
	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());
		});

	/* Falsifique a atualização da janela*/
	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());
		});
}

class AutoGratis extends ModernUtil {
    constructor(c, s) {
        super(c, s);

        if (this.storage.load('enable_autogratis', false)) this.toggle();
    }

    settings = () => {
        return `
        <div class="game_border" style="margin-bottom: 20px">
            <div class="game_border_top"></div>
            <div class="game_border_bottom"></div>
            <div class="game_border_left"></div>
            <div class="game_border_right"></div>
            <div class="game_border_corner corner1"></div>
            <div class="game_border_corner corner2"></div>
            <div class="game_border_corner corner3"></div>
            <div class="game_border_corner corner4"></div>
            <div id="auto_gratis_title" style="cursor: pointer; filter: ${this.autogratis ? 'brightness(100%) saturate(186%) hue-rotate(241deg)' : ''
            }" class="game_header bold" onclick="window.modernBot.autoGratis.toggle()"> Finalizar as Construçoes Gratuitamente <span class="command_count"></span>
                <div style="position: absolute; right: 10px; top: 4px; font-size: 10px;"> <svg style="width: 20px;margin-top: -3px;" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512" xml:space="preserve" fill="#000000"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <circle style="fill:#5ac827;" cx="256" cy="256" r="245.801"></circle> <polygon style="fill:#FFF;" points="195.825,391.629 376.351,256 195.825,120.371 "></polygon> <g> <path style="fill:#4D4D4D;" d="M256,512c-68.381,0-132.667-26.628-181.019-74.98C26.628,388.667,0,324.38,0,256 S26.628,123.333,74.981,74.98C123.333,26.628,187.619,0,256,0s132.667,26.628,181.019,74.98C485.372,123.333,512,187.62,512,256 s-26.628,132.667-74.981,181.02C388.667,485.372,324.381,512,256,512z M256,20.398C126.089,20.398,20.398,126.089,20.398,256 S126.089,491.602,256,491.602S491.602,385.911,491.602,256S385.911,20.398,256,20.398z"></path> <path style="fill:#4D4D4D;" d="M195.824,401.828c-1.553,0-3.115-0.355-4.557-1.075c-3.458-1.727-5.641-5.26-5.641-9.124V120.371 c0-3.864,2.185-7.397,5.641-9.124c3.458-1.726,7.593-1.351,10.685,0.97l180.526,135.629c2.564,1.927,4.073,4.948,4.073,8.154 s-1.508,6.228-4.073,8.154L201.951,399.783C200.15,401.137,197.994,401.828,195.824,401.828z M206.024,140.791v230.418L359.371,256 L206.024,140.791z"></path> <path style="fill:#4D4D4D;" d="M256,473.243c-5.632,0-10.199-4.566-10.199-10.199c0-5.633,4.567-10.199,10.199-10.199 c52.815,0,102.404-20.633,139.633-58.1c3.973-3.996,10.429-4.015,14.425-0.045c3.995,3.971,4.016,10.428,0.046,14.424 C369.016,450.471,314.287,473.243,256,473.243z"></path> <path style="fill:#4D4D4D;" d="M430.396,377.825c-1.886,0-3.793-0.522-5.498-1.617c-4.741-3.041-6.118-9.351-3.076-14.092 c1.514-2.36,2.998-4.788,4.411-7.216c2.834-4.867,9.077-6.516,13.945-3.684c4.868,2.833,6.518,9.077,3.684,13.945 c-1.56,2.681-3.201,5.363-4.873,7.97C437.043,376.168,433.754,377.825,430.396,377.825z"></path> </g> </g></svg> </div>
            </div>
            <div style="padding: 5px; font-weight: 600">
                 (Construcao Auto Gratis) Permite finalizar as construcoes <div id="dummy_free" class="btn_time_reduction button_new js-item-btn-premium-action js-tutorial-queue-item-btn-premium-action type_building_queue type_instant_buy instant_buy type_free">
                <div class="left"></div>
                <div class="right"></div>
                <div class="caption js-caption">Gratis<div class="effect js-effect"></div></div>
            </div> (finaliza a construcao nos 4 segundos)
            </div>
        </div>
        `;
    };

    /* Ligue para acionar o autogratis */
    toggle = () => {
        if (!this.autogratis) {
            uw.$('#auto_gratis_title').css(
                'filter',
                'brightness(100%) saturate(186%) hue-rotate(241deg)',
            );
            this.autogratis = setInterval(this.main, 4000);
            this.console.log('Construcao Auto Gratis -> On');
        } else {
            uw.$('#auto_gratis_title').css('filter', '');
            clearInterval(this.autogratis);
            this.autogratis = null;
            this.console.log('Construcao Auto Gratis -> Off');
        }
        this.storage.save('enable_autogratis', !!this.autogratis);
    };

    /* Loop principal para o bot autogratis */
    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;
            }
            //const now = new Date()
            //if ((model.attributes.to_be_completed_at - now / 1000) > 300) continue;
        }
    };

    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, 60000)//intervalo de 10mn (1mn/60000)

        const addButton = () => {
            let box = $('.order_count');
            if (box.length) {
                let butt = $('<div/>', {
                    class: 'button_new',
                    id: 'autoCaveButton',
                    style: 'float: right; margin: 0px; left: 169px; position: absolute; top: 56px; width: 66px',
                    html: '<div onclick="window.modernBot.autoHide.toggle()"><div class="left"></div><div class="right"></div><div class="caption js-caption"> Auto <div class="effect js-effect"></div></div><div>'
                });
                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 `
        <div class="game_border" style="margin-bottom: 20px">
            <div class="game_border_top"></div>
            <div class="game_border_bottom"></div>
            <div class="game_border_left"></div>
            <div class="game_border_right"></div>
            <div class="game_border_corner corner1"></div>
            <div class="game_border_corner corner2"></div>
            <div class="game_border_corner corner3"></div>
            <div class="game_border_corner corner4"></div>
            <div id="auto_cave_title" style="cursor: pointer; filter: ${this.autogratis ? 'brightness(100%) saturate(186%) hue-rotate(241deg)' : ''
            }" class="game_header bold" onclick="window.modernBot.autoHide.toggle()"> Auto gruta <span class="command_count"></span>
                <div style="position: absolute; right: 10px; top: 4px; font-size: 10px;"> <svg style="width: 20px;margin-top: -3px;" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512" xml:space="preserve" fill="#000000"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <circle style="fill:#5ac827;" cx="256" cy="256" r="245.801"></circle> <polygon style="fill:#FFF;" points="195.825,391.629 376.351,256 195.825,120.371 "></polygon> <g> <path style="fill:#4D4D4D;" d="M256,512c-68.381,0-132.667-26.628-181.019-74.98C26.628,388.667,0,324.38,0,256 S26.628,123.333,74.981,74.98C123.333,26.628,187.619,0,256,0s132.667,26.628,181.019,74.98C485.372,123.333,512,187.62,512,256 s-26.628,132.667-74.981,181.02C388.667,485.372,324.381,512,256,512z M256,20.398C126.089,20.398,20.398,126.089,20.398,256 S126.089,491.602,256,491.602S491.602,385.911,491.602,256S385.911,20.398,256,20.398z"></path> <path style="fill:#4D4D4D;" d="M195.824,401.828c-1.553,0-3.115-0.355-4.557-1.075c-3.458-1.727-5.641-5.26-5.641-9.124V120.371 c0-3.864,2.185-7.397,5.641-9.124c3.458-1.726,7.593-1.351,10.685,0.97l180.526,135.629c2.564,1.927,4.073,4.948,4.073,8.154 s-1.508,6.228-4.073,8.154L201.951,399.783C200.15,401.137,197.994,401.828,195.824,401.828z M206.024,140.791v230.418L359.371,256 L206.024,140.791z"></path> <path style="fill:#4D4D4D;" d="M256,473.243c-5.632,0-10.199-4.566-10.199-10.199c0-5.633,4.567-10.199,10.199-10.199 c52.815,0,102.404-20.633,139.633-58.1c3.973-3.996,10.429-4.015,14.425-0.045c3.995,3.971,4.016,10.428,0.046,14.424 C369.016,450.471,314.287,473.243,256,473.243z"></path> <path style="fill:#4D4D4D;" d="M430.396,377.825c-1.886,0-3.793-0.522-5.498-1.617c-4.741-3.041-6.118-9.351-3.076-14.092 c1.514-2.36,2.998-4.788,4.411-7.216c2.834-4.867,9.077-6.516,13.945-3.684c4.868,2.833,6.518,9.077,3.684,13.945 c-1.56,2.681-3.201,5.363-4.873,7.97C437.043,376.168,433.754,377.825,430.396,377.825z"></path> </g> </g></svg> </div>
            </div>
            <div style="padding: 5px; font-weight: 600">
                Envia 1K de prata para a gruta, mas mantém 5K no armazem, Verificacao do estatos todos os 1 minute.
            </div>
        </div>
        `;
    };

    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("O Nivel 10 e exigido para todas as grutas das cidades");
        }
        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();

    const ironToStore = Math.min(iron - 5000, 1000);

    if (ironToStore > 0) {
        this.storeIron(this.activePolis, ironToStore);
    }
}

    /*
    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 `
        <div class="game_border" style="margin-bottom: 20px">
            ${this.getTitleHtml('auto_party_title', 'Auto Cultura', this.toggle, '', this.enable)}

            <div id="autoparty_types" class="split_content">
                <div style="padding: 5px;">
                ${this.getButtonHtml('autoparty_festival', 'Festival', this.triggerType, 'festival')}
                ${this.getButtonHtml('autoparty_procession', 'Desfile V.', this.triggerType, 'procession')}
                </div>

                <div style="padding: 5px;">
                ${this.getButtonHtml('autoparty_single', 'Unica C.', this.triggerSingle, 0)}
                ${this.getButtonHtml('autoparty_multiple', 'Todas C.', this.triggerSingle, 1)}
                </div>
            </div>
        </div>
        `;
	};

	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);
		}
	};

	/* Ligue para ligar/desligar */
	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);
	};

	/* Lista de retorno da cidade com celebração ativa */
	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;
				/* Evite que a promessa seja muito longa */
				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) {
			// único e múltiplo são trocados ...
			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;
				/* Evite que a promessa seja muito longa */
				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 `
        <div class="game_border" style="margin-bottom: 20px;">
            ${this.getTitleHtml('auto_rural_level', 'Subir o nível das aldeias bárbaras automaticamente', this.toggle, '', this.enable)}

            <div id="rural_lvl_buttons" style="padding: 5px">
                ${this.getButtonHtml('rural_lvl_1', 'lvl 1', this.setRuralLevel, 1)}
                ${this.getButtonHtml('rural_lvl_2', 'lvl 2', this.setRuralLevel, 2)}
                ${this.getButtonHtml('rural_lvl_3', 'lvl 3', this.setRuralLevel, 3)}
                ${this.getButtonHtml('rural_lvl_4', 'lvl 4', this.setRuralLevel, 4)}
                ${this.getButtonHtml('rural_lvl_5', 'lvl 5', this.setRuralLevel, 5)}
                ${this.getButtonHtml('rural_lvl_6', 'lvl 6', this.setRuralLevel, 6)}
            </div>
        </div>`;
	};

	/* gerar a lista contendo 1 polis por ilha */
	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;

		/* Obtenha controle com todas as áreas rurais bloqueadas */
		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;

		/* Se algumas zonas rurais ainda precisam ser desbloqueadas */
		if (locked.length > 0) {
			/* Os 5 primeiros rurais têm desconto*/
			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 {
			/* caso contrário, verifique cada nível uma vez por vez */
			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;
		}

		/* Desligamento automático quando o nível é atingido */
		this.toggle();
	};

	/*
        Solicitações de postagem
    */
	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 `
        <div class="game_border">
            <div class="game_border_top"></div>
            <div class="game_border_bottom"></div>
            <div class="game_border_left"></div>
            <div class="game_border_right"></div>
            <div class="game_border_corner corner1"></div>
            <div class="game_border_corner corner2"></div>
            <div class="game_border_corner corner3"></div>
            <div class="game_border_corner corner4"></div>
            <div class="game_header bold" style="position: relative; cursor: pointer" onclick="window.modernBot.autoRuralTrade.main()">
            <span style="z-index: 10; position: relative;">Troca de Recursos Automático entre as aldeias </span>
            <div id="res_progress_bar" class="progress_bar_auto"></div>
            <div style="position: absolute; right: 10px; top: 4px; font-size: 10px; z-index: 10"><svg style="width: 20px;margin-top: -3px;" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512" xml:space="preserve" fill="#000000"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <circle style="fill:#5ac827;" cx="256" cy="256" r="245.801"></circle> <polygon style="fill:#FFF;" points="195.825,391.629 376.351,256 195.825,120.371 "></polygon> <g> <path style="fill:#4D4D4D;" d="M256,512c-68.381,0-132.667-26.628-181.019-74.98C26.628,388.667,0,324.38,0,256 S26.628,123.333,74.981,74.98C123.333,26.628,187.619,0,256,0s132.667,26.628,181.019,74.98C485.372,123.333,512,187.62,512,256 s-26.628,132.667-74.981,181.02C388.667,485.372,324.381,512,256,512z M256,20.398C126.089,20.398,20.398,126.089,20.398,256 S126.089,491.602,256,491.602S491.602,385.911,491.602,256S385.911,20.398,256,20.398z"></path> <path style="fill:#4D4D4D;" d="M195.824,401.828c-1.553,0-3.115-0.355-4.557-1.075c-3.458-1.727-5.641-5.26-5.641-9.124V120.371 c0-3.864,2.185-7.397,5.641-9.124c3.458-1.726,7.593-1.351,10.685,0.97l180.526,135.629c2.564,1.927,4.073,4.948,4.073,8.154 s-1.508,6.228-4.073,8.154L201.951,399.783C200.15,401.137,197.994,401.828,195.824,401.828z M206.024,140.791v230.418L359.371,256 L206.024,140.791z"></path> <path style="fill:#4D4D4D;" d="M256,473.243c-5.632,0-10.199-4.566-10.199-10.199c0-5.633,4.567-10.199,10.199-10.199 c52.815,0,102.404-20.633,139.633-58.1c3.973-3.996,10.429-4.015,14.425-0.045c3.995,3.971,4.016,10.428,0.046,14.424 C369.016,450.471,314.287,473.243,256,473.243z"></path> <path style="fill:#4D4D4D;" d="M430.396,377.825c-1.886,0-3.793-0.522-5.498-1.617c-4.741-3.041-6.118-9.351-3.076-14.092 c1.514-2.36,2.998-4.788,4.411-7.216c2.834-4.867,9.077-6.516,13.945-3.684c4.868,2.833,6.518,9.077,3.684,13.945 c-1.56,2.681-3.201,5.363-4.873,7.97C437.043,376.168,433.754,377.825,430.396,377.825z"></path> </g> </g></svg></div>
            <span class="command_count"></span></div>

            <div class="split_content">
                <div id="autotrade_lvl_buttons" style="padding: 5px;">
                    ${this.getButtonHtml('autotrade_lvl_1', 'Prata', this.main, 'iron')}
                    ${this.getButtonHtml('autotrade_lvl_2', 'Pedra', this.main, 'stone')}
                    ${this.getButtonHtml('autotrade_lvl_3', 'Madeira', this.main, 'wood')}
                </div>

                <div id="min_rural_ratio" style="padding: 5px">
                    ${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)}
                </div>
            </div>
        </div>
        `;
	};

	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);
		}
	};

	/*  Comércio com todos os rurais*/
	main = async resouce => {
		if (resouce) {
			/* Definir botão desativado */
			[1, 2, 3, 4].forEach(i => {
				uw.$(`#autotrade_lvl_${i}`).addClass('disabled').css('cursor', 'auto');
			});
			this.trade_resouce = resouce;

			/* Defina o comércio atual para polis no índice 0 */
			this.total_trade = Object.keys(uw.ITowns.towns).length;
			this.done_trade = 0;

			/* Defina o intervalo */
			this.auto_trade_resouces_loop = setInterval(this.mainTradeLoop, 1500);
		} else {
			/* Clear the interval */
			clearInterval(this.auto_trade_resouces_loop);

			/* Reative os botões e defina o progresso para 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;

		/* Crie uma lista com todas as cidades agrícolas da atual polis insular */
		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 () => {
		/* Se for a última polis, então acione para parar */
		if (this.done_trade >= this.total_trade) {
			this.main();
			return;
		}

		/* realizar negociações com o índice atual */
		let towns = Object.keys(uw.ITowns.towns);
		await this.tradeWithRural(towns[this.done_trade]);

		/* atualizar barra de progresso */
		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 `
        <div class="game_border" style="margin-bottom: 20px">
            ${this.getTitleHtml('auto_trade', 'Auto Trade', '', '', this.enable_auto_farming)}
            <div class="split_content">
            <div id="trade_types" style="padding: 5px;">
                ${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)}
            </div>
            </div>
        </div> `;
	};
}

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;
	};

	/* devolver todas as negociações */
	async function getAllTrades() {
		return new Promise(function (myResolve, myReject) {
			uw.gpAjax.ajaxGet('town_overviews', 'trade_overview', {}, !0, e => {
				myResolve(e.movements);
			});
		});
	}

	/* Devolva a quantidade de tropas duráveis com os recursos atuais */
	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;
	}

	/* Retorne o id da cidade e do nome */
	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;
		}
	}

	/* Devolva a quantidade de tropas devidas por recursos em um comércio */
	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;
	}

	/* Devolver quantidade de recursos a serem enviados */
	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);

		/* Se a polis tiver todos os recursos -> nenhum recurso precisa ser enviado */
		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;
	}

	/* Definir aguardar e adicionar promessa */
	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);
			});
		});
	}

	/* Envie recursos da polis para o alvo, balanceados para aquela tropa, retorne a contagem atualizadat*/
	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', 'manticore'];
	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],
        manticore: [2, 1],
		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 `
        <div class="game_border" style="margin-bottom: 20px">
            <div class="game_border_top"></div>
            <div class="game_border_bottom"></div>
            <div class="game_border_left"></div>
            <div class="game_border_right"></div>
            <div class="game_border_corner corner1"></div>
            <div class="game_border_corner corner2"></div>
            <div class="game_border_corner corner3"></div>
            <div class="game_border_corner corner4"></div>
            <div class="game_header bold" style="position: relative; cursor: pointer">
            <span style="z-index: 10; position: relative; height: 17px; display: block;"><svg style="width: 22px;margin-top: -2px;" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="#109813"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path d="M18.48 18.5369H21M10.169 12.0441H21M12.801 5.55127L3 5.55127M21 5.55127H18.48M3 18.5369H12.801" stroke="#fff" stroke-width="1.5" stroke-linecap="round"></path> <path d="M17.88 18.6C17.88 19.9255 16.8055 21 15.48 21C14.1545 21 13.08 19.9255 13.08 18.6C13.08 17.2745 14.1545 16.2 15.48 16.2C16.8055 16.2 17.88 17.2745 17.88 18.6Z" stroke="#1de63c" stroke-width="1.5" stroke-linecap="round"></path> <path d="M9.47999 12C9.47999 13.3255 8.40548 14.4 7.07999 14.4C5.75451 14.4 4.67999 13.3255 4.67999 12C4.67999 10.6745 5.75451 9.6 7.07999 9.6C8.40548 9.6 9.47999 10.6745 9.47999 12Z" stroke="#dd4141" stroke-width="1.5" stroke-linecap="round"></path> <path d="M17.88 5.4C17.88 6.72548 16.8055 7.8 15.48 7.8C14.1545 7.8 13.08 6.72548 13.08 5.4C13.08 4.07452 14.1545 3 15.48 3C16.8055 3 17.88 4.07452 17.88 5.4Z" stroke="#ffd700" stroke-width="1.5" stroke-linecap="round"></path> </g></svg> </span>
            <span class="command_count"></span></div>

            <div class="split_content">
                <div style="padding: 5px;">
                ${this.getButtonHtml('train_passive', 'Normal', this.handleSpell, 0)}
                ${this.getButtonHtml('train_spell', 'feitiço', this.handleSpell, 1)}
                </div>

                <div id="train_percentuals" style="padding: 5px;">
                ${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)}
                </div>
            </div>
        </div>

        <div class="game_border">
            <div class="game_border_top"></div>
            <div class="game_border_bottom"></div>
            <div class="game_border_left"></div>
            <div class="game_border_right"></div>
            <div class="game_border_corner corner1"></div>
            <div class="game_border_corner corner2"></div>
            <div class="game_border_corner corner3"></div>
            <div class="game_border_corner corner4"></div>
            <div id="auto_train_title" class="game_header bold" style="position: relative; cursor: pointer" onclick="window.modernBot.autoTrain.trigger()">
            <span style="z-index: 10; position: relative;">Treinamento Automatico </span>
            <div style="position: absolute; right: 10px; top: 4px; font-size: 10px; z-index: 10"> <svg style="width: 22px;margin-top: -3px;" fill="#3df307" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <g fill-rule="evenodd"> <path d="M3.84378472,5.45744972 L5.08665407,6.70031907 C5.13788527,6.75155027 5.16666667,6.8210347 5.16666667,6.89348657 C5.16666667,7.04435977 5.04435977,7.16666667 4.89348657,7.16666667 L1.2731801,7.16666667 C1.1223069,7.16666667 1,7.04435977 1,6.89348657 L1,3.2731801 C1,3.20072823 1.02878139,3.13124381 1.0800126,3.0800126 C1.18669606,2.97332913 1.35966414,2.97332913 1.4663476,3.0800126 L2.66267797,4.27634296 C3.56481504,3.46700863 4.74391608,3 6,3 C8.76142375,3 11,5.23857625 11,8 C11,10.7614237 8.76142375,13 6,13 C4.92163683,13 3.89288448,12.6568129 3.04163107,12.0312326 C2.67076949,11.7586889 2.59106707,11.2371059 2.86361074,10.8662443 C3.13615441,10.4953827 3.65773742,10.4156803 4.028599,10.688224 C4.59610216,11.1052782 5.27973007,11.3333333 6,11.3333333 C7.84094917,11.3333333 9.33333333,9.84094917 9.33333333,8 C9.33333333,6.15905083 7.84094917,4.66666667 6,4.66666667 C5.19350009,4.66666667 4.43586982,4.95451814 3.84378472,5.45744972 Z M13,4 L14,4 C14.5522847,4 15,4.44771525 15,5 C15,5.55228475 14.5522847,6 14,6 L13,6 C12.4477153,6 12,5.55228475 12,5 C12,4.44771525 12.4477153,4 13,4 Z M13,7 L14,7 C14.5522847,7 15,7.44771525 15,8 C15,8.55228475 14.5522847,9 14,9 L13,9 C12.4477153,9 12,8.55228475 12,8 C12,7.44771525 12.4477153,7 13,7 Z M13,10 L14,10 C14.5522847,10 15,10.4477153 15,11 C15,11.5522847 14.5522847,12 14,12 L13,12 C12.4477153,12 12,11.5522847 12,11 C12,10.4477153 12.4477153,10 13,10 Z"> </path> </g> </g></svg> </div>
            <span class="command_count"></span></div>
            <div id="troops_lvl_buttons"></div>
        </div>
    `;
	};

	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; // Tipo de tropa não reconhecido
			}

			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) => {// todas os navios troop, researches, buildings implementar de 1 ou -1
			let gray = isGray(troop, researches, buildings);
			let color = 'red';

			if (gray) {
				return `
                <div class="auto_build_box">
                    <div class="item_icon auto_trade_troop" style="background-position: -${bg[0]}px -${bg[1]}px; filter: grayscale(1);"></div>
                </div>
                `;
			}
			return `
                <div class="auto_build_box">
                <div class="item_icon auto_trade_troop" onclick="window.modernBot.autoTrain.editTroopCount(${town_id}, '${troop}', 0)" style="background-position: -${bg[0]}px -${bg[1]}px; cursor: pointer">
                    <div class="auto_build_up_arrow" onclick="event.stopPropagation(); window.modernBot.autoTrain.editTroopCount(${town_id}, '${troop}', 1)" ></div>
                    <div class="auto_build_down_arrow" onclick="event.stopPropagation(); window.modernBot.autoTrain.editTroopCount(${town_id}, '${troop}', -1)"></div>
                    <p style="color: ${color}" id="troop_lvl_${troop}" class="auto_build_lvl"> 0 <p>
                </div>
            </div>`;
		};

        		const getTrooppHtml = (troop, bg) => {// todas as tropas terrestres implementar de 10 ou -10 (getTrooppHtml)
			let gray = isGray(troop);
			let color = 'red';

			if (gray) {
				return `
                <div class="auto_build_box">
                    <div class="item_icon auto_trade_troop" style="background-position: -${bg[0]}px -${bg[1]}px; filter: grayscale(1);"></div>
                </div>
                `;
			}
			return `
                <div class="auto_build_box">
                <div class="item_icon auto_trade_troop" onclick="window.modernBot.autoTrain.editTroopCount(${town_id}, '${troop}', 0)" style="background-position: -${bg[0]}px -${bg[1]}px; cursor: pointer">
                    <div class="auto_build_up_arrow" onclick="event.stopPropagation(); window.modernBot.autoTrain.editTroopCount(${town_id}, '${troop}', 10)" ></div>
                    <div class="auto_build_down_arrow" onclick="event.stopPropagation(); window.modernBot.autoTrain.editTroopCount(${town_id}, '${troop}', -10)"></div>
                    <p style="color: ${color}" id="troop_lvl_${troop}" class="auto_build_lvl"> 0 <p>
                </div>
            </div>`;
		};

		uw.$('#troops_lvl_buttons').html(`
        <div id="troops_settings_${town_id}">
            <div style="width: 600px; margin-bottom: 3px; display: inline-flex">
            <a class="gp_town_link" href="${town.getLinkFragment()}">${town.getName()}</a>
            <p style="font-weight: bold; margin: 0px 5px"> [${town.getPoints()} pts] </p>
            <p style="font-weight: bold; margin: 0px 5px"> </p>
            <div class="population_icon">
                <p id="troops_lvl_population"> ${this.getTotalPopulation(town_id)} <p>
            </div>
            </div>
            <div style="width: 831px; display: inline-flex; gap: 1px;">
            ${getTrooppHtml('sword', [400, 0])}
            ${getTrooppHtml('archer', [50, 100])}
            ${getTrooppHtml('hoplite', [300, 50])}
            ${getTrooppHtml('slinger', [250, 350])}
            ${getTrooppHtml('rider', [50, 350])}
            ${getTrooppHtml('chariot', [200, 100])}
            ${getTrooppHtml('catapult', [150, 150])}
            ${getTrooppHtml('manticore', [0, 300])}

            ${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])}
            </div>
        </div>`);
	};

	editTroopCount = (town_id, troop, count) => {
		/* reinicie o intervalo para evitar spam*/
		clearInterval(this.interval);
		this.interval = setInterval(this.main, 10000);

		const { units } = GameData;
		const { city_troops } = this;

		// Adicione a cidade ao objeto city_troops se ela ainda não existir
		if (!city_troops.hasOwnProperty(town_id)) city_troops[town_id] = {};

		if (count) {
			// Modifique a contagem com base no fato de a tecla Shift estar pressionada
			const index = count > 0 ? 0 : 1;
			count = this.shiftHeld ? count * this.SHIFT_LEVELS[troop][index] : count;
		} else {
			count = 10000;
		}

		// Verifique se a contagem de tropas pode ser aumentada sem exceder a capacidade populacional
		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);

		// Atualize a contagem de tropas para a cidade e tipo de tropa especificados
		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);
		}
	};

	/* retornar a contagem do tipo de pedido (naval ou terrestre) */
	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: viajar
		if (count < 0) return 0;

		/*Obtenha o valor devido com os recursos atuais da 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));

		/* Verifique se a população libre */
		let duable_with_pop = parseInt(resources.population / uw.GameData.units[troop].population); // para cada tropa

		/* Obtenha o máximo possível */
		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 é o percentual completo -> 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;
		}
	};

	/* Verifique a cidade indicada, para terreno ou terreno*/
	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;
		}
	};

	/* Retornar lista de cidades que possuem energia ativa */
	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;
	};

	/*Faça solicitação de build ao servidor */
	buildPost = (town_id, unit, count) => {
		let data = {
			unit_id: unit,
			amount: count,
			town_id: town_id,
		};
		uw.gpAjax.ajaxPost('building_barracks', 'build', data);
        uw.gpAjax.ajaxPost('building_temple', 'build', data);
	};

	/* devolver as cidades ativas */
	getActiveList = () => {
		if (!this.spell) return Object.keys(this.city_troops);
		return this.getPowerActive();
	};

	/* Função principal, chamada no 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 `<div class="console_modernbot" id="modern_console"><div>`;
	};

	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(`<p id="log_id_${i}">${e}</p>`);
		});
	};
}

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',
                manticore: 'J', // tentar acrescentar as manticore na lista
		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);
	}

	/* Passe um objeto de armazenamento, retorne-o codificado */
	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;
	}

	/* Dê o objeto de construção, retorne a string codificada */
	encode_building(obj) {
		let str = '';
		for (let item in obj) {
			str += this.ITEMS[item] + this.NUMBERS[obj[item]];
		}
		return str;
	}

	/* Forneça uma string codificada com construção, retorne o objeto correspondente */
	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);
	}
}

/*
    Crie uma nova janela
 */
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;

		/* Métodos privados */
		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.$(
				`<style id="${this.id}_custom_window_style">
                 #${this.id} .tab_icon { left: 23px;}
                 #${this.id} {top: -36px; right: 95px;}
                 #${this.id} .submenu_link {color: #000;}
                 #${this.id} .submenu_link:hover {text-decoration: none;}
                 #${this.id} li { float:left; min-width: 60px; }
                 </style>
                `,
			).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();
		};

		/* abre a janela */
		this.openWindow = function () {
			let wn = uw.Layout.wnd.getOpenFirst(uw.GPWindowMgr[`TYPE_${this.id}`]);

			/* se open for chamado, mas a janela já estiver aberta minimizada, maximize isso */
			if (wn) {
				if (wn.isMinimized()) {
					wn.maximizeWindow();
				}
				return;
			}

			let content = `<ul id="${this.id}" class="menu_inner"></ul><div id="${this.id}_content"> </div>`;
			uw.Layout.wnd.Create(uw.GPWindowMgr[`TYPE_${this.id}`]).setContent(content);
			/* Adicionar e redefinir guias */
			this.tabs.forEach((e) => {
				let html = `
                    <li><a id="${e.id}" class="submenu_link" href="#"><span class="left"><span class="right"><span class="middle">
                    <span class="tab_label"> ${e.title} </span>
                    </span></span></span></a></li>
                `;
				uw.$(html).appendTo(`#${this.id}`);
			});

			/* Adicione eventos às guias */
			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);
			});
			/* renderizar guia padrão*/
			this.renderTab(this.tabs[this.start_tab].id);
		};

		this.closeWindow = function () {
			uw.Layout.wnd.getOpenFirst(uw.GPWindowMgr[`TYPE_${this.id}`]).close();
		};

		/* Lidar com guia ativa */
		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:
// - desabilitar notificação de nota
// - adicionar logs no console

class ModernStorage extends Compressor {
	constructor() {
		super();
		this.check_done = 0;

		/* Adicionar evento para adicionar o botão nas notas */
		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;
	};

	/* Ligue para salvar a configuração no ID da nota fornecido */
	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;
	};

	/* Ligue para adicionar os botões */
	addButton = () => {
		this.check_done += 1;
		if ($('#modern_storage_load').length) return;

		const modern_settings_load = $('<div/>', {
			class: 'button_new',
			id: 'modern_storage_load',
			style: 'position: absolute; bottom: 5px; left: 6px; ',
			onclick: 'modernBot.storage.loadSettings()',
			html: '<div class="left"></div><div class="right"></div><div class="caption js-caption"> Load <div class="effect js-effect"></div></div>',
		});

		const modern_settings_save = $('<div/>', {
			class: 'button_new',
			id: 'modern_storage_save',
			style: 'position: absolute; bottom: 5px; left: 75px; ',
			onclick: 'modernBot.storage.saveSettings()',
			html: '<div class="left"></div><div class="right"></div><div class="caption js-caption"> Save <div class="effect js-effect"></div></div>',
		});

		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',
			'Esta operação irá sobrescrever a nota atual com as configurações locais do ModernBot JoeMan',
			() => {
				// acionado quando o usuário pressiona sim
				const note = this.getActiveNote();
				if (!note) return; // TODO: exibir um erro
				const content = this.saveSettingsNote(note.id);
				$('.preview_box').text(content);
			},
			() => {}
		);
	};

	loadSettings = () => {
		// TODO: verifique se a nota atual possui configurações
		uw.ConfirmationWindowFactory.openSimpleConfirmation(
			'ModernStorage',
			'Esta operação carregará as configurações da nota atual e substituirá as configurações locais',
			() => {
				// Acionado quando o usuário pressiona sim
				const note = this.getActiveNote();
				const { text } = note.attributes;
				let decoded;
				try {
					decoded = this.decode(JSON.parse(text));
				} catch {
					HumanMessage.error("Este pedido não contém as configurações");
					return;
				}

				this.saveStorage(decoded);
				location.reload();
			},
			() => {}
		);
	};

	/* Retornar a nota ativa atual */
	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];
	}
}

/* Configure o farm automático no objeto de janela */

class ModernBot {
	constructor() {
		this.console = new BotConsole();
		this.storage = new ModernStorage();

		this.autoGratis = new AutoGratis(this.console, this.storage);
		this.autoFarm = new AutoFarm(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 Lang.℗ PT_BR By JoeMan',
			size: [845, 300],
			tabs: [
				{
					title: 'Farm',
					id: 'farm',
					render: this.settingsFarm,
				},
				{
					title: 'Const',
					id: 'build',
					render: this.settingsBuild,
				},
				{
					title: 'Trein',
					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 = () => {
		/* Ativar */
		this.settingsFactory.activate();
		uw.$('.gods_area_buttons').append("<div class='circle_button modern_bot_settings' onclick='window.modernBot.settingsFactory.openWindow()'><div style='width: 27px; height: 27px; background: url(https://raw.githubusercontent.com/Sau1707/ModernBot/main/img/gear.png) no-repeat 6px 5px' class='icon js-caption'></div></div>");

		/* Adicionar evento ao menu da lista de polis */
		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 = `<div style='position: absolute; background-image: url(https://raw.githubusercontent.com/Sau1707/ModernBot/main/img/hammer_wrench.png); background-size: 19px 19px; margin: 1px; background-repeat: no-repeat; position: absolute; height: 20px; width: 25px; right: 18px;'></div>`;
				const build = `<div style='background-image: url(https://raw.githubusercontent.com/Sau1707/ModernBot/main/img/hammer_only.png); background-size: 19px 19px; margin: 1px; background-repeat: no-repeat; position: absolute; height: 20px; width: 25px; right: 18px;'></div>`;
				const troop = `<div style='background-image: url(https://raw.githubusercontent.com/Sau1707/ModernBot/main/img/wrench.png); background-size: 19px 19px; margin: 1px; background-repeat: no-repeat; position: absolute; height: 20px; width: 25px; right: 18px;'></div>`;
				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);
	};
}

setTimeout(async () => {
	/* */
	uw.modernBot = new ModernBot();
	setTimeout(() => uw.modernBot.settingsFactory.openWindow(), 500);
}, 1000);