G420/misc 20190908 (#876)

* spoilers

* spoilers

* spoilers

* Make effarig and ra use bits for their unlocks

* switch glyphs to using faster RNG, fix up seed to be 32 bit

* move tt autobuyer out of dumb place

* clean up rng code a bit

* switch other celestials to using bits for unlocks

* add progress bar for long amplified reality

could also be used for offline progress

* add documentation to async-utils
This commit is contained in:
garnet420 2019-09-14 12:50:42 -04:00 committed by GitHub
parent 7f5dcb98cf
commit fe7f30b8fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 278 additions and 74 deletions

View File

@ -91,6 +91,7 @@
<script type="text/javascript" src="javascripts/core/format.js"></script>
<script type="text/javascript" src="javascripts/core/constants.js"></script>
<script type="text/javascript" src="javascripts/core/math.js"></script>
<script type="text/javascript" src="javascripts/core/async-utils.js"></script>
<script type="text/javascript" src="javascripts/core/automator/automator-backend.js"></script>
<script type="text/javascript" src="javascripts/core/secret-formula/effects.js"></script>
@ -332,6 +333,7 @@
<script type="text/javascript" src="javascripts/components/modals/modal-import-tree.js"></script>
<script type="text/javascript" src="javascripts/components/modals/modal-load-game.js"></script>
<script type="text/javascript" src="javascripts/components/modals/modal-h2p.js"></script>
<script type="text/javascript" src="javascripts/components/modals/modal-progress-bar.js"></script>
<script type="text/javascript" src="javascripts/components/modals/options/modal-options.js"></script>
<script type="text/javascript" src="javascripts/components/modals/options/modal-animation-options.js"></script>
<script type="text/javascript" src="javascripts/components/modals/options/modal-confirmation-options.js"></script>

View File

@ -11,7 +11,6 @@ Vue.component("ra-tab", {
},
methods: {
update() {
this.unlocks = player.celestials.ra.unlocks;
this.expMults = this.pets.map(obj => obj.pet.expBoost);
this.currentExpGain = Ra.pets.teresa.baseExp;
this.showReality = Ra.pets.teresa.level > 2;

View File

@ -27,6 +27,7 @@ Vue.component("game-ui", {
</component>
<modal-popup v-if="view.modal.current" />
<modal-glyph-selection v-if="view.modal.glyphSelection" />
<modal-progress-bar v-if="view.modal.progressBar" />
<link v-if="view.theme !== 'Normal'" type="text/css" rel="stylesheet" :href="themeCss">
<div id="notification-container" class="l-notification-container" />
<help-me />

View File

@ -0,0 +1,29 @@
"use strict";
Vue.component("modal-progress-bar", {
computed: {
progress() {
return this.$viewModel.modal.progressBar;
},
foregroundStyle() {
return {
width: `${this.progress.current / this.progress.max * 100}%`,
};
},
},
methods: {
},
template: `
<div class="l-modal-overlay c-modal-overlay">
<div class="l-modal-progress-bar c-modal">
<div class="c-modal-progress-bar__label"> {{progress.label}} </div>
<div class="l-modal-progress-bar__hbox">
<span>0</span>
<div class="l-modal-progress-bar__bg c-modal-progress-bar__bg">
<div class="l-modal-progress-bar__fg c-modal-progress-bar__fg" :style="foregroundStyle"/>
</div>
<span>{{progress.max}}</span>
</div>
</div>
</div>`,
});

View File

@ -9,6 +9,7 @@ let ui = {
callback: undefined,
closeButton: false,
glyphSelection: false,
progressBar: undefined,
},
tabs: {
dimensions: {

View File

@ -0,0 +1,56 @@
"use strict";
/**
* Async is used for making a big pile of computation into a manageable
* set of batches that don't lock up the UI.
* run() is the nominal entry point.
*/
const Async = {
runForTime(fun, maxIter, config) {
const batchSize = config.batchSize || 1;
const maxTime = config.maxTime;
const t0 = Date.now();
for (let remaining = maxIter; remaining > 0;) {
for (let j = 0; j < Math.min(remaining, batchSize); ++j) {
fun();
--remaining;
}
if (Date.now() - t0 >= maxTime) return remaining;
}
return 0;
},
sleepPromise: ms => new Promise(resolve => setTimeout(resolve, ms)),
/**
* Asynchronously run the specified function maxIter times, letting the event
* loop run periodically. The function is run in chunks of config.batchSize;
* when the elapsed time reaches a specified amount, execution will pause for
* config.sleepTime
* @param {function} fun Function to run (e.g. do some computation)
* @param {number} maxIter Total number of times to run the function
* @param {Object} config Options for how to do the calculation
* @param {Number} config.maxTime Max time, ms, over which to run continuously
* @param {Number} [config.batchSize] Number of times to run fun between time checks. Since Date.now() takes
* non-zero time to execute, you don't necessarily want to check every iteration
* @param {Number} [config.sleepTime] Amount of time to suspend between computing
* @param {function(Number)} [config.asyncEntry] IF CALCULATION ISN'T DONE IN ONE BATCH, then this
* gets called before the first sleep. Use this to set up a progress bar, for example. The function will
* get passed the number of iterations run so far.
* @param {function(Number)} [config.asyncProgress] Called after the second and subsequent batches, with the
* total number of iterations done thus far
* @param {function} [config.asyncExit] If more than one batch was done, this will be called. For example, can
* be used to hide a progress bar.
* @returns {Promise}
*/
async run(fun, maxIter, config) {
let remaining = this.runForTime(fun, maxIter, config);
const sleepTime = config.sleepTime || 1;
if (!remaining) return;
if (config.asyncEntry) config.asyncEntry(maxIter - remaining);
do {
await this.sleepPromise(sleepTime);
remaining = this.runForTime(fun, remaining, config);
if (config.asyncProgress) config.asyncProgress(maxIter - remaining);
} while (remaining > 0);
if (config.asyncExit) config.asyncExit()
}
};

View File

@ -28,12 +28,12 @@ class VRunUnlockState extends GameMechanicState {
set completions(value) {
player.celestials.v.runUnlocks[this.id] = value;
}
tryComplete() {
if (this.completions === 6 || !this.config.condition(this.conditionValue)) return;
this.completions++;
GameUI.notify.success(`You have unlocked V achievement '${this.config.name}' tier ${this.completions}`);
V.updateTotalRunUnlocks()
V.updateTotalRunUnlocks();
}
}
@ -64,7 +64,6 @@ const V_UNLOCKS = {
if (player.dilation.dilatedTime.lt(db.dilatedTime)) return false;
if (player.replicanti.amount.lt(db.replicanti)) return false;
if (player.reality.realityMachines.lt(db.rm)) return false;
return true;
}
},
@ -99,14 +98,16 @@ const V = {
checkForUnlocks() {
if (!V.has(V_UNLOCKS.MAIN_UNLOCK) && V_UNLOCKS.MAIN_UNLOCK.requirement()) {
player.celestials.v.unlocks.push(V_UNLOCKS.MAIN_UNLOCK.id);
// eslint-disable-next-line no-bitwise
player.celestials.v.unlockBits |= (1 << V_UNLOCKS.MAIN_UNLOCK.id);
GameUI.notify.success(V_UNLOCKS.MAIN_UNLOCK.description);
}
for (let i = 0; i<V_UNLOCKS.RUN_UNLOCK_THRESHOLDS.length; i++) {
const unl = V_UNLOCKS.RUN_UNLOCK_THRESHOLDS[i];
if (unl.requirement() && !this.has(unl)) {
player.celestials.v.unlocks.push(unl.id);
// eslint-disable-next-line no-bitwise
player.celestials.v.unlockBits |= (1 << unl.id);
GameUI.notify.success(unl.description);
}
}
@ -118,7 +119,8 @@ const V = {
}
},
has(info) {
return player.celestials.v.unlocks.includes(info.id);
// eslint-disable-next-line no-bitwise
return Boolean(player.celestials.v.unlockBits & (1 << info.id));
},
startRun() {
player.celestials.v.run = startRealityOver() || player.celestials.v.run;

View File

@ -28,18 +28,23 @@ const EFFARIG_STAGES = {
};
class EffarigUnlockState extends GameMechanicState {
get cost() {
return this.config.cost;
constructor(config) {
super(config);
if (this.id < 0 || this.id > 31) throw new Error(`Id ${this.id} out of bit range`);
}
get cost() {
return this.config.cost;
}
get isUnlocked() {
return player.celestials.effarig.unlocks.includes(this.id);
// eslint-disable-next-line no-bitwise
return Boolean(player.celestials.effarig.unlockBits & (1 << this.id));
}
unlock() {
if (!this.isUnlocked) {
player.celestials.effarig.unlocks.push(this.id);
}
// eslint-disable-next-line no-bitwise
player.celestials.effarig.unlockBits |= (1 << this.id);
}
purchase() {

View File

@ -49,11 +49,12 @@ const Laitela = {
d.amount = new Decimal(1);
d.timeSinceLastUpdate = 0;
}
}
}
},
has(info) {
return player.celestials.laitela.unlocks.includes(info.id);
// eslint-disable-next-line no-bitwise
return Boolean(player.celestials.laitela.unlockBits & (1 << info.id));
},
canBuyUnlock(info) {
if (this.matter.lt(info.price)) return false;
@ -62,7 +63,8 @@ const Laitela = {
buyUnlock(info) {
if (!this.canBuyUnlock) return false;
this.matter = this.matter.minus(info.price);
player.celestials.laitela.unlocks.push(info.id);
// eslint-disable-next-line no-bitwise
player.celestials.laitela.unlockBits |= (1 << info.id);
return true;
},
startRun() {

View File

@ -193,7 +193,7 @@ const Ra = {
// Dev/debug function for easier testing
reset() {
const data = player.celestials.ra;
data.unlocks = [];
data.unlockBits = 0;
data.run = false;
data.charged = new Set();
data.quoteIdx = 0;
@ -217,15 +217,18 @@ const Ra = {
},
checkForUnlocks() {
for (const unl of Object.values(RA_UNLOCKS)) {
if (unl.pet.level >= unl.level && !this.has(unl)) player.celestials.ra.unlocks.push(unl.id);
// eslint-disable-next-line no-bitwise
if (unl.pet.level >= unl.level && !this.has(unl)) player.celestials.ra.unlockBits |= (1 << unl.id);
}
if (this.petList.every(pet => pet.level >= 20) && !this.has(RA_LAITELA_UNLOCK)) {
player.celestials.ra.unlocks.push(24);
// eslint-disable-next-line no-bitwise
player.celestials.ra.unlockBits |= (1 << 24);
MatterDimension(1).amount = new Decimal(1);
}
},
has(info) {
return player.celestials.ra.unlocks.includes(info.id);
// eslint-disable-next-line no-bitwise
return Boolean(player.celestials.ra.unlockBits & (1 << info.id));
},
startRun() {
player.celestials.ra.run = startRealityOver() || player.celestials.ra.run;

View File

@ -52,13 +52,15 @@ const Teresa = {
checkForUnlocks() {
for (const info of Object.values(Teresa.unlockInfo)) {
if (!this.has(info) && this.rmStore >= info.price) {
player.celestials.teresa.unlocks.push(info.id);
// eslint-disable-next-line no-bitwise
player.celestials.teresa.unlockBits |= (1 << info.id);
}
}
},
has(info) {
if (!info.hasOwnProperty("id")) throw "Pass in the whole TERESA UNLOCK object";
return player.celestials.teresa.unlocks.includes(info.id);
// eslint-disable-next-line no-bitwise
return Boolean(player.celestials.teresa.unlockBits & (1 << info.id));
},
startRun() {
player.celestials.teresa.run = startRealityOver() || player.celestials.teresa.run;
@ -85,9 +87,10 @@ const Teresa = {
return teresaQuotes[player.celestials.teresa.quoteIdx];
},
nextQuote() {
if (player.celestials.teresa.quoteIdx < 4 + player.celestials.teresa.unlocks.length) {
//if (player.celestials.teresa.quoteIdx < 4 + player.celestials.teresa.unlocks.length) {
// FIXME: redo quote system
player.celestials.teresa.quoteIdx++;
}
//}
},
get isRunning() {
return player.celestials.teresa.run;

View File

@ -405,17 +405,24 @@ const logFactorial = (function() {
}());
/** 32 bit XORSHIFT generator */
function xorshift32Update(state) {
/* eslint-disable no-bitwise */
/* eslint-disable no-param-reassign */
state ^= state << 13;
state ^= state >>> 17;
state ^= state << 5;
/* eslint-enable no-param-reassign */
/* eslint-enable no-bitwise */
return state;
}
const fastRandom = (function() {
let state = Math.floor(Date.now()) % Math.pow(2, 32);
const scale = 1 / (Math.pow(2, 32));
/* eslint-disable no-bitwise */
return () => {
state ^= state << 13;
state ^= state >>> 17;
state ^= state << 5;
state = xorshift32Update(state);
return state * scale + 0.5;
};
/* eslint-enable no-bitwise */
}());
// Normal distribution with specified mean and standard deviation

View File

@ -125,6 +125,7 @@ let player = {
infDimTimer: 0,
repUpgradeTimer: 0,
dilUpgradeTimer: 0,
ttTimer: 0,
},
infinityPoints: new Decimal(0),
infinitied: new Decimal(0),
@ -321,14 +322,14 @@ let player = {
teresa: {
rmStore: 0,
quoteIdx: 0,
unlocks: [],
unlockBits: 0,
run: false,
bestRunAM: new Decimal(1),
perkShop: Array.repeat(0, 4)
},
effarig: {
relicShards: 0,
unlocks: [],
unlocksBits: 0,
run: false,
quoteIdx: 0,
glyphWeights: {
@ -364,7 +365,7 @@ let player = {
maxQuotes: 6
},
v: {
unlocks: [],
unlockBits: 0,
quoteIdx: 0,
run: false,
runUnlocks: [0, 0, 0, 0, 0, 0],
@ -398,7 +399,7 @@ let player = {
amount: 0,
reaction: false
})),
unlocks: [],
unlocksBits: 0,
run: false,
charged: new Set(),
quoteIdx: 0,
@ -414,7 +415,7 @@ let player = {
laitela: {
matter: new Decimal(0),
run: false,
unlocks: [],
unlockBits: 0,
dimensions: Array.range(0, 4).map(() =>
({
amount: new Decimal(0),

View File

@ -148,9 +148,9 @@ function requestManualReality() {
function triggerManualReality() {
if (player.options.animations.reality) {
runRealityAnimation();
setTimeout(completeReality, 3000, false, false);
setTimeout(beginProcessReality, 3000, false, false);
} else {
completeReality();
beginProcessReality();
}
}
@ -199,21 +199,17 @@ function autoReality() {
Enslaved.lockedInGlyphLevel = gainedLevel;
Enslaved.lockedInRealityMachines = gainedRealityMachines();
Enslaved.lockedInShardsGained = Effarig.shardsGained;
completeReality(false, false, true);
beginProcessReality(false, false);
return;
}
processAutoGlyph(gainedLevel);
completeReality(false, false, true);
beginProcessReality(false, false);
}
// The ratio is the amount on top of the regular reality amount.
function boostedRealityRewards(ratio) {
player.reality.realityMachines = player.reality.realityMachines
.plus(Enslaved.lockedInRealityMachines.times(ratio));
// No glyph reward was given earlier
for (let glyphCount = 0; glyphCount < ratio + 1; ++glyphCount) {
processAutoGlyph(Enslaved.lockedInGlyphLevel);
}
player.realities += ratio;
player.reality.pp += ratio;
if (Teresa.has(TERESA_UNLOCKS.EFFARIG)) {
@ -234,13 +230,46 @@ function boostedRealityRewards(ratio) {
}
}
function completeReality(force, reset, auto = false) {
if (!reset) {
EventHub.dispatch(GameEvent.REALITY_RESET_BEFORE);
const simulatedRealities = simulatedRealityCount(true);
if (simulatedRealities > 0) {
// Due to simulated realities taking a long time in late game, this function might not immediately
// reality, but start an update loop that shows a progress bar.
function beginProcessReality(force, reset) {
if (reset) {
finishProcessReality(force, reset);
return;
}
EventHub.dispatch(GameEvent.REALITY_RESET_BEFORE);
const simulatedRealities = simulatedRealityCount(true);
// No glyph reward was given earlier
const glyphsToProcess = simulatedRealities + 1;
Async.run(() => processAutoGlyph(Enslaved.lockedInGlyphLevel),
glyphsToProcess,
{
batchSize: 100,
maxTime: 33,
sleepTime: 1,
asyncEntry: doneSoFar => {
GameIntervals.stop();
ui.$viewModel.modal.progressBar = {
label: "Processing new glyphs...",
current: doneSoFar,
max: glyphsToProcess,
};
},
asyncProgress: doneSoFar => {
ui.$viewModel.modal.progressBar.current = doneSoFar;
},
asyncExit: () => {
ui.$viewModel.modal.progressBar = undefined;
GameIntervals.start();
}
}).then(() => {
boostedRealityRewards(simulatedRealities);
}
finishProcessReality(force, reset);
});
}
function finishProcessReality(force, reset) {
if (!reset) {
if (player.thisReality < player.bestReality) {
player.bestReality = player.thisReality;
}
@ -485,7 +514,7 @@ function handleCelestialRuns(force) {
function startRealityOver() {
if (confirm("This will put you at the start of your reality and reset your progress in this reality. Are you sure you want to do this?")) {
completeReality(true, true);
beginProcessReality(true, true);
return true;
}
return false;

View File

@ -239,8 +239,9 @@ const GlyphGenerator = {
},
random() {
const x = Math.sin(player.reality.seed++) * 10000;
return x - Math.floor(x);
const state = xorshift32Update(player.reality.seed);
player.reality.seed = state;
return state * 2.3283064365386963e-10 + 0.5;
},
/**

View File

@ -335,25 +335,29 @@ GameDatabase.reality.perks = {
id: 104,
label: "TT1",
family: PerkFamily.AUTOMATION,
description: "Autobuy max TT every 10 seconds."
description: "Autobuy max TT every 10 seconds.",
effect: () => 10000,
},
autobuyerTT2: {
id: 105,
label: "TT2",
family: PerkFamily.AUTOMATION,
description: "Autobuy max TT every 5 seconds."
description: "Autobuy max TT every 5 seconds.",
effect: () => 5000,
},
autobuyerTT3: {
id: 106,
label: "TT3",
family: PerkFamily.AUTOMATION,
description: "Autobuy max TT every 3 seconds."
description: "Autobuy max TT every 3 seconds.",
effect: () => 3000,
},
autobuyerTT4: {
id: 107,
label: "TT4",
family: PerkFamily.AUTOMATION,
description: "Autobuy max TT every second."
description: "Autobuy max TT every second.",
effect: () => 1000,
},
achievementRowGroup1: {
id: 201,

View File

@ -1,5 +1,12 @@
"use strict";
function arrayToBits(array) {
let bits = 0;
// eslint-disable-next-line no-bitwise
for (const id of array) bits |= (1 << id);
return bits;
}
// WARNING: Don't use state accessors and functions from global scope here, that's not safe in long-term
GameStorage.devMigrations = {
patches: [
@ -275,10 +282,7 @@ GameStorage.devMigrations = {
player.celestials.teresa.rmStore = Teresa.rmStoreMax;
}
if (player.reality.upg) {
for (const upg of player.reality.upg) {
// eslint-disable-next-line no-bitwise
player.reality.upgradeBits |= (1 << upg);
}
player.reality.upgradeBits = arrayToBits(player.reality.upg);
delete player.reality.upg;
}
// eslint-disable-next-line no-bitwise
@ -442,7 +446,22 @@ GameStorage.devMigrations = {
},
GameStorage.migrations.renameNewsOption,
GameStorage.migrations.removeDimensionCosts,
GameStorage.migrations.renameTickspeedPurchaseBumps
GameStorage.migrations.renameTickspeedPurchaseBumps,
player => {
player.celestials.teresa.unlockBits = arrayToBits(player.celestials.teresa.unlocks);
delete player.celestials.teresa.unlocks;
player.celestials.effarig.unlockBits = arrayToBits(player.celestials.effarig.unlocks);
delete player.celestials.effarig.unlocks;
player.celestials.v.unlockBits = arrayToBits(player.celestials.v.unlocks);
delete player.celestials.v.unlocks;
player.celestials.ra.unlockBits = arrayToBits(player.celestials.ra.unlocks);
delete player.celestials.ra.unlocks;
player.celestials.laitela.unlockBits = arrayToBits(player.celestials.laitela.unlocks);
delete player.celestials.laitela.unlocks;
},
player => {
player.reality.seed = Math.floor(Math.abs(player.reality.seed)) % 0xFFFFFFFF;
}
],
patch(player) {

View File

@ -78,7 +78,7 @@ const GameStorage = {
},
save(silent = false) {
if (GlyphSelection.active) return;
if (GlyphSelection.active || ui.$viewModel.modal.progressBar !== undefined) return;
if (++this.saved > 99) SecretAchievement(12).unlock();
const root = {
current: this.currentSlot,

View File

@ -102,16 +102,19 @@ const TimeTheorems = {
}
};
function autoBuyMaxTheorems() {
if (!player.ttbuyer) return false;
if (Perk.autobuyerTT4.isBought ||
(Perk.autobuyerTT3.isBought && ttMaxTimer >= 3) ||
(Perk.autobuyerTT2.isBought && ttMaxTimer >= 5) ||
(Perk.autobuyerTT1.isBought && ttMaxTimer >= 10)) {
function autoBuyMaxTheorems(realDiff) {
if (!player.ttbuyer) return;
player.auto.ttTimer += realDiff;
const period = Effects.min(
Number.POSITIVE_INFINITY,
Perk.autobuyerTT1,
Perk.autobuyerTT2,
Perk.autobuyerTT3,
Perk.autobuyerTT4);
if (player.auto.ttTimer > period) {
TimeTheorems.buyMax(true);
return true;
player.auto.ttTimer = Math.min(player.auto.ttTimer - period, period);
}
return false;
}
function calculateTimeStudiesCost() {

View File

@ -293,17 +293,12 @@ function kongLog10StatSubmission() {
setInterval(kongLog10StatSubmission, 10000)
var ttMaxTimer = 0;
function randomStuffThatShouldBeRefactored() {
// document.getElementById("kongip").textContent = "Double your IP gain from all sources (additive). Forever. Currently: x"+kongIPMult+", next: x"+(kongIPMult==1? 2: kongIPMult+2)
// document.getElementById("kongep").textContent = "Triple your EP gain from all sources (additive). Forever. Currently: x"+kongEPMult+", next: x"+(kongEPMult==1? 3: kongEPMult+3)
// document.getElementById("kongdim").textContent = "Double all your normal dimension multipliers (multiplicative). Forever. Currently: x"+kongDimMult+", next: x"+(kongDimMult*2)
// document.getElementById("kongalldim").textContent = "Double ALL the dimension multipliers (Normal, Infinity, Time) (multiplicative until 32x). Forever. Currently: x"+kongAllDimMult+", next: x"+((kongAllDimMult < 32) ? kongAllDimMult * 2 : kongAllDimMult + 32)
ttMaxTimer++;
if (autoBuyMaxTheorems()) ttMaxTimer = 0;
if (!Teresa.has(TERESA_UNLOCKS.EFFARIG)) player.celestials.teresa.rmStore *= Math.pow(0.98, 1/60) // Teresa container leak, 2% every minute, only works online.
}
@ -910,6 +905,7 @@ function slowerAutobuyers(realDiff) {
player.auto.dilUpgradeTimer = Math.min(player.auto.dilUpgradeTimer - dilUpgradePeriod, dilUpgradePeriod);
autoBuyDilationUpgrades();
}
autoBuyMaxTheorems(realDiff);
}
setInterval(function () {

View File

@ -5065,6 +5065,47 @@ kbd {
/*#endregion c-modal-shortcuts*/
.l-modal-progress-bar {
position: fixed;
z-index: 3;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
left: 50vw;
top: 50vh;
transform: translate(-50%, -50%);
}
.l-modal-progress-bar__hbox {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.c-modal-progress-bar__label {
padding-bottom: 0.5rem;
}
.l-modal-progress-bar__bg {
width: 20rem;
margin-left: 1rem;
margin-right: 1rem;
height: 2rem;
}
.c-modal-progress-bar__bg {
background: black;
}
.l-modal-progress-bar__fg {
height: 100%;
}
.c-modal-progress-bar__fg {
background: blue;
}
/*#endregion Modals*/
.l-notification-container {