From: Christian Heller Date: Sun, 13 Dec 2020 01:23:07 +0000 (+0100) Subject: Make terrain types configurable. X-Git-Url: https://plomlompom.com/repos/%7B%7B%20web_path%20%7D%7D/decks/%7B%7Bdb.prefix%7D%7D/static/blog?a=commitdiff_plain;h=a789724e6b1b5eb514f82ac4d7092f7575180c31;p=plomrogue2 Make terrain types configurable. --- diff --git a/plomrogue/commands.py b/plomrogue/commands.py index 026f996..df4b7e2 100644 --- a/plomrogue/commands.py +++ b/plomrogue/commands.py @@ -1,5 +1,6 @@ from plomrogue.misc import quote from plomrogue.errors import GameError, ArgError +from plomrogue.misc import Terrain @@ -16,11 +17,17 @@ def cmd_THING_TYPES(game, connection_id): cmd_THING_TYPES.argtypes = '' def cmd_TERRAINS(game, connection_id): - for t in game.terrains.keys(): - game.io.send('TERRAIN %s %s' % (quote(t), quote(game.terrains[t])), - connection_id) + for t in game.terrains.values(): + game.io.send('TERRAIN %s %s' % (quote(t.character), + quote(t.description)), connection_id) cmd_TERRAINS.argtypes = '' +def cmd_TERRAIN(game, character, description, + blocks_light, blocks_sound, blocks_movement): + game.terrains[character] = Terrain(character, description, blocks_light, + blocks_sound, blocks_movement) +cmd_TERRAIN.argtypes = 'char string bool bool bool' + def cmd_ALL(game, msg, connection_id): speaker = game.get_player(connection_id) if not speaker: @@ -226,6 +233,7 @@ def cmd_MAP(game, geometry, size): if geometry == 'Hex': map_geometry_class = MapGeometryHex game.new_world(map_geometry_class(size)) + game.terrains = {} cmd_MAP.argtypes = 'string:map_geometry yx_tuple:pos' def cmd_MAP_CONTROL_LINE(game, big_yx, y, line): diff --git a/plomrogue/game.py b/plomrogue/game.py index e1c92cf..7859814 100755 --- a/plomrogue/game.py +++ b/plomrogue/game.py @@ -12,7 +12,7 @@ class GameBase: def __init__(self): self.turn = 0 self.things = [] - self.map_geometry = MapGeometrySquare(YX(24, 40)) + self.map_geometry = MapGeometrySquare(YX(32, 32)) self.commands = {} def get_thing(self, id_): @@ -115,6 +115,7 @@ import os class Game(GameBase): def __init__(self, save_file, *args, **kwargs): + from plomrogue.misc import Terrain super().__init__(*args, **kwargs) self.changed = True self.changed_tiles = [] @@ -137,15 +138,11 @@ class Game(GameBase): self.last_send_gamestate = datetime.datetime.now() -\ self.send_gamestate_interval self.terrains = { - '.': 'floor', - 'X': 'wall', - '=': 'window', - '#': 'bed', - 'T': 'desk', - '8': 'cupboard', - '[': 'glass door', - 'o': 'sink', - 'O': 'toilet' + '.': Terrain('.', 'floor'), + 'X': Terrain('X', 'wall', blocks_light=True, blocks_sound=True, + blocks_movement=True), + '=': Terrain('=', 'glass', blocks_sound=True, blocks_movement=True), + 'T': Terrain('T', 'table', blocks_movement=True), } if os.path.exists(self.io.save_file): if not os.path.isfile(self.io.save_file): @@ -431,6 +428,28 @@ class Game(GameBase): self.player_char_i = 0 return self.player_chars[self.player_char_i] + def get_foo_blockers(self, foo): + foo_blockers = '' + for t in self.terrains.values(): + block_attr = getattr(t, 'blocks_' + foo) + if block_attr: + foo_blockers += t.character + return foo_blockers + + def get_sound_blockers(self): + return self.get_foo_blockers('sound') + + def get_light_blockers(self): + return self.get_foo_blockers('light') + + def get_movement_blockers(self): + return self.get_foo_blockers('movement') + + def get_flatland(self): + for t in self.terrains.values: + if not t.blocks_movement: + return t.character + def save(self): def write(f, msg): @@ -440,6 +459,12 @@ class Game(GameBase): write(f, 'TURN %s' % self.turn) map_geometry_shape = self.get_map_geometry_shape() write(f, 'MAP %s %s' % (map_geometry_shape, self.map_geometry.size,)) + for terrain in self.terrains.values(): + write(f, 'TERRAIN %s %s %s %s %s' % (quote(terrain.character), + quote(terrain.description), + int(terrain.blocks_light), + int(terrain.blocks_sound), + int(terrain.blocks_movement))) for big_yx in [yx for yx in self.maps if self.maps[yx].modified]: for y, line in self.maps[big_yx].lines(): write(f, 'MAP_LINE %s %5s %s' % (big_yx, y, quote(line))) diff --git a/plomrogue/mapping.py b/plomrogue/mapping.py index 2b19f56..e3c071f 100644 --- a/plomrogue/mapping.py +++ b/plomrogue/mapping.py @@ -165,7 +165,7 @@ class Map(): def __init__(self, map_geometry): self.geometry = map_geometry - self.terrain = '.' * self.size_i + self.terrain = '.' * self.size_i # TODO: use Game.get_flatland()? def __getitem__(self, yx): return self.terrain[self.get_position_index(yx)] @@ -210,7 +210,9 @@ class Map(): class SourcedMap(Map): - def __init__(self, things, source_maps, source_center, radius, get_map): + def __init__(self, block_chars, things, source_maps, source_center, radius, + get_map): + self.block_chars = block_chars self.radius = radius example_map = get_map(YX(0, 0)) self.source_geometry = example_map.geometry @@ -231,7 +233,7 @@ class SourcedMap(Map): for yx in self: # TODO: iter and source_yxyx expensive, cache earlier? big_yx, little_yx = self.source_yxyx(yx) if big_yx in obstacles and little_yx in obstacles[big_yx]: - self.source_map_segment += 'X' + self.source_map_segment += self.block_chars[0] else: self.source_map_segment += source_maps[big_yx][little_yx] @@ -270,7 +272,7 @@ class DijkstraMap(SourcedMap): while shrunk: shrunk = False for i in range(self.size_i): - if self.source_map_segment[i] in 'X=': + 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]]: @@ -310,7 +312,8 @@ class FovMap(SourcedMap): return self def throws_shadow(self, yx): - return self.source_map_segment[self.get_position_index(yx)] == 'X' + return self.source_map_segment[self.get_position_index(yx)]\ + in self.block_chars def shadow_process(self, yx, distance_to_center, dir_i, dir_progress): # Possible optimization: If no shadow_cones yet and self[yx] == '.', @@ -377,8 +380,7 @@ class FovMap(SourcedMap): return mover(pos) def circle_out(self, yx, f): - # Optimization potential: Precalculate movement positions. (How to check - # circle_in_map then?) + # Optimization potential: Precalculate movement positions. # Optimization potential: Precalculate what tiles are shaded by what tile # and skip evaluation of already shaded tile. (This only works if tiles # shading implies they completely lie in existing shades; otherwise we diff --git a/plomrogue/misc.py b/plomrogue/misc.py index a3f7298..84b0a8a 100644 --- a/plomrogue/misc.py +++ b/plomrogue/misc.py @@ -1,3 +1,15 @@ +class Terrain: + + def __init__(self, character, description, blocks_light=False, + blocks_sound=False, blocks_movement=False): + self.character = character + self.description = description + self.blocks_light = blocks_light + self.blocks_sound = blocks_sound + self.blocks_movement = blocks_movement + + + def quote(string): """Quote & escape string so client interprets it as single token.""" quoted = [] diff --git a/plomrogue/tasks.py b/plomrogue/tasks.py index 4b63634..f7fb312 100644 --- a/plomrogue/tasks.py +++ b/plomrogue/tasks.py @@ -34,10 +34,11 @@ class Task_MOVE(Task): def check(self): test_yxyx = self._get_move_target() + move_blockers = self.thing.game.get_movement_blockers() if test_yxyx in [t.position for t in self.thing.game.things if t.blocking]: raise PlayError('blocked by other thing') - elif self.thing.game.maps[test_yxyx[0]][test_yxyx[1]] != '.': + elif self.thing.game.maps[test_yxyx[0]][test_yxyx[1]] in move_blockers: raise PlayError('blocked by impassable tile') def do(self): @@ -77,7 +78,7 @@ class Task_FLATTEN_SURROUNDINGS(Task): self.thing.position).values()): if not self.thing.game.can_do_tile_with_pw(*yxyx, self.args[0]): continue - self.thing.game.maps[yxyx[0]][yxyx[1]] = '.' + self.thing.game.maps[yxyx[0]][yxyx[1]] = self.game.get_flatland() self.thing.game.record_fov_change(yxyx) diff --git a/plomrogue/things.py b/plomrogue/things.py index 8885aa0..70d9994 100644 --- a/plomrogue/things.py +++ b/plomrogue/things.py @@ -70,7 +70,8 @@ class Thing(ThingBase): largest_audible_distance = 20 # player's don't block sound (or should they?) things = [t for t in self.game.things if t.type_ != 'Player'] - dijkstra_map = DijkstraMap(things, self.game.maps, self.position, + sound_blockers = self.game.get_sound_blockers() + dijkstra_map = DijkstraMap(sound_blockers, things, self.game.maps, self.position, largest_audible_distance, self.game.get_map) url_limits = [] for m in re.finditer('https?://[^\s]+', msg): @@ -182,7 +183,8 @@ class Thing_Bottle(Thing): # and ThingPlayer.fov_test fov_map_class = self.game.map_geometry.fov_map_class fov_radius = 12 - fov = fov_map_class(self.game.things, self.game.maps, + light_blockers = self.game.get_light_blockers() + fov = fov_map_class(light_blockers, self.game.things, self.game.maps, self.position, fov_radius, self.game.get_map) fov.init_terrain() visible_players = [] @@ -439,7 +441,8 @@ class ThingAnimate(Thing): 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 - self._fov = fov_map_class(self.game.things, self.game.maps, + light_blockers = self.game.get_light_blockers() + self._fov = fov_map_class(light_blockers, self.game.things, self.game.maps, self.position, fov_radius, self.game.get_map) def multiprocessible_fov_stencil(self): diff --git a/rogue_chat.py b/rogue_chat.py index 8187583..d7dd64b 100755 --- a/rogue_chat.py +++ b/rogue_chat.py @@ -11,7 +11,7 @@ from plomrogue.commands import (cmd_ALL, cmd_LOGIN, cmd_NICK, cmd_PING, cmd_THIN cmd_GOD_THING_PROTECTION, cmd_THING_PROTECTION, cmd_SET_MAP_CONTROL_PASSWORD, cmd_SPAWN_POINT, cmd_THING_MUSICPLAYER_SETTINGS, cmd_THING_HAT_DESIGN, - cmd_THING_MUSICPLAYER_PLAYLIST_ITEM, + cmd_THING_MUSICPLAYER_PLAYLIST_ITEM, cmd_TERRAIN, cmd_THING_BOTTLE_EMPTY, cmd_PLAYER_FACE, cmd_GOD_PLAYER_FACE, cmd_GOD_PLAYER_HAT) from plomrogue.tasks import (Task_WAIT, Task_MOVE, Task_WRITE, Task_PICK_UP, @@ -32,6 +32,7 @@ game.register_command(cmd_LOGIN) game.register_command(cmd_NICK) game.register_command(cmd_TURN) game.register_command(cmd_MAP) +game.register_command(cmd_TERRAIN) game.register_command(cmd_MAP_LINE) game.register_command(cmd_MAP_CONTROL_LINE) game.register_command(cmd_MAP_CONTROL_PW)