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ů.
 
 
 
 

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