engine.js 60 KB

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