home · contact · privacy
In client map drawing, draw Player things last (= on top).
[plomrogue2] / rogue_chat.html
index f245644599457e6e438254732dc181ae49106946..ef87c0f5888b8b449680cd22357bd1c5865da2fe 100644 (file)
@@ -20,60 +20,63 @@ terminal rows: <input id="n_rows" type="number" step=4 min=24 value=24 />
 keyboard input/control: <span id="keyboard_control"></span>
 </div>
 <h3>button controls for mouse players</h3>
-<table style="float: left">
+<table id="move_table" style="float: left">
   <tr>
-    <td style="text-align: right"><button id="hex_move_upleft">up-left</button></td>
-    <td style="text-align: center"><button id="square_move_up">up</button></td>
-    <td><button id="hex_move_upright">up-right</button></td>
+    <td style="text-align: right"><button id="hex_move_upleft"></button></td>
+    <td style="text-align: center"><button id="square_move_up"></button></td>
+    <td><button id="hex_move_upright"></button></td>
   </tr>
   <tr>
-    <td style="text-align: right;"><button id="square_move_left">left</button><button id="hex_move_left">left</button></td>
+    <td style="text-align: right;"><button id="square_move_left"></button><button id="hex_move_left">left</button></td>
     <td stlye="text-align: center;">move</td>
-    <td><button id="square_move_right">right</button><button id="hex_move_right">right</button></td>
+    <td><button id="square_move_right"></button><button id="hex_move_right"></button></td>
   </tr>
   <tr>
-    <td><button id="hex_move_downleft">down-left</button></td>
-    <td style="text-align: center"><button id="square_move_down">down</button></td>
-    <td><button id="hex_move_downright">down-right</button></td>
+    <td><button id="hex_move_downleft"></button></td>
+    <td style="text-align: center"><button id="square_move_down"></button></td>
+    <td><button id="hex_move_downright"></button></td>
   </tr>
 </table>
 <table>
   <tr>
-    <td><button id="help">help</button></td>
+    <td><button id="help"></button></td>
   </tr>
   <tr>
-    <td><button id="switch_to_chat">chat mode</button><br /></td>
+    <td><button id="switch_to_chat"></button><br /></td>
   </tr>
   <tr>
-    <td><button id="switch_to_study">study mode</button></td>
-    <td><button id="toggle_map_mode">toggle map view</button>
+    <td><button id="switch_to_study"></button></td>
+    <td><button id="toggle_map_mode"></button>
   </tr>
   <tr>
-    <td><button id="switch_to_play">play mode</button></td>
+    <td><button id="switch_to_play"></button></td>
     <td>
-      <button id="take_thing">pick up thing</button>
-      <button id="drop_thing">drop thing</button>
-      <button id="teleport">teleport</button>
+      <button id="take_thing"></button>
+      <button id="drop_thing"></button>
+      <button id="door"></button>
+      <button id="consume"></button>
+      <button id="switch_to_command_thing"></button>
+      <button id="teleport"></button>
     </td>
   </tr>
   <tr>
-    <td><button id="switch_to_edit">world edit mode</button></td>
+    <td><button id="switch_to_edit"></button></td>
     <td>
-      <button id="switch_to_write">change terrain</button>
-      <button id="flatten">flatten surroundings</button>
-      <button id="switch_to_annotate">annotate tile</button>
-      <button id="switch_to_portal">edit portal</button>
-      <button id="switch_to_name_thing">name thing</button>
-      <button id="switch_to_password">enter world edit password</button>
+      <button id="switch_to_write"></button>
+      <button id="flatten"></button>
+      <button id="switch_to_annotate"></button>
+      <button id="switch_to_portal"></button>
+      <button id="switch_to_name_thing"></button>
+      <button id="switch_to_password"></button>
     </td>
   </tr>
   <tr>
-    <td><button id="switch_to_admin_enter">admin mode</button></td>
+    <td><button id="switch_to_admin_enter"></button></td>
     <td>
-      <button id="switch_to_control_pw_type">change protection character password</button>
-      <button id="switch_to_control_tile_type">change protection areas</button>
-      <button id="switch_to_admin_thing_protect">change thing protection</button>
-      <button id="toggle_tile_draw">toggle protection character drawing</button>
+      <button id="switch_to_control_pw_type"></button>
+      <button id="switch_to_control_tile_type"></button>
+      <button id="switch_to_admin_thing_protect"></button>
+      <button id="toggle_tile_draw"></button>
     </td>
   <tr>
   </tr>
@@ -95,12 +98,15 @@ keyboard input/control: <span id="keyboard_control"></span>
 <li>teleport: <input id="key_teleport" type="text" value="p" />
 <li>pick up thing: <input id="key_take_thing" type="text" value="z" />
 <li>drop thing: <input id="key_drop_thing" type="text" value="u" />
+<li>open/close: <input id="key_door" type="text" value="D" />
+<li>consume: <input id="key_consume" type="text" value="C" />
 <li><input id="key_switch_to_chat" type="text" value="t" />
 <li><input id="key_switch_to_play" type="text" value="p" />
 <li><input id="key_switch_to_study" type="text" value="?" />
 <li><input id="key_switch_to_edit" type="text" value="E" />
 <li><input id="key_switch_to_write" type="text" value="m" />
 <li><input id="key_switch_to_name_thing" type="text" value="N" />
+<li><input id="key_switch_to_command_thing" type="text" value="O" />
 <li><input id="key_switch_to_password" type="text" value="P" />
 <li><input id="key_switch_to_admin_enter" type="text" value="A" />
 <li><input id="key_switch_to_control_pw_type" type="text" value="C" />
@@ -115,7 +121,7 @@ keyboard input/control: <span id="keyboard_control"></span>
 <script>
 "use strict";
 let websocket_location = "wss://plomlompom.com/rogue_chat/";
-//let websocket_location = "ws://localhost:8000/";
+//let websocket_location = "ws://localhost:8001/";
 
 let mode_helps = {
     'play': {
@@ -133,6 +139,10 @@ let mode_helps = {
         'short': 'name thing',
         'long': 'Give name to/change name of thing here.'
     },
+    'command_thing': {
+        'short': 'command thing',
+        'long': 'Enter a command to the thing you carry.  Enter nothing to return to play mode.'
+    },
     'admin_thing_protect': {
         'short': 'change thing protection',
         'long': 'Change protection character for thing here.'
@@ -200,6 +210,8 @@ let key_descriptions = {
     'teleport': 'teleport',
     'take_thing': 'pick up thing',
     'drop_thing': 'drop thing',
+    'door': 'open/close',
+    'consume': 'consume',
     'toggle_map_mode': 'toggle map view',
     'toggle_tile_draw': 'toggle protection character drawing',
     'hex_move_upleft': 'up-left',
@@ -248,14 +260,12 @@ function escapeHTML(str) {
 };
 
 let terminal = {
-  foreground: 'white',
-  background: 'black',
   initialize: function() {
     this.rows = rows_selector.value;
     this.cols = cols_selector.value;
     this.pre_el = document.getElementById("terminal");
-    this.pre_el.style.color = this.foreground;
-    this.pre_el.style.backgroundColor = this.background;
+    this.set_default_colors();
+    this.apply_colors();
     this.content = [];
       let line = []
     for (let y = 0, x = 0; y <= this.rows; x++) {
@@ -271,6 +281,23 @@ let terminal = {
         line.push(' ');
     }
   },
+  apply_colors: function() {
+    this.pre_el.style.color = this.foreground;
+    this.pre_el.style.backgroundColor = this.background;
+  },
+  set_default_colors: function() {
+      this.foreground = 'white';
+      this.background = 'black';
+      this.apply_colors();
+  },
+  set_random_colors: function() {
+      function rand(offset) {
+          return Math.floor(offset + Math.random() * 96).toString(16).padStart(2, '0');
+      }
+      this.foreground = '#' + rand(159) + rand(159) + rand(159);
+      this.background = '#' + rand(0) + rand(0) + rand(0);
+      this.apply_colors();
+  },
   blink_screen: function() {
       this.pre_el.style.color = this.background;
       this.pre_el.style.backgroundColor = this.foreground;
@@ -410,9 +437,10 @@ let server = {
         let tokens = parser.tokenize(event.data);
         if (tokens[0] === 'TURN') {
             game.turn_complete = false;
-            explorer.empty_info_db();
+            explorer.empty_annotations();
             game.things = {};
             game.portals = {};
+            game.fov = '';
             game.turn = parseInt(tokens[1]);
         } else if (tokens[0] === 'THING') {
             let t = game.get_thing(tokens[4], true);
@@ -427,11 +455,12 @@ let server = {
         } else if (tokens[0] === 'THING_CHAR') {
             let t = game.get_thing(tokens[1], false);
             if (t) {
-                t.player_char = tokens[2];
+                t.thing_char = tokens[2];
             };
         } else if (tokens[0] === 'TASKS') {
             game.tasks = tokens[1].split(',');
             tui.mode_write.legal = game.tasks.includes('WRITE');
+            tui.mode_command_thing.legal = game.tasks.includes('WRITE');
         } else if (tokens[0] === 'THING_TYPE') {
             game.thing_types[tokens[1]] = tokens[2]
         } else if (tokens[0] === 'TERRAIN') {
@@ -449,17 +478,22 @@ let server = {
             game.turn_complete = true;
             if (tui.mode.name == 'post_login_wait') {
                 tui.switch_mode('play');
-            } else if (tui.mode.name == 'study') {
-                explorer.query_info();
             }
+            explorer.info_cached = false;
             tui.full_refresh();
         } else if (tokens[0] === 'CHAT') {
              tui.log_msg('# ' + tokens[1], 1);
+        } else if (tokens[0] === 'REPLY') {
+             tui.log_msg('#MUSICPLAYER: ' + tokens[1], 1);
         } else if (tokens[0] === 'PLAYER_ID') {
             game.player_id = parseInt(tokens[1]);
         } else if (tokens[0] === 'LOGIN_OK') {
             this.send(['GET_GAMESTATE']);
             tui.switch_mode('post_login_wait');
+        } else if (tokens[0] === 'DEFAULT_COLORS') {
+            terminal.set_default_colors();
+        } else if (tokens[0] === 'RANDOM_COLORS') {
+            terminal.set_random_colors();
         } else if (tokens[0] === 'ADMIN_OK') {
             tui.is_admin = true;
             tui.log_msg('@ you now have admin rights');
@@ -467,12 +501,9 @@ let server = {
         } else if (tokens[0] === 'PORTAL') {
             let position = parser.parse_yx(tokens[1]);
             game.portals[position] = tokens[2];
-        } else if (tokens[0] === 'ANNOTATION_HINT') {
-            let position = parser.parse_yx(tokens[1]);
-            explorer.info_hints = explorer.info_hints.concat([position]);
         } else if (tokens[0] === 'ANNOTATION') {
             let position = parser.parse_yx(tokens[1]);
-            explorer.update_info_db(position, tokens[2]);
+            explorer.update_annotations(position, tokens[2]);
             tui.full_refresh();
         } else if (tokens[0] === 'UNHANDLED_INPUT') {
             tui.log_msg('? unknown command');
@@ -589,6 +620,7 @@ let tui = {
   mode_portal: new Mode('portal', true, true),
   mode_password: new Mode('password', true),
   mode_name_thing: new Mode('name_thing', true, true),
+  mode_command_thing: new Mode('command_thing', true),
   mode_admin_enter: new Mode('admin_enter', true),
   mode_admin: new Mode('admin'),
   mode_control_pw_pw: new Mode('control_pw_pw', true),
@@ -599,11 +631,18 @@ let tui = {
       'take_thing': 'PICK_UP',
       'drop_thing': 'DROP',
       'move': 'MOVE',
+      'door': 'DOOR',
+      'command': 'COMMAND',
+      'consume': 'INTOXICATE',
   },
+  offset: [0,0],
+  map_lines: [],
   init: function() {
       this.mode_chat.available_modes = ["play", "study", "edit", "admin_enter"]
-      this.mode_play.available_modes = ["chat", "study", "edit", "admin_enter"]
-      this.mode_play.available_actions = ["move", "take_thing", "drop_thing", "teleport"];
+      this.mode_play.available_modes = ["chat", "study", "edit", "admin_enter",
+                                        "command_thing"]
+      this.mode_play.available_actions = ["move", "take_thing", "drop_thing",
+                                          "teleport", "door", "consume"];
       this.mode_study.available_modes = ["chat", "play", "admin_enter", "edit"]
       this.mode_study.available_actions = ["toggle_map_mode", "move_explorer"];
       this.mode_admin.available_modes = ["admin_thing_protect", "control_pw_type",
@@ -625,15 +664,16 @@ let tui = {
       this.init_keys();
   },
   init_keys: function() {
+    document.getElementById("move_table").hidden = true;
     this.keys = {};
     for (let key_selector of key_selectors) {
         this.keys[key_selector.id.slice(4)] = key_selector.value;
     }
     this.movement_keys = {};
-    if (!game.map_geometry) {
-        return;
+    let geometry_prefix = 'undefinedMapGeometry_';
+    if (game.map_geometry) {
+        geometry_prefix = game.map_geometry.toLowerCase() + '_';
     }
-    let geometry_prefix = game.map_geometry.toLowerCase() + '_';
     for (const key_name of Object.keys(key_descriptions)) {
         if (key_name.startsWith(geometry_prefix)) {
             let direction = key_name.split('_')[2].toUpperCase();
@@ -642,9 +682,13 @@ let tui = {
         }
     };
     for (const move_button of document.querySelectorAll('[id*="_move_"]')) {
+        if (move_button.id.startsWith('key_')) {
+            continue;
+        }
         move_button.hidden = true;
     };
     for (const move_button of document.querySelectorAll('[id^="' + geometry_prefix + 'move_"]')) {
+        document.getElementById("move_table").hidden = false;
         move_button.hidden = false;
     };
     for (let el of document.getElementsByTagName("button")) {
@@ -695,9 +739,6 @@ let tui = {
     }
     if (game.player_id in game.things && (this.mode.shows_info || this.mode.name == 'control_tile_draw')) {
         explorer.position = game.things[game.player_id].position;
-        if (this.mode.shows_info) {
-            explorer.query_info();
-        }
     }
     this.inputEl.value = "";
     this.restore_input_values();
@@ -729,6 +770,8 @@ let tui = {
         }
     } else if (this.mode.is_single_char_entry) {
         this.show_help = true;
+    } else if (this.mode.name == 'command_thing') {
+        server.send(['TASK:COMMAND', 'HELP']);
     } else if (this.mode.name == 'admin_enter') {
         this.log_msg('@ enter admin password:')
     } else if (this.mode.name == 'control_pw_type') {
@@ -757,8 +800,8 @@ let tui = {
       }
   },
   restore_input_values: function() {
-      if (this.mode.name == 'annotate' && explorer.position in explorer.info_db) {
-          let info = explorer.info_db[explorer.position];
+      if (this.mode.name == 'annotate' && explorer.position in explorer.annotations) {
+          let info = explorer.annotations[explorer.position];
           if (info != "(none)") {
               this.inputEl.value = info;
           }
@@ -856,85 +899,101 @@ let tui = {
       this.full_refresh();
   },
   draw_map: function() {
-    let map_lines_split = [];
-    let line = [];
-    for (let i = 0, j = 0; i < game.map.length; i++, j++) {
-        if (j == game.map_size[1]) {
-            map_lines_split.push(line);
-            line = [];
-            j = 0;
+    if (!game.turn_complete && this.map_lines.length == 0) {
+        return;
+    }
+    if (game.turn_complete) {
+        let map_lines_split = [];
+        let line = [];
+        for (let i = 0, j = 0; i < game.map.length; i++, j++) {
+            if (j == game.map_size[1]) {
+                map_lines_split.push(line);
+                line = [];
+                j = 0;
+            };
+            if (this.map_mode == 'protections') {
+                line.push(game.map_control[i] + ' ');
+            } else {
+                line.push(game.map[i] + ' ');
+            }
         };
-        if (this.map_mode == 'protections') {
-            line.push(game.map_control[i] + ' ');
-        } else {
-            line.push(game.map[i] + ' ');
-        }
-    };
-    map_lines_split.push(line);
-    if (this.map_mode == 'terrain + annotations') {
-        for (const coordinate of explorer.info_hints) {
-            map_lines_split[coordinate[0]][coordinate[1]] = 'A ';
+        map_lines_split.push(line);
+        if (this.map_mode == 'terrain + annotations') {
+            for (const coordinate of explorer.info_hints) {
+                map_lines_split[coordinate[0]][coordinate[1]] = 'A ';
+            }
+        } else if (this.map_mode == 'terrain + things') {
+            for (const p in game.portals) {
+                let coordinate = p.split(',')
+                let original = map_lines_split[coordinate[0]][coordinate[1]];
+                map_lines_split[coordinate[0]][coordinate[1]] = original[0] + 'P';
+            }
+            let used_positions = [];
+            function draw_thing(t, used_positions) {
+                let symbol = game.thing_types[t.type_];
+                let meta_char = ' ';
+                if (t.thing_char) {
+                    meta_char = t.thing_char;
+                }
+                if (used_positions.includes(t.position.toString())) {
+                    meta_char = '+';
+                };
+                map_lines_split[t.position[0]][t.position[1]] = symbol + meta_char;
+                used_positions.push(t.position.toString());
+            }
+            for (const thing_id in game.things) {
+                let t = game.things[thing_id];
+                if (t.type_ != 'Player') {
+                    draw_thing(t, used_positions);
+                }
+            };
+            for (const thing_id in game.things) {
+                let t = game.things[thing_id];
+                if (t.type_ == 'Player') {
+                    draw_thing(t, used_positions);
+                }
+            };
         }
-    } else if (this.map_mode == 'terrain + things') {
-        for (const p in game.portals) {
-            let coordinate = p.split(',')
-            let original = map_lines_split[coordinate[0]][coordinate[1]];
-            map_lines_split[coordinate[0]][coordinate[1]] = original[0] + 'P';
+        let player = game.things[game.player_id];
+        if (tui.mode.shows_info || tui.mode.name == 'control_tile_draw') {
+            map_lines_split[explorer.position[0]][explorer.position[1]] = '??';
+        } else if (tui.map_mode != 'terrain + things') {
+            map_lines_split[player.position[0]][player.position[1]] = '??';
         }
-        let used_positions = [];
-        for (const thing_id in game.things) {
-            let t = game.things[thing_id];
-            let symbol = game.thing_types[t.type_];
-            let meta_char = ' ';
-            if (t.player_char) {
-                meta_char = t.player_char;
-            }
-            if (used_positions.includes(t.position.toString())) {
-                meta_char = '+';
+        this.map_lines = []
+        if (game.map_geometry == 'Square') {
+            for (let line_split of map_lines_split) {
+                this.map_lines.push(line_split.join(''));
             };
-            map_lines_split[t.position[0]][t.position[1]] = symbol + meta_char;
-            used_positions.push(t.position.toString());
-        };
-    }
-    let player = game.things[game.player_id];
-    if (tui.mode.shows_info || tui.mode.name == 'control_tile_draw') {
-        map_lines_split[explorer.position[0]][explorer.position[1]] = '??';
-    } else if (tui.map_mode != 'terrain + things') {
-        map_lines_split[player.position[0]][player.position[1]] = '??';
-    }
-    let map_lines = []
-    if (game.map_geometry == 'Square') {
-        for (let line_split of map_lines_split) {
-            map_lines.push(line_split.join(''));
-        };
-    } else if (game.map_geometry == 'Hex') {
-        let indent = 0
-        for (let line_split of map_lines_split) {
-            map_lines.push(' '.repeat(indent) + line_split.join(''));
-            if (indent == 0) {
-                indent = 1;
-            } else {
-                indent = 0;
+        } else if (game.map_geometry == 'Hex') {
+            let indent = 0
+            for (let line_split of map_lines_split) {
+                this.map_lines.push(' '.repeat(indent) + line_split.join(''));
+                if (indent == 0) {
+                    indent = 1;
+                } else {
+                    indent = 0;
+                };
             };
+        }
+        let window_center = [terminal.rows / 2, this.window_width / 2];
+        let center_position = [player.position[0], player.position[1]];
+        if (tui.mode.shows_info || tui.mode.name == 'control_tile_draw') {
+            center_position = [explorer.position[0], explorer.position[1]];
+        }
+        center_position[1] = center_position[1] * 2;
+        this.offset = [center_position[0] - window_center[0],
+                       center_position[1] - window_center[1]]
+        if (game.map_geometry == 'Hex' && this.offset[0] % 2) {
+            this.offset[1] += 1;
         };
-    }
-    let window_center = [terminal.rows / 2, this.window_width / 2];
-    let center_position = [player.position[0], player.position[1]];
-    if (tui.mode.shows_info || tui.mode.name == 'control_tile_draw') {
-        center_position = [explorer.position[0], explorer.position[1]];
-    }
-    center_position[1] = center_position[1] * 2;
-    let offset = [center_position[0] - window_center[0],
-                  center_position[1] - window_center[1]]
-    if (game.map_geometry == 'Hex' && offset[0] % 2) {
-        offset[1] += 1;
     };
-    let term_y = Math.max(0, -offset[0]);
-    let term_x = Math.max(0, -offset[1]);
-    let map_y = Math.max(0, offset[0]);
-    let map_x = Math.max(0, offset[1]);
+    let term_y = Math.max(0, -this.offset[0]);
+    let term_x = Math.max(0, -this.offset[1]);
+    let map_y = Math.max(0, this.offset[0]);
+    let map_x = Math.max(0, this.offset[1]);
     for (; term_y < terminal.rows && map_y < game.map_size[0]; term_y++, map_y++) {
-        let to_draw = map_lines[map_y].slice(map_x, this.window_width + offset[1]);
+        let to_draw = this.map_lines[map_y].slice(map_x, this.window_width + this.offset[1]);
         terminal.write(term_y, term_x, to_draw);
     }
   },
@@ -946,7 +1005,9 @@ let tui = {
       terminal.write(0, this.window_width, 'MODE: ' + this.mode.short_desc + ' – ' + help);
   },
   draw_turn_line: function(n) {
-    terminal.write(1, this.window_width, 'TURN: ' + game.turn);
+      if (game.turn_complete) {
+          terminal.write(1, this.window_width, 'TURN: ' + game.turn);
+      }
   },
   draw_history: function() {
       let log_display_lines = [];
@@ -981,8 +1042,8 @@ let tui = {
       this.offset_links(offset, log_links);
   },
   draw_info: function() {
-      let [lines, link_data] = this.msg_into_lines_of_width(explorer.get_info(),
-                                                            this.window_width);
+      const info = "MAP VIEW: " + tui.map_mode + "\n" + explorer.get_info();
+      let [lines, link_data] = this.msg_into_lines_of_width(info, this.window_width);
       let offset = [this.height_header, this.window_width];
       for (let y = offset[0], i = 0; y < terminal.rows && i < lines.length; y++, i++) {
         terminal.write(y, offset[1], lines[i]);
@@ -1064,10 +1125,8 @@ let tui = {
         this.draw_history();
         this.draw_input();
     } else {
-        if (game.turn_complete) {
-            this.draw_map();
-            this.draw_turn_line();
-        }
+        this.draw_map();
+        this.draw_turn_line();
         this.draw_mode_line();
         if (this.mode.shows_info) {
           this.draw_info();
@@ -1163,81 +1222,79 @@ server.init(websocket_location);
 
 let explorer = {
     position: [0,0],
-    info_db: {},
-    info_hints: [],
+    annotations: {},
+    info_cached: false,
     move: function(direction) {
         let target = game.move(this.position, direction);
         if (target) {
             this.position = target
-            if (tui.mode.shows_info) {
-                this.query_info();
-            } else if (tui.tile_draw) {
+            this.info_cached = false;
+            if (tui.tile_draw) {
                 this.send_tile_control_command();
             }
         } else {
             terminal.blink_screen();
         };
     },
-    update_info_db: function(yx, str) {
-        this.info_db[yx] = str;
+    update_annotations: function(yx, str) {
+        this.annotations[yx] = str;
         if (tui.mode.name == 'study') {
             tui.full_refresh();
         }
     },
-    empty_info_db: function() {
-        this.info_db = {};
-        this.info_hints = [];
+    empty_annotations: function() {
+        this.annotations = {};
         if (tui.mode.name == 'study') {
             tui.full_refresh();
         }
     },
-    query_info: function() {
-        server.send(["GET_ANNOTATION", unparser.to_yx(explorer.position)]);
-    },
     get_info: function() {
-        let info = "MAP VIEW: " + tui.map_mode + "\n";
+        if (this.info_cached) {
+            return this.info_cached;
+        }
+        let info_to_cache = '';
         let position_i = this.position[0] * game.map_size[1] + this.position[1];
         if (game.fov[position_i] != '.') {
-            return info + 'outside field of view';
-        };
-        let terrain_char = game.map[position_i]
-        let terrain_desc = '?'
-        if (game.terrains[terrain_char]) {
-            terrain_desc = game.terrains[terrain_char];
-        };
-        info += 'TERRAIN: "' + terrain_char + '" / ' + terrain_desc + "\n";
-        let protection = game.map_control[position_i];
-        if (protection == '.') {
-            protection = 'unprotected';
-        };
-        info += 'PROTECTION: ' + protection + '\n';
-        for (let t_id in game.things) {
-             let t = game.things[t_id];
-             if (t.position[0] == this.position[0] && t.position[1] == this.position[1]) {
-                 let symbol = game.thing_types[t.type_];
-                 let protection = t.protection;
-                 if (protection == '.') {
-                     protection = 'unprotected';
-                 }
-                 info += "THING: " + t.type_ + " / protection: " + protection + " / " + symbol;
-                 if (t.player_char) {
-                     info += t.player_char;
-                 };
-                 if (t.name_) {
-                     info += " (" + t.name_ + ")";
-                 }
-                 info += "\n";
-             }
-        }
-        if (this.position in game.portals) {
-            info += "PORTAL: " + game.portals[this.position] + "\n";
-        }
-        if (this.position in this.info_db) {
-            info += "ANNOTATIONS: " + this.info_db[this.position];
+            info_to_cache += 'outside field of view';
         } else {
-            info += 'waiting …';
+            let terrain_char = game.map[position_i]
+            let terrain_desc = '?'
+            if (game.terrains[terrain_char]) {
+                terrain_desc = game.terrains[terrain_char];
+            };
+            info_to_cache += 'TERRAIN: "' + terrain_char + '" / ' + terrain_desc + "\n";
+            let protection = game.map_control[position_i];
+            if (protection == '.') {
+                protection = 'unprotected';
+            };
+            info_to_cache += 'PROTECTION: ' + protection + '\n';
+            for (let t_id in game.things) {
+                 let t = game.things[t_id];
+                 if (t.position[0] == this.position[0] && t.position[1] == this.position[1]) {
+                     let symbol = game.thing_types[t.type_];
+                     let protection = t.protection;
+                     if (protection == '.') {
+                         protection = 'none';
+                     }
+                     info_to_cache += "THING: " + t.type_ + " / " + symbol;
+                     if (t.thing_char) {
+                         info_to_cache += t.thing_char;
+                     };
+                     if (t.name_) {
+                         info_to_cache += " (" + t.name_ + ")";
+                     }
+                     info_to_cache += " / protection: " + protection + "\n";
+                 }
+            }
+            if (this.position in game.portals) {
+                info_to_cache += "PORTAL: " + game.portals[this.position] + "\n";
+            }
+            if (this.position in this.annotations) {
+                info_to_cache += "ANNOTATION: " + this.annotations[this.position];
+            }
         }
-        return info;
+        this.info_cached = info_to_cache;
+        return this.info_cached;
     },
     annotate: function(msg) {
         if (msg.length == 0) {
@@ -1287,6 +1344,14 @@ tui.inputEl.addEventListener('keydown', (event) => {
         tui.login_name = tui.inputEl.value;
         server.send(['LOGIN', tui.inputEl.value]);
         tui.inputEl.value = "";
+    } else if (tui.mode.name == 'command_thing' && event.key == 'Enter') {
+        if (tui.inputEl.value.length == 0) {
+            tui.log_msg('@ aborted');
+            tui.switch_mode('play');
+        } else if (tui.task_action_on('command')) {
+            server.send(['TASK:COMMAND', tui.inputEl.value]);
+            tui.inputEl.value = "";
+        }
     } else if (tui.mode.name == 'control_pw_pw' && event.key == 'Enter') {
         if (tui.inputEl.value.length == 0) {
             tui.log_msg('@ aborted');
@@ -1377,6 +1442,10 @@ tui.inputEl.addEventListener('keydown', (event) => {
               server.send(["TASK:PICK_UP"]);
           } else if (event.key === tui.keys.drop_thing && tui.task_action_on('drop_thing')) {
               server.send(["TASK:DROP"]);
+          } else if (event.key === tui.keys.consume && tui.task_action_on('consume')) {
+              server.send(["TASK:INTOXICATE"]);
+          } else if (event.key === tui.keys.door && tui.task_action_on('door')) {
+              server.send(["TASK:DOOR"]);
           } else if (event.key in tui.movement_keys && tui.task_action_on('move')) {
               server.send(['TASK:MOVE', tui.movement_keys[event.key]]);
           } else if (event.key === tui.keys.teleport) {
@@ -1493,6 +1562,12 @@ document.getElementById("drop_thing").onclick = function() {
 document.getElementById("flatten").onclick = function() {
     server.send(['TASK:FLATTEN_SURROUNDINGS', tui.password]);
 };
+document.getElementById("door").onclick = function() {
+    server.send(['TASK:DOOR']);
+};
+document.getElementById("consume").onclick = function() {
+    server.send(['TASK:INTOXICATE']);
+};
 document.getElementById("teleport").onclick = function() {
     game.teleport();
 };