359 lines
12 KiB
JavaScript
359 lines
12 KiB
JavaScript
Vue.component("rune", {
|
||
props: ["layer", "data"],
|
||
template: `<div class="upgAlign">
|
||
<button class="upg can rune" style="width: 60px; min-height: 60px;"
|
||
v-on:click="setRune(data)"
|
||
v-bind:style="{
|
||
background: (player.rituals.board[\`\$\{data[0]\}\$\{data[1]\}\`] ? 'url(images/' + player.rituals.board[\`\$\{data[0]\}\$\{data[1]\}\`] + 'Rune.webp) no-repeat center / contain ' : '') + (layers[player.rituals.board[\`\$\{data[0]\}\$\{data[1]\}\`]]?.color || ritualsColor)
|
||
}">
|
||
</button>
|
||
</div>`
|
||
});
|
||
|
||
Vue.component("ritual", {
|
||
props: ["layer", "data"],
|
||
template: `<div class="ritual">
|
||
<span>
|
||
<div v-for="(row, index) in data.pattern" v-key="index" style="text-align: center;">
|
||
<div v-for="(symbol, index) in row" v-key="index" class="upgAlign" style="display: inline-block;">
|
||
<span class="upg can rune"
|
||
style="display: inline-block; width: 60px; min-height: 60px; font-size: xx-large; text-align: center; line-height: 60px;"
|
||
v-bind:style="{ backgroundColor: symbol == null ? '#3a3e45' : ritualsColor }">{{ symbol == null ? '' : '#' + symbol }}</span>
|
||
</div>
|
||
</div>
|
||
</span>
|
||
<span><h3>{{ data.title }}</h3>{{ data.description }}</span>
|
||
</div>`
|
||
});
|
||
|
||
const rituals = {
|
||
xp: {
|
||
title: "Ritual of Doctrina",
|
||
description: "Each of these rituals exponentially increases the amount of ritual xp gained per second",
|
||
pattern: [ [ 0, 1 ], [ 1, 0 ] ],
|
||
effect: (amount, effectiveness) => Decimal.pow(4, amount).sub(1).times(effectiveness),
|
||
effectDisplay: () => format(ritualEffect("xp")) + " xp/s",
|
||
unlocked: () => true
|
||
},
|
||
gain: {
|
||
title: "Ritual of Emolumentum",
|
||
description: "Each of these rituals increases the amount of each job's primary resource is gained",
|
||
pattern: [ [ 0, 1, 1, 0 ] ],
|
||
effect: (amount, effectiveness) => new Decimal(amount).times(effectiveness).add(1),
|
||
effectDisplay: () => "x" + format(ritualEffect("gain")) + " all job's primary resources",
|
||
unlocked: () => hasMilestone("rituals", 0)
|
||
},
|
||
improvement: {
|
||
title: "Ritual of Melius",
|
||
description: "Each of these rituals increases the effectiveness of all other rituals over time, with diminishing returns",
|
||
pattern: [ [ 0, 1, 0 ], [ 1, 2, 1 ], [ 0, 1, 0 ] ],
|
||
effect: amount => new Decimal(amount).pow(2).times(.01),
|
||
effectDisplay: () => "+" + format(ritualEffect("improvement")) + " increased effectiveness of all other ritual effects/s",
|
||
unlocked: () => hasMilestone("rituals", 1)
|
||
},
|
||
globalXp: {
|
||
title: "Ritual of Colegium",
|
||
description: "Each of these rituals increases the amount of each job's xp gain",
|
||
pattern: [ [ 0, null, 0 ], [ null, null, null ], [ 0, null, 0 ] ],
|
||
effect: (amount, effectiveness) => new Decimal(amount).times(effectiveness).add(1),
|
||
effectDisplay: () => "x" + format(ritualEffect("globalXp")) + " all job's xp gain",
|
||
unlocked: () => hasMilestone("rituals", 3)
|
||
},
|
||
speed: {
|
||
title: "Ritual of Celeritas",
|
||
description: "Each of these rituals increases the flow of time itself",
|
||
pattern: [ [ 0, null, null, 0 ], [ null, 1, 1, null ], [ null, 1, 1, null ], [ 0, null, null, 0 ] ],
|
||
effect: (amount, effectiveness) => new Decimal(amount).times(effectiveness).add(1),
|
||
effectDisplay: () => "x" + format(ritualEffect("speed")) + " global speed multiplier",
|
||
unlocked: () => hasMilestone("rituals", 3)
|
||
}
|
||
};
|
||
|
||
function ritualEffect(id) {
|
||
let level = player.tab === "rituals" || player.rituals.timeLoopActive ? player.rituals.rituals[id] || 0 : 0;
|
||
let effect = rituals[id].effect(level, player.rituals.effectiveness.max(1).times(Decimal.pow(1.1, getJobLevel("rituals"))).log2().add(1));
|
||
if (player.generators.ritualsActive && (player.tab === "generators" || player.generators.timeLoopActive)) {
|
||
effect = effect.sqrt();
|
||
}
|
||
return effect;
|
||
}
|
||
|
||
// Note: id is the corresponding *buyable* ID
|
||
function createRuneSelector(id, rune) {
|
||
// TODO image based on rune
|
||
return {
|
||
color: layers[rune]?.color || ritualsColor,
|
||
class: {
|
||
rune: true
|
||
},
|
||
style: {
|
||
width: "60px",
|
||
minHeight: "60px",
|
||
background: rune ? 'url(images/' + rune + 'Rune.webp) no-repeat center / contain' : '',
|
||
backgroundColor: layers[rune]?.color || ritualsColor,
|
||
"--count": rune === null ? "" : () => (getBuyableAmount("rituals", id)?.toNumber() || 0) - Object.values(player.rituals.board).filter(r => r === rune).length
|
||
},
|
||
canClick: () => player.rituals.selectedRune !== rune,
|
||
onClick: () => player.rituals.selectedRune = rune
|
||
}
|
||
}
|
||
|
||
function createRuneBuyable(id, title) {
|
||
return {
|
||
title: title + "<br/>",
|
||
display() {
|
||
return `Craft another rune<br/><br/>Currently: ${formatWhole(getBuyableAmount("rituals", this.id))}<br/><br/>Cost: ${format(this.cost())} ${layers[id].resource}`;
|
||
},
|
||
runeType: id,
|
||
color: layers[id].color,
|
||
style: {
|
||
width: '160px',
|
||
height: '160px'
|
||
},
|
||
cost(x) {
|
||
const amount = x || getBuyableAmount("rituals", this.id);
|
||
return new Decimal(1e9).times(new Decimal(10).pow(amount));
|
||
},
|
||
canAfford() {
|
||
return player[id].points.gte(this.cost());
|
||
},
|
||
buy() {
|
||
player[id].points = player[id].points.sub(this.cost());
|
||
setBuyableAmount("rituals", this.id, getBuyableAmount("rituals", this.id).add(1));
|
||
},
|
||
unlocked: () => tmp[id].layerShown
|
||
};
|
||
}
|
||
|
||
function getRows() {
|
||
let rows = 3;
|
||
if (hasMilestone("rituals", 1)) {
|
||
rows++;
|
||
}
|
||
if (hasMilestone("rituals", 4)) {
|
||
rows++;
|
||
}
|
||
return rows;
|
||
}
|
||
|
||
function getCols() {
|
||
let cols = 3;
|
||
if (hasMilestone("rituals", 0)) {
|
||
cols++;
|
||
}
|
||
if (hasMilestone("rituals", 3)) {
|
||
cols++;
|
||
}
|
||
return cols;
|
||
}
|
||
|
||
function setRune([row, col]) {
|
||
if (player.rituals.selectedRune == null || (getBuyableAmount("rituals", Object.values(layers.rituals.buyables).find(b => b.runeType === player.rituals.selectedRune).id)?.toNumber() || 0) - Object.values(player.rituals.board).filter(r => r === player.rituals.selectedRune).length > 0) {
|
||
player.rituals.board[`${row}${col}`] = player.rituals.selectedRune;
|
||
player.rituals.rituals = getRituals();
|
||
}
|
||
}
|
||
|
||
function checkRitual(ritual, top, left) {
|
||
// Store a lookup table of what runes this pattern is using
|
||
const types = {};
|
||
|
||
for (let r = 0; r < ritual.pattern.length; r++) {
|
||
for (let c = 0; c < ritual.pattern[r].length; c++) {
|
||
let patternTile = ritual.pattern[r][c];
|
||
if (patternTile == null) {
|
||
continue;
|
||
}
|
||
let tile = player.rituals.board[`${top + r}${left + c}`];
|
||
if ((patternTile in types && types[patternTile] !== tile) ||
|
||
(!(patternTile in types) && Object.values(types).includes(tile)) ||
|
||
tile == null) {
|
||
return false;
|
||
}
|
||
types[patternTile] = tile;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
function getRituals() {
|
||
const rows = getRows();
|
||
const cols = getCols();
|
||
const ritualCounts = {};
|
||
Object.entries(rituals).forEach(([id, ritual]) => {
|
||
if (!ritual.unlocked()) {
|
||
return;
|
||
}
|
||
let ritualCount = 0;
|
||
for (let row = 0; row < rows && row <= rows - ritual.pattern.length; row++) {
|
||
for (let col = 0; col < cols && col <= cols - ritual.pattern[0].length; col++) {
|
||
// [row, col] is the top left of the ritual
|
||
// TODO allow negative numbers to represent "not this type"
|
||
if (checkRitual(ritual, row, col)) {
|
||
ritualCount++;
|
||
}
|
||
}
|
||
}
|
||
if (ritualCount > 0) {
|
||
ritualCounts[id] = ritualCount;
|
||
}
|
||
});
|
||
return ritualCounts;
|
||
}
|
||
|
||
addLayer("rituals", {
|
||
name: "rituals",
|
||
image: "images/bright-72804.jpg",
|
||
color: ritualsColor,
|
||
jobName: "Perform Rituals",
|
||
showJobDelay: 1.25,
|
||
layerShown: () => hasMilestone("sands", 5),
|
||
tooltip: "",
|
||
startData() {
|
||
return {
|
||
unlocked: true,
|
||
xp: new Decimal(0),
|
||
lastLevel: new Decimal(0),
|
||
timeLoopActive: false,
|
||
board: {},
|
||
selectedRune: null,
|
||
rituals: {},
|
||
effectiveness: new Decimal(1)
|
||
};
|
||
},
|
||
tabFormat: {
|
||
"Main": {
|
||
content: () => [
|
||
["sticky", [0, ["row", [["bar", "job"], ["display-text", `<span style="margin-left: 20px;">Lv. ${getJobLevel("rituals")}</span>`]]]]],
|
||
"blank",
|
||
["display-text", (() => {
|
||
if (!hasMilestone("rituals", 0)) {
|
||
return "Discover new ways to harness the arcane power at level 2";
|
||
}
|
||
if (!hasMilestone("rituals", 1)) {
|
||
return "Discover new ways to harness the arcane power at level 4";
|
||
}
|
||
if (!hasMilestone("rituals", 3)) {
|
||
return "Discover new ways to harness the arcane power at level 6";
|
||
}
|
||
if (!hasMilestone("rituals", 4)) {
|
||
return "Discover new ways to harness the arcane power at level 8";
|
||
}
|
||
if (!hasMilestone("rituals", 5)) {
|
||
return "Discover new ways to harness the arcane power at level 10";
|
||
}
|
||
return "";
|
||
})()],
|
||
"blank",
|
||
"buyables",
|
||
"blank",
|
||
["sticky", ["36px", ["clickables"]]],
|
||
"blank",
|
||
...new Array(getRows()).fill(0).map((_,row) => ["row", new Array(getCols()).fill(0).map((_,col) => ["rune", [row, col]])]),
|
||
"blank",
|
||
...Object.keys(rituals).filter(id => id in player.rituals.rituals && player.rituals.rituals[id] > 0).map(id => ["display-text", `${rituals[id].title} (${player.rituals.rituals[id]}): ${rituals[id].effectDisplay()}<br/>`]),
|
||
"blank",
|
||
["milestones-filtered", [2, 5, 6]]
|
||
],
|
||
shouldNotify: () => Object.values(tmp.rituals.buyables).some(buyable => buyable.unlocked && buyable.canAfford)
|
||
},
|
||
"Ritual Book": {
|
||
content: () => [
|
||
["sticky", [0, ["row", [["bar", "job"], ["display-text", `<span style="margin-left: 20px;">Lv. ${getJobLevel("rituals")}</span>`]]]]],
|
||
"blank",
|
||
["display-text", "Form rituals in the grid to gain powerful effects. You can have multiples of each ritual, and runes can be a part of multiple, overlapping rituals.<br/><br/>For any ritual, replace the tiles with any rune, but each tile with the same number must have the same rune.<br/>Blank tiles can have anything in them."],
|
||
"blank",
|
||
"blank",
|
||
...Object.values(rituals).filter(ritual => ritual.unlocked()).map(ritual => ["ritual", ritual])
|
||
]
|
||
}
|
||
},
|
||
update(diff) {
|
||
if (player.tab === this.layer || player[this.layer].timeLoopActive) {
|
||
if (player.generators.ritualsActive && (player.tab === "generators" || player.generators.timeLoopActive)) {
|
||
diff = diff / 10;
|
||
}
|
||
|
||
player.rituals.effectiveness = player.rituals.effectiveness.add(ritualEffect("improvement").times(diff));
|
||
|
||
let xpGain = ritualEffect("xp").times(diff);
|
||
xpGain = xpGain.times(ritualEffect("globalXp"));
|
||
player[this.layer].xp = player[this.layer].xp.add(xpGain);
|
||
checkJobXP(this.layer);
|
||
}
|
||
},
|
||
milestones: {
|
||
0: {
|
||
requirementDescription: "Level 2",
|
||
done: () => player.rituals.xp.gte(10)
|
||
},
|
||
1: {
|
||
requirementDescription: "Level 4",
|
||
done: () => player.rituals.xp.gte(1e3)
|
||
},
|
||
2: {
|
||
title: "You know the laws, Miss Granger.",
|
||
requirementDescription: "Level 5",
|
||
"effectDescription": "Unlock a new feature in distill job",
|
||
done: () => player.rituals.xp.gte(1e4)
|
||
},
|
||
3: {
|
||
requirementDescription: "Level 6",
|
||
done: () => player.rituals.xp.gte(1e5)
|
||
},
|
||
4: {
|
||
requirementDescription: "Level 8",
|
||
done: () => player.rituals.xp.gte(1e7)
|
||
},
|
||
5: {
|
||
title: "You must not be seen.",
|
||
requirementDescription: "Level 10",
|
||
"effectDescription": "Unlock the Ritual of Ascensio",
|
||
done: () => player.rituals.xp.gte(1e9),
|
||
unlocked: () => hasMilestone("rituals", 2)
|
||
},
|
||
6: {
|
||
title: "And you would do well, I feel, to return before this last chime.",
|
||
requirementDescription: "Level 25",
|
||
"effectDescription": "Unlock ???",
|
||
done: () => player.rituals.xp.gte(1e24) && player.chapter > 2,
|
||
unlocked: () => hasMilestone("rituals", 5) && player.chapter > 2
|
||
}
|
||
},
|
||
clickables: {
|
||
rows: 1,
|
||
cols: 7,
|
||
11: {
|
||
title: "Clear All",
|
||
style: {
|
||
color: "white",
|
||
minHeight: "60px"
|
||
},
|
||
canClick: () => Object.keys(player.rituals.board).length > 0,
|
||
onClick: () => {
|
||
player.rituals.board = {};
|
||
player.rituals.rituals = getRituals();
|
||
}
|
||
},
|
||
12: createRuneSelector(null, null),
|
||
13: createRuneSelector(11, "flowers"),
|
||
14: createRuneSelector(12, "study"),
|
||
15: createRuneSelector(13, "distill"),
|
||
16: createRuneSelector(14, "sands"),
|
||
17: createRuneSelector(15, "generators")
|
||
},
|
||
buyables: {
|
||
rows: 1,
|
||
cols: 5,
|
||
11: createRuneBuyable("flowers", "I did my waiting!"),
|
||
12: createRuneBuyable("study", "I solemnly swear that I am up to no good."),
|
||
13: createRuneBuyable("distill", "We enter a world that is entirely our own"),
|
||
14: createRuneBuyable("sands", "Finally the flesh reflects the madness within."),
|
||
15: createRuneBuyable("generators", "Mysterious thing, time")
|
||
},
|
||
bars: {
|
||
job: getJobProgressBar("rituals", ritualsColor)
|
||
}
|
||
});
|
||
|
||
// Names from https://en.wikiquote.org/wiki/Harry_Potter_and_the_Prisoner_of_Azkaban_(film)
|