pages/the_ascension_tree/js/classes/layer.js

510 lines
26 KiB
JavaScript
Raw Normal View History

class Layer {
constructor(seed, id=0, parent_layer=undefined, is_ngminus=false) {
this.parent_layer = parent_layer;
this.is_ngminus = is_ngminus;
this.id = id;
this.points = new Decimal(0);
this.upgrades = {};
this.child_left = undefined;
this.child_right = undefined;
this.boost = new Decimal(1);
this.points_name = "";
if (parent_layer !== undefined) {
if (this.is_ngminus) parent_layer.child_left = this;
else parent_layer.child_right = this;
if (this.is_ngminus) {
this.upgrade_time = parent_layer.upgrade_time.mul(2);
this.final_goal = parent_layer.final_goal.pow(1.2);
}
else {
this.upgrade_time = parent_layer.upgrade_time.mul(1.5);
this.final_goal = parent_layer.final_goal.pow(3);
}
this.name = parent_layer.name;
if (this.name == "Original") this.name = "NG";
if (this.is_ngminus) this.name += "-";
else this.name += "+";
this.depth = parent_layer.depth + 1;
this.coord = 2 * parent_layer.coord;
if (!this.is_ngminus) this.coord += 1;
this.coord = this.coord % (2 ** 32);
if (this.is_ngminus) this.color = mixColors(parent_layer.color, [72, 159, 181]);
else this.color = mixColors(parent_layer.color, [214, 40, 40]);
}
else {
this.upgrade_time = new Decimal(10);
this.final_goal = new Decimal(1e10);
this.name = "Original";
this.depth = 0;
this.coord = 0;
this.color = [19, 138, 54];
}
this.left_branch = false;
this.right_branch = false;
this.rng = sfc32(this.depth, this.coord, seed, 0xDEADBEEF);
for (let i = 0; i < 15; i++) this.rng();
if (parent_layer != undefined) this.points_name = choose(ITY_WORDS, this.rng);
this.el = document.createElement("div");
this.el.className = "tree-node-container";
if (parent_layer === undefined) {
this.el.style.top = "0px";
this.el.style.left = "0px";
this.el.style.transform = "translate(-50%, -50%)";
document.getElementById("tree").appendChild(this.el);
} else {
this.el.style.top = "15em";
this.el.style.left = (this.is_ngminus ? "-" : "") + "10em";
this.el.style.transform = "";
parent_layer.el.appendChild(this.el);
}
this.nodeEl = document.createElement("div");
this.nodeEl.onclick = () => this.selectLayer();
this.nodeEl.className = "tree-node";
this.nodeEl.style.backgroundColor = formAsRGB(this.color);
this.el.appendChild(this.nodeEl);
this.label = document.createElement("p");
this.label.className = "node-text";
this.label.innerText = parent_layer === undefined ? "OG" : this.points_name.slice(0, 3);
this.nodeEl.appendChild(this.label);
this.unlockReq = document.createElement("p");
this.unlockReq.className = "unlock-req";
this.unlockReq.innerText = `Get ${formatNumber(this.final_goal)} ${this.points_name ? this.points_name + " points" : "points"} to unlock`;
this.el.appendChild(this.unlockReq);
this.generateUpgrades();
this.balanceUpgrades();
this.screenUpdate();
}
generateUpgrade() {
let type_probs = {
"add": Object.keys(this.upgrades).length == 0 ? (this.depth == 0 ? 0 : 1) : 1 / Object.keys(this.upgrades).length,
"mul": 0.5,
"pow": Math.pow(Object.keys(this.upgrades).length, 2) / 100,
"mul_log": Math.pow(Object.keys(this.upgrades).length, 0.5) / 10,
"mul_pow": 0//Math.pow(Object.keys(this.upgrades).length, 1) / 50
}
let type = chooseDict(type_probs, this.rng);
let target_probs = {
"points": 10
}
if (type != "mul_log" && type != "mul_pow") {
for (let key of Object.keys(this.upgrades)) {
if (this.upgrades[key].type == type) continue;
if (this.upgrades[key].type == "mul_log" || this.upgrades[key].type == "mul_pow" || this.upgrades[key].target != "points") continue;
if (type == "mul" && this.upgrades[key].type != "pow") target_probs[key] = 1;
if (type == "pow" && this.upgrades[key].type != "add") target_probs[key] = 2;
}
}
let target = chooseDict(target_probs, this.rng);
let upgrade = new Upgrade(this, this.depth + "_" + Object.keys(this.upgrades).length, type, 0, target, 0, this.rng);
this.upgrades[upgrade.id] = upgrade;
}
generateUpgrades() {
if (this.is_ngminus) for (let i = 0; i < 4; i++) this.generateUpgrade();
if (this.parent_layer == undefined) for (let i = 0; i < 8; i++) this.generateUpgrade();
else {
for (let key of Object.keys(this.parent_layer.upgrades)) {
this.upgrades[key] = new Upgrade(this, key, 0, 0, "points", 0);
this.upgrades[key].copyUpgrade(this.parent_layer.upgrades[key]);
}
if (!this.is_ngminus) for (let i = 0; i < 4; i++) this.generateUpgrade();
}
}
balanceUpgrades() {
let upgrades_left = Object.keys(this.upgrades).length;
let last_target = new Decimal(1);
let inflation_precaution = 1;
for (let key of Object.keys(this.upgrades)) {
let separation_pow = upgrades_left;
if (this.upgrades[key].type == "add") separation_pow = Math.pow(separation_pow, 1.9 + 0.2 * this.rng());
if (this.upgrades[key].type == "mul") separation_pow = Math.pow(separation_pow, 1.15 + 0.2 * this.rng());
if (this.upgrades[key].type == "pow") separation_pow = Math.pow(separation_pow, 0.65 + 0.2 * this.rng());
if (this.upgrades[key].type == "mul_log") separation_pow = Math.pow(separation_pow, 0.9 + 0.2 * this.rng());
if (this.upgrades[key].type == "mul_pow") separation_pow = Math.pow(separation_pow, 0.6 + 0.2 * this.rng());
let base_production = this.calculateProduction(this.depth == 0 ? 1 : 0.1 / this.depth, last_target);
let base_last_target = last_target.max(base_production.mul(this.upgrade_time)).min(this.final_goal.div(last_target).pow(1 / Math.pow(separation_pow, 0.25)).mul(last_target));
let base_target = properPrecision(this.final_goal.div(base_last_target).pow(1 / separation_pow).mul(base_last_target).round().max(last_target.add(1)).min(this.final_goal), 1);
let target_production = new Decimal(base_target);
target_production = target_production.div(this.upgrade_time).max(base_production);
this.upgrades[key].cost = new Decimal(last_target);
//console.log(key + ", base: " + formatNumber(base_production));
//console.log(key + ", target: " + formatNumber(target_production));
if (this.upgrades[key].target != "points") {
let root_upgrade = this.upgrades[key].target;
while (this.upgrades[root_upgrade].target != "points") root_upgrade = this.upgrades[root_upgrade].target;
base_production = new Decimal(this.depth == 0 ? 1 : 0.1 / this.depth);
for (let key2 of Object.keys(this.upgrades)) {
if (this.upgrades[key2].target != "points") continue;
base_production = this.upgrades[key2].applyEffect(base_production, last_target);
if (key2 == root_upgrade) break;
}
for (let key2 of Object.keys(this.upgrades).reverse()) {
if (key2 == root_upgrade) break;
if (this.upgrades[key2].target != "points") continue;
target_production = this.upgrades[key2].applyReverseEffect(target_production, last_target);
}
if (this.upgrades[root_upgrade].type == "add") {
//console.log(key + " unraveling, base: " + formatNumber(base_production));
//console.log(key + " unraveling, target: " + formatNumber(target_production));
let other_prod = base_production.sub(this.upgrades[root_upgrade].applyEffect(1));
//console.log("other_prod: " + formatNumber(other_prod));
base_production = this.upgrades[root_upgrade].applyEffect(1);
target_production = target_production.sub(other_prod);
}
if (this.upgrades[root_upgrade].type == "mul") {
//console.log(key + " unraveling, base: " + formatNumber(base_production));
//console.log(key + " unraveling, target: " + formatNumber(target_production));
let other_prod = base_production.div(this.upgrades[root_upgrade].applyEffect(1));
//console.log("other_prod: " + formatNumber(other_prod));
base_production = this.upgrades[root_upgrade].applyEffect(1);
target_production = target_production.div(other_prod);
}
if (this.upgrades[root_upgrade].type == "pow") {
//console.log(key + " unraveling, base: " + formatNumber(base_production));
//console.log(key + " unraveling, target: " + formatNumber(target_production));
let other_prod = base_production.root(this.upgrades[root_upgrade].applyEffect(1)).max(2);
//console.log("other_prod: " + formatNumber(other_prod));
base_production = this.upgrades[root_upgrade].applyEffect(1);
target_production = target_production.log(other_prod);
}
//console.log(key + ", result base: " + formatNumber(base_production));
//console.log(key + ", result target: " + formatNumber(target_production));
}
//if (this.upgrades[key].type == "add") console.log("base result: " + formatNumber(new Decimal(target_production.sub(base_production))));
//if (this.upgrades[key].type == "mul") console.log("base result: " + formatNumber(new Decimal(target_production.div(base_production))));
//if (this.upgrades[key].type == "pow") console.log("base result: " + formatNumber(new Decimal(target_production.log(base_production.max(2)))));
//if (this.upgrades[key].type == "mul_log") console.log("base result: " + formatNumber(new Decimal(target_production.div(base_production).log(last_target.max(1).log10().max(2)))));
this.upgrades[key].bought = true;
if (this.upgrades[key].type == "add") this.upgrades[key].effect = properPrecision(new Decimal(target_production.sub(base_production).max(this.upgrades[key].target == "points" ? 1 : 0.001)), 0);
if (this.upgrades[key].type == "mul") this.upgrades[key].effect = properPrecision(new Decimal(target_production.div(base_production).max(1.1)), 1);
if (this.upgrades[key].type == "pow") this.upgrades[key].effect = properPrecision(new Decimal(target_production.log(base_production.max(2)).max(1.001)), 3);
if (this.upgrades[key].type == "mul_log") this.upgrades[key].effect = new Decimal(target_production.div(base_production).log(last_target.max(1).log10().max(2)).max(0.1));
if (this.upgrades[key].type == "mul_pow") {
this.upgrades[key].effect = new Decimal(target_production.div(base_production).log(last_target.max(2)).max(0.001).min(inflation_precaution * 0.3));
inflation_precaution -= this.upgrades[key].effect.toNumber();
}
last_target = new Decimal(base_target);
upgrades_left -= 1;
}
for (let key of Object.keys(this.upgrades)) this.upgrades[key].bought = false;
}
calculateProduction(base=1, total=this.points, ignore_add=false) {
let production = new Decimal(base);
for (let key of Object.keys(this.upgrades)) {
if (this.upgrades[key].target == "points" && (!ignore_add || this.upgrades[key].type != "add")) production = this.upgrades[key].applyEffect(production, total);
}
production = production.mul(this.boost);
return production;
}
calculateReverseProduction(base=1, total=this.points, ignore_add=false) {
let production = new Decimal(base);
production = production.div(this.boost);
for (let key of Object.keys(this.upgrades).reverse()) {
if (this.upgrades[key].target == "points" && (!ignore_add || this.upgrades[key].type != "add")) production = this.upgrades[key].applyReverseEffect(production, total);
}
return production;
}
getBoostValue() {
let boost = this.points.add(1).log(this.final_goal).pow(0.5).mul(3).add(1);
// Softcaps
if (boost.gt(10)) boost = boost.div(10).log10().add(1).mul(10);
if (boost.gt(1000)) boost = boost.div(1000).log10().add(1).mul(1000);
if (boost.gt(1e10)) boost = boost.div(1e10).log10().add(1).mul(1e10);
return boost;
}
propagateBoost() {
this.boost = new Decimal(1);
if (this.child_left != undefined) this.child_left.propagateBoost();
if (this.child_right != undefined) this.child_right.propagateBoost();
if (this.parent_layer != undefined) this.parent_layer.boost = this.parent_layer.boost.mul(this.boost).mul(this.getBoostValue());
}
processTimedelta(delta) {
this.points = this.points.add(this.calculateProduction(this.depth == 0 ? 1 : 0).mul(delta / 1000));
if (this.right_branch) this.points = this.points.add(this.prestigeGain().mul(delta / 1000));
if ((this.child_left == undefined || this.child_right == undefined) && this.points.gt(this.final_goal)) {
player.layers.push(new Layer(player.seed, player.layers.length, this, true));
player.layers.push(new Layer(player.seed, player.layers.length, this, false));
}
}
screenUpdate() {
this.unlockReq.style.visibility = this.child_left === undefined || this.child_right === undefined ? "" : "hidden";
let purchaseAvailable = Object.values(this.upgrades).some(upg => !upg.bought && upg.canBuy()) ||
(this.parent_layer != undefined && this.child_left != undefined && this.child_left.points.gte(this.child_left.final_goal) && !this.left_branch) ||
(this.parent_layer != undefined && this.child_right != undefined && this.child_right.points.gte(this.child_right.final_goal) && !this.right_branch);
let ascensionAvailable = Object.values(this.upgrades).some(upg => !upg.bought && !upg.canBuy() && this.points.add(this.prestigeGain()).gte(upg.cost));
this.nodeEl.className = `tree-node${ascensionAvailable ? ' ascensionAvailable' : ''}${purchaseAvailable ? ' purchaseAvailable' : ''}`;
}
screenUpdateCurrent() {
let layer_container = document.getElementById('layer_info');
layer_container.style.setProperty("--color-layer", formAsRGB(this.color));
layer_container.getElementsByClassName('type')[0].textContent = this.name;
layer_container.getElementsByClassName('point-amount')[0].textContent = formatNumber(this.points, true, true);
layer_container.getElementsByClassName('gain-amount')[0].textContent = formatNumber(this.calculateProduction(this.depth == 0 ? 1 : 0), true);
layer_container.getElementsByClassName('boost-from-value')[0].textContent = formatNumber(this.boost, true);
layer_container.getElementsByClassName('boost-to-value')[0].textContent = formatNumber(this.getBoostValue(), true);
if (this.parent_layer == undefined) layer_container.getElementsByClassName('boost-to')[0].style.visibility = "hidden";
else layer_container.getElementsByClassName('boost-to')[0].style.visibility = "";
for (let element of layer_container.getElementsByClassName('point-name')) {
if (this.points_name == "") element.textContent = "points";
else element.textContent = this.points_name + " points";
}
for (let element of layer_container.getElementsByClassName('prev-point-name')) {
if (this.parent_layer == undefined) element.textContent = "April fools";
else if (this.parent_layer.points_name == "") element.textContent = "points";
else element.textContent = this.parent_layer.points_name + " points";
}
if (this.parent_layer == undefined) layer_container.getElementsByClassName('prestige')[0].style.visibility = "hidden";
else layer_container.getElementsByClassName('prestige')[0].style.visibility = "";
if (this.parent_layer == undefined || this.child_left == undefined) document.getElementById("qol_left").style.visibility = "hidden";
else {
document.getElementById("qol_left").style.visibility = "";
document.getElementById("qol_left").disabled = this.child_left.points.lt(this.child_left.final_goal);
if (this.left_branch) document.getElementById("qol_left").classList.add("complete");
else document.getElementById("qol_left").classList.remove("complete");
layer_container.getElementsByClassName('left-child-req')[0].textContent = formatNumber(this.child_left.final_goal, true);
layer_container.getElementsByClassName('left-child-name')[0].textContent = this.child_left.points_name + " points";
}
if (this.parent_layer == undefined || this.child_right == undefined) document.getElementById("qol_right").style.visibility = "hidden";
else {
document.getElementById("qol_right").style.visibility = "";
document.getElementById("qol_right").disabled = this.child_right.points.lt(this.child_right.final_goal);
if (this.right_branch) document.getElementById("qol_right").classList.add("complete");
else document.getElementById("qol_right").classList.remove("complete");
layer_container.getElementsByClassName('right-child-req')[0].textContent = formatNumber(this.child_right.final_goal, true);
layer_container.getElementsByClassName('right-child-name')[0].textContent = this.child_right.points_name + " points";
}
if (this.canPrestige()) {
layer_container.getElementsByClassName('prestige')[0].disabled = false;
layer_container.getElementsByClassName('cannot-prestige')[0].style.display = "none";
layer_container.getElementsByClassName('can-prestige')[0].style.display = "";
}
else {
layer_container.getElementsByClassName('prestige')[0].disabled = true;
layer_container.getElementsByClassName('cannot-prestige')[0].style.display = "";
layer_container.getElementsByClassName('can-prestige')[0].style.display = "none";
}
for (let element of layer_container.getElementsByClassName('prestige-need')) {
element.textContent = formatNumber(this.prestigeNeed().add(1), true, true);
}
if (this.prestigeGain().gt(100)) layer_container.getElementsByClassName('next-at')[0].style.display = "none";
else layer_container.getElementsByClassName('next-at')[0].style.display = "";
layer_container.getElementsByClassName('prestige-gain')[0].textContent = formatNumber(this.prestigeGain(), true, true);
for (let key of Object.keys(this.upgrades)) {
this.upgrades[key].screenUpdate();
}
}
selectLayer(forceZoom, instant = false) {
let layer_container = document.getElementById('layer_info');
let upgrade_container = layer_container.getElementsByClassName('upgrades-list')[0];
let upgrade_elements = "";
for (let key of Object.keys(this.upgrades)) {
upgrade_elements += '<button class="upgrade" id="' + key + '" onclick="player.current_layer.upgrades[this.id].buy()"><div class="content"><p class="upgrade-name">&nbsp;</p><p class="upgrade-desc">' + this.upgrades[key].getDescCode() + '</p><p class="divider"></p><p class="upgrade-cost">Cost: <span class="cost"></span> <span class="point-name"></span></p></div></button>';
}
upgrade_container.innerHTML = upgrade_elements;
const shouldZoom = player.current_layer === this || player.singleclick;
player.current_layer = this;
if (shouldZoom || forceZoom === true) {
const treeContainer = document.getElementById("tree-container").getBoundingClientRect();
const zoom = Decimal.pow(2, this.depth).times(player.zoomModifier).toNumber();
const nodeRect = this.el.getBoundingClientRect();
const rootRect = player.layers[0].el.getBoundingClientRect();
const x = (rootRect.x + rootRect.width / 2 - nodeRect.x - nodeRect.width / 2) / panzoom.getScale() + treeContainer.width / 2;
const y = (rootRect.y + rootRect.height / 2 - nodeRect.y - nodeRect.height / 2) / panzoom.getScale() + treeContainer.height / 2 / zoom;
panzoom.zoom(zoom, { animate: !instant && player.animations });
panzoom.pan(x, y, { animate: !instant && player.animations });
}
screenUpdate();
}
canPrestige() {
return this.prestigeGain().gt(0);
}
prestigeGain() {
if (this.parent_layer == undefined) return new Decimal(0);
return this.calculateProduction(this.parent_layer.points.max(1).log(this.parent_layer.final_goal).pow(Math.log2(Math.max(1, this.depth) + 1)), this.points, true).floor();
}
prestigeNeed() {
if (this.parent_layer == undefined) return new Decimal(0);
return this.parent_layer.final_goal.pow(this.calculateReverseProduction(this.prestigeGain().add(1), this.points, true).pow(1 / Math.log2(Math.max(1, this.depth) + 1)));
}
prestige() {
this.points = this.points.add(this.prestigeGain()).round();
if (this.parent_layer != undefined) this.parent_layer.reset();
screenUpdate();
}
reset() {
if (!this.left_branch) {
for (let key of Object.keys(this.upgrades)) {
this.upgrades[key].bought = false;
}
}
this.points = new Decimal(0);
//if (this.parent_layer != undefined) this.parent_layer.reset();
}
buyLeft() {
if (this.child_left == undefined) return;
if (this.child_left.points.lt(this.child_left.final_goal)) return;
if (this.left_branch) return;
this.left_branch = true;
this.child_left.points = this.child_left.points.sub(this.child_left.final_goal);
}
buyRight() {
if (this.child_right == undefined) return;
if (this.child_right.points.lt(this.child_right.final_goal)) return;
if (this.right_branch) return;
this.right_branch = true;
this.child_right.points = this.child_right.points.sub(this.child_right.final_goal);
}
save() {
let data = [];
data.push(this.id);
if (this.parent_layer != undefined) data.push(this.parent_layer.id);
else data.push(-1);
data.push(this.is_ngminus);
data.push([this.points.sign, this.points.layer, this.points.mag]);
data.push([this.upgrade_time.sign, this.upgrade_time.layer, this.upgrade_time.mag]);
data.push([this.final_goal.sign, this.final_goal.layer, this.final_goal.mag]);
data.push(this.name);
data.push(this.points_name);
data.push(this.depth);
data.push(this.color);
let upgrade_data = [];
for (let key of Object.keys(this.upgrades)) upgrade_data.push(this.upgrades[key].save());
data.push(upgrade_data);
data.push(this.left_branch);
data.push(this.right_branch);
return data;
}
load(player, data) {
this.id = data[0];
if (data[1] == -1) this.parent_layer = undefined;
else this.parent_layer = player.layers[data[1]];
this.is_ngminus = data[2];
this.points.fromComponents(data[3][0], data[3][1], data[3][2]);
this.upgrade_time.fromComponents(data[4][0], data[4][1], data[4][2]);
this.final_goal.fromComponents(data[5][0], data[5][1], data[5][2]);
this.name = data[6];
this.points_name = data[7];
this.depth = data[8];
this.coord = 0;
this.color = data[9];
this.upgrades = {};
for (let upg of data[10]) {
this.upgrades[upg[0]] = new Upgrade();
this.upgrades[upg[0]].load(this, upg);
}
if (data.length > 11) this.left_branch = data[11];
if (data.length > 12) this.right_branch = data[12];
this.child_left = undefined;
this.child_right = undefined;
if (this.parent_layer != undefined) {
if (this.is_ngminus) this.parent_layer.child_left = this;
else this.parent_layer.child_right = this;
this.coord = 2 * this.parent_layer.coord;
if (!this.is_ngminus) this.coord += 1;
this.coord = this.coord % (2 ** 32);
}
this.nodeEl.style.backgroundColor = formAsRGB(this.color);
if (this.parent_layer === undefined) {
this.el.style.top = "0px";
this.el.style.left = "0px";
this.el.style.transform = "translate(-50%, -50%)";
document.getElementById("tree").appendChild(this.el);
} else {
this.el.style.top = "15em";
this.el.style.left = (this.is_ngminus ? "-" : "") + "10em";
this.el.style.transform = "";
this.parent_layer.el.appendChild(this.el);
}
this.label.innerText = this.parent_layer === undefined ? "OG" : this.points_name.slice(0, 3);
this.unlockReq.innerText = `Get ${formatNumber(this.final_goal)} ${this.points_name ? this.points_name + " points" : "points"} to unlock`;
}
};