Skip to content

Instantly share code, notes, and snippets.

@Tydus
Last active October 13, 2023 18:33
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Tydus/bdaaa7f9bc9455bbcbf02c5784ed1dc3 to your computer and use it in GitHub Desktop.
Save Tydus/bdaaa7f9bc9455bbcbf02c5784ed1dc3 to your computer and use it in GitHub Desktop.
FortuneCookie.js
Game.Win('Third-party');
if(FortuneCookie === undefined) var FortuneCookie = {};
if(typeof CCSE == 'undefined') Game.LoadMod('https://klattmose.github.io/CookieClicker/' + (0 ? 'Beta/' : '') + 'CCSE.js');
FortuneCookie.name = 'Fortune Cookie';
FortuneCookie.version = '2.17';
FortuneCookie.GameVersion = '2.022';
FortuneCookie.launch = function(){
FortuneCookie.init = function(){
FortuneCookie.isLoaded = 1;
FortuneCookie.Backup = {};
FortuneCookie.config = {};
FortuneCookie.config = FortuneCookie.defaultConfig();
FortuneCookie.loadConfig();
CCSE.customLoad.push(FortuneCookie.loadConfig);
CCSE.customSave.push(FortuneCookie.saveConfig);
FortuneCookie.ReplaceNativeGrimoire();
FortuneCookie.initMembraneForecast();
Game.customOptionsMenu.push(function(){
CCSE.AppendCollapsibleOptionsMenu(FortuneCookie.name, FortuneCookie.getMenuString());
});
Game.customStatsMenu.push(function(){
CCSE.AppendStatsVersionNumber(FortuneCookie.name, FortuneCookie.version);
});
//***********************************
// Post-Load Hooks
// To support other mods interfacing with this one
//***********************************
if(FortuneCookie.postloadHooks) {
for(var i = 0; i < FortuneCookie.postloadHooks.length; ++i) {
(FortuneCookie.postloadHooks[i])();
}
}
if (Game.prefs.popups) Game.Popup('Fortune Cookie loaded!');
else Game.Notify('Fortune Cookie loaded!', '', '', 1, 1);
}
//***********************************
// Configuration
//***********************************
FortuneCookie.saveConfig = function(config){
CCSE.save.OtherMods.FortuneCookie = FortuneCookie.config;
}
FortuneCookie.loadConfig = function(){
if(CCSE.save.OtherMods.FortuneCookie){
var config = CCSE.save.OtherMods.FortuneCookie;
for(var pref in config){
FortuneCookie.config[pref] = config[pref];
}
}
}
FortuneCookie.defaultConfig = function(){
return {
spellForecastLength : 10,
simGCs : 0,
colorOverride: {
'Building Special' : "#FF00FF",
'Click Frenzy' : "#4BB8F0",
'Elder Frenzy' : "#E1C699",
'Free Sugar Lump' : "#DAA560"
}
}
}
FortuneCookie.UpdatePref = function(prefName, value){
FortuneCookie.config[prefName] = value;
}
FortuneCookie.SetOverrideColor = function(effect, color){
FortuneCookie.config.colorOverride[effect] = color;
Game.UpdateMenu();
}
FortuneCookie.getSimGCs = function(){
// default to 0 if some BS causes this to be undefined
return (FortuneCookie.config.simGCs ? FortuneCookie.config.simGCs : 0);
}
FortuneCookie.AddColorOverride = function(){
var str = '<h3>New color override</h3><div class="block">';
str += '<table style="width:80%;">';
str += '<tr><td style="text-align:right; width:45%;">Effect:</td><td style="width:5%;"></td><td style="text-align:left; width:50%;"><input id="effectEditor" class="option" type="text" value="" style="width: 65px;" /></td></tr>';
str += '<tr><td style="text-align:right;">Color:</td><td></td><td style="text-align:left;"><input id="colorEditor" class="option" type="text" value="#FFFFFF" style="width: 65px;" /></td></tr>';
str += '</table></div>';
Game.Prompt(str, [['Save', 'FortuneCookie.config.colorOverride[l("effectEditor").value] = l("colorEditor").value; Game.ClosePrompt(); Game.UpdateMenu();'],
['Nevermind', 'Game.ClosePrompt();']], 0);
}
//***********************************
// Replacement
//***********************************
FortuneCookie.getMenuString = function(){
function WriteSlider(slider, leftText, rightText, startValueFunction, callback, min, max, step){
if (!callback) callback = '';
if (!min) min = 0;
if (!max) max = 100;
if (!step) step = 1;
return '<div class="sliderBox"><div style="float:left;">' + leftText + '</div><div style="float:right;" id="' + slider + 'RightText">' + rightText.replace('[$]', startValueFunction()) + '</div><input class="slider" style="clear:both;" type="range" min="' + min + '" max="' + max + '" step="' + step + '" value="' + startValueFunction() + '" onchange="' + callback + '" oninput="'+callback+'" onmouseup="PlaySound(\'snd/tick.mp3\');" id="' + slider + '"/></div>';
}
function WriteHeader(text){
return '<div class="listing" style="padding: 5px 16px; opacity: 0.7; font-size: 17px; font-family: Kavoon, Georgia, serif;">' + text + '</div>';
}
var str = '<div class="listing">' +
WriteSlider('spellForecastSlider', 'Forecast Length', '[$]', function(){return FortuneCookie.config.spellForecastLength;}, "FortuneCookie.UpdatePref('spellForecastLength', Math.round(l('spellForecastSlider').value)); l('spellForecastSliderRightText').innerHTML = FortuneCookie.config.spellForecastLength;", 0, 100, 1) + '<br>'+
'</div>';
str += WriteHeader('Force the Hand of Fate') +
'<div class="listing">This spell\'s outcome changes based on the season, if the Golden cookie sound selector is on, how many Golden Cookies are already on screen, and if a Dragonflight buff is currently active.</div>' +
'<div class="listing">Column 1 : Golden cookie sound selector is Off <b>AND</b> the season is neither Easter nor Valentine\'s.</div>' +
'<div class="listing">Column 2 : Golden cookie sound selector is On <b>OR</b> the season is either Easter or Valentine\'s.</div>' +
'<div class="listing">Column 3 : Golden cookie sound selector is On <b>AND</b> the season is either Easter or Valentine\'s.</div>' +
'<div class="listing">You can use this slider to forecast the outcome with more Golden Cookies on screen.</div>' +
'<div class="listing">' +
WriteSlider('simGCsSlider', 'Simulate GCs', '[$]', FortuneCookie.getSimGCs, "FortuneCookie.UpdatePref('simGCs', Math.round(l('simGCsSlider').value)); l('simGCsSliderRightText').innerHTML = FortuneCookie.config.simGCs;", 0, 10, 1) + '<br>'+
'</div>';
str += WriteHeader('Color Override') +
'<div class="listing">Set the color coding of the Force the Hand of Fate outcomes.</div>' +
'<div class="listing">Default is <span class="green">green for success</span>, and <span class="red">red for backfire</span>.</div>';
str += '<div class="listing"><a class="option" ' + Game.clickStr + '="FortuneCookie.AddColorOverride(); PlaySound(\'snd/tick.mp3\');">Add</a></div>'
for(var color in FortuneCookie.config.colorOverride){
var style = 'width:65px;' +
'background-color:' + FortuneCookie.config.colorOverride[color] + ';';
str += '<div class="listing">' +
'<a class="option" ' + Game.clickStr + '="delete FortuneCookie.config.colorOverride[\'' + color + '\']; PlaySound(\'snd/tick.mp3\'); Game.UpdateMenu();">Remove</a>' +
'<input id="FortuneCookieColorOverride' + color + '" class="option" style="' + style + '" value="' + FortuneCookie.config.colorOverride[color] + '" onChange="FortuneCookie.SetOverrideColor(\'' + color + '\', l(\'FortuneCookieColorOverride' + color + '\').value)">' +
'<label>' + color + '</label>' +
'</div>';
}
return str;
}
FortuneCookie.ReplaceNativeGrimoire = function() {
if(!Game.customMinigame['Wizard tower'].spellTooltip) Game.customMinigame['Wizard tower'].spellTooltip = [];
Game.customMinigame['Wizard tower'].spellTooltip.push(function(id, str){
return str.replace( '</div></div>',
'<div style="height:8px;"></div>' +
FortuneCookie.spellForecast(Game.Objects['Wizard tower'].minigame.spellsById[id]) +
'</div></div>');
});
}
//***********************************
// Membrane Forecast
//***********************************
FortuneCookie.initMembraneForecast = function(){
var descFunc = function(me, desc){
var str = desc;
if (Game.Has('Reinforced membrane') && FortuneCookie.config.spellForecastLength){
var durable = FortuneCookie.forecastMembrane('click', 0);
var golddurable = FortuneCookie.forecastMembrane('shimmer', 0);
str += '<br/><br/>';
var durCount = FortuneCookie.countMembraneDurability('click');
var golddurCount = FortuneCookie.countMembraneDurability('shimmer');
if(durable)
str += '<span class="green">Reinforced against cookie clicks (for ' + (durCount==-1?('>'+FortuneCookie.config.spellForecastLength):durCount) + ' click' + (durCount==1?'':'s') + ')</span><br/>';
else
str += '<span class="red">Unreinforced against cookie clicks (for ' + (durCount==-1?('>'+FortuneCookie.config.spellForecastLength):durCount) + ' click' + (durCount==1?'':'s') + ')</span><br/>';
if(golddurable)
str += '<span class="green">Reinforced against golden cookie clicks (for ' + (golddurCount==-1?('>'+FortuneCookie.config.spellForecastLength):golddurCount) + ' click' + (golddurCount==1?'':'s') + ')</span><br/>';
else
str += '<span class="red">Unreinforced against golden cookie clicks (for ' + (golddurCount==-1?('>'+FortuneCookie.config.spellForecastLength):golddurCount) + ' click' + (golddurCount==1?'':'s') + ')</span><br/>';
}
return str;
}
Game.customUpgrades['Shimmering veil [off]'].descFunc.push(descFunc);
Game.customUpgrades['Shimmering veil [on]'].descFunc.push(descFunc);
}
FortuneCookie.forecastMembrane = function(context, offset){
if (context=='shimmer') Math.seedrandom(Game.seed + '/' + (Game.goldenClicks + offset));
else if (context=='click') Math.seedrandom(Game.seed + '/' + (Game.cookieClicks + offset));
if (Math.random() < 0.1){
return true;
} else {
return false;
}
}
FortuneCookie.countMembraneDurability = function(context){
var i;
var initialSuccess = FortuneCookie.forecastMembrane(context, 0);
for(i = 1; i <= FortuneCookie.config.spellForecastLength; i++){
if(FortuneCookie.forecastMembrane(context, i) != initialSuccess) return i;
}
return -1;
}
//***********************************
// Grimoire forecast
//***********************************
// customFateChecker functions are for people who add their own outcome to FtHoF
if(!FortuneCookie.customFateCheckerWin) FortuneCookie.customFateCheckerWin = [];
if(!FortuneCookie.customFateCheckerFail) FortuneCookie.customFateCheckerFail = [];
FortuneCookie.FateChecker = function(spellCount, idx, backfire, active){
var res = '';
var FTHOFcookie = '';
Math.seedrandom(Game.seed + '/' + spellCount);
roll = Math.random();
if(roll < (1 - backfire)){
/* Random is called a few times in setting up the golden cookie */
if (idx > 0) Math.random();
if (idx > 1) Math.random();
Math.random();
Math.random();
var choices = [];
choices.push('Frenzy','Lucky');
if (!Game.hasBuff('Dragonflight')) choices.push('Click Frenzy');
if (Math.random() < 0.1) choices.push('Cookie Storm','Cookie Storm','Blab');
if (Game.BuildingsOwned >= 10 && Math.random() < 0.25) choices.push('Building Special');
if (Math.random() < 0.15) choices = ['Cookie Storm'];
if (Math.random() < 0.0001) choices.push('Free Sugar Lump');
for(var i in FortuneCookie.customFateCheckerWin) FortuneCookie.customFateCheckerWin[i](spellCount, idx, choices);
FTHOFcookie = choose(choices);
res = '<span class="green">' + FTHOFcookie + '</span><br/>';
} else {
/* Random is called a few times in setting up the golden cookie */
if (idx > 0) Math.random();
if (idx > 1) Math.random();
Math.random();
Math.random();
var choices = [];
choices.push('Clot','Ruin');
if (Math.random() < 0.1) choices.push('Cursed Finger','Elder Frenzy');
if (Math.random() < 0.003) choices.push('Free Sugar Lump');
if (Math.random() < 0.1) choices=['Blab'];
for(var i in FortuneCookie.customFateCheckerFail) FortuneCookie.customFateCheckerFail[i](spellCount, idx, choices);
FTHOFcookie = choose(choices);
res = '<span class="red">' + FTHOFcookie + '</span><br/>';
}
if(FortuneCookie.config.colorOverride[FTHOFcookie] !== undefined) res = '<span style="color:' + FortuneCookie.config.colorOverride[FTHOFcookie] + ';">' + FTHOFcookie + '</span><br/>';
return '<td' + (active ? ' style="border-left: 2px solid grey;"' : '') + '>' + res + '</td>';
}
FortuneCookie.gamblerFateChecker = function(spellCount, idx, forceTrue){
var res = '';
Math.seedrandom(Game.seed + '/' + spellCount);
roll = Math.random();
if(forceTrue){
/* Random is called a few times in setting up the golden cookie */
if (idx > 0) Math.random();
if (idx > 1) Math.random();
Math.random();
Math.random();
var choices = [];
choices.push('Frenzy','Lucky');
if (!Game.hasBuff('Dragonflight')) choices.push('Click Frenzy');
if (Math.random() < 0.1) choices.push('Cookie Storm','Cookie Storm','Blab');
if (Game.BuildingsOwned >= 10 && Math.random() < 0.25) choices.push('Building Special');
if (Math.random() < 0.15) choices = ['Cookie Storm'];
if (Math.random() < 0.0001) choices.push('Free Sugar Lump');
for(var i in FortuneCookie.customFateCheckerWin) FortuneCookie.customFateCheckerWin[i](spellCount, idx, choices);
return choose(choices);
} else {
/* Random is called a few times in setting up the golden cookie */
if (idx > 0) Math.random();
if (idx > 1) Math.random();
Math.random();
Math.random();
var choices = [];
choices.push('Clot','Ruin');
if (Math.random() < 0.1) choices.push('Cursed Finger','Elder Frenzy');
if (Math.random() < 0.003) choices.push('Free Sugar Lump');
if (Math.random() < 0.1) choices = ['Blab'];
for(var i in FortuneCookie.customFateCheckerFail) FortuneCookie.customFateCheckerFail[i](spellCount, idx, choices);
return choose(choices);
}
}
FortuneCookie.gamblerEdificeChecker = function(spellCount, forceTrue){
Math.seedrandom(Game.seed + '/' + spellCount);
Math.random();
if(forceTrue){
var buildings = [];
var max = 0;
var n = 0;
for (var i in Game.Objects)
{
if (Game.Objects[i].amount > max) max = Game.Objects[i].amount;
if (Game.Objects[i].amount > 0) n++;
}
for (var i in Game.Objects){
if ((Game.Objects[i].amount<max || n == 1) && Game.Objects[i].getPrice() <= Game.cookies * 2 && Game.Objects[i].amount < 400)
buildings.push(Game.Objects[i]);
}
if (buildings.length == 0){
return "Nothing";
}else{
var building = choose(buildings);
return building.name;
}
} else {
if (Game.BuildingsOwned == 0){
return "Nothing";
} else {
var buildings = [];
for (var i in Game.Objects){
if (Game.Objects[i].amount > 0)
buildings.push(Game.Objects[i]);
}
var building=choose(buildings);
return building.name;
}
}
}
// customSpellForecast functions should return HTML to append to the spell tooltip.
// Return spellForecast to have no effect
if(!FortuneCookie.customSpellForecast) FortuneCookie.customSpellForecast = [];
FortuneCookie.spellForecast=function(spell){
if(FortuneCookie.config.spellForecastLength == 0) return '';
var spellOutcome = '<div width="100%"><b>Forecast:</b><br/>';
var M = Game.Objects["Wizard tower"].minigame;
var backfire = M.getFailChance(spell);
var spellsCast = M.spellsCastTotal;
var target = spellsCast + FortuneCookie.config.spellForecastLength;
var idx = ((Game.season == "valentines" || Game.season == "easter") ? 1 : 0) + ((Game.chimeType == 1 && Game.ascensionMode != 1) ? 1 : 0);
switch(spell.name){
case "Force the Hand of Fate":
backfire += 0.15 * FortuneCookie.getSimGCs();
spellOutcome = spellOutcome.replace('<br/>', '<span style="color:yellow;">This spell is a bit complicated. See the Options menu for an explanation.</span><br/>') +
'<table width="100%"><tr>';
spellOutcome += '<td width="10%"></td>'
for(var i = 0; i < 3; i++)
spellOutcome += '<td width="30%">' + ((i == idx) ? 'Active' : '') + '</td>';
spellOutcome += '</tr><br/>';
for(var ntime = 1; spellsCast < target; ntime++){
spellOutcome += '<tr>';
spellOutcome += '<td>' + ntime + "</td>";
for(var i = 0; i < 3; i++)
spellOutcome += FortuneCookie.FateChecker(spellsCast, i, backfire, false); // Change false to idx == i for an identifier
spellOutcome += '</tr>';
spellsCast += 1;
Math.seedrandom();
}
spellOutcome += '</table></div>';
break;
case "Spontaneous Edifice":
for(var ntime = 1; spellsCast < target; ntime++){
spellOutcome += '<span class="white">' + ntime + '&nbsp;&nbsp;&nbsp;&nbsp;</span>';
Math.seedrandom(Game.seed + '/' + spellsCast);
if(Math.random() < (1 - backfire)){
var buildings = [];
var max = 0;
var n = 0;
for (var i in Game.Objects)
{
if (Game.Objects[i].amount > max) max = Game.Objects[i].amount;
if (Game.Objects[i].amount > 0) n++;
}
for (var i in Game.Objects){
if ((Game.Objects[i].amount < max || n == 1) && Game.Objects[i].getPrice() <= Game.cookies * 2 && Game.Objects[i].amount < 400)
buildings.push(Game.Objects[i]);
}
if (buildings.length == 0){
spellOutcome += '<span class="white">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;No buildings to improve!</span><br/>';
}else{
var building = choose(buildings);
spellOutcome += '<span class="green">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' + building.name + '</span><br/>';
}
}else{
if (Game.BuildingsOwned == 0){
spellOutcome += '<span class="white">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Backfired, but no buildings to destroy!</span><br/>';
}else{
var buildings = [];
for (var i in Game.Objects){
if (Game.Objects[i].amount > 0)
buildings.push(Game.Objects[i]);
}
var building=choose(buildings);
spellOutcome += '<span class="red">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' + building.name + '</span><br/>';
}
}
spellsCast += 1;
Math.seedrandom();
}
break;
case "Gambler's Fever Dream":
for(var ntime = 1; spellsCast < target; ntime++){
spellOutcome += '<span class="white">' + ntime + '&nbsp;&nbsp;&nbsp;&nbsp;</span>';
Math.seedrandom(Game.seed + '/' + spellsCast);
var spells = [];
var selfCost = M.getSpellCost(M.spells["gambler's fever dream"]);
for (var i in M.spells){
if (i != "gambler's fever dream" && (M.magic-selfCost) >= M.getSpellCost(M.spells[i]) * 0.5)
spells.push(M.spells[i]);
}
if (spells.length == 0){
spellOutcome += '<span class="white">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;No eligible spells!</span><br/>';
}else{
var gfdSpell = choose(spells);
var gfdBackfire = M.getFailChance(gfdSpell);
if(FortuneCookie.detectKUGamblerPatch()) gfdBackfire *= 2;
else gfdBackfire = Math.max(gfdBackfire, 0.5);
Math.seedrandom(Game.seed + '/' + (spellsCast + 1));
if(Math.random() < (1 - gfdBackfire)){
spellOutcome += '<span class="green">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' + gfdSpell.name;
if(gfdSpell.name == "Force the Hand of Fate") spellOutcome += ' (' + FortuneCookie.gamblerFateChecker(spellsCast + 1, idx, true) + ')';
if(gfdSpell.name == "Spontaneous Edifice") spellOutcome += ' (' + FortuneCookie.gamblerEdificeChecker(spellsCast + 1, true) + ')';
spellOutcome += '</span><br/>';
}else{
spellOutcome += '<span class="red">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' + gfdSpell.name;
if(gfdSpell.name == "Force the Hand of Fate") spellOutcome += ' (' + FortuneCookie.gamblerFateChecker(spellsCast + 1, idx, false) + ')';
if(gfdSpell.name == "Spontaneous Edifice") spellOutcome += ' (' + FortuneCookie.gamblerEdificeChecker(spellsCast + 1, false) + ')';
spellOutcome += '</span><br/>';
}
}
spellsCast+=1;
Math.seedrandom();
}
break;
default:
for(var ntime = 1; spellsCast < target; ntime++){
spellOutcome += '<span class="white">' + ntime + '&nbsp;&nbsp;&nbsp;&nbsp;</span>';
Math.seedrandom(Game.seed + '/' + spellsCast);
if(Math.random() < (1 - backfire))
spellOutcome += '<span class="green">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Success</span><br/>';
else
spellOutcome += '<span class="red">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Backfire</span><br/>';
spellsCast += 1;
Math.seedrandom();
}
}
for(var i in CCSE.customSpellForecast) spellOutcome = CCSE.customSpellForecast[i](spellOutcome, spell);
return spellOutcome;
}
FortuneCookie.detectKUGamblerPatch = function(){
if(typeof KlattmoseUtilities == 'undefined') return false;
if(typeof KlattmoseUtilities.config == 'undefined') return false;
if(typeof KlattmoseUtilities.config.patches == 'undefined') return false;
return KlattmoseUtilities.config.patches.gamblersFeverDreamFix == 1;
}
if(CCSE.ConfirmGameVersion(FortuneCookie.name, FortuneCookie.version, FortuneCookie.GameVersion)) FortuneCookie.init();
}
if(!FortuneCookie.isLoaded){
if(CCSE && CCSE.isLoaded){
FortuneCookie.launch();
}
else{
if(!CCSE) var CCSE = {};
if(!CCSE.postLoadHooks) CCSE.postLoadHooks = [];
CCSE.postLoadHooks.push(FortuneCookie.launch);
}
}
@TMilesss
Copy link

Thanks, very useful for combining several buffs together!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment