class_name FogRenderer extends TileMapLayer # Fog renderer — draws fog overlay on non-visible tiles (D-011) # Three visibility states per tile: # visible = no fog tile (clear) # fog-edge = semi-transparent dark overlay (adjacent to visible) # hidden = opaque black overlay # # Atlas layout: # (0,0) = full fog (opaque black) # (1,0) = fog edge (semi-transparent) # # Note: fog-edge uses 8-directional neighbors for visual smoothness. # Actual visibility boundaries come from the server's shadowcasting (D-011). # Fog-returns-over-time (D-011 decay) is tracked in #113, not here. const TILE_SIZE: int = Constants.TILE_SIZE var _initialized: bool = false var _all_tile_positions: Dictionary = {} # Vector2i -> true, all known map tiles func _ready() -> void: _setup_tileset() _initialized = true print("FogRenderer: Initialized") func _setup_tileset() -> void: var ts := TileSet.new() ts.tile_size = Vector2i(TILE_SIZE, TILE_SIZE) var source := TileSetAtlasSource.new() var img := Image.create(TILE_SIZE * 2, TILE_SIZE, false, Image.FORMAT_RGBA8) # Full fog (0,0) — opaque black img.fill_rect(Rect2i(0, 0, TILE_SIZE, TILE_SIZE), Color(0.02, 0.02, 0.05, 1.0)) # Fog edge (1,0) — semi-transparent dark img.fill_rect(Rect2i(TILE_SIZE, 0, TILE_SIZE, TILE_SIZE), Color(0.02, 0.02, 0.05, 0.6)) var tex := ImageTexture.create_from_image(img) source.texture = tex source.texture_region_size = Vector2i(TILE_SIZE, TILE_SIZE) source.create_tile(Vector2i(0, 0)) source.create_tile(Vector2i(1, 0)) ts.add_source(source) tile_set = ts # Register all known tile positions (called when tile data arrives) func register_tile_positions(tiles: Array) -> void: _all_tile_positions.clear() for tile_data in tiles: if tile_data.has("x") and tile_data.has("y"): _all_tile_positions[Vector2i(tile_data.x, tile_data.y)] = true # Update fog based on visible positions. # player_pos reserved for future fog-decay tracking (#113). func update_fog(visible_positions: Dictionary, _player_pos: Vector2) -> void: if not _initialized: return clear() if _all_tile_positions.is_empty() or visible_positions.is_empty(): return # Build set of fog-edge positions (8-directional neighbors of visible tiles) var fog_edge: Dictionary = {} var neighbors := [ Vector2i(-1, 0), Vector2i(1, 0), Vector2i(0, -1), Vector2i(0, 1), Vector2i(-1, -1), Vector2i(1, -1), Vector2i(-1, 1), Vector2i(1, 1), ] for pos in visible_positions: for offset in neighbors: var neighbor_pos: Vector2i = pos + offset if not visible_positions.has(neighbor_pos) and _all_tile_positions.has(neighbor_pos): fog_edge[neighbor_pos] = true # Place fog tiles on all known positions that aren't visible for pos in _all_tile_positions: if visible_positions.has(pos): continue # Visible — no fog elif fog_edge.has(pos): set_cell(pos, 0, Vector2i(1, 0)) # Fog edge — semi-transparent else: set_cell(pos, 0, Vector2i(0, 0)) # Full fog — opaque