cookie clicker but bigger
Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 
 

1319 wiersze
31 KiB

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