Last active
October 26, 2024 20:14
-
-
Save Zirak/63a778031e7cab40aa80a8787eec158c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
typeof zInterval !== 'undefined' && clearInterval(zInterval); | |
var zInterval = setInterval(loop, 100); | |
var zState = Object.assign({ | |
// TODO: Magical challenges | |
challengeCounter: 1, | |
// For tracking challenges and sigils | |
resetAt: null, | |
// For tracking hell | |
hellResetAt: Date.now(), | |
// Holy symbols | |
holyResetAt: null, | |
}, typeof zState === 'undefined' ? {} : zState); | |
var realMagicReset = typeof realMagicReset === 'undefined' ? magicReset : realMagicReset; | |
var magicReset = function() { | |
zState.resetAt = Date.now(); | |
return realMagicReset.apply(this, arguments); | |
}; | |
var realPolyReset = typeof realPolyReset === 'undefined' ? holyPolyhedronReset : realPolyReset; | |
var holyPolyhedronReset = function() { | |
zState.holyResetAt = Date.now(); | |
return realPolyReset.apply(this, arguments); | |
}; | |
function loop() { | |
const timeSinceReset = Date.now() - zState.resetAt; | |
// Get ourselves as least one miner | |
const moneyForMiner = 20; | |
if (game.gold.lte(moneyForMiner)) { | |
produceGold(); | |
} | |
// Stolen from game code: How many miners the current gold can buy | |
const minersWeCanAfford = Decimal.affordGeometricSeries( | |
game.gold, | |
20, | |
new Decimal(1.1).sub(game.platinumUpgradesBought[3] * 0.005), | |
game.miners | |
); | |
// Ensure we have at least *one* miner. Then, increase our miner count by | |
// 25% a pop | |
const hasAnyMiners = game.miners.gt(0); | |
const potentialMinersVsCurrent = minersWeCanAfford.div(game.miners); | |
if (!hasAnyMiners || potentialMinersVsCurrent.gt(0.25)) { | |
buyMaxMiners(); | |
} | |
// The logic for when new dragons are unlocked gets funky, so let's just | |
// iterate over the elements | |
[...document.querySelectorAll('.upgradeDragonButton')] | |
.forEach(clickclack); | |
dragonFeed(); | |
// Same with dragon playtime, just check display state. We want to be a | |
// *little* smart since without the autoplay, we can't run challenges during | |
// playtime. So do it until the cap. It's not optimised but eh | |
if ( | |
dragonAffectionStuff.style.display !== 'none' && | |
!document.getElementById("dragonTimeEffectCap").textContent | |
) { | |
dragonSpendTime(); | |
} | |
// ...but we should always try to pet the dragon, because it can be hungy | |
dragonPet(); | |
// Likewise for unlocks (dragon, alchemy, magic, etc.) - it's super weird. I | |
// don't have all of these yet, and they don't have any coherent naming, so | |
// let's just...list them out? | |
[ | |
'unlockDragonButton', | |
'unlockAlchemyButton', | |
'unlockMagicButton', | |
'moreMagicUpgradesButton', | |
'morePUupgradesButton', | |
'unlockDarkMagicUpgradesButton', | |
'unlockBloodButton', | |
'moreDarkMagicUpgradesButton', | |
'unlockVoidMagicUpgradesButton', | |
'unlockPlanetsButton', | |
'omniverseWarning', | |
'unlockEssencesButton', | |
'unlockDeathEssenceButton', | |
// odd one out buuutt | |
'buyFireUpgradesButton', | |
] | |
.map((id) => document.getElementById(id)) | |
.filter(clickclack); | |
// Max all fire upgrades except gold-to-click | |
for (let i = 1; i <= 6; i += 1) { | |
i !== 3 && fireBuyMax(i) | |
} | |
// Prefer waiting to buy the passive platinum buff | |
const gigaPlatinumIndex = 4; | |
if (!game.platinumUpgradesBought[gigaPlatinumIndex]) { | |
// buy = index + 1 for some reason (only for platinum) | |
buyPlatinumUpgrade(gigaPlatinumIndex + 1); | |
} else { | |
platinumMaxAll(); | |
} | |
uraniumMaxAll(); | |
plutoniumMaxAll(); | |
[...document.querySelectorAll('.oganessonUpgrade')].forEach(clickclack); | |
magicUpgradeBuyMax(); | |
darkMagicUpgradeBuyMax(); | |
[ | |
...document.querySelectorAll('.voidMagicUpgrade') | |
].forEach(clickclack); | |
// After some trial and error, the challenges are roughly ordered like this, in terms of least negative effect: | |
// | |
// B2 > B1 > A1 > A2 | |
const challenges = [ | |
// A1 A2 B1 B2 | |
[false, false, false, true], | |
[false, false, true, true], | |
[true, false, true, true], | |
[true, true, true, true], | |
]; | |
// Because challenges are very resource intensive, let's only run them for | |
// 10 seconds, giving 30 seconds of peace between them | |
// TODO: What I just said above | |
if (tab_magicChallenges.style.display !== 'none') { | |
} | |
// Reset when we'd gain 125% magic, as long as we're not capped | |
// | |
// TODO: This isn't good for the early game, since each magic is super | |
// meaningful | |
const isFirstMagic = game.magicToGet.gt(0) && game.magic.eq(0); | |
if (isFirstMagic || | |
(game.magicToGet.div(game.magic) >= 1.25 && !magicEffectCap.textContent) | |
) { | |
magicReset(); | |
} | |
[ | |
...document.querySelectorAll('.cyanSigilUpgrade'), | |
...document.querySelectorAll('.blueSigilUpgrade'), | |
...document.querySelectorAll('.indigoSigilUpgrade'), | |
...document.querySelectorAll('.violetSigilUpgrade'), | |
...document.querySelectorAll('.pinkSigilUpgrade'), | |
...document.querySelectorAll('.redSigilUpgrade'), | |
...document.querySelectorAll('.orangeSigilUpgrade'), | |
...document.querySelectorAll('.yellowSigilUpgrade'), | |
].forEach(clickclack); | |
// Sigils | |
// | |
// I don't have a good logic here, so here goes something: Wait some time | |
// after resetting to let gold/magic accumulate. Then, reset for the sigil | |
// that'll give the best (expected / current) ratio | |
const sigils = [ | |
{ | |
name: 'cyan', | |
index: 1, | |
current: game.cyanSigils, | |
expected: game.cyanSigilsToGet, | |
}, | |
{ | |
name: 'blue', | |
index: 2, | |
current: game.blueSigils, | |
expected: game.blueSigilsToGet, | |
}, | |
{ | |
name: 'indigo', | |
index: 3, | |
current: game.indigoSigils, | |
expected: game.indigoSigilsToGet, | |
}, | |
{ | |
name: 'violet', | |
index: 4, | |
current: game.violetSigils, | |
expected: game.violetSigilsToGet, | |
}, | |
{ | |
name: 'pink', | |
index: 5, | |
current: game.pinkSigils, | |
expected: game.pinkSigilsToGet, | |
}, | |
{ | |
name: 'red', | |
index: 6, | |
current: game.redSigils, | |
expected: game.redSigilsToGet, | |
}, | |
{ | |
name: 'orange', | |
index: 7, | |
current: game.orangeSigils, | |
expected: game.orangeSigilsToGet, | |
}, | |
{ | |
name: 'yellow', | |
index: 8, | |
current: game.yellowSigils, | |
expected: game.yellowSigilsToGet, | |
}, | |
]; | |
const chosenSigil = sigils.reduce(chooseBestRatio); | |
// See above for why timeout. Change timeouts slightly as game progresses | |
const oneMinute = 1000 * 60; | |
const thirtySeconds = oneMinute / 2; | |
const fifteenSeconds = thirtySeconds / 2; | |
const sigilTimeout = tab_knowledge.style.display === 'none' | |
? oneMinute | |
: game.tomes.gt(1) | |
? thirtySeconds | |
: thirtySeconds; | |
if ( | |
chosenSigil.expected.gt(0) && ( | |
chosenSigil.current.eq(0) || ( | |
chosenSigil.expected.div(chosenSigil.current).gt(0.5) && | |
chosenSigil.current.lt('1e200') | |
) || | |
timeSinceReset > sigilTimeout | |
) | |
) { | |
console.log('YOU HAVE BEEN CHOSEN', chosenSigil); | |
sigilReset(chosenSigil.index); | |
} | |
// Knowledge | |
trades(); | |
// Don't upgrade trading once tomes are unlocked | |
if (tab_tomes.style.display !== 'none') { | |
[...document.querySelectorAll('.knowledgeUpgrade')].forEach(clickclack); | |
} | |
// Tomes | |
// TODO: Is this...ok? | |
buyTome(); | |
[...document.querySelectorAll('.tomeUpgrade')].forEach(clickclack); | |
// Blue fire | |
// | |
// Since blue fire production scales with fire production, it feels like it | |
// would be prudent to alter sigil reset timers. But, blue fire *doesn't* | |
// reset on sigil resets. We end up trading potential blue fire gains with | |
// knowledge gains, and tomes suck ass, so this feels like a decent trade | |
blueFireMaxAll(); | |
// Holy figures | |
// | |
// Fuck holy tetrahedrons, all my homies hate holy tetrahedrons. I don't | |
// have a good early-game logic for them - but once tetradedrons get into | |
// the groove it's easier to do resets. At that point the logic is pretty | |
// much bang-on like sigils | |
const holyShapes = [ | |
{ | |
name: 'tetradedron', | |
index: 1, | |
current: game.holyTetrahedrons, | |
expected: game.holyTetrahedronsToGet, | |
}, | |
{ | |
name: 'octahedron', | |
index: 2, | |
current: game.holyOctahedrons, | |
expected: game.holyOctahedronsToGet, | |
}, | |
{ | |
name: 'dodecahedron', | |
index: 3, | |
current: game.holyDodecahedrons, | |
expected: game.holyDodecahedronsToGet, | |
}, | |
]; | |
const twoMinutes = oneMinute * 2; | |
const mostHolyForTheBuck = holyShapes.reduce(chooseBestRatio); | |
const timeSinceHolyReset = Date.now() - zState.holyResetAt; | |
if ( | |
mostHolyForTheBuck.expected.gt(0) && ( | |
mostHolyForTheBuck.current.eq(0) || timeSinceHolyReset > twoMinutes | |
) | |
) { | |
console.log('HOOOLLLYYYY', mostHolyForTheBuck); | |
holyPolyhedronReset(mostHolyForTheBuck.index); | |
} | |
[ | |
...document.querySelectorAll('.holyTetrahedronUpgrade'), | |
...document.querySelectorAll('.holyOctahedronUpgrade'), | |
...document.querySelectorAll('.holyDodecahedronUpgrade'), | |
].forEach(clickclack); | |
// Hell | |
// | |
// TODO: Dunno about choosing the right level of hell - maybe just rotate? | |
// TODO: This is messed up with the tetrahedron resets - have to | |
// rethink. Right now we're doing it once after a reset | |
// | |
// Hell is kinda like sigils - just reset on a timer. Continue with the | |
// sigil resets to gain sigils, but offset the timer with some jitter. | |
// Since holy tetrahedrons reset EVERYTHING, it'll be useless to enter hell | |
// before we get our sigil affairs i order | |
const timeSinceHellReset = Date.now() - zState.hellResetAt; | |
const afterHolyReset = zState.hellResetAt < zState.holyResetAt; | |
const gainingBloodAnyway = !game.inHell && game.bloodPerSecond.gt(0); | |
// Different timeouts for how long we want to be in hell | |
const hellTimeout = game.inHell ? fifteenSeconds : thirtySeconds; | |
const inHellForLongEnough = game.inHell && timeSinceHellReset > fifteenSeconds; | |
const wantToEnterHellForTheFirstTime = !game.inHell && afterHolyReset && timeSinceHolyReset > thirtySeconds; | |
if ( | |
// tab_blood.style.display !== 'none' && | |
// enoughGoldForHell && | |
// timeSinceHellReset > hellTimeout | |
inHellForLongEnough || ( | |
wantToEnterHellForTheFirstTime && !gainingBloodAnyway | |
) | |
) { | |
console.log('Welcome to hell', { inHellForLongEnough, wantToEnterHellForTheFirstTime }); | |
zState.hellResetAt = Date.now(); | |
enterExitHell(); | |
} | |
// Holy fire | |
// | |
holyFireMaxAll(); | |
// Planets | |
// | |
// Compared to the other mechanics - super simple. Just buy dumdum | |
if (tab_planets.style.display !== 'none') { | |
[ | |
...document.querySelectorAll('.formPlanetButton'), | |
document.querySelector('button[onclick="gainSupercluster()"]'), | |
].forEach(clickclack); | |
} | |
// Hypergods | |
// | |
// I wanna click these bad bois myself | |
const border = game.gold.gte(hypergodDefeatCosts[game.hypergodsDefeated]) ? 'green' : 'red'; | |
tab_omniversalHypergods.style.border = `10px solid ${border}`; | |
// Plague | |
// | |
gainMaxSpores(); | |
plagueMaxAll(); | |
function trades() { | |
if (tab_knowledge.style.display === 'none') { | |
return; | |
} | |
// Make a nice datastructure so we can work with...something | |
const trades = [ | |
{ | |
index: 1, | |
colors: game.knowledgeTrade1SigilTypes, | |
amounts: game.knowledgeTrade1Amounts, | |
}, | |
{ | |
index: 2, | |
colors: game.knowledgeTrade2SigilTypes, | |
amounts: game.knowledgeTrade2Amounts, | |
}, | |
{ | |
index: 3, | |
colors: game.knowledgeTrade3SigilTypes, | |
amounts: game.knowledgeTrade3Amounts, | |
}, | |
]; | |
const whoCanWeAfford = trades.filter(({ colors, amounts }) => { | |
// Trade colors are indexes into `sigilColours`, which are the sigil | |
// names. We turn them into a name, and we index into `game` based | |
// off of that. ...yeah | |
// oh yeah lel the trades are 1 based the array is 0 based (: | |
const whatWeHave = colors.map( | |
(c) => game[sigilColours[c - 1] + 'Sigils'] | |
); | |
return amounts | |
.every((needed, index) => needed.lt(whatWeHave[index])); | |
}); | |
const currentKnowledgeTradeLevel = game.knowledgeTradeLevel; | |
// If we can afford everything: Increase trade level | |
const canAffordEverything = whoCanWeAfford.length && whoCanWeAfford.every(Boolean); | |
if (canAffordEverything) { | |
const nextpls = String(currentKnowledgeTradeLevel.add(1)); | |
document.getElementById("knowledgeLevelInput").value = nextpls; | |
// 2 === read from string input, 1 === read from range slider | |
updateKnowledgeTradeLevel(2); | |
} | |
// Read the current costs, see if they're not too far away from us | |
// Note that at this point we haven't yet purchased anything - we | |
// just know that we can afford, so our current sigil count reflects | |
// what we can actually afford | |
// sigilColorNameIndex => medianCost mapping | |
const nextSigilCounts = Array.from( | |
document.querySelectorAll('.knowledgeTradeCostRange') | |
).map((node) => { | |
const [min, max] = node.textContent | |
.split(' - ') | |
.map((t) => new Decimal(t)); | |
return min.add(max).div(2); | |
}); | |
const nextToCurrentRatios = nextSigilCounts.map((median, index) => { | |
const current = game[sigilColours[index] + 'Sigils']; | |
return current.div(median); | |
}); | |
const canReasonablyAffordNext = nextToCurrentRatios.every( | |
// Fairly random number | |
(ratio) => ratio.gte(0.75) | |
); | |
if (!canReasonablyAffordNext && game.knowledgeTradeLevel.gt(1)) { | |
const downOneLevel = game.knowledgeTradeLevel.sub(1); | |
// console.log('Bumping us down to', +downOneLevel); | |
// Bump up down a notch | |
document.getElementById("knowledgeLevelInput").value = | |
String(downOneLevel); | |
// 2 === read from string input, 1 === read from range slider | |
updateKnowledgeTradeLevel(2); | |
// This is what's called under the hood when we click | |
// the reset trades | |
setKnowledgeTrade(1); | |
setKnowledgeTrade(2); | |
setKnowledgeTrade(3); | |
} | |
// Just do the first purchase we can afford | |
const chickenDinner = whoCanWeAfford[0]; | |
if (chickenDinner) { | |
// console.log('smurt', chickenDinner); | |
purchaseKnowledgeTrade(chickenDinner.index); | |
} | |
} | |
function chooseBestRatio(bestSoFar, currentThing) { | |
// Prioritise brand-new ones | |
if (currentThing.current.eq(0) && currentThing.expected.gt(0)) { | |
return currentThing; | |
} | |
const bestSoFarRatio = bestSoFar.expected.div(bestSoFar.current); | |
const currentRatio = currentThing.expected.div(currentThing.current); | |
return currentRatio.gt(bestSoFarRatio) ? currentThing : bestSoFar; | |
} | |
} | |
function clickclack(node) { | |
if (!node) { | |
console.trace('wtf is this', arguments); | |
return; | |
} | |
node.style.display !== 'none' && !node.disabled && node.onclick(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment