forked from profectus/Profectus
622 lines
18 KiB
JavaScript
622 lines
18 KiB
JavaScript
import { isFunction, isPlainObject } from '../util/common';
|
|
import { createProxy, createGridProxy, player } from './proxies';
|
|
import Decimal from '../util/bignum';
|
|
import store from './index';
|
|
import { resetLayer, noCache, getStartingBuyables, getStartingClickables, getStartingChallenges } from '../util/layers';
|
|
|
|
export const layers = {};
|
|
export const hotkeys = [];
|
|
window.layers = layers;
|
|
|
|
export function addLayer(layer) {
|
|
// Check for required properties
|
|
if (!('id' in layer)) {
|
|
console.error(`Cannot add layer without a "id" property!`, layer);
|
|
return;
|
|
}
|
|
if (layer.type === "static" || layer.type === "normal") {
|
|
const missingProperty = [ 'baseAmount', 'requires' ].find(prop => !(prop in layer));
|
|
if (missingProperty) {
|
|
console.error(`Cannot add layer without a "${missingProperty}" property!`, layer);
|
|
return;
|
|
}
|
|
}
|
|
if (layer.type === "static" && (layer.base == undefined || Decimal.lte(layer.base, 1))) {
|
|
layer.base = 2;
|
|
}
|
|
|
|
// Set default property values
|
|
layer = Object.assign({}, defaultLayerProperties, layer);
|
|
layer.layer = layer.id;
|
|
if (layer.shown == undefined) {
|
|
layer.shown = true;
|
|
}
|
|
if (layer.onClick != undefined) {
|
|
layer.onClick.forceCached = false;
|
|
}
|
|
if (layer.update != undefined) {
|
|
layer.update.forceCached = false;
|
|
}
|
|
|
|
const getters = {};
|
|
|
|
// Process each feature
|
|
for (let property of gridProperties) {
|
|
if (layer[property]) {
|
|
setRowCol(layer[property]);
|
|
}
|
|
}
|
|
for (let property of featureProperties) {
|
|
if (layer[property]) {
|
|
setupFeature(layer.id, layer[property]);
|
|
}
|
|
}
|
|
if (layer.upgrades) {
|
|
if (player[layer.id].upgrades == undefined) {
|
|
player[layer.id].upgrades = [];
|
|
}
|
|
for (let id in layer.upgrades) {
|
|
if (isPlainObject(layer.upgrades[id])) {
|
|
layer.upgrades[id].bought = function() {
|
|
return !layer.deactivated && player[layer.id].upgrades.some(upgrade => upgrade == id);
|
|
}
|
|
if (layer.upgrades[id].canAfford == undefined) {
|
|
layer.upgrades[id].canAfford = function() {
|
|
if (this.currencyInternalName) {
|
|
let name = this.currencyInternalName;
|
|
if (this.currencyLocation) {
|
|
return !(this.currencyLocation[name].lt(this.cost));
|
|
} else if (this.currencyLayer) {
|
|
let lr = this.currencyLayer;
|
|
return !(player[lr][name].lt(this.cost));
|
|
} else {
|
|
return !(player[name].lt(this.cost));
|
|
}
|
|
} else {
|
|
return !(player[this.layer].points.lt(this.cost))
|
|
}
|
|
}
|
|
}
|
|
if (layer.upgrades[id].pay == undefined) {
|
|
layer.upgrades[id].pay = noCache(function() {
|
|
if (this.bought || !this.canAfford) {
|
|
return;
|
|
}
|
|
if (this.currencyInternalName) {
|
|
let name = this.currencyInternalName
|
|
if (this.currencyLocation) {
|
|
if (this.currencyLocation[name].lt(this.cost)) {
|
|
return;
|
|
}
|
|
this.currencyLocation[name] = this.currencyLocation[name].sub(this.cost);
|
|
} else if (this.currencyLayer) {
|
|
let lr = this.currencyLayer;
|
|
if (player[lr][name].lt(this.cost)) {
|
|
return;
|
|
}
|
|
player[lr][name] = player[lr][name].sub(this.cost);
|
|
} else {
|
|
if (player[name].lt(this.cost)) {
|
|
return;
|
|
}
|
|
player[name] = player[name].sub(this.cost);
|
|
}
|
|
} else {
|
|
if (player[this.layer].points.lt(this.cost)) {
|
|
return;
|
|
}
|
|
player[this.layer].points = player[this.layer].points.sub(this.cost);
|
|
}
|
|
});
|
|
} else {
|
|
layer.upgrades[id].pay.forceCached = false;
|
|
}
|
|
if (layer.upgrades[id].buy == undefined) {
|
|
layer.upgrades[id].buy = noCache(function() {
|
|
if (this.bought || !this.canAfford) {
|
|
return;
|
|
}
|
|
this.pay();
|
|
player[this.layer].upgrades.push(this.id);
|
|
this.onPurchase?.();
|
|
});
|
|
} else {
|
|
layer.upgrades[id].buy.forceCached = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (layer.achievements) {
|
|
if (player[layer.id].achievements == undefined) {
|
|
player[layer.id].achievements = [];
|
|
}
|
|
for (let id in layer.achievements) {
|
|
if (isPlainObject(layer.achievements[id])) {
|
|
layer.achievements[id].earned = function() {
|
|
return !layer.deactivated && player[layer.id].achievements.some(achievement => achievement == id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (layer.challenges) {
|
|
if (player[layer.id].challenges == undefined) {
|
|
player[layer.id].challenges = getStartingChallenges(layer);
|
|
}
|
|
for (let id in layer.challenges) {
|
|
if (isPlainObject(layer.challenges[id])) {
|
|
layer.challenges[id].completed = function() {
|
|
return !layer.deactivated && !!player[layer.id].challenges[id];
|
|
}
|
|
layer.challenges[id].completions = function() {
|
|
return player[layer.id].challenges[id];
|
|
}
|
|
layer.challenges[id].maxed = function() {
|
|
return !layer.deactivated && Decimal.gte(player[layer.id].challenges[id], this.completionLimit);
|
|
}
|
|
if (layer.challenges[id].mark == undefined) {
|
|
layer.challenges[id].mark = function() {
|
|
return this.maxed;
|
|
}
|
|
}
|
|
layer.challenges[id].active = function() {
|
|
return !layer.deactivated && player[layer.id].activeChallenge === id;
|
|
}
|
|
if (layer.challenges[id].canComplete == undefined) {
|
|
layer.challenges[id].canComplete = function() {
|
|
if (this.active) {
|
|
return false;
|
|
}
|
|
|
|
if (this.currencyInternalName) {
|
|
let name = this.currencyInternalName;
|
|
if (this.currencyLocation) {
|
|
return !(this.currencyLocation[name].lt(this.goal));
|
|
} else if (this.currencyLayer) {
|
|
let lr = this.currencyLayer;
|
|
return !(player[lr][name].lt(this.goal));
|
|
} else {
|
|
return !(player[name].lt(this.goal));
|
|
}
|
|
} else {
|
|
return !(player.points.lt(this.goal));
|
|
}
|
|
}
|
|
}
|
|
if (layer.challenges[id].completionLimit == undefined) {
|
|
layer.challenges[id].completionLimit = new Decimal(1);
|
|
}
|
|
layer.challenges[id].toggle = function() {
|
|
let exiting = player[layer.id].activeChallenge === id;
|
|
if (exiting) {
|
|
if (this.canComplete && !this.maxed) {
|
|
let completions = this.canComplete;
|
|
if (completions === true) {
|
|
completions = 1;
|
|
}
|
|
player[layer.id].challenges[id] =
|
|
Decimal.min(player[layer.id].challenges[id].add(completions), this.completionLimit);
|
|
this.onComplete?.();
|
|
}
|
|
player[layer.id].activeChallenge = null;
|
|
this.onExit?.();
|
|
resetLayer(layer.id, true);
|
|
} else if (!exiting && this.canStart) {
|
|
resetLayer(layer.id, true);
|
|
player[layer.id].activeChallenge = id;
|
|
this.onEnter?.();
|
|
}
|
|
}
|
|
if (layer.challenges[id].canStart == undefined) {
|
|
layer.challenges[id].canStart = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (layer.buyables) {
|
|
if (player[layer.id].buyables == undefined) {
|
|
player[layer.id].buyables = getStartingBuyables(layer);
|
|
}
|
|
if (layer.buyables.reset == undefined) {
|
|
layer.buyables.reset = noCache(function() {
|
|
player[this.layer].buyables = getStartingBuyables(layer);
|
|
});
|
|
}
|
|
for (let id in layer.buyables) {
|
|
if (isPlainObject(layer.buyables[id])) {
|
|
layer.buyables[id].amount = function() {
|
|
return player[layer.id].buyables[id];
|
|
}
|
|
layer.buyables[id].amountSet = function(amount) {
|
|
player[layer.id].buyables[id] = amount;
|
|
}
|
|
layer.buyables[id].canBuy = function() {
|
|
return !layer.deactivated && this.unlocked !== false && this.canAfford !== false &&
|
|
Decimal.lt(player[layer.id].buyables[id], this.purchaseLimit);
|
|
}
|
|
if (layer.buyables[id].purchaseLimit == undefined) {
|
|
layer.buyables[id].purchaseLimit = new Decimal(Infinity);
|
|
}
|
|
if (layer.buyables[id].cost != undefined && layer.buyables[id].buy == undefined) {
|
|
layer.buyables[id].buy = noCache(function() {
|
|
player[this.layer].points = player[this.layer].points.sub(this.cost());
|
|
this.amount = this.amount.add(1);
|
|
});
|
|
} else {
|
|
layer.buyables[id].buy.forceCached = false;
|
|
}
|
|
if (layer.buyables[id].sellOne != undefined) {
|
|
layer.buyables[id].sellOne.forceCached = false;
|
|
}
|
|
if (layer.buyables[id].sellAll != undefined) {
|
|
layer.buyables[id].sellAll.forceCached = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (layer.clickables) {
|
|
if (player[layer.id].clickables == undefined) {
|
|
player[layer.id].clickables = getStartingClickables(layer);
|
|
}
|
|
for (let id in layer.clickables) {
|
|
if (isPlainObject(layer.clickables[id])) {
|
|
layer.clickables[id].state = function() {
|
|
return player[layer.id].clickables[id];
|
|
}
|
|
layer.clickables[id].stateSet = function(state) {
|
|
player[layer.id].clickables[id] = state;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (layer.milestones) {
|
|
if (player[layer.id].milestones == undefined) {
|
|
player[layer.id].milestones = [];
|
|
}
|
|
for (let id in layer.milestones) {
|
|
if (isPlainObject(layer.milestones[id])) {
|
|
layer.milestones[id].shown = function() {
|
|
if (!this.unlocked) {
|
|
return false;
|
|
}
|
|
|
|
switch (player.msDisplay) {
|
|
default:
|
|
case "all":
|
|
return true;
|
|
case "last":
|
|
return this.optionsDisplay || !this.earned ||
|
|
player[this.layer].milestones[player[this.layer].milestones.length - 1] === this.id;
|
|
case "configurable":
|
|
return this.optionsDisplay || !this.earned;
|
|
case "incomplete":
|
|
return !this.earned;
|
|
case "none":
|
|
return false;
|
|
}
|
|
}
|
|
layer.milestones[id].earned = function() {
|
|
return !layer.deactivated && player[layer.id].milestones.some(milestone => milestone == id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (layer.grids) {
|
|
if (player[layer.id].grids == undefined) {
|
|
player[layer.id].grids = {};
|
|
}
|
|
for (let id in layer.grids) {
|
|
if (isPlainObject(layer.grids[id])) {
|
|
if (player[layer.id].grids[id] == undefined) {
|
|
player[layer.id].grids[id] = {};
|
|
}
|
|
if (layer.grids[id].getUnlocked == undefined) {
|
|
layer.grids[id].getUnlocked = true;
|
|
}
|
|
if (layer.grids[id].getCanClick == undefined) {
|
|
layer.grids[id].getCanClick = true;
|
|
}
|
|
if (layer.grids[id].getStartData == undefined) {
|
|
layer.grids[id].getStartData = "";
|
|
}
|
|
layer.grids[id].data = function(cell) {
|
|
if (player[layer.id].grids[id][cell] != undefined) {
|
|
return player[layer.id].grids[id][cell];
|
|
}
|
|
if (isFunction(this.getStartData)) {
|
|
return this.getStartData(cell);
|
|
}
|
|
return this.getStartData;
|
|
}
|
|
layer.grids[id].dataSet = function(cell, data) {
|
|
player[layer.id].grids[id][cell] = data;
|
|
}
|
|
layer.grids[id] = createGridProxy(layer.grids[id], getters, `${layer.id}/grids-${id}-`);
|
|
}
|
|
}
|
|
}
|
|
if (layer.subtabs) {
|
|
for (let id in layer.subtabs) {
|
|
if (isPlainObject(layer.subtabs[id])) {
|
|
layer.subtabs[id].active = function() {
|
|
return player.subtabs[this.layer]?.mainTabs === this.id;
|
|
}
|
|
}
|
|
}
|
|
layer.activeSubtab = function() {
|
|
if (this.subtabs != undefined) {
|
|
if (this.subtabs[player.subtabs[layer.id]?.mainTabs] &&
|
|
this.subtabs[player.subtabs[layer.id].mainTabs].unlocked !== false) {
|
|
return this.subtabs[player.subtabs[layer.id].mainTabs];
|
|
}
|
|
// Default to first unlocked tab
|
|
return Object.values(this.subtabs).find(subtab => subtab.unlocked !== false);
|
|
}
|
|
}
|
|
}
|
|
if (layer.microtabs) {
|
|
for (let family in layer.microtabs) {
|
|
for (let id in layer.microtabs[family]) {
|
|
if (isPlainObject(layer.microtabs[family][id])) {
|
|
layer.microtabs[family][id].layer = layer.id;
|
|
layer.microtabs[family][id].family = family;
|
|
layer.microtabs[family][id].id = id;
|
|
layer.microtabs[family][id].active = function() {
|
|
return player.subtabs[this.layer]?.[this.family] === this.id;
|
|
}
|
|
}
|
|
}
|
|
layer.microtabs[family].activeMicrotab = function() {
|
|
if (this[player.subtabs[layer.id][family]]?.unlocked !== false) {
|
|
return this[player.subtabs[layer.id][family]];
|
|
}
|
|
// Default to first unlocked tab
|
|
return Object.values(this).find(microtab => microtab.unlocked !== false);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create layer proxy
|
|
layer = createProxy(layer, getters, `${layer.id}/`);
|
|
|
|
// Register layer
|
|
layers[layer.id] = layer;
|
|
store.registerModule(`layer-${layer.id}`, { getters });
|
|
|
|
// Register hotkeys
|
|
if (layer.hotkeys) {
|
|
for (let id in layer.hotkeys) {
|
|
hotkeys[id] = layer.hotkeys[id];
|
|
}
|
|
}
|
|
}
|
|
|
|
export function removeLayer(layer) {
|
|
// Un-set hotkeys
|
|
if (layers[layer].hotkeys) {
|
|
for (let id in layers[layer].hotkeys) {
|
|
delete hotkeys[id];
|
|
}
|
|
}
|
|
|
|
// Un-register layer
|
|
store.unregisterModule(`layer-${layer}`);
|
|
}
|
|
|
|
export function reloadLayer(layer) {
|
|
removeLayer(layer.id);
|
|
|
|
// Re-create layer
|
|
addLayer(layer);
|
|
}
|
|
|
|
export const defaultLayerProperties = {
|
|
type: "none",
|
|
layerShown: true,
|
|
glowColor: "red",
|
|
minWidth: 640,
|
|
displayRow() {
|
|
return this.row;
|
|
},
|
|
symbol() {
|
|
return this.id;
|
|
},
|
|
unlocked() {
|
|
if (player[this.id].unlocked) {
|
|
return true;
|
|
}
|
|
if (this.type !== "none" && this.canReset && this.layerShown) {
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
trueGlowColor() {
|
|
if (this.subtabs) {
|
|
for (let subtab of Object.values(this.subtabs)) {
|
|
if (subtab.notify) {
|
|
return subtab.glowColor || "red";
|
|
}
|
|
}
|
|
}
|
|
if (this.microtabs) {
|
|
for (let microtab of Object.values(this.microtabs)) {
|
|
if (microtab.notify) {
|
|
return microtab.glowColor || "red";
|
|
}
|
|
}
|
|
}
|
|
return this.glowColor || "red";
|
|
},
|
|
resetGain() {
|
|
if (this.type === "none" || this.type === "custom") {
|
|
return new Decimal(0);
|
|
}
|
|
if (this.gainExp?.eq(0)) {
|
|
return new Decimal(0);
|
|
}
|
|
if (this.baseAmount.lt(this.requires)) {
|
|
return new Decimal(0);
|
|
}
|
|
if (this.type === "static") {
|
|
if (!this.canBuyMax) {
|
|
return new Decimal(1);
|
|
}
|
|
let gain = this.baseAmount.div(this.requires).div(this.gainMult || 1).max(1).log(this.base)
|
|
.times(this.gainExp || 1).pow(Decimal.pow(this.exponent || 1, -1));
|
|
gain = gain.times(this.directMult || 1);
|
|
return gain.floor().sub(player[this.layer].points).add(1).max(1);
|
|
}
|
|
if (this.type === "normal") {
|
|
let gain = this.baseAmount.div(this.requires).pow(this.exponent || 1).times(this.gainMult || 1)
|
|
.pow(this.gainExp || 1);
|
|
if (this.softcap && gain.gte(this.softcap)) {
|
|
gain = gain.pow(this.softcapPower).times(this.softcap.pow(Decimal.sub(1, this.softcapPower)));
|
|
}
|
|
gain = gain.times(this.directMult || 1);
|
|
return gain.floor().max(0);
|
|
}
|
|
// Unknown prestige type
|
|
return new Decimal(0);
|
|
},
|
|
nextAt() {
|
|
if (this.type === "none" || this.type === "custom") {
|
|
return new Decimal(Infinity);
|
|
}
|
|
if (this.gainMult?.lte(0) || this.gainExp?.lte(0)) {
|
|
return new Decimal(Infinity);
|
|
}
|
|
if (this.type === "static") {
|
|
const amount = player[this.layer].points.div(this.directMult || 1);
|
|
const extraCost = Decimal.pow(this.base, amount.pow(this.exponent || 1).div(this.gainExp || 1))
|
|
.times(this.gainMult || 1);
|
|
let cost = extraCost.times(this.requires).max(this.requires);
|
|
if (this.roundUpCost) {
|
|
cost = cost.ceil();
|
|
}
|
|
return cost;
|
|
}
|
|
if (this.type === "normal") {
|
|
let next = this.resetGain.div(this.directMult || 1);
|
|
if (this.softcap && next.gte(this.softcap)) {
|
|
next = next.div(this.softcap.pow(Decimal.sub(1, this.softcapPower)))
|
|
.pow(Decimal.div(1, this.softcapPower));
|
|
}
|
|
next = next.root(this.gainExp || 1).div(this.gainMult || 1).root(this.exponent || 1)
|
|
.times(this.requires).max(this.requires);
|
|
if (this.roundUpCost) {
|
|
next = next.ceil();
|
|
}
|
|
return next;
|
|
}
|
|
// Unknown prestige type
|
|
return new Decimal(0);
|
|
},
|
|
nextAtMax() {
|
|
if (!this.canBuyMax || this.type !== "static") {
|
|
return this.nextAt;
|
|
}
|
|
const amount = player[this.layer].points.plus(this.resetGain).div(this.directMult || 1);
|
|
const extraCost = Decimal.pow(this.base, amount.pow(this.exponent || 1).div(this.gainExp || 1))
|
|
.times(this.gainMult || 1);
|
|
let cost = extraCost.times(this.requires).max(this.requires);
|
|
if (this.roundUpCost) {
|
|
cost = cost.ceil();
|
|
}
|
|
return cost;
|
|
},
|
|
canReset() {
|
|
if (this.type === "normal") {
|
|
return this.baseAmount.gte(this.requires);
|
|
}
|
|
if (this.type === "static") {
|
|
return this.baseAmount.gte(this.nextAt);
|
|
}
|
|
return false;
|
|
},
|
|
notify() {
|
|
if (this.upgrades) {
|
|
if (Object.values(this.upgrades).some(upgrade => upgrade.canAfford && !upgrade.bought && upgrade.unlocked)) {
|
|
return true;
|
|
}
|
|
}
|
|
if (player[this.layer].activeChallenge && this.challenges[player[this.layer].activeChallenge].canComplete) {
|
|
return true;
|
|
}
|
|
if (this.subtabs) {
|
|
if (Object.values(this.subtabs).some(subtab => subtab.notify)) {
|
|
return true;
|
|
}
|
|
}
|
|
if (this.microtabs) {
|
|
if (Object.values(this.microtabs).some(subtab => subtab.notify)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
},
|
|
resetNotify() {
|
|
if (this.subtabs) {
|
|
if (Object.values(this.subtabs).some(subtab => subtab.prestigeNotify)) {
|
|
return true;
|
|
}
|
|
}
|
|
if (this.microtabs) {
|
|
if (Object.values(this.microtabs).some(microtab => microtab.prestigeNotify)) {
|
|
return true;
|
|
}
|
|
}
|
|
if (this.autoPrestige || this.passiveGeneration) {
|
|
return false;
|
|
}
|
|
if (this.type === "static") {
|
|
return this.canReset;
|
|
}
|
|
if (this.type === "normal") {
|
|
return this.canReset && this.resetGain.gte(player[this.layer].points.div(10));
|
|
}
|
|
return false;
|
|
},
|
|
reset(force = false) {
|
|
console.warn("Not yet implemented!", force);
|
|
},
|
|
resetData(keep = []) {
|
|
console.warn("Not yet implemented!", keep);
|
|
}
|
|
};
|
|
const gridProperties = [ 'upgrades', 'achievements', 'challenges', 'buyables', 'clickables' ];
|
|
const featureProperties = [ 'upgrades', 'achievements', 'challenges', 'buyables', 'clickables', 'milestones', 'bars',
|
|
'infoboxes', 'grids', 'hotkeys', 'subtabs' ];
|
|
|
|
function setRowCol(features) {
|
|
if (features.rows && features.cols) {
|
|
return
|
|
}
|
|
let maxRow = 0;
|
|
let maxCol = 0;
|
|
for (let id in features) {
|
|
if (!isNaN(id)) {
|
|
if (Math.floor(id / 10) > maxRow) {
|
|
maxRow = Math.floor(id / 10);
|
|
}
|
|
if (id % 10 > maxCol) {
|
|
maxCol = id % 10;
|
|
}
|
|
}
|
|
}
|
|
features.rows = maxRow;
|
|
features.cols = maxCol;
|
|
}
|
|
|
|
function setupFeature(layer, features) {
|
|
features.layer = layer;
|
|
for (let id in features) {
|
|
const feature = features[id];
|
|
if (isPlainObject(feature)) {
|
|
feature.id = id;
|
|
feature.layer = layer;
|
|
if (feature.unlocked == undefined) {
|
|
feature.unlocked = true;
|
|
}
|
|
}
|
|
}
|
|
}
|