From aab94ffb12aa0dedc240d7b29001699b95c49249 Mon Sep 17 00:00:00 2001 From: Christian Heller Date: Sun, 13 Dec 2020 05:21:16 +0100 Subject: [PATCH] Enable Hat editing with characters earned by eating cookies from a CookieSpawner. --- config.json | 1 + plomrogue/commands.py | 24 ++++++++++++++++++++++++ plomrogue/game.py | 6 ++++++ plomrogue/tasks.py | 42 +++++++++++++++++++++++++++++------------- plomrogue/things.py | 35 +++++++++++++++++++++++++++++++++-- rogue_chat.html | 22 +++++++++++++++++++++- rogue_chat.py | 10 ++++++++-- rogue_chat_curses.py | 25 ++++++++++++++++++++++++- 8 files changed, 146 insertions(+), 19 deletions(-) diff --git a/config.json b/config.json index 76a1f72..79537a9 100644 --- a/config.json +++ b/config.json @@ -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", diff --git a/plomrogue/commands.py b/plomrogue/commands.py index df4b7e2..aed5830 100644 --- a/plomrogue/commands.py +++ b/plomrogue/commands.py @@ -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') diff --git a/plomrogue/game.py b/plomrogue/game.py index aa5c0db..a3a636d 100755 --- a/plomrogue/game.py +++ b/plomrogue/game.py @@ -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_)) diff --git a/plomrogue/tasks.py b/plomrogue/tasks.py index 68f3269..4a6df16 100644 --- a/plomrogue/tasks.py +++ b/plomrogue/tasks.py @@ -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) diff --git a/plomrogue/things.py b/plomrogue/things.py index 70d9994..5948cb6 100644 --- a/plomrogue/things.py +++ b/plomrogue/things.py @@ -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 diff --git a/rogue_chat.html b/rogue_chat.html index 883afc9..4285ded 100644 --- a/rogue_chat.html +++ b/rogue_chat.html @@ -69,6 +69,7 @@ terminal rows: + @@ -104,6 +105,7 @@ terminal rows:
  • (un-)wear:
  • +
  • @@ -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 = ""; diff --git a/rogue_chat.py b/rogue_chat.py index d7dd64b..680caa9 100755 --- a/rogue_chat.py +++ b/rogue_chat.py @@ -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']: diff --git a/rogue_chat_curses.py b/rogue_chat_curses.py index d76c3be..e9f7f6b 100755 --- a/rogue_chat_curses.py +++ b/rogue_chat_curses.py @@ -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': -- 2.30.2