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 style="float: left">
25 <td style="text-align: right"><button id="move_upleft">up-left</button></td>
26 <td style="text-align: center"><button id="move_up">up</button></td>
27 <td><button id="move_upright">up-right</button></td>
30 <td style="text-align: right;"><button id="move_left">left</button></td>
31 <td stlye="text-align: center;">move</td>
32 <td><button id="move_right">right</button></td>
35 <td><button id="move_downleft">down-left</button></td>
36 <td style="text-align: center"><button id="move_down">down</button></td>
37 <td><button id="move_downright">down-right</button></td>
42 <td><button id="help">help</button></td>
45 <td><button id="switch_to_chat">chat mode</button><br /></td>
48 <td><button id="switch_to_study">study mode</button></td>
49 <td><button id="toggle_map_mode">toggle map view</button>
52 <td><button id="switch_to_play">play mode</button></td>
54 <button id="take_thing">pick up thing</button>
55 <button id="drop_thing">drop thing</button>
56 <button id="teleport">teleport</button>
60 <td><button id="switch_to_edit">map edit mode</button></td>
62 <button id="switch_to_write">change terrain</button>
63 <button id="flatten">flatten surroundings</button>
64 <button id="switch_to_annotate">annotate tile</button>
65 <button id="switch_to_portal">edit portal</button>
66 <button id="switch_to_name_thing">name thing</button>
67 <button id="switch_to_password">enter map edit password</button>
71 <td><button id="switch_to_admin_enter">admin mode</button></td>
73 <button id="switch_to_control_pw_type">change protection character password</button>
74 <button id="switch_to_control_tile_type">change protection areas</button>
75 <button id="switch_to_admin_thing_protect">change thing protection</button>
76 <button id="toggle_tile_draw">toggle protection character drawing</button>
81 <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 />
83 <li>move up (square grid): <input id="key_square_move_up" type="text" value="w" /> (hint: ArrowUp)
84 <li>move left (square grid): <input id="key_square_move_left" type="text" value="a" /> (hint: ArrowLeft)
85 <li>move down (square grid): <input id="key_square_move_down" type="text" value="s" /> (hint: ArrowDown)
86 <li>move right (square grid): <input id="key_square_move_right" type="text" value="d" /> (hint: ArrowRight)
87 <li>move up-left (hex grid): <input id="key_hex_move_upleft" type="text" value="w" />
88 <li>move up-right (hex grid): <input id="key_hex_move_upright" type="text" value="e" />
89 <li>move right (hex grid): <input id="key_hex_move_right" type="text" value="d" />
90 <li>move down-right (hex grid): <input id="key_hex_move_downright" type="text" value="x" />
91 <li>move down-left (hex grid): <input id="key_hex_move_downleft" type="text" value="y" />
92 <li>move left (hex grid): <input id="key_hex_move_left" type="text" value="a" />
93 <li>help: <input id="key_help" type="text" value="h" />
94 <li>flatten surroundings: <input id="key_flatten" type="text" value="F" />
95 <li>teleport: <input id="key_teleport" type="text" value="p" />
96 <li>pick up thing: <input id="key_take_thing" type="text" value="z" />
97 <li>drop thing: <input id="key_drop_thing" type="text" value="u" />
98 <li><input id="key_switch_to_chat" type="text" value="t" />
99 <li><input id="key_switch_to_play" type="text" value="p" />
100 <li><input id="key_switch_to_study" type="text" value="?" />
101 <li><input id="key_switch_to_edit" type="text" value="E" />
102 <li><input id="key_switch_to_write" type="text" value="m" />
103 <li><input id="key_switch_to_name_thing" type="text" value="N" />
104 <li><input id="key_switch_to_password" type="text" value="P" />
105 <li><input id="key_switch_to_admin_enter" type="text" value="A" />
106 <li><input id="key_switch_to_control_pw_type" type="text" value="C" />
107 <li><input id="key_switch_to_control_tile_type" type="text" value="Q" />
108 <li><input id="key_switch_to_admin_thing_protect" type="text" value="T" />
109 <li><input id="key_switch_to_annotate" type="text" value="M" />
110 <li><input id="key_switch_to_portal" type="text" value="T" />
111 <li>toggle map view: <input id="key_toggle_map_mode" type="text" value="L" />
112 <li>toggle protection character drawing: <input id="key_toggle_tile_draw" type="text" value="m" />
117 let websocket_location = "wss://plomlompom.com/rogue_chat/";
118 //let websocket_location = "ws://localhost:8000/";
123 'long': 'This mode allows you to interact with the map in various ways.'
127 '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.'},
129 'short': 'world edit',
130 '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 map edit password that matches its protection character. The character "." marks the absence of protection: Such tiles can always be edited.'
133 'short': 'name thing',
134 'long': 'Give name to/change name of thing here.'
136 'admin_thing_protect': {
137 'short': 'change thing protection',
138 'long': 'Change protection character for thing here.'
141 'short': 'change terrain',
142 'long': 'This mode allows you to change the map tile you currently stand on (if your map editing password authorizes you so). Just enter any printable ASCII character to imprint it on the ground below you.'
145 'short': 'change protection character password',
146 'long': 'This mode is the first of two steps to change the password for a tile protection character. First enter the tile protection character for which you want to change the password.'
149 'short': 'change tiles protection password',
150 'long': 'This mode is the second of two steps to change the password for a tile protection character. Enter the new password for the tile protection character you chose.'
152 'control_tile_type': {
153 'short': 'change tiles protection',
154 'long': 'This mode is the first of two steps to change tile protection areas on the map. First enter the tile tile protection character you want to write.'
156 'control_tile_draw': {
157 'short': 'change tiles protection',
158 '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 tile protection character.'
161 'short': 'annotate tile',
162 'long': 'This mode allows you to add/edit a comment on the tile you are currently standing on (provided your map editing password authorizes you so). Hit Return to leave.'
165 'short': 'edit portal',
166 'long': 'This mode allows you to imprint/edit/remove a teleportation target on the ground you are currently standing on (provided your map editing password authorizes you so). Enter or edit a URL to imprint a teleportation target; enter emptiness to remove a pre-existing teleportation target. Hit Return to leave.'
170 '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:'
174 'long': 'Enter your player name.'
176 'waiting_for_server': {
177 'short': 'waiting for server response',
178 'long': 'Waiting for a server response.'
181 'short': 'waiting for server response',
182 'long': 'Waiting for a server response.'
185 'short': 'set map edit password',
186 'long': 'This mode allows you to change the password that you send to authorize yourself for editing password-protected map tiles. Hit return to confirm and leave.'
189 'short': 'become admin',
190 'long': 'This mode allows you to become admin if you know an admin password.'
194 'long': 'This mode allows you access to actions limited to administrators.'
198 let rows_selector = document.getElementById("n_rows");
199 let cols_selector = document.getElementById("n_cols");
200 let key_selectors = document.querySelectorAll('[id^="key_"]');
202 for (const key_switch_selector of document.querySelectorAll('[id^="key_switch_to_"]')) {
203 const action = key_switch_selector.id.slice("key_switch_to_".length);
204 key_switch_selector.parentNode.prepend(mode_helps[action].short + ': ');
207 function restore_selector_value(selector) {
208 let stored_selection = window.localStorage.getItem(selector.id);
209 if (stored_selection) {
210 selector.value = stored_selection;
213 restore_selector_value(rows_selector);
214 restore_selector_value(cols_selector);
215 for (let key_selector of key_selectors) {
216 restore_selector_value(key_selector);
222 initialize: function() {
223 this.rows = rows_selector.value;
224 this.cols = cols_selector.value;
225 this.pre_el = document.getElementById("terminal");
226 this.pre_el.style.color = this.foreground;
227 this.pre_el.style.backgroundColor = this.background;
230 for (let y = 0, x = 0; y <= this.rows; x++) {
231 if (x == this.cols) {
234 this.content.push(line);
236 if (y == this.rows) {
243 blink_screen: function() {
244 this.pre_el.style.color = this.background;
245 this.pre_el.style.backgroundColor = this.foreground;
247 this.pre_el.style.color = this.foreground;
248 this.pre_el.style.backgroundColor = this.background;
251 refresh: function() {
252 function escapeHTML(str) {
254 replace(/&/g, '&').
255 replace(/</g, '<').
256 replace(/>/g, '>').
257 replace(/'/g, ''').
258 replace(/"/g, '"');
260 let pre_content = '';
261 for (let y = 0; y < this.rows; y++) {
262 let line = this.content[y].join('');
264 if (y in tui.links) {
266 for (let span of tui.links[y]) {
267 chunks.push(escapeHTML(line.slice(start_x, span[0])));
268 chunks.push('<a target="_blank" href="');
269 chunks.push(escapeHTML(span[2]));
271 chunks.push(escapeHTML(line.slice(span[0], span[1])));
275 chunks.push(escapeHTML(line.slice(start_x)));
277 chunks = [escapeHTML(line)];
279 for (const chunk of chunks) {
280 pre_content += chunk;
284 this.pre_el.innerHTML = pre_content;
286 write: function(start_y, start_x, msg) {
287 for (let x = start_x, i = 0; x < this.cols && i < msg.length; x++, i++) {
288 this.content[start_y][x] = msg[i];
291 drawBox: function(start_y, start_x, height, width) {
292 let end_y = start_y + height;
293 let end_x = start_x + width;
294 for (let y = start_y, x = start_x; y < this.rows; x++) {
302 this.content[y][x] = ' ';
306 terminal.initialize();
309 tokenize: function(str) {
314 for (let i = 0; i < str.length; i++) {
320 } else if (c == '\\') {
322 } else if (c == '"') {
327 } else if (c == '"') {
329 } else if (c === ' ') {
330 if (token.length > 0) {
338 if (token.length > 0) {
343 parse_yx: function(position_string) {
344 let coordinate_strings = position_string.split(',')
345 let position = [0, 0];
346 position[0] = parseInt(coordinate_strings[0].slice(2));
347 position[1] = parseInt(coordinate_strings[1].slice(2));
359 init: function(url) {
361 this.websocket = new WebSocket(this.url);
362 this.websocket.onopen = function(event) {
363 server.connected = true;
364 game.thing_types = {};
366 server.send(['TASKS']);
367 server.send(['TERRAINS']);
368 server.send(['THING_TYPES']);
369 tui.log_msg("@ server connected! :)");
370 tui.switch_mode('login');
372 this.websocket.onclose = function(event) {
373 server.connected = false;
374 tui.switch_mode('waiting_for_server');
375 tui.log_msg("@ server disconnected :(");
377 this.websocket.onmessage = this.handle_event;
379 reconnect_to: function(url) {
380 this.websocket.close();
383 send: function(tokens) {
384 this.websocket.send(unparser.untokenize(tokens));
386 handle_event: function(event) {
387 let tokens = parser.tokenize(event.data);
388 if (tokens[0] === 'TURN') {
389 game.turn_complete = false;
390 explorer.empty_info_db();
393 game.turn = parseInt(tokens[1]);
394 } else if (tokens[0] === 'THING') {
395 let t = game.get_thing(tokens[4], true);
396 t.position = parser.parse_yx(tokens[1]);
398 t.protection = tokens[3];
399 } else if (tokens[0] === 'THING_NAME') {
400 let t = game.get_thing(tokens[1], false);
404 } else if (tokens[0] === 'THING_CHAR') {
405 let t = game.get_thing(tokens[1], false);
407 t.player_char = tokens[2];
409 } else if (tokens[0] === 'TASKS') {
410 game.tasks = tokens[1].split(',');
411 tui.mode_write.legal = game.tasks.includes('WRITE');
412 } else if (tokens[0] === 'THING_TYPE') {
413 game.thing_types[tokens[1]] = tokens[2]
414 } else if (tokens[0] === 'TERRAIN') {
415 game.terrains[tokens[1]] = tokens[2]
416 } else if (tokens[0] === 'MAP') {
417 game.map_geometry = tokens[1];
419 game.map_size = parser.parse_yx(tokens[2]);
421 } else if (tokens[0] === 'FOV') {
423 } else if (tokens[0] === 'MAP_CONTROL') {
424 game.map_control = tokens[1]
425 } else if (tokens[0] === 'GAME_STATE_COMPLETE') {
426 game.turn_complete = true;
427 if (tui.mode.name == 'post_login_wait') {
428 tui.switch_mode('play');
429 } else if (tui.mode.name == 'study') {
430 explorer.query_info();
433 } else if (tokens[0] === 'CHAT') {
434 tui.log_msg('# ' + tokens[1], 1);
435 } else if (tokens[0] === 'PLAYER_ID') {
436 game.player_id = parseInt(tokens[1]);
437 } else if (tokens[0] === 'LOGIN_OK') {
438 this.send(['GET_GAMESTATE']);
439 tui.switch_mode('post_login_wait');
440 } else if (tokens[0] === 'ADMIN_OK') {
442 tui.log_msg('@ you now have admin rights');
443 tui.switch_mode('admin');
444 } else if (tokens[0] === 'PORTAL') {
445 let position = parser.parse_yx(tokens[1]);
446 game.portals[position] = tokens[2];
447 } else if (tokens[0] === 'ANNOTATION_HINT') {
448 let position = parser.parse_yx(tokens[1]);
449 explorer.info_hints = explorer.info_hints.concat([position]);
450 } else if (tokens[0] === 'ANNOTATION') {
451 let position = parser.parse_yx(tokens[1]);
452 explorer.update_info_db(position, tokens[2]);
454 } else if (tokens[0] === 'UNHANDLED_INPUT') {
455 tui.log_msg('? unknown command');
456 } else if (tokens[0] === 'PLAY_ERROR') {
457 tui.log_msg('? ' + tokens[1]);
458 terminal.blink_screen();
459 } else if (tokens[0] === 'ARGUMENT_ERROR') {
460 tui.log_msg('? syntax error: ' + tokens[1]);
461 } else if (tokens[0] === 'GAME_ERROR') {
462 tui.log_msg('? game error: ' + tokens[1]);
463 } else if (tokens[0] === 'PONG') {
466 tui.log_msg('? unhandled input: ' + event.data);
472 quote: function(str) {
474 for (let i = 0; i < str.length; i++) {
476 if (['"', '\\'].includes(c)) {
482 return quoted.join('');
484 to_yx: function(yx_coordinate) {
485 return "Y:" + yx_coordinate[0] + ",X:" + yx_coordinate[1];
487 untokenize: function(tokens) {
488 let quoted_tokens = [];
489 for (let token of tokens) {
490 quoted_tokens.push(this.quote(token));
492 return quoted_tokens.join(" ");
497 constructor(name, has_input_prompt=false, shows_info=false,
498 is_intro=false, is_single_char_entry=false) {
500 this.short_desc = mode_helps[name].short;
501 this.available_modes = [];
502 this.has_input_prompt = has_input_prompt;
503 this.shows_info= shows_info;
504 this.is_intro = is_intro;
505 this.help_intro = mode_helps[name].long;
506 this.is_single_char_entry = is_single_char_entry;
509 *iter_available_modes() {
510 for (let mode_name of this.available_modes) {
511 let mode = tui['mode_' + mode_name];
515 let key = tui.keys['switch_to_' + mode.name];
519 list_available_modes() {
521 if (this.available_modes.length > 0) {
522 msg += 'Other modes available from here:\n';
523 for (let [mode, key] of this.iter_available_modes()) {
524 msg += '[' + key + '] – ' + mode.short_desc + '\n';
529 mode_switch_on_key(key_event) {
530 for (let [mode, key] of this.iter_available_modes()) {
531 if (key_event.key == key) {
532 event.preventDefault();
533 tui.switch_mode(mode.name);
545 window_width: terminal.cols / 2,
553 mode_waiting_for_server: new Mode('waiting_for_server',
555 mode_login: new Mode('login', true, false, true),
556 mode_post_login_wait: new Mode('post_login_wait'),
557 mode_chat: new Mode('chat', true),
558 mode_annotate: new Mode('annotate', true, true),
559 mode_play: new Mode('play'),
560 mode_study: new Mode('study', false, true),
561 mode_write: new Mode('write', false, false, false, true),
562 mode_edit: new Mode('edit'),
563 mode_control_pw_type: new Mode('control_pw_type', true),
564 mode_admin_thing_protect: new Mode('admin_thing_protect', true),
565 mode_portal: new Mode('portal', true, true),
566 mode_password: new Mode('password', true),
567 mode_name_thing: new Mode('name_thing', true, true),
568 mode_admin_enter: new Mode('admin_enter', true),
569 mode_admin: new Mode('admin'),
570 mode_control_pw_pw: new Mode('control_pw_pw', true),
571 mode_control_tile_type: new Mode('control_tile_type', true),
572 mode_control_tile_draw: new Mode('control_tile_draw'),
574 this.mode_play.available_modes = ["chat", "study", "edit", "admin_enter"]
575 this.mode_study.available_modes = ["chat", "play", "admin_enter", "edit"]
576 this.mode_admin.available_modes = ["admin_thing_protect", "control_pw_type",
577 "control_tile_type", "chat",
578 "study", "play", "edit"]
579 this.mode_control_tile_draw.available_modes = ["admin_enter"]
580 this.mode_edit.available_modes = ["write", "annotate", "portal", "name_thing",
581 "password", "chat", "study", "play",
583 this.mode = this.mode_waiting_for_server;
584 this.inputEl = document.getElementById("input");
585 this.inputEl.focus();
586 this.recalc_input_lines();
587 this.height_header = this.height_turn_line + this.height_mode_line;
588 this.log_msg("@ waiting for server connection ...");
591 init_keys: function() {
593 for (let key_selector of key_selectors) {
594 this.keys[key_selector.id.slice(4)] = key_selector.value;
596 if (game.map_geometry == 'Square') {
597 this.movement_keys = {
598 [this.keys.square_move_up]: 'UP',
599 [this.keys.square_move_left]: 'LEFT',
600 [this.keys.square_move_down]: 'DOWN',
601 [this.keys.square_move_right]: 'RIGHT'
603 document.getElementById("move_upright").hidden = true;
604 document.getElementById("move_upleft").hidden = true;
605 document.getElementById("move_downright").hidden = true;
606 document.getElementById("move_downleft").hidden = true;
607 document.getElementById("move_up").hidden = false;
608 document.getElementById("move_down").hidden = false;
609 } else if (game.map_geometry == 'Hex') {
610 document.getElementById("move_upright").hidden = false;
611 document.getElementById("move_upleft").hidden = false;
612 document.getElementById("move_downright").hidden = false;
613 document.getElementById("move_downleft").hidden = false;
614 document.getElementById("move_up").hidden = true;
615 document.getElementById("move_down").hidden = true;
616 this.movement_keys = {
617 [this.keys.hex_move_upleft]: 'UPLEFT',
618 [this.keys.hex_move_upright]: 'UPRIGHT',
619 [this.keys.hex_move_right]: 'RIGHT',
620 [this.keys.hex_move_downright]: 'DOWNRIGHT',
621 [this.keys.hex_move_downleft]: 'DOWNLEFT',
622 [this.keys.hex_move_left]: 'LEFT'
626 switch_mode: function(mode_name) {
627 if (this.mode.name == 'control_tile_draw') {
628 tui.log_msg('@ finished tile protection drawing.')
630 this.tile_draw = false;
631 if (mode_name == 'admin_enter' && this.is_admin) {
633 } else if (['name_thing', 'admin_thing_protect'].includes(mode_name)) {
634 let player_position = game.things[game.player_id].position;
636 for (let t_id in game.things) {
637 if (t_id == game.player_id) {
640 let t = game.things[t_id];
641 if (player_position[0] == t.position[0] && player_position[1] == t.position[1]) {
647 terminal.blink_screen();
648 this.log_msg('? not standing over thing');
651 this.selected_thing_id = thing_id;
654 this.mode = this['mode_' + mode_name];
655 if (["control_tile_draw", "control_tile_type", "control_pw_type"].includes(this.mode.name)) {
656 this.map_mode = 'protections';
657 } else if (this.mode.name != "edit") {
658 this.map_mode = 'terrain + things';
660 if (this.mode.has_input_prompt || this.mode.is_single_char_entry) {
661 this.inputEl.focus();
663 if (game.player_id in game.things && (this.mode.shows_info || this.mode.name == 'control_tile_draw')) {
664 explorer.position = game.things[game.player_id].position;
665 if (this.mode.shows_info) {
666 explorer.query_info();
669 this.inputEl.value = "";
670 this.restore_input_values();
671 for (let el of document.getElementsByTagName("button")) {
674 document.getElementById("help").disabled = false;
675 if (this.mode.name == 'play' || this.mode.name == 'study' || this.mode.name == 'control_tile_draw' || this.mode.name == 'edit') {
676 for (const move_key of document.querySelectorAll('[id^="move_"]')) {
677 move_key.disabled = false;
680 if (!this.mode.is_intro && this.mode.name != 'play') {
681 document.getElementById("switch_to_play").disabled = false;
683 if (!this.mode.is_intro && this.mode.name != 'study') {
684 document.getElementById("switch_to_study").disabled = false;
686 if (!this.mode.is_intro && this.mode.name != 'chat') {
687 document.getElementById("switch_to_chat").disabled = false;
689 if (!this.mode.is_intro && this.mode.name != 'edit') {
690 document.getElementById("switch_to_edit").disabled = false;
692 if (!this.mode.is_intro && this.mode.name != 'admin' && this.mode.name != 'admin_enter') {
693 document.getElementById("switch_to_admin_enter").disabled = false;
695 if (this.mode.name == 'login') {
696 if (this.login_name) {
697 server.send(['LOGIN', this.login_name]);
699 this.log_msg("? need login name");
701 } else if (this.mode.name == 'play') {
702 if (game.tasks.includes('PICK_UP')) {
703 document.getElementById("take_thing").disabled = false;
705 if (game.tasks.includes('DROP')) {
706 document.getElementById("drop_thing").disabled = false;
708 if (game.tasks.includes('MOVE')) {
710 document.getElementById("teleport").disabled = false;
711 } else if (this.mode.name == 'edit') {
712 if (game.tasks.includes('FLATTEN_SURROUNDINGS')) {
713 document.getElementById("flatten").disabled = false;
715 document.getElementById("switch_to_annotate").disabled = false;
716 document.getElementById("switch_to_write").disabled = false;
717 document.getElementById("switch_to_portal").disabled = false;
718 document.getElementById("switch_to_password").disabled = false;
719 document.getElementById("switch_to_name_thing").disabled = false;
720 document.getElementById("toggle_map_mode").disabled = false;
721 } else if (this.mode.name == 'admin') {
722 document.getElementById("switch_to_control_pw_type").disabled = false;
723 document.getElementById("switch_to_control_tile_type").disabled = false;
724 document.getElementById("switch_to_admin_thing_protect").disabled = false;
725 } else if (this.mode.name == 'study') {
726 document.getElementById("toggle_map_mode").disabled = false;
727 } else if (this.mode.is_single_char_entry) {
728 this.show_help = true;
729 } else if (this.mode.name == 'admin_enter') {
730 this.log_msg('@ enter admin password:')
731 } else if (this.mode.name == 'control_pw_type') {
732 this.log_msg('@ enter tile protection character for which you want to change the password:')
733 } else if (this.mode.name == 'control_tile_type') {
734 this.log_msg('@ enter tile protection character which you want to draw:')
735 } else if (this.mode.name == 'admin_thing_protect') {
736 this.log_msg('@ enter thing protection character:')
737 } else if (this.mode.name == 'control_pw_pw') {
738 this.log_msg('@ enter tile protection password for "' + this.tile_control_char + '":');
739 } else if (this.mode.name == 'control_tile_draw') {
740 document.getElementById("toggle_tile_draw").disabled = false;
741 this.log_msg('@ can draw tile protection character "' + this.tile_control_char + '", turn drawing on/off with [' + this.keys.toggle_tile_draw + '], finish with [' + this.keys.switch_to_admin_enter + '].')
745 offset_links: function(offset, links) {
746 for (let y in links) {
747 let real_y = offset[0] + parseInt(y);
748 if (!this.links[real_y]) {
749 this.links[real_y] = [];
751 for (let link of links[y]) {
752 const offset_link = [link[0] + offset[1], link[1] + offset[1], link[2]];
753 this.links[real_y].push(offset_link);
757 restore_input_values: function() {
758 if (this.mode.name == 'annotate' && explorer.position in explorer.info_db) {
759 let info = explorer.info_db[explorer.position];
760 if (info != "(none)") {
761 this.inputEl.value = info;
763 } else if (this.mode.name == 'portal' && explorer.position in game.portals) {
764 let portal = game.portals[explorer.position]
765 this.inputEl.value = portal;
766 } else if (this.mode.name == 'password') {
767 this.inputEl.value = this.password;
768 } else if (this.mode.name == 'name_thing') {
769 let t = game.get_thing(this.selected_thing_id);
771 this.inputEl.value = t.name_;
773 } else if (this.mode.name == 'admin_thing_protect') {
774 let t = game.get_thing(this.selected_thing_id);
775 if (t && t.protection) {
776 this.inputEl.value = t.protection;
780 recalc_input_lines: function() {
781 if (this.mode.has_input_prompt) {
783 [this.input_lines, _] = this.msg_into_lines_of_width(this.input_prompt + this.inputEl.value, this.window_width);
785 this.input_lines = [];
787 this.height_input = this.input_lines.length;
789 msg_into_lines_of_width: function(msg, width) {
790 function push_inner_link(y, end_x) {
791 if (!inner_links[y]) {
794 inner_links[y].push([url_start_x, end_x, url]);
796 const matches = msg.matchAll(/https?:\/\/[^\s]+/g)
799 for (const match of matches) {
800 const url = match[0];
801 const url_start = match.index;
802 const url_end = match.index + match[0].length;
803 link_data[url_start] = url;
804 url_ends.push(url_end);
808 let inner_links = {};
812 for (let i = 0, x = 0, y = 0; i < msg.length; i++, x++) {
813 if (x >= width || msg[i] == "\n") {
815 push_inner_link(y, chunk.length);
821 if (msg[i] == "\n") {
826 if (msg[i] != "\n") {
829 if (i in link_data) {
833 } else if (url_ends.includes(i)) {
834 push_inner_link(y, x);
840 push_inner_link(lines.length - 1, chunk.length);
842 return [lines, inner_links];
844 log_msg: function(msg) {
846 while (this.log.length > 100) {
851 draw_map: function() {
852 let map_lines_split = [];
854 for (let i = 0, j = 0; i < game.map.length; i++, j++) {
855 if (j == game.map_size[1]) {
856 map_lines_split.push(line);
860 if (this.map_mode == 'protections') {
861 line.push(game.map_control[i] + ' ');
863 line.push(game.map[i] + ' ');
866 map_lines_split.push(line);
867 if (this.map_mode == 'terrain + annotations') {
868 for (const coordinate of explorer.info_hints) {
869 map_lines_split[coordinate[0]][coordinate[1]] = 'A ';
871 } else if (this.map_mode == 'terrain + things') {
872 for (const p in game.portals) {
873 let coordinate = p.split(',')
874 let original = map_lines_split[coordinate[0]][coordinate[1]];
875 map_lines_split[coordinate[0]][coordinate[1]] = original[0] + 'P';
877 let used_positions = [];
878 for (const thing_id in game.things) {
879 let t = game.things[thing_id];
880 let symbol = game.thing_types[t.type_];
883 meta_char = t.player_char;
885 if (used_positions.includes(t.position.toString())) {
888 map_lines_split[t.position[0]][t.position[1]] = symbol + meta_char;
889 used_positions.push(t.position.toString());
892 let player = game.things[game.player_id];
893 if (tui.mode.shows_info || tui.mode.name == 'control_tile_draw') {
894 map_lines_split[explorer.position[0]][explorer.position[1]] = '??';
895 } else if (tui.map_mode != 'terrain + things') {
896 map_lines_split[player.position[0]][player.position[1]] = '??';
899 if (game.map_geometry == 'Square') {
900 for (let line_split of map_lines_split) {
901 map_lines.push(line_split.join(''));
903 } else if (game.map_geometry == 'Hex') {
905 for (let line_split of map_lines_split) {
906 map_lines.push(' '.repeat(indent) + line_split.join(''));
914 let window_center = [terminal.rows / 2, this.window_width / 2];
915 let center_position = [player.position[0], player.position[1]];
916 if (tui.mode.shows_info || tui.mode.name == 'control_tile_draw') {
917 center_position = [explorer.position[0], explorer.position[1]];
919 center_position[1] = center_position[1] * 2;
920 let offset = [center_position[0] - window_center[0],
921 center_position[1] - window_center[1]]
922 if (game.map_geometry == 'Hex' && offset[0] % 2) {
925 let term_y = Math.max(0, -offset[0]);
926 let term_x = Math.max(0, -offset[1]);
927 let map_y = Math.max(0, offset[0]);
928 let map_x = Math.max(0, offset[1]);
929 for (; term_y < terminal.rows && map_y < game.map_size[0]; term_y++, map_y++) {
930 let to_draw = map_lines[map_y].slice(map_x, this.window_width + offset[1]);
931 terminal.write(term_y, term_x, to_draw);
934 draw_mode_line: function() {
935 let help = 'hit [' + this.keys.help + '] for help';
936 if (this.mode.has_input_prompt) {
937 help = 'enter /help for help';
939 terminal.write(0, this.window_width, 'MODE: ' + this.mode.short_desc + ' – ' + help);
941 draw_turn_line: function(n) {
942 terminal.write(1, this.window_width, 'TURN: ' + game.turn);
944 draw_history: function() {
945 let log_display_lines = [];
947 let y_offset_in_log = 0;
948 for (let line of this.log) {
949 let [new_lines, link_data] = this.msg_into_lines_of_width(line,
951 log_display_lines = log_display_lines.concat(new_lines);
952 for (const y in link_data) {
953 const rel_y = y_offset_in_log + parseInt(y);
954 log_links[rel_y] = [];
955 for (let link of link_data[y]) {
956 log_links[rel_y].push(link);
959 y_offset_in_log += new_lines.length;
961 let i = log_display_lines.length - 1;
962 for (let y = terminal.rows - 1 - this.height_input;
963 y >= this.height_header && i >= 0;
965 terminal.write(y, this.window_width, log_display_lines[i]);
967 for (const key of Object.keys(log_links)) {
968 if (parseInt(key) <= i) {
969 delete log_links[key];
972 let offset = [terminal.rows - this.height_input - log_display_lines.length,
974 this.offset_links(offset, log_links);
976 draw_info: function() {
977 let [lines, link_data] = this.msg_into_lines_of_width(explorer.get_info(),
979 let offset = [this.height_header, this.window_width];
980 for (let y = offset[0], i = 0; y < terminal.rows && i < lines.length; y++, i++) {
981 terminal.write(y, offset[1], lines[i]);
983 this.offset_links(offset, link_data);
985 draw_input: function() {
986 if (this.mode.has_input_prompt) {
987 for (let y = terminal.rows - this.height_input, i = 0; i < this.input_lines.length; y++, i++) {
988 terminal.write(y, this.window_width, this.input_lines[i]);
992 draw_help: function() {
993 let movement_keys_desc = '';
994 if (!this.mode.is_intro) {
995 movement_keys_desc = Object.keys(this.movement_keys).join(',');
997 let content = this.mode.short_desc + " help\n\n" + this.mode.help_intro + "\n\n";
998 if (this.mode.name == 'play') {
999 content += "Available actions:\n";
1000 if (game.tasks.includes('MOVE')) {
1001 content += "[" + movement_keys_desc + "] – move player\n";
1003 if (game.tasks.includes('PICK_UP')) {
1004 content += "[" + this.keys.take_thing + "] – pick up thing\n";
1006 if (game.tasks.includes('DROP')) {
1007 content += "[" + this.keys.drop_thing + "] – drop thing\n";
1009 content += "[" + tui.keys.teleport + "] – teleport\n";
1011 } else if (this.mode.name == 'study') {
1012 content += "Available actions:\n";
1013 content += '[' + movement_keys_desc + '] – move question mark\n';
1014 content += '[' + this.keys.toggle_map_mode + '] – toggle map view\n';
1016 } else if (this.mode.name == 'edit') {
1017 content += "Available actions:\n";
1018 if (game.tasks.includes('MOVE')) {
1019 content += "[" + movement_keys_desc + "] – move player\n";
1021 if (game.tasks.includes('FLATTEN_SURROUNDINGS')) {
1022 content += "[" + tui.keys.flatten + "] – flatten surroundings\n";
1024 content += '[' + this.keys.toggle_map_mode + '] – toggle map view\n';
1026 } else if (this.mode.name == 'control_tile_draw') {
1027 content += "Available actions:\n";
1028 content += "[" + tui.keys.toggle_tile_draw + "] – toggle protection character drawing\n";
1030 } else if (this.mode.name == 'chat') {
1031 content += '/nick NAME – re-name yourself to NAME\n';
1032 content += '/' + this.keys.switch_to_play + ' or /play – switch to play mode\n';
1033 content += '/' + this.keys.switch_to_study + ' or /study – switch to study mode\n';
1034 content += '/' + this.keys.switch_to_edit + ' or /edit – switch to map edit mode\n';
1035 content += '/' + this.keys.switch_to_admin_enter + ' or /admin – switch to admin mode\n';
1037 content += this.mode.list_available_modes();
1039 if (!this.mode.has_input_prompt) {
1040 start_x = this.window_width
1042 terminal.drawBox(0, start_x, terminal.rows, this.window_width);
1043 let [lines, _] = this.msg_into_lines_of_width(content, this.window_width);
1044 for (let y = 0, i = 0; y < terminal.rows && i < lines.length; y++, i++) {
1045 terminal.write(y, start_x, lines[i]);
1048 toggle_tile_draw: function() {
1049 if (tui.tile_draw) {
1050 tui.tile_draw = false;
1052 tui.tile_draw = true;
1055 toggle_map_mode: function() {
1056 if (tui.map_mode == 'terrain only') {
1057 tui.map_mode = 'terrain + annotations';
1058 } else if (tui.map_mode == 'terrain + annotations') {
1059 tui.map_mode = 'terrain + things';
1060 } else if (tui.map_mode == 'terrain + things') {
1061 tui.map_mode = 'protections';
1062 } else if (tui.map_mode == 'protections') {
1063 tui.map_mode = 'terrain only';
1066 full_refresh: function() {
1068 terminal.drawBox(0, 0, terminal.rows, terminal.cols);
1069 this.recalc_input_lines();
1070 if (this.mode.is_intro) {
1071 this.draw_history();
1074 if (game.turn_complete) {
1076 this.draw_turn_line();
1078 this.draw_mode_line();
1079 if (this.mode.shows_info) {
1082 this.draw_history();
1086 if (this.show_help) {
1098 this.map_control = "";
1099 this.map_size = [0,0];
1100 this.player_id = -1;
1104 get_thing: function(id_, create_if_not_found=false) {
1105 if (id_ in game.things) {
1106 return game.things[id_];
1107 } else if (create_if_not_found) {
1108 let t = new Thing([0,0]);
1109 game.things[id_] = t;
1113 move: function(start_position, direction) {
1114 let target = [start_position[0], start_position[1]];
1115 if (direction == 'LEFT') {
1117 } else if (direction == 'RIGHT') {
1119 } else if (game.map_geometry == 'Square') {
1120 if (direction == 'UP') {
1122 } else if (direction == 'DOWN') {
1125 } else if (game.map_geometry == 'Hex') {
1126 let start_indented = start_position[0] % 2;
1127 if (direction == 'UPLEFT') {
1129 if (!start_indented) {
1132 } else if (direction == 'UPRIGHT') {
1134 if (start_indented) {
1137 } else if (direction == 'DOWNLEFT') {
1139 if (!start_indented) {
1142 } else if (direction == 'DOWNRIGHT') {
1144 if (start_indented) {
1149 if (target[0] < 0 || target[1] < 0 ||
1150 target[0] >= this.map_size[0] || target[1] >= this.map_size[1]) {
1155 teleport: function() {
1156 let player = this.get_thing(game.player_id);
1157 if (player.position in this.portals) {
1158 server.reconnect_to(this.portals[player.position]);
1160 terminal.blink_screen();
1161 tui.log_msg('? not standing on portal')
1169 server.init(websocket_location);
1175 move: function(direction) {
1176 let target = game.move(this.position, direction);
1178 this.position = target
1179 if (tui.mode.shows_info) {
1181 } else if (tui.tile_draw) {
1182 this.send_tile_control_command();
1185 terminal.blink_screen();
1188 update_info_db: function(yx, str) {
1189 this.info_db[yx] = str;
1190 if (tui.mode.name == 'study') {
1194 empty_info_db: function() {
1196 this.info_hints = [];
1197 if (tui.mode.name == 'study') {
1201 query_info: function() {
1202 server.send(["GET_ANNOTATION", unparser.to_yx(explorer.position)]);
1204 get_info: function() {
1205 let info = "MAP VIEW: " + tui.map_mode + "\n";
1206 let position_i = this.position[0] * game.map_size[1] + this.position[1];
1207 if (game.fov[position_i] != '.') {
1208 return info + 'outside field of view';
1210 let terrain_char = game.map[position_i]
1211 let terrain_desc = '?'
1212 if (game.terrains[terrain_char]) {
1213 terrain_desc = game.terrains[terrain_char];
1215 info += 'TERRAIN: "' + terrain_char + '" / ' + terrain_desc + "\n";
1216 let protection = game.map_control[position_i];
1217 if (protection == '.') {
1218 protection = 'unprotected';
1220 info += 'PROTECTION: ' + protection + '\n';
1221 for (let t_id in game.things) {
1222 let t = game.things[t_id];
1223 if (t.position[0] == this.position[0] && t.position[1] == this.position[1]) {
1224 let symbol = game.thing_types[t.type_];
1225 let protection = t.protection;
1226 if (protection == '.') {
1227 protection = 'unprotected';
1229 info += "THING: " + t.type_ + " / protection: " + protection + " / " + symbol;
1230 if (t.player_char) {
1231 info += t.player_char;
1234 info += " (" + t.name_ + ")";
1239 if (this.position in game.portals) {
1240 info += "PORTAL: " + game.portals[this.position] + "\n";
1242 if (this.position in this.info_db) {
1243 info += "ANNOTATIONS: " + this.info_db[this.position];
1245 info += 'waiting …';
1249 annotate: function(msg) {
1250 if (msg.length == 0) {
1251 msg = " "; // triggers annotation deletion
1253 server.send(["ANNOTATE", unparser.to_yx(explorer.position), msg, tui.password]);
1255 set_portal: function(msg) {
1256 if (msg.length == 0) {
1257 msg = " "; // triggers portal deletion
1259 server.send(["PORTAL", unparser.to_yx(explorer.position), msg, tui.password]);
1261 send_tile_control_command: function() {
1262 server.send(["SET_TILE_CONTROL", unparser.to_yx(this.position), tui.tile_control_char]);
1266 tui.inputEl.addEventListener('input', (event) => {
1267 if (tui.mode.has_input_prompt) {
1268 let max_length = tui.window_width * terminal.rows - tui.input_prompt.length;
1269 if (tui.inputEl.value.length > max_length) {
1270 tui.inputEl.value = tui.inputEl.value.slice(0, max_length);
1272 } else if (tui.mode.name == 'write' && tui.inputEl.value.length > 0) {
1273 server.send(["TASK:WRITE", tui.inputEl.value[0], tui.password]);
1274 tui.switch_mode('edit');
1278 document.onclick = function() {
1279 tui.show_help = false;
1281 tui.inputEl.addEventListener('keydown', (event) => {
1282 tui.show_help = false;
1283 if (event.key == 'Enter') {
1284 event.preventDefault();
1286 if (tui.mode.has_input_prompt && event.key == 'Enter' && tui.inputEl.value == '/help') {
1287 tui.show_help = true;
1288 tui.inputEl.value = "";
1289 tui.restore_input_values();
1290 } else if (!tui.mode.has_input_prompt && event.key == tui.keys.help
1291 && !tui.mode.is_single_char_entry) {
1292 tui.show_help = true;
1293 } else if (tui.mode.name == 'login' && event.key == 'Enter') {
1294 tui.login_name = tui.inputEl.value;
1295 server.send(['LOGIN', tui.inputEl.value]);
1296 tui.inputEl.value = "";
1297 } else if (tui.mode.name == 'control_pw_pw' && event.key == 'Enter') {
1298 if (tui.inputEl.value.length == 0) {
1299 tui.log_msg('@ aborted');
1301 server.send(['SET_MAP_CONTROL_PASSWORD',
1302 tui.tile_control_char, tui.inputEl.value]);
1303 tui.log_msg('@ sent new password for protection character "' + tui.tile_control_char + '".');
1305 tui.switch_mode('admin');
1306 } else if (tui.mode.name == 'portal' && event.key == 'Enter') {
1307 explorer.set_portal(tui.inputEl.value);
1308 tui.switch_mode('edit');
1309 } else if (tui.mode.name == 'name_thing' && event.key == 'Enter') {
1310 if (tui.inputEl.value.length == 0) {
1311 tui.inputEl.value = " ";
1313 server.send(["THING_NAME", tui.selected_thing_id, tui.inputEl.value,
1315 tui.switch_mode('edit');
1316 } else if (tui.mode.name == 'annotate' && event.key == 'Enter') {
1317 explorer.annotate(tui.inputEl.value);
1318 tui.switch_mode('edit');
1319 } else if (tui.mode.name == 'password' && event.key == 'Enter') {
1320 if (tui.inputEl.value.length == 0) {
1321 tui.inputEl.value = " ";
1323 tui.password = tui.inputEl.value
1324 tui.switch_mode('edit');
1325 } else if (tui.mode.name == 'admin_enter' && event.key == 'Enter') {
1326 server.send(['BECOME_ADMIN', tui.inputEl.value]);
1327 tui.switch_mode('play');
1328 } else if (tui.mode.name == 'control_pw_type' && event.key == 'Enter') {
1329 if (tui.inputEl.value.length != 1) {
1330 tui.log_msg('@ entered non-single-char, therefore aborted');
1331 tui.switch_mode('admin');
1333 tui.tile_control_char = tui.inputEl.value[0];
1334 tui.switch_mode('control_pw_pw');
1336 } else if (tui.mode.name == 'control_tile_type' && event.key == 'Enter') {
1337 if (tui.inputEl.value.length != 1) {
1338 tui.log_msg('@ entered non-single-char, therefore aborted');
1339 tui.switch_mode('admin');
1341 tui.tile_control_char = tui.inputEl.value[0];
1342 tui.switch_mode('control_tile_draw');
1344 } else if (tui.mode.name == 'admin_thing_protect' && event.key == 'Enter') {
1345 if (tui.inputEl.value.length != 1) {
1346 tui.log_msg('@ entered non-single-char, therefore aborted');
1348 server.send(['THING_PROTECTION', tui.selected_thing_id, tui.inputEl.value])
1349 tui.log_msg('@ sent new protection character for thing');
1351 tui.switch_mode('admin');
1352 } else if (tui.mode.name == 'chat' && event.key == 'Enter') {
1353 let tokens = parser.tokenize(tui.inputEl.value);
1354 if (tokens.length > 0 && tokens[0].length > 0) {
1355 if (tui.inputEl.value[0][0] == '/') {
1356 if (tokens[0].slice(1) == 'play' || tokens[0][1] == tui.keys.switch_to_play) {
1357 tui.switch_mode('play');
1358 } else if (tokens[0].slice(1) == 'study' || tokens[0][1] == tui.keys.switch_to_study) {
1359 tui.switch_mode('study');
1360 } else if (tokens[0].slice(1) == 'edit' || tokens[0][1] == tui.keys.switch_to_edit) {
1361 tui.switch_mode('edit');
1362 } else if (tokens[0].slice(1) == 'admin' || tokens[0][1] == tui.keys.switch_to_admin_enter) {
1363 tui.switch_mode('admin_enter');
1364 } else if (tokens[0].slice(1) == 'nick') {
1365 if (tokens.length > 1) {
1366 server.send(['NICK', tokens[1]]);
1368 tui.log_msg('? need new name');
1371 tui.log_msg('? unknown command');
1374 server.send(['ALL', tui.inputEl.value]);
1376 } else if (tui.inputEl.valuelength > 0) {
1377 server.send(['ALL', tui.inputEl.value]);
1379 tui.inputEl.value = "";
1380 } else if (tui.mode.name == 'play') {
1381 if (tui.mode.mode_switch_on_key(event)) {
1383 } else if (event.key === tui.keys.take_thing
1384 && game.tasks.includes('PICK_UP')) {
1385 server.send(["TASK:PICK_UP"]);
1386 } else if (event.key === tui.keys.drop_thing
1387 && game.tasks.includes('DROP')) {
1388 server.send(["TASK:DROP"]);
1389 } else if (event.key in tui.movement_keys
1390 && game.tasks.includes('MOVE')) {
1391 server.send(['TASK:MOVE', tui.movement_keys[event.key]]);
1392 } else if (event.key === tui.keys.teleport) {
1395 } else if (tui.mode.name == 'study') {
1396 if (tui.mode.mode_switch_on_key(event)) {
1398 } else if (event.key in tui.movement_keys) {
1399 explorer.move(tui.movement_keys[event.key]);
1400 } else if (event.key == tui.keys.toggle_map_mode) {
1401 tui.toggle_map_mode();
1403 } else if (tui.mode.name == 'control_tile_draw') {
1404 if (tui.mode.mode_switch_on_key(event)) {
1406 } else if (event.key in tui.movement_keys) {
1407 explorer.move(tui.movement_keys[event.key]);
1408 } else if (event.key === tui.keys.toggle_tile_draw) {
1409 tui.toggle_tile_draw();
1411 } else if (tui.mode.name == 'admin') {
1412 if (tui.mode.mode_switch_on_key(event)) {
1415 } else if (tui.mode.name == 'edit') {
1416 if (tui.mode.mode_switch_on_key(event)) {
1418 } else if (event.key in tui.movement_keys
1419 && game.tasks.includes('MOVE')) {
1420 server.send(['TASK:MOVE', tui.movement_keys[event.key]]);
1421 } else if (event.key === tui.keys.flatten
1422 && game.tasks.includes('FLATTEN_SURROUNDINGS')) {
1423 server.send(["TASK:FLATTEN_SURROUNDINGS", tui.password]);
1424 } else if (event.key == tui.keys.toggle_map_mode) {
1425 tui.toggle_map_mode();
1431 rows_selector.addEventListener('input', function() {
1432 if (rows_selector.value % 4 != 0 || rows_selector.value < 24) {
1435 window.localStorage.setItem(rows_selector.id, rows_selector.value);
1436 terminal.initialize();
1439 cols_selector.addEventListener('input', function() {
1440 if (cols_selector.value % 4 != 0 || cols_selector.value < 80) {
1443 window.localStorage.setItem(cols_selector.id, cols_selector.value);
1444 terminal.initialize();
1445 tui.window_width = terminal.cols / 2,
1448 for (let key_selector of key_selectors) {
1449 key_selector.addEventListener('input', function() {
1450 window.localStorage.setItem(key_selector.id, key_selector.value);
1454 window.setInterval(function() {
1455 if (server.connected) {
1456 server.send(['PING']);
1458 server.reconnect_to(server.url);
1459 tui.log_msg('@ attempting reconnect …')
1462 window.setInterval(function() {
1464 if (document.activeElement == tui.inputEl) {
1465 val = "on (click outside terminal to change)";
1467 val = "off (click into terminal to change)";
1469 document.getElementById("keyboard_control").textContent = val;
1471 document.getElementById("terminal").onclick = function() {
1472 tui.inputEl.focus();
1474 document.getElementById("help").onclick = function() {
1475 tui.show_help = true;
1478 for (const switchEl of document.querySelectorAll('[id^="switch_to_"]')) {
1479 const mode = switchEl.id.slice("switch_to_".length);
1480 switchEl.onclick = function() {
1481 tui.switch_mode(mode);
1485 document.getElementById("toggle_tile_draw").onclick = function() {
1486 tui.toggle_tile_draw();
1488 document.getElementById("toggle_map_mode").onclick = function() {
1489 tui.toggle_map_mode();
1492 document.getElementById("take_thing").onclick = function() {
1493 server.send(['TASK:PICK_UP']);
1495 document.getElementById("drop_thing").onclick = function() {
1496 server.send(['TASK:DROP']);
1498 document.getElementById("flatten").onclick = function() {
1499 server.send(['TASK:FLATTEN_SURROUNDINGS', tui.password]);
1501 document.getElementById("teleport").onclick = function() {
1504 document.getElementById("move_upleft").onclick = function() {
1505 if (tui.mode.name == 'play' || tui.mode.name == 'edit') {
1506 server.send(['TASK:MOVE', 'UPLEFT']);
1508 explorer.move('UPLEFT');
1511 document.getElementById("move_left").onclick = function() {
1512 if (tui.mode.name == 'play' || tui.mode.name == 'edit') {
1513 server.send(['TASK:MOVE', 'LEFT']);
1515 explorer.move('LEFT');
1518 document.getElementById("move_downleft").onclick = function() {
1519 if (tui.mode.name == 'play' || tui.mode.name == 'edit') {
1520 server.send(['TASK:MOVE', 'DOWNLEFT']);
1522 explorer.move('DOWNLEFT');
1525 document.getElementById("move_down").onclick = function() {
1526 if (tui.mode.name == 'play' || tui.mode.name == 'edit') {
1527 server.send(['TASK:MOVE', 'DOWN']);
1529 explorer.move('DOWN');
1532 document.getElementById("move_up").onclick = function() {
1533 if (tui.mode.name == 'play' || tui.mode.name == 'edit') {
1534 server.send(['TASK:MOVE', 'UP']);
1536 explorer.move('UP');
1539 document.getElementById("move_upright").onclick = function() {
1540 if (tui.mode.name == 'play' || tui.mode.name == 'edit') {
1541 server.send(['TASK:MOVE', 'UPRIGHT']);
1543 explorer.move('UPRIGHT');
1546 document.getElementById("move_right").onclick = function() {
1547 if (tui.mode.name == 'play' || tui.mode.name == 'edit') {
1548 server.send(['TASK:MOVE', 'RIGHT']);
1550 explorer.move('RIGHT');
1553 document.getElementById("move_downright").onclick = function() {
1554 if (tui.mode.name == 'play' || tui.mode.name == 'edit') {
1555 server.send(['TASK:MOVE', 'DOWNRIGHT']);
1557 explorer.move('DOWNRIGHT');