cookie clicker but bigger
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

1241 lines
30 KiB

  1. "use strict";
  2. const belongings = {};
  3. const ownedUpgrades = {};
  4. const remainingUpgrades = [];
  5. let showOwnedUpgrades = false;
  6. const effects = {};
  7. const resources = {};
  8. let updateRate = 60;
  9. const currentProductivity = {};
  10. const contributions = {};
  11. const clickPowers = {
  12. clickBonus: 0,
  13. clickMultiplier: 1,
  14. clickVictim: "micro"
  15. }
  16. let clickBonus = 0;
  17. let clickVictim = "micro";
  18. let lastTime = 0;
  19. let controlHeld = false;
  20. let shiftHeld = false;
  21. let mouseTarget = undefined;
  22. const state = {
  23. ownedUpgrades: ownedUpgrades,
  24. resources: resources,
  25. currentProductivity: currentProductivity,
  26. belongings: belongings,
  27. clickPowers: clickPowers
  28. };
  29. const numberModes = {
  30. words: {
  31. name: "Words",
  32. render: numberText,
  33. next: "smallWords"
  34. },
  35. smallWords: {
  36. name: "Small Words",
  37. render: numberTextSmall,
  38. next: "scientific"
  39. },
  40. scientific: {
  41. name: "Scientific",
  42. render: numberScientific,
  43. next: "full",
  44. },
  45. full: {
  46. name: "Full",
  47. render: numberFull,
  48. next: "words"
  49. }
  50. }
  51. deepFreeze(numberModes);
  52. let numberMode = numberModes["words"];
  53. const activePowerups = {};
  54. function tickPowerups(delta) {
  55. const powerupList = document.querySelector("#powerup-list");
  56. let changed = false;
  57. // I love mutating arrays as I traverse them.
  58. Object.entries(activePowerups).filter(x => x[1].life > 0).forEach(([key, data]) => {
  59. const newLife = data.life - delta;
  60. if (newLife <= 0) {
  61. setTimeout(() => {
  62. powerupList.removeChild(data.element);
  63. }, 1000);
  64. data.element.classList.add("powerup-entry-done");
  65. activePowerups[key].life = 0;
  66. changed = true;
  67. } else {
  68. data.life = newLife;
  69. const frac = (data.maxLife - data.life) / (data.maxLife);
  70. data.element.style.setProperty("--progress", frac * 100 + "%")
  71. }
  72. });
  73. if (changed) {
  74. updateAll();
  75. }
  76. }
  77. function addPowerup(key, powerup) {
  78. // powerup already exists
  79. if (activePowerups[key].life > 0) {
  80. activePowerups[key].life += powerup.duration;
  81. activePowerups[key].maxLife = activePowerups[key].life;
  82. } else {
  83. const powerupList = document.querySelector("#powerup-list");
  84. const powerupEntry = document.createElement("div");
  85. powerupEntry.classList.add("powerup-entry");
  86. powerupEntry.innerText = powerup.name;
  87. powerupList.appendChild(powerupEntry);
  88. activePowerups[key] = {powerup: powerup, life: powerup.duration, maxLife: powerup.duration, element: powerupEntry };
  89. updateAll();
  90. }
  91. }
  92. function applyGlobalProdBonus(cost) {
  93. for (let effect of effects["prod-all"]) {
  94. if (ownedUpgrades[effect.parent]) {
  95. effect.apply(cost);
  96. }
  97. }
  98. }
  99. function calculateProductivity() {
  100. let productivity = makeCost();
  101. for (const [key, value] of Object.entries(belongings)) {
  102. const provided = productivityOf(key);
  103. productivity = addCost(productivity, provided);
  104. contributions[key] = provided;
  105. }
  106. return productivity;
  107. }
  108. // here's where upgrades will go :3
  109. function applyProductivityMultipliers(type, cost) {
  110. for (let effect of effects["prod"]) {
  111. if (ownedUpgrades[effect.parent] && effect.target == type) {
  112. effect.apply(cost);
  113. }
  114. }
  115. for (let effect of effects["helper"]) {
  116. if (ownedUpgrades[effect.parent] && effect.helped == type) {
  117. effect.apply(cost, belongings[effect.helper].count);
  118. }
  119. }
  120. }
  121. function productivityOf(type) {
  122. let baseProd = makeCost(buildings[type].prod);
  123. applyProductivityMultipliers(type, baseProd);
  124. applyGlobalProdBonus(baseProd);
  125. scaleCost(baseProd, belongings[type].count);
  126. return baseProd;
  127. }
  128. function makeCost(source) {
  129. const empty = mapObject(resourceTypes, () => 0);
  130. return {...empty, ...source};
  131. }
  132. function addCost(cost1, cost2) {
  133. return Object.keys(resourceTypes).reduce((o, k) => {o[k] += cost2[k]; return o;}, cost1);
  134. }
  135. function scaleCost(cost, scale) {
  136. return Object.keys(resourceTypes).reduce((o, k) => {o[k] *= scale; return o;}, cost);
  137. }
  138. function costOfBuilding(type, count = 1) {
  139. let total = makeCost();
  140. while (count > 0) {
  141. let baseCost = makeCost(buildings[type].cost);
  142. baseCost = scaleCost(baseCost, Math.pow(1.15, belongings[type].count + count - 1));
  143. total = addCost(total, baseCost);
  144. count--;
  145. }
  146. return mapObject(total, round);
  147. }
  148. function buildingCount() {
  149. if (controlHeld) {
  150. return 10;
  151. } else if (shiftHeld) {
  152. return 5;
  153. } else {
  154. return 1;
  155. }
  156. }
  157. function buyBuilding(type, e) {
  158. const count = buildingCount();
  159. let cost = costOfBuilding(type, count);
  160. if (canAfford(cost)) {
  161. spend(cost);
  162. belongings[type].count += count;
  163. }
  164. updateProductivity();
  165. }
  166. function updateAll() {
  167. updateProductivity();
  168. updateClickVictim();
  169. updateOptions();
  170. }
  171. function updateOptions() {
  172. cache.optionButtons.numbers.innerText = "Number mode: " + numberMode.name;
  173. }
  174. // update stuff
  175. function updateDisplay() {
  176. let newTime = performance.now();
  177. let delta = newTime - lastTime;
  178. lastTime = newTime;
  179. addResources(delta);
  180. displayResources();
  181. displayBuildings();
  182. displayUpgrades(showOwnedUpgrades);
  183. tickPowerups(delta);
  184. setTimeout(updateDisplay, 1000 / updateRate);
  185. }
  186. function updateProductivity() {
  187. Object.assign(currentProductivity, calculateProductivity());
  188. // maybe this should go somewhere else - it also does clicking...
  189. updateClickPowers();
  190. Object.entries(activePowerups).forEach(([key, entry]) => {
  191. if (entry.life > 0) {
  192. const powerup = entry.powerup;
  193. powerup.effect(state);
  194. }
  195. });
  196. }
  197. function addResources(delta) {
  198. for (const [resource, amount] of Object.entries(currentProductivity)) {
  199. resources[resource] += amount * delta / 1000;
  200. }
  201. }
  202. function displayResources() {
  203. document.title = "Gorge - " + round(resources.food) + " food";
  204. Object.keys(resources).forEach(key => {
  205. cache.resourceLabels[key].quantity.innerText = render(resources[key]) + " " + resourceTypes[key].name;
  206. if (resourceTypes[key].generated)
  207. cache.resourceLabels[key].rate.innerText = render(currentProductivity[key]) + " " + resourceTypes[key].name + "/sec";
  208. })
  209. }
  210. function displayBuildings() {
  211. const count = buildingCount();
  212. for (const [key, value] of Object.entries(belongings)) {
  213. let available = states.buildings[key].available;
  214. if (!belongings[key].visible) {
  215. if (resources.food * 10 >= costOfBuilding(key).food) {
  216. unlockBuilding(key);
  217. } if (belongings[key].count > 0) {
  218. unlockBuilding(key);
  219. } else {
  220. continue;
  221. }
  222. belongings[key].visible = true;
  223. let button = cache.buildingButtons[key].button;
  224. button.classList.remove("hidden");
  225. }
  226. let button = cache.buildingButtons[key].button;
  227. let name = cache.buildingButtons[key].name;
  228. let cost = cache.buildingButtons[key].cost;
  229. const buildingCost = costOfBuilding(key, count);
  230. const newName = value.count + " " + (value.count == 1 ? buildings[key].name : buildings[key].plural);
  231. if (newName != states.buildings[key].name) {
  232. name.innerText = newName;
  233. states.buildings[key].name = newName;
  234. }
  235. const newCost = render(buildingCost.food) + " food";
  236. if (newCost != states.buildings[key].cost) {
  237. cost.innerText = newCost;
  238. states.buildings[key].cost = newCost;
  239. }
  240. if (canAfford(buildingCost) && available !== true) {
  241. button.classList.remove("building-button-disabled");
  242. cost.classList.add("building-button-cost-valid");
  243. states.buildings[key].available = true;
  244. } else if (!canAfford(buildingCost) && available !== false) {
  245. button.classList.add("building-button-disabled");
  246. cost.classList.add("building-button-cost-invalid");
  247. states.buildings[key].available = false;
  248. }
  249. }
  250. }
  251. function canAfford(cost) {
  252. for (const [resource, amount] of Object.entries(cost)) {
  253. if (resources[resource] < amount) {
  254. return false;
  255. }
  256. }
  257. return true;
  258. }
  259. function spend(cost) {
  260. for (const [resource, amount] of Object.entries(cost)) {
  261. resources[resource] -= amount;
  262. }
  263. }
  264. function switchShowOwnedUpgrades() {
  265. initializeUpgradeStates();
  266. if (showOwnedUpgrades) {
  267. document.querySelector("#upgrades").innerText = "Upgrades";
  268. } else {
  269. document.querySelector("#upgrades").innerText = "Owned Upgrades";
  270. }
  271. showOwnedUpgrades = !showOwnedUpgrades;
  272. }
  273. function displayUpgrades(owned) {
  274. if (owned) {
  275. Object.entries(ownedUpgrades).forEach(([key, val]) => {
  276. let button = cache.upgradeButtons[key];
  277. if (val) {
  278. button.classList.remove("hidden");
  279. } else {
  280. button.classList.add("hidden");
  281. }
  282. });
  283. }
  284. else {
  285. for (let id of remainingUpgrades) {
  286. let button = cache.upgradeButtons[id];
  287. let visible = states.upgrades[id].visible;
  288. let available = states.upgrades[id].available;
  289. if (ownedUpgrades[id] && visible !== false) {
  290. button.classList.add("hidden");
  291. states.upgrades[id].visible = false;
  292. continue;
  293. }
  294. if (upgradeReachable(id) && visible !== true) {
  295. button.classList.remove("hidden");
  296. states.upgrades[id].visible = true;
  297. } else if (!upgradeReachable(id) && visible !== false) {
  298. button.classList.add("hidden");
  299. states.upgrades[id].visible = false;
  300. }
  301. if (upgradeAvailable(id) && available !== true) {
  302. button.classList.remove("upgrade-button-inactive");
  303. states.upgrades[id].available = true;
  304. } else if (!upgradeAvailable(id) && available !== false) {
  305. button.classList.add("upgrade-button-inactive");
  306. states.upgrades[id].available = false;
  307. }
  308. }
  309. // we aren't trimming the list of upgrades now
  310. // because we need to switch between owned and unowned upgrades
  311. // - thus we need to be able to show or hide anything
  312. /*
  313. for (let i = remainingUpgrades.length-1; i >= 0; i--) {
  314. if (ownedUpgrades[remainingUpgrades[i]]) {
  315. remainingUpgrades.splice(i, 1);
  316. }
  317. }*/
  318. }
  319. }
  320. function updateClickPowers() {
  321. let bonus = 0;
  322. clickPowers.clickMultiplier = 1;
  323. for (let effect of effects["click"]) {
  324. if (ownedUpgrades[effect.parent]) {
  325. bonus = effect.apply(bonus, currentProductivity["food"]);
  326. }
  327. }
  328. clickPowers.clickBonus = bonus;
  329. }
  330. function updateClickVictim() {
  331. for (let effect of effects["click-victim"]) {
  332. if (ownedUpgrades[effect.parent]) {
  333. clickPowers.clickVictim = effect.id;
  334. document.querySelector("#tasty-micro").innerText = "Eat " + buildings[effect.id].name;
  335. }
  336. }
  337. }
  338. function buyUpgrade(id, e) {
  339. if (ownedUpgrades[id]) {
  340. return;
  341. }
  342. let upgrade = upgrades[id];
  343. if (!upgradeAvailable(id)) {
  344. return;
  345. }
  346. spend(upgrade.cost);
  347. ownedUpgrades[id] = true;
  348. let text = "Bought " + upgrade.name + "!";
  349. clickPopup(text, "upgrade", [e.clientX, e.clientY]);
  350. updateProductivity();
  351. updateClickVictim();
  352. }
  353. function eatPrey() {
  354. const add = clickPowers.clickMultiplier * (buildings[clickPowers.clickVictim]["prod"].food * 10 + clickPowers.clickBonus);
  355. resources.food += add;
  356. return add;
  357. }
  358. // setup stuff lol
  359. // we'll initialize the dict of buildings we can own
  360. function setup() {
  361. // create static data
  362. createTemplateUpgrades();
  363. // prepare dynamic stuff
  364. initializeData();
  365. createButtons();
  366. createDisplays();
  367. registerListeners();
  368. load();
  369. unlockAtStart();
  370. initializeCaches();
  371. initializeStates();
  372. updateAll();
  373. }
  374. const cache = {};
  375. function initializeCaches() {
  376. const buildingButtons = {};
  377. for (const [key, value] of Object.entries(belongings)) {
  378. let button = document.querySelector("#building-" + key);
  379. let name = document.querySelector("#building-" + key + " > .building-button-name");
  380. let cost = document.querySelector("#building-" + key + " > .building-button-cost");
  381. buildingButtons[key] = {
  382. button: button,
  383. name: name,
  384. cost: cost
  385. }
  386. }
  387. cache.buildingButtons = buildingButtons;
  388. const upgradeButtons = {};
  389. Object.keys(upgrades).forEach(key => {
  390. upgradeButtons[key] = document.querySelector("#upgrade-" + key);
  391. });
  392. cache.upgradeButtons = upgradeButtons;
  393. const resourceLabels = {};
  394. Object.keys(resourceTypes).forEach(key => {
  395. resourceLabels[key] = {
  396. quantity: document.querySelector("#resource-quantity-" + key),
  397. }
  398. if (resourceTypes[key].generated)
  399. resourceLabels[key].rate = document.querySelector("#resource-rate-" + key);
  400. });
  401. cache.resourceLabels = resourceLabels;
  402. const optionButtons = {};
  403. optionButtons.numbers = document.querySelector("#numbers");
  404. cache.optionButtons = optionButtons;
  405. }
  406. const states = {};
  407. // we can keep track of some things, like whether
  408. // specific upgrades are currently visible. this
  409. // way, we don't have to set them visible every tick;
  410. // we can just check if they've been handled already
  411. function initializeStates() {
  412. initializeBuildingStates();
  413. initializeUpgradeStates();
  414. }
  415. function initializeBuildingStates() {
  416. const buildingStates = {};
  417. Object.keys(buildings).forEach(key => {
  418. buildingStates[key] = {
  419. visible: undefined,
  420. available: undefined,
  421. name: undefined,
  422. cost: undefined
  423. }
  424. });
  425. states.buildings = buildingStates;
  426. }
  427. function initializeUpgradeStates() {
  428. const upgradeStates = {};
  429. Object.keys(upgrades).forEach(key => {
  430. upgradeStates[key] = {
  431. visible: undefined,
  432. available: undefined
  433. }
  434. });
  435. states.upgrades = upgradeStates;
  436. }
  437. function unlockAtStart() {
  438. unlockBuilding("micro");
  439. for (const [key, value] of Object.entries(belongings)) {
  440. if (belongings[key].visible) {
  441. unlockBuilding(key);
  442. }
  443. }
  444. }
  445. function unlockBuilding(id) {
  446. belongings[id].visible = true;
  447. document.querySelector("#building-" + id).classList.remove("hidden");
  448. }
  449. function initializeData() {
  450. for (const [key, value] of Object.entries(buildings)) {
  451. belongings[key] = {};
  452. belongings[key].count = 0;
  453. belongings[key].visible = false;
  454. contributions[key] = makeCost();
  455. }
  456. for (const [key, value] of Object.entries(resourceTypes)) {
  457. resources[key] = 0;
  458. currentProductivity[key] = 0;
  459. }
  460. for (const [id, upgrade] of Object.entries(upgrades)) {
  461. ownedUpgrades[id] = false;
  462. for (let effect of upgrade.effects) {
  463. if (effects[effect.type] === undefined) {
  464. effects[effect.type] = [];
  465. }
  466. // copy the data and add an entry for the upgrade id that owns the effect
  467. let newEffect = {};
  468. for (const [key, value] of Object.entries(effect)) {
  469. newEffect[key] = value;
  470. }
  471. newEffect.parent = id;
  472. // unfortunate name collision here
  473. // I'm using apply() to pass on any number of arguments to the
  474. // apply() function of the effect type
  475. newEffect.apply = function (...args) { return effect_types[effect.type].apply.apply(null, [effect].concat(args)); }
  476. effects[effect.type].push(newEffect);
  477. }
  478. }
  479. Object.keys(powerups).filter(x => powerups[x].duration !== undefined).forEach(key => activePowerups[key] = {
  480. life: 0
  481. });
  482. }
  483. function registerListeners() {
  484. document.querySelector("#tasty-micro").addEventListener("click", (e) => {
  485. const add = eatPrey();
  486. const text = "+" + render(round(add, 1), 3) + " food";
  487. const gulp = "*glp*";
  488. clickPopup(text, "food", [e.clientX, e.clientY]);
  489. clickPopup(gulp, "gulp", [e.clientX, e.clientY]);
  490. });
  491. document.querySelector("#save").addEventListener("click", save);
  492. document.querySelector("#reset").addEventListener("click", reset);
  493. document.querySelector("#numbers").addEventListener("click", cycleNumbers);
  494. document.querySelector("#upgrades").addEventListener("click", switchShowOwnedUpgrades);
  495. document.addEventListener("keydown", e => {
  496. shiftHeld = e.shiftKey;
  497. controlHeld = e.ctrlKey;
  498. if (mouseTarget)
  499. mouseTarget.dispatchEvent(new Event("mousemove"));
  500. return true;
  501. });
  502. document.addEventListener("keyup", e => {
  503. shiftHeld = e.shiftKey;
  504. controlHeld = e.ctrlKey;
  505. if (mouseTarget)
  506. mouseTarget.dispatchEvent(new Event("mousemove"));
  507. return true;
  508. });
  509. }
  510. function createButtons() {
  511. createBuildings();
  512. createUpgrades();
  513. }
  514. function createBuildings() {
  515. let container = document.querySelector("#buildings-list");
  516. for (const [key, value] of Object.entries(buildings)) {
  517. let button = document.createElement("div");
  518. button.classList.add("building-button");
  519. button.classList.add("hidden");
  520. button.id = "building-" + key;
  521. let buttonName = document.createElement("div");
  522. buttonName.classList.add("building-button-name");
  523. let buttonCost = document.createElement("div");
  524. buttonCost.classList.add("building-button-cost");
  525. let buildingIcon = document.createElement("i");
  526. buildingIcon.classList.add("fas");
  527. buildingIcon.classList.add(value.icon);
  528. button.appendChild(buttonName);
  529. button.appendChild(buttonCost);
  530. button.appendChild(buildingIcon);
  531. button.addEventListener("mousemove", function (e) { mouseTarget = button; buildingTooltip(key, e); });
  532. button.addEventListener("mouseleave", function () { mouseTarget = undefined; buildingTooltipRemove(); });
  533. button.addEventListener("click", function (e) { buyBuilding(key, e); });
  534. button.addEventListener("click", function (e) { buildingTooltip(key, e); });
  535. container.appendChild(button);
  536. }
  537. }
  538. // do we have previous techs and at least one of each building?
  539. function upgradeReachable(id) {
  540. if (ownedUpgrades[id]) {
  541. return false;
  542. }
  543. if (upgrades[id].prereqs !== undefined) {
  544. for (const [type, reqs] of Object.entries(upgrades[id].prereqs)) {
  545. if (type == "buildings") {
  546. for (const [building, amount] of Object.entries(reqs)) {
  547. if (belongings[building].count == 0) {
  548. return false;
  549. }
  550. }
  551. }
  552. else if (type == "upgrades") {
  553. for (let upgrade of reqs) {
  554. if (!ownedUpgrades[upgrade]) {
  555. return false;
  556. }
  557. }
  558. }
  559. else if (type == "resources") {
  560. for (const [resource, amount] of Object.entries(reqs)) {
  561. if (resources[resource] < amount) {
  562. return false;
  563. }
  564. };
  565. }
  566. }
  567. }
  568. return true;
  569. }
  570. function upgradeAvailable(id) {
  571. if (!upgradeReachable(id)) {
  572. return false;
  573. }
  574. if (!canAfford(upgrades[id].cost)) {
  575. return false;
  576. }
  577. if (upgrades[id].prereqs !== undefined) {
  578. for (const [type, reqs] of Object.entries(upgrades[id].prereqs)) {
  579. if (type == "buildings") {
  580. for (const [building, amount] of Object.entries(upgrades[id].prereqs[type])) {
  581. if (belongings[building].count < amount) {
  582. return false;
  583. }
  584. }
  585. } else if (type == "productivity") {
  586. for (const [key, value] of Object.entries(reqs)) {
  587. if (currentProductivity[key] < value) {
  588. return false;
  589. }
  590. }
  591. }
  592. }
  593. }
  594. return true;
  595. }
  596. function createUpgrades() {
  597. let container = document.querySelector("#upgrades-list");
  598. for (const [key, value] of Object.entries(upgrades)) {
  599. remainingUpgrades.push(key);
  600. let button = document.createElement("div");
  601. button.classList.add("upgrade-button");
  602. button.classList.add("hidden");
  603. button.id = "upgrade-" + key;
  604. let buttonName = document.createElement("div");
  605. buttonName.classList.add("upgrade-button-name");
  606. buttonName.innerText = value.name;
  607. let upgradeIcon = document.createElement("i");
  608. upgradeIcon.classList.add("fas");
  609. upgradeIcon.classList.add(value.icon);
  610. button.appendChild(buttonName);
  611. button.appendChild(upgradeIcon);
  612. button.addEventListener("mouseenter", function (e) { mouseTarget = button; upgradeTooltip(key, e); });
  613. button.addEventListener("mousemove", function (e) { mouseTarget = button; upgradeTooltip(key, e); });
  614. button.addEventListener("mouseleave", function () { mouseTarget = undefined; upgradeTooltipRemove(); });
  615. button.addEventListener("click", function (e) { buyUpgrade(key, e); });
  616. container.appendChild(button);
  617. }
  618. }
  619. function createDisplays() {
  620. const resourceList = document.querySelector("#resource-list");
  621. Object.keys(resourceTypes).forEach(key => {
  622. const quantity = document.createElement("div");
  623. quantity.classList.add("resource-quantity");
  624. quantity.id = "resource-quantity-" + key;
  625. resourceList.appendChild(quantity);
  626. if (resourceTypes[key].generated) {
  627. const rate = document.createElement("div");
  628. rate.classList.add("resource-rate");
  629. rate.id = "resource-rate-" + key;
  630. resourceList.appendChild(rate);
  631. }
  632. })
  633. }
  634. function renderLine(line) {
  635. let div = document.createElement("div");
  636. div.innerText = line.text;
  637. if (line.valid !== undefined) {
  638. if (line.valid) {
  639. div.classList.add("cost-met");
  640. } else {
  641. div.classList.add("cost-unmet");
  642. }
  643. }
  644. if (line.class !== undefined) {
  645. for (let entry of line.class.split(",")) {
  646. div.classList.add(entry);
  647. }
  648. }
  649. return div;
  650. }
  651. function renderLines(lines) {
  652. let divs = [];
  653. for (let line of lines) {
  654. divs.push(renderLine(line));
  655. }
  656. return divs;
  657. }
  658. function renderCost(cost) {
  659. let list = [];
  660. list.push({
  661. "text": "Cost:"
  662. });
  663. for (const [key, value] of Object.entries(cost)) {
  664. list.push({
  665. "text": render(value, 0) + " " + resourceTypes[key].name,
  666. "valid": resources[key] >= value
  667. });
  668. }
  669. return renderLines(list);
  670. }
  671. function renderPrereqs(prereqs) {
  672. let list = [];
  673. if (prereqs === undefined) {
  674. return renderLines(list);
  675. }
  676. list.push({
  677. "text": "Own:"
  678. });
  679. for (const [key, value] of Object.entries(prereqs)) {
  680. if (key == "buildings") {
  681. for (const [id, amount] of Object.entries(prereqs.buildings)) {
  682. list.push({
  683. "text": buildings[id].name + " x" + render(amount, 0),
  684. "valid": belongings[id].count >= amount
  685. });
  686. }
  687. } else if (key == "productivity") {
  688. for (const [id, amount] of Object.entries(prereqs.productivity)) {
  689. list.push({
  690. "text": render(amount, 0) + " " + resourceTypes[id].name + "/s",
  691. "valid": currentProductivity[id] >= amount
  692. });
  693. }
  694. }
  695. }
  696. return renderLines(list);
  697. }
  698. function renderEffects(effectList) {
  699. let list = [];
  700. for (let effect of effectList) {
  701. list.push({ "text": effect_types[effect.type].desc(effect) });
  702. }
  703. return renderLines(list);
  704. }
  705. function clickPopup(text, type, location) {
  706. const div = document.createElement("div");
  707. div.textContent = text;
  708. div.classList.add("click-popup-" + type);
  709. var direction;
  710. if (type == "food") {
  711. direction = -150;
  712. } else if (type == "gulp") {
  713. direction = -150;
  714. } else if (type == "upgrade") {
  715. direction = -50;
  716. } else if (type == "info") {
  717. direction = 0;
  718. }
  719. direction *= Math.random() * 0.5 + 1;
  720. direction = Math.round(direction) + "px"
  721. div.style.setProperty("--target", direction)
  722. div.style.left = location[0] + "px";
  723. div.style.top = location[1] + "px";
  724. const body = document.querySelector("body");
  725. body.appendChild(div);
  726. setTimeout(() => {
  727. body.removeChild(div);
  728. }, 2000);
  729. }
  730. function doNews() {
  731. let options = [];
  732. news.forEach(entry => {
  733. if (entry.condition(state)) {
  734. options = options.concat(entry.lines);
  735. }
  736. });
  737. const choice = Math.floor(Math.random() * options.length);
  738. showNews(options[choice](state));
  739. setTimeout(() => {
  740. doNews();
  741. }, 15000 + Math.random() * 2500);
  742. }
  743. function showNews(text) {
  744. const div = document.createElement("div");
  745. div.textContent = text;
  746. div.classList.add("news-text");
  747. const body = document.querySelector("body");
  748. body.appendChild(div);
  749. setTimeout(() => {
  750. body.removeChild(div);
  751. }, 10000);
  752. }
  753. function doPowerup() {
  754. const lifetime = 10000;
  755. const button = document.createElement("div");
  756. const left = Math.round(Math.random() * 50 + 25) + "%";
  757. const top = Math.round(Math.random() * 50 + 25) + "%";
  758. button.classList.add("powerup");
  759. button.style.setProperty("--lifetime", lifetime / 1000 + "s");
  760. button.style.setProperty("--leftpos", left);
  761. button.style.setProperty("--toppos", top);
  762. const body = document.querySelector("body");
  763. body.appendChild(button);
  764. const choices = [];
  765. Object.entries(powerups).forEach(([key, val]) => {
  766. if (val.prereqs(state))
  767. choices.push(key);
  768. });
  769. const choice = Math.floor(Math.random() * choices.length);
  770. const powerup = powerups[choices[choice]];
  771. const icon = document.createElement("div");
  772. icon.classList.add("fas");
  773. icon.classList.add(powerup.icon);
  774. button.appendChild(icon);
  775. const remove = setTimeout(() => {
  776. body.removeChild(button);
  777. }, lifetime);
  778. let delay = 60000 + Math.random() * 30000;
  779. for (let effect of effects["powerup-freq"]) {
  780. if (ownedUpgrades[effect.parent]) {
  781. delay = effect.apply(delay);
  782. }
  783. }
  784. setTimeout(() => {
  785. doPowerup();
  786. }, delay);
  787. button.addEventListener("mousedown", e => {
  788. if (powerup.duration !== undefined) {
  789. addPowerup(choices[choice], powerup);
  790. } else {
  791. powerup.effect(state);
  792. }
  793. powerup.popup(powerup, e);
  794. button.classList.add("powerup-clicked");
  795. resources.powerups += 1;
  796. clearTimeout(remove);
  797. setTimeout(() => {
  798. body.removeChild(button);
  799. }, 500);
  800. });
  801. }
  802. function fillTooltip(type, field, content) {
  803. let item = document.querySelector("#" + type + "-tooltip-" + field);
  804. if (typeof (content) === "string") {
  805. item.innerText = content;
  806. } else {
  807. replaceChildren(item, content);
  808. }
  809. }
  810. function upgradeTooltip(id, event) {
  811. let tooltip = document.querySelector("#upgrade-tooltip");
  812. tooltip.style.setProperty("display", "inline-block");
  813. fillTooltip("upgrade", "name", upgrades[id].name);
  814. fillTooltip("upgrade", "desc", upgrades[id].desc);
  815. fillTooltip("upgrade", "effect", renderEffects(upgrades[id].effects));
  816. fillTooltip("upgrade", "cost", renderCost(upgrades[id].cost));
  817. fillTooltip("upgrade", "prereqs", renderPrereqs(upgrades[id].prereqs));
  818. let yOffset = tooltip.parentElement.getBoundingClientRect().y;
  819. let tooltipSize = tooltip.getBoundingClientRect().height;
  820. let yTrans = Math.round(event.clientY - yOffset);
  821. var body = document.body,
  822. html = document.documentElement;
  823. var height = Math.max(window.innerHeight);
  824. yTrans = Math.min(yTrans, height - tooltipSize - 150);
  825. tooltip.style.setProperty("transform", "translate(-220px, " + yTrans + "px)");
  826. }
  827. function upgradeTooltipRemove() {
  828. let tooltip = document.querySelector("#upgrade-tooltip");
  829. tooltip.style.setProperty("display", "none");
  830. }
  831. function prodSummary(id) {
  832. let list = [];
  833. list.push(
  834. { "text": "Each " + buildings[id].name + " produces " + render(belongings[id].count == 0 ? 0 : contributions[id].food / belongings[id].count, 3) + " food/sec" }
  835. );
  836. list.push(
  837. { "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" }
  838. );
  839. let percentage = round(100 * contributions[id].food / currentProductivity["food"], 2);
  840. if (isNaN(percentage)) {
  841. percentage = 0;
  842. }
  843. list.push(
  844. { "text": "(" + percentage + "% of all food)" }
  845. );
  846. return renderLines(list);
  847. }
  848. function buildingTooltip(id, event) {
  849. let tooltip = document.querySelector("#building-tooltip");
  850. tooltip.style.setProperty("display", "inline-block");
  851. const count = buildingCount();
  852. fillTooltip("building", "name", (count != 1 ? count + "x " : "") + buildings[id].name);
  853. fillTooltip("building", "desc", buildings[id].desc);
  854. fillTooltip("building", "cost", render(costOfBuilding(id, count).food) + " food");
  855. fillTooltip("building", "prod", prodSummary(id));
  856. let xPos = tooltip.parentElement.getBoundingClientRect().x - 450;
  857. // wow browsers are bad
  858. var body = document.body,
  859. html = document.documentElement;
  860. var height = Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight);
  861. let yPos = Math.min(event.clientY, height - 200);
  862. tooltip.style.setProperty("transform", "translate(" + xPos + "px, " + yPos + "px)")
  863. }
  864. function buildingTooltipRemove() {
  865. let tooltip = document.querySelector("#building-tooltip");
  866. tooltip.style.setProperty("display", "none");
  867. }
  868. window.onload = function () {
  869. setup();
  870. lastTime = performance.now();
  871. doNews();
  872. doPowerup();
  873. setTimeout(updateDisplay, 1000 / updateRate);
  874. setTimeout(autosave, 60000);
  875. }
  876. function autosave() {
  877. saveGame();
  878. let x = window.innerWidth / 2;
  879. let y = window.innerHeight * 9 / 10;
  880. clickPopup("Autosaving...", "info", [x, y]);
  881. setTimeout(autosave, 60000);
  882. }
  883. function save(e) {
  884. saveGame();
  885. clickPopup("Saved!", "info", [e.clientX, e.clientY]);
  886. }
  887. function saveGame() {
  888. try {
  889. let storage = window.localStorage;
  890. storage.setItem("save-version", 1);
  891. storage.setItem("ownedUpgrades", JSON.stringify(ownedUpgrades));
  892. storage.setItem("resources", JSON.stringify(resources));
  893. storage.setItem("belongings", JSON.stringify(belongings));
  894. } catch (e) {
  895. clickPopup("Can't save - no access to local storage.", "info", [window.innerWidth / 2, window.innerHeight / 5]);
  896. }
  897. }
  898. function load() {
  899. try {
  900. let storage = window.localStorage;
  901. if (!storage.getItem("save-version")) {
  902. return;
  903. }
  904. let newOwnedUpgrades = JSON.parse(storage.getItem("ownedUpgrades"));
  905. for (const [key, value] of Object.entries(newOwnedUpgrades)) {
  906. ownedUpgrades[key] = value;
  907. }
  908. let newResources = JSON.parse(storage.getItem("resources"));
  909. for (const [key, value] of Object.entries(newResources)) {
  910. resources[key] = value;
  911. }
  912. let newBelongings = JSON.parse(storage.getItem("belongings"));
  913. for (const [key, value] of Object.entries(newBelongings)) {
  914. belongings[key] = value;
  915. }
  916. } catch (e) {
  917. clickPopup("Can't load - no access to local storage.", "info", [window.innerWidth / 2, window.innerHeight / 5]);
  918. }
  919. }
  920. function reset() {
  921. window.localStorage.clear();
  922. }
  923. function cycleNumbers() {
  924. numberMode = numberModes[numberMode.next];
  925. updateOptions();
  926. }