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 += ''; } 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`; } };