mirror of
https://github.com/IvarK/AntimatterDimensionsSourceCode.git
synced 2024-11-12 23:23:20 +00:00
Further TimeStudyTree code restructuring and standardization
This commit is contained in:
parent
83fedc02b2
commit
bf826aa267
@ -31,7 +31,7 @@ Vue.component("ec-time-study", {
|
||||
return typeof this.study.requirementCurrent === "number";
|
||||
},
|
||||
formatValue() {
|
||||
return this.config.requirement.formatValue;
|
||||
return this.config.secondary.formatValue;
|
||||
},
|
||||
// Linebreaks added to avoid twitching in scientific notation
|
||||
needsFirstLinebreak() {
|
||||
@ -69,12 +69,11 @@ Vue.component("ec-time-study", {
|
||||
<br>
|
||||
Requirement:
|
||||
<br v-if="needsFirstLinebreak">
|
||||
<span v-if="id === 12">Use only the Time Dimension path</span>
|
||||
<span v-else-if="id === 11">Use only the Antimatter Dimension path</span>
|
||||
<span v-if="config.secondary.path">Use only the {{ config.secondary.path }} path</span>
|
||||
<span v-else>
|
||||
{{ formatValue(requirement.current) }}/{{ formatValue(requirement.total) }}
|
||||
<br v-if="needsSecondLinebreak">
|
||||
{{ config.requirement.resource }}
|
||||
{{ config.secondary.resource }}
|
||||
</span>
|
||||
</template>
|
||||
<span v-if="isUnlocked && !isRunning"><br>Double click to start</span>
|
||||
|
@ -22,17 +22,9 @@ Vue.component("normal-time-study", {
|
||||
},
|
||||
hintText() {
|
||||
const id = this.study.id;
|
||||
switch (this.setup.path) {
|
||||
case TIME_STUDY_PATH.ANTIMATTER_DIM: return `${id} Antimatter Dims`;
|
||||
case TIME_STUDY_PATH.INFINITY_DIM: return `${id} Infinity Dims`;
|
||||
case TIME_STUDY_PATH.TIME_DIM: return `${id} Time Dims`;
|
||||
case TIME_STUDY_PATH.ACTIVE: return `${id} Active`;
|
||||
case TIME_STUDY_PATH.PASSIVE: return `${id} Passive`;
|
||||
case TIME_STUDY_PATH.IDLE: return `${id} Idle`;
|
||||
case TIME_STUDY_PATH.LIGHT: return `${id} Light`;
|
||||
case TIME_STUDY_PATH.DARK: return `${id} Dark`;
|
||||
}
|
||||
return id;
|
||||
if (!this.setup.path) return id;
|
||||
const pathEntry = NormalTimeStudies.pathList.find(p => p.path === this.setup.path);
|
||||
return `${id} ${pathEntry.name}`;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -9,65 +9,28 @@ Vue.component("modal-edit-tree", {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
tree() {
|
||||
importedTree() {
|
||||
if (!this.inputIsValidTree) return false;
|
||||
const formattedInput = this.input.split("|")[0].split(",");
|
||||
const eternityChallenge = TimeStudy.eternityChallenge(this.input.split("|")[1]);
|
||||
const hasEternityChallenge = eternityChallenge !== undefined;
|
||||
const studies = new Set();
|
||||
for (const study of formattedInput) {
|
||||
studies.add(TimeStudy(study));
|
||||
}
|
||||
let totalCost = 0;
|
||||
let missingCost = 0;
|
||||
if (hasEternityChallenge) {
|
||||
totalCost += eternityChallenge.cost;
|
||||
if (player.challenge.eternity.unlocked !== eternityChallenge.id) {
|
||||
missingCost += eternityChallenge.cost;
|
||||
}
|
||||
}
|
||||
const firstSplitPaths = new Set();
|
||||
const secondSplitPaths = new Set();
|
||||
for (const study of studies) {
|
||||
if (study.cost) {
|
||||
totalCost += study.cost;
|
||||
if (!study.isBought) {
|
||||
missingCost += study.cost;
|
||||
}
|
||||
}
|
||||
switch (study.path) {
|
||||
case TIME_STUDY_PATH.ANTIMATTER_DIM:
|
||||
firstSplitPaths.add("Antimatter Dims");
|
||||
break;
|
||||
case TIME_STUDY_PATH.INFINITY_DIM:
|
||||
firstSplitPaths.add("Infinity Dims");
|
||||
break;
|
||||
case TIME_STUDY_PATH.TIME_DIM:
|
||||
firstSplitPaths.add("Time Dims");
|
||||
break;
|
||||
case TIME_STUDY_PATH.ACTIVE:
|
||||
secondSplitPaths.add("Active");
|
||||
break;
|
||||
case TIME_STUDY_PATH.PASSIVE:
|
||||
secondSplitPaths.add("Passive");
|
||||
break;
|
||||
case TIME_STUDY_PATH.IDLE:
|
||||
secondSplitPaths.add("Idle");
|
||||
}
|
||||
}
|
||||
const totalST = this.calculateMissingSTCost([...studies], true);
|
||||
const missingST = this.calculateMissingSTCost([...studies], false);
|
||||
const importedTree = new TimeStudyTree(this.input, Currency.timeTheorems.value, V.spaceTheorems);
|
||||
return {
|
||||
totalST,
|
||||
missingST,
|
||||
totalCost,
|
||||
missingCost,
|
||||
firstSplitPaths,
|
||||
secondSplitPaths,
|
||||
eternityChallenge,
|
||||
hasEternityChallenge
|
||||
totalTT: importedTree.runningCost[0],
|
||||
totalST: importedTree.runningCost[1],
|
||||
newStudies: makeEnumeration(importedTree.purchasedStudies),
|
||||
invalidStudies: importedTree.invalidStudies,
|
||||
firstPaths: makeEnumeration(importedTree.firstSplitPaths),
|
||||
secondPaths: makeEnumeration(importedTree.secondSplitPaths),
|
||||
ec: importedTree.ec,
|
||||
};
|
||||
},
|
||||
invalidMessage() {
|
||||
if (!this.inputIsValidTree || this.importedTree.invalidStudies.length === 0) return null;
|
||||
let coloredString = this.input;
|
||||
for (const id of this.importedTree.invalidStudies) {
|
||||
coloredString = coloredString.replaceAll(new RegExp(`(,)?(${id})(,)?`, "gu"),
|
||||
`$1<span style="color: var(--color-bad);">$2</span>$3`);
|
||||
}
|
||||
return `Your import string has invalid study IDs: ${coloredString}`;
|
||||
},
|
||||
editLabel() {
|
||||
return `Editing ${this.name}`;
|
||||
},
|
||||
@ -89,47 +52,11 @@ Vue.component("modal-edit-tree", {
|
||||
this.emitClose();
|
||||
}
|
||||
},
|
||||
formatPaths(paths) {
|
||||
return Array.from(paths).join(", ");
|
||||
},
|
||||
calculateMissingSTCost(studiesToBuy, ignoreCurrentStudies) {
|
||||
// Explicitly hardcoding how the study tree affects total ST should be fine here, as it massively simplifies
|
||||
// the code and the study tree structure is very unlikely to change. Note that all studies within the same
|
||||
// set also have identical ST costs. Triads also have identical costs too.
|
||||
const conflictingStudySets = [
|
||||
[121, 122, 123],
|
||||
[131, 132, 133],
|
||||
[141, 142, 143],
|
||||
[221, 222],
|
||||
[223, 224],
|
||||
[225, 226],
|
||||
[227, 228],
|
||||
[231, 232],
|
||||
[233, 234],
|
||||
];
|
||||
let totalSTSpent = 0;
|
||||
for (const studySet of conflictingStudySets) {
|
||||
const studiesInSet = studiesToBuy.filter(study => studySet.includes(study.id));
|
||||
if (studiesInSet.length > 1) {
|
||||
totalSTSpent += TimeStudy(studySet[0]).STCost * (studiesInSet.length - 1);
|
||||
if (!ignoreCurrentStudies) {
|
||||
const currStudies = player.timestudy.studies;
|
||||
const alreadyBought = studiesInSet.filter(study => currStudies.includes(study.id));
|
||||
totalSTSpent -= TimeStudy(studySet[0]).STCost * Math.clampMin(alreadyBought.length - 1, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Triad studies don't have .cost
|
||||
const triads = studiesToBuy.filter(study => !study.cost);
|
||||
if (ignoreCurrentStudies) {
|
||||
totalSTSpent += triads.length * TriadStudy(1).STCost;
|
||||
} else {
|
||||
totalSTSpent += TriadStudy(1).STCost * triads
|
||||
.filter(study => !player.celestials.v.triadStudies.includes(study))
|
||||
.length;
|
||||
}
|
||||
return totalSTSpent;
|
||||
},
|
||||
formatTheoremCost(tt, st) {
|
||||
const strTT = `${formatWithCommas(tt)} TT`;
|
||||
const strST = `${formatWithCommas(st)} ST`;
|
||||
return st === 0 ? strTT : `${strTT} + ${strST}`;
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="c-modal-import-tree l-modal-content--centered">
|
||||
@ -147,29 +74,27 @@ Vue.component("modal-edit-tree", {
|
||||
<div v-if="!inputIsValid">Invalid tree</div>
|
||||
<template v-if="inputIsValidTree">
|
||||
<div class="l-modal-import-tree__tree-info-line">
|
||||
Total tree cost:
|
||||
{{ quantify("Time Theorem", tree.totalCost, 0, 0, formatWithCommas) }}
|
||||
<span v-if="tree.totalST !== 0">
|
||||
and {{ quantify("Space Theorem", tree.totalST, 0, 0, formatWithCommas) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="l-modal-import-tree__tree-info-line">
|
||||
Cost of missing studies:
|
||||
{{ quantify("Time Theorem", tree.missingCost, 0, 0, formatWithCommas) }}
|
||||
<span v-if="tree.missingST !== 0">
|
||||
and {{ quantify("Space Theorem", tree.missingST, 0, 0, formatWithCommas) }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="tree.firstSplitPaths.size > 0" class="l-modal-import-tree__tree-info-line">
|
||||
{{ pluralize("First split path", tree.firstSplitPaths.size) }}:
|
||||
{{ formatPaths(tree.firstSplitPaths) }}
|
||||
</div>
|
||||
<div v-if="tree.secondSplitPaths.size > 0" class="l-modal-import-tree__tree-info-line">
|
||||
{{ pluralize("Second split path", tree.secondSplitPaths.size) }}:
|
||||
{{ formatPaths(tree.secondSplitPaths) }}
|
||||
<div v-if="importedTree.totalTT === 0">
|
||||
<i>Importing this into an empty tree will not purchase anything.</i>
|
||||
</div>
|
||||
<div v-if="tree.hasEternityChallenge" class="l-modal-import-tree__tree-info-line">
|
||||
Eternity challenge: {{ tree.eternityChallenge.id }}
|
||||
<div v-else>
|
||||
Importing into an empty tree will purchase:
|
||||
<br>
|
||||
{{ importedTree.newStudies }}
|
||||
(Cost: {{ formatTheoremCost(importedTree.totalTT, importedTree.totalST) }})
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<div v-if="invalidMessage" class="l-modal-import-tree__tree-info-line" v-html="invalidMessage" />
|
||||
<br>
|
||||
<div v-if="importedTree.firstPaths" class="l-modal-import-tree__tree-info-line">
|
||||
First split: {{ importedTree.firstPaths }}
|
||||
</div>
|
||||
<div v-if="importedTree.secondPaths" class="l-modal-import-tree__tree-info-line">
|
||||
Second split: {{ importedTree.secondPaths }}
|
||||
</div>
|
||||
<div v-if="importedTree.ec > 0" class="l-modal-import-tree__tree-info-line">
|
||||
Eternity challenge: {{ importedTree.ec }}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
@ -8,65 +8,48 @@ Vue.component("modal-import-tree", {
|
||||
this.$refs.input.select();
|
||||
},
|
||||
computed: {
|
||||
tree() {
|
||||
importedTree() {
|
||||
if (!this.inputIsValidTree) return false;
|
||||
const formattedInput = this.truncatedInput.split("|")[0].split(",");
|
||||
const eternityChallenge = TimeStudy.eternityChallenge(this.truncatedInput.split("|")[1]);
|
||||
const hasEternityChallenge = eternityChallenge !== undefined;
|
||||
const studies = new Set();
|
||||
for (const study of formattedInput) {
|
||||
studies.add(TimeStudy(study));
|
||||
}
|
||||
let totalCost = 0;
|
||||
let missingCost = 0;
|
||||
if (hasEternityChallenge) {
|
||||
totalCost += eternityChallenge.cost;
|
||||
if (player.challenge.eternity.unlocked !== eternityChallenge.id) {
|
||||
missingCost += eternityChallenge.cost;
|
||||
}
|
||||
}
|
||||
const firstSplitPaths = new Set();
|
||||
const secondSplitPaths = new Set();
|
||||
for (const study of studies) {
|
||||
if (study.cost) {
|
||||
totalCost += study.cost;
|
||||
if (!study.isBought) {
|
||||
missingCost += study.cost;
|
||||
}
|
||||
}
|
||||
switch (study.path) {
|
||||
case TIME_STUDY_PATH.ANTIMATTER_DIM:
|
||||
firstSplitPaths.add("Antimatter Dims");
|
||||
break;
|
||||
case TIME_STUDY_PATH.INFINITY_DIM:
|
||||
firstSplitPaths.add("Infinity Dims");
|
||||
break;
|
||||
case TIME_STUDY_PATH.TIME_DIM:
|
||||
firstSplitPaths.add("Time Dims");
|
||||
break;
|
||||
case TIME_STUDY_PATH.ACTIVE:
|
||||
secondSplitPaths.add("Active");
|
||||
break;
|
||||
case TIME_STUDY_PATH.PASSIVE:
|
||||
secondSplitPaths.add("Passive");
|
||||
break;
|
||||
case TIME_STUDY_PATH.IDLE:
|
||||
secondSplitPaths.add("Idle");
|
||||
}
|
||||
}
|
||||
const totalST = this.calculateMissingSTCost([...studies], true);
|
||||
const missingST = this.calculateMissingSTCost([...studies], false);
|
||||
const importedTree = new TimeStudyTree(this.truncatedInput, Currency.timeTheorems.value, V.spaceTheorems);
|
||||
return {
|
||||
totalST,
|
||||
missingST,
|
||||
totalCost,
|
||||
missingCost,
|
||||
firstSplitPaths,
|
||||
secondSplitPaths,
|
||||
eternityChallenge,
|
||||
hasEternityChallenge
|
||||
totalTT: importedTree.runningCost[0],
|
||||
totalST: importedTree.runningCost[1],
|
||||
newStudies: makeEnumeration(importedTree.purchasedStudies),
|
||||
invalidStudies: importedTree.invalidStudies,
|
||||
};
|
||||
},
|
||||
combinedTree() {
|
||||
if (!this.inputIsValidTree) return false;
|
||||
const currentStudies = player.timestudy.studies.map(s => `${s}`)
|
||||
.concat(player.celestials.v.triadStudies.map(s => `T${s}`));
|
||||
if (player.challenge.eternity.current !== 0) currentStudies.push(`EC${player.challenge.eternity.current}`);
|
||||
|
||||
// We know that we have enough for all existing studies because we actually purchased them, so setting initial
|
||||
// theorem values to e308 ensures we have enough to actually properly initialize a Tree object with all the
|
||||
// current studies. Then we set theorem totals to their proper values immediately AFTER everything is bought
|
||||
const currentStudyTree = new TimeStudyTree(currentStudies, Number.MAX_VALUE, Number.MAX_VALUE);
|
||||
currentStudyTree.remainingTheorems = [Currency.timeTheorems.value, V.spaceTheorems];
|
||||
const importedTree = new TimeStudyTree(this.truncatedInput, Currency.timeTheorems.value, V.spaceTheorems);
|
||||
const compositeTree = currentStudyTree.createCombinedTree(importedTree);
|
||||
return {
|
||||
missingTT: compositeTree.runningCost[0] - currentStudyTree.runningCost[0],
|
||||
missingST: compositeTree.runningCost[1] - currentStudyTree.runningCost[1],
|
||||
newStudies: makeEnumeration(compositeTree.purchasedStudies
|
||||
.filter(s => !currentStudyTree.purchasedStudies.includes(s))),
|
||||
firstPaths: makeEnumeration(compositeTree.firstSplitPaths),
|
||||
secondPaths: makeEnumeration(compositeTree.secondSplitPaths),
|
||||
ec: compositeTree.ec,
|
||||
};
|
||||
},
|
||||
invalidMessage() {
|
||||
if (!this.inputIsValidTree || this.importedTree.invalidStudies.length === 0) return null;
|
||||
let coloredString = this.truncatedInput;
|
||||
for (const id of this.importedTree.invalidStudies) {
|
||||
coloredString = coloredString.replaceAll(new RegExp(`(,)?(${id})(,)?`, "gu"),
|
||||
`$1<span style="color: var(--color-bad);">$2</span>$3`);
|
||||
}
|
||||
return `Your import string has invalid study IDs: ${coloredString}`;
|
||||
},
|
||||
truncatedInput() {
|
||||
// If last character is "," remove it
|
||||
return this.input.replace(/,$/u, "");
|
||||
@ -97,47 +80,11 @@ Vue.component("modal-import-tree", {
|
||||
formatCost(cost) {
|
||||
return formatWithCommas(cost);
|
||||
},
|
||||
formatPaths(paths) {
|
||||
return Array.from(paths).join(", ");
|
||||
},
|
||||
calculateMissingSTCost(studiesToBuy, ignoreCurrentStudies) {
|
||||
// Explicitly hardcoding how the study tree affects total ST should be fine here, as it massively simplifies
|
||||
// the code and the study tree structure is very unlikely to change. Note that all studies within the same
|
||||
// set also have identical ST costs. Triads also have identical costs too.
|
||||
const conflictingStudySets = [
|
||||
[121, 122, 123],
|
||||
[131, 132, 133],
|
||||
[141, 142, 143],
|
||||
[221, 222],
|
||||
[223, 224],
|
||||
[225, 226],
|
||||
[227, 228],
|
||||
[231, 232],
|
||||
[233, 234],
|
||||
];
|
||||
let totalSTSpent = 0;
|
||||
for (const studySet of conflictingStudySets) {
|
||||
const studiesInSet = studiesToBuy.filter(study => studySet.includes(study.id));
|
||||
if (studiesInSet.length > 1) {
|
||||
totalSTSpent += TimeStudy(studySet[0]).STCost * (studiesInSet.length - 1);
|
||||
if (!ignoreCurrentStudies) {
|
||||
const currStudies = player.timestudy.studies;
|
||||
const alreadyBought = studiesInSet.filter(study => currStudies.includes(study.id));
|
||||
totalSTSpent -= TimeStudy(studySet[0]).STCost * Math.clampMin(alreadyBought.length - 1, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Triad studies don't have .cost
|
||||
const triads = studiesToBuy.filter(study => !study.cost);
|
||||
if (ignoreCurrentStudies) {
|
||||
totalSTSpent += triads.length * TriadStudy(1).STCost;
|
||||
} else {
|
||||
totalSTSpent += TriadStudy(1).STCost * triads
|
||||
.filter(study => !player.celestials.v.triadStudies.includes(study))
|
||||
.length;
|
||||
}
|
||||
return totalSTSpent;
|
||||
},
|
||||
formatTheoremCost(tt, st) {
|
||||
const strTT = `${formatWithCommas(tt)} TT`;
|
||||
const strST = `${formatWithCommas(st)} ST`;
|
||||
return st === 0 ? strTT : `${strTT} + ${strST}`;
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="c-modal-import-tree l-modal-content--centered">
|
||||
@ -155,29 +102,38 @@ Vue.component("modal-import-tree", {
|
||||
<div v-if="inputIsSecret">???</div>
|
||||
<template v-else-if="inputIsValidTree">
|
||||
<div class="l-modal-import-tree__tree-info-line">
|
||||
Total tree cost:
|
||||
{{ quantify("Time Theorem", tree.totalCost, 0, 0, formatWithCommas) }}
|
||||
<span v-if="tree.totalST !== 0">
|
||||
and {{ quantify("Space Theorem", tree.totalST, 0, 0, formatWithCommas) }}
|
||||
</span>
|
||||
<div v-if="combinedTree.missingTT === 0">
|
||||
<i>Importing this with your current studies will not purchase anything.</i>
|
||||
</div>
|
||||
<div v-else>
|
||||
Importing with your current studies will purchase:
|
||||
<br>
|
||||
{{ combinedTree.newStudies }}
|
||||
(Cost: {{ formatTheoremCost(combinedTree.missingTT, combinedTree.missingST) }})
|
||||
</div>
|
||||
</div>
|
||||
<div class="l-modal-import-tree__tree-info-line">
|
||||
Cost of missing studies:
|
||||
{{ quantify("Time Theorem", tree.missingCost, 0, 0, formatWithCommas) }}
|
||||
<span v-if="tree.missingST !== 0">
|
||||
and {{ quantify("Space Theorem", tree.missingST, 0, 0, formatWithCommas) }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="tree.firstSplitPaths.size > 0" class="l-modal-import-tree__tree-info-line">
|
||||
{{ pluralize("First split path", tree.firstSplitPaths.size) }}:
|
||||
{{ formatPaths(tree.firstSplitPaths) }}
|
||||
</div>
|
||||
<div v-if="tree.secondSplitPaths.size > 0" class="l-modal-import-tree__tree-info-line">
|
||||
{{ pluralize("Second split path", tree.secondSplitPaths.size) }}:
|
||||
{{ formatPaths(tree.secondSplitPaths) }}
|
||||
<div v-if="importedTree.totalTT === 0">
|
||||
<i>Importing this into an empty tree will not purchase anything.</i>
|
||||
</div>
|
||||
<div v-if="tree.hasEternityChallenge" class="l-modal-import-tree__tree-info-line">
|
||||
Eternity challenge: {{ tree.eternityChallenge.id }}
|
||||
<div v-else>
|
||||
Importing into an empty tree will purchase:
|
||||
<br>
|
||||
{{ importedTree.newStudies }}
|
||||
(Cost: {{ formatTheoremCost(importedTree.totalTT, importedTree.totalST) }})
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<div v-if="invalidMessage" class="l-modal-import-tree__tree-info-line" v-html="invalidMessage" />
|
||||
<br>
|
||||
<div v-if="combinedTree.firstPaths" class="l-modal-import-tree__tree-info-line">
|
||||
First split: {{ combinedTree.firstPaths }}
|
||||
</div>
|
||||
<div v-if="combinedTree.secondPaths" class="l-modal-import-tree__tree-info-line">
|
||||
Second split: {{ combinedTree.secondPaths }}
|
||||
</div>
|
||||
<div v-if="combinedTree.ec > 0" class="l-modal-import-tree__tree-info-line">
|
||||
Eternity challenge: {{ combinedTree.ec }}
|
||||
</div>
|
||||
</template>
|
||||
<div v-else-if="hasInput">Not a valid tree</div>
|
||||
|
@ -5,7 +5,9 @@ GameDatabase.eternity.timeStudies.ec = [
|
||||
{
|
||||
id: 1,
|
||||
cost: 30,
|
||||
requirement: {
|
||||
requirement: [171],
|
||||
reqType: TS_REQUIREMENT_TYPE.AT_LEAST_ONE,
|
||||
secondary: {
|
||||
resource: "Eternities",
|
||||
current: () => Currency.eternities.value,
|
||||
required: completions => new Decimal(20000 + completions * 20000),
|
||||
@ -15,7 +17,9 @@ GameDatabase.eternity.timeStudies.ec = [
|
||||
{
|
||||
id: 2,
|
||||
cost: 35,
|
||||
requirement: {
|
||||
requirement: [171],
|
||||
reqType: TS_REQUIREMENT_TYPE.AT_LEAST_ONE,
|
||||
secondary: {
|
||||
resource: "Tickspeed upgrades from Time Dimensions",
|
||||
current: () => player.totalTickGained,
|
||||
required: completions => 1300 + completions * 150,
|
||||
@ -25,7 +29,9 @@ GameDatabase.eternity.timeStudies.ec = [
|
||||
{
|
||||
id: 3,
|
||||
cost: 40,
|
||||
requirement: {
|
||||
requirement: [171],
|
||||
reqType: TS_REQUIREMENT_TYPE.AT_LEAST_ONE,
|
||||
secondary: {
|
||||
resource: "8th Antimatter Dimensions",
|
||||
current: () => AntimatterDimension(8).totalAmount,
|
||||
required: completions => new Decimal(17300 + completions * 1250),
|
||||
@ -35,7 +41,9 @@ GameDatabase.eternity.timeStudies.ec = [
|
||||
{
|
||||
id: 4,
|
||||
cost: 70,
|
||||
requirement: {
|
||||
requirement: [143],
|
||||
reqType: TS_REQUIREMENT_TYPE.AT_LEAST_ONE,
|
||||
secondary: {
|
||||
resource: "Infinities",
|
||||
current: () => Currency.infinitiesTotal.value,
|
||||
required: completions => new Decimal(1e8 + completions * 5e7),
|
||||
@ -45,7 +53,9 @@ GameDatabase.eternity.timeStudies.ec = [
|
||||
{
|
||||
id: 5,
|
||||
cost: 130,
|
||||
requirement: {
|
||||
requirement: [42],
|
||||
reqType: TS_REQUIREMENT_TYPE.AT_LEAST_ONE,
|
||||
secondary: {
|
||||
resource: "Antimatter Galaxies",
|
||||
current: () => player.galaxies,
|
||||
required: completions => 160 + completions * 14,
|
||||
@ -55,7 +65,9 @@ GameDatabase.eternity.timeStudies.ec = [
|
||||
{
|
||||
id: 6,
|
||||
cost: 85,
|
||||
requirement: {
|
||||
requirement: [121],
|
||||
reqType: TS_REQUIREMENT_TYPE.AT_LEAST_ONE,
|
||||
secondary: {
|
||||
resource: "Replicanti Galaxies",
|
||||
current: () => player.replicanti.galaxies,
|
||||
required: completions => 40 + completions * 5,
|
||||
@ -65,7 +77,9 @@ GameDatabase.eternity.timeStudies.ec = [
|
||||
{
|
||||
id: 7,
|
||||
cost: 115,
|
||||
requirement: {
|
||||
requirement: [111],
|
||||
reqType: TS_REQUIREMENT_TYPE.AT_LEAST_ONE,
|
||||
secondary: {
|
||||
resource: "antimatter",
|
||||
current: () => Currency.antimatter.value,
|
||||
required: completions => DC.E300000.pow(completions).times(DC.E500000),
|
||||
@ -75,7 +89,9 @@ GameDatabase.eternity.timeStudies.ec = [
|
||||
{
|
||||
id: 8,
|
||||
cost: 115,
|
||||
requirement: {
|
||||
requirement: [123],
|
||||
reqType: TS_REQUIREMENT_TYPE.AT_LEAST_ONE,
|
||||
secondary: {
|
||||
resource: "Infinity Points",
|
||||
current: () => Currency.infinityPoints.value,
|
||||
required: completions => DC.E1000.pow(completions).times(DC.E4000),
|
||||
@ -85,7 +101,9 @@ GameDatabase.eternity.timeStudies.ec = [
|
||||
{
|
||||
id: 9,
|
||||
cost: 415,
|
||||
requirement: {
|
||||
requirement: [151],
|
||||
reqType: TS_REQUIREMENT_TYPE.AT_LEAST_ONE,
|
||||
secondary: {
|
||||
resource: "Infinity Power",
|
||||
current: () => Currency.infinityPower.value,
|
||||
required: completions => DC.E2000.pow(completions).times(DC.E17500),
|
||||
@ -95,7 +113,9 @@ GameDatabase.eternity.timeStudies.ec = [
|
||||
{
|
||||
id: 10,
|
||||
cost: 550,
|
||||
requirement: {
|
||||
requirement: [181],
|
||||
reqType: TS_REQUIREMENT_TYPE.AT_LEAST_ONE,
|
||||
secondary: {
|
||||
resource: "Eternity Points",
|
||||
current: () => Currency.eternityPoints.value,
|
||||
required: completions => DC.E20.pow(completions).times(DC.E100),
|
||||
@ -104,10 +124,20 @@ GameDatabase.eternity.timeStudies.ec = [
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
cost: 1
|
||||
cost: 1,
|
||||
requirement: [231, 232],
|
||||
reqType: TS_REQUIREMENT_TYPE.AT_LEAST_ONE,
|
||||
secondary: {
|
||||
path: "Antimatter Dimension",
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
cost: 1
|
||||
cost: 1,
|
||||
requirement: [233, 234],
|
||||
reqType: TS_REQUIREMENT_TYPE.AT_LEAST_ONE,
|
||||
secondary: {
|
||||
path: "Time Dimension",
|
||||
}
|
||||
}
|
||||
];
|
||||
|
@ -3,14 +3,14 @@ import { GameMechanicState } from "./game-mechanics/index.js";
|
||||
export const NormalTimeStudies = {};
|
||||
|
||||
NormalTimeStudies.pathList = [
|
||||
{ path: TIME_STUDY_PATH.ANTIMATTER_DIM, studies: [71, 81, 91, 101] },
|
||||
{ path: TIME_STUDY_PATH.INFINITY_DIM, studies: [72, 82, 92, 102] },
|
||||
{ path: TIME_STUDY_PATH.TIME_DIM, studies: [73, 83, 93, 103] },
|
||||
{ path: TIME_STUDY_PATH.ACTIVE, studies: [121, 131, 141] },
|
||||
{ path: TIME_STUDY_PATH.PASSIVE, studies: [122, 132, 142] },
|
||||
{ path: TIME_STUDY_PATH.IDLE, studies: [123, 133, 143] },
|
||||
{ path: TIME_STUDY_PATH.LIGHT, studies: [221, 223, 225, 227, 231, 233] },
|
||||
{ path: TIME_STUDY_PATH.DARK, studies: [222, 224, 226, 228, 232, 234] }
|
||||
{ path: TIME_STUDY_PATH.ANTIMATTER_DIM, studies: [71, 81, 91, 101], name: "Antimatter Dims" },
|
||||
{ path: TIME_STUDY_PATH.INFINITY_DIM, studies: [72, 82, 92, 102], name: "Infinity Dims" },
|
||||
{ path: TIME_STUDY_PATH.TIME_DIM, studies: [73, 83, 93, 103], name: "Time Dims" },
|
||||
{ path: TIME_STUDY_PATH.ACTIVE, studies: [121, 131, 141], name: "Active" },
|
||||
{ path: TIME_STUDY_PATH.PASSIVE, studies: [122, 132, 142], name: "Passive" },
|
||||
{ path: TIME_STUDY_PATH.IDLE, studies: [123, 133, 143], name: "Idle" },
|
||||
{ path: TIME_STUDY_PATH.LIGHT, studies: [221, 223, 225, 227, 231, 233], name: "Light" },
|
||||
{ path: TIME_STUDY_PATH.DARK, studies: [222, 224, 226, 228, 232, 234], name: "Dark" }
|
||||
];
|
||||
|
||||
NormalTimeStudies.paths = NormalTimeStudies.pathList.mapToObject(e => e.path, e => e.studies);
|
||||
@ -210,10 +210,6 @@ class TimeStudyState extends GameMechanicState {
|
||||
constructor(config, type) {
|
||||
super(config);
|
||||
this.type = type;
|
||||
/**
|
||||
* @type {TimeStudyConnection[]}
|
||||
*/
|
||||
this.incomingConnections = [];
|
||||
}
|
||||
|
||||
get cost() {
|
||||
@ -331,36 +327,45 @@ NormalTimeStudyState.all = NormalTimeStudyState.studies.filter(e => e !== undefi
|
||||
* in any TimeStudyState objects. During parsing, additional info is stored in order to improve user feedback when
|
||||
* attempting to import other study trees.
|
||||
*
|
||||
* @member {Number} usableTT Time theorems remaining after attempting import
|
||||
* @member {Number} usableST Space theorems remaining after attempting import
|
||||
* @member {Number} totalTT Total time theorem cost for importing all valid studies
|
||||
* @member {Number} totalST Total space theorem cost for importing all valid studies
|
||||
* @member {Array: Number} initialTheorems Two-element array containing starting totals of TT/ST before buying
|
||||
* @member {Array: Number} remainingTheorems Two-element array containing leftover TT/ST totals after buying
|
||||
* @member {Array: Number} runningCost Two-element array containing the total cost of buying all valid,
|
||||
* buyable studies whether or not they are actually affordable
|
||||
* @member {Array: String} plannedStudies Array of valid studies to be purchased from the initial import string;
|
||||
* all entries are Strings because both numbers (normal TS) and T# (triads) need to be supported
|
||||
* all entries are Strings because numbers (normal TS), T# (triads), and EC# (ECs) need to be supported
|
||||
* @member {Array: String} invalidStudies Array of studies from the initial string which are correctly formatted
|
||||
* but don't actually exist; used for informational purposes elsewhere
|
||||
* @member {Array: String} purchasedStudies Array of studies which were actually purchased, using the given amount
|
||||
* of available theorems. Will always be a subset of plannedStudies
|
||||
* @member {Array: String} errors Array of Strings indicating possible reasons the import string is invalid
|
||||
* @member {Number} ec Numerical ID of the EC which is unlocked from the imported tree, zero if none
|
||||
* @member {Number} ec Numerical ID of the EC which is unlocked from the imported tree. Equal to 0 if no
|
||||
* EC has been attempted, or the negative of the ID if purchasing was attempted but failed (needed for merging logic)
|
||||
*/
|
||||
export class TimeStudyTree {
|
||||
constructor(importString, usableTT, usableST) {
|
||||
this.usableTT = usableTT;
|
||||
this.usableST = usableST;
|
||||
this.totalTT = 0;
|
||||
this.totalST = 0;
|
||||
// The first parameter will either be an import string or an array of studies (possibly with an EC at the end)
|
||||
constructor(studies, initialTT, initialST) {
|
||||
// If we have above e308 TT there's no way buying studies will put a dent in our total anyway
|
||||
this.initialTheorems = [Decimal.min(initialTT, Number.MAX_VALUE).toNumber(), initialST];
|
||||
this.remainingTheorems = [...this.initialTheorems];
|
||||
this.runningCost = [0, 0];
|
||||
this.plannedStudies = [];
|
||||
this.invalidStudies = [];
|
||||
this.errors = [];
|
||||
if (TimeStudyTree.isValidImportString(importString)) {
|
||||
this.parseValidStudies(importString);
|
||||
this.parseEC(importString);
|
||||
this.attemptBuyAll();
|
||||
} else {
|
||||
this.plannedStudies = [];
|
||||
this.invalidStudies = [];
|
||||
this.ec = 0;
|
||||
this.purchasedStudies = [];
|
||||
this.ec = 0;
|
||||
|
||||
switch (typeof studies) {
|
||||
case "string":
|
||||
// Input parameter is an unparsed study import string
|
||||
if (TimeStudyTree.isValidImportString(studies)) {
|
||||
this.parseStudyImport(studies);
|
||||
this.attemptBuyAll();
|
||||
}
|
||||
break;
|
||||
case "object":
|
||||
// Input parameter is an array of strings assumed to be already formatted as plannedStudies would be after
|
||||
// import parsing.
|
||||
this.plannedStudies = [...studies];
|
||||
this.attemptBuyAll();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -371,9 +376,21 @@ export class TimeStudyTree {
|
||||
return /^(\d+|T\d+)(,(\d+|T\d+))*(\|\d+)?$/u.test(input);
|
||||
}
|
||||
|
||||
// Takes this tree and imports the other tree on top of its state, returning a new composite tree with the studies of
|
||||
// both trees together. Note the order of combination matters since one tree may fulfill requirements in the other.
|
||||
// The only information taken from the other tree is studies; TT/ST counts are taken from this tree.
|
||||
createCombinedTree(otherTree) {
|
||||
const thisStudyList = [...this.purchasedStudies];
|
||||
if (this.ec > 0) currentStudies.push(`EC${this.ec}`);
|
||||
const compositeTree = new TimeStudyTree(thisStudyList, this.initialTheorems[0], this.initialTheorems[1]);
|
||||
compositeTree.plannedStudies = compositeTree.plannedStudies.concat(otherTree.plannedStudies);
|
||||
compositeTree.attemptBuyAll();
|
||||
return compositeTree;
|
||||
}
|
||||
|
||||
// This reads off all the studies in the import string and splits them into invalid and valid study IDs. We hold on
|
||||
// to invalid studies for additional information to present to the player
|
||||
parseValidStudies(input) {
|
||||
parseStudyImport(input) {
|
||||
const treeStudies = input.split("|")[0].split(",");
|
||||
const normalIDs = GameDatabase.eternity.timeStudies.normal.map(s => s.id);
|
||||
const triadIDs = GameDatabase.celestials.v.triadStudies.map(s => s.id);
|
||||
@ -383,45 +400,44 @@ export class TimeStudyTree {
|
||||
: normalIDs.includes(parseInt(str, 10))
|
||||
);
|
||||
|
||||
let hasInvalidStudies = false;
|
||||
for (const study of treeStudies) {
|
||||
if (doesStudyExist(study)) this.plannedStudies.push(study);
|
||||
else {
|
||||
hasInvalidStudies = true;
|
||||
this.invalidStudies.push(study);
|
||||
}
|
||||
else this.invalidStudies.push(study);
|
||||
}
|
||||
if (hasInvalidStudies) this.errors.push("String has invalid study IDs.");
|
||||
}
|
||||
|
||||
// Parses out the EC from the import string, pushing an error message if it doesn't exist
|
||||
parseEC(input) {
|
||||
// If the string has an EC indicated in it, append that to the end of the study array
|
||||
const ecString = input.split("|")[1];
|
||||
if (!ecString) {
|
||||
// Study strings without an ending "|##" are still valid, but will result in ecString being undefined
|
||||
this.ec = 0;
|
||||
return;
|
||||
}
|
||||
const ecID = parseInt(ecString, 10);
|
||||
if (!GameDatabase.challenges.eternity.map(c => c.id).includes(ecID)) {
|
||||
this.ec = 0;
|
||||
this.errors.push(`Eternity Challenge ${ecID} does not exist.`);
|
||||
const ecDB = GameDatabase.eternity.timeStudies.ec;
|
||||
// Specifically exclude 0 because saved presets will contain it by default
|
||||
if (!ecDB.map(c => c.id).includes(ecID) && ecID !== 0) {
|
||||
this.invalidStudies.push(`${ecID}$`);
|
||||
return;
|
||||
}
|
||||
this.ec = ecID;
|
||||
this.plannedStudies.push(`EC${ecID}`);
|
||||
}
|
||||
|
||||
// Attempt to purchase all studies specified in the initial import string
|
||||
attemptBuyAll() {
|
||||
const normalDB = GameDatabase.eternity.timeStudies.normal;
|
||||
const ecDB = GameDatabase.eternity.timeStudies.ec;
|
||||
const triadDB = GameDatabase.celestials.v.triadStudies;
|
||||
const getStudyEntry = str => (
|
||||
/^T\d+$/u.test(str)
|
||||
? triadDB.find(s => s.id === parseInt(str.substr(1), 10))
|
||||
: normalDB.find(s => s.id === parseInt(str, 10))
|
||||
);
|
||||
const getStudyEntry = str => {
|
||||
const id = `${str}`.match(/(T|EC)?(\d+)/u);
|
||||
switch (id[1]) {
|
||||
case "T":
|
||||
return triadDB.find(s => s.id === parseInt(id[2].substr(1), 10));
|
||||
case "EC":
|
||||
return ecDB.find(s => s.id === parseInt(id[2].substr(1), 10));
|
||||
default:
|
||||
return normalDB.find(s => s.id === parseInt(id[2], 10));
|
||||
}
|
||||
};
|
||||
|
||||
this.purchasedStudies = [];
|
||||
for (const study of this.plannedStudies) {
|
||||
const toBuy = getStudyEntry(study);
|
||||
if (this.attemptBuySingle(toBuy)) this.purchasedStudies.push(study);
|
||||
@ -432,6 +448,9 @@ export class TimeStudyTree {
|
||||
// requirement is satisfied, then the running theorem costs will be updated (always) and the remaining usable
|
||||
// theorems will be decremented (only if there are enough left to actually purchase)
|
||||
attemptBuySingle(dbEntry) {
|
||||
// Import strings can contain repeated or undefined entries
|
||||
if (!dbEntry || this.purchasedStudies.includes(`${dbEntry.id}`)) return false;
|
||||
|
||||
const check = req => (typeof req === "number"
|
||||
? this.purchasedStudies.includes(`${req}`)
|
||||
: req());
|
||||
@ -454,11 +473,19 @@ export class TimeStudyTree {
|
||||
const stNeeded = dbEntry.STCost && dbEntry.requiresST.some(id => this.purchasedStudies.includes(id))
|
||||
? dbEntry.STCost
|
||||
: 0;
|
||||
this.totalTT += dbEntry.cost;
|
||||
this.totalST += stNeeded;
|
||||
if (dbEntry.cost > this.usableTT || stNeeded > this.usableST) return false;
|
||||
this.usableTT -= dbEntry.cost;
|
||||
this.usableST -= stNeeded;
|
||||
const canAfford = this.remainingTheorems[0] >= dbEntry.cost && this.remainingTheorems[1] >= stNeeded;
|
||||
|
||||
// We have to handle ECs slightly differently because you can only have one at once and merging trees may attempt
|
||||
// to buy another. To distinguish between successful and failed purchases, we give failed ones a negative sign
|
||||
if (dbEntry.secondary) {
|
||||
if (this.ec !== 0) return false;
|
||||
this.ec = canAfford ? dbEntry.id : -dbEntry.id;
|
||||
}
|
||||
this.runningCost[0] += dbEntry.cost;
|
||||
this.runningCost[1] += stNeeded;
|
||||
if (!canAfford) return false;
|
||||
this.remainingTheorems[0] -= dbEntry.cost;
|
||||
this.remainingTheorems[1] -= stNeeded;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -471,6 +498,36 @@ export class TimeStudyTree {
|
||||
if (this.purchasedStudies.includes("201")) return 2;
|
||||
return 1;
|
||||
}
|
||||
|
||||
get firstSplitPaths() {
|
||||
const pathSet = new Set();
|
||||
const validPaths = [TIME_STUDY_PATH.ANTIMATTER_DIM, TIME_STUDY_PATH.INFINITY_DIM, TIME_STUDY_PATH.TIME_DIM];
|
||||
for (const path of validPaths) {
|
||||
const pathEntry = NormalTimeStudies.pathList.find(p => p.path === path);
|
||||
for (const study of this.purchasedStudies) {
|
||||
if (pathEntry.studies.includes(parseInt(study, 10))) {
|
||||
pathSet.add(pathEntry.name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Array.from(pathSet);
|
||||
}
|
||||
|
||||
get secondSplitPaths() {
|
||||
const pathSet = new Set();
|
||||
const validPaths = [TIME_STUDY_PATH.ACTIVE, TIME_STUDY_PATH.PASSIVE, TIME_STUDY_PATH.IDLE];
|
||||
for (const path of validPaths) {
|
||||
const pathEntry = NormalTimeStudies.pathList.find(p => p.path === path);
|
||||
for (const study of this.purchasedStudies) {
|
||||
if (pathEntry.studies.includes(parseInt(study, 10))) {
|
||||
pathSet.add(pathEntry.name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Array.from(pathSet);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -577,9 +634,9 @@ export class ECTimeStudyState extends TimeStudyState {
|
||||
if (player.challenge.eternity.unlocked !== 0) {
|
||||
return false;
|
||||
}
|
||||
const isConnectionSatisfied = this.incomingConnections
|
||||
.some(connection => connection.isSatisfied);
|
||||
if (!isConnectionSatisfied) {
|
||||
// We'd have a switch case here if we wanted to generalize, but in our case it doesn't matter because all ECs have
|
||||
// the same study restriction of type TS_REQUIREMENT_TYPE.AT_LEAST_ONE - so we just assume that behavior instead
|
||||
if (!this.config.requirement.some(s => TimeStudy(s).isBought)) {
|
||||
return false;
|
||||
}
|
||||
if (player.etercreq === this.id && this.id !== 11 && this.id !== 12) {
|
||||
@ -599,11 +656,11 @@ export class ECTimeStudyState extends TimeStudyState {
|
||||
}
|
||||
|
||||
get requirementTotal() {
|
||||
return this.config.requirement.required(this.challenge.completions);
|
||||
return this.config.secondary.required(this.challenge.completions);
|
||||
}
|
||||
|
||||
get requirementCurrent() {
|
||||
const current = this.config.requirement.current();
|
||||
const current = this.config.secondary.current();
|
||||
if (this.cachedCurrentRequirement === undefined) {
|
||||
this.cachedCurrentRequirement = current;
|
||||
} else if (typeof current === "number") {
|
||||
@ -931,8 +988,5 @@ TimeStudy.allConnections = (function() {
|
||||
[TimeStudy.timeDimension(8), TimeStudy.reality]
|
||||
].map(props => new TimeStudyConnection(props[0], props[1], props[2]));
|
||||
|
||||
for (const connection of connections) {
|
||||
connection.to.incomingConnections.push(connection);
|
||||
}
|
||||
return connections;
|
||||
}());
|
||||
|
@ -5214,6 +5214,7 @@ screen and (max-width: 480px) {
|
||||
|
||||
.c-modal-import-tree {
|
||||
width: 48rem;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.l-modal-import-tree__tree-info-line {
|
||||
|
Loading…
Reference in New Issue
Block a user