Skip to content

Instantly share code, notes, and snippets.

@GabeStah
Last active January 25, 2024 20:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save GabeStah/a687f64db4bb844a4aadd367052bf152 to your computer and use it in GitHub Desktop.
Save GabeStah/a687f64db4bb844a4aadd367052bf152 to your computer and use it in GitHub Desktop.
the-idle-class-helper
// ==UserScript==
// @name The Idle Class Helper
// @namespace http://tampermonkey.net/
// @version 0.1.5
// @description try to take over the world!
// @author Gabe Wyatt <gabe@gabewyatt.com>
// @match https://www.smallgraygames.com/the-idle-class
// @grant none
// @require https://cdn.jsdelivr.net/npm/lodash@4.17.15/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