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 window.setInterval(function() { server.send(['PING']) }, 30000);
180 tui.log_msg("@ server connected! :)");
181 tui.switch_mode(mode_login);
183 this.websocket.onclose = function(event) {
184 tui.log_msg("@ server disconnected :(");
185 tui.log_msg("@ hint: try the '/reconnect' command");
187 this.websocket.onmessage = this.handle_event;
189 reconnect: function() {
190 this.reconnect_to(this.url);
192 reconnect_to: function(url) {
193 this.websocket.close();
196 send: function(tokens) {
197 this.websocket.send(unparser.untokenize(tokens));
199 handle_event: function(event) {
200 let tokens = parser.tokenize(event.data)[0];
201 if (tokens[0] === 'TURN') {
202 game.turn_complete = false;
205 game.turn = parseInt(tokens[1]);
206 } else if (tokens[0] === 'THING_POS') {
207 game.get_thing(tokens[1], true).position = parser.parse_yx(tokens[2]);
208 } else if (tokens[0] === 'THING_NAME') {
209 game.get_thing(tokens[1], true).name_ = tokens[2];
210 } else if (tokens[0] === 'TASKS') {
211 game.tasks = tokens[1].split(',')
212 } else if (tokens[0] === 'MAP') {
213 game.map_geometry = tokens[1];
215 game.map_size = parser.parse_yx(tokens[2]);
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() {
685 let position_i = this.position[0] * game.map_size[1] + this.position[1];
686 info += "TERRAIN: " + game.map[position_i] + "\n";
687 for (let t_id in game.things) {
688 let t = game.things[t_id];
689 if (t.position[0] == this.position[0] && t.position[1] == this.position[1]) {
692 info += ": " + t.name_;
697 if (this.position in game.portals) {
698 info += "PORTAL: " + game.portals[this.position] + "\n";
700 if (this.position in this.info_db) {
701 info += "ANNOTATIONS: " + this.info_db[this.position];
707 annotate: function(msg) {
708 if (msg.length == 0) {
709 msg = " "; // triggers annotation deletion
711 server.send(["ANNOTATE", unparser.to_yx(explorer.position), msg, tui.password]);
713 set_portal: function(msg) {
714 if (msg.length == 0) {
715 msg = " "; // triggers portal deletion
717 server.send(["PORTAL", unparser.to_yx(explorer.position), msg, tui.password]);
721 tui.inputEl.addEventListener('input', (event) => {
722 if (tui.mode.has_input_prompt) {
723 let max_length = tui.window_width * terminal.rows - tui.input_prompt.length;
724 if (tui.inputEl.value.length > max_length) {
725 tui.inputEl.value = tui.inputEl.value.slice(0, max_length);
727 tui.recalc_input_lines();
728 } else if (tui.mode == mode_edit && tui.inputEl.value.length > 0) {
729 server.send(["TASK:WRITE", tui.inputEl.value[0], tui.password]);
730 tui.switch_mode(mode_play);
734 tui.inputEl.addEventListener('keydown', (event) => {
735 tui.show_help = false;
736 if (event.key == 'Enter') {
737 event.preventDefault();
739 if (tui.mode.has_input_prompt && event.key == 'Enter' && tui.inputEl.value == '/help') {
740 tui.show_help = true;
742 tui.restore_input_values();
743 } else if (!tui.mode.has_input_prompt && event.key == tui.keys.help) {
744 tui.show_help = true;
745 } else if (tui.mode == mode_login && event.key == 'Enter') {
746 tui.login_name = tui.inputEl.value;
747 server.send(['LOGIN', tui.inputEl.value]);
749 } else if (tui.mode == mode_portal && event.key == 'Enter') {
750 explorer.set_portal(tui.inputEl.value);
751 tui.switch_mode(mode_play);
752 } else if (tui.mode == mode_annotate && event.key == 'Enter') {
753 explorer.annotate(tui.inputEl.value);
754 tui.switch_mode(mode_play);
755 } else if (tui.mode == mode_password && event.key == 'Enter') {
756 if (tui.inputEl.value.length == 0) {
757 tui.inputEl.value = " ";
759 tui.password = tui.inputEl.value
760 tui.switch_mode(mode_play);
761 } else if (tui.mode == mode_teleport && event.key == 'Enter') {
762 if (tui.inputEl.value == 'YES!') {
763 server.reconnect_to(tui.teleport_target);
765 tui.log_msg('@ teleport aborted');
766 tui.switch_mode(mode_play);
768 } else if (tui.mode == mode_chat && event.key == 'Enter') {
769 let [tokens, token_starts] = parser.tokenize(tui.inputEl.value);
770 if (tokens.length > 0 && tokens[0].length > 0) {
771 if (tui.inputEl.value[0][0] == '/') {
772 if (tokens[0].slice(1) == 'play' || tokens[0][1] == tui.keys.switch_to_play) {
773 tui.switch_mode(mode_play);
774 } else if (tokens[0].slice(1) == 'study' || tokens[0][1] == tui.keys.switch_to_study) {
775 tui.switch_mode(mode_study);
776 } else if (tokens[0].slice(1) == 'nick') {
777 if (tokens.length > 1) {
778 server.send(['NICK', tokens[1]]);
780 tui.log_msg('? need new name');
782 } else if (tokens[0].slice(1) == 'msg') {
783 if (tokens.length > 2) {
784 let msg = tui.inputEl.value.slice(token_starts[2]);
785 server.send(['QUERY', tokens[1], msg]);
787 tui.log_msg('? need message target and message');
789 } else if (tokens[0].slice(1) == 'reconnect') {
790 if (tokens.length > 1) {
791 server.reconnect_to(tokens[1]);
796 tui.log_msg('? unknown command');
799 server.send(['ALL', tui.inputEl.value]);
801 } else if (tui.inputEl.valuelength > 0) {
802 server.send(['ALL', tui.inputEl.value]);
805 } else if (tui.mode == mode_play) {
806 if (event.key === tui.keys.switch_to_chat) {
807 event.preventDefault();
808 tui.switch_mode(mode_chat);
809 } else if (event.key === tui.keys.switch_to_edit
810 && game.tasks.includes('WRITE')) {
811 event.preventDefault();
812 tui.switch_mode(mode_edit);
813 } else if (event.key === tui.keys.switch_to_study) {
814 tui.switch_mode(mode_study);
815 } else if (event.key === tui.keys.switch_to_password) {
816 event.preventDefault();
817 tui.switch_mode(mode_password);
818 } else if (event.key === tui.keys.flatten
819 && game.tasks.includes('FLATTEN_SURROUNDINGS')) {
820 server.send(["TASK:FLATTEN_SURROUNDINGS", tui.password]);
821 } else if (event.key in tui.movement_keys
822 && game.tasks.includes('MOVE')) {
823 server.send(['TASK:MOVE', tui.movement_keys[event.key]]);
824 } else if (event.key === tui.keys.switch_to_portal) {
825 event.preventDefault();
826 tui.switch_mode(mode_portal);
827 } else if (event.key === tui.keys.switch_to_annotate) {
828 event.preventDefault();
829 tui.switch_mode(mode_annotate);
831 } else if (tui.mode == mode_study) {
832 if (event.key === tui.keys.switch_to_chat) {
833 event.preventDefault();
834 tui.switch_mode(mode_chat);
835 } else if (event.key == tui.keys.switch_to_play) {
836 tui.switch_mode(mode_play);
837 } else if (event.key in tui.movement_keys) {
838 explorer.move(tui.movement_keys[event.key]);
839 } else if (event.key == tui.keys.toggle_map_mode) {
840 if (tui.map_mode == 'terrain') {
841 tui.map_mode = 'control';
843 tui.map_mode = 'terrain';
850 rows_selector.addEventListener('input', function() {
851 if (rows_selector.value % 4 != 0) {
854 window.localStorage.setItem(rows_selector.id, rows_selector.value);
855 terminal.initialize();
858 cols_selector.addEventListener('input', function() {
859 if (cols_selector.value % 4 != 0) {
862 window.localStorage.setItem(cols_selector.id, cols_selector.value);
863 terminal.initialize();
864 tui.window_width = terminal.cols / 2,
867 for (let key_selector of key_selectors) {
868 key_selector.addEventListener('input', function() {
869 window.localStorage.setItem(key_selector.id, key_selector.value);
873 window.setInterval(function() {
874 if (!(['input', 'n_cols', 'n_rows'].includes(document.activeElement.id)
875 || document.activeElement.id.startsWith('key_'))) {