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 switch to chat mode: <input id="key_switch_to_chat" type="text" value="t" /><br />
27 switch to play mode: <input id="key_switch_to_play" type="text" value="p" /><br />
28 switch to study mode: <input id="key_switch_to_study" type="text" value="?" /><br />
29 edit tile (from play mode): <input id="key_switch_to_edit" type="text" value="m" /><br />
30 enter tile password (from play mode): <input id="key_switch_to_password" type="text" value="P" /><br />
31 annotate tile (from play mode): <input id="key_switch_to_annotate" type="text" value="M" /><br />
32 annotate portal (from play mode): <input id="key_switch_to_portal" type="text" value="T" /><br />
33 toggle terrain/control view (from study mode): <input id="key_toggle_map_mode" type="text" value="M" /><br />
37 let websocket_location = "wss://plomlompom.com/rogue_chat/";
39 let rows_selector = document.getElementById("n_rows");
40 let cols_selector = document.getElementById("n_cols");
41 let key_selectors = document.querySelectorAll('[id^="key_"]');
43 function restore_selector_value(selector) {
44 let stored_selection = window.localStorage.getItem(selector.id);
45 if (stored_selection) {
46 selector.value = stored_selection;
49 restore_selector_value(rows_selector);
50 restore_selector_value(cols_selector);
51 for (let key_selector of key_selectors) {
52 restore_selector_value(key_selector);
58 initialize: function() {
59 this.rows = rows_selector.value;
60 this.cols = cols_selector.value;
61 this.pre_el = document.getElementById("terminal");
62 this.pre_el.style.color = this.foreground;
63 this.pre_el.style.backgroundColor = this.background;
66 for (let y = 0, x = 0; y <= this.rows; x++) {
70 this.content.push(line);
79 blink_screen: function() {
80 this.pre_el.style.color = this.background;
81 this.pre_el.style.backgroundColor = this.foreground;
83 this.pre_el.style.color = this.foreground;
84 this.pre_el.style.backgroundColor = this.background;
89 for (let y = 0; y < this.rows; y++) {
90 let line = this.content[y].join('');
91 pre_string += line + '\n';
93 this.pre_el.textContent = pre_string;
95 write: function(start_y, start_x, msg) {
96 for (let x = start_x, i = 0; x < this.cols && i < msg.length; x++, i++) {
97 this.content[start_y][x] = msg[i];
100 drawBox: function(start_y, start_x, height, width) {
101 let end_y = start_y + height;
102 let end_x = start_x + width;
103 for (let y = start_y, x = start_x; y < this.rows; x++) {
111 this.content[y][x] = ' ';
115 terminal.initialize();
118 tokenize: function(str) {
124 for (let i = 0; i < str.length; i++) {
130 } else if (c == '\\') {
132 } else if (c == '"') {
137 } else if (c == '"') {
139 } else if (c === ' ') {
140 if (token.length > 0) {
149 if (token.length > 0) {
152 let token_starts = [];
153 for (let i = 0; i < token_ends.length; i++) {
154 token_starts.push(token_ends[i] - tokens[i].length);
156 return [tokens, token_starts];
158 parse_yx: function(position_string) {
159 let coordinate_strings = position_string.split(',')
160 let position = [0, 0];
161 position[0] = parseInt(coordinate_strings[0].slice(2));
162 position[1] = parseInt(coordinate_strings[1].slice(2));
174 init: function(url) {
176 this.websocket = new WebSocket(this.url);
177 this.websocket.onopen = function(event) {
178 server.connected = true;
179 server.send(['TASKS']);
180 tui.log_msg("@ server connected! :)");
181 tui.switch_mode(mode_login);
183 this.websocket.onclose = function(event) {
184 server.connected = false;
185 tui.switch_mode(mode_waiting_for_server);
186 tui.log_msg("@ server disconnected :(");
188 this.websocket.onmessage = this.handle_event;
190 reconnect_to: function(url) {
191 this.websocket.close();
194 send: function(tokens) {
195 this.websocket.send(unparser.untokenize(tokens));
197 handle_event: function(event) {
198 let tokens = parser.tokenize(event.data)[0];
199 if (tokens[0] === 'TURN') {
200 game.turn_complete = false;
203 game.turn = parseInt(tokens[1]);
204 } else if (tokens[0] === 'THING_POS') {
205 game.get_thing(tokens[1], true).position = parser.parse_yx(tokens[2]);
206 } else if (tokens[0] === 'THING_NAME') {
207 game.get_thing(tokens[1], true).name_ = tokens[2];
208 } else if (tokens[0] === 'TASKS') {
209 game.tasks = tokens[1].split(',')
210 } else if (tokens[0] === 'MAP') {
211 game.map_geometry = tokens[1];
213 game.map_size = parser.parse_yx(tokens[2]);
215 } else if (tokens[0] === 'FOV') {
217 } else if (tokens[0] === 'MAP_CONTROL') {
218 game.map_control = tokens[1]
219 } else if (tokens[0] === 'GAME_STATE_COMPLETE') {
220 game.turn_complete = true;
221 explorer.empty_info_db();
222 if (tui.mode == mode_post_login_wait) {
223 tui.switch_mode(mode_play);
224 } else if (tui.mode == mode_study) {
225 explorer.query_info();
227 let t = game.get_thing(game.player_id);
228 if (t.position in game.portals) {
229 tui.teleport_target = game.portals[t.position];
230 tui.switch_mode(mode_teleport);
234 } else if (tokens[0] === 'CHAT') {
235 tui.log_msg('# ' + tokens[1], 1);
236 } else if (tokens[0] === 'PLAYER_ID') {
237 game.player_id = parseInt(tokens[1]);
238 } else if (tokens[0] === 'LOGIN_OK') {
239 this.send(['GET_GAMESTATE']);
240 tui.switch_mode(mode_post_login_wait);
241 } else if (tokens[0] === 'PORTAL') {
242 let position = parser.parse_yx(tokens[1]);
243 game.portals[position] = tokens[2];
244 } else if (tokens[0] === 'ANNOTATION') {
245 let position = parser.parse_yx(tokens[1]);
246 explorer.update_info_db(position, tokens[2]);
247 } else if (tokens[0] === 'UNHANDLED_INPUT') {
248 tui.log_msg('? unknown command');
249 } else if (tokens[0] === 'PLAY_ERROR') {
250 terminal.blink_screen();
251 } else if (tokens[0] === 'ARGUMENT_ERROR') {
252 tui.log_msg('? syntax error: ' + tokens[1]);
253 } else if (tokens[0] === 'GAME_ERROR') {
254 tui.log_msg('? game error: ' + tokens[1]);
255 } else if (tokens[0] === 'PONG') {
258 tui.log_msg('? unhandled input: ' + event.data);
264 quote: function(str) {
266 for (let i = 0; i < str.length; i++) {
268 if (['"', '\\'].includes(c)) {
274 return quoted.join('');
276 to_yx: function(yx_coordinate) {
277 return "Y:" + yx_coordinate[0] + ",X:" + yx_coordinate[1];
279 untokenize: function(tokens) {
280 let quoted_tokens = [];
281 for (let token of tokens) {
282 quoted_tokens.push(this.quote(token));
284 return quoted_tokens.join(" ");
289 constructor(name, help_intro, has_input_prompt=false, shows_info=false, is_intro=false) {
291 this.has_input_prompt = has_input_prompt;
292 this.shows_info= shows_info;
293 this.is_intro = is_intro;
294 this.help_intro = help_intro;
297 let mode_waiting_for_server = new Mode('waiting_for_server', 'Waiting for a server response.', false, false, true);
298 let mode_login = new Mode('login', 'Pick your player name.', true, false, true);
299 let mode_post_login_wait = new Mode('waiting for game world', 'Waiting for a server response.', false, false, true);
300 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 to all users. Lines that start with a "/" are used for commands like:', true, false);
301 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);
302 let mode_play = new Mode('play', 'This mode allows you to interact with the map.', false, false);
303 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);
304 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);
305 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);
306 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);
307 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);
310 mode: mode_waiting_for_server,
314 window_width: terminal.cols / 2,
321 this.inputEl = document.getElementById("input");
322 this.inputEl.focus();
323 this.recalc_input_lines();
324 this.height_header = this.height_turn_line + this.height_mode_line;
325 this.log_msg("@ waiting for server connection ...");
328 init_keys: function() {
330 for (let key_selector of key_selectors) {
331 this.keys[key_selector.id.slice(4)] = key_selector.value;
333 this.movement_keys = {
334 [this.keys.square_move_up]: 'UP',
335 [this.keys.square_move_left]: 'LEFT',
336 [this.keys.square_move_down]: 'DOWN',
337 [this.keys.square_move_right]: 'RIGHT'
339 if (game.map_geometry == 'Hex') {
340 this.movement_keys = {
341 [this.keys.hex_move_upleft]: 'UPLEFT',
342 [this.keys.hex_move_upright]: 'UPRIGHT',
343 [this.keys.hex_move_right]: 'RIGHT',
344 [this.keys.hex_move_downright]: 'DOWNRIGHT',
345 [this.keys.hex_move_downleft]: 'DOWNLEFT',
346 [this.keys.hex_move_left]: 'LEFT'
350 switch_mode: function(mode) {
351 this.show_help = false;
352 this.map_mode = 'terrain';
353 if (mode.shows_info && game.player_id in game.things) {
354 explorer.position = game.things[game.player_id].position;
358 this.restore_input_values();
359 if (mode == mode_login) {
360 if (this.login_name) {
361 server.send(['LOGIN', this.login_name]);
363 this.log_msg("? need login name");
365 } else if (mode == mode_edit) {
366 this.show_help = true;
367 } else if (mode == mode_teleport) {
368 tui.log_msg("@ May teleport to: " + tui.teleport_target);
369 tui.log_msg("@ Enter 'YES!' to entusiastically affirm.");
373 restore_input_values: function() {
374 if (this.mode == mode_annotate && explorer.position in explorer.info_db) {
375 let info = explorer.info_db[explorer.position];
376 if (info != "(none)") {
377 this.inputEl.value = info;
378 this.recalc_input_lines();
380 } else if (this.mode == mode_portal && explorer.position in game.portals) {
381 let portal = game.portals[explorer.position]
382 this.inputEl.value = portal;
383 this.recalc_input_lines();
384 } else if (this.mode == mode_password) {
385 this.inputEl.value = this.password;
386 this.recalc_input_lines();
389 empty_input: function(str) {
390 this.inputEl.value = "";
391 if (this.mode.has_input_prompt) {
392 this.recalc_input_lines();
394 this.height_input = 0;
397 recalc_input_lines: function() {
398 this.input_lines = this.msg_into_lines_of_width(this.input_prompt + this.inputEl.value, this.window_width);
399 this.height_input = this.input_lines.length;
401 msg_into_lines_of_width: function(msg, width) {
404 for (let i = 0, x = 0; i < msg.length; i++, x++) {
405 if (x >= width || msg[i] == "\n") {
410 if (msg[i] != "\n") {
417 log_msg: function(msg) {
419 while (this.log.length > 100) {
424 draw_map: function() {
425 let map_lines_split = [];
427 let map_content = game.map;
428 if (this.map_mode == 'control') {
429 map_content = game.map_control;
431 for (let i = 0, j = 0; i < game.map.length; i++, j++) {
432 if (j == game.map_size[1]) {
433 map_lines_split.push(line);
437 line.push(map_content[i]);
439 map_lines_split.push(line);
440 if (this.map_mode == 'terrain') {
441 for (const thing_id in game.things) {
442 let t = game.things[thing_id];
443 map_lines_split[t.position[0]][t.position[1]] = '@';
446 if (tui.mode.shows_info) {
447 map_lines_split[explorer.position[0]][explorer.position[1]] = '?';
450 if (game.map_geometry == 'Square') {
451 for (let line_split of map_lines_split) {
452 map_lines.push(line_split.join(' '));
454 } else if (game.map_geometry == 'Hex') {
456 for (let line_split of map_lines_split) {
457 map_lines.push(' '.repeat(indent) + line_split.join(' '));
465 let window_center = [terminal.rows / 2, this.window_width / 2];
466 let player = game.things[game.player_id];
467 let center_position = [player.position[0], player.position[1]];
468 if (tui.mode.shows_info) {
469 center_position = [explorer.position[0], explorer.position[1]];
471 center_position[1] = center_position[1] * 2;
472 let offset = [center_position[0] - window_center[0],
473 center_position[1] - window_center[1]]
474 if (game.map_geometry == 'Hex' && offset[0] % 2) {
477 let term_y = Math.max(0, -offset[0]);
478 let term_x = Math.max(0, -offset[1]);
479 let map_y = Math.max(0, offset[0]);
480 let map_x = Math.max(0, offset[1]);
481 for (; term_y < terminal.rows && map_y < game.map_size[0]; term_y++, map_y++) {
482 let to_draw = map_lines[map_y].slice(map_x, this.window_width + offset[1]);
483 terminal.write(term_y, term_x, to_draw);
486 draw_mode_line: function() {
487 let help = 'hit [' + this.keys.help + '] for help';
488 if (this.mode.has_input_prompt) {
489 help = 'enter /help for help';
491 terminal.write(0, this.window_width, 'MODE: ' + this.mode.name + ' – ' + help);
493 draw_turn_line: function(n) {
494 terminal.write(1, this.window_width, 'TURN: ' + game.turn);
496 draw_history: function() {
497 let log_display_lines = [];
498 for (let line of this.log) {
499 log_display_lines = log_display_lines.concat(this.msg_into_lines_of_width(line, this.window_width));
501 for (let y = terminal.rows - 1 - this.height_input,
502 i = log_display_lines.length - 1;
503 y >= this.height_header && i >= 0;
505 terminal.write(y, this.window_width, log_display_lines[i]);
508 draw_info: function() {
509 let lines = this.msg_into_lines_of_width(explorer.get_info(), this.window_width);
510 for (let y = this.height_header, i = 0; y < terminal.rows && i < lines.length; y++, i++) {
511 terminal.write(y, this.window_width, lines[i]);
514 draw_input: function() {
515 if (this.mode.has_input_prompt) {
516 for (let y = terminal.rows - this.height_input, i = 0; i < this.input_lines.length; y++, i++) {
517 terminal.write(y, this.window_width, this.input_lines[i]);
521 draw_help: function() {
522 let movement_keys_desc = Object.keys(this.movement_keys).join(',');
523 let content = this.mode.name + " mode help\n\n" + this.mode.help_intro + "\n\n";
524 if (this.mode == mode_play) {
525 content += "Available actions:\n";
526 if (game.tasks.includes('MOVE')) {
527 content += "[" + movement_keys_desc + "] – move player\n";
529 if (game.tasks.includes('FLATTEN_SURROUNDINGS')) {
530 content += "[" + tui.keys.flatten + "] – flatten player's surroundings\n";
532 content += '\nOther modes available from here:\n';
533 content += '[' + this.keys.switch_to_chat + '] – chat mode\n';
534 content += '[' + this.keys.switch_to_study + '] – study mode\n';
535 content += '[' + this.keys.switch_to_edit + '] – terrain edit mode\n';
536 content += '[' + this.keys.switch_to_portal + '] – portal edit mode\n';
537 content += '[' + this.keys.switch_to_annotate + '] – annotation mode\n';
538 content += '[' + this.keys.switch_to_password + '] – password input mode\n';
539 } else if (this.mode == mode_study) {
540 content += "Available actions:\n";
541 content += '[' + movement_keys_desc + '] – move question mark\n';
542 content += '[' + this.keys.toggle_map_mode + '] – toggle view between terrain, and password protection areas\n';
543 content += '\nOther modes available from here:\n';
544 content += '[' + this.keys.switch_to_chat + '] – chat mode\n';
545 content += '[' + this.keys.switch_to_play + '] – play mode\n';
546 } else if (this.mode == mode_chat) {
547 content += '/nick NAME – re-name yourself to NAME\n';
548 content += '/msg USER TEXT – send TEXT to USER\n';
549 content += '/' + this.keys.switch_to_play + ' or /play – switch to play mode\n';
550 content += '/' + this.keys.switch_to_study + ' or /study – switch to study mode\n';
553 if (!this.mode.has_input_prompt) {
554 start_x = this.window_width
556 terminal.drawBox(0, start_x, terminal.rows, this.window_width);
557 let lines = this.msg_into_lines_of_width(content, this.window_width);
558 for (let y = 0, i = 0; y < terminal.rows && i < lines.length; y++, i++) {
559 terminal.write(y, start_x, lines[i]);
562 full_refresh: function() {
563 terminal.drawBox(0, 0, terminal.rows, terminal.cols);
564 if (this.mode.is_intro) {
568 if (game.turn_complete) {
570 this.draw_turn_line();
572 this.draw_mode_line();
573 if (this.mode.shows_info) {
580 if (this.show_help) {
592 this.map_control = "";
593 this.map_size = [0,0];
598 get_thing: function(id_, create_if_not_found=false) {
599 if (id_ in game.things) {
600 return game.things[id_];
601 } else if (create_if_not_found) {
602 let t = new Thing([0,0]);
603 game.things[id_] = t;
607 move: function(start_position, direction) {
608 let target = [start_position[0], start_position[1]];
609 if (direction == 'LEFT') {
611 } else if (direction == 'RIGHT') {
613 } else if (game.map_geometry == 'Square') {
614 if (direction == 'UP') {
616 } else if (direction == 'DOWN') {
619 } else if (game.map_geometry == 'Hex') {
620 let start_indented = start_position[0] % 2;
621 if (direction == 'UPLEFT') {
623 if (!start_indented) {
626 } else if (direction == 'UPRIGHT') {
628 if (start_indented) {
631 } else if (direction == 'DOWNLEFT') {
633 if (!start_indented) {
636 } else if (direction == 'DOWNRIGHT') {
638 if (start_indented) {
643 if (target[0] < 0 || target[1] < 0 ||
644 target[0] >= this.map_size[0] || target[1] >= this.map_size[1]) {
654 server.init(websocket_location);
659 move: function(direction) {
660 let target = game.move(this.position, direction);
662 this.position = target
665 terminal.blink_screen();
668 update_info_db: function(yx, str) {
669 this.info_db[yx] = str;
670 if (tui.mode == mode_study) {
674 empty_info_db: function() {
676 if (tui.mode == mode_study) {
680 query_info: function() {
681 server.send(["GET_ANNOTATION", unparser.to_yx(explorer.position)]);
683 get_info: function() {
684 let position_i = this.position[0] * game.map_size[1] + this.position[1];
685 if (game.fov[position_i] != '.') {
686 return 'outside field of view';
689 info += "TERRAIN: " + game.map[position_i] + "\n";
690 for (let t_id in game.things) {
691 let t = game.things[t_id];
692 if (t.position[0] == this.position[0] && t.position[1] == this.position[1]) {
695 info += ": " + t.name_;
700 if (this.position in game.portals) {
701 info += "PORTAL: " + game.portals[this.position] + "\n";
703 if (this.position in this.info_db) {
704 info += "ANNOTATIONS: " + this.info_db[this.position];
710 annotate: function(msg) {
711 if (msg.length == 0) {
712 msg = " "; // triggers annotation deletion
714 server.send(["ANNOTATE", unparser.to_yx(explorer.position), msg, tui.password]);
716 set_portal: function(msg) {
717 if (msg.length == 0) {
718 msg = " "; // triggers portal deletion
720 server.send(["PORTAL", unparser.to_yx(explorer.position), msg, tui.password]);
724 tui.inputEl.addEventListener('input', (event) => {
725 if (tui.mode.has_input_prompt) {
726 let max_length = tui.window_width * terminal.rows - tui.input_prompt.length;
727 if (tui.inputEl.value.length > max_length) {
728 tui.inputEl.value = tui.inputEl.value.slice(0, max_length);
730 tui.recalc_input_lines();
731 } else if (tui.mode == mode_edit && tui.inputEl.value.length > 0) {
732 server.send(["TASK:WRITE", tui.inputEl.value[0], tui.password]);
733 tui.switch_mode(mode_play);
737 tui.inputEl.addEventListener('keydown', (event) => {
738 tui.show_help = false;
739 if (event.key == 'Enter') {
740 event.preventDefault();
742 if (tui.mode.has_input_prompt && event.key == 'Enter' && tui.inputEl.value == '/help') {
743 tui.show_help = true;
745 tui.restore_input_values();
746 } else if (!tui.mode.has_input_prompt && event.key == tui.keys.help) {
747 tui.show_help = true;
748 } else if (tui.mode == mode_login && event.key == 'Enter') {
749 tui.login_name = tui.inputEl.value;
750 server.send(['LOGIN', tui.inputEl.value]);
752 } else if (tui.mode == mode_portal && event.key == 'Enter') {
753 explorer.set_portal(tui.inputEl.value);
754 tui.switch_mode(mode_play);
755 } else if (tui.mode == mode_annotate && event.key == 'Enter') {
756 explorer.annotate(tui.inputEl.value);
757 tui.switch_mode(mode_play);
758 } else if (tui.mode == mode_password && event.key == 'Enter') {
759 if (tui.inputEl.value.length == 0) {
760 tui.inputEl.value = " ";
762 tui.password = tui.inputEl.value
763 tui.switch_mode(mode_play);
764 } else if (tui.mode == mode_teleport && event.key == 'Enter') {
765 if (tui.inputEl.value == 'YES!') {
766 server.reconnect_to(tui.teleport_target);
768 tui.log_msg('@ teleport aborted');
769 tui.switch_mode(mode_play);
771 } else if (tui.mode == mode_chat && event.key == 'Enter') {
772 let [tokens, token_starts] = parser.tokenize(tui.inputEl.value);
773 if (tokens.length > 0 && tokens[0].length > 0) {
774 if (tui.inputEl.value[0][0] == '/') {
775 if (tokens[0].slice(1) == 'play' || tokens[0][1] == tui.keys.switch_to_play) {
776 tui.switch_mode(mode_play);
777 } else if (tokens[0].slice(1) == 'study' || tokens[0][1] == tui.keys.switch_to_study) {
778 tui.switch_mode(mode_study);
779 } else if (tokens[0].slice(1) == 'nick') {
780 if (tokens.length > 1) {
781 server.send(['NICK', tokens[1]]);
783 tui.log_msg('? need new name');
785 } else if (tokens[0].slice(1) == 'msg') {
786 if (tokens.length > 2) {
787 let msg = tui.inputEl.value.slice(token_starts[2]);
788 server.send(['QUERY', tokens[1], msg]);
790 tui.log_msg('? need message target and message');
793 tui.log_msg('? unknown command');
796 server.send(['ALL', tui.inputEl.value]);
798 } else if (tui.inputEl.valuelength > 0) {
799 server.send(['ALL', tui.inputEl.value]);
802 } else if (tui.mode == mode_play) {
803 if (event.key === tui.keys.switch_to_chat) {
804 event.preventDefault();
805 tui.switch_mode(mode_chat);
806 } else if (event.key === tui.keys.switch_to_edit
807 && game.tasks.includes('WRITE')) {
808 event.preventDefault();
809 tui.switch_mode(mode_edit);
810 } else if (event.key === tui.keys.switch_to_study) {
811 tui.switch_mode(mode_study);
812 } else if (event.key === tui.keys.switch_to_password) {
813 event.preventDefault();
814 tui.switch_mode(mode_password);
815 } else if (event.key === tui.keys.flatten
816 && game.tasks.includes('FLATTEN_SURROUNDINGS')) {
817 server.send(["TASK:FLATTEN_SURROUNDINGS", tui.password]);
818 } else if (event.key in tui.movement_keys
819 && game.tasks.includes('MOVE')) {
820 server.send(['TASK:MOVE', tui.movement_keys[event.key]]);
821 } else if (event.key === tui.keys.switch_to_portal) {
822 event.preventDefault();
823 tui.switch_mode(mode_portal);
824 } else if (event.key === tui.keys.switch_to_annotate) {
825 event.preventDefault();
826 tui.switch_mode(mode_annotate);
828 } else if (tui.mode == mode_study) {
829 if (event.key === tui.keys.switch_to_chat) {
830 event.preventDefault();
831 tui.switch_mode(mode_chat);
832 } else if (event.key == tui.keys.switch_to_play) {
833 tui.switch_mode(mode_play);
834 } else if (event.key in tui.movement_keys) {
835 explorer.move(tui.movement_keys[event.key]);
836 } else if (event.key == tui.keys.toggle_map_mode) {
837 if (tui.map_mode == 'terrain') {
838 tui.map_mode = 'control';
840 tui.map_mode = 'terrain';
847 rows_selector.addEventListener('input', function() {
848 if (rows_selector.value % 4 != 0) {
851 window.localStorage.setItem(rows_selector.id, rows_selector.value);
852 terminal.initialize();
855 cols_selector.addEventListener('input', function() {
856 if (cols_selector.value % 4 != 0) {
859 window.localStorage.setItem(cols_selector.id, cols_selector.value);
860 terminal.initialize();
861 tui.window_width = terminal.cols / 2,
864 for (let key_selector of key_selectors) {
865 key_selector.addEventListener('input', function() {
866 window.localStorage.setItem(key_selector.id, key_selector.value);
870 window.setInterval(function() {
871 if (!(['input', 'n_cols', 'n_rows'].includes(document.activeElement.id)
872 || document.activeElement.id.startsWith('key_'))) {
876 window.setInterval(function() {
877 if (server.connected) {
878 server.send(['PING']);
880 server.reconnect_to(server.url);
881 tui.log_msg('@ attempting reconnect …')