From 8d1f606e1dd84a5418f1612d266ece318747832b Mon Sep 17 00:00:00 2001 From: Christian Heller Date: Tue, 15 Dec 2020 05:47:36 +0100 Subject: [PATCH 01/16] Remove debugging information from toilet need message. --- plomrogue/things.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plomrogue/things.py b/plomrogue/things.py index 6ad8534..ef50a63 100644 --- a/plomrogue/things.py +++ b/plomrogue/things.py @@ -585,7 +585,7 @@ class Thing_Player(ThingAnimate): self.send_msg('CHAT "You use the toilet. What a relief!"') self.need_for_toilet = 0 if 10000 * random.random() < self.need_for_toilet / 100000: - self.send_msg('CHAT "You need to go to a toilet. %s"' % self.need_for_toilet) + self.send_msg('CHAT "You need to go to a toilet."') if self.need_for_toilet > 1000000: self.send_msg('CHAT "You pee into your pants. Eww!"') self.need_for_toilet = 0 -- 2.30.2 From 96348254efb8e2ee17d597994ded663b5b036ade Mon Sep 17 00:00:00 2001 From: Christian Heller Date: Wed, 16 Dec 2020 16:29:46 +0100 Subject: [PATCH 02/16] In web client, get rid of matchAll, unsupported in older browsers. --- rogue_chat.html | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rogue_chat.html b/rogue_chat.html index c685581..b9673a1 100644 --- a/rogue_chat.html +++ b/rogue_chat.html @@ -986,10 +986,11 @@ let tui = { }; inner_links[y].push([url_start_x, end_x, url]); }; - const matches = msg.matchAll(/https?:\/\/[^\s]+/g) let link_data = {}; let url_ends = []; - for (const match of matches) { + const regexp = RegExp('https?://[^\\s]+', 'g'); + let match; + while ((match = regexp.exec(msg)) !== null) { const url = match[0]; const url_start = match.index; const url_end = match.index + match[0].length; -- 2.30.2 From 04fb13f0269b7d77ec178a797599c3d9a54061a6 Mon Sep 17 00:00:00 2001 From: Christian Heller Date: Wed, 16 Dec 2020 16:43:33 +0100 Subject: [PATCH 03/16] Fix CSS bug that made URL links unclickable in web client history. --- rogue_chat.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rogue_chat.html b/rogue_chat.html index b9673a1..212a1d6 100644 --- a/rogue_chat.html +++ b/rogue_chat.html @@ -16,7 +16,7 @@ terminal rows:

-
+
 

button controls for hard-to-remember keybindings

-- 2.30.2 From 8a8f2a0ebe1eeff55760a81aae58ed6e85b09294 Mon Sep 17 00:00:00 2001 From: Christian Heller Date: Wed, 16 Dec 2020 17:17:38 +0100 Subject: [PATCH 04/16] Fix bug of server not sending map data to player due to sound processing. --- plomrogue/things.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/plomrogue/things.py b/plomrogue/things.py index ef50a63..a3d622a 100644 --- a/plomrogue/things.py +++ b/plomrogue/things.py @@ -92,7 +92,17 @@ class Thing(ThingBase): lowered_nick = lower_msg_by_volume(name, volume, largest_audible_distance) symbol = '' - if listener.fov_test(self.position[0], self.position[1]): + # if listener.fov_test(self.position[0], self.position[1]): + # TODO: We might want to only show chat faces of players that are + # in the listener's FOV. However, if we do a fov_test here, + # this might set up a listener._fov where previously there was None, + # with ._fov = None serving to Game.send_gamestate() as an indicator + # that map view data for listener might be subject to change and + # therefore needs to be re-sent. If we generate an un-set ._fov + # here, this inhibits send_gamestate() from sending new map view + # data to listener. We need to re-structure this whole process + # if we want to use a FOV test on listener here. + if listener_distance < largest_audible_distance / 2: self.game.io.send('CHATFACE %s' % self.id_, c_id) if self.type_ == 'Player' and hasattr(self, 'thing_char'): symbol = '/@' + self.thing_char @@ -490,9 +500,6 @@ class ThingAnimate(Thing): self.multiprocessible_fov_stencil() return self._fov - def fov_stencil_make(self): - self._fov.make() - def fov_test(self, big_yx, little_yx): test_position = self.fov_stencil.target_yx(big_yx, little_yx) if self.fov_stencil.inside(test_position): -- 2.30.2 From 44ebe11030d1f2c6faf5cf57b83e950318893500 Mon Sep 17 00:00:00 2001 From: Christian Heller Date: Wed, 16 Dec 2020 17:48:11 +0100 Subject: [PATCH 05/16] To avoid login/logout race conditions, move login into Game.run_tick. --- plomrogue/commands.py | 18 +----------------- plomrogue/game.py | 32 +++++++++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/plomrogue/commands.py b/plomrogue/commands.py index 49312ee..653fc74 100644 --- a/plomrogue/commands.py +++ b/plomrogue/commands.py @@ -60,25 +60,9 @@ def cmd_LOGIN(game, nick, connection_id): nick = nick.strip() if len(nick) == 0: raise GameError('empty name') - for t in [t for t in game.things if t.type_ == 'Player' and t.name == nick]: - raise GameError('name already in use') if game.get_player(connection_id): raise GameError('cannot log in twice') - t = game.add_thing('Player', game.spawn_point) - t.name = nick - t.thing_char = game.get_next_player_char() - game.sessions[connection_id] = { - 'thing_id': t.id_, - 'status': 'player' - } - game.io.send('PLAYER_ID %s' % t.id_, connection_id) - game.io.send('LOGIN_OK', connection_id) - game.io.send('CHAT ' + quote(t.name + ' entered the map.')) - for s in [s for s in game.things - if s.type_ == 'SpawnPoint' and s.name == t.name]: - t.position = s.position - break - # game.changed = True # handled by game.add_thing + game.login_requests += [(nick, connection_id)] cmd_LOGIN.argtypes = 'string' def cmd_BECOME_ADMIN(game, password, connection_id): diff --git a/plomrogue/game.py b/plomrogue/game.py index 202a0fc..ec663b7 100755 --- a/plomrogue/game.py +++ b/plomrogue/game.py @@ -120,6 +120,7 @@ class Game(GameBase): self.changed = True self.changed_tiles = {'fov': [], 'other': []} self.io = GameIO(self, save_file) + self.login_requests = [] self.tasks = {} self.thing_types = {} self.sessions = {} @@ -328,7 +329,30 @@ class Game(GameBase): little_yx)] self.changed = True + def login(self, nick, connection_id): + for t in [t for t in self.things + if t.type_ == 'Player' and t.name == nick]: + self.io.send('GAME_ERROR ' + quote('name already in use'), + connection_id) + return + t = self.add_thing('Player', self.spawn_point) + t.name = nick + t.thing_char = self.get_next_player_char() + self.sessions[connection_id] = { + 'thing_id': t.id_, + 'status': 'player' + } + self.io.send('PLAYER_ID %s' % t.id_, connection_id) + self.io.send('LOGIN_OK', connection_id) + self.io.send('CHAT ' + quote(t.name + ' entered the map.')) + for s in [s for s in self.things + if s.type_ == 'SpawnPoint' and s.name == t.name]: + t.position = s.position + break + def run_tick(self): + + # update player sessions to_delete = [] for connection_id in self.sessions: connection_id_found = False @@ -344,7 +368,11 @@ class Game(GameBase): to_delete += [connection_id] for connection_id in to_delete: del self.sessions[connection_id] - # self.changed = True already handled by remove_thing + while len(self.login_requests) > 0: + login_request = self.login_requests.pop() + self.login(login_request[0], login_request[1]) + + # update game state for t in [t for t in self.things]: if t in self.things: try: @@ -357,6 +385,8 @@ class Game(GameBase): for connection_id in [c_id for c_id in self.sessions if self.sessions[c_id]['thing_id'] == t.id_]: self.io.send('PLAY_ERROR ' + quote(str(e)), connection_id) + + # send gamestate if it makes sense at this point if self.changed: self.turn += 1 # send_gamestate() can be rather expensive, due to among other reasons -- 2.30.2 From 9ac7e8befde463275086945c1ed5399bb8ef3af0 Mon Sep 17 00:00:00 2001 From: Christian Heller Date: Wed, 16 Dec 2020 18:55:14 +0100 Subject: [PATCH 06/16] Use circle-out passes for DijkstraMap, refactor with FovMap code. --- plomrogue/mapping.py | 89 ++++++++++++++++++++++---------------------- plomrogue/things.py | 12 +++--- 2 files changed, 49 insertions(+), 52 deletions(-) diff --git a/plomrogue/mapping.py b/plomrogue/mapping.py index 7b7ad3c..29078d8 100644 --- a/plomrogue/mapping.py +++ b/plomrogue/mapping.py @@ -85,6 +85,10 @@ class MapGeometry(): x = big_yx.x * self.size.x + little_yx.x return YX(y, x) + def basic_circle_out_move(self, position, direction): + mover = getattr(self, 'move__' + direction) + return mover(position) + class MapGeometryWithLeftRightMoves(MapGeometry): @@ -98,10 +102,12 @@ class MapGeometryWithLeftRightMoves(MapGeometry): class MapGeometrySquare(MapGeometryWithLeftRightMoves): + circle_out_directions = (('DOWN', 'LEFT'), ('LEFT', 'UP'), + ('UP', 'RIGHT'), ('RIGHT', 'DOWN')) - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fov_map_class = FovMapSquare + def circle_out_move(self, yx, direction): + yx = self.basic_circle_out_move(yx, direction[0]) + return self.basic_circle_out_move(yx, direction[1]) def define_segment(self, source_center, radius): source_center = self.undouble_yxyx(*source_center) @@ -118,10 +124,11 @@ class MapGeometrySquare(MapGeometryWithLeftRightMoves): class MapGeometryHex(MapGeometryWithLeftRightMoves): + circle_out_directions = ('DOWNLEFT', 'LEFT', 'UPLEFT', + 'UPRIGHT', 'RIGHT', 'DOWNRIGHT') - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fov_map_class = FovMapHex + def circle_out_move(self, yx, direction): + return self.basic_circle_out_move(yx, direction) def define_segment(self, source_center, radius): source_center = self.undouble_yxyx(*source_center) @@ -251,7 +258,6 @@ class DijkstraMap(SourcedMap): def __init__(self, *args, **kwargs): # TODO: check potential optimizations: - # - do a first pass circling out from the center # - somehow ignore tiles that have the lowest possible value (we can # compare with a precalculated map for given starting position) # - check if Python offers more efficient data structures to use here @@ -259,18 +265,34 @@ class DijkstraMap(SourcedMap): super().__init__(*args, **kwargs) self.terrain = [255] * self.size_i self[self.center] = 0 + + def work_tile(position_i): + shrunk_test = False + if self.source_map_segment[position_i] in self.block_chars: + return shrunk_test + neighbors = self.geometry.get_neighbors_i(position_i) + for direction in [d for d in neighbors if neighbors[d]]: + j = neighbors[direction] + if self.terrain[j] < self.terrain[position_i] - 1: + self.terrain[position_i] = self.terrain[j] + 1 + shrunk_test = True + return shrunk_test + + # TODO: refactor with FovMap.circle_out() shrunk = True while shrunk: shrunk = False - for i in range(self.size_i): - if self.source_map_segment[i] in self.block_chars: - continue - neighbors = self.geometry.get_neighbors_i(i) - for direction in [d for d in neighbors if neighbors[d]]: - j = neighbors[direction] - if self.terrain[j] < self.terrain[i] - 1: - self.terrain[i] = self.terrain[j] + 1 - shrunk = True + yx = self.center + distance = 1 + while distance <= self.radius: + yx = self.geometry.basic_circle_out_move(yx, 'RIGHT') + for dir_i in range(len(self.geometry.circle_out_directions)): + for dir_progress in range(distance): + direction = self.geometry.circle_out_directions[dir_i] + yx = self.geometry.circle_out_move(yx, direction) + position_i = self.get_position_index(yx) + shrunk = True if work_tile(position_i) else shrunk + distance += 1 # print('DEBUG Dijkstra') # line_to_print = [] # x = 0 @@ -353,7 +375,8 @@ class FovMap(SourcedMap): if unmerged: self.shadow_cones += [cone] - step_size = (CIRCLE / len(self.circle_out_directions)) / distance_to_center + step_size = (CIRCLE / len(self.geometry.circle_out_directions))\ + / distance_to_center number_steps = dir_i * distance_to_center + dir_progress left_arm = correct_arm(step_size / 2 + step_size * number_steps) right_arm = correct_arm(left_arm + step_size) @@ -366,10 +389,6 @@ class FovMap(SourcedMap): else: eval_cone([left_arm, right_arm]) - def basic_circle_out_move(self, pos, direction): - mover = getattr(self.geometry, 'move__' + direction) - return mover(pos) - def circle_out(self, yx, f): # Optimization potential: Precalculate movement positions. # Optimization potential: Precalculate what tiles are shaded by what tile @@ -379,30 +398,10 @@ class FovMap(SourcedMap): distance = 1 yx = YX(yx.y, yx.x) while distance <= self.radius: - yx = self.basic_circle_out_move(yx, 'RIGHT') - for dir_i in range(len(self.circle_out_directions)): + yx = self.geometry.basic_circle_out_move(yx, 'RIGHT') + for dir_i in range(len(self.geometry.circle_out_directions)): for dir_progress in range(distance): - direction = self.circle_out_directions[dir_i] - yx = self.circle_out_move(yx, direction) + direction = self.geometry.circle_out_directions[dir_i] + yx = self.geometry.circle_out_move(yx, direction) f(yx, distance, dir_i, dir_progress) distance += 1 - - - - -class FovMapHex(FovMap): - circle_out_directions = ('DOWNLEFT', 'LEFT', 'UPLEFT', - 'UPRIGHT', 'RIGHT', 'DOWNRIGHT') - - def circle_out_move(self, yx, direction): - return self.basic_circle_out_move(yx, direction) - - - -class FovMapSquare(FovMap): - circle_out_directions = (('DOWN', 'LEFT'), ('LEFT', 'UP'), - ('UP', 'RIGHT'), ('RIGHT', 'DOWN')) - - def circle_out_move(self, yx, direction): - yx = self.basic_circle_out_move(yx, direction[0]) - return self.basic_circle_out_move(yx, direction[1]) diff --git a/plomrogue/things.py b/plomrogue/things.py index a3d622a..950a777 100644 --- a/plomrogue/things.py +++ b/plomrogue/things.py @@ -1,5 +1,5 @@ from plomrogue.errors import GameError, PlayError -from plomrogue.mapping import YX +from plomrogue.mapping import YX, FovMap from plomrogue.misc import quote import random @@ -210,12 +210,11 @@ class Thing_Bottle(Thing): all_players = [t for t in self.game.things if t.type_ == 'Player'] # TODO: refactor with ThingPlayer.prepare_multiprocessible_fov_stencil # and ThingPlayer.fov_test - fov_map_class = self.game.map_geometry.fov_map_class fov_radius = 12 light_blockers = self.game.get_light_blockers() obstacles = [t.position for t in self.game.things if t.blocks_light] - fov = fov_map_class(light_blockers, obstacles, self.game.maps, - self.position, fov_radius, self.game.get_map) + fov = FovMap(light_blockers, obstacles, self.game.maps, + self.position, fov_radius, self.game.get_map) fov.init_terrain() visible_players = [] for p in all_players: @@ -480,12 +479,11 @@ class ThingAnimate(Thing): self.task = self.get_next_task() def prepare_multiprocessible_fov_stencil(self): - fov_map_class = self.game.map_geometry.fov_map_class fov_radius = 3 if self.drunk > 0 else 12 light_blockers = self.game.get_light_blockers() obstacles = [t.position for t in self.game.things if t.blocks_light] - self._fov = fov_map_class(light_blockers, obstacles, self.game.maps, - self.position, fov_radius, self.game.get_map) + self._fov = FovMap(light_blockers, obstacles, self.game.maps, + self.position, fov_radius, self.game.get_map) def multiprocessible_fov_stencil(self): self._fov.init_terrain() -- 2.30.2 From 0f924e175d0f321e703e3f00511b547c4a027dbc Mon Sep 17 00:00:00 2001 From: Christian Heller Date: Wed, 16 Dec 2020 19:28:45 +0100 Subject: [PATCH 07/16] Only calculate DijkstraMap until reachable targets. --- plomrogue/mapping.py | 19 ++++++++++++++----- plomrogue/things.py | 7 ++++--- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/plomrogue/mapping.py b/plomrogue/mapping.py index 29078d8..7071112 100644 --- a/plomrogue/mapping.py +++ b/plomrogue/mapping.py @@ -256,15 +256,21 @@ class SourcedMap(Map): class DijkstraMap(SourcedMap): - def __init__(self, *args, **kwargs): + def __init__(self, potential_targets, *args, **kwargs): # TODO: check potential optimizations: # - somehow ignore tiles that have the lowest possible value (we can # compare with a precalculated map for given starting position) # - check if Python offers more efficient data structures to use here - # - shorten radius to nearest possible target super().__init__(*args, **kwargs) self.terrain = [255] * self.size_i self[self.center] = 0 + targets = [] + for target_yxyx in potential_targets: + target = self.target_yx(*target_yxyx) + if target == self.center: + continue + if self.inside(target): + targets += [target] def work_tile(position_i): shrunk_test = False @@ -280,18 +286,21 @@ class DijkstraMap(SourcedMap): # TODO: refactor with FovMap.circle_out() shrunk = True - while shrunk: + while shrunk and len(targets) > 0: shrunk = False yx = self.center distance = 1 - while distance <= self.radius: + while distance <= self.radius and len(targets) > 0: yx = self.geometry.basic_circle_out_move(yx, 'RIGHT') for dir_i in range(len(self.geometry.circle_out_directions)): for dir_progress in range(distance): direction = self.geometry.circle_out_directions[dir_i] yx = self.geometry.circle_out_move(yx, direction) position_i = self.get_position_index(yx) - shrunk = True if work_tile(position_i) else shrunk + cur_shrunk = work_tile(position_i) + if cur_shrunk and yx in targets: + targets.remove(yx) + shrunk = shrunk or cur_shrunk distance += 1 # print('DEBUG Dijkstra') # line_to_print = [] diff --git a/plomrogue/things.py b/plomrogue/things.py index 950a777..dd14db5 100644 --- a/plomrogue/things.py +++ b/plomrogue/things.py @@ -70,10 +70,11 @@ class Thing(ThingBase): largest_audible_distance = 20 obstacles = [t.position for t in self.game.things if t.blocks_sound] + targets = [t.position for t in self.game.things if t.type_ == 'Player'] sound_blockers = self.game.get_sound_blockers() - dijkstra_map = DijkstraMap(sound_blockers, obstacles, self.game.maps, - self.position, largest_audible_distance, - self.game.get_map) + dijkstra_map = DijkstraMap(targets, sound_blockers, obstacles, + self.game.maps, self.position, + largest_audible_distance, self.game.get_map) url_limits = [] for m in re.finditer('https?://[^\s]+', msg): url_limits += [m.start(), m.end()] -- 2.30.2 From 6a16b94fff101209e90ce93ee53847d84a25da5c Mon Sep 17 00:00:00 2001 From: Christian Heller Date: Wed, 16 Dec 2020 19:56:50 +0100 Subject: [PATCH 08/16] Return cookie chars sorted. --- plomrogue/things.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/plomrogue/things.py b/plomrogue/things.py index dd14db5..4cc3512 100644 --- a/plomrogue/things.py +++ b/plomrogue/things.py @@ -626,6 +626,9 @@ class Thing_Player(ThingAnimate): self.game.players_hat_chars[self.name] += c def get_cookie_chars(self): + chars = ' #' # default if self.name in self.game.players_hat_chars: - return self.game.players_hat_chars[self.name] - return ' #' # default + chars = self.game.players_hat_chars[self.name] + chars_split = list(chars) + chars_split.sort() + return ''.join(chars_split) -- 2.30.2 From a87fff596af07090497fc16c429c4df739f0c3d9 Mon Sep 17 00:00:00 2001 From: Christian Heller Date: Wed, 16 Dec 2020 20:13:02 +0100 Subject: [PATCH 09/16] Make players non-blocking, but movement through them awkward. --- plomrogue/tasks.py | 11 +++++++++++ plomrogue/things.py | 1 - 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/plomrogue/tasks.py b/plomrogue/tasks.py index de9f1c8..fced650 100644 --- a/plomrogue/tasks.py +++ b/plomrogue/tasks.py @@ -49,6 +49,17 @@ class Task_MOVE(Task): if self.thing.blocks_light: self.thing.game.record_change(self.thing.position, 'fov') self.thing.position = self._get_move_target() + for t in [t for t in self.thing.game.things + if t.type_ == 'Player' and not t == self.thing + and t.position == self.thing.position]: + self.thing.send_msg('CHAT %s' % + quote('You get awkwardly close to %s.' % t.name)) + for c_id in self.thing.game.sessions: + if self.thing.game.sessions[c_id]['thing_id'] == t.id_: + t.send_msg('CHAT %s' % + quote('%s gets awkwardly close to you.' % + self.thing.name)) + break self.thing.game.record_change(self.thing.position, 'other') terrain = \ self.thing.game.maps[self.thing.position[0]][self.thing.position[1]] diff --git a/plomrogue/things.py b/plomrogue/things.py index 4cc3512..98d056a 100644 --- a/plomrogue/things.py +++ b/plomrogue/things.py @@ -433,7 +433,6 @@ class Thing_CookieSpawner(Thing): class ThingAnimate(Thing): - blocks_movement = True def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) -- 2.30.2 From 1101707a5518de54204fb2f856bdc540ae622991 Mon Sep 17 00:00:00 2001 From: Christian Heller Date: Wed, 16 Dec 2020 20:33:55 +0100 Subject: [PATCH 10/16] Add TASK:DOOR error message on absence of adjacent door. --- plomrogue/tasks.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/plomrogue/tasks.py b/plomrogue/tasks.py index fced650..4cf8700 100644 --- a/plomrogue/tasks.py +++ b/plomrogue/tasks.py @@ -187,6 +187,13 @@ class Task_DROP(Task): class Task_DOOR(Task): + def check(self): + action_radius = list(self.thing.game.map_geometry. + get_neighbors_yxyx(self.thing.position).values()) + if len([t for t in self.thing.game.things if + t.type_ == 'Door' and t.position in action_radius]) == 0: + raise PlayError('not standing next to a door to open/close') + def do(self): action_radius = list(self.thing.game.map_geometry. get_neighbors_yxyx(self.thing.position).values()) -- 2.30.2 From 5cd44408532e23648ddcd1d59004a9dae59694af Mon Sep 17 00:00:00 2001 From: Christian Heller Date: Wed, 16 Dec 2020 20:57:23 +0100 Subject: [PATCH 11/16] Add door keys and door locking. --- plomrogue/commands.py | 20 ++++++++++++++++++-- plomrogue/game.py | 4 +++- plomrogue/tasks.py | 19 +++++++++++++++++-- plomrogue/things.py | 23 +++++++++++++++++++++-- rogue_chat.py | 6 ++++-- 5 files changed, 63 insertions(+), 9 deletions(-) diff --git a/plomrogue/commands.py b/plomrogue/commands.py index 653fc74..074cc34 100644 --- a/plomrogue/commands.py +++ b/plomrogue/commands.py @@ -277,14 +277,16 @@ def cmd_GOD_THING_PROTECTION(game, thing_id, protection_char): t.protection = protection_char cmd_GOD_THING_PROTECTION.argtypes = 'int:pos char' -def cmd_THING_DOOR_CLOSED(game, thing_id): +def cmd_THING_DOOR_CLOSED(game, thing_id, locked): t = game.get_thing(thing_id) if not t: raise GameError('thing of ID %s not found' % thing_id) if not t.type_ == 'Door': raise GameError('thing of ID %s not door' % thing_id) t.close() -cmd_THING_DOOR_CLOSED.argtypes = 'int:pos' + if locked: + t.lock() +cmd_THING_DOOR_CLOSED.argtypes = 'int:pos bool' def cmd_THING_MUSICPLAYER_SETTINGS(game, thing_id, playing, index, repeat): t = game.get_thing(thing_id) @@ -380,3 +382,17 @@ def cmd_THING_HAT_DESIGN(game, thing_id, design): raise GameError('thing of ID %s not a hat' % thing_id) t.design = design cmd_THING_HAT_DESIGN.argtypes = 'int:pos string' + +def cmd_THING_DOOR_KEY(game, key_id, door_id): + key = game.get_thing(key_id) + if not key: + raise GameError('thing of ID %s not found' % key_id) + if key.type_ != 'DoorKey': + raise GameError('thing of ID %s not a door key' % key_id) + door = game.get_thing(door_id) + if not door: + raise GameError('thing of ID %s not found' % door_id) + if door.type_ != 'Door': + raise GameError('thing of ID %s not a door' % key_id) + key.door = door +cmd_THING_DOOR_KEY.argtypes = 'int:pos int:pos' diff --git a/plomrogue/game.py b/plomrogue/game.py index ec663b7..d3ab1e7 100755 --- a/plomrogue/game.py +++ b/plomrogue/game.py @@ -545,7 +545,7 @@ class Game(GameBase): if hasattr(t, 'installable') and (not t.portable): write(f, 'THING_INSTALLED %s' % t.id_) if t.type_ == 'Door' and t.blocks_movement: - write(f, 'THING_DOOR_CLOSED %s' % t.id_) + write(f, 'THING_DOOR_CLOSED %s %s' % (t.id_, int(t.locked))) elif t.type_ == 'Hat': write(f, 'THING_HAT_DESIGN %s %s' % (t.id_, quote(t.design))) @@ -557,6 +557,8 @@ class Game(GameBase): (t.id_, quote(item[0]), item[1])) elif t.type_ == 'Bottle' and not t.full: write(f, 'THING_BOTTLE_EMPTY %s' % t.id_) + elif t.type_ == 'DoorKey': + write(f, 'THING_DOOR_KEY %s %s' % (t.id_, t.door.id_)) write(f, 'SPAWN_POINT %s %s' % (self.spawn_point[0], self.spawn_point[1])) diff --git a/plomrogue/tasks.py b/plomrogue/tasks.py index 4cf8700..6ee2731 100644 --- a/plomrogue/tasks.py +++ b/plomrogue/tasks.py @@ -190,9 +190,19 @@ class Task_DOOR(Task): def check(self): action_radius = list(self.thing.game.map_geometry. get_neighbors_yxyx(self.thing.position).values()) - if len([t for t in self.thing.game.things if - t.type_ == 'Door' and t.position in action_radius]) == 0: + reachable_doors = [t for t in self.thing.game.things if + t.type_ == 'Door' and t.position in action_radius] + if len(reachable_doors) == 0: raise PlayError('not standing next to a door to open/close') + for door in reachable_doors: + if not door.blocks_movement: + return + if not door.locked: + return + if self.thing.carrying and self.thing.carrying.type_ == 'DoorKey'\ + and self.thing.carrying.door == door: + return + raise PlayError('cannot open locked door without its key') def do(self): action_radius = list(self.thing.game.map_geometry. @@ -203,6 +213,11 @@ class Task_DOOR(Task): t.open() else: t.close() + if self.thing.carrying and\ + self.thing.carrying.type_ == 'DoorKey' and\ + self.thing.carrying.door == t: + self.thing.send_msg('CHAT "You lock the door."') + t.lock() self.thing.game.record_change(t.position, 'other') self.thing.game.record_change(t.position, 'fov') diff --git a/plomrogue/things.py b/plomrogue/things.py index 98d056a..3686ab0 100644 --- a/plomrogue/things.py +++ b/plomrogue/things.py @@ -127,8 +127,8 @@ class ThingSpawner(Thing): def proceed(self): for t in [t for t in self.game.things if t != self and t.position == self.position]: - return - self.game.add_thing(self.child_type, self.position) + return None + return self.game.add_thing(self.child_type, self.position) @@ -164,16 +164,31 @@ class ThingInstallable(Thing): class Thing_DoorSpawner(ThingSpawner): child_type = 'Door' + def proceed(self): + door = super().proceed() + if door: + key = self.game.add_thing('DoorKey', self.position) + key.door = door + + + +class Thing_DoorKey(Thing): + portable = True + symbol_hint = 'k' + + class Thing_Door(ThingInstallable): symbol_hint = 'D' blocks_movement = False + locked = False def open(self): self.blocks_movement = False self.blocks_light = False self.blocks_sound = False + self.locked = False del self.thing_char def close(self): @@ -182,6 +197,10 @@ class Thing_Door(ThingInstallable): self.blocks_sound = True self.thing_char = '#' + def lock(self): + self.locked = True + self.thing_char = 'L' + class Thing_Psychedelic(Thing): diff --git a/rogue_chat.py b/rogue_chat.py index 31e53e4..a3654b0 100755 --- a/rogue_chat.py +++ b/rogue_chat.py @@ -15,7 +15,7 @@ from plomrogue.commands import (cmd_ALL, cmd_LOGIN, cmd_NICK, cmd_PING, cmd_THIN cmd_THING_BOTTLE_EMPTY, cmd_PLAYER_FACE, cmd_GOD_PLAYER_FACE, cmd_GOD_PLAYER_HAT, cmd_GOD_PLAYERS_HAT_CHARS, cmd_PLAYER_HAT, - cmd_TERRAIN_TAG) + cmd_TERRAIN_TAG, cmd_THING_DOOR_KEY) 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, @@ -26,7 +26,7 @@ from plomrogue.things import (Thing_Player, Thing_Item, Thing_ItemSpawner, Thing_BottleSpawner, Thing_BottleDeposit, Thing_MusicPlayer, Thing_Hat, Thing_HatRemixer, Thing_Cookie, Thing_CookieSpawner, Thing_Psychedelic, - Thing_PsychedelicSpawner) + Thing_PsychedelicSpawner, Thing_DoorKey) from plomrogue.config import config game = Game(config['savefile']) @@ -70,6 +70,7 @@ 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_command(cmd_THING_DOOR_KEY) game.register_task(Task_WAIT) game.register_task(Task_MOVE) game.register_task(Task_WRITE) @@ -88,6 +89,7 @@ game.register_thing_type(Thing_ItemSpawner) game.register_thing_type(Thing_SpawnPoint) game.register_thing_type(Thing_SpawnPointSpawner) game.register_thing_type(Thing_Door) +game.register_thing_type(Thing_DoorKey) game.register_thing_type(Thing_DoorSpawner) game.register_thing_type(Thing_Bottle) game.register_thing_type(Thing_BottleSpawner) -- 2.30.2 From 15ad903f7da0dd35945f79ff26e4528378eaf31f Mon Sep 17 00:00:00 2001 From: Christian Heller Date: Wed, 16 Dec 2020 21:23:39 +0100 Subject: [PATCH 12/16] Replace client TURN line with frequently updated toilet need stat. --- plomrogue/game.py | 11 +++++++---- rogue_chat.html | 18 ++++++++++-------- rogue_chat_curses.py | 17 +++++++++++------ 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/plomrogue/game.py b/plomrogue/game.py index d3ab1e7..5f91a11 100755 --- a/plomrogue/game.py +++ b/plomrogue/game.py @@ -136,9 +136,10 @@ class Game(GameBase): self.players_hat_chars = {} self.player_char_i = -1 self.admin_passwords = [] - self.send_gamestate_interval = datetime.timedelta(seconds=0.04) + self.send_gamestate_min_interval = datetime.timedelta(seconds=0.04) + self.send_gamestate_max_interval = datetime.timedelta(seconds=5) self.last_send_gamestate = datetime.datetime.now() -\ - self.send_gamestate_interval + self.send_gamestate_min_interval self.terrains = { '.': Terrain('.', 'floor'), 'X': Terrain('X', 'wall', blocks_light=True, blocks_sound=True, @@ -276,6 +277,7 @@ class Game(GameBase): player = self.get_player(c_id) self.io.send('PLAYERS_HAT_CHARS ' + quote(player.get_cookie_chars()), c_id) + self.io.send('BLADDER_PRESSURE %s' % player.need_for_toilet) if player.id_ in player_ids_send_fov: self.io.send('FOV %s' % quote(player.fov_stencil.terrain), c_id) self.io.send('MAP %s %s %s' % (self.get_map_geometry_shape(), @@ -387,12 +389,13 @@ class Game(GameBase): self.io.send('PLAY_ERROR ' + quote(str(e)), connection_id) # send gamestate if it makes sense at this point - if self.changed: + if self.changed or self.last_send_gamestate < \ + datetime.datetime.now() - self.send_gamestate_max_interval: self.turn += 1 # send_gamestate() can be rather expensive, due to among other reasons # re-calculating players' FOVs, so don't send it out too often if self.last_send_gamestate < \ - datetime.datetime.now() -self.send_gamestate_interval: + datetime.datetime.now() - self.send_gamestate_min_interval: n_changes = 0 for type_ in self.changed_tiles: n_changes += len(self.changed_tiles[type_]) diff --git a/rogue_chat.html b/rogue_chat.html index 212a1d6..291a98c 100644 --- a/rogue_chat.html +++ b/rogue_chat.html @@ -129,8 +129,8 @@ terminal rows: