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 world 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 protection character. First enter the protection character for which you want to change the password.'
149 'short': 'change protection character password',
150 '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.'
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 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 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 world 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 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.'
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 world edit password',
186 '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.'
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' || this.mode.name == 'admin') {
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 protection character for which you want to change the password:')
733 } else if (this.mode.name == 'control_tile_type') {
734 this.log_msg('@ enter 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 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 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);
817 if (url_ends[0] == i) {
825 if (msg[i] == "\n") {
830 if (msg[i] != "\n") {
833 if (i in link_data) {
837 } else if (url_ends[0] == i) {
839 push_inner_link(y, x);
845 push_inner_link(lines.length - 1, chunk.length);
847 return [lines, inner_links];
849 log_msg: function(msg) {
851 while (this.log.length > 100) {
856 draw_map: function() {
857 let map_lines_split = [];
859 for (let i = 0, j = 0; i < game.map.length; i++, j++) {
860 if (j == game.map_size[1]) {
861 map_lines_split.push(line);
865 if (this.map_mode == 'protections') {
866 line.push(game.map_control[i] + ' ');
868 line.push(game.map[i] + ' ');
871 map_lines_split.push(line);
872 if (this.map_mode == 'terrain + annotations') {
873 for (const coordinate of explorer.info_hints) {
874 map_lines_split[coordinate[0]][coordinate[1]] = 'A ';
876 } else if (this.map_mode == 'terrain + things') {
877 for (const p in game.portals) {
878 let coordinate = p.split(',')
879 let original = map_lines_split[coordinate[0]][coordinate[1]];
880 map_lines_split[coordinate[0]][coordinate[1]] = original[0] + 'P';
882 let used_positions = [];
883 for (const thing_id in game.things) {
884 let t = game.things[thing_id];
885 let symbol = game.thing_types[t.type_];
888 meta_char = t.player_char;
890 if (used_positions.includes(t.position.toString())) {
893 map_lines_split[t.position[0]][t.position[1]] = symbol + meta_char;
894 used_positions.push(t.position.toString());
897 let player = game.things[game.player_id];
898 if (tui.mode.shows_info || tui.mode.name == 'control_tile_draw') {
899 map_lines_split[explorer.position[0]][explorer.position[1]] = '??';
900 } else if (tui.map_mode != 'terrain + things') {
901 map_lines_split[player.position[0]][player.position[1]] = '??';
904 if (game.map_geometry == 'Square') {
905 for (let line_split of map_lines_split) {
906 map_lines.push(line_split.join(''));
908 } else if (game.map_geometry == 'Hex') {
910 for (let line_split of map_lines_split) {
911 map_lines.push(' '.repeat(indent) + line_split.join(''));
919 let window_center = [terminal.rows / 2, this.window_width / 2];
920 let center_position = [player.position[0], player.position[1]];
921 if (tui.mode.shows_info || tui.mode.name == 'control_tile_draw') {
922 center_position = [explorer.position[0], explorer.position[1]];
924 center_position[1] = center_position[1] * 2;
925 let offset = [center_position[0] - window_center[0],
926 center_position[1] - window_center[1]]
927 if (game.map_geometry == 'Hex' && offset[0] % 2) {
930 let term_y = Math.max(0, -offset[0]);
931 let term_x = Math.max(0, -offset[1]);
932 let map_y = Math.max(0, offset[0]);
933 let map_x = Math.max(0, offset[1]);
934 for (; term_y < terminal.rows && map_y < game.map_size[0]; term_y++, map_y++) {
935 let to_draw = map_lines[map_y].slice(map_x, this.window_width + offset[1]);
936 terminal.write(term_y, term_x, to_draw);
939 draw_mode_line: function() {
940 let help = 'hit [' + this.keys.help + '] for help';
941 if (this.mode.has_input_prompt) {
942 help = 'enter /help for help';
944 terminal.write(0, this.window_width, 'MODE: ' + this.mode.short_desc + ' – ' + help);
946 draw_turn_line: function(n) {
947 terminal.write(1, this.window_width, 'TURN: ' + game.turn);
949 draw_history: function() {
950 let log_display_lines = [];
952 let y_offset_in_log = 0;
953 for (let line of this.log) {
954 let [new_lines, link_data] = this.msg_into_lines_of_width(line,
956 log_display_lines = log_display_lines.concat(new_lines);
957 for (const y in link_data) {
958 const rel_y = y_offset_in_log + parseInt(y);
959 log_links[rel_y] = [];
960 for (let link of link_data[y]) {
961 log_links[rel_y].push(link);
964 y_offset_in_log += new_lines.length;
966 let i = log_display_lines.length - 1;
967 for (let y = terminal.rows - 1 - this.height_input;
968 y >= this.height_header && i >= 0;
970 terminal.write(y, this.window_width, log_display_lines[i]);
972 for (const key of Object.keys(log_links)) {
973 if (parseInt(key) <= i) {
974 delete log_links[key];
977 let offset = [terminal.rows - this.height_input - log_display_lines.length,
979 this.offset_links(offset, log_links);
981 draw_info: function() {
982 let [lines, link_data] = this.msg_into_lines_of_width(explorer.get_info(),
984 let offset = [this.height_header, this.window_width];
985 for (let y = offset[0], i = 0; y < terminal.rows && i < lines.length; y++, i++) {
986 terminal.write(y, offset[1], lines[i]);
988 this.offset_links(offset, link_data);
990 draw_input: function() {
991 if (this.mode.has_input_prompt) {
992 for (let y = terminal.rows - this.height_input, i = 0; i < this.input_lines.length; y++, i++) {
993 terminal.write(y, this.window_width, this.input_lines[i]);
997 draw_help: function() {
998 let movement_keys_desc = '';
999 if (!this.mode.is_intro) {
1000 movement_keys_desc = Object.keys(this.movement_keys).join(',');
1002 let content = this.mode.short_desc + " help\n\n" + this.mode.help_intro + "\n\n";
1003 if (this.mode.name == 'play') {
1004 content += "Available actions:\n";
1005 if (game.tasks.includes('MOVE')) {
1006 content += "[" + movement_keys_desc + "] – move player\n";
1008 if (game.tasks.includes('PICK_UP')) {
1009 content += "[" + this.keys.take_thing + "] – pick up thing\n";
1011 if (game.tasks.includes('DROP')) {
1012 content += "[" + this.keys.drop_thing + "] – drop thing\n";
1014 content += "[" + tui.keys.teleport + "] – teleport\n";
1016 } else if (this.mode.name == 'study') {
1017 content += "Available actions:\n";
1018 content += '[' + movement_keys_desc + '] – move question mark\n';
1019 content += '[' + this.keys.toggle_map_mode + '] – toggle map view\n';
1021 } else if (this.mode.name == 'edit') {
1022 content += "Available actions:\n";
1023 if (game.tasks.includes('MOVE')) {
1024 content += "[" + movement_keys_desc + "] – move player\n";
1026 if (game.tasks.includes('FLATTEN_SURROUNDINGS')) {
1027 content += "[" + tui.keys.flatten + "] – flatten surroundings\n";
1029 content += '[' + this.keys.toggle_map_mode + '] – toggle map view\n';
1031 } else if (this.mode.name == 'control_tile_draw') {
1032 content += "Available actions:\n";
1033 content += "[" + tui.keys.toggle_tile_draw + "] – toggle protection character drawing\n";
1035 } else if (this.mode.name == 'chat') {
1036 content += '/nick NAME – re-name yourself to NAME\n';
1037 content += '/' + this.keys.switch_to_play + ' or /play – switch to play mode\n';
1038 content += '/' + this.keys.switch_to_study + ' or /study – switch to study mode\n';
1039 content += '/' + this.keys.switch_to_edit + ' or /edit – switch to map edit mode\n';
1040 content += '/' + this.keys.switch_to_admin_enter + ' or /admin – switch to admin mode\n';
1041 } else if (this.mode.name == 'admin') {
1042 content += "Available actions:\n";
1043 if (game.tasks.includes('MOVE')) {
1044 content += "[" + movement_keys_desc + "] – move player\n";
1048 content += this.mode.list_available_modes();
1050 if (!this.mode.has_input_prompt) {
1051 start_x = this.window_width
1053 terminal.drawBox(0, start_x, terminal.rows, this.window_width);
1054 let [lines, _] = this.msg_into_lines_of_width(content, this.window_width);
1055 for (let y = 0, i = 0; y < terminal.rows && i < lines.length; y++, i++) {
1056 terminal.write(y, start_x, lines[i]);
1059 toggle_tile_draw: function() {
1060 if (tui.tile_draw) {
1061 tui.tile_draw = false;
1063 tui.tile_draw = true;
1066 toggle_map_mode: function() {
1067 if (tui.map_mode == 'terrain only') {
1068 tui.map_mode = 'terrain + annotations';
1069 } else if (tui.map_mode == 'terrain + annotations') {
1070 tui.map_mode = 'terrain + things';
1071 } else if (tui.map_mode == 'terrain + things') {
1072 tui.map_mode = 'protections';
1073 } else if (tui.map_mode == 'protections') {
1074 tui.map_mode = 'terrain only';
1077 full_refresh: function() {
1079 terminal.drawBox(0, 0, terminal.rows, terminal.cols);
1080 this.recalc_input_lines();
1081 if (this.mode.is_intro) {
1082 this.draw_history();
1085 if (game.turn_complete) {
1087 this.draw_turn_line();
1089 this.draw_mode_line();
1090 if (this.mode.shows_info) {
1093 this.draw_history();
1097 if (this.show_help) {
1109 this.map_control = "";
1110 this.map_size = [0,0];
1111 this.player_id = -1;
1115 get_thing: function(id_, create_if_not_found=false) {
1116 if (id_ in game.things) {
1117 return game.things[id_];
1118 } else if (create_if_not_found) {
1119 let t = new Thing([0,0]);
1120 game.things[id_] = t;
1124 move: function(start_position, direction) {
1125 let target = [start_position[0], start_position[1]];
1126 if (direction == 'LEFT') {
1128 } else if (direction == 'RIGHT') {
1130 } else if (game.map_geometry == 'Square') {
1131 if (direction == 'UP') {
1133 } else if (direction == 'DOWN') {
1136 } else if (game.map_geometry == 'Hex') {
1137 let start_indented = start_position[0] % 2;
1138 if (direction == 'UPLEFT') {
1140 if (!start_indented) {
1143 } else if (direction == 'UPRIGHT') {
1145 if (start_indented) {
1148 } else if (direction == 'DOWNLEFT') {
1150 if (!start_indented) {
1153 } else if (direction == 'DOWNRIGHT') {
1155 if (start_indented) {
1160 if (target[0] < 0 || target[1] < 0 ||
1161 target[0] >= this.map_size[0] || target[1] >= this.map_size[1]) {
1166 teleport: function() {
1167 let player = this.get_thing(game.player_id);
1168 if (player.position in this.portals) {
1169 server.reconnect_to(this.portals[player.position]);
1171 terminal.blink_screen();
1172 tui.log_msg('? not standing on portal')
1180 server.init(websocket_location);
1186 move: function(direction) {
1187 let target = game.move(this.position, direction);
1189 this.position = target
1190 if (tui.mode.shows_info) {
1192 } else if (tui.tile_draw) {
1193 this.send_tile_control_command();
1196 terminal.blink_screen();
1199 update_info_db: function(yx, str) {
1200 this.info_db[yx] = str;
1201 if (tui.mode.name == 'study') {
1205 empty_info_db: function() {
1207 this.info_hints = [];
1208 if (tui.mode.name == 'study') {
1212 query_info: function() {
1213 server.send(["GET_ANNOTATION", unparser.to_yx(explorer.position)]);
1215 get_info: function() {
1216 let info = "MAP VIEW: " + tui.map_mode + "\n";
1217 let position_i = this.position[0] * game.map_size[1] + this.position[1];
1218 if (game.fov[position_i] != '.') {
1219 return info + 'outside field of view';
1221 let terrain_char = game.map[position_i]
1222 let terrain_desc = '?'
1223 if (game.terrains[terrain_char]) {
1224 terrain_desc = game.terrains[terrain_char];
1226 info += 'TERRAIN: "' + terrain_char + '" / ' + terrain_desc + "\n";
1227 let protection = game.map_control[position_i];
1228 if (protection == '.') {
1229 protection = 'unprotected';
1231 info += 'PROTECTION: ' + protection + '\n';
1232 for (let t_id in game.things) {
1233 let t = game.things[t_id];
1234 if (t.position[0] == this.position[0] && t.position[1] == this.position[1]) {
1235 let symbol = game.thing_types[t.type_];
1236 let protection = t.protection;
1237 if (protection == '.') {
1238 protection = 'unprotected';
1240 info += "THING: " + t.type_ + " / protection: " + protection + " / " + symbol;
1241 if (t.player_char) {
1242 info += t.player_char;
1245 info += " (" + t.name_ + ")";
1250 if (this.position in game.portals) {
1251 info += "PORTAL: " + game.portals[this.position] + "\n";
1253 if (this.position in this.info_db) {
1254 info += "ANNOTATIONS: " + this.info_db[this.position];
1256 info += 'waiting …';
1260 annotate: function(msg) {
1261 if (msg.length == 0) {
1262 msg = " "; // triggers annotation deletion
1264 server.send(["ANNOTATE", unparser.to_yx(explorer.position), msg, tui.password]);
1266 set_portal: function(msg) {
1267 if (msg.length == 0) {
1268 msg = " "; // triggers portal deletion
1270 server.send(["PORTAL", unparser.to_yx(explorer.position), msg, tui.password]);
1272 send_tile_control_command: function() {
1273 server.send(["SET_TILE_CONTROL", unparser.to_yx(this.position), tui.tile_control_char]);
1277 tui.inputEl.addEventListener('input', (event) => {
1278 if (tui.mode.has_input_prompt) {
1279 let max_length = tui.window_width * terminal.rows - tui.input_prompt.length;
1280 if (tui.inputEl.value.length > max_length) {
1281 tui.inputEl.value = tui.inputEl.value.slice(0, max_length);
1283 } else if (tui.mode.name == 'write' && tui.inputEl.value.length > 0) {
1284 server.send(["TASK:WRITE", tui.inputEl.value[0], tui.password]);
1285 tui.switch_mode('edit');
1289 document.onclick = function() {
1290 tui.show_help = false;
1292 tui.inputEl.addEventListener('keydown', (event) => {
1293 tui.show_help = false;
1294 if (event.key == 'Enter') {
1295 event.preventDefault();
1297 if (tui.mode.has_input_prompt && event.key == 'Enter' && tui.inputEl.value == '/help') {
1298 tui.show_help = true;
1299 tui.inputEl.value = "";
1300 tui.restore_input_values();
1301 } else if (!tui.mode.has_input_prompt && event.key == tui.keys.help
1302 && !tui.mode.is_single_char_entry) {
1303 tui.show_help = true;
1304 } else if (tui.mode.name == 'login' && event.key == 'Enter') {
1305 tui.login_name = tui.inputEl.value;
1306 server.send(['LOGIN', tui.inputEl.value]);
1307 tui.inputEl.value = "";
1308 } else if (tui.mode.name == 'control_pw_pw' && event.key == 'Enter') {
1309 if (tui.inputEl.value.length == 0) {
1310 tui.log_msg('@ aborted');
1312 server.send(['SET_MAP_CONTROL_PASSWORD',
1313 tui.tile_control_char, tui.inputEl.value]);
1314 tui.log_msg('@ sent new password for protection character "' + tui.tile_control_char + '".');
1316 tui.switch_mode('admin');
1317 } else if (tui.mode.name == 'portal' && event.key == 'Enter') {
1318 explorer.set_portal(tui.inputEl.value);
1319 tui.switch_mode('edit');
1320 } else if (tui.mode.name == 'name_thing' && event.key == 'Enter') {
1321 if (tui.inputEl.value.length == 0) {
1322 tui.inputEl.value = " ";
1324 server.send(["THING_NAME", tui.selected_thing_id, tui.inputEl.value,
1326 tui.switch_mode('edit');
1327 } else if (tui.mode.name == 'annotate' && event.key == 'Enter') {
1328 explorer.annotate(tui.inputEl.value);
1329 tui.switch_mode('edit');
1330 } else if (tui.mode.name == 'password' && event.key == 'Enter') {
1331 if (tui.inputEl.value.length == 0) {
1332 tui.inputEl.value = " ";
1334 tui.password = tui.inputEl.value
1335 tui.switch_mode('edit');
1336 } else if (tui.mode.name == 'admin_enter' && event.key == 'Enter') {
1337 server.send(['BECOME_ADMIN', tui.inputEl.value]);
1338 tui.switch_mode('play');
1339 } else if (tui.mode.name == 'control_pw_type' && event.key == 'Enter') {
1340 if (tui.inputEl.value.length != 1) {
1341 tui.log_msg('@ entered non-single-char, therefore aborted');
1342 tui.switch_mode('admin');
1344 tui.tile_control_char = tui.inputEl.value[0];
1345 tui.switch_mode('control_pw_pw');
1347 } else if (tui.mode.name == 'control_tile_type' && event.key == 'Enter') {
1348 if (tui.inputEl.value.length != 1) {
1349 tui.log_msg('@ entered non-single-char, therefore aborted');
1350 tui.switch_mode('admin');
1352 tui.tile_control_char = tui.inputEl.value[0];
1353 tui.switch_mode('control_tile_draw');
1355 } else if (tui.mode.name == 'admin_thing_protect' && event.key == 'Enter') {
1356 if (tui.inputEl.value.length != 1) {
1357 tui.log_msg('@ entered non-single-char, therefore aborted');
1359 server.send(['THING_PROTECTION', tui.selected_thing_id, tui.inputEl.value])
1360 tui.log_msg('@ sent new protection character for thing');
1362 tui.switch_mode('admin');
1363 } else if (tui.mode.name == 'chat' && event.key == 'Enter') {
1364 let tokens = parser.tokenize(tui.inputEl.value);
1365 if (tokens.length > 0 && tokens[0].length > 0) {
1366 if (tui.inputEl.value[0][0] == '/') {
1367 if (tokens[0].slice(1) == 'play' || tokens[0][1] == tui.keys.switch_to_play) {
1368 tui.switch_mode('play');
1369 } else if (tokens[0].slice(1) == 'study' || tokens[0][1] == tui.keys.switch_to_study) {
1370 tui.switch_mode('study');
1371 } else if (tokens[0].slice(1) == 'edit' || tokens[0][1] == tui.keys.switch_to_edit) {
1372 tui.switch_mode('edit');
1373 } else if (tokens[0].slice(1) == 'admin' || tokens[0][1] == tui.keys.switch_to_admin_enter) {
1374 tui.switch_mode('admin_enter');
1375 } else if (tokens[0].slice(1) == 'nick') {
1376 if (tokens.length > 1) {
1377 server.send(['NICK', tokens[1]]);
1379 tui.log_msg('? need new name');
1382 tui.log_msg('? unknown command');
1385 server.send(['ALL', tui.inputEl.value]);
1387 } else if (tui.inputEl.valuelength > 0) {
1388 server.send(['ALL', tui.inputEl.value]);
1390 tui.inputEl.value = "";
1391 } else if (tui.mode.name == 'play') {
1392 if (tui.mode.mode_switch_on_key(event)) {
1394 } else if (event.key === tui.keys.take_thing
1395 && game.tasks.includes('PICK_UP')) {
1396 server.send(["TASK:PICK_UP"]);
1397 } else if (event.key === tui.keys.drop_thing
1398 && game.tasks.includes('DROP')) {
1399 server.send(["TASK:DROP"]);
1400 } else if (event.key in tui.movement_keys
1401 && game.tasks.includes('MOVE')) {
1402 server.send(['TASK:MOVE', tui.movement_keys[event.key]]);
1403 } else if (event.key === tui.keys.teleport) {
1406 } else if (tui.mode.name == 'study') {
1407 if (tui.mode.mode_switch_on_key(event)) {
1409 } else if (event.key in tui.movement_keys) {
1410 explorer.move(tui.movement_keys[event.key]);
1411 } else if (event.key == tui.keys.toggle_map_mode) {
1412 tui.toggle_map_mode();
1414 } else if (tui.mode.name == 'control_tile_draw') {
1415 if (tui.mode.mode_switch_on_key(event)) {
1417 } else if (event.key in tui.movement_keys) {
1418 explorer.move(tui.movement_keys[event.key]);
1419 } else if (event.key === tui.keys.toggle_tile_draw) {
1420 tui.toggle_tile_draw();
1422 } else if (tui.mode.name == 'admin') {
1423 if (tui.mode.mode_switch_on_key(event)) {
1425 } else if (event.key in tui.movement_keys
1426 && game.tasks.includes('MOVE')) {
1427 server.send(['TASK:MOVE', tui.movement_keys[event.key]]);
1429 } else if (tui.mode.name == 'edit') {
1430 if (tui.mode.mode_switch_on_key(event)) {
1432 } else if (event.key in tui.movement_keys
1433 && game.tasks.includes('MOVE')) {
1434 server.send(['TASK:MOVE', tui.movement_keys[event.key]]);
1435 } else if (event.key === tui.keys.flatten
1436 && game.tasks.includes('FLATTEN_SURROUNDINGS')) {
1437 server.send(["TASK:FLATTEN_SURROUNDINGS", tui.password]);
1438 } else if (event.key == tui.keys.toggle_map_mode) {
1439 tui.toggle_map_mode();
1445 rows_selector.addEventListener('input', function() {
1446 if (rows_selector.value % 4 != 0 || rows_selector.value < 24) {
1449 window.localStorage.setItem(rows_selector.id, rows_selector.value);
1450 terminal.initialize();
1453 cols_selector.addEventListener('input', function() {
1454 if (cols_selector.value % 4 != 0 || cols_selector.value < 80) {
1457 window.localStorage.setItem(cols_selector.id, cols_selector.value);
1458 terminal.initialize();
1459 tui.window_width = terminal.cols / 2,
1462 for (let key_selector of key_selectors) {
1463 key_selector.addEventListener('input', function() {
1464 window.localStorage.setItem(key_selector.id, key_selector.value);
1468 window.setInterval(function() {
1469 if (server.connected) {
1470 server.send(['PING']);
1472 server.reconnect_to(server.url);
1473 tui.log_msg('@ attempting reconnect …')
1476 window.setInterval(function() {
1478 if (document.activeElement == tui.inputEl) {
1479 val = "on (click outside terminal to change)";
1481 val = "off (click into terminal to change)";
1483 document.getElementById("keyboard_control").textContent = val;
1485 document.getElementById("terminal").onclick = function() {
1486 tui.inputEl.focus();
1488 document.getElementById("help").onclick = function() {
1489 tui.show_help = true;
1492 for (const switchEl of document.querySelectorAll('[id^="switch_to_"]')) {
1493 const mode = switchEl.id.slice("switch_to_".length);
1494 switchEl.onclick = function() {
1495 tui.switch_mode(mode);
1499 document.getElementById("toggle_tile_draw").onclick = function() {
1500 tui.toggle_tile_draw();
1502 document.getElementById("toggle_map_mode").onclick = function() {
1503 tui.toggle_map_mode();
1506 document.getElementById("take_thing").onclick = function() {
1507 server.send(['TASK:PICK_UP']);
1509 document.getElementById("drop_thing").onclick = function() {
1510 server.send(['TASK:DROP']);
1512 document.getElementById("flatten").onclick = function() {
1513 server.send(['TASK:FLATTEN_SURROUNDINGS', tui.password]);
1515 document.getElementById("teleport").onclick = function() {
1518 document.getElementById("move_upleft").onclick = function() {
1519 if (tui.mode.name == 'play' || tui.mode.name == 'edit') {
1520 server.send(['TASK:MOVE', 'UPLEFT']);
1522 explorer.move('UPLEFT');
1525 document.getElementById("move_left").onclick = function() {
1526 if (tui.mode.name == 'play' || tui.mode.name == 'edit') {
1527 server.send(['TASK:MOVE', 'LEFT']);
1529 explorer.move('LEFT');
1532 document.getElementById("move_downleft").onclick = function() {
1533 if (tui.mode.name == 'play' || tui.mode.name == 'edit') {
1534 server.send(['TASK:MOVE', 'DOWNLEFT']);
1536 explorer.move('DOWNLEFT');
1539 document.getElementById("move_down").onclick = function() {
1540 if (tui.mode.name == 'play' || tui.mode.name == 'edit') {
1541 server.send(['TASK:MOVE', 'DOWN']);
1543 explorer.move('DOWN');
1546 document.getElementById("move_up").onclick = function() {
1547 if (tui.mode.name == 'play' || tui.mode.name == 'edit') {
1548 server.send(['TASK:MOVE', 'UP']);
1550 explorer.move('UP');
1553 document.getElementById("move_upright").onclick = function() {
1554 if (tui.mode.name == 'play' || tui.mode.name == 'edit') {
1555 server.send(['TASK:MOVE', 'UPRIGHT']);
1557 explorer.move('UPRIGHT');
1560 document.getElementById("move_right").onclick = function() {
1561 if (tui.mode.name == 'play' || tui.mode.name == 'edit') {
1562 server.send(['TASK:MOVE', 'RIGHT']);
1564 explorer.move('RIGHT');
1567 document.getElementById("move_downright").onclick = function() {
1568 if (tui.mode.name == 'play' || tui.mode.name == 'edit') {
1569 server.send(['TASK:MOVE', 'DOWNRIGHT']);
1571 explorer.move('DOWNRIGHT');