cookie clicker but bigger
Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.
 
 
 
 

722 Zeilen
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) {
  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. updateProductivity();
  177. updateClickBonus();
  178. }
  179. function eatMicro() {
  180. const add = productivityMultiplierOf("micro") + clickBonus;
  181. resources.food += add;
  182. return add;
  183. }
  184. // setup stuff lol
  185. // we'll initialize the dict of buildings we can own
  186. function setup() {
  187. // create static data
  188. createTemplateUpgrades();
  189. // prepare dynamic stuff
  190. initializeData();
  191. createButtons();
  192. createDisplays();
  193. registerListeners();
  194. load();
  195. unlockAtStart();
  196. updateProductivity();
  197. }
  198. function unlockAtStart() {
  199. unlockBuilding("micro");
  200. for (const [key, value] of Object.entries(belongings)) {
  201. if (belongings[key].visible) {
  202. unlockBuilding(key);
  203. }
  204. }
  205. }
  206. function unlockBuilding(id) {
  207. belongings[id].visible = true;
  208. document.querySelector("#building-" + id).classList.remove("hidden");
  209. }
  210. function initializeData() {
  211. for (const [key, value] of Object.entries(buildings)) {
  212. belongings[key] = {};
  213. belongings[key].count = 0;
  214. belongings[key].visible = false;
  215. }
  216. for (const [key, value] of Object.entries(resourceTypes)) {
  217. resources[key] = 0;
  218. currentProductivity[key] = 0;
  219. }
  220. for (const [id, upgrade] of Object.entries(upgrades)) {
  221. ownedUpgrades[id] = false;
  222. for (let effect of upgrade.effects) {
  223. if (effects[effect.type] === undefined) {
  224. effects[effect.type] = [];
  225. }
  226. // copy the data and add an entry for the upgrade id that owns the effect
  227. let newEffect = {};
  228. for (const [key, value] of Object.entries(effect)) {
  229. newEffect[key] = value;
  230. }
  231. newEffect.parent = id;
  232. // unfortunate name collision here
  233. // I'm using apply() to pass on any number of arguments to the
  234. // apply() function of the effect type
  235. newEffect.apply = function(...args) { return effect_types[effect.type].apply.apply(null, [effect].concat(args)); }
  236. effects[effect.type].push(newEffect);
  237. }
  238. }
  239. }
  240. function registerListeners() {
  241. document.querySelector("#tasty-micro").addEventListener("click", (e) => {
  242. const add = eatMicro();
  243. const text = "+" + round(add, 1) + " food";
  244. const gulp = "*glp*";
  245. clickPopup(text, "food", [e.clientX, e.clientY]);
  246. clickPopup(gulp, "gulp", [e.clientX, e.clientY]);
  247. });
  248. document.querySelector("#save").addEventListener("click", save);
  249. document.querySelector("#reset").addEventListener("click", reset);
  250. }
  251. function createButtons() {
  252. createBuildings();
  253. createUpgrades();
  254. }
  255. function createBuildings() {
  256. let container = document.querySelector("#buildings-area");
  257. for (const [key, value] of Object.entries(buildings)) {
  258. let button = document.createElement("div");
  259. button.classList.add("building-button");
  260. button.classList.add("hidden");
  261. button.id = "building-" + key;
  262. let buttonName = document.createElement("div");
  263. buttonName.classList.add("building-button-name");
  264. let buttonCost = document.createElement("div");
  265. buttonCost.classList.add("building-button-cost");
  266. button.appendChild(buttonName);
  267. button.appendChild(buttonCost);
  268. button.addEventListener("mousemove", function(e) { buildingTooltip(key, e); });
  269. button.addEventListener("mouseleave", function() { buildingTooltipRemove(); });
  270. button.addEventListener("click", function() { buyBuilding(key); });
  271. button.addEventListener("click", function(e) { buildingTooltip(key, e); });
  272. container.appendChild(button);
  273. }
  274. }
  275. // do we have previous techs and at least one of each building?
  276. function upgradeReachable(id) {
  277. if (ownedUpgrades[id]) {
  278. return false;
  279. }
  280. if (upgrades[id].prereqs !== undefined ){
  281. for (const [type, reqs] of Object.entries(upgrades[id].prereqs)) {
  282. if (type == "buildings") {
  283. for (const [building, amount] of Object.entries(reqs)) {
  284. if (belongings[building].count == 0) {
  285. return false;
  286. }
  287. }
  288. }
  289. else if (type == "upgrades") {
  290. for (let upgrade of reqs) {
  291. if (!ownedUpgrades[upgrade]) {
  292. return false;
  293. }
  294. }
  295. }
  296. }
  297. }
  298. return true;
  299. }
  300. function upgradeAvailable(id) {
  301. if (!upgradeReachable(id)) {
  302. return false;
  303. }
  304. if (!canAfford(upgrades[id].cost)) {
  305. return false;
  306. }
  307. if (upgrades[id].prereqs !== undefined) {
  308. for (const [type, reqs] of Object.entries(upgrades[id].prereqs)) {
  309. if (type == "buildings") {
  310. for (const [building, amount] of Object.entries(upgrades[id].prereqs[type])) {
  311. if (belongings[building].count < amount) {
  312. return false;
  313. }
  314. }
  315. } else if (type == "productivity") {
  316. for (const [key, value] of Object.entries(reqs)) {
  317. if (currentProductivity[key] < value) {
  318. return false;
  319. }
  320. }
  321. }
  322. }
  323. }
  324. return true;
  325. }
  326. function createUpgrades() {
  327. let container = document.querySelector("#upgrades-list");
  328. for (const [key, value] of Object.entries(upgrades)) {
  329. remainingUpgrades.push(key);
  330. let button = document.createElement("div");
  331. button.classList.add("upgrade-button");
  332. button.classList.add("hidden");
  333. button.id = "upgrade-" + key;
  334. let buttonName = document.createElement("div");
  335. buttonName.classList.add("upgrade-button-name");
  336. buttonName.innerText = value.name;
  337. button.appendChild(buttonName);
  338. button.addEventListener("mousemove", function(e) { upgradeTooltip(key, e); });
  339. button.addEventListener("mouseleave", function() { upgradeTooltipRemove(); });
  340. button.addEventListener("click", function() { buyUpgrade(key); });
  341. container.appendChild(button);
  342. }
  343. }
  344. function createDisplays() {
  345. // nop
  346. }
  347. function renderLine(line) {
  348. let div = document.createElement("div");
  349. div.innerText = line.text;
  350. if (line.valid !== undefined) {
  351. if (line.valid) {
  352. div.classList.add("cost-met");
  353. } else {
  354. div.classList.add("cost-unmet");
  355. }
  356. }
  357. if (line.class !== undefined) {
  358. for (let entry of line.class.split(",")) {
  359. div.classList.add(entry);
  360. }
  361. }
  362. return div;
  363. }
  364. function renderLines(lines) {
  365. let divs = [];
  366. for (let line of lines) {
  367. divs.push(renderLine(line));
  368. }
  369. return divs;
  370. }
  371. function renderCost(cost) {
  372. let list = [];
  373. list.push({
  374. "text": "Cost:"
  375. });
  376. for (const [key, value] of Object.entries(cost)) {
  377. list.push({
  378. "text": render(value,0) + " " + resourceTypes[key].name,
  379. "valid": resources[key] >= value
  380. });
  381. }
  382. return renderLines(list);
  383. }
  384. function renderPrereqs(prereqs) {
  385. let list = [];
  386. if (prereqs === undefined) {
  387. return renderLines(list);
  388. }
  389. list.push({
  390. "text": "Own:"
  391. });
  392. for (const [key, value] of Object.entries(prereqs)) {
  393. if (key == "buildings") {
  394. for (const [id, amount] of Object.entries(prereqs.buildings)) {
  395. list.push({
  396. "text": buildings[id].name + " x" + render(amount,0),
  397. "valid": belongings[id].count >= amount
  398. });
  399. }
  400. } else if (key == "productivity") {
  401. for (const [id, amount] of Object.entries(prereqs.productivity)) {
  402. list.push({
  403. "text": render(amount,0) + " " + resourceTypes[id].name + "/s",
  404. "valid": currentProductivity[id] >= amount
  405. });
  406. }
  407. }
  408. }
  409. return renderLines(list);
  410. }
  411. function renderEffects(effectList) {
  412. let list = [];
  413. for (let effect of effectList) {
  414. list.push({"text": effect_types[effect.type].desc(effect)});
  415. }
  416. return renderLines(list);
  417. }
  418. function clickPopup(text, type, location) {
  419. const div = document.createElement("div");
  420. div.textContent = text;
  421. div.classList.add("click-popup-" + type);
  422. var direction;
  423. if (type == "food") {
  424. direction = -150;
  425. } else if (type == "gulp") {
  426. direction = -150;
  427. }
  428. direction *= Math.random() * 0.5 + 1;
  429. direction = Math.round(direction) + "px"
  430. div.style.setProperty("--target", direction)
  431. div.style.left = location[0] + "px";
  432. div.style.top = location[1] + "px";
  433. const body = document.querySelector("body");
  434. body.appendChild(div);
  435. setTimeout(() => {
  436. body.removeChild(div);
  437. }, 2000);
  438. }
  439. function fillTooltip(type, field, content) {
  440. let item = document.querySelector("#" + type + "-tooltip-" + field);
  441. if (typeof(content) === "string") {
  442. item.innerText = content;
  443. } else {
  444. replaceChildren(item, content);
  445. }
  446. }
  447. function upgradeTooltip(id, event) {
  448. let tooltip = document.querySelector("#upgrade-tooltip");
  449. tooltip.style.setProperty("display", "inline-block");
  450. fillTooltip("upgrade", "name", upgrades[id].name);
  451. fillTooltip("upgrade", "desc", upgrades[id].desc);
  452. fillTooltip("upgrade", "effect", renderEffects(upgrades[id].effects));
  453. fillTooltip("upgrade", "cost", renderCost(upgrades[id].cost));
  454. fillTooltip("upgrade", "prereqs", renderPrereqs(upgrades[id].prereqs));
  455. let yOffset = tooltip.parentElement.getBoundingClientRect().y;
  456. let yTrans = Math.round(event.clientY - yOffset);
  457. tooltip.style.setProperty("transform", "translate(-220px, " + yTrans + "px)");
  458. }
  459. function upgradeTooltipRemove() {
  460. let tooltip = document.querySelector("#upgrade-tooltip");
  461. tooltip.style.setProperty("display", "none");
  462. }
  463. function prodSummary(id) {
  464. let list = [];
  465. list.push(
  466. {"text": "Each " + buildings[id].name + " produces " + round(productivityMultiplierOf(id) * buildings[id].prod,1) + " food/sec"}
  467. );
  468. list.push(
  469. {"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"}
  470. );
  471. let percentage = round(100 * productivityOf(id) / currentProductivity["food"], 2);
  472. if (isNaN(percentage)) {
  473. percentage = 0;
  474. }
  475. list.push(
  476. {"text": "(" + percentage + "% of all food)"}
  477. );
  478. return renderLines(list);
  479. }
  480. function buildingTooltip(id, event) {
  481. let tooltip = document.querySelector("#building-tooltip");
  482. tooltip.style.setProperty("display", "inline-block");
  483. fillTooltip("building", "name", buildings[id].name);
  484. fillTooltip("building", "desc", buildings[id].desc);
  485. fillTooltip("building", "cost", render(costOfBuilding(id)) + " food");
  486. fillTooltip("building", "prod", prodSummary(id));
  487. let yOffset = tooltip.parentElement.getBoundingClientRect().y;
  488. let xPos = tooltip.parentElement.getBoundingClientRect().x - 450;
  489. // wow browsers are bad
  490. var body = document.body,
  491. html = document.documentElement;
  492. var height = Math.max( body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight );
  493. let yPos = Math.min(event.clientY, height - 200);
  494. tooltip.style.setProperty("transform", "translate(" + xPos + "px, " + yPos + "px)")
  495. }
  496. function buildingTooltipRemove() {
  497. let tooltip = document.querySelector("#building-tooltip");
  498. tooltip.style.setProperty("display", "none");
  499. }
  500. window.onload = function() {
  501. setup();
  502. lastTime = performance.now();
  503. setTimeout(updateDisplay, 1000/updateRate);
  504. }
  505. function save() {
  506. let storage = window.localStorage;
  507. storage.setItem("save-version", "0.0.1");
  508. storage.setItem("ownedUpgrades", JSON.stringify(ownedUpgrades));
  509. storage.setItem("resources", JSON.stringify(resources));
  510. storage.setItem("belongings", JSON.stringify(belongings));
  511. }
  512. function load() {
  513. let storage = window.localStorage;
  514. if (!storage.getItem("save-version")) {
  515. return;
  516. }
  517. let newOwnedUpgrades = JSON.parse(storage.getItem("ownedUpgrades"));
  518. for (const [key, value] of Object.entries(newOwnedUpgrades)) {
  519. ownedUpgrades[key] = value;
  520. }
  521. let newResources = JSON.parse(storage.getItem("resources"));
  522. for (const [key, value] of Object.entries(newResources)) {
  523. resources[key] = value;
  524. }
  525. let newBelongings = JSON.parse(storage.getItem("belongings"));
  526. for (const [key, value] of Object.entries(newBelongings)) {
  527. belongings[key] = value;
  528. }
  529. }
  530. function reset() {
  531. window.localStorage.clear();
  532. }