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:8001/";
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_annotations();
444 game.turn = parseInt(tokens[1]);
445 } else if (tokens[0] === 'THING') {
446 let t = game.get_thing(tokens[4], true);
447 t.position = parser.parse_yx(tokens[1]);
449 t.protection = tokens[3];
450 } else if (tokens[0] === 'THING_NAME') {
451 let t = game.get_thing(tokens[1], false);
455 } else if (tokens[0] === 'THING_CHAR') {
456 let t = game.get_thing(tokens[1], false);
458 t.thing_char = tokens[2];
460 } else if (tokens[0] === 'TASKS') {
461 game.tasks = tokens[1].split(',');
462 tui.mode_write.legal = game.tasks.includes('WRITE');
463 tui.mode_command_thing.legal = game.tasks.includes('WRITE');
464 } else if (tokens[0] === 'THING_TYPE') {
465 game.thing_types[tokens[1]] = tokens[2]
466 } else if (tokens[0] === 'TERRAIN') {
467 game.terrains[tokens[1]] = tokens[2]
468 } else if (tokens[0] === 'MAP') {
469 game.map_geometry = tokens[1];
471 game.map_size = parser.parse_yx(tokens[2]);
473 } else if (tokens[0] === 'FOV') {
475 } else if (tokens[0] === 'MAP_CONTROL') {
476 game.map_control = tokens[1]
477 } else if (tokens[0] === 'GAME_STATE_COMPLETE') {
478 game.turn_complete = true;
479 if (tui.mode.name == 'post_login_wait') {
480 tui.switch_mode('play');
482 explorer.info_cached = false;
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') {
505 let position = parser.parse_yx(tokens[1]);
506 explorer.update_annotations(position, tokens[2]);
508 } else if (tokens[0] === 'UNHANDLED_INPUT') {
509 tui.log_msg('? unknown command');
510 } else if (tokens[0] === 'PLAY_ERROR') {
511 tui.log_msg('? ' + tokens[1]);
512 terminal.blink_screen();
513 } else if (tokens[0] === 'ARGUMENT_ERROR') {
514 tui.log_msg('? syntax error: ' + tokens[1]);
515 } else if (tokens[0] === 'GAME_ERROR') {
516 tui.log_msg('? game error: ' + tokens[1]);
517 } else if (tokens[0] === 'PONG') {
520 tui.log_msg('? unhandled input: ' + event.data);
526 quote: function(str) {
528 for (let i = 0; i < str.length; i++) {
530 if (['"', '\\'].includes(c)) {
536 return quoted.join('');
538 to_yx: function(yx_coordinate) {
539 return "Y:" + yx_coordinate[0] + ",X:" + yx_coordinate[1];
541 untokenize: function(tokens) {
542 let quoted_tokens = [];
543 for (let token of tokens) {
544 quoted_tokens.push(this.quote(token));
546 return quoted_tokens.join(" ");
551 constructor(name, has_input_prompt=false, shows_info=false,
552 is_intro=false, is_single_char_entry=false) {
554 this.short_desc = mode_helps[name].short;
555 this.available_modes = [];
556 this.available_actions = [];
557 this.has_input_prompt = has_input_prompt;
558 this.shows_info= shows_info;
559 this.is_intro = is_intro;
560 this.help_intro = mode_helps[name].long;
561 this.is_single_char_entry = is_single_char_entry;
564 *iter_available_modes() {
565 for (let mode_name of this.available_modes) {
566 let mode = tui['mode_' + mode_name];
570 let key = tui.keys['switch_to_' + mode.name];
574 list_available_modes() {
576 if (this.available_modes.length > 0) {
577 msg += 'Other modes available from here:\n';
578 for (let [mode, key] of this.iter_available_modes()) {
579 msg += '[' + key + '] – ' + mode.short_desc + '\n';
584 mode_switch_on_key(key_event) {
585 for (let [mode, key] of this.iter_available_modes()) {
586 if (key_event.key == key) {
587 event.preventDefault();
588 tui.switch_mode(mode.name);
600 window_width: terminal.cols / 2,
608 mode_waiting_for_server: new Mode('waiting_for_server',
610 mode_login: new Mode('login', true, false, true),
611 mode_post_login_wait: new Mode('post_login_wait'),
612 mode_chat: new Mode('chat', true),
613 mode_annotate: new Mode('annotate', true, true),
614 mode_play: new Mode('play'),
615 mode_study: new Mode('study', false, true),
616 mode_write: new Mode('write', false, false, false, true),
617 mode_edit: new Mode('edit'),
618 mode_control_pw_type: new Mode('control_pw_type', true),
619 mode_admin_thing_protect: new Mode('admin_thing_protect', true),
620 mode_portal: new Mode('portal', true, true),
621 mode_password: new Mode('password', true),
622 mode_name_thing: new Mode('name_thing', true, true),
623 mode_command_thing: new Mode('command_thing', true),
624 mode_admin_enter: new Mode('admin_enter', true),
625 mode_admin: new Mode('admin'),
626 mode_control_pw_pw: new Mode('control_pw_pw', true),
627 mode_control_tile_type: new Mode('control_tile_type', true),
628 mode_control_tile_draw: new Mode('control_tile_draw'),
630 'flatten': 'FLATTEN_SURROUNDINGS',
631 'take_thing': 'PICK_UP',
632 'drop_thing': 'DROP',
635 'command': 'COMMAND',
636 'consume': 'INTOXICATE',
641 this.mode_chat.available_modes = ["play", "study", "edit", "admin_enter"]
642 this.mode_play.available_modes = ["chat", "study", "edit", "admin_enter",
644 this.mode_play.available_actions = ["move", "take_thing", "drop_thing",
645 "teleport", "door", "consume"];
646 this.mode_study.available_modes = ["chat", "play", "admin_enter", "edit"]
647 this.mode_study.available_actions = ["toggle_map_mode", "move_explorer"];
648 this.mode_admin.available_modes = ["admin_thing_protect", "control_pw_type",
649 "control_tile_type", "chat",
650 "study", "play", "edit"]
651 this.mode_admin.available_actions = ["move"];
652 this.mode_control_tile_draw.available_modes = ["admin_enter"]
653 this.mode_control_tile_draw.available_actions = ["toggle_tile_draw"];
654 this.mode_edit.available_modes = ["write", "annotate", "portal", "name_thing",
655 "password", "chat", "study", "play",
657 this.mode_edit.available_actions = ["move", "flatten", "toggle_map_mode"]
658 this.mode = this.mode_waiting_for_server;
659 this.inputEl = document.getElementById("input");
660 this.inputEl.focus();
661 this.recalc_input_lines();
662 this.height_header = this.height_turn_line + this.height_mode_line;
663 this.log_msg("@ waiting for server connection ...");
666 init_keys: function() {
667 document.getElementById("move_table").hidden = true;
669 for (let key_selector of key_selectors) {
670 this.keys[key_selector.id.slice(4)] = key_selector.value;
672 this.movement_keys = {};
673 let geometry_prefix = 'undefinedMapGeometry_';
674 if (game.map_geometry) {
675 geometry_prefix = game.map_geometry.toLowerCase() + '_';
677 for (const key_name of Object.keys(key_descriptions)) {
678 if (key_name.startsWith(geometry_prefix)) {
679 let direction = key_name.split('_')[2].toUpperCase();
680 let key = this.keys[key_name];
681 this.movement_keys[key] = direction;
684 for (const move_button of document.querySelectorAll('[id*="_move_"]')) {
685 if (move_button.id.startsWith('key_')) {
688 move_button.hidden = true;
690 for (const move_button of document.querySelectorAll('[id^="' + geometry_prefix + 'move_"]')) {
691 document.getElementById("move_table").hidden = false;
692 move_button.hidden = false;
694 for (let el of document.getElementsByTagName("button")) {
695 let action_desc = key_descriptions[el.id];
696 let action_key = '[' + this.keys[el.id] + ']';
697 el.innerHTML = escapeHTML(action_desc) + '<br /><span class="keyboard_controlled">' + escapeHTML(action_key) + '</span>';
700 task_action_on: function(action) {
701 return game.tasks.includes(this.action_tasks[action]);
703 switch_mode: function(mode_name) {
704 if (this.mode.name == 'control_tile_draw') {
705 tui.log_msg('@ finished tile protection drawing.')
707 this.tile_draw = false;
708 if (mode_name == 'admin_enter' && this.is_admin) {
710 } else if (['name_thing', 'admin_thing_protect'].includes(mode_name)) {
711 let player_position = game.things[game.player_id].position;
713 for (let t_id in game.things) {
714 if (t_id == game.player_id) {
717 let t = game.things[t_id];
718 if (player_position[0] == t.position[0] && player_position[1] == t.position[1]) {
724 terminal.blink_screen();
725 this.log_msg('? not standing over thing');
728 this.selected_thing_id = thing_id;
731 this.mode = this['mode_' + mode_name];
732 if (["control_tile_draw", "control_tile_type", "control_pw_type"].includes(this.mode.name)) {
733 this.map_mode = 'protections';
734 } else if (this.mode.name != "edit") {
735 this.map_mode = 'terrain + things';
737 if (this.mode.has_input_prompt || this.mode.is_single_char_entry) {
738 this.inputEl.focus();
740 if (game.player_id in game.things && (this.mode.shows_info || this.mode.name == 'control_tile_draw')) {
741 explorer.position = game.things[game.player_id].position;
743 this.inputEl.value = "";
744 this.restore_input_values();
745 for (let el of document.getElementsByTagName("button")) {
748 document.getElementById("help").disabled = false;
749 for (const action of this.mode.available_actions) {
750 if (["move", "move_explorer"].includes(action)) {
751 for (const move_key of document.querySelectorAll('[id*="_move_"]')) {
752 move_key.disabled = false;
754 } else if (Object.keys(this.action_tasks).includes(action)) {
755 if (this.task_action_on(action)) {
756 document.getElementById(action).disabled = false;
759 document.getElementById(action).disabled = false;
762 for (const mode_name of this.mode.available_modes) {
763 document.getElementById('switch_to_' + mode_name).disabled = false;
765 if (this.mode.name == 'login') {
766 if (this.login_name) {
767 server.send(['LOGIN', this.login_name]);
769 this.log_msg("? need login name");
771 } else if (this.mode.is_single_char_entry) {
772 this.show_help = true;
773 } else if (this.mode.name == 'command_thing') {
774 server.send(['TASK:COMMAND', 'HELP']);
775 } else if (this.mode.name == 'admin_enter') {
776 this.log_msg('@ enter admin password:')
777 } else if (this.mode.name == 'control_pw_type') {
778 this.log_msg('@ enter protection character for which you want to change the password:')
779 } else if (this.mode.name == 'control_tile_type') {
780 this.log_msg('@ enter protection character which you want to draw:')
781 } else if (this.mode.name == 'admin_thing_protect') {
782 this.log_msg('@ enter thing protection character:')
783 } else if (this.mode.name == 'control_pw_pw') {
784 this.log_msg('@ enter protection password for "' + this.tile_control_char + '":');
785 } else if (this.mode.name == 'control_tile_draw') {
786 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 + '].')
790 offset_links: function(offset, links) {
791 for (let y in links) {
792 let real_y = offset[0] + parseInt(y);
793 if (!this.links[real_y]) {
794 this.links[real_y] = [];
796 for (let link of links[y]) {
797 const offset_link = [link[0] + offset[1], link[1] + offset[1], link[2]];
798 this.links[real_y].push(offset_link);
802 restore_input_values: function() {
803 if (this.mode.name == 'annotate' && explorer.position in explorer.annotations) {
804 let info = explorer.annotations[explorer.position];
805 if (info != "(none)") {
806 this.inputEl.value = info;
808 } else if (this.mode.name == 'portal' && explorer.position in game.portals) {
809 let portal = game.portals[explorer.position]
810 this.inputEl.value = portal;
811 } else if (this.mode.name == 'password') {
812 this.inputEl.value = this.password;
813 } else if (this.mode.name == 'name_thing') {
814 let t = game.get_thing(this.selected_thing_id);
816 this.inputEl.value = t.name_;
818 } else if (this.mode.name == 'admin_thing_protect') {
819 let t = game.get_thing(this.selected_thing_id);
820 if (t && t.protection) {
821 this.inputEl.value = t.protection;
825 recalc_input_lines: function() {
826 if (this.mode.has_input_prompt) {
828 [this.input_lines, _] = this.msg_into_lines_of_width(this.input_prompt + this.inputEl.value, this.window_width);
830 this.input_lines = [];
832 this.height_input = this.input_lines.length;
834 msg_into_lines_of_width: function(msg, width) {
835 function push_inner_link(y, end_x) {
836 if (!inner_links[y]) {
839 inner_links[y].push([url_start_x, end_x, url]);
841 const matches = msg.matchAll(/https?:\/\/[^\s]+/g)
844 for (const match of matches) {
845 const url = match[0];
846 const url_start = match.index;
847 const url_end = match.index + match[0].length;
848 link_data[url_start] = url;
849 url_ends.push(url_end);
853 let inner_links = {};
857 for (let i = 0, x = 0, y = 0; i < msg.length; i++, x++) {
858 if (x >= width || msg[i] == "\n") {
860 push_inner_link(y, chunk.length);
862 if (url_ends[0] == i) {
870 if (msg[i] == "\n") {
875 if (msg[i] != "\n") {
878 if (i in link_data) {
882 } else if (url_ends[0] == i) {
884 push_inner_link(y, x);
890 push_inner_link(lines.length - 1, chunk.length);
892 return [lines, inner_links];
894 log_msg: function(msg) {
896 while (this.log.length > 100) {
901 draw_map: function() {
902 if (game.turn_complete) {
903 let map_lines_split = [];
905 for (let i = 0, j = 0; i < game.map.length; i++, j++) {
906 if (j == game.map_size[1]) {
907 map_lines_split.push(line);
911 if (this.map_mode == 'protections') {
912 line.push(game.map_control[i] + ' ');
914 line.push(game.map[i] + ' ');
917 map_lines_split.push(line);
918 if (this.map_mode == 'terrain + annotations') {
919 for (const coordinate of explorer.info_hints) {
920 map_lines_split[coordinate[0]][coordinate[1]] = 'A ';
922 } else if (this.map_mode == 'terrain + things') {
923 for (const p in game.portals) {
924 let coordinate = p.split(',')
925 let original = map_lines_split[coordinate[0]][coordinate[1]];
926 map_lines_split[coordinate[0]][coordinate[1]] = original[0] + 'P';
928 let used_positions = [];
929 for (const thing_id in game.things) {
930 let t = game.things[thing_id];
931 let symbol = game.thing_types[t.type_];
934 meta_char = t.thing_char;
936 if (used_positions.includes(t.position.toString())) {
939 map_lines_split[t.position[0]][t.position[1]] = symbol + meta_char;
940 used_positions.push(t.position.toString());
943 let player = game.things[game.player_id];
944 if (tui.mode.shows_info || tui.mode.name == 'control_tile_draw') {
945 map_lines_split[explorer.position[0]][explorer.position[1]] = '??';
946 } else if (tui.map_mode != 'terrain + things') {
947 map_lines_split[player.position[0]][player.position[1]] = '??';
950 if (game.map_geometry == 'Square') {
951 for (let line_split of map_lines_split) {
952 this.map_lines.push(line_split.join(''));
954 } else if (game.map_geometry == 'Hex') {
956 for (let line_split of map_lines_split) {
957 this.map_lines.push(' '.repeat(indent) + line_split.join(''));
965 let window_center = [terminal.rows / 2, this.window_width / 2];
966 let center_position = [player.position[0], player.position[1]];
967 if (tui.mode.shows_info || tui.mode.name == 'control_tile_draw') {
968 center_position = [explorer.position[0], explorer.position[1]];
970 center_position[1] = center_position[1] * 2;
971 this.offset = [center_position[0] - window_center[0],
972 center_position[1] - window_center[1]]
973 if (game.map_geometry == 'Hex' && this.offset[0] % 2) {
977 let term_y = Math.max(0, -this.offset[0]);
978 let term_x = Math.max(0, -this.offset[1]);
979 let map_y = Math.max(0, this.offset[0]);
980 let map_x = Math.max(0, this.offset[1]);
981 for (; term_y < terminal.rows && map_y < game.map_size[0]; term_y++, map_y++) {
982 let to_draw = this.map_lines[map_y].slice(map_x, this.window_width + this.offset[1]);
983 terminal.write(term_y, term_x, to_draw);
986 draw_mode_line: function() {
987 let help = 'hit [' + this.keys.help + '] for help';
988 if (this.mode.has_input_prompt) {
989 help = 'enter /help for help';
991 terminal.write(0, this.window_width, 'MODE: ' + this.mode.short_desc + ' – ' + help);
993 draw_turn_line: function(n) {
994 if (game.turn_complete) {
995 terminal.write(1, this.window_width, 'TURN: ' + game.turn);
998 draw_history: function() {
999 let log_display_lines = [];
1001 let y_offset_in_log = 0;
1002 for (let line of this.log) {
1003 let [new_lines, link_data] = this.msg_into_lines_of_width(line,
1005 log_display_lines = log_display_lines.concat(new_lines);
1006 for (const y in link_data) {
1007 const rel_y = y_offset_in_log + parseInt(y);
1008 log_links[rel_y] = [];
1009 for (let link of link_data[y]) {
1010 log_links[rel_y].push(link);
1013 y_offset_in_log += new_lines.length;
1015 let i = log_display_lines.length - 1;
1016 for (let y = terminal.rows - 1 - this.height_input;
1017 y >= this.height_header && i >= 0;
1019 terminal.write(y, this.window_width, log_display_lines[i]);
1021 for (const key of Object.keys(log_links)) {
1022 if (parseInt(key) <= i) {
1023 delete log_links[key];
1026 let offset = [terminal.rows - this.height_input - log_display_lines.length,
1028 this.offset_links(offset, log_links);
1030 draw_info: function() {
1031 const info = "MAP VIEW: " + tui.map_mode + "\n" + explorer.get_info();
1032 let [lines, link_data] = this.msg_into_lines_of_width(info, this.window_width);
1033 let offset = [this.height_header, this.window_width];
1034 for (let y = offset[0], i = 0; y < terminal.rows && i < lines.length; y++, i++) {
1035 terminal.write(y, offset[1], lines[i]);
1037 this.offset_links(offset, link_data);
1039 draw_input: function() {
1040 if (this.mode.has_input_prompt) {
1041 for (let y = terminal.rows - this.height_input, i = 0; i < this.input_lines.length; y++, i++) {
1042 terminal.write(y, this.window_width, this.input_lines[i]);
1046 draw_help: function() {
1047 let movement_keys_desc = '';
1048 if (!this.mode.is_intro) {
1049 movement_keys_desc = Object.keys(this.movement_keys).join(',');
1051 let content = this.mode.short_desc + " help\n\n" + this.mode.help_intro + "\n\n";
1052 if (this.mode.name == 'chat') {
1053 content += '/nick NAME – re-name yourself to NAME\n';
1054 content += '/' + this.keys.switch_to_play + ' or /play – switch to play mode\n';
1055 content += '/' + this.keys.switch_to_study + ' or /study – switch to study mode\n';
1056 content += '/' + this.keys.switch_to_edit + ' or /edit – switch to world edit mode\n';
1057 content += '/' + this.keys.switch_to_admin_enter + ' or /admin – switch to admin mode\n';
1058 } else if (this.mode.available_actions.length > 0) {
1059 content += "Available actions:\n";
1060 for (let action of this.mode.available_actions) {
1061 if (Object.keys(this.action_tasks).includes(action)) {
1062 if (!this.task_action_on(action)) {
1066 if (action == 'move_explorer') {
1069 if (action == 'move') {
1070 content += "[" + movement_keys_desc + "] – move\n"
1072 content += "[" + this.keys[action] + "] – " + key_descriptions[action] + "\n";
1077 content += this.mode.list_available_modes();
1079 if (!this.mode.has_input_prompt) {
1080 start_x = this.window_width
1082 terminal.drawBox(0, start_x, terminal.rows, this.window_width);
1083 let [lines, _] = this.msg_into_lines_of_width(content, this.window_width);
1084 for (let y = 0, i = 0; y < terminal.rows && i < lines.length; y++, i++) {
1085 terminal.write(y, start_x, lines[i]);
1088 toggle_tile_draw: function() {
1089 if (tui.tile_draw) {
1090 tui.tile_draw = false;
1092 tui.tile_draw = true;
1095 toggle_map_mode: function() {
1096 if (tui.map_mode == 'terrain only') {
1097 tui.map_mode = 'terrain + annotations';
1098 } else if (tui.map_mode == 'terrain + annotations') {
1099 tui.map_mode = 'terrain + things';
1100 } else if (tui.map_mode == 'terrain + things') {
1101 tui.map_mode = 'protections';
1102 } else if (tui.map_mode == 'protections') {
1103 tui.map_mode = 'terrain only';
1106 full_refresh: function() {
1108 terminal.drawBox(0, 0, terminal.rows, terminal.cols);
1109 this.recalc_input_lines();
1110 if (this.mode.is_intro) {
1111 this.draw_history();
1115 this.draw_turn_line();
1116 this.draw_mode_line();
1117 if (this.mode.shows_info) {
1120 this.draw_history();
1124 if (this.show_help) {
1136 this.map_control = "";
1137 this.map_size = [0,0];
1138 this.player_id = -1;
1142 get_thing: function(id_, create_if_not_found=false) {
1143 if (id_ in game.things) {
1144 return game.things[id_];
1145 } else if (create_if_not_found) {
1146 let t = new Thing([0,0]);
1147 game.things[id_] = t;
1151 move: function(start_position, direction) {
1152 let target = [start_position[0], start_position[1]];
1153 if (direction == 'LEFT') {
1155 } else if (direction == 'RIGHT') {
1157 } else if (game.map_geometry == 'Square') {
1158 if (direction == 'UP') {
1160 } else if (direction == 'DOWN') {
1163 } else if (game.map_geometry == 'Hex') {
1164 let start_indented = start_position[0] % 2;
1165 if (direction == 'UPLEFT') {
1167 if (!start_indented) {
1170 } else if (direction == 'UPRIGHT') {
1172 if (start_indented) {
1175 } else if (direction == 'DOWNLEFT') {
1177 if (!start_indented) {
1180 } else if (direction == 'DOWNRIGHT') {
1182 if (start_indented) {
1187 if (target[0] < 0 || target[1] < 0 ||
1188 target[0] >= this.map_size[0] || target[1] >= this.map_size[1]) {
1193 teleport: function() {
1194 let player = this.get_thing(game.player_id);
1195 if (player.position in this.portals) {
1196 server.reconnect_to(this.portals[player.position]);
1198 terminal.blink_screen();
1199 tui.log_msg('? not standing on portal')
1207 server.init(websocket_location);
1213 move: function(direction) {
1214 let target = game.move(this.position, direction);
1216 this.position = target
1217 this.info_cached = false;
1218 if (tui.tile_draw) {
1219 this.send_tile_control_command();
1222 terminal.blink_screen();
1225 update_annotations: function(yx, str) {
1226 this.annotations[yx] = str;
1227 if (tui.mode.name == 'study') {
1231 empty_annotations: function() {
1232 this.annotations = {};
1233 if (tui.mode.name == 'study') {
1237 get_info: function() {
1238 if (this.info_cached) {
1239 return this.info_cached;
1241 let info_to_cache = '';
1242 let position_i = this.position[0] * game.map_size[1] + this.position[1];
1243 if (game.fov[position_i] != '.') {
1244 info_to_cache += 'outside field of view';
1246 let terrain_char = game.map[position_i]
1247 let terrain_desc = '?'
1248 if (game.terrains[terrain_char]) {
1249 terrain_desc = game.terrains[terrain_char];
1251 info_to_cache += 'TERRAIN: "' + terrain_char + '" / ' + terrain_desc + "\n";
1252 let protection = game.map_control[position_i];
1253 if (protection == '.') {
1254 protection = 'unprotected';
1256 info_to_cache += 'PROTECTION: ' + protection + '\n';
1257 for (let t_id in game.things) {
1258 let t = game.things[t_id];
1259 if (t.position[0] == this.position[0] && t.position[1] == this.position[1]) {
1260 let symbol = game.thing_types[t.type_];
1261 let protection = t.protection;
1262 if (protection == '.') {
1263 protection = 'none';
1265 info_to_cache += "THING: " + t.type_ + " / " + symbol;
1267 info_to_cache += t.thing_char;
1270 info_to_cache += " (" + t.name_ + ")";
1272 info_to_cache += " / protection: " + protection + "\n";
1275 if (this.position in game.portals) {
1276 info_to_cache += "PORTAL: " + game.portals[this.position] + "\n";
1278 if (this.position in this.annotations) {
1279 info_to_cache += "ANNOTATION: " + this.annotations[this.position];
1282 this.info_cached = info_to_cache;
1283 return this.info_cached;
1285 annotate: function(msg) {
1286 if (msg.length == 0) {
1287 msg = " "; // triggers annotation deletion
1289 server.send(["ANNOTATE", unparser.to_yx(explorer.position), msg, tui.password]);
1291 set_portal: function(msg) {
1292 if (msg.length == 0) {
1293 msg = " "; // triggers portal deletion
1295 server.send(["PORTAL", unparser.to_yx(explorer.position), msg, tui.password]);
1297 send_tile_control_command: function() {
1298 server.send(["SET_TILE_CONTROL", unparser.to_yx(this.position), tui.tile_control_char]);
1302 tui.inputEl.addEventListener('input', (event) => {
1303 if (tui.mode.has_input_prompt) {
1304 let max_length = tui.window_width * terminal.rows - tui.input_prompt.length;
1305 if (tui.inputEl.value.length > max_length) {
1306 tui.inputEl.value = tui.inputEl.value.slice(0, max_length);
1308 } else if (tui.mode.name == 'write' && tui.inputEl.value.length > 0) {
1309 server.send(["TASK:WRITE", tui.inputEl.value[0], tui.password]);
1310 tui.switch_mode('edit');
1314 document.onclick = function() {
1315 tui.show_help = false;
1317 tui.inputEl.addEventListener('keydown', (event) => {
1318 tui.show_help = false;
1319 if (event.key == 'Enter') {
1320 event.preventDefault();
1322 if (tui.mode.has_input_prompt && event.key == 'Enter' && tui.inputEl.value == '/help') {
1323 tui.show_help = true;
1324 tui.inputEl.value = "";
1325 tui.restore_input_values();
1326 } else if (!tui.mode.has_input_prompt && event.key == tui.keys.help
1327 && !tui.mode.is_single_char_entry) {
1328 tui.show_help = true;
1329 } else if (tui.mode.name == 'login' && event.key == 'Enter') {
1330 tui.login_name = tui.inputEl.value;
1331 server.send(['LOGIN', tui.inputEl.value]);
1332 tui.inputEl.value = "";
1333 } else if (tui.mode.name == 'command_thing' && event.key == 'Enter') {
1334 if (tui.inputEl.value.length == 0) {
1335 tui.log_msg('@ aborted');
1336 tui.switch_mode('play');
1337 } else if (tui.task_action_on('command')) {
1338 server.send(['TASK:COMMAND', tui.inputEl.value]);
1339 tui.inputEl.value = "";
1341 } else if (tui.mode.name == 'control_pw_pw' && event.key == 'Enter') {
1342 if (tui.inputEl.value.length == 0) {
1343 tui.log_msg('@ aborted');
1345 server.send(['SET_MAP_CONTROL_PASSWORD',
1346 tui.tile_control_char, tui.inputEl.value]);
1347 tui.log_msg('@ sent new password for protection character "' + tui.tile_control_char + '".');
1349 tui.switch_mode('admin');
1350 } else if (tui.mode.name == 'portal' && event.key == 'Enter') {
1351 explorer.set_portal(tui.inputEl.value);
1352 tui.switch_mode('edit');
1353 } else if (tui.mode.name == 'name_thing' && event.key == 'Enter') {
1354 if (tui.inputEl.value.length == 0) {
1355 tui.inputEl.value = " ";
1357 server.send(["THING_NAME", tui.selected_thing_id, tui.inputEl.value,
1359 tui.switch_mode('edit');
1360 } else if (tui.mode.name == 'annotate' && event.key == 'Enter') {
1361 explorer.annotate(tui.inputEl.value);
1362 tui.switch_mode('edit');
1363 } else if (tui.mode.name == 'password' && event.key == 'Enter') {
1364 if (tui.inputEl.value.length == 0) {
1365 tui.inputEl.value = " ";
1367 tui.password = tui.inputEl.value
1368 tui.switch_mode('edit');
1369 } else if (tui.mode.name == 'admin_enter' && event.key == 'Enter') {
1370 server.send(['BECOME_ADMIN', tui.inputEl.value]);
1371 tui.switch_mode('play');
1372 } else if (tui.mode.name == 'control_pw_type' && event.key == 'Enter') {
1373 if (tui.inputEl.value.length != 1) {
1374 tui.log_msg('@ entered non-single-char, therefore aborted');
1375 tui.switch_mode('admin');
1377 tui.tile_control_char = tui.inputEl.value[0];
1378 tui.switch_mode('control_pw_pw');
1380 } else if (tui.mode.name == 'control_tile_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_tile_draw');
1388 } else if (tui.mode.name == 'admin_thing_protect' && event.key == 'Enter') {
1389 if (tui.inputEl.value.length != 1) {
1390 tui.log_msg('@ entered non-single-char, therefore aborted');
1392 server.send(['THING_PROTECTION', tui.selected_thing_id, tui.inputEl.value])
1393 tui.log_msg('@ sent new protection character for thing');
1395 tui.switch_mode('admin');
1396 } else if (tui.mode.name == 'chat' && event.key == 'Enter') {
1397 let tokens = parser.tokenize(tui.inputEl.value);
1398 if (tokens.length > 0 && tokens[0].length > 0) {
1399 if (tui.inputEl.value[0][0] == '/') {
1400 if (tokens[0].slice(1) == 'play' || tokens[0][1] == tui.keys.switch_to_play) {
1401 tui.switch_mode('play');
1402 } else if (tokens[0].slice(1) == 'study' || tokens[0][1] == tui.keys.switch_to_study) {
1403 tui.switch_mode('study');
1404 } else if (tokens[0].slice(1) == 'edit' || tokens[0][1] == tui.keys.switch_to_edit) {
1405 tui.switch_mode('edit');
1406 } else if (tokens[0].slice(1) == 'admin' || tokens[0][1] == tui.keys.switch_to_admin_enter) {
1407 tui.switch_mode('admin_enter');
1408 } else if (tokens[0].slice(1) == 'nick') {
1409 if (tokens.length > 1) {
1410 server.send(['NICK', tokens[1]]);
1412 tui.log_msg('? need new name');
1415 tui.log_msg('? unknown command');
1418 server.send(['ALL', tui.inputEl.value]);
1420 } else if (tui.inputEl.valuelength > 0) {
1421 server.send(['ALL', tui.inputEl.value]);
1423 tui.inputEl.value = "";
1424 } else if (tui.mode.name == 'play') {
1425 if (tui.mode.mode_switch_on_key(event)) {
1427 } else if (event.key === tui.keys.take_thing && tui.task_action_on('take_thing')) {
1428 server.send(["TASK:PICK_UP"]);
1429 } else if (event.key === tui.keys.drop_thing && tui.task_action_on('drop_thing')) {
1430 server.send(["TASK:DROP"]);
1431 } else if (event.key === tui.keys.consume && tui.task_action_on('consume')) {
1432 server.send(["TASK:INTOXICATE"]);
1433 } else if (event.key === tui.keys.door && tui.task_action_on('door')) {
1434 server.send(["TASK:DOOR"]);
1435 } else if (event.key in tui.movement_keys && tui.task_action_on('move')) {
1436 server.send(['TASK:MOVE', tui.movement_keys[event.key]]);
1437 } else if (event.key === tui.keys.teleport) {
1440 } else if (tui.mode.name == 'study') {
1441 if (tui.mode.mode_switch_on_key(event)) {
1443 } else if (event.key in tui.movement_keys) {
1444 explorer.move(tui.movement_keys[event.key]);
1445 } else if (event.key == tui.keys.toggle_map_mode) {
1446 tui.toggle_map_mode();
1448 } else if (tui.mode.name == 'control_tile_draw') {
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_tile_draw) {
1454 tui.toggle_tile_draw();
1456 } else if (tui.mode.name == 'admin') {
1457 if (tui.mode.mode_switch_on_key(event)) {
1459 } else if (event.key in tui.movement_keys && tui.task_action_on('move')) {
1460 server.send(['TASK:MOVE', tui.movement_keys[event.key]]);
1462 } else if (tui.mode.name == 'edit') {
1463 if (tui.mode.mode_switch_on_key(event)) {
1465 } else if (event.key in tui.movement_keys && tui.task_action_on('move')) {
1466 server.send(['TASK:MOVE', tui.movement_keys[event.key]]);
1467 } else if (event.key === tui.keys.flatten && tui.task_action_on('flatten')) {
1468 server.send(["TASK:FLATTEN_SURROUNDINGS", tui.password]);
1469 } else if (event.key == tui.keys.toggle_map_mode) {
1470 tui.toggle_map_mode();
1476 rows_selector.addEventListener('input', function() {
1477 if (rows_selector.value % 4 != 0 || rows_selector.value < 24) {
1480 window.localStorage.setItem(rows_selector.id, rows_selector.value);
1481 terminal.initialize();
1484 cols_selector.addEventListener('input', function() {
1485 if (cols_selector.value % 4 != 0 || cols_selector.value < 80) {
1488 window.localStorage.setItem(cols_selector.id, cols_selector.value);
1489 terminal.initialize();
1490 tui.window_width = terminal.cols / 2,
1493 for (let key_selector of key_selectors) {
1494 key_selector.addEventListener('input', function() {
1495 window.localStorage.setItem(key_selector.id, key_selector.value);
1499 window.setInterval(function() {
1500 if (server.connected) {
1501 server.send(['PING']);
1503 server.reconnect_to(server.url);
1504 tui.log_msg('@ attempting reconnect …')
1507 window.setInterval(function() {
1509 let span_decoration = "none";
1510 if (document.activeElement == tui.inputEl) {
1511 val = "on (click outside terminal to change)";
1513 val = "off (click into terminal to change)";
1514 span_decoration = "line-through";
1516 document.getElementById("keyboard_control").textContent = val;
1517 for (const span of document.querySelectorAll('.keyboard_controlled')) {
1518 span.style.textDecoration = span_decoration;
1521 document.getElementById("terminal").onclick = function() {
1522 tui.inputEl.focus();
1524 document.getElementById("help").onclick = function() {
1525 tui.show_help = true;
1528 for (const switchEl of document.querySelectorAll('[id^="switch_to_"]')) {
1529 const mode = switchEl.id.slice("switch_to_".length);
1530 switchEl.onclick = function() {
1531 tui.switch_mode(mode);
1535 document.getElementById("toggle_tile_draw").onclick = function() {
1536 tui.toggle_tile_draw();
1538 document.getElementById("toggle_map_mode").onclick = function() {
1539 tui.toggle_map_mode();
1542 document.getElementById("take_thing").onclick = function() {
1543 server.send(['TASK:PICK_UP']);
1545 document.getElementById("drop_thing").onclick = function() {
1546 server.send(['TASK:DROP']);
1548 document.getElementById("flatten").onclick = function() {
1549 server.send(['TASK:FLATTEN_SURROUNDINGS', tui.password]);
1551 document.getElementById("door").onclick = function() {
1552 server.send(['TASK:DOOR']);
1554 document.getElementById("consume").onclick = function() {
1555 server.send(['TASK:INTOXICATE']);
1557 document.getElementById("teleport").onclick = function() {
1560 for (const move_button of document.querySelectorAll('[id*="_move_"]')) {
1561 let direction = move_button.id.split('_')[2].toUpperCase();
1562 move_button.onclick = function() {
1563 if (tui.mode.available_actions.includes("move")
1564 || tui.mode.available_actions.includes("move_explorer")) {
1565 server.send(['TASK:MOVE', direction]);
1567 explorer.move(direction);