cookie clicker but bigger
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 

745 linhas
17 KiB

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