NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name LG Tools // @version 0.51 // @description Tools to make your Grepolis prettier. // @author Vít Dolínek // @include http://*.grepolis.com/game/* // @include https://*.grepolis.com/game/* // @icon64 https://st2.depositphotos.com/5943796/11382/v/950/depositphotos_113825054-stock-illustration-initial-letter-lg-green-swoosh.jpg // @require https://cdn.jsdelivr.net/npm/lodash@4.17.15/lodash.min.js // @copyright 2020, LG (https://openuserjs.org/users/shigatora) // @licence MIT // ==/UserScript== const w = unsafeWindow || window; const $ = w.jQuery || jQuery; const troops = {}; let troopOptionSelected = {}; let tradeUnitSelected; const farmPopulation = { 1: 114, 2: 121, 3: 134, 4: 152, 5: 175, 6: 206, 7: 245, 8: 291, 9: 341, 10: 399, }; /** * Loads player's currently available troops. */ function loadTroops() { const towns = _.values(ITowns.getTowns()); _.each( GameData.units, (unit) => (troops[unit.id] = { name: unit.name, count: { inside: 0, outside: 0, }, }) ); towns.map((town) => { const unitsInside = town.units(); const unitsOutside = town.unitsOuter(); for (const [name, value] of Object.entries(unitsInside)) { _.set( troops[name].count, "inside", parseInt(troops[name].count.inside) + value ); } for (const [name, value] of Object.entries(unitsOutside)) { _.set( troops[name].count, "outside", parseInt(troops[name].count.outside) + value ); } }); } /** * Shows movements in conquest overview. * @param newChild * @param refChild */ function countConquestMovements(newChild, refChild) { const movementTab = $(document) .find("#unit_movements") .closest(".gpwindow_content .tab_content"); const wrapper = $("<div />", { class: "lgt-conquest-movements town_groups_list", style: "width: 99%; display: flex; justify-content: center; position: relative !important; font-size: 20px;", }); const supportMovementCount = movementTab.find(".support.incoming").length; const attackMovementCount = movementTab.find(".attack.incoming").length; const supportMovementWrapper = $("<div />", { style: "display: flex; align-items: center; padding: 0 5px; color: #a0c82d;", }); const supportMovementIcon = $("<span />", { class: "support_icon", }); const supportMovementText = $("<span />", { text: supportMovementCount, style: "padding-left: 4px;", }); supportMovementWrapper.append(supportMovementIcon); supportMovementWrapper.append(supportMovementText); const attackMovementWrapper = $("<div />", { style: "display: flex; align-items: center; padding: 0 5px; color: red;", }); const attackMovementIcon = $("<span />", { class: "attack_icon", }); const attackMovementText = $("<span />", { text: attackMovementCount, style: "padding-left: 4px;", }); attackMovementWrapper.append(attackMovementIcon); attackMovementWrapper.append(attackMovementText); wrapper.append(attackMovementWrapper); wrapper.append(supportMovementWrapper); if (!$(document).find(".lgt-conquest-movements").length) { wrapper.insertBefore(movementTab, refChild); } else { $(document).find(".lgt-conquest-movements").html(wrapper); } } /** * Shows movements in forum posts and private message posts. * @param newChild * @param refChild */ function countForumConquestMovements(newChild, refChild) { const movementTabs = $(".fight_report_classic.conquest.published"); movementTabs.each((index, element) => { const wrapper = $("<div />", { class: "lgt-conquest-movements town_groups_list", style: "width: 99%; display: flex; justify-content: center; position: relative !important; font-size: 20px; margin: 10px 0;", }); const supportMovementCount = $(element).find(".support.incoming").length; const attackMovementCount = $(element).find(".attack_sea.incoming").length + $(element).find(".attack_land.incoming").length; const supportMovementWrapper = $("<div />", { style: "display: flex; align-items: center; padding: 0 5px; color: #a0c82d;", }); const supportMovementIcon = $("<span />", { class: "support_icon", }); const supportMovementText = $("<span />", { text: supportMovementCount, style: "padding-left: 4px; font-weight: bold;", }); supportMovementWrapper.append(supportMovementIcon); supportMovementWrapper.append(supportMovementText); const attackMovementWrapper = $("<div />", { style: "display: flex; align-items: center; padding: 0 5px; color: red;", }); const attackMovementIcon = $("<span />", { class: "attack_icon", }); const attackMovementText = $("<span />", { text: attackMovementCount, style: "padding-left: 4px; font-weight: bold;", }); attackMovementWrapper.append(attackMovementIcon); attackMovementWrapper.append(attackMovementText); wrapper.append(attackMovementWrapper); wrapper.append(supportMovementWrapper); if (!$(element).find(".lgt-conquest-movements").length) { wrapper.insertBefore($(element).find("#unit_movements li")[0], refChild); } else { $(this).find(".lgt-conquest-movements").html(wrapper); } }); } /** * Counts population usage in every city and displays it in town list overview. * @param townId */ function populationUsage(townId = null) { let buildings = GameData.buildings; layout_main_controller.sub_controllers[14].controller.town_groups_list_view .render; Object.values(ITowns.getTowns()).map((town) => { if (townId && town.id !== townId) return; let populationUsedOnBuildings = 0; const buildingLevels = town.buildings().getLevels(); const maxPopulationWithoutBonuses = buildingLevels.farm > 10 ? Math.floor( buildings.farm.farm_factor * Math.pow(buildingLevels.farm, buildings.farm.farm_pow) ) : farmPopulation[buildingLevels.farm]; const thermalBathFactor = town.getBuildings().getBuildingLevel("thermal") ? 1.1 : 1.0; const plowResearchValue = town.getResearches().attributes.plow ? 200 : 0; const extraPopulation = town.getPopulationExtra(); const isPygmalionActive = town.god() === "aphrodite"; const pygmalionBonus = isPygmalionActive ? town.getBuildings().getBuildingLevel("farm") * 5 : 0; const maximumPopulationWithBonuses = maxPopulationWithoutBonuses * thermalBathFactor + plowResearchValue + pygmalionBonus + extraPopulation; for (const building in buildingLevels) { if (buildingLevels.hasOwnProperty(building)) { populationUsedOnBuildings += Math.round( buildings[building].pop * Math.pow(buildingLevels[building], buildings[building].pop_factor) ); } } const populationUsedInMilitary = parseInt( maximumPopulationWithBonuses - (populationUsedOnBuildings + town.getAvailablePopulation()), 10 ); const percentageOfPopulationFree = Math.round( (populationUsedInMilitary / (maximumPopulationWithBonuses - populationUsedOnBuildings)) * 100 ); let percentageColor = ""; if (percentageOfPopulationFree <= 20) percentageColor = "red"; if (percentageOfPopulationFree > 20 && percentageOfPopulationFree <= 70) percentageColor = "#ffa600"; if (percentageOfPopulationFree >= 70) percentageColor = "#49d004"; const percentageDiv = $(document).find( `.item.town_group_town[data-townid=${town.id}] .population-percentage` ); const percentage = $("<div />", { class: "population-percentage", style: `float: right; color: ${percentageColor};text-shadow: 0.5px 0.5px 0.5px black, 0.5px 0.5px 1px black;`, html: `${percentageOfPopulationFree}%`, }); if (percentageDiv.length) { percentageDiv.html(percentage[0].outerHTML); } else { $(document) .find(`.item.town_group_town[data-townid=${town.id}]`) .append(percentage[0].outerHTML); } }); } /** * Initializes trading between cities. * @param newChild * @param refChild */ function cityTrader(newChild, refChild) { if (ITowns.towns[Game.townId].getBuildings().getBuildingLevel("market") < 1) return; $.each($(document).find("[class^=trade_tab_target_]"), (index, element) => { let tradeTabId = element.className.match(/trade_tab_target_(\d+)/)[1]; if ("undefined" == typeof troopOptionSelected[tradeTabId]) troopOptionSelected[tradeTabId] = { troop: null, percent: 0, }; if (!$(element).find(`#trade_input_${tradeTabId}`).length) { let previousSliderValue = 0; function calculateResources(v, t = null) { let dividedResourceValue = 0; let totalWood = 0; let totalStone = 0; let totalIron = 0; previousSliderValue = v; if ($(`.lgt_troop_select_unit_${tradeTabId}`).val() || t) { const unit = GameData.units[ $(`.lgt_troop_select_unit_${tradeTabId}`).val() || t ]; const totalResourceCost = unit.resources.wood + unit.resources.stone + unit.resources.iron; const ratioWood = unit.resources.wood / totalResourceCost; const ratioStone = unit.resources.stone / totalResourceCost; const ratioIron = unit.resources.iron / totalResourceCost; totalWood = Math.floor(v * ratioWood); totalStone = Math.floor(v * ratioStone); totalIron = Math.floor(v * ratioIron); } else { dividedResourceValue = Math.floor(v / 3); } $(element) .find("#trade_type_wood input") .select() .val(dividedResourceValue || totalWood) .blur(); $(element) .find("#trade_type_stone input") .select() .val(dividedResourceValue || totalStone) .blur(); $(element) .find("#trade_type_iron input") .select() .val(dividedResourceValue || totalIron) .blur(); $(element).find(`#trade_input_${tradeTabId}`).val(v); } $(`<div class="trade-slider-wrapper" style="display: flex;width: 100%;align-items: center;justify-content: center;"> <div class="windowmgr_slider" style="width: 30%"> <div class="grepo_slider" id="lgt_trage_slider_${tradeTabId}"> <div class="button_down left js-button-left"></div> <div class="bar js-slider js-slider-handle-container"> </div> <div class="button_up right js-button-right"></div> </div> </div> <input type="text" class="lgt_troop_select_unit_${tradeTabId}" hidden /> <div class="textbox" style="width:10%"> <div class="left"></div> <div class="right"></div> <div class="middle"> <div class="initial-message-box js-empty" style="display: none;"></div> <div class="ie7fix"><input type="text" id="trade_input_${tradeTabId}" tabindex="1" placeholder="" pattern="[0-9]*"></div> <div class="clear-button js-clear"></div> </div> <div class="error-msg js-txt-error-msg"></div> </div> </div>`).insertBefore($(element).find("#duration_container"), refChild); const slider = $(`#lgt_trage_slider_${tradeTabId}`).grepoSlider({ min: 0, max: ITowns.getCurrentTown().getAvailableTradeCapacity(), step: ITowns.getCurrentTown().getAvailableTradeCapacity() / 10, value: Math.round( (ITowns.getCurrentTown().getAvailableTradeCapacity() / 100) * troopOptionSelected[tradeTabId].percent ), }); const troopRatioSelector = $("<div />", { id: `lgt_troop_selector_${tradeTabId}`, class: `dropdown default`, style: "width: 100px; position: absolute; left: 30px; top: 98px;", }).insertBefore($(element).find(".content .resource_selector")[0]); troopRatioSelector .dropdown({ id: tradeTabId, options: Object.values(GameData.units).map((troopInformation) => ({ value: troopInformation.id, name: troopInformation.name, })), value: $(`.lgt_troop_select_unit_${tradeTabId}`).val() || troopOptionSelected[tradeTabId].troop || null, }) .on("dd:change:value", function (e, i) { troopOptionSelected[tradeTabId].troop = i; $(`.lgt_troop_select_unit_${tradeTabId}`).val(i); tradeUnitSelected = i; slider.trigger("sl:change:value"); }); if (troopOptionSelected[tradeTabId].percent) { const tradeAmountSelected = Math.round( (ITowns.getCurrentTown().getAvailableTradeCapacity() / 100) * troopOptionSelected[tradeTabId].percent ); calculateResources( tradeAmountSelected, troopOptionSelected[tradeTabId].troop ); } slider.on("sl:change:value", (e, _sl, v) => { v = v || previousSliderValue; troopOptionSelected[tradeTabId].percent = Math.round( (v / ITowns.getCurrentTown().getAvailableTradeCapacity()) * 100 ); calculateResources(v, tradeUnitSelected); }); } }); } /** * Initializes and handles buttons that send resources needed for City Festival. */ function cultureTrader() { $.each($(document).find("[class^=trade_tab_target_]"), (index, element) => { let tradeTabId = element.className.match(/trade_tab_target_(\d+)/)[1]; if (!ITowns.isMyTown(tradeTabId)) return; const resourceIndicators = $(element).find(".town-capacity-indicator"); $.each(resourceIndicators, (index, element) => { let top, type; if (index === 0) { top = 218; type = "wood"; } if (index === 1) { top = 251; type = "stone"; } if (index === 2) { top = 286; type = "iron"; } if ($(element).find(".culture-trade").length !== 3) { $(`<div class="btn_trade_button culture-trade button_new" data-townId="${tradeTabId}" data-type="${type}" style="width: 33px;position: absolute;top: ${top}px;right: 55px;"> <div class="left"></div> <div class="right"></div> <div class="caption js-caption"><img src="https://gpus.innogamescdn.com/images/game/overviews/culture_25x25.png" style="position: absolute;right: 2px;top: -1px;" align="absmiddle"><div class="effect js-effect"></div></div> </div>`).insertAfter(element); } }); }); $(".culture-trade").on("click", function () { const town = ITowns.getTown($(this).data("townid")); const resourceType = $(this).data("type"); const currentResources = town.getCurrentResources(); if (resourceType === "wood" || resourceType === "iron") { let resourcesNeeded = 15000 - currentResources[resourceType]; resourcesNeeded > ITowns.getCurrentTown().getAvailableTradeCapacity() ? (resourcesNeeded = ITowns.getCurrentTown().getAvailableTradeCapacity()) : resourcesNeeded; resourcesNeeded > 0 ? $(this) .closest("[class^=trade_tab_target_]") .find(`#trade_type_${resourceType} input`) .select() .val(resourcesNeeded) .blur() : null; } if (resourceType === "stone") { let resourcesNeeded = 18000 - currentResources[resourceType]; resourcesNeeded > ITowns.getCurrentTown().getAvailableTradeCapacity() ? (resourcesNeeded = ITowns.getCurrentTown().getAvailableTradeCapacity()) : ""; resourcesNeeded > 0 ? $(this) .closest("[class^=trade_tab_target_]") .find(`#trade_type_${resourceType} input`) .select() .val(resourcesNeeded) .blur() : null; } }); } /** * Initialize all required particles on game:load event. */ $(document).on("game:load", function () { "use strict"; var windows = require("game/windows/ids"); $(document).ajaxComplete(function (d, f, c) { if ("undefined" != typeof c) { d = c.url.replace(/\/game\/(.*)\?.*/, "$1"); const urlParams = new URLSearchParams(c.url); var l = "frontend_bridge" !== d ? d : -1 < c.url.indexOf("json") ? JSON.parse(unescape(c.url).split("&")[3].split("=")[1]).window_type : d; if ("frontend_bridge" === d) { var m = WM.getWindowByType(l)[0]; } $.each(Layout.wnd.getAllOpen(), function (c, d) { c = Layout.wnd.GetByID(d.getID()); switch (c.getController()) { case "command_info": switch (c.getContext().sub) { case "command_info_conquest_movements": countConquestMovements(); break; } break; case "alliance_forum": countForumConquestMovements(); break; case "message": countForumConquestMovements(); break; case "town_overviews_command_overview": populationUsage(); break; case "town_info": switch (c.getContext().sub) { case "town_info_trading": cityTrader(); cultureTrader(); break; } break; } }); } }); (function () { let i = 0; while ( layout_main_controller.sub_controllers[i].name !== "town_name_area" ) { i++; } layout_main_controller.sub_controllers[ i ].controller.town_groups_list_view.render_old = layout_main_controller.sub_controllers[ i ].controller.town_groups_list_view.render; layout_main_controller.sub_controllers[ i ].controller.town_groups_list_view.render = function () { layout_main_controller.sub_controllers[ i ].controller.town_groups_list_view.render_old(); populationUsage(); }; loadTroops(); })(); (function () { const troopOverviewIcon = $("<div />", { class: "icon js-caption", style: `background: url(https://gppt.innogamescdn.com/images/game/autogenerated/layout/layout_d1cf0d4.png) no-repeat 0 -635px; width: 26px; height: 27px; position: absolute; right: 10%; top: 3%;`, }); const troopOverviewButton = $("<div />", { class: "circle_button", style: "position: absolute; right: 33px; top: 0; height: 31px", }).append(troopOverviewIcon); troopOverviewButton.bind("click", () => WF.open("lgt_troopoverview")); const helpIcon = $("<span />", { style: "position: absolute; color: #fff; top: 7px; right: 12px;", }).text("?"); const helpButton = $("<div />", { class: "circle_button", style: "position: absolute; right: 0; top: 0; height: 31px", }); helpButton.append(helpIcon); helpButton.bind("click", () => WF.open("lgt_help")); const cityOverviewIcon = $("<div />", { class: "pointer", }); const cityOverviewButton = $("<div />", { class: "circle_button city_overview", style: "position: absolute; right: 66px; top: 0; height: 31px", }).append(cityOverviewIcon); cityOverviewButton.bind("click", () => WF.open("lgt_cityoverview")); const toolbarMiddle = $(".nui_toolbar .middle"); toolbarMiddle.append(troopOverviewButton); toolbarMiddle.append(cityOverviewButton); toolbarMiddle.append(helpButton); })(); (function () { require("game/windows/ids").LgT_TroopOverview = "lgt_troopoverview"; (function () { function b(type) { loadTroops(); let wrapper = $("<div />", { id: "troop_overview_wrapper", }); _.each(troops, (unit, unitKey) => { let text; switch (type) { case "all": text = unit.count.inside + unit.count.outside; break; case "outside": text = unit.count.outside; break; case "inside": text = unit.count.inside; break; } let troopCount = $("<span />", { style: "font-size: inherit; bottom: 0; right: 0; text-shadow: 1px 1px 1px black, 1px 1px 2px black;", text: text, }); let unitBox = $("<div />", { class: `unit index_unit bold unit_icon40x40 ${unitKey}`, }).append(troopCount); wrapper.append(unitBox); }); return wrapper; } (function () { var ae = w.GameControllers.TabController, a = ae.extend({ initialize: function () { ae.prototype.initialize.apply(this, arguments); }, render: function () { this.getWindowModel().showLoading(); this.$el.html(b("all")); this.getWindowModel().hideLoading(); }, }); w.GameViews.LgTV_TroopOverviewAll = a; })(); (function () { var oe = w.GameControllers.TabController, o = oe.extend({ initialize: function () { oe.prototype.initialize.apply(this, arguments); this.registerListeners(); }, registerListeners: function () { $.Observer("town:units:change").subscribe(this._units.bind(this)); }, render: function () { this.getWindowModel().showLoading(); this.$el.html(b("outside")); this.getWindowModel().hideLoading(); }, _units: function () { setTimeout(() => { this.render(); }, 100); }, }); w.GameViews.LgTV_TroopOverviewOutside = o; })(); var ie = w.GameControllers.TabController, i = ie.extend({ initialize: function () { ie.prototype.initialize.apply(this, arguments); this.registerListeners(); }, registerListeners: function () { $.Observer("town:units:change").subscribe(this._units.bind(this)); }, render: function () { this.getWindowModel().showLoading(); this.$el.html(b("inside")); this.getWindowModel().hideLoading(); }, _units: function () { setTimeout(() => { this.render(); }, 100); }, }); w.GameViews.LgTV_TroopOverviewInside = i; })(); })(); (function () { function a() { return ` <div class="keyboard" style=" width: 20px; height: 20px; border: 1px solid; background-color: #fff; font-weight: 900; text-align: center; font-size: medium; margin: 5px; display: inline-block; ">F</div> - Farming villages (Premium required)<br /> <div class="keyboard" style=" width: 20px; height: 20px; border: 1px solid; background-color: #fff; font-weight: 900; text-align: center; font-size: medium; margin: 5px; display: inline-block; ">C</div> - Culture overview (Premium required)<br /> `; } require("game/windows/ids").LgT_Help = "lgt_help"; var ie = w.GameControllers.TabController, i = ie.extend({ initialize: function () { ie.prototype.initialize.apply(this, arguments); }, render: function () { this.getWindowModel().showLoading(); this.$el.html(a()); this.getWindowModel().hideLoading(); }, }); w.GameViews.LgTV_Help = i; })(); (function () { function a() { const castedPowers = Object.values( ITowns.getCurrentTown().getCastedPowers() ); const wrapper = $("<div />"); castedPowers.map((castedPower) => { const tooltip = GameDataPowers.getTooltipPowerData( GameData.powers[castedPower.power_id], castedPower.configuration, castedPower.configuration.level ); const powerImage = $("<div />", { class: `casted_power power_icon45x45 ${GameDataPowers.getCssPowerId( castedPower )} ${ castedPower.configuration.level ? `lvl lvl${castedPower.configuration.level}` : "" }`, style: "width: 45px; height: 45px;", }); const powerTextWrapper = $("<div />", { style: "display: flex; flex-direction: column; margin: 0 5px; max-width: calc(100% - 60px)", }); const powerTextExpiration = $("<div />", { html: `<b>${ castedPower.end_at ? `Power until: ${readableUnixTimestamp( castedPower.end_at, "player_timezone", { extended_date: !0, } )}` : "Permanently" }</b>`, }); const powerTextDescription = $("<div />").html(tooltip.i_effect).text(); const powerWrapper = $("<div />", { style: "display: flex; width: 100%; padding: 5px", }); powerTextWrapper.append(powerTextExpiration); powerTextWrapper.append(powerTextDescription); powerWrapper.append(powerImage); powerWrapper.append(powerTextWrapper); wrapper.append(powerWrapper); }); return wrapper; } require("game/windows/ids").LgT_CityOverview = "lgt_cityoverview"; var ie = w.GameControllers.TabController, i = ie.extend({ initialize: function () { ie.prototype.initialize.apply(this, arguments); this.registerListeners(); }, registerListeners: function () { $(document).on(GameEvents.town.town_switch, () => this.render()); }, render: function () { this.getWindowModel().showLoading(); this.$el.html(a()); this.getWindowModel().hideLoading(); }, }); w.GameViews.LgTV_CityOverviewEffects = i; })(); (function () { require("game/windows/ids").LgT_CityOverview = "lgt_cityoverview"; var a = w.GameViews, b = w.WindowFactorySettings, c = require("game/windows/ids"), e = require("game/windows/tabs"), f = c.LgT_CityOverview; b[f] = function (b) { b = b || {}; return us.extend({ title: "City Overview", window_type: f, minheight: 200, maxheight: 630, width: 830, tabs: [ { type: "effects", title: "Effects", content_view_constructor: a.LgTV_CityOverviewEffects, hidden: false, }, ], max_instances: 1, activepagenr: 0, minimizable: !0, }); }; })(); (function () { require("game/windows/ids").LgT_Help = "lgt_help"; var a = w.GameViews, b = w.WindowFactorySettings, c = require("game/windows/ids"), e = require("game/windows/tabs"), f = c.LgT_Help; b[f] = function (b) { b = b || {}; return us.extend({ title: "LG Tools Help", window_type: f, minheight: 200, maxheight: 630, width: 830, tabs: [ { type: "shortcuts", title: "Shortcuts", content_view_constructor: a.LgTV_Help, hidden: false, }, ], max_instances: 1, activepagenr: 0, minimizable: !0, }); }; })(); (function () { require("game/windows/ids").LgT_TroopOverview = "lgt_troopoverview"; var a = w.GameViews, b = w.WindowFactorySettings, c = require("game/windows/ids"), e = require("game/windows/tabs"), f = c.LgT_TroopOverview; b[f] = function (b) { b = b || {}; return us.extend({ title: "Troop Overview", window_type: f, minheight: 200, maxheight: 630, width: 500, tabs: [ { type: "all", title: "All", content_view_constructor: a.LgTV_TroopOverviewAll, hidden: false, }, { type: "inside", title: "Home", content_view_constructor: a.LgTV_TroopOverviewInside, hidden: false, }, { type: "outside", title: "Outside", content_view_constructor: a.LgTV_TroopOverviewOutside, hidden: false, }, ], max_instances: 1, activepagenr: 0, minimizable: !0, }); }; })(); (function () { $("head").append(` <style> .farm_village_island_available { background: red !important; } .farm_village_island_farmed { background: green !important; } .farm_village_town_hidden { display: none; } </style> `); })(); });