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/";
41 let rows_selector = document.getElementById("n_rows");
42 let cols_selector = document.getElementById("n_cols");
43 let key_selectors = document.querySelectorAll('[id^="key_"]');
45 function restore_selector_value(selector) {
46 let stored_selection = window.localStorage.getItem(selector.id);
47 if (stored_selection) {
48 selector.value = stored_selection;
51 restore_selector_value(rows_selector);
52 restore_selector_value(cols_selector);
53 for (let key_selector of key_selectors) {
54 restore_selector_value(key_selector);
60 initialize: function() {
61 this.rows = rows_selector.value;
62 this.cols = cols_selector.value;
63 this.pre_el = document.getElementById("terminal");
64 this.pre_el.style.color = this.foreground;
65 this.pre_el.style.backgroundColor = this.background;
68 for (let y = 0, x = 0; y <= this.rows; x++) {
72 this.content.push(line);
81 blink_screen: function() {
82 this.pre_el.style.color = this.background;
83 this.pre_el.style.backgroundColor = this.foreground;
85 this.pre_el.style.color = this.foreground;
86 this.pre_el.style.backgroundColor = this.background;
91 for (let y = 0; y < this.rows; y++) {
92 let line = this.content[y].join('');
93 pre_string += line + '\n';
95 this.pre_el.textContent = pre_string;
97 write: function(start_y, start_x, msg) {
98 for (let x = start_x, i = 0; x < this.cols && i < msg.length; x++, i++) {
99 this.content[start_y][x] = msg[i];
102 drawBox: function(start_y, start_x, height, width) {
103 let end_y = start_y + height;
104 let end_x = start_x + width;
105 for (let y = start_y, x = start_x; y < this.rows; x++) {
113 this.content[y][x] = ' ';
117 terminal.initialize();
120 tokenize: function(str) {
126 for (let i = 0; i < str.length; i++) {
132 } else if (c == '\\') {
134 } else if (c == '"') {
139 } else if (c == '"') {
141 } else if (c === ' ') {
142 if (token.length > 0) {
151 if (token.length > 0) {
154 let token_starts = [];
155 for (let i = 0; i < token_ends.length; i++) {
156 token_starts.push(token_ends[i] - tokens[i].length);
158 return [tokens, token_starts];
160 parse_yx: function(position_string) {
161 let coordinate_strings = position_string.split(',')
162 let position = [0, 0];
163 position[0] = parseInt(coordinate_strings[0].slice(2));
164 position[1] = parseInt(coordinate_strings[1].slice(2));
176 init: function(url) {
178 this.websocket = new WebSocket(this.url);
179 this.websocket.onopen = function(event) {
180 server.connected = true;
181 game.thing_types = {};
182 server.send(['TASKS']);
183 server.send(['THING_TYPES']);
184 tui.log_msg("@ server connected! :)");
185 tui.switch_mode(mode_login);
187 this.websocket.onclose = function(event) {
188 server.connected = false;
189 tui.switch_mode(mode_waiting_for_server);
190 tui.log_msg("@ server disconnected :(");
192 this.websocket.onmessage = this.handle_event;
194 reconnect_to: function(url) {
195 this.websocket.close();
198 send: function(tokens) {
199 this.websocket.send(unparser.untokenize(tokens));
201 handle_event: function(event) {
202 let tokens = parser.tokenize(event.data)[0];
203 if (tokens[0] === 'TURN') {
204 game.turn_complete = false;
207 game.turn = parseInt(tokens[1]);
208 } else if (tokens[0] === 'THING') {
209 let t = game.get_thing(tokens[3], true);
210 t.position = parser.parse_yx(tokens[1]);
212 } else if (tokens[0] === 'THING_NAME') {
213 let t = game.get_thing(tokens[1], false);
217 } else if (tokens[0] === 'TASKS') {
218 game.tasks = tokens[1].split(',')
219 } else if (tokens[0] === 'THING_TYPE') {
220 game.thing_types[tokens[1]] = tokens[2]
221 } else if (tokens[0] === 'MAP') {
222 game.map_geometry = tokens[1];
224 game.map_size = parser.parse_yx(tokens[2]);
226 } else if (tokens[0] === 'FOV') {
228 } else if (tokens[0] === 'MAP_CONTROL') {
229 game.map_control = tokens[1]
230 } else if (tokens[0] === 'GAME_STATE_COMPLETE') {
231 game.turn_complete = true;
232 explorer.empty_info_db();
233 if (tui.mode == mode_post_login_wait) {
234 tui.switch_mode(mode_play);
235 } else if (tui.mode == mode_study) {
236 explorer.query_info();
238 let t = game.get_thing(game.player_id);
239 if (t.position in game.portals) {
240 tui.teleport_target = game.portals[t.position];
241 tui.switch_mode(mode_teleport);
245 } else if (tokens[0] === 'CHAT') {
246 tui.log_msg('# ' + tokens[1], 1);
247 } else if (tokens[0] === 'PLAYER_ID') {
248 game.player_id = parseInt(tokens[1]);
249 } else if (tokens[0] === 'LOGIN_OK') {
250 this.send(['GET_GAMESTATE']);
251 tui.switch_mode(mode_post_login_wait);
252 } else if (tokens[0] === 'PORTAL') {
253 let position = parser.parse_yx(tokens[1]);
254 game.portals[position] = tokens[2];
255 } else if (tokens[0] === 'ANNOTATION') {
256 let position = parser.parse_yx(tokens[1]);
257 explorer.update_info_db(position, tokens[2]);
258 } else if (tokens[0] === 'UNHANDLED_INPUT') {
259 tui.log_msg('? unknown command');
260 } else if (tokens[0] === 'PLAY_ERROR') {
261 terminal.blink_screen();
262 } else if (tokens[0] === 'ARGUMENT_ERROR') {
263 tui.log_msg('? syntax error: ' + tokens[1]);
264 } else if (tokens[0] === 'GAME_ERROR') {
265 tui.log_msg('? game error: ' + tokens[1]);
266 } else if (tokens[0] === 'PONG') {
269 tui.log_msg('? unhandled input: ' + event.data);
275 quote: function(str) {
277 for (let i = 0; i < str.length; i++) {
279 if (['"', '\\'].includes(c)) {
285 return quoted.join('');
287 to_yx: function(yx_coordinate) {
288 return "Y:" + yx_coordinate[0] + ",X:" + yx_coordinate[1];
290 untokenize: function(tokens) {
291 let quoted_tokens = [];
292 for (let token of tokens) {
293 quoted_tokens.push(this.quote(token));
295 return quoted_tokens.join(" ");
300 constructor(name, help_intro, has_input_prompt=false, shows_info=false, is_intro=false) {
302 this.has_input_prompt = has_input_prompt;
303 this.shows_info= shows_info;
304 this.is_intro = is_intro;
305 this.help_intro = help_intro;
308 let mode_waiting_for_server = new Mode('waiting_for_server', 'Waiting for a server response.', false, false, true);
309 let mode_login = new Mode('login', 'Pick your player name.', true, false, true);
310 let mode_post_login_wait = new Mode('waiting for game world', 'Waiting for a server response.', false, false, true);
311 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);
312 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);
313 let mode_play = new Mode('play', 'This mode allows you to interact with the map.', false, false);
314 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);
315 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);
316 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);
317 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);
318 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);
321 mode: mode_waiting_for_server,
325 window_width: terminal.cols / 2,
332 this.inputEl = document.getElementById("input");
333 this.inputEl.focus();
334 this.recalc_input_lines();
335 this.height_header = this.height_turn_line + this.height_mode_line;
336 this.log_msg("@ waiting for server connection ...");
339 init_keys: function() {
341 for (let key_selector of key_selectors) {
342 this.keys[key_selector.id.slice(4)] = key_selector.value;
344 this.movement_keys = {
345 [this.keys.square_move_up]: 'UP',
346 [this.keys.square_move_left]: 'LEFT',
347 [this.keys.square_move_down]: 'DOWN',
348 [this.keys.square_move_right]: 'RIGHT'
350 if (game.map_geometry == 'Hex') {
351 this.movement_keys = {
352 [this.keys.hex_move_upleft]: 'UPLEFT',
353 [this.keys.hex_move_upright]: 'UPRIGHT',
354 [this.keys.hex_move_right]: 'RIGHT',
355 [this.keys.hex_move_downright]: 'DOWNRIGHT',
356 [this.keys.hex_move_downleft]: 'DOWNLEFT',
357 [this.keys.hex_move_left]: 'LEFT'
361 switch_mode: function(mode) {
362 this.show_help = false;
363 this.map_mode = 'terrain';
364 if (mode.shows_info && game.player_id in game.things) {
365 explorer.position = game.things[game.player_id].position;
369 this.restore_input_values();
370 if (mode == mode_login) {
371 if (this.login_name) {
372 server.send(['LOGIN', this.login_name]);
374 this.log_msg("? need login name");
376 } else if (mode == mode_edit) {
377 this.show_help = true;
378 } else if (mode == mode_teleport) {
379 tui.log_msg("@ May teleport to: " + tui.teleport_target);
380 tui.log_msg("@ Enter 'YES!' to entusiastically affirm.");
384 restore_input_values: function() {
385 if (this.mode == mode_annotate && explorer.position in explorer.info_db) {
386 let info = explorer.info_db[explorer.position];
387 if (info != "(none)") {
388 this.inputEl.value = info;
389 this.recalc_input_lines();
391 } else if (this.mode == mode_portal && explorer.position in game.portals) {
392 let portal = game.portals[explorer.position]
393 this.inputEl.value = portal;
394 this.recalc_input_lines();
395 } else if (this.mode == mode_password) {
396 this.inputEl.value = this.password;
397 this.recalc_input_lines();
400 empty_input: function(str) {
401 this.inputEl.value = "";
402 if (this.mode.has_input_prompt) {
403 this.recalc_input_lines();
405 this.height_input = 0;
408 recalc_input_lines: function() {
409 this.input_lines = this.msg_into_lines_of_width(this.input_prompt + this.inputEl.value, this.window_width);
410 this.height_input = this.input_lines.length;
412 msg_into_lines_of_width: function(msg, width) {
415 for (let i = 0, x = 0; i < msg.length; i++, x++) {
416 if (x >= width || msg[i] == "\n") {
421 if (msg[i] != "\n") {
428 log_msg: function(msg) {
430 while (this.log.length > 100) {
435 draw_map: function() {
436 let map_lines_split = [];
438 let map_content = game.map;
439 if (this.map_mode == 'control') {
440 map_content = game.map_control;
442 for (let i = 0, j = 0; i < game.map.length; i++, j++) {
443 if (j == game.map_size[1]) {
444 map_lines_split.push(line);
448 line.push(map_content[i] + ' ');
450 map_lines_split.push(line);
451 if (this.map_mode == 'terrain') {
452 let used_positions = [];
453 for (const thing_id in game.things) {
454 let t = game.things[thing_id];
455 let symbol = game.thing_types[t.type_];
456 if (used_positions.includes(t.position.toString())) {
457 map_lines_split[t.position[0]][t.position[1]] = symbol + '+';
459 map_lines_split[t.position[0]][t.position[1]] = symbol + ' ';
461 used_positions.push(t.position.toString());
464 if (tui.mode.shows_info) {
465 map_lines_split[explorer.position[0]][explorer.position[1]] = '??';
468 if (game.map_geometry == 'Square') {
469 for (let line_split of map_lines_split) {
470 map_lines.push(line_split.join(''));
472 } else if (game.map_geometry == 'Hex') {
474 for (let line_split of map_lines_split) {
475 map_lines.push(' '.repeat(indent) + line_split.join(''));
483 let window_center = [terminal.rows / 2, this.window_width / 2];
484 let player = game.things[game.player_id];
485 let center_position = [player.position[0], player.position[1]];
486 if (tui.mode.shows_info) {
487 center_position = [explorer.position[0], explorer.position[1]];
489 center_position[1] = center_position[1] * 2;
490 let offset = [center_position[0] - window_center[0],
491 center_position[1] - window_center[1]]
492 if (game.map_geometry == 'Hex' && offset[0] % 2) {
495 let term_y = Math.max(0, -offset[0]);
496 let term_x = Math.max(0, -offset[1]);
497 let map_y = Math.max(0, offset[0]);
498 let map_x = Math.max(0, offset[1]);
499 for (; term_y < terminal.rows && map_y < game.map_size[0]; term_y++, map_y++) {
500 let to_draw = map_lines[map_y].slice(map_x, this.window_width + offset[1]);
501 terminal.write(term_y, term_x, to_draw);
504 draw_mode_line: function() {
505 let help = 'hit [' + this.keys.help + '] for help';
506 if (this.mode.has_input_prompt) {
507 help = 'enter /help for help';
509 terminal.write(0, this.window_width, 'MODE: ' + this.mode.name + ' – ' + help);
511 draw_turn_line: function(n) {
512 terminal.write(1, this.window_width, 'TURN: ' + game.turn);
514 draw_history: function() {
515 let log_display_lines = [];
516 for (let line of this.log) {
517 log_display_lines = log_display_lines.concat(this.msg_into_lines_of_width(line, this.window_width));
519 for (let y = terminal.rows - 1 - this.height_input,
520 i = log_display_lines.length - 1;
521 y >= this.height_header && i >= 0;
523 terminal.write(y, this.window_width, log_display_lines[i]);
526 draw_info: function() {
527 let lines = this.msg_into_lines_of_width(explorer.get_info(), this.window_width);
528 for (let y = this.height_header, i = 0; y < terminal.rows && i < lines.length; y++, i++) {
529 terminal.write(y, this.window_width, lines[i]);
532 draw_input: function() {
533 if (this.mode.has_input_prompt) {
534 for (let y = terminal.rows - this.height_input, i = 0; i < this.input_lines.length; y++, i++) {
535 terminal.write(y, this.window_width, this.input_lines[i]);
539 draw_help: function() {
540 let movement_keys_desc = Object.keys(this.movement_keys).join(',');
541 let content = this.mode.name + " mode help\n\n" + this.mode.help_intro + "\n\n";
542 if (this.mode == mode_play) {
543 content += "Available actions:\n";
544 if (game.tasks.includes('MOVE')) {
545 content += "[" + movement_keys_desc + "] – move player\n";
547 if (game.tasks.includes('PICK_UP')) {
548 content += "[" + this.keys.take_thing + "] – take thing under player\n";
550 if (game.tasks.includes('DROP')) {
551 content += "[" + this.keys.drop_thing + "] – drop carried thing\n";
553 if (game.tasks.includes('FLATTEN_SURROUNDINGS')) {
554 content += "[" + tui.keys.flatten + "] – flatten player's surroundings\n";
556 content += '\nOther modes available from here:\n';
557 content += '[' + this.keys.switch_to_chat + '] – chat mode\n';
558 content += '[' + this.keys.switch_to_study + '] – study mode\n';
559 content += '[' + this.keys.switch_to_edit + '] – terrain edit mode\n';
560 content += '[' + this.keys.switch_to_portal + '] – portal edit mode\n';
561 content += '[' + this.keys.switch_to_annotate + '] – annotation mode\n';
562 content += '[' + this.keys.switch_to_password + '] – password input mode\n';
563 } else if (this.mode == mode_study) {
564 content += "Available actions:\n";
565 content += '[' + movement_keys_desc + '] – move question mark\n';
566 content += '[' + this.keys.toggle_map_mode + '] – toggle view between terrain, and password protection areas\n';
567 content += '\nOther modes available from here:\n';
568 content += '[' + this.keys.switch_to_chat + '] – chat mode\n';
569 content += '[' + this.keys.switch_to_play + '] – play mode\n';
570 } else if (this.mode == mode_chat) {
571 content += '/nick NAME – re-name yourself to NAME\n';
572 //content += '/msg USER TEXT – send TEXT to USER\n';
573 content += '/' + this.keys.switch_to_play + ' or /play – switch to play mode\n';
574 content += '/' + this.keys.switch_to_study + ' or /study – switch to study mode\n';
577 if (!this.mode.has_input_prompt) {
578 start_x = this.window_width
580 terminal.drawBox(0, start_x, terminal.rows, this.window_width);
581 let lines = this.msg_into_lines_of_width(content, this.window_width);
582 for (let y = 0, i = 0; y < terminal.rows && i < lines.length; y++, i++) {
583 terminal.write(y, start_x, lines[i]);
586 full_refresh: function() {
587 terminal.drawBox(0, 0, terminal.rows, terminal.cols);
588 if (this.mode.is_intro) {
592 if (game.turn_complete) {
594 this.draw_turn_line();
596 this.draw_mode_line();
597 if (this.mode.shows_info) {
604 if (this.show_help) {
616 this.map_control = "";
617 this.map_size = [0,0];
622 get_thing: function(id_, create_if_not_found=false) {
623 if (id_ in game.things) {
624 return game.things[id_];
625 } else if (create_if_not_found) {
626 let t = new Thing([0,0]);
627 game.things[id_] = t;
631 move: function(start_position, direction) {
632 let target = [start_position[0], start_position[1]];
633 if (direction == 'LEFT') {
635 } else if (direction == 'RIGHT') {
637 } else if (game.map_geometry == 'Square') {
638 if (direction == 'UP') {
640 } else if (direction == 'DOWN') {
643 } else if (game.map_geometry == 'Hex') {
644 let start_indented = start_position[0] % 2;
645 if (direction == 'UPLEFT') {
647 if (!start_indented) {
650 } else if (direction == 'UPRIGHT') {
652 if (start_indented) {
655 } else if (direction == 'DOWNLEFT') {
657 if (!start_indented) {
660 } else if (direction == 'DOWNRIGHT') {
662 if (start_indented) {
667 if (target[0] < 0 || target[1] < 0 ||
668 target[0] >= this.map_size[0] || target[1] >= this.map_size[1]) {
678 server.init(websocket_location);
683 move: function(direction) {
684 let target = game.move(this.position, direction);
686 this.position = target
689 terminal.blink_screen();
692 update_info_db: function(yx, str) {
693 this.info_db[yx] = str;
694 if (tui.mode == mode_study) {
698 empty_info_db: function() {
700 if (tui.mode == mode_study) {
704 query_info: function() {
705 server.send(["GET_ANNOTATION", unparser.to_yx(explorer.position)]);
707 get_info: function() {
708 let position_i = this.position[0] * game.map_size[1] + this.position[1];
709 if (game.fov[position_i] != '.') {
710 return 'outside field of view';
713 info += "TERRAIN: " + game.map[position_i] + "\n";
714 for (let t_id in game.things) {
715 let t = game.things[t_id];
716 if (t.position[0] == this.position[0] && t.position[1] == this.position[1]) {
717 info += "THING: " + t.type_;
719 info += " (name: " + t.name_ + ")";
724 if (this.position in game.portals) {
725 info += "PORTAL: " + game.portals[this.position] + "\n";
727 if (this.position in this.info_db) {
728 info += "ANNOTATIONS: " + this.info_db[this.position];
734 annotate: function(msg) {
735 if (msg.length == 0) {
736 msg = " "; // triggers annotation deletion
738 server.send(["ANNOTATE", unparser.to_yx(explorer.position), msg, tui.password]);
740 set_portal: function(msg) {
741 if (msg.length == 0) {
742 msg = " "; // triggers portal deletion
744 server.send(["PORTAL", unparser.to_yx(explorer.position), msg, tui.password]);
748 tui.inputEl.addEventListener('input', (event) => {
749 if (tui.mode.has_input_prompt) {
750 let max_length = tui.window_width * terminal.rows - tui.input_prompt.length;
751 if (tui.inputEl.value.length > max_length) {
752 tui.inputEl.value = tui.inputEl.value.slice(0, max_length);
754 tui.recalc_input_lines();
755 } else if (tui.mode == mode_edit && tui.inputEl.value.length > 0) {
756 server.send(["TASK:WRITE", tui.inputEl.value[0], tui.password]);
757 tui.switch_mode(mode_play);
761 tui.inputEl.addEventListener('keydown', (event) => {
762 tui.show_help = false;
763 if (event.key == 'Enter') {
764 event.preventDefault();
766 if (tui.mode.has_input_prompt && event.key == 'Enter' && tui.inputEl.value == '/help') {
767 tui.show_help = true;
769 tui.restore_input_values();
770 } else if (!tui.mode.has_input_prompt && event.key == tui.keys.help) {
771 tui.show_help = true;
772 } else if (tui.mode == mode_login && event.key == 'Enter') {
773 tui.login_name = tui.inputEl.value;
774 server.send(['LOGIN', tui.inputEl.value]);
776 } else if (tui.mode == mode_portal && event.key == 'Enter') {
777 explorer.set_portal(tui.inputEl.value);
778 tui.switch_mode(mode_play);
779 } else if (tui.mode == mode_annotate && event.key == 'Enter') {
780 explorer.annotate(tui.inputEl.value);
781 tui.switch_mode(mode_play);
782 } else if (tui.mode == mode_password && event.key == 'Enter') {
783 if (tui.inputEl.value.length == 0) {
784 tui.inputEl.value = " ";
786 tui.password = tui.inputEl.value
787 tui.switch_mode(mode_play);
788 } else if (tui.mode == mode_teleport && event.key == 'Enter') {
789 if (tui.inputEl.value == 'YES!') {
790 server.reconnect_to(tui.teleport_target);
792 tui.log_msg('@ teleport aborted');
793 tui.switch_mode(mode_play);
795 } else if (tui.mode == mode_chat && event.key == 'Enter') {
796 let [tokens, token_starts] = parser.tokenize(tui.inputEl.value);
797 if (tokens.length > 0 && tokens[0].length > 0) {
798 if (tui.inputEl.value[0][0] == '/') {
799 if (tokens[0].slice(1) == 'play' || tokens[0][1] == tui.keys.switch_to_play) {
800 tui.switch_mode(mode_play);
801 } else if (tokens[0].slice(1) == 'study' || tokens[0][1] == tui.keys.switch_to_study) {
802 tui.switch_mode(mode_study);
803 } else if (tokens[0].slice(1) == 'nick') {
804 if (tokens.length > 1) {
805 server.send(['NICK', tokens[1]]);
807 tui.log_msg('? need new name');
809 //} else if (tokens[0].slice(1) == 'msg') {
810 // if (tokens.length > 2) {
811 // let msg = tui.inputEl.value.slice(token_starts[2]);
812 // server.send(['QUERY', tokens[1], msg]);
814 // tui.log_msg('? need message target and message');
817 tui.log_msg('? unknown command');
820 server.send(['ALL', tui.inputEl.value]);
822 } else if (tui.inputEl.valuelength > 0) {
823 server.send(['ALL', tui.inputEl.value]);
826 } else if (tui.mode == mode_play) {
827 if (event.key === tui.keys.switch_to_chat) {
828 event.preventDefault();
829 tui.switch_mode(mode_chat);
830 } else if (event.key === tui.keys.switch_to_edit
831 && game.tasks.includes('WRITE')) {
832 event.preventDefault();
833 tui.switch_mode(mode_edit);
834 } else if (event.key === tui.keys.switch_to_study) {
835 tui.switch_mode(mode_study);
836 } else if (event.key === tui.keys.switch_to_password) {
837 event.preventDefault();
838 tui.switch_mode(mode_password);
839 } else if (event.key === tui.keys.flatten
840 && game.tasks.includes('FLATTEN_SURROUNDINGS')) {
841 server.send(["TASK:FLATTEN_SURROUNDINGS", tui.password]);
842 } else if (event.key === tui.keys.take_thing
843 && game.tasks.includes('PICK_UP')) {
844 server.send(["TASK:PICK_UP"]);
845 } else if (event.key === tui.keys.drop_thing
846 && game.tasks.includes('DROP')) {
847 server.send(["TASK:DROP"]);
848 } else if (event.key in tui.movement_keys
849 && game.tasks.includes('MOVE')) {
850 server.send(['TASK:MOVE', tui.movement_keys[event.key]]);
851 } else if (event.key === tui.keys.switch_to_portal) {
852 event.preventDefault();
853 tui.switch_mode(mode_portal);
854 } else if (event.key === tui.keys.switch_to_annotate) {
855 event.preventDefault();
856 tui.switch_mode(mode_annotate);
858 } else if (tui.mode == mode_study) {
859 if (event.key === tui.keys.switch_to_chat) {
860 event.preventDefault();
861 tui.switch_mode(mode_chat);
862 } else if (event.key == tui.keys.switch_to_play) {
863 tui.switch_mode(mode_play);
864 } else if (event.key in tui.movement_keys) {
865 explorer.move(tui.movement_keys[event.key]);
866 } else if (event.key == tui.keys.toggle_map_mode) {
867 if (tui.map_mode == 'terrain') {
868 tui.map_mode = 'control';
870 tui.map_mode = 'terrain';
877 rows_selector.addEventListener('input', function() {
878 if (rows_selector.value % 4 != 0) {
881 window.localStorage.setItem(rows_selector.id, rows_selector.value);
882 terminal.initialize();
885 cols_selector.addEventListener('input', function() {
886 if (cols_selector.value % 4 != 0) {
889 window.localStorage.setItem(cols_selector.id, cols_selector.value);
890 terminal.initialize();
891 tui.window_width = terminal.cols / 2,
894 for (let key_selector of key_selectors) {
895 key_selector.addEventListener('input', function() {
896 window.localStorage.setItem(key_selector.id, key_selector.value);
900 window.setInterval(function() {
901 if (!(['input', 'n_cols', 'n_rows'].includes(document.activeElement.id)
902 || document.activeElement.id.startsWith('key_'))) {
906 window.setInterval(function() {
907 if (server.connected) {
908 server.send(['PING']);
910 server.reconnect_to(server.url);
911 tui.log_msg('@ attempting reconnect …')