cookie clicker but bigger
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 

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