engine.js 60 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746
  1. /*
  2. @licstart The following is the entire license notice for the
  3. JavaScript code in this page.
  4. Copyright (C) 2014 - 2015 SylvieLorxu <sylvie@contracode.nl>
  5. This program is free software: you can redistribute it and/or modify
  6. it under the terms of the GNU General Public License as published by
  7. the Free Software Foundation, either version 3 of the License, or
  8. (at your option) any later version.
  9. This program is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. GNU General Public License for more details.
  13. You should have received a copy of the GNU General Public License
  14. along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. @licend The above is the entire license notice
  16. for the JavaScript code in this page.
  17. */
  18. $(document).ready( function() {
  19. window.username = "";
  20. // Register empty command history command
  21. window.commandhistory = [];
  22. window.commandposition = 0;
  23. window.isLooking = false;
  24. window.peer = null; // Multiplayer connectivity
  25. window.conns = []; // List of connections and related data
  26. // Start log timer
  27. setInterval(function() { manageAndShowLog() }, 1000);
  28. // Focus on the input bar
  29. document.getElementById("inputbar").focus();
  30. playing = false;
  31. $("#inputbar").keydown( function(event) {
  32. // Let the user use the up/down keys to go through command history
  33. if (event.keyCode == 38) { // Up
  34. event.preventDefault();
  35. if (commandhistory && commandposition) {
  36. commandposition -= 1;
  37. $("#inputbar").val(commandhistory[commandposition]);
  38. }
  39. } else if (event.keyCode == 40) { // Down
  40. event.preventDefault();
  41. if (commandhistory && commandposition < commandhistory.length) {
  42. commandposition += 1;
  43. $("#inputbar").val(commandhistory[commandposition]);
  44. }
  45. // When the user presses enter and has text in the input field, parse it
  46. } else if (event.which == 13) {
  47. event.preventDefault();
  48. var input = $("#inputbar").val();
  49. if (input) {
  50. if (commandhistory[commandhistory.length-1] != input && ["again", "g"].indexOf(input) == -1) {
  51. commandhistory.push(input);
  52. commandposition = commandhistory.length;
  53. }
  54. parseInput(input);
  55. }
  56. }
  57. });
  58. // Ensure input bar takes all input by ensuring focus on input
  59. $("body").keydown( function(event) {
  60. $("#inputbar").focus();
  61. });
  62. // Save the session on unload
  63. window.addEventListener("beforeunload", function( event ) {
  64. stopMultiplayer();
  65. stopGame();
  66. show("");
  67. });
  68. show("Who will be going on adventure today?");
  69. });
  70. var showHome = function() {
  71. show('<p>Hello ' + window.username + ', welcome to HERITAGE.</p><p>Heritage Equals Retro Interpreting Text Adventure Game Engine.</p><p>Type "help" for help.</p>', "html");
  72. if (supports_html_storage && localStorage.length > 0) {
  73. if (localStorage.games && localStorage.games.length > 0) {
  74. show($("#message").html() + "Cached games found. Type 'games' for a list of local games, or 'cleargames' to delete all locally cached games.</p><p>", "html");
  75. };
  76. if (localStorage.savedGames && localStorage.savedGames.length > 0) {
  77. show($("#message").html() + "Saved sessions found. Type 'loadsave' to load a saved session, or 'clearsaves' to delete all sessions in progress.", "html");
  78. };
  79. };
  80. };
  81. var supports_html_storage = function () {
  82. try {
  83. return 'localStorage' in window && window['localStorage'] !== null;
  84. } catch (e) {
  85. return false;
  86. }
  87. };
  88. var isString = function(value) {
  89. if ((value[0] == '"' && value[value.length-1] == '"') || (value[0] == "'" && value[value.length-1] == "'"))
  90. return true;
  91. return false;
  92. };
  93. var joinWithAnd = function(list) {
  94. return list.splice(0, list.length - 1).join(', ') + " and " + list[list.length-1];
  95. };
  96. var escapeHTML = function( s ) {
  97. return String(s).replace(/&(?!\w+;)/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
  98. };
  99. var stopGame = function() {
  100. if (!playing) return;
  101. show("Saving session...");
  102. saveSession();
  103. show("Saving session... Done!");
  104. };
  105. var sessionify = function(complete) {
  106. if (!info && !info["title"]) { gameinfo["title"] = "Unknown Game" }
  107. if (!info && !gameinfo["author"]) { gameinfo["author"] = "Unknown Author" }
  108. var session = {
  109. 'gameinfo' : gameinfo,
  110. 'variables' : variables,
  111. 'rooms' : rooms,
  112. 'items' : items,
  113. 'actions' : actions,
  114. 'exits' : exits,
  115. 'inventory' : inventory,
  116. };
  117. if (complete) {
  118. session['savetime'] = Date.now();
  119. session['roomhistory'] = roomhistory;
  120. session['currentlocation'] = currentlocation;
  121. };
  122. return session;
  123. };
  124. var saveSession = function() {
  125. var session = sessionify(true);
  126. var games;
  127. if (localStorage.getItem('savedGames')) {
  128. games = JSON.parse(localStorage.getItem('savedGames'));
  129. } else {
  130. games = [];
  131. }
  132. if (typeof loadedgame !== 'undefined') {
  133. // If this is the continuation of a loaded game, override the slot
  134. games[loadedgame] = session;
  135. } else {
  136. games.push(session);
  137. }
  138. localStorage.setItem('savedGames', JSON.stringify(games));
  139. };
  140. var saveGame = function(source) {
  141. var games;
  142. if (localStorage.getItem('games')) {
  143. games = JSON.parse(localStorage.getItem('games'));
  144. } else {
  145. games = [];
  146. };
  147. // Make sure the game isn't already in the library
  148. for (game in games) {
  149. var toCheck = games[game]["gameinfo"];
  150. if (toCheck["author"] == source["gameinfo"]["author"] && toCheck["title"] == source["gameinfo"]["title"] && toCheck["version"] == source["gameinfo"]["version"]) {
  151. return;
  152. };
  153. };
  154. games.push(source);
  155. localStorage.setItem('games', JSON.stringify(games));
  156. };
  157. var loadGameFromLocalStorage = function(id) {
  158. games = JSON.parse(localStorage.games);
  159. window.sources = games[id - 1];
  160. initStepTwo(0);
  161. };
  162. var loadSessionFromLocalStorage = function(id) {
  163. loadedgame = id - 1; // Save the game's slot so we can override it later
  164. var sessions = JSON.parse(localStorage.savedGames);
  165. if (!sessions[loadedgame]) {
  166. show("Could not find session with id " + id);
  167. return;
  168. };
  169. loadSession(sessions[loadedgame]);
  170. };
  171. var loadSession = function(session) {
  172. gameinfo = {};
  173. for (variable in session["gameinfo"]) {
  174. gameinfo[variable] = escapeHTML(session["gameinfo"][variable]);
  175. };
  176. variables = {};
  177. for (variable in session["variables"]) {
  178. variables[variable] = escapeHTML(session["variables"][variable]);
  179. };
  180. rooms = {};
  181. for (variable in session["rooms"]) {
  182. rooms[variable] = {};
  183. for (subvariable in session["rooms"][variable]) {
  184. rooms[variable][subvariable] = escapeHTML(session["rooms"][variable][subvariable]);
  185. };
  186. };
  187. roomhistory = [];
  188. for (variable in session["roomhistory"]) {
  189. roomhistory.push(escapeHTML(variable));
  190. };
  191. items = {};
  192. for (variable in session["items"]) {
  193. items[variable] = {};
  194. for (subvariable in session["items"][variable]) {
  195. items[variable][subvariable] = escapeHTML(session["items"][variable][subvariable]);
  196. };
  197. };
  198. actions = {};
  199. for (variable in session["actions"]) {
  200. actions[variable] = {};
  201. for (subvariable in session["actions"][variable]) {
  202. actions[variable][subvariable] = escapeHTML(session["actions"][variable][subvariable]);
  203. };
  204. };
  205. exits = {};
  206. for (variable in session["exits"]) {
  207. exits[variable] = {};
  208. for (subvariable in session["exits"][variable]) {
  209. exits[variable][subvariable] = escapeHTML(session["exits"][variable][subvariable]);
  210. };
  211. };
  212. inventory = [];
  213. for (variable in session["inventory"]) {
  214. inventory.push(escapeHTML(variable));
  215. };
  216. if (session["currentlocation"]) {
  217. currentlocation = escapeHTML(session["currentlocation"]);
  218. } else {
  219. currentlocation = "0.0.0";
  220. };
  221. };
  222. var show = function(message, type) {
  223. window.isLooking = false;
  224. if (typeof(variables) != "undefined" && getVarValue("_game_over")) {
  225. type = "game_over";
  226. };
  227. if (type != "html") {
  228. message = escapeHTML(message).split("\n").join("<br />");
  229. if (message.substr(0,6) == "<br />") {
  230. message = message.substr(6);
  231. };
  232. };
  233. switch (type) {
  234. case "error": $("#message").html("<span class='error'>" + message + "</span>"); break;
  235. case "html": $("#message").html("<p>" + message + "</p>"); break;
  236. case "game_over": $("#message").html("<p>" + message + "</p><p class='error'>GAME OVER<br />Type 'start' to replay, or 'load' another game.</p>"); playing = false; stopMultiplayer(); break;
  237. default: $("#message").html("<p>" + message + "</p>");
  238. }
  239. };
  240. var addToLog = function(message) {
  241. $("<div>").html('<em>' + message + ' (<span>30</span>)</em>').prependTo("#log").hide().slideDown();
  242. };
  243. var manageAndShowLog = function() {
  244. $('#log > div').each(function() {
  245. var timeelement = $(this).find('em').find('span');
  246. var timevalue = timeelement.text();
  247. timevalue--;
  248. if (timevalue) {
  249. timeelement.text(timevalue);
  250. } else {
  251. $(this).slideUp();
  252. };
  253. });
  254. };
  255. var init = function(gamename) {
  256. playing = false;
  257. // Keep game sources in memory to share on multiplayer and cache locally
  258. window.sources = {gamename: gamename};
  259. var importqueue = 1;
  260. $.get(gamename + "/main.heritage", function( filedata ) {
  261. show("Downloading game file(s)...")
  262. var filedata = filedata.split('\n');
  263. window.sources["main.heritage"] = filedata;
  264. for (var linenumber = 0; linenumber < filedata.length; linenumber++) {
  265. var gameline = filedata[linenumber];
  266. if (gameline.substr(0,7) == "import(") {
  267. importqueue++;
  268. var importname = gameline.substr(7).split(")")[0] + ".heritage";
  269. if (importname.indexOf("../") != -1) {
  270. show("Failed to load import file " + importname + ".heritage (HERITAGE security exception: traversing directory upwards not allowed)", "error");
  271. return;
  272. };
  273. $.get(gamename + "/" + importname, function ( importedgamedata ) {
  274. window.sources[this.url.slice(this.url.lastIndexOf("/") + 1)] = importedgamedata.split('\n');
  275. importqueue--;
  276. initStepTwo(importqueue);
  277. }, "text").fail(function() { show("Failed to load import file " + this.url.slice(this.url.lastIndexOf("/") + 1) + " (AJAX request failed)", "error"); return;});
  278. }
  279. };
  280. importqueue--;
  281. initStepTwo(importqueue);
  282. }, "text").fail(function() { show("Failed to load game files (AJAX request failed)", "error") });
  283. };
  284. var initStepTwo = function(importqueue) {
  285. // Wait until all importing is done
  286. if (importqueue > 0) return;
  287. show("Combining game sources...");
  288. var gamedata = [];
  289. var imports = [];
  290. for (var i = 0; i < window.sources["main.heritage"].length; i++) {
  291. if (window.sources["main.heritage"][i].substr(0,7) == "import(") {
  292. var importname = window.sources["main.heritage"][i].substr(7).split(")")[0] + ".heritage";
  293. imports.push(importname);
  294. for (var j = 0; j < window.sources[importname].length; j++) {
  295. gamedata.push(window.sources[importname][j]);
  296. };
  297. } else {
  298. gamedata.push(window.sources["main.heritage"][i]);
  299. };
  300. };
  301. initComplete(gamedata, imports);
  302. };
  303. var initComplete = function(gamedata, imports) {
  304. // Load all the game data into Javascript variables so that it can be played
  305. currentsetting = "";
  306. gameinfo = {};
  307. variables = {"_game_over": 0, "_turn": 0, "_write_to": 0};
  308. rooms = {};
  309. roomhistory = [];
  310. items = {};
  311. actions = {};
  312. exits = {};
  313. inventory = [];
  314. currentmode = null;
  315. for (var linenumber = 0; linenumber < gamedata.length; linenumber++) {
  316. show("Parsing game... (" + linenumber + "/" + gamedata.length + ")");
  317. var gameline = gamedata[linenumber].replace(/\/\*.*?\*\//g, "").trim(); // Trim the line and remove all comments
  318. // Prevent HERITAGE from showing a blank line if the line is completely comment
  319. if (!gameline && gamedata[linenumber].trim()) continue;
  320. if (gameline.substr(0,7) == "import(") continue;
  321. var newmode = initGetMode(gameline, currentmode);
  322. if (currentmode && (currentmode == newmode)) {
  323. parseForMode(gameline, currentmode);
  324. };
  325. var currentmode = newmode;
  326. };
  327. window.sources["gameinfo"] = gameinfo;
  328. saveGame(window.sources);
  329. currentlocation = "0.0.0";
  330. // Done initializing, display info!
  331. gamemessage = "";
  332. for (info in gameinfo) {
  333. gamemessage += escapeHTML(info + ": " + gameinfo[info]) + "<br />";
  334. };
  335. var sourcemessage = "Source file(s): <a href='" + window.sources["gamename"] + "/main.heritage'>main.heritage</a>";
  336. $.each(imports, function() {
  337. sourcemessage += " <a href='" + window.sources["gamename"] + "/" + this + "'>" + this + "</a>";
  338. });
  339. show(gamemessage + "<br />" + sourcemessage + "<br /><br />Type 'start' to start a private game, or 'start multiplayer' to start a multiplayer game.", "html");
  340. };
  341. var startGame = function(multiplayer) {
  342. playing = true;
  343. if (multiplayer) {
  344. startServer();
  345. };
  346. userLook();
  347. };
  348. var initGetMode = function(line, currentmode) {
  349. if (line.substr(0,5) == "info(") {
  350. return ["info"];
  351. } else if (line.substr(0,4) == "var(") {
  352. varandvalue = line.substr(4).split(")")[0];
  353. if (varandvalue.indexOf(",") > -1) {
  354. setVarValue(varandvalue.split(",")[0].trim(), varandvalue.split(",")[1].trim());
  355. } else {
  356. setVarValue(varandvalue, 0);
  357. };
  358. } else if (line.substr(0,5) == "room(") {
  359. var roomlocation = line.substr(5).split(")")[0].trim();
  360. rooms[roomlocation] = {};
  361. // These need to exist, so make them empty in case the game doesn't define them
  362. rooms[roomlocation]["description"] = "";
  363. rooms[roomlocation]["items"] = "";
  364. rooms[roomlocation]["exits"] = "";
  365. return ["room", roomlocation];
  366. } else if (line.substr(0,5) == "item(") {
  367. var iteminfo = line.substr(5).split(")")[0].trim();
  368. items[iteminfo] = {};
  369. return ["item", iteminfo]
  370. } else if (line.substr(0,7) == "action(") {
  371. var actioninfo = line.substr(7).split(")")[0].trim();
  372. var dotsplit = actioninfo.lastIndexOf(".");
  373. if (dotsplit > -1) {
  374. var addtophrase = actioninfo.substr(dotsplit);
  375. var actioninforeal = actioninfo.substr(0,dotsplit);
  376. } else {
  377. var addtophrase = "";
  378. var actioninforeal = actioninfo;
  379. }
  380. phrases = actioninforeal.split("|");
  381. for (phrase in phrases) {
  382. phrase = phrases[phrase].trim() + addtophrase;
  383. actions[phrase] = {};
  384. };
  385. return ["action", actioninfo];
  386. } else if (line.substr(0,5) == "exit(") {
  387. var exitinfo = line.substr(5).split(")")[0].trim();
  388. exits[exitinfo] = {};
  389. return ["exit", exitinfo];
  390. } else {
  391. return currentmode;
  392. };
  393. };
  394. var returnSettingAndValue = function(line) {
  395. line = line.trim();
  396. linesplit = line.indexOf(":");
  397. firstspace = line.indexOf(" ");
  398. checkorset = ["$(", "#("].indexOf(line.substr(0,2)) > -1;
  399. if (checkorset | linesplit == -1 | firstspace < linesplit) {
  400. return [currentsetting, line];
  401. } else {
  402. currentsetting = line.substr(0,linesplit).trim();
  403. return [currentsetting, line.substr(linesplit+1).trim()];
  404. }
  405. };
  406. var parseForMode = function(line, currentmode) {
  407. switch (currentmode[0]) {
  408. case "info":
  409. var linedata = returnSettingAndValue(line);
  410. if (!gameinfo[linedata[0]]) {
  411. gameinfo[linedata[0]] = "";
  412. } else {
  413. gameinfo[linedata[0]] += "\n";
  414. }
  415. gameinfo[linedata[0]] += linedata[1];
  416. break;
  417. case "room":
  418. var linedata = returnSettingAndValue(line);
  419. if (!rooms[currentmode[1]][linedata[0]]) {
  420. rooms[currentmode[1]][linedata[0]] = "";
  421. } else {
  422. if (["description"].indexOf(linedata[0]) > -1 || ["first_enter"].indexOf(linedata[0]) > -1) {
  423. rooms[currentmode[1]][linedata[0]] += "\n";
  424. }
  425. }
  426. rooms[currentmode[1]][linedata[0]] += linedata[1];
  427. break;
  428. case "item":
  429. var itemdata = returnSettingAndValue(line);
  430. if (!items[currentmode[1]][itemdata[0]]) {
  431. items[currentmode[1]][itemdata[0]] = "";
  432. } else {
  433. items[currentmode[1]][itemdata[0]] += "\n";
  434. }
  435. items[currentmode[1]][itemdata[0]] += itemdata[1].trim();
  436. break;
  437. case "action":
  438. var dotsplit = currentmode[1].lastIndexOf(".");
  439. if (dotsplit > -1) {
  440. var addtophrase = currentmode[1].substr(dotsplit);
  441. var phrasedata = currentmode[1].substr(0,dotsplit);
  442. } else {
  443. var addtophrase = "";
  444. var phrasedata = currentmode[1];
  445. }
  446. var phrases = phrasedata.split("|");
  447. for (phrase in phrases) {
  448. var phrase = phrases[phrase].trim() + addtophrase;
  449. var linedata = returnSettingAndValue(line);
  450. if (!actions[phrase][linedata[0]]) { actions[phrase][linedata[0]] = ""; }
  451. if (["succeed", "fail"].indexOf(linedata[0]) > -1 && actions[phrase][linedata[0]]) { actions[phrase][linedata[0]] += "\n"; }
  452. actions[phrase][linedata[0]] += linedata[1];
  453. };
  454. break;
  455. case "exit":
  456. var linedata = returnSettingAndValue(line);
  457. if (!exits[currentmode[1]][linedata[0]]) { exits[currentmode[1]][linedata[0]] = ""; }
  458. if (["succeed", "fail"].indexOf(linedata[0]) > -1 && exits[currentmode[1]][linedata[0]]) { exits[currentmode[1]][linedata[0]] += "\n"; }
  459. exits[currentmode[1]][linedata[0]] += linedata[1];
  460. break;
  461. };
  462. };
  463. var parseInput = function(input) {
  464. /* This function receives the input, and passes it on to another function
  465. * which will return 0 on success, and non-zero on fail.
  466. * If the input was succesful, and we're playing a match, we increment the
  467. * current turn pseudo-variable by one.
  468. */
  469. $( "#inputbar" ).val("");
  470. if (!window.username) {
  471. setUsername(input);
  472. return;
  473. };
  474. var failure = parseInputReal(input);
  475. if(!failure) {
  476. setVarValue("_turn", getVarValue("_turn") + 1);
  477. };
  478. };
  479. var parseInputReal = function(input) {
  480. /* This function parses the input and returns an error code.
  481. * 0 = success
  482. * 1 = succesful command that should not be counted as a turn
  483. * 2 = invalid command
  484. * 3 = invalid parameters
  485. * 4 = executing command changes nothing
  486. */
  487. var input = input.trim();
  488. var splitinput = input.split(" ");
  489. if (playing && getVarValue("_write_to") != 0) {
  490. setVarValue(getVarValue("_write_to"), '"' + input + '"');
  491. setVarValue("_write_to", 0);
  492. parseInputReal("look");
  493. return 1;
  494. };
  495. var only_direction = false;
  496. switch (input) {
  497. // Shortcuts for directions
  498. case "n":
  499. splitinput[1] = "north";
  500. only_direction = true;
  501. break;
  502. case "ne":
  503. splitinput[1] = "northeast";
  504. only_direction = true;
  505. break;
  506. case "e":
  507. splitinput[1] = "east";
  508. only_direction = true;
  509. break;
  510. case "se":
  511. splitinput[1] = "southeast";
  512. only_direction = true;
  513. break;
  514. case "s":
  515. splitinput[1] = "south";
  516. only_direction = true;
  517. break;
  518. case "sw":
  519. splitinput[1] = "southwest";
  520. only_direction = true;
  521. break;
  522. case "w":
  523. splitinput[1] = "west";
  524. only_direction = true;
  525. break;
  526. case "nw":
  527. splitinput[1] = "northwest";
  528. only_direction = true;
  529. break;
  530. case "up":
  531. splitinput[1] = "up";
  532. only_direction = true;
  533. break;
  534. case "down":
  535. splitinput[1] = "down";
  536. only_direction = true;
  537. break;
  538. };
  539. if (only_direction) {
  540. splitinput[0] = "go";
  541. input = "go " + splitinput[1];
  542. };
  543. if (splitinput[0][0] == '/') {
  544. coreCommand = true;
  545. splitinput[0] = splitinput[0].slice(1);
  546. } else {
  547. coreCommand = false;
  548. };
  549. // Core functions
  550. switch (splitinput[0]) {
  551. case "help":
  552. if (playing && !coreCommand)
  553. break;
  554. show("Type 'load &lt;gamename/URL&gt;' to load a game. An example game is available under the name 'example' (type 'load example' to load it).</p><p>When in-game, you can look around using 'look', go somewhere using 'go', take something using 'take' or 'grab' and check your inventory using 'inventory'.</p><p>That is all for the introduction.</p><p>Remember, games can register any commands themselves. 'examine' posters, 'sit on' a chair, experiment and have fun!", "html");
  555. return 1;
  556. case "games":
  557. if (playing && !coreCommand)
  558. break;
  559. var toShow = ["To load a game, type 'load' followed by the game number.<br/>To get a list of games in progress, type 'saves'.<br/>"];
  560. JSON.parse(localStorage.getItem('games')).forEach(function( gamedata ) {
  561. toShow.push(toShow.length + ". " + gamedata["gameinfo"]["title"] + " by " + gamedata["gameinfo"]["author"] + " (version " + gamedata["version"] + ")");
  562. });
  563. show(toShow.join("<br />"), "html");
  564. return 1;
  565. case "cleargames":
  566. if (playing && !coreCommand)
  567. break;
  568. // Delete all games
  569. localStorage.games = [];
  570. showHome();
  571. return 1;
  572. case "load":
  573. if (playing && !coreCommand)
  574. break;
  575. // Start initializing the chosen game
  576. if (splitinput.length > 1) {
  577. if (splitinput.length == 2 && parseInt(splitinput[1]) == splitinput[1]) {
  578. loadGameFromLocalStorage(splitinput[1]);
  579. } else {
  580. var toload = splitinput.splice(1).join("%20");
  581. init(toload);
  582. };
  583. } else {
  584. show("Error: Incorrect argument count. Correct usage: 'load <gamename/URL/gamenumber>'.", "error");
  585. };
  586. return 1;
  587. case "saves":
  588. if (playing && !coreCommand)
  589. break;
  590. var toShow = ["To restore a session, type 'loadsave' followed by the session number.<br />"];
  591. JSON.parse(localStorage.getItem('savedGames')).forEach(function( sessiondata ) {
  592. toShow.push(toshow.length + ". " + sessiondata["gameinfo"]["title"] + " by " + sessiondata["gameinfo"]["author"] + " (" + new Date(sessiondata["savetime"]).toString() + ")");
  593. });
  594. show(toShow.join("<br />"), "html");
  595. return 1;
  596. case "loadsave":
  597. if (playing && !coreCommand)
  598. break;
  599. // Restore a saved session
  600. if (localStorage.savedGames.length == 0) {
  601. show("There are no sessions in progress to load");
  602. return 1;
  603. }
  604. if (splitinput.length > 1) {
  605. loadSessionFromLocalStorage(splitinput[1]);
  606. } else {
  607. show("Error: Incorrect argument count. Correct usage: 'loadsave <savenumber>'.", "error");
  608. }
  609. return 1;
  610. case "clearsaves":
  611. if (playing && !coreCommand)
  612. break;
  613. // Delete all saves
  614. localStorage.savedGames = [];
  615. showHome();
  616. return 1;
  617. case "start":
  618. if (!playing) {
  619. if (typeof(variables) != "undefined") {
  620. setVarValue("_game_over", 0);
  621. };
  622. if (window.sources) {
  623. initStepTwo();
  624. };
  625. if (splitinput.length == 1) {
  626. startGame();
  627. } else if (splitinput.length == 2 && splitinput[1] == 'multiplayer') {
  628. startGame(true);
  629. };
  630. return 1;
  631. } else if (coreCommand && splitinput.length == 2 && splitinput[1] == 'multiplayer') {
  632. startServer();
  633. return 1;
  634. };
  635. break;
  636. case "stop":
  637. if (playing && !coreCommand)
  638. break;
  639. if (splitinput.length == 1) {
  640. stopMultiplayer();
  641. stopGame();
  642. showHome();
  643. } else if (splitinput.length == 2 && splitinput[1] == 'multiplayer') {
  644. stopMultiplayer();
  645. };
  646. return 1;
  647. case "again":
  648. case "g":
  649. parseInput(commandhistory[commandhistory.length-1]);
  650. return 1;
  651. case "inventory":
  652. case "i":
  653. if (!playing)
  654. break;
  655. userInventory();
  656. return 1;
  657. case "go":
  658. if (!playing)
  659. break;
  660. if (splitinput.length < 1) {
  661. show("Error: Incorrect argument count. Correct usage: 'go <direction>'.", "error");
  662. return 3;
  663. };
  664. var movefail = userMove(splitinput.slice(1).join(" "), false);
  665. if (!movefail) {
  666. sendMultiplayerMessage("location", currentlocation);
  667. userLook();
  668. };
  669. return 0;
  670. case "take":
  671. case "grab":
  672. case "pick":
  673. if (!playing)
  674. break;
  675. var errcode = userTake(splitinput, false);
  676. switch (errcode) {
  677. case 0: return 0;
  678. case 1: show("Error: Incorrect argument count. Correct usage: 'take <itemname>'.", "error"); return 3;
  679. case 2: break; // It starts with pick but it's not "pick up"
  680. default: return 3;
  681. };
  682. case "look":
  683. case "l":
  684. if (!playing)
  685. break;
  686. // Ensure the user is only looking. Items should have their own on_look_at handler
  687. if (splitinput.length == 1) {
  688. userLook();
  689. return 1;
  690. };
  691. break;
  692. case "wait":
  693. case "z":
  694. if (!playing)
  695. break;
  696. show("You wait...");
  697. return 0;
  698. case "x":
  699. command = "examine";
  700. input = "examine " + input.substr(2);
  701. break;
  702. case "join":
  703. if (playing && !coreCommand)
  704. break;
  705. if (splitinput.length != 2) {
  706. show("Error: Incorrect argument count. Correct usage: '/join <id>'.", "error");
  707. return 3;
  708. };
  709. prepareConnect();
  710. connectToPlayer(splitinput[1]);
  711. return 1;
  712. case "say":
  713. if (!playing || !coreCommand)
  714. break;
  715. sendMultiplayerChatMessage(splitinput.slice(1).join(" "));
  716. return 1;
  717. case "username":
  718. if (playing && !coreCommand)
  719. break;
  720. setUsername(splitinput.slice(1).join(" "));
  721. return 1;
  722. };
  723. if (!playing) {
  724. if (typeof(variables) == "undefined" || !getVarValue("_game_over")) {
  725. show("Invalid command.");
  726. };
  727. return 1;
  728. };
  729. // Check for actions
  730. var toshow = "";
  731. var success = false;
  732. for (action in actions) {
  733. var action = action;
  734. var dotsplit = action.lastIndexOf(".");
  735. if (dotsplit > -1) {
  736. var actionname = action.substr(0,dotsplit);
  737. } else {
  738. var actionname = action;
  739. }
  740. if (actionname == input.replace(/ /g, "_")) {
  741. if (conditionsSatisfied(actions[action])) {
  742. success = true;
  743. executeActions(actions[action]);
  744. if (actions[action]["succeed"]) {
  745. var addtotoshow = format(actions[action]["succeed"]);
  746. if (addtotoshow) { toshow += "\n" + addtotoshow; }
  747. }
  748. } else {
  749. if (actions[action]["fail"]) {
  750. var addtotoshow = format(actions[action]["fail"]);
  751. if (addtotoshow) { toshow += "\n" + addtotoshow; }
  752. }
  753. }
  754. }
  755. };
  756. if (toshow) {
  757. show(toshow);
  758. if (success) {
  759. return 0;
  760. } else {
  761. return 4;
  762. }
  763. };
  764. // Check for specific item functions
  765. var roomitems = getRoomItems(currentlocation);
  766. for (item in roomitems) {
  767. var item = roomitems[item];
  768. var inputitemname = "";
  769. if (item.substr(0,4) == "syn:") {
  770. // Translate synonym
  771. var itemdata = item.substr(4).split(":");
  772. var item = itemdata[1];
  773. var inputitemname = itemdata[0].split("_");
  774. }
  775. if (item.indexOf(".") > -1) {
  776. var itemdata = item.split(".");
  777. var itemname = itemdata[0].split("_");
  778. var iteminstance = "." + itemdata[1];
  779. } else {
  780. var itemname = item.split("_");
  781. var iteminstance = "";
  782. }
  783. if (!inputitemname) { var inputitemname = itemname; };
  784. if (splitinput.slice(-itemname.length).join("_") == inputitemname.join("_") ) {
  785. var itemhandler = splitinput.slice(0, splitinput.length-inputitemname.length);
  786. var tofind = "on_" + itemhandler.join("_");
  787. var itemfind = inputitemname.join("_") + iteminstance;
  788. if (items[itemfind] && items[itemfind][tofind]) {
  789. show(format(items[itemfind][tofind]));
  790. return 0;
  791. } else {
  792. show("I don't know how to " + itemhandler.join(" ") + " the " + inputitemname.join(" ") + ".");
  793. return 4;
  794. }
  795. }
  796. };
  797. // Generic error
  798. show("I don't know how to " + input + ".");
  799. return 1;
  800. };
  801. var conditionsSatisfied = function(objectid) {
  802. for (condition in objectid) {
  803. var conditions = objectid[condition].replace(/ /g, "").split(",");
  804. switch (condition) {
  805. case "require_location":
  806. var requiredlocation = conditions[0];
  807. var requiredlist = conditions.slice(1);
  808. for (required in requiredlist) {
  809. if (getRoomItems(requiredlocation).indexOf(requiredlist[required]) == -1) { return false; };
  810. };
  811. break;
  812. case "require_here":
  813. for (required in conditions) {
  814. if (getRoomItems(currentlocation).indexOf(conditions[required]) == -1) { return false; };
  815. };
  816. break;
  817. case "require_inventory":
  818. for (required in conditions) {
  819. if (inventory.indexOf(conditions[required]) == -1) { return false; };
  820. };
  821. break;
  822. case "equals":
  823. if (getVarValue(conditions[0]) != conditions[1]) { return false; };
  824. break;
  825. case "less_than":
  826. if (getVarValue(conditions[0]) > conditions[1]) { return false; };
  827. break;
  828. case "more_than":
  829. if (getVarValue(conditions[0]) < conditions[1]) { return false; };
  830. break;
  831. };
  832. };
  833. return true;
  834. };
  835. var executeActions = function(objectid) {
  836. for (action in objectid) {
  837. var itemlist = objectid[action].replace(/ /g, "").split(",");
  838. switch (action) {
  839. case "lose":
  840. for (item in itemlist) {
  841. var item = itemlist[item];
  842. var index = inventory.indexOf(item);
  843. if (index > -1) {
  844. inventory.splice(index, 1);
  845. sendMultiplayerMessage("inventoryremove", item);
  846. };
  847. };
  848. break;
  849. case "gain":
  850. for (item in itemlist) {
  851. var item = itemlist[item];
  852. inventory.push(item);
  853. sendMultiplayerMessage("inventoryadd", item);
  854. };
  855. break;
  856. case "drop":
  857. for (item in itemlist) {
  858. var item = itemlist[item];
  859. var index = inventory.indexOf(item);
  860. if (index > -1) {
  861. inventory.splice(index, 1);
  862. sendMultiplayerMessage("inventoryremove", item);
  863. };
  864. addRoomItem(currentlocation, item);
  865. };
  866. break;
  867. case "disappear":
  868. for (item in itemlist) {
  869. var item = itemlist[item];
  870. removeRoomItem(currentlocation, item);
  871. };
  872. break;
  873. };
  874. };
  875. };
  876. var setUsername = function(username) {
  877. username = escapeHTML(username.trim());
  878. if (!username) {
  879. show("Please enter a valid name.", "error");
  880. return;
  881. };
  882. window.username = username;
  883. if (playing) {
  884. sendMultiplayerMessage("name", window.username);
  885. userLook();
  886. } else {
  887. // Check if a game URL has already been passed (example.com/HERITAGE/?url_to_load)
  888. var toload = window.location.search.substring(1);
  889. if (toload) {
  890. parseInput("load " + toload);
  891. return;
  892. };
  893. showHome();
  894. };
  895. };
  896. var userLook = function() {
  897. if (rooms[currentlocation]["first_enter"] && (roomhistory.indexOf(currentlocation) == -1)) {
  898. show(format(rooms[currentlocation]["first_enter"]));
  899. roomhistory.push(currentlocation);
  900. } else {
  901. var otherplayers = [];
  902. for (var i = 0; i < window.conns.length; i++) {
  903. if (window.conns[i]._location == currentlocation) {
  904. otherplayers.push(window.conns[i]._nickname);
  905. };
  906. };
  907. var roomdescription = format(rooms[currentlocation]["description"]);
  908. if (otherplayers.length == 0) {
  909. show(roomdescription);
  910. } else if (otherplayers.length == 1) {
  911. show(roomdescription + '\n\n' + otherplayers[0] + " is here too.");
  912. } else {
  913. show(roomdescription + '\n\n' + joinWithAnd(otherplayers) + " are here too.");
  914. };
  915. window.isLooking = true;
  916. };
  917. };
  918. var format = function(text) {
  919. /* Format and calculate text and its values
  920. * This format finds the most inner check, and then calculates outwards.
  921. *
  922. * However, we only take care of #(changeVarValue)# in the second round,
  923. * because this action is destructive and should not be executed unless
  924. * we're sure all conditions are satisfied.
  925. *
  926. * Example order:
  927. * $(Third #(Fourth @(Second !(First)! )@ #) )$
  928. */
  929. var minindex = 0;
  930. var characters = ["!@$", "#"];
  931. var round = 0;
  932. while(true) {
  933. var checkon = text.substr(minindex);
  934. var closingposition = checkon.indexOf(")");
  935. if (closingposition == -1) {
  936. if (round == 0) {
  937. minindex = 0;
  938. round = 1;
  939. continue;
  940. } else {
  941. break;
  942. };
  943. };
  944. var character = checkon[closingposition + 1];
  945. if (characters[round].indexOf(character) == -1) {
  946. minindex = closingposition+1;
  947. continue;
  948. };
  949. var start = text.substr(0, minindex + closingposition).lastIndexOf(character + "(");
  950. if (start == -1) {
  951. break;
  952. };
  953. var manipulatetext = text.substr(start + 2, minindex + closingposition - start - 2);
  954. switch(character) {
  955. case "!": var newtext = echoVar(manipulatetext); break;
  956. case "@": var newtext = calculateVarValue(manipulatetext); break;
  957. case "#": var newtext = ""; changeVarValue(manipulatetext); break;
  958. case "$": var newtext = formatVariableText(manipulatetext); break;
  959. };
  960. text = text.substr(0, start) + newtext + text.substr(minindex + closingposition + 2);
  961. minindex = 0;
  962. };
  963. return text;
  964. };
  965. var getVarValue = function(variable) {
  966. /* Returns the value of real and pseudo-variables
  967. * Available pseudo-variables:
  968. * _random: returns a random number from 1 through 100 (inclusive)
  969. * _yesno: returns either 0 or 1
  970. * _turn: get the current turn
  971. */
  972. switch(variable) {
  973. case "_name": return window.username;
  974. case "_random": return parseInt(Math.random() * (100 - 1) + 1);
  975. case "_yesno": return parseInt(Math.random());
  976. };
  977. if (variables[variable] == null) {
  978. console.log("Variable " + variable + " does not exist. Did you forget to initialize it? Returning 0");
  979. return 0;
  980. };
  981. if (parseInt(variables[variable]) != variables[variable] && !variables[variables[variable]] && !isString(variables[variable])) {
  982. console.log('Variable ' + variable + ' refers to non-existent variable ' + variables[variable] + '. Did you mean to set it to "' + variables[variable] + '"? Returning 0.');
  983. return 0;
  984. };
  985. return variables[variable];
  986. };
  987. var setVarValue = function(variable, value, broadcast) {
  988. broadcast = typeof broadcast !== 'undefined' ? broadcast : true;
  989. variables[variable] = value;
  990. if (broadcast && variable[0] != "_") {
  991. sendMultiplayerMessage("var", [variable, value]);
  992. };
  993. };
  994. var calculateNewValue = function(variable, operator, value) {
  995. if (!isString(value) && parseInt(value) != value) {
  996. value = getVarValue(value);
  997. };
  998. if (operator != "=" && isString(value)) {
  999. console.log("Cannot calculate on string value. Variable: " + variable + ". Operator: " + operator + ". Value: " + value);
  1000. return value;
  1001. };
  1002. switch(operator) {
  1003. case "+": return variable += value;
  1004. case "-": return variable -= value;
  1005. case "/": return variable /= value;
  1006. case "*": return variable *= value;
  1007. case "%": return variable %= value;
  1008. default: return value;
  1009. };
  1010. };
  1011. var getOperator = function(text) {
  1012. /* I wanted to return the operator in the for loop here, otherwise null,
  1013. * but JavaScript decided that readable code is a bad thing.
  1014. */
  1015. var operators = ["=", "+", "-", "/", "*", "%"];
  1016. var result = null;
  1017. operators.forEach(function(operator) {
  1018. if (text.indexOf(operator) > -1) {
  1019. result = operator;
  1020. };
  1021. });
  1022. return result;
  1023. };
  1024. var echoVar = function(text) {
  1025. var value = getVarValue(text);
  1026. if (isString(value)) {
  1027. return value.substr(1, value.length-2);
  1028. } else {
  1029. return value;
  1030. };
  1031. };
  1032. var calculateVarValue = function(text) {
  1033. /* Return the result of an operation on a variable, without changing the
  1034. * value of the original variable
  1035. */
  1036. var operator = getOperator(text);
  1037. if (!operator) {
  1038. console.log("Invalid statement: @(" + text + ")@");
  1039. return "";
  1040. };
  1041. var variable = text.split(operator)[0];
  1042. var value = text.split(operator)[1];
  1043. return calculateNewValue(variable, operator, value);
  1044. };
  1045. var changeVarValue = function(text) {
  1046. /* Change the value of a variable
  1047. * This function overwrites the original variable
  1048. */
  1049. var operator = getOperator(text);
  1050. if (!operator) {
  1051. console.log("Invalid statement: #(" + text + ")#");
  1052. return;
  1053. };
  1054. var variable = text.split(operator)[0];
  1055. var value = text.split(operator)[1];
  1056. if (["_random", "_turn"].indexOf(variable) > -1) {
  1057. console.log("Cannot write to internal variable " + variable);
  1058. return;
  1059. } else if (variable == "_write_to") {
  1060. setVarValue(variable, value);
  1061. return;
  1062. };
  1063. setVarValue(variable, calculateNewValue(variable, operator, value));
  1064. return;
  1065. };
  1066. var formatVariableText = function(text) {
  1067. // Remove or add text depending on certain status
  1068. var requirement = text.split(";")[0];
  1069. var requirement_type = requirement.split(":")[0];
  1070. var requirement = requirement.split(":")[1];
  1071. var text = text.substr(2+requirement_type.length+requirement.length);
  1072. var else_position = findSingle(text, "|");
  1073. if (else_position > -1) {
  1074. var text_if_false = text.substr(else_position + 1);
  1075. var text_if_true = text.substr(0, else_position);
  1076. } else {
  1077. var text_if_false = "";
  1078. var text_if_true = text;
  1079. };
  1080. var tocheck = {};
  1081. if (requirement_type[0] == "!") {
  1082. var requirement_type = requirement_type.substr(1);
  1083. tocheck[requirement_type] = requirement.replace(/ /g,'');
  1084. if (!conditionsSatisfied(tocheck)) {
  1085. text = text_if_true;
  1086. } else {
  1087. text = text_if_false;
  1088. };
  1089. } else {
  1090. tocheck[requirement_type] = requirement.replace(/ /g,'');
  1091. if (conditionsSatisfied(tocheck)) {
  1092. text = text_if_true;
  1093. } else {
  1094. text = text_if_false;
  1095. };
  1096. };
  1097. return text.replace(/\|\|/g,'|');
  1098. };
  1099. var findSingle = function(string, seperator) {
  1100. /* Finds the first single instance of seperator.
  1101. *
  1102. * Example:
  1103. * seperator: |
  1104. * This is || a seperated | string | yeah
  1105. * ^ Return this position
  1106. */
  1107. var index = 0;
  1108. while(true) {
  1109. var check = string.substr(index);
  1110. var found_at = check.indexOf(seperator);
  1111. if (found_at == -1) {
  1112. return -1;
  1113. };
  1114. if (check[found_at+1] != seperator) {
  1115. return found_at + index;
  1116. };
  1117. index = found_at + 2;
  1118. };
  1119. };
  1120. var getRoomItems = function(roomname) {
  1121. var itemlist = format(rooms[roomname]["items"]).replace(/ /g,'').split(",");
  1122. var founditems = [];
  1123. for (item in itemlist) {
  1124. item = itemlist[item];
  1125. var index = item.lastIndexOf(".");
  1126. if (index > -1) {
  1127. var special = item.substr(index);
  1128. item = item.substr(0,index);
  1129. } else {
  1130. var special = "";
  1131. }
  1132. var itemslist = item.split("|");
  1133. for (item in itemslist) {
  1134. if (item == 0) {
  1135. founditems.push(itemslist[item]+special);
  1136. } else {
  1137. founditems.push("syn:"+itemslist[item]+":"+itemslist[0]+special);
  1138. }
  1139. };
  1140. };
  1141. return founditems;
  1142. };
  1143. var addRoomItem = function(roomname, itemname, broadcast) {
  1144. broadcast = typeof broadcast !== 'undefined' ? broadcast : true;
  1145. rooms[roomname]["items"] += "," + itemname;
  1146. if (broadcast) {
  1147. sendMultiplayerMessage("roomitemadd", [roomname, itemname]);
  1148. };
  1149. };
  1150. var removeRoomItem = function(roomname, itemname, broadcast) {
  1151. broadcast = typeof broadcast !== 'undefined' ? broadcast : true;
  1152. // TODO: This code doesn't care for edge cases /at all/. Could cause problems later on.
  1153. itemindex = rooms[roomname]["items"].indexOf(itemname);
  1154. rooms[roomname]["items"] = rooms[roomname]["items"].substr(0,itemindex) + rooms[roomname]["items"].substr(itemindex+itemname.length+1);
  1155. if (broadcast) {
  1156. sendMultiplayerMessage("roomitemremove", [roomname, itemname]);
  1157. };
  1158. };
  1159. var getRoomExits = function(roomname) {
  1160. // Synonyms are added as syn:synonym_name:original_name exits
  1161. var exitlist = format(rooms[roomname]["exits"]).replace(/ /g,'').split(",");
  1162. var foundexits = [];
  1163. for (exit in exitlist) {
  1164. exit = exitlist[exit];
  1165. var index = exit.lastIndexOf(".");
  1166. if (index > -1) {
  1167. var special = exit.substr(index);
  1168. exit = exit.substr(0,index);
  1169. } else {
  1170. var special = "";
  1171. }
  1172. var exitslist = exit.split("|");
  1173. for (exit in exitslist) {
  1174. if (exit == 0) {
  1175. foundexits.push(exitslist[exit]+special);
  1176. } else {
  1177. foundexits.push("syn:"+exitslist[exit]+":"+exitslist[0]);
  1178. }
  1179. };
  1180. };
  1181. return foundexits;
  1182. };
  1183. var userInventory = function() {
  1184. if (inventory.length > 0) {
  1185. /* TODO: Properly display an item of which we have more than one copy or special items */
  1186. show("You are holding: " + inventory.join(", ") + ".");
  1187. } else {
  1188. show("Your inventory is empty.");
  1189. };
  1190. };
  1191. var userMove = function(direction, silent) {
  1192. inputdirection = direction;
  1193. roomexits = [];
  1194. specialexits = [];
  1195. exitlist = getRoomExits(currentlocation);
  1196. for (exit in exitlist) {
  1197. exit = exitlist[exit];
  1198. if (exit.indexOf(".") > -1) {
  1199. exit = exit.split(".");
  1200. specialexits[exit[0]] = exit[1];
  1201. exit = exit[0];
  1202. } else if (exit.substr(0,4) == "syn:") {
  1203. // Translate synonym
  1204. exitdata = exit.substr(4).split(":");
  1205. if (direction == exitdata[0]) {
  1206. direction = exitdata[1];
  1207. };
  1208. } else {
  1209. roomexits.push(exit);
  1210. };
  1211. };
  1212. if (roomexits.indexOf(direction) > -1) {
  1213. newlocation = calculateNewLocation(direction);
  1214. if (rooms[newlocation]) {
  1215. currentlocation = newlocation;
  1216. } else {
  1217. show("GAME ERROR: Exit points to a non-existent location. Please file a bug report to the game's creator, telling them that the exit " + inputdirection + " in room " + currentlocation + " is leading nowhere. Location was not changed.", "error");
  1218. return 1
  1219. };
  1220. return
  1221. } else if (specialexits[direction]) {
  1222. if (conditionsSatisfied(exits[specialexits[direction]])) {
  1223. if (exits[specialexits[direction]]["new_location"]) {
  1224. newlocation = exits[specialexits[direction]]["new_location"];
  1225. } else {
  1226. newlocation = calculateNewLocation(direction);
  1227. };
  1228. if (rooms[newlocation]) {
  1229. currentlocation = newlocation;
  1230. } else {
  1231. show("GAME ERROR: Exit points to a non-existent location. Please file a bug report to the game's creator, telling them that the exit " + inputdirection + " in room " + currentlocation + " is leading nowhere. Location was not changed.", "error");
  1232. return 1
  1233. };
  1234. } else {
  1235. show(exits[specialexits[direction]]["fail"]);
  1236. return 1
  1237. };
  1238. } else if (!silent) {
  1239. show("I can't go " + inputdirection + ".");
  1240. return 1
  1241. };
  1242. return
  1243. };
  1244. var calculateNewLocation = function(direction) {
  1245. // Split location into X, Y, Z
  1246. newlocation = currentlocation.split(".");
  1247. switch (direction) {
  1248. case "north":
  1249. newlocation[1] = parseInt(newlocation[1]); newlocation[1]++; break;
  1250. case "east":
  1251. newlocation[0] = parseInt(newlocation[0]); newlocation[0]++; break;
  1252. case "south":
  1253. newlocation[1] = parseInt(newlocation[1]); newlocation[1]--; break;
  1254. case "west":
  1255. newlocation[0] = parseInt(newlocation[0]); newlocation[0]--; break;
  1256. case "up":
  1257. newlocation[2] = parseInt(newlocation[2]); newlocation[2]++; break;
  1258. case "down":
  1259. newlocation[2] = parseInt(newlocation[2]); newlocation[2]--; break;
  1260. case "northeast":
  1261. newlocation[1] = parseInt(newlocation[1]); newlocation[1]++;
  1262. newlocation[0] = parseInt(newlocation[0]); newlocation[0]++;
  1263. break;
  1264. case "northwest":
  1265. newlocation[1] = parseInt(newlocation[1]); newlocation[1]++;
  1266. newlocation[0] = parseInt(newlocation[0]); newlocation[0]--;
  1267. break;
  1268. case "southeast":
  1269. newlocation[1] = parseInt(newlocation[1]); newlocation[1]--;
  1270. newlocation[0] = parseInt(newlocation[0]); newlocation[0]++;
  1271. break;
  1272. case "southwest":
  1273. newlocation[1] = parseInt(newlocation[1]); newlocation[1]--;
  1274. newlocation[0] = parseInt(newlocation[0]); newlocation[0]--;
  1275. break;
  1276. };
  1277. return newlocation.join(".");
  1278. };
  1279. var userTake = function(input) {
  1280. /* Let the user take an item
  1281. * Return values:
  1282. * 0 = item taken
  1283. * 1 = missing parameter
  1284. * 2 = function called incorrectly
  1285. * 3 = can't take item
  1286. * 4 = can't see item
  1287. */
  1288. if (input.length > 2 && input[0] == "pick" && input[1] != "up") { return 2; };
  1289. if (input.length == 1) { return 1; };
  1290. itemname = input[input.length-1];
  1291. arraylocation = getRoomItems(currentlocation).indexOf(itemname);
  1292. if (arraylocation > -1) {
  1293. if (items[itemname] && items[itemname]["allow_take"]) {
  1294. inventory.push(itemname);
  1295. sendMultiplayerMessage("inventoryadd", itemname);
  1296. removeRoomItem(currentlocation, itemname);
  1297. show("You take the " + itemname + ".");
  1298. return 0;
  1299. } else {
  1300. show("I can't take this " + itemname + ".");
  1301. return 3;
  1302. };
  1303. } else {
  1304. show("I don't see any " + itemname + ".");
  1305. return 4;
  1306. };
  1307. };
  1308. // Multiplayer functionality
  1309. var startServer = function() {
  1310. if (playing != true) {
  1311. show("There doesn't seem to be any game in progress", "error");
  1312. return;
  1313. };
  1314. if (window.peer && window.peer.id) {
  1315. addToLog("A server is already running. Friends can join this game by typing '/join " + window.peer.id + "'");
  1316. } else {
  1317. prepareConnect();
  1318. window.peer.on('open', function(id) {
  1319. addToLog("Friends can join this game by typing '/join " + id + "'");
  1320. });
  1321. };
  1322. };
  1323. var prepareConnect = function() {
  1324. window.peerReconnectDelay = 0;
  1325. // List of servers from https://gist.github.com/yetithefoot/7592580
  1326. window.peer = new Peer({host: 'heritage.contracode.nl', port: 9000,
  1327. config: {'iceServers': [
  1328. {url:'stun:stun01.sipphone.com'},
  1329. {url:'stun:stun.ekiga.net'},
  1330. {url:'stun:stun.fwdnet.net'},
  1331. {url:'stun:stun.ideasip.com'},
  1332. {url:'stun:stun.iptel.org'},
  1333. {url:'stun:stun.rixtelecom.se'},
  1334. {url:'stun:stun.schlund.de'},
  1335. {url:'stun:stun.l.google.com:19302'},
  1336. {url:'stun:stun1.l.google.com:19302'},
  1337. {url:'stun:stun2.l.google.com:19302'},
  1338. {url:'stun:stun3.l.google.com:19302'},
  1339. {url:'stun:stun4.l.google.com:19302'},
  1340. {url:'stun:stunserver.org'},
  1341. {url:'stun:stun.softjoys.com'},
  1342. {url:'stun:stun.voiparound.com'},
  1343. {url:'stun:stun.voipbuster.com'},
  1344. {url:'stun:stun.voipstunt.com'},
  1345. {url:'stun:stun.voxgratia.org'},
  1346. {url:'stun:stun.xten.com'},
  1347. {
  1348. url: 'turn:numb.viagenie.ca',
  1349. credential: 'muazkh',
  1350. username: 'webrtc@live.com'
  1351. },
  1352. {
  1353. url: 'turn:192.158.29.39:3478?transport=udp',
  1354. credential: 'JZEOEt2V3Qb0y27GRntt2u2PAYA=',
  1355. username: '28224511:1379330808'
  1356. },
  1357. {
  1358. url: 'turn:192.158.29.39:3478?transport=tcp',
  1359. credential: 'JZEOEt2V3Qb0y27GRntt2u2PAYA=',
  1360. username: '28224511:1379330808'
  1361. }
  1362. ]}});
  1363. window.peer.on('open', function(id) {
  1364. addToLog("Connection to connection broker established");
  1365. window.peerReconnectDelay = 1000; // Reset exponential backoff
  1366. window.peer.on('connection', function(conn) {
  1367. multiplayerMain(conn);
  1368. });
  1369. });
  1370. window.peer.on('disconnected', function() {
  1371. // Don't try to reconnect if we killed the connection ourselves
  1372. if (!window.peer)
  1373. return;
  1374. if (!window.peerReconnectDelay) {
  1375. addToLog("Failed to start a multiplayer session, connection broker may be offline. Type '/start multiplayer' to try again");
  1376. return;
  1377. };
  1378. window.peerReconnectDelay = 1.5*window.peerReconnectDelay;
  1379. setTimeout(function() {
  1380. addToLog("Attempting to reconnect to connection broker");
  1381. window.peer.reconnect();
  1382. }, window.peerReconnectDelay);
  1383. });
  1384. };
  1385. var connectToPlayer = function(id) {
  1386. var conn = window.peer.connect(id, {reliable: true});
  1387. multiplayerMain(conn);
  1388. };
  1389. var multiplayerMain = function(conn) {
  1390. conn.on('open', function() {
  1391. conn._nickname = conn.peer;
  1392. conn._location = null;
  1393. window.conns.push(conn);
  1394. addToLog(conn._nickname + " joins the game");
  1395. conn.send(['name', window.username]);
  1396. addToLog("Sent name to " + conn._nickname);
  1397. conn.send(['sources', window.sources]);
  1398. addToLog("Sent game source to " + conn._nickname);
  1399. conn.send(['state', sessionify(false)]);
  1400. addToLog("Sent game state to " + conn._nickname);
  1401. conn.send(["location", currentlocation]);
  1402. announceNewPlayer(conn);
  1403. announceAllPlayers(conn);
  1404. });
  1405. conn.on('close', function() {
  1406. addToLog(conn._nickname + " left the game");
  1407. for (var i = 0; i < window.conns.length; i++) {
  1408. if (window.conns[i]._nickname === conn._nickname) {
  1409. window.conns.splice(i, 1);
  1410. if (window.isLooking) {
  1411. userLook();
  1412. };
  1413. return;
  1414. };
  1415. };
  1416. });
  1417. conn.on('data', function(data) {
  1418. var type = data[0];
  1419. var data = data[1];
  1420. switch(type) {
  1421. case 'chat':
  1422. addToLog(conn._nickname + ' says, "' + data + '"');
  1423. break;
  1424. case 'state':
  1425. addToLog("Received game state from " + conn._nickname);
  1426. if (!playing) {
  1427. loadSession(data);
  1428. parseInput("start");
  1429. conn.send(["location", currentlocation]);
  1430. } else {
  1431. addToLog("...but don't need it, so ignoring");
  1432. };
  1433. break;
  1434. case "inventoryadd":
  1435. inventory.push(escapeHTML(data));
  1436. break;
  1437. case "inventoryremove":
  1438. var index = inventory.indexOf(escapeHTML(data));
  1439. if (index > -1) {
  1440. inventory.splice(index, 1);
  1441. } else {
  1442. addToLog("WARNING: Inventories out of sync!");
  1443. };
  1444. break;
  1445. case "location":
  1446. if (conn._location === data) break;
  1447. var lookAgain = false;
  1448. if (data === currentlocation) {
  1449. addToLog(conn._nickname + " enters the room");
  1450. lookAgain = true;
  1451. } else if (conn._location === currentlocation) {
  1452. addToLog(conn._nickname + " leaves the room");
  1453. lookAgain = true;
  1454. };
  1455. conn._location = data;
  1456. if (lookAgain && window.isLooking) {
  1457. userLook();
  1458. };
  1459. break;
  1460. case "name":
  1461. data = data.trim();
  1462. if (!data) {
  1463. // Empty strings are silly
  1464. conn.send(["nameinuse", ""]);
  1465. };
  1466. if (escapeHTML(data) == window.username) {
  1467. conn.send(["nameinuse", ""]);
  1468. return;
  1469. };
  1470. for (var i = 0; i < window.conns.length; i++) {
  1471. if (window.conns[i] != conn) {
  1472. if (window.conns[i]._nickname == escapeHTML(data)) {
  1473. conn.send(["nameinuse", ""]);
  1474. return;
  1475. };
  1476. };
  1477. };
  1478. addToLog(conn._nickname + " is now known as " + data);
  1479. conn._nickname = escapeHTML(data);
  1480. break;
  1481. case "nameinuse":
  1482. show("Your chosen name, " + window.username + ", is already in use. Please choose a new name.", "error");
  1483. window.username = ""; // Force setting of username
  1484. break;
  1485. case "newplayer":
  1486. addToLog("Received request to connect to " + data);
  1487. if (window.peer.id == data) {
  1488. addToLog("...but we are " + data);
  1489. return;
  1490. };
  1491. for (var i = 0; i < window.conns.length; i++) {
  1492. if (window.conns[i].peer == data) {
  1493. addToLog("...but we are already connected to " + data);
  1494. return;
  1495. };
  1496. };
  1497. connectToPlayer(data);
  1498. addToLog("Connected to " + data);
  1499. break;
  1500. case "sources":
  1501. addToLog("Received game's source code from " + conn._nickname);
  1502. if (window.sources) {
  1503. addToLog("...but don't need it, so ignoring");
  1504. break;
  1505. };
  1506. window.sources = data;
  1507. saveGame(window.sources);
  1508. addToLog("Saved game to local library");
  1509. break;
  1510. case "roomitemadd":
  1511. addRoomItem(data[0], escapeHTML(data[1]), false);
  1512. if (window.isLooking && data[0] == currentlocation) {
  1513. userLook();
  1514. };
  1515. break;
  1516. case "roomitemremove":
  1517. removeRoomItem(data[0], escapeHTML(data[1]), false);
  1518. if (window.isLooking && data[0] == currentlocation) {
  1519. userLook();
  1520. };
  1521. break;
  1522. case "var":
  1523. setVarValue(data[0], escapeHTML(data[1]), false);
  1524. // Variable could possibly affect the current room
  1525. if (window.isLooking) {
  1526. userLook();
  1527. };
  1528. break;
  1529. default:
  1530. addToLog("Unknown message type received from " + conn._nickname + ": " + type);
  1531. };
  1532. });
  1533. };
  1534. var sendMultiplayerMessage = function(type, message) {
  1535. for (var i = 0; i < window.conns.length; i++) {
  1536. window.conns[i].send([type, message]);
  1537. };
  1538. };
  1539. var sendMultiplayerChatMessage = function(message) {
  1540. sendMultiplayerMessage('chat', message);
  1541. addToLog('You say, "' + message + '"');
  1542. };
  1543. var announceNewPlayer = function(conn) {
  1544. for (var i = 0; i < window.conns.length; i++) {
  1545. if (window.conns[i].peer != conn.peer) {
  1546. window.conns[i].send(['newplayer', conn.peer]);
  1547. };
  1548. };
  1549. };
  1550. var announceAllPlayers = function(conn) {
  1551. for (var i = 0; i < window.conns.length; i++) {
  1552. if (window.conns[i].id == conn.id) {
  1553. if (window.conns[i].peer != conn.peer && window.conns[i].peer != window.peer.id) {
  1554. window.conns[i].send(['newplayer', window.conns[i].peer]);
  1555. };
  1556. };
  1557. };
  1558. };
  1559. var stopMultiplayer = function() {
  1560. if (window.peer) {
  1561. window.conns = [];
  1562. window.peer.destroy();
  1563. window.peer = null;
  1564. addToLog("Multiplayer connections closed. Type '/start multiplayer' to start a new multiplayer session");
  1565. };
  1566. };