Skip to content

Instantly share code, notes, and snippets.

@Zirak
Last active October 26, 2024 20:14
Show Gist options
  • Save Zirak/63a778031e7cab40aa80a8787eec158c to your computer and use it in GitHub Desktop.
Save Zirak/63a778031e7cab40aa80a8787eec158c to your computer and use it in GitHub Desktop.
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