From f256fda141aeed64cde5b173b48d9be05d67ea06 Mon Sep 17 00:00:00 2001 From: Maruno17 Date: Sat, 2 Oct 2021 23:54:06 +0100 Subject: [PATCH] Removed old map renderer, tidied up --- Data/Scripts/005_Sprites/007_Spriteset_Map.rb | 19 - .../005_Sprites/008_Sprite_AnimationSprite.rb | 2 - .../006_Map renderer/001_TilemapLoader.rb | 59 - ...1_NewTilemap.rb => 001_TilemapRenderer.rb} | 228 +--- .../006_Map renderer/002_CustomTilemap.rb | 1026 --------------- .../006_Map renderer/002_TilesetWrapper.rb | 96 ++ .../006_Map renderer/003_AutotileExpander.rb | 72 ++ ...wingHelper.rb => 004_TileDrawingHelper.rb} | 14 +- .../New Tilemap/002_TileWrap.rb | 100 -- .../New Tilemap/003_AutotileExpander.rb | 55 - .../008_Overworld_RandomDungeons.rb | 1125 +++++++++-------- 11 files changed, 806 insertions(+), 1990 deletions(-) delete mode 100644 Data/Scripts/006_Map renderer/001_TilemapLoader.rb rename Data/Scripts/006_Map renderer/{New Tilemap/001_NewTilemap.rb => 001_TilemapRenderer.rb} (72%) delete mode 100644 Data/Scripts/006_Map renderer/002_CustomTilemap.rb create mode 100644 Data/Scripts/006_Map renderer/002_TilesetWrapper.rb create mode 100644 Data/Scripts/006_Map renderer/003_AutotileExpander.rb rename Data/Scripts/006_Map renderer/{003_TileDrawingHelper.rb => 004_TileDrawingHelper.rb} (95%) delete mode 100644 Data/Scripts/006_Map renderer/New Tilemap/002_TileWrap.rb delete mode 100644 Data/Scripts/006_Map renderer/New Tilemap/003_AutotileExpander.rb diff --git a/Data/Scripts/005_Sprites/007_Spriteset_Map.rb b/Data/Scripts/005_Sprites/007_Spriteset_Map.rb index 37e2f4609..fcc5dcb46 100644 --- a/Data/Scripts/005_Sprites/007_Spriteset_Map.rb +++ b/Data/Scripts/005_Sprites/007_Spriteset_Map.rb @@ -33,7 +33,6 @@ end class Spriteset_Map attr_reader :map - attr_accessor :tilemap @@viewport0 = Viewport.new(0, 0, Settings::SCREEN_WIDTH, Settings::SCREEN_HEIGHT) # Panorama @@viewport0.z = -100 @@viewport1 = Viewport.new(0, 0, Settings::SCREEN_WIDTH, Settings::SCREEN_HEIGHT) # Map, events, player, fog @@ -49,15 +48,6 @@ class Spriteset_Map @map = (map) ? map : $game_map $scene.map_renderer.add_tileset(@map.tileset_name) @map.autotile_names.each { |filename| $scene.map_renderer.add_autotile(filename) } -# @tilemap = TilemapLoader.new(@@viewport1) -# @tilemap.tileset = pbGetTileset(@map.tileset_name) -# for i in 0...7 -# autotile_name = @map.autotile_names[i] -# @tilemap.autotiles[i] = pbGetAutotile(autotile_name) -# end -# @tilemap.map_data = @map.data -# @tilemap.priorities = @map.priorities -# @tilemap.terrain_tags = @map.terrain_tags @panorama = AnimatedPlane.new(@@viewport0) @fog = AnimatedPlane.new(@@viewport1) @fog.z = 3000 @@ -72,11 +62,6 @@ class Spriteset_Map end def dispose -# @tilemap.tileset.dispose -# for i in 0...7 -# @tilemap.autotiles[i].dispose -# end -# @tilemap.dispose $scene.map_renderer.remove_tileset(@map.tileset_name) @map.autotile_names.each { |filename| $scene.map_renderer.remove_autotile(filename) } @panorama.dispose @@ -85,7 +70,6 @@ class Spriteset_Map sprite.dispose end @weather.dispose -# @tilemap = nil @panorama = nil @fog = nil @character_sprites.clear @@ -117,13 +101,10 @@ class Spriteset_Map end tmox = (@map.display_x/Game_Map::X_SUBPIXELS).round tmoy = (@map.display_y/Game_Map::Y_SUBPIXELS).round -# @tilemap.ox = tmox -# @tilemap.oy = tmoy @@viewport1.rect.set(0,0,Graphics.width,Graphics.height) @@viewport1.ox = 0 @@viewport1.oy = 0 @@viewport1.ox += $game_screen.shake -# @tilemap.update @panorama.ox = tmox/2 @panorama.oy = tmoy/2 @fog.ox = tmox+@map.fog_ox diff --git a/Data/Scripts/005_Sprites/008_Sprite_AnimationSprite.rb b/Data/Scripts/005_Sprites/008_Sprite_AnimationSprite.rb index dfdccf7fb..8d904f0f5 100644 --- a/Data/Scripts/005_Sprites/008_Sprite_AnimationSprite.rb +++ b/Data/Scripts/005_Sprites/008_Sprite_AnimationSprite.rb @@ -75,8 +75,6 @@ class Spriteset_Map end def update -# return if @tilemap.disposed? -# pbDayNightTint(@tilemap) @@viewport3.tone.set(0,0,0,0) _animationSprite_update for i in 0...@usersprites.length diff --git a/Data/Scripts/006_Map renderer/001_TilemapLoader.rb b/Data/Scripts/006_Map renderer/001_TilemapLoader.rb deleted file mode 100644 index 9540718eb..000000000 --- a/Data/Scripts/006_Map renderer/001_TilemapLoader.rb +++ /dev/null @@ -1,59 +0,0 @@ -class TilemapLoader - def initialize(viewport) - @viewport = viewport - @tilemap = nil - @color = Color.new(0,0,0,0) - @tone = Tone.new(0,0,0,0) - updateClass - end - - def updateClass - setClass(CustomTilemap) - end - - def setClass(cls) - newtilemap = cls.new(@viewport) - if @tilemap - newtilemap.tileset = @tilemap.tileset - newtilemap.map_data = @tilemap.map_data - newtilemap.flash_data = @tilemap.flash_data - newtilemap.priorities = @tilemap.priorities - newtilemap.terrain_tags = @tilemap.terrain_tags - newtilemap.visible = @tilemap.visible - newtilemap.ox = @tilemap.ox - newtilemap.oy = @tilemap.oy - for i in 0...7 - newtilemap.autotiles[i] = @tilemap.autotiles[i] - end - @tilemap.dispose - @tilemap = newtilemap - newtilemap.update - else - @tilemap = newtilemap - end - end - - def dispose; @tilemap.dispose; end - def disposed?; @tilemap && @tilemap.disposed?; end - def update; @tilemap.update; end - def viewport; @tilemap.viewport; end - def autotiles; @tilemap.autotiles; end - def tileset; @tilemap.tileset; end - def tileset=(v); @tilemap.tileset = v; end - def map_data; @tilemap.map_data; end - def map_data=(v); @tilemap.map_data = v; end - def flash_data; @tilemap.flash_data; end - def flash_data=(v); @tilemap.flash_data = v; end - def priorities; @tilemap.priorities; end - def priorities=(v); @tilemap.priorities = v; end - def terrain_tags; (@tilemap.terrain_tags rescue nil); end - def terrain_tags=(v); (@tilemap.terrain_tags = v rescue nil); end - def visible; @tilemap.visible; end - def visible=(v); @tilemap.visible = v; end - def tone; (@tilemap.tone rescue @tone); end - def tone=(value); (@tilemap.tone = value rescue nil); end - def ox; @tilemap.ox; end - def ox=(v); @tilemap.ox = v; end - def oy; @tilemap.oy; end - def oy=(v); @tilemap.oy = v; end -end diff --git a/Data/Scripts/006_Map renderer/New Tilemap/001_NewTilemap.rb b/Data/Scripts/006_Map renderer/001_TilemapRenderer.rb similarity index 72% rename from Data/Scripts/006_Map renderer/New Tilemap/001_NewTilemap.rb rename to Data/Scripts/006_Map renderer/001_TilemapRenderer.rb index 492ab4511..cfd9bc8c6 100644 --- a/Data/Scripts/006_Map renderer/New Tilemap/001_NewTilemap.rb +++ b/Data/Scripts/006_Map renderer/001_TilemapRenderer.rb @@ -1,48 +1,28 @@ -# NOTES -# - @tiles has the extra tile hanging off the left/top of the screen, because -# the pixel offset values are positive and added to the coordinates. - -# - An alternative to rearranging @tiles in def check_if_screen_moved would -# be to have extra variables that determine how much the @tiles array has -# wrapped around (e.g. 1 means the tile sprites should be 1 tile further right -# or down than their indices in the array would suggest). This would be more -# convenient if I also have an array of x/y/layer triplets marking tile -# sprites using autotiles with 2+ frames. - #=============================================================================== # #=============================================================================== class TilemapRenderer attr_reader :tilesets attr_reader :autotiles - attr_reader :graphics_width - attr_reader :graphics_height attr_accessor :tone attr_accessor :color attr_reader :viewport - # TODO: ox, oy and visible don't do anything. Should they? - attr_accessor :ox - attr_accessor :oy - attr_accessor :visible + attr_accessor :ox # Does nothing + attr_accessor :oy # Does nothing + attr_accessor :visible # Does nothing - DISPLAY_TILE_WIDTH = Game_Map::TILE_WIDTH rescue 32 - DISPLAY_TILE_HEIGHT = Game_Map::TILE_HEIGHT rescue 32 - SOURCE_TILE_WIDTH = 32 - SOURCE_TILE_HEIGHT = 32 - - # If an autotile's filename ends with [x], its frame duration will be x/20 + DISPLAY_TILE_WIDTH = Game_Map::TILE_WIDTH rescue 32 + DISPLAY_TILE_HEIGHT = Game_Map::TILE_HEIGHT rescue 32 + SOURCE_TILE_WIDTH = 32 + SOURCE_TILE_HEIGHT = 32 + TILESET_TILES_PER_ROW = 8 + AUTOTILES_COUNT = 8 # Counting the blank tile as an autotile + TILES_PER_AUTOTILE = 48 + TILESET_START_ID = AUTOTILES_COUNT * TILES_PER_AUTOTILE + # If an autotile's filename ends with "[x]", its frame duration will be x/20 # seconds instead. AUTOTILE_FRAME_DURATION = 5 # In 1/20ths of a second - TILESET_TILES_PER_ROW = 8 - AUTOTILES_COUNT = 8 # Counting the blank tile as an autotile - TILES_PER_AUTOTILE = 48 - TILESET_START_ID = AUTOTILES_COUNT * TILES_PER_AUTOTILE - - # TODO: Flash duration is hardcoded to 0.05 seconds per "frame". However, this - # kind of flash is unused, but it should be supported anyway. - FLASH_OPACITY = [100, 90, 80, 70, 80, 90] - #============================================================================= # #============================================================================= @@ -77,7 +57,7 @@ class TilemapRenderer bitmap = pbGetTileset(filename) @bitmap_wraps[filename] = false if bitmap.mega? - self[filename] = TileWrap::wrapTileset(bitmap) + self[filename] = TilemapRenderer::TilesetWrapper.wrapTileset(bitmap) @bitmap_wraps[filename] = true bitmap.dispose else @@ -261,57 +241,37 @@ class TilemapRenderer # #============================================================================= def initialize(viewport) - @tilesets = TilesetBitmaps.new - @autotiles = AutotileBitmaps.new - - @can_query_graphics_size = (Graphics.width != nil rescue false) - if @can_query_graphics_size - @graphics_width = Graphics.width - @graphics_height = Graphics.height - else - @graphics_width = 640 - @graphics_height = 480 - end - - @tiles_horizontal_count = (@graphics_width.to_f / DISPLAY_TILE_WIDTH).ceil + 1 - @tiles_vertical_count = (@graphics_height.to_f / DISPLAY_TILE_HEIGHT).ceil + 1 - - @tone = Tone.new(0, 0, 0, 0) - @old_tone = Tone.new(0, 0, 0, 0) - @color = Color.new(0, 0, 0, 0) - @old_color = Color.new(0, 0, 0, 0) - - @self_viewport = Viewport.new(0, 0, graphics_width, graphics_height) - @viewport = (viewport) ? viewport : @self_viewport - @old_viewport_ox = 0 - @old_viewport_oy = 0 - - @tiles = [] + @tilesets = TilesetBitmaps.new + @autotiles = AutotileBitmaps.new + @tiles_horizontal_count = (Graphics.width.to_f / DISPLAY_TILE_WIDTH).ceil + 1 + @tiles_vertical_count = (Graphics.height.to_f / DISPLAY_TILE_HEIGHT).ceil + 1 + @tone = Tone.new(0, 0, 0, 0) + @old_tone = Tone.new(0, 0, 0, 0) + @color = Color.new(0, 0, 0, 0) + @old_color = Color.new(0, 0, 0, 0) + @self_viewport = Viewport.new(0, 0, Graphics.width, Graphics.height) + @viewport = (viewport) ? viewport : @self_viewport + @old_viewport_ox = 0 + @old_viewport_oy = 0 + # NOTE: The extra tiles horizontally/vertically hang off the left and top + # edges of the screen, because the pixel_offset values are positive + # and are added to the tile sprite coordinates. + @tiles = [] @tiles_horizontal_count.times do |i| @tiles[i] = [] @tiles_vertical_count.times do |j| - @tiles[i][j] = Array.new(3) { TileSprite.new(@viewport) } + @tiles[i][j] = Array.new(3) { TileSprite.new(@viewport) } end end - - @current_map_id = 0 - @tile_offset_x = 0 - @tile_offset_y = 0 - @pixel_offset_x = 0 - @pixel_offset_y = 0 - - @ox = 0 # Bitmap Offsets - @oy = 0 # Bitmap Offsets - - @visible = true - - @flash = nil - @oxFlash = 0 - @oyFlash = 0 - @flashChanged = false - @firsttimeflash = true - - @disposed = false + @current_map_id = 0 + @tile_offset_x = 0 + @tile_offset_y = 0 + @pixel_offset_x = 0 + @pixel_offset_y = 0 + @ox = 0 + @oy = 0 + @visible = true + @disposed = false end def dispose @@ -321,12 +281,6 @@ class TilemapRenderer coord.each { |tile| tile.dispose } end end - if @flash - @flash.bitmap.dispose if !@flash.disposed? - @flash.bitmap = nil if !@flash.disposed? - @flash.dispose - @flash = nil - end @tilesets.bitmaps.each_value { |bitmap| bitmap.dispose } @autotiles.bitmaps.each_value { |bitmap| bitmap.dispose } @self_viewport.dispose @@ -358,87 +312,7 @@ class TilemapRenderer #============================================================================= - # TODO: Flash stuff, including usage of flash_data. - def refresh_flash - if @flash_data && !@flash - @flash = TileSprite.new(viewport) - @flash.visible = true - @flash.z = 1 - @flash.tone = tone - @flash.color = color - @flash.blend_type = 1 - @flash.bitmap = Bitmap.new([graphics_width * 2, 1].max, [graphics_height * 2, 1].max) - @firsttimeflash = true - elsif !@flash_data && @flash - @flash.bitmap.dispose if @flash.bitmap - @flash.dispose - @flash = nil - @firsttimeflash = false - end - end - - def refreshFlashSprite - return if !@flash || @flash_data.nil? - ptX = @ox-@oxFlash - ptY = @oy-@oyFlash - if !@firsttimeflash && - ptX>=0 && ptX+@viewport.rect.width<=@flash.bitmap.width && - ptY>=0 && ptY+@viewport.rect.height<=@flash.bitmap.height - @flash.ox = 0 - @flash.oy = 0 - @flash.src_rect.set(ptX.round,ptY.round, - @viewport.rect.width,@viewport.rect.height) - return - end - width = @flash.bitmap.width - height = @flash.bitmap.height - bitmap = @flash.bitmap - ysize = @map_data.ysize - xsize = @map_data.xsize - @firsttimeflash = false - @oxFlash = @ox-(width>>2) - @oyFlash = @oy-(height>>2) - @flash.ox = 0 - @flash.oy = 0 - @flash.src_rect.set(width>>2,height>>2, - @viewport.rect.width,@viewport.rect.height) - @flash.bitmap.clear - @oxFlash = @oxFlash.floor - @oyFlash = @oyFlash.floor - xStart = @oxFlash / DISPLAY_TILE_WIDTH - xStart = 0 if xStart<0 - yStart = @oyFlash / DISPLAY_TILE_HEIGHT - yStart = 0 if yStart<0 - xEnd = xStart + (width / DISPLAY_TILE_WIDTH) + 1 - yEnd = yStart + (height / DISPLAY_TILE_HEIGHT) + 1 - xEnd = xsize if xEnd>=xsize - yEnd = ysize if yEnd>=ysize - if xStart>8)&15 - g = (id>>4)&15 - b = (id)&15 - tmpcolor.set(r<<4,g<<4,b<<4) - bitmap.fill_rect(xpos, ypos, DISPLAY_TILE_WIDTH, DISPLAY_TILE_HEIGHT, tmpcolor) - end - end - end - end - - #============================================================================= - - def refresh(autotiles = false) - refreshFlashSprite - end - - #============================================================================= + def refresh; end def refresh_tile_bitmap(tile, map, tile_id) if tile_id < TILES_PER_AUTOTILE @@ -519,7 +393,6 @@ class TilemapRenderer @current_map_id = $game_map.map_id ret = true end - # Check for tile movement current_map_display_x = ($game_map.display_x.to_f / Game_Map::X_SUBPIXELS).round current_map_display_y = ($game_map.display_y.to_f / Game_Map::Y_SUBPIXELS).round @@ -572,7 +445,6 @@ class TilemapRenderer @screen_moved_vertically = true @tile_offset_y = new_tile_offset_y end - # Check for pixel movement new_pixel_offset_x = current_map_display_x % SOURCE_TILE_WIDTH new_pixel_offset_y = current_map_display_y % SOURCE_TILE_HEIGHT @@ -591,16 +463,8 @@ class TilemapRenderer #============================================================================= def update - # Check if screen was resized - # TODO: If it was resized, change how many TileSprites there are. - # CustomTilemap only uses this for the flash graphic. - if @can_query_graphics_size - @graphics_width = Graphics.width - @graphics_height = Graphics.height - end # Update tone if @old_tone != @tone - @flash.tone = @tone if @flash @tiles.each do |col| col.each do |coord| coord.each { |tile| tile.tone = @tone } @@ -610,7 +474,6 @@ class TilemapRenderer end # Update color if @old_color != @color - @flash.color = @color if @flash @tiles.each do |col| col.each do |coord| coord.each { |tile| tile.color = @tone } @@ -621,30 +484,22 @@ class TilemapRenderer # Recalculate autotile frames @tilesets.update @autotiles.update - - # Update flash - refresh_flash if @flashChanged - @flash.opacity = FLASH_OPACITY[(Graphics.frame_count / 2) % 6] if @flash - do_full_refresh = false if @viewport.ox != @old_viewport_ox || @viewport.oy != @old_viewport_oy @old_viewport_ox = @viewport.ox @old_viewport_oy = @viewport.oy do_full_refresh = true end - # Check whether the screen has moved since the last update @screen_moved = false @screen_moved_vertically = false do_full_refresh = true if check_if_screen_moved - # Update all tile sprites visited = [] @tiles_horizontal_count.times do |i| visited[i] = [] @tiles_vertical_count.times { |j| visited[i][j] = false } end - $MapFactory.maps.each do |map| # Calculate x/y ranges of tile sprites that represent them map_display_x = (map.display_x.to_f / Game_Map::X_SUBPIXELS).round @@ -658,7 +513,6 @@ class TilemapRenderer end_y = @tiles_vertical_count - 1 end_y = [end_y, map.height - map_display_y_tile - 1].min next if start_x > end_x || start_y > end_y || end_x < 0 || end_y < 0 - # Update all tile sprites representing this map for i in start_x..end_x tile_x = i + map_display_x_tile @@ -681,7 +535,6 @@ class TilemapRenderer end end end - # Clear all unvisited tile sprites @tiles.each_with_index do |col, i| col.each_with_index do |coord, j| @@ -693,7 +546,6 @@ class TilemapRenderer end end end - @autotiles.changed = false end end diff --git a/Data/Scripts/006_Map renderer/002_CustomTilemap.rb b/Data/Scripts/006_Map renderer/002_CustomTilemap.rb deleted file mode 100644 index 5b9717c9c..000000000 --- a/Data/Scripts/006_Map renderer/002_CustomTilemap.rb +++ /dev/null @@ -1,1026 +0,0 @@ -#======================================================================= -# This module is a little fix that works around PC hardware limitations. -# Since Essentials isn't working with software rendering anymore, it now -# has to deal with the limits of the GPU. For the most part this is no -# big deal, but people do have some really big tilesets. -# -# The fix is simple enough: If your tileset is too big, a new -# bitmap will be constructed with all the excess pixels sent to the -# image's right side. This basically means that you now have a limit -# far higher than you should ever actually need. -# -# Hardware limit -> max tileset length: -# 1024px -> 4096px -# 2048px -> 16384px (enough to get the normal limit) -# 4096px -> 65536px (enough to load pretty much any tileset) -# 8192px -> 262144px -# 16384px -> 1048576px (what most people have at this point) - -# ~Roza/Zoroark -#======================================================================= - -module TileWrap - - TILESET_WIDTH = 0x100 - # Looks useless, but covers weird numbers given to mkxp.json or a funky driver - MAX_TEX_SIZE = (Bitmap.max_size / 1024) * 1024 - MAX_TEX_SIZE_BOOSTED = MAX_TEX_SIZE**2/TILESET_WIDTH - - def self.clamp(val, min, max) - val = max if val > max - val = min if val < min - return val - end - - def self.wrapTileset(originalbmp) - width = originalbmp.width - height = originalbmp.height - if width == TILESET_WIDTH && originalbmp.mega? - columns = (height / MAX_TEX_SIZE.to_f).ceil - - if columns * TILESET_WIDTH > MAX_TEX_SIZE - raise "Tilemap is too long!\n\nSIZE: #{originalbmp.height}px\nHARDWARE LIMIT: #{MAX_TEX_SIZE}px\nBOOSTED LIMIT: #{MAX_TEX_SIZE_BOOSTED}px" - end - bmp = Bitmap.new(TILESET_WIDTH*columns, MAX_TEX_SIZE) - remainder = height % MAX_TEX_SIZE - - columns.times{|col| - srcrect = Rect.new(0, col * MAX_TEX_SIZE, width, (col + 1 == columns) ? remainder : MAX_TEX_SIZE) - bmp.blt(col*TILESET_WIDTH, 0, originalbmp, srcrect) - } - return bmp - end - - return originalbmp - end - - def self.getWrappedRect(src_rect) - ret = Rect.new(0,0,0,0) - col = (src_rect.y / MAX_TEX_SIZE.to_f).floor - ret.x = col * TILESET_WIDTH + clamp(src_rect.x,0,TILESET_WIDTH) - ret.y = src_rect.y % MAX_TEX_SIZE - ret.width = clamp(src_rect.width, 0, TILESET_WIDTH - src_rect.x) - ret.height = clamp(src_rect.height, 0, MAX_TEX_SIZE) - return ret - end - - def self.blitWrappedPixels(destX, destY, dest, src, srcrect) - if (srcrect.y + srcrect.width < MAX_TEX_SIZE) - # Save the processing power - dest.blt(destX, destY, src, srcrect) - return - end - merge = (srcrect.y % MAX_TEX_SIZE) > ((srcrect.y + srcrect.height) % MAX_TEX_SIZE) - - srcrect_mod = getWrappedRect(srcrect) - - if !merge - dest.blt(destX, destY, src, srcrect_mod) - else - #FIXME won't work on heights longer than two columns, but nobody should need - # more than 32k pixels high at once anyway - side = {:a => MAX_TEX_SIZE - srcrect_mod.y, :b => srcrect_mod.height - (MAX_TEX_SIZE - srcrect_mod.y)} - dest.blt(destX, destY, src, Rect.new(srcrect_mod.x, srcrect_mod.y, srcrect_mod.width, side[:a])) - dest.blt(destX, destY + side[:a], src, Rect.new(srcrect_mod.x + TILESET_WIDTH, 0, srcrect_mod.width, side[:b])) - end - end - - def self.stretchBlitWrappedPixels(destrect, dest, src, srcrect) - if (srcrect.y + srcrect.width < MAX_TEX_SIZE) - # Save the processing power - dest.stretch_blt(destrect, src, srcrect) - return - end - # Does a regular blit to a non-megasurface, then stretch_blts that to - # the destination. Yes it is slow - tmp = Bitmap.new(srcrect.width, srcrect.height) - blitWrappedPixels(0,0,tmp,src,srcrect) - dest.stretch_blt(destrect, tmp, Rect.new(0,0,srcrect.width,srcrect.height)) - end -end - -#=============================================================================== -# -#=============================================================================== -class CustomTilemapAutotiles - attr_accessor :changed - - def initialize - @changed = true - @tiles = [nil,nil,nil,nil,nil,nil,nil] - end - - def [](i) - return @tiles[i] - end - - def []=(i,value) - @tiles[i] = value - @changed = true - end -end - - - -class CustomTilemapSprite < Sprite -end - - - -#=============================================================================== -# -#=============================================================================== -class CustomTilemap - attr_reader :tileset - attr_reader :autotiles - attr_reader :map_data - attr_reader :flash_data - attr_reader :priorities - attr_reader :terrain_tags - attr_reader :visible - attr_reader :viewport - attr_reader :graphicsWidth - attr_reader :graphicsHeight - attr_reader :ox - attr_reader :oy - attr_accessor :tone - attr_accessor :color - - Autotiles = [ - [ [27, 28, 33, 34], [ 5, 28, 33, 34], [27, 6, 33, 34], [ 5, 6, 33, 34], - [27, 28, 33, 12], [ 5, 28, 33, 12], [27, 6, 33, 12], [ 5, 6, 33, 12] ], - [ [27, 28, 11, 34], [ 5, 28, 11, 34], [27, 6, 11, 34], [ 5, 6, 11, 34], - [27, 28, 11, 12], [ 5, 28, 11, 12], [27, 6, 11, 12], [ 5, 6, 11, 12] ], - [ [25, 26, 31, 32], [25, 6, 31, 32], [25, 26, 31, 12], [25, 6, 31, 12], - [15, 16, 21, 22], [15, 16, 21, 12], [15, 16, 11, 22], [15, 16, 11, 12] ], - [ [29, 30, 35, 36], [29, 30, 11, 36], [ 5, 30, 35, 36], [ 5, 30, 11, 36], - [39, 40, 45, 46], [ 5, 40, 45, 46], [39, 6, 45, 46], [ 5, 6, 45, 46] ], - [ [25, 30, 31, 36], [15, 16, 45, 46], [13, 14, 19, 20], [13, 14, 19, 12], - [17, 18, 23, 24], [17, 18, 11, 24], [41, 42, 47, 48], [ 5, 42, 47, 48] ], - [ [37, 38, 43, 44], [37, 6, 43, 44], [13, 18, 19, 24], [13, 14, 43, 44], - [37, 42, 43, 48], [17, 18, 47, 48], [13, 18, 43, 48], [ 1, 2, 7, 8] ] - ] - Animated_Autotiles_Frames = 5*Graphics.frame_rate/20 # Frequency of updating animated autotiles - FlashOpacity = [100,90,80,70,80,90] - - def initialize(viewport) - @tileset = nil # Refers to Map Tileset Name - @autotiles = CustomTilemapAutotiles.new - @map_data = nil # Refers to 3D Array Of Tile Settings - @flash_data = nil # Refers to 3D Array of Tile Flashdata - @priorities = nil # Refers to Tileset Priorities - @terrain_tags = nil # Refers to Tileset Terrain Tags - @visible = true # Refers to Tileset Visibleness - @ox = 0 # Bitmap Offsets - @oy = 0 # Bitmap Offsets - @plane = false - @haveGraphicsWH = (Graphics.width!=nil rescue false) - if @haveGraphicsWH - @graphicsWidth = Graphics.width - @graphicsHeight = Graphics.height - else - @graphicsWidth = 640 - @graphicsHeight = 480 - end - @tileWidth = Game_Map::TILE_WIDTH rescue 32 - @tileHeight = Game_Map::TILE_HEIGHT rescue 32 - @tileSrcWidth = 32 - @tileSrcHeight = 32 - @diffsizes = (@tileWidth!=@tileSrcWidth) || (@tileHeight!=@tileSrcHeight) - @tone = Tone.new(0,0,0,0) - @oldtone = Tone.new(0,0,0,0) - @color = Color.new(0,0,0,0) - @oldcolor = Color.new(0,0,0,0) - @selfviewport = Viewport.new(0,0,graphicsWidth,graphicsHeight) - @viewport = (viewport) ? viewport : @selfviewport - @tiles = [] - @autotileInfo = [] - @regularTileInfo = [] - @oldOx = 0 - @oldOy = 0 - @oldViewportOx = 0 - @oldViewportOy = 0 - @layer0 = CustomTilemapSprite.new(viewport) - @layer0.visible = true - @nowshown = false - @layer0.bitmap = Bitmap.new([graphicsWidth+320,1].max,[graphicsHeight+320,1].max) - @layer0.z = 0 - @layer0.ox = 0 - @layer0.oy = 0 - @oxLayer0 = 0 - @oyLayer0 = 0 - @flash = nil - @oxFlash = 0 - @oyFlash = 0 - @priotiles = {} - @priotilesfast = [] - @prioautotiles = {} - @autosprites = [] - @framecount = [0,0,0,0,0,0,0,0] # For autotiles - @tilesetChanged = true - @flashChanged = false - @firsttime = true - @disposed = false - @usedsprites = false - @layer0clip = true - @firsttimeflash = true - @fullyrefreshed = false - @fullyrefreshedautos = false - @shouldWrap = false - end - - def dispose - return if disposed? - @help.dispose if @help - @help = nil - i = 0; len = @autotileInfo.length - while i=xsize - xEnd = (@ox+@viewport.rect.width)/@tileWidth + 1 - xEnd = 0 if xEnd<0 - xEnd = xsize-1 if xEnd>=xsize - return false if xStart>=xEnd - ysize = @map_data.ysize - yStart = @oy/@tileHeight - 1 - yStart = 0 if yStart<0 - yStart = ysize-1 if yStart>=ysize - yEnd = (@oy+@viewport.rect.height)/@tileHeight + 1 - yEnd = 0 if yEnd<0 - yEnd = ysize-1 if yEnd>=ysize - return false if yStart>=yEnd - return true - end - - def autotileNumFrames(id) - autotile = @autotiles[id/48-1] - return 0 if !autotile || autotile.disposed? - frames = 1 - if autotile.height==@tileHeight - frames = autotile.width/@tileWidth - else - frames = autotile.width/(3*@tileWidth) - end - return frames - end - - def autotileFrame(id) - autotile = @autotiles[id/48-1] - return -1 if !autotile || autotile.disposed? - frames = 1 - if autotile.height==@tileHeight - frames = autotile.width/@tileWidth - else - frames = autotile.width/(3*@tileWidth) - end - return (Graphics.frame_count/Animated_Autotiles_Frames)%frames - end - - def repaintAutotiles - for i in 0...@autotileInfo.length - next if !@autotileInfo[i] - frame = autotileFrame(i) - @autotileInfo[i].clear - bltAutotile(@autotileInfo[i],0,0,i,frame) - end - end - - def bltAutotile(bitmap,x,y,id,frame) - return if frame<0 - autotile = @autotiles[id/48-1] - return if !autotile || autotile.disposed? - if autotile.height==@tileSrcHeight - anim = frame*@tileSrcWidth - src_rect = Rect.new(anim,0,@tileSrcWidth,@tileSrcHeight) - if @diffsizes - bitmap.stretch_blt(Rect.new(x,y,@tileWidth,@tileHeight),autotile,src_rect) - else - bitmap.blt(x,y,autotile,src_rect) - end - else - anim = frame*3*@tileSrcWidth - id %= 48 - tiles = Autotiles[id>>3][id&7] - src = Rect.new(0,0,0,0) - halfTileWidth = @tileWidth>>1 - halfTileHeight = @tileHeight>>1 - halfTileSrcWidth = @tileSrcWidth>>1 - halfTileSrcHeight = @tileSrcHeight>>1 - for i in 0...4 - tile_position = tiles[i] - 1 - src.set( (tile_position % 6)*halfTileSrcWidth + anim, - (tile_position / 6)*halfTileSrcHeight, halfTileSrcWidth, halfTileSrcHeight) - if @diffsizes - bitmap.stretch_blt( - Rect.new(i%2*halfTileWidth+x,i/2*halfTileHeight+y,halfTileWidth,halfTileHeight), - autotile,src) - else - bitmap.blt(i%2*halfTileWidth+x,i/2*halfTileHeight+y, autotile, src) - end - end - end - end - - def getAutotile(sprite,id) - frames = @framecount[id/48-1] - if frames<=1 - anim = 0 - else - anim = (Graphics.frame_count/Animated_Autotiles_Frames)%frames - end - return if anim<0 - bitmap = @autotileInfo[id] - if !bitmap - bitmap = Bitmap.new(@tileWidth,@tileHeight) - bltAutotile(bitmap,0,0,id,anim) - @autotileInfo[id] = bitmap - end - sprite.bitmap = bitmap if sprite.bitmap!=bitmap - end - - def getRegularTile(sprite,id) - if @diffsizes - bitmap = @regularTileInfo[id] - if !bitmap - bitmap = Bitmap.new(@tileWidth,@tileHeight) - rect = Rect.new(((id - 384)&7)*@tileSrcWidth,((id - 384)>>3)*@tileSrcHeight, - @tileSrcWidth,@tileSrcHeight) - TileWrap::stretchBlitWrappedPixels(Rect.new(0,0,@tileWidth,@tileHeight), bitmap, @tileset, rect) - @regularTileInfo[id] = bitmap - end - sprite.bitmap = bitmap if sprite.bitmap!=bitmap - else - sprite.bitmap = @tileset if sprite.bitmap!=@tileset - rect = Rect.new(((id - 384)&7)*@tileSrcWidth,((id - 384)>>3)*@tileSrcHeight, - @tileSrcWidth,@tileSrcHeight) - rect = TileWrap::getWrappedRect(rect) if @shouldWrap - sprite.src_rect = rect - end - end - - def addTile(tiles,count,xpos,ypos,id) - terrain = @terrain_tags[id] - priority = @priorities[id] - if id >= 384 # Tileset tile - if count>=tiles.length - sprite = CustomTilemapSprite.new(@viewport) - tiles.push(sprite,0) - else - sprite = tiles[count] - tiles[count+1] = 0 - end - sprite.visible = @visible - sprite.x = xpos - sprite.y = ypos - sprite.tone = @tone - sprite.color = @color - getRegularTile(sprite,id) - else # Autotile - if count>=tiles.length - sprite = CustomTilemapSprite.new(@viewport) - tiles.push(sprite,1) - else - sprite = tiles[count] - tiles[count+1] = 1 - end - sprite.visible = @visible - sprite.x = xpos - sprite.y = ypos - sprite.tone = @tone - sprite.color = @color - getAutotile(sprite,id) - end - terrain_tag_data = GameData::TerrainTag.try_get(terrain) - if terrain_tag_data.shows_reflections - spriteZ = -100 - elsif $PokemonGlobal.bridge > 0 && terrain_tag_data.bridge - spriteZ = 1 - else - spriteZ = calculate_sprite_priority(priority,ypos) - end - sprite.z = spriteZ - count += 2 - return count - end - - def calculate_sprite_priority(priority,ypos) - begin - return (priority==0) ? 0 : ypos+priority*32+32 - rescue - return 0 - end - end - - def refresh_flash - if @flash_data && !@flash - @flash = CustomTilemapSprite.new(viewport) - @flash.visible = true - @flash.z = 1 - @flash.tone = tone - @flash.color = color - @flash.blend_type = 1 - @flash.bitmap = Bitmap.new([graphicsWidth*2,1].max,[graphicsHeight*2,1].max) - @firsttimeflash = true - elsif !@flash_data && @flash - @flash.bitmap.dispose if @flash.bitmap - @flash.dispose - @flash = nil - @firsttimeflash = false - end - end - - def refreshFlashSprite - return if !@flash || @flash_data.nil? - ptX = @ox-@oxFlash - ptY = @oy-@oyFlash - if !@firsttimeflash && !@usedsprites && - ptX>=0 && ptX+@viewport.rect.width<=@flash.bitmap.width && - ptY>=0 && ptY+@viewport.rect.height<=@flash.bitmap.height - @flash.ox = 0 - @flash.oy = 0 - @flash.src_rect.set(ptX.round,ptY.round, - @viewport.rect.width,@viewport.rect.height) - return - end - width = @flash.bitmap.width - height = @flash.bitmap.height - bitmap = @flash.bitmap - ysize = @map_data.ysize - xsize = @map_data.xsize - @firsttimeflash = false - @oxFlash = @ox-(width>>2) - @oyFlash = @oy-(height>>2) - @flash.ox = 0 - @flash.oy = 0 - @flash.src_rect.set(width>>2,height>>2, - @viewport.rect.width,@viewport.rect.height) - @flash.bitmap.clear - @oxFlash = @oxFlash.floor - @oyFlash = @oyFlash.floor - xStart = @oxFlash/@tileWidth - xStart = 0 if xStart<0 - yStart = @oyFlash/@tileHeight - yStart = 0 if yStart<0 - xEnd = xStart+(width/@tileWidth)+1 - yEnd = yStart+(height/@tileHeight)+1 - xEnd = xsize if xEnd>=xsize - yEnd = ysize if yEnd>=ysize - if xStart>8)&15 - g = (id>>4)&15 - b = (id)&15 - tmpcolor.set(r<<4,g<<4,b<<4) - bitmap.fill_rect(xpos,ypos,@tileWidth,@tileHeight,tmpcolor) - end - end - end - end - - def refresh_tileset - i = 0 - len = @regularTileInfo.length - while i < len - if @regularTileInfo[i] - @regularTileInfo[i].dispose - @regularTileInfo[i] = nil - end - i += 1 - end - @regularTileInfo.clear - @priotiles.clear - ysize = @map_data.ysize - xsize = @map_data.xsize - zsize = @map_data.zsize - if xsize > 100 || ysize > 100 - @fullyrefreshed = false - else - for z in 0...zsize - for y in 0...ysize - for x in 0...xsize - id = @map_data[x, y, z] - next if id == 0 - next if @priorities[id] == 0 && !GameData::TerrainTag.try_get(@terrain_tags[id]).shows_reflections - @priotiles[[x, y]] = [] if !@priotiles[[x, y]] - @priotiles[[x, y]].push([z, id]) - end - end - end - @fullyrefreshed = true - end - end - - def refresh_autotiles - i = 0 - len = @autotileInfo.length - while i < len - if @autotileInfo[i] - @autotileInfo[i].dispose - @autotileInfo[i] = nil - end - i += 1 - end - i = 0 - len = @autosprites.length - while i < len - if @autosprites[i] - @autosprites[i].dispose - @autosprites[i] = nil - end - i += 2 - end - @autosprites.clear - @autotileInfo.clear - @prioautotiles.clear - @priorect = nil - @priorectautos = nil - hasanimated = false - for i in 0...7 - numframes = autotileNumFrames(48 * (i + 1)) - hasanimated = true if numframes >= 2 - @framecount[i] = numframes - end - if hasanimated - ysize = @map_data.ysize - xsize = @map_data.xsize - zsize = @map_data.zsize - if xsize > 100 || ysize > 100 - @fullyrefreshedautos = false - else - for y in 0...ysize - for x in 0...xsize - for z in 0...zsize - id = @map_data[x, y, z] - next if id == 0 || id >= 384 # Skip non-autotiles - next if @priorities[id] != 0 || GameData::TerrainTag.try_get(@terrain_tags[id]).shows_reflections - next if @framecount[id / 48 - 1] < 2 - @prioautotiles[[x, y]] = true - break - end - end - end - @fullyrefreshedautos = true - end - else - @fullyrefreshedautos = true - end - end - - def refreshLayer0(autotiles = false) - return true if autotiles && !shown? - ptX = @ox - @oxLayer0 - ptY = @oy - @oyLayer0 - if !autotiles && !@firsttime && !@usedsprites && - ptX >= 0 && ptX + @viewport.rect.width <= @layer0.bitmap.width && - ptY >= 0 && ptY + @viewport.rect.height <= @layer0.bitmap.height - if @layer0clip && @viewport.ox == 0 && @viewport.oy == 0 - @layer0.ox = 0 - @layer0.oy = 0 - @layer0.src_rect.set(ptX.round, ptY.round, @viewport.rect.width, @viewport.rect.height) - else - @layer0.ox = ptX.round - @layer0.oy = ptY.round - @layer0.src_rect.set(0, 0, @layer0.bitmap.width, @layer0.bitmap.height) - end - return true - end - width = @layer0.bitmap.width - height = @layer0.bitmap.height - bitmap = @layer0.bitmap - ysize = @map_data.ysize - xsize = @map_data.xsize - zsize = @map_data.zsize - twidth = @tileWidth - theight = @tileHeight - mapdata = @map_data - if autotiles - return true if @fullyrefreshedautos && @prioautotiles.length == 0 - xStart = @oxLayer0 / twidth - xStart = 0 if xStart < 0 - yStart = @oyLayer0 / theight - yStart = 0 if yStart < 0 - xEnd = xStart + (width / twidth) + 1 - xEnd = xsize if xEnd > xsize - yEnd = yStart + (height / theight) + 1 - yEnd = ysize if yEnd > ysize - return true if xStart >= xEnd || yStart >= yEnd - trans = Color.new(0, 0, 0, 0) - temprect = Rect.new(0, 0, 0, 0) - tilerect = Rect.new(0, 0, twidth, theight) - zrange = 0...zsize - overallcount = 0 - count = 0 - if !@fullyrefreshedautos - for y in yStart..yEnd - for x in xStart..xEnd - for z in zrange - id = mapdata[x, y, z] - next if !id || id < 48 || id >= 384 # Skip non-autotiles - prioid = @priorities[id] - next if prioid != 0 || GameData::TerrainTag.try_get(@terrain_tags[id]).shows_reflections - fcount = @framecount[id / 48 - 1] - next if !fcount || fcount < 2 - overallcount += 1 - xpos = (x * twidth) - @oxLayer0 - ypos = (y * theight) - @oyLayer0 - bitmap.fill_rect(xpos, ypos, twidth, theight, trans) if overallcount <= 2000 - break - end - for z in zrange - id = mapdata[x, y, z] - next if !id || id < 48 - prioid = @priorities[id] - next if prioid != 0 || GameData::TerrainTag.try_get(@terrain_tags[id]).shows_reflections - if overallcount > 2000 - xpos = (x * twidth) - @oxLayer0 - ypos = (y * theight) - @oyLayer0 - count = addTile(@autosprites, count, xpos, ypos, id) - elsif id >= 384 # Tileset tiles - temprect.set(((id - 384) & 7) * @tileSrcWidth, - ((id - 384) >> 3) * @tileSrcHeight, - @tileSrcWidth, @tileSrcHeight) - xpos = (x * twidth) - @oxLayer0 - ypos = (y * theight) - @oyLayer0 - if @diffsizes - TileWrap::stretchBlitWrappedPixels(Rect.new(xpos, ypos, twidth, theight), bitmap, @tileset, temprect) - else - TileWrap::blitWrappedPixels(xpos,ypos, bitmap, @tileset, temprect) - end - else # Autotiles - tilebitmap = @autotileInfo[id] - if !tilebitmap - anim = autotileFrame(id) - next if anim < 0 - tilebitmap = Bitmap.new(twidth, theight) - bltAutotile(tilebitmap, 0, 0, id, anim) - @autotileInfo[id] = tilebitmap - end - xpos = (x * twidth) - @oxLayer0 - ypos = (y * theight) - @oyLayer0 - bitmap.blt(xpos, ypos, tilebitmap, tilerect) - end - end - end - end - Graphics.frame_reset - else - if !@priorect || !@priorectautos || - @priorect[0] != xStart || @priorect[1] != yStart || - @priorect[2] != xEnd || @priorect[3] != yEnd - @priorect = [xStart, yStart, xEnd, yEnd] - @priorectautos = [] - for y in yStart..yEnd - for x in xStart..xEnd - @priorectautos.push([x, y]) if @prioautotiles[[x, y]] - end - end - end - for tile in @priorectautos - x = tile[0] - y = tile[1] - overallcount += 1 - xpos = (x * twidth) - @oxLayer0 - ypos = (y * theight) - @oyLayer0 - bitmap.fill_rect(xpos, ypos, twidth, theight, trans) - z = 0 - while z < zsize - id = mapdata[x, y, z] - z += 1 - next if !id || id < 48 - prioid = @priorities[id] - next if prioid != 0 || GameData::TerrainTag.try_get(@terrain_tags[id]).shows_reflections - if id >= 384 # Tileset tiles - temprect.set(((id - 384) & 7) * @tileSrcWidth, - ((id - 384) >> 3) * @tileSrcHeight, - @tileSrcWidth, @tileSrcHeight) - if @diffsizes - TileWrap::stretchBlitWrappedPixels(Rect.new(xpos, ypos, twidth, theight), bitmap, @tileset, temprect) - else - TileWrap::blitWrappedPixels(xpos,ypos, bitmap, @tileset, temprect) - end - else # Autotiles - tilebitmap = @autotileInfo[id] - if !tilebitmap - anim = autotileFrame(id) - next if anim < 0 - tilebitmap = Bitmap.new(twidth, theight) - bltAutotile(tilebitmap, 0, 0, id, anim) - @autotileInfo[id] = tilebitmap - end - bitmap.blt(xpos, ypos, tilebitmap, tilerect) - end - end - end - Graphics.frame_reset if overallcount > 500 - end - @usedsprites = false - return true - end - return false if @usedsprites - @firsttime = false - @oxLayer0 = @ox - (width >> 2) - @oyLayer0 = @oy - (height >> 2) - if @layer0clip - @layer0.ox = 0 - @layer0.oy = 0 - @layer0.src_rect.set(width >> 2, height >> 2, @viewport.rect.width, @viewport.rect.height) - else - @layer0.ox = (width >> 2) - @layer0.oy = (height >> 2) - end - @layer0.bitmap.clear - @oxLayer0 = @oxLayer0.round - @oyLayer0 = @oyLayer0.round - xStart = @oxLayer0 / twidth - xStart = 0 if xStart < 0 - yStart = @oyLayer0 / theight - yStart = 0 if yStart < 0 - xEnd = xStart + (width / twidth) + 1 - xEnd = xsize if xEnd >= xsize - yEnd = yStart + (height / theight) + 1 - yEnd = ysize if yEnd >= ysize - if xStart < xEnd && yStart < yEnd - tmprect = Rect.new(0, 0, 0, 0) - yrange = yStart...yEnd - xrange = xStart...xEnd - for z in 0...zsize - for y in yrange - ypos = (y * theight) - @oyLayer0 - for x in xrange - xpos = (x * twidth) - @oxLayer0 - id = mapdata[x, y, z] - next if id == 0 || @priorities[id] != 0 || GameData::TerrainTag.try_get(@terrain_tags[id]).shows_reflections - if id >= 384 # Tileset tiles - tmprect.set(((id - 384) & 7) * @tileSrcWidth, - ((id - 384) >> 3) * @tileSrcHeight, - @tileSrcWidth, @tileSrcHeight) - if @diffsizes - TileWrap::stretchBlitWrappedPixels(Rect.new(xpos, ypos, twidth, theight), bitmap, @tileset, tmprect) - else - TileWrap::blitWrappedPixels(xpos,ypos, bitmap, @tileset, tmprect) - end - else # Autotiles - frames = @framecount[id / 48 - 1] - if frames <= 1 - frame = 0 - else - frame = (Graphics.frame_count / Animated_Autotiles_Frames) % frames - end - bltAutotile(bitmap, xpos, ypos, id, frame) - end - end - end - end - Graphics.frame_reset - end - return true - end - - def refresh(autotiles = false) - @oldOx = @ox - @oldOy = @oy - usesprites = false - if @layer0 - @layer0.visible = @visible - usesprites = !refreshLayer0(autotiles) - return if autotiles && !usesprites - else - usesprites = true - end - refreshFlashSprite - xsize = @map_data.xsize - ysize = @map_data.ysize - minX = (@ox / @tileWidth) - 1 - minX = minX.clamp(0, xsize - 1) - maxX = ((@ox + @viewport.rect.width) / @tileWidth) + 1 - maxX = maxX.clamp(0, xsize - 1) - minY = (@oy / @tileHeight) - 1 - minY = minY.clamp(0, ysize - 1) - maxY = ((@oy + @viewport.rect.height) / @tileHeight) + 1 - maxY = maxY.clamp(0, ysize - 1) - count = 0 - if minX < maxX && minY < maxY - @usedsprites = usesprites || @usedsprites - @layer0.visible = false if usesprites && @layer0 - if !@priotilesrect || !@priotilesfast || - @priotilesrect[0] != minX || @priotilesrect[1] != minY || - @priotilesrect[2] != maxX || @priotilesrect[3] != maxY - @priotilesrect = [minX, minY, maxX, maxY] - @priotilesfast = [] - if @fullyrefreshed - for y in minY..maxY - for x in minX..maxX - next if !@priotiles[[x, y]] - @priotiles[[x, y]].each { |tile| @priotilesfast.push([x, y, tile[0], tile[1]]) } - end - end - else - for z in 0...@map_data.zsize - for y in minY..maxY - for x in minX..maxX - id = @map_data[x, y, z] - next if id == 0 - next if @priorities[id] == 0 && !GameData::TerrainTag.try_get(@terrain_tags[id]).shows_reflections - @priotilesfast.push([x, y, z, id]) - end - end - end - end - end - for prio in @priotilesfast - xpos = (prio[0] * @tileWidth) - @ox - ypos = (prio[1] * @tileHeight) - @oy - count = addTile(@tiles, count, xpos, ypos, prio[3]) - end - end - if count < @tiles.length - bigchange = (count <= (@tiles.length * 2 / 3)) && @tiles.length > 40 - j = count - len = @tiles.length - while j < len - sprite = @tiles[j] - @tiles[j + 1] = -1 - if bigchange - sprite.dispose - @tiles[j] = nil - @tiles[j + 1] = nil - elsif !@tiles[j].disposed? - sprite.visible = false if sprite.visible - end - j += 2 - end - @tiles.compact! if bigchange - end - end - - def update - if @haveGraphicsWH - @graphicsWidth = Graphics.width - @graphicsHeight = Graphics.height - end - # Update tone - if @oldtone != @tone - @layer0.tone = @tone - @flash.tone = @tone if @flash - for sprite in @autosprites - sprite.tone = @tone if sprite.is_a?(Sprite) - end - for sprite in @tiles - sprite.tone = @tone if sprite.is_a?(Sprite) - end - @oldtone = @tone.clone - end - # Update color - if @oldcolor != @color - @layer0.color = @color - @flash.color = @color if @flash - for sprite in @autosprites - sprite.color = @color if sprite.is_a?(Sprite) - end - for sprite in @tiles - sprite.color = @color if sprite.is_a?(Sprite) - end - @oldcolor = @color.clone - end - # Refresh anything that has changed - if @autotiles.changed - refresh_autotiles - repaintAutotiles - end - refresh_flash if @flashChanged - refresh_tileset if @tilesetChanged - @flash.opacity = FlashOpacity[(Graphics.frame_count / 2) % 6] if @flash - mustrefresh = (@oldOx != @ox || @oldOy != @oy || @tilesetChanged || @autotiles.changed) - if @viewport.ox != @oldViewportOx || @viewport.oy != @oldViewportOy - mustrefresh = true - @oldViewportOx = @viewport.ox - @oldViewportOy = @viewport.oy - end - refresh if mustrefresh - if (Graphics.frame_count % Animated_Autotiles_Frames) == 0 || @nowshown - repaintAutotiles - refresh(true) - end - @nowshown = false - @autotiles.changed = false - @tilesetChanged = false - end -end diff --git a/Data/Scripts/006_Map renderer/002_TilesetWrapper.rb b/Data/Scripts/006_Map renderer/002_TilesetWrapper.rb new file mode 100644 index 000000000..76727a337 --- /dev/null +++ b/Data/Scripts/006_Map renderer/002_TilesetWrapper.rb @@ -0,0 +1,96 @@ +#======================================================================= +# This module is a little fix that works around PC hardware limitations. +# Since Essentials isn't working with software rendering anymore, it now +# has to deal with the limits of the GPU. For the most part this is no +# big deal, but people do have some really big tilesets. +# +# The fix is simple enough: If your tileset is too big, a new +# bitmap will be constructed with all the excess pixels sent to the +# image's right side. This basically means that you now have a limit +# far higher than you should ever actually need. +# +# Hardware limit -> max tileset length: +# 1024px -> 4096px +# 2048px -> 16384px (enough to get the normal limit) +# 4096px -> 65536px (enough to load pretty much any tileset) +# 8192px -> 262144px +# 16384px -> 1048576px (what most people have at this point) + +# ~Roza/Zoroark +#======================================================================= +class TilemapRenderer + module TilesetWrapper + TILESET_WIDTH = SOURCE_TILE_WIDTH * TILESET_TILES_PER_ROW + # Looks useless, but covers weird numbers given to mkxp.json or a funky driver + MAX_TEX_SIZE = (Bitmap.max_size / 1024) * 1024 + MAX_TEX_SIZE_BOOSTED = MAX_TEX_SIZE**2 / TILESET_WIDTH + + module_function + + def wrapTileset(originalbmp) + width = originalbmp.width + height = originalbmp.height + if width == TILESET_WIDTH && originalbmp.mega? + columns = (height / MAX_TEX_SIZE.to_f).ceil + if columns * TILESET_WIDTH > MAX_TEX_SIZE + raise "Tileset is too long!\n\nSIZE: #{originalbmp.height}px\nHARDWARE LIMIT: #{MAX_TEX_SIZE}px\nBOOSTED LIMIT: #{MAX_TEX_SIZE_BOOSTED}px" + end + bmp = Bitmap.new(TILESET_WIDTH * columns, MAX_TEX_SIZE) + remainder = height % MAX_TEX_SIZE + columns.times do |col| + srcrect = Rect.new(0, col * MAX_TEX_SIZE, width, (col + 1 == columns) ? remainder : MAX_TEX_SIZE) + bmp.blt(col * TILESET_WIDTH, 0, originalbmp, srcrect) + end + return bmp + end + return originalbmp + end + + def getWrappedRect(src_rect) + ret = Rect.new(0, 0, 0, 0) + col = (src_rect.y / MAX_TEX_SIZE.to_f).floor + ret.x = col * TILESET_WIDTH + src_rect.x.clamp(0, TILESET_WIDTH) + ret.y = src_rect.y % MAX_TEX_SIZE + ret.width = src_rect.width.clamp(0, TILESET_WIDTH - src_rect.x) + ret.height = src_rect.height.clamp(0, MAX_TEX_SIZE) + return ret + end + + private + + def blitWrappedPixels(destX, destY, dest, src, srcrect) + if srcrect.y + srcrect.width < MAX_TEX_SIZE + # Save the processing power + dest.blt(destX, destY, src, srcrect) + return + end + merge = (srcrect.y % MAX_TEX_SIZE) > ((srcrect.y + srcrect.height) % MAX_TEX_SIZE) + srcrect_mod = getWrappedRect(srcrect) + if merge + # FIXME won't work on heights longer than two columns, but nobody should need + # more than 32k pixels high at once anyway + side = { + :a => MAX_TEX_SIZE - srcrect_mod.y, + :b => srcrect_mod.height - MAX_TEX_SIZE + srcrect_mod.y + } + dest.blt(destX, destY, src, Rect.new(srcrect_mod.x, srcrect_mod.y, srcrect_mod.width, side[:a])) + dest.blt(destX, destY + side[:a], src, Rect.new(srcrect_mod.x + TILESET_WIDTH, 0, srcrect_mod.width, side[:b])) + else + dest.blt(destX, destY, src, srcrect_mod) + end + end + + def stretchBlitWrappedPixels(destrect, dest, src, srcrect) + if srcrect.y + srcrect.width < MAX_TEX_SIZE + # Save the processing power + dest.stretch_blt(destrect, src, srcrect) + return + end + # Does a regular blit to a non-megasurface, then stretch_blts that to + # the destination. Yes it is slow + tmp = Bitmap.new(srcrect.width, srcrect.height) + blitWrappedPixels(0, 0, tmp, src, srcrect) + dest.stretch_blt(destrect, tmp, Rect.new(0, 0, srcrect.width, srcrect.height)) + end + end +end diff --git a/Data/Scripts/006_Map renderer/003_AutotileExpander.rb b/Data/Scripts/006_Map renderer/003_AutotileExpander.rb new file mode 100644 index 000000000..26ded25af --- /dev/null +++ b/Data/Scripts/006_Map renderer/003_AutotileExpander.rb @@ -0,0 +1,72 @@ +class TilemapRenderer + module AutotileExpander + MAX_TEXTURE_SIZE = (Bitmap.max_size / 1024) * 1024 + + module_function + + # This doesn't allow for cache sizes smaller than 768, but if that applies + # to you, you've got bigger problems. + def expand(bitmap) + return bitmap if bitmap.height == SOURCE_TILE_HEIGHT + expanded_format = (bitmap.height == SOURCE_TILE_HEIGHT * 6) + wrap = false + if MAX_TEXTURE_SIZE < TILES_PER_AUTOTILE * SOURCE_TILE_HEIGHT + wrap = true # Each autotile will occupy two columns instead of one + end + frames_count = [bitmap.width / (3 * SOURCE_TILE_WIDTH), 1].max + new_bitmap = Bitmap.new(frames_count * (wrap ? 2 : 1) * SOURCE_TILE_WIDTH, + TILES_PER_AUTOTILE * SOURCE_TILE_HEIGHT / (wrap ? 2 : 1)) + rect = Rect.new(0, 0, SOURCE_TILE_WIDTH / 2, SOURCE_TILE_HEIGHT / 2) + TILES_PER_AUTOTILE.times do |id| + pattern = TileDrawingHelper::AUTOTILE_PATTERNS[id >> 3][id % TILESET_TILES_PER_ROW] + wrap_offset_x = (wrap && id >= TILES_PER_AUTOTILE / 2) ? SOURCE_TILE_WIDTH : 0 + wrap_offset_y = (wrap && id >= TILES_PER_AUTOTILE / 2) ? (TILES_PER_AUTOTILE / 2) * SOURCE_TILE_HEIGHT : 0 + frames_count.times do |frame| + if expanded_format && [1, 2, 4, 8].include?(id) + dest_x = frame * SOURCE_TILE_WIDTH * (wrap ? 2 : 1) + dest_x += wrap_offset_x + next if dest_x > MAX_TEXTURE_SIZE + dest_y = id * SOURCE_TILE_HEIGHT + dest_y -= wrap_offset_y + next if dest_y > MAX_TEXTURE_SIZE + case id + when 1 # Top left corner + new_bitmap.blt(dest_x, dest_y, bitmap, + Rect.new(frame * SOURCE_TILE_WIDTH * 3, SOURCE_TILE_HEIGHT * 4, + SOURCE_TILE_WIDTH, SOURCE_TILE_HEIGHT)) + when 2 # Top right corner + new_bitmap.blt(dest_x, dest_y, bitmap, + Rect.new(SOURCE_TILE_WIDTH + frame * SOURCE_TILE_WIDTH * 3, SOURCE_TILE_HEIGHT * 4, + SOURCE_TILE_WIDTH, SOURCE_TILE_HEIGHT)) + when 4 # Bottom right corner + new_bitmap.blt(dest_x, dest_y, bitmap, + Rect.new(SOURCE_TILE_WIDTH + frame * SOURCE_TILE_WIDTH * 3, SOURCE_TILE_HEIGHT * 5, + SOURCE_TILE_WIDTH, SOURCE_TILE_HEIGHT)) + when 8 # Bottom left corner + new_bitmap.blt(dest_x, dest_y, bitmap, + Rect.new(frame * SOURCE_TILE_WIDTH * 3, SOURCE_TILE_HEIGHT * 5, + SOURCE_TILE_WIDTH, SOURCE_TILE_HEIGHT)) + end + next + end + pattern.each_with_index do |src_chunk, i| + real_src_chunk = src_chunk - 1 + dest_x = (i % 2) * SOURCE_TILE_WIDTH / 2 + dest_x += frame * SOURCE_TILE_WIDTH * (wrap ? 2 : 1) + dest_x += wrap_offset_x + next if dest_x > MAX_TEXTURE_SIZE + dest_y = (i / 2) * SOURCE_TILE_HEIGHT / 2 + dest_y += id * SOURCE_TILE_HEIGHT + dest_y -= wrap_offset_y + next if dest_y > MAX_TEXTURE_SIZE + rect.x = (real_src_chunk % 6) * SOURCE_TILE_WIDTH / 2 + rect.x += SOURCE_TILE_WIDTH * 3 * frame + rect.y = (real_src_chunk / 6) * SOURCE_TILE_HEIGHT / 2 + new_bitmap.blt(dest_x, dest_y, bitmap, rect) + end + end + end + return new_bitmap + end + end +end diff --git a/Data/Scripts/006_Map renderer/003_TileDrawingHelper.rb b/Data/Scripts/006_Map renderer/004_TileDrawingHelper.rb similarity index 95% rename from Data/Scripts/006_Map renderer/003_TileDrawingHelper.rb rename to Data/Scripts/006_Map renderer/004_TileDrawingHelper.rb index 80d9e3277..91e51d0a2 100644 --- a/Data/Scripts/006_Map renderer/003_TileDrawingHelper.rb +++ b/Data/Scripts/006_Map renderer/004_TileDrawingHelper.rb @@ -2,7 +2,7 @@ class TileDrawingHelper attr_accessor :tileset attr_accessor :autotiles - Autotiles = [ + AUTOTILE_PATTERNS = [ [ [27, 28, 33, 34], [ 5, 28, 33, 34], [27, 6, 33, 34], [ 5, 6, 33, 34], [27, 28, 33, 12], [ 5, 28, 33, 12], [27, 6, 33, 12], [ 5, 6, 33, 12] ], [ [27, 28, 11, 34], [ 5, 28, 11, 34], [27, 6, 11, 34], [ 5, 6, 11, 34], @@ -18,7 +18,7 @@ class TileDrawingHelper ] # converts neighbors returned from tableNeighbors to tile indexes - NeighborsToTiles = [ + NEIGHBORS_TO_AUTOTILE_INDEX = [ 46, 44, 46, 44, 43, 41, 43, 40, 46, 44, 46, 44, 43, 41, 43, 40, 42, 32, 42, 32, 35, 19, 35, 18, 42, 32, 42, 32, 34, 17, 34, 16, 46, 44, 46, 44, 43, 41, 43, 40, 46, 44, 46, 44, 43, 41, 43, 40, @@ -68,7 +68,7 @@ class TileDrawingHelper def initialize(tileset, autotiles) if tileset.mega? - @tileset = TileWrap::wrapTileset(tileset) + @tileset = TilemapRenderer::TilesetWrapper.wrapTileset(tileset) tileset.dispose @shouldWrap = true else @@ -100,7 +100,7 @@ class TileDrawingHelper else anim = frame * 96 id %= 48 - tiles = TileDrawingHelper::Autotiles[id >> 3][id & 7] + tiles = AUTOTILE_PATTERNS[id >> 3][id & 7] src = Rect.new(0, 0, 0, 0) for i in 0...4 tile_position = tiles[i] - 1 @@ -114,7 +114,7 @@ class TileDrawingHelper def bltSmallRegularTile(bitmap,x,y,cxTile,cyTile,id) return if id < 384 || !@tileset || @tileset.disposed? rect = Rect.new((id - 384) % 8 * 32, (id - 384) / 8 * 32, 32, 32) - rect = TileWrap::getWrappedRect(rect) if @shouldWrap + rect = TilemapRenderer::TilesetWrapper.getWrappedRect(rect) if @shouldWrap bitmap.stretch_blt(Rect.new(x, y, cxTile, cyTile), @tileset, rect) end @@ -176,7 +176,7 @@ def bltMinimapAutotile(dstBitmap,x,y,srcBitmap,id) anim=0 cxTile=3 cyTile=3 - tiles = TileDrawingHelper::Autotiles[id>>3][id&7] + tiles = TileDrawingHelper::AUTOTILE_PATTERNS[id>>3][id&7] src=Rect.new(0,0,0,0) for i in 0...4 tile_position = tiles[i] - 1 @@ -213,7 +213,7 @@ def getPassabilityMinimap(mapid) passtable[i,j]=pass ? 1 : 0 end end - neighbors=TileDrawingHelper::NeighborsToTiles + neighbors=TileDrawingHelper::NEIGHBORS_TO_AUTOTILE_INDEX for i in 0...map.width for j in 0...map.height if passtable[i,j]==0 diff --git a/Data/Scripts/006_Map renderer/New Tilemap/002_TileWrap.rb b/Data/Scripts/006_Map renderer/New Tilemap/002_TileWrap.rb deleted file mode 100644 index 54db0d180..000000000 --- a/Data/Scripts/006_Map renderer/New Tilemap/002_TileWrap.rb +++ /dev/null @@ -1,100 +0,0 @@ -#======================================================================= -# This module is a little fix that works around PC hardware limitations. -# Since Essentials isn't working with software rendering anymore, it now -# has to deal with the limits of the GPU. For the most part this is no -# big deal, but people do have some really big tilesets. -# -# The fix is simple enough: If your tileset is too big, a new -# bitmap will be constructed with all the excess pixels sent to the -# image's right side. This basically means that you now have a limit -# far higher than you should ever actually need. -# -# Hardware limit -> max tileset length: -# 1024px -> 4096px -# 2048px -> 16384px (enough to get the normal limit) -# 4096px -> 65536px (enough to load pretty much any tileset) -# 8192px -> 262144px -# 16384px -> 1048576px (what most people have at this point) - -# ~Roza/Zoroark -#======================================================================= - -module TileWrap - - TILESET_WIDTH = 0x100 - # Looks useless, but covers weird numbers given to mkxp.json or a funky driver - MAX_TEX_SIZE = (Bitmap.max_size / 1024) * 1024 - MAX_TEX_SIZE_BOOSTED = MAX_TEX_SIZE**2/TILESET_WIDTH - - def self.clamp(val, min, max) - val = max if val > max - val = min if val < min - return val - end - - def self.wrapTileset(originalbmp) - width = originalbmp.width - height = originalbmp.height - if width == TILESET_WIDTH && originalbmp.mega? - columns = (height / MAX_TEX_SIZE.to_f).ceil - - if columns * TILESET_WIDTH > MAX_TEX_SIZE - raise "Tilemap is too long!\n\nSIZE: #{originalbmp.height}px\nHARDWARE LIMIT: #{MAX_TEX_SIZE}px\nBOOSTED LIMIT: #{MAX_TEX_SIZE_BOOSTED}px" - end - bmp = Bitmap.new(TILESET_WIDTH*columns, MAX_TEX_SIZE) - remainder = height % MAX_TEX_SIZE - - columns.times{|col| - srcrect = Rect.new(0, col * MAX_TEX_SIZE, width, (col + 1 == columns) ? remainder : MAX_TEX_SIZE) - bmp.blt(col*TILESET_WIDTH, 0, originalbmp, srcrect) - } - return bmp - end - - return originalbmp - end - - def self.getWrappedRect(src_rect) - ret = Rect.new(0,0,0,0) - col = (src_rect.y / MAX_TEX_SIZE.to_f).floor - ret.x = col * TILESET_WIDTH + clamp(src_rect.x,0,TILESET_WIDTH) - ret.y = src_rect.y % MAX_TEX_SIZE - ret.width = clamp(src_rect.width, 0, TILESET_WIDTH - src_rect.x) - ret.height = clamp(src_rect.height, 0, MAX_TEX_SIZE) - return ret - end - - def self.blitWrappedPixels(destX, destY, dest, src, srcrect) - if (srcrect.y + srcrect.width < MAX_TEX_SIZE) - # Save the processing power - dest.blt(destX, destY, src, srcrect) - return - end - merge = (srcrect.y % MAX_TEX_SIZE) > ((srcrect.y + srcrect.height) % MAX_TEX_SIZE) - - srcrect_mod = getWrappedRect(srcrect) - - if !merge - dest.blt(destX, destY, src, srcrect_mod) - else - #FIXME won't work on heights longer than two columns, but nobody should need - # more than 32k pixels high at once anyway - side = {:a => MAX_TEX_SIZE - srcrect_mod.y, :b => srcrect_mod.height - (MAX_TEX_SIZE - srcrect_mod.y)} - dest.blt(destX, destY, src, Rect.new(srcrect_mod.x, srcrect_mod.y, srcrect_mod.width, side[:a])) - dest.blt(destX, destY + side[:a], src, Rect.new(srcrect_mod.x + TILESET_WIDTH, 0, srcrect_mod.width, side[:b])) - end - end - - def self.stretchBlitWrappedPixels(destrect, dest, src, srcrect) - if (srcrect.y + srcrect.width < MAX_TEX_SIZE) - # Save the processing power - dest.stretch_blt(destrect, src, srcrect) - return - end - # Does a regular blit to a non-megasurface, then stretch_blts that to - # the destination. Yes it is slow - tmp = Bitmap.new(srcrect.width, srcrect.height) - blitWrappedPixels(0,0,tmp,src,srcrect) - dest.stretch_blt(destrect, tmp, Rect.new(0,0,srcrect.width,srcrect.height)) - end -end diff --git a/Data/Scripts/006_Map renderer/New Tilemap/003_AutotileExpander.rb b/Data/Scripts/006_Map renderer/New Tilemap/003_AutotileExpander.rb deleted file mode 100644 index e83183970..000000000 --- a/Data/Scripts/006_Map renderer/New Tilemap/003_AutotileExpander.rb +++ /dev/null @@ -1,55 +0,0 @@ -class TilemapRenderer - module AutotileExpander - MAX_TEXTURE_SIZE = (Bitmap.max_size / 1024) * 1024 - AUTOTILE_PATTERNS = [ - [ [27, 28, 33, 34], [ 5, 28, 33, 34], [27, 6, 33, 34], [ 5, 6, 33, 34], - [27, 28, 33, 12], [ 5, 28, 33, 12], [27, 6, 33, 12], [ 5, 6, 33, 12] ], - [ [27, 28, 11, 34], [ 5, 28, 11, 34], [27, 6, 11, 34], [ 5, 6, 11, 34], - [27, 28, 11, 12], [ 5, 28, 11, 12], [27, 6, 11, 12], [ 5, 6, 11, 12] ], - [ [25, 26, 31, 32], [25, 6, 31, 32], [25, 26, 31, 12], [25, 6, 31, 12], - [15, 16, 21, 22], [15, 16, 21, 12], [15, 16, 11, 22], [15, 16, 11, 12] ], - [ [29, 30, 35, 36], [29, 30, 11, 36], [ 5, 30, 35, 36], [ 5, 30, 11, 36], - [39, 40, 45, 46], [ 5, 40, 45, 46], [39, 6, 45, 46], [ 5, 6, 45, 46] ], - [ [25, 30, 31, 36], [15, 16, 45, 46], [13, 14, 19, 20], [13, 14, 19, 12], - [17, 18, 23, 24], [17, 18, 11, 24], [41, 42, 47, 48], [ 5, 42, 47, 48] ], - [ [37, 38, 43, 44], [37, 6, 43, 44], [13, 18, 19, 24], [13, 14, 43, 44], - [37, 42, 43, 48], [17, 18, 47, 48], [13, 18, 43, 48], [ 1, 2, 7, 8] ] - ] - - # TODO: Doesn't allow for cache sizes smaller than 768. - def self.expand(bitmap) - return bitmap if bitmap.height == SOURCE_TILE_HEIGHT - wrap = false - if MAX_TEXTURE_SIZE < TILES_PER_AUTOTILE * SOURCE_TILE_HEIGHT - wrap = true # Each autotile will occupy two columns instead of one - end - frames_count = [bitmap.width / (3 * SOURCE_TILE_WIDTH), 1].max - new_bitmap = Bitmap.new(frames_count * (wrap ? 2 : 1) * SOURCE_TILE_WIDTH, - TILES_PER_AUTOTILE * SOURCE_TILE_HEIGHT / (wrap ? 2 : 1)) - rect = Rect.new(0, 0, SOURCE_TILE_WIDTH / 2, SOURCE_TILE_HEIGHT / 2) - TILES_PER_AUTOTILE.times do |id| - pattern = AUTOTILE_PATTERNS[id >> 3][id % TILESET_TILES_PER_ROW] - wrap_offset_x = (wrap && id >= TILES_PER_AUTOTILE / 2) ? SOURCE_TILE_WIDTH : 0 - wrap_offset_y = (wrap && id >= TILES_PER_AUTOTILE / 2) ? (TILES_PER_AUTOTILE / 2) * SOURCE_TILE_HEIGHT : 0 - frames_count.times do |frame| - pattern.each_with_index do |src_chunk, i| - real_src_chunk = src_chunk - 1 - dest_x = (i % 2) * SOURCE_TILE_WIDTH / 2 - dest_x += frame * SOURCE_TILE_WIDTH * (wrap ? 2 : 1) - dest_x += wrap_offset_x - next if dest_x > MAX_TEXTURE_SIZE - dest_y = (i / 2) * SOURCE_TILE_HEIGHT / 2 - dest_y += id * SOURCE_TILE_HEIGHT - dest_y -= wrap_offset_y - next if dest_y > MAX_TEXTURE_SIZE - rect.x = (real_src_chunk % 6) * SOURCE_TILE_WIDTH / 2 - rect.x += SOURCE_TILE_WIDTH * 3 * frame - rect.y = (real_src_chunk / 6) * SOURCE_TILE_HEIGHT / 2 - new_bitmap.blt(dest_x, dest_y, bitmap, rect) - end - end - end - return new_bitmap - end - end -end diff --git a/Data/Scripts/012_Overworld/008_Overworld_RandomDungeons.rb b/Data/Scripts/012_Overworld/008_Overworld_RandomDungeons.rb index 4df5b34cf..e5f2a6794 100644 --- a/Data/Scripts/012_Overworld/008_Overworld_RandomDungeons.rb +++ b/Data/Scripts/012_Overworld/008_Overworld_RandomDungeons.rb @@ -1,610 +1,667 @@ #=============================================================================== -# This class is designed to favor different values more than a uniform -# random generator does +# Code that generates a random dungeon layout, and implements it in a given map. #=============================================================================== -class AntiRandom - def initialize(size) - @old = [] - @new = [] - @new = Array.new(size) { |i| i } - end - - def get - if @new.length == 0 # No new values - @new = @old.clone - @old.clear +module RandomDungeonGenerator + #============================================================================= + # This class is designed to favor different values more than a uniform + # random generator does. + #============================================================================= + class AntiRandom + def initialize(size) + @old = [] + @new = Array.new(size) { |i| i } end - if @old.length > 0 && rand(7) == 0 # Get old value - return @old[rand(@old.length)] - end - if @new.length > 0 # Get new value - ret = @new.delete_at(rand(@new.length)) - @old.push(ret) - return ret - end - return @old[rand(@old.length)] # Get old value - end -end - - -#=============================================================================== -# -#=============================================================================== -module DungeonMaze - TILE_WIDTH = 13 - TILE_HEIGHT = 13 - MINWIDTH = 5 - MINHEIGHT = 4 - MAXWIDTH = 11 - MAXHEIGHT = 10 - None = 0 - TurnLeft = 1 - TurnRight = 2 - Turn180 = 3 - - def self.paintRect(tile, x, y, width, height) # paints a room - for j in 0...height - for i in 0...width - tile[(y + j) * TILE_WIDTH + (x + i)] = 3 + def get + if @new.length == 0 # No new values + @new = @old.clone + @old.clear end + if @old.length > 0 && rand(7) == 0 # Get old value + return @old[rand(@old.length)] + end + if @new.length > 0 # Get new value + ret = @new.delete_at(rand(@new.length)) + @old.push(ret) + return ret + end + return @old[rand(@old.length)] # Get old value end end - def self.paintTile(dungeon, dstX, dstY, tile, rotation) # paints a tile - case rotation - when None - for y in 0...TILE_HEIGHT - for x in 0...TILE_WIDTH - dungeon[x + dstX, y + dstY] = tile[y * TILE_WIDTH + x] + #============================================================================= + # Contains constants that define what types of tiles a random dungeon map can + # consist of, and helper methods that translate those tiles into data usable + # by a map/printable to the console (for debug purposes). + #============================================================================= + module DungeonTile + VOID = 0 + ROOM = 1 + WALL = 2 + CORRIDOR = 3 + # Which autotile each type of tile uses (1-7) + TILE_IDS = { + VOID => 1, + ROOM => 2, + WALL => 3, + CORRIDOR => 2 + } + # Used for debugging when printing out an ASCII image of the dungeon + TEXT_SYMBOLS = { + VOID => "#", + ROOM => " ", + WALL => "-", + CORRIDOR => "." + } + + module_function + + def to_tile_id(value) + return TILE_IDS[value] || TILE_IDS[VOID] + end + + def to_text(value) + return TEXT_SYMBOLS[value] || TEXT_SYMBOLS[VOID] + end + end + + #============================================================================= + # Helper functions that set tiles in the map to a particular type. + #============================================================================= + module DungeonMaze + CELL_WIDTH = 13 # Should be at least 7 + CELL_HEIGHT = 13 # Should be at least 7 + ROOM_MIN_WIDTH = 5 + ROOM_MAX_WIDTH = CELL_WIDTH - 2 # Should be at most CELL_WIDTH - 2 + ROOM_MIN_HEIGHT = 4 + ROOM_MAX_HEIGHT = CELL_HEIGHT - 3 # Should be at most CELL_HEIGHT - 3 + CORRIDOR_WIDTH = 3 + None = 0 + TurnLeft = 1 + TurnRight = 2 + Turn180 = 3 + @@corridor_layouts = nil + + module_function + + # Generates sets of tiles depicting corridors coming out of a room, for all + # combinations of the sides that they can come out of. + def generate_corridor_patterns + if !@@corridor_layouts + tiles = [] + x_offset = (CELL_WIDTH - CORRIDOR_WIDTH) / 2 + y_offset = (CELL_HEIGHT - CORRIDOR_WIDTH) / 2 + for combo in 0...16 + tiles[combo] = [] + for i in 0...CELL_WIDTH * CELL_HEIGHT + tiles[combo][i] = DungeonTile::VOID + end + if (combo & EdgeMasks::North) == 0 + paint_corridor(tiles[combo], x_offset, 0, CORRIDOR_WIDTH, y_offset + CORRIDOR_WIDTH) + end + if (combo & EdgeMasks::South) == 0 + paint_corridor(tiles[combo], x_offset, y_offset, CORRIDOR_WIDTH, CELL_HEIGHT - y_offset) + end + if (combo & EdgeMasks::East) == 0 + paint_corridor(tiles[combo], x_offset, y_offset, CELL_WIDTH - x_offset, CORRIDOR_WIDTH) + end + if (combo & EdgeMasks::West) == 0 + paint_corridor(tiles[combo], 0, y_offset, x_offset + CORRIDOR_WIDTH, CORRIDOR_WIDTH) + end end + @@corridor_layouts = tiles end - when TurnLeft - for y in 0...TILE_HEIGHT - for x in 0...TILE_WIDTH - dungeon[y + dstX , TILE_WIDTH - 1 - x + dstY] = tile[y * TILE_WIDTH + x] - end - end - when TurnRight - for y in 0...TILE_HEIGHT - for x in 0...TILE_WIDTH - dungeon[TILE_HEIGHT - 1 - y + dstX, x + dstY] = tile[y * TILE_WIDTH + x] - end - end - when Turn180 - for y in 0...TILE_HEIGHT - for x in 0...TILE_WIDTH - dungeon[TILE_WIDTH - 1 - x + dstX, TILE_HEIGHT - 1 - y + dstY] = tile[y * TILE_WIDTH + x] + return @@corridor_layouts + end + + # Makes all tiles in a particular area corridor tiles. + def paint_corridor(tile, x, y, width, height) + for j in 0...height + for i in 0...width + tile[(y + j) * CELL_WIDTH + (x + i)] = DungeonTile::CORRIDOR end end end - end - def self.paintCell(dungeon, xDst, yDst, tile, rotation) - return false if !tile - paintTile(dungeon, xDst, yDst, tile, rotation) - return false if rand(100) < 30 - # Generate a randomly placed room - width = rand(MINWIDTH..MAXWIDTH) - height = rand(MINHEIGHT..MAXHEIGHT) - return false if width <= 0 || height <= 0 - centerX = TILE_WIDTH / 2 + rand(5) - 2 - centerY = TILE_HEIGHT / 2 + rand(5) - 2 - x = centerX - (width / 2) - y = centerY - (height / 2) - rect = [x, y, width, height] - rect[0] = 1 if rect[0] < 1 - rect[1] = 2 if rect[1] < 2 - rect[0] = TILE_WIDTH - 1 - width if rect[0] + width > TILE_WIDTH - 1 - rect[1] = TILE_HEIGHT - 1 - height if rect[0] + height > TILE_HEIGHT - 1 - dungeon.paint(rect, xDst, yDst) - return true - end - - def self.generateTiles - tiles = [] - for i in 0...6 - tiles[i] = [] - for j in 0...TILE_WIDTH * TILE_HEIGHT - tiles[i][j] = 0 + # Used to draw tiles from the given tile_layout and rotation (for corridors). + def paint_tile_layout(dungeon, dstX, dstY, tile_layout, rotation) + case rotation + when None + for y in 0...CELL_HEIGHT + for x in 0...CELL_WIDTH + dungeon[x + dstX, y + dstY] = tile_layout[y * CELL_WIDTH + x] + end + end + when TurnLeft + for y in 0...CELL_HEIGHT + for x in 0...CELL_WIDTH + dungeon[y + dstX , CELL_WIDTH - 1 - x + dstY] = tile_layout[y * CELL_WIDTH + x] + end + end + when TurnRight + for y in 0...CELL_HEIGHT + for x in 0...CELL_WIDTH + dungeon[CELL_HEIGHT - 1 - y + dstX, x + dstY] = tile_layout[y * CELL_WIDTH + x] + end + end + when Turn180 + for y in 0...CELL_HEIGHT + for x in 0...CELL_WIDTH + dungeon[CELL_WIDTH - 1 - x + dstX, CELL_HEIGHT - 1 - y + dstY] = tile_layout[y * CELL_WIDTH + x] + end + end end end - paintRect(tiles[0], 5, 0, 3, 10) # N - paintRect(tiles[1], 5, 0, 3, 8) # N E - paintRect(tiles[1], 5, 5, 8, 3) - paintRect(tiles[2], 5, 0, 3, 8) # N W E - paintRect(tiles[2], 0, 5, 13, 3) - paintRect(tiles[3], 5, 0, 3, 13) # N S - paintRect(tiles[4], 5, 0, 3, 13) - paintRect(tiles[4], 0, 5, 13, 3) - realtiles = [ - [tiles[4], None], # N W E S - [tiles[2], Turn180], # W E S - [tiles[2], TurnRight], # N E S - [tiles[1], TurnRight], # E S - [tiles[2], TurnLeft], # N W S - [tiles[1], Turn180], # W S - [tiles[3], None], # N S - [tiles[0], Turn180], # S - [tiles[2], None], # N W E - [tiles[3], TurnLeft], # W E - [tiles[1], None], # N E - [tiles[0], TurnRight], # E - [tiles[1], TurnLeft], # N W - [tiles[0], TurnLeft], # W - [tiles[0], None], # N - [nil, None] - ] - return realtiles - end -end - - -module EdgeMasks - North = 1 - West = 2 - East = 4 - South = 8 - Visited = 16 -end - - - -class MazeNode - def initialize - @edges = 0 - end - - def setEdge(e); @edges |= e; end - def clearEdge(e); @edges &= ~e; end - def clear; @edges = 0; end - def set; @edges = 15; end - def getEdge(e); return (@edges & e) != 0; end - def isBlocked?; return @edges != 0; end -end - - - -class NodeListElement - attr_accessor :x, :y - - def initialize(x, y) - @x = x - @y = y - end -end - - - -class Maze - attr_accessor :cellWidth, :cellHeight, :nodeWidth, :nodeHeight - - @@dirs = [EdgeMasks::North, EdgeMasks::South, EdgeMasks::East, EdgeMasks::West] - - def initialize(cw, ch) - @nodes = [] - @cells = [] - raise ArgumentError.new if cw == 0 || ch == 0 - @cellWidth = cw - @cellHeight = ch - @nodeWidth = cw + 1 - @nodeHeight = ch + 1 - for i in 0...@nodeWidth * @nodeHeight - @nodes[i] = MazeNode.new + # Draws a cell's contents, which is an underlying pattern based on tile + #_layout and a rotation (the corridors), and possibly a room on top of that. + def paint_cell_contents(dungeon, xDst, yDst, tile_layout, rotation) + return false if !tile_layout + # Draw the corridors + paint_tile_layout(dungeon, xDst, yDst, tile_layout, rotation) + return false if rand(100) < 30 + # Generate a randomly placed room + width = rand(ROOM_MIN_WIDTH..ROOM_MAX_WIDTH) + height = rand(ROOM_MIN_HEIGHT..ROOM_MAX_HEIGHT) + return false if width <= 0 || height <= 0 + centerX = CELL_WIDTH / 2 + rand(5) - 2 + centerY = CELL_HEIGHT / 2 + rand(5) - 2 + x = centerX - (width / 2) + y = centerY - (height / 2) + rect = [x, y, width, height] + rect[0] = rect[0].clamp(1, CELL_WIDTH - 1 - width) + rect[1] = rect[1].clamp(2, CELL_HEIGHT - 1 - height) # 2 because walls are 2 tiles tall + dungeon.paint_room(rect, xDst, yDst) + return true end - for i in 0...cw * ch - @cells[i] = 0 - end - clearAllEdges() - clearAllCells() end - def buildNodeList - list = [] - for x in 0...nodeWidth - for y in 0...nodeHeight - list.push(NodeListElement.new(x, y)) + #============================================================================= + # Bitwise values used to keep track of the generation of node connections. + #============================================================================= + module EdgeMasks + North = 1 + West = 2 + East = 4 + South = 8 + Visited = 16 + end + + #============================================================================= + # A node in a randomly generated dungeon. There is one node per cell, and + # nodes are connected to each other. + #============================================================================= + class MazeNode + def initialize + @edges = 0 + end + + def setEdge(e); @edges |= e; end + def clearEdge(e); @edges &= ~e; end + def clear; @edges = 0; end + def set; @edges = 15; end + def getEdge(e); return (@edges & e) != 0; end + def isBlocked?; return @edges != 0; end + end + + #============================================================================= + # Vector class representing the location of a node. + #============================================================================= + class NodeListElement + attr_accessor :x, :y + + def initialize(x, y) + @x = x + @y = y + end + end + + #============================================================================= + # Maze generator. Given the number of cells horizontally and vertically in a + # map, connects all the cells together. + # A node is the boundary between two adjacent cells, which may or may not be a + # connection. + #============================================================================= + class Maze + attr_accessor :cellWidth, :cellHeight, :nodeWidth, :nodeHeight + DIRECTIONS = [EdgeMasks::North, EdgeMasks::South, EdgeMasks::East, EdgeMasks::West] + + def initialize(cw, ch) + raise ArgumentError.new if cw == 0 || ch == 0 + @cellWidth = cw + @cellHeight = ch + @nodeWidth = cw + 1 + @nodeHeight = ch + 1 + @cells = [] + clearAllCells + @nodes = Array.new(@nodeWidth * @nodeHeight) { MazeNode.new } + end + + def randomDir + return DIRECTIONS[rand(4)] + end + + def getVisited(x, y) + return false if x < 0 || y < 0 || x >= cellWidth || x >= cellHeight + return (@cells[y * cellWidth + x] & EdgeMasks::Visited) != 0 + end + + def setVisited(x, y) + return if x < 0 || y < 0 || x >= cellWidth || x >= cellHeight + @cells[y * cellWidth + x] |= EdgeMasks::Visited + end + + def clearVisited(x, y) + return if x < 0 || y < 0 || x >= cellWidth || x >= cellHeight + @cells[y * cellWidth + x] &=~EdgeMasks::Visited + end + + def clearAllCells + for c in 0...cellWidth * cellHeight + @cells[c] = 0 end end - list.shuffle! - return list - end - def setEdgeNode(x, y, edge) - return if x < 0 || x >= nodeWidth || y < 0 || y >= nodeHeight - @nodes[y * nodeWidth + x].setEdge(edge) - e = 0 - nx = 0 - ny = 0 - case edge - when EdgeMasks::North - e = EdgeMasks::South + def getEdgeNode(x, y, edge) + return false if x < 0 || y < 0 || x >= nodeWidth || y >= nodeHeight + return @nodes[y * nodeWidth + x].getEdge(edge) + end + + def setEdgeNode(x, y, edge) + return if x < 0 || x >= nodeWidth || y < 0 || y >= nodeHeight + @nodes[y * nodeWidth + x].setEdge(edge) + e = 0 nx = x - ny = y - 1 - when EdgeMasks::South - e = EdgeMasks::North - nx = x - ny = y + 1 - when EdgeMasks::East - e = EdgeMasks::West - nx = x + 1 ny = y - when EdgeMasks::West - e = EdgeMasks::East - nx = x - 1 - ny = y - else - return - end - return if nx < 0 || ny < 0 || nx >= nodeWidth || ny >= nodeHeight - @nodes[ny * nodeWidth + nx].setEdge(e) - end - - def clearEdgeNode(x, y, edge) - return if x < 0 || x >= nodeWidth || y < 0 || y >= nodeHeight - @nodes[y * nodeWidth + x].clearEdge(edge) - e = 0 - nx = 0 - ny = 0 - case edge - when EdgeMasks::North - e = EdgeMasks::South - nx = x - ny = y - 1 - when EdgeMasks::South - e = EdgeMasks::North - nx = x - ny = y + 1 - when EdgeMasks::East - e = EdgeMasks::West - nx = x + 1 - ny = y - when EdgeMasks::West - e = EdgeMasks::East - nx = x - 1 - ny = y - else - raise ArgumentError.new - end - return if nx < 0 || ny < 0 || nx >= nodeWidth || ny >= nodeHeight - @nodes[ny * nodeWidth + nx].clearEdge(e) - end - - def isBlockedNode?(x, y) - return false if x < 0 || y < 0 || x >= nodeWidth || y >= nodeHeight - return @nodes[y * nodeWidth + x].isBlocked? - end - - def getEdgeNode(x, y, edge) - return false if x < 0 || y < 0 || x >= nodeWidth || y >= nodeHeight - return @nodes[y * nodeWidth + x].getEdge(edge) - end - - def getEdgePattern(x, y) - pattern = 0 - pattern |= EdgeMasks::North if getEdgeNode(x, y, EdgeMasks::North) - pattern |= EdgeMasks::South if getEdgeNode(x, y, EdgeMasks::South) - pattern |= EdgeMasks::East if getEdgeNode(x, y, EdgeMasks::East) - pattern |= EdgeMasks::West if getEdgeNode(x, y, EdgeMasks::West) - return pattern - end - - def setAllEdges - for c in 0...nodeWidth * nodeHeight - @nodes[c].set - end - end - - def clearAllEdges - for c in 0...nodeWidth * nodeHeight - @nodes[c].clear - end - end - - def clearAllCells - for c in 0...cellWidth * cellHeight - @cells[c] = 0 - end - end - - def setVisited(x, y) - return if x < 0 || y < 0 || x >= cellWidth || x >= cellHeight - @cells[y * cellWidth + x] |= EdgeMasks::Visited - end - - def getVisited(x, y) - return false if x < 0 || y < 0 || x >= cellWidth || x >= cellHeight - return (@cells[y * cellWidth + x] & EdgeMasks::Visited) != 0 - end - - def clearVisited(x, y) - return if x < 0 || y < 0 || x >= cellWidth || x >= cellHeight - @cells[y * cellWidth + x] &=~EdgeMasks::Visited - end - - def randomDir - return @@dirs[rand(4)] - end - - def buildMazeWall(x, y, dir, len) - return if isBlockedNode?(x, y) - wx = x - wy = y - len.times do - ox = wx - oy = wy - wy -= 1 if dir == EdgeMasks::North - wx -= 1 if dir == EdgeMasks::West - wx += 1 if dir == EdgeMasks::East - wy += 1 if dir == EdgeMasks::South - if isBlockedNode?(wx, wy) - setEdgeNode(ox, oy, dir) + case edge + when EdgeMasks::North + e = EdgeMasks::South + ny = y - 1 + when EdgeMasks::South + e = EdgeMasks::North + ny = y + 1 + when EdgeMasks::East + e = EdgeMasks::West + nx = x + 1 + when EdgeMasks::West + e = EdgeMasks::East + nx = x - 1 + else return end - setEdgeNode(ox,oy,dir) + return if nx < 0 || ny < 0 || nx >= nodeWidth || ny >= nodeHeight + @nodes[ny * nodeWidth + nx].setEdge(e) end - end - def generateWallGrowthMaze(minWall = 0, maxWall = nil) - maxWall = cellWidth if !maxWall - nlist = buildNodeList() - return if nlist.length == 0 - for c in 0...nlist.length - d = randomDir() - len = rand(maxWall + 1) - x = nlist[c].x - y = nlist[c].y - buildMazeWall(x, y, d, len) + def setAllEdges + for c in 0...nodeWidth * nodeHeight + @nodes[c].set + end end - end - def recurseDepthFirst(x, y, depth) - setVisited(x, y) - dirs = @@dirs.shuffle! - for c in 0...4 - d = dirs[c] - cx = 0 - cy = 0 - case d + def clearEdgeNode(x, y, edge) + return if x < 0 || x >= nodeWidth || y < 0 || y >= nodeHeight + @nodes[y * nodeWidth + x].clearEdge(edge) + e = 0 + nx = x + ny = y + case edge when EdgeMasks::North - cx = x - cy = y - 1 + e = EdgeMasks::South + ny -= 1 when EdgeMasks::South - cx = x - cy = y + 1 + e = EdgeMasks::North + ny += 1 when EdgeMasks::East - cx = x + 1 - cy = y + e = EdgeMasks::West + nx += 1 when EdgeMasks::West - cx = x - 1 + e = EdgeMasks::East + nx -= 1 + else + raise ArgumentError.new + end + return if nx < 0 || ny < 0 || nx >= nodeWidth || ny >= nodeHeight + @nodes[ny * nodeWidth + nx].clearEdge(e) + end + + def clearAllEdges + for c in 0...nodeWidth * nodeHeight + @nodes[c].clear + end + end + + def isBlockedNode?(x, y) + return false if x < 0 || y < 0 || x >= nodeWidth || y >= nodeHeight + return @nodes[y * nodeWidth + x].isBlocked? + end + + def getEdgePattern(x, y) + pattern = 0 + pattern |= EdgeMasks::North if getEdgeNode(x, y, EdgeMasks::North) + pattern |= EdgeMasks::South if getEdgeNode(x, y, EdgeMasks::South) + pattern |= EdgeMasks::East if getEdgeNode(x, y, EdgeMasks::East) + pattern |= EdgeMasks::West if getEdgeNode(x, y, EdgeMasks::West) + return pattern + end + + def buildMazeWall(x, y, dir, len) + return if isBlockedNode?(x, y) + wx = x + wy = y + len.times do + ox = wx + oy = wy + case dir + when EdgeMasks::North + wy -= 1 + when EdgeMasks::West + wx -= 1 + when EdgeMasks::East + wx += 1 + when EdgeMasks::South + wy += 1 + end + if isBlockedNode?(wx, wy) + setEdgeNode(ox, oy, dir) + return + end + setEdgeNode(ox,oy,dir) + end + end + + def buildNodeList + list = [] + for x in 0...nodeWidth + for y in 0...nodeHeight + list.push(NodeListElement.new(x, y)) + end + end + list.shuffle! + return list + end + + def generateWallGrowthMaze(minWall = 0, maxWall = nil) + maxWall = cellWidth if !maxWall + nlist = buildNodeList() + return if nlist.length == 0 + for c in 0...nlist.length + d = randomDir() + len = rand(maxWall + 1) + x = nlist[c].x + y = nlist[c].y + buildMazeWall(x, y, d, len) + end + end + + def recurseDepthFirst(x, y, depth) + setVisited(x, y) + dirs = DIRECTIONS.shuffle + for c in 0...4 + d = dirs[c] + cx = x cy = y - end - if cx >= 0 && cy >= 0 && cx < cellWidth && cy < cellHeight - if !getVisited(cx, cy) - clearEdgeNode(x, y, d) - recurseDepthFirst(cx, cy, depth + 1) + case d + when EdgeMasks::North + cy -= 1 + when EdgeMasks::South + cy += 1 + when EdgeMasks::East + cx += 1 + when EdgeMasks::West + cx -= 1 + end + if cx >= 0 && cy >= 0 && cx < cellWidth && cy < cellHeight + if !getVisited(cx, cy) + clearEdgeNode(x, y, d) + recurseDepthFirst(cx, cy, depth + 1) + end end end end - end - def generateDepthFirstMaze - sx = rand(cellWidth) - sy = rand(cellHeight) - setAllEdges() - recurseDepthFirst(sx, sy, 0) - end -end - - - -class Dungeon - attr_accessor :width, :height - XBUFFER = 8 - YBUFFER = 6 - - class DungeonTable - def initialize(dungeon) - @dungeon = dungeon - end - - def xsize; @dungeon.width; end - def ysize; @dungeon.height; end - - def [](x, y) - [1, 2, 3, 2][@dungeon[x, y]] # Void, room floor, wall, corridor floor + def generateDepthFirstMaze + # Pick a cell to start in + sx = rand(cellWidth) + sy = rand(cellHeight) + # Set up all nodes + setAllEdges + # Generate a maze + recurseDepthFirst(sx, sy, 0) end end - def initialize(width, height) - @width = width - @height = height - @array = [] - end - - def clear - for i in 0...width * height - @array[i] = 0 - end - end - - def write - ret = "" - i = 0 - for y in 0...@height - for x in 0...@width - ret += [" ", ".", "~", ","][value(x, y)] # Void, room floor, wall, corridor floor - i += 1 + #============================================================================= + # Random dungeon generator class. Calls class Maze to generate the abstract + # layout of the dungeon, and turns that into usable map data. + #============================================================================= + class Dungeon + class DungeonTable + def initialize(dungeon) + @dungeon = dungeon end - ret += "\r\n" - end - return ret - end - def [](x, y) - @array[y * @width + x] - end + def xsize; @dungeon.width; end + def ysize; @dungeon.height; end - def []=(x, y, value) - @array[y * @width + x] = value - end - - def value(x, y) - return 0 if x < 0 || y < 0 || x >= @width || y >= @height - @array[y * @width + x] - end - - def get(x, y) - return false if x < 0 || y < 0 || x >= @width || y >= @height - @array[y * @width + x] != 0 - end - - def isWall?(x, y) - if value(x, y) == 0 # This tile is void - v1 = value(x, y + 1) - return true if v1 == 1 || v1 == 3 # The tile below is room floor/corridor floor - if v1 == 0 # The tile below is void - v1 = value(x, y + 2) - return true if v1 == 1 || v1 == 3 # The tile below that is room floor/corridor floor + # Returns which tile in the tileset corresponds to the type of tile is at + # the given coordinates + def [](x, y) + return DungeonTile.to_tile_id(@dungeon[x, y]) end end - return false - end - def isRoom?(x, y) - if value(x, y) == 1 # This tile is a room floor - return false if value(x - 1, y - 1) == 3 - return false if value( x, y - 1) == 3 - return false if value(x + 1, y - 1) == 3 - return false if value(x - 1, y) == 3 - return false if value(x + 1, y) == 3 - return false if value(x - 1, y + 1) == 3 - return false if value( x, y + 1) == 3 - return false if value(x + 1, y + 1) == 3 - return true # No surrounding tiles are corridor floor + attr_accessor :width, :height + BUFFER_X = 8 + BUFFER_Y = 6 + + def initialize(width, height) + @width = width + @height = height + @array = [] end - return false - end - def generate - self.clear - maxWidth = @width - XBUFFER * 2 - maxHeight = @height - YBUFFER * 2 - cellWidth = DungeonMaze::TILE_WIDTH - cellHeight = DungeonMaze::TILE_HEIGHT - return if maxWidth < 0 || maxHeight < 0 - if maxWidth < cellWidth || maxHeight < cellHeight # Map is too small - for x in 0...maxWidth - for y in 0...maxHeight - self[x + XBUFFER, y + YBUFFER] = 1 # Make all tiles room floor + def clear + for i in 0...width * height + @array[i] = DungeonTile::VOID + end + end + + def write + ret = "" + i = 0 + for y in 0...@height + for x in 0...@width + ret += DungeonTile.to_text(value(x, y)) + i += 1 end + ret += "\r\n" end - return - end - maze = Maze.new(maxWidth / cellWidth, maxHeight / cellHeight) - maze.generateDepthFirstMaze() - tiles = DungeonMaze.generateTiles() - roomcount = 0 - for y in 0...maxHeight / cellHeight - for x in 0...maxWidth / cellWidth - tile = maze.getEdgePattern(x, y) - if DungeonMaze.paintCell(self, XBUFFER + x * cellWidth, YBUFFER + y * cellHeight, - tiles[tile][0], tiles[tile][1]) - roomcount += 1 - end - end - end - if roomcount == 0 - # Handle situation where no rooms were generated - for x in 0...maxWidth - for y in 0...maxHeight - self[x + XBUFFER, y + YBUFFER] = 1 # Make all tiles room floor - end - end - end - # Generate walls - for y in 0...@height - for x in 0...@width - self[x, y] = 2 if isWall?(x, y) # Make appropriate tiles wall tiles - end - end - end - - def generateMapInPlace(map) - tbl = DungeonTable.new(self) - for i in 0...map.width - for j in 0...map.height - nb = TileDrawingHelper.tableNeighbors(tbl, i, j) - tile = TileDrawingHelper::NeighborsToTiles[nb] - map.data[i, j, 0] = tile + 48 * (tbl[i, j]) - map.data[i, j, 1] = 0 - map.data[i, j, 2] = 0 - end - end - end - - def paint(rect,offsetX,offsetY) - for y in (rect[1] + offsetY)...(rect[1] + offsetY + rect[3]) - for x in (rect[0] + offsetX)...(rect[0] + offsetX + rect[2]) - self[x, y] = 1 # room tile - end - end - end - - def intersects?(r1, r2) - return !(((r2[0] + r2[2] <= r1[0]) || - (r2[0] >= r1[0] + r1[2]) || - (r2[1] + r2[3] <= r1[1]) || - (r2[1] >= r1[1] + r1[3])) && - ((r1[0] <= r2[0] + r2[2])|| - (r1[0] >= r2[0] + r2[2]) || - (r1[1] + r1[3] <= r2[1]) || - (r1[1] >= r2[1] + r2[3])) - ); - end -end - - - -# Get a random room tile that isn't too close to a corridor (to avoid blocking -# a room's entrance) -def pbRandomRoomTile(dungeon, tiles) - ar1 = AntiRandom.new(dungeon.width) - ar2 = AntiRandom.new(dungeon.height) - ((tiles.length + 1) * 1000).times do - x = ar1.get() - y = ar2.get() - if dungeon.isRoom?(x, y) && - !tiles.any? { |item| (item[0] - x).abs < 2 && (item[1] - y).abs < 2 } - ret = [x, y] - tiles.push(ret) return ret end + + def [](x, y) + return @array[y * @width + x] + end + + def []=(x, y, value) + @array[y * @width + x] = value + end + + def value(x, y) + return DungeonTile::VOID if x < 0 || y < 0 || x >= @width || y >= @height + return @array[y * @width + x] + end + + # Unused + def get(x, y) + return false if x < 0 || y < 0 || x >= @width || y >= @height + return @array[y * @width + x] != DungeonTile::VOID + end + + # Unused + def intersects?(r1, r2) + return !(((r2[0] + r2[2] <= r1[0]) || + (r2[0] >= r1[0] + r1[2]) || + (r2[1] + r2[3] <= r1[1]) || + (r2[1] >= r1[1] + r1[3])) && + ((r1[0] <= r2[0] + r2[2])|| + (r1[0] >= r2[0] + r2[2]) || + (r1[1] + r1[3] <= r2[1]) || + (r1[1] >= r2[1] + r2[3])) + ) + end + + # Returns whether the given coordinates are a room floor that isn't too close + # to a corridor + def isRoom?(x, y) + if value(x, y) == DungeonTile::ROOM + return false if value(x - 1, y - 1) == DungeonTile::CORRIDOR + return false if value( x, y - 1) == DungeonTile::CORRIDOR + return false if value(x + 1, y - 1) == DungeonTile::CORRIDOR + return false if value(x - 1, y) == DungeonTile::CORRIDOR + return false if value(x + 1, y) == DungeonTile::CORRIDOR + return false if value(x - 1, y + 1) == DungeonTile::CORRIDOR + return false if value( x, y + 1) == DungeonTile::CORRIDOR + return false if value(x + 1, y + 1) == DungeonTile::CORRIDOR + return true # No surrounding tiles are corridor floor + end + return false + end + + def isWall?(x, y) + if value(x, y) == DungeonTile::VOID + v1 = value(x, y + 1) + return true if v1 == DungeonTile::ROOM || v1 == DungeonTile::CORRIDOR + if v1 == DungeonTile::VOID # The tile below is void + v1 = value(x, y + 2) + return true if v1 == DungeonTile::ROOM || v1 == DungeonTile::CORRIDOR + end + end + return false + end + + def paint_room(rect,offsetX,offsetY) + for y in (rect[1] + offsetY)...(rect[1] + offsetY + rect[3]) + for x in (rect[0] + offsetX)...(rect[0] + offsetX + rect[2]) + self[x, y] = DungeonTile::ROOM + end + end + end + + def generate + self.clear + maxWidth = @width - BUFFER_X * 2 + maxHeight = @height - BUFFER_Y * 2 + cellWidth = DungeonMaze::CELL_WIDTH + cellHeight = DungeonMaze::CELL_HEIGHT + return if maxWidth < 0 || maxHeight < 0 + if maxWidth < cellWidth || maxHeight < cellHeight # Map is too small + for x in 0...maxWidth + for y in 0...maxHeight + self[x + BUFFER_X, y + BUFFER_Y] = DungeonTile::ROOM + end + end + return + end + # Generate connections between cells + maze = Maze.new(maxWidth / cellWidth, maxHeight / cellHeight) + maze.generateDepthFirstMaze() + # Draw each cell's contents in turn (room and corridors) + corridor_patterns = DungeonMaze.generate_corridor_patterns + roomcount = 0 + for y in 0...maxHeight / cellHeight + for x in 0...maxWidth / cellWidth + pattern = maze.getEdgePattern(x, y) + if DungeonMaze.paint_cell_contents( + self, BUFFER_X + x * cellWidth, BUFFER_Y + y * cellHeight, + corridor_patterns[pattern], DungeonMaze::None) + roomcount += 1 + end + end + end + # If no rooms were generated, make the whole map a room + if roomcount == 0 + for x in 0...maxWidth + for y in 0...maxHeight + self[x + BUFFER_X, y + BUFFER_Y] = DungeonTile::ROOM + end + end + end + # Generate walls + for y in 0...@height + for x in 0...@width + self[x, y] = DungeonTile::WALL if isWall?(x, y) # Make appropriate tiles wall tiles + end + end + end + + # Convert dungeon layout into proper map tiles + def generateMapInPlace(map) + tbl = DungeonTable.new(self) + for i in 0...map.width + for j in 0...map.height + nb = TileDrawingHelper.tableNeighbors(tbl, i, j) + tile = TileDrawingHelper::NEIGHBORS_TO_AUTOTILE_INDEX[nb] + map.data[i, j, 0] = tile + 48 * (tbl[i, j]) + map.data[i, j, 1] = 0 + map.data[i, j, 2] = 0 + end + end + end + end + + #============================================================================= + # + #============================================================================= + # Get a random room tile that isn't too close to a corridor (to avoid blocking + # a room's entrance). + def self.pbRandomRoomTile(dungeon, tiles) + ar1 = AntiRandom.new(dungeon.width) + ar2 = AntiRandom.new(dungeon.height) + ((tiles.length + 1) * 1000).times do + x = ar1.get() + y = ar2.get() + if dungeon.isRoom?(x, y) && + !tiles.any? { |item| (item[0] - x).abs < 2 && (item[1] - y).abs < 2 } + ret = [x, y] + tiles.push(ret) + return ret + end + end + return nil + end + + # Test method that generates a dungeon map and prints it to the console. + # @param x_cells [Integer] the number of cells wide the dungeon will be + # @param y_cells [Intenger] the number of cells tall the dungeon will be + def self.generate_test_dungeon(x_cells = 4, y_cells = 4) + dungeon = Dungeon.new(Dungeon::BUFFER_X * 2 + DungeonMaze::CELL_WIDTH * x_cells, + Dungeon::BUFFER_Y * 2 + DungeonMaze::CELL_HEIGHT * y_cells) + dungeon.generate + echoln dungeon.write end - return nil end Events.onMapCreate += proc { |_sender, e| mapID = e[0] map = e[1] - next if !GameData::MapMetadata.exists?(mapID) || - !GameData::MapMetadata.get(mapID).random_dungeon + next if !GameData::MapMetadata.try_get(mapID)&.random_dungeon # this map is a randomly generated dungeon - dungeon = Dungeon.new(map.width, map.height) + dungeon = RandomDungeonGenerator::Dungeon.new(map.width, map.height) dungeon.generate dungeon.generateMapInPlace(map) roomtiles = [] # Reposition events for event in map.events.values - tile = pbRandomRoomTile(dungeon, roomtiles) + tile = RandomDungeonGenerator.pbRandomRoomTile(dungeon, roomtiles) if tile event.x = tile[0] event.y = tile[1] end end # Override transfer X and Y - tile = pbRandomRoomTile(dungeon, roomtiles) + tile = RandomDungeonGenerator.pbRandomRoomTile(dungeon, roomtiles) if tile $game_temp.player_new_x = tile[0] $game_temp.player_new_y = tile[1]