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

511 рядки
15 KiB

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