"use strict"; let belongings = {}; let ownedUpgrades = {}; let remainingUpgrades = []; let showOwnedUpgrades = false; let effects = {}; let resources = {}; let updateRate = 60; let currentProductivity = {}; let clickBonus = 0; let clickVictim = "micro"; let lastTime = 0; function applyGlobalProdBonuses(productivity) { for (let effect of effects["prod-all"]) { if (ownedUpgrades[effect.parent]) { productivity = effect.apply(productivity); } } return productivity; } function calculateProductivity() { let productivity = 0; for (const [key, value] of Object.entries(belongings)) { productivity += productivityOf(key); } return productivity; } // here's where upgrades will go :3 function productivityMultiplierOf(type) { let base = 1; for (let effect of effects["prod"]) { if (ownedUpgrades[effect.parent] && effect.target == type) { base = effect.apply(base); } } for (let effect of effects["helper"]) { if (ownedUpgrades[effect.parent] && effect.helped == type) { base = effect.apply(base, belongings[effect.helper].count); } } return base; } function productivityOf(type) { let baseProd = buildings[type].prod; let prod = baseProd * productivityMultiplierOf(type); prod = applyGlobalProdBonuses(prod); return prod * belongings[type].count; } function costOfBuilding(type) { let baseCost = buildings[type].cost let countCost = baseCost * Math.pow(1.15, belongings[type].count); return Math.round(countCost); } function buyBuilding(type) { let cost = costOfBuilding(type); if (resources.food >= cost) { belongings[type].count += 1; resources.food -= cost; } updateProductivity(); updateClickBonus(); } // update stuff function updateDisplay() { let newTime = performance.now(); let delta = newTime - lastTime; lastTime = newTime; addResources(delta); displayResources(); displayBuildings(); displayUpgrades(showOwnedUpgrades); setTimeout(updateDisplay, 1000/updateRate); } function updateProductivity() { currentProductivity["food"] = calculateProductivity(); } function addResources(delta) { for (const [resource, amount] of Object.entries(currentProductivity)) { resources[resource] += amount * delta / 1000; } } function displayResources() { document.title = "Gorge - " + round(resources.food) + " food"; replaceChildren(document.querySelector("#resource-list"), renderResources()); } function renderResources() { let list = []; for (const [key, value] of Object.entries(resources)) { let line1 = render(value, 3, 0) + " " + resourceTypes[key].name; let line2 = render(currentProductivity[key], 1, 1) + " " + resourceTypes[key].name + "/sec"; list.push({"text": line1, "class": "resource-quantity"}); list.push({"text": line2, "class": "resource-rate"}); } return renderLines(list); } function displayBuildings() { for (const [key, value] of Object.entries(belongings)) { if (!belongings[key].visible) { if (resources.food * 10 >= costOfBuilding(key)) { unlockBuilding(key); } else { continue; } belongings[key].visible = true; document.querySelector("#building-" + key).classList.remove("hidden"); } let button = document.querySelector("#building-" + key); let name = document.querySelector("#building-" + key + " > .building-button-name"); let cost = document.querySelector("#building-" + key + " > .building-button-cost"); name.innerText = value.count + " " + (value.count == 1 ? buildings[key].name : buildings[key].plural); cost.innerText = render(costOfBuilding(key)) + " food"; if (costOfBuilding(key) > resources.food) { button.classList.add("building-button-disabled"); cost.classList.add("building-button-cost-invalid"); } else { button.classList.remove("building-button-disabled"); cost.classList.add("building-button-cost-valid"); } } } function canAfford(cost) { for (const [resource, amount] of Object.entries(cost)) { if (resources[resource] < amount) { return false; } } return true; } function spend(cost) { for (const [resource, amount] of Object.entries(cost)) { resources[resource] -= amount; } } function switchShowOwnedUpgrades() { if (showOwnedUpgrades) { document.querySelector("#upgrades").innerText = "Upgrades"; } else { document.querySelector("#upgrades").innerText = "Owned Upgrades"; } showOwnedUpgrades = !showOwnedUpgrades; } function displayUpgrades(owned) { if (owned) { Object.entries(ownedUpgrades).forEach(([key, val]) => { let button = document.querySelector("#upgrade-" + key); if (val) { button.classList.remove("hidden"); } else { button.classList.add("hidden"); } }); } else { for (let id of remainingUpgrades) { let button = document.querySelector("#upgrade-" + id); if (ownedUpgrades[id]) { button.classList.add("hidden"); continue; } if (upgradeReachable(id)) { button.classList.remove("hidden"); } else { button.classList.add("hidden"); } if (upgradeAvailable(id)) { button.classList.remove("upgrade-button-inactive"); } else { button.classList.add("upgrade-button-inactive"); } } // we aren't trimming the list of upgrades now // because we need to switch between owned and unowned upgrades // - thus we need to be able to show or hide anything /* for (let i = remainingUpgrades.length-1; i >= 0; i--) { if (ownedUpgrades[remainingUpgrades[i]]) { remainingUpgrades.splice(i, 1); } }*/ } } function updateClickBonus() { let bonus = 0; for (let effect of effects["click"]) { if (ownedUpgrades[effect.parent]) { bonus = effect.apply(bonus, currentProductivity["food"]); } } clickBonus = bonus; } function updateClickVictim() { for (let effect of effects["click-victim"]) { if (ownedUpgrades[effect.parent]) { clickVictim = effect.id; document.querySelector("#tasty-micro").innerText = "Eat " + buildings[effect.id].name; } } } function buyUpgrade(id, e) { if (ownedUpgrades[id]) { return; } let upgrade = upgrades[id]; if (!upgradeAvailable(id)) { return; } spend(upgrade.cost); ownedUpgrades[id] = true; let text = "Bought " + upgrade.name + "!"; clickPopup(text, "upgrade", [e.clientX, e.clientY]); updateProductivity(); updateClickBonus(); updateClickVictim(); } function eatPrey() { const add = buildings[clickVictim]["prod"] * 10 * productivityMultiplierOf(clickVictim) + clickBonus; resources.food += add; return add; } // setup stuff lol // we'll initialize the dict of buildings we can own function setup() { // create static data createTemplateUpgrades(); // prepare dynamic stuff initializeData(); createButtons(); createDisplays(); registerListeners(); load(); unlockAtStart(); updateProductivity(); } function unlockAtStart() { unlockBuilding("micro"); for (const [key, value] of Object.entries(belongings)) { if (belongings[key].visible) { unlockBuilding(key); } } } function unlockBuilding(id) { belongings[id].visible = true; document.querySelector("#building-" + id).classList.remove("hidden"); } function initializeData() { for (const [key, value] of Object.entries(buildings)) { belongings[key] = {}; belongings[key].count = 0; belongings[key].visible = false; } for (const [key, value] of Object.entries(resourceTypes)) { resources[key] = 0; currentProductivity[key] = 0; } for (const [id, upgrade] of Object.entries(upgrades)) { ownedUpgrades[id] = false; for (let effect of upgrade.effects) { if (effects[effect.type] === undefined) { effects[effect.type] = []; } // copy the data and add an entry for the upgrade id that owns the effect let newEffect = {}; for (const [key, value] of Object.entries(effect)) { newEffect[key] = value; } newEffect.parent = id; // unfortunate name collision here // I'm using apply() to pass on any number of arguments to the // apply() function of the effect type newEffect.apply = function(...args) { return effect_types[effect.type].apply.apply(null, [effect].concat(args)); } effects[effect.type].push(newEffect); } } } function registerListeners() { document.querySelector("#tasty-micro").addEventListener("click", (e) => { const add = eatPrey(); const text = "+" + round(add, 1) + " food"; const gulp = "*glp*"; clickPopup(text, "food", [e.clientX, e.clientY]); clickPopup(gulp, "gulp", [e.clientX, e.clientY]); }); document.querySelector("#save").addEventListener("click", save); document.querySelector("#reset").addEventListener("click", reset); document.querySelector("#upgrades").addEventListener("click", switchShowOwnedUpgrades); } function createButtons() { createBuildings(); createUpgrades(); } function createBuildings() { let container = document.querySelector("#buildings-list"); for (const [key, value] of Object.entries(buildings)) { let button = document.createElement("div"); button.classList.add("building-button"); button.classList.add("hidden"); button.id = "building-" + key; let buttonName = document.createElement("div"); buttonName.classList.add("building-button-name"); let buttonCost = document.createElement("div"); buttonCost.classList.add("building-button-cost"); let buildingIcon = document.createElement("i"); buildingIcon.classList.add("fas"); buildingIcon.classList.add(value.icon); button.appendChild(buttonName); button.appendChild(buttonCost); button.appendChild(buildingIcon); button.addEventListener("mousemove", function(e) { buildingTooltip(key, e); }); button.addEventListener("mouseleave", function() { buildingTooltipRemove(); }); button.addEventListener("click", function() { buyBuilding(key); }); button.addEventListener("click", function(e) { buildingTooltip(key, e); }); container.appendChild(button); } } // do we have previous techs and at least one of each building? function upgradeReachable(id) { if (ownedUpgrades[id]) { return false; } if (upgrades[id].prereqs !== undefined ){ for (const [type, reqs] of Object.entries(upgrades[id].prereqs)) { if (type == "buildings") { for (const [building, amount] of Object.entries(reqs)) { if (belongings[building].count == 0) { return false; } } } else if (type == "upgrades") { for (let upgrade of reqs) { if (!ownedUpgrades[upgrade]) { return false; } } } } } return true; } function upgradeAvailable(id) { if (!upgradeReachable(id)) { return false; } if (!canAfford(upgrades[id].cost)) { return false; } if (upgrades[id].prereqs !== undefined) { for (const [type, reqs] of Object.entries(upgrades[id].prereqs)) { if (type == "buildings") { for (const [building, amount] of Object.entries(upgrades[id].prereqs[type])) { if (belongings[building].count < amount) { return false; } } } else if (type == "productivity") { for (const [key, value] of Object.entries(reqs)) { if (currentProductivity[key] < value) { return false; } } } } } return true; } function createUpgrades() { let container = document.querySelector("#upgrades-list"); for (const [key, value] of Object.entries(upgrades)) { remainingUpgrades.push(key); let button = document.createElement("div"); button.classList.add("upgrade-button"); button.classList.add("hidden"); button.id = "upgrade-" + key; let buttonName = document.createElement("div"); buttonName.classList.add("upgrade-button-name"); buttonName.innerText = value.name; let upgradeIcon = document.createElement("i"); upgradeIcon.classList.add("fas"); upgradeIcon.classList.add(value.icon); button.appendChild(buttonName); button.appendChild(upgradeIcon); button.addEventListener("mousemove", function(e) { upgradeTooltip(key, e); }); button.addEventListener("mouseleave", function() { upgradeTooltipRemove(); }); button.addEventListener("click", function(e) { buyUpgrade(key, e); }); container.appendChild(button); } } function createDisplays() { // nop } function renderLine(line) { let div = document.createElement("div"); div.innerText = line.text; if (line.valid !== undefined) { if (line.valid) { div.classList.add("cost-met"); } else { div.classList.add("cost-unmet"); } } if (line.class !== undefined) { for (let entry of line.class.split(",")) { div.classList.add(entry); } } return div; } function renderLines(lines) { let divs = []; for (let line of lines) { divs.push(renderLine(line)); } return divs; } function renderCost(cost) { let list = []; list.push({ "text": "Cost:" }); for (const [key, value] of Object.entries(cost)) { list.push({ "text": render(value,0) + " " + resourceTypes[key].name, "valid": resources[key] >= value }); } return renderLines(list); } function renderPrereqs(prereqs) { let list = []; if (prereqs === undefined) { return renderLines(list); } list.push({ "text": "Own:" }); for (const [key, value] of Object.entries(prereqs)) { if (key == "buildings") { for (const [id, amount] of Object.entries(prereqs.buildings)) { list.push({ "text": buildings[id].name + " x" + render(amount,0), "valid": belongings[id].count >= amount }); } } else if (key == "productivity") { for (const [id, amount] of Object.entries(prereqs.productivity)) { list.push({ "text": render(amount,0) + " " + resourceTypes[id].name + "/s", "valid": currentProductivity[id] >= amount }); } } } return renderLines(list); } function renderEffects(effectList) { let list = []; for (let effect of effectList) { list.push({"text": effect_types[effect.type].desc(effect)}); } return renderLines(list); } function clickPopup(text, type, location) { const div = document.createElement("div"); div.textContent = text; div.classList.add("click-popup-" + type); var direction; if (type == "food") { direction = -150; } else if (type == "gulp") { direction = -150; } else if (type == "upgrade") { direction = -50; } else if (type == "info") { direction = 0; } direction *= Math.random() * 0.5 + 1; direction = Math.round(direction) + "px" div.style.setProperty("--target", direction) div.style.left = location[0] + "px"; div.style.top = location[1] + "px"; const body = document.querySelector("body"); body.appendChild(div); setTimeout(() => { body.removeChild(div); }, 2000); } function fillTooltip(type, field, content) { let item = document.querySelector("#" + type + "-tooltip-" + field); if (typeof(content) === "string") { item.innerText = content; } else { replaceChildren(item, content); } } function upgradeTooltip(id, event) { let tooltip = document.querySelector("#upgrade-tooltip"); tooltip.style.setProperty("display", "inline-block"); fillTooltip("upgrade", "name", upgrades[id].name); fillTooltip("upgrade", "desc", upgrades[id].desc); fillTooltip("upgrade", "effect", renderEffects(upgrades[id].effects)); fillTooltip("upgrade", "cost", renderCost(upgrades[id].cost)); fillTooltip("upgrade", "prereqs", renderPrereqs(upgrades[id].prereqs)); let yOffset = tooltip.parentElement.getBoundingClientRect().y; let tooltipSize = tooltip.getBoundingClientRect().height; let yTrans = Math.round(event.clientY - yOffset); var body = document.body, html = document.documentElement; var height = Math.max(window.innerHeight); yTrans = Math.min(yTrans, height - tooltipSize - 150); tooltip.style.setProperty("transform", "translate(-220px, " + yTrans + "px)"); } function upgradeTooltipRemove() { let tooltip = document.querySelector("#upgrade-tooltip"); tooltip.style.setProperty("display", "none"); } function prodSummary(id) { let list = []; list.push( {"text": "Each " + buildings[id].name + " produces " + round(productivityMultiplierOf(id) * buildings[id].prod,1) + " food/sec"} ); list.push( {"text": "Your " + render(belongings[id].count) + " " + (belongings[id].count == 1 ? buildings[id].name + " is": buildings[id].plural + " are") + " producing " + round(productivityOf(id),1) + " food/sec"} ); let percentage = round(100 * productivityOf(id) / currentProductivity["food"], 2); if (isNaN(percentage)) { percentage = 0; } list.push( {"text": "(" + percentage + "% of all food)"} ); return renderLines(list); } function buildingTooltip(id, event) { let tooltip = document.querySelector("#building-tooltip"); tooltip.style.setProperty("display", "inline-block"); fillTooltip("building", "name", buildings[id].name); fillTooltip("building", "desc", buildings[id].desc); fillTooltip("building", "cost", render(costOfBuilding(id)) + " food"); fillTooltip("building", "prod", prodSummary(id)); let xPos = tooltip.parentElement.getBoundingClientRect().x - 450; // wow browsers are bad var body = document.body, html = document.documentElement; var height = Math.max( body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight ); let yPos = Math.min(event.clientY, height - 200); tooltip.style.setProperty("transform", "translate(" + xPos + "px, " + yPos + "px)") } function buildingTooltipRemove() { let tooltip = document.querySelector("#building-tooltip"); tooltip.style.setProperty("display", "none"); } window.onload = function() { setup(); lastTime = performance.now(); setTimeout(updateDisplay, 1000/updateRate); setTimeout(autosave, 60000); } function autosave() { saveGame(); let x = window.innerWidth / 2; let y = window.innerHeight * 9 / 10; clickPopup("Autosaving...", "info", [x, y]); setTimeout(autosave, 60000); } function save(e) { saveGame(); clickPopup("Saved!", "info", [e.clientX, e.clientY]); } function saveGame() { let storage = window.localStorage; storage.setItem("save-version", "0.0.1"); storage.setItem("ownedUpgrades", JSON.stringify(ownedUpgrades)); storage.setItem("resources", JSON.stringify(resources)); storage.setItem("belongings", JSON.stringify(belongings)); } function load() { let storage = window.localStorage; if (!storage.getItem("save-version")) { return; } let newOwnedUpgrades = JSON.parse(storage.getItem("ownedUpgrades")); for (const [key, value] of Object.entries(newOwnedUpgrades)) { ownedUpgrades[key] = value; } let newResources = JSON.parse(storage.getItem("resources")); for (const [key, value] of Object.entries(newResources)) { resources[key] = value; } let newBelongings = JSON.parse(storage.getItem("belongings")); for (const [key, value] of Object.entries(newBelongings)) { belongings[key] = value; } } function reset() { window.localStorage.clear(); }