cookie clicker but bigger
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。
 
 
 
 

1446 行
35 KiB

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