##[br] Reads a TileSet and dictates which tiles in the display map ##[br] match up with its neighbors in the world map class_name TerrainDual extends Resource # Functions are ordered top to bottom in the transformation pipeline ## Maps a TileSet to a Neighborhood. static func tileset_neighborhood(tile_set: TileSet) -> Neighborhood: return GRID_NEIGHBORHOODS[Display.tileset_gridshape(tile_set)] ## Maps a GridShape to a Neighborhood. const GRID_NEIGHBORHOODS = { Display.GridShape.SQUARE: Neighborhood.SQUARE, Display.GridShape.ISO: Neighborhood.ISOMETRIC, Display.GridShape.HALF_OFF_HORI: Neighborhood.TRIANGLE_HORIZONTAL, Display.GridShape.HALF_OFF_VERT: Neighborhood.TRIANGLE_VERTICAL, Display.GridShape.HEX_HORI: Neighborhood.TRIANGLE_HORIZONTAL, Display.GridShape.HEX_VERT: Neighborhood.TRIANGLE_VERTICAL, } ## A specific neighborhood that the Display tiles will look at. enum Neighborhood { SQUARE, ISOMETRIC, TRIANGLE_HORIZONTAL, TRIANGLE_VERTICAL, } ## Maps a Neighborhood to a set of atlas terrain neighbors. const NEIGHBORHOOD_LAYERS := { Neighborhood.SQUARE: [ { # [] 'terrain_neighborhood': [ TileSet.CELL_NEIGHBOR_TOP_LEFT_CORNER, TileSet.CELL_NEIGHBOR_TOP_RIGHT_CORNER, TileSet.CELL_NEIGHBOR_BOTTOM_LEFT_CORNER, TileSet.CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER, ], 'display_to_world_neighborhood': [ [TileSet.CELL_NEIGHBOR_TOP_LEFT_CORNER], [TileSet.CELL_NEIGHBOR_TOP_SIDE], [TileSet.CELL_NEIGHBOR_LEFT_SIDE], [], ], }, ], Neighborhood.ISOMETRIC: [ { # <> 'terrain_neighborhood': [ TileSet.CELL_NEIGHBOR_TOP_CORNER, TileSet.CELL_NEIGHBOR_RIGHT_CORNER, TileSet.CELL_NEIGHBOR_LEFT_CORNER, TileSet.CELL_NEIGHBOR_BOTTOM_CORNER, ], 'display_to_world_neighborhood': [ [TileSet.CELL_NEIGHBOR_TOP_CORNER], [TileSet.CELL_NEIGHBOR_TOP_RIGHT_SIDE], [TileSet.CELL_NEIGHBOR_TOP_LEFT_SIDE], [], ], }, ], Neighborhood.TRIANGLE_HORIZONTAL: [ { # v 'terrain_neighborhood': [ TileSet.CELL_NEIGHBOR_BOTTOM_CORNER, TileSet.CELL_NEIGHBOR_TOP_LEFT_CORNER, TileSet.CELL_NEIGHBOR_TOP_RIGHT_CORNER, ], 'display_to_world_neighborhood': [ [], [TileSet.CELL_NEIGHBOR_TOP_LEFT_SIDE], [TileSet.CELL_NEIGHBOR_TOP_RIGHT_SIDE], ], }, { # ^ 'terrain_neighborhood': [ TileSet.CELL_NEIGHBOR_TOP_CORNER, TileSet.CELL_NEIGHBOR_BOTTOM_LEFT_CORNER, TileSet.CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER, ], 'display_to_world_neighborhood': [ [TileSet.CELL_NEIGHBOR_TOP_LEFT_SIDE], [TileSet.CELL_NEIGHBOR_LEFT_SIDE], [], ], }, ], # TODO: this is just TRIANGLE_HORIZONTAL but transposed. this can be refactored. Neighborhood.TRIANGLE_VERTICAL: [ { # > 'terrain_neighborhood': [ TileSet.CELL_NEIGHBOR_RIGHT_CORNER, TileSet.CELL_NEIGHBOR_TOP_LEFT_CORNER, TileSet.CELL_NEIGHBOR_BOTTOM_LEFT_CORNER, ], 'display_to_world_neighborhood': [ [], [TileSet.CELL_NEIGHBOR_TOP_LEFT_SIDE], [TileSet.CELL_NEIGHBOR_BOTTOM_LEFT_SIDE], ], }, { # < 'terrain_neighborhood': [ TileSet.CELL_NEIGHBOR_LEFT_CORNER, TileSet.CELL_NEIGHBOR_TOP_RIGHT_CORNER, TileSet.CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER, ], 'display_to_world_neighborhood': [ [TileSet.CELL_NEIGHBOR_TOP_LEFT_SIDE], [TileSet.CELL_NEIGHBOR_TOP_SIDE], [], ], }, ], } ## The Neighborhood type of this TerrainDual. var neighborhood: Neighborhood ## Maps a terrain type to its sprite as registered in the TerrainDual. var terrains: Dictionary ## The TerrainLayers for this TerrainDual. var layers: Array var _tileset_watcher: TileSetWatcher func _init(tileset_watcher: TileSetWatcher) -> void: _tileset_watcher = tileset_watcher _tileset_watcher.terrains_changed.connect(_changed, 1) _changed() ##[br] Emitted when any of the terrains change. ##[br] NOTE: Prefer connecting to TerrainDual.changed instead of TileSetWatcher.terrains_changed. func _changed() -> void: #print('SIGNAL EMITTED: changed(%s)' % {}) read_tileset(_tileset_watcher.tile_set) emit_changed() ## Create rules for every atlas in a TileSet. func read_tileset(tile_set: TileSet) -> void: terrains = {} layers = [] neighborhood = Neighborhood.SQUARE # default if tile_set == null: return neighborhood = tileset_neighborhood(tile_set) layers = NEIGHBORHOOD_LAYERS[neighborhood].map(TerrainLayer.new) for i in tile_set.get_source_count(): var sid := tile_set.get_source_id(i) var src := tile_set.get_source(sid) if src is not TileSetAtlasSource: continue read_atlas(src, sid) ## Create rules for every tile in an atlas. func read_atlas(atlas: TileSetAtlasSource, sid: int) -> void: var size = atlas.get_atlas_grid_size() for y in size.y: for x in size.x: var tile := Vector2i(x, y) # Take only existing tiles if not atlas.has_tile(tile): continue read_tile(atlas, sid, tile) ## Add a new rule for a specific tile in an atlas. func read_tile(atlas: TileSetAtlasSource, sid: int, tile: Vector2i) -> void: var data := atlas.get_tile_data(tile, 0) var mapping := {'sid': sid, 'tile': tile} var terrain_set := data.terrain_set if terrain_set != 0: push_warning( "The tile at %s has a terrain set of %d. Only terrain set 0 is supported." % [mapping, terrain_set] ) return var terrain := data.terrain if terrain != -1: if not terrain in terrains: terrains[terrain] = mapping var filters = NEIGHBORHOOD_LAYERS[neighborhood] for i in layers.size(): var layer: TerrainLayer = layers[i] layer._register_tile(data, mapping)