munch
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

529 lines
16 KiB

  1. let currentRoom = null;
  2. let currentDialog = null;
  3. let currentFoe = null;
  4. let dirButtons = [];
  5. let actionButtons = [];
  6. let mode = "explore";
  7. let actions = [];
  8. let time = 9*60*60;
  9. let newline = " ";
  10. let player = new Player();
  11. let playerAttacks = [];
  12. let respawnRoom;
  13. function join(things) {
  14. if (things.length == 1) {
  15. return things[0].description("a");
  16. } else if (things.length == 2) {
  17. return things[0].description("a") + " and " + things[1].description("a");
  18. } else {
  19. let line = "";
  20. line = things.slice(0,-1).reduce((line, prey) => line + prey.description("a") + ", ", line);
  21. line += " and " + things[things.length-1].description("a");
  22. return line;
  23. }
  24. }
  25. function pickRandom(list) {
  26. return list[Math.floor(Math.random() * list.length)];
  27. }
  28. function pick(list, attacker, defender) {
  29. if (list.length == 0)
  30. return null;
  31. else {
  32. let sum = list.reduce((sum, choice) => choice.weight == undefined ? sum + 1 : sum + choice.weight(attacker, defender) + sum, 0);
  33. let target = Math.random() * sum;
  34. for (let i = 0; i < list.length; i++) {
  35. sum -= list[i].weight == undefined ? 1 : list[i].weight(attacker, defender);
  36. if (sum <= target) {
  37. return list[i];
  38. }
  39. }
  40. return list[list.length-1];
  41. }
  42. }
  43. function filterValid(options, attacker, defender) {
  44. let filtered = options.filter(option => option.conditions == undefined || option.conditions.reduce((result, test) => result && test(attacker, defender), true));
  45. return filtered.filter(option => option.requirements == undefined || option.requirements.reduce((result, test) => result && test(attacker, defender), true));
  46. }
  47. function filterPriority(options) {
  48. let max = options.reduce((max, option) => option.priority > max ? option.priority : max, -1000);
  49. return options.filter(option => option.priority == max);
  50. }
  51. function round(number, digits) {
  52. return Math.round(number * Math.pow(10,digits)) / Math.pow(10,digits);
  53. }
  54. function updateExploreCompass() {
  55. for (let i = 0; i < dirButtons.length; i++) {
  56. let button = dirButtons[i];
  57. button.classList.remove("active-button");
  58. button.classList.remove("inactive-button");
  59. button.classList.remove("disabled-button");
  60. if (currentRoom.exits[i] == null) {
  61. button.disabled = true;
  62. button.classList.add("inactive-button");
  63. button.innerHTML = "";
  64. } else {
  65. if (currentRoom.exits[i].conditions.reduce((result, test) => result && test(player.prefs), true)) {
  66. button.disabled = false;
  67. button.classList.add("active-button");
  68. button.innerHTML = currentRoom.exits[i].name;
  69. } else {
  70. button.disabled = true;
  71. button.classList.add("disabled-button");
  72. button.innerHTML = currentRoom.exits[i].name;
  73. }
  74. }
  75. }
  76. }
  77. function updateExploreActions() {
  78. for (let i = 0; i < actionButtons.length; i++) {
  79. if (i < actions.length) {
  80. actionButtons[i].disabled = false;
  81. actionButtons[i].innerHTML = actions[i].name;
  82. actionButtons[i].classList.remove("inactive-button");
  83. actionButtons[i].classList.add("active-button");
  84. }
  85. else {
  86. actionButtons[i].disabled = true;
  87. actionButtons[i].innerHTML = "";
  88. actionButtons[i].classList.remove("active-button");
  89. actionButtons[i].classList.add("inactive-button");
  90. }
  91. }
  92. }
  93. function updateExplore() {
  94. updateExploreCompass();
  95. updateExploreActions();
  96. }
  97. function updateEaten() {
  98. let list = document.getElementById("eaten");
  99. while(list.firstChild) {
  100. list.removeChild(list.firstChild);
  101. }
  102. for (let i = 0; i < currentFoe.struggles.length; i++) {
  103. let li = document.createElement("li");
  104. let button = document.createElement("button");
  105. button.classList.add("eaten-button");
  106. button.innerHTML = currentFoe.struggles[i].name;
  107. button.addEventListener("click", function() { struggleClicked(i); } );
  108. button.addEventListener("mouseover", function() { struggleHovered(i); } );
  109. button.addEventListener("mouseout", function() { document.getElementById("eaten-desc").innerHTML = ""; } );
  110. li.appendChild(button);
  111. list.appendChild(li);
  112. }
  113. }
  114. function updateCombat() {
  115. let list = document.getElementById("combat");
  116. while(list.firstChild) {
  117. list.removeChild(list.firstChild);
  118. }
  119. playerAttacks = filterValid(player.attacks, player, currentFoe);
  120. if (playerAttacks.length == 0)
  121. playerAttacks = [player.backupAttack];
  122. for (let i = 0; i < playerAttacks.length; i++) {
  123. let li = document.createElement("li");
  124. let button = document.createElement("button");
  125. button.classList.add("combat-button");
  126. button.innerHTML = playerAttacks[i].name;
  127. button.addEventListener("click", function() { attackClicked(i); } );
  128. button.addEventListener("mouseover", function() { attackHovered(i); } );
  129. button.addEventListener("mouseout", function() { document.getElementById("combat-desc").innerHTML = ""; } );
  130. li.appendChild(button);
  131. list.appendChild(li);
  132. }
  133. document.getElementById("stat-foe-name").innerHTML = "Name: " + currentFoe.name;
  134. document.getElementById("stat-foe-health").innerHTML = "Health: " + currentFoe.health + "/" + currentFoe.maxHealth;
  135. document.getElementById("stat-foe-stamina").innerHTML = "Stamina: " + currentFoe.stamina + "/" + currentFoe.maxStamina;
  136. }
  137. function updateDialog() {
  138. let list = document.getElementById("dialog");
  139. while(list.firstChild) {
  140. list.removeChild(list.firstChild);
  141. }
  142. for (let i = 0; i < currentDialog.choices.length; i++) {
  143. let activated = currentDialog.choices[i].node.requirements == undefined || currentDialog.choices[i].node.requirements.reduce((result, test) => result && test(player, currentFoe), true);
  144. let li = document.createElement("li");
  145. let button = document.createElement("button");
  146. button.classList.add("dialog-button");
  147. button.innerHTML = currentDialog.choices[i].text;
  148. button.addEventListener("click", function() { dialogClicked(i); });
  149. if (!activated) {
  150. button.classList.add("disabled-button");
  151. button.disabled = true;
  152. }
  153. li.appendChild(button);
  154. list.appendChild(li);
  155. }
  156. }
  157. function updateDisplay() {
  158. document.querySelectorAll(".selector").forEach(function (x) {
  159. x.style.display = "none";
  160. });
  161. switch(mode) {
  162. case "explore":
  163. document.getElementById("selector-explore").style.display = "flex";
  164. updateExplore();
  165. break;
  166. case "combat":
  167. document.getElementById("selector-combat").style.display = "flex";
  168. updateCombat();
  169. break;
  170. case "dialog":
  171. document.getElementById("selector-dialog").style.display = "flex";
  172. updateDialog();
  173. break;
  174. case "eaten":
  175. document.getElementById("selector-eaten").style.display = "flex";
  176. updateEaten();
  177. break;
  178. }
  179. document.getElementById("time").innerHTML = "Time: " + renderTime(time);
  180. document.getElementById("stat-name").innerHTML = "Name: " + player.name;
  181. document.getElementById("stat-health").innerHTML = "Health: " + round(player.health,0) + "/" + round(player.maxHealth,0);
  182. document.getElementById("stat-cash").innerHTML = "Cash: $" + round(player.cash,0);
  183. document.getElementById("stat-stamina").innerHTML = "Stamina: " + round(player.stamina,0) + "/" + round(player.maxStamina,0);
  184. document.getElementById("stat-fullness").innerHTML = "Fullness: " + round(player.fullness(),0);
  185. if (player.prefs.scat) {
  186. document.getElementById("stat-bowels").innerHTML = "Bowels: " + round(player.bowels.fullness,0);
  187. } else {
  188. document.getElementById("stat-bowels").innerHTML = "";
  189. }
  190. }
  191. function advanceTime(amount) {
  192. time = (time + amount) % 86400;
  193. player.restoreHealth(amount);
  194. player.restoreStamina(amount);
  195. update(player.stomach.digest(amount));
  196. update(player.butt.digest(amount));
  197. }
  198. function renderTime(time) {
  199. let suffix = (time < 43200) ? "AM" : "PM";
  200. let hour = Math.floor((time % 43200) / 3600);
  201. if (hour == 0)
  202. hour = 12;
  203. let minute = Math.floor(time / 60) % 60;
  204. if (minute < 9)
  205. minute = "0" + minute;
  206. return hour + ":" + minute + " " + suffix;
  207. }
  208. function move(direction) {
  209. let target = currentRoom.exits[direction];
  210. if (target == null) {
  211. alert("Tried to move to an empty room!");
  212. return;
  213. }
  214. moveTo(target,currentRoom.exitDescs[direction]);
  215. }
  216. function moveTo(room,desc="You go places lol") {
  217. actions = [];
  218. currentRoom = room;
  219. advanceTime(30);
  220. currentRoom.objects.forEach(function (object) {
  221. object.actions.forEach(function (action) {
  222. if (action.conditions == undefined || action.conditions.reduce((result, cond) => result && cond(player.prefs), true))
  223. actions.push(action);
  224. });
  225. });
  226. update([desc,newline]);
  227. currentRoom.visit();
  228. }
  229. window.addEventListener('load', function(event) {
  230. document.getElementById("start-button").addEventListener("click", start, false);
  231. });
  232. function start() {
  233. applySettings(generateSettings());
  234. document.getElementById("create").style.display = "none";
  235. document.getElementById("game").style.display = "block";
  236. loadActions();
  237. loadCompass();
  238. loadDialog();
  239. currentRoom = createWorld();
  240. respawnRoom = currentRoom;
  241. moveTo(currentRoom);
  242. updateDisplay();
  243. }
  244. // copied from Stroll LUL
  245. function generateSettings() {
  246. let form = document.forms.namedItem("character-form");
  247. let settings = {};
  248. for (let i=0; i<form.length; i++) {
  249. let value = form[i].value == "" ? form[i].placeholder : form[i].value;
  250. if (form[i].type == "text")
  251. if (form[i].value == "")
  252. settings[form[i].name] = form[i].placeholder;
  253. else
  254. settings[form[i].name] = value;
  255. else if (form[i].type == "number")
  256. settings[form[i].name] = parseFloat(value);
  257. else if (form[i].type == "checkbox") {
  258. settings[form[i].name] = form[i].checked;
  259. } else if (form[i].type == "radio") {
  260. let name = form[i].name;
  261. if (form[i].checked)
  262. settings[name] = form[i].value;
  263. } else if (form[i].type == "select-one") {
  264. settings[form[i].name] = form[i][form[i].selectedIndex].value;
  265. }
  266. }
  267. return settings;
  268. }
  269. function applySettings(settings) {
  270. player.name = settings.name;
  271. player.species = settings.species;
  272. for (let key in settings) {
  273. if (settings.hasOwnProperty(key)) {
  274. if (key.match(/prefs/)) {
  275. let tokens = key.split("-");
  276. let pref = player.prefs;
  277. pref = tokens.slice(1,-1).reduce((pref, key) => pref[key], pref);
  278. pref[tokens.slice(-1)[0]] = settings[key];
  279. }
  280. }
  281. }
  282. }
  283. function saveSettings() {
  284. window.localStorage.setItem("settings", JSON.stringify(generateSettings()));
  285. }
  286. function retrieveSettings() {
  287. return JSON.parse(window.localStorage.getItem("settings"));
  288. }
  289. function update(lines=[]) {
  290. let log = document.getElementById("log");
  291. for (let i=0; i<lines.length; i++) {
  292. let div = document.createElement("div");
  293. div.innerHTML = lines[i];
  294. log.appendChild(div);
  295. }
  296. log.scrollTop = log.scrollHeight;
  297. updateDisplay();
  298. }
  299. function changeMode(newMode) {
  300. mode = newMode;
  301. let body = document.querySelector("body");
  302. document.getElementById("foe-stats").style.display = "none";
  303. body.className = "";
  304. switch(mode) {
  305. case "explore":
  306. case "dialog":
  307. body.classList.add("explore");
  308. break;
  309. case "combat":
  310. body.classList.add("combat");
  311. document.getElementById("foe-stats").style.display = "block";
  312. break;
  313. case "eaten":
  314. body.classList.add("eaten");
  315. break;
  316. }
  317. updateDisplay();
  318. }
  319. function respawn(respawnRoom) {
  320. moveTo(respawnRoom,"You drift through space and time...");
  321. player.clear();
  322. player.stomach.contents = [];
  323. player.butt.contents = [];
  324. advanceTime(86400/2);
  325. changeMode("explore");
  326. player.health = 100;
  327. update(["You wake back up in your bed."]);
  328. }
  329. function startCombat(opponent) {
  330. currentFoe = opponent;
  331. changeMode("combat");
  332. update(["Oh shit it's " + opponent.description("a")]);
  333. }
  334. function attackClicked(index) {
  335. update(playerAttacks[index].attack(currentFoe));
  336. if (currentFoe.health <= 0) {
  337. update([currentFoe.description("The") + " falls to the ground!"]);
  338. startDialog(new FallenFoe(currentFoe));
  339. } else if (mode == "combat") {
  340. let attack = pick(filterPriority(filterValid(currentFoe.attacks, currentFoe, player)), currentFoe, player);
  341. if (attack == null) {
  342. attack = currentFoe.backupAttack;
  343. }
  344. update(attack.attackPlayer(player));
  345. if (player.health <= -100) {
  346. update(["You die..."]);
  347. respawn(respawnRoom);
  348. } else if (player.health <= 0) {
  349. update(["You fall to the ground..."]);
  350. if (player.prefs.prey) {
  351. changeMode("eaten");
  352. } else {
  353. respawn(respawnRoom);
  354. }
  355. }
  356. }
  357. }
  358. function attackHovered(index) {
  359. document.getElementById("combat-desc").innerHTML = playerAttacks[index].desc;
  360. }
  361. function struggleClicked(index) {
  362. let struggle = currentFoe.struggles[index];
  363. let result = struggle.struggle(player);
  364. update([result.lines]);
  365. if (result.escape) {
  366. changeMode("explore");
  367. } else {
  368. let digest = pick(filterValid(currentFoe.digests, currentFoe, player), currentFoe, player);
  369. if (digest == null) {
  370. digest = currentFoe.backupDigest;
  371. }
  372. update([digest.digest(player)]);
  373. if (player.health <= -100) {
  374. update(["You digest in the depths of " + currentFoe.description("the")]);
  375. respawn(respawnRoom);
  376. }
  377. }
  378. }
  379. function struggleHovered(index) {
  380. document.getElementById("eaten-desc").innerHTML = currentFoe.struggles[index].desc;
  381. }
  382. function startDialog(dialog) {
  383. currentDialog = dialog;
  384. changeMode("dialog");
  385. update(currentDialog.text);
  386. currentDialog.visit();
  387. updateDisplay();
  388. }
  389. function dialogClicked(index) {
  390. currentDialog = currentDialog.choices[index].node;
  391. update(currentDialog.text);
  392. currentDialog.visit();
  393. if (currentDialog.choices.length == 0 && mode == "dialog") {
  394. changeMode("explore");
  395. updateDisplay();
  396. }
  397. }
  398. function loadDialog() {
  399. dialogButtons = Array.from( document.querySelectorAll(".dialog-button"));
  400. for (let i = 0; i < dialogButtons.length; i++) {
  401. dialogButtons[i].addEventListener("click", function() { dialogClicked(i); });
  402. }
  403. }
  404. function actionClicked(index) {
  405. actions[index].action();
  406. }
  407. function loadActions() {
  408. actionButtons = Array.from( document.querySelectorAll(".action-button"));
  409. for (let i = 0; i < actionButtons.length; i++) {
  410. actionButtons[i].addEventListener("click", function() { actionClicked(i); });
  411. }
  412. }
  413. function loadCompass() {
  414. dirButtons[NORTH_WEST] = document.getElementById("compass-north-west");
  415. dirButtons[NORTH_WEST].addEventListener("click", function() {
  416. move(NORTH_WEST);
  417. });
  418. dirButtons[NORTH] = document.getElementById("compass-north");
  419. dirButtons[NORTH].addEventListener("click", function() {
  420. move(NORTH);
  421. });
  422. dirButtons[NORTH_EAST] = document.getElementById("compass-north-east");
  423. dirButtons[NORTH_EAST].addEventListener("click", function() {
  424. move(NORTH_EAST);
  425. });
  426. dirButtons[WEST] = document.getElementById("compass-west");
  427. dirButtons[WEST].addEventListener("click", function() {
  428. move(WEST);
  429. });
  430. dirButtons[EAST] = document.getElementById("compass-east");
  431. dirButtons[EAST].addEventListener("click", function() {
  432. move(EAST);
  433. });
  434. dirButtons[SOUTH_WEST] = document.getElementById("compass-south-west");
  435. dirButtons[SOUTH_WEST].addEventListener("click", function() {
  436. move(SOUTH_WEST);
  437. });
  438. dirButtons[SOUTH] = document.getElementById("compass-south");
  439. dirButtons[SOUTH].addEventListener("click", function() {
  440. move(SOUTH);
  441. });
  442. dirButtons[SOUTH_EAST] = document.getElementById("compass-south-east");
  443. dirButtons[SOUTH_EAST].addEventListener("click", function() {
  444. move(SOUTH_EAST);
  445. });
  446. document.getElementById("compass-look").addEventListener("click", look, false);
  447. }
  448. function look() {
  449. update([currentRoom.description]);
  450. }