cookie clicker but bigger
Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.
 
 
 
 

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