13 terminal rows: <input id="n_rows" type="number" step=4 min=24 value=24 />
14 / terminal columns: <input id="n_cols" type="number" step=4 min=80 value=80 />
15 / <a href="https://plomlompom.com/repos/?p=plomrogue2;a=summary">source code</a> (includes proper terminal/ncurses client)
17 <pre id="terminal"></pre>
18 <textarea id="input" style="opacity: 0; width: 0px;"></textarea>
20 keyboard input/control: <span id="keyboard_control"></span>
22 <h3>button controls for mouse players</h3>
23 <table id="move_table" style="float: left">
25 <td style="text-align: right"><button id="hex_move_upleft"></button></td>
26 <td style="text-align: center"><button id="square_move_up"></button></td>
27 <td><button id="hex_move_upright"></button></td>
30 <td style="text-align: right;"><button id="square_move_left"></button><button id="hex_move_left">left</button></td>
31 <td stlye="text-align: center;">move</td>
32 <td><button id="square_move_right"></button><button id="hex_move_right"></button></td>
35 <td><button id="hex_move_downleft"></button></td>
36 <td style="text-align: center"><button id="square_move_down"></button></td>
37 <td><button id="hex_move_downright"></button></td>
42 <td><button id="help"></button></td>
45 <td><button id="switch_to_chat"></button><br /></td>
48 <td><button id="switch_to_study"></button></td>
49 <td><button id="toggle_map_mode"></button>
52 <td><button id="switch_to_play"></button></td>
54 <button id="take_thing"></button>
55 <button id="drop_thing"></button>
56 <button id="door"></button>
57 <button id="consume"></button>
58 <button id="switch_to_command_thing"></button>
59 <button id="teleport"></button>
63 <td><button id="switch_to_edit"></button></td>
65 <button id="switch_to_write"></button>
66 <button id="flatten"></button>
67 <button id="switch_to_annotate"></button>
68 <button id="switch_to_portal"></button>
69 <button id="switch_to_name_thing"></button>
70 <button id="switch_to_password"></button>
74 <td><button id="switch_to_admin_enter"></button></td>
76 <button id="switch_to_control_pw_type"></button>
77 <button id="switch_to_control_tile_type"></button>
78 <button id="switch_to_admin_thing_protect"></button>
79 <button id="toggle_tile_draw"></button>
84 <h3>edit keybindings</h3> (see <a href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values">here</a> for non-obvious available values):<br />
86 <li>move up (square grid): <input id="key_square_move_up" type="text" value="w" /> (hint: ArrowUp)
87 <li>move left (square grid): <input id="key_square_move_left" type="text" value="a" /> (hint: ArrowLeft)
88 <li>move down (square grid): <input id="key_square_move_down" type="text" value="s" /> (hint: ArrowDown)
89 <li>move right (square grid): <input id="key_square_move_right" type="text" value="d" /> (hint: ArrowRight)
90 <li>move up-left (hex grid): <input id="key_hex_move_upleft" type="text" value="w" />
91 <li>move up-right (hex grid): <input id="key_hex_move_upright" type="text" value="e" />
92 <li>move right (hex grid): <input id="key_hex_move_right" type="text" value="d" />
93 <li>move down-right (hex grid): <input id="key_hex_move_downright" type="text" value="x" />
94 <li>move down-left (hex grid): <input id="key_hex_move_downleft" type="text" value="y" />
95 <li>move left (hex grid): <input id="key_hex_move_left" type="text" value="a" />
96 <li>help: <input id="key_help" type="text" value="h" />
97 <li>flatten surroundings: <input id="key_flatten" type="text" value="F" />
98 <li>teleport: <input id="key_teleport" type="text" value="p" />
99 <li>pick up thing: <input id="key_take_thing" type="text" value="z" />
100 <li>drop thing: <input id="key_drop_thing" type="text" value="u" />
101 <li>open/close: <input id="key_door" type="text" value="D" />
102 <li>consume: <input id="key_consume" type="text" value="C" />
103 <li><input id="key_switch_to_chat" type="text" value="t" />
104 <li><input id="key_switch_to_play" type="text" value="p" />
105 <li><input id="key_switch_to_study" type="text" value="?" />
106 <li><input id="key_switch_to_edit" type="text" value="E" />
107 <li><input id="key_switch_to_write" type="text" value="m" />
108 <li><input id="key_switch_to_name_thing" type="text" value="N" />
109 <li><input id="key_switch_to_command_thing" type="text" value="O" />
110 <li><input id="key_switch_to_password" type="text" value="P" />
111 <li><input id="key_switch_to_admin_enter" type="text" value="A" />
112 <li><input id="key_switch_to_control_pw_type" type="text" value="C" />
113 <li><input id="key_switch_to_control_tile_type" type="text" value="Q" />
114 <li><input id="key_switch_to_admin_thing_protect" type="text" value="T" />
115 <li><input id="key_switch_to_annotate" type="text" value="M" />
116 <li><input id="key_switch_to_portal" type="text" value="T" />
117 <li>toggle map view: <input id="key_toggle_map_mode" type="text" value="L" />
118 <li>toggle protection character drawing: <input id="key_toggle_tile_draw" type="text" value="m" />
123 let websocket_location = "wss://plomlompom.com/rogue_chat/";
124 //let websocket_location = "ws://localhost:8000/";
129 'long': 'This mode allows you to interact with the map in various ways.'
133 'long': '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. Toggle the map view to show or hide different information layers.'},
135 'short': 'world edit',
136 'long': 'This mode allows you to change the game world in various ways. Individual map tiles can be protected by "protection characters", which you can see by toggling into the protections map view. You can edit a tile if you set the world edit password that matches its protection character. The character "." marks the absence of protection: Such tiles can always be edited.'
139 'short': 'name thing',
140 'long': 'Give name to/change name of thing here.'
143 'short': 'command thing',
144 'long': 'Enter a command to the thing you carry. Enter nothing to return to play mode.'
146 'admin_thing_protect': {
147 'short': 'change thing protection',
148 'long': 'Change protection character for thing here.'
151 'short': 'change terrain',
152 'long': 'This mode allows you to change the map tile you currently stand on (if your world editing password authorizes you so). Just enter any printable ASCII character to imprint it on the ground below you.'
155 'short': 'change protection character password',
156 'long': 'This mode is the first of two steps to change the password for a protection character. First enter the protection character for which you want to change the password.'
159 'short': 'change protection character password',
160 'long': 'This mode is the second of two steps to change the password for a protection character. Enter the new password for the protection character you chose.'
162 'control_tile_type': {
163 'short': 'change tiles protection',
164 'long': 'This mode is the first of two steps to change tile protection areas on the map. First enter the tile protection character you want to write.'
166 'control_tile_draw': {
167 'short': 'change tiles protection',
168 'long': 'This mode is the second of two steps to change tile protection areas on the map. Toggle tile protection drawing on/off and move the ?? cursor around the map to draw the selected protection character.'
171 'short': 'annotate tile',
172 'long': 'This mode allows you to add/edit a comment on the tile you are currently standing on (provided your world editing password authorizes you so). Hit Return to leave.'
175 'short': 'edit portal',
176 'long': 'This mode allows you to imprint/edit/remove a teleportation target on the ground you are currently standing on (provided your world 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.'
180 'long': '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:'
184 'long': 'Enter your player name.'
186 'waiting_for_server': {
187 'short': 'waiting for server response',
188 'long': 'Waiting for a server response.'
191 'short': 'waiting for server response',
192 'long': 'Waiting for a server response.'
195 'short': 'set world edit password',
196 'long': 'This mode allows you to change the password that you send to authorize yourself for editing password-protected elements of the world. Hit return to confirm and leave.'
199 'short': 'become admin',
200 'long': 'This mode allows you to become admin if you know an admin password.'
204 'long': 'This mode allows you access to actions limited to administrators.'
207 let key_descriptions = {
209 'flatten': 'flatten surroundings',
210 'teleport': 'teleport',
211 'take_thing': 'pick up thing',
212 'drop_thing': 'drop thing',
213 'door': 'open/close',
214 'consume': 'consume',
215 'toggle_map_mode': 'toggle map view',
216 'toggle_tile_draw': 'toggle protection character drawing',
217 'hex_move_upleft': 'up-left',
218 'hex_move_upright': 'up-right',
219 'hex_move_right': 'right',
220 'hex_move_left': 'left',
221 'hex_move_downleft': 'down-left',
222 'hex_move_downright': 'down-right',
223 'square_move_up': 'up',
224 'square_move_left': 'left',
225 'square_move_down': 'down',
226 'square_move_right': 'right',
228 for (const mode_name of Object.keys(mode_helps)) {
229 key_descriptions['switch_to_' + mode_name] = mode_helps[mode_name].short;
232 let rows_selector = document.getElementById("n_rows");
233 let cols_selector = document.getElementById("n_cols");
234 let key_selectors = document.querySelectorAll('[id^="key_"]');
236 for (const key_switch_selector of document.querySelectorAll('[id^="key_switch_to_"]')) {
237 const action = key_switch_selector.id.slice("key_switch_to_".length);
238 key_switch_selector.parentNode.prepend(mode_helps[action].short + ': ');
241 function restore_selector_value(selector) {
242 let stored_selection = window.localStorage.getItem(selector.id);
243 if (stored_selection) {
244 selector.value = stored_selection;
247 restore_selector_value(rows_selector);
248 restore_selector_value(cols_selector);
249 for (let key_selector of key_selectors) {
250 restore_selector_value(key_selector);
253 function escapeHTML(str) {
255 replace(/&/g, '&').
256 replace(/</g, '<').
257 replace(/>/g, '>').
258 replace(/'/g, ''').
259 replace(/"/g, '"');
263 initialize: function() {
264 this.rows = rows_selector.value;
265 this.cols = cols_selector.value;
266 this.pre_el = document.getElementById("terminal");
267 this.set_default_colors();
271 for (let y = 0, x = 0; y <= this.rows; x++) {
272 if (x == this.cols) {
275 this.content.push(line);
277 if (y == this.rows) {
284 apply_colors: function() {
285 this.pre_el.style.color = this.foreground;
286 this.pre_el.style.backgroundColor = this.background;
288 set_default_colors: function() {
289 this.foreground = 'white';
290 this.background = 'black';
293 set_random_colors: function() {
294 function rand(offset) {
295 return Math.floor(offset + Math.random() * 96).toString(16).padStart(2, '0');
297 this.foreground = '#' + rand(159) + rand(159) + rand(159);
298 this.background = '#' + rand(0) + rand(0) + rand(0);
301 blink_screen: function() {
302 this.pre_el.style.color = this.background;
303 this.pre_el.style.backgroundColor = this.foreground;
305 this.pre_el.style.color = this.foreground;
306 this.pre_el.style.backgroundColor = this.background;
309 refresh: function() {
310 let pre_content = '';
311 for (let y = 0; y < this.rows; y++) {
312 let line = this.content[y].join('');
314 if (y in tui.links) {
316 for (let span of tui.links[y]) {
317 chunks.push(escapeHTML(line.slice(start_x, span[0])));
318 chunks.push('<a target="_blank" href="');
319 chunks.push(escapeHTML(span[2]));
321 chunks.push(escapeHTML(line.slice(span[0], span[1])));
325 chunks.push(escapeHTML(line.slice(start_x)));
327 chunks = [escapeHTML(line)];
329 for (const chunk of chunks) {
330 pre_content += chunk;
334 this.pre_el.innerHTML = pre_content;
336 write: function(start_y, start_x, msg) {
337 for (let x = start_x, i = 0; x < this.cols && i < msg.length; x++, i++) {
338 this.content[start_y][x] = msg[i];
341 drawBox: function(start_y, start_x, height, width) {
342 let end_y = start_y + height;
343 let end_x = start_x + width;
344 for (let y = start_y, x = start_x; y < this.rows; x++) {
352 this.content[y][x] = ' ';
356 terminal.initialize();
359 tokenize: function(str) {
364 for (let i = 0; i < str.length; i++) {
370 } else if (c == '\\') {
372 } else if (c == '"') {
377 } else if (c == '"') {
379 } else if (c === ' ') {
380 if (token.length > 0) {
388 if (token.length > 0) {
393 parse_yx: function(position_string) {
394 let coordinate_strings = position_string.split(',')
395 let position = [0, 0];
396 position[0] = parseInt(coordinate_strings[0].slice(2));
397 position[1] = parseInt(coordinate_strings[1].slice(2));
409 init: function(url) {
411 this.websocket = new WebSocket(this.url);
412 this.websocket.onopen = function(event) {
413 server.connected = true;
414 game.thing_types = {};
416 server.send(['TASKS']);
417 server.send(['TERRAINS']);
418 server.send(['THING_TYPES']);
419 tui.log_msg("@ server connected! :)");
420 tui.switch_mode('login');
422 this.websocket.onclose = function(event) {
423 server.connected = false;
424 tui.switch_mode('waiting_for_server');
425 tui.log_msg("@ server disconnected :(");
427 this.websocket.onmessage = this.handle_event;
429 reconnect_to: function(url) {
430 this.websocket.close();
433 send: function(tokens) {
434 this.websocket.send(unparser.untokenize(tokens));
436 handle_event: function(event) {
437 let tokens = parser.tokenize(event.data);
438 if (tokens[0] === 'TURN') {
439 game.turn_complete = false;
440 explorer.empty_info_db();
443 game.turn = parseInt(tokens[1]);
444 } else if (tokens[0] === 'THING') {
445 let t = game.get_thing(tokens[4], true);
446 t.position = parser.parse_yx(tokens[1]);
448 t.protection = tokens[3];
449 } else if (tokens[0] === 'THING_NAME') {
450 let t = game.get_thing(tokens[1], false);
454 } else if (tokens[0] === 'THING_CHAR') {
455 let t = game.get_thing(tokens[1], false);
457 t.thing_char = tokens[2];
459 } else if (tokens[0] === 'TASKS') {
460 game.tasks = tokens[1].split(',');
461 tui.mode_write.legal = game.tasks.includes('WRITE');
462 tui.mode_command_thing.legal = game.tasks.includes('WRITE');
463 } else if (tokens[0] === 'THING_TYPE') {
464 game.thing_types[tokens[1]] = tokens[2]
465 } else if (tokens[0] === 'TERRAIN') {
466 game.terrains[tokens[1]] = tokens[2]
467 } else if (tokens[0] === 'MAP') {
468 game.map_geometry = tokens[1];
470 game.map_size = parser.parse_yx(tokens[2]);
472 } else if (tokens[0] === 'FOV') {
474 } else if (tokens[0] === 'MAP_CONTROL') {
475 game.map_control = tokens[1]
476 } else if (tokens[0] === 'GAME_STATE_COMPLETE') {
477 game.turn_complete = true;
478 if (tui.mode.name == 'post_login_wait') {
479 tui.switch_mode('play');
480 } else if (tui.mode.name == 'study') {
481 explorer.query_info();
484 } else if (tokens[0] === 'CHAT') {
485 tui.log_msg('# ' + tokens[1], 1);
486 } else if (tokens[0] === 'REPLY') {
487 tui.log_msg('#MUSICPLAYER: ' + tokens[1], 1);
488 } else if (tokens[0] === 'PLAYER_ID') {
489 game.player_id = parseInt(tokens[1]);
490 } else if (tokens[0] === 'LOGIN_OK') {
491 this.send(['GET_GAMESTATE']);
492 tui.switch_mode('post_login_wait');
493 } else if (tokens[0] === 'DEFAULT_COLORS') {
494 terminal.set_default_colors();
495 } else if (tokens[0] === 'RANDOM_COLORS') {
496 terminal.set_random_colors();
497 } else if (tokens[0] === 'ADMIN_OK') {
499 tui.log_msg('@ you now have admin rights');
500 tui.switch_mode('admin');
501 } else if (tokens[0] === 'PORTAL') {
502 let position = parser.parse_yx(tokens[1]);
503 game.portals[position] = tokens[2];
504 } else if (tokens[0] === 'ANNOTATION_HINT') {
505 let position = parser.parse_yx(tokens[1]);
506 explorer.info_hints = explorer.info_hints.concat([position]);
507 } else if (tokens[0] === 'ANNOTATION') {
508 let position = parser.parse_yx(tokens[1]);
509 explorer.update_info_db(position, tokens[2]);
511 } else if (tokens[0] === 'UNHANDLED_INPUT') {
512 tui.log_msg('? unknown command');
513 } else if (tokens[0] === 'PLAY_ERROR') {
514 tui.log_msg('? ' + tokens[1]);
515 terminal.blink_screen();
516 } else if (tokens[0] === 'ARGUMENT_ERROR') {
517 tui.log_msg('? syntax error: ' + tokens[1]);
518 } else if (tokens[0] === 'GAME_ERROR') {
519 tui.log_msg('? game error: ' + tokens[1]);
520 } else if (tokens[0] === 'PONG') {
523 tui.log_msg('? unhandled input: ' + event.data);
529 quote: function(str) {
531 for (let i = 0; i < str.length; i++) {
533 if (['"', '\\'].includes(c)) {
539 return quoted.join('');
541 to_yx: function(yx_coordinate) {
542 return "Y:" + yx_coordinate[0] + ",X:" + yx_coordinate[1];
544 untokenize: function(tokens) {
545 let quoted_tokens = [];
546 for (let token of tokens) {
547 quoted_tokens.push(this.quote(token));
549 return quoted_tokens.join(" ");
554 constructor(name, has_input_prompt=false, shows_info=false,
555 is_intro=false, is_single_char_entry=false) {
557 this.short_desc = mode_helps[name].short;
558 this.available_modes = [];
559 this.available_actions = [];
560 this.has_input_prompt = has_input_prompt;
561 this.shows_info= shows_info;
562 this.is_intro = is_intro;
563 this.help_intro = mode_helps[name].long;
564 this.is_single_char_entry = is_single_char_entry;
567 *iter_available_modes() {
568 for (let mode_name of this.available_modes) {
569 let mode = tui['mode_' + mode_name];
573 let key = tui.keys['switch_to_' + mode.name];
577 list_available_modes() {
579 if (this.available_modes.length > 0) {
580 msg += 'Other modes available from here:\n';
581 for (let [mode, key] of this.iter_available_modes()) {
582 msg += '[' + key + '] – ' + mode.short_desc + '\n';
587 mode_switch_on_key(key_event) {
588 for (let [mode, key] of this.iter_available_modes()) {
589 if (key_event.key == key) {
590 event.preventDefault();
591 tui.switch_mode(mode.name);
603 window_width: terminal.cols / 2,
611 mode_waiting_for_server: new Mode('waiting_for_server',
613 mode_login: new Mode('login', true, false, true),
614 mode_post_login_wait: new Mode('post_login_wait'),
615 mode_chat: new Mode('chat', true),
616 mode_annotate: new Mode('annotate', true, true),
617 mode_play: new Mode('play'),
618 mode_study: new Mode('study', false, true),
619 mode_write: new Mode('write', false, false, false, true),
620 mode_edit: new Mode('edit'),
621 mode_control_pw_type: new Mode('control_pw_type', true),
622 mode_admin_thing_protect: new Mode('admin_thing_protect', true),
623 mode_portal: new Mode('portal', true, true),
624 mode_password: new Mode('password', true),
625 mode_name_thing: new Mode('name_thing', true, true),
626 mode_command_thing: new Mode('command_thing', true),
627 mode_admin_enter: new Mode('admin_enter', true),
628 mode_admin: new Mode('admin'),
629 mode_control_pw_pw: new Mode('control_pw_pw', true),
630 mode_control_tile_type: new Mode('control_tile_type', true),
631 mode_control_tile_draw: new Mode('control_tile_draw'),
633 'flatten': 'FLATTEN_SURROUNDINGS',
634 'take_thing': 'PICK_UP',
635 'drop_thing': 'DROP',
638 'command': 'COMMAND',
639 'consume': 'INTOXICATE',
644 this.mode_chat.available_modes = ["play", "study", "edit", "admin_enter"]
645 this.mode_play.available_modes = ["chat", "study", "edit", "admin_enter",
647 this.mode_play.available_actions = ["move", "take_thing", "drop_thing",
648 "teleport", "door", "consume"];
649 this.mode_study.available_modes = ["chat", "play", "admin_enter", "edit"]
650 this.mode_study.available_actions = ["toggle_map_mode", "move_explorer"];
651 this.mode_admin.available_modes = ["admin_thing_protect", "control_pw_type",
652 "control_tile_type", "chat",
653 "study", "play", "edit"]
654 this.mode_admin.available_actions = ["move"];
655 this.mode_control_tile_draw.available_modes = ["admin_enter"]
656 this.mode_control_tile_draw.available_actions = ["toggle_tile_draw"];
657 this.mode_edit.available_modes = ["write", "annotate", "portal", "name_thing",
658 "password", "chat", "study", "play",
660 this.mode_edit.available_actions = ["move", "flatten", "toggle_map_mode"]
661 this.mode = this.mode_waiting_for_server;
662 this.inputEl = document.getElementById("input");
663 this.inputEl.focus();
664 this.recalc_input_lines();
665 this.height_header = this.height_turn_line + this.height_mode_line;
666 this.log_msg("@ waiting for server connection ...");
669 init_keys: function() {
670 document.getElementById("move_table").hidden = true;
672 for (let key_selector of key_selectors) {
673 this.keys[key_selector.id.slice(4)] = key_selector.value;
675 this.movement_keys = {};
676 let geometry_prefix = 'undefinedMapGeometry_';
677 if (game.map_geometry) {
678 geometry_prefix = game.map_geometry.toLowerCase() + '_';
680 for (const key_name of Object.keys(key_descriptions)) {
681 if (key_name.startsWith(geometry_prefix)) {
682 let direction = key_name.split('_')[2].toUpperCase();
683 let key = this.keys[key_name];
684 this.movement_keys[key] = direction;
687 for (const move_button of document.querySelectorAll('[id*="_move_"]')) {
688 if (move_button.id.startsWith('key_')) {
691 move_button.hidden = true;
693 for (const move_button of document.querySelectorAll('[id^="' + geometry_prefix + 'move_"]')) {
694 document.getElementById("move_table").hidden = false;
695 move_button.hidden = false;
697 for (let el of document.getElementsByTagName("button")) {
698 let action_desc = key_descriptions[el.id];
699 let action_key = '[' + this.keys[el.id] + ']';
700 el.innerHTML = escapeHTML(action_desc) + '<br /><span class="keyboard_controlled">' + escapeHTML(action_key) + '</span>';
703 task_action_on: function(action) {
704 return game.tasks.includes(this.action_tasks[action]);
706 switch_mode: function(mode_name) {
707 if (this.mode.name == 'control_tile_draw') {
708 tui.log_msg('@ finished tile protection drawing.')
710 this.tile_draw = false;
711 if (mode_name == 'admin_enter' && this.is_admin) {
713 } else if (['name_thing', 'admin_thing_protect'].includes(mode_name)) {
714 let player_position = game.things[game.player_id].position;
716 for (let t_id in game.things) {
717 if (t_id == game.player_id) {
720 let t = game.things[t_id];
721 if (player_position[0] == t.position[0] && player_position[1] == t.position[1]) {
727 terminal.blink_screen();
728 this.log_msg('? not standing over thing');
731 this.selected_thing_id = thing_id;
734 this.mode = this['mode_' + mode_name];
735 if (["control_tile_draw", "control_tile_type", "control_pw_type"].includes(this.mode.name)) {
736 this.map_mode = 'protections';
737 } else if (this.mode.name != "edit") {
738 this.map_mode = 'terrain + things';
740 if (this.mode.has_input_prompt || this.mode.is_single_char_entry) {
741 this.inputEl.focus();
743 if (game.player_id in game.things && (this.mode.shows_info || this.mode.name == 'control_tile_draw')) {
744 explorer.position = game.things[game.player_id].position;
745 if (this.mode.shows_info) {
746 explorer.query_info();
749 this.inputEl.value = "";
750 this.restore_input_values();
751 for (let el of document.getElementsByTagName("button")) {
754 document.getElementById("help").disabled = false;
755 for (const action of this.mode.available_actions) {
756 if (["move", "move_explorer"].includes(action)) {
757 for (const move_key of document.querySelectorAll('[id*="_move_"]')) {
758 move_key.disabled = false;
760 } else if (Object.keys(this.action_tasks).includes(action)) {
761 if (this.task_action_on(action)) {
762 document.getElementById(action).disabled = false;
765 document.getElementById(action).disabled = false;
768 for (const mode_name of this.mode.available_modes) {
769 document.getElementById('switch_to_' + mode_name).disabled = false;
771 if (this.mode.name == 'login') {
772 if (this.login_name) {
773 server.send(['LOGIN', this.login_name]);
775 this.log_msg("? need login name");
777 } else if (this.mode.is_single_char_entry) {
778 this.show_help = true;
779 } else if (this.mode.name == 'command_thing') {
780 server.send(['TASK:COMMAND', 'HELP']);
781 } else if (this.mode.name == 'admin_enter') {
782 this.log_msg('@ enter admin password:')
783 } else if (this.mode.name == 'control_pw_type') {
784 this.log_msg('@ enter protection character for which you want to change the password:')
785 } else if (this.mode.name == 'control_tile_type') {
786 this.log_msg('@ enter protection character which you want to draw:')
787 } else if (this.mode.name == 'admin_thing_protect') {
788 this.log_msg('@ enter thing protection character:')
789 } else if (this.mode.name == 'control_pw_pw') {
790 this.log_msg('@ enter protection password for "' + this.tile_control_char + '":');
791 } else if (this.mode.name == 'control_tile_draw') {
792 this.log_msg('@ can draw protection character "' + this.tile_control_char + '", turn drawing on/off with [' + this.keys.toggle_tile_draw + '], finish with [' + this.keys.switch_to_admin_enter + '].')
796 offset_links: function(offset, links) {
797 for (let y in links) {
798 let real_y = offset[0] + parseInt(y);
799 if (!this.links[real_y]) {
800 this.links[real_y] = [];
802 for (let link of links[y]) {
803 const offset_link = [link[0] + offset[1], link[1] + offset[1], link[2]];
804 this.links[real_y].push(offset_link);
808 restore_input_values: function() {
809 if (this.mode.name == 'annotate' && explorer.position in explorer.info_db) {
810 let info = explorer.info_db[explorer.position];
811 if (info != "(none)") {
812 this.inputEl.value = info;
814 } else if (this.mode.name == 'portal' && explorer.position in game.portals) {
815 let portal = game.portals[explorer.position]
816 this.inputEl.value = portal;
817 } else if (this.mode.name == 'password') {
818 this.inputEl.value = this.password;
819 } else if (this.mode.name == 'name_thing') {
820 let t = game.get_thing(this.selected_thing_id);
822 this.inputEl.value = t.name_;
824 } else if (this.mode.name == 'admin_thing_protect') {
825 let t = game.get_thing(this.selected_thing_id);
826 if (t && t.protection) {
827 this.inputEl.value = t.protection;
831 recalc_input_lines: function() {
832 if (this.mode.has_input_prompt) {
834 [this.input_lines, _] = this.msg_into_lines_of_width(this.input_prompt + this.inputEl.value, this.window_width);
836 this.input_lines = [];
838 this.height_input = this.input_lines.length;
840 msg_into_lines_of_width: function(msg, width) {
841 function push_inner_link(y, end_x) {
842 if (!inner_links[y]) {
845 inner_links[y].push([url_start_x, end_x, url]);
847 const matches = msg.matchAll(/https?:\/\/[^\s]+/g)
850 for (const match of matches) {
851 const url = match[0];
852 const url_start = match.index;
853 const url_end = match.index + match[0].length;
854 link_data[url_start] = url;
855 url_ends.push(url_end);
859 let inner_links = {};
863 for (let i = 0, x = 0, y = 0; i < msg.length; i++, x++) {
864 if (x >= width || msg[i] == "\n") {
866 push_inner_link(y, chunk.length);
868 if (url_ends[0] == i) {
876 if (msg[i] == "\n") {
881 if (msg[i] != "\n") {
884 if (i in link_data) {
888 } else if (url_ends[0] == i) {
890 push_inner_link(y, x);
896 push_inner_link(lines.length - 1, chunk.length);
898 return [lines, inner_links];
900 log_msg: function(msg) {
902 while (this.log.length > 100) {
907 draw_map: function() {
908 if (game.turn_complete) {
909 let map_lines_split = [];
911 for (let i = 0, j = 0; i < game.map.length; i++, j++) {
912 if (j == game.map_size[1]) {
913 map_lines_split.push(line);
917 if (this.map_mode == 'protections') {
918 line.push(game.map_control[i] + ' ');
920 line.push(game.map[i] + ' ');
923 map_lines_split.push(line);
924 if (this.map_mode == 'terrain + annotations') {
925 for (const coordinate of explorer.info_hints) {
926 map_lines_split[coordinate[0]][coordinate[1]] = 'A ';
928 } else if (this.map_mode == 'terrain + things') {
929 for (const p in game.portals) {
930 let coordinate = p.split(',')
931 let original = map_lines_split[coordinate[0]][coordinate[1]];
932 map_lines_split[coordinate[0]][coordinate[1]] = original[0] + 'P';
934 let used_positions = [];
935 for (const thing_id in game.things) {
936 let t = game.things[thing_id];
937 let symbol = game.thing_types[t.type_];
940 meta_char = t.thing_char;
942 if (used_positions.includes(t.position.toString())) {
945 map_lines_split[t.position[0]][t.position[1]] = symbol + meta_char;
946 used_positions.push(t.position.toString());
949 let player = game.things[game.player_id];
950 if (tui.mode.shows_info || tui.mode.name == 'control_tile_draw') {
951 map_lines_split[explorer.position[0]][explorer.position[1]] = '??';
952 } else if (tui.map_mode != 'terrain + things') {
953 map_lines_split[player.position[0]][player.position[1]] = '??';
956 if (game.map_geometry == 'Square') {
957 for (let line_split of map_lines_split) {
958 this.map_lines.push(line_split.join(''));
960 } else if (game.map_geometry == 'Hex') {
962 for (let line_split of map_lines_split) {
963 this.map_lines.push(' '.repeat(indent) + line_split.join(''));
971 let window_center = [terminal.rows / 2, this.window_width / 2];
972 let center_position = [player.position[0], player.position[1]];
973 if (tui.mode.shows_info || tui.mode.name == 'control_tile_draw') {
974 center_position = [explorer.position[0], explorer.position[1]];
976 center_position[1] = center_position[1] * 2;
977 this.offset = [center_position[0] - window_center[0],
978 center_position[1] - window_center[1]]
979 if (game.map_geometry == 'Hex' && this.offset[0] % 2) {
983 let term_y = Math.max(0, -this.offset[0]);
984 let term_x = Math.max(0, -this.offset[1]);
985 let map_y = Math.max(0, this.offset[0]);
986 let map_x = Math.max(0, this.offset[1]);
987 for (; term_y < terminal.rows && map_y < game.map_size[0]; term_y++, map_y++) {
988 let to_draw = this.map_lines[map_y].slice(map_x, this.window_width + this.offset[1]);
989 terminal.write(term_y, term_x, to_draw);
992 draw_mode_line: function() {
993 let help = 'hit [' + this.keys.help + '] for help';
994 if (this.mode.has_input_prompt) {
995 help = 'enter /help for help';
997 terminal.write(0, this.window_width, 'MODE: ' + this.mode.short_desc + ' – ' + help);
999 draw_turn_line: function(n) {
1000 if (game.turn_complete) {
1001 terminal.write(1, this.window_width, 'TURN: ' + game.turn);
1004 draw_history: function() {
1005 let log_display_lines = [];
1007 let y_offset_in_log = 0;
1008 for (let line of this.log) {
1009 let [new_lines, link_data] = this.msg_into_lines_of_width(line,
1011 log_display_lines = log_display_lines.concat(new_lines);
1012 for (const y in link_data) {
1013 const rel_y = y_offset_in_log + parseInt(y);
1014 log_links[rel_y] = [];
1015 for (let link of link_data[y]) {
1016 log_links[rel_y].push(link);
1019 y_offset_in_log += new_lines.length;
1021 let i = log_display_lines.length - 1;
1022 for (let y = terminal.rows - 1 - this.height_input;
1023 y >= this.height_header && i >= 0;
1025 terminal.write(y, this.window_width, log_display_lines[i]);
1027 for (const key of Object.keys(log_links)) {
1028 if (parseInt(key) <= i) {
1029 delete log_links[key];
1032 let offset = [terminal.rows - this.height_input - log_display_lines.length,
1034 this.offset_links(offset, log_links);
1036 draw_info: function() {
1037 let [lines, link_data] = this.msg_into_lines_of_width(explorer.get_info(),
1039 let offset = [this.height_header, this.window_width];
1040 for (let y = offset[0], i = 0; y < terminal.rows && i < lines.length; y++, i++) {
1041 terminal.write(y, offset[1], lines[i]);
1043 this.offset_links(offset, link_data);
1045 draw_input: function() {
1046 if (this.mode.has_input_prompt) {
1047 for (let y = terminal.rows - this.height_input, i = 0; i < this.input_lines.length; y++, i++) {
1048 terminal.write(y, this.window_width, this.input_lines[i]);
1052 draw_help: function() {
1053 let movement_keys_desc = '';
1054 if (!this.mode.is_intro) {
1055 movement_keys_desc = Object.keys(this.movement_keys).join(',');
1057 let content = this.mode.short_desc + " help\n\n" + this.mode.help_intro + "\n\n";
1058 if (this.mode.name == 'chat') {
1059 content += '/nick NAME – re-name yourself to NAME\n';
1060 content += '/' + this.keys.switch_to_play + ' or /play – switch to play mode\n';
1061 content += '/' + this.keys.switch_to_study + ' or /study – switch to study mode\n';
1062 content += '/' + this.keys.switch_to_edit + ' or /edit – switch to world edit mode\n';
1063 content += '/' + this.keys.switch_to_admin_enter + ' or /admin – switch to admin mode\n';
1064 } else if (this.mode.available_actions.length > 0) {
1065 content += "Available actions:\n";
1066 for (let action of this.mode.available_actions) {
1067 if (Object.keys(this.action_tasks).includes(action)) {
1068 if (!this.task_action_on(action)) {
1072 if (action == 'move_explorer') {
1075 if (action == 'move') {
1076 content += "[" + movement_keys_desc + "] – move\n"
1078 content += "[" + this.keys[action] + "] – " + key_descriptions[action] + "\n";
1083 content += this.mode.list_available_modes();
1085 if (!this.mode.has_input_prompt) {
1086 start_x = this.window_width
1088 terminal.drawBox(0, start_x, terminal.rows, this.window_width);
1089 let [lines, _] = this.msg_into_lines_of_width(content, this.window_width);
1090 for (let y = 0, i = 0; y < terminal.rows && i < lines.length; y++, i++) {
1091 terminal.write(y, start_x, lines[i]);
1094 toggle_tile_draw: function() {
1095 if (tui.tile_draw) {
1096 tui.tile_draw = false;
1098 tui.tile_draw = true;
1101 toggle_map_mode: function() {
1102 if (tui.map_mode == 'terrain only') {
1103 tui.map_mode = 'terrain + annotations';
1104 } else if (tui.map_mode == 'terrain + annotations') {
1105 tui.map_mode = 'terrain + things';
1106 } else if (tui.map_mode == 'terrain + things') {
1107 tui.map_mode = 'protections';
1108 } else if (tui.map_mode == 'protections') {
1109 tui.map_mode = 'terrain only';
1112 full_refresh: function() {
1114 terminal.drawBox(0, 0, terminal.rows, terminal.cols);
1115 this.recalc_input_lines();
1116 if (this.mode.is_intro) {
1117 this.draw_history();
1121 this.draw_turn_line();
1122 this.draw_mode_line();
1123 if (this.mode.shows_info) {
1126 this.draw_history();
1130 if (this.show_help) {
1142 this.map_control = "";
1143 this.map_size = [0,0];
1144 this.player_id = -1;
1148 get_thing: function(id_, create_if_not_found=false) {
1149 if (id_ in game.things) {
1150 return game.things[id_];
1151 } else if (create_if_not_found) {
1152 let t = new Thing([0,0]);
1153 game.things[id_] = t;
1157 move: function(start_position, direction) {
1158 let target = [start_position[0], start_position[1]];
1159 if (direction == 'LEFT') {
1161 } else if (direction == 'RIGHT') {
1163 } else if (game.map_geometry == 'Square') {
1164 if (direction == 'UP') {
1166 } else if (direction == 'DOWN') {
1169 } else if (game.map_geometry == 'Hex') {
1170 let start_indented = start_position[0] % 2;
1171 if (direction == 'UPLEFT') {
1173 if (!start_indented) {
1176 } else if (direction == 'UPRIGHT') {
1178 if (start_indented) {
1181 } else if (direction == 'DOWNLEFT') {
1183 if (!start_indented) {
1186 } else if (direction == 'DOWNRIGHT') {
1188 if (start_indented) {
1193 if (target[0] < 0 || target[1] < 0 ||
1194 target[0] >= this.map_size[0] || target[1] >= this.map_size[1]) {
1199 teleport: function() {
1200 let player = this.get_thing(game.player_id);
1201 if (player.position in this.portals) {
1202 server.reconnect_to(this.portals[player.position]);
1204 terminal.blink_screen();
1205 tui.log_msg('? not standing on portal')
1213 server.init(websocket_location);
1219 move: function(direction) {
1220 let target = game.move(this.position, direction);
1222 this.position = target
1223 if (tui.mode.shows_info) {
1225 } else if (tui.tile_draw) {
1226 this.send_tile_control_command();
1229 terminal.blink_screen();
1232 update_info_db: function(yx, str) {
1233 this.info_db[yx] = str;
1234 if (tui.mode.name == 'study') {
1238 empty_info_db: function() {
1240 this.info_hints = [];
1241 if (tui.mode.name == 'study') {
1245 query_info: function() {
1246 server.send(["GET_ANNOTATION", unparser.to_yx(explorer.position)]);
1248 get_info: function() {
1249 let info = "MAP VIEW: " + tui.map_mode + "\n";
1250 let position_i = this.position[0] * game.map_size[1] + this.position[1];
1251 if (game.fov[position_i] != '.') {
1252 return info + 'outside field of view';
1254 let terrain_char = game.map[position_i]
1255 let terrain_desc = '?'
1256 if (game.terrains[terrain_char]) {
1257 terrain_desc = game.terrains[terrain_char];
1259 info += 'TERRAIN: "' + terrain_char + '" / ' + terrain_desc + "\n";
1260 let protection = game.map_control[position_i];
1261 if (protection == '.') {
1262 protection = 'unprotected';
1264 info += 'PROTECTION: ' + protection + '\n';
1265 for (let t_id in game.things) {
1266 let t = game.things[t_id];
1267 if (t.position[0] == this.position[0] && t.position[1] == this.position[1]) {
1268 let symbol = game.thing_types[t.type_];
1269 let protection = t.protection;
1270 if (protection == '.') {
1271 protection = 'none';
1273 info += "THING: " + t.type_ + " / " + symbol;
1275 info += t.thing_char;
1278 info += " (" + t.name_ + ")";
1280 info += " / protection: " + protection + "\n";
1283 if (this.position in game.portals) {
1284 info += "PORTAL: " + game.portals[this.position] + "\n";
1286 if (this.position in this.info_db) {
1287 info += "ANNOTATIONS: " + this.info_db[this.position];
1289 info += 'waiting …';
1293 annotate: function(msg) {
1294 if (msg.length == 0) {
1295 msg = " "; // triggers annotation deletion
1297 server.send(["ANNOTATE", unparser.to_yx(explorer.position), msg, tui.password]);
1299 set_portal: function(msg) {
1300 if (msg.length == 0) {
1301 msg = " "; // triggers portal deletion
1303 server.send(["PORTAL", unparser.to_yx(explorer.position), msg, tui.password]);
1305 send_tile_control_command: function() {
1306 server.send(["SET_TILE_CONTROL", unparser.to_yx(this.position), tui.tile_control_char]);
1310 tui.inputEl.addEventListener('input', (event) => {
1311 if (tui.mode.has_input_prompt) {
1312 let max_length = tui.window_width * terminal.rows - tui.input_prompt.length;
1313 if (tui.inputEl.value.length > max_length) {
1314 tui.inputEl.value = tui.inputEl.value.slice(0, max_length);
1316 } else if (tui.mode.name == 'write' && tui.inputEl.value.length > 0) {
1317 server.send(["TASK:WRITE", tui.inputEl.value[0], tui.password]);
1318 tui.switch_mode('edit');
1322 document.onclick = function() {
1323 tui.show_help = false;
1325 tui.inputEl.addEventListener('keydown', (event) => {
1326 tui.show_help = false;
1327 if (event.key == 'Enter') {
1328 event.preventDefault();
1330 if (tui.mode.has_input_prompt && event.key == 'Enter' && tui.inputEl.value == '/help') {
1331 tui.show_help = true;
1332 tui.inputEl.value = "";
1333 tui.restore_input_values();
1334 } else if (!tui.mode.has_input_prompt && event.key == tui.keys.help
1335 && !tui.mode.is_single_char_entry) {
1336 tui.show_help = true;
1337 } else if (tui.mode.name == 'login' && event.key == 'Enter') {
1338 tui.login_name = tui.inputEl.value;
1339 server.send(['LOGIN', tui.inputEl.value]);
1340 tui.inputEl.value = "";
1341 } else if (tui.mode.name == 'command_thing' && event.key == 'Enter') {
1342 if (tui.inputEl.value.length == 0) {
1343 tui.log_msg('@ aborted');
1344 tui.switch_mode('play');
1345 } else if (tui.task_action_on('command')) {
1346 server.send(['TASK:COMMAND', tui.inputEl.value]);
1347 tui.inputEl.value = "";
1349 } else if (tui.mode.name == 'control_pw_pw' && event.key == 'Enter') {
1350 if (tui.inputEl.value.length == 0) {
1351 tui.log_msg('@ aborted');
1353 server.send(['SET_MAP_CONTROL_PASSWORD',
1354 tui.tile_control_char, tui.inputEl.value]);
1355 tui.log_msg('@ sent new password for protection character "' + tui.tile_control_char + '".');
1357 tui.switch_mode('admin');
1358 } else if (tui.mode.name == 'portal' && event.key == 'Enter') {
1359 explorer.set_portal(tui.inputEl.value);
1360 tui.switch_mode('edit');
1361 } else if (tui.mode.name == 'name_thing' && event.key == 'Enter') {
1362 if (tui.inputEl.value.length == 0) {
1363 tui.inputEl.value = " ";
1365 server.send(["THING_NAME", tui.selected_thing_id, tui.inputEl.value,
1367 tui.switch_mode('edit');
1368 } else if (tui.mode.name == 'annotate' && event.key == 'Enter') {
1369 explorer.annotate(tui.inputEl.value);
1370 tui.switch_mode('edit');
1371 } else if (tui.mode.name == 'password' && event.key == 'Enter') {
1372 if (tui.inputEl.value.length == 0) {
1373 tui.inputEl.value = " ";
1375 tui.password = tui.inputEl.value
1376 tui.switch_mode('edit');
1377 } else if (tui.mode.name == 'admin_enter' && event.key == 'Enter') {
1378 server.send(['BECOME_ADMIN', tui.inputEl.value]);
1379 tui.switch_mode('play');
1380 } else if (tui.mode.name == 'control_pw_type' && event.key == 'Enter') {
1381 if (tui.inputEl.value.length != 1) {
1382 tui.log_msg('@ entered non-single-char, therefore aborted');
1383 tui.switch_mode('admin');
1385 tui.tile_control_char = tui.inputEl.value[0];
1386 tui.switch_mode('control_pw_pw');
1388 } else if (tui.mode.name == 'control_tile_type' && event.key == 'Enter') {
1389 if (tui.inputEl.value.length != 1) {
1390 tui.log_msg('@ entered non-single-char, therefore aborted');
1391 tui.switch_mode('admin');
1393 tui.tile_control_char = tui.inputEl.value[0];
1394 tui.switch_mode('control_tile_draw');
1396 } else if (tui.mode.name == 'admin_thing_protect' && event.key == 'Enter') {
1397 if (tui.inputEl.value.length != 1) {
1398 tui.log_msg('@ entered non-single-char, therefore aborted');
1400 server.send(['THING_PROTECTION', tui.selected_thing_id, tui.inputEl.value])
1401 tui.log_msg('@ sent new protection character for thing');
1403 tui.switch_mode('admin');
1404 } else if (tui.mode.name == 'chat' && event.key == 'Enter') {
1405 let tokens = parser.tokenize(tui.inputEl.value);
1406 if (tokens.length > 0 && tokens[0].length > 0) {
1407 if (tui.inputEl.value[0][0] == '/') {
1408 if (tokens[0].slice(1) == 'play' || tokens[0][1] == tui.keys.switch_to_play) {
1409 tui.switch_mode('play');
1410 } else if (tokens[0].slice(1) == 'study' || tokens[0][1] == tui.keys.switch_to_study) {
1411 tui.switch_mode('study');
1412 } else if (tokens[0].slice(1) == 'edit' || tokens[0][1] == tui.keys.switch_to_edit) {
1413 tui.switch_mode('edit');
1414 } else if (tokens[0].slice(1) == 'admin' || tokens[0][1] == tui.keys.switch_to_admin_enter) {
1415 tui.switch_mode('admin_enter');
1416 } else if (tokens[0].slice(1) == 'nick') {
1417 if (tokens.length > 1) {
1418 server.send(['NICK', tokens[1]]);
1420 tui.log_msg('? need new name');
1423 tui.log_msg('? unknown command');
1426 server.send(['ALL', tui.inputEl.value]);
1428 } else if (tui.inputEl.valuelength > 0) {
1429 server.send(['ALL', tui.inputEl.value]);
1431 tui.inputEl.value = "";
1432 } else if (tui.mode.name == 'play') {
1433 if (tui.mode.mode_switch_on_key(event)) {
1435 } else if (event.key === tui.keys.take_thing && tui.task_action_on('take_thing')) {
1436 server.send(["TASK:PICK_UP"]);
1437 } else if (event.key === tui.keys.drop_thing && tui.task_action_on('drop_thing')) {
1438 server.send(["TASK:DROP"]);
1439 } else if (event.key === tui.keys.consume && tui.task_action_on('consume')) {
1440 server.send(["TASK:INTOXICATE"]);
1441 } else if (event.key === tui.keys.door && tui.task_action_on('door')) {
1442 server.send(["TASK:DOOR"]);
1443 } else if (event.key in tui.movement_keys && tui.task_action_on('move')) {
1444 server.send(['TASK:MOVE', tui.movement_keys[event.key]]);
1445 } else if (event.key === tui.keys.teleport) {
1448 } else if (tui.mode.name == 'study') {
1449 if (tui.mode.mode_switch_on_key(event)) {
1451 } else if (event.key in tui.movement_keys) {
1452 explorer.move(tui.movement_keys[event.key]);
1453 } else if (event.key == tui.keys.toggle_map_mode) {
1454 tui.toggle_map_mode();
1456 } else if (tui.mode.name == 'control_tile_draw') {
1457 if (tui.mode.mode_switch_on_key(event)) {
1459 } else if (event.key in tui.movement_keys) {
1460 explorer.move(tui.movement_keys[event.key]);
1461 } else if (event.key === tui.keys.toggle_tile_draw) {
1462 tui.toggle_tile_draw();
1464 } else if (tui.mode.name == 'admin') {
1465 if (tui.mode.mode_switch_on_key(event)) {
1467 } else if (event.key in tui.movement_keys && tui.task_action_on('move')) {
1468 server.send(['TASK:MOVE', tui.movement_keys[event.key]]);
1470 } else if (tui.mode.name == 'edit') {
1471 if (tui.mode.mode_switch_on_key(event)) {
1473 } else if (event.key in tui.movement_keys && tui.task_action_on('move')) {
1474 server.send(['TASK:MOVE', tui.movement_keys[event.key]]);
1475 } else if (event.key === tui.keys.flatten && tui.task_action_on('flatten')) {
1476 server.send(["TASK:FLATTEN_SURROUNDINGS", tui.password]);
1477 } else if (event.key == tui.keys.toggle_map_mode) {
1478 tui.toggle_map_mode();
1484 rows_selector.addEventListener('input', function() {
1485 if (rows_selector.value % 4 != 0 || rows_selector.value < 24) {
1488 window.localStorage.setItem(rows_selector.id, rows_selector.value);
1489 terminal.initialize();
1492 cols_selector.addEventListener('input', function() {
1493 if (cols_selector.value % 4 != 0 || cols_selector.value < 80) {
1496 window.localStorage.setItem(cols_selector.id, cols_selector.value);
1497 terminal.initialize();
1498 tui.window_width = terminal.cols / 2,
1501 for (let key_selector of key_selectors) {
1502 key_selector.addEventListener('input', function() {
1503 window.localStorage.setItem(key_selector.id, key_selector.value);
1507 window.setInterval(function() {
1508 if (server.connected) {
1509 server.send(['PING']);
1511 server.reconnect_to(server.url);
1512 tui.log_msg('@ attempting reconnect …')
1515 window.setInterval(function() {
1517 let span_decoration = "none";
1518 if (document.activeElement == tui.inputEl) {
1519 val = "on (click outside terminal to change)";
1521 val = "off (click into terminal to change)";
1522 span_decoration = "line-through";
1524 document.getElementById("keyboard_control").textContent = val;
1525 for (const span of document.querySelectorAll('.keyboard_controlled')) {
1526 span.style.textDecoration = span_decoration;
1529 document.getElementById("terminal").onclick = function() {
1530 tui.inputEl.focus();
1532 document.getElementById("help").onclick = function() {
1533 tui.show_help = true;
1536 for (const switchEl of document.querySelectorAll('[id^="switch_to_"]')) {
1537 const mode = switchEl.id.slice("switch_to_".length);
1538 switchEl.onclick = function() {
1539 tui.switch_mode(mode);
1543 document.getElementById("toggle_tile_draw").onclick = function() {
1544 tui.toggle_tile_draw();
1546 document.getElementById("toggle_map_mode").onclick = function() {
1547 tui.toggle_map_mode();
1550 document.getElementById("take_thing").onclick = function() {
1551 server.send(['TASK:PICK_UP']);
1553 document.getElementById("drop_thing").onclick = function() {
1554 server.send(['TASK:DROP']);
1556 document.getElementById("flatten").onclick = function() {
1557 server.send(['TASK:FLATTEN_SURROUNDINGS', tui.password]);
1559 document.getElementById("door").onclick = function() {
1560 server.send(['TASK:DOOR']);
1562 document.getElementById("consume").onclick = function() {
1563 server.send(['TASK:INTOXICATE']);
1565 document.getElementById("teleport").onclick = function() {
1568 for (const move_button of document.querySelectorAll('[id*="_move_"]')) {
1569 let direction = move_button.id.split('_')[2].toUpperCase();
1570 move_button.onclick = function() {
1571 if (tui.mode.available_actions.includes("move")
1572 || tui.mode.available_actions.includes("move_explorer")) {
1573 server.send(['TASK:MOVE', direction]);
1575 explorer.move(direction);