home · contact · privacy
Enable Hat editing with characters earned by eating cookies from a CookieSpawner.
authorChristian Heller <c.heller@plomlompom.de>
Sun, 13 Dec 2020 04:21:16 +0000 (05:21 +0100)
committerChristian Heller <c.heller@plomlompom.de>
Sun, 13 Dec 2020 04:21:16 +0000 (05:21 +0100)
config.json
plomrogue/commands.py
plomrogue/game.py
plomrogue/tasks.py
plomrogue/things.py
rogue_chat.html
rogue_chat.py
rogue_chat_curses.py

index 76a1f7247902b57e1d63d35f3947040f03826542..79537a94deec4490963d60be0332c606744a9ac0 100644 (file)
@@ -15,6 +15,7 @@
     "switch_to_admin_thing_protect": "T",
     "flatten": "F",
     "switch_to_enter_face": "f",
+    "switch_to_enter_hat": "H",
     "switch_to_take_thing": "z",
     "switch_to_drop_thing": "u",
     "teleport": "p",
index df4b7e204a26b353c8e46db71281e7b9d345717e..aed5830b5d64c6ea1615fdb57210f8229b3ab50d 100644 (file)
@@ -347,6 +347,26 @@ def cmd_PLAYER_FACE(game, face, connection_id):
     game.record_fov_change(t.position)
 cmd_PLAYER_FACE.argtypes = 'string'
 
+def cmd_PLAYER_HAT(game, hat, connection_id):
+    t = game.get_player(connection_id)
+    if not t:
+        raise GameError('can only edit hat when already logged in')
+    if not t.name in game.hats:
+        raise GameError('not currently wearing an editable hat')
+    if len(hat) != 18:
+        raise GameError('wrong hat string length')
+    legal_chars = t.get_cookie_chars()
+    for c in hat:
+        if c not in legal_chars:
+            raise GameError('used illegal character: "%s" – '
+                            'allowed characters: %s'
+                            % (c, legal_chars))
+    game.hats[t.name] = hat
+    game.changed = True
+    # FIXME: pseudo-FOV-change actually
+    game.record_fov_change(t.position)
+cmd_PLAYER_HAT.argtypes = 'string'
+
 def cmd_GOD_PLAYER_FACE(game, name, face):
     if len(face) != 18:
         raise GameError('wrong face string length')
@@ -359,6 +379,10 @@ def cmd_GOD_PLAYER_HAT(game, name, hat):
     game.hats[name] = hat
 cmd_GOD_PLAYER_HAT.argtypes = 'string string'
 
+def cmd_GOD_PLAYERS_HAT_CHARS(game, name, hat_chars):
+    game.players_hat_chars[name] = hat_chars
+cmd_GOD_PLAYERS_HAT_CHARS.argtypes = 'string string'
+
 def cmd_THING_HAT_DESIGN(game, thing_id, design):
     if len(design) != 18:
         raise GameError('hat design of wrong length')
index aa5c0db902ed9beda89ffe9f2dba8d9840170d73..a3a636def40ba816c7dd658465f47bb2a59d4a54 100755 (executable)
@@ -132,6 +132,7 @@ class Game(GameBase):
         self.spawn_point = YX(0, 0), YX(0, 0)
         self.portals = {}
         self.player_chars = string.digits + string.ascii_letters
+        self.players_hat_chars = {}
         self.player_char_i = -1
         self.admin_passwords = []
         self.send_gamestate_interval = datetime.timedelta(seconds=0.04)
@@ -277,6 +278,8 @@ class Game(GameBase):
                 # collected here as a shortcut, but a cleaner way would be to
                 # differentiate the changes somehow.
                 self.io.send('PSEUDO_FOV_WIPE', c_id)
+                self.io.send('PLAYERS_HAT_CHARS ' + quote(player.get_cookie_chars()),
+                             c_id)
                 for t in player.seen_things:
                     target_yx = player.fov_stencil.target_yx(*t.position)
                     self.io.send('THING %s %s %s %s %s %s'
@@ -491,6 +494,9 @@ class Game(GameBase):
             for name in self.hats:
                 write(f, 'GOD_PLAYER_HAT %s %s' % (quote(name),
                                                    quote(self.hats[name])))
+            for name in self.players_hat_chars:
+                write(f, 'GOD_PLAYERS_HAT_CHARS %s %s' %
+                      (quote(name), quote(self.players_hat_chars[name])))
             for t in [t for t in self.things if not t.type_ == 'Player']:
                 write(f, 'THING %s %s %s %s' % (t.position[0],
                                                 t.position[1], t.type_, t.id_))
index 68f3269c04738ee8f44e785453f5c50fb8e2f713..4a6df1641202d75ca66f7f1a057d779774f155f9 100644 (file)
@@ -133,7 +133,16 @@ class Task_DROP(Task):
         target_position = self._get_move_target()
         dropped = self.thing.uncarry()
         dropped.position = target_position
-        if dropped.type_ == 'Bottle' and not dropped.full:
+        over_cookie_spawner = None
+        for t in [t for t in self.thing.game.things
+                  if t.type_ == 'CookieSpawner'
+                  and t.position == dropped.position]:
+            over_cookie_spawner = t
+            break
+        if over_cookie_spawner:
+            over_cookie_spawner.accept(dropped)
+            self.thing.game.remove_thing(dropped)
+        elif dropped.type_ == 'Bottle' and not dropped.full:
             for t in [t for t in self.thing.game.things
                       if t.type_ == 'BottleDeposit'
                       and t.position == dropped.position]:
@@ -171,19 +180,26 @@ class Task_INTOXICATE(Task):
     def check(self):
         if self.thing.carrying is None:
             raise PlayError('carrying nothing to drink from')
-        if self.thing.carrying.type_ != 'Bottle':
-            raise PlayError('cannot drink from non-bottle')
-        if not self.thing.carrying.full:
+        if self.thing.carrying.type_ not in {'Bottle', 'Cookie'}:
+            raise PlayError('cannot consume this kind of thing')
+        if self.thing.carrying.type_ == 'Bottle' and\
+           not self.thing.carrying.full:
             raise PlayError('bottle is empty')
 
     def do(self):
-        self.thing.carrying.full = False
-        self.thing.carrying.empty()
-        self.thing.send_msg('RANDOM_COLORS')
-        self.thing.send_msg('CHAT "You are drunk now."')
-        self.thing.drunk = 10000
-        # FIXME: pseudo-FOV-change actually
-        self.thing.game.record_fov_change(self.thing.position)
+        if self.thing.carrying.type_ == 'Bottle':
+            self.thing.carrying.full = False
+            self.thing.carrying.empty()
+            self.thing.send_msg('RANDOM_COLORS')
+            self.thing.send_msg('CHAT "You are drunk now."')
+            self.thing.drunk = 10000
+            # FIXME: pseudo-FOV-change actually
+            self.thing.game.record_fov_change(self.thing.position)
+        elif self.thing.carrying.type_ == 'Cookie':
+            self.thing.send_msg('CHAT ' + quote('You eat a cookie and gain the ability to draw the following character: "%s"' % self.thing.carrying.thing_char))
+            self.thing.add_cookie_char(self.thing.carrying.thing_char)
+            eaten = self.thing.uncarry()
+            self.thing.game.remove_thing(eaten)
 
 
 
@@ -269,8 +285,8 @@ class Task_WEAR(Task):
                 self.thing.game.hats[self.thing.name] =\
                     self.thing.carrying.design
                 self.thing.send_msg('CHAT "You put on a hat."')
-            self.thing.game.remove_thing(self.thing.carrying)
-            self.thing.carrying = None
+            dropped = self.uncarry()
+            self.thing.game.remove_thing(dropped)
         # FIXME: pseudo-FOV-change actually
         self.thing.game.record_fov_change(self.thing.position)
 
index 70d9994d1d5782152b9d563b8db1cb1697b85b3b..5948cb6b6ebbc1b92f6ec8d8adc76f919351d578 100644 (file)
@@ -247,7 +247,7 @@ class Thing_HatRemixer(Thing):
         self.sound('HAT REMIXER', 'remixing a hat …')
         self.game.changed = True
         # FIXME: pseudo-FOV-change actually
-        self.game.record_fov_change(self.thing.position)
+        self.game.record_fov_change(self.position)
 
 
 
@@ -383,6 +383,26 @@ class Thing_BottleDeposit(Thing):
 
 
 
+class Thing_Cookie(Thing):
+    symbol_hint = 'c'
+    portable = True
+
+    def __init__(self, *args, **kwargs):
+        import string
+        super().__init__(*args, **kwargs)
+        legal_chars = string.ascii_letters + string.digits + string.punctuation + ' '
+        self.thing_char = random.choice(list(legal_chars))
+
+
+
+class Thing_CookieSpawner(Thing):
+    symbol_hint = 'O'
+
+    def accept(self, thing):
+        self.sound('OVEN', '*heat* *brrzt* here\'s a cookie!')
+        self.game.add_thing('Cookie', self.position)
+
+
 
 class ThingAnimate(Thing):
     blocking = True
@@ -421,7 +441,7 @@ class ThingAnimate(Thing):
                     self.game.io.send('CHAT "You sober up."', c_id)
                     #self.invalidate_map_view()
                     # FIXME: pseudo-FOV-change actually
-                    self.game.record_fov_change(self.thing.position)
+                    self.game.record_fov_change(self.position)
                     break
             self.game.changed = True
         if self.task is None:
@@ -520,3 +540,14 @@ class Thing_Player(ThingAnimate):
         t.carried = False
         self.carrying = None
         return t
+
+    def add_cookie_char(self, c):
+        if not self.name in self.game.players_hat_chars:
+            self.game.players_hat_chars[self.name] = ' #'  # default
+        if not c in self.game.players_hat_chars[self.name]:
+            self.game.players_hat_chars[self.name] += c
+
+    def get_cookie_chars(self):
+        if self.name in self.game.players_hat_chars:
+            return self.game.players_hat_chars[self.name]
+        return ' #'  # default
index 883afc9322ad994c260c3a422921f477c58fb2e7..4285ded541ea1912fd83f36ba37337761407f074 100644 (file)
@@ -69,6 +69,7 @@ terminal rows: <input id="n_rows" type="number" step=4 min=24 value=24 />
       <button id="switch_to_name_thing"></button>
       <button id="switch_to_password"></button>
       <button id="switch_to_enter_face"></button>
+      <button id="switch_to_enter_hat"></button>
     </td>
   </tr>
   <tr>
@@ -104,6 +105,7 @@ terminal rows: <input id="n_rows" type="number" step=4 min=24 value=24 />
 <li>(un-)wear: <input id="key_wear" type="text" value="W" />
 <li><input id="key_switch_to_drop_thing" type="text" value="u" />
 <li><input id="key_switch_to_enter_face" type="text" value="f" />
+<li><input id="key_switch_to_enter_hat" type="text" value="H" />
 <li><input id="key_switch_to_take_thing" type="text" value="z" />
 <li><input id="key_switch_to_chat" type="text" value="t" />
 <li><input id="key_switch_to_play" type="text" value="p" />
@@ -173,6 +175,11 @@ let mode_helps = {
         'intro': '@ enter face line (enter nothing to abort):',
         'long': 'Draw your face as ASCII art.  The string you enter must be 18 characters long, and will be divided on display into 3 lines of 6 characters each, from top to bottom..'
     },
+    'enter_hat': {
+        'short': 'enter your hat',
+        'intro': '@ enter hat line (enter nothing to abort):',
+        'long': 'Draw your hat as ASCII art.  The string you enter must be 18 characters long, and will be divided on display into 3 lines of 6 characters each, from top to bottom..'
+    },
     'write': {
         'short': 'change terrain',
         'intro': '',
@@ -536,6 +543,7 @@ let server = {
             explorer.info_cached = false;
             game.things = game.things_new;
             game.player = game.things[game.player_id];
+            game.players_hat_chars = game.players_hat_chars_new;
             game.turn_complete = true;
             if (tui.mode.name == 'post_login_wait') {
                 tui.switch_mode('play');
@@ -551,6 +559,8 @@ let server = {
              tui.log_msg('#MUSICPLAYER: ' + tokens[1], 1);
         } else if (tokens[0] === 'PLAYER_ID') {
             game.player_id = parseInt(tokens[1]);
+        } else if (tokens[0] === 'PLAYERS_HAT_CHARS') {
+            game.players_hat_chars_new = tokens[1];
         } else if (tokens[0] === 'LOGIN_OK') {
             this.send(['GET_GAMESTATE']);
             tui.switch_mode('post_login_wait');
@@ -688,6 +698,7 @@ let tui = {
   mode_take_thing: new Mode('take_thing', true),
   mode_drop_thing: new Mode('drop_thing', true),
   mode_enter_face: new Mode('enter_face', true),
+  mode_enter_hat: new Mode('enter_hat', true),
   mode_admin_enter: new Mode('admin_enter', true),
   mode_admin: new Mode('admin'),
   mode_control_pw_pw: new Mode('control_pw_pw', true),
@@ -724,7 +735,7 @@ let tui = {
       this.mode_control_tile_draw.available_actions = ["toggle_tile_draw"];
       this.mode_edit.available_modes = ["write", "annotate", "portal", "name_thing",
                                         "password", "chat", "study", "play",
-                                        "admin_enter", "enter_face"]
+                                        "admin_enter", "enter_face", "enter_hat"]
       this.mode_edit.available_actions = ["move", "flatten", "install",
                                           "toggle_map_mode"]
       this.inputEl = document.getElementById("input");
@@ -1322,6 +1333,7 @@ let game = {
         this.map_size_new = [0,0];
         this.portals = {};
         this.portals_new = {};
+        this.players_hat_chars = "";
     },
     get_thing_temp: function(id_, create_if_not_found=false) {
         if (id_ in game.things_new) {
@@ -1543,6 +1555,14 @@ tui.inputEl.addEventListener('keydown', (event) => {
         }
         tui.inputEl.value = "";
         tui.switch_mode('edit');
+    } else if (tui.mode.name == 'enter_hat' && event.key == 'Enter') {
+        if (tui.inputEl.value.length != 18) {
+            tui.log_msg('? wrong input length, aborting');
+        } else {
+            server.send(['PLAYER_HAT', tui.inputEl.value]);
+        }
+        tui.inputEl.value = "";
+        tui.switch_mode('edit');
     } else if (tui.mode.name == 'command_thing' && event.key == 'Enter') {
         server.send(['TASK:COMMAND', tui.inputEl.value]);
         tui.inputEl.value = "";
index d7dd64bf19e767f4bbd7a66234c44589ceb88f4c..680caa9a3c48e035151c2b5c43ced032976adce6 100755 (executable)
@@ -13,7 +13,8 @@ from plomrogue.commands import (cmd_ALL, cmd_LOGIN, cmd_NICK, cmd_PING, cmd_THIN
                                 cmd_THING_MUSICPLAYER_SETTINGS, cmd_THING_HAT_DESIGN,
                                 cmd_THING_MUSICPLAYER_PLAYLIST_ITEM, cmd_TERRAIN,
                                 cmd_THING_BOTTLE_EMPTY, cmd_PLAYER_FACE,
-                                cmd_GOD_PLAYER_FACE, cmd_GOD_PLAYER_HAT)
+                                cmd_GOD_PLAYER_FACE, cmd_GOD_PLAYER_HAT,
+                                cmd_GOD_PLAYERS_HAT_CHARS, cmd_PLAYER_HAT)
 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,
@@ -22,7 +23,8 @@ from plomrogue.things import (Thing_Player, Thing_Item, Thing_ItemSpawner,
                               Thing_SpawnPoint, Thing_SpawnPointSpawner,
                               Thing_Door, Thing_DoorSpawner, Thing_Bottle,
                               Thing_BottleSpawner, Thing_BottleDeposit,
-                              Thing_MusicPlayer, Thing_Hat, Thing_HatRemixer)
+                              Thing_MusicPlayer, Thing_Hat, Thing_HatRemixer,
+                              Thing_Cookie, Thing_CookieSpawner)
 
 from plomrogue.config import config
 game = Game(config['savefile'])
@@ -62,6 +64,8 @@ game.register_command(cmd_THING_BOTTLE_EMPTY)
 game.register_command(cmd_PLAYER_FACE)
 game.register_command(cmd_GOD_PLAYER_FACE)
 game.register_command(cmd_GOD_PLAYER_HAT)
+game.register_command(cmd_GOD_PLAYERS_HAT_CHARS)
+game.register_command(cmd_PLAYER_HAT)
 game.register_command(cmd_THING_HAT_DESIGN)
 game.register_task(Task_WAIT)
 game.register_task(Task_MOVE)
@@ -88,6 +92,8 @@ game.register_thing_type(Thing_BottleDeposit)
 game.register_thing_type(Thing_MusicPlayer)
 game.register_thing_type(Thing_Hat)
 game.register_thing_type(Thing_HatRemixer)
+game.register_thing_type(Thing_Cookie)
+game.register_thing_type(Thing_CookieSpawner)
 game.read_savefile()
 game.io.start_loop()
 for port in config['servers']:
index d76c3bebd0b41622a41d3191b8db20836e989f8d..e9f7f6b93c21936d37e783eb9d2d651553e8fc0d 100755 (executable)
@@ -56,6 +56,11 @@ mode_helps = {
         'intro': '@ enter face line (enter nothing to abort):',
         'long': 'Draw your face as ASCII art.  The string you enter must be 18 characters long, and will be divided on display into 3 lines of 6 characters each, from top to bottom..'
     },
+    'enter_hat': {
+        'short': 'enter your hat',
+        'intro': '@ enter hat line (enter nothing to abort):',
+        'long': 'Draw your hat as ASCII art.  The string you enter must be 18 characters long, and will be divided on display into 3 lines of 6 characters each, from top to bottom..'
+    },
     'write': {
         'short': 'change terrain',
         'intro': '',
@@ -211,6 +216,10 @@ def cmd_PLAYER_ID(game, player_id):
     game.player_id = player_id
 cmd_PLAYER_ID.argtypes = 'int:nonneg'
 
+def cmd_PLAYERS_HAT_CHARS(game, hat_chars):
+    game.players_hat_chars_new = hat_chars
+cmd_PLAYERS_HAT_CHARS.argtypes = 'string'
+
 def cmd_THING(game, yx, thing_type, protection, thing_id, portable, commandable):
     t = game.get_thing_temp(thing_id)
     if not t:
@@ -284,6 +293,7 @@ def cmd_GAME_STATE_COMPLETE(game):
     game.map_content = game.map_content_new
     game.map_control_content = game.map_control_content_new
     game.player = game.get_thing(game.player_id)
+    game.players_hat_chars = game.players_hat_chars_new
     game.turn_complete = True
     if game.tui.mode.name == 'post_login_wait':
         game.tui.switch_mode('play')
@@ -382,6 +392,7 @@ class Game(GameBase):
         self.register_command(cmd_PORTAL)
         self.register_command(cmd_ANNOTATION)
         self.register_command(cmd_GAME_STATE_COMPLETE)
+        self.register_command(cmd_PLAYERS_HAT_CHARS)
         self.register_command(cmd_ARGUMENT_ERROR)
         self.register_command(cmd_GAME_ERROR)
         self.register_command(cmd_PLAY_ERROR)
@@ -390,6 +401,7 @@ class Game(GameBase):
         self.register_command(cmd_DEFAULT_COLORS)
         self.register_command(cmd_RANDOM_COLORS)
         self.map_content = ''
+        self.players_hat_chars = ''
         self.player_id = -1
         self.annotations = {}
         self.annotations_new = {}
@@ -480,6 +492,7 @@ class TUI:
     mode_take_thing = Mode('take_thing', has_input_prompt=True)
     mode_drop_thing = Mode('drop_thing', has_input_prompt=True)
     mode_enter_face = Mode('enter_face', has_input_prompt=True)
+    mode_enter_hat = Mode('enter_hat', has_input_prompt=True)
     is_admin = False
     tile_draw = False
 
@@ -501,7 +514,7 @@ class TUI:
         self.mode_control_tile_draw.available_actions = ["move_explorer",
                                                          "toggle_tile_draw"]
         self.mode_edit.available_modes = ["write", "annotate", "portal",
-                                          "name_thing", "enter_face", "password",
+                                          "name_thing", "enter_face", "enter_hat", "password",
                                           "chat", "study", "play", "admin_enter"]
         self.mode_edit.available_actions = ["move", "flatten", "install",
                                             "toggle_map_mode"]
@@ -534,6 +547,7 @@ class TUI:
             'switch_to_admin_thing_protect': 'T',
             'flatten': 'F',
             'switch_to_enter_face': 'f',
+            'switch_to_enter_hat': 'H',
             'switch_to_take_thing': 'z',
             'switch_to_drop_thing': 'u',
             'teleport': 'p',
@@ -731,6 +745,8 @@ class TUI:
                 ['HERE'] + list(self.game.tui.movement_keys.values())
             for i in range(len(self.selectables)):
                 self.log_msg(str(i) + ': ' + self.selectables[i])
+        elif self.mode.name == 'enter_hat':
+            self.log_msg('legal characters: ' + self.game.players_hat_chars)
         elif self.mode.name == 'command_thing':
             self.send('TASK:COMMAND ' + quote('HELP'))
         elif self.mode.name == 'control_pw_pw':
@@ -1176,6 +1192,13 @@ class TUI:
                     self.send('PLAYER_FACE %s' % quote(self.input_))
                 self.input_ = ""
                 self.switch_mode('edit')
+            elif self.mode.name == 'enter_hat' and key == '\n':
+                if len(self.input_) != 18:
+                    self.log_msg('? wrong input length, aborting')
+                else:
+                    self.send('PLAYER_HAT %s' % quote(self.input_))
+                self.input_ = ""
+                self.switch_mode('edit')
             elif self.mode.name == 'take_thing' and key == '\n':
                 pick_selectable('PICK_UP')
             elif self.mode.name == 'drop_thing' and key == '\n':