function createCard(title, description = "", onDraw = null, modifyNextCard = null) {
return {title, description, onDraw, modifyNextCard};
}
const cardLevel = (card) => {
return (getBuyableAmount("study", card) || new Decimal(0)).add(player.study.deep);
};
const cards = {
nothing: createCard("His job is not to wield power but to draw attention away from it.", "Do nothing."),
gainPoints: createCard("Don't Panic.", level => `Successfully study ${format(getResetGain("study").times(level.add(1)))} properties.`, level => addPoints("study", getResetGain("study").times(level.add(1)))),
gainBigPoints: createCard("In his experience the Universe simply didn't work like that.", level => `Successfully study ${format(getResetGain("study").times(level.add(1)).pow(1.2))} properties. Destroy this card.`, (level, canDestroy = true) => {
addPoints("study", getResetGain("study").times(level.add(1)).pow(1.2));
if (canDestroy) {
player.study.cards.gainBigPoints = player.study.cards.gainBigPoints - 1;
}
}),
gainInsight: createCard("And it shall be called... the Earth.", level => level === 0 ? "Gain a key insight." : `Gain ${formatWhole(level.add(1))} key insights.`, level => {
player.study.insights = player.study.insights.add(level).add(1);
gainStudyXp(level.add(1).times(10));
checkJobXP("study");
}),
gainBigInsight: createCard("Yes! I shall design this computer for you.", level => `Gain ${new Decimal(getCardAmount()).times(level.add(1)).sqrt().floor()} key insights.
(based on number of cards in the deck)`, level => {
const amount = new Decimal(getCardAmount()).times(level.add(1)).sqrt().floor();
player.study.insights = player.study.insights.add(amount);
gainStudyXp(amount.times(10));
checkJobXP("study");
}),
playTwice: createCard("Oh no, not again.", level => level.eq(0) ? "Play the next card twice." : `Play the next card twice, with the effect boosted by ${level.div(4)} levels.`, null, (nextCard, level) => {
if (nextCard in cards && cards[nextCard].onDraw) {
cards[nextCard].onDraw(cardLevel(nextCard).add(level.div(4)));
cards[nextCard].onDraw(cardLevel(nextCard).add(level.div(4)), false);
}
}),
increasePointsGain: createCard("Have another drink, enjoy yourself.", level => `Permanently increase studied properties gain by ${formatWhole(new Decimal(25).times(Decimal.pow(1.25, level)))}%.
Currently: +${formatWhole(player.study.increasePointsGain.times(new Decimal(25).times(Decimal.pow(1.25, level))))}%`, () => player.study.increasePointsGain = player.study.increasePointsGain.add(1)),
multiplyPointsGain: createCard("Reality is frequently inaccurate.", level => {
const scale = new Decimal(1.02).add(level.add(1).sqrt().div(100));
const effect = scale.pow(softcap(player.study.multiplyPointsGain, new Decimal(100).times(level.div(4).add(1)), .2));
let text = `Permanently multiply studied properties gain by x${format(scale)}
Currently: x${format(effect)}`;
const leftTillSoftcap = new Decimal(100).times(level.div(4).add(1)).sub(player.study.multiplyPointsGain);
if (leftTillSoftcap.gt(0)) {
text = text + "
(softcapped in " + formatWhole(leftTillSoftcap) + " draws)";
} else {
text = text + "
(softcapped)";
}
return text;
}, () => player.study.multiplyPointsGain = player.study.multiplyPointsGain.add(1)),
sellDiscount: createCard("It doesn't grow on trees you know.", level => {
const effect = new Decimal(0.98).pow(softcap(player.study.sellDiscount, new Decimal(100).times(level.div(2).add(1)), .5));
let text = `Permanently multiply sell cost by 0.98
Currently: x${formatSmall(effect)}`;
const leftTillSoftcap = new Decimal(100).times(level.div(2).add(1)).sub(player.study.sellDiscount);
if (leftTillSoftcap.gt(0)) {
text = text + "
(softcapped in " + formatWhole(leftTillSoftcap) + " draws)";
} else {
text = text + "
(softcapped)";
}
return text;
}, () => player.study.sellDiscount = player.study.sellDiscount.add(1)),
soldOut: createCard("Out of Stock!"),
gainXp: createCard("A billion times over ... and no one learns anything.", level => `Gain xp equal to ${level.eq(0) ? "" : `${format(level.div(4).add(1))}x times `}your number of properties.`, level => {
gainStudyXp(player.study.points.times(level.div(4).add(1)));
checkJobXP("study");
}),
increaseXpGain: createCard("There's a whole new Guide!", level => `Permanently increase this job's xp gain by ${formatWhole(new Decimal(10).times(Decimal.pow(1.5, level)))}%.
Currently: +${formatWhole(player.study.increaseXpGain.times(new Decimal(10).times(Decimal.pow(1.5, level))))}%`, () => player.study.increaseXpGain = player.study.increaseXpGain.add(1))
};
const shopCards = [
{card: "gainPoints", price: 1},
{card: "gainInsight", price: 0},
{card: "gainBigPoints", price: 8},
{card: "gainBigInsight", price: 13},
{card: "playTwice", price: 16},
{card: "increasePointsGain", price: 6},
{card: "multiplyPointsGain", price: 18},
{card: "sellDiscount", price: 14},
{card: "gainXp", price: 25},
{card: "increaseXpGain", price: 12},
];
const baseCards = () => {
return { nothing: 4, gainPoints: 4, gainInsight: 2 };
};
const getShop = (numCards = 3) => {
return new Array(numCards).fill(1).map(() => shopCards[Math.floor(Math.random() * shopCards.length)]);
};
Vue.component("card", {
props: ["layer", "data"], // data is object with card, id, className, onclick, overrideLevel, width, height, and note
template: `
`,
computed: {
title() {
return this.data.card ? isFunction(cards[this.data.card].title) ? cards[this.data.card].title(overrideLevel || cardLevel(this.data.card)) : cards[this.data.card].title : "";
},
description() {
return this.data.card ? isFunction(cards[this.data.card].description) ? cards[this.data.card].description(this.data.overrideLevel || cardLevel(this.data.card)) : cards[this.data.card].description : "";
}
},
methods: {
onclick() {
if (this.data.onclick) {
this.data.onclick();
}
}
}
});
function getCardUpgradeBuyable(id) {
const cost = x => {
const amount = x || getBuyableAmount("study", id);
return new Decimal(10).pow(amount.add(2));
};
return {
title: "Upgrade
",
style: {
width: "100px",
height: "100px",
marginLeft: "10px"
},
display() {
return `Level ${formatWhole(cardLevel(id))}
${format(cost())} insights`;
},
canAfford() {
return player.study.insights.gte(cost());
},
buy() {
player.study.insights = player.study.insights.sub(cost());
setBuyableAmount("study", id, getBuyableAmount("study", id).add(1));
},
unlocked: () => id in player.study.cards && hasMilestone("study", 3)
};
}
function getCardAmount() {
return Object.values(player.study.cards).reduce((acc, curr) => acc + curr, 0);
}
// noinspection JSUnusedGlobalSymbols
function purchaseCard(index) {
const {card, price} = player.study.shop[index];
if (card && player.study.insights.gte(price) && getCardAmount() < 30) {
player.study.insights = player.study.insights.sub(price);
player.study.shop[index] = {card: null, price: ""};
player.study.cards[card] = (player.study.cards[card] || 0) + 1;
}
}
// noinspection JSUnusedGlobalSymbols
function toggleSelectCard(index) {
if (player.study.selected === index) {
player.study.selected = -1;
} else {
player.study.selected = index;
}
}
function getDrawDuration() {
let drawSpeed = new Decimal(10);
drawSpeed = drawSpeed.div(new Decimal(1.1).pow(getJobLevel("study")));
drawSpeed = drawSpeed.times(new Decimal(2).pow(player.study.deep));
if (player.generators.studyActive && (player.tab === "generators" || player.generators.timeLoopActive)) {
drawSpeed = drawSpeed.times(10);
}
return drawSpeed;
}
function getRefreshDraws() {
let refreshDraws = new Decimal(12);
return refreshDraws;
}
function gainStudyXp(xpGain) {
if (hasUpgrade("generators", 13)) {
xpGain = xpGain.times(layers.generators.clickables.study.effect());
}
xpGain = xpGain.times(player.study.increaseXpGain.times(new Decimal(.1).times(Decimal.pow(1.5, cardLevel("increaseXpGain")))).add(1));
xpGain = xpGain.times(ritualEffect("globalXp"));
player.study.xp = player.study.xp.add(xpGain);
checkJobXP("study");
}
function drawNextCard() {
player.study.drawProgress = 0;
let random = Math.random() * Object.values(player.study.cards).reduce((acc, curr) => acc + curr);
const ownedCards = Object.keys(player.study.cards);
let newCard = null;
for (let i = 0; i < ownedCards.length; i++) {
if (random < player.study.cards[ownedCards[i]]) {
newCard = ownedCards[i];
break;
}
random -= player.study.cards[ownedCards[i]];
}
if (newCard) {
if (player.study.lastCard && player.study.lastCard in cards && cards[player.study.lastCard].modifyNextCard) {
cards[player.study.lastCard].modifyNextCard(newCard, cardLevel(newCard));
} else if (cards[newCard].onDraw) {
cards[newCard].onDraw(cardLevel(newCard));
}
player.study.lastCard = newCard;
const card = document.getElementById("mainCard");
if (card != null) {
card.classList.remove("flipCard");
void card.offsetWidth;
card.classList.add("flipCard");
}
if (hasMilestone("study", 0)) {
player.study.refreshProgress++;
}
if (player.study.refreshProgress >= getRefreshDraws()) {
player.study.refreshProgress = 0;
player.study.shop = getShop();
for (let card of document.getElementsByClassName("shopCard")) {
if (card != null) {
card.classList.remove("flipCard");
void card.offsetWidth;
card.classList.add("flipCard");
}
}
if (player.study.notifs) {
doPopup("none", "Shop restocked", "Check it out!", 1, layers.study.color);
}
}
}
}
addLayer("study", {
name: "study",
resource: "properties studied",
image: "images/orchid_sketch.jpg",
color: studyColor,
jobName: "Study flowers",
showJobDelay: 0.25,
layerShown: () => player.chapter > 1 && hasMilestone("flowers", 4),
startData() {
return {
unlocked: true,
points: new Decimal(0),
insights: new Decimal(0),
total: new Decimal(0),
xp: new Decimal(0),
lastLevel: new Decimal(0),
timeLoopActive: false,
drawProgress: 0,
refreshProgress: 0,
cards: baseCards(),
lastCard: null,
shop: getShop(),
notifs: true,
increasePointsGain: new Decimal(0),
multiplyPointsGain: new Decimal(0),
increaseXpGain: new Decimal(0),
sellDiscount: new Decimal(0),
cardsSold: new Decimal(0),
selected: -1,
deep: new Decimal(0)
};
},
getResetGain() {
if (!tmp[this.layer].layerShown || (player.tab !== this.layer && !player[this.layer].timeLoopActive)) {
return new Decimal(0);
}
let gain = new Decimal(10);
gain = gain.times(player.study.increasePointsGain.times(new Decimal(.25).times(Decimal.pow(1.25, cardLevel("increasePointsGain")))).add(1));
gain = gain.times(new Decimal(1.02).pow(softcap(player.study.multiplyPointsGain, new Decimal(100).times(cardLevel("multiplyPointsGain").div(4).add(1)), .2)));
gain = gain.times(layers.generators.clickables[this.layer].effect());
gain = gain.times(ritualEffect("gain"));
if (player.generators.studyActive && (player.tab === "generators" || player.generators.timeLoopActive)) {
gain = gain.sqrt();
}
return gain;
},
tabFormat: {
"Main": {
content: () => {
const drawDuration = getDrawDuration();
return [
["sticky", [0, ["row", [["bar", "job"], ["display-text", `Lv. ${getJobLevel("study")}`]]]]],
"blank",
["sticky", ["36px", ["display-text", `You have ${formatWhole(player.study.points)}
properties studied,
and ${formatWhole(player.study.insights)}
key insights`]]],
"blank",
["display-text", (() => {
if (!hasMilestone("study", 0)) {
return "Discover new ways to harness the power of the cards at level 2";
}
if (!hasMilestone("study", 1)) {
return "Discover new ways to harness the power of the cards at level 4";
}
if (!hasMilestone("study", 3)) {
return "Discover new ways to harness the power of the cards at level 6";
}
if (!hasMilestone("study", 4)) {
return "Discover new ways to harness the power of the cards at level 8";
}
return "";
})()],
"blank",
["display-text", `Next card will auto-draw in ${new Decimal(drawDuration - player.study.drawProgress).clampMax(drawDuration - 0.01).toFixed(2)} seconds
`],
["display-text", drawDuration / 10 > player.study.drawProgress ? `You can manually draw next card in ${new Decimal(drawDuration / 10 - player.study.drawProgress).clampMax(drawDuration / 10 - 0.01).toFixed(2)} seconds` : `You can manually draw the next card NOW`],
"blank",
["card", { card: player.study.lastCard, id: "mainCard", className: "flipCard" }],
"blank",
["clickable", "manual"],
"blank",
["milestones-filtered", [2, 5, 6]]
];
}
},
"Deck": {
content: () => [
["sticky", [0, ["row", [["bar", "job"], ["display-text", `Lv. ${getJobLevel("study")}`]]]]],
"blank",
["clickable", "reset"],
"blank",
["row", Object.entries(player.study.cards).map(([card, amount]) => ["card", { card, note: amount }])]
]
},
"Buy Cards": {
content: () => [
["sticky", [0, ["row", [["bar", "job"], ["display-text", `Lv. ${getJobLevel("study")}`]]]]],
"blank",
["sticky", ["36px", ["display-text", `You have ${formatWhole(player.study.insights)}
key insights`]]],
"blank",
["row", [
["display-text", "Shop Refresh Notifications"],
"blank",
["toggle", ["study", "notifs"]]
]],
"blank",
["display-text", `Cards refresh in ${getRefreshDraws() - player.study.refreshProgress} draws`],
"blank",
["display-text", `Your deck has ${getCardAmount()} out of the 30 card limit.`],
"blank",
["row", player.study.shop.map(({card, price}, i) =>
["column", [
card == null ? ["card", { card: "soldOut" }] : ["card", { card, className: "shopCard flipCard", onclick: () => purchaseCard(i) }],
["display-text", `${card in player.study.cards ? player.study.cards[card] : card == null ? '' : 'New!'}
`],
"blank",
["display-text", `${card == null ? "" /*zero width space*/ : formatWhole(price)}
`]
], {
margin: "auto 10px 20px",
cursor: "pointer",
opacity: card != null && player.study.insights.gte(price) ? 1 : 0.5
}]), {width: "100%"}]
],
unlocked: () => hasMilestone("study", 0)
},
"Destroy Cards": {
content: () => [
["sticky", [0, ["row", [["bar", "job"], ["display-text", `Lv. ${getJobLevel("study")}`]]]]],
"blank",
["sticky", ["36px", ["display-text", `You have ${formatWhole(player.study.points)}
properties studied`]]],
"blank",
["sticky", ["86px", ["clickable", "sell"]]],
"blank",
["row", Object.entries(player.study.cards).map(([card, amount]) => ["card", { card, note: amount, className: player.study.selected === card ? "selectedCard cursor" : "cursor", onclick: () => toggleSelectCard(card) }]), {width: "100%"}]
],
unlocked: () => hasMilestone("study", 1)
},
"Upgrade Cards": {
content: () => [
["sticky", [0, ["row", [["bar", "job"], ["display-text", `Lv. ${getJobLevel("study")}`]]]]],
"blank",
["sticky", ["36px", ["display-text", `You have ${formatWhole(player.study.insights)}
key insights`]]],
"blank",
hasMilestone("study", 4) ? ["column", [
["display-text", `Deep Thought is currently giving ${formatWhole(player.study.deep)} bonus levels to every card,
but makes each draw take ${formatWhole(new Decimal(2).pow(player.study.deep))}x longer due to processing time.
You cannot add more bonus levels than your level at this job.`],
"blank",
["row", [
["clickable", "deep0"],
"blank",
["clickable", "deep-"],
"blank",
["clickable", "deep+"],
"blank",
["clickable", "deepMax"]
]],
"blank"
]] : null,
["column", Object.keys(player.study.cards).filter(card => card in layers.study.buyables).map(card => ["row", [
["card", { card }],
["display-text", "〉〉", {fontSize: "36px", margin: "10px"}],
["card", { card, overrideLevel: cardLevel(card).add(1) }],
["buyable", card]
]])]
],
unlocked: () => hasMilestone("study", 3),
shouldNotify: () => Object.values(tmp.study.buyables).some(buyable => buyable.unlocked && buyable.canAfford)
}
},
update(diff) {
if (player.tab === this.layer || player[this.layer].timeLoopActive) {
if (player.generators.studyActive && (player.tab === "generators" || player.generators.timeLoopActive)) {
diff = diff / 10;
}
player[this.layer].drawProgress += diff;
// TODO draws/sec
if (player[this.layer].drawProgress > getDrawDuration()) {
drawNextCard();
}
}
},
onAddPoints(gain) {
gainStudyXp(gain);
},
milestones: {
0: {
requirementDescription: "Level 2",
done: () => player.study.xp.gte(10)
},
1: {
requirementDescription: "Level 4",
done: () => player.study.xp.gte(1e3)
},
2: {
title: "And all dared to brave unknown terrors, to do mighty deeds,",
requirementDescription: "Level 5",
"effectDescription": "Unlock distill flowers job",
done: () => player.study.xp.gte(1e4)
},
3: {
requirementDescription: "Level 6",
done: () => player.study.xp.gte(1e5)
},
4: {
requirementDescription: "Level 8",
done: () => player.study.xp.gte(1e7)
},
5: {
title: "to boldly split infinitives that no man had split before—",
requirementDescription: "Level 10",
"effectDescription": "Unlock experiments job",
done: () => player.study.xp.gte(1e9),
unlocked: () => hasMilestone("study", 2)
},
6: {
title: "and thus was the Empire forged.",
requirementDescription: "Level 25",
"effectDescription": "Unlock ???",
done: () => player.study.xp.gte(1e24) && player.chapter > 2,
unlocked: () => hasMilestone("study", 5) && player.chapter > 2
}
},
clickables: {
manual: {
title: "Think before you pluck. Irresponsible plucking costs lives.",
display: "Draw next card",
canClick: () => getDrawDuration() / 10 < player.study.drawProgress,
onClick: drawNextCard
},
reset: {
title: "RESET DECK",
style: {
background: "#dc3545"
},
display: "Reset your deck to the basic starter deck. Resets destroyed cards and played cards counts. Does not reset the rest of this job.",
canClick: true,
onClick: () => {
if (confirm("Are you sure you want to reset your deck to the starter deck?")) {
player.study.cards = baseCards();
player.study.cardsSold = new Decimal(0);
player.study.lastCard = null;
player.study.increasePointsGain = new Decimal(0);
player.study.multiplyPointsGain = new Decimal(0);
player.study.increaseXpGain = new Decimal(0);
player.study.sellDiscount = new Decimal(0);
}
}
},
sell: {
title: "They obstinately persisted in their absence.
",
style: {
width: "200px",
height: "200px"
},
display() {
return `Remove a card from your deck. Cost multiplies by 10 for each card manually destroyed.
Cost: ${formatWhole(this.cost())} properties studied`;
},
cost(x) {
let cost = new Decimal(500).times(new Decimal(10).pow(player[this.layer].cardsSold));
cost = cost.times(new Decimal(0.98).pow(softcap(player.study.sellDiscount, new Decimal(100).times(cardLevel("multiplyPointsGain").div(4).add(1)), .5)));
return cost;
},
canClick() {
if (!(player.study.selected in player.study.cards)) {
return false;
}
if (player.study.cards[player.study.selected] <= 0) {
return false;
}
return player[this.layer].points.gte(this.cost());
},
onClick() {
player[this.layer].points = player[this.layer].points.sub(this.cost());
player[this.layer].cardsSold = player[this.layer].cardsSold.add(1);
player[this.layer].cards[player[this.layer].selected] = (player[this.layer].cards[player[this.layer].selected] || 0) - 1;
if (player[this.layer].cards[player[this.layer].selected] <= 0) {
delete player[this.layer].cards[player[this.layer].selected];
player[this.layer].selected = -1;
}
},
unlocked: () => hasMilestone("study", 1),
layer: "study"
},
"deep0": {
title: "0",
style: {
width: "60px",
minHeight: "60px"
},
canClick: () => player.study.deep.neq(0),
onClick: () => {
player.study.deep = new Decimal(0);
}
},
"deep-": {
title: "- 1",
style: {
width: "60px",
minHeight: "60px"
},
canClick: () => player.study.deep.gt(0),
onClick: () => {
player.study.deep = player.study.deep.sub(1);
}
},
"deep+": {
title: "+ 1",
style: {
width: "60px",
minHeight: "60px"
},
canClick: () => player.study.deep.lt(getJobLevel("study")),
onClick: () => {
player.study.deep = player.study.deep.add(1);
}
},
"deepMax": {
title: () => formatWhole(getJobLevel("study")),
style: {
width: "60px",
minHeight: "60px"
},
canClick: () => player.study.deep.neq(getJobLevel("study")),
onClick: () => {
player.study.deep = getJobLevel("study");
}
}
},
buyables: {
gainPoints: getCardUpgradeBuyable("gainPoints"),
gainBigPoints: getCardUpgradeBuyable("gainBigPoints"),
gainInsight: getCardUpgradeBuyable("gainInsight"),
gainBigInsight: getCardUpgradeBuyable("gainBigInsight"),
playTwice: getCardUpgradeBuyable("playTwice"),
increasePointsGain: getCardUpgradeBuyable("increasePointsGain"),
multiplyPointsGain: getCardUpgradeBuyable("multiplyPointsGain"),
sellDiscount: getCardUpgradeBuyable("sellDiscount"),
gainXp: getCardUpgradeBuyable("gainXp"),
increaseXpGain: getCardUpgradeBuyable("increaseXpGain"),
},
bars: {
job: getJobProgressBar("study", studyColor)
}
});
// Names references:
// https://www.shmoop.com/study-guides/literature/hitchhikers-guide-to-the-galaxy/quotes
// https://en.wikiquote.org/wiki/The_Hitchhiker's_Guide_to_the_Galaxy