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

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