cookie clicker but bigger
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 

1335 行
32 KiB

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