cookie clicker but bigger
Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.
 
 
 
 

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