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

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