cookie clicker but bigger
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 
 

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