cookie clicker but bigger
Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.
 
 
 
 

801 řádky
19 KiB

  1. "use strict";
  2. let belongings = {};
  3. let ownedUpgrades = {};
  4. let remainingUpgrades = [];
  5. let showOwnedUpgrades = false;
  6. let effects = {};
  7. let resources = {};
  8. let updateRate = 60;
  9. let currentProductivity = {};
  10. let clickBonus = 0;
  11. let clickVictim = "micro";
  12. let lastTime = 0;
  13. function applyGlobalProdBonuses(productivity) {
  14. for (let effect of effects["prod-all"]) {
  15. if (ownedUpgrades[effect.parent]) {
  16. productivity = effect.apply(productivity);
  17. }
  18. }
  19. return productivity;
  20. }
  21. function calculateProductivity() {
  22. let productivity = 0;
  23. for (const [key, value] of Object.entries(belongings)) {
  24. productivity += productivityOf(key);
  25. }
  26. return productivity;
  27. }
  28. // here's where upgrades will go :3
  29. function productivityMultiplierOf(type) {
  30. let base = 1;
  31. for (let effect of effects["prod"]) {
  32. if (ownedUpgrades[effect.parent] && effect.target == type) {
  33. base = effect.apply(base);
  34. }
  35. }
  36. for (let effect of effects["helper"]) {
  37. if (ownedUpgrades[effect.parent] && effect.helped == type) {
  38. base = effect.apply(base, belongings[effect.helper].count);
  39. }
  40. }
  41. return base;
  42. }
  43. function productivityOf(type) {
  44. let baseProd = buildings[type].prod;
  45. let prod = baseProd * productivityMultiplierOf(type);
  46. prod = applyGlobalProdBonuses(prod);
  47. return prod * belongings[type].count;
  48. }
  49. function costOfBuilding(type) {
  50. let baseCost = buildings[type].cost
  51. let countCost = baseCost * Math.pow(1.15, belongings[type].count);
  52. return Math.round(countCost);
  53. }
  54. function buyBuilding(type) {
  55. let cost = costOfBuilding(type);
  56. if (resources.food >= cost) {
  57. belongings[type].count += 1;
  58. resources.food -= cost;
  59. }
  60. updateProductivity();
  61. updateClickBonus();
  62. }
  63. // update stuff
  64. function updateDisplay() {
  65. let newTime = performance.now();
  66. let delta = newTime - lastTime;
  67. lastTime = newTime;
  68. addResources(delta);
  69. displayResources();
  70. displayBuildings();
  71. displayUpgrades(showOwnedUpgrades);
  72. setTimeout(updateDisplay, 1000/updateRate);
  73. }
  74. function updateProductivity() {
  75. currentProductivity["food"] = calculateProductivity();
  76. }
  77. function addResources(delta) {
  78. for (const [resource, amount] of Object.entries(currentProductivity)) {
  79. resources[resource] += amount * delta / 1000;
  80. }
  81. }
  82. function displayResources() {
  83. document.title = "Gorge - " + round(resources.food) + " food";
  84. replaceChildren(document.querySelector("#resource-list"), renderResources());
  85. }
  86. function renderResources() {
  87. let list = [];
  88. for (const [key, value] of Object.entries(resources)) {
  89. let line1 = render(value, 3, 0) + " " + resourceTypes[key].name;
  90. let line2 = render(currentProductivity[key], 1, 1) + " " + resourceTypes[key].name + "/sec";
  91. list.push({"text": line1, "class": "resource-quantity"});
  92. list.push({"text": line2, "class": "resource-rate"});
  93. }
  94. return renderLines(list);
  95. }
  96. function displayBuildings() {
  97. for (const [key, value] of Object.entries(belongings)) {
  98. if (!belongings[key].visible) {
  99. if (resources.food * 10 >= costOfBuilding(key)) {
  100. unlockBuilding(key);
  101. } else {
  102. continue;
  103. }
  104. belongings[key].visible = true;
  105. document.querySelector("#building-" + key).classList.remove("hidden");
  106. }
  107. let button = document.querySelector("#building-" + key);
  108. let name = document.querySelector("#building-" + key + " > .building-button-name");
  109. let cost = document.querySelector("#building-" + key + " > .building-button-cost");
  110. name.innerText = value.count + " " + (value.count == 1 ? buildings[key].name : buildings[key].plural);
  111. cost.innerText = render(costOfBuilding(key)) + " food";
  112. if (costOfBuilding(key) > resources.food) {
  113. button.classList.add("building-button-disabled");
  114. cost.classList.add("building-button-cost-invalid");
  115. } else {
  116. button.classList.remove("building-button-disabled");
  117. cost.classList.add("building-button-cost-valid");
  118. }
  119. }
  120. }
  121. function canAfford(cost) {
  122. for (const [resource, amount] of Object.entries(cost)) {
  123. if (resources[resource] < amount) {
  124. return false;
  125. }
  126. }
  127. return true;
  128. }
  129. function spend(cost) {
  130. for (const [resource, amount] of Object.entries(cost)) {
  131. resources[resource] -= amount;
  132. }
  133. }
  134. function switchShowOwnedUpgrades() {
  135. if (showOwnedUpgrades) {
  136. document.querySelector("#upgrades").innerText = "Upgrades";
  137. } else {
  138. document.querySelector("#upgrades").innerText = "Owned Upgrades";
  139. }
  140. showOwnedUpgrades = !showOwnedUpgrades;
  141. }
  142. function displayUpgrades(owned) {
  143. if (owned) {
  144. Object.entries(ownedUpgrades).forEach(([key, val]) => {
  145. let button = document.querySelector("#upgrade-" + key);
  146. if (val) {
  147. button.classList.remove("hidden");
  148. } else {
  149. button.classList.add("hidden");
  150. }
  151. });
  152. }
  153. else {
  154. for (let id of remainingUpgrades) {
  155. let button = document.querySelector("#upgrade-" + id);
  156. if (ownedUpgrades[id]) {
  157. button.classList.add("hidden");
  158. continue;
  159. }
  160. if (upgradeReachable(id)) {
  161. button.classList.remove("hidden");
  162. } else {
  163. button.classList.add("hidden");
  164. }
  165. if (upgradeAvailable(id)) {
  166. button.classList.remove("upgrade-button-inactive");
  167. } else {
  168. button.classList.add("upgrade-button-inactive");
  169. }
  170. }
  171. // we aren't trimming the list of upgrades now
  172. // because we need to switch between owned and unowned upgrades
  173. // - thus we need to be able to show or hide anything
  174. /*
  175. for (let i = remainingUpgrades.length-1; i >= 0; i--) {
  176. if (ownedUpgrades[remainingUpgrades[i]]) {
  177. remainingUpgrades.splice(i, 1);
  178. }
  179. }*/
  180. }
  181. }
  182. function updateClickBonus() {
  183. let bonus = 0;
  184. for (let effect of effects["click"]) {
  185. if (ownedUpgrades[effect.parent]) {
  186. bonus = effect.apply(bonus, currentProductivity["food"]);
  187. }
  188. }
  189. clickBonus = bonus;
  190. }
  191. function updateClickVictim() {
  192. for (let effect of effects["click-victim"]) {
  193. if (ownedUpgrades[effect.parent]) {
  194. clickVictim = effect.id;
  195. document.querySelector("#tasty-micro").innerText = "Eat " + buildings[effect.id].name;
  196. }
  197. }
  198. }
  199. function buyUpgrade(id, e) {
  200. if (ownedUpgrades[id]) {
  201. return;
  202. }
  203. let upgrade = upgrades[id];
  204. if (!upgradeAvailable(id)) {
  205. return;
  206. }
  207. spend(upgrade.cost);
  208. ownedUpgrades[id] = true;
  209. let text = "Bought " + upgrade.name + "!";
  210. clickPopup(text, "upgrade", [e.clientX, e.clientY]);
  211. updateProductivity();
  212. updateClickBonus();
  213. updateClickVictim();
  214. }
  215. function eatPrey() {
  216. const add = buildings[clickVictim]["prod"] * 10 * productivityMultiplierOf(clickVictim) + clickBonus;
  217. resources.food += add;
  218. return add;
  219. }
  220. // setup stuff lol
  221. // we'll initialize the dict of buildings we can own
  222. function setup() {
  223. // create static data
  224. createTemplateUpgrades();
  225. // prepare dynamic stuff
  226. initializeData();
  227. createButtons();
  228. createDisplays();
  229. registerListeners();
  230. load();
  231. unlockAtStart();
  232. updateProductivity();
  233. }
  234. function unlockAtStart() {
  235. unlockBuilding("micro");
  236. for (const [key, value] of Object.entries(belongings)) {
  237. if (belongings[key].visible) {
  238. unlockBuilding(key);
  239. }
  240. }
  241. }
  242. function unlockBuilding(id) {
  243. belongings[id].visible = true;
  244. document.querySelector("#building-" + id).classList.remove("hidden");
  245. }
  246. function initializeData() {
  247. for (const [key, value] of Object.entries(buildings)) {
  248. belongings[key] = {};
  249. belongings[key].count = 0;
  250. belongings[key].visible = false;
  251. }
  252. for (const [key, value] of Object.entries(resourceTypes)) {
  253. resources[key] = 0;
  254. currentProductivity[key] = 0;
  255. }
  256. for (const [id, upgrade] of Object.entries(upgrades)) {
  257. ownedUpgrades[id] = false;
  258. for (let effect of upgrade.effects) {
  259. if (effects[effect.type] === undefined) {
  260. effects[effect.type] = [];
  261. }
  262. // copy the data and add an entry for the upgrade id that owns the effect
  263. let newEffect = {};
  264. for (const [key, value] of Object.entries(effect)) {
  265. newEffect[key] = value;
  266. }
  267. newEffect.parent = id;
  268. // unfortunate name collision here
  269. // I'm using apply() to pass on any number of arguments to the
  270. // apply() function of the effect type
  271. newEffect.apply = function(...args) { return effect_types[effect.type].apply.apply(null, [effect].concat(args)); }
  272. effects[effect.type].push(newEffect);
  273. }
  274. }
  275. }
  276. function registerListeners() {
  277. document.querySelector("#tasty-micro").addEventListener("click", (e) => {
  278. const add = eatPrey();
  279. const text = "+" + round(add, 1) + " food";
  280. const gulp = "*glp*";
  281. clickPopup(text, "food", [e.clientX, e.clientY]);
  282. clickPopup(gulp, "gulp", [e.clientX, e.clientY]);
  283. });
  284. document.querySelector("#save").addEventListener("click", save);
  285. document.querySelector("#reset").addEventListener("click", reset);
  286. document.querySelector("#upgrades").addEventListener("click", switchShowOwnedUpgrades);
  287. }
  288. function createButtons() {
  289. createBuildings();
  290. createUpgrades();
  291. }
  292. function createBuildings() {
  293. let container = document.querySelector("#buildings-list");
  294. for (const [key, value] of Object.entries(buildings)) {
  295. let button = document.createElement("div");
  296. button.classList.add("building-button");
  297. button.classList.add("hidden");
  298. button.id = "building-" + key;
  299. let buttonName = document.createElement("div");
  300. buttonName.classList.add("building-button-name");
  301. let buttonCost = document.createElement("div");
  302. buttonCost.classList.add("building-button-cost");
  303. let buildingIcon = document.createElement("i");
  304. buildingIcon.classList.add("fas");
  305. buildingIcon.classList.add(value.icon);
  306. button.appendChild(buttonName);
  307. button.appendChild(buttonCost);
  308. button.appendChild(buildingIcon);
  309. button.addEventListener("mousemove", function(e) { buildingTooltip(key, e); });
  310. button.addEventListener("mouseleave", function() { buildingTooltipRemove(); });
  311. button.addEventListener("click", function() { buyBuilding(key); });
  312. button.addEventListener("click", function(e) { buildingTooltip(key, e); });
  313. container.appendChild(button);
  314. }
  315. }
  316. // do we have previous techs and at least one of each building?
  317. function upgradeReachable(id) {
  318. if (ownedUpgrades[id]) {
  319. return false;
  320. }
  321. if (upgrades[id].prereqs !== undefined ){
  322. for (const [type, reqs] of Object.entries(upgrades[id].prereqs)) {
  323. if (type == "buildings") {
  324. for (const [building, amount] of Object.entries(reqs)) {
  325. if (belongings[building].count == 0) {
  326. return false;
  327. }
  328. }
  329. }
  330. else if (type == "upgrades") {
  331. for (let upgrade of reqs) {
  332. if (!ownedUpgrades[upgrade]) {
  333. return false;
  334. }
  335. }
  336. }
  337. }
  338. }
  339. return true;
  340. }
  341. function upgradeAvailable(id) {
  342. if (!upgradeReachable(id)) {
  343. return false;
  344. }
  345. if (!canAfford(upgrades[id].cost)) {
  346. return false;
  347. }
  348. if (upgrades[id].prereqs !== undefined) {
  349. for (const [type, reqs] of Object.entries(upgrades[id].prereqs)) {
  350. if (type == "buildings") {
  351. for (const [building, amount] of Object.entries(upgrades[id].prereqs[type])) {
  352. if (belongings[building].count < amount) {
  353. return false;
  354. }
  355. }
  356. } else if (type == "productivity") {
  357. for (const [key, value] of Object.entries(reqs)) {
  358. if (currentProductivity[key] < value) {
  359. return false;
  360. }
  361. }
  362. }
  363. }
  364. }
  365. return true;
  366. }
  367. function createUpgrades() {
  368. let container = document.querySelector("#upgrades-list");
  369. for (const [key, value] of Object.entries(upgrades)) {
  370. remainingUpgrades.push(key);
  371. let button = document.createElement("div");
  372. button.classList.add("upgrade-button");
  373. button.classList.add("hidden");
  374. button.id = "upgrade-" + key;
  375. let buttonName = document.createElement("div");
  376. buttonName.classList.add("upgrade-button-name");
  377. buttonName.innerText = value.name;
  378. let upgradeIcon = document.createElement("i");
  379. upgradeIcon.classList.add("fas");
  380. upgradeIcon.classList.add(value.icon);
  381. button.appendChild(buttonName);
  382. button.appendChild(upgradeIcon);
  383. button.addEventListener("mouseenter", function(e) { upgradeTooltip(key, e); });
  384. button.addEventListener("mousemove", function(e) { upgradeTooltip(key, e); });
  385. button.addEventListener("mouseleave", function() { upgradeTooltipRemove(); });
  386. button.addEventListener("click", function(e) { buyUpgrade(key, e); });
  387. container.appendChild(button);
  388. }
  389. }
  390. function createDisplays() {
  391. // nop
  392. }
  393. function renderLine(line) {
  394. let div = document.createElement("div");
  395. div.innerText = line.text;
  396. if (line.valid !== undefined) {
  397. if (line.valid) {
  398. div.classList.add("cost-met");
  399. } else {
  400. div.classList.add("cost-unmet");
  401. }
  402. }
  403. if (line.class !== undefined) {
  404. for (let entry of line.class.split(",")) {
  405. div.classList.add(entry);
  406. }
  407. }
  408. return div;
  409. }
  410. function renderLines(lines) {
  411. let divs = [];
  412. for (let line of lines) {
  413. divs.push(renderLine(line));
  414. }
  415. return divs;
  416. }
  417. function renderCost(cost) {
  418. let list = [];
  419. list.push({
  420. "text": "Cost:"
  421. });
  422. for (const [key, value] of Object.entries(cost)) {
  423. list.push({
  424. "text": render(value,0) + " " + resourceTypes[key].name,
  425. "valid": resources[key] >= value
  426. });
  427. }
  428. return renderLines(list);
  429. }
  430. function renderPrereqs(prereqs) {
  431. let list = [];
  432. if (prereqs === undefined) {
  433. return renderLines(list);
  434. }
  435. list.push({
  436. "text": "Own:"
  437. });
  438. for (const [key, value] of Object.entries(prereqs)) {
  439. if (key == "buildings") {
  440. for (const [id, amount] of Object.entries(prereqs.buildings)) {
  441. list.push({
  442. "text": buildings[id].name + " x" + render(amount,0),
  443. "valid": belongings[id].count >= amount
  444. });
  445. }
  446. } else if (key == "productivity") {
  447. for (const [id, amount] of Object.entries(prereqs.productivity)) {
  448. list.push({
  449. "text": render(amount,0) + " " + resourceTypes[id].name + "/s",
  450. "valid": currentProductivity[id] >= amount
  451. });
  452. }
  453. }
  454. }
  455. return renderLines(list);
  456. }
  457. function renderEffects(effectList) {
  458. let list = [];
  459. for (let effect of effectList) {
  460. list.push({"text": effect_types[effect.type].desc(effect)});
  461. }
  462. return renderLines(list);
  463. }
  464. function clickPopup(text, type, location) {
  465. const div = document.createElement("div");
  466. div.textContent = text;
  467. div.classList.add("click-popup-" + type);
  468. var direction;
  469. if (type == "food") {
  470. direction = -150;
  471. } else if (type == "gulp") {
  472. direction = -150;
  473. } else if (type == "upgrade") {
  474. direction = -50;
  475. } else if (type == "info") {
  476. direction = 0;
  477. }
  478. direction *= Math.random() * 0.5 + 1;
  479. direction = Math.round(direction) + "px"
  480. div.style.setProperty("--target", direction)
  481. div.style.left = location[0] + "px";
  482. div.style.top = location[1] + "px";
  483. const body = document.querySelector("body");
  484. body.appendChild(div);
  485. setTimeout(() => {
  486. body.removeChild(div);
  487. }, 2000);
  488. }
  489. function fillTooltip(type, field, content) {
  490. let item = document.querySelector("#" + type + "-tooltip-" + field);
  491. if (typeof(content) === "string") {
  492. item.innerText = content;
  493. } else {
  494. replaceChildren(item, content);
  495. }
  496. }
  497. function upgradeTooltip(id, event) {
  498. let tooltip = document.querySelector("#upgrade-tooltip");
  499. tooltip.style.setProperty("display", "inline-block");
  500. fillTooltip("upgrade", "name", upgrades[id].name);
  501. fillTooltip("upgrade", "desc", upgrades[id].desc);
  502. fillTooltip("upgrade", "effect", renderEffects(upgrades[id].effects));
  503. fillTooltip("upgrade", "cost", renderCost(upgrades[id].cost));
  504. fillTooltip("upgrade", "prereqs", renderPrereqs(upgrades[id].prereqs));
  505. let yOffset = tooltip.parentElement.getBoundingClientRect().y;
  506. let tooltipSize = tooltip.getBoundingClientRect().height;
  507. let yTrans = Math.round(event.clientY - yOffset);
  508. var body = document.body,
  509. html = document.documentElement;
  510. var height = Math.max(window.innerHeight);
  511. yTrans = Math.min(yTrans, height - tooltipSize - 150);
  512. tooltip.style.setProperty("transform", "translate(-220px, " + yTrans + "px)");
  513. }
  514. function upgradeTooltipRemove() {
  515. let tooltip = document.querySelector("#upgrade-tooltip");
  516. tooltip.style.setProperty("display", "none");
  517. }
  518. function prodSummary(id) {
  519. let list = [];
  520. list.push(
  521. {"text": "Each " + buildings[id].name + " produces " + round(productivityMultiplierOf(id) * buildings[id].prod,1) + " food/sec"}
  522. );
  523. list.push(
  524. {"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"}
  525. );
  526. let percentage = round(100 * productivityOf(id) / currentProductivity["food"], 2);
  527. if (isNaN(percentage)) {
  528. percentage = 0;
  529. }
  530. list.push(
  531. {"text": "(" + percentage + "% of all food)"}
  532. );
  533. return renderLines(list);
  534. }
  535. function buildingTooltip(id, event) {
  536. let tooltip = document.querySelector("#building-tooltip");
  537. tooltip.style.setProperty("display", "inline-block");
  538. fillTooltip("building", "name", buildings[id].name);
  539. fillTooltip("building", "desc", buildings[id].desc);
  540. fillTooltip("building", "cost", render(costOfBuilding(id)) + " food");
  541. fillTooltip("building", "prod", prodSummary(id));
  542. let xPos = tooltip.parentElement.getBoundingClientRect().x - 450;
  543. // wow browsers are bad
  544. var body = document.body,
  545. html = document.documentElement;
  546. var height = Math.max( body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight );
  547. let yPos = Math.min(event.clientY, height - 200);
  548. tooltip.style.setProperty("transform", "translate(" + xPos + "px, " + yPos + "px)")
  549. }
  550. function buildingTooltipRemove() {
  551. let tooltip = document.querySelector("#building-tooltip");
  552. tooltip.style.setProperty("display", "none");
  553. }
  554. window.onload = function() {
  555. setup();
  556. lastTime = performance.now();
  557. setTimeout(updateDisplay, 1000/updateRate);
  558. setTimeout(autosave, 60000);
  559. }
  560. function autosave() {
  561. saveGame();
  562. let x = window.innerWidth / 2;
  563. let y = window.innerHeight * 9 / 10;
  564. clickPopup("Autosaving...", "info", [x, y]);
  565. setTimeout(autosave, 60000);
  566. }
  567. function save(e) {
  568. saveGame();
  569. clickPopup("Saved!", "info", [e.clientX, e.clientY]);
  570. }
  571. function saveGame() {
  572. let storage = window.localStorage;
  573. storage.setItem("save-version", "0.0.1");
  574. storage.setItem("ownedUpgrades", JSON.stringify(ownedUpgrades));
  575. storage.setItem("resources", JSON.stringify(resources));
  576. storage.setItem("belongings", JSON.stringify(belongings));
  577. }
  578. function load() {
  579. let storage = window.localStorage;
  580. if (!storage.getItem("save-version")) {
  581. return;
  582. }
  583. let newOwnedUpgrades = JSON.parse(storage.getItem("ownedUpgrades"));
  584. for (const [key, value] of Object.entries(newOwnedUpgrades)) {
  585. ownedUpgrades[key] = value;
  586. }
  587. let newResources = JSON.parse(storage.getItem("resources"));
  588. for (const [key, value] of Object.entries(newResources)) {
  589. resources[key] = value;
  590. }
  591. let newBelongings = JSON.parse(storage.getItem("belongings"));
  592. for (const [key, value] of Object.entries(newBelongings)) {
  593. belongings[key] = value;
  594. }
  595. }
  596. function reset() {
  597. window.localStorage.clear();
  598. }