cookie clicker but bigger
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 

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