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.
 
 
 
 

1279 lines
31 KiB

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