diff --git a/Data/Scripts/005_Sprites/007_Spriteset_Map.rb b/Data/Scripts/005_Sprites/007_Spriteset_Map.rb index fcc5dcb46..37e2f4609 100644 --- a/Data/Scripts/005_Sprites/007_Spriteset_Map.rb +++ b/Data/Scripts/005_Sprites/007_Spriteset_Map.rb @@ -33,6 +33,7 @@ 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 @@ -48,6 +49,15 @@ 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 @@ -62,6 +72,11 @@ 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 @@ -70,6 +85,7 @@ class Spriteset_Map sprite.dispose end @weather.dispose +# @tilemap = nil @panorama = nil @fog = nil @character_sprites.clear @@ -101,10 +117,13 @@ 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 8d904f0f5..dfdccf7fb 100644 --- a/Data/Scripts/005_Sprites/008_Sprite_AnimationSprite.rb +++ b/Data/Scripts/005_Sprites/008_Sprite_AnimationSprite.rb @@ -75,6 +75,8 @@ 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 new file mode 100644 index 000000000..9540718eb --- /dev/null +++ b/Data/Scripts/006_Map renderer/001_TilemapLoader.rb @@ -0,0 +1,59 @@ +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/002_CustomTilemap.rb b/Data/Scripts/006_Map renderer/002_CustomTilemap.rb new file mode 100644 index 000000000..5b9717c9c --- /dev/null +++ b/Data/Scripts/006_Map renderer/002_CustomTilemap.rb @@ -0,0 +1,1026 @@ +#======================================================================= +# 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 deleted file mode 100644 index 76727a337..000000000 --- a/Data/Scripts/006_Map renderer/002_TilesetWrapper.rb +++ /dev/null @@ -1,96 +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 -#======================================================================= -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 deleted file mode 100644 index 26ded25af..000000000 --- a/Data/Scripts/006_Map renderer/003_AutotileExpander.rb +++ /dev/null @@ -1,72 +0,0 @@ -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/004_TileDrawingHelper.rb b/Data/Scripts/006_Map renderer/003_TileDrawingHelper.rb similarity index 95% rename from Data/Scripts/006_Map renderer/004_TileDrawingHelper.rb rename to Data/Scripts/006_Map renderer/003_TileDrawingHelper.rb index 91e51d0a2..80d9e3277 100644 --- a/Data/Scripts/006_Map renderer/004_TileDrawingHelper.rb +++ b/Data/Scripts/006_Map renderer/003_TileDrawingHelper.rb @@ -2,7 +2,7 @@ class TileDrawingHelper attr_accessor :tileset attr_accessor :autotiles - AUTOTILE_PATTERNS = [ + 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], @@ -18,7 +18,7 @@ class TileDrawingHelper ] # converts neighbors returned from tableNeighbors to tile indexes - NEIGHBORS_TO_AUTOTILE_INDEX = [ + NeighborsToTiles = [ 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 = TilemapRenderer::TilesetWrapper.wrapTileset(tileset) + @tileset = TileWrap::wrapTileset(tileset) tileset.dispose @shouldWrap = true else @@ -100,7 +100,7 @@ class TileDrawingHelper else anim = frame * 96 id %= 48 - tiles = AUTOTILE_PATTERNS[id >> 3][id & 7] + tiles = TileDrawingHelper::Autotiles[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 = TilemapRenderer::TilesetWrapper.getWrappedRect(rect) if @shouldWrap + rect = TileWrap::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::AUTOTILE_PATTERNS[id>>3][id&7] + tiles = TileDrawingHelper::Autotiles[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::NEIGHBORS_TO_AUTOTILE_INDEX + neighbors=TileDrawingHelper::NeighborsToTiles 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/001_TilemapRenderer.rb b/Data/Scripts/006_Map renderer/New Tilemap/001_NewTilemap.rb similarity index 72% rename from Data/Scripts/006_Map renderer/001_TilemapRenderer.rb rename to Data/Scripts/006_Map renderer/New Tilemap/001_NewTilemap.rb index cfd9bc8c6..492ab4511 100644 --- a/Data/Scripts/006_Map renderer/001_TilemapRenderer.rb +++ b/Data/Scripts/006_Map renderer/New Tilemap/001_NewTilemap.rb @@ -1,28 +1,48 @@ +# 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 - attr_accessor :ox # Does nothing - attr_accessor :oy # Does nothing - attr_accessor :visible # Does nothing + # TODO: ox, oy and visible don't do anything. Should they? + attr_accessor :ox + attr_accessor :oy + attr_accessor :visible - 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 + 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 # 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] + #============================================================================= # #============================================================================= @@ -57,7 +77,7 @@ class TilemapRenderer bitmap = pbGetTileset(filename) @bitmap_wraps[filename] = false if bitmap.mega? - self[filename] = TilemapRenderer::TilesetWrapper.wrapTileset(bitmap) + self[filename] = TileWrap::wrapTileset(bitmap) @bitmap_wraps[filename] = true bitmap.dispose else @@ -241,37 +261,57 @@ class TilemapRenderer # #============================================================================= def initialize(viewport) - @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 = [] + @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 = [] @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 - @oy = 0 - @visible = 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 # Bitmap Offsets + @oy = 0 # Bitmap Offsets + + @visible = true + + @flash = nil + @oxFlash = 0 + @oyFlash = 0 + @flashChanged = false + @firsttimeflash = true + + @disposed = false end def dispose @@ -281,6 +321,12 @@ 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 @@ -312,7 +358,87 @@ class TilemapRenderer #============================================================================= - def refresh; end + # 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_tile_bitmap(tile, map, tile_id) if tile_id < TILES_PER_AUTOTILE @@ -393,6 +519,7 @@ 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 @@ -445,6 +572,7 @@ 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 @@ -463,8 +591,16 @@ 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 } @@ -474,6 +610,7 @@ 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 } @@ -484,22 +621,30 @@ 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 @@ -513,6 +658,7 @@ 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 @@ -535,6 +681,7 @@ class TilemapRenderer end end end + # Clear all unvisited tile sprites @tiles.each_with_index do |col, i| col.each_with_index do |coord, j| @@ -546,6 +693,7 @@ class TilemapRenderer end end end + @autotiles.changed = false end end diff --git a/Data/Scripts/006_Map renderer/New Tilemap/002_TileWrap.rb b/Data/Scripts/006_Map renderer/New Tilemap/002_TileWrap.rb new file mode 100644 index 000000000..54db0d180 --- /dev/null +++ b/Data/Scripts/006_Map renderer/New Tilemap/002_TileWrap.rb @@ -0,0 +1,100 @@ +#======================================================================= +# 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 new file mode 100644 index 000000000..e83183970 --- /dev/null +++ b/Data/Scripts/006_Map renderer/New Tilemap/003_AutotileExpander.rb @@ -0,0 +1,55 @@ +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 e5f2a6794..4df5b34cf 100644 --- a/Data/Scripts/012_Overworld/008_Overworld_RandomDungeons.rb +++ b/Data/Scripts/012_Overworld/008_Overworld_RandomDungeons.rb @@ -1,667 +1,610 @@ #=============================================================================== -# Code that generates a random dungeon layout, and implements it in a given map. +# This class is designed to favor different values more than a uniform +# random generator does #=============================================================================== -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 - - 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 +class AntiRandom + def initialize(size) + @old = [] + @new = [] + @new = Array.new(size) { |i| i } end - #============================================================================= - # 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] + def get + if @new.length == 0 # No new values + @new = @old.clone + @old.clear end - - def to_text(value) - return TEXT_SYMBOLS[value] || TEXT_SYMBOLS[VOID] + if @old.length > 0 && rand(7) == 0 # Get old value + return @old[rand(@old.length)] 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 - 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 - - # 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 - - # 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 - end - - #============================================================================= - # 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 - - 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 - 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 - return if nx < 0 || ny < 0 || nx >= nodeWidth || ny >= nodeHeight - @nodes[ny * nodeWidth + nx].setEdge(e) - end - - def setAllEdges - for c in 0...nodeWidth * nodeHeight - @nodes[c].set - end - 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 = x - ny = y - case edge - when EdgeMasks::North - e = EdgeMasks::South - ny -= 1 - when EdgeMasks::South - e = EdgeMasks::North - ny += 1 - when EdgeMasks::East - e = EdgeMasks::West - nx += 1 - when EdgeMasks::West - 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 - 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 - - 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 - - #============================================================================= - # 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 - - def xsize; @dungeon.width; end - def ysize; @dungeon.height; end - - # 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 - - attr_accessor :width, :height - BUFFER_X = 8 - BUFFER_Y = 6 - - def initialize(width, height) - @width = width - @height = height - @array = [] - end - - 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 + 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 [](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 +#=============================================================================== +# +#=============================================================================== +module DungeonMaze + TILE_WIDTH = 13 + TILE_HEIGHT = 13 + MINWIDTH = 5 + MINHEIGHT = 4 + MAXWIDTH = 11 + MAXHEIGHT = 10 + None = 0 + TurnLeft = 1 + TurnRight = 2 + Turn180 = 3 - # 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 + 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 end - return false end + 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 + 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] 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 + 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] end 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 + 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 + 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 + 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)) + 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 + 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) 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 + setEdgeNode(ox,oy,dir) 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 + 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 = @@dirs.shuffle! + for c in 0...4 + d = dirs[c] + cx = 0 + cy = 0 + case d + when EdgeMasks::North + cx = x + cy = y - 1 + when EdgeMasks::South + cx = x + cy = y + 1 + when EdgeMasks::East + cx = x + 1 + cy = y + when EdgeMasks::West + cx = x - 1 + 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) 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 + 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 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 + 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 + end + ret += "\r\n" + end + return ret + end + + def [](x, y) + @array[y * @width + x] + 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 + 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 + 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 + end + 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 + end + return nil end Events.onMapCreate += proc { |_sender, e| mapID = e[0] map = e[1] - next if !GameData::MapMetadata.try_get(mapID)&.random_dungeon + next if !GameData::MapMetadata.exists?(mapID) || + !GameData::MapMetadata.get(mapID).random_dungeon # this map is a randomly generated dungeon - dungeon = RandomDungeonGenerator::Dungeon.new(map.width, map.height) + dungeon = Dungeon.new(map.width, map.height) dungeon.generate dungeon.generateMapInPlace(map) roomtiles = [] # Reposition events for event in map.events.values - tile = RandomDungeonGenerator.pbRandomRoomTile(dungeon, roomtiles) + tile = pbRandomRoomTile(dungeon, roomtiles) if tile event.x = tile[0] event.y = tile[1] end end # Override transfer X and Y - tile = RandomDungeonGenerator.pbRandomRoomTile(dungeon, roomtiles) + tile = pbRandomRoomTile(dungeon, roomtiles) if tile $game_temp.player_new_x = tile[0] $game_temp.player_new_y = tile[1]