G420/glyph equip undo (#909)

* add support for undo of equipping a glyph

Simplistic, only restores EP and EC completions right now

* save am, ip, and tt for glyph undo

* do not respec compression upgrades or ra charges on glyph undo

* eslint warning fixes for rm.js

* make glyph undo a 1e10RM teresa unlock

* added glyph undo to confirmation settings

* fix time theorem calculation in glyph undo save

don't count normally purchased TT, as we refund their costs and player can buy them again

count dilation time study cost, since those studies will be removed

* remove studyPath function as it's not used any more

(was for old automator)
This commit is contained in:
garnet420 2019-09-18 18:01:33 -04:00 committed by GitHub
parent b085457aec
commit 7c7de9eea1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 192 additions and 158 deletions

View File

@ -8,7 +8,9 @@ Vue.component("modal-confirmation-options", {
challenges: false,
eternity: false,
dilation: false,
reality: false
reality: false,
glyphUndo: false,
glyphUndoUnlocked: false,
};
},
watch: {
@ -26,7 +28,10 @@ Vue.component("modal-confirmation-options", {
},
reality(newValue) {
player.options.confirmations.reality = newValue;
}
},
glyphUndo(newValue) {
player.options.confirmations.glyphUndo = newValue;
},
},
methods: {
update() {
@ -36,6 +41,8 @@ Vue.component("modal-confirmation-options", {
this.eternity = options.eternity;
this.dilation = options.dilation;
this.reality = options.reality;
this.glyphUndo = options.glyphUndo;
this.glyphUndoUnlocked = Teresa.has(TERESA_UNLOCKS.UNDO);
}
},
template:
@ -45,5 +52,6 @@ Vue.component("modal-confirmation-options", {
<on-off-button v-if="eternityUnlocked" v-model="eternity" text="Eternity:"/>
<on-off-button v-if="dilationUnlocked" v-model="dilation" text="Dilation:"/>
<on-off-button v-if="realityUnlocked" v-model="reality" text="Reality:"/>
<on-off-button v-if="glyphUndoUnlocked" v-model="glyphUndo" text="Glyph undo:"/>
</modal-options>`
});

View File

@ -6,6 +6,8 @@ Vue.component("equipped-glyphs", {
glyphs: [],
dragoverIndex: -1,
respec: player.reality.respec,
undoAvailable: false,
undoVisible: false,
};
},
computed: {
@ -22,6 +24,12 @@ Vue.component("equipped-glyphs", {
? "Respec is active and will place your currently - equipped glyphs into your inventory after reality."
: "Your currently-equipped glyphs will stay equipped on reality.";
},
undoTooltip() {
return this.undoAvailable
? ("Unequip the last equipped glyph and rewind reality to when you equipped it." +
" (Most resources will be fully reset)")
: "Undo is only available for glyphs equipped during this reality";
}
},
created() {
this.on$(GameEvent.GLYPHS_CHANGED, this.glyphsChanged);
@ -63,10 +71,22 @@ Vue.component("equipped-glyphs", {
},
update() {
this.respec = player.reality.respec;
this.undoVisible = Teresa.has(TERESA_UNLOCKS.UNDO);
this.undoAvailable = this.undoVisible && player.reality.glyphs.undo.length > 0;
},
glyphsChanged() {
this.glyphs = Glyphs.active.map(GlyphGenerator.copy);
},
undo() {
if (!this.undoAvailable) return;
if (player.options.confirmations.glyphUndo &&
!confirm("The last equipped glyph will be removed. Reality will be reset, but you will get the antimatter," +
" infinity points, eternity points, time theorems, and eternity challenge completions that you had when it " +
"was equipped.")) {
return;
}
Glyphs.undo();
}
},
template: `
<div class="l-equipped-glyphs">
@ -89,11 +109,20 @@ Vue.component("equipped-glyphs", {
</div>
</template>
</div>
<button :class="['l-equipped-glyphs__respec', 'c-reality-upgrade-btn', {'c-reality-upgrade-btn--bought': respec}]"
:ach-tooltip="respecTooltip"
@click="toggleRespec">
Clear glyph slots on Reality
</button>
<div class="l-equipped-glyphs__buttons">
<button :class="['l-equipped-glyphs__respec', 'c-reality-upgrade-btn', {'c-reality-upgrade-btn--bought': respec}]"
:ach-tooltip="respecTooltip"
@click="toggleRespec">
Clear glyph slots on Reality
</button>
<button v-if="undoVisible"
class="l-equipped-glyphs__undo c-reality-upgrade-btn"
:class="{'c-reality-upgrade-btn--unavailable': !undoAvailable}"
:ach-tooltip="undoTooltip"
@click="undo">
Undo
</button>
</div>
</div>
`,
});

View File

@ -21,6 +21,11 @@ const TERESA_UNLOCKS = {
price: 1e24,
description: "unlock Perk Point Shop.",
},
UNDO: {
id: 4,
price: 1e10,
description: 'unlock "Undo" of equipping a glyph.',
}
};
const Teresa = {
@ -160,3 +165,5 @@ EventHub.logic.on(GameEvent.REALITY_RESET_BEFORE, () => {
if (!Teresa.isRunning) return;
Teresa.quotes.show(Teresa.quotes.COMPLETE_REALITY);
});
EventHub.logic.on(GameEvent.GAME_LOAD, () => Teresa.checkForUnlocks());

View File

@ -263,6 +263,7 @@ let player = {
effarig: 0,
reality: 0
},
undo: [],
},
seed: Math.floor(Date.now() * Math.random() + 1),
rebuyables: {
@ -464,6 +465,7 @@ let player = {
dilation: true,
reality: true,
glyphSacrifice: true,
glyphUndo: true,
}
}
};

View File

@ -201,6 +201,7 @@ function processAutoGlyph(gainedLevel) {
function getRealityProps(isReset, alreadyGotGlyph = false) {
if (isReset) return {
reset: true,
glyphUndo: false,
};
return {
reset: false,
@ -209,6 +210,7 @@ function getRealityProps(isReset, alreadyGotGlyph = false) {
gainedShards: Effarig.shardsGained,
simulatedRealities: simulatedRealityCount(true),
alreadyGotGlyph,
glyphUndo: false,
};
}
@ -298,11 +300,14 @@ function beginProcessReality(realityProps) {
function finishProcessReality(realityProps) {
const isReset = realityProps.reset;
if (!isReset) giveRealityRewards(realityProps);
if (player.reality.respec) {
respecGlyphs();
if (!realityProps.glyphUndo) {
Glyphs.clearUndo();
if (player.reality.respec) respecGlyphs();
if (player.celestials.ra.disCharge) disChargeAll();
if (player.celestials.ra.compression.respec) CompressionUpgrades.respec();
}
clearCelestialRuns();
TimeCompression.isActive = false;
const celestialRunState = clearCelestialRuns();
recalculateAllGlyphs()
//reset global values to avoid a tick of unupdated production
@ -411,8 +416,6 @@ function finishProcessReality(realityProps) {
resetChallengeStuff();
NormalDimensions.reset();
secondSoftReset();
if (player.celestials.ra.disCharge) disChargeAll();
if (player.celestials.ra.compression.respec) CompressionUpgrades.respec();
player.celestials.ra.peakGamespeed = 1;
if (isRUPG10Bought) {
player.eternities = new Decimal(100);
@ -478,16 +481,36 @@ function finishProcessReality(realityProps) {
player.reality.gainedAutoAchievements = false;
tryUnlockAchievementsOnReality();
if (realityProps.glyphUndo) restoreCelestialRuns(celestialRunState);
}
function restoreCelestialRuns(celestialRunState) {
player.celestials.teresa.run = celestialRunState.teresa;
player.celestials.effarig.run = celestialRunState.effarig;
player.celestials.enslaved.run = celestialRunState.enslaved;
player.celestials.v.run = celestialRunState.v;
player.celestials.ra.run = celestialRunState.ra;
player.celestials.laitela.run = celestialRunState.laitela;
// For effarig, in order to make sure glyph effects are correctly capped
recalculateAllGlyphs();
}
function clearCelestialRuns() {
const saved = {
teresa: player.celestials.teresa.run,
effarig: player.celestials.effarig.run,
enslaved: player.celestials.enslaved.run,
v: player.celestials.v.run,
ra: player.celestials.ra.run,
laitela: player.celestials.laitela.run,
};
player.celestials.teresa.run = false;
player.celestials.effarig.run = false;
player.celestials.enslaved.run = false;
player.celestials.v.run = false;
player.celestials.ra.run = false;
TimeCompression.isActive = false;
player.celestials.laitela.run = false;
return saved;
}
function startRealityOver() {

View File

@ -8,6 +8,7 @@ const orderedEffectList = ["powerpow", "infinitypow", "replicationpow", "timepow
"effarigblackhole", "effarigrm", "effarigglyph", "effarigachievement",
"effarigforgotten", "effarigdimensions", "effarigantimatter"];
// eslint-disable-next-line no-unused-vars
const GlyphEffectOrder = orderedEffectList.mapToObject(e => e, (e, idx) => idx);
function rarityToStrength(x) {
@ -68,14 +69,15 @@ const AutoGlyphPicker = {
switch (AutoGlyphPicker.mode) {
case AutoGlyphPickMode.RANDOM: return Math.random();
case AutoGlyphPickMode.RARITY: return strengthToRarity(glyph.strength);
case AutoGlyphPickMode.ABOVE_SACRIFICE_THRESHOLD:
let comparedToThreshold = AutoGlyphSacrifice.comparedToThreshold(glyph);
case AutoGlyphPickMode.ABOVE_SACRIFICE_THRESHOLD: {
const comparedToThreshold = AutoGlyphSacrifice.comparedToThreshold(glyph);
if (comparedToThreshold < 0) {
// We're going to sacrifice the glyph anyway. Also, if we have 1000% rarity glyphs everything has broken,
// so subtracting 1000 should be safe (glyphs we would sacrifice are sorted below all other glyphs).
return strengthToRarity(glyph.strength) - 1000;
}
return comparedToThreshold;
}
case AutoGlyphPickMode.LOWEST_ALCHEMY_RESOURCE: return -AlchemyResource[glyph.type].amount;
}
throw new Error("Unknown auto glyph picker mode");
@ -83,7 +85,7 @@ const AutoGlyphPicker = {
pick(glyphs) {
return glyphs
.map(g => ({glyph: g, score: this.getPickScore(g)}))
.reduce((x, y) => x.score > y.score ? x : y)
.reduce((x, y) => (x.score > y.score ? x : y))
.glyph;
}
};
@ -116,8 +118,8 @@ const GlyphGenerator = {
return {
id: this.makeID(),
idx: null,
type: type,
strength: strength,
type,
strength,
level: level.actualLevel,
rawLevel: level.rawLevel,
effects: effectBitmask,
@ -283,7 +285,7 @@ const Glyphs = {
},
refreshActive() {
this.active = new Array(this.activeSlotCount).fill(null);
for (let g of player.reality.glyphs.active) {
for (const g of player.reality.glyphs.active) {
if (this.active[g.idx]) {
throw new Error("Stacked active glyphs?");
}
@ -294,8 +296,8 @@ const Glyphs = {
this.refreshActive();
this.inventory = new Array(player.reality.glyphs.inventorySize).fill(null);
// Glyphs could previously end up occupying the same inventory slot (Stacking)
let stacked = [];
for (let g of player.reality.glyphs.inventory) {
const stacked = [];
for (const g of player.reality.glyphs.inventory) {
if (this.inventory[g.idx]) {
stacked.push(g);
} else {
@ -304,9 +306,9 @@ const Glyphs = {
}
// Try to unstack glyphs:
while (stacked.length) {
let freeIndex = this.findFreeIndex();
const freeIndex = this.findFreeIndex();
if (freeIndex >= 0) {
let glyph = stacked.shift();
const glyph = stacked.shift();
this.inventory[freeIndex] = glyph;
glyph.idx = freeIndex;
} else {
@ -336,23 +338,34 @@ const Glyphs = {
if (this.active[targetSlot] !== null) return;
if (glyph.type === "effarig" && this.active.some(x => x && x.type === "effarig")) return;
if (glyph.type === "reality" && this.active.some(x => x && x.type === "reality")) return;
const oldIndex = glyph.idx;
this.removeFromInventory(glyph);
player.reality.glyphs.active.push(glyph);
glyph.idx = targetSlot;
this.active[targetSlot] = glyph;
this.saveUndo(oldIndex, targetSlot);
EventHub.dispatch(GameEvent.GLYPHS_CHANGED);
this.validate();
},
unequipAll() {
while (player.reality.glyphs.active.length) {
let freeIndex = this.findFreeIndex();
const freeIndex = this.findFreeIndex();
if (freeIndex < 0) break;
let glyph = player.reality.glyphs.active.pop();
const glyph = player.reality.glyphs.active.pop();
this.active[glyph.idx] = null;
Glyphs.addToInventory(glyph);
}
EventHub.dispatch(GameEvent.GLYPHS_CHANGED);
},
unequip(activeIndex, requestedInventoryIndex) {
if (this.active[activeIndex] === null) return;
const storedIndex = player.reality.glyphs.active.findIndex(glyph => glyph.idx === activeIndex);
if (storedIndex < 0) return;
const glyph = player.reality.glyphs.active.splice(storedIndex, 1)[0];
this.active[activeIndex] = null;
Glyphs.addToInventory(glyph, requestedInventoryIndex);
EventHub.dispatch(GameEvent.GLYPHS_CHANGED);
},
moveToSlot(glyph, targetSlot) {
if (this.inventory[targetSlot] === null) this.moveToEmpty(glyph, targetSlot);
else this.swap(glyph, this.inventory[targetSlot]);
@ -377,16 +390,19 @@ const Glyphs = {
this.validate();
this.inventory[glyphA.idx] = glyphB;
this.inventory[glyphB.idx] = glyphA;
const tmp = glyphA.idx;
const swapGlyph = glyphA.idx;
glyphA.idx = glyphB.idx;
glyphB.idx = tmp;
glyphB.idx = swapGlyph;
this.validate();
EventHub.dispatch(GameEvent.GLYPHS_CHANGED);
},
addToInventory(glyph) {
addToInventory(glyph, requestedInventoryIndex) {
this.validate();
let index = this.findFreeIndex();
if (index < 0) return;
if (requestedInventoryIndex !== undefined) {
if (this.inventory[requestedInventoryIndex] === null) index = requestedInventoryIndex;
}
this.inventory[index] = glyph;
glyph.idx = index;
player.reality.glyphs.inventory.push(glyph);
@ -396,7 +412,7 @@ const Glyphs = {
removeFromInventory(glyph) {
this.validate();
// This can get called on a glyph not in inventory, during auto sacrifice.
let index = player.reality.glyphs.inventory.indexOf(glyph);
const index = player.reality.glyphs.inventory.indexOf(glyph);
if (index < 0) return;
this.inventory[glyph.idx] = null;
player.reality.glyphs.inventory.splice(index, 1);
@ -417,11 +433,11 @@ const Glyphs = {
},
sort() {
const freeSpace = this.freeInventorySpace;
let byType = GLYPH_TYPES.mapToObject(g => g, () => ({ glyphs: [], padding: 0 }));
for (let g of player.reality.glyphs.inventory) byType[g.type].glyphs.push(g);
const byType = GLYPH_TYPES.mapToObject(g => g, () => ({ glyphs: [], padding: 0 }));
for (const g of player.reality.glyphs.inventory) byType[g.type].glyphs.push(g);
const compareGlyphs = (a, b) => -a.level * a.strength + b.level * b.strength;
let totalDesiredPadding = 0;
for (let t of Object.values(byType)) {
for (const t of Object.values(byType)) {
t.glyphs.sort(compareGlyphs);
t.padding = Math.ceil(t.glyphs.length / 10) * 10 - t.glyphs.length;
// Try to get a full row of padding if possible in some cases
@ -452,6 +468,40 @@ const Glyphs = {
get levelCap() {
return 10000 + AlchemyResource.boundless.effectValue;
},
clearUndo() {
player.reality.glyphs.undo = [];
},
saveUndo(oldIndex, targetSlot) {
const undoData = {
oldIndex,
targetSlot,
am: new Decimal(player.antimatter),
ip: new Decimal(player.infinityPoints),
ep: new Decimal(player.eternityPoints),
tt: player.timestudy.theorem.plus(calculateTimeStudiesCost() + calculateDilationStudiesCost() -
TimeTheorems.totalPurchased()),
ecs: EternityChallenges.all.map(e => e.completions),
thisReality: player.thisReality,
thisRealityRealTime: player.thisRealityRealTime
};
player.reality.glyphs.undo.push(undoData);
},
undo() {
if (player.reality.glyphs.undo.length === 0) return;
const undoData = player.reality.glyphs.undo.pop();
this.unequip(undoData.targetSlot, undoData.oldIndex);
finishProcessReality({
reset: true,
glyphUndo: true,
});
player.antimatter.copyFrom(undoData.am);
player.infinityPoints.copyFrom(undoData.ip);
player.eternityPoints.copyFrom(undoData.ep);
player.timestudy.theorem.copyFrom(undoData.tt);
EternityChallenges.all.map((ec, ecIndex) => ec.completions = undoData.ecs[ecIndex]);
player.thisReality = undoData.thisReality;
player.thisRealityRealTime = undoData.thisRealityRealTime;
}
};
class GlyphSacrificeState extends GameMechanicState {
@ -621,7 +671,10 @@ function getActiveGlyphEffects() {
function deleteGlyph(id, force) {
const glyph = Glyphs.findById(id);
if (canSacrifice()) return sacrificeGlyph(glyph, force);
if (canSacrifice()) {
sacrificeGlyph(glyph, force);
return;
}
if (force || confirm("Do you really want to delete this glyph?")) {
Glyphs.removeFromInventory(glyph);
}
@ -776,14 +829,14 @@ function getGlyphLevelInputs() {
const levelCapped = scaledLevel > levelHardcap;
scaledLevel = Math.min(scaledLevel, levelHardcap);
return {
epEffect: epEffect,
replEffect: replEffect,
dtEffect: dtEffect,
eterEffect: eterEffect,
epEffect,
replEffect,
dtEffect,
eterEffect,
perkShop: perkShopEffect,
scalePenalty: scalePenalty,
perkFactor: perkFactor,
shardFactor: shardFactor,
scalePenalty,
perkFactor,
shardFactor,
rawLevel: baseLevel,
actualLevel: Math.max(1, scaledLevel),
capped: levelCapped

View File

@ -128,6 +128,12 @@ function calculateTimeStudiesCost() {
return totalCost;
}
function calculateDilationStudiesCost() {
return TimeStudy.boughtDilationTS()
.map(ts => ts.cost)
.reduce(Number.sumReducer, 0);
}
function unlockDilation(quiet) {
if (!quiet) {
Tab.eternity.dilation.show();
@ -259,121 +265,6 @@ function studiesUntil(id) {
}
}
function studyPath(mode, args, auto) {
if (!(mode === 'none' || mode === 'all')) return false;
if (args === undefined) args = [];
args = args.map(function (x) { if (!isNaN(x)) return parseInt(x); else return x; });
let row = 0;
let master = [];
let locks = [0, 0, 0];
main: while (row < 24) {
row++;
if (mode === 'none') {
if (row >= 2 && row <= 4) {
for (let i = 20; i <= 40; i += 10) {
if (args.includes(i + 1) && !master.includes(row*10 + 1)) master.push(row*10 + 1);
if (args.includes(i + 2) && !master.includes(row*10 + 2)) master.push(row*10 + 2);
}
if (row === 3 && args.includes(33)) master.push(33);
continue main;
}
if (row === 6) {
if (args.includes(62)) master.push(61, 62);
else master.push(61);
continue main;
}
if (row === 16) {
if (args.includes(161)) master.push(161);
if (args.includes(162)) master.push(162);
continue main;
}
if (row === 19) {
if (args.includes(191)) master.push(191);
if (args.includes(192) || args.includes(201)) master.push(192);
if (args.includes(193)) master.push(193);
continue main;
}
if (row === 21) {
for (let i = 0; i < args.length; i++) {
if (!isNaN(args[i])) {
if (Math.floor(args[i] / 10) === 21 && args[i] % 10 < 5 && args[i] % 10 > 0) {
master.push(args[i]);
}
}
}
continue main;
}
}
if (row >= 7 && row <= 10) {
if (mode === 'all' && DilationUpgrade.timeStudySplit.isBought) {
master.push(row*10 + 1, row*10 + 2, row*10 + 3);
continue main;
}
if (locks[0] === 0) {
let temp = [];
let options = ['nd', 'id', 'td', 'normal', 'infinity', 'time'];
for (let k = 0; k < args.length; k++) {
for (let i = 70; i <= 100; i += 10) {
for (let j = 1; j <= 3; j++) {
if (args[k] === i + j || args[k] === options[j - 1] || args[k] === options[j+3]) temp.push(j);
}
}
}
if (temp.length === 0) break main;
locks[0] = temp[0];
temp = temp.filter(function (x) { if (x !== locks[0]) return x;});
if (temp.length > 0) locks[2] = temp[0];
}
master.push(row*10 + locks[0]);
continue main;
}
if (row >= 12 && row <= 14) {
if (locks[1] === 0) {
let temp = [];
let options = ['active', 'passive', 'idle'];
for (let k = 0; k < args.length; k++) {
for (let i = 120; i <= 140; i += 10) {
for (let j = 1; j <= 3; j++) {
if (args[k] === i + j || args[k] === options[j - 1]) temp.push(j);
}
}
}
if (temp.length === 0) break main;
locks[1] = temp[0];
}
master.push(row*10 + locks[1]);
continue main;
}
if (row === 22 || row === 23) {
col: for (let i = 1; i <= 8 / (row - 21); i += 2) {
for (let j = 0; j < args.length; j++) {
for (let k = 0; k < 2; k++) {
if (args[j] === row*10 + i + k) {
master.push(args[j]);
continue col;
}
}
}
}
continue main;
}
for (let i = 1; TimeStudy(row * 10 + i) !== undefined; i++) {
master.push(row * 10 + i);
}
}
if (locks[2] > 0) {
master.push(70 + locks[2], 80 + locks[2], 90 + locks[2], 100 + locks[2]);
}
let string = master.reduce(function (acc, x) {
return acc += x + ',';
}, '');
string = string.slice(0, -1);
string += '|0';
importStudyTree(string, auto);
}
function respecTimeStudies(auto) {
for (const study of TimeStudy.boughtNormalTS()) {
study.refund();
@ -691,6 +582,11 @@ TimeStudy.timeDimension = function(tier) {
*/
TimeStudy.reality = DilationTimeStudyState.studies[6];
TimeStudy.boughtDilationTS = function() {
return player.dilation.studies.map(id => DilationTimeStudyState.studies[id]);
};
class TimeStudyConnection {
constructor(from, to, override) {
this._from = from;

View File

@ -384,6 +384,13 @@
box-shadow: 0 0 0.5rem 0.25rem #444;
}
.l-equipped-glyphs__buttons {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.l-equipped-glyphs__respec {
width: 15rem;
height: 4rem;
@ -392,6 +399,15 @@
margin-bottom: 1rem;
}
.l-equipped-glyphs__undo {
width: 5em;
height: 4rem;
margin: auto;
margin-top: 2rem;
margin-bottom: 1rem;
margin-left: 0.2rem;
}
.c-current-glyph-effects__effect--capped {
color: #ff8000;
}

View File

@ -955,7 +955,7 @@ body.t-dark {
margin: 0.5rem;
}
.c-reality-upgrade-btn {
.l-reality-upgrade-btn {
width: 15.6rem;
}

View File

@ -6891,6 +6891,7 @@ kbd {
align-items: center;
flex-direction: column;
position: relative;
width: 25rem;
}
.c-reality-upgrade-btn {
@ -6903,7 +6904,6 @@ kbd {
text-align: center;
font-size: 1rem;
font-family: Typewriter, serif;
width: 25rem;
}
.c-reality-upgrade-btn__requirement {