|  | "use strict";
const belongings = {};
const stats = {};
const macroDesc = {
  name: "Fen",
  species: "crux"
}
const ownedUpgrades = {};
const remainingUpgrades = [];
let showOwnedUpgrades = false;
const effects = {};
const resources = {};
let updateRate = 60;
const currentProductivity = {};
const contributions = {};
const clickPowers = {
  clickBonus: 0,
  clickMultiplier: 1,
  clickVictim: "micro"
}
let clickBonus = 0;
let clickVictim = "micro";
let lastTime = 0;
let controlHeld = false;
let shiftHeld = false;
let mouseTarget = undefined;
let newsShowTimer;
let newsRemoveTimer;
const newsDelay = 8000;
const state = {
  ownedUpgrades: ownedUpgrades,
  resources: resources,
  currentProductivity: currentProductivity,
  belongings: belongings,
  clickPowers: clickPowers,
  stats: stats
};
const numberModes = {
  words: {
    name: "Words",
    render: numberText,
    next: "smallWords"
  },
  smallWords: {
    name: "Small Words",
    render: numberTextSmall,
    next: "scientific"
  },
  scientific: {
    name: "Scientific",
    render: numberScientific,
    next: "full",
  },
  full: {
    name: "Full",
    render: numberFull,
    next: "words"
  }
}
deepFreeze(numberModes);
let numberMode = numberModes["words"];
const activePowerups = {};
function tickPowerups(delta) {
  const powerupList = document.querySelector("#powerup-list");
  let changed = false;
  // I love mutating arrays as I traverse them.
  Object.entries(activePowerups).filter(x => x[1].life > 0).forEach(([key, data]) => {
    const newLife = data.life - delta;
    if (newLife <= 0) {
      setTimeout(() => {
        powerupList.removeChild(data.element);
      }, 1000);
      data.element.classList.add("powerup-entry-done");
      activePowerups[key].life = 0;
      changed = true;
    } else {
      data.life = newLife;
      const frac = (data.maxLife - data.life) / (data.maxLife);
      data.element.style.setProperty("--progress", frac * 100 + "%")
    }
  });
  if (changed) {
    updateAll();
  }
}
function addPowerup(key, powerup) {
  // powerup already exists
  if (activePowerups[key].life > 0) {
    activePowerups[key].life += powerup.duration;
    activePowerups[key].maxLife = activePowerups[key].life;
  } else {
    const powerupList = document.querySelector("#powerup-list");
    const powerupEntry = document.createElement("div");
    powerupEntry.classList.add("powerup-entry");
    powerupEntry.innerText = powerup.name;
  
    powerupList.appendChild(powerupEntry);
  
    activePowerups[key] = {powerup: powerup, life: powerup.duration, maxLife: powerup.duration, element: powerupEntry };
    
    powerupEntry.addEventListener("mousemove", function (e) { powerupTooltip(key, e); });
    powerupEntry.addEventListener("mouseleave", function () { powerupTooltipRemove(); });
    updateAll();
  }
  
}
function applyGlobalProdBonus(cost) {
  for (let effect of effects["prod-all"]) {
    if (ownedUpgrades[effect.parent]) {
      effect.apply(cost);
    }
  }
}
function calculateProductivity() {
  let productivity = makeCost();
  for (const [key, value] of Object.entries(belongings)) {
    const provided = productivityOf(key);
    productivity = addCost(productivity, provided);
    contributions[key] = provided;
  }
  return productivity;
}
// here's where upgrades will go :3
function applyProductivityMultipliers(type, cost) {
  for (let effect of effects["prod"]) {
    if (ownedUpgrades[effect.parent] && effect.target == type) {
      effect.apply(cost);
    }
  }
  for (let effect of effects["helper"]) {
    if (ownedUpgrades[effect.parent] && effect.helped == type) {
      effect.apply(cost, belongings[effect.helper].count);
    }
  }
}
function productivityOf(type) {
  let baseProd = makeCost(buildings[type].prod);
  applyProductivityMultipliers(type, baseProd);
  applyGlobalProdBonus(baseProd);
  scaleCost(baseProd, belongings[type].count);
  return baseProd;
}
function makeCost(source) {
  const empty = mapObject(resourceTypes, () => 0);
  return {...empty, ...source};
}
function addCost(cost1, cost2) {
  return Object.keys(resourceTypes).reduce((o, k) => {o[k] += cost2[k]; return o;}, cost1);
}
function scaleCost(cost, scale) {
  return Object.keys(resourceTypes).reduce((o, k) => {o[k] *= scale; return o;}, cost);
}
function costOfBuilding(type, count = 1) {
  let total = makeCost();
  while (count > 0) {
    let baseCost = makeCost(buildings[type].cost);
    baseCost  = scaleCost(baseCost, Math.pow(1.15, belongings[type].count + count - 1));
    total = addCost(total, baseCost);
    count--;
  }
  return mapObject(total, round);
}
function buildingCount() {
  if (controlHeld) {
    return 10;
  } else if (shiftHeld) {
    return 100;
  } else {
    return 1;
  }
}
function buyBuilding(type, e) {
  const count = buildingCount();
  let cost = costOfBuilding(type, count);
  if (canAfford(cost)) {
    spend(cost);
    belongings[type].count += count;
  }
  updateProductivity();
}
function updateAll() {
  updateProductivity();
  updateClickVictim();
  updateOptions();
}
function updateOptions() {
  cache.optionButtons.numbers.innerText = "Number mode: " + numberMode.name;
}
// update stuff
function updateDisplay() {
  let newTime = performance.now();
  let delta = newTime - lastTime;
  lastTime = newTime;
  addResources(delta);
  displayResources();
  displayBuildings();
  displayUpgrades(showOwnedUpgrades);
  tickPowerups(delta);
  Object.keys(statTypes).forEach(key => {
    const value = document.querySelector("#stat-value-" + key);
    value.innerText = render(stats[key]);
  })
  stats.seconds += delta / 1000;
  setTimeout(updateDisplay, 1000 / updateRate);
}
function updateProductivity() {
  Object.assign(currentProductivity, calculateProductivity());
  // maybe this should go somewhere else - it also does clicking...
  updateClickPowers();
  Object.entries(activePowerups).forEach(([key, entry]) => {
    if (entry.life > 0) {
      const powerup = entry.powerup;
  
      powerup.effect(state);
    }
    
  });
}
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";
  Object.keys(resources).forEach(key => {
    cache.resourceLabels[key].quantity.innerText = render(resources[key]) + " " + resourceTypes[key].name;
    
    if (resourceTypes[key].generated)
      cache.resourceLabels[key].rate.innerText = render(currentProductivity[key]) + " " + resourceTypes[key].name + "/sec";
    
  })
}
function displayBuildings() {
  const count = buildingCount();
  for (const [key, value] of Object.entries(belongings)) {
    let available = states.buildings[key].available;
    if (!belongings[key].visible) {
      if (resources.food * 10 >= costOfBuilding(key).food) {
        unlockBuilding(key);
      } if (belongings[key].count > 0) {
        unlockBuilding(key);
      } else {
        continue;
      }
      belongings[key].visible = true;
      let button = cache.buildingButtons[key].button;
      button.classList.remove("hidden");
    }
    let button = cache.buildingButtons[key].button;
    let name = cache.buildingButtons[key].name;
    let cost = cache.buildingButtons[key].cost;
    const buildingCost = costOfBuilding(key, count);
    const newName = value.count + " " + (value.count == 1 ? buildings[key].name : buildings[key].plural);
    if (newName != states.buildings[key].name) {
      name.innerText = newName;
      states.buildings[key].name = newName;
    }
    
    const newCost = render(buildingCost.food) + " food";
    if (newCost != states.buildings[key].cost) {
      cost.innerText = newCost;
      states.buildings[key].cost = newCost;
    }
    if (canAfford(buildingCost) && available !== true) {
      button.classList.remove("building-button-disabled");
      cost.classList.add("building-button-cost-valid");
      states.buildings[key].available = true;
    } else if (!canAfford(buildingCost) && available !== false) {
      button.classList.add("building-button-disabled");
      cost.classList.add("building-button-cost-invalid");
      states.buildings[key].available = false;
    }
  }
}
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() {
  initializeUpgradeStates();
  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 = cache.upgradeButtons[key];
      if (val) {
        button.classList.remove("hidden");
      } else {
        button.classList.add("hidden");
      }
    });
  }
  else {
    for (let id of remainingUpgrades) {
      let button = cache.upgradeButtons[id];
      let visible = states.upgrades[id].visible;
      let available = states.upgrades[id].available;
      if (ownedUpgrades[id] && visible !== false) {
        button.classList.add("hidden");
        states.upgrades[id].visible = false;
        continue;
      }
      if (upgradeReachable(id) && visible !== true) {
        button.classList.remove("hidden");
        states.upgrades[id].visible = true;
      } else if (!upgradeReachable(id) && visible !== false) {
        button.classList.add("hidden");
        states.upgrades[id].visible = false;
      }
      if (upgradeAvailable(id) && available !== true) {
        button.classList.remove("upgrade-button-inactive");
        states.upgrades[id].available = true;
      } else if (!upgradeAvailable(id) && available !== false) {
        button.classList.add("upgrade-button-inactive");
        states.upgrades[id].available = false;
      }
    }
    // 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 updateClickPowers() {
  let bonus = 0;
  clickPowers.clickMultiplier = 1;
  for (let effect of effects["click"]) {
    if (ownedUpgrades[effect.parent]) {
      bonus = effect.apply(bonus, currentProductivity["food"]);
    }
  }
  clickPowers.clickBonus = bonus;
}
function updateClickVictim() {
  const button = document.querySelector("#tasty-micro");
  button.classList.remove(...button.classList);
  for (let i=effects["click-victim"].length - 1; i >=0; i--) {
    const effect = effects["click-victim"][i];
    if (ownedUpgrades[effect.parent]) {
      clickPowers.clickVictim = effect.id;
      button.classList.add("fas")
      button.classList.add(buildings[effect.id].icon)
      return;
    }
  }
  clickPowers.clickVictim = "micro";
  button.classList.add("fas")
  button.classList.add(buildings.micro.icon)
}
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();
  updateClickVictim();
}
function eatPrey() {
  const add = clickPowers.clickMultiplier * (buildings[clickPowers.clickVictim]["prod"].food * 10 + clickPowers.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();
  initializeCaches();
  initializeStates();
  updateAll();
}
const cache = {};
function initializeCaches() {
  const buildingButtons = {};
  for (const [key, value] of Object.entries(belongings)) {
    let button = document.querySelector("#building-" + key);
    let name = document.querySelector("#building-" + key + " > .building-button-name");
    let cost = document.querySelector("#building-" + key + " > .building-button-cost");
    buildingButtons[key] = {
      button: button,
      name: name,
      cost: cost
    }
  }
  cache.buildingButtons = buildingButtons;
  const upgradeButtons = {};
  Object.keys(upgrades).forEach(key => {
    upgradeButtons[key] = document.querySelector("#upgrade-" + key);
  });
  cache.upgradeButtons = upgradeButtons;
  const resourceLabels = {};
  Object.keys(resourceTypes).forEach(key => {
    resourceLabels[key] = {
      quantity: document.querySelector("#resource-quantity-" + key),
    }
    if (resourceTypes[key].generated)
      resourceLabels[key].rate = document.querySelector("#resource-rate-" + key);
  });
  cache.resourceLabels = resourceLabels;
  const optionButtons = {};
  optionButtons.numbers = document.querySelector("#numbers");
  cache.optionButtons = optionButtons;
}
const states = {};
// we can keep track of some things, like whether
// specific upgrades are currently visible. this
// way, we don't have to set them visible every tick;
// we can just check if they've been handled already
function initializeStates() {
  initializeBuildingStates();
  initializeUpgradeStates();
}
function initializeBuildingStates() {
  const buildingStates = {};
  Object.keys(buildings).forEach(key => {
    buildingStates[key] = {
      visible: undefined,
      available: undefined,
      name: undefined,
      cost: undefined
    }
  });
  states.buildings = buildingStates;
}
function initializeUpgradeStates() {
  const upgradeStates = {};
  Object.keys(upgrades).forEach(key => {
    upgradeStates[key] = {
      visible: undefined,
      available: undefined
    }
  });
  states.upgrades = upgradeStates;
}
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;
    contributions[key] = makeCost();
  }
  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);
    }
  }
  Object.keys(powerups).filter(x => powerups[x].duration !== undefined).forEach(key => activePowerups[key] = {
    life: 0
  });
  Object.entries(statTypes).forEach(([key, info]) => {
    stats[key] = 0;
  });
}
function registerListeners() {
  document.querySelector("#tasty-micro").addEventListener("click", (e) => {
    const add = eatPrey();
    const text = "+" + render(round(add, 1), 3) + " food";
    const gulp = "*glp*";
    clickPopup(text, "food", [e.clientX, e.clientY]);
    clickPopup(gulp, "gulp", [e.clientX, e.clientY]);
    stats.clicks += 1;
  });
  document.querySelector("#save").addEventListener("click", save);
  document.querySelector("#reset").addEventListener("click", reset);
  document.querySelector("#numbers").addEventListener("click", cycleNumbers);
  document.querySelector("#stats").addEventListener("click", () => document.querySelector("#stats-modal").classList.add("modal-active"));
  document.querySelector("#options").addEventListener("click", openOptions);
  document.querySelector("#stats-exit").addEventListener("click", () => document.querySelector("#stats-modal").classList.remove("modal-active"));
  document.querySelector("#options-exit").addEventListener("click", closeOptions);
  
  document.querySelector("#upgrades").addEventListener("click", switchShowOwnedUpgrades);
  document.addEventListener("keydown", e => {
    shiftHeld = e.shiftKey;
    controlHeld = e.ctrlKey;
    if (mouseTarget)
      mouseTarget.dispatchEvent(new Event("mousemove"));
    return true;
  });
  document.addEventListener("keyup", e => {
    shiftHeld = e.shiftKey;
    controlHeld = e.ctrlKey;
    if (mouseTarget)
      mouseTarget.dispatchEvent(new Event("mousemove"));
    return true;
  });
}
function openOptions() {
  document.querySelector("#options-modal").classList.add("modal-active");
  Object.keys(options).forEach(key => {
    const input = document.querySelector("#option-value-" + key);
    input.value = options[key].get();
  });
}
function closeOptions() {
  document.querySelector("#options-modal").classList.remove("modal-active");
  Object.keys(options).forEach(key => {
    const input = document.querySelector("#option-value-" + key);
    options[key].set(input.value);
  });
}
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) { mouseTarget = button; buildingTooltip(key, e); });
    button.addEventListener("mouseleave", function () { mouseTarget = undefined; buildingTooltipRemove(); });
    button.addEventListener("click", function (e) { buyBuilding(key, e); });
    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;
          }
        }
      }
      else if (type == "resources") {
        for (const [resource, amount] of Object.entries(reqs)) {
          if (resources[resource] < amount) {
            return false;
          }
        };
      }
      else if (type == "stats") {
        for (const [stat, amount] of Object.entries(reqs)) {
          if (stats[stat] < amount) {
            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;
    if (typeof(value.icon) == "object") {
      value.icon.forEach(icon => {
        let upgradeIcon = document.createElement("i");
        upgradeIcon.classList.add("fas");
        upgradeIcon.classList.add(icon.icon);
        upgradeIcon.style.color = icon.color;
        button.appendChild(upgradeIcon);
      })
    } else {
      let upgradeIcon = document.createElement("i");
      upgradeIcon.classList.add("fas");
      upgradeIcon.classList.add(value.icon);
      button.appendChild(upgradeIcon);
    }
    
    button.addEventListener("mouseenter", function (e) { mouseTarget = button; upgradeTooltip(key, e); });
    button.addEventListener("mousemove", function (e) { mouseTarget = button; upgradeTooltip(key, e); });
    button.addEventListener("mouseleave", function () { mouseTarget = undefined; upgradeTooltipRemove(); });
    button.addEventListener("click", function (e) { buyUpgrade(key, e); });
    container.appendChild(button);
  }
}
function createDisplays() {
  const resourceList = document.querySelector("#resource-list");
  Object.keys(resourceTypes).forEach(key => {
    const quantity = document.createElement("div");
    quantity.classList.add("resource-quantity");
    quantity.id = "resource-quantity-" + key;
    resourceList.appendChild(quantity);
    if (resourceTypes[key].generated) {
      const rate = document.createElement("div");
      rate.classList.add("resource-rate");
      rate.id = "resource-rate-" + key;
      resourceList.appendChild(rate);
    }
    
  })
  const statHolder = document.querySelector("#stats-holder");
  Object.keys(statTypes).forEach(key => {
    const div = document.createElement("div");
    div.classList.add("stat-line");
    const name = document.createElement("div");
    name.classList.add("stat-name");
    const value = document.createElement("div");
    value.classList.add("stat-value");
    value.id = "stat-value-" + key;
    name.innerText = statTypes[key].name;
    value.innerText = stats[key];
    div.appendChild(name);
    div.appendChild(value);
    statHolder.append(div);
  });
  const optionHolder = document.querySelector("#options-holder");
  Object.keys(options).forEach(key => {
    const div = document.createElement("div");
    div.classList.add("option-line");
    const name = document.createElement("div");
    name.classList.add("option-name");
    const value = document.createElement("input");
    value.classList.add("option-value");
    value.id = "option-value-" + key;
    name.innerText = options[key].name;
    value.innerText = options[key].get();
    div.appendChild(name);
    div.appendChild(value);
    optionHolder.append(div);
  });
}
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 doNews() {
  let options = [];
  news.forEach(entry => {
    if (entry.condition(state)) {
      options = options.concat(entry.lines);
    }
  });
  const choice = Math.floor(Math.random() * options.length);
  showNews(options[choice](state));
  newsShowTimer = setTimeout(() => {
    doNews();
  }, 8000);
}
function showNews(text) {
  const div = document.createElement("div");
  div.textContent = text;
  div.classList.add("news-text");
  div.addEventListener("click", () => {
    clearTimeout(newsShowTimer);
    clearTimeout(newsRemoveTimer);
    body.removeChild(div);
    doNews();
  });
  const body = document.querySelector("body");
  body.appendChild(div);
  newsRemoveTimer = setTimeout(() => {
    body.removeChild(div);
  }, 8000);
}
function doPowerup() {
  const lifetime = 10000;
  const button = document.createElement("div");
  const left = Math.round(Math.random() * 50 + 25) + "%";
  const top = Math.round(Math.random() * 50 + 25) + "%";
  button.classList.add("powerup");
  button.style.setProperty("--lifetime", lifetime / 1000 + "s");
  button.style.setProperty("--leftpos", left);
  button.style.setProperty("--toppos", top);
  const body = document.querySelector("body");
  body.appendChild(button);
  const choices = [];
  Object.entries(powerups).forEach(([key, val]) => {
    if (val.prereqs(state))
      choices.push(key);
  });
  const choice = Math.floor(Math.random() * choices.length);
  const powerup = powerups[choices[choice]];
  const icon = document.createElement("div");
  icon.classList.add("fas");
  icon.classList.add(powerup.icon);
  button.appendChild(icon);
  const remove = setTimeout(() => {
    body.removeChild(button);
  }, lifetime);
  let delay = 60000 + Math.random() * 30000;
  for (let effect of effects["powerup-freq"]) {
    if (ownedUpgrades[effect.parent]) {
      delay = effect.apply(delay);
    }
  }
  setTimeout(() => {
    doPowerup();
  }, delay);
  button.addEventListener("mousedown", e => {
    if (powerup.duration !== undefined) {
      addPowerup(choices[choice], powerup);
    } else {
      powerup.effect(state);
    }
    powerup.popup(powerup, e);
    button.classList.add("powerup-clicked");
    resources.powerups += 1;
    clearTimeout(remove);
    stats.powerups += 1;
    setTimeout(() => {
      body.removeChild(button);
    }, 500);
  });
}
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(-420px, " + 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 " + render(belongings[id].count == 0 ? 0 :  contributions[id].food / belongings[id].count, 3) + " food/sec" }
  );
  list.push(
    { "text": "Your " + render(belongings[id].count) + " " + (belongings[id].count == 1 ? buildings[id].name + " is" : buildings[id].plural + " are") + " producing " + render(contributions[id].food, 3) + " food/sec" }
  );
  let percentage = round(100 * contributions[id].food / 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");
  const count = buildingCount();
  fillTooltip("building", "name", (count != 1 ? count + "x " : "") + buildings[id].name);
  fillTooltip("building", "desc", buildings[id].desc);
  fillTooltip("building", "cost", render(costOfBuilding(id, count).food) + " 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");
}
function powerupTooltip(id, event) {
  let tooltip = document.querySelector("#powerup-tooltip");
  tooltip.style.setProperty("display", "inline-block");
  const count = buildingCount();
  fillTooltip("powerup", "name", powerups[id].name);
  fillTooltip("powerup", "desc", powerups[id].description);
  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 powerupTooltipRemove() {
  let tooltip = document.querySelector("#powerup-tooltip");
  tooltip.style.setProperty("display", "none");
}
window.onload = function () {
  setup();
  lastTime = performance.now();
  doNews();
  doPowerup();
  setTimeout(updateDisplay, 1000 / updateRate);
  setTimeout(autosave, 60000);
}
window.onblur = function() {
  controlHeld = false;
  shiftHeld = false;
}
window.onfocus = function() {
  window.dispatchEvent(new Event("keydown"))
}
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() {
  try {
    let storage = window.localStorage;
    const save = {}
    save.version = migrations.length;
    save.ownedUpgrades = ownedUpgrades;
    save.resources = resources;
    save.belongings = belongings;
    save.stats = stats;
    save.macroDesc = macroDesc;
    storage.setItem("save", JSON.stringify(save));
  } catch (e) {
    clickPopup("Can't save - no access to local storage.", "info", [window.innerWidth / 2, window.innerHeight / 5]);
  }
}
const migrations = [
  // dummy migration, because there was no version 0
  save => {
  },
  // introduce stats
  save => {
    save.stats = {}
  },
  // introduce macroDesc
  save => {
    save.macroDesc = {}
  }
]
function migrate(save) {
  let version = save.version;
  while (version != migrations.length) {
    migrations[version](save);
    version += 1;
  }
  save.version = version;
}
function load() {
  try {
    let storage = window.localStorage;
    
    // migrate to everything in one
    if (storage.getItem("save-version") !== null) {
      const save = {};
      save.ownedUpgrades = JSON.parse(storage.getItem("ownedUpgrades"));
      save.resources = JSON.parse(storage.getItem("resources"));
      save.belongings = JSON.parse(storage.getItem("belongings"));
      save.version = 1;
      storage.clear();
      storage.setItem("save", JSON.stringify(save))
    }
    
    const save = JSON.parse(storage.getItem("save"));
    if (save == null)
      return;
    migrate(save);
    for (const [key, value] of Object.entries(save.ownedUpgrades)) {
      ownedUpgrades[key] = value;
    }
    for (const [key, value] of Object.entries(save.resources)) {
      resources[key] = value;
    }
    for (const [key, value] of Object.entries(save.belongings)) {
      belongings[key] = value;
    }
    for (const [key, value] of Object.entries(save.stats)) {
      stats[key] = value;
    }
    for (const [key, value] of Object.entries(save.macroDesc)) {
      macroDesc[key] = value;
    }
  } catch (e) {
    console.error(e);
    clickPopup("Can't load - no access to local storage.", "info", [window.innerWidth / 2, window.innerHeight / 5]);
  }
}
function reset() {
  window.localStorage.clear();
}
function cycleNumbers() {
  numberMode = numberModes[numberMode.next];
  updateOptions();
}
 |