cookie clicker but bigger
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 
 

1424 lignes
34 KiB

  1. "use strict";
  2. const belongings = {};
  3. const stats = {};
  4. const macroDesc = {
  5. name: "Fen",
  6. species: "crux"
  7. }
  8. const ownedUpgrades = {};
  9. const remainingUpgrades = [];
  10. let showOwnedUpgrades = false;
  11. const effects = {};
  12. const resources = {};
  13. let updateRate = 60;
  14. const currentProductivity = {};
  15. const contributions = {};
  16. const clickPowers = {
  17. clickBonus: 0,
  18. clickMultiplier: 1,
  19. clickVictim: "micro"
  20. }
  21. let clickBonus = 0;
  22. let clickVictim = "micro";
  23. let lastTime = 0;
  24. let controlHeld = false;
  25. let shiftHeld = false;
  26. let mouseTarget = undefined;
  27. const state = {
  28. ownedUpgrades: ownedUpgrades,
  29. resources: resources,
  30. currentProductivity: currentProductivity,
  31. belongings: belongings,
  32. clickPowers: clickPowers,
  33. stats: stats
  34. };
  35. const numberModes = {
  36. words: {
  37. name: "Words",
  38. render: numberText,
  39. next: "smallWords"
  40. },
  41. smallWords: {
  42. name: "Small Words",
  43. render: numberTextSmall,
  44. next: "scientific"
  45. },
  46. scientific: {
  47. name: "Scientific",
  48. render: numberScientific,
  49. next: "full",
  50. },
  51. full: {
  52. name: "Full",
  53. render: numberFull,
  54. next: "words"
  55. }
  56. }
  57. deepFreeze(numberModes);
  58. let numberMode = numberModes["words"];
  59. const activePowerups = {};
  60. function tickPowerups(delta) {
  61. const powerupList = document.querySelector("#powerup-list");
  62. let changed = false;
  63. // I love mutating arrays as I traverse them.
  64. Object.entries(activePowerups).filter(x => x[1].life > 0).forEach(([key, data]) => {
  65. const newLife = data.life - delta;
  66. if (newLife <= 0) {
  67. setTimeout(() => {
  68. powerupList.removeChild(data.element);
  69. }, 1000);
  70. data.element.classList.add("powerup-entry-done");
  71. activePowerups[key].life = 0;
  72. changed = true;
  73. } else {
  74. data.life = newLife;
  75. const frac = (data.maxLife - data.life) / (data.maxLife);
  76. data.element.style.setProperty("--progress", frac * 100 + "%")
  77. }
  78. });
  79. if (changed) {
  80. updateAll();
  81. }
  82. }
  83. function addPowerup(key, powerup) {
  84. // powerup already exists
  85. if (activePowerups[key].life > 0) {
  86. activePowerups[key].life += powerup.duration;
  87. activePowerups[key].maxLife = activePowerups[key].life;
  88. } else {
  89. const powerupList = document.querySelector("#powerup-list");
  90. const powerupEntry = document.createElement("div");
  91. powerupEntry.classList.add("powerup-entry");
  92. powerupEntry.innerText = powerup.name;
  93. powerupList.appendChild(powerupEntry);
  94. activePowerups[key] = {powerup: powerup, life: powerup.duration, maxLife: powerup.duration, element: powerupEntry };
  95. powerupEntry.addEventListener("mousemove", function (e) { powerupTooltip(key, e); });
  96. powerupEntry.addEventListener("mouseleave", function () { powerupTooltipRemove(); });
  97. updateAll();
  98. }
  99. }
  100. function applyGlobalProdBonus(cost) {
  101. for (let effect of effects["prod-all"]) {
  102. if (ownedUpgrades[effect.parent]) {
  103. effect.apply(cost);
  104. }
  105. }
  106. }
  107. function calculateProductivity() {
  108. let productivity = makeCost();
  109. for (const [key, value] of Object.entries(belongings)) {
  110. const provided = productivityOf(key);
  111. productivity = addCost(productivity, provided);
  112. contributions[key] = provided;
  113. }
  114. return productivity;
  115. }
  116. // here's where upgrades will go :3
  117. function applyProductivityMultipliers(type, cost) {
  118. for (let effect of effects["prod"]) {
  119. if (ownedUpgrades[effect.parent] && effect.target == type) {
  120. effect.apply(cost);
  121. }
  122. }
  123. for (let effect of effects["helper"]) {
  124. if (ownedUpgrades[effect.parent] && effect.helped == type) {
  125. effect.apply(cost, belongings[effect.helper].count);
  126. }
  127. }
  128. }
  129. function productivityOf(type) {
  130. let baseProd = makeCost(buildings[type].prod);
  131. applyProductivityMultipliers(type, baseProd);
  132. applyGlobalProdBonus(baseProd);
  133. scaleCost(baseProd, belongings[type].count);
  134. return baseProd;
  135. }
  136. function makeCost(source) {
  137. const empty = mapObject(resourceTypes, () => 0);
  138. return {...empty, ...source};
  139. }
  140. function addCost(cost1, cost2) {
  141. return Object.keys(resourceTypes).reduce((o, k) => {o[k] += cost2[k]; return o;}, cost1);
  142. }
  143. function scaleCost(cost, scale) {
  144. return Object.keys(resourceTypes).reduce((o, k) => {o[k] *= scale; return o;}, cost);
  145. }
  146. function costOfBuilding(type, count = 1) {
  147. let total = makeCost();
  148. while (count > 0) {
  149. let baseCost = makeCost(buildings[type].cost);
  150. baseCost = scaleCost(baseCost, Math.pow(1.15, belongings[type].count + count - 1));
  151. total = addCost(total, baseCost);
  152. count--;
  153. }
  154. return mapObject(total, round);
  155. }
  156. function buildingCount() {
  157. if (controlHeld) {
  158. return 10;
  159. } else if (shiftHeld) {
  160. return 5;
  161. } else {
  162. return 1;
  163. }
  164. }
  165. function buyBuilding(type, e) {
  166. const count = buildingCount();
  167. let cost = costOfBuilding(type, count);
  168. if (canAfford(cost)) {
  169. spend(cost);
  170. belongings[type].count += count;
  171. }
  172. updateProductivity();
  173. }
  174. function updateAll() {
  175. updateProductivity();
  176. updateClickVictim();
  177. updateOptions();
  178. }
  179. function updateOptions() {
  180. cache.optionButtons.numbers.innerText = "Number mode: " + numberMode.name;
  181. }
  182. // update stuff
  183. function updateDisplay() {
  184. let newTime = performance.now();
  185. let delta = newTime - lastTime;
  186. lastTime = newTime;
  187. addResources(delta);
  188. displayResources();
  189. displayBuildings();
  190. displayUpgrades(showOwnedUpgrades);
  191. tickPowerups(delta);
  192. Object.keys(statTypes).forEach(key => {
  193. const value = document.querySelector("#stat-value-" + key);
  194. value.innerText = render(stats[key]);
  195. })
  196. stats.seconds += delta / 1000;
  197. setTimeout(updateDisplay, 1000 / updateRate);
  198. }
  199. function updateProductivity() {
  200. Object.assign(currentProductivity, calculateProductivity());
  201. // maybe this should go somewhere else - it also does clicking...
  202. updateClickPowers();
  203. Object.entries(activePowerups).forEach(([key, entry]) => {
  204. if (entry.life > 0) {
  205. const powerup = entry.powerup;
  206. powerup.effect(state);
  207. }
  208. });
  209. }
  210. function addResources(delta) {
  211. for (const [resource, amount] of Object.entries(currentProductivity)) {
  212. resources[resource] += amount * delta / 1000;
  213. }
  214. }
  215. function displayResources() {
  216. document.title = "Gorge - " + round(resources.food) + " food";
  217. Object.keys(resources).forEach(key => {
  218. cache.resourceLabels[key].quantity.innerText = render(resources[key]) + " " + resourceTypes[key].name;
  219. if (resourceTypes[key].generated)
  220. cache.resourceLabels[key].rate.innerText = render(currentProductivity[key]) + " " + resourceTypes[key].name + "/sec";
  221. })
  222. }
  223. function displayBuildings() {
  224. const count = buildingCount();
  225. for (const [key, value] of Object.entries(belongings)) {
  226. let available = states.buildings[key].available;
  227. if (!belongings[key].visible) {
  228. if (resources.food * 10 >= costOfBuilding(key).food) {
  229. unlockBuilding(key);
  230. } if (belongings[key].count > 0) {
  231. unlockBuilding(key);
  232. } else {
  233. continue;
  234. }
  235. belongings[key].visible = true;
  236. let button = cache.buildingButtons[key].button;
  237. button.classList.remove("hidden");
  238. }
  239. let button = cache.buildingButtons[key].button;
  240. let name = cache.buildingButtons[key].name;
  241. let cost = cache.buildingButtons[key].cost;
  242. const buildingCost = costOfBuilding(key, count);
  243. const newName = value.count + " " + (value.count == 1 ? buildings[key].name : buildings[key].plural);
  244. if (newName != states.buildings[key].name) {
  245. name.innerText = newName;
  246. states.buildings[key].name = newName;
  247. }
  248. const newCost = render(buildingCost.food) + " food";
  249. if (newCost != states.buildings[key].cost) {
  250. cost.innerText = newCost;
  251. states.buildings[key].cost = newCost;
  252. }
  253. if (canAfford(buildingCost) && available !== true) {
  254. button.classList.remove("building-button-disabled");
  255. cost.classList.add("building-button-cost-valid");
  256. states.buildings[key].available = true;
  257. } else if (!canAfford(buildingCost) && available !== false) {
  258. button.classList.add("building-button-disabled");
  259. cost.classList.add("building-button-cost-invalid");
  260. states.buildings[key].available = false;
  261. }
  262. }
  263. }
  264. function canAfford(cost) {
  265. for (const [resource, amount] of Object.entries(cost)) {
  266. if (resources[resource] < amount) {
  267. return false;
  268. }
  269. }
  270. return true;
  271. }
  272. function spend(cost) {
  273. for (const [resource, amount] of Object.entries(cost)) {
  274. resources[resource] -= amount;
  275. }
  276. }
  277. function switchShowOwnedUpgrades() {
  278. initializeUpgradeStates();
  279. if (showOwnedUpgrades) {
  280. document.querySelector("#upgrades").innerText = "Upgrades";
  281. } else {
  282. document.querySelector("#upgrades").innerText = "Owned Upgrades";
  283. }
  284. showOwnedUpgrades = !showOwnedUpgrades;
  285. }
  286. function displayUpgrades(owned) {
  287. if (owned) {
  288. Object.entries(ownedUpgrades).forEach(([key, val]) => {
  289. let button = cache.upgradeButtons[key];
  290. if (val) {
  291. button.classList.remove("hidden");
  292. } else {
  293. button.classList.add("hidden");
  294. }
  295. });
  296. }
  297. else {
  298. for (let id of remainingUpgrades) {
  299. let button = cache.upgradeButtons[id];
  300. let visible = states.upgrades[id].visible;
  301. let available = states.upgrades[id].available;
  302. if (ownedUpgrades[id] && visible !== false) {
  303. button.classList.add("hidden");
  304. states.upgrades[id].visible = false;
  305. continue;
  306. }
  307. if (upgradeReachable(id) && visible !== true) {
  308. button.classList.remove("hidden");
  309. states.upgrades[id].visible = true;
  310. } else if (!upgradeReachable(id) && visible !== false) {
  311. button.classList.add("hidden");
  312. states.upgrades[id].visible = false;
  313. }
  314. if (upgradeAvailable(id) && available !== true) {
  315. button.classList.remove("upgrade-button-inactive");
  316. states.upgrades[id].available = true;
  317. } else if (!upgradeAvailable(id) && available !== false) {
  318. button.classList.add("upgrade-button-inactive");
  319. states.upgrades[id].available = false;
  320. }
  321. }
  322. // we aren't trimming the list of upgrades now
  323. // because we need to switch between owned and unowned upgrades
  324. // - thus we need to be able to show or hide anything
  325. /*
  326. for (let i = remainingUpgrades.length-1; i >= 0; i--) {
  327. if (ownedUpgrades[remainingUpgrades[i]]) {
  328. remainingUpgrades.splice(i, 1);
  329. }
  330. }*/
  331. }
  332. }
  333. function updateClickPowers() {
  334. let bonus = 0;
  335. clickPowers.clickMultiplier = 1;
  336. for (let effect of effects["click"]) {
  337. if (ownedUpgrades[effect.parent]) {
  338. bonus = effect.apply(bonus, currentProductivity["food"]);
  339. }
  340. }
  341. clickPowers.clickBonus = bonus;
  342. }
  343. function updateClickVictim() {
  344. for (let effect of effects["click-victim"]) {
  345. if (ownedUpgrades[effect.parent]) {
  346. clickPowers.clickVictim = effect.id;
  347. document.querySelector("#tasty-micro").innerText = "Eat " + buildings[effect.id].name;
  348. }
  349. }
  350. }
  351. function buyUpgrade(id, e) {
  352. if (ownedUpgrades[id]) {
  353. return;
  354. }
  355. let upgrade = upgrades[id];
  356. if (!upgradeAvailable(id)) {
  357. return;
  358. }
  359. spend(upgrade.cost);
  360. ownedUpgrades[id] = true;
  361. let text = "Bought " + upgrade.name + "!";
  362. clickPopup(text, "upgrade", [e.clientX, e.clientY]);
  363. updateProductivity();
  364. updateClickVictim();
  365. }
  366. function eatPrey() {
  367. const add = clickPowers.clickMultiplier * (buildings[clickPowers.clickVictim]["prod"].food * 10 + clickPowers.clickBonus);
  368. resources.food += add;
  369. return add;
  370. }
  371. // setup stuff lol
  372. // we'll initialize the dict of buildings we can own
  373. function setup() {
  374. // create static data
  375. createTemplateUpgrades();
  376. // prepare dynamic stuff
  377. initializeData();
  378. createButtons();
  379. createDisplays();
  380. registerListeners();
  381. load();
  382. unlockAtStart();
  383. initializeCaches();
  384. initializeStates();
  385. updateAll();
  386. }
  387. const cache = {};
  388. function initializeCaches() {
  389. const buildingButtons = {};
  390. for (const [key, value] of Object.entries(belongings)) {
  391. let button = document.querySelector("#building-" + key);
  392. let name = document.querySelector("#building-" + key + " > .building-button-name");
  393. let cost = document.querySelector("#building-" + key + " > .building-button-cost");
  394. buildingButtons[key] = {
  395. button: button,
  396. name: name,
  397. cost: cost
  398. }
  399. }
  400. cache.buildingButtons = buildingButtons;
  401. const upgradeButtons = {};
  402. Object.keys(upgrades).forEach(key => {
  403. upgradeButtons[key] = document.querySelector("#upgrade-" + key);
  404. });
  405. cache.upgradeButtons = upgradeButtons;
  406. const resourceLabels = {};
  407. Object.keys(resourceTypes).forEach(key => {
  408. resourceLabels[key] = {
  409. quantity: document.querySelector("#resource-quantity-" + key),
  410. }
  411. if (resourceTypes[key].generated)
  412. resourceLabels[key].rate = document.querySelector("#resource-rate-" + key);
  413. });
  414. cache.resourceLabels = resourceLabels;
  415. const optionButtons = {};
  416. optionButtons.numbers = document.querySelector("#numbers");
  417. cache.optionButtons = optionButtons;
  418. }
  419. const states = {};
  420. // we can keep track of some things, like whether
  421. // specific upgrades are currently visible. this
  422. // way, we don't have to set them visible every tick;
  423. // we can just check if they've been handled already
  424. function initializeStates() {
  425. initializeBuildingStates();
  426. initializeUpgradeStates();
  427. }
  428. function initializeBuildingStates() {
  429. const buildingStates = {};
  430. Object.keys(buildings).forEach(key => {
  431. buildingStates[key] = {
  432. visible: undefined,
  433. available: undefined,
  434. name: undefined,
  435. cost: undefined
  436. }
  437. });
  438. states.buildings = buildingStates;
  439. }
  440. function initializeUpgradeStates() {
  441. const upgradeStates = {};
  442. Object.keys(upgrades).forEach(key => {
  443. upgradeStates[key] = {
  444. visible: undefined,
  445. available: undefined
  446. }
  447. });
  448. states.upgrades = upgradeStates;
  449. }
  450. function unlockAtStart() {
  451. unlockBuilding("micro");
  452. for (const [key, value] of Object.entries(belongings)) {
  453. if (belongings[key].visible) {
  454. unlockBuilding(key);
  455. }
  456. }
  457. }
  458. function unlockBuilding(id) {
  459. belongings[id].visible = true;
  460. document.querySelector("#building-" + id).classList.remove("hidden");
  461. }
  462. function initializeData() {
  463. for (const [key, value] of Object.entries(buildings)) {
  464. belongings[key] = {};
  465. belongings[key].count = 0;
  466. belongings[key].visible = false;
  467. contributions[key] = makeCost();
  468. }
  469. for (const [key, value] of Object.entries(resourceTypes)) {
  470. resources[key] = 0;
  471. currentProductivity[key] = 0;
  472. }
  473. for (const [id, upgrade] of Object.entries(upgrades)) {
  474. ownedUpgrades[id] = false;
  475. for (let effect of upgrade.effects) {
  476. if (effects[effect.type] === undefined) {
  477. effects[effect.type] = [];
  478. }
  479. // copy the data and add an entry for the upgrade id that owns the effect
  480. let newEffect = {};
  481. for (const [key, value] of Object.entries(effect)) {
  482. newEffect[key] = value;
  483. }
  484. newEffect.parent = id;
  485. // unfortunate name collision here
  486. // I'm using apply() to pass on any number of arguments to the
  487. // apply() function of the effect type
  488. newEffect.apply = function (...args) { return effect_types[effect.type].apply.apply(null, [effect].concat(args)); }
  489. effects[effect.type].push(newEffect);
  490. }
  491. }
  492. Object.keys(powerups).filter(x => powerups[x].duration !== undefined).forEach(key => activePowerups[key] = {
  493. life: 0
  494. });
  495. Object.entries(statTypes).forEach(([key, info]) => {
  496. stats[key] = 0;
  497. });
  498. }
  499. function registerListeners() {
  500. document.querySelector("#tasty-micro").addEventListener("click", (e) => {
  501. const add = eatPrey();
  502. const text = "+" + render(round(add, 1), 3) + " food";
  503. const gulp = "*glp*";
  504. clickPopup(text, "food", [e.clientX, e.clientY]);
  505. clickPopup(gulp, "gulp", [e.clientX, e.clientY]);
  506. stats.clicks += 1;
  507. });
  508. document.querySelector("#save").addEventListener("click", save);
  509. document.querySelector("#reset").addEventListener("click", reset);
  510. document.querySelector("#numbers").addEventListener("click", cycleNumbers);
  511. document.querySelector("#stats").addEventListener("click", () => document.querySelector("#stats-modal").classList.add("modal-active"));
  512. document.querySelector("#options").addEventListener("click", openOptions);
  513. document.querySelector("#stats-exit").addEventListener("click", () => document.querySelector("#stats-modal").classList.remove("modal-active"));
  514. document.querySelector("#options-exit").addEventListener("click", closeOptions);
  515. document.querySelector("#upgrades").addEventListener("click", switchShowOwnedUpgrades);
  516. document.addEventListener("keydown", e => {
  517. shiftHeld = e.shiftKey;
  518. controlHeld = e.ctrlKey;
  519. if (mouseTarget)
  520. mouseTarget.dispatchEvent(new Event("mousemove"));
  521. return true;
  522. });
  523. document.addEventListener("keyup", e => {
  524. shiftHeld = e.shiftKey;
  525. controlHeld = e.ctrlKey;
  526. if (mouseTarget)
  527. mouseTarget.dispatchEvent(new Event("mousemove"));
  528. return true;
  529. });
  530. }
  531. function openOptions() {
  532. document.querySelector("#options-modal").classList.add("modal-active");
  533. Object.keys(options).forEach(key => {
  534. const input = document.querySelector("#option-value-" + key);
  535. input.value = options[key].get();
  536. });
  537. }
  538. function closeOptions() {
  539. document.querySelector("#options-modal").classList.remove("modal-active");
  540. Object.keys(options).forEach(key => {
  541. const input = document.querySelector("#option-value-" + key);
  542. options[key].set(input.value);
  543. });
  544. }
  545. function createButtons() {
  546. createBuildings();
  547. createUpgrades();
  548. }
  549. function createBuildings() {
  550. let container = document.querySelector("#buildings-list");
  551. for (const [key, value] of Object.entries(buildings)) {
  552. let button = document.createElement("div");
  553. button.classList.add("building-button");
  554. button.classList.add("hidden");
  555. button.id = "building-" + key;
  556. let buttonName = document.createElement("div");
  557. buttonName.classList.add("building-button-name");
  558. let buttonCost = document.createElement("div");
  559. buttonCost.classList.add("building-button-cost");
  560. let buildingIcon = document.createElement("i");
  561. buildingIcon.classList.add("fas");
  562. buildingIcon.classList.add(value.icon);
  563. button.appendChild(buttonName);
  564. button.appendChild(buttonCost);
  565. button.appendChild(buildingIcon);
  566. button.addEventListener("mousemove", function (e) { mouseTarget = button; buildingTooltip(key, e); });
  567. button.addEventListener("mouseleave", function () { mouseTarget = undefined; buildingTooltipRemove(); });
  568. button.addEventListener("click", function (e) { buyBuilding(key, e); });
  569. button.addEventListener("click", function (e) { buildingTooltip(key, e); });
  570. container.appendChild(button);
  571. }
  572. }
  573. // do we have previous techs and at least one of each building?
  574. function upgradeReachable(id) {
  575. if (ownedUpgrades[id]) {
  576. return false;
  577. }
  578. if (upgrades[id].prereqs !== undefined) {
  579. for (const [type, reqs] of Object.entries(upgrades[id].prereqs)) {
  580. if (type == "buildings") {
  581. for (const [building, amount] of Object.entries(reqs)) {
  582. if (belongings[building].count == 0) {
  583. return false;
  584. }
  585. }
  586. }
  587. else if (type == "upgrades") {
  588. for (let upgrade of reqs) {
  589. if (!ownedUpgrades[upgrade]) {
  590. return false;
  591. }
  592. }
  593. }
  594. else if (type == "resources") {
  595. for (const [resource, amount] of Object.entries(reqs)) {
  596. if (resources[resource] < amount) {
  597. return false;
  598. }
  599. };
  600. }
  601. else if (type == "stats") {
  602. for (const [stat, amount] of Object.entries(reqs)) {
  603. if (stats[stat] < amount) {
  604. return false;
  605. }
  606. };
  607. }
  608. }
  609. }
  610. return true;
  611. }
  612. function upgradeAvailable(id) {
  613. if (!upgradeReachable(id)) {
  614. return false;
  615. }
  616. if (!canAfford(upgrades[id].cost)) {
  617. return false;
  618. }
  619. if (upgrades[id].prereqs !== undefined) {
  620. for (const [type, reqs] of Object.entries(upgrades[id].prereqs)) {
  621. if (type == "buildings") {
  622. for (const [building, amount] of Object.entries(upgrades[id].prereqs[type])) {
  623. if (belongings[building].count < amount) {
  624. return false;
  625. }
  626. }
  627. } else if (type == "productivity") {
  628. for (const [key, value] of Object.entries(reqs)) {
  629. if (currentProductivity[key] < value) {
  630. return false;
  631. }
  632. }
  633. }
  634. }
  635. }
  636. return true;
  637. }
  638. function createUpgrades() {
  639. let container = document.querySelector("#upgrades-list");
  640. for (const [key, value] of Object.entries(upgrades)) {
  641. remainingUpgrades.push(key);
  642. let button = document.createElement("div");
  643. button.classList.add("upgrade-button");
  644. button.classList.add("hidden");
  645. button.id = "upgrade-" + key;
  646. let buttonName = document.createElement("div");
  647. buttonName.classList.add("upgrade-button-name");
  648. buttonName.innerText = value.name;
  649. let upgradeIcon = document.createElement("i");
  650. upgradeIcon.classList.add("fas");
  651. upgradeIcon.classList.add(value.icon);
  652. button.appendChild(buttonName);
  653. button.appendChild(upgradeIcon);
  654. button.addEventListener("mouseenter", function (e) { mouseTarget = button; upgradeTooltip(key, e); });
  655. button.addEventListener("mousemove", function (e) { mouseTarget = button; upgradeTooltip(key, e); });
  656. button.addEventListener("mouseleave", function () { mouseTarget = undefined; upgradeTooltipRemove(); });
  657. button.addEventListener("click", function (e) { buyUpgrade(key, e); });
  658. container.appendChild(button);
  659. }
  660. }
  661. function createDisplays() {
  662. const resourceList = document.querySelector("#resource-list");
  663. Object.keys(resourceTypes).forEach(key => {
  664. const quantity = document.createElement("div");
  665. quantity.classList.add("resource-quantity");
  666. quantity.id = "resource-quantity-" + key;
  667. resourceList.appendChild(quantity);
  668. if (resourceTypes[key].generated) {
  669. const rate = document.createElement("div");
  670. rate.classList.add("resource-rate");
  671. rate.id = "resource-rate-" + key;
  672. resourceList.appendChild(rate);
  673. }
  674. })
  675. const statHolder = document.querySelector("#stats-holder");
  676. Object.keys(statTypes).forEach(key => {
  677. const div = document.createElement("div");
  678. div.classList.add("stat-line");
  679. const name = document.createElement("div");
  680. name.classList.add("stat-name");
  681. const value = document.createElement("div");
  682. value.classList.add("stat-value");
  683. value.id = "stat-value-" + key;
  684. name.innerText = statTypes[key].name;
  685. value.innerText = stats[key];
  686. div.appendChild(name);
  687. div.appendChild(value);
  688. statHolder.append(div);
  689. });
  690. const optionHolder = document.querySelector("#options-holder");
  691. Object.keys(options).forEach(key => {
  692. const div = document.createElement("div");
  693. div.classList.add("option-line");
  694. const name = document.createElement("div");
  695. name.classList.add("option-name");
  696. const value = document.createElement("input");
  697. value.classList.add("option-value");
  698. value.id = "option-value-" + key;
  699. name.innerText = options[key].name;
  700. value.innerText = options[key].get();
  701. div.appendChild(name);
  702. div.appendChild(value);
  703. optionHolder.append(div);
  704. });
  705. }
  706. function renderLine(line) {
  707. let div = document.createElement("div");
  708. div.innerText = line.text;
  709. if (line.valid !== undefined) {
  710. if (line.valid) {
  711. div.classList.add("cost-met");
  712. } else {
  713. div.classList.add("cost-unmet");
  714. }
  715. }
  716. if (line.class !== undefined) {
  717. for (let entry of line.class.split(",")) {
  718. div.classList.add(entry);
  719. }
  720. }
  721. return div;
  722. }
  723. function renderLines(lines) {
  724. let divs = [];
  725. for (let line of lines) {
  726. divs.push(renderLine(line));
  727. }
  728. return divs;
  729. }
  730. function renderCost(cost) {
  731. let list = [];
  732. list.push({
  733. "text": "Cost:"
  734. });
  735. for (const [key, value] of Object.entries(cost)) {
  736. list.push({
  737. "text": render(value, 0) + " " + resourceTypes[key].name,
  738. "valid": resources[key] >= value
  739. });
  740. }
  741. return renderLines(list);
  742. }
  743. function renderPrereqs(prereqs) {
  744. let list = [];
  745. if (prereqs === undefined) {
  746. return renderLines(list);
  747. }
  748. list.push({
  749. "text": "Own:"
  750. });
  751. for (const [key, value] of Object.entries(prereqs)) {
  752. if (key == "buildings") {
  753. for (const [id, amount] of Object.entries(prereqs.buildings)) {
  754. list.push({
  755. "text": buildings[id].name + " x" + render(amount, 0),
  756. "valid": belongings[id].count >= amount
  757. });
  758. }
  759. } else if (key == "productivity") {
  760. for (const [id, amount] of Object.entries(prereqs.productivity)) {
  761. list.push({
  762. "text": render(amount, 0) + " " + resourceTypes[id].name + "/s",
  763. "valid": currentProductivity[id] >= amount
  764. });
  765. }
  766. }
  767. }
  768. return renderLines(list);
  769. }
  770. function renderEffects(effectList) {
  771. let list = [];
  772. for (let effect of effectList) {
  773. list.push({ "text": effect_types[effect.type].desc(effect) });
  774. }
  775. return renderLines(list);
  776. }
  777. function clickPopup(text, type, location) {
  778. const div = document.createElement("div");
  779. div.textContent = text;
  780. div.classList.add("click-popup-" + type);
  781. var direction;
  782. if (type == "food") {
  783. direction = -150;
  784. } else if (type == "gulp") {
  785. direction = -150;
  786. } else if (type == "upgrade") {
  787. direction = -50;
  788. } else if (type == "info") {
  789. direction = 0;
  790. }
  791. direction *= Math.random() * 0.5 + 1;
  792. direction = Math.round(direction) + "px"
  793. div.style.setProperty("--target", direction)
  794. div.style.left = location[0] + "px";
  795. div.style.top = location[1] + "px";
  796. const body = document.querySelector("body");
  797. body.appendChild(div);
  798. setTimeout(() => {
  799. body.removeChild(div);
  800. }, 2000);
  801. }
  802. function doNews() {
  803. let options = [];
  804. news.forEach(entry => {
  805. if (entry.condition(state)) {
  806. options = options.concat(entry.lines);
  807. }
  808. });
  809. const choice = Math.floor(Math.random() * options.length);
  810. showNews(options[choice](state));
  811. setTimeout(() => {
  812. doNews();
  813. }, 8000);
  814. }
  815. function showNews(text) {
  816. const div = document.createElement("div");
  817. div.textContent = text;
  818. div.classList.add("news-text");
  819. const body = document.querySelector("body");
  820. body.appendChild(div);
  821. setTimeout(() => {
  822. body.removeChild(div);
  823. }, 8000);
  824. }
  825. function doPowerup() {
  826. const lifetime = 10000;
  827. const button = document.createElement("div");
  828. const left = Math.round(Math.random() * 50 + 25) + "%";
  829. const top = Math.round(Math.random() * 50 + 25) + "%";
  830. button.classList.add("powerup");
  831. button.style.setProperty("--lifetime", lifetime / 1000 + "s");
  832. button.style.setProperty("--leftpos", left);
  833. button.style.setProperty("--toppos", top);
  834. const body = document.querySelector("body");
  835. body.appendChild(button);
  836. const choices = [];
  837. Object.entries(powerups).forEach(([key, val]) => {
  838. if (val.prereqs(state))
  839. choices.push(key);
  840. });
  841. const choice = Math.floor(Math.random() * choices.length);
  842. const powerup = powerups[choices[choice]];
  843. const icon = document.createElement("div");
  844. icon.classList.add("fas");
  845. icon.classList.add(powerup.icon);
  846. button.appendChild(icon);
  847. const remove = setTimeout(() => {
  848. body.removeChild(button);
  849. }, lifetime);
  850. let delay = 60000 + Math.random() * 30000;
  851. for (let effect of effects["powerup-freq"]) {
  852. if (ownedUpgrades[effect.parent]) {
  853. delay = effect.apply(delay);
  854. }
  855. }
  856. setTimeout(() => {
  857. doPowerup();
  858. }, delay);
  859. button.addEventListener("mousedown", e => {
  860. if (powerup.duration !== undefined) {
  861. addPowerup(choices[choice], powerup);
  862. } else {
  863. powerup.effect(state);
  864. }
  865. powerup.popup(powerup, e);
  866. button.classList.add("powerup-clicked");
  867. resources.powerups += 1;
  868. clearTimeout(remove);
  869. stats.powerups += 1;
  870. setTimeout(() => {
  871. body.removeChild(button);
  872. }, 500);
  873. });
  874. }
  875. function fillTooltip(type, field, content) {
  876. let item = document.querySelector("#" + type + "-tooltip-" + field);
  877. if (typeof (content) === "string") {
  878. item.innerText = content;
  879. } else {
  880. replaceChildren(item, content);
  881. }
  882. }
  883. function upgradeTooltip(id, event) {
  884. let tooltip = document.querySelector("#upgrade-tooltip");
  885. tooltip.style.setProperty("display", "inline-block");
  886. fillTooltip("upgrade", "name", upgrades[id].name);
  887. fillTooltip("upgrade", "desc", upgrades[id].desc);
  888. fillTooltip("upgrade", "effect", renderEffects(upgrades[id].effects));
  889. fillTooltip("upgrade", "cost", renderCost(upgrades[id].cost));
  890. fillTooltip("upgrade", "prereqs", renderPrereqs(upgrades[id].prereqs));
  891. let yOffset = tooltip.parentElement.getBoundingClientRect().y;
  892. let tooltipSize = tooltip.getBoundingClientRect().height;
  893. let yTrans = Math.round(event.clientY - yOffset);
  894. var body = document.body,
  895. html = document.documentElement;
  896. var height = Math.max(window.innerHeight);
  897. yTrans = Math.min(yTrans, height - tooltipSize - 150);
  898. tooltip.style.setProperty("transform", "translate(-220px, " + yTrans + "px)");
  899. }
  900. function upgradeTooltipRemove() {
  901. let tooltip = document.querySelector("#upgrade-tooltip");
  902. tooltip.style.setProperty("display", "none");
  903. }
  904. function prodSummary(id) {
  905. let list = [];
  906. list.push(
  907. { "text": "Each " + buildings[id].name + " produces " + render(belongings[id].count == 0 ? 0 : contributions[id].food / belongings[id].count, 3) + " food/sec" }
  908. );
  909. list.push(
  910. { "text": "Your " + render(belongings[id].count) + " " + (belongings[id].count == 1 ? buildings[id].name + " is" : buildings[id].plural + " are") + " producing " + render(contributions[id].food, 3) + " food/sec" }
  911. );
  912. let percentage = round(100 * contributions[id].food / currentProductivity["food"], 2);
  913. if (isNaN(percentage)) {
  914. percentage = 0;
  915. }
  916. list.push(
  917. { "text": "(" + percentage + "% of all food)" }
  918. );
  919. return renderLines(list);
  920. }
  921. function buildingTooltip(id, event) {
  922. let tooltip = document.querySelector("#building-tooltip");
  923. tooltip.style.setProperty("display", "inline-block");
  924. const count = buildingCount();
  925. fillTooltip("building", "name", (count != 1 ? count + "x " : "") + buildings[id].name);
  926. fillTooltip("building", "desc", buildings[id].desc);
  927. fillTooltip("building", "cost", render(costOfBuilding(id, count).food) + " food");
  928. fillTooltip("building", "prod", prodSummary(id));
  929. let xPos = tooltip.parentElement.getBoundingClientRect().x - 450;
  930. // wow browsers are bad
  931. var body = document.body,
  932. html = document.documentElement;
  933. var height = Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight);
  934. let yPos = Math.min(event.clientY, height - 200);
  935. tooltip.style.setProperty("transform", "translate(" + xPos + "px, " + yPos + "px)")
  936. }
  937. function buildingTooltipRemove() {
  938. let tooltip = document.querySelector("#building-tooltip");
  939. tooltip.style.setProperty("display", "none");
  940. }
  941. function powerupTooltip(id, event) {
  942. let tooltip = document.querySelector("#powerup-tooltip");
  943. tooltip.style.setProperty("display", "inline-block");
  944. const count = buildingCount();
  945. fillTooltip("powerup", "name", powerups[id].name);
  946. fillTooltip("powerup", "desc", powerups[id].description);
  947. let xPos = tooltip.parentElement.getBoundingClientRect().x + 450;
  948. // wow browsers are bad
  949. var body = document.body,
  950. html = document.documentElement;
  951. var height = Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight);
  952. let yPos = Math.min(event.clientY, height - 200);
  953. tooltip.style.setProperty("transform", "translate(" + xPos + "px, " + yPos + "px)")
  954. }
  955. function powerupTooltipRemove() {
  956. let tooltip = document.querySelector("#powerup-tooltip");
  957. tooltip.style.setProperty("display", "none");
  958. }
  959. window.onload = function () {
  960. setup();
  961. lastTime = performance.now();
  962. doNews();
  963. doPowerup();
  964. setTimeout(updateDisplay, 1000 / updateRate);
  965. setTimeout(autosave, 60000);
  966. }
  967. function autosave() {
  968. saveGame();
  969. let x = window.innerWidth / 2;
  970. let y = window.innerHeight * 9 / 10;
  971. clickPopup("Autosaving...", "info", [x, y]);
  972. setTimeout(autosave, 60000);
  973. }
  974. function save(e) {
  975. saveGame();
  976. clickPopup("Saved!", "info", [e.clientX, e.clientY]);
  977. }
  978. function saveGame() {
  979. try {
  980. let storage = window.localStorage;
  981. const save = {}
  982. save.version = migrations.length;
  983. save.ownedUpgrades = ownedUpgrades;
  984. save.resources = resources;
  985. save.belongings = belongings;
  986. save.stats = stats;
  987. save.macroDesc = macroDesc;
  988. storage.setItem("save", JSON.stringify(save));
  989. } catch (e) {
  990. clickPopup("Can't save - no access to local storage.", "info", [window.innerWidth / 2, window.innerHeight / 5]);
  991. }
  992. }
  993. const migrations = [
  994. // dummy migration, because there was no version 0
  995. save => {
  996. },
  997. // introduce stats
  998. save => {
  999. save.stats = {}
  1000. },
  1001. // introduce macroDesc
  1002. save => {
  1003. save.macroDesc = {}
  1004. }
  1005. ]
  1006. function migrate(save) {
  1007. let version = save.version;
  1008. while (version != migrations.length) {
  1009. migrations[version](save);
  1010. version += 1;
  1011. }
  1012. save.version = version;
  1013. }
  1014. function load() {
  1015. try {
  1016. let storage = window.localStorage;
  1017. // migrate to everything in one
  1018. if (storage.getItem("save-version") !== null) {
  1019. const save = {};
  1020. save.ownedUpgrades = JSON.parse(storage.getItem("ownedUpgrades"));
  1021. save.resources = JSON.parse(storage.getItem("resources"));
  1022. save.belongings = JSON.parse(storage.getItem("belongings"));
  1023. save.version = 1;
  1024. storage.clear();
  1025. storage.setItem("save", JSON.stringify(save))
  1026. }
  1027. const save = JSON.parse(storage.getItem("save"));
  1028. if (save == null)
  1029. return;
  1030. migrate(save);
  1031. for (const [key, value] of Object.entries(save.ownedUpgrades)) {
  1032. ownedUpgrades[key] = value;
  1033. }
  1034. for (const [key, value] of Object.entries(save.resources)) {
  1035. resources[key] = value;
  1036. }
  1037. for (const [key, value] of Object.entries(save.belongings)) {
  1038. belongings[key] = value;
  1039. }
  1040. for (const [key, value] of Object.entries(save.stats)) {
  1041. stats[key] = value;
  1042. }
  1043. for (const [key, value] of Object.entries(save.macroDesc)) {
  1044. macroDesc[key] = value;
  1045. }
  1046. } catch (e) {
  1047. console.error(e);
  1048. clickPopup("Can't load - no access to local storage.", "info", [window.innerWidth / 2, window.innerHeight / 5]);
  1049. }
  1050. }
  1051. function reset() {
  1052. window.localStorage.clear();
  1053. }
  1054. function cycleNumbers() {
  1055. numberMode = numberModes[numberMode.next];
  1056. updateOptions();
  1057. }