cookie clicker but bigger
Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.
 
 
 
 

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