cookie clicker but bigger
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

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