7 terminal rows: <input id="n_rows" type="number" step=4 min=8 value=24 />
8 terminal columns: <input id="n_cols" type="number" step=4 min=20 value=80 />
10 <pre id="terminal" style="display: inline-block;"></pre>
11 <textarea id="input" style="opacity: 0; width: 0px;"></textarea>
13 keys (see <a href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values">here</a> for non-obvious available values):<br />
14 move up (square grid): <input id="key_square_move_up" type="text" value="w" /> (hint: ArrowUp)<br />
15 move left (square grid): <input id="key_square_move_left" type="text" value="a" /> (hint: ArrowLeft)<br />
16 move down (square grid): <input id="key_square_move_down" type="text" value="s" /> (hint: ArrowDown)<br />
17 move right (square grid): <input id="key_square_move_right" type="text" value="d" /> (hint: ArrowRight)<br />
18 move up-left (hex grid): <input id="key_hex_move_upleft" type="text" value="w" /><br />
19 move up-right (hex grid): <input id="key_hex_move_upright" type="text" value="e" /><br />
20 move right (hex grid): <input id="key_hex_move_right" type="text" value="d" /><br />
21 move down-right (hex grid): <input id="key_hex_move_downright" type="text" value="x" /><br />
22 move down-left (hex grid): <input id="key_hex_move_downleft" type="text" value="y" /><br />
23 move left (hex grid): <input id="key_hex_move_left" type="text" value="a" /><br />
24 help: <input id="key_help" type="text" value="h" /><br />
25 flatten surroundings: <input id="key_flatten" type="text" value="F" /><br />
26 take thing under player: <input id="key_take_thing" type="text" value="z" /><br />
27 drop carried thing: <input id="key_drop_thing" type="text" value="u" /><br />
28 switch to chat mode: <input id="key_switch_to_chat" type="text" value="t" /><br />
29 switch to play mode: <input id="key_switch_to_play" type="text" value="p" /><br />
30 switch to study mode: <input id="key_switch_to_study" type="text" value="?" /><br />
31 edit tile (from play mode): <input id="key_switch_to_edit" type="text" value="m" /><br />
32 enter tile password (from play mode): <input id="key_switch_to_password" type="text" value="P" /><br />
33 annotate tile (from play mode): <input id="key_switch_to_annotate" type="text" value="M" /><br />
34 annotate portal (from play mode): <input id="key_switch_to_portal" type="text" value="T" /><br />
35 toggle terrain/control view (from study mode): <input id="key_toggle_map_mode" type="text" value="M" /><br />
39 //let websocket_location = "wss://plomlompom.com/rogue_chat/";
40 let websocket_location = "ws://localhost:8000/";
42 let rows_selector = document.getElementById("n_rows");
43 let cols_selector = document.getElementById("n_cols");
44 let key_selectors = document.querySelectorAll('[id^="key_"]');
46 function restore_selector_value(selector) {
47 let stored_selection = window.localStorage.getItem(selector.id);
48 if (stored_selection) {
49 selector.value = stored_selection;
52 restore_selector_value(rows_selector);
53 restore_selector_value(cols_selector);
54 for (let key_selector of key_selectors) {
55 restore_selector_value(key_selector);
61 initialize: function() {
62 this.rows = rows_selector.value;
63 this.cols = cols_selector.value;
64 this.pre_el = document.getElementById("terminal");
65 this.pre_el.style.color = this.foreground;
66 this.pre_el.style.backgroundColor = this.background;
69 for (let y = 0, x = 0; y <= this.rows; x++) {
73 this.content.push(line);
82 blink_screen: function() {
83 this.pre_el.style.color = this.background;
84 this.pre_el.style.backgroundColor = this.foreground;
86 this.pre_el.style.color = this.foreground;
87 this.pre_el.style.backgroundColor = this.background;
92 for (let y = 0; y < this.rows; y++) {
93 let line = this.content[y].join('');
94 pre_string += line + '\n';
96 this.pre_el.textContent = pre_string;
98 write: function(start_y, start_x, msg) {
99 for (let x = start_x, i = 0; x < this.cols && i < msg.length; x++, i++) {
100 this.content[start_y][x] = msg[i];
103 drawBox: function(start_y, start_x, height, width) {
104 let end_y = start_y + height;
105 let end_x = start_x + width;
106 for (let y = start_y, x = start_x; y < this.rows; x++) {
114 this.content[y][x] = ' ';
118 terminal.initialize();
121 tokenize: function(str) {
127 for (let i = 0; i < str.length; i++) {
133 } else if (c == '\\') {
135 } else if (c == '"') {
140 } else if (c == '"') {
142 } else if (c === ' ') {
143 if (token.length > 0) {
152 if (token.length > 0) {
155 let token_starts = [];
156 for (let i = 0; i < token_ends.length; i++) {
157 token_starts.push(token_ends[i] - tokens[i].length);
159 return [tokens, token_starts];
161 parse_yx: function(position_string) {
162 let coordinate_strings = position_string.split(',')
163 let position = [0, 0];
164 position[0] = parseInt(coordinate_strings[0].slice(2));
165 position[1] = parseInt(coordinate_strings[1].slice(2));
177 init: function(url) {
179 this.websocket = new WebSocket(this.url);
180 this.websocket.onopen = function(event) {
181 server.connected = true;
182 game.thing_types = {};
183 server.send(['TASKS']);
184 server.send(['THING_TYPES']);
185 tui.log_msg("@ server connected! :)");
186 tui.switch_mode(mode_login);
188 this.websocket.onclose = function(event) {
189 server.connected = false;
190 tui.switch_mode(mode_waiting_for_server);
191 tui.log_msg("@ server disconnected :(");
193 this.websocket.onmessage = this.handle_event;
195 reconnect_to: function(url) {
196 this.websocket.close();
199 send: function(tokens) {
200 this.websocket.send(unparser.untokenize(tokens));
202 handle_event: function(event) {
203 let tokens = parser.tokenize(event.data)[0];
204 if (tokens[0] === 'TURN') {
205 game.turn_complete = false;
208 game.turn = parseInt(tokens[1]);
209 } else if (tokens[0] === 'THING') {
210 let t = game.get_thing(tokens[3], true);
211 t.position = parser.parse_yx(tokens[1]);
213 } else if (tokens[0] === 'THING_NAME') {
214 let t = game.get_thing(tokens[1], false);
218 } else if (tokens[0] === 'TASKS') {
219 game.tasks = tokens[1].split(',')
220 } else if (tokens[0] === 'THING_TYPE') {
221 game.thing_types[tokens[1]] = tokens[2]
222 } else if (tokens[0] === 'MAP') {
223 game.map_geometry = tokens[1];
225 game.map_size = parser.parse_yx(tokens[2]);
227 } else if (tokens[0] === 'FOV') {
229 } else if (tokens[0] === 'MAP_CONTROL') {
230 game.map_control = tokens[1]
231 } else if (tokens[0] === 'GAME_STATE_COMPLETE') {
232 game.turn_complete = true;
233 explorer.empty_info_db();
234 if (tui.mode == mode_post_login_wait) {
235 tui.switch_mode(mode_play);
236 } else if (tui.mode == mode_study) {
237 explorer.query_info();
239 let t = game.get_thing(game.player_id);
240 if (t.position in game.portals) {
241 tui.teleport_target = game.portals[t.position];
242 tui.switch_mode(mode_teleport);
246 } else if (tokens[0] === 'CHAT') {
247 tui.log_msg('# ' + tokens[1], 1);
248 } else if (tokens[0] === 'PLAYER_ID') {
249 game.player_id = parseInt(tokens[1]);
250 } else if (tokens[0] === 'LOGIN_OK') {
251 this.send(['GET_GAMESTATE']);
252 tui.switch_mode(mode_post_login_wait);
253 } else if (tokens[0] === 'PORTAL') {
254 let position = parser.parse_yx(tokens[1]);
255 game.portals[position] = tokens[2];
256 } else if (tokens[0] === 'ANNOTATION') {
257 let position = parser.parse_yx(tokens[1]);
258 explorer.update_info_db(position, tokens[2]);
259 } else if (tokens[0] === 'UNHANDLED_INPUT') {
260 tui.log_msg('? unknown command');
261 } else if (tokens[0] === 'PLAY_ERROR') {
262 terminal.blink_screen();
263 } else if (tokens[0] === 'ARGUMENT_ERROR') {
264 tui.log_msg('? syntax error: ' + tokens[1]);
265 } else if (tokens[0] === 'GAME_ERROR') {
266 tui.log_msg('? game error: ' + tokens[1]);
267 } else if (tokens[0] === 'PONG') {
270 tui.log_msg('? unhandled input: ' + event.data);
276 quote: function(str) {
278 for (let i = 0; i < str.length; i++) {
280 if (['"', '\\'].includes(c)) {
286 return quoted.join('');
288 to_yx: function(yx_coordinate) {
289 return "Y:" + yx_coordinate[0] + ",X:" + yx_coordinate[1];
291 untokenize: function(tokens) {
292 let quoted_tokens = [];
293 for (let token of tokens) {
294 quoted_tokens.push(this.quote(token));
296 return quoted_tokens.join(" ");
301 constructor(name, help_intro, has_input_prompt=false, shows_info=false, is_intro=false) {
303 this.has_input_prompt = has_input_prompt;
304 this.shows_info= shows_info;
305 this.is_intro = is_intro;
306 this.help_intro = help_intro;
309 let mode_waiting_for_server = new Mode('waiting_for_server', 'Waiting for a server response.', false, false, true);
310 let mode_login = new Mode('login', 'Pick your player name.', true, false, true);
311 let mode_post_login_wait = new Mode('waiting for game world', 'Waiting for a server response.', false, false, true);
312 let mode_chat = new Mode('chat', 'This mode allows you to engage in chit-chat with other users. Any line you enter into the input prompt that does not start with a "/" will be sent out to nearby players – but barriers and distance will reduce what they can read, so stand close to them to ensure they get your message. Lines that start with a "/" are used for commands like:', true, false);
313 let mode_annotate = new Mode('annotate', 'This mode allows you to add/edit a comment on the tile you are currently standing on (provided your map editing password authorizes you so). Hit Return to leave.', true, true);
314 let mode_play = new Mode('play', 'This mode allows you to interact with the map.', false, false);
315 let mode_study = new Mode('study', 'This mode allows you to study the map and its tiles in detail. Move the question mark over a tile, and the right half of the screen will show detailed information on it.', false, true);
316 let mode_edit = new Mode('edit', 'This mode allows you to change the map tile you currently stand on (if your map editing password authorizes you so). Just enter any printable ASCII character to imprint it on the ground below you.', false, false);
317 let mode_teleport = new Mode('teleport', 'Follow the instructions to re-connect and log-in to another server, or enter anything else to abort.', true);
318 let mode_portal = new Mode('portal', 'This mode allows you to imprint/edit/remove a teleportation target on the ground you are currently standing on (provided your map editing password authorizes you so). Enter or edit a URL to imprint a teleportation target; enter emptiness to remove a pre-existing teleportation target. Hit Return to leave.', true, true);
319 let mode_password = new Mode('password', 'This mode allows you to change the password that you send to authorize yourself for editing password-protected map tiles. Hit return to confirm and leave.', true, false, false);
322 mode: mode_waiting_for_server,
326 window_width: terminal.cols / 2,
333 this.inputEl = document.getElementById("input");
334 this.inputEl.focus();
335 this.recalc_input_lines();
336 this.height_header = this.height_turn_line + this.height_mode_line;
337 this.log_msg("@ waiting for server connection ...");
340 init_keys: function() {
342 for (let key_selector of key_selectors) {
343 this.keys[key_selector.id.slice(4)] = key_selector.value;
345 this.movement_keys = {
346 [this.keys.square_move_up]: 'UP',
347 [this.keys.square_move_left]: 'LEFT',
348 [this.keys.square_move_down]: 'DOWN',
349 [this.keys.square_move_right]: 'RIGHT'
351 if (game.map_geometry == 'Hex') {
352 this.movement_keys = {
353 [this.keys.hex_move_upleft]: 'UPLEFT',
354 [this.keys.hex_move_upright]: 'UPRIGHT',
355 [this.keys.hex_move_right]: 'RIGHT',
356 [this.keys.hex_move_downright]: 'DOWNRIGHT',
357 [this.keys.hex_move_downleft]: 'DOWNLEFT',
358 [this.keys.hex_move_left]: 'LEFT'
362 switch_mode: function(mode) {
363 this.show_help = false;
364 this.map_mode = 'terrain';
365 if (mode.shows_info && game.player_id in game.things) {
366 explorer.position = game.things[game.player_id].position;
370 this.restore_input_values();
371 if (mode == mode_login) {
372 if (this.login_name) {
373 server.send(['LOGIN', this.login_name]);
375 this.log_msg("? need login name");
377 } else if (mode == mode_edit) {
378 this.show_help = true;
379 } else if (mode == mode_teleport) {
380 tui.log_msg("@ May teleport to: " + tui.teleport_target);
381 tui.log_msg("@ Enter 'YES!' to entusiastically affirm.");
385 restore_input_values: function() {
386 if (this.mode == mode_annotate && explorer.position in explorer.info_db) {
387 let info = explorer.info_db[explorer.position];
388 if (info != "(none)") {
389 this.inputEl.value = info;
390 this.recalc_input_lines();
392 } else if (this.mode == mode_portal && explorer.position in game.portals) {
393 let portal = game.portals[explorer.position]
394 this.inputEl.value = portal;
395 this.recalc_input_lines();
396 } else if (this.mode == mode_password) {
397 this.inputEl.value = this.password;
398 this.recalc_input_lines();
401 empty_input: function(str) {
402 this.inputEl.value = "";
403 if (this.mode.has_input_prompt) {
404 this.recalc_input_lines();
406 this.height_input = 0;
409 recalc_input_lines: function() {
410 this.input_lines = this.msg_into_lines_of_width(this.input_prompt + this.inputEl.value, this.window_width);
411 this.height_input = this.input_lines.length;
413 msg_into_lines_of_width: function(msg, width) {
416 for (let i = 0, x = 0; i < msg.length; i++, x++) {
417 if (x >= width || msg[i] == "\n") {
422 if (msg[i] != "\n") {
429 log_msg: function(msg) {
431 while (this.log.length > 100) {
436 draw_map: function() {
437 let map_lines_split = [];
439 let map_content = game.map;
440 if (this.map_mode == 'control') {
441 map_content = game.map_control;
443 for (let i = 0, j = 0; i < game.map.length; i++, j++) {
444 if (j == game.map_size[1]) {
445 map_lines_split.push(line);
449 line.push(map_content[i] + ' ');
451 map_lines_split.push(line);
452 if (this.map_mode == 'terrain') {
453 let used_positions = [];
454 for (const thing_id in game.things) {
455 let t = game.things[thing_id];
456 let symbol = game.thing_types[t.type_];
457 if (used_positions.includes(t.position.toString())) {
458 map_lines_split[t.position[0]][t.position[1]] = symbol + '+';
460 map_lines_split[t.position[0]][t.position[1]] = symbol + ' ';
462 used_positions.push(t.position.toString());
465 if (tui.mode.shows_info) {
466 map_lines_split[explorer.position[0]][explorer.position[1]] = '??';
469 if (game.map_geometry == 'Square') {
470 for (let line_split of map_lines_split) {
471 map_lines.push(line_split.join(' '));
473 } else if (game.map_geometry == 'Hex') {
475 for (let line_split of map_lines_split) {
476 map_lines.push(' '.repeat(indent) + line_split.join(''));
484 let window_center = [terminal.rows / 2, this.window_width / 2];
485 let player = game.things[game.player_id];
486 let center_position = [player.position[0], player.position[1]];
487 if (tui.mode.shows_info) {
488 center_position = [explorer.position[0], explorer.position[1]];
490 center_position[1] = center_position[1] * 2;
491 let offset = [center_position[0] - window_center[0],
492 center_position[1] - window_center[1]]
493 if (game.map_geometry == 'Hex' && offset[0] % 2) {
496 let term_y = Math.max(0, -offset[0]);
497 let term_x = Math.max(0, -offset[1]);
498 let map_y = Math.max(0, offset[0]);
499 let map_x = Math.max(0, offset[1]);
500 for (; term_y < terminal.rows && map_y < game.map_size[0]; term_y++, map_y++) {
501 let to_draw = map_lines[map_y].slice(map_x, this.window_width + offset[1]);
502 terminal.write(term_y, term_x, to_draw);
505 draw_mode_line: function() {
506 let help = 'hit [' + this.keys.help + '] for help';
507 if (this.mode.has_input_prompt) {
508 help = 'enter /help for help';
510 terminal.write(0, this.window_width, 'MODE: ' + this.mode.name + ' – ' + help);
512 draw_turn_line: function(n) {
513 terminal.write(1, this.window_width, 'TURN: ' + game.turn);
515 draw_history: function() {
516 let log_display_lines = [];
517 for (let line of this.log) {
518 log_display_lines = log_display_lines.concat(this.msg_into_lines_of_width(line, this.window_width));
520 for (let y = terminal.rows - 1 - this.height_input,
521 i = log_display_lines.length - 1;
522 y >= this.height_header && i >= 0;
524 terminal.write(y, this.window_width, log_display_lines[i]);
527 draw_info: function() {
528 let lines = this.msg_into_lines_of_width(explorer.get_info(), this.window_width);
529 for (let y = this.height_header, i = 0; y < terminal.rows && i < lines.length; y++, i++) {
530 terminal.write(y, this.window_width, lines[i]);
533 draw_input: function() {
534 if (this.mode.has_input_prompt) {
535 for (let y = terminal.rows - this.height_input, i = 0; i < this.input_lines.length; y++, i++) {
536 terminal.write(y, this.window_width, this.input_lines[i]);
540 draw_help: function() {
541 let movement_keys_desc = Object.keys(this.movement_keys).join(',');
542 let content = this.mode.name + " mode help\n\n" + this.mode.help_intro + "\n\n";
543 if (this.mode == mode_play) {
544 content += "Available actions:\n";
545 if (game.tasks.includes('MOVE')) {
546 content += "[" + movement_keys_desc + "] – move player\n";
548 if (game.tasks.includes('PICK_UP')) {
549 content += "[" + this.keys.take_thing + "] – take thing under player\n";
551 if (game.tasks.includes('DROP')) {
552 content += "[" + this.keys.drop_thing + "] – drop carried thing\n";
554 if (game.tasks.includes('FLATTEN_SURROUNDINGS')) {
555 content += "[" + tui.keys.flatten + "] – flatten player's surroundings\n";
557 content += '\nOther modes available from here:\n';
558 content += '[' + this.keys.switch_to_chat + '] – chat mode\n';
559 content += '[' + this.keys.switch_to_study + '] – study mode\n';
560 content += '[' + this.keys.switch_to_edit + '] – terrain edit mode\n';
561 content += '[' + this.keys.switch_to_portal + '] – portal edit mode\n';
562 content += '[' + this.keys.switch_to_annotate + '] – annotation mode\n';
563 content += '[' + this.keys.switch_to_password + '] – password input mode\n';
564 } else if (this.mode == mode_study) {
565 content += "Available actions:\n";
566 content += '[' + movement_keys_desc + '] – move question mark\n';
567 content += '[' + this.keys.toggle_map_mode + '] – toggle view between terrain, and password protection areas\n';
568 content += '\nOther modes available from here:\n';
569 content += '[' + this.keys.switch_to_chat + '] – chat mode\n';
570 content += '[' + this.keys.switch_to_play + '] – play mode\n';
571 } else if (this.mode == mode_chat) {
572 content += '/nick NAME – re-name yourself to NAME\n';
573 //content += '/msg USER TEXT – send TEXT to USER\n';
574 content += '/' + this.keys.switch_to_play + ' or /play – switch to play mode\n';
575 content += '/' + this.keys.switch_to_study + ' or /study – switch to study mode\n';
578 if (!this.mode.has_input_prompt) {
579 start_x = this.window_width
581 terminal.drawBox(0, start_x, terminal.rows, this.window_width);
582 let lines = this.msg_into_lines_of_width(content, this.window_width);
583 for (let y = 0, i = 0; y < terminal.rows && i < lines.length; y++, i++) {
584 terminal.write(y, start_x, lines[i]);
587 full_refresh: function() {
588 terminal.drawBox(0, 0, terminal.rows, terminal.cols);
589 if (this.mode.is_intro) {
593 if (game.turn_complete) {
595 this.draw_turn_line();
597 this.draw_mode_line();
598 if (this.mode.shows_info) {
605 if (this.show_help) {
617 this.map_control = "";
618 this.map_size = [0,0];
623 get_thing: function(id_, create_if_not_found=false) {
624 if (id_ in game.things) {
625 return game.things[id_];
626 } else if (create_if_not_found) {
627 let t = new Thing([0,0]);
628 game.things[id_] = t;
632 move: function(start_position, direction) {
633 let target = [start_position[0], start_position[1]];
634 if (direction == 'LEFT') {
636 } else if (direction == 'RIGHT') {
638 } else if (game.map_geometry == 'Square') {
639 if (direction == 'UP') {
641 } else if (direction == 'DOWN') {
644 } else if (game.map_geometry == 'Hex') {
645 let start_indented = start_position[0] % 2;
646 if (direction == 'UPLEFT') {
648 if (!start_indented) {
651 } else if (direction == 'UPRIGHT') {
653 if (start_indented) {
656 } else if (direction == 'DOWNLEFT') {
658 if (!start_indented) {
661 } else if (direction == 'DOWNRIGHT') {
663 if (start_indented) {
668 if (target[0] < 0 || target[1] < 0 ||
669 target[0] >= this.map_size[0] || target[1] >= this.map_size[1]) {
679 server.init(websocket_location);
684 move: function(direction) {
685 let target = game.move(this.position, direction);
687 this.position = target
690 terminal.blink_screen();
693 update_info_db: function(yx, str) {
694 this.info_db[yx] = str;
695 if (tui.mode == mode_study) {
699 empty_info_db: function() {
701 if (tui.mode == mode_study) {
705 query_info: function() {
706 server.send(["GET_ANNOTATION", unparser.to_yx(explorer.position)]);
708 get_info: function() {
709 let position_i = this.position[0] * game.map_size[1] + this.position[1];
710 if (game.fov[position_i] != '.') {
711 return 'outside field of view';
714 info += "TERRAIN: " + game.map[position_i] + "\n";
715 for (let t_id in game.things) {
716 let t = game.things[t_id];
717 if (t.position[0] == this.position[0] && t.position[1] == this.position[1]) {
718 info += "THING: " + t.type_;
720 info += " (name: " + t.name_ + ")";
725 if (this.position in game.portals) {
726 info += "PORTAL: " + game.portals[this.position] + "\n";
728 if (this.position in this.info_db) {
729 info += "ANNOTATIONS: " + this.info_db[this.position];
735 annotate: function(msg) {
736 if (msg.length == 0) {
737 msg = " "; // triggers annotation deletion
739 server.send(["ANNOTATE", unparser.to_yx(explorer.position), msg, tui.password]);
741 set_portal: function(msg) {
742 if (msg.length == 0) {
743 msg = " "; // triggers portal deletion
745 server.send(["PORTAL", unparser.to_yx(explorer.position), msg, tui.password]);
749 tui.inputEl.addEventListener('input', (event) => {
750 if (tui.mode.has_input_prompt) {
751 let max_length = tui.window_width * terminal.rows - tui.input_prompt.length;
752 if (tui.inputEl.value.length > max_length) {
753 tui.inputEl.value = tui.inputEl.value.slice(0, max_length);
755 tui.recalc_input_lines();
756 } else if (tui.mode == mode_edit && tui.inputEl.value.length > 0) {
757 server.send(["TASK:WRITE", tui.inputEl.value[0], tui.password]);
758 tui.switch_mode(mode_play);
762 tui.inputEl.addEventListener('keydown', (event) => {
763 tui.show_help = false;
764 if (event.key == 'Enter') {
765 event.preventDefault();
767 if (tui.mode.has_input_prompt && event.key == 'Enter' && tui.inputEl.value == '/help') {
768 tui.show_help = true;
770 tui.restore_input_values();
771 } else if (!tui.mode.has_input_prompt && event.key == tui.keys.help) {
772 tui.show_help = true;
773 } else if (tui.mode == mode_login && event.key == 'Enter') {
774 tui.login_name = tui.inputEl.value;
775 server.send(['LOGIN', tui.inputEl.value]);
777 } else if (tui.mode == mode_portal && event.key == 'Enter') {
778 explorer.set_portal(tui.inputEl.value);
779 tui.switch_mode(mode_play);
780 } else if (tui.mode == mode_annotate && event.key == 'Enter') {
781 explorer.annotate(tui.inputEl.value);
782 tui.switch_mode(mode_play);
783 } else if (tui.mode == mode_password && event.key == 'Enter') {
784 if (tui.inputEl.value.length == 0) {
785 tui.inputEl.value = " ";
787 tui.password = tui.inputEl.value
788 tui.switch_mode(mode_play);
789 } else if (tui.mode == mode_teleport && event.key == 'Enter') {
790 if (tui.inputEl.value == 'YES!') {
791 server.reconnect_to(tui.teleport_target);
793 tui.log_msg('@ teleport aborted');
794 tui.switch_mode(mode_play);
796 } else if (tui.mode == mode_chat && event.key == 'Enter') {
797 let [tokens, token_starts] = parser.tokenize(tui.inputEl.value);
798 if (tokens.length > 0 && tokens[0].length > 0) {
799 if (tui.inputEl.value[0][0] == '/') {
800 if (tokens[0].slice(1) == 'play' || tokens[0][1] == tui.keys.switch_to_play) {
801 tui.switch_mode(mode_play);
802 } else if (tokens[0].slice(1) == 'study' || tokens[0][1] == tui.keys.switch_to_study) {
803 tui.switch_mode(mode_study);
804 } else if (tokens[0].slice(1) == 'nick') {
805 if (tokens.length > 1) {
806 server.send(['NICK', tokens[1]]);
808 tui.log_msg('? need new name');
810 //} else if (tokens[0].slice(1) == 'msg') {
811 // if (tokens.length > 2) {
812 // let msg = tui.inputEl.value.slice(token_starts[2]);
813 // server.send(['QUERY', tokens[1], msg]);
815 // tui.log_msg('? need message target and message');
818 tui.log_msg('? unknown command');
821 server.send(['ALL', tui.inputEl.value]);
823 } else if (tui.inputEl.valuelength > 0) {
824 server.send(['ALL', tui.inputEl.value]);
827 } else if (tui.mode == mode_play) {
828 if (event.key === tui.keys.switch_to_chat) {
829 event.preventDefault();
830 tui.switch_mode(mode_chat);
831 } else if (event.key === tui.keys.switch_to_edit
832 && game.tasks.includes('WRITE')) {
833 event.preventDefault();
834 tui.switch_mode(mode_edit);
835 } else if (event.key === tui.keys.switch_to_study) {
836 tui.switch_mode(mode_study);
837 } else if (event.key === tui.keys.switch_to_password) {
838 event.preventDefault();
839 tui.switch_mode(mode_password);
840 } else if (event.key === tui.keys.flatten
841 && game.tasks.includes('FLATTEN_SURROUNDINGS')) {
842 server.send(["TASK:FLATTEN_SURROUNDINGS", tui.password]);
843 } else if (event.key === tui.keys.take_thing
844 && game.tasks.includes('PICK_UP')) {
845 server.send(["TASK:PICK_UP"]);
846 } else if (event.key === tui.keys.drop_thing
847 && game.tasks.includes('DROP')) {
848 server.send(["TASK:DROP"]);
849 } else if (event.key in tui.movement_keys
850 && game.tasks.includes('MOVE')) {
851 server.send(['TASK:MOVE', tui.movement_keys[event.key]]);
852 } else if (event.key === tui.keys.switch_to_portal) {
853 event.preventDefault();
854 tui.switch_mode(mode_portal);
855 } else if (event.key === tui.keys.switch_to_annotate) {
856 event.preventDefault();
857 tui.switch_mode(mode_annotate);
859 } else if (tui.mode == mode_study) {
860 if (event.key === tui.keys.switch_to_chat) {
861 event.preventDefault();
862 tui.switch_mode(mode_chat);
863 } else if (event.key == tui.keys.switch_to_play) {
864 tui.switch_mode(mode_play);
865 } else if (event.key in tui.movement_keys) {
866 explorer.move(tui.movement_keys[event.key]);
867 } else if (event.key == tui.keys.toggle_map_mode) {
868 if (tui.map_mode == 'terrain') {
869 tui.map_mode = 'control';
871 tui.map_mode = 'terrain';
878 rows_selector.addEventListener('input', function() {
879 if (rows_selector.value % 4 != 0) {
882 window.localStorage.setItem(rows_selector.id, rows_selector.value);
883 terminal.initialize();
886 cols_selector.addEventListener('input', function() {
887 if (cols_selector.value % 4 != 0) {
890 window.localStorage.setItem(cols_selector.id, cols_selector.value);
891 terminal.initialize();
892 tui.window_width = terminal.cols / 2,
895 for (let key_selector of key_selectors) {
896 key_selector.addEventListener('input', function() {
897 window.localStorage.setItem(key_selector.id, key_selector.value);
901 window.setInterval(function() {
902 if (!(['input', 'n_cols', 'n_rows'].includes(document.activeElement.id)
903 || document.activeElement.id.startsWith('key_'))) {
907 window.setInterval(function() {
908 if (server.connected) {
909 server.send(['PING']);
911 server.reconnect_to(server.url);
912 tui.log_msg('@ attempting reconnect …')