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

1503 строки
36 KiB

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