From c3ada0bf213337ff2c97e2f33bbf6e6dbedaea38 Mon Sep 17 00:00:00 2001
From: Christian Heller <c.heller@plomlompom.de>
Date: Sun, 20 Dec 2020 15:28:50 +0100
Subject: [PATCH] Make sign design sizes variable.

---
 plomrogue/commands.py |  26 +++++++++++
 plomrogue/game.py     |   3 ++
 rogue_chat.html       | 103 ++++++++++++++++++++++++++++--------------
 rogue_chat.py         |   5 +-
 rogue_chat_curses.py  |  57 ++++++++++++++++++-----
 5 files changed, 148 insertions(+), 46 deletions(-)

diff --git a/plomrogue/commands.py b/plomrogue/commands.py
index 9c34a1c..a7b704e 100644
--- a/plomrogue/commands.py
+++ b/plomrogue/commands.py
@@ -378,6 +378,21 @@ def cmd_THING_DESIGN(game, design, pw, connection_id):
     game.record_change(player.carrying.position, 'other')
 cmd_THING_DESIGN.argtypes = 'string string'
 
+def cmd_THING_DESIGN_SIZE(game, size, pw, connection_id):
+    player = game.get_player(connection_id)
+    if not player:
+        raise GameError('need to be logged in for this')
+    if not player.carrying:
+        raise GameError('need to carry a thing to re-draw it')
+    if not game.can_do_thing_with_pw(player.carrying, pw):
+        raise GameError('wrong password for thing')
+    if not hasattr(player.carrying, 'design'):
+        raise GameError('carried thing not designable')
+    if player.carrying.type_ == 'Hat':
+        raise GameError('may not change Hat size')
+    player.carrying.design_size = size
+cmd_THING_DESIGN_SIZE.argtypes = 'yx_tuple:nonneg string'
+
 def cmd_GOD_THING_DESIGN(game, thing_id, design):
     t = game.get_thing(thing_id)
     if not t:
@@ -389,6 +404,17 @@ def cmd_GOD_THING_DESIGN(game, thing_id, design):
     t.design = design
 cmd_GOD_THING_DESIGN.argtypes = 'int:pos string'
 
+def cmd_GOD_THING_DESIGN_SIZE(game, thing_id, size):
+    t = game.get_thing(thing_id)
+    if not t:
+        raise GameError('thing of ID %s not found' % thing_id)
+    if not hasattr(t, 'design'):
+        raise GameError('thing of ID %s not designable' % thing_id)
+    if t.type_ == 'Hat':
+        raise GameError('may not change Hat size')
+    t.design_size = size
+cmd_GOD_THING_DESIGN_SIZE.argtypes = 'int:pos yx_tuple:nonneg'
+
 # TODO: refactor similar god and player commands
 
 def cmd_THING_DOOR_KEY(game, key_id, door_id):
diff --git a/plomrogue/game.py b/plomrogue/game.py
index c560993..415c3e8 100755
--- a/plomrogue/game.py
+++ b/plomrogue/game.py
@@ -557,6 +557,9 @@ class Game(GameBase):
                 if hasattr(t, 'installable') and (not t.portable):
                     write(f, 'THING_INSTALLED %s' % t.id_)
                 if hasattr(t, 'design'):
+                    if t.type_ != 'Hat':
+                        write(f, 'GOD_THING_DESIGN_SIZE %s %s' % (t.id_,
+                                                                  t.design_size))
                     write(f, 'GOD_THING_DESIGN %s %s' % (t.id_, quote(t.design)))
                 if t.type_ == 'Door' and t.blocks_movement:
                     write(f, 'THING_DOOR_CLOSED %s %s' % (t.id_, int(t.locked)))
diff --git a/rogue_chat.html b/rogue_chat.html
index 1847c09..5338e9d 100644
--- a/rogue_chat.html
+++ b/rogue_chat.html
@@ -933,12 +933,15 @@ let tui = {
             this.log_msg(i + ': ' + direction);
         };
     } else if (this.mode.name == 'enter_design') {
-        this.log_msg('@ The design you enter must be '
-                     + game.player.carrying.design[0][0] + ' lines of max '
-                     + game.player.carrying.design[0][1] + ' characters width each');
         if (game.player.carrying.type_ == 'Hat') {
+            this.log_msg('@ The design you enter must be '
+                         + game.player.carrying.design[0][0] + ' lines of max '
+                         + game.player.carrying.design[0][1] + ' characters width each');
             this.log_msg('@ Legal characters: ' + game.players_hat_chars);
             this.log_msg('@ (Eat cookies to extend the ASCII characters available for drawing.)');
+        } else {
+            this.log_msg('@ Width of first line determines maximum width for remaining design')
+            this.log_msg('@ Finish design by entering an empty line (multiple space characters do not count as empty)')
         }
     } else if (this.mode.name == 'command_thing') {
         server.send(['TASK:COMMAND', 'HELP']);
@@ -1078,31 +1081,55 @@ let tui = {
       this.inputEl.value = "";
       this.switch_mode('play');
   },
-  enter_ascii_art: function(command, height, width, with_pw=false) {
-      if (this.inputEl.value.length > width) {
-          this.log_msg('? wrong input length, must be max ' + width + '; try again');
-          return;
-      } else if (this.inputEl.value.length < width) {
-          while (this.inputEl.value.length < width) {
-              this.inputEl.value += ' ';
-          }
-      }
-      this.log_msg('  ' + this.inputEl.value);
-      this.full_ascii_draw += this.inputEl.value;
-      this.ascii_draw_stage += 1;
-      if (this.ascii_draw_stage < height) {
-          this.restore_input_values();
-      } else {
-          if (with_pw) {
-              server.send([command, this.full_ascii_draw, this.password]);
-          } else {
-              server.send([command, this.full_ascii_draw]);
-          }
-          this.full_ascii_draw = '';
-          this.ascii_draw_stage = 0;
-          this.inputEl.value = '';
-          this.switch_mode('edit');
-      }
+    enter_ascii_art: function(command, height, width, with_pw=false, with_size=false) {
+        if (with_size && this.ascii_draw_stage == 0) {
+            width = this.inputEl.value.length;
+            if (width > 36) {
+                this.log_msg('? wrong input length, must be max 36; try again');
+                return;
+            }
+            if (width != game.player.carrying.design[0][1]) {
+                game.player.carrying.design[1] = '';
+                game.player.carrying.design[0][1] = width;
+            }
+        } else if (this.inputEl.value.length > width) {
+            this.log_msg('? wrong input length, must be max ' + width + '; try again');
+            return;
+        }
+        this.log_msg('  ' + this.inputEl.value);
+        if (with_size && ['', ' '].includes(this.inputEl.value) && this.ascii_draw_stage > 0) {
+          height = this.ascii_draw_stage;
+        } else {
+            if (with_size) {
+                height = this.ascii_draw_stage + 2;
+            }
+            while (this.inputEl.value.length < width) {
+                this.inputEl.value += ' ';
+            }
+            this.full_ascii_draw += this.inputEl.value;
+        }
+        if (with_size) {
+            game.player.carrying.design[0][0] = height;
+        }
+        this.ascii_draw_stage += 1;
+        if (this.ascii_draw_stage < height) {
+            this.restore_input_values();
+        } else {
+            if (with_pw && with_size) {
+                server.send([command + '_SIZE',
+                             unparser.to_yx(game.player.carrying.design[0]),
+                             this.password]);
+            }
+            if (with_pw) {
+                server.send([command, this.full_ascii_draw, this.password]);
+            } else {
+                server.send([command, this.full_ascii_draw]);
+            }
+            this.full_ascii_draw = '';
+            this.ascii_draw_stage = 0;
+            this.inputEl.value = '';
+            this.switch_mode('edit');
+        }
   },
   draw_map: function() {
     if (!game.turn_complete && this.map_lines.length == 0) {
@@ -1570,8 +1597,12 @@ let explorer = {
             if (t.design) {
                 const line_length = t.design[0][1];
                 info += '-'.repeat(line_length + 4) + '\n';
-                const regexp = RegExp('.{1,' + line_length + '}', 'g');
-                const lines = t.design[1].match(regexp);
+                console.log(line_length)
+                let lines = ['']
+                if (line_length > 0) {
+                    const regexp = RegExp('.{1,' + line_length + '}', 'g');
+                    lines = t.design[1].match(regexp);
+                }
                 for (const line of lines) {
                     info += '| ' + line + ' |\n';
                 }
@@ -1644,9 +1675,15 @@ tui.inputEl.addEventListener('keydown', (event) => {
     } else if (tui.mode.name == 'enter_face' && event.key == 'Enter') {
         tui.enter_ascii_art('PLAYER_FACE', 3, 6);
     } else if (tui.mode.name == 'enter_design' && event.key == 'Enter') {
-        tui.enter_ascii_art('THING_DESIGN',
-                            game.player.carrying.design[0][0],
-                            game.player.carrying.design[0][1], true);
+        if (game.player.carrying.type_ == 'Hat') {
+            tui.enter_ascii_art('THING_DESIGN',
+                                game.player.carrying.design[0][0],
+                                game.player.carrying.design[0][1], true);
+        } else {
+            tui.enter_ascii_art('THING_DESIGN',
+                                game.player.carrying.design[0][0],
+                                game.player.carrying.design[0][1], true, true);
+        }
     } else if (tui.mode.name == 'command_thing' && event.key == 'Enter') {
         server.send(['TASK:COMMAND', tui.inputEl.value]);
         tui.inputEl.value = "";
diff --git a/rogue_chat.py b/rogue_chat.py
index af63366..b49f07c 100755
--- a/rogue_chat.py
+++ b/rogue_chat.py
@@ -18,7 +18,8 @@ from plomrogue.commands import (cmd_ALL, cmd_LOGIN, cmd_NICK, cmd_PING, cmd_THIN
                                 cmd_TERRAIN_TAG, cmd_THING_DOOR_KEY,
                                 cmd_THING_CRATE_ITEM, cmd_MAP_CONTROL_PRESETS,
                                 cmd_THING_SPAWNPOINT_CREATED, cmd_GOD_THING_DESIGN,
-                                cmd_THING_DESIGN)
+                                cmd_THING_DESIGN, cmd_THING_DESIGN_SIZE,
+                                cmd_GOD_THING_DESIGN_SIZE)
 from plomrogue.tasks import (Task_WAIT, Task_MOVE, Task_WRITE, Task_PICK_UP,
                              Task_DROP, Task_FLATTEN_SURROUNDINGS, Task_DOOR,
                              Task_INTOXICATE, Task_COMMAND, Task_INSTALL,
@@ -77,7 +78,9 @@ game.register_command(cmd_GOD_PLAYERS_HAT_CHARS)
 game.register_command(cmd_THING_DOOR_KEY)
 game.register_command(cmd_THING_CRATE_ITEM)
 game.register_command(cmd_THING_DESIGN)
+game.register_command(cmd_THING_DESIGN_SIZE)
 game.register_command(cmd_GOD_THING_DESIGN)
+game.register_command(cmd_GOD_THING_DESIGN_SIZE)
 game.register_command(cmd_MAP_CONTROL_PRESETS)
 game.register_command(cmd_THING_SPAWNPOINT_CREATED)
 game.register_task(Task_WAIT)
diff --git a/rogue_chat_curses.py b/rogue_chat_curses.py
index ff0c46c..fd28049 100755
--- a/rogue_chat_curses.py
+++ b/rogue_chat_curses.py
@@ -786,13 +786,16 @@ class TUI:
             for i in range(len(self.selectables)):
                 self.log_msg(str(i) + ': ' + self.selectables[i])
         elif self.mode.name == 'enter_design':
-            self.log_msg('@ The design you enter must be %s lines of max %s '
-                         'characters width each'
-                         % (self.game.player.carrying.design[0].y,
-                            self.game.player.carrying.design[0].x))
             if self.game.player.carrying.type_ == 'Hat':
+                self.log_msg('@ The design you enter must be %s lines of max %s '
+                             'characters width each'
+                             % (self.game.player.carrying.design[0].y,
+                                self.game.player.carrying.design[0].x))
                 self.log_msg('@ Legal characters: ' + self.game.players_hat_chars)
                 self.log_msg('@ (Eat cookies to extend the ASCII characters available for drawing.)')
+            else:
+                self.log_msg('@ Width of first line determines maximum width for remaining design')
+                self.log_msg('@ Finish design by entering an empty line (multiple space characters do not count as empty)')
         elif self.mode.name == 'command_thing':
             self.send('TASK:COMMAND ' + quote('HELP'))
         elif self.mode.name == 'control_pw_pw':
@@ -1156,19 +1159,43 @@ class TUI:
             self.input_ = ''
             self.switch_mode('play')
 
-        def enter_ascii_art(command, height, width, with_pw=False):
-            if len(self.input_) > width:
+        def enter_ascii_art(command, height, width,
+                            with_pw=False, with_size=False):
+            if with_size and self.ascii_draw_stage == 0:
+                width = len(self.input_)
+                if width > 36:
+                    self.log_msg('? input too long, must be max 36; try again')
+                    # TODO: move max width mechanism server-side
+                    return
+                old_size = self.game.player.carrying.design[0]
+                if width != old_size.x:
+                    # TODO: save remaining design?
+                    self.game.player.carrying.design[1] = ''
+                    self.game.player.carrying.design[0] = YX(old_size.y, width)
+            elif len(self.input_) > width:
                 self.log_msg('? input too long, '
                              'must be max %s; try again' % width)
                 return
-            if len(self.input_) < width:
-                self.input_ += ' ' * (width - len(self.input_))
             self.log_msg('  ' + self.input_)
-            self.full_ascii_draw += self.input_
+            if with_size and self.input_ in {'', ' '}\
+               and self.ascii_draw_stage > 0:
+                height = self.ascii_draw_stage
+            else:
+                if with_size:
+                    height = self.ascii_draw_stage + 2
+                if len(self.input_) < width:
+                    self.input_ += ' ' * (width - len(self.input_))
+                self.full_ascii_draw += self.input_
+            if with_size:
+                old_size = self.game.player.carrying.design[0]
+                self.game.player.carrying.design[0] = YX(height, old_size.x)
             self.ascii_draw_stage += 1
             if self.ascii_draw_stage < height:
                 self.restore_input_values()
             else:
+                if with_pw and with_size:
+                    self.send('%s_SIZE %s %s' % (command, YX(height, width),
+                                                 quote(self.password)))
                 if with_pw:
                     self.send('%s %s %s' % (command, quote(self.full_ascii_draw),
                                             quote(self.password)))
@@ -1292,9 +1319,15 @@ class TUI:
             elif self.mode.name == 'enter_face' and key == '\n':
                 enter_ascii_art('PLAYER_FACE', 3, 6)
             elif self.mode.name == 'enter_design' and key == '\n':
-                enter_ascii_art('THING_DESIGN',
-                                self.game.player.carrying.design[0].y,
-                                self.game.player.carrying.design[0].x, True)
+                if self.game.player.carrying.type_ == 'Hat':
+                    enter_ascii_art('THING_DESIGN',
+                                    self.game.player.carrying.design[0].y,
+                                    self.game.player.carrying.design[0].x, True)
+                else:
+                    enter_ascii_art('THING_DESIGN',
+                                    self.game.player.carrying.design[0].y,
+                                    self.game.player.carrying.design[0].x,
+                                    True, True)
             elif self.mode.name == 'take_thing' and key == '\n':
                 pick_selectable('PICK_UP')
             elif self.mode.name == 'drop_thing' and key == '\n':
-- 
2.30.2