// ==UserScript==
// @name TheresMoreHelp
// @namespace TheresMoreGame.com
// @match https://www.theresmoregame.com/play/
// @grant none
// @version 1.6
// @description Helper for TheresMoreGame
// @license MIT
// @run-at document-idle
// ==/UserScript==
;(async () => {
const cheatsOff = true
const buildingsList = [
{ name: 'City center', resources: [], alwaysBuild: true, isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
{ name: 'Cathedral', resources: [], alwaysBuild: true, isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
{ name: 'Great fair', resources: [], alwaysBuild: true, isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
{ name: 'Palisade', resources: [], alwaysBuild: true, isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
{ name: 'Wall', resources: [], alwaysBuild: true, isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
{
name: 'City center part',
resources: ['Gold', 'Wood', 'Stone', 'Copper', 'Iron', 'Tools'],
alwaysBuild: true,
isSafe: true,
requires: { resource: '', parameter: '', minValue: 0 },
},
{ name: 'Great fair unit', resources: ['Gold', 'Cow', 'Horse'], alwaysBuild: true, isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
{
name: 'Cathedral part',
resources: ['Gold', 'Wood', 'Stone', 'Materials', 'Supplies', 'Crystal'],
alwaysBuild: true,
isSafe: true,
requires: { resource: '', parameter: '', minValue: 0 },
},
{
name: 'Guild of craftsmen',
resources: ['Gold', 'Wood', 'Stone', 'Tools'],
alwaysBuild: true,
isSafe: true,
requires: { resource: '', parameter: '', minValue: 0 },
},
{
name: 'Library of Theresmore',
resources: ['Gold', 'Wood', 'Stone', 'Tools'],
alwaysBuild: true,
isSafe: true,
requires: { resource: '', parameter: '', minValue: 0 },
},
{
name: 'Watchman Outpost',
resources: ['Wood', 'Tools', 'Supplies', 'Crystal'],
alwaysBuild: true,
isSafe: true,
requires: { resource: '', parameter: '', minValue: 0 },
},
{ name: 'Foundry', resources: ['Gold', 'Wood', 'Stone', 'Copper', 'Iron', 'Tools'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
{ name: 'Bank', resources: ['Gold', 'Wood', 'Stone', 'Tools', 'Materials', 'Steel'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
{ name: 'Marketplace', resources: ['Gold', 'Wood', 'Copper', 'Iron', 'Tools'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
{ name: 'Artisan Workshop', resources: ['Gold', 'Wood', 'Stone'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
{ name: 'Granary', resources: ['Gold', 'Wood', 'Tools'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
{ name: 'Fiefdom', resources: ['Gold', 'Wood', 'Stone', 'Copper', 'Iron', 'Tools'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
{ name: 'University', resources: ['Gold', 'Wood', 'Stone', 'Tools', 'Materials'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
{ name: 'School', resources: ['Gold', 'Wood', 'Stone', 'Tools'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
{ name: 'Lumberjack Camp', resources: ['Gold', 'Wood', 'Stone'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
{ name: 'Quarry', resources: ['Gold', 'Wood', 'Stone'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
{ name: 'Mine', resources: ['Gold', 'Wood', 'Stone'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
{ name: 'Palisade part', resources: ['Wood', 'Tools'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
{ name: 'Wall part', resources: ['Stone', 'Tools'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
{ name: 'Farm', resources: ['Gold', 'Wood'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
{ name: 'Temple', resources: ['Gold', 'Wood', 'Stone', 'Copper', 'Iron', 'Tools'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
{ name: 'Altar of sacrifices', resources: ['Gold', 'Tools', 'Cow', 'Horse'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
{ name: 'Stable', resources: ['Gold', 'Wood', 'Tools'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
{ name: 'Valley of plenty', resources: ['Gold', 'Tools', 'Materials'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
{ name: 'Magic Circle', resources: ['Stone', 'Copper', 'Faith'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
{ name: 'Carpenter workshop', resources: ['Gold', 'Wood', 'Stone', 'Iron', 'Tools'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
{ name: 'Grocery', resources: ['Gold', 'Tools', 'Materials'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
{ name: 'Steelworks', resources: ['Gold', 'Tools', 'Materials'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
{ name: 'Ballista', resources: ['Wood', 'Materials'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
{
name: 'Recruit training center',
resources: ['Gold', 'Iron', 'Tools', 'Materials'],
isSafe: true,
requires: { resource: '', parameter: '', minValue: 0 },
},
{
name: 'Large storehouse',
resources: ['Wood', 'Stone', 'Tools', 'Materials', 'Steel'],
isSafe: true,
requires: { resource: '', parameter: '', minValue: 0 },
},
{ name: 'Store', resources: ['Wood', 'Stone', 'Tools'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
{ name: 'Barracks', resources: ['Gold', 'Wood', 'Stone', 'Iron', 'Tools'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
{ name: 'Fortune grove', resources: ['Faith', 'Mana'], isSafe: true, requires: { resource: '', parameter: '', minValue: 0 } },
{
name: 'Mansion',
resources: ['Gold', 'Wood', 'Stone', 'Tools', 'Materials'],
isSafe: false,
requires: { resource: 'Food', parameter: 'speed', minValue: 3 },
},
{
name: 'City Hall',
resources: ['Gold', 'Wood', 'Stone', 'Copper', 'Iron', 'Tools'],
isSafe: false,
requires: { resource: 'Food', parameter: 'speed', minValue: 1.5 },
},
{ name: 'Common House', resources: ['Wood', 'Stone'], isSafe: false, requires: { resource: 'Food', parameter: 'speed', minValue: 1 } },
].filter((building) => building.name)
const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
// 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 modes = {
BUILD: 'Build',
DEFAULT: 'Default',
}
let mode = modes.DEFAULT
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 findResource = (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.forEach((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())
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 getAllResources = () => {
const resources = document.querySelectorAll('#root div > div > div > table > tbody > tr > td:nth-child(1) > span')
const resourceNames = []
resources.forEach((resource) => resourceNames.push(resource.textContent.trim()))
return resourceNames
}
let shouldStopClicking = false
window.stopClicking = () => (shouldStopClicking = true)
window.generateGold = async () => {
window.stopClicking()
await sleep(10)
shouldStopClicking = false
let gold = findResource('Gold')
if (gold && gold.current < gold.max) {
const buttons = document.querySelectorAll('[id^="headlessui-tabs-tab-"]')
let marketplace = false
buttons.forEach((button) => {
if (button.innerText.includes('Marketplace')) {
marketplace = button
}
})
if (marketplace) {
marketplace.click()
await sleep(100)
const buyHorses = document.querySelector(
'div > div > div.grid.gap-3.grid-cols-fill-240.min-w-full > div:nth-child(8) > div.grid.gap-2.grid-cols-2 > button:nth-child(4)'
)
const sellHorses = document.querySelector(
'div > div > div.grid.gap-3.grid-cols-fill-240.min-w-full > div:nth-child(8) > div.grid.gap-2.grid-cols-2 > button:nth-child(3)'
)
while (gold && gold.current < gold.max && !shouldStopClicking) {
buyHorses.click()
await sleep(1)
sellHorses.click()
await sleep(1)
gold = findResource('Gold')
}
}
}
shouldStopClicking = false
}
window.getManualResources = async () => {
window.stopClicking()
await sleep(10)
shouldStopClicking = false
const manualResources = ['Food', 'Wood', 'Stone']
const resourcesToClick = []
const buttonsToClick = []
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'
)
manualResources.forEach((resourceName) => {
const resource = findResource(resourceName)
if (resource && resource.current < resource.max) {
resourcesToClick.push(resourceName)
}
})
buttons.forEach((button) => {
if (resourcesToClick.includes(button.innerText.trim())) {
buttonsToClick.push(button)
}
})
while (buttonsToClick.length && !shouldStopClicking) {
const buttonToClick = buttonsToClick.shift()
const resource = findResource(buttonToClick.innerText.trim())
if (resource && resource.current < resource.max) {
buttonToClick.click()
await sleep(1)
buttonsToClick.push(buttonToClick)
}
}
shouldStopClicking = false
}
const spendResourcesBuilding = async () => {
if (cheatsOff) return
let isOnBuildPage = false
const navButtons = document.querySelectorAll('#main-tabs > div > button')
navButtons.forEach((button) => {
if (button.innerText.includes('Build') && button.getAttribute('aria-selected') === 'true') {
isOnBuildPage = true
}
})
if (!isOnBuildPage) {
setTimeout(spendResourcesBuilding, 2000)
return
}
const resourceList = getAllResources().map((resource) => findResource(resource))
const resourceNamesList = resourceList.map((resource) => resource.name)
const buttonsList = document.querySelectorAll('button.btn:not(.btn-off)')
const buildingNames = []
buttonsList.forEach((button) => buildingNames.push(button.innerText.split('\n').shift()))
let hasBuilt = false
for (let i = 0; i < buildingsList.length && !hasBuilt; i++) {
if (hasBuilt) break
const building = buildingsList[i]
let shouldBuild = true
building.resources.forEach((resource) => (shouldBuild = shouldBuild && resourceNamesList.includes(resource)))
shouldBuild = shouldBuild || !!building.alwaysBuild
if (!building.isSafe) {
const requiredResource = findResource(building.requires.resource)
if (!requiredResource) {
shouldBuild = false
} else {
shouldBuild = shouldBuild && requiredResource[building.requires.parameter] > building.requires.minValue
}
}
if (shouldBuild && !hasBuilt) {
if (building.alwaysBuild || mode === modes.BUILD) {
buttonsList.forEach((button) => {
if (button.innerText.split('\n').shift() === building.name) {
button.click()
hasBuilt = true
mode = modes.DEFAULT
console.log(`Started building ${building.name}`)
}
})
} else {
mode = modes.BUILD
}
}
}
setTimeout(spendResourcesBuilding, 15000)
}
const createPanel = () => {
if (cheatsOff) return
const controlPanel = document.querySelector('div#theresMoreHelpControlPanel')
if (!controlPanel) {
const controlPanelElement = document.createElement('div')
controlPanelElement.id = 'theresMoreHelpControlPanel'
controlPanelElement.style.position = 'fixed'
controlPanelElement.style.bottom = '20px'
controlPanelElement.style.left = '20px'
controlPanelElement.style.zIndex = '99999999'
controlPanelElement.innerHTML = `
<button onClick="window.stopClicking()" title="Stop auto-clicking">🛑</button>
<button onClick="window.getManualResources()" title="Get Manual Resources"><svg viewBox="0 0 24 24" role="presentation" class="icon inline -mt-1 mr-2"><path d="M14.79,10.62L3.5,21.9L2.1,20.5L13.38,9.21L14.79,10.62M19.27,7.73L19.86,7.14L19.07,6.35L19.71,5.71L18.29,4.29L17.65,4.93L16.86,4.14L16.27,4.73C14.53,3.31 12.57,2.17 10.47,1.37L9.64,3.16C11.39,4.08 13,5.19 14.5,6.5L14,7L17,10L17.5,9.5C18.81,11 19.92,12.61 20.84,14.36L22.63,13.53C21.83,11.43 20.69,9.47 19.27,7.73Z" style="fill: currentcolor;"></path></svg></button>
<button onClick="window.generateGold()" title="Get Gold From Market"><svg viewBox="0 0 24 24" role="presentation" class="icon inline -mt-1 mr-2"><path d="M1 22L2.5 17H9.5L11 22H1M13 22L14.5 17H21.5L23 22H13M6 15L7.5 10H14.5L16 15H6M23 6.05L19.14 7.14L18.05 11L16.96 7.14L13.1 6.05L16.96 4.96L18.05 1.1L19.14 4.96L23 6.05Z" style="fill: currentcolor;"></path></svg></button>
`
document.querySelector('div#root').insertAdjacentElement('afterend', controlPanelElement)
}
}
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 = findResource(resourceName)
let ttf = ''
if (resource && resource.current < resource.max && resource.speed) {
ttf = resource.ttf ? resource.ttf.timeLong : resource.ttz.timeLong
}
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 = findResource(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 performRoutineTasks = () => {
createPanel()
calculateTTF()
}
const performFastTasks = () => {
calculateTippyTTF()
}
spendResourcesBuilding()
window.setInterval(performRoutineTasks, 1000)
window.setInterval(performFastTasks, 100)
})()