cookie clicker but bigger
Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.
 
 
 
 

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