cookie clicker but bigger
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 
 

1148 lignes
27 KiB

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