home · contact · privacy
Add terrain editing access control via passwords.
authorChristian Heller <c.heller@plomlompom.de>
Tue, 10 Nov 2020 02:20:56 +0000 (03:20 +0100)
committerChristian Heller <c.heller@plomlompom.de>
Tue, 10 Nov 2020 02:20:56 +0000 (03:20 +0100)
config.json
plomrogue/commands.py
plomrogue/game.py
plomrogue/parser.py
plomrogue/tasks.py
rogue_chat.py
rogue_chat_curses.py
rogue_chat_nocanvas_monochrome.html

index c594191307c344f9457838706cb0bae1adc5374d..be3072cd9e7942d74a2c6ea705b08a09e0476f27 100644 (file)
@@ -6,6 +6,7 @@
     "switch_to_study": "?",
     "switch_to_edit": "m",
     "flatten": "F",
+    "toggle_map_mode": "M",
     "hex_move_upleft": "w",
     "hex_move_upright": "e",
     "hex_move_right": "d",
index e75cbe9001f495a0177d65ee0859d148486e897f..f8c8e3aa5aa139d3ece7746abe37162ed7ad58f9 100644 (file)
@@ -105,3 +105,11 @@ def cmd_MAP(game, geometry, size):
     map_geometry_class = globals()['MapGeometry' + geometry]
     game.new_world(map_geometry_class(size))
 cmd_MAP.argtypes = 'string:map_geometry yx_tuple:pos'
+
+def cmd_MAP_CONTROL_LINE(game, y, line):
+    game.map_control.set_line(y, line)
+cmd_MAP_CONTROL_LINE.argtypes = 'int:nonneg string'
+
+def cmd_MAP_CONTROL_PW(game, tile_class, password):
+    game.map_control_passwords[tile_class] = password
+cmd_MAP_CONTROL_PW.argtypes = 'char string'
index 68bcb65ccd98ff456512a97071b541addc637b6f..accf35deebc2c3b41947f7503b44c9c635874dec 100755 (executable)
@@ -49,6 +49,8 @@ class Game(GameBase):
         self.thing_types = {'player': ThingPlayer}
         self.sessions = {}
         self.map = Map(self.map_geometry.size)
+        self.map_control = Map(self.map_geometry.size)
+        self.map_control_passwords = {}
         self.annotations = {}
         self.portals = {}
         if os.path.exists(self.io.save_file):
@@ -98,6 +100,7 @@ class Game(GameBase):
             send_thing(t)
         self.io.send('MAP %s %s %s' % (self.get_map_geometry_shape(),
                                        self.map_geometry.size, quote(self.map.terrain)))
+        self.io.send('MAP_CONTROL %s' % quote(self.map_control.terrain))
         for yx in self.portals:
             self.io.send('PORTAL %s %s' % (yx, quote(self.portals[yx])))
         self.io.send('GAME_STATE_COMPLETE')
@@ -196,8 +199,14 @@ class Game(GameBase):
               write(f, 'ANNOTATE %s %s' % (yx, quote(self.annotations[yx])))
           for yx in self.portals:
               write(f, 'PORTAL %s %s' % (yx, quote(self.portals[yx])))
+          for y, line in self.map_control.lines():
+              write(f, 'MAP_CONTROL_LINE %5s %s' % (y, quote(line)))
+          for tile_class in self.map_control_passwords:
+              write(f, 'MAP_CONTROL_PW %s %s' % (tile_class,
+                                                 self.map_control_passwords[tile_class]))
 
     def new_world(self, map_geometry):
         self.map_geometry = map_geometry
         self.map = Map(self.map_geometry.size)
+        self.map_control = Map(self.map_geometry.size)
         self.annotations = {}
index 5782d695fe1f026d4dc2b00f422cda389fae9988..6a6aaaaa59087c2f5a240efbff92ec4025bbc752 100644 (file)
@@ -106,6 +106,12 @@ class Parser:
                 if not arg.isdigit():
                     raise ArgError('Argument must be non-negative integer.')
                 args += [int(arg)]
+            elif tmpl == 'char':
+                try:
+                    ord(arg)
+                except TypeError:
+                    raise ArgError('Argument must be single character.')
+                args += [arg]
             elif tmpl == 'yx_tuple:nonneg':
                 args += [self.parse_yx_tuple(arg, 'nonneg')]
             elif tmpl == 'yx_tuple:pos':
index 99bb41f516249e81dc3470caa09444e18c21047d..55669eccb85e6f4ae738f566d8c96b9761debe03 100644 (file)
@@ -1,4 +1,4 @@
-from plomrogue.errors import PlayError
+from plomrogue.errors import PlayError, GameError
 from plomrogue.mapping import YX
 
 
@@ -48,10 +48,14 @@ class Task_MOVE(Task):
 
 class Task_WRITE(Task):
     todo = 1
-    argtypes = 'string:char'
+    argtypes = 'string:char string'
 
     def check(self):
-        pass
+        tile_class = self.thing.game.map_control[self.thing.position]
+        if tile_class in self.thing.game.map_control_passwords:
+            tile_pw = self.thing.game.map_control_passwords[tile_class]
+            if self.args[1] != tile_pw:
+                raise GameError('wrong password for tile')
 
     def do(self):
         self.thing.game.map[self.thing.position] = self.args[0]
index d2e41177049c8ed6371e79b3d8ec75169796d430..aebeffbf7c2186da680734d099e31c7f2b0018b3 100755 (executable)
@@ -5,7 +5,7 @@ from plomrogue.io_tcp import PlomTCPServer
 from plomrogue.commands import (cmd_ALL, cmd_LOGIN, cmd_NICK, cmd_QUERY, cmd_PING,
                                 cmd_MAP, cmd_TURN, cmd_MAP_LINE, cmd_GET_ANNOTATION,
                                 cmd_ANNOTATE, cmd_PORTAL, cmd_GET_GAMESTATE,
-                                cmd_TASKS)
+                                cmd_TASKS, cmd_MAP_CONTROL_LINE, cmd_MAP_CONTROL_PW)
 from plomrogue.tasks import (Task_WAIT, Task_MOVE, Task_WRITE,
                              Task_FLATTEN_SURROUNDINGS)
 import sys
@@ -22,6 +22,8 @@ game.register_command(cmd_QUERY)
 game.register_command(cmd_TURN)
 game.register_command(cmd_MAP)
 game.register_command(cmd_MAP_LINE)
+game.register_command(cmd_MAP_CONTROL_LINE)
+game.register_command(cmd_MAP_CONTROL_PW)
 game.register_command(cmd_GET_ANNOTATION)
 game.register_command(cmd_ANNOTATE)
 game.register_command(cmd_PORTAL)
index 64eaf03959436cc768af00c0b323a7ab66f41259..bf5ad1f65fcb103d7dbad0e5b4a84a913e2a5b55 100755 (executable)
@@ -103,6 +103,10 @@ def cmd_MAP(game, geometry, size, content):
         }
 cmd_MAP.argtypes = 'string:map_geometry yx_tuple:pos string'
 
+def cmd_MAP_CONTROL(game, content):
+    game.map_control_content = content
+cmd_MAP_CONTROL.argtypes = 'string'
+
 def cmd_GAME_STATE_COMPLETE(game):
     game.info_db = {}
     if game.tui.mode.name == 'post_login_wait':
@@ -166,6 +170,7 @@ class Game(GameBase):
         self.register_command(cmd_THING_POS)
         self.register_command(cmd_THING_NAME)
         self.register_command(cmd_MAP)
+        self.register_command(cmd_MAP_CONTROL)
         self.register_command(cmd_PORTAL)
         self.register_command(cmd_ANNOTATION)
         self.register_command(cmd_GAME_STATE_COMPLETE)
@@ -214,6 +219,7 @@ class TUI:
         self.mode_login = self.Mode('login', has_input_prompt=True, is_intro=True)
         self.mode_post_login_wait = self.Mode('post_login_wait', is_intro=True)
         self.mode_teleport = self.Mode('teleport', has_input_prompt=True)
+        self.mode_password = self.Mode('password', has_input_prompt=True)
         self.game = Game()
         self.game.tui = self
         self.parser = Parser(self.game)
@@ -221,15 +227,19 @@ class TUI:
         self.do_refresh = True
         self.queue = queue.Queue()
         self.login_name = None
+        self.map_mode = 'terrain'
+        self.password = 'foo'
         self.switch_mode('waiting_for_server')
         self.keys = {
             'switch_to_chat': 't',
             'switch_to_play': 'p',
+            'switch_to_password': 'p',
             'switch_to_annotate': 'm',
             'switch_to_portal': 'P',
             'switch_to_study': '?',
             'switch_to_edit': 'm',
             'flatten': 'F',
+            'toggle_map_mode': 'M',
             'hex_move_upleft': 'w',
             'hex_move_upright': 'e',
             'hex_move_right': 'd',
@@ -269,6 +279,7 @@ class TUI:
         self.send('GET_ANNOTATION ' + str(self.explorer))
 
     def switch_mode(self, mode_name, keep_position = False):
+        self.map_mode = 'terrain'
         self.mode = getattr(self, 'mode_' + mode_name)
         if self.mode.shows_info and not keep_position:
             player = self.game.get_thing(self.game.player_id, False)
@@ -289,6 +300,8 @@ class TUI:
                 self.input_ = info
         elif self.mode.name == 'portal' and self.explorer in self.game.portals:
             self.input_ = self.game.portals[self.explorer]
+        elif self.mode.name == 'password':
+            self.input_ = self.password
 
     def help(self):
         self.log_msg("HELP:");
@@ -303,16 +316,18 @@ class TUI:
             self.log_msg("  %s - move" % ','.join(self.movement_keys));
         self.log_msg("  %s - switch to chat mode" % self.keys['switch_to_chat']);
         self.log_msg("commands specific to play mode:");
+        self.log_msg("  %s - enter terrain password" % self.keys['switch_to_password']);
         if 'WRITE' in self.game.tasks:
             self.log_msg("  %s - write following ASCII character" % self.keys['switch_to_edit']);
         if 'FLATTEN_SURROUNDINGS' in self.game.tasks:
             self.log_msg("  %s - flatten surroundings" % self.keys['flatten']);
         self.log_msg("  %s - switch to study mode" % self.keys['switch_to_study']);
         self.log_msg("commands specific to study mode:");
+        self.log_msg("  %s - switch to play mode" % self.keys['switch_to_play']);
         if 'MOVE' not in self.game.tasks:
             self.log_msg("  %s - move" % ','.join(self.movement_keys));
         self.log_msg("  %s - annotate terrain" % self.keys['switch_to_annotate']);
-        self.log_msg("  %s - switch to play mode" % self.keys['switch_to_play']);
+        self.log_msg("  %s - toggle terrain/control view" % self.keys['toggle_map_mode']);
 
     def loop(self, stdscr):
         import time
@@ -455,12 +470,16 @@ class TUI:
             if not self.game.turn_complete:
                 return
             map_lines_split = []
+            map_content = self.game.map_content
+            if self.map_mode == 'control':
+                map_content = self.game.map_control_content
             for y in range(self.game.map_geometry.size.y):
                 start = self.game.map_geometry.size.x * y
                 end = start + self.game.map_geometry.size.x
-                map_lines_split += [list(self.game.map_content[start:end])]
-            for t in self.game.things:
-                map_lines_split[t.position.y][t.position.x] = '@'
+                map_lines_split += [list(map_content[start:end])]
+            if self.map_mode == 'terrain':
+                for t in self.game.things:
+                    map_lines_split[t.position.y][t.position.x] = '@'
             if self.mode.shows_info:
                 map_lines_split[self.explorer.y][self.explorer.x] = '?'
             map_lines = []
@@ -548,6 +567,12 @@ class TUI:
                 self.login_name = self.input_
                 self.send('LOGIN ' + quote(self.input_))
                 self.input_ = ""
+            elif self.mode == self.mode_password and key == '\n':
+                if self.input_ == '':
+                    self.input_ = ' '
+                self.password = self.input_
+                self.input_ = ""
+                self.switch_mode('play')
             elif self.mode == self.mode_chat and key == '\n':
                 if self.input_[0] == '/':
                     if self.input_ in {'/' + self.keys['switch_to_play'], '/play'}:
@@ -605,6 +630,11 @@ class TUI:
                     self.switch_mode('annotate', keep_position=True)
                 elif key == self.keys['switch_to_portal']:
                     self.switch_mode('portal', keep_position=True)
+                elif key == self.keys['toggle_map_mode']:
+                    if self.map_mode == 'terrain':
+                        self.map_mode = 'control'
+                    else:
+                        self.map_mode = 'terrain'
                 elif key in self.movement_keys:
                     move_explorer(self.movement_keys[key])
             elif self.mode == self.mode_play:
@@ -612,6 +642,8 @@ class TUI:
                     self.switch_mode('chat')
                 elif key == self.keys['switch_to_study']:
                     self.switch_mode('study')
+                elif key == self.keys['switch_to_password']:
+                    self.switch_mode('password')
                 if key == self.keys['switch_to_edit'] and\
                    'WRITE' in self.game.tasks:
                     self.switch_mode('edit')
@@ -621,7 +653,7 @@ class TUI:
                 elif key in self.movement_keys and 'MOVE' in self.game.tasks:
                     self.send('TASK:MOVE ' + self.movement_keys[key])
             elif self.mode == self.mode_edit:
-                self.send('TASK:WRITE ' + key)
+                self.send('TASK:WRITE %s %s' % (key, quote(self.password)))
                 self.switch_mode('play')
 
 TUI('localhost:5000')
index 4adb4ee24c7875b6775b7b904b3b23b6665712c1..942cdbf12dd6bf750f5fa382d75a19e212bf18b9 100644 (file)
@@ -26,8 +26,10 @@ switch to chat mode: <input id="key_switch_to_chat" type="text" value="t" /><br
 switch to play mode: <input id="key_switch_to_play" type="text" value="p" /><br />
 switch to study mode: <input id="key_switch_to_study" type="text" value="?" /><br />
 edit terrain (from play mode): <input id="key_switch_to_edit" type="text" value="m" /><br />
+enter terrain password (from play mode): <input id="key_switch_to_password" type="text" value="P" /><br />
 annotate terrain (from study mode): <input id="key_switch_to_annotate" type="text" value="m" /><br />
 annotate portal (from study mode): <input id="key_switch_to_portal" type="text" value="P" /><br />
+toggle terrain/control view (from study mode): <input id="key_toggle_map_mode" type="text" value="M" /><br />
 </div>
 <script>
 "use strict";
@@ -211,6 +213,8 @@ let server = {
             tui.init_keys();
             game.map_size = parser.parse_yx(tokens[2]);
             game.map = tokens[3]
+        } else if (tokens[0] === 'MAP_CONTROL') {
+            game.map_control = tokens[1]
         } else if (tokens[0] === 'GAME_STATE_COMPLETE') {
             game.turn_complete = true;
             explorer.empty_info_db();
@@ -299,6 +303,7 @@ let mode_study = new Mode('check map tiles for messages', false, true);
 let mode_edit = new Mode('write ASCII char to map tile', false, false);
 let mode_teleport = new Mode('teleport away?', true);
 let mode_portal = new Mode('add portal to map tile', true, true);
+let mode_password = new Mode('enter terrain password', true, false, false);
 
 let tui = {
   mode: mode_waiting_for_server,
@@ -309,6 +314,7 @@ let tui = {
   height_turn_line: 1,
   height_mode_line: 1,
   height_input: 1,
+  password: 'foo',
   init: function() {
       this.inputEl = document.getElementById("input");
       this.inputEl.focus();
@@ -341,6 +347,7 @@ let tui = {
     };
   },
   switch_mode: function(mode, keep_pos=false) {
+    this.map_mode = 'terrain';
     if (mode == mode_study && !keep_pos && game.player_id in game.things) {
       explorer.position = game.things[game.player_id].position;
     }
@@ -363,6 +370,9 @@ let tui = {
         let portal = game.portals[explorer.position]
         this.inputEl.value = portal;
         this.recalc_input_lines();
+    } else if (mode == mode_password) {
+        this.inputEl.value = this.password;
+        this.recalc_input_lines();
     } else if (mode == mode_teleport) {
         tui.log_msg("@ May teleport to: " + tui.teleport_target);
         tui.log_msg("@ Enter 'YES!' to entusiastically affirm.");
@@ -419,6 +429,7 @@ let tui = {
     }
     this.log_msg("  " + this.keys.switch_to_chat + " - switch to chat mode");
     this.log_msg("commands specific to play mode:");
+    this.log_msg("  " + this.keys.switch_to_password + " - enter terrain password");
     if (game.tasks.includes('WRITE')) {
         this.log_msg("  " + this.keys.switch_to_edit + " - write following ASCII character");
     }
@@ -427,28 +438,35 @@ let tui = {
     }
     this.log_msg("  " + this.keys.switch_to_study + " - switch to study mode");
     this.log_msg("commands specific to study mode:");
+    this.log_msg("  " + this.keys.switch_to_play + " - switch to play mode");
     if (!game.tasks.includes('MOVE')) {
         this.log_msg("  " + movement_keys_desc + " - move");
     }
     this.log_msg("  " + this.keys.switch_to_annotate + " - annotate terrain");
-    this.log_msg("  " + this.keys.switch_to_play + " - switch to play mode");
+    this.log_msg("  " + this.keys.toggle_map_mode + " - toggle terrain/control view");
   },
   draw_map: function() {
     let map_lines_split = [];
     let line = [];
+    let map_content = game.map;
+    if (this.map_mode == 'control') {
+        map_content = game.map_control;
+    }
     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;
         };
-        line.push(game.map[i]);
+        line.push(map_content[i]);
     };
     map_lines_split.push(line);
-    for (const thing_id in game.things) {
-        let t = game.things[thing_id];
-        map_lines_split[t.position[0]][t.position[1]] = '@';
-    };
+    if (this.map_mode == 'terrain') {
+        for (const thing_id in game.things) {
+            let t = game.things[thing_id];
+            map_lines_split[t.position[0]][t.position[1]] = '@';
+        };
+    }
     if (tui.mode.shows_info) {
         map_lines_split[explorer.position[0]][explorer.position[1]] = '?';
     }
@@ -547,6 +565,7 @@ let game = {
         this.things = {};
         this.turn = -1;
         this.map = "";
+        this.map_control = "";
         this.map_size = [0,0];
         this.player_id = -1;
         this.portals = {};
@@ -685,7 +704,7 @@ tui.inputEl.addEventListener('input', (event) => {
         tui.recalc_input_lines();
         tui.full_refresh();
     } else if (tui.mode == mode_edit && tui.inputEl.value.length > 0) {
-        server.send(["TASK:WRITE", tui.inputEl.value[0]]);
+        server.send(["TASK:WRITE", tui.inputEl.value[0], tui.password]);
         tui.switch_mode(mode_play);
     } else if (tui.mode == mode_teleport) {
         if (['Y', 'y'].includes(tui.inputEl.value[0])) {
@@ -710,6 +729,12 @@ tui.inputEl.addEventListener('keydown', (event) => {
     } else if (tui.mode == mode_annotate && event.key == 'Enter') {
         explorer.annotate(tui.inputEl.value);
         tui.switch_mode(mode_study, true);
+    } else if (tui.mode == mode_password && event.key == 'Enter') {
+        if (tui.inputEl.value.length == 0) {
+            tui.inputEl.value = " ";
+        }
+        tui.password = tui.inputEl.value
+        tui.switch_mode(mode_play);
     } else if (tui.mode == mode_teleport && event.key == 'Enter') {
         if (tui.inputEl.value == 'YES!') {
             server.reconnect_to(tui.teleport_target);
@@ -767,6 +792,8 @@ tui.inputEl.addEventListener('keydown', (event) => {
               tui.switch_mode(mode_edit);
           } else if (event.key === tui.keys.switch_to_study) {
               tui.switch_mode(mode_study);
+          } else if (event.key === tui.keys.switch_to_password) {
+              tui.switch_mode(mode_password);
           } else if (event.key === tui.keys.flatten
                      && game.tasks.includes('FLATTEN_SURROUNDINGS')) {
               server.send(["TASK:FLATTEN_SURROUNDINGS"]);
@@ -785,9 +812,16 @@ tui.inputEl.addEventListener('keydown', (event) => {
             tui.switch_mode(mode_portal);
         } else if (event.key in tui.movement_keys) {
             explorer.move(tui.movement_keys[event.key]);
+        } else if (event.key == tui.keys.toggle_map_mode) {
+            if (tui.map_mode == 'terrain') {
+                tui.map_mode = 'control';
+            } else {
+                tui.map_mode = 'terrain';
+            }
+            tui.full_refresh();
         } else if (event.key === tui.keys.switch_to_annotate) {
-          event.preventDefault();
-          tui.switch_mode(mode_annotate);
+            event.preventDefault();
+            tui.switch_mode(mode_annotate);
         };
     }
 }, false);