crunch
25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

747 lines
21 KiB

  1. let world = null;
  2. let currentRoom = null;
  3. let currentDialog = null;
  4. let currentFoe = null;
  5. let dirButtons = [];
  6. let actionButtons = [];
  7. let mode = "explore";
  8. let actions = [];
  9. let time = 9*60*60;
  10. let date = 1;
  11. let newline = " ";
  12. let player = new Player();
  13. let playerAttacks = [];
  14. let struggles = [];
  15. let killingBlow = null;
  16. let deaths = [];
  17. let respawnRoom;
  18. function join(things) {
  19. if (things.length == 1) {
  20. return things[0].description("a");
  21. } else if (things.length == 2) {
  22. return things[0].description("a") + " and " + things[1].description("a");
  23. } else {
  24. let line = "";
  25. line = things.slice(0,-1).reduce((line, prey) => line + prey.description("a") + ", ", line);
  26. line += " and " + things[things.length-1].description("a");
  27. return line;
  28. }
  29. }
  30. function pickRandom(list) {
  31. return list[Math.floor(Math.random() * list.length)];
  32. }
  33. function pick(list, attacker, defender) {
  34. if (list.length == 0)
  35. return null;
  36. else {
  37. let sum = list.reduce((sum, choice) => choice.weight == undefined ? sum + 1 : sum + choice.weight(attacker, defender), 0);
  38. let target = Math.random() * sum;
  39. for (let i = 0; i < list.length; i++) {
  40. sum -= list[i].weight == undefined ? 1 : list[i].weight(attacker, defender);
  41. if (sum <= target) {
  42. return list[i];
  43. }
  44. }
  45. return list[list.length-1];
  46. }
  47. }
  48. function filterValid(options, attacker, defender) {
  49. let filtered = options.filter(option => option.conditions == undefined || option.conditions.reduce((result, test) => result && test(attacker, defender), true));
  50. return filtered.filter(option => option.requirements == undefined || option.requirements.reduce((result, test) => result && test(attacker, defender), true));
  51. }
  52. function filterPriority(options) {
  53. let max = options.reduce((max, option) => option.priority > max ? option.priority : max, -1000);
  54. return options.filter(option => option.priority == max);
  55. }
  56. function round(number, digits) {
  57. return Math.round(number * Math.pow(10,digits)) / Math.pow(10,digits);
  58. }
  59. function updateExploreCompass() {
  60. for (let i = 0; i < dirButtons.length; i++) {
  61. let button = dirButtons[i];
  62. button.classList.remove("active-button");
  63. button.classList.remove("inactive-button");
  64. button.classList.remove("disabled-button");
  65. if (currentRoom.exits[i] == null) {
  66. button.disabled = true;
  67. button.classList.add("inactive-button");
  68. button.innerHTML = "";
  69. } else {
  70. if (currentRoom.exits[i].conditions.reduce((result, test) => result && test(player.prefs), true)) {
  71. button.disabled = false;
  72. button.classList.add("active-button");
  73. button.innerHTML = currentRoom.exits[i].name;
  74. } else {
  75. button.disabled = true;
  76. button.classList.add("disabled-button");
  77. button.innerHTML = currentRoom.exits[i].name;
  78. }
  79. }
  80. }
  81. }
  82. function updateExploreActions() {
  83. for (let i = 0; i < actionButtons.length; i++) {
  84. if (i < actions.length) {
  85. actionButtons[i].disabled = false;
  86. actionButtons[i].innerHTML = actions[i].name;
  87. actionButtons[i].classList.remove("inactive-button");
  88. actionButtons[i].classList.add("active-button");
  89. }
  90. else {
  91. actionButtons[i].disabled = true;
  92. actionButtons[i].innerHTML = "";
  93. actionButtons[i].classList.remove("active-button");
  94. actionButtons[i].classList.add("inactive-button");
  95. }
  96. }
  97. }
  98. function updateExplore() {
  99. updateExploreCompass();
  100. updateExploreActions();
  101. }
  102. function updateEaten() {
  103. let list = document.getElementById("eaten");
  104. while(list.firstChild) {
  105. list.removeChild(list.firstChild);
  106. }
  107. if (player.health > 0)
  108. struggles = filterValid(currentFoe.struggles, currentFoe, player);
  109. else
  110. struggles = [submit(currentFoe)];
  111. for (let i = 0; i < struggles.length; i++) {
  112. let li = document.createElement("li");
  113. let button = document.createElement("button");
  114. button.classList.add("eaten-button");
  115. button.innerHTML = struggles[i].name;
  116. button.addEventListener("click", function() { struggleClicked(i); } );
  117. button.addEventListener("mouseover", function() { struggleHovered(i); } );
  118. button.addEventListener("mouseout", function() { document.getElementById("eaten-desc").innerHTML = ""; } );
  119. li.appendChild(button);
  120. list.appendChild(li);
  121. }
  122. }
  123. function updateCombat() {
  124. let list = document.getElementById("combat");
  125. while(list.firstChild) {
  126. list.removeChild(list.firstChild);
  127. }
  128. if (player.health > 0)
  129. playerAttacks = filterValid(player.attacks, player, currentFoe);
  130. else
  131. playerAttacks = [pass(player)];
  132. if (playerAttacks.length == 0)
  133. playerAttacks = [player.backupAttack];
  134. for (let i = 0; i < playerAttacks.length; i++) {
  135. let li = document.createElement("li");
  136. let button = document.createElement("button");
  137. button.classList.add("combat-button");
  138. button.innerHTML = playerAttacks[i].name;
  139. button.addEventListener("click", function() { attackClicked(i); } );
  140. button.addEventListener("mouseover", function() { attackHovered(i); } );
  141. button.addEventListener("mouseout", function() { document.getElementById("combat-desc").innerHTML = ""; } );
  142. li.appendChild(button);
  143. list.appendChild(li);
  144. }
  145. document.getElementById("stat-foe-name").innerHTML = "Name: " + currentFoe.name;
  146. document.getElementById("stat-foe-health").innerHTML = "Health: " + currentFoe.health + "/" + currentFoe.maxHealth;
  147. document.getElementById("stat-foe-stamina").innerHTML = "Stamina: " + currentFoe.stamina + "/" + currentFoe.maxStamina;
  148. document.getElementById("stat-foe-str").innerHTML = "Str: " + currentFoe.str;
  149. document.getElementById("stat-foe-dex").innerHTML = "Dex: " + currentFoe.dex;
  150. document.getElementById("stat-foe-con").innerHTML = "Con: " + currentFoe.con;
  151. }
  152. function updateDialog() {
  153. let list = document.getElementById("dialog");
  154. while(list.firstChild) {
  155. list.removeChild(list.firstChild);
  156. }
  157. for (let i = 0; i < currentDialog.choices.length; i++) {
  158. let activated = currentDialog.choices[i].node.requirements == undefined || currentDialog.choices[i].node.requirements.reduce((result, test) => result && test(player, currentFoe), true);
  159. let li = document.createElement("li");
  160. let button = document.createElement("button");
  161. button.classList.add("dialog-button");
  162. button.innerHTML = currentDialog.choices[i].text;
  163. button.addEventListener("click", function() { dialogClicked(i); });
  164. if (!activated) {
  165. button.classList.add("disabled-button");
  166. button.disabled = true;
  167. }
  168. li.appendChild(button);
  169. list.appendChild(li);
  170. }
  171. }
  172. function updateDisplay() {
  173. document.querySelectorAll(".selector").forEach(function (x) {
  174. x.style.display = "none";
  175. });
  176. switch(mode) {
  177. case "explore":
  178. document.getElementById("selector-explore").style.display = "flex";
  179. updateExplore();
  180. break;
  181. case "combat":
  182. document.getElementById("selector-combat").style.display = "flex";
  183. updateCombat();
  184. break;
  185. case "dialog":
  186. document.getElementById("selector-dialog").style.display = "flex";
  187. updateDialog();
  188. break;
  189. case "eaten":
  190. document.getElementById("selector-eaten").style.display = "flex";
  191. updateEaten();
  192. break;
  193. }
  194. document.getElementById("time").innerHTML = "Time: " + renderTime(time);
  195. document.getElementById("date").innerHTML = "Day " + date;
  196. document.getElementById("stat-name").innerHTML = "Name: " + player.name;
  197. document.getElementById("stat-health").innerHTML = "Health: " + round(player.health,0) + "/" + round(player.maxHealth,0);
  198. document.getElementById("stat-cash").innerHTML = "Cash: $" + round(player.cash,0);
  199. document.getElementById("stat-stamina").innerHTML = "Stamina: " + round(player.stamina,0) + "/" + round(player.maxStamina,0);
  200. document.getElementById("stat-fullness").innerHTML = "Fullness: " + round(player.fullness(),0);
  201. if (player.prefs.scat) {
  202. document.getElementById("stat-bowels").innerHTML = "Bowels: " + round(player.bowels.fullness,0);
  203. } else {
  204. document.getElementById("stat-bowels").innerHTML = "";
  205. }
  206. }
  207. function advanceTime(amount) {
  208. time = (time + amount);
  209. date += Math.floor(time / 86400);
  210. time = time % 86400;
  211. player.restoreHealth(amount);
  212. player.restoreStamina(amount);
  213. update(player.stomach.digest(amount));
  214. update(player.butt.digest(amount));
  215. }
  216. function renderTime(time) {
  217. let suffix = (time < 43200) ? "AM" : "PM";
  218. let hour = Math.floor((time % 43200) / 3600);
  219. if (hour == 0)
  220. hour = 12;
  221. let minute = Math.floor(time / 60) % 60;
  222. if (minute < 9)
  223. minute = "0" + minute;
  224. return hour + ":" + minute + " " + suffix;
  225. }
  226. function move(direction) {
  227. let target = currentRoom.exits[direction];
  228. if (target == null) {
  229. alert("Tried to move to an empty room!");
  230. return;
  231. }
  232. moveTo(target,currentRoom.exitDescs[direction]);
  233. }
  234. function moveToByName(roomName, desc="You go places lol", loading=false) {
  235. moveTo(world[roomName], desc, loading);
  236. }
  237. function moveTo(room,desc="You go places lol", loading=false) {
  238. actions = [];
  239. currentRoom = room;
  240. if (!loading)
  241. advanceTime(30);
  242. currentRoom.objects.forEach(function (object) {
  243. object.actions.forEach(function (action) {
  244. if (action.conditions == undefined || action.conditions.reduce((result, cond) => result && cond(player.prefs), true))
  245. actions.push(action);
  246. });
  247. });
  248. update([desc,newline]);
  249. currentRoom.visit();
  250. }
  251. window.addEventListener('load', function(event) {
  252. document.getElementById("start-button").addEventListener("click", start, false);
  253. });
  254. function start() {
  255. applySettings(generateSettings());
  256. transformVorePrefs(player.prefs);
  257. document.getElementById("create").style.display = "none";
  258. document.getElementById("game").style.display = "block";
  259. document.getElementById("stat-button-status").addEventListener("click", status, false);
  260. loadActions();
  261. loadCompass();
  262. loadDialog();
  263. world = createWorld();
  264. currentRoom = world["Bedroom"];
  265. respawnRoom = currentRoom;
  266. moveTo(currentRoom,"");
  267. updateDisplay();
  268. }
  269. // copied from Stroll LUL
  270. function generateSettings() {
  271. let form = document.forms.namedItem("character-form");
  272. let settings = {};
  273. for (let i=0; i<form.length; i++) {
  274. let value = form[i].value == "" ? form[i].placeholder : form[i].value;
  275. if (form[i].type == "text")
  276. if (form[i].value == "")
  277. settings[form[i].name] = form[i].placeholder;
  278. else
  279. settings[form[i].name] = value;
  280. else if (form[i].type == "number")
  281. settings[form[i].name] = parseFloat(value);
  282. else if (form[i].type == "checkbox") {
  283. settings[form[i].name] = form[i].checked;
  284. } else if (form[i].type == "radio") {
  285. let name = form[i].name;
  286. if (form[i].checked)
  287. settings[name] = form[i].value;
  288. } else if (form[i].type == "select-one") {
  289. settings[form[i].name] = form[i][form[i].selectedIndex].value;
  290. }
  291. }
  292. return settings;
  293. }
  294. function applySettings(settings) {
  295. player.name = settings.name;
  296. player.species = settings.species;
  297. for (let key in settings) {
  298. if (settings.hasOwnProperty(key)) {
  299. if (key.match(/prefs/)) {
  300. let tokens = key.split("-");
  301. let pref = player.prefs;
  302. pref = tokens.slice(1,-1).reduce(function(pref, key) {
  303. if (pref[key] == undefined)
  304. pref[key] = {};
  305. return pref[key];
  306. }, pref);
  307. pref[tokens.slice(-1)[0]] = settings[key];
  308. }
  309. }
  310. }
  311. }
  312. // turn things like "1" into a number
  313. function transformVorePrefs(prefs) {
  314. for (let key in prefs.vore) {
  315. if (prefs.vore.hasOwnProperty(key)) {
  316. switch(prefs.vore[key]) {
  317. case "0": prefs.vore[key] = 0; break;
  318. case "1": prefs.vore[key] = 0.5; break;
  319. case "2": prefs.vore[key] = 1; break;
  320. case "3": prefs.vore[key] = 2; break;
  321. }
  322. }
  323. }
  324. return prefs;
  325. }
  326. function saveSettings() {
  327. window.localStorage.setItem("settings", JSON.stringify(generateSettings()));
  328. }
  329. function retrieveSettings() {
  330. return JSON.parse(window.localStorage.getItem("settings"));
  331. }
  332. function clearScreen() {
  333. let log = document.getElementById("log");
  334. let child = log.firstChild;
  335. while (child != null) {
  336. log.removeChild(child);
  337. child = log.firstChild;
  338. }
  339. }
  340. function update(lines=[]) {
  341. let log = document.getElementById("log");
  342. for (let i=0; i<lines.length; i++) {
  343. let div = document.createElement("div");
  344. div.innerHTML = lines[i];
  345. log.appendChild(div);
  346. }
  347. log.scrollTop = log.scrollHeight;
  348. updateDisplay();
  349. }
  350. function changeMode(newMode) {
  351. mode = newMode;
  352. let body = document.querySelector("body");
  353. document.getElementById("foe-stats").style.display = "none";
  354. body.className = "";
  355. switch(mode) {
  356. case "explore":
  357. case "dialog":
  358. body.classList.add("explore");
  359. break;
  360. case "combat":
  361. body.classList.add("combat");
  362. document.getElementById("foe-stats").style.display = "block";
  363. break;
  364. case "eaten":
  365. body.classList.add("eaten");
  366. document.getElementById("foe-stats").style.display = "block";
  367. break;
  368. }
  369. updateDisplay();
  370. }
  371. function respawn(respawnRoom) {
  372. if (killingBlow.gameover == undefined) {
  373. if (player.prefs.prey) {
  374. deaths.push("Digested by " + currentFoe.description("a") + " at " + renderTime(time) + " on day " + date);
  375. } else {
  376. deaths.push("Defeated by " + currentFoe.description("a") + " at " + renderTime(time) + " on day " + date);
  377. }
  378. } else {
  379. deaths.push(killingBlow.gameover() + " at " + renderTime(time) + " on day " + date);
  380. }
  381. moveTo(respawnRoom,"You drift through space and time...");
  382. player.clear();
  383. player.stomach.contents = [];
  384. player.butt.contents = [];
  385. player.bowels.contents = [];
  386. player.bowels.fullness = 0;
  387. advanceTime(Math.floor(86400 / 2 * (Math.random() * 0.5 - 0.25 + 1)));
  388. changeMode("explore");
  389. player.health = 100;
  390. update(["You wake back up in your bed."]);
  391. }
  392. function startCombat(opponent) {
  393. currentFoe = opponent;
  394. changeMode("combat");
  395. update(opponent.startCombat());
  396. }
  397. function attackClicked(index) {
  398. update(playerAttacks[index].attack(currentFoe).concat([newline]));
  399. if (currentFoe.health <= 0) {
  400. currentFoe.defeated();
  401. } else if (mode == "combat") {
  402. let attack = pick(filterPriority(filterValid(currentFoe.attacks, currentFoe, player)), currentFoe, player);
  403. if (attack == null) {
  404. attack = currentFoe.backupAttack;
  405. }
  406. update(attack.attackPlayer(player).concat([newline]));
  407. if (player.health <= -100) {
  408. killingBlow = attack;
  409. update(["You die..."]);
  410. respawn(respawnRoom);
  411. } else if (player.health <= 0) {
  412. update(["You're too weak to do anything..."]);
  413. if (player.prefs.prey) {
  414. // nada
  415. } else {
  416. killingBlow = attack;
  417. update(["You die..."]);
  418. respawn(respawnRoom);
  419. }
  420. }
  421. }
  422. }
  423. function attackHovered(index) {
  424. document.getElementById("combat-desc").innerHTML = playerAttacks[index].desc;
  425. }
  426. function struggleClicked(index) {
  427. let struggle = struggles[index];
  428. let result = struggle.struggle(player);
  429. update(result.lines.concat([newline]));
  430. if (result.escape == "stay") {
  431. changeMode("combat");
  432. } else if (result.escape == "escape") {
  433. changeMode("explore");
  434. } else {
  435. let digest = pick(filterValid(currentFoe.digests, currentFoe, player), currentFoe, player);
  436. if (digest == null) {
  437. digest = currentFoe.backupDigest;
  438. }
  439. update(digest.digest(player).concat([newline]));
  440. if (player.health <= -100) {
  441. killingBlow = digest;
  442. update(currentFoe.finishDigest().concat([newline]));
  443. respawn(respawnRoom);
  444. }
  445. }
  446. }
  447. function struggleHovered(index) {
  448. document.getElementById("eaten-desc").innerHTML = currentFoe.struggles[index].desc;
  449. }
  450. function startDialog(dialog) {
  451. currentDialog = dialog;
  452. changeMode("dialog");
  453. update(currentDialog.text);
  454. currentDialog.visit();
  455. updateDisplay();
  456. }
  457. function dialogClicked(index) {
  458. currentDialog = currentDialog.choices[index].node;
  459. update(currentDialog.text);
  460. currentDialog.visit();
  461. if (currentDialog.choices.length == 0 && mode == "dialog") {
  462. changeMode("explore");
  463. updateDisplay();
  464. }
  465. }
  466. function loadDialog() {
  467. dialogButtons = Array.from( document.querySelectorAll(".dialog-button"));
  468. for (let i = 0; i < dialogButtons.length; i++) {
  469. dialogButtons[i].addEventListener("click", function() { dialogClicked(i); });
  470. }
  471. }
  472. function actionClicked(index) {
  473. actions[index].action();
  474. }
  475. function loadActions() {
  476. actionButtons = Array.from( document.querySelectorAll(".action-button"));
  477. for (let i = 0; i < actionButtons.length; i++) {
  478. actionButtons[i].addEventListener("click", function() { actionClicked(i); });
  479. }
  480. }
  481. function loadCompass() {
  482. dirButtons[NORTH_WEST] = document.getElementById("compass-north-west");
  483. dirButtons[NORTH_WEST].addEventListener("click", function() {
  484. move(NORTH_WEST);
  485. });
  486. dirButtons[NORTH] = document.getElementById("compass-north");
  487. dirButtons[NORTH].addEventListener("click", function() {
  488. move(NORTH);
  489. });
  490. dirButtons[NORTH_EAST] = document.getElementById("compass-north-east");
  491. dirButtons[NORTH_EAST].addEventListener("click", function() {
  492. move(NORTH_EAST);
  493. });
  494. dirButtons[WEST] = document.getElementById("compass-west");
  495. dirButtons[WEST].addEventListener("click", function() {
  496. move(WEST);
  497. });
  498. dirButtons[EAST] = document.getElementById("compass-east");
  499. dirButtons[EAST].addEventListener("click", function() {
  500. move(EAST);
  501. });
  502. dirButtons[SOUTH_WEST] = document.getElementById("compass-south-west");
  503. dirButtons[SOUTH_WEST].addEventListener("click", function() {
  504. move(SOUTH_WEST);
  505. });
  506. dirButtons[SOUTH] = document.getElementById("compass-south");
  507. dirButtons[SOUTH].addEventListener("click", function() {
  508. move(SOUTH);
  509. });
  510. dirButtons[SOUTH_EAST] = document.getElementById("compass-south-east");
  511. dirButtons[SOUTH_EAST].addEventListener("click", function() {
  512. move(SOUTH_EAST);
  513. });
  514. document.getElementById("compass-look").addEventListener("click", look, false);
  515. }
  516. function look() {
  517. update([currentRoom.description]);
  518. }
  519. function status() {
  520. let lines = [];
  521. lines.push("You are a " + player.species);
  522. lines.push(newline);
  523. if (player.stomach.contents.length > 0) {
  524. lines.push("Your stomach bulges with prey.");
  525. player.stomach.contents.map(function(prey) {
  526. let state = "";
  527. let healthRatio = prey.health / prey.maxHealth;
  528. if (healthRatio > 0.75) {
  529. state = "is thrashing in your gut";
  530. } else if (healthRatio > 0.5) {
  531. state = "is squirming in your belly";
  532. } else if (healthRatio > 0.25) {
  533. state = "is pressing out at your stomach walls";
  534. } else if (healthRatio > 0) {
  535. state = "is weakly squirming";
  536. } else {
  537. state = "has stopped moving";
  538. }
  539. lines.push(prey.description("A") + " " + state);
  540. });
  541. lines.push(newline);
  542. }
  543. if (player.butt.contents.length > 0) {
  544. lines.push("Your bowels churn with prey.");
  545. player.butt.contents.map(function(prey) {
  546. let state = "";
  547. let healthRatio = prey.health / prey.maxHealth;
  548. if (healthRatio > 0.75) {
  549. state = "is writhing in your bowels";
  550. } else if (healthRatio > 0.5) {
  551. state = "is struggling against your intestines";
  552. } else if (healthRatio > 0.25) {
  553. state = "is bulging out of your lower belly";
  554. } else if (healthRatio > 0) {
  555. state = "is squirming weakly, slipping deeper and deeper";
  556. } else {
  557. state = "has succumbed to your bowels";
  558. }
  559. lines.push(prey.description("A") + " " + state);
  560. });
  561. lines.push(newline);
  562. }
  563. update(lines);
  564. }
  565. let toSave = ["str","dex","con","name","species"];
  566. function saveGame() {
  567. let save = {};
  568. save.player = JSON.stringify(player, function(key, value) {
  569. if (toSave.includes(key) || key == "") {
  570. return value;
  571. } else {
  572. return undefined;
  573. }
  574. });
  575. save.prefs = JSON.stringify(player.prefs);
  576. save.position = currentRoom.name;
  577. save.date = date;
  578. save.time = time;
  579. save.deaths = deaths;
  580. let stringified = JSON.stringify(save);
  581. window.localStorage.setItem("save", stringified);
  582. }
  583. function loadGame() {
  584. changeMode("explore");
  585. let save = JSON.parse(window.localStorage.getItem("save"));
  586. let playerSave = JSON.parse(save.player);
  587. for (let key in playerSave) {
  588. if (playerSave.hasOwnProperty(key)) {
  589. player[key] = playerSave[key];
  590. }
  591. }
  592. player.prefs = JSON.parse(save.prefs);
  593. deaths = save.deaths;
  594. date = save.date;
  595. time = save.time;
  596. clearScreen();
  597. moveToByName(save.position, "");
  598. }
  599. // wow polyfills
  600. if (![].includes) {
  601. Array.prototype.includes = function(searchElement /*, fromIndex*/ ) {
  602. 'use strict';
  603. var O = Object(this);
  604. var len = parseInt(O.length) || 0;
  605. if (len === 0) {
  606. return false;
  607. }
  608. var n = parseInt(arguments[1]) || 0;
  609. var k;
  610. if (n >= 0) {
  611. k = n;
  612. } else {
  613. k = len + n;
  614. if (k < 0) {k = 0;}
  615. }
  616. var currentElement;
  617. while (k < len) {
  618. currentElement = O[k];
  619. if (searchElement === currentElement ||
  620. (searchElement !== searchElement && currentElement !== currentElement)) {
  621. return true;
  622. }
  623. k++;
  624. }
  625. return false;
  626. };
  627. }