您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Helper for TheresMoreGame
// ==UserScript== // @name TheresMoreFastPrestige // @namespace TheresMoreGame.com // @match https://www.theresmoregame.com/play/ // @grant none // @version 1.4.0 // @description Helper for TheresMoreGame // @license MIT // @run-at document-idle // ==/UserScript== // Strat by SirDuncan: https://discord.com/channels/968528778819162112/1040316754397765674/1067302170258583615 ;(async () => { let cheatsOff = false let scriptPaused = true let haveManualResourceButtons = true let isClicking = false let mainLoopRunning = false const buildingsList = [ { id: 'City center', isSafe: true }, { id: 'City center part', isSafe: true }, { id: 'Watchman Outpost', isSafe: true, max: 4 }, { id: 'Stable', isSafe: true, max: 1 }, { id: 'Marketplace', isSafe: true, max: 1 }, { id: 'Carpenter workshop', isSafe: true, max: 3 }, { id: 'Grocery', isSafe: true, max: 3 }, { id: 'Guild of craftsmen', isSafe: true, max: 5 }, { id: 'Machines of gods', isSafe: true, max: 5 }, { id: 'Library of Theresmore', isSafe: true, max: 5 }, { id: 'Undead Herds', isSafe: true, max: 3 }, { id: 'Monastery', isSafe: true, max: 5 }, { id: 'Hall of the dead', isSafe: true }, { id: 'Sawmill', isSafe: true, max: 5 }, { id: 'Monument', isSafe: true }, { id: 'University', isSafe: true, max: 3 }, { id: 'Foundry', isSafe: true, max: 3 }, { id: 'Canava trading post', isSafe: true, max: 3 }, { id: 'Artisan Workshop', isSafe: true, max: 10 }, { id: 'Granary', isSafe: true, max: 3 }, { id: 'Hall of wisdom', isSafe: true, max: 3 }, { id: 'School', isSafe: true, max: 15 }, { id: 'Research plant', isSafe: true }, { id: 'Lumberjack Camp', isSafe: true, max: 13 }, { id: 'Quarry', isSafe: true, max: 13 }, { id: 'Mine', isSafe: true, max: 5 }, { id: 'Farm', isSafe: true, max: 8 }, { id: 'Mind Shrine', isSafe: true }, { id: 'Statue of Firio', isSafe: true }, { id: 'Mansion', isSafe: false, requires: { resource: 'Food', parameter: 'speed', minValue: 3 }, max: 3 }, { id: 'City Hall', isSafe: false, requires: { resource: 'Food', parameter: 'speed', minValue: 1.5 }, max: 5 }, { id: 'Common House', isSafe: false, requires: { resource: 'Food', parameter: 'speed', minValue: 1 }, max: 25 }, { id: 'Fiefdom', isSafe: true, max: 2 }, ] .filter((building) => building.id) .map((building, index) => { return { ...building, order: index, } }) const lang = { pop_artisan: 'Artisan', pop_breeder: 'Breeder', pop_farmer: 'Farmer', pop_lumberjack: 'Lumberjack', pop_merchant: 'Merchant', pop_trader: 'Trader', pop_miner: 'Miner', pop_quarryman: 'Quarryman', pop_priest: 'Priest', pop_carpenter: 'Carpenter', pop_steelworker: 'Steelworker', pop_professor: 'Professor', pop_skymancer: 'Skymancer', pop_supplier: 'Supplier', pop_alchemist: 'Alchemist', pop_unemployed: 'Unemployed', pop_natro_refiner: 'Nat-Refiner', pop_researcher: 'Researcher', res_army: 'Army', res_coin: 'Coin', res_copper: 'Copper', res_cow: 'Cow', res_crystal: 'Crystal', res_faith: 'Faith', res_fame: 'Fame', res_food: 'Food', res_gold: 'Gold', res_horse: 'Horse', res_iron: 'Iron', res_legacy: 'Legacy', res_luck: 'Luck', res_mana: 'Mana', res_natronite: 'Natronite', res_population: 'Population', res_stone: 'Stone', res_relic: 'Relic', res_research: 'Research', res_tools: 'Tools', res_wood: 'Wood', res_building_material: 'Materials', res_steel: 'Steel', res_supplies: 'Supplies', res_saltpetre: 'Saltpetre', res_tome_wisdom: 'Tome of Wisdom', res_gem: 'Gem', } const allJobs = [ { id: 'unemployed', }, { id: 'farmer', req: [ { type: 'building', id: 'farm', value: 1, }, ], gen: [ { type: 'resource', id: 'food', value: 1.6, }, ], }, { id: 'lumberjack', req: [ { type: 'building', id: 'lumberjack_camp', value: 1, }, ], gen: [ { type: 'resource', id: 'wood', value: 0.7, }, ], }, { id: 'quarryman', req: [ { type: 'building', id: 'quarry', value: 1, }, ], gen: [ { type: 'resource', id: 'stone', value: 0.6, }, ], }, { id: 'miner', req: [ { type: 'building', id: 'mine', value: 1, }, ], gen: [ { type: 'resource', id: 'copper', value: 0.5, }, { type: 'resource', id: 'iron', value: 0.3, }, ], }, { id: 'artisan', req: [ { type: 'building', id: 'artisan_workshop', value: 1, }, ], gen: [ { type: 'resource', id: 'gold', value: 0.5, }, { type: 'resource', id: 'tools', value: 0.3, }, ], }, { id: 'merchant', req: [ { type: 'building', id: 'marketplace', value: 1, }, ], gen: [ { type: 'resource', id: 'gold', value: 3, }, ], }, { id: 'trader', req: [ { type: 'building', id: 'credit_union', value: 1, }, ], gen: [ { type: 'resource', id: 'gold', value: 6, }, ], }, { id: 'breeder', req: [ { type: 'building', id: 'stable', value: 1, }, ], gen: [ { type: 'resource', id: 'cow', value: 0.2, }, { type: 'resource', id: 'horse', value: 0.1, }, ], }, { id: 'carpenter', req: [ { type: 'building', id: 'carpenter_workshop', value: 1, }, ], gen: [ { type: 'resource', id: 'building_material', value: 0.3, }, { type: 'resource', id: 'wood', value: -3, }, { type: 'resource', id: 'stone', value: -1.5, }, { type: 'resource', id: 'tools', value: -0.5, }, ], }, { id: 'steelworker', req: [ { type: 'building', id: 'steelworks', value: 1, }, ], gen: [ { type: 'resource', id: 'steel', value: 0.4, }, { type: 'resource', id: 'copper', value: -1, }, { type: 'resource', id: 'iron', value: -0.5, }, ], }, { id: 'professor', req: [ { type: 'building', id: 'university', value: 1, }, ], gen: [ { type: 'resource', id: 'crystal', value: 0.06, }, { type: 'resource', id: 'research', value: 1, }, ], }, { id: 'researcher', req: [ { type: 'building', id: 'research_plant', value: 1, }, ], gen: [ { type: 'resource', id: 'research', value: 3, }, ], }, { id: 'supplier', req: [ { type: 'building', id: 'grocery', value: 1, }, ], gen: [ { type: 'resource', id: 'supplies', value: 0.4, }, { type: 'resource', id: 'food', value: -2, }, { type: 'resource', id: 'cow', value: -0.2, }, ], }, { id: 'skymancer', req: [ { type: 'building', id: 'observatory', value: 1, }, ], gen: [ { type: 'resource', id: 'faith', value: 3, }, { type: 'resource', id: 'mana', value: 3, }, ], }, { id: 'alchemist', req: [ { type: 'building', id: 'alchemic_laboratory', value: 1, }, ], gen: [ { type: 'resource', id: 'saltpetre', value: 0.7, }, ], }, { id: 'natro_refiner', req: [ { type: 'building', id: 'natronite_refinery', value: 1, }, ], gen: [ { type: 'resource', id: 'natronite', value: 1, }, { type: 'resource', id: 'mana', value: -5, }, { type: 'resource', id: 'saltpetre', value: -0.5, }, ], }, ] .filter((job) => job.gen && job.gen.length) .map((job) => { return { id: lang[`pop_${job.id}`], gen: job.gen .filter((gen) => gen.type === 'resource') .map((gen) => { return { id: lang[`res_${gen.id}`], value: gen.value, } }), } }) .map((job) => { return { id: job.id, isSafe: !job.gen.find((gen) => gen.value < 0), resourcesGenerated: job.gen .filter((gen) => gen.value > 0) .map((gen) => { return { id: gen.id, value: gen.value } }), resourcesUsed: job.gen .filter((gen) => gen.value < 0) .map((gen) => { return { id: gen.id, value: gen.value } }), } }) const resourcesToTrade = ['Cow', 'Horse', 'Food', 'Copper', 'Wood', 'Stone', 'Iron', 'Tools'] const timeToWaitUntilFullGold = 60 const minFarmers = 5 const sleep = (miliseconds) => new Promise((resolve) => setTimeout(resolve, miliseconds)) // https://stackoverflow.com/a/55366435 class NumberParser { constructor(locale) { const format = new Intl.NumberFormat(locale) const parts = format.formatToParts(12345.6) const numerals = Array.from({ length: 10 }).map((_, i) => format.format(i)) const index = new Map(numerals.map((d, i) => [d, i])) this._group = new RegExp(`[${parts.find((d) => d.type === 'group').value}]`, 'g') this._decimal = new RegExp(`[${parts.find((d) => d.type === 'decimal').value}]`) this._numeral = new RegExp(`[${numerals.join('')}]`, 'g') this._index = (d) => index.get(d) } parse(string) { let multiplier = 1 if (string.includes('K')) { multiplier = 1000 } else if (string.includes('M')) { multiplier = 1000000 } return (string = string.replace('K', '').replace('M', '').trim().replace(this._group, '').replace(this._decimal, '.').replace(this._numeral, this._index)) ? +string * multiplier : NaN } } const numberParser = new NumberParser() const formatTime = (timeToFormat) => { const timeValues = { seconds: 0, minutes: 0, hours: 0, days: 0, } let timeShort = '' let timeLong = '' timeValues.seconds = timeToFormat % 60 timeToFormat = (timeToFormat - (timeToFormat % 60)) / 60 timeValues.minutes = timeToFormat % 60 timeToFormat = (timeToFormat - (timeToFormat % 60)) / 60 timeValues.hours = timeToFormat % 24 timeToFormat = (timeToFormat - (timeToFormat % 24)) / 24 timeValues.days = timeToFormat if (timeValues.days) { timeShort += `${timeValues.days}d ` timeLong += `${timeValues.days} days ` } if (timeValues.hours) { timeShort += `${timeValues.hours}h ` timeLong += `${timeValues.hours} hrs ` } if (timeValues.minutes) { timeShort += `${timeValues.minutes}m ` timeLong += `${timeValues.minutes} min ` } if (timeValues.seconds) { timeShort += `${timeValues.seconds}s ` timeLong += `${timeValues.seconds} sec ` } timeShort = timeShort.trim() timeLong = timeLong.trim() return { timeShort, timeLong, timeValues, } } const logger = ({ msgLevel, msg }) => { const logText = `[TMH][${new Date().toLocaleTimeString()}] ${msg}` const levelsToLog = ['log', 'warn', 'error'] if (levelsToLog.includes(msgLevel)) { const logHolder = document.querySelector('#root > div > div > div > div.w-full.order-2.flex-grow.overflow-x-hidden.overflow-y-auto.pr-4') const tmhLogs = [...logHolder.querySelectorAll('.tmh-log')] if (tmhLogs.length > 10) { for (let i = 10; i < tmhLogs.length; i++) { tmhLogs[i].remove() } } const p = document.createElement('p') p.classList.add('text-xs', 'mb-2', 'text-green-600', 'tmh-log') p.innerText = logText logHolder.insertAdjacentElement('afterbegin', p) } console[msgLevel](logText) } const getAllButtons = () => { return [...document.querySelectorAll('#maintabs-container > div > div[role=tabpanel] button.btn.btn-dark:not(.btn-off)')] } const getResource = (resourceName = 'Gold') => { let resourceFound = false let resourceToFind = { name: resourceName, current: 0, max: 0, speed: 0, ttf: null, ttz: null } const resources = [...document.querySelectorAll('#root div > div > div > table > tbody > tr > td:nth-child(1) > span')] resources.map((resource) => { if (resource.textContent.includes(resourceName)) { resourceFound = true const values = resource.parentNode.parentNode.childNodes[1].textContent .split('/') .map((x) => numberParser.parse(x.replace(/[^0-9KM\-,\.]/g, '').trim())) resourceToFind.current = values[0] resourceToFind.max = values[1] resourceToFind.speed = numberParser.parse(resource.parentNode.parentNode.childNodes[2].textContent.replace(/[^0-9KM\-,\.]/g, '').trim()) || 0 resourceToFind.ttf = resourceToFind.speed > 0 && resourceToFind.max !== resourceToFind.current ? formatTime(Math.ceil((resourceToFind.max - resourceToFind.current) / resourceToFind.speed)) : null resourceToFind.ttz = resourceToFind.speed < 0 && resourceToFind.current ? formatTime(Math.ceil(resourceToFind.current / (resourceToFind.speed * -1))) : null } }) return resourceFound ? resourceToFind : null } const hasUnassignedPopulation = () => { let unassignedPopulation = false const navButtons = document.querySelectorAll('#main-tabs > div > button') navButtons.forEach((button) => { if (button.innerText.includes(KEYS.PAGES.POPULATION)) { unassignedPopulation = !!button.querySelector('span') } }) return unassignedPopulation } const canAffordBA = () => { return false } const shouldBuyBA = () => { return false } const lastSell = {} const shouldSell = () => { return !!resourcesToTrade.find((resName) => { if (!lastSell[resName]) lastSell[resName] = 0 const res = getResource(resName) if ( res && (res.current === res.max || res.current + res.speed * timeToWaitUntilFullGold >= res.max) && lastSell[resName] + 90 * 1000 < new Date().getTime() ) return true }) } const KEYS = { PAGES: { BUILD: 'Build', RESEARCH: 'Research', POPULATION: 'Population', ARMY: 'Army', MARKETPLACE: 'Marketplace', MAGIC: 'Magic', }, } const hasPage = (page) => { const navButtons = [...document.querySelectorAll('#main-tabs > div > button')] return !!navButtons.find((button) => button.innerText.includes(page)) } const switchPage = async (page) => { let foundPage = hasPage(page) if (!foundPage) { await switchPage(KEYS.PAGES.BUILD) return } let pageButton let switchedPage = false const navButtons = document.querySelectorAll('#main-tabs > div > button') navButtons.forEach((button) => { if (button.innerText.includes(page) && button.getAttribute('aria-selected') !== 'true') { pageButton = button } }) if (pageButton) { pageButton.click() switchedPage = true } await sleep(2000) if (switchedPage) { logger({ msgLevel: 'debug', msg: `Switched page to ${page}` }) } } const pages = [ { id: KEYS.PAGES.BUILD, action: async () => { await switchPage(KEYS.PAGES.BUILD) let buttons = getAllButtons() .map((button) => { const id = button.innerText.split('\n').shift() const count = button.querySelector('span') ? numberParser.parse(button.querySelector('span').innerText) : 0 return { id: id, element: button, count: count, building: buildingsList.find((building) => building.id === id) } }) .filter((button) => button.building) .filter((button) => !button.building.max || button.count < button.building.max) .sort((a, b) => a.building.order - b.building.order) if (buttons.length) { while (!scriptPaused && buttons.length) { let shouldBuild = true const notBuiltIndex = buttons.findIndex((button) => button.count === 0) const button = notBuiltIndex > -1 ? buttons[notBuiltIndex] : buttons.shift() if (!button.building.isSafe) { const requiredResource = getResource(button.building.requires.resource) if (!requiredResource) { shouldBuild = false } else { if (button.id === 'Common House' && button.count < 2) { shouldBuild = true } else { shouldBuild = shouldBuild && requiredResource[button.building.requires.parameter] > button.building.requires.minValue && (!button.building.max || button.count < button.building.max) } } } if (shouldBuild) { button.element.click() logger({ msgLevel: 'log', msg: `Building ${button.building.id}` }) await sleep(6000) buttons = getAllButtons() .map((button) => { const id = button.innerText.split('\n').shift() const count = button.querySelector('span') ? numberParser.parse(button.querySelector('span').innerText) : 0 return { id: id, element: button, count: count, building: buildingsList.find((building) => building.id === id) } }) .filter((button) => button.building) .filter((button) => !button.building.max || button.count < button.building.max) .sort((a, b) => a.building.order - b.building.order) } else if (notBuiltIndex > -1) { buttons.splice(notBuiltIndex, 1) } } } await sleep(5000) }, }, { id: KEYS.PAGES.RESEARCH, action: async () => { await switchPage(KEYS.PAGES.RESEARCH) const allowedResearches = [ 'Housing', 'Agriculture', 'Wood cutting', 'Stone masonry', 'Storage', 'Breeding', 'Crop Rotation', 'Woodcarvers', 'Pottery', 'Mining', 'Stone extraction tools', 'Bronze working', 'Servitude', 'Mining efficiency', 'Iron working', 'Writing', 'Religion', 'Mythology', 'Municipal Administration', 'Local products', 'End Ancient Era', 'Feudalism', 'Education', 'Food conservation', 'Architecture', 'Establish boundaries', 'Canava herald', 'Monument', 'Heirloom of the Contract', 'Heirloom of the Horseshoes', 'Heirloom of the Housing', 'Heirloom of the Momento', 'A moonlight night', ] const optionalResearches = [ 'Grain surplus', 'Wood saw', 'Mathematics', 'Metal casting', 'Guild', 'Banking', 'Currency', 'Regional Markets', 'Guild of the Craftsmen', 'Remember the Ancients', ] const research = getResource('Research') let buttonsList = getAllButtons().filter((button) => allowedResearches.includes(button.innerText.split('\n').shift())) let optionalButtonsList = getAllButtons().filter((button) => optionalResearches.includes(button.innerText.split('\n').shift())) if (!buttonsList.length && optionalButtonsList.length && research && research.speed > 50) { buttonsList = optionalButtonsList } if (buttonsList.length) { while (!scriptPaused && buttonsList.length) { const button = buttonsList.shift() button.click() logger({ msgLevel: 'log', msg: `Researching ${button.innerText.split('\n').shift()}` }) await sleep(6000) if (button.innerText.split('\n').shift() === 'A moonlight night') { document .querySelector( '#headlessui-portal-root > div > div > div > div.fixed.z-10.inset-0.overflow-y-auto > div > div > div > div > div.w-full.text-center.flex.lg\\:block.lg\\:text-right > button.btn.px-6.lg\\:mt-0.btn-red' ) .click() await sleep(5000) document.querySelector('#headlessui-portal-root > div > div > div div > div > div.text-center.mb-6.px-9.lg\\:px-0 > button').click() await sleep(5000) document .querySelector( '#headlessui-portal-root > div > div > div div > div > div.w-full.text-center.flex.lg\\:block.lg\\:text-right > button.btn.px-6.lg\\:mt-0.btn-red' ) .click() await sleep(2000) document .querySelector( '#headlessui-portal-root > div > div > div div > div > div.w-full.text-center.flex.lg\\:block.lg\\:text-right > button.btn.px-6.lg\\:mt-0.btn-red' ) .click() await sleep(2000) document .querySelector( '#headlessui-portal-root > div > div > div div > div > div.w-full.text-center.flex.lg\\:block.lg\\:text-right > button.btn.px-6.lg\\:mt-0.btn-red' ) .click() await sleep(10000) break } buttonsList = getAllButtons().filter((button) => allowedResearches.includes(button.innerText.split('\n').shift())) optionalButtonsList = getAllButtons().filter((button) => optionalResearches.includes(button.innerText.split('\n').shift())) if (!buttonsList.length && optionalButtonsList.length && research && research.speed > 50) { buttonsList = optionalButtonsList } } } await sleep(5000) }, }, { id: KEYS.PAGES.POPULATION, action: async () => { await switchPage(KEYS.PAGES.POPULATION) let canAssignJobs = true const container = document.querySelector('#maintabs-container > div > div[role=tabpanel]') let availablePop = container .querySelector('div > span.ml-2') .textContent.split('/') .map((pop) => numberParser.parse(pop.trim())) const availableJobsQSA = container.querySelectorAll('h5') const availableJobs = [] availableJobsQSA.forEach((job) => { const jobTitle = job.textContent.trim() availableJobs.push({ ...allJobs.find((allJob) => allJob.id === jobTitle), container: job.parentElement.parentElement, current: +job.parentElement.parentElement.querySelector('input').value.split('/').shift().trim(), max: +job.parentElement.parentElement.querySelector('input').value.split('/').pop().trim(), }) }) if (availablePop[0] > 0) { while (!scriptPaused && canAssignJobs) { const jobsWithSpace = availableJobs.filter((job) => !!job.container.querySelector('button.btn-green')) canAssignJobs = false if (jobsWithSpace.length) { const foodJob = jobsWithSpace.find((job) => job.resourcesGenerated.find((res) => res.id === 'Food')) const supplierJob = jobsWithSpace.find((job) => job.resourcesGenerated.find((res) => res.id === 'Supplies')) if ( foodJob && (getResource('Food').speed <= 1 || (availablePop[0] >= 5 && getResource('Food').speed <= 5) || (supplierJob && getResource('Food').speed <= 5)) ) { const addJobButton = foodJob.container.querySelector('button.btn-green') if (addJobButton) { logger({ msgLevel: 'log', msg: `Assigning worker as ${foodJob.id}` }) addJobButton.click() canAssignJobs = true await sleep(1000) } } else { let unassigned = container .querySelector('div > span.ml-2') .textContent.split('/') .map((pop) => numberParser.parse(pop.trim())) .shift() if (unassigned > 0) { const resources = [ 'Natronite', 'Saltpetre', 'Tools', 'Wood', 'Stone', 'Iron', // 'Copper', // Same as Iron 'Mana', // 'Faith', // Same as Mana 'Research', 'Materials', 'Steel', 'Supplies', 'Gold', 'Crystal', 'Horse', // 'Cow', // Same as Horse ] .filter((res) => getResource(res)) .filter((res) => jobsWithSpace.find((job) => job.resourcesGenerated.find((resGen) => resGen.id === res))) const resourcesWithNegativeGen = resources.filter((res) => getResource(res) && res.speed < 0) const resourcesWithNoGen = resources.filter((res) => !resourcesWithNegativeGen.includes(res) && getResource(res) && !res.speed) const resourcesLeft = resources.filter((res) => !resourcesWithNegativeGen.includes(res) && !resourcesWithNoGen.includes(res)) const resourcesSorted = resourcesWithNegativeGen.concat(resourcesWithNoGen).concat(resourcesLeft) if (resourcesSorted.length) { for (let i = 0; i < resourcesSorted.length && !scriptPaused; i++) { if (unassigned === 0) break const resourceName = resourcesSorted[i] const jobsForResource = jobsWithSpace .filter((job) => job.resourcesGenerated.find((resGen) => resGen.id === resourceName)) .sort( (a, b) => b.resourcesGenerated.find((resGen) => resGen.id === resourceName).value - a.resourcesGenerated.find((resGen) => resGen.id === resourceName).value ) if (jobsForResource.length) { for (let i = 0; i < jobsForResource.length && !scriptPaused; i++) { if (unassigned === 0) break const job = jobsForResource[i] let isSafeToAdd = true if (!job.isSafe) { job.resourcesUsed.forEach((resUsed) => { const res = getResource(resUsed.id) if (!res || res.speed < Math.abs(resUsed.value * 2)) { isSafeToAdd = false } }) } if (isSafeToAdd) { const addJobButton = job.container.querySelector('button.btn-green') if (addJobButton) { logger({ msgLevel: 'log', msg: `Assigning worker as ${job.id}` }) addJobButton.click() unassigned -= 1 canAssignJobs = !!unassigned await sleep(1000) } } } } } } } } } const unassigned = container .querySelector('div > span.ml-2') .textContent.split('/') .map((pop) => numberParser.parse(pop.trim())) .shift() if (unassigned === 0) { canAssignJobs = false } await sleep(10) } } await sleep(5000) }, }, { id: KEYS.PAGES.ARMY, action: async () => { await sleep(1) }, }, { id: KEYS.PAGES.MARKETPLACE, action: async () => { await switchPage(KEYS.PAGES.MARKETPLACE) let gold = getResource('Gold') if (gold && gold.current < gold.max && shouldSell()) { const resourceHoldersQSA = document.querySelectorAll('div > div.tab-container > div > div > div') const resourceHolders = [] if (resourceHoldersQSA) { resourceHoldersQSA.forEach((resourceHolder) => { const resNameElem = resourceHolder.querySelector('h5') if (resNameElem) { const resName = resNameElem.innerText const res = getResource(resName) if (resourcesToTrade.includes(resName) && res && (res.current === res.max || res.current + res.speed * timeToWaitUntilFullGold >= res.max)) { resourceHolders.push(resourceHolder) } } }) } let goldEarned = 0 let soldTotals = {} for (let i = 0; i < resourceHolders.length && !scriptPaused; i++) { gold = getResource('Gold') const resourceHolder = resourceHolders[i] const resName = resourceHolder.querySelector('h5').innerText let res = getResource(resName) const initialPrice = numberParser.parse(resourceHolder.querySelector('div:nth-child(2) > div > table > tbody > tr > td:nth-child(2)').innerText) let price = initialPrice let sellButtons = resourceHolder.querySelectorAll('div > div.grid.gap-3 button.btn-red:not(.btn-dark)') while ( !scriptPaused && sellButtons && sellButtons.length && gold.current < gold.max && res.current + res.speed * timeToWaitUntilFullGold * 2 >= res.max ) { let maxSellButton = 2 const missingResToSell = Math.ceil((gold.max - gold.current) / price) if (missingResToSell < 80) { maxSellButton = 0 } else if (missingResToSell < 800) { maxSellButton = 1 } maxSellButton = Math.min(maxSellButton, sellButtons.length - 1) sellButtons[maxSellButton].click() lastSell[resName] = new Date().getTime() soldTotals[resName] = soldTotals[resName] ? soldTotals[resName] : { amount: 0, gold: 0 } soldTotals[resName].amount += +sellButtons[maxSellButton].innerText soldTotals[resName].gold += +sellButtons[maxSellButton].innerText * price logger({ msgLevel: 'debug', msg: `Selling ${sellButtons[maxSellButton].innerText} of ${res.name} for ${price}` }) goldEarned += numberParser.parse(sellButtons[maxSellButton].innerText) * price await sleep(10) sellButtons = resourceHolder.querySelectorAll('div:nth-child(2) > div.grid.gap-3 button:not(.btn-dark)') gold = getResource('Gold') res = getResource(resName) price = numberParser.parse(resourceHolder.querySelector('div:nth-child(2) > div > table > tbody > tr > td:nth-child(2)').innerText) await sleep(1) if (price / initialPrice < 0.1) { break } } } if (goldEarned) { const totals = Object.keys(soldTotals) .filter((resName) => soldTotals[resName] && soldTotals[resName].gold && soldTotals[resName].amount) .map( (resName) => `${resName}: ${new Intl.NumberFormat().format(soldTotals[resName].amount)} units for ${new Intl.NumberFormat().format( Math.round(soldTotals[resName].gold) )} gold (avg price: ${(soldTotals[resName].gold / soldTotals[resName].amount).toFixed(2)})` ) logger({ msgLevel: 'log', msg: `Earned ${new Intl.NumberFormat().format(goldEarned)} gold on Marketplace [${totals.join(', ')}]` }) } } await sleep(5000) }, }, ] window.switchFastPrestigeScriptState = () => { scriptPaused = !scriptPaused window.localStorage.setItem('TMH_cheatsOff', JSON.stringify(false)) window.localStorage.setItem('TMH_scriptPaused', JSON.stringify(scriptPaused)) if (!scriptPaused) { mainLoop() } } const lastVisited = { [KEYS.PAGES.BUILD]: 1, [KEYS.PAGES.RESEARCH]: 0, [KEYS.PAGES.POPULATION]: 0, [KEYS.PAGES.ARMY]: 0, [KEYS.PAGES.MARKETPLACE]: 0, } const mainLoop = async () => { if (cheatsOff) return if (mainLoopRunning) { setTimeout(mainLoop, 1000) return } mainLoopRunning = true while (!scriptPaused) { const should = { [KEYS.PAGES.BUILD]: () => { return hasPage(KEYS.PAGES.BUILD) && lastVisited[KEYS.PAGES.BUILD] < lastVisited[KEYS.PAGES.RESEARCH] }, [KEYS.PAGES.RESEARCH]: () => { return hasPage(KEYS.PAGES.RESEARCH) && lastVisited[KEYS.PAGES.RESEARCH] < lastVisited[KEYS.PAGES.BUILD] }, [KEYS.PAGES.POPULATION]: () => { return hasPage(KEYS.PAGES.POPULATION) && hasUnassignedPopulation() }, [KEYS.PAGES.ARMY]: () => { const timeout = lastVisited[KEYS.PAGES.ARMY] + 2 * 60 * 1000 < new Date().getTime() return hasPage(KEYS.PAGES.ARMY) && canAffordBA() && shouldBuyBA() && timeout }, [KEYS.PAGES.MARKETPLACE]: () => { const gold = getResource('Gold') return hasPage(KEYS.PAGES.MARKETPLACE) && gold.current + gold.speed * timeToWaitUntilFullGold < gold.max && shouldSell() }, } const pagesToCheck = [KEYS.PAGES.POPULATION, KEYS.PAGES.MARKETPLACE, KEYS.PAGES.RESEARCH, KEYS.PAGES.BUILD] while (!scriptPaused && pagesToCheck.length) { const pageToCheck = pagesToCheck.shift() if (should[pageToCheck] && should[pageToCheck]()) { const page = pages.find((page) => page.id === pageToCheck) if (page) { logger({ msgLevel: 'debug', msg: `Executing ${page.id} action` }) lastVisited[page.id] = new Date().getTime() await page.action() await sleep(1000) } } const ancestorPage = document.querySelector( '#root > div.mt-6.lg\\:mt-12.xl\\:mt-24.\\32 xl\\:mt-12.\\34 xl\\:mt-24 > div > div.text-center > p.mt-6.lg\\:mt-8.text-lg.lg\\:text-xl.text-gray-500.dark\\:text-gray-400' ) if (ancestorPage) { const ancestor = [...document.querySelectorAll('button.btn')].find((button) => button.parentElement.innerText.includes('Gathering')) ancestor.click() await sleep(5000) } } await sleep(1000) } mainLoopRunning = false } const managePanel = () => { if (cheatsOff) return const controlPanel = document.querySelector('div#theresMoreHelpControlPanel') let scriptState = scriptPaused ? `▶️` : `⏸️` if (!controlPanel) { const controlPanelElement = document.createElement('div') controlPanelElement.id = 'theresMoreHelpControlPanel' controlPanelElement.classList.add('dark') controlPanelElement.classList.add('dark:bg-mydark-300') controlPanelElement.style.position = 'fixed' controlPanelElement.style.bottom = '10px' controlPanelElement.style.left = '10px' controlPanelElement.style.zIndex = '99999999' controlPanelElement.style.border = '1px black solid' controlPanelElement.style.padding = '10px' controlPanelElement.innerHTML = ` <p class="mb-2">TheresMoreFastPrestige: <button onClick="window.switchFastPrestigeScriptState()" title="Start/stop script" class="scriptState">${scriptState}</button> </p> ` document.querySelector('div#root').insertAdjacentElement('afterend', controlPanelElement) } else { controlPanel.querySelector('.scriptState').textContent = scriptState } if (!scriptPaused) { const fullPageOverlay = document.querySelector('div > div.absolute.top-0.right-0.z-20.pt-4.pr-4 > button') if (fullPageOverlay) { fullPageOverlay.click() } } } const calculateTTF = () => { const resourceTrNodes = document.querySelectorAll('#root > div > div:not(#maintabs-container) > div > div > div > table:not(.hidden) > tbody > tr') resourceTrNodes.forEach((row) => { const cells = row.querySelectorAll('td') const resourceName = cells[0].textContent.trim() const resource = getResource(resourceName) let ttf = '' if (resource && resource.current < resource.max && resource.speed) { ttf = resource.ttf ? resource.ttf.timeShort : resource.ttz ? resource.ttz.timeShort : '' } if (!cells[3]) { const ttfElement = document.createElement('td') ttfElement.className = 'px-3 3xl:px-5 py-3 lg:py-2 3xl:py-3 whitespace-nowrap w-1/3 text-right' ttfElement.textContent = ttf row.appendChild(ttfElement) } else { cells[3].textContent = ttf } }) } const calculateTippyTTF = () => { let potentialResourcesToFillTable = document.querySelectorAll('div.tippy-box > div.tippy-content > div > div > table') if (potentialResourcesToFillTable.length) { potentialResourcesToFillTable = potentialResourcesToFillTable[0] const rows = potentialResourcesToFillTable.querySelectorAll('tr') rows.forEach((row) => { const cells = row.querySelectorAll('td') const resourceName = cells[0].textContent.trim() const resource = getResource(resourceName) if (resource) { let ttf = '✅' const target = numberParser.parse( cells[1].textContent .split(' ') .shift() .replace(/[^0-9KM\-,\.]/g, '') .trim() ) if (target > resource.max || resource.speed <= 0) { ttf = 'never' } else if (target > resource.current) { ttf = formatTime(Math.ceil((target - resource.current) / resource.speed)).timeShort } if (!cells[2]) { const ttfElement = document.createElement('td') ttfElement.className = 'px-4 3xl:py-1 text-right' ttfElement.textContent = ttf row.appendChild(ttfElement) } else { cells[2].textContent = ttf } } }) } } const tossACoinToYourClicker = async () => { if (cheatsOff) return if (!haveManualResourceButtons) return if (scriptPaused) return if (isClicking) return isClicking = true const manualResources = ['Food', 'Wood', 'Stone'].filter((resourceName) => { const resource = getResource(resourceName) if (resource && resource.current < Math.min(200, resource.max) && (!resource.speed || resource.speed <= 0)) { return true } }) const buttons = [ ...document.querySelectorAll('#root > div.flex.flex-wrap.w-full.mx-auto.p-2 > div.w-full.lg\\:pl-2 > div > div.order-2.flex.flex-wrap.gap-3 > button'), ] if (!buttons.length) { haveManualResourceButtons = false return } const buttonsToClick = buttons.filter((button) => manualResources.includes(button.innerText.trim())) while (!scriptPaused && buttonsToClick.length) { const buttonToClick = buttonsToClick.shift() buttonToClick.click() await sleep(250) } isClicking = false } const performRoutineTasks = async () => { calculateTTF() if (!cheatsOff) { managePanel() if (haveManualResourceButtons) tossACoinToYourClicker() } } const performFastTasks = async () => { calculateTippyTTF() } window.setInterval(performRoutineTasks, 1000) window.setInterval(performFastTasks, 100) const loadSettingsFromLocalStorage = () => { const TMH_scriptPaused = window.localStorage.getItem('TMH_scriptPaused') const TMH_cheatsOff = window.localStorage.getItem('TMH_cheatsOff') if (TMH_cheatsOff) { cheatsOff = JSON.parse(TMH_cheatsOff) } if (TMH_scriptPaused) { scriptPaused = JSON.parse(TMH_scriptPaused) } } loadSettingsFromLocalStorage() if (!cheatsOff) { await sleep(5000) if (!scriptPaused) { mainLoop() } } })()