cookie clicker but bigger
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 

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