Nome do script

Copy the code
Antiga versao 1.18.3 | Ultima versao 1.18.5 | versao atualizada


// ==UserScript==
// @name         ModernBot
// @author       Sau1707
// @description  A modern grepolis bot
// @version      1.18.3
// @match        http://*.grepolis.com/game/*
// @match        https://*.grepolis.com/game/*
// @updateURL    https://github.com/Sau1707/ModernBot/blob/main/dist/merged.user.js
// @downloadURL  https://github.com/Sau1707/ModernBot/blob/main/dist/merged.user.js
// @icon         https://raw.githubusercontent.com/Sau1707/ModernBot/main/img/gear.png
// @require		 http://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js
// ==/UserScript==

var uw;
if (typeof unsafeWindow == 'undefined') {
	uw = window;
} else {
	uw = unsafeWindow;
}

var style = document.createElement("style");
style.textContent = `.auto_build_up_arrow{background:url(https://gpit.innogamescdn.com/images/game/academy/up.png) no-repeat -2px -2px;width:18px;height:18px;position:absolute;right:-2px;bottom:12px;transform:scale(.8);cursor:pointer}.auto_build_down_arrow{background:url(https://gpit.innogamescdn.com/images/game/academy/up.png) no-repeat -2px -2px;width:18px;height:18px;position:absolute;right:-2px;bottom:-3px;transform:scale(.8) rotate(180deg);cursor:pointer}.auto_build_box{background:url(https://gpit.innogamescdn.com/images/game/academy/tech_frame.png) no-repeat 0 0;width:58px;height:59px;position:relative;overflow:hidden;display:inline-block;vertical-align:middle}.auto_build_building{position:absolute;top:4px;left:4px;width:50px;height:50px;background:url(https://gpit.innogamescdn.com/images/game/main/buildings_sprite_50x50.png) no-repeat 0 0}.auto_trade_troop{position:absolute;top:4px;left:4px;width:50px;height:50px;background:url(https://gpit.innogamescdn.com/images/game/autogenerated/units/unit_icons_50x50_654368f.png) no-repeat 0 0}.auto_build_lvl{position:absolute;bottom:3px;left:3px;margin:0;font-weight:700;font-size:12px;color:#fff;text-shadow:0 0 2px #000,1px 1px 2px #000,0 2px 2px #000}#buildings_lvl_buttons{padding:5px;max-height:400px;user-select:none}#troops_lvl_buttons{padding:5px;max-height:400px;user-select:none}.progress_bar_auto{position:absolute;z-index:1;height:100%;left:0;top:0;background-image:url(https://gpit.innogamescdn.com/images/game/border/header.png);background-position:0 -1px;filter:brightness(100%) saturate(186%) hue-rotate(241deg)}.modern_bot_settings{z-index:10;position:absolute;top:52px!important;right:116px!important}.console_modernbot{width:100%;height:100%;background-color:#000;color:#fff;font-family:monospace;font-size:16px;padding:20px;box-sizing:border-box;overflow-y:scroll;display:flex;flex-direction:column-reverse}#MODERN_BOT_content{height:100%}.console_modernbot p{margin:1px}.population_icon{background:url(https://gpit.innogamescdn.com/images/game/autogenerated/layout/layout_095495a.png) no-repeat -697px -647px;width:25px;height:20px;position:absolute;right:2px}.population_icon p{text-align:end;position:absolute;right:30px;padding:0;margin:0;color:#000;font-weight:700}.split_content{width:100%;display:inline-flex;justify-content:space-between}@keyframes rotateForever{from{transform:rotate(0)}to{transform:rotate(360deg)}}.rotate-forever{animation:rotateForever 5s linear infinite;transform-origin:16px 15px;filter:hue-rotate(72deg) saturate(2.5)}.enabled .game_header{filter:brightness(100%) saturate(186%) hue-rotate(241deg)}`;
document.head.appendChild(style);

class ModernUtil {
    /* CONSTANTS */

    REQUIREMENTS = {
        sword: {},
        archer: { research: 'archer' },
        hoplite: { research: 'hoplite' },
        slinger: { research: 'slinger' },
        catapult: { research: 'catapult' },
        rider: { research: 'rider', building: 'barracks', level: 10 },
        chariot: { research: 'chariot', building: 'barracks', level: 15 },
        big_transporter: { building: 'docks', level: 1 },
        small_transporter: { research: 'small_transporter', building: 'docks', level: 1 },
        bireme: { research: 'bireme', building: 'docks', level: 1 },
        attack_ship: { research: 'attack_ship', building: 'docks', level: 1 },
        trireme: { research: 'trireme', building: 'docks', level: 1 },
        colonize_ship: { research: 'colonize_ship', building: 'docks', level: 10 },
    };

    constructor(console, storage) {
        this.console = console;
        this.storage = storage;
    }
    /* Usage async this.sleep(ms) -> stop the code for ms */
    sleep = (ms, stdDev) => {
        // Check if a standard deviation is not provided
        if (typeof stdDev === 'undefined') return new Promise(resolve => setTimeout(resolve, ms));

        const mean = ms;
        let u = 0, v = 0;
        while (u === 0) u = Math.random(); // Converting [0,1) to (0,1)
        while (v === 0) v = Math.random();
        let num = Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v);

        num = num * stdDev + mean; // Scale and translate to desired mean and stdDev
        return new Promise(resolve => setTimeout(resolve, num));
    };

    /**
     * Generate a list of town IDs that are located on large islands.
     * A large island is defined as an island that has at least one town that is not on a small island.
     * @returns {Array} - Array of town IDs.
     */
    generateList = () => {
        const townList = uw.MM.getOnlyCollectionByName('Town').models;
        const islandsList = [];
        const polisList = [];

        for (const town of townList) {
            const { island_id, id, on_small_island } = town.attributes;

            if (on_small_island) continue; // Skip towns on small islands

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

        return polisList;
    };

    /**
     * Returns HTML code for a button with a specified ID, text, function, and optional properties.
     *
     * @param {string} id - The ID for the button.
     * @param {string} text - The text to display on the button.
     * @param {Function} fn - The function to call when the button is clicked.
     * @param {string} [props] - Optional properties to pass to the function.
     * @returns {string} - The HTML code for the button.
     */
    getButtonHtml(id, text, fn, props) {
        const name = this.constructor.name.charAt(0).toLowerCase() + this.constructor.name.slice(1);
        props = isNaN(parseInt(props)) ? `'${props}'` : props;
        const click = `window.modernBot.${name}.${fn.name}(${props || ''})`;

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

    /**
     * Returns the HTML for a game title with a clickable header that toggles a function.
     *
     * @param {string} id - The ID for the HTML element.
     * @param {string} text - The text to display in the title.
     * @param {function} fn - The function to toggle.
     * @param {string|number} props - The properties to pass to the function.
     * @param {boolean} enable - Whether the title is enabled or not.
     * @param {string} [desc='(click to toggle)'] - The description to display.
     * @returns {string} The HTML for the game title.
     */
    getTitleHtml(id, text, fn, props, enable, desc = '(click to toggle)') {
        const name = this.constructor.name.charAt(0).toLowerCase() + this.constructor.name.slice(1);
        props = isNaN(parseInt(props)) && props ? `"${props}"` : props;
        const click = `window.modernBot.${name}.${fn.name}(${props || ''})`;
        const filter = 'brightness(100%) saturate(186%) hue-rotate(241deg)';

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

    /**
     * Calculates the total population of a collection of units.
     *
     * @param {Object} units - The collection of units to count population for.
     * @returns {number} - The total population of all units in the collection.
     */
    countPopulation(obj) {
        const data = GameData.units;
        let total = 0;
        for (let key in obj) {
            total += data[key].population * obj[key];
        }
        return total;
    }

    isActive(type) {
        return uw.GameDataPremium.isAdvisorActivated(type);
    }


    /**
     * const button = elements.createButton('id', 'text', fn);
     * $('body').append(button);
     * To disable/enable the button:
     * button.addClass('disabled'); button.removeClass('disabled');
     * $('#id').addClass('disabled'); $('#id').removeClass('disabled');
     * NOTE: Even when the button is disabled, the click event will still be triggered.
     */
    createButton = (id, text, fn) => {
        const $button = $('<div>', {
            'id': id,
            'class': 'button_new',
        });

        // Add the left and right divs to the button
        $button.append($('<div>', { 'class': 'left' }));
        $button.append($('<div>', { 'class': 'right' }));
        $button.append($('<div>', {
            'class': 'caption js-caption',
            'html': `${text} <div class="effect js-effect"></div>`
        }));

        // Add the click event to the button if a function is provided
        if (fn) $(document).on('click', `#${id}`, fn);

        return $button;
    }


    /**
     * const title = elements.createTitle('id', 'text', fn, description);
     * $('body').append(title);
     * To disable/enable the title:
     * title.addClass('disabled'); title.removeClass('disabled');
     * $('#id').addClass('disabled'); $('#id').removeClass('disabled');
     * NOTE: Even when the title is disabled, the click event will still be triggered.
     */
    createTitle = (id, text, fn, desc = '(click to toggle)') => {
        const $div = $('<div>').addClass('game_header bold').attr('id', id).css({
            cursor: 'pointer',
            position: 'relative',
        }).html(text);

        const $span = $('<span>').addClass('command_count');
        const $descDiv = $('<div>').css({
            position: 'absolute',
            right: '10px',
            top: '4px',
            fontSize: '10px'
        }).text(desc);

        $div.append($span).append($descDiv);
        if (fn) $(document).on('click', `#${id}`, fn);

        return $('<div>')
            .append('<div class="game_border_top"></div>')
            .append('<div class="game_border_bottom"></div>')
            .append('<div class="game_border_left"></div>')
            .append('<div class="game_border_right"></div>')
            .append('<div class="game_border_corner corner1"></div>')
            .append('<div class="game_border_corner corner2"></div>')
            .append('<div class="game_border_corner corner3"></div>')
            .append('<div class="game_border_corner corner4"></div>')
            .append($div);
    }


    createActivity = (background) => {
        const $activity_wrap = $('<div class="activity_wrap"></div>');
        const $activity = $('<div class="activity"></div>');
        const $icon = $('<div class="icon"></div>').css({
            "background": background,
            "position": "absolute",
            "top": "-1px",
            "left": "-1px",
        });
        const $count = $('<div class="count js-caption"></div>').text(0);
        $icon.append($count);
        $activity.append($icon);
        $activity_wrap.append($activity);
        return { $activity, $count };
    }


    createPopup = (left, width, height, $content) => {
        const $box = $('<div class="sandy-box js-dropdown-list" id="toolbar_activity_recruits_list"></div>').css({
            "left": `${left}px`,
            "position": "absolute",
            "width": `${width}px`,
            "height": `${height}px`,
            "top": "29px",
            "margin-left": "0px",
            "display": "none",
        });

        // Make all the corners
        const $corner_tl = $('<div class="corner_tl"></div>');
        const $corner_tr = $('<div class="corner_tr"></div>');
        const $corner_bl = $('<div class="corner_bl"></div>');
        const $corner_br = $('<div class="corner_br"></div>');
        // Make all the borders
        const $border_t = $('<div class="border_t"></div>');
        const $border_b = $('<div class="border_b"></div>');
        const $border_l = $('<div class="border_l"></div>');
        const $border_r = $('<div class="border_r"></div>');
        // Make the middle
        const $middle = $('<div class="middle"></div>').css({
            "left": "10px",
            "right": "20px",
            "top": "14px",
            "bottom": "20px",
        });

        const $middle_content = $('<div class="content js-dropdown-item-list"></div>').append($content);
        $middle.append($middle_content);

        $box.append($corner_tl, $corner_tr, $corner_bl, $corner_br, $border_t, $border_b, $border_l, $border_r, $middle);
        return $box;
    }

}

// TO BE FINISCHED
class About {
	constructor() {
		this.checkVersion();
	}

	settings = () => {};

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

		/* Check that the version it's the current one */
		const installedVersion = GM_info.script.version;
		const file = await fetch('https://raw.githubusercontent.com/Sau1707/ModernBot/main/version.txt');
		const lastVersion = await file.text();

		if (lastVersion != installedVersion) {
			console.log('Versions differents');
		}
		console.log(lastVersion, installedVersion);
	};
}

class AntiRage extends ModernUtil {
	GOODS_ICONS = {
		athena: 'js-power-icon.animated_power_icon.animated_power_icon_45x45.power_icon45x45.power.strength_of_heroes',
		zeus: 'js-power-icon.animated_power_icon.animated_power_icon_45x45.power_icon45x45.power.fair_wind',
		artemis: 'js-power-icon.animated_power_icon.animated_power_icon_45x45.power_icon45x45.power.effort_of_the_huntress',
	};

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

		this.loop_funct = null;
		this.active_god_el = null;

		let commandId;
		const oldCreate = GPWindowMgr.Create;
		GPWindowMgr.Create = function (type, title, params, id) {
			if (type === GPWindowMgr.TYPE_ATK_COMMAND && id) commandId = id;
			return oldCreate.apply(this, arguments);
		};

		/* Attach event to attack opening */
		uw.$.Observer(uw.GameEvents.window.open).subscribe((e, data) => {
			if (data.context != 'atk_command') return;
			//const id = data.wnd.getID();

			let max = 10;
			const addSpell = () => {
				let spellMenu = $('#command_info-god')[0];
				if (!spellMenu) {
					if (max > 0) {
						max -= 1;
						setTimeout(addSpell, 50);
					}
					return;
				}
				$(spellMenu).on('click', this.trigger);

				this.command_id = commandId;
			};

			setTimeout(addSpell, 50);
		});
	}

	handleGod = good => {
		const godEl = $(`.god_mini.${good}.${good}`).eq(0);
		if (!godEl.length) return;

		const powerClassName = this.GOODS_ICONS[good];

		godEl.css({
			zIndex: 10,
			cursor: 'pointer',
			borderRadius: '100%',
			outline: 'none',
			boxShadow: '0px 0px 10px 5px rgba(255, 215, 0, 0.5)',
		});

		const powerEl = $(`.${powerClassName}`).eq(0);
		if (!powerEl.length) return;

		godEl.click(() => {
			// deactivate the previously active god

			if (this.active_god_el && this.active_god_el.get(0) === godEl.get(0)) {
				clearInterval(this.loop_funct);
				this.loop_funct = null;
				this.setColor(this.active_god_el.get(0), false);
				this.active_god_el = null;
				return;
			}

			if (this.active_god_el && this.active_god_el.get(0) !== godEl.get(0)) {
				clearInterval(this.loop_funct);
				this.setColor(this.active_god_el.get(0), false);
			}

			this.loop_funct = setInterval(this.clicker, 1000, powerEl);
			this.active_god_el = godEl;
			this.setColor(godEl.get(0), true);
		});
	};

	setColor = (elm, apply) => {
		if (apply) {
			elm.style.filter = 'brightness(100%) sepia(100%) hue-rotate(90deg) saturate(1500%) contrast(0.8)';
		} else {
			elm.style.filter = '';
		}
	};

	trigger = () => {
		setTimeout(() => {
			this.handleGod('athena');
			this.handleGod('zeus');
			this.handleGod('artemis');

			$('.js-god-box[data-god_id="zeus"]').find('.powers').append(`
            <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>Enchanted Rage</h4>
                                    <p> An Enchanted version of the normal rage </p> 
                                    <p> Made for who try to troll with the autoclick </p>
                                    <p><b> Cast Purification and Rage at the same time </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: disable if not enable
			$('#enchanted_rage').on({
				click: () => {
					if (!enable) return;
					this.enchanted('zeus');
				},
				mouseenter: event => {
					$('#popup_div_curtain').html(html);
					const $popupDiv = $('#popup_div');
					const offset = $popupDiv.offset();
					const height = $popupDiv.outerHeight();
					const width = $popupDiv.outerWidth();
					const left = event.pageX + 10;
					const top = event.pageY + 10;
					if (left + width > $(window).width()) {
						offset.left -= width;
					} else {
						offset.left = left;
					}
					if (top + height > $(window).height()) {
						offset.top -= height;
					} else {
						offset.top = top;
					}
					$popupDiv.css({
						left: offset.left + 'px',
						top: offset.top + 'px',
						display: 'block',
					});
				},
				mousemove: event => {
					const $popupDiv = $('#popup_div');
					if ($popupDiv.is(':visible')) {
						const offset = $popupDiv.offset();
						const height = $popupDiv.outerHeight();
						const width = $popupDiv.outerWidth();
						const left = event.pageX + 10;
						const top = event.pageY + 10;
						if (left + width > $(window).width()) {
							offset.left -= width;
						} else {
							offset.left = left;
						}
						if (top + height > $(window).height()) {
							offset.top -= height;
						} else {
							offset.top = top;
						}
						$popupDiv.css({
							left: offset.left + 'px',
							top: offset.top + 'px',
						});
					}
				},
				mouseleave: () => {
					$('#popup_div_curtain').html(default_popup);
				},
			});
		}, 100);
	};

	clicker = el => {
		let check = $('.js-power-icon.animated_power_icon.animated_power_icon_45x45.power_icon45x45.power').eq(0);
		if (!check.length) {
			clearInterval(this.loop_funct);
			this.loop_funct = null;
			this.active_god_el = null;
			return;
		}
		el.click();
		let delta_time = 500;
		let rand = 500 + Math.floor(Math.random() * delta_time);
		clearInterval(this.loop_funct);
		this.loop_funct = setInterval(this.clicker, rand, el);
	};

	enchanted = async type => {
		if (type === 'zeus') {
			this.cast(this.command_id, 'cleanse');
			//await this.sleep(1);
			this.cast(this.command_id, 'transformation');
		}
	};

	cast = (id, type) => {
		let data = {
			model_url: 'Commands',
			action_name: 'cast',
			arguments: {
				id: id,
				power_id: type,
			},
		};
		uw.gpAjax.ajaxPost('frontend_bridge', 'execute', data);
	};
}

/* 

<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(console, storage) {
        super(console, storage);

        // Create the buttons for the settings
        this.$title = this.createTitle('auto_autobootcamp', 'Auto Bootcamp', this.toggle, '(click to toggle)');
        this.$button_only_off = this.createButton('autobootcamp_off', 'Only off', this.triggerUseDef);
        this.$button_off_def = this.createButton('autobootcamp_def', 'Off & Def', this.triggerUseDef);
        this.$settings = this.createSettingsHtml();

        // Save the state of the auto bootcamp
        if (this.storage.load('ab_active', false)) this.toggle();
        if (this.storage.load('bootcamp_use_def', false)) this.triggerUseDef();

        // Attach the observer to the window open event
        uw.$.Observer(GameEvents.window.open).subscribe("modernAttackSpot", this.updateWindow);
    }

    updateWindow = (event, handler) => {
        if (!handler.attributes || handler.attributes.window_type !== 'attack_spot') return

        const cid = handler.cid;
        const $window = $(`#window_${cid}`);

        // Add height to the window
        $window.css('height', '660px');

        // Wait for the content to be loaded
        const interval = setInterval(() => {
            const $content = $window.find('.window_content');
            if ($content.length === 0) return;
            clearInterval(interval);
            $content.append(this.$settings);
        }, 100);
    }

    // Add the settings to the window, keep this for backwards compatibility
    settings = () => {
        return ""
    }

    createSettingsHtml = () => {
        // Create the settings box
        const $div = $('<div>')
        $div.css({
            display: 'flex',
            justifyContent: 'center',
            alignItems: 'center',
            padding: '10px',
        });

        // Add the buttons to the settings box
        $div.append(this.$button_only_off);
        $div.append(this.$button_off_def);

        // Create the box
        const $box = $('<div>')
        $box.addClass('game_border')
        $box.css({
            margin: '20px',
        });
        $box.append(this.$title)
        $box.append($div);

        return $box;
    };

    /* Update the settings title and buttons */
    updateSettings = () => {
        if (this.use_def) {
            this.$button_only_off.addClass('disabled');
            this.$button_off_def.removeClass('disabled');
        } else {
            this.$button_off_def.addClass('disabled');
            this.$button_only_off.removeClass('disabled');
        }

        if (this.enable_auto_bootcamp) this.$title.addClass('enabled');
        else this.$title.removeClass('enabled');
    }

    // Toggle the use of def units
    triggerUseDef = () => {
        this.use_def = !this.use_def;
        this.storage.save('bootcamp_use_def', this.use_def);
        this.updateSettings();
    };

    toggle = () => {
        if (!this.enable_auto_bootcamp) {
            this.enable_auto_bootcamp = setInterval(this.main, 4000);
        } else {
            clearInterval(this.enable_auto_bootcamp);
            this.enable_auto_bootcamp = null;
        }
        this.storage.save('ab_active', !!this.enable_auto_bootcamp);
        this.updateSettings();
    };

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

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

        // Check if there is already an active attack
        if (MovementsUnits != null) {
            if (Object.keys(MovementsUnits).length > 0) {
                var attack_list = Object.keys(MovementsUnits);
                for (var i = 0; i < Object.keys(MovementsUnits).length; i++) {
                    if (MovementsUnits[attack_list[i]].attributes.destination_is_attack_spot) return false;
                    if (MovementsUnits[attack_list[i]].attributes.origin_is_attack_spot) return false;
                }
            }
        }

        // Get the units
        var units = { ...uw.ITowns.towns[uw.Game.townId].units() };
        delete units.militia;

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

        // Remove def units if the setting is off
        if (!this.use_def) {
            delete units.sword;
            delete units.archer;
        }

        // If there are not enough units, return
        // TODO: here check if the units are enough to attack
        if (Object.keys(units).length === 0) return false;

        // Send the attack
        this.postAttackBootcamp(units);

        return true;
    };

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

        // Stop if level is not found
        if (typeof model.getLevel() == 'undefined') {
            this.toggle();
            return true;
        }

        // Check if there is a reward
        let hasReward = model.hasReward();
        if (!hasReward) return false;

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

        // Check if the reward is stashable
        if (reward.stashable) this.stashBootcampReward();
        else this.useBootcampReward();

        return true;
    };

    /* Main function, call in loop */
    main = () => {
        if (this.rewardBootcamp()) return;
        if (this.attackBootcamp()) return;
    };

    /* Send post request to attack with the given units */
    postAttackBootcamp = units => {
        const data = {
            model_url: `PlayerAttackSpot/${uw.Game.player_id}`,
            action_name: 'attack',
            arguments: units,
        };
        uw.gpAjax.ajaxPost('frontend_bridge', 'execute', data);
    };

    /* Send requesto to the server to use the reward */
    useBootcampReward = () => {
        const data = {
            model_url: `PlayerAttackSpot/${uw.Game.player_id}`,
            action_name: 'useReward',
            arguments: {},
        };
        uw.gpAjax.ajaxPost('frontend_bridge', 'execute', data);
    };

    /* Send request to the server to stash the reward */
    stashBootcampReward = () => {
        const data = {
            model_url: `PlayerAttackSpot/${uw.Game.player_id}`,
            action_name: 'stashReward',
            arguments: {},
        };
        uw.gpAjax.ajaxPost('frontend_bridge', 'execute', data, 0, {
            error: this.useBootcampReward,
        });
    };
}

// var r = Math.round(e.building.points * Math.pow(e.building.points_factor, e.next_level)) - Math.round(e.building.points * Math.pow(e.building.points_factor, e.level))

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

        /* Load settings, the polis in the settings are the active */
        this.towns_buildings = this.storage.load('buildings', {});

        /* Check if shift is pressed */
        this.shiftHeld = false;

        /* Active always, check if the towns are in the active list */
        this.enable = setInterval(this.main, 20000);

        /* Add listener that change the Senate look */
        uw.$.Observer(GameEvents.window.open).subscribe("modernSenate", this.updateSenate);
    }

    settings = () => {
        /* Apply event to shift */
        requestAnimationFrame(() => {
            uw.$('#buildings_lvl_buttons').on('mousedown', e => {
                this.shiftHeld = e.shiftKey;
            });

            this.setPolisInSettings(uw.ITowns.getCurrentTown().id);
            this.updateTitle();

            uw.$.Observer(uw.GameEvents.town.town_switch).subscribe(() => {
                this.setPolisInSettings(uw.ITowns.getCurrentTown().id);
                this.updateTitle();
            });
        });

        return `
        <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()"> Auto Build <span class="command_count"></span>
                <div style="position: absolute; right: 10px; top: 4px; font-size: 10px;"> (click to toggle) </div>
            </div>
            <div id="buildings_lvl_buttons"></div>    
        </div> `;
    };


    /* Update the senate view */
    updateSenate = (event, handler) => {
        if (handler.context !== "building_senate") return;

        // Edit the width of the window to fit the new element
        handler.wnd.setWidth(850)

        // Compute the id of the window
        const id = `gpwnd_${handler.wnd.getID()}`

        // Loop until the element is found
        const updateView = () => {

            const interval = setInterval(() => {
                const $window = $('#' + id);

                const $mainTasks = $window.find('#main_tasks');
                if (!$mainTasks.length) return;

                $mainTasks.hide();

                let $newElement = $('<div></div>').append(this.settings());

                $newElement.css({
                    position: $mainTasks.css('position'),
                    left: $mainTasks.css('left') - 20,
                    top: $mainTasks.css('top'),
                });
                $mainTasks.after($newElement);

                // Center the techTree
                const $techTree = $window.find('#techtree');
                $techTree.css({
                    position: 'relative',
                    left: "40px",
                });

                // Edit the width of the 
                $window.css({
                    overflowY: 'visible',
                });

                clearInterval(interval);
            }, 10);

            // If the element is not found, stop the interval 
            setTimeout(() => {
                clearInterval(interval);
            }, 100);
        }

        // subscribe to set content event
        const oldSetContent = handler.wnd.setContent2;
        handler.wnd.setContent2 = (...params) => {
            updateView();
            oldSetContent(...params);
        }

    }

    /* Given the town id, set the polis in the settings menu */
    setPolisInSettings = town_id => {
        let town = uw.ITowns.towns[town_id];

        /* If the town is in the active list set*/
        let town_buildings = this.towns_buildings?.[town_id] ?? { ...town.buildings()?.attributes } ?? {};
        let buildings = { ...town.buildings().attributes };

        const getBuildingHtml = (building, bg) => {
            let color = 'lime';
            if (buildings[building] > town_buildings[building]) color = 'red';
            else if (buildings[building] < town_buildings[building]) color = 'orange';

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

        /* If the town is in a group, the the groups */
        const groups =
            `(${Object.values(uw.ITowns.getTownGroups())
                .filter(group => group.id > 0 && group.id !== -1 && group.towns[town_id])
                .map(group => group.name)
                .join(', ')})` || '';

        uw.$('[id="buildings_lvl_buttons"]').html(`
        <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>`);
    };

    /* call with town_id, building type and level to be added */
    editBuildingLevel = (town_id, name, d) => {
        const town = uw.ITowns.getTown(town_id);

        const { max_level, min_level } = uw.GameData.buildings[name];

        const town_buildings = this.towns_buildings?.[town_id] ?? { ...town.buildings()?.attributes } ?? {};
        const townBuildings = town.buildings().attributes;
        const current_lvl = parseInt(uw.$(`#build_lvl_${name}`).text());
        if (d) {
            /* if shift is pressed, add or remove 10 */
            d = this.shiftHeld ? d * 10 : d;

            /* Check if bottom or top overflow */
            town_buildings[name] = Math.min(Math.max(current_lvl + d, min_level), max_level);
        } else {
            if (town_buildings[name] == current_lvl) town_buildings[name] = Math.min(Math.max(50, min_level), max_level);
            else town_buildings[name] = townBuildings[name];
        }

        const color = town_buildings[name] > townBuildings[name] ? 'orange' : town_buildings[name] < townBuildings[name] ? 'red' : 'lime';

        uw.$(`#build_settings_${town_id} #build_lvl_${name}`).css('color', color).text(town_buildings[name]);

        if (town_id.toString() in this.towns_buildings) {
            this.towns_buildings[town_id] = town_buildings;
            this.storage.save('buildings', this.towns_buildings);
        }
    };

    isActive = town_id => {
        let town = uw.ITowns.towns[town_id];
        return !this.towns_buildings?.[town.id];
    };

    updateTitle = () => {
        let town = uw.ITowns.getCurrentTown();
        if (town.id.toString() in this.towns_buildings) {
            uw.$('[id="auto_build_title"]').css('filter', 'brightness(100%) saturate(186%) hue-rotate(241deg)');
        } else {
            uw.$('[id="auto_build_title"]').css('filter', '');
        }
    };

    /* Call to toggle on and off (trigger the current town) */
    toggle = () => {
        let town = uw.ITowns.getCurrentTown();

        if (!(town.id.toString() in this.towns_buildings)) {
            this.console.log(`${town.name}: Auto Build On`);
            this.towns_buildings[town.id] = {};
            let buildins = ['main', 'storage', 'farm', 'academy', 'temple', 'barracks', 'docks', 'market', 'hide', 'lumber', 'stoner', 'ironer', 'wall'];
            buildins.forEach(e => {
                let lvl = parseInt(uw.$(`#build_lvl_${e}`).text());
                this.towns_buildings[town.id][e] = lvl;
            });
            this.storage.save('buildings', this.towns_buildings);
        } else {
            delete this.towns_buildings[town.id];
            this.storage.save('buildings', this.towns_buildings);
            this.console.log(`${town.name}: Auto Build Off`);
        }

        this.updateTitle();
    };

    /* Main loop for building */
    main = async () => {
        for (let town_id of Object.keys(this.towns_buildings)) {
            /* If the town don't exists in list, remove it to prevent errors */
            if (!uw.ITowns.towns[town_id]) {
                delete this.towns_buildings[town_id];
                this.storage.save('buildings', this.towns_buildings);
                continue;
            }

            if (this.isFullQueue(town_id)) continue;

            /* If town is done, remove from the list */
            if (this.isDone(town_id)) {
                delete this.towns_buildings[town_id];
                this.storage.save('buildings', this.towns_buildings);
                this.updateTitle();
                const town = uw.ITowns.getTown(town_id);
                this.console.log(`${town.name}: Auto Build Done`);
                continue;
            }
            await this.getNextBuild(town_id);
        }
    };

    /* Make post request to the server to buildup the building */
    postBuild = async (type, town_id) => {
        const town = uw.ITowns.getTown(town_id);
        let { wood, stone, iron } = town.resources();
        let { resources_for, population_for } = uw.MM.getModels().BuildingBuildData[town_id].attributes.building_data[type];

        if (town.getAvailablePopulation() < population_for) return;
        const m = 20;
        if (wood < resources_for.wood + m || stone < resources_for.stone + m || iron < resources_for.iron + m) return;
        let data = {
            model_url: 'BuildingOrder',
            action_name: 'buildUp',
            arguments: { building_id: type },
            town_id: town_id,
        };
        uw.gpAjax.ajaxPost('frontend_bridge', 'execute', data);
        this.console.log(`${town.getName()}: buildUp ${type}`);
        await this.sleep(500);
    };

    /* Make post request to tear building down */
    postTearDown = async (type, town_id) => {
        let data = {
            model_url: 'BuildingOrder',
            action_name: 'tearDown',
            arguments: { building_id: type },
            town_id: town_id,
        };
        uw.gpAjax.ajaxPost('frontend_bridge', 'execute', data);
        await this.sleep(500);
    };

    /* return true if the quee is full */
    isFullQueue = town_id => {
        const town = uw.ITowns.getTown(town_id);
        if (uw.GameDataPremium.isAdvisorActivated('curator') && town.buildingOrders().length >= 7) {
            return true;
        }
        if (!uw.GameDataPremium.isAdvisorActivated('curator') && town.buildingOrders().length >= 2) {
            return true;
        }
        return false;
    };

    /* return true if building match polis */
    isDone = town_id => {
        const town = uw.ITowns.getTown(town_id);
        let buildings = town.getBuildings().attributes;
        for (let build of Object.keys(this.towns_buildings[town_id])) {
            if (this.towns_buildings[town_id][build] != buildings[build]) {
                return false;
            }
        }
        return true;
    };

    /* */
    getNextBuild = async town_id => {
        let town = ITowns.towns[town_id];

        /* livello attuale */
        let buildings = { ...town.getBuildings().attributes };

        /* Add the the list the current building progress */
        for (let order of town.buildingOrders().models) {
            if (order.attributes.tear_down) {
                buildings[order.attributes.building_type] -= 1;
            } else {
                buildings[order.attributes.building_type] += 1;
            }
        }
        /* livello in cui deve arrivare */
        let target = this.towns_buildings[town_id];

        /* Check if the building is duable, if yes build it and return true, else false  */
        const check = async (build, level) => {
            /* if the given is an array, randomically try all of the array */
            if (Array.isArray(build)) {
                build.sort(() => Math.random() - 0.5);
                for (let el of build) {
                    if (await check(el, level)) return true;
                }
                return false;
            }
            if (target[build] <= buildings[build]) return false;
            else if (buildings[build] < level) {
                await this.postBuild(build, town_id);
                return true;
            }
            return false;
        };

        const tearCheck = async build => {
            if (Array.isArray(build)) {
                build.sort(() => Math.random() - 0.5);
                for (let el of build) {
                    if (await tearCheck(el)) return true;
                }
                return false;
            }
            if (target[build] < buildings[build]) {
                await this.postTearDown(build, town_id);
                return true;
            }
            return false;
        };

        /* IF the docks is not build yet, then follow the tutorial */
        if (buildings.docks < 1) {
            if (await check('lumber', 3)) return;
            if (await check('stoner', 3)) return;
            if (await check('farm', 4)) return;
            if (await check('ironer', 3)) return;
            if (await check('storage', 4)) return;
            if (await check('temple', 3)) return;
            if (await check('main', 5)) return;
            if (await check('barracks', 5)) return;
            if (await check('storage', 5)) return;
            if (await check('stoner', 6)) return;
            if (await check('lumber', 6)) return;
            if (await check('ironer', 6)) return;
            if (await check('main', 8)) return;
            if (await check('farm', 8)) return;
            if (await check('market', 6)) return;
            if (await check('storage', 8)) return;
            if (await check('academy', 7)) return;
            if (await check('temple', 5)) return;
            if (await check('farm', 12)) return;
            if (await check('main', 15)) return;
            if (await check('storage', 12)) return;
            if (await check('main', 25)) return;
            if (await check('hide', 10)) return;
        }

        /* Resouces */
        // WALLS!
        if (await check('farm', 15)) return;
        if (await check(['storage', 'main'], 25)) return;
        if (await check('market', 4)) return;
        if (await check('hide', 10)) return;
        if (await check(['lumber', 'stoner', 'ironer'], 15)) return;
        if (await check(['academy', 'farm'], 36)) return;
        if (await check(['docks', 'barracks'], 10)) return;
        if (await check('wall', 25)) return;
        // terme
        if (await check(['docks', 'barracks', 'market'], 20)) return;
        if (await check('farm', 45)) return;
        if (await check(['docks', 'barracks', 'market'], 30)) return;
        if (await check(['lumber', 'stoner', 'ironer'], 40)) return;
        if (await check('temple', 30)) return;
        if (await check('storage', 35)) return;

        /* Demolish */
        let lista = ['lumber', 'stoner', 'ironer', 'docks', 'barracks', 'market', 'temple', 'academy', 'farm', 'hide', 'storage', 'wall'];
        if (await tearCheck(lista)) return;
        if (await tearCheck('main')) return;
    };
}

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

        // Load the settings
        this.timing = this.storage.load('af_level', 300000);
        this.percent = this.storage.load('af_percent', 1);
        this.active = this.storage.load('af_active', false);
        this.gui = this.storage.load('af_gui', false);

        // Create the elements for the new menu
        const { $activity, $count } = this.createActivity("url(https://gpit.innogamescdn.com/images/game/premium_features/feature_icons_2.08.png) no-repeat 0 -240px");
        this.$activity = $activity
        this.$count = $count
        this.$activity.on('click', this.toggle)

        this.createDropdown();
        this.updateButtons();

        this.timer = 0;
        this.lastTime = Date.now();
        if (this.active) this.active = setInterval(this.main, 1000);
    }

    /* Create the dropdown menu */
    createDropdown = () => {
        this.$content = $("<div></div>")
        this.$title = $("<p>Modern Farm</p>").css({ "text-align": "center", "margin": "2px", "font-weight": "bold", "font-size": "16px" })
        this.$content.append(this.$title)

        this.$duration = $("<p>Duration:</p>").css({ "text-align": "left", "margin": "2px", "font-weight": "bold" })
        this.$button5 = this.createButton("modern_farm_5", "5 min", this.toggleDuration)
        this.$button10 = this.createButton("modern_farm_10", "10 min", this.toggleDuration)
        this.$button20 = this.createButton("modern_farm_20", "20 min", this.toggleDuration)
        this.$content.append(this.$duration, this.$button5, this.$button10, this.$button20)

        this.$storage = $("<p>Storage:</p>").css({ "text-align": "left", "margin": "2px", "font-weight": "bold" })
        this.$button80 = this.createButton("modern_farm_80", "80%", this.toggleStorage).css({ "width": "70px" })
        this.$button90 = this.createButton("modern_farm_90", "90%", this.toggleStorage).css({ "width": "80px" })
        this.$button100 = this.createButton("modern_farm_100", "100%", this.toggleStorage).css({ "width": "80px" })
        this.$content.append(this.$storage, this.$button80, this.$button90, this.$button100)

        this.$gui = $("<p>Gui:</p>").css({ "text-align": "left", "margin": "2px", "font-weight": "bold" })
        this.$guiOn = this.createButton("modern_farm_gui_on", "ON", this.toggleGui)
        this.$guiOff = this.createButton("modern_farm_gui_off", "OFF", this.toggleGui)
        this.$content.append(this.$gui, this.$guiOn, this.$guiOff)

        this.$popup = this.createPopup(423, 250, 170, this.$content)
        this.dropdown_active = false

        // Open and close the dropdown with the mouse
        const close = () => {
            if (!this.dropdown_active) this.$popup.hide()
            this.dropdown_active = false
        }

        const open = () => {
            if (this.dropdown_active) this.$popup.show()
        }

        this.$activity.on({
            mouseenter: () => {
                this.dropdown_active = true
                setTimeout(open, 1000)
            },
            mouseleave: () => {
                this.dropdown_active = false
                setTimeout(close, 50)
            }
        })

        this.$popup.on({
            mouseenter: () => {
                this.dropdown_active = true
            },
            mouseleave: () => {
                this.dropdown_active = false
                setTimeout(close, 50)
            }
        })
    }

    /* Update the buttons */
    updateButtons = () => {
        this.$button5.addClass('disabled')
        this.$button10.addClass('disabled')
        this.$button20.addClass('disabled')
        this.$button80.addClass('disabled')
        this.$button90.addClass('disabled')
        this.$button100.addClass('disabled')

        if (this.timing == 300000) this.$button5.removeClass('disabled')
        if (this.timing == 600000) this.$button10.removeClass('disabled')
        if (this.timing == 1200000) this.$button20.removeClass('disabled')

        if (this.percent == 0.8) this.$button80.removeClass('disabled')
        if (this.percent == 0.9) this.$button90.removeClass('disabled')
        if (this.percent == 1) this.$button100.removeClass('disabled')

        if (!this.active) {
            this.$count.css('color', "red")
            this.$count.text("")
        }

        this.$guiOn.addClass('disabled')
        this.$guiOff.addClass('disabled')
        if (this.gui) this.$guiOn.removeClass('disabled')
        else this.$guiOff.removeClass('disabled')
    }

    toggleDuration = (event) => {
        const { id } = event.currentTarget

        // Update the timer
        if (id == "modern_farm_5") this.timing = 300_000
        if (id == "modern_farm_10") this.timing = 600_000
        if (id == "modern_farm_20") this.timing = 1_200_000

        // Save the settings and update the buttons
        this.storage.save('af_level', this.timing);
        this.updateButtons()
    }

    toggleStorage = (event) => {
        const { id } = event.currentTarget

        // Update the percent
        if (id == "modern_farm_80") this.percent = 0.8
        if (id == "modern_farm_90") this.percent = 0.9
        if (id == "modern_farm_100") this.percent = 1

        // Save the settings and update the buttons
        this.storage.save('af_percent', this.percent);
        this.updateButtons()
    }


    toggleGui = (event) => {
        const { id } = event.currentTarget

        // Update the gui
        if (id == "modern_farm_gui_on") this.gui = true
        if (id == "modern_farm_gui_off") this.gui = false

        // Save the settings and update the buttons
        this.storage.save('af_gui', this.gui);
        this.updateButtons()
    }

    /* generate the list containing 1 polis per island */
    generateList = () => {
        const islands_list = new Set();
        const polis_list = [];
        let minResource = 0;
        let min_percent = 0;

        const { models: towns } = uw.MM.getOnlyCollectionByName('Town');

        for (const town of towns) {
            const { on_small_island, island_id, id } = town.attributes;
            if (on_small_island || islands_list.has(island_id)) continue;

            // Check the min percent for each town
            const { wood, stone, iron, storage } = uw.ITowns.getTown(id).resources();
            minResource = Math.min(wood, stone, iron);
            min_percent = minResource / storage;

            islands_list.add(island_id);
            polis_list.push(town.id);
            // if (min_percent < this.percent) continue;
        }

        return polis_list;
    };

    toggle = () => {
        if (this.active) {
            clearInterval(this.active);
            this.active = null;
            this.updateButtons();
        }
        else {
            this.updateTimer();
            this.active = setInterval(this.main, 1000);
        }

        // Save the settings
        this.storage.save('af_active', !!this.active);
    };

    /* return the time before the next collection */
    getNextCollection = () => {
        const { models } = uw.MM.getCollections().FarmTownPlayerRelation[0];

        const lootCounts = {};
        for (const model of models) {
            const { lootable_at } = model.attributes;
            lootCounts[lootable_at] = (lootCounts[lootable_at] || 0) + 1;
        }

        let maxLootableTime = 0;
        let maxValue = 0;
        for (const lootableTime in lootCounts) {
            const value = lootCounts[lootableTime];
            if (value < maxValue) continue;
            maxLootableTime = lootableTime;
            maxValue = value;
        }

        const seconds = maxLootableTime - Math.floor(Date.now() / 1000);
        return seconds > 0 ? seconds * 1000 : 0;
    };

    /* Call to update the timer */
    updateTimer = () => {
        const currentTime = Date.now();
        this.timer -= currentTime - this.lastTime;
        this.lastTime = currentTime;

        // Update the count
        const isCaptainActive = uw.GameDataPremium.isAdvisorActivated('captain');
        this.$count.text(Math.round(Math.max(this.timer, 0) / 1000));
        this.$count.css('color', isCaptainActive ? "#1aff1a" : "yellow");
    };

    claim = async () => {
        const isCaptainActive = uw.GameDataPremium.isAdvisorActivated('captain');
        const polis_list = this.generateList();

        // If the captain is active, claim all the resources at once and fake the opening
        if (isCaptainActive && !this.gui) {
            await this.fakeOpening();
            await this.sleep(Math.random() * 2000 + 1000); // random between 1 second and 3
            await this.fakeSelectAll();
            await this.sleep(Math.random() * 2000 + 1000);
            if (this.timing <= 600_000) await this.claimMultiple(300, 600);
            if (this.timing > 600_000) await this.claimMultiple(1200, 2400);
            await this.fakeUpdate();

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

        if (isCaptainActive && this.gui) {
            await this.fakeGuiUpdate();
            return;
        }

        // If the captain is not active, claim the resources one by one, but limit the number of claims
        let max = 60;
        const { models: player_relation_models } = uw.MM.getOnlyCollectionByName('FarmTownPlayerRelation');
        const { models: farm_town_models } = uw.MM.getOnlyCollectionByName('FarmTown');
        const now = Math.floor(Date.now() / 1000);
        for (let town_id of polis_list) {
            let town = uw.ITowns.towns[town_id];
            let x = town.getIslandCoordinateX();
            let y = town.getIslandCoordinateY();

            for (let farm_town of farm_town_models) {
                if (farm_town.attributes.island_x != x) continue;
                if (farm_town.attributes.island_y != y) continue;

                for (let relation of player_relation_models) {
                    if (farm_town.attributes.id != relation.attributes.farm_town_id) continue;
                    if (relation.attributes.relation_status !== 1) continue;
                    if (relation.attributes.lootable_at !== null && now < relation.attributes.lootable_at) continue;

                    this.claimSingle(town_id, relation.attributes.farm_town_id, relation.id, Math.ceil(this.timing / 600_000));
                    await this.sleep(500);
                    if (!max) return;
                    else max -= 1;
                }
            }
        }

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

    /* Return the total resources of the polis in the list */
    getTotalResources = () => {
        const polis_list = this.generateList();

        let total = {
            wood: 0,
            stone: 0,
            iron: 0,
            storage: 0,
        };

        for (let town_id of polis_list) {
            const town = uw.ITowns.getTown(town_id);
            const { wood, stone, iron, storage } = town.resources();
            total.wood += wood;
            total.stone += stone;
            total.iron += iron;
            total.storage += storage;
        }

        return total;
    };

    main = async () => {
        // Check that the timer is not too high
        const next_collection = this.getNextCollection();
        if (next_collection && (this.timer > next_collection + 60 * 1_000 || this.timer < next_collection)) {
            this.timer = next_collection + Math.floor(Math.random() * 20_000) + 10_000;
        }

        // Claim resources when timer has passed
        if (this.timer < 1) {
            // Generate the list of polis and claim resources
            this.polis_list = this.generateList();

            // Claim the resources, stop the interval and restart it
            clearInterval(this.active);
            this.active = null;

            await this.claim();
            this.active = setInterval(this.main, 1000);

            // Set the new timer 
            const rand = Math.floor(Math.random() * 20_000) + 10_000;
            this.timer = this.timing + rand;
            if (this.timer < next_collection) this.timer = next_collection + rand;
        }

        // update the timer
        this.updateTimer();
    };

    /* Claim resources from a single polis */
    claimSingle = (town_id, farm_town_id, relation_id, option = 1) => {
        const data = {
            model_url: `FarmTownPlayerRelation/${relation_id}`,
            action_name: 'claim',
            arguments: {
                farm_town_id: farm_town_id,
                type: 'resources',
                option: option,
            },
            town_id: town_id,
        };
        uw.gpAjax.ajaxPost('frontend_bridge', 'execute', data);
    };

    /* Claim resources from multiple polis */
    claimMultiple = (base = 300, boost = 600) =>
        new Promise((myResolve, myReject) => {
            const polis_list = this.generateList();
            let data = {
                towns: polis_list,
                time_option_base: base,
                time_option_booty: boost,
                claim_factor: 'normal',
            };
            uw.gpAjax.ajaxPost('farm_town_overviews', 'claim_loads_multiple', data, false, () => myResolve());
        });

    /* Pretend that the window it's opening */
    fakeOpening = () =>
        new Promise((myResolve, myReject) => {
            uw.gpAjax.ajaxGet('farm_town_overviews', 'index', {}, false, async () => {
                await this.sleep(10);
                await this.fakeUpdate();
                myResolve();
            });
        });

    /* Fake the user selecting the list */
    fakeSelectAll = () =>
        new Promise((myResolve, myReject) => {
            const data = {
                town_ids: this.polislist,
            };
            uw.gpAjax.ajaxGet('farm_town_overviews', 'get_farm_towns_from_multiple_towns', data, false, () => myResolve());
        });

    /* Fake the window update*/
    fakeUpdate = () =>
        new Promise((myResolve, myReject) => {
            const town = uw.ITowns.getCurrentTown();
            const { attributes: booty } = town.getResearches();
            const { attributes: trade_office } = town.getBuildings();
            const data = {
                island_x: town.getIslandCoordinateX(),
                island_y: town.getIslandCoordinateY(),
                current_town_id: town.id,
                booty_researched: booty ? 1 : 0,
                diplomacy_researched: '',
                trade_office: trade_office ? 1 : 0,
            };
            uw.gpAjax.ajaxGet('farm_town_overviews', 'get_farm_towns_for_town', data, false, () => myResolve());
        });

    /* Fake the gui update */
    fakeGuiUpdate = () =>
        new Promise(async (myResolve, myReject) => {
            // Open the farm town overview
            $(".toolbar_button.premium .icon").trigger('mouseenter')
            await this.sleep(1019.39, 127.54)

            // Click on the farm town overview
            $(".farm_town_overview a").trigger('click')
            await this.sleep(1156.65, 165.62)

            // Select all the polis
            $(".checkbox.select_all").trigger("click")
            await this.sleep(1036.20, 135.69)

            // Claim the resources
            $("#fto_claim_button").trigger("click")
            await this.sleep(1036.20, 135.69)

            // Confirm the claim if needed
            const el = $(".confirmation .btn_confirm.button_new")
            if (el.length) {
                el.trigger("click")
                await this.sleep(1036.20, 135.69)
            }

            // Close the window
            $(".icon_right.icon_type_speed.ui-dialog-titlebar-close").trigger("click")
            myResolve();
        });
}

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

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

    settings = () => {
        return `
        <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()"> Auto Gratis <span class="command_count"></span>
                <div style="position: absolute; right: 10px; top: 4px; font-size: 10px;"> (click to toggle) </div>
            </div>
            <div style="padding: 5px; font-weight: 600">
                Trigger to automatically press the <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> button (try every 4 seconds)
            </div>    
        </div>
        `;
    };

    /* Call to trigger the Auto Gratis */
    toggle = () => {
        if (!this.autogratis) {
            uw.$('#auto_gratis_title').css(
                'filter',
                'brightness(100%) saturate(186%) hue-rotate(241deg)',
            );
            this.autogratis = setInterval(this.main, 4000);
        } else {
            uw.$('#auto_gratis_title').css('filter', '');
            clearInterval(this.autogratis);
            this.autogratis = null;
        }
        this.storage.save('enable_autogratis', !!this.autogratis);
    };

    /* Main loop for the autogratis bot */
    main = () => {
        const el = uw.$('.type_building_queue.type_free').not('#dummy_free');
        if (el.length) el.click();

        const town = uw.ITowns.getCurrentTown();
        for (let model of town.buildingOrders().models) {
            if (model.attributes.building_time < 300) {
                this.callGratis(town.id, model.id)
                return;
            }
        }
    };

    /* Post request to call the gratis */
    callGratis = (town_id, order_id) => {
        const data = {
            "model_url": `BuildingOrder/${order_id}`,
            "action_name": "buyInstant",
            "arguments": {
                "order_id": order_id
            },
            "town_id": town_id
        }
        uw.gpAjax.ajaxPost('frontend_bridge', 'execute', data);
    }
}

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

        this.activePolis = this.storage.load('autohide_active', 0);

        setInterval(this.main, 5000)

        const addButton = () => {
            let box = $('.order_count');
            if (box.length) {
                let butt = $('<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 Hide <span class="command_count"></span>
                <div style="position: absolute; right: 10px; top: 4px; font-size: 10px;"> (click to toggle) </div>
            </div>
            <div style="padding: 5px; font-weight: 600">
                Check every 5 seconds, if there is more then 5000 iron store it in the hide
            </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("Hide must be at level 10");
        }
        this.storage.save("autohide_active", this.activePolis)
        this.updateSettings(town.id)
    }

    updateSettings = (town_id) => {
        if (town_id == this.activePolis) {
            $('#auto_cave_title').css({
                'filter': 'brightness(100%) saturate(186%) hue-rotate(241deg)'
            });
            $('#autoCaveButton').css({
                'filter': ' brightness(100%) sepia(100%) hue-rotate(90deg) saturate(1500%) contrast(0.8)'
            });
        } else {
            $('#auto_cave_title, #autoCaveButton').css({
                'filter': ''
            });
        }
    }

    main = () => {
        if (this.activePolis == 0) return;
        const town = uw.ITowns.towns[this.activePolis];
        const { iron } = town.resources()
        if (iron > 5000) {
            this.storeIron(this.activePolis, iron)
        }
    }

    storeIron = (town_id, count) => {
        const data = {
            "model_url": "BuildingHide",
            "action_name": "storeIron",
            "arguments": {
                "iron_to_store": count
            },
            "town_id": town_id,
        }

        uw.gpAjax.ajaxPost('frontend_bridge', 'execute', data);
    }

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

		this.active_types = this.storage.load('ap_types', { festival: false, procession: false });
		this.single = this.storage.load('ap_single', true);
		if (this.storage.load('ap_enable', false)) {
			this.enable = setInterval(this.main, 30000);
		}
	}

	// ${this.getButtonHtml('autoparty_lvl_1', 'Olympic', this.setRuralLevel, 1)}

	settings = () => {
		requestAnimationFrame(() => {
			this.triggerType('festival', false);
			this.triggerType('procession', false);

			this.triggerSingle(this.single);
		});

		return `
        <div class="game_border" style="margin-bottom: 20px">
            ${this.getTitleHtml('auto_party_title', 'Auto Party', this.toggle, '', this.enable)}

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

                <div style="padding: 5px;">
                ${this.getButtonHtml('autoparty_single', 'Single', this.triggerSingle, 0)}
                ${this.getButtonHtml('autoparty_multiple', 'All', 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);
		}
	};

	/* Call to toggle on/off */
	toggle = () => {
		if (!this.enable) {
			uw.$('#auto_party_title').css('filter', 'brightness(100%) saturate(186%) hue-rotate(241deg)');
			this.enable = setInterval(this.main, 30000);
		} else {
			uw.$('#auto_party_title').css('filter', '');
			clearInterval(this.enable);
			this.enable = null;
		}
		this.storage.save('ap_enable', !!this.enable);
	};

	/* Return list of town with active celebration */
	getCelebrationsList = type => {
		const celebrationModels = uw.MM.getModels().Celebration;
		if (typeof celebrationModels === 'undefined') return [];
		const triumphs = Object.values(celebrationModels)
			.filter(celebration => celebration.attributes.celebration_type === type)
			.map(triumph => triumph.attributes.town_id);
		return triumphs;
	};

	checkParty = async () => {
		let max = 10;
		let party = this.getCelebrationsList('party');
		if (this.single) {
			for (let town_id in uw.ITowns.towns) {
				if (party.includes(parseInt(town_id))) continue;
				let town = uw.ITowns.towns[town_id];
				if (town.getBuildings().attributes.academy < 30) continue;
				let { wood, stone, iron } = town.resources();
				if (wood < 15000 || stone < 18000 || iron < 15000) continue;
				this.makeCelebration('party', town_id);
				await this.sleep(750);
				max -= 1;
				/* Prevent that the promise it's to long */
				if (max <= 0) return;
			}
		} else {
			if (party.length > 1) return;
			this.makeCelebration('party');
		}
	};

	checkTriumph = async () => {
		let max = 10;
		let killpoints = uw.MM.getModelByNameAndPlayerId('PlayerKillpoints').attributes;
		let available = killpoints.att + killpoints.def - killpoints.used;
		if (available < 300) return;

		let triumph = this.getCelebrationsList('triumph');
		if (!this.single) {
			// single and multiple are swapped...
			for (let town_id in uw.ITowns.towns) {
				if (triumph.includes(parseInt(town_id))) continue;
				this.makeCelebration('triumph', town_id);
				await this.sleep(500);
				available -= 300;
				if (available < 300) return;
				max -= 1;
				/* Prevent that the promise it's to long */
				if (max <= 0) return;
			}
		} else {
			if (triumph.length > 1) return;
			this.makeCelebration('triumph');
		}
	};

	main = async () => {
		if (this.active_types['procession']) await this.checkTriumph();
		if (this.active_types['festival']) await this.checkParty();
	};

	makeCelebration = (type, town_id) => {
		if (typeof town_id === 'undefined') {
			let data = {
				celebration_type: type,
			};
			uw.gpAjax.ajaxPost('town_overviews', 'start_all_celebrations', data);
		} else {
			let data = {
				celebration_type: type,
				town_id: town_id,
			};
			uw.gpAjax.ajaxPost('building_place', 'start_celebration', data);
		}
	};
}

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

		this.rural_level = this.storage.load('enable_autorural_level', 1);
		if (this.storage.load('enable_autorural_level_active')) {
			this.enable = setInterval(this.main, 20000);
		}
	}

	settings = () => {
		requestAnimationFrame(() => {
			this.setRuralLevel(this.rural_level);
		});

		return `
        <div class="game_border" style="margin-bottom: 20px;">
            ${this.getTitleHtml('auto_rural_level', 'Auto Rural level', 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>`;
	};

	/* generate the list containing 1 polis per island */
	generateList = () => {
		let islands_list = [];
		let polis_list = [];

		let town_list = uw.MM.getOnlyCollectionByName('Town').models;

		for (let town of town_list) {
			if (town.attributes.on_small_island) continue;
			let { island_id, id } = town.attributes;
			if (!islands_list.includes(island_id)) {
				islands_list.push(island_id);
				polis_list.push(id);
			}
		}

		return polis_list;
	};

	setRuralLevel = n => {
		uw.$('#rural_lvl_buttons .button_new').addClass('disabled');
		uw.$(`#rural_lvl_${n}`).removeClass('disabled');

		if (this.rural_level != n) {
			this.rural_level = n;
			this.storage.save('enable_autorural_level', this.rural_level);
		}
	};

	toggle = () => {
		if (!this.enable) {
			uw.$('#auto_rural_level').css('filter', 'brightness(100%) saturate(186%) hue-rotate(241deg)');
			this.enable = setInterval(this.main, 20000);
		} else {
			uw.$('#auto_rural_level').css('filter', '');
			clearInterval(this.enable);
			this.enable = null;
		}
		this.storage.save('enable_autorural_level_active', !!this.enable);
	};

	main = async () => {
		let player_relation_models = uw.MM.getOnlyCollectionByName('FarmTownPlayerRelation').models;
		let farm_town_models = uw.MM.getOnlyCollectionByName('FarmTown').models;
		let killpoints = uw.MM.getModelByNameAndPlayerId('PlayerKillpoints').attributes;

		/* Get array with all locked rurals */
		const locked = player_relation_models.filter(model => model.attributes.relation_status === 0);

		/* Get killpoints */
		let available = killpoints.att + killpoints.def - killpoints.used;
		let unlocked = player_relation_models.length - locked.length;

		/* If some rurals still have to be unlocked */
		if (locked.length > 0) {
			/* The first 5 rurals have discount */
			const discounts = [2, 8, 10, 30, 50, 100];
			if (unlocked < discounts.length && available < discounts[unlocked]) return;
			else if (available < 100) return;

			let towns = this.generateList();
			for (let town_id of towns) {
				let town = uw.ITowns.towns[town_id];
				let x = town.getIslandCoordinateX(),
					y = town.getIslandCoordinateY();

				for (let farmtown of farm_town_models) {
					if (farmtown.attributes.island_x != x || farmtown.attributes.island_y != y) continue;

					for (let relation of locked) {
						if (farmtown.attributes.id != relation.attributes.farm_town_id) continue;
						this.unlockRural(town_id, relation.attributes.farm_town_id, relation.id);
						this.console.log(`Island ${farmtown.attributes.island_xy}: unlocked ${farmtown.attributes.name}`);
						return;
					}
				}
			}
		} else {
			/* else check each level once at the time */
			let towns = this.generateList();
			let expansion = false;
			const levelCosts = [1, 5, 25, 50, 100];
			for (let level = 1; level < this.rural_level; level++) {
				if (available < levelCosts[level - 1]) return;

				for (let town_id of towns) {
					let town = uw.ITowns.towns[town_id];
					let x = town.getIslandCoordinateX();
					let y = town.getIslandCoordinateY();

					for (let farmtown of farm_town_models) {
						if (farmtown.attributes.island_x != x) continue;
						if (farmtown.attributes.island_y != y) continue;

						for (let relation of player_relation_models) {
							if (farmtown.attributes.id != relation.attributes.farm_town_id) {
								continue;
							}
							if (relation.attributes.expansion_at) {
								expansion = true;
								continue;
							}
							if (relation.attributes.expansion_stage > level) continue;
							this.upgradeRural(town_id, relation.attributes.farm_town_id, relation.attributes.id);
							this.console.log(`Island ${farmtown.attributes.island_xy}: upgraded ${farmtown.attributes.name}`);
							return;
						}
					}
				}
			}

			if (expansion) return;
		}

		/* Auto turn off when the level is reached */
		this.toggle();
	};

	/* 
        Post requests
    */
	unlockRural = (town_id, farm_town_id, relation_id) => {
		let data = {
			model_url: `FarmTownPlayerRelation/${relation_id}`,
			action_name: 'unlock',
			arguments: {
				farm_town_id: farm_town_id,
			},
			town_id: town_id,
		};
		uw.gpAjax.ajaxPost('frontend_bridge', 'execute', data);
	};

	upgradeRural = (town_id, farm_town_id, relation_id) => {
		let data = {
			model_url: `FarmTownPlayerRelation/${relation_id}`,
			action_name: 'upgrade',
			arguments: {
				farm_town_id: farm_town_id,
			},
			town_id: town_id,
		};
		uw.gpAjax.ajaxPost('frontend_bridge', 'execute', data);
	};
}

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

		this.ratio = this.storage.load('rt_ratio', 5);
	}

	settings = () => {
		requestAnimationFrame(() => {
			this.setMinRatioLevel(this.ratio);
		});

		return `
        <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;">Auto Trade resouces </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"> (click to stop) </div>
            <span class="command_count"></span></div>

            <div class="split_content">
                <div id="autotrade_lvl_buttons" style="padding: 5px;">
                    ${this.getButtonHtml('autotrade_lvl_1', 'Iron', this.main, 'iron')}
                    ${this.getButtonHtml('autotrade_lvl_2', 'Stone', this.main, 'stone')}
                    ${this.getButtonHtml('autotrade_lvl_3', 'Wood', 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);
		}
	};

	/*  Trade with all rurals*/
	main = async resouce => {
		if (resouce) {
			/* Set button disabled */
			[1, 2, 3, 4].forEach(i => {
				uw.$(`#autotrade_lvl_${i}`).addClass('disabled').css('cursor', 'auto');
			});
			this.trade_resouce = resouce;

			/* Set the current trade to polis at index 0 */
			this.total_trade = Object.keys(uw.ITowns.towns).length;
			this.done_trade = 0;

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

			/* Re-enable buttons and set progress to 0 */
			uw.$('#res_progress_bar').css('width', 0);
			[1, 2, 3, 4].forEach(i => {
				uw.$(`#autotrade_lvl_${i}`).removeClass('disabled').css('cursor', 'pointer');
			});
		}
	};

	tradeWithRural = async polis_id => {
		let town = uw.ITowns.towns[polis_id];
		if (!town) return;
		if (town.getAvailableTradeCapacity() < 3000) return;
		//if (this.check_for_hide && town.getBuildings().attributes.hide < 10) return;

		let farm_town_models = uw.MM.getOnlyCollectionByName('FarmTown').models;
		let player_relation_models = uw.MM.getOnlyCollectionByName('FarmTownPlayerRelation').models;

		/* Create list with all the farmtown in current island polis */
		let x = town.getIslandCoordinateX(),
			y = town.getIslandCoordinateY();
		let resources = town.resources();

		for (const farmtown of farm_town_models) {
			if (farmtown.attributes.island_x != x || farmtown.attributes.island_y != y) continue;
			if (farmtown.attributes.resource_offer != this.trade_resouce) continue;
			if (resources[farmtown.attributes.resource_demand] < 3000) continue;

			for (const relation of player_relation_models) {
				if (farmtown.attributes.id != relation.attributes.farm_town_id) continue;
				if (relation.attributes.current_trade_ratio < this.min_rural_ratio * 0.25) continue;
				if (town.getAvailableTradeCapacity() < 3000) continue;
				this.tradeRuralPost(relation.attributes.farm_town_id, relation.attributes.id, town.getAvailableTradeCapacity(), town.id);
				await this.sleep(750);
			}
		}
	};

	mainTradeLoop = async () => {
		/* If last polis, then trigger to stop */
		if (this.done_trade >= this.total_trade) {
			this.main();
			return;
		}

		/* perform trade with current index */
		let towns = Object.keys(uw.ITowns.towns);
		await this.tradeWithRural(towns[this.done_trade]);

		/* update progress bar */
		uw.$('#res_progress_bar').css('width', `${(this.done_trade / this.total_trade) * 100}%`);

		this.done_trade += 1;
	};

	tradeRuralPost = (farm_town_id, relation_id, count, town_id) => {
		if (count < 100) return;
		const data = {
			model_url: `FarmTownPlayerRelation/${relation_id}`,
			action_name: 'trade',
			arguments: { farm_town_id: farm_town_id, amount: count > 3000 ? 3000 : count },
			town_id: town_id,
		};
		uw.gpAjax.ajaxPost('frontend_bridge', 'execute', data);
	};
}

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

	settings = () => {
		return `
        <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;
	};

	/* return all the trades */
	async function getAllTrades() {
		return new Promise(function (myResolve, myReject) {
			uw.gpAjax.ajaxGet('town_overviews', 'trade_overview', {}, !0, e => {
				myResolve(e.movements);
			});
		});
	}

	/* Return the ammount of toops duable with the current resouces */
	function getCount(targtet_id, troop) {
		let target_polis = uw.ITowns.towns[targtet_id];
		if (!target_polis) return {};
		let resources = target_polis.resources();
		let wood = resources.wood / uw.GameData.units[troop].resources.wood;
		let stone = resources.stone / uw.GameData.units[troop].resources.stone;
		let iron = resources.iron / uw.GameData.units[troop].resources.iron;
		let min = Math.min(wood, stone, iron);
		return min;
	}

	/* Return the id of the polis from the name */
	function getTradeTarget(html) {
		const element = document.createElement('div');
		element.innerHTML = html;
		let name = element.textContent;
		for (let town of Object.values(uw.ITowns.towns)) {
			if (town.name == name) return town.id;
		}
	}

	/* Return ammount of troops duable for resouces in a trade */
	function getCountFromTrade(trade, troop) {
		let wood = trade.res.wood / uw.GameData.units[troop].resources.wood;
		let stone = trade.res.stone / uw.GameData.units[troop].resources.stone;
		let iron = trade.res.iron / uw.GameData.units[troop].resources.iron;
		let min = Math.min(wood, stone, iron);
		return min;
	}

	/* Return ammount of resouces to be send */
	async function calculateAmmount(targtet_id, troop) {
		let target_polis = uw.ITowns.towns[targtet_id];
		if (!target_polis) return {};
		let current_count = {};

		let discount = uw.GeneralModifications.getUnitBuildResourcesModification(targtet_id, uw.GameData.units[troop]);
		let todo = parseInt(target_polis.getAvailablePopulation() / uw.GameData.units[troop].population) * discount;
		let in_polis = getCount(targtet_id, troop);

		/* If the polis has all the resouces -> no resouces has to be sent */
		todo -= in_polis;
		if (todo < 0) return 0;

		let trade = uw.MM.getCollections().Trade[0].models;
		let trades = await getAllTrades();
		for (let trade of trades) {
			if (getTradeTarget(trade.to.link) != targtet_id) continue;
			todo -= getCountFromTrade(trade, troop);
		}
		return todo;
	}

	function getCountWithTrade(targtet_id, troop) {
		let target_polis = uw.ITowns.towns[targtet_id];
		if (!target_polis) return {};
		let resources = target_polis.resources();
		let wood = resources.wood / uw.GameData.units[troop].resources.wood;
		let stone = resources.stone / uw.GameData.units[troop].resources.stone;
		let iron = resources.iron / uw.GameData.units[troop].resources.iron;
		let min_resouces = Math.min(wood, stone, iron); // min ammount
		let trade = target_polis.getAvailableTradeCapacity();
		let max_trade = trade / (uw.GameData.units[troop].resources.wood + uw.GameData.units[troop].resources.stone + uw.GameData.units[troop].resources.iron); // max tradable

		if (max_trade < min_resouces) return max_trade;
		else return min_resouces;
	}

	/* Set await and add promise */
	function sendTradeRequest(from_id, target_id, troop, count) {
		let data = {
			id: target_id,
			wood: uw.GameData.units[troop].resources.wood * count,
			stone: uw.GameData.units[troop].resources.stone * count,
			iron: uw.GameData.units[troop].resources.iron * count,
			town_id: from_id,
			nl_init: true,
		};

		return new Promise(function (myResolve, myReject) {
			uw.gpAjax.ajaxPost('town_info', 'trade', data, !0, () => {
				setTimeout(() => myResolve(), 500);
			});
		});
	}

	/* Send resouces from polis to target, balanced for that troop, return updated count*/
	async function sendBalance(polis_id, target_id, troop, count) {
		let troops_ammount = unit_counnt[troop];
		if (!troops_ammount) return 0;
		if (polis_id == target_id) return count;
		let sender_polis = uw.ITowns.towns[polis_id];
		let duable = getCount(polis_id, troop);
		if (duable < troops_ammount) return count;
		if (sender_polis.getAvailableTradeCapacity() < 500) return count;
		let duable_with_trade = getCountWithTrade(polis_id, troop);
		if (duable_with_trade < troops_ammount) return count;
		await sendTradeRequest(polis_id, target_id, troop, troops_ammount);
		return count - troops_ammount < 0 ? 0 : count - troops_ammount;
	}

	function sleep(time) {
		return new Promise(function (myResolve, myReject) {
			setTimeout(() => myResolve(), time);
		});
	}
}

class AutoTrain extends ModernUtil {
	POWER_LIST = ['call_of_the_ocean', 'spartan_training', 'fertility_improvement'];
	GROUND_ORDER = ['catapult', 'sword', 'archer', 'hoplite', 'slinger', 'rider', 'chariot'];
	NAVAL_ORDER = ['small_transporter', 'bireme', 'trireme', 'attack_ship', 'big_transporter', 'demolition_ship', 'colonize_ship'];
	SHIFT_LEVELS = {
		catapult: [5, 5],
		sword: [200, 50],
		archer: [200, 50],
		hoplite: [200, 50],
		slinger: [200, 50],
		rider: [100, 25],
		chariot: [100, 25],
		small_transporter: [10, 5],
		bireme: [50, 10],
		trireme: [50, 10],
		attack_ship: [50, 10],
		big_transporter: [50, 10],
		demolition_ship: [50, 10],
		colonize_ship: [5, 1],
	};

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

		this.spell = this.storage.load('at_spell', false);
		this.percentual = this.storage.load('at_per', 1);
		this.city_troops = this.storage.load('troops', {});
		this.shiftHeld = false;

		this.interval = setInterval(this.main, 10000);
	}

	settings = () => {
		requestAnimationFrame(() => {
			this.setPolisInSettings(uw.ITowns.getCurrentTown().id);
			this.updatePolisInSettings(uw.ITowns.getCurrentTown().id);
			this.handlePercentual(this.percentual);
			this.handleSpell(this.spell);

			uw.$.Observer(uw.GameEvents.town.town_switch).subscribe(() => {
				this.setPolisInSettings(uw.ITowns.getCurrentTown().id);
				this.updatePolisInSettings(uw.ITowns.getCurrentTown().id);
			});

			uw.$('#troops_lvl_buttons').on('mousedown', e => {
				this.shiftHeld = e.shiftKey;
			});
		});

		return `
        <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;"> Settings </span>
            <span class="command_count"></span></div>

            <div class="split_content">
                <div style="padding: 5px;">
                ${this.getButtonHtml('train_passive', 'Passive', this.handleSpell, 0)}
                ${this.getButtonHtml('train_spell', 'Spell', 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;">Auto Train </span>
            <div style="position: absolute; right: 10px; top: 4px; font-size: 10px; z-index: 10"> (click to reset) </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; // Troop type not recognized
			}

			const { research, building, level } = this.REQUIREMENTS[troop];
			if (research && !researches[research]) return true;
			if (building && buildings[building] < level) return true;
			return false;
		};

		const getTroopHtml = (troop, bg) => {
			let gray = isGray(troop, researches, buildings);
			let color = 'red';

			if (gray) {
				return `
                <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>`;
		};

		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;">
            ${getTroopHtml('sword', [400, 0])}
            ${getTroopHtml('archer', [50, 100])}
            ${getTroopHtml('hoplite', [300, 50])}
            ${getTroopHtml('slinger', [250, 350])}
            ${getTroopHtml('rider', [50, 350])}
            ${getTroopHtml('chariot', [200, 100])}
            ${getTroopHtml('catapult', [150, 150])}

            ${getTroopHtml('big_transporter', [0, 150])}
            ${getTroopHtml('small_transporter', [300, 350])}
            ${getTroopHtml('bireme', [50, 150])}
            ${getTroopHtml('demolition_ship', [250, 0])}
            ${getTroopHtml('attack_ship', [150, 100])}
            ${getTroopHtml('trireme', [400, 250])}
            ${getTroopHtml('colonize_ship', [50, 200])}
            </div>
        </div>`);
	};

	editTroopCount = (town_id, troop, count) => {
		/* restart the interval to prevent spam*/
		clearInterval(this.interval);
		this.interval = setInterval(this.main, 10000);

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

		// Add the town to the city_troops object if it doesn't already exist
		if (!city_troops.hasOwnProperty(town_id)) city_troops[town_id] = {};

		if (count) {
			// Modify count based on whether the shift key is held down
			const index = count > 0 ? 0 : 1;
			count = this.shiftHeld ? count * this.SHIFT_LEVELS[troop][index] : count;
		} else {
			count = 10000;
		}

		// Check if the troop count can be increased without exceeding population capacity
		const total_pop = this.getTotalPopulation(town_id);
		const used_pop = this.countPopulation(this.city_troops[town_id]);
		const unit_pop = units[troop].population;
		if (total_pop - used_pop < unit_pop * count) count = parseInt((total_pop - used_pop) / unit_pop);

		// Update the troop count for the specified town and troop type
		if (troop in city_troops[town_id]) city_troops[town_id][troop] += count;
		else city_troops[town_id][troop] = count;

		/* Clenaup */
		if (city_troops[town_id][troop] <= 0) delete city_troops[town_id][troop];
		if (uw.$.isEmptyObject(city_troops[town_id])) delete this.city_troops[town_id];

		this.updatePolisInSettings(town_id);
		this.storage.save('troops', this.city_troops);
	};

	updatePolisInSettings = town_id => {
		const { units } = GameData;
		const cityTroops = this.city_troops[town_id];

		Object.keys(units).forEach(troop => {
			const guiCount = cityTroops?.[troop] ?? 0;
			const selector = `#troops_settings_${town_id} #troop_lvl_${troop}`;

			if (guiCount > 0) uw.$(selector).css('color', 'orange').text(guiCount);
			else uw.$(selector).css('color', '').text('-');
		});

		const isTownActive = this.city_troops[town_id];
		uw.$('#auto_train_title').css('filter', isTownActive ? 'brightness(100%) saturate(186%) hue-rotate(241deg)' : '');
	};

	trigger = () => {
		const town = uw.ITowns.getCurrentTown();
		const town_id = town.getId();
		if (this.city_troops[town_id]) {
			delete this.city_troops[town_id];
			[...this.NAVAL_ORDER, ...this.GROUND_ORDER].forEach(troop => {
				const selector = `#troops_settings_${town_id} #troop_lvl_${troop}`;
				uw.$(selector).css('color', '').text('-');
			});
			uw.$('#auto_train_title').css('filter', '');
			this.storage.save('troops', this.city_troops);
		}
	};

	/* return the count of the order type (naval or ground) */
	getUnitOrdersCount = (type, town_id) => {
		const town = uw.ITowns.getTown(town_id);
		return town.getUnitOrdersCollection().where({ kind: type }).length;
	};

	getNextInList = (unitType, town_id) => {
		const troops = this.city_troops[town_id];
		if (!troops) return null;

		const unitOrder = unitType === 'naval' ? this.NAVAL_ORDER : this.GROUND_ORDER;
		for (const unit of unitOrder) {
			if (troops[unit] && this.getTroopCount(unit, town_id) !== 0) return unit;
		}

		return null;
	};

	getTroopCount = (troop, town_id) => {
		const town = uw.ITowns.getTown(town_id);
		if (!this.city_troops[town_id] || !this.city_troops[town_id][troop]) return 0;
		let count = this.city_troops[town_id][troop];
		for (let order of town.getUnitOrdersCollection().models) {
			if (order.attributes.unit_type === troop) count -= order.attributes.count;
		}
		let townUnits = town.units();
		if (townUnits.hasOwnProperty(troop)) count -= townUnits[troop];
		let outerUnits = town.unitsOuter();
		if (outerUnits.hasOwnProperty(troop)) count -= outerUnits[troop];
		//TODO: in viaggio
		if (count < 0) return 0;

		/* Get the duable ammount with the current resouces of the polis */
		let resources = town.resources();
		let discount = uw.GeneralModifications.getUnitBuildResourcesModification(town_id, uw.GameData.units[troop]);
		let { wood, stone, iron } = uw.GameData.units[troop].resources;
		let w = resources.wood / Math.round(wood * discount);
		let s = resources.stone / Math.round(stone * discount);
		let i = resources.iron / Math.round(iron * discount);
		let current = parseInt(Math.min(w, s, i));

		/* Check for free population */
		let duable_with_pop = parseInt(resources.population / uw.GameData.units[troop].population); // for each troop

		/* Get the max duable */
		let w_max = resources.storage / (wood * discount);
		let s_max = resources.storage / (stone * discount);
		let i_max = resources.storage / (iron * discount);
		let max = parseInt(Math.min(w_max, s_max, i_max) * 0.85); // 0.8 it's the full percentual -> 80%
		max = max > duable_with_pop ? duable_with_pop : max;

		if (max > count) {
			return count > current ? -1 : count;
		} else {
			if (current >= max && current < duable_with_pop) return current;
			if (current >= max && current > duable_with_pop) return duable_with_pop;
			return -1;
		}
	};

	/* Check the given town, for ground or land */
	checkPolis = (type, town_id) => {
		let order_count = this.getUnitOrdersCount(type, town_id);
		if (order_count > 6) return 0;
		let count = 1;
		while (count >= 0) {
			let next = this.getNextInList(type, town_id);
			if (!next) return 0;
			count = this.getTroopCount(next, town_id);
			if (count < 0) return 0;
			if (count === 0) continue;
			this.buildPost(town_id, next, count);
			return true;
		}
	};

	/* Return list of town that have power active */
	getPowerActive = () => {
		const { fragments } = MM.getFirstTownAgnosticCollectionByName('CastedPowers');
		let towns_list = [];
		for (let town_id in this.city_troops) {
			const { models } = fragments[town_id];
			for (let power of models) {
				let { attributes } = power;
				if (this.POWER_LIST.includes(attributes.power_id)) {
					towns_list.push(town_id);
					break;
				}
			}
		}
		return towns_list;
	};

	/* Make build request to the server */
	buildPost = (town_id, unit, count) => {
		let data = {
			unit_id: unit,
			amount: count,
			town_id: town_id,
		};
		uw.gpAjax.ajaxPost('building_barracks', 'build', data);
	};

	/* return the active towns */
	getActiveList = () => {
		if (!this.spell) return Object.keys(this.city_troops);
		return this.getPowerActive();
	};

	/* Main function, call in the loop */
	main = () => {
		let town_list = this.getActiveList();

		for (let town_id of town_list) {
			if (town_id in uw.ITowns.towns) {
				if (this.checkPolis('naval', town_id)) return;
				if (this.checkPolis('ground', town_id)) return;
			} else {
				delete this.city_troops[town_id];
			}
		}
	};
}

/* 
    botConsole.log(message);
    ideas:
        - add colors  
*/

class BotConsole {
	constructor() {
		this.string = [];
		this.updateSettings();
	}

	renderSettings = () => {
		setTimeout(() => {
			this.updateSettings();
			let interval = setInterval(() => {
				this.updateSettings();
				if (!uw.$('#modern_console').length) clearInterval(interval);
			}, 1000);
		}, 100);
		return `<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',
		bireme: 'L',
		demolition_ship: 'M',
		attack_ship: 'N',
		trireme: 'O',
		colonize_ship: 'P',
	};

	constructor() {
		const swap = json => {
			var ret = {};
			for (var key in json) {
				ret[json[key]] = key;
			}
			return ret;
		};

		this.ITEMS_REV = swap(this.ITEMS);
	}

	/* Pass a storage object, return it encoded */
	encode(storage) {
		for (let item in storage) {
			if (typeof storage[item] !== 'object') continue;

			if (item == 'buildings') {
				for (let polis_id in storage[item]) {
					let obj = storage[item][polis_id];
					storage[item][polis_id] = this.encode_building(obj);
				}
			}

			if (item == 'troops') {
				for (let polis_id in storage[item]) {
					let obj = storage[item][polis_id];
					storage[item][polis_id] = this.encode_troops(obj);
				}
			}
		}

		return storage;
	}

	decode(storage) {
		for (let item in storage) {
			if (typeof storage[item] !== 'object') continue;

			if (item == 'buildings') {
				for (let polis_id in storage[item]) {
					let str = storage[item][polis_id];
					storage[item][polis_id] = this.decode_bulding(str);
				}
			}

			if (item === 'troops') {
				for (let polis_id in storage[item]) {
					let str = storage[item][polis_id];
					storage[item][polis_id] = this.decode_troops(str);
				}
			}
		}

		return storage;
	}

	compressNumber(num) {
		let base = this.SYMBOLS.length;
		let digits = [];
		while (num > 0) {
			digits.unshift(this.SYMBOLS[num % base]);
			num = Math.floor(num / base);
		}
		if (digits.length == 1) {
			digits.unshift('0');
		}
		return digits.slice(-2).join('');
	}

	decompressNumber(str) {
		let base = this.SYMBOLS.length;
		let digits = str.split('');
		let num = 0;
		for (let i = 0; i < digits.length; i++) {
			num += this.SYMBOLS.indexOf(digits[i]) * Math.pow(base, digits.length - i - 1);
		}
		return num;
	}

	/* Give the object of building, return the encoded string */
	encode_building(obj) {
		let str = '';
		for (let item in obj) {
			str += this.ITEMS[item] + this.NUMBERS[obj[item]];
		}
		return str;
	}

	/* Give an encoded string with building, return the correspong object */
	decode_bulding(str) {
		let json_str = '{';
		for (let item of str.match(/.{1,2}/g)) {
			json_str += `"${this.ITEMS_REV[item[0]]}"` + ':' + this.NUMBERS.indexOf(item[1]) + ',';
		}
		json_str = json_str.replace(/,$/, '}');
		return JSON.parse(json_str);
	}

	encode_troops(obj) {
		let str = '';
		for (let item in obj) {
			str += this.ITEMS[item] + this.compressNumber(obj[item]);
		}
		return str;
	}

	decode_troops(str) {
		let json_str = '{';
		for (let item of str.match(/.{1,3}/g)) {
			json_str += `"${this.ITEMS_REV[item[0]]}"` + ':' + this.decompressNumber(item.slice(-2)) + ',';
		}
		json_str = json_str.replace(/,$/, '}');
		return JSON.parse(json_str);
	}
}

/* 
    Create a new window
 */
class createGrepoWindow {
	constructor({ id, title, size, tabs, start_tab, minimizable = true }) {
		this.minimizable = minimizable;
		this.width = size[0];
		this.height = size[1];
		this.title = title;
		this.id = id;
		this.tabs = tabs;
		this.start_tab = start_tab;

		/* Private methods */
		const createWindowType = (name, title, width, height, minimizable) => {
			function WndHandler(wndhandle) {
				this.wnd = wndhandle;
			}
			Function.prototype.inherits.call(WndHandler, uw.WndHandlerDefault);
			WndHandler.prototype.getDefaultWindowOptions = function () {
				return {
					position: ['center', 'center', 100, 100],
					width: width,
					height: height,
					minimizable: minimizable,
					title: title,
				};
			};
			uw.GPWindowMgr.addWndType(name, `${name}_75624`, WndHandler, 1);
		};

		const getTabById = (id) => {
			return this.tabs.filter((tab) => tab.id === id)[0];
		};

		this.activate = function () {
			createWindowType(this.id, this.title, this.width, this.height, this.minimizable); //
			uw.$(
				`<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();
		};

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

			/* if open is called but window it's alreay open minimized, maximize that */
			if (wn) {
				if (wn.isMinimized()) {
					wn.maximizeWindow();
				}
				return;
			}

			let content = `<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);
			/* Add and reder tabs */
			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}`);
			});

			/* Add events to tabs */
			let tabs = '';
			this.tabs.forEach((e) => {
				tabs += `#${this.id} #${e.id}, `;
			});
			tabs = tabs.slice(0, -2);
			let self = this;
			uw.$(tabs).click(function () {
				self.renderTab(this.id);
			});
			/* render default tab*/
			this.renderTab(this.tabs[this.start_tab].id);
		};

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

		/* Handle active tab */
		this.renderTab = function (id) {
			let tab = getTabById(id);
			uw.$(`#${this.id}_content`).html(getTabById(id).render());
			uw.$(`#${this.id} .active`).removeClass('active');
			uw.$(`#${id}`).addClass('active');
			getTabById(id).afterRender ? getTabById(id).afterRender() : '';
		};
	}
}

// TODO:
// - disable note notifiation
// - add logs in console

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

		/* Add event to add the button in the notes */
		uw.$.Observer(uw.GameEvents.window.open).subscribe((e, i) => {
			if (!i.attributes) return;
			if (i.attributes.window_type != 'notes') return;
			setTimeout(this.addButton, 100);
		});
		uw.$.Observer(uw.GameEvents.window.tab.rendered).subscribe((e, i) => {
			const { attributes } = i.window_model;
			if (!attributes) return;
			if (attributes.window_type !== 'notes') return;
			requestAnimationFrame(this.addButton);
		});
	}

	getStorage = () => {
		const worldId = uw.Game.world_id;
		const savedValue = localStorage.getItem(`${worldId}_modernBot`);
		let storage = {};

		if (savedValue !== null && savedValue !== undefined) {
			try {
				storage = JSON.parse(savedValue);
			} catch (error) {
				console.error(`Error parsing localStorage data: ${error}`);
			}
		}

		return storage;
	};

	saveStorage = storage => {
		try {
			const worldId = uw.Game.world_id;
			localStorage.setItem(`${worldId}_modernBot`, JSON.stringify(storage));
			this.lastUpdateTime = Date.now();
			return true;
		} catch (error) {
			console.error(`Error saving data to localStorage: ${error}`);
			return false;
		}
	};

	save = (key, content) => {
		const storage = this.getStorage();
		storage[key] = content;
		return this.saveStorage(storage);
	};

	load = (key, defaultValue = null) => {
		const storage = this.getStorage();
		const savedValue = storage[key];
		return savedValue !== undefined ? savedValue : defaultValue;
	};

	/* Call to save the setting to the given note id */
	saveSettingsNote = note_id => {
		const storage = JSON.stringify(this.encode(this.getStorage()));
		const data = {
			model_url: `PlayerNote/${note_id}`,
			action_name: 'save',
			arguments: {
				id: note_id,
				text: storage,
			},
		};
		uw.gpAjax.ajaxPost('frontend_bridge', 'execute', data);
		return storage;
	};

	/* Call to add the buttons */
	addButton = () => {
		this.check_done += 1;
		if ($('#modern_storage_load').length) return;

		const modern_settings_load = $('<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',
			'This operation will overwrite the current note with the local settings of the ModernBot',
			() => {
				// trigged when user press yes
				const note = this.getActiveNote();
				if (!note) return; // TODO: display an error
				const content = this.saveSettingsNote(note.id);
				$('.preview_box').text(content);
			},
			() => {}
		);
	};

	loadSettings = () => {
		// TODO: check that the current note has settimhs
		uw.ConfirmationWindowFactory.openSimpleConfirmation(
			'ModernStorage',
			'This operation will load the settings of the current note and overwrite the local settings',
			() => {
				// Trigged when the user press yes
				const note = this.getActiveNote();
				const { text } = note.attributes;
				let decoded;
				try {
					decoded = this.decode(JSON.parse(text));
				} catch {
					HumanMessage.error("This note don't contains the settings");
					return;
				}

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

	/* Return the current active note */
	getActiveNote() {
		const noteClass = $('.tab.selected').attr('class');
		if (!noteClass) return null;
		const noteX = noteClass.match(/note(\d+)/)[1];
		const note_index = parseInt(noteX) - 1;

		const collection = MM.getOnlyCollectionByName('PlayerNote');
		if (!collection) return null;
		let { models } = collection;

		return models[note_index];
	}
}

/* Setup autofarm in the window object */

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

        this.$ui = $("#ui_box");
        // Create the quick menu and the divider element
        this.$menu = this.createModernMenu();
        const $divider = $('<div class="divider"></div>');

        // Add AutoFarm to the new menu
        this.autoFarm = new AutoFarm(this.console, this.storage);
        this.$menu.append(this.autoFarm.$activity)
        this.$ui.append(this.autoFarm.$popup)

        //const $farm = this.createActivity("url(https://gpit.innogamescdn.com/images/game/premium_features/feature_icons_2.08.png) no-repeat 0 -240px");
        // this.$menu.append($farm, $divider.clone());

        this.autoGratis = new AutoGratis(this.console, this.storage);
        this.autoRuralLevel = new AutoRuralLevel(this.console, this.storage);
        this.autoBuild = new AutoBuild(this.console, this.storage);
        this.autoRuralTrade = new AutoRuralTrade(this.console, this.storage);
        this.autoBootcamp = new AutoBootcamp(this.console, this.storage);
        this.autoParty = new AutoParty(this.console, this.storage);
        this.autoTrain = new AutoTrain(this.console, this.storage);
        this.autoHide = new AutoHide(this.console, this.storage);
        this.antiRage = new AntiRage(this.console, this.storage);
        this.autoTrade = new AutoTrade(this.console, this.storage);

        this.settingsFactory = new createGrepoWindow({
            id: 'MODERN_BOT',
            title: 'ModernBot',
            size: [845, 300],
            tabs: [
                {
                    title: 'Farm',
                    id: 'farm',
                    render: this.settingsFarm,
                },
                {
                    title: 'Build',
                    id: 'build',
                    render: this.settingsBuild,
                },
                {
                    title: 'Train',
                    id: 'train',
                    render: this.settingsTrain,
                } /*
				{
					title: 'Trade',
					id: 'trade',
					render: this.settingsTrade,
				},*/,
                {
                    title: 'Mix',
                    id: 'mix',
                    render: this.settingsMix,
                },
                {
                    title: 'Console',
                    id: 'console',
                    render: this.console.renderSettings,
                },
            ],
            start_tab: 0,
        });

        this.setup();
    }

    settingsFarm = () => {
        let html = '';
        // html += this.autoFarm.settings();
        html += this.autoRuralLevel.settings();
        html += this.autoRuralTrade.settings();
        return html;
    };

    settingsBuild = () => {
        let html = '';
        html += this.autoGratis.settings();
        html += this.autoBuild.settings();
        return html;
    };

    settingsMix = () => {
        let html = '';
        html += this.autoBootcamp.settings();
        html += this.autoParty.settings();
        html += this.autoHide.settings();
        return html;
    };

    settingsTrain = () => {
        let html = '';
        html += this.autoTrain.settings();
        return html;
    };

    settingsTrade = () => {
        let html = ``;
        html += this.autoTrade.settings();
        return html;
    };

    setup = () => {
        /* Activate */
        this.settingsFactory.activate();
        uw.$('.gods_area_buttons').append("<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>");

        /* Add event to polis list menu */
        const editController = () => {
            const townController = uw.layout_main_controller.sub_controllers.find(controller => controller.name === 'town_name_area');
            if (!townController) {
                setTimeout(editController, 2500);
                return;
            }

            const oldRender = townController.controller.town_groups_list_view.render;
            townController.controller.town_groups_list_view.render = function () {
                oldRender.call(this);
                const both = `<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);
    };

    /* New quick menu */
    // Create the html of an activity in the new quick menu
    createModernMenu = () => {
        const $menu = $('<div id="modern_menu" class="toolbar_activities"></div>');
        $menu.css({
            'position': 'absolute',
            'top': '3px',
            'left': '400px',
            'z-index': '1000',
        });

        // Add left, middle, right
        const $left = $('<div class="left"></div>');
        const $middle = $('<div class="middle"></div>');
        const $right = $('<div class="right"></div>');

        $menu.append($left, $middle, $right);
        $("#ui_box").prepend($menu);

        return $middle
    }
}

// Load the bot when the loader is ready
const loader = setInterval(() => {
    if ($("#loader").length > 0) return;
    uw.modernBot = new ModernBot();
    clearInterval(loader);
}, 100);