Last active
January 25, 2024 20:58
-
-
Save GabeStah/a687f64db4bb844a4aadd367052bf152 to your computer and use it in GitHub Desktop.
the-idle-class-helper
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
// ==UserScript== | |
// @name The Idle Class Helper | |
// @namespace http://tampermonkey.net/ | |
// @version 0.1.5 | |
// @description try to take over the world! | |
// @author Gabe Wyatt <[email protected]> | |
// @match https://www.smallgraygames.com/the-idle-class | |
// @grant none | |
// @require https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js | |
// @updateURL https://gist.githubusercontent.com/GabeStah/a687f64db4bb844a4aadd367052bf152/raw/index.user.js | |
// @downloadURL https://gist.githubusercontent.com/GabeStah/a687f64db4bb844a4aadd367052bf152/raw/index.user.js | |
// ==/UserScript== | |
(function() { | |
'use strict'; | |
const mediumIntervalCounter = ko.observable(0); | |
/** | |
* num - the number to be formatted | |
* longerFormat - for numbers where you need a third decimal point for changes to be visible | |
* longerSingleDigit - for smaller numbers (usually multipliers) that require a third decimal point for changes to be visible | |
*/ | |
function format(num, longerFormat, longerSingleDigit) { | |
var name; | |
if (num < 0) { | |
num = Math.abs(num); | |
} | |
if (num < 1000000) { | |
num = num | |
.toFixed(longerSingleDigit ? 3 : 2) | |
.replace(/\.00$/, '') | |
.replace(/\.000$/, ''); | |
return num.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,'); | |
} | |
var abrev = window.screen.availWidth <= 400; | |
if (num >= 1000000000000000000000000000000000000000000000) { | |
num = num / 1000000000000000000000000000000000000000000000; | |
name = abrev ? 'Qad' : 'quattuordecillion'; | |
} else if (num >= 1000000000000000000000000000000000000000000) { | |
num = num / 1000000000000000000000000000000000000000000; | |
name = abrev ? 'Td' : 'tredecillion'; | |
} else if (num >= 1000000000000000000000000000000000000000) { | |
num = num / 1000000000000000000000000000000000000000; | |
name = abrev ? 'Dd' : 'duodecillion'; | |
} else if (num >= 1000000000000000000000000000000000000) { | |
num = num / 1000000000000000000000000000000000000; | |
name = abrev ? 'Ud' : 'undecillion'; | |
} else if (num >= 1000000000000000000000000000000000) { | |
num = num / 1000000000000000000000000000000000; | |
name = abrev ? 'Dc' : 'decillion'; | |
} else if (num >= 1000000000000000000000000000000) { | |
num = num / 1000000000000000000000000000000; | |
name = abrev ? 'No' : 'nonillion'; | |
} else if (num >= 1000000000000000000000000000) { | |
num = num / 1000000000000000000000000000; | |
name = abrev ? 'Oc' : 'octillion'; | |
} else if (num >= 1000000000000000000000000) { | |
num = num / 1000000000000000000000000; | |
name = abrev ? 'Sp' : 'septillion'; | |
} else if (num >= 1000000000000000000000) { | |
num = num / 1000000000000000000000; | |
name = abrev ? 'Sx' : 'sextillion'; | |
} else if (num >= 1000000000000000000) { | |
num = num / 1000000000000000000; | |
name = abrev ? 'Qi' : 'quintillion'; | |
} else if (num >= 1000000000000000) { | |
num = num / 1000000000000000; | |
name = abrev ? 'Qa' : 'quadrillion'; | |
} else if (num >= 1000000000000) { | |
num = num / 1000000000000; | |
name = abrev ? 'T' : 'trillion'; | |
} else if (num >= 1000000000) { | |
num = num / 1000000000; | |
name = abrev ? 'B' : 'billion'; | |
} else if (num >= 1000000) { | |
num = num / 1000000; | |
name = abrev ? 'M' : 'million'; | |
} | |
if (longerFormat) { | |
return num.toFixed(3).replace(/\.000$/, '') + ' ' + name; | |
} else { | |
return num.toFixed(2).replace(/\.00$/, '') + ' ' + name; | |
} | |
} | |
function formattedTimeHelper(time, doNotRound) { | |
if (doNotRound) { | |
return time.toFixed(2).replace(/\.00$/, ''); | |
} else { | |
return Math.floor(time); | |
} | |
} | |
function getFormattedTime(time, skipHours, doNotRound) { | |
var mins = time / 1000 / 60; | |
if (mins > 1) { | |
mins = formattedTimeHelper(mins, doNotRound); | |
} | |
if (mins >= 2880 && !skipHours) { | |
// Two days | |
return formattedTimeHelper(mins / 60 / 24, doNotRound) + ' days'; | |
} else if (mins >= 120) { | |
return formattedTimeHelper(mins / 60, doNotRound) + ' hours'; | |
} else if (mins >= 60) { | |
return ( | |
formattedTimeHelper(mins / 60, doNotRound) + | |
(mins > 60 ? ' hours' : ' hour') | |
); | |
} else if (mins >= 1) { | |
return mins + (mins == 1 ? ' minute' : ' minutes'); | |
} else { | |
var sec = Math.round(mins * 60); | |
return sec + (sec === 1 ? ' second' : ' seconds'); | |
} | |
} | |
function timeSince(startTime) { | |
return getFormattedTime(new Date() - new Date(startTime)) + ' ago'; | |
} | |
class Stat { | |
constructor( | |
name, | |
baseVal, | |
beforeDisplay, | |
afterDisplay, | |
longerFormat, | |
longerSingleDigit, | |
info | |
) { | |
this.name = name; | |
this.baseVal = baseVal; | |
this.beforeDisplay = beforeDisplay ? beforeDisplay : ''; | |
this.afterDisplay = afterDisplay ? afterDisplay : ''; | |
this.info = info; | |
if (isNaN(baseVal)) { | |
this.val = baseVal; | |
} else { | |
this.val = ko.observable(baseVal); | |
} | |
this.displayVal = ko.computed(function() { | |
return ( | |
(typeof this.val === 'function' && this.val() < 0 ? '- ' : '') + | |
this.beforeDisplay + | |
(typeof this.val === 'function' | |
? format(this.val(), longerFormat, longerSingleDigit) | |
: this.val) + | |
this.afterDisplay | |
); | |
}, this); | |
this.isCash = ko.computed(function() { | |
return this.name.toLowerCase().indexOf('cash') > -1; | |
}, this); | |
} | |
} | |
class DateStat { | |
constructor(name, baseVal, info, startTime) { | |
this.info = info; | |
this.type = 'date'; | |
this.name = name; | |
this.baseVal = baseVal; | |
this.startTime = startTime; | |
this.val = ko.observable(baseVal); | |
this.displayVal = ko.computed(function() { | |
return this.val() ? getFormattedTime(this.val() - new Date(this.startTime)) + ' elapsed' : 'N/A'; | |
}, this); | |
} | |
} | |
const bankPerSecondMaximum = new Stat( | |
'Bankruptcy Per Second - Maximum', | |
0, | |
null, | |
null, | |
true, | |
null, | |
'Maximum <b>Next Bankruptcy Multiplier</b> earned per second during this reset.' | |
); | |
const bankPerSecondMaximumDate = new DateStat( | |
'Bankruptcy Per Second - Maximum, Elapsed', | |
Date.now(), | |
'Elapsed time of maximum <b>Next Bankruptcy Multiplier</b> earned per second during this reset.', | |
new Date(game.startTime.val()) | |
); | |
const bankPerSecond = new Stat( | |
'Bankruptcy Per Second', | |
ko.computed(() => { | |
const elapsed = (new Date() - new Date(game.startTime.val())) / 1000; | |
const nextBonus = game.nextBankruptcyBonus.val(); | |
const val = elapsed > 0 ? nextBonus / elapsed : 0; | |
// Only update maximum after settled | |
if (elapsed >= 5) { | |
if (val > bankPerSecondMaximum.val()) { | |
bankPerSecondMaximum.val(val); | |
bankPerSecondMaximumDate.val(new Date()); | |
} | |
} | |
return val; | |
}, this), | |
null, | |
null, | |
true, | |
null, | |
'<b>Next Bankruptcy Multiplier</b> earned per second during this reset.' | |
); | |
const bankPerMinute = new Stat( | |
'Bankruptcy Per Minute', | |
ko.computed(() => { | |
const elapsed = | |
(new Date() - new Date(game.startTime.val())) / (60 * 1000); | |
const nextBonus = game.nextBankruptcyBonus.val(); | |
return elapsed > 0 ? nextBonus / elapsed : 0; | |
}, this), | |
null, | |
null, | |
true, | |
null, | |
'<b>Next Bankruptcy Multiplier</b> earned per minute during this reset.' | |
); | |
const extraStats = ko.observableArray([ | |
bankPerMinute, | |
bankPerSecond, | |
bankPerSecondMaximum, | |
bankPerSecondMaximumDate | |
]); | |
const model = { | |
mediumIntervalCounter, | |
bankPerMinute, | |
bankPerSecond, | |
bankPerSecondMaximum, | |
bankPerSecondMaximumDate, | |
extraStats | |
}; | |
let oldRestartGame = game.restartGame; | |
game.restartGame = function() { | |
// Reset all | |
bankPerSecondMaximum.val(0); | |
bankPerSecondMaximumDate.startTime = new Date(); | |
bankPerSecondMaximumDate.val(new Date()); | |
oldRestartGame(); | |
}; | |
const panel = jQuery(`<div id="extraStats" class="col-md-6"> | |
<div class="panel panel-default"> | |
<div class="panel-heading"> | |
<h3 class="panel-title">Extra Stats</h3> | |
</div> | |
<div class="panel-body"> | |
<table class="earned-table table"> | |
<thead> | |
<tr> | |
<th>Name</th> | |
<th>Value</th> | |
</tr> | |
</thead> | |
<tbody | |
data-bind="template: { name: 'stat-template', foreach: extraStats }" | |
> | |
</tbody> | |
</table> | |
</div> | |
</div> | |
</div> | |
`); | |
const template = jQuery(` | |
<script type="text/html" id="extra-stat-template"> | |
<tr> | |
<td data-bind="css: { 'info-stat': info }"> | |
<div style="position:relative;"> | |
<span data-bind="text: name"></span> | |
<button data-bind="if: info" class="hide-mobile stat-info icon-button"> | |
<i class="material-icons">help</i> | |
</button> | |
<div class="hover stat-hover"> | |
<p data-bind="html: info"></p> | |
</div> | |
</div> | |
</td> | |
<td> | |
<span data-bind="text: displayVal">-</span> | |
</td> | |
</tr> | |
</script> | |
`); | |
jQuery('div#stats').append(panel); | |
class CheatManager { | |
constructor(cheats) { | |
this.cheats = cheats; | |
} | |
addCheat(cheat) { | |
this.cheats.push(cheat); | |
} | |
createCheatModal() { | |
jQuery("footer[class='footer']").after(this.modalHtml); | |
} | |
createCheatMenuOption() { | |
jQuery("nav div[class~='navbar-right'] ul[class~='dropdown-menu'] li") | |
.eq(4) | |
.after( | |
'<li><a href="#" data-toggle="modal" data-target="#cheatsModal">Cheats</a></li>' | |
); | |
} | |
createUI() { | |
this.createCheatModal(); | |
this.createCheatMenuOption(); | |
} | |
getCheatByName(val) { | |
return _.find(this.cheats, ['name', val]); | |
} | |
get htmlElements() { | |
return _.map(this.cheats, cheat => cheat.htmlElement); | |
} | |
get modalHtml() { | |
const modal = jQuery(`<div class="modal fade" id="cheatsModal" tabindex="-1" role="dialog" aria-labelledby="cheatsModalLabel" style="display: none;"> | |
<div class="modal-dialog" role="document"> | |
<div class="modal-content"> | |
<div class="modal-header"> | |
<h3 class="modal-title" id="cheatsModalLabel">Cheats</h3> | |
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> | |
<span aria-hidden="true">×</span> | |
</button> | |
</div> | |
<div class="modal-body"> | |
<fieldset> | |
<!-- <legend>Cheats</legend> --> | |
<ul id="cheatList"></ul> | |
</fieldset> | |
</div> | |
<div class="modal-footer"> | |
<button type="button" class="btn btn-primary" data-dismiss="modal">OK</button> | |
</div> | |
</div> | |
</div> | |
</div>`); | |
modal.find('#cheatList').append(this.htmlElements); | |
return modal; | |
} | |
} | |
class Cheat { | |
constructor({ name, frequency, action, enabled = true, desc }) { | |
this.action = action; | |
this.enabled = enabled; | |
this.frequency = frequency; | |
this.name = name; | |
this.desc = desc || name; | |
// Start if enabled | |
if (this.enabled) this.start(); | |
} | |
get htmlElement() { | |
const input = jQuery( | |
`<input name="${this.name}" id="${this.name}" type="checkbox" />` | |
); | |
input.prop('checked', this.enabled); | |
input.change(() => this.toggle()); | |
const label = jQuery(`<label for="${this.name}">${this.desc}</label>`); | |
const li = jQuery(`<li />`, { | |
id: this.name | |
}).append([label, input]); | |
return li; | |
} | |
fire() { | |
if (!this.enabled) return; | |
this.action && (_.isFunction(this.action) ? this.action() : this.action); | |
} | |
start() { | |
if (!this.enabled || !this.frequency) return; | |
this.interval = setInterval(() => this.fire(), this.frequency); | |
} | |
stop() { | |
clearInterval(this.interval); | |
} | |
reset() { | |
this.stop(); | |
} | |
toggle() { | |
this.enabled = !this.enabled; | |
// Always stop. | |
this.stop(); | |
if (this.enabled) { | |
// Start | |
this.start(); | |
} | |
} | |
} | |
const getPotentialEarnings = unit => { | |
let allEmployeeMod = _.find(game.stats(), [ | |
'name', | |
'Employee-Wide Modifier' | |
]).val(); | |
let baseDPSMod = _.find(game.stats(), [ | |
'name', | |
'Total Mod to Cash Per Second' | |
]).val(); | |
let bankruptcyBonus = _.find(game.stats(), [ | |
'name', | |
'Bankruptcy Multiplier' | |
]).val(); | |
let idleBonus = _.find(game.cashStats(), [ | |
'name', | |
'Idle Bonus Multiplier' | |
]).val(); | |
var baseCPS = | |
parseFloat(unit.baseClick()) * | |
Math.pow(2, unit.mod.val() + allEmployeeMod) * | |
1; | |
var baseMod = baseCPS * (baseDPSMod / 100); | |
var quantityMod = (unit.numMod.val() * 1) / 100; | |
quantityMod = quantityMod > 1 ? quantityMod : 1; | |
return (baseCPS + baseMod) * bankruptcyBonus * idleBonus * quantityMod; | |
}; | |
const getIgnoredUnit = () => { | |
return _.find(game.units(), unit => unit.numCanAfford() >= 25); | |
}; | |
const getMostProfitableUnit = () => { | |
return _.reduce(game.units(), (retained, current) => { | |
const currentEarningPerUnit = | |
current.num.val() > 0 | |
? current.cps.val() / current.num.val() | |
: getPotentialEarnings(current); | |
const currentPricePerUnit = | |
current.price.val() / (current.numCanAfford() || 1); | |
const currentProfit = currentEarningPerUnit / currentPricePerUnit; | |
const retainedEarningPerUnit = | |
retained.num.val() > 0 | |
? retained.cps.val() / retained.num.val() | |
: getPotentialEarnings(retained); | |
const retainedPricePerUnit = | |
retained.price.val() / (retained.numCanAfford() || 1); | |
const retainedProfit = retainedEarningPerUnit / retainedPricePerUnit; | |
return currentProfit > retainedProfit ? current : retained; | |
}); | |
}; | |
const buy = unit => unit.buy(); | |
const percentageOfResearchingEmployee = (type = 'intern') => { | |
switch (type) { | |
case 'intern': | |
return game.units()[0] && game.units()[0].num.val() > 0 | |
? Number(game.research().intern()) / game.units()[0].num.val() | |
: 0; | |
case 'wage': | |
return game.units()[1] && game.units()[1].num.val() > 0 | |
? Number(game.research().wage()) / game.units()[1].num.val() | |
: 0; | |
case 'sales': | |
return game.units()[2] && game.units()[2].num.val() > 0 | |
? Number(game.research().sales()) / game.units()[2].num.val() | |
: 0; | |
case 'manager': | |
return game.units()[3] && game.units()[3].num.val() > 0 | |
? Number(game.research().manager()) / game.units()[3].num.val() | |
: 0; | |
default: | |
return game.units()[0] && game.units()[0].num.val() > 0 | |
? Number(game.research().intern()) / game.units()[0].num.val() | |
: 0; | |
} | |
}; | |
const manager = new CheatManager([ | |
new Cheat({ | |
name: 'click', | |
desc: 'Auto Click', | |
action: mainClick, | |
frequency: 100 | |
}), | |
new Cheat({ | |
name: 'email-reply', | |
desc: 'Auto Reply to Email', | |
action: () => jQuery('#inbox button[type=submit]').click(), | |
frequency: 1000 | |
}), | |
new Cheat({ | |
name: 'upgrades', | |
desc: 'Auto Buy Upgrades', | |
action: () => game.buyAllUpgrades(), | |
frequency: 500 | |
}), | |
new Cheat({ | |
name: 'fire', | |
desc: 'Auto Fire/Sell Acquisitions', | |
action: () => { | |
_.forEach(game.activeAcquisitions(), acquisition => { | |
if (!acquisition.active()) { | |
acquisition.sell(); | |
} | |
acquisition.fire(); | |
}); | |
}, | |
frequency: 200 | |
}), | |
new Cheat({ | |
name: 'acquire', | |
desc: 'Auto Acquire Acquisitions' | |
}), | |
new Cheat({ | |
name: 'employee', | |
desc: 'Auto Buy Employees', | |
action: () => buy(getIgnoredUnit() || getMostProfitableUnit()), | |
frequency: 500 | |
}), | |
new Cheat({ | |
name: 'research', | |
desc: 'Auto Research', | |
action: () => { | |
// Sell patents (safe) | |
game.research().sellPatents(); | |
// Check if any employee type under 80% research | |
// AND if percentage complete is under 25% | |
if ( | |
_.some( | |
['intern', 'wage', 'sales', 'manager'], | |
type => percentageOfResearchingEmployee(type) < 0.8 | |
) && | |
parseFloat(game.research().percentageRipe()) / 100 <= 0.25 | |
) { | |
// Stop research, if necessary | |
if (game.research().active()) { | |
game.research().toggleProduction(); | |
} | |
// Assign all | |
game.research().assignMax(); | |
} | |
// Ensure always active | |
if (!game.research().active()) { | |
game.research().toggleProduction(); | |
} | |
}, | |
frequency: 1000 | |
}), | |
new Cheat({ | |
name: 'restart', | |
desc: 'Auto Restart', | |
action: () => game.restartGame(), | |
// Every X minutes | |
frequency: 5 * 60 * 1000 | |
}) | |
]); | |
manager.addCheat( | |
new Cheat({ | |
name: 'invest', | |
desc: 'Auto Invest', | |
action: () => { | |
// Invest | |
let timeTilRipe = getInvestmentFormData(); | |
const slots = | |
game.totalSimultaneousInvestmentsAllowed.val() - | |
game.activeInvestments().length; | |
for (var i = 0; i < slots; i++) { | |
game.makeInvestment(10, timeTilRipe || 1); | |
} | |
// Check if acquisition is enabled | |
if (manager.getCheatByName('acquire').enabled) { | |
// First inactive investment | |
const investment = _.find( | |
game.activeInvestments(), | |
investment => !investment.active() | |
); | |
if (investment) { | |
investment.handleAcquisition(); | |
} | |
} | |
game.cashOutAllInvestments(); | |
}, | |
frequency: 1000 | |
}) | |
); | |
manager.createUI(); | |
const forceFinishAllTraining = () => { | |
_.forEach(game.units(), unit => { | |
unit.trainingActive(false); | |
unit.trainingFinished(true); | |
clearInterval(unit.handleTimer); | |
}); | |
}; | |
const addTrainingUI = () => { | |
const obj = { | |
button: jQuery('<button class="btn btn-sm btn-success">Force</button>') | |
}; | |
obj.button.click(() => forceFinishAllTraining()); | |
jQuery( | |
"div[data-bind='visible: isTrainingView'][class='buy-rate-selector']" | |
).append(obj.button); | |
}; | |
addTrainingUI(); | |
ko.applyBindings(model, document.getElementById('extraStats')); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment