cookie clicker but bigger
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 

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