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

1471 řádky
35 KiB

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