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.
 
 
 
 

608 line
14 KiB

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