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="teleport"></button>
62 <td><button id="switch_to_edit"></button></td>
64 <button id="switch_to_write"></button>
65 <button id="flatten"></button>
66 <button id="switch_to_annotate"></button>
67 <button id="switch_to_portal"></button>
68 <button id="switch_to_name_thing"></button>
69 <button id="switch_to_password"></button>
73 <td><button id="switch_to_admin_enter"></button></td>
75 <button id="switch_to_control_pw_type"></button>
76 <button id="switch_to_control_tile_type"></button>
77 <button id="switch_to_admin_thing_protect"></button>
78 <button id="toggle_tile_draw"></button>
83 <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 />
85 <li>move up (square grid): <input id="key_square_move_up" type="text" value="w" /> (hint: ArrowUp)
86 <li>move left (square grid): <input id="key_square_move_left" type="text" value="a" /> (hint: ArrowLeft)
87 <li>move down (square grid): <input id="key_square_move_down" type="text" value="s" /> (hint: ArrowDown)
88 <li>move right (square grid): <input id="key_square_move_right" type="text" value="d" /> (hint: ArrowRight)
89 <li>move up-left (hex grid): <input id="key_hex_move_upleft" type="text" value="w" />
90 <li>move up-right (hex grid): <input id="key_hex_move_upright" type="text" value="e" />
91 <li>move right (hex grid): <input id="key_hex_move_right" type="text" value="d" />
92 <li>move down-right (hex grid): <input id="key_hex_move_downright" type="text" value="x" />
93 <li>move down-left (hex grid): <input id="key_hex_move_downleft" type="text" value="y" />
94 <li>move left (hex grid): <input id="key_hex_move_left" type="text" value="a" />
95 <li>help: <input id="key_help" type="text" value="h" />
96 <li>flatten surroundings: <input id="key_flatten" type="text" value="F" />
97 <li>teleport: <input id="key_teleport" type="text" value="p" />
98 <li>pick up thing: <input id="key_take_thing" type="text" value="z" />
99 <li>drop thing: <input id="key_drop_thing" type="text" value="u" />
100 <li>open/close: <input id="key_door" type="text" value="D" />
101 <li>consume: <input id="key_consume" type="text" value="C" />
102 <li><input id="key_switch_to_chat" type="text" value="t" />
103 <li><input id="key_switch_to_play" type="text" value="p" />
104 <li><input id="key_switch_to_study" type="text" value="?" />
105 <li><input id="key_switch_to_edit" type="text" value="E" />
106 <li><input id="key_switch_to_write" type="text" value="m" />
107 <li><input id="key_switch_to_name_thing" type="text" value="N" />
108 <li><input id="key_switch_to_password" type="text" value="P" />
109 <li><input id="key_switch_to_admin_enter" type="text" value="A" />
110 <li><input id="key_switch_to_control_pw_type" type="text" value="C" />
111 <li><input id="key_switch_to_control_tile_type" type="text" value="Q" />
112 <li><input id="key_switch_to_admin_thing_protect" type="text" value="T" />
113 <li><input id="key_switch_to_annotate" type="text" value="M" />
114 <li><input id="key_switch_to_portal" type="text" value="T" />
115 <li>toggle map view: <input id="key_toggle_map_mode" type="text" value="L" />
116 <li>toggle protection character drawing: <input id="key_toggle_tile_draw" type="text" value="m" />
121 let websocket_location = "wss://plomlompom.com/rogue_chat/";
122 //let websocket_location = "ws://localhost:8000/";
127 'long': 'This mode allows you to interact with the map in various ways.'
131 '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.'},
133 'short': 'world edit',
134 '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.'
137 'short': 'name thing',
138 'long': 'Give name to/change name of thing here.'
140 'admin_thing_protect': {
141 'short': 'change thing protection',
142 'long': 'Change protection character for thing here.'
145 'short': 'change terrain',
146 '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.'
149 'short': 'change protection character password',
150 '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.'
153 'short': 'change protection character password',
154 '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.'
156 'control_tile_type': {
157 'short': 'change tiles protection',
158 '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.'
160 'control_tile_draw': {
161 'short': 'change tiles protection',
162 '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.'
165 'short': 'annotate tile',
166 '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.'
169 'short': 'edit portal',
170 '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.'
174 '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:'
178 'long': 'Enter your player name.'
180 'waiting_for_server': {
181 'short': 'waiting for server response',
182 'long': 'Waiting for a server response.'
185 'short': 'waiting for server response',
186 'long': 'Waiting for a server response.'
189 'short': 'set world edit password',
190 '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.'
193 'short': 'become admin',
194 'long': 'This mode allows you to become admin if you know an admin password.'
198 'long': 'This mode allows you access to actions limited to administrators.'
201 let key_descriptions = {
203 'flatten': 'flatten surroundings',
204 'teleport': 'teleport',
205 'take_thing': 'pick up thing',
206 'drop_thing': 'drop thing',
207 'door': 'open/close',
208 'consume': 'consume',
209 'toggle_map_mode': 'toggle map view',
210 'toggle_tile_draw': 'toggle protection character drawing',
211 'hex_move_upleft': 'up-left',
212 'hex_move_upright': 'up-right',
213 'hex_move_right': 'right',
214 'hex_move_left': 'left',
215 'hex_move_downleft': 'down-left',
216 'hex_move_downright': 'down-right',
217 'square_move_up': 'up',
218 'square_move_left': 'left',
219 'square_move_down': 'down',
220 'square_move_right': 'right',
222 for (const mode_name of Object.keys(mode_helps)) {
223 key_descriptions['switch_to_' + mode_name] = mode_helps[mode_name].short;
226 let rows_selector = document.getElementById("n_rows");
227 let cols_selector = document.getElementById("n_cols");
228 let key_selectors = document.querySelectorAll('[id^="key_"]');
230 for (const key_switch_selector of document.querySelectorAll('[id^="key_switch_to_"]')) {
231 const action = key_switch_selector.id.slice("key_switch_to_".length);
232 key_switch_selector.parentNode.prepend(mode_helps[action].short + ': ');
235 function restore_selector_value(selector) {
236 let stored_selection = window.localStorage.getItem(selector.id);
237 if (stored_selection) {
238 selector.value = stored_selection;
241 restore_selector_value(rows_selector);
242 restore_selector_value(cols_selector);
243 for (let key_selector of key_selectors) {
244 restore_selector_value(key_selector);
247 function escapeHTML(str) {
249 replace(/&/g, '&').
250 replace(/</g, '<').
251 replace(/>/g, '>').
252 replace(/'/g, ''').
253 replace(/"/g, '"');
257 initialize: function() {
258 this.rows = rows_selector.value;
259 this.cols = cols_selector.value;
260 this.pre_el = document.getElementById("terminal");
261 this.set_default_colors();
265 for (let y = 0, x = 0; y <= this.rows; x++) {
266 if (x == this.cols) {
269 this.content.push(line);
271 if (y == this.rows) {
278 apply_colors: function() {
279 this.pre_el.style.color = this.foreground;
280 this.pre_el.style.backgroundColor = this.background;
282 set_default_colors: function() {
283 this.foreground = 'white';
284 this.background = 'black';
287 set_random_colors: function() {
288 function rand(offset) {
289 return Math.floor(offset + Math.random() * 96).toString(16).padStart(2, '0');
291 this.foreground = '#' + rand(159) + rand(159) + rand(159);
292 this.background = '#' + rand(0) + rand(0) + rand(0);
295 blink_screen: function() {
296 this.pre_el.style.color = this.background;
297 this.pre_el.style.backgroundColor = this.foreground;
299 this.pre_el.style.color = this.foreground;
300 this.pre_el.style.backgroundColor = this.background;
303 refresh: function() {
304 let pre_content = '';
305 for (let y = 0; y < this.rows; y++) {
306 let line = this.content[y].join('');
308 if (y in tui.links) {
310 for (let span of tui.links[y]) {
311 chunks.push(escapeHTML(line.slice(start_x, span[0])));
312 chunks.push('<a target="_blank" href="');
313 chunks.push(escapeHTML(span[2]));
315 chunks.push(escapeHTML(line.slice(span[0], span[1])));
319 chunks.push(escapeHTML(line.slice(start_x)));
321 chunks = [escapeHTML(line)];
323 for (const chunk of chunks) {
324 pre_content += chunk;
328 this.pre_el.innerHTML = pre_content;
330 write: function(start_y, start_x, msg) {
331 for (let x = start_x, i = 0; x < this.cols && i < msg.length; x++, i++) {
332 this.content[start_y][x] = msg[i];
335 drawBox: function(start_y, start_x, height, width) {
336 let end_y = start_y + height;
337 let end_x = start_x + width;
338 for (let y = start_y, x = start_x; y < this.rows; x++) {
346 this.content[y][x] = ' ';
350 terminal.initialize();
353 tokenize: function(str) {
358 for (let i = 0; i < str.length; i++) {
364 } else if (c == '\\') {
366 } else if (c == '"') {
371 } else if (c == '"') {
373 } else if (c === ' ') {
374 if (token.length > 0) {
382 if (token.length > 0) {
387 parse_yx: function(position_string) {
388 let coordinate_strings = position_string.split(',')
389 let position = [0, 0];
390 position[0] = parseInt(coordinate_strings[0].slice(2));
391 position[1] = parseInt(coordinate_strings[1].slice(2));
403 init: function(url) {
405 this.websocket = new WebSocket(this.url);
406 this.websocket.onopen = function(event) {
407 server.connected = true;
408 game.thing_types = {};
410 server.send(['TASKS']);
411 server.send(['TERRAINS']);
412 server.send(['THING_TYPES']);
413 tui.log_msg("@ server connected! :)");
414 tui.switch_mode('login');
416 this.websocket.onclose = function(event) {
417 server.connected = false;
418 tui.switch_mode('waiting_for_server');
419 tui.log_msg("@ server disconnected :(");
421 this.websocket.onmessage = this.handle_event;
423 reconnect_to: function(url) {
424 this.websocket.close();
427 send: function(tokens) {
428 this.websocket.send(unparser.untokenize(tokens));
430 handle_event: function(event) {
431 let tokens = parser.tokenize(event.data);
432 if (tokens[0] === 'TURN') {
433 game.turn_complete = false;
434 explorer.empty_info_db();
437 game.turn = parseInt(tokens[1]);
438 } else if (tokens[0] === 'THING') {
439 let t = game.get_thing(tokens[4], true);
440 t.position = parser.parse_yx(tokens[1]);
442 t.protection = tokens[3];
443 } else if (tokens[0] === 'THING_NAME') {
444 let t = game.get_thing(tokens[1], false);
448 } else if (tokens[0] === 'THING_CHAR') {
449 let t = game.get_thing(tokens[1], false);
451 t.thing_char = tokens[2];
453 } else if (tokens[0] === 'TASKS') {
454 game.tasks = tokens[1].split(',');
455 tui.mode_write.legal = game.tasks.includes('WRITE');
456 } else if (tokens[0] === 'THING_TYPE') {
457 game.thing_types[tokens[1]] = tokens[2]
458 } else if (tokens[0] === 'TERRAIN') {
459 game.terrains[tokens[1]] = tokens[2]
460 } else if (tokens[0] === 'MAP') {
461 game.map_geometry = tokens[1];
463 game.map_size = parser.parse_yx(tokens[2]);
465 } else if (tokens[0] === 'FOV') {
467 } else if (tokens[0] === 'MAP_CONTROL') {
468 game.map_control = tokens[1]
469 } else if (tokens[0] === 'GAME_STATE_COMPLETE') {
470 game.turn_complete = true;
471 if (tui.mode.name == 'post_login_wait') {
472 tui.switch_mode('play');
473 } else if (tui.mode.name == 'study') {
474 explorer.query_info();
477 } else if (tokens[0] === 'CHAT') {
478 tui.log_msg('# ' + tokens[1], 1);
479 } else if (tokens[0] === 'PLAYER_ID') {
480 game.player_id = parseInt(tokens[1]);
481 } else if (tokens[0] === 'LOGIN_OK') {
482 this.send(['GET_GAMESTATE']);
483 tui.switch_mode('post_login_wait');
484 } else if (tokens[0] === 'DEFAULT_COLORS') {
485 terminal.set_default_colors();
486 } else if (tokens[0] === 'RANDOM_COLORS') {
487 terminal.set_random_colors();
488 } else if (tokens[0] === 'ADMIN_OK') {
490 tui.log_msg('@ you now have admin rights');
491 tui.switch_mode('admin');
492 } else if (tokens[0] === 'PORTAL') {
493 let position = parser.parse_yx(tokens[1]);
494 game.portals[position] = tokens[2];
495 } else if (tokens[0] === 'ANNOTATION_HINT') {
496 let position = parser.parse_yx(tokens[1]);
497 explorer.info_hints = explorer.info_hints.concat([position]);
498 } else if (tokens[0] === 'ANNOTATION') {
499 let position = parser.parse_yx(tokens[1]);
500 explorer.update_info_db(position, tokens[2]);
502 } else if (tokens[0] === 'UNHANDLED_INPUT') {
503 tui.log_msg('? unknown command');
504 } else if (tokens[0] === 'PLAY_ERROR') {
505 tui.log_msg('? ' + tokens[1]);
506 terminal.blink_screen();
507 } else if (tokens[0] === 'ARGUMENT_ERROR') {
508 tui.log_msg('? syntax error: ' + tokens[1]);
509 } else if (tokens[0] === 'GAME_ERROR') {
510 tui.log_msg('? game error: ' + tokens[1]);
511 } else if (tokens[0] === 'PONG') {
514 tui.log_msg('? unhandled input: ' + event.data);
520 quote: function(str) {
522 for (let i = 0; i < str.length; i++) {
524 if (['"', '\\'].includes(c)) {
530 return quoted.join('');
532 to_yx: function(yx_coordinate) {
533 return "Y:" + yx_coordinate[0] + ",X:" + yx_coordinate[1];
535 untokenize: function(tokens) {
536 let quoted_tokens = [];
537 for (let token of tokens) {
538 quoted_tokens.push(this.quote(token));
540 return quoted_tokens.join(" ");
545 constructor(name, has_input_prompt=false, shows_info=false,
546 is_intro=false, is_single_char_entry=false) {
548 this.short_desc = mode_helps[name].short;
549 this.available_modes = [];
550 this.available_actions = [];
551 this.has_input_prompt = has_input_prompt;
552 this.shows_info= shows_info;
553 this.is_intro = is_intro;
554 this.help_intro = mode_helps[name].long;
555 this.is_single_char_entry = is_single_char_entry;
558 *iter_available_modes() {
559 for (let mode_name of this.available_modes) {
560 let mode = tui['mode_' + mode_name];
564 let key = tui.keys['switch_to_' + mode.name];
568 list_available_modes() {
570 if (this.available_modes.length > 0) {
571 msg += 'Other modes available from here:\n';
572 for (let [mode, key] of this.iter_available_modes()) {
573 msg += '[' + key + '] – ' + mode.short_desc + '\n';
578 mode_switch_on_key(key_event) {
579 for (let [mode, key] of this.iter_available_modes()) {
580 if (key_event.key == key) {
581 event.preventDefault();
582 tui.switch_mode(mode.name);
594 window_width: terminal.cols / 2,
602 mode_waiting_for_server: new Mode('waiting_for_server',
604 mode_login: new Mode('login', true, false, true),
605 mode_post_login_wait: new Mode('post_login_wait'),
606 mode_chat: new Mode('chat', true),
607 mode_annotate: new Mode('annotate', true, true),
608 mode_play: new Mode('play'),
609 mode_study: new Mode('study', false, true),
610 mode_write: new Mode('write', false, false, false, true),
611 mode_edit: new Mode('edit'),
612 mode_control_pw_type: new Mode('control_pw_type', true),
613 mode_admin_thing_protect: new Mode('admin_thing_protect', true),
614 mode_portal: new Mode('portal', true, true),
615 mode_password: new Mode('password', true),
616 mode_name_thing: new Mode('name_thing', true, true),
617 mode_admin_enter: new Mode('admin_enter', true),
618 mode_admin: new Mode('admin'),
619 mode_control_pw_pw: new Mode('control_pw_pw', true),
620 mode_control_tile_type: new Mode('control_tile_type', true),
621 mode_control_tile_draw: new Mode('control_tile_draw'),
623 'flatten': 'FLATTEN_SURROUNDINGS',
624 'take_thing': 'PICK_UP',
625 'drop_thing': 'DROP',
628 'consume': 'INTOXICATE',
633 this.mode_chat.available_modes = ["play", "study", "edit", "admin_enter"]
634 this.mode_play.available_modes = ["chat", "study", "edit", "admin_enter"]
635 this.mode_play.available_actions = ["move", "take_thing", "drop_thing",
636 "teleport", "door", "consume"];
637 this.mode_study.available_modes = ["chat", "play", "admin_enter", "edit"]
638 this.mode_study.available_actions = ["toggle_map_mode", "move_explorer"];
639 this.mode_admin.available_modes = ["admin_thing_protect", "control_pw_type",
640 "control_tile_type", "chat",
641 "study", "play", "edit"]
642 this.mode_admin.available_actions = ["move"];
643 this.mode_control_tile_draw.available_modes = ["admin_enter"]
644 this.mode_control_tile_draw.available_actions = ["toggle_tile_draw"];
645 this.mode_edit.available_modes = ["write", "annotate", "portal", "name_thing",
646 "password", "chat", "study", "play",
648 this.mode_edit.available_actions = ["move", "flatten", "toggle_map_mode"]
649 this.mode = this.mode_waiting_for_server;
650 this.inputEl = document.getElementById("input");
651 this.inputEl.focus();
652 this.recalc_input_lines();
653 this.height_header = this.height_turn_line + this.height_mode_line;
654 this.log_msg("@ waiting for server connection ...");
657 init_keys: function() {
658 document.getElementById("move_table").hidden = true;
660 for (let key_selector of key_selectors) {
661 this.keys[key_selector.id.slice(4)] = key_selector.value;
663 this.movement_keys = {};
664 let geometry_prefix = 'undefinedMapGeometry_';
665 if (game.map_geometry) {
666 geometry_prefix = game.map_geometry.toLowerCase() + '_';
668 for (const key_name of Object.keys(key_descriptions)) {
669 if (key_name.startsWith(geometry_prefix)) {
670 let direction = key_name.split('_')[2].toUpperCase();
671 let key = this.keys[key_name];
672 this.movement_keys[key] = direction;
675 for (const move_button of document.querySelectorAll('[id*="_move_"]')) {
676 if (move_button.id.startsWith('key_')) {
679 move_button.hidden = true;
681 for (const move_button of document.querySelectorAll('[id^="' + geometry_prefix + 'move_"]')) {
682 document.getElementById("move_table").hidden = false;
683 move_button.hidden = false;
685 for (let el of document.getElementsByTagName("button")) {
686 let action_desc = key_descriptions[el.id];
687 let action_key = '[' + this.keys[el.id] + ']';
688 el.innerHTML = escapeHTML(action_desc) + '<br /><span class="keyboard_controlled">' + escapeHTML(action_key) + '</span>';
691 task_action_on: function(action) {
692 return game.tasks.includes(this.action_tasks[action]);
694 switch_mode: function(mode_name) {
695 if (this.mode.name == 'control_tile_draw') {
696 tui.log_msg('@ finished tile protection drawing.')
698 this.tile_draw = false;
699 if (mode_name == 'admin_enter' && this.is_admin) {
701 } else if (['name_thing', 'admin_thing_protect'].includes(mode_name)) {
702 let player_position = game.things[game.player_id].position;
704 for (let t_id in game.things) {
705 if (t_id == game.player_id) {
708 let t = game.things[t_id];
709 if (player_position[0] == t.position[0] && player_position[1] == t.position[1]) {
715 terminal.blink_screen();
716 this.log_msg('? not standing over thing');
719 this.selected_thing_id = thing_id;
722 this.mode = this['mode_' + mode_name];
723 if (["control_tile_draw", "control_tile_type", "control_pw_type"].includes(this.mode.name)) {
724 this.map_mode = 'protections';
725 } else if (this.mode.name != "edit") {
726 this.map_mode = 'terrain + things';
728 if (this.mode.has_input_prompt || this.mode.is_single_char_entry) {
729 this.inputEl.focus();
731 if (game.player_id in game.things && (this.mode.shows_info || this.mode.name == 'control_tile_draw')) {
732 explorer.position = game.things[game.player_id].position;
733 if (this.mode.shows_info) {
734 explorer.query_info();
737 this.inputEl.value = "";
738 this.restore_input_values();
739 for (let el of document.getElementsByTagName("button")) {
742 document.getElementById("help").disabled = false;
743 for (const action of this.mode.available_actions) {
744 if (["move", "move_explorer"].includes(action)) {
745 for (const move_key of document.querySelectorAll('[id*="_move_"]')) {
746 move_key.disabled = false;
748 } else if (Object.keys(this.action_tasks).includes(action)) {
749 if (this.task_action_on(action)) {
750 document.getElementById(action).disabled = false;
753 document.getElementById(action).disabled = false;
756 for (const mode_name of this.mode.available_modes) {
757 document.getElementById('switch_to_' + mode_name).disabled = false;
759 if (this.mode.name == 'login') {
760 if (this.login_name) {
761 server.send(['LOGIN', this.login_name]);
763 this.log_msg("? need login name");
765 } else if (this.mode.is_single_char_entry) {
766 this.show_help = true;
767 } else if (this.mode.name == 'admin_enter') {
768 this.log_msg('@ enter admin password:')
769 } else if (this.mode.name == 'control_pw_type') {
770 this.log_msg('@ enter protection character for which you want to change the password:')
771 } else if (this.mode.name == 'control_tile_type') {
772 this.log_msg('@ enter protection character which you want to draw:')
773 } else if (this.mode.name == 'admin_thing_protect') {
774 this.log_msg('@ enter thing protection character:')
775 } else if (this.mode.name == 'control_pw_pw') {
776 this.log_msg('@ enter protection password for "' + this.tile_control_char + '":');
777 } else if (this.mode.name == 'control_tile_draw') {
778 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 + '].')
782 offset_links: function(offset, links) {
783 for (let y in links) {
784 let real_y = offset[0] + parseInt(y);
785 if (!this.links[real_y]) {
786 this.links[real_y] = [];
788 for (let link of links[y]) {
789 const offset_link = [link[0] + offset[1], link[1] + offset[1], link[2]];
790 this.links[real_y].push(offset_link);
794 restore_input_values: function() {
795 if (this.mode.name == 'annotate' && explorer.position in explorer.info_db) {
796 let info = explorer.info_db[explorer.position];
797 if (info != "(none)") {
798 this.inputEl.value = info;
800 } else if (this.mode.name == 'portal' && explorer.position in game.portals) {
801 let portal = game.portals[explorer.position]
802 this.inputEl.value = portal;
803 } else if (this.mode.name == 'password') {
804 this.inputEl.value = this.password;
805 } else if (this.mode.name == 'name_thing') {
806 let t = game.get_thing(this.selected_thing_id);
808 this.inputEl.value = t.name_;
810 } else if (this.mode.name == 'admin_thing_protect') {
811 let t = game.get_thing(this.selected_thing_id);
812 if (t && t.protection) {
813 this.inputEl.value = t.protection;
817 recalc_input_lines: function() {
818 if (this.mode.has_input_prompt) {
820 [this.input_lines, _] = this.msg_into_lines_of_width(this.input_prompt + this.inputEl.value, this.window_width);
822 this.input_lines = [];
824 this.height_input = this.input_lines.length;
826 msg_into_lines_of_width: function(msg, width) {
827 function push_inner_link(y, end_x) {
828 if (!inner_links[y]) {
831 inner_links[y].push([url_start_x, end_x, url]);
833 const matches = msg.matchAll(/https?:\/\/[^\s]+/g)
836 for (const match of matches) {
837 const url = match[0];
838 const url_start = match.index;
839 const url_end = match.index + match[0].length;
840 link_data[url_start] = url;
841 url_ends.push(url_end);
845 let inner_links = {};
849 for (let i = 0, x = 0, y = 0; i < msg.length; i++, x++) {
850 if (x >= width || msg[i] == "\n") {
852 push_inner_link(y, chunk.length);
854 if (url_ends[0] == i) {
862 if (msg[i] == "\n") {
867 if (msg[i] != "\n") {
870 if (i in link_data) {
874 } else if (url_ends[0] == i) {
876 push_inner_link(y, x);
882 push_inner_link(lines.length - 1, chunk.length);
884 return [lines, inner_links];
886 log_msg: function(msg) {
888 while (this.log.length > 100) {
893 draw_map: function() {
894 if (game.turn_complete) {
895 let map_lines_split = [];
897 for (let i = 0, j = 0; i < game.map.length; i++, j++) {
898 if (j == game.map_size[1]) {
899 map_lines_split.push(line);
903 if (this.map_mode == 'protections') {
904 line.push(game.map_control[i] + ' ');
906 line.push(game.map[i] + ' ');
909 map_lines_split.push(line);
910 if (this.map_mode == 'terrain + annotations') {
911 for (const coordinate of explorer.info_hints) {
912 map_lines_split[coordinate[0]][coordinate[1]] = 'A ';
914 } else if (this.map_mode == 'terrain + things') {
915 for (const p in game.portals) {
916 let coordinate = p.split(',')
917 let original = map_lines_split[coordinate[0]][coordinate[1]];
918 map_lines_split[coordinate[0]][coordinate[1]] = original[0] + 'P';
920 let used_positions = [];
921 for (const thing_id in game.things) {
922 let t = game.things[thing_id];
923 let symbol = game.thing_types[t.type_];
926 meta_char = t.thing_char;
928 if (used_positions.includes(t.position.toString())) {
931 map_lines_split[t.position[0]][t.position[1]] = symbol + meta_char;
932 used_positions.push(t.position.toString());
935 let player = game.things[game.player_id];
936 if (tui.mode.shows_info || tui.mode.name == 'control_tile_draw') {
937 map_lines_split[explorer.position[0]][explorer.position[1]] = '??';
938 } else if (tui.map_mode != 'terrain + things') {
939 map_lines_split[player.position[0]][player.position[1]] = '??';
942 if (game.map_geometry == 'Square') {
943 for (let line_split of map_lines_split) {
944 this.map_lines.push(line_split.join(''));
946 } else if (game.map_geometry == 'Hex') {
948 for (let line_split of map_lines_split) {
949 this.map_lines.push(' '.repeat(indent) + line_split.join(''));
957 let window_center = [terminal.rows / 2, this.window_width / 2];
958 let center_position = [player.position[0], player.position[1]];
959 if (tui.mode.shows_info || tui.mode.name == 'control_tile_draw') {
960 center_position = [explorer.position[0], explorer.position[1]];
962 center_position[1] = center_position[1] * 2;
963 this.offset = [center_position[0] - window_center[0],
964 center_position[1] - window_center[1]]
965 if (game.map_geometry == 'Hex' && this.offset[0] % 2) {
969 let term_y = Math.max(0, -this.offset[0]);
970 let term_x = Math.max(0, -this.offset[1]);
971 let map_y = Math.max(0, this.offset[0]);
972 let map_x = Math.max(0, this.offset[1]);
973 for (; term_y < terminal.rows && map_y < game.map_size[0]; term_y++, map_y++) {
974 let to_draw = this.map_lines[map_y].slice(map_x, this.window_width + this.offset[1]);
975 terminal.write(term_y, term_x, to_draw);
978 draw_mode_line: function() {
979 let help = 'hit [' + this.keys.help + '] for help';
980 if (this.mode.has_input_prompt) {
981 help = 'enter /help for help';
983 terminal.write(0, this.window_width, 'MODE: ' + this.mode.short_desc + ' – ' + help);
985 draw_turn_line: function(n) {
986 if (game.turn_complete) {
987 terminal.write(1, this.window_width, 'TURN: ' + game.turn);
990 draw_history: function() {
991 let log_display_lines = [];
993 let y_offset_in_log = 0;
994 for (let line of this.log) {
995 let [new_lines, link_data] = this.msg_into_lines_of_width(line,
997 log_display_lines = log_display_lines.concat(new_lines);
998 for (const y in link_data) {
999 const rel_y = y_offset_in_log + parseInt(y);
1000 log_links[rel_y] = [];
1001 for (let link of link_data[y]) {
1002 log_links[rel_y].push(link);
1005 y_offset_in_log += new_lines.length;
1007 let i = log_display_lines.length - 1;
1008 for (let y = terminal.rows - 1 - this.height_input;
1009 y >= this.height_header && i >= 0;
1011 terminal.write(y, this.window_width, log_display_lines[i]);
1013 for (const key of Object.keys(log_links)) {
1014 if (parseInt(key) <= i) {
1015 delete log_links[key];
1018 let offset = [terminal.rows - this.height_input - log_display_lines.length,
1020 this.offset_links(offset, log_links);
1022 draw_info: function() {
1023 let [lines, link_data] = this.msg_into_lines_of_width(explorer.get_info(),
1025 let offset = [this.height_header, this.window_width];
1026 for (let y = offset[0], i = 0; y < terminal.rows && i < lines.length; y++, i++) {
1027 terminal.write(y, offset[1], lines[i]);
1029 this.offset_links(offset, link_data);
1031 draw_input: function() {
1032 if (this.mode.has_input_prompt) {
1033 for (let y = terminal.rows - this.height_input, i = 0; i < this.input_lines.length; y++, i++) {
1034 terminal.write(y, this.window_width, this.input_lines[i]);
1038 draw_help: function() {
1039 let movement_keys_desc = '';
1040 if (!this.mode.is_intro) {
1041 movement_keys_desc = Object.keys(this.movement_keys).join(',');
1043 let content = this.mode.short_desc + " help\n\n" + this.mode.help_intro + "\n\n";
1044 if (this.mode.name == 'chat') {
1045 content += '/nick NAME – re-name yourself to NAME\n';
1046 content += '/' + this.keys.switch_to_play + ' or /play – switch to play mode\n';
1047 content += '/' + this.keys.switch_to_study + ' or /study – switch to study mode\n';
1048 content += '/' + this.keys.switch_to_edit + ' or /edit – switch to world edit mode\n';
1049 content += '/' + this.keys.switch_to_admin_enter + ' or /admin – switch to admin mode\n';
1050 } else if (this.mode.available_actions.length > 0) {
1051 content += "Available actions:\n";
1052 for (let action of this.mode.available_actions) {
1053 if (Object.keys(this.action_tasks).includes(action)) {
1054 if (!this.task_action_on(action)) {
1058 if (action == 'move_explorer') {
1061 if (action == 'move') {
1062 content += "[" + movement_keys_desc + "] – move\n"
1064 content += "[" + this.keys[action] + "] – " + key_descriptions[action] + "\n";
1069 content += this.mode.list_available_modes();
1071 if (!this.mode.has_input_prompt) {
1072 start_x = this.window_width
1074 terminal.drawBox(0, start_x, terminal.rows, this.window_width);
1075 let [lines, _] = this.msg_into_lines_of_width(content, this.window_width);
1076 for (let y = 0, i = 0; y < terminal.rows && i < lines.length; y++, i++) {
1077 terminal.write(y, start_x, lines[i]);
1080 toggle_tile_draw: function() {
1081 if (tui.tile_draw) {
1082 tui.tile_draw = false;
1084 tui.tile_draw = true;
1087 toggle_map_mode: function() {
1088 if (tui.map_mode == 'terrain only') {
1089 tui.map_mode = 'terrain + annotations';
1090 } else if (tui.map_mode == 'terrain + annotations') {
1091 tui.map_mode = 'terrain + things';
1092 } else if (tui.map_mode == 'terrain + things') {
1093 tui.map_mode = 'protections';
1094 } else if (tui.map_mode == 'protections') {
1095 tui.map_mode = 'terrain only';
1098 full_refresh: function() {
1100 terminal.drawBox(0, 0, terminal.rows, terminal.cols);
1101 this.recalc_input_lines();
1102 if (this.mode.is_intro) {
1103 this.draw_history();
1107 this.draw_turn_line();
1108 this.draw_mode_line();
1109 if (this.mode.shows_info) {
1112 this.draw_history();
1116 if (this.show_help) {
1128 this.map_control = "";
1129 this.map_size = [0,0];
1130 this.player_id = -1;
1134 get_thing: function(id_, create_if_not_found=false) {
1135 if (id_ in game.things) {
1136 return game.things[id_];
1137 } else if (create_if_not_found) {
1138 let t = new Thing([0,0]);
1139 game.things[id_] = t;
1143 move: function(start_position, direction) {
1144 let target = [start_position[0], start_position[1]];
1145 if (direction == 'LEFT') {
1147 } else if (direction == 'RIGHT') {
1149 } else if (game.map_geometry == 'Square') {
1150 if (direction == 'UP') {
1152 } else if (direction == 'DOWN') {
1155 } else if (game.map_geometry == 'Hex') {
1156 let start_indented = start_position[0] % 2;
1157 if (direction == 'UPLEFT') {
1159 if (!start_indented) {
1162 } else if (direction == 'UPRIGHT') {
1164 if (start_indented) {
1167 } else if (direction == 'DOWNLEFT') {
1169 if (!start_indented) {
1172 } else if (direction == 'DOWNRIGHT') {
1174 if (start_indented) {
1179 if (target[0] < 0 || target[1] < 0 ||
1180 target[0] >= this.map_size[0] || target[1] >= this.map_size[1]) {
1185 teleport: function() {
1186 let player = this.get_thing(game.player_id);
1187 if (player.position in this.portals) {
1188 server.reconnect_to(this.portals[player.position]);
1190 terminal.blink_screen();
1191 tui.log_msg('? not standing on portal')
1199 server.init(websocket_location);
1205 move: function(direction) {
1206 let target = game.move(this.position, direction);
1208 this.position = target
1209 if (tui.mode.shows_info) {
1211 } else if (tui.tile_draw) {
1212 this.send_tile_control_command();
1215 terminal.blink_screen();
1218 update_info_db: function(yx, str) {
1219 this.info_db[yx] = str;
1220 if (tui.mode.name == 'study') {
1224 empty_info_db: function() {
1226 this.info_hints = [];
1227 if (tui.mode.name == 'study') {
1231 query_info: function() {
1232 server.send(["GET_ANNOTATION", unparser.to_yx(explorer.position)]);
1234 get_info: function() {
1235 let info = "MAP VIEW: " + tui.map_mode + "\n";
1236 let position_i = this.position[0] * game.map_size[1] + this.position[1];
1237 if (game.fov[position_i] != '.') {
1238 return info + 'outside field of view';
1240 let terrain_char = game.map[position_i]
1241 let terrain_desc = '?'
1242 if (game.terrains[terrain_char]) {
1243 terrain_desc = game.terrains[terrain_char];
1245 info += 'TERRAIN: "' + terrain_char + '" / ' + terrain_desc + "\n";
1246 let protection = game.map_control[position_i];
1247 if (protection == '.') {
1248 protection = 'unprotected';
1250 info += 'PROTECTION: ' + protection + '\n';
1251 for (let t_id in game.things) {
1252 let t = game.things[t_id];
1253 if (t.position[0] == this.position[0] && t.position[1] == this.position[1]) {
1254 let symbol = game.thing_types[t.type_];
1255 let protection = t.protection;
1256 if (protection == '.') {
1257 protection = 'none';
1259 info += "THING: " + t.type_ + " / " + symbol;
1261 info += t.thing_char;
1264 info += " (" + t.name_ + ")";
1266 info += " / protection: " + protection + "\n";
1269 if (this.position in game.portals) {
1270 info += "PORTAL: " + game.portals[this.position] + "\n";
1272 if (this.position in this.info_db) {
1273 info += "ANNOTATIONS: " + this.info_db[this.position];
1275 info += 'waiting …';
1279 annotate: function(msg) {
1280 if (msg.length == 0) {
1281 msg = " "; // triggers annotation deletion
1283 server.send(["ANNOTATE", unparser.to_yx(explorer.position), msg, tui.password]);
1285 set_portal: function(msg) {
1286 if (msg.length == 0) {
1287 msg = " "; // triggers portal deletion
1289 server.send(["PORTAL", unparser.to_yx(explorer.position), msg, tui.password]);
1291 send_tile_control_command: function() {
1292 server.send(["SET_TILE_CONTROL", unparser.to_yx(this.position), tui.tile_control_char]);
1296 tui.inputEl.addEventListener('input', (event) => {
1297 if (tui.mode.has_input_prompt) {
1298 let max_length = tui.window_width * terminal.rows - tui.input_prompt.length;
1299 if (tui.inputEl.value.length > max_length) {
1300 tui.inputEl.value = tui.inputEl.value.slice(0, max_length);
1302 } else if (tui.mode.name == 'write' && tui.inputEl.value.length > 0) {
1303 server.send(["TASK:WRITE", tui.inputEl.value[0], tui.password]);
1304 tui.switch_mode('edit');
1308 document.onclick = function() {
1309 tui.show_help = false;
1311 tui.inputEl.addEventListener('keydown', (event) => {
1312 tui.show_help = false;
1313 if (event.key == 'Enter') {
1314 event.preventDefault();
1316 if (tui.mode.has_input_prompt && event.key == 'Enter' && tui.inputEl.value == '/help') {
1317 tui.show_help = true;
1318 tui.inputEl.value = "";
1319 tui.restore_input_values();
1320 } else if (!tui.mode.has_input_prompt && event.key == tui.keys.help
1321 && !tui.mode.is_single_char_entry) {
1322 tui.show_help = true;
1323 } else if (tui.mode.name == 'login' && event.key == 'Enter') {
1324 tui.login_name = tui.inputEl.value;
1325 server.send(['LOGIN', tui.inputEl.value]);
1326 tui.inputEl.value = "";
1327 } else if (tui.mode.name == 'control_pw_pw' && event.key == 'Enter') {
1328 if (tui.inputEl.value.length == 0) {
1329 tui.log_msg('@ aborted');
1331 server.send(['SET_MAP_CONTROL_PASSWORD',
1332 tui.tile_control_char, tui.inputEl.value]);
1333 tui.log_msg('@ sent new password for protection character "' + tui.tile_control_char + '".');
1335 tui.switch_mode('admin');
1336 } else if (tui.mode.name == 'portal' && event.key == 'Enter') {
1337 explorer.set_portal(tui.inputEl.value);
1338 tui.switch_mode('edit');
1339 } else if (tui.mode.name == 'name_thing' && event.key == 'Enter') {
1340 if (tui.inputEl.value.length == 0) {
1341 tui.inputEl.value = " ";
1343 server.send(["THING_NAME", tui.selected_thing_id, tui.inputEl.value,
1345 tui.switch_mode('edit');
1346 } else if (tui.mode.name == 'annotate' && event.key == 'Enter') {
1347 explorer.annotate(tui.inputEl.value);
1348 tui.switch_mode('edit');
1349 } else if (tui.mode.name == 'password' && event.key == 'Enter') {
1350 if (tui.inputEl.value.length == 0) {
1351 tui.inputEl.value = " ";
1353 tui.password = tui.inputEl.value
1354 tui.switch_mode('edit');
1355 } else if (tui.mode.name == 'admin_enter' && event.key == 'Enter') {
1356 server.send(['BECOME_ADMIN', tui.inputEl.value]);
1357 tui.switch_mode('play');
1358 } else if (tui.mode.name == 'control_pw_type' && event.key == 'Enter') {
1359 if (tui.inputEl.value.length != 1) {
1360 tui.log_msg('@ entered non-single-char, therefore aborted');
1361 tui.switch_mode('admin');
1363 tui.tile_control_char = tui.inputEl.value[0];
1364 tui.switch_mode('control_pw_pw');
1366 } else if (tui.mode.name == 'control_tile_type' && event.key == 'Enter') {
1367 if (tui.inputEl.value.length != 1) {
1368 tui.log_msg('@ entered non-single-char, therefore aborted');
1369 tui.switch_mode('admin');
1371 tui.tile_control_char = tui.inputEl.value[0];
1372 tui.switch_mode('control_tile_draw');
1374 } else if (tui.mode.name == 'admin_thing_protect' && event.key == 'Enter') {
1375 if (tui.inputEl.value.length != 1) {
1376 tui.log_msg('@ entered non-single-char, therefore aborted');
1378 server.send(['THING_PROTECTION', tui.selected_thing_id, tui.inputEl.value])
1379 tui.log_msg('@ sent new protection character for thing');
1381 tui.switch_mode('admin');
1382 } else if (tui.mode.name == 'chat' && event.key == 'Enter') {
1383 let tokens = parser.tokenize(tui.inputEl.value);
1384 if (tokens.length > 0 && tokens[0].length > 0) {
1385 if (tui.inputEl.value[0][0] == '/') {
1386 if (tokens[0].slice(1) == 'play' || tokens[0][1] == tui.keys.switch_to_play) {
1387 tui.switch_mode('play');
1388 } else if (tokens[0].slice(1) == 'study' || tokens[0][1] == tui.keys.switch_to_study) {
1389 tui.switch_mode('study');
1390 } else if (tokens[0].slice(1) == 'edit' || tokens[0][1] == tui.keys.switch_to_edit) {
1391 tui.switch_mode('edit');
1392 } else if (tokens[0].slice(1) == 'admin' || tokens[0][1] == tui.keys.switch_to_admin_enter) {
1393 tui.switch_mode('admin_enter');
1394 } else if (tokens[0].slice(1) == 'nick') {
1395 if (tokens.length > 1) {
1396 server.send(['NICK', tokens[1]]);
1398 tui.log_msg('? need new name');
1401 tui.log_msg('? unknown command');
1404 server.send(['ALL', tui.inputEl.value]);
1406 } else if (tui.inputEl.valuelength > 0) {
1407 server.send(['ALL', tui.inputEl.value]);
1409 tui.inputEl.value = "";
1410 } else if (tui.mode.name == 'play') {
1411 if (tui.mode.mode_switch_on_key(event)) {
1413 } else if (event.key === tui.keys.take_thing && tui.task_action_on('take_thing')) {
1414 server.send(["TASK:PICK_UP"]);
1415 } else if (event.key === tui.keys.drop_thing && tui.task_action_on('drop_thing')) {
1416 server.send(["TASK:DROP"]);
1417 } else if (event.key === tui.keys.consume && tui.task_action_on('consume')) {
1418 server.send(["TASK:INTOXICATE"]);
1419 } else if (event.key === tui.keys.door && tui.task_action_on('door')) {
1420 server.send(["TASK:DOOR"]);
1421 } else if (event.key in tui.movement_keys && tui.task_action_on('move')) {
1422 server.send(['TASK:MOVE', tui.movement_keys[event.key]]);
1423 } else if (event.key === tui.keys.teleport) {
1426 } else if (tui.mode.name == 'study') {
1427 if (tui.mode.mode_switch_on_key(event)) {
1429 } else if (event.key in tui.movement_keys) {
1430 explorer.move(tui.movement_keys[event.key]);
1431 } else if (event.key == tui.keys.toggle_map_mode) {
1432 tui.toggle_map_mode();
1434 } else if (tui.mode.name == 'control_tile_draw') {
1435 if (tui.mode.mode_switch_on_key(event)) {
1437 } else if (event.key in tui.movement_keys) {
1438 explorer.move(tui.movement_keys[event.key]);
1439 } else if (event.key === tui.keys.toggle_tile_draw) {
1440 tui.toggle_tile_draw();
1442 } else if (tui.mode.name == 'admin') {
1443 if (tui.mode.mode_switch_on_key(event)) {
1445 } else if (event.key in tui.movement_keys && tui.task_action_on('move')) {
1446 server.send(['TASK:MOVE', tui.movement_keys[event.key]]);
1448 } else if (tui.mode.name == 'edit') {
1449 if (tui.mode.mode_switch_on_key(event)) {
1451 } else if (event.key in tui.movement_keys && tui.task_action_on('move')) {
1452 server.send(['TASK:MOVE', tui.movement_keys[event.key]]);
1453 } else if (event.key === tui.keys.flatten && tui.task_action_on('flatten')) {
1454 server.send(["TASK:FLATTEN_SURROUNDINGS", tui.password]);
1455 } else if (event.key == tui.keys.toggle_map_mode) {
1456 tui.toggle_map_mode();
1462 rows_selector.addEventListener('input', function() {
1463 if (rows_selector.value % 4 != 0 || rows_selector.value < 24) {
1466 window.localStorage.setItem(rows_selector.id, rows_selector.value);
1467 terminal.initialize();
1470 cols_selector.addEventListener('input', function() {
1471 if (cols_selector.value % 4 != 0 || cols_selector.value < 80) {
1474 window.localStorage.setItem(cols_selector.id, cols_selector.value);
1475 terminal.initialize();
1476 tui.window_width = terminal.cols / 2,
1479 for (let key_selector of key_selectors) {
1480 key_selector.addEventListener('input', function() {
1481 window.localStorage.setItem(key_selector.id, key_selector.value);
1485 window.setInterval(function() {
1486 if (server.connected) {
1487 server.send(['PING']);
1489 server.reconnect_to(server.url);
1490 tui.log_msg('@ attempting reconnect …')
1493 window.setInterval(function() {
1495 let span_decoration = "none";
1496 if (document.activeElement == tui.inputEl) {
1497 val = "on (click outside terminal to change)";
1499 val = "off (click into terminal to change)";
1500 span_decoration = "line-through";
1502 document.getElementById("keyboard_control").textContent = val;
1503 for (const span of document.querySelectorAll('.keyboard_controlled')) {
1504 span.style.textDecoration = span_decoration;
1507 document.getElementById("terminal").onclick = function() {
1508 tui.inputEl.focus();
1510 document.getElementById("help").onclick = function() {
1511 tui.show_help = true;
1514 for (const switchEl of document.querySelectorAll('[id^="switch_to_"]')) {
1515 const mode = switchEl.id.slice("switch_to_".length);
1516 switchEl.onclick = function() {
1517 tui.switch_mode(mode);
1521 document.getElementById("toggle_tile_draw").onclick = function() {
1522 tui.toggle_tile_draw();
1524 document.getElementById("toggle_map_mode").onclick = function() {
1525 tui.toggle_map_mode();
1528 document.getElementById("take_thing").onclick = function() {
1529 server.send(['TASK:PICK_UP']);
1531 document.getElementById("drop_thing").onclick = function() {
1532 server.send(['TASK:DROP']);
1534 document.getElementById("flatten").onclick = function() {
1535 server.send(['TASK:FLATTEN_SURROUNDINGS', tui.password]);
1537 document.getElementById("door").onclick = function() {
1538 server.send(['TASK:DOOR']);
1540 document.getElementById("consume").onclick = function() {
1541 server.send(['TASK:INTOXICATE']);
1543 document.getElementById("teleport").onclick = function() {
1546 for (const move_button of document.querySelectorAll('[id*="_move_"]')) {
1547 let direction = move_button.id.split('_')[2].toUpperCase();
1548 move_button.onclick = function() {
1549 if (tui.mode.available_actions.includes("move")
1550 || tui.mode.available_actions.includes("move_explorer")) {
1551 server.send(['TASK:MOVE', direction]);
1553 explorer.move(direction);