cookie clicker but bigger
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 

1222 linhas
29 KiB

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