From 52fbf45ba03356a024740d5f4a004786ff5e1254 Mon Sep 17 00:00:00 2001 From: Maruno17 Date: Wed, 29 Sep 2021 18:18:50 +0100 Subject: [PATCH] Added new map renderer --- .../003_Game processing/002_Scene_Map.rb | 10 +- .../004_Game classes/006_Game_MapFactory.rb | 2 +- .../004_Game classes/007_Game_Character.rb | 4 +- Data/Scripts/005_Sprites/007_Spriteset_Map.rb | 41 +- .../005_Sprites/008_Sprite_AnimationSprite.rb | 4 +- .../New Tilemap/001_NewTilemap.rb | 699 ++++++++++++++++++ .../New Tilemap/002_TileWrap.rb | 100 +++ .../New Tilemap/003_AutotileExpander.rb | 55 ++ 8 files changed, 890 insertions(+), 25 deletions(-) create mode 100644 Data/Scripts/006_Map renderer/New Tilemap/001_NewTilemap.rb create mode 100644 Data/Scripts/006_Map renderer/New Tilemap/002_TileWrap.rb create mode 100644 Data/Scripts/006_Map renderer/New Tilemap/003_AutotileExpander.rb diff --git a/Data/Scripts/003_Game processing/002_Scene_Map.rb b/Data/Scripts/003_Game processing/002_Scene_Map.rb index 25fdcdf34..d61b76d3a 100644 --- a/Data/Scripts/003_Game processing/002_Scene_Map.rb +++ b/Data/Scripts/003_Game processing/002_Scene_Map.rb @@ -5,6 +5,7 @@ #=============================================================================== class Scene_Map attr_reader :spritesetGlobal + attr_reader :map_renderer def spriteset for i in @spritesets.values @@ -14,6 +15,7 @@ class Scene_Map end def createSpritesets + @map_renderer = TilemapRenderer.new(Spriteset_Map.viewport) @spritesetGlobal = Spriteset_Global.new @spritesets = {} for map in $MapFactory.maps @@ -42,6 +44,8 @@ class Scene_Map @spritesets = {} @spritesetGlobal.dispose @spritesetGlobal = nil + @map_renderer.dispose + @map_renderer = nil end def autofade(mapid) @@ -126,8 +130,8 @@ class Scene_Map def miniupdate $PokemonTemp.miniupdate = true loop do - updateMaps $game_player.update + updateMaps $game_system.update $game_screen.update break unless $game_temp.player_transferring @@ -161,14 +165,16 @@ class Scene_Map for map in $MapFactory.maps @spritesets[map.map_id] = Spriteset_Map.new(map) if !@spritesets[map.map_id] end + pbDayNightTint(@map_renderer) + @map_renderer.update Events.onMapUpdate.trigger(self) end def update loop do - updateMaps pbMapInterpreter.update $game_player.update + updateMaps $game_system.update $game_screen.update break unless $game_temp.player_transferring diff --git a/Data/Scripts/004_Game classes/006_Game_MapFactory.rb b/Data/Scripts/004_Game classes/006_Game_MapFactory.rb index 9c4dcf6ce..b0ba2b677 100644 --- a/Data/Scripts/004_Game classes/006_Game_MapFactory.rb +++ b/Data/Scripts/004_Game classes/006_Game_MapFactory.rb @@ -254,7 +254,7 @@ class PokemonMapFactory posX = thisX + conn[1] - conn[4] + otherX posY = thisY + conn[2] - conn[5] + otherY return [posX, posY] - elsif conn[1] == otherMapID + elsif conn[3] == otherMapID posX = thisX + conn[4] - conn[1] + otherX posY = thisY + conn[5] - conn[2] + otherY return [posX, posY] diff --git a/Data/Scripts/004_Game classes/007_Game_Character.rb b/Data/Scripts/004_Game classes/007_Game_Character.rb index 2fc2ddda0..c79551bae 100644 --- a/Data/Scripts/004_Game classes/007_Game_Character.rb +++ b/Data/Scripts/004_Game classes/007_Game_Character.rb @@ -301,13 +301,13 @@ class Game_Character # Screen position of the character #============================================================================= def screen_x - ret = ((@real_x - self.map.display_x) / Game_Map::X_SUBPIXELS).round + ret = ((@real_x.to_f - self.map.display_x) / Game_Map::X_SUBPIXELS).round ret += @width * Game_Map::TILE_WIDTH / 2 return ret end def screen_y_ground - ret = ((@real_y - self.map.display_y) / Game_Map::Y_SUBPIXELS).round + ret = ((@real_y.to_f - self.map.display_y) / Game_Map::Y_SUBPIXELS).round ret += Game_Map::TILE_HEIGHT return ret end diff --git a/Data/Scripts/005_Sprites/007_Spriteset_Map.rb b/Data/Scripts/005_Sprites/007_Spriteset_Map.rb index ac0f1490e..37e2f4609 100644 --- a/Data/Scripts/005_Sprites/007_Spriteset_Map.rb +++ b/Data/Scripts/005_Sprites/007_Spriteset_Map.rb @@ -1,3 +1,4 @@ +# Unused class ClippableSprite < Sprite_Character def initialize(viewport,event,tilemap) @tilemap = tilemap @@ -46,15 +47,17 @@ class Spriteset_Map def initialize(map=nil) @map = (map) ? map : $game_map - @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 + $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 @@ -69,18 +72,20 @@ class Spriteset_Map end def dispose - @tilemap.tileset.dispose - for i in 0...7 - @tilemap.autotiles[i].dispose - end - @tilemap.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 @fog.dispose for sprite in @character_sprites sprite.dispose end @weather.dispose - @tilemap = nil +# @tilemap = nil @panorama = nil @fog = nil @character_sprites.clear @@ -112,13 +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 +# @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 +# @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 a39f0c485..dfdccf7fb 100644 --- a/Data/Scripts/005_Sprites/008_Sprite_AnimationSprite.rb +++ b/Data/Scripts/005_Sprites/008_Sprite_AnimationSprite.rb @@ -75,8 +75,8 @@ class Spriteset_Map end def update - return if @tilemap.disposed? - pbDayNightTint(@tilemap) +# 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/New Tilemap/001_NewTilemap.rb b/Data/Scripts/006_Map renderer/New Tilemap/001_NewTilemap.rb new file mode 100644 index 000000000..492ab4511 --- /dev/null +++ b/Data/Scripts/006_Map renderer/New Tilemap/001_NewTilemap.rb @@ -0,0 +1,699 @@ +# 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 + + 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] + + #============================================================================= + # + #============================================================================= + class TilesetBitmaps + attr_accessor :changed + attr_accessor :bitmaps + + def initialize + @bitmaps = {} + @bitmap_wraps = {} # Whether each tileset is a mega texture and has multiple columns + @load_counts = {} + @changed = true + end + + def [](filename) + return @bitmaps[filename] + end + + def []=(filename, bitmap) + return if nil_or_empty?(filename) + @bitmaps[filename] = bitmap + @bitmap_wraps[filename] = false + @changed = true + end + + def add(filename) + return if nil_or_empty?(filename) + if @bitmaps[filename] + @load_counts[filename] += 1 + return + end + bitmap = pbGetTileset(filename) + @bitmap_wraps[filename] = false + if bitmap.mega? + self[filename] = TileWrap::wrapTileset(bitmap) + @bitmap_wraps[filename] = true + bitmap.dispose + else + self[filename] = bitmap + end + @load_counts[filename] = 1 + end + + def remove(filename) + return if nil_or_empty?(filename) || !@bitmaps[filename] + if @load_counts[filename] > 1 + @load_counts[filename] -= 1 + return + end + @bitmaps[filename].dispose + @bitmaps.delete(filename) + @bitmap_wraps.delete(filename) + @load_counts.delete(filename) + end + + def set_src_rect(tile, tile_id) + return if nil_or_empty?(tile.filename) + return if !@bitmaps[tile.filename] + tile.src_rect.x = ((tile_id - TILESET_START_ID) % TILESET_TILES_PER_ROW) * SOURCE_TILE_WIDTH + tile.src_rect.y = ((tile_id - TILESET_START_ID) / TILESET_TILES_PER_ROW) * SOURCE_TILE_HEIGHT + if @bitmap_wraps[tile.filename] + height = @bitmaps[tile.filename].height + col = (tile_id - TILESET_START_ID) * SOURCE_TILE_HEIGHT / height + tile.src_rect.x += col * AUTOTILES_COUNT * SOURCE_TILE_WIDTH + tile.src_rect.y -= col * height + end + end + + def update; end + end + + #============================================================================= + # + #============================================================================= + class AutotileBitmaps < TilesetBitmaps + attr_reader :current_frames + + def initialize + super + @frame_counts = {} # Number of frames in each autotile + @frame_durations = {} # How long each frame lasts per autotile + @current_frames = {} # Which frame each autotile is currently showing + @timer = 0.0 + end + + def []=(filename, value) + super + return if nil_or_empty?(filename) + frame_count(filename, true) + set_current_frame(filename) + end + + def add(filename) + return if nil_or_empty?(filename) + if @bitmaps[filename] + @load_counts[filename] += 1 + return + end + orig_bitmap = pbGetAutotile(filename) + @bitmap_wraps[filename] = false + duration = AUTOTILE_FRAME_DURATION + if filename[/\[\s*(\d+?)\s*\]\s*$/] + duration = $~[1].to_i + end + @frame_durations[filename] = duration.to_f / 20 + bitmap = AutotileExpander.expand(orig_bitmap) + self[filename] = bitmap + if bitmap.height > SOURCE_TILE_HEIGHT && bitmap.height < TILES_PER_AUTOTILE * SOURCE_TILE_HEIGHT + @bitmap_wraps[filename] = true + end + orig_bitmap.dispose if orig_bitmap != bitmap + @load_counts[filename] = 1 + end + + def remove(filename) + super + return if @load_counts[filename] && @load_counts[filename] > 0 + @frame_counts.delete(filename) + @current_frames.delete(filename) + @frame_durations.delete(filename) + end + + def frame_count(filename, force_recalc = false) + if !@frame_counts[filename] || force_recalc + return 0 if !@bitmaps[filename] + bitmap = @bitmaps[filename] + @frame_counts[filename] = [bitmap.width / SOURCE_TILE_WIDTH, 1].max + if bitmap.height > SOURCE_TILE_HEIGHT && @bitmap_wraps[filename] + @frame_counts[filename] /= 2 if @bitmap_wraps[filename] + end + end + return @frame_counts[filename] + end + + def animated?(filename) + return frame_count(filename) > 1 + end + + def current_frame(filename) + if !@current_frames[filename] + set_current_frame(filename) + end + return @current_frames[filename] + end + + def set_current_frame(filename) + frames = frame_count(filename) + if frames < 2 + @current_frames[filename] = 0 + else + @current_frames[filename] = (@timer / @frame_durations[filename]).floor % frames + end + end + + def set_src_rect(tile, tile_id) + return if nil_or_empty?(tile.filename) + return if !@bitmaps[tile.filename] + return if tile_id < TILES_PER_AUTOTILE # Blank tile + frame = current_frame(tile.filename) + if @bitmaps[tile.filename].height == SOURCE_TILE_HEIGHT + tile.src_rect.x = frame * SOURCE_TILE_WIDTH + tile.src_rect.y = 0 + return + end + wraps = @bitmap_wraps[tile.filename] + high_id = ((tile_id % TILES_PER_AUTOTILE) >= TILES_PER_AUTOTILE / 2) + tile.src_rect.x = 0 + tile.src_rect.y = (tile_id % TILES_PER_AUTOTILE) * SOURCE_TILE_HEIGHT + if wraps && high_id + tile.src_rect.x = SOURCE_TILE_WIDTH + tile.src_rect.y -= SOURCE_TILE_HEIGHT * TILES_PER_AUTOTILE / 2 + end + tile.src_rect.x += frame * SOURCE_TILE_WIDTH * (wraps ? 2 : 1) + end + + def update + super + @timer += Graphics.delta_s + # Update the current frame for each autotile + @bitmaps.keys.each do |filename| + next if !@bitmaps[filename] || @bitmaps[filename].disposed? + old_frame = @current_frames[filename] + set_current_frame(filename) + @changed = true if @current_frames[filename] != old_frame + end + end + end + + #============================================================================= + # + #============================================================================= + class TileSprite < Sprite + attr_accessor :filename + attr_accessor :is_autotile + attr_accessor :animated + attr_accessor :priority + attr_accessor :shows_reflection + attr_accessor :bridge + attr_accessor :need_refresh + + def set_bitmap(filename, autotile, animated, priority, bitmap) + self.bitmap = bitmap + self.src_rect = Rect.new(0, 0, DISPLAY_TILE_WIDTH, DISPLAY_TILE_HEIGHT) + @filename = filename + @is_autotile = autotile + @animated = animated + @priority = priority + @shows_reflection = false + @bridge = false + self.visible = !bitmap.nil? + @need_refresh = true + end + end + + #============================================================================= + # + #============================================================================= + 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 = [] + @tiles_horizontal_count.times do |i| + @tiles[i] = [] + @tiles_vertical_count.times do |j| + @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 + end + + def dispose + return if disposed? + @tiles.each do |col| + col.each do |coord| + 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 + @self_viewport = nil + @disposed = true + end + + def disposed? + return @disposed + end + + #============================================================================= + + def add_tileset(filename) + @tilesets.add(filename) + end + + def remove_tileset(filename) + @tilesets.remove(filename) + end + + def add_autotile(filename) + @autotiles.add(filename) + end + + def remove_autotile(filename) + @autotiles.remove(filename) + 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 + tile.set_bitmap("", false, false, 0, nil) + tile.shows_reflection = false + tile.bridge = false + else + terrain_tag = map.terrain_tags[tile_id] || 0 + terrain_tag_data = GameData::TerrainTag.try_get(terrain_tag) + priority = map.priorities[tile_id] || 0 + if tile_id < TILESET_START_ID + filename = map.autotile_names[tile_id / TILES_PER_AUTOTILE - 1] + tile.set_bitmap(filename, true, @autotiles.animated?(filename), + priority, @autotiles[filename]) + else + filename = map.tileset_name + tile.set_bitmap(filename, false, false, priority, @tilesets[filename]) + end + tile.shows_reflection = terrain_tag_data&.shows_reflections + tile.bridge = terrain_tag_data&.bridge + end + refresh_tile_src_rect(tile, tile_id) + end + + def refresh_tile_src_rect(tile, tile_id) + if tile_id < TILESET_START_ID + @autotiles.set_src_rect(tile, tile_id) + else + @tilesets.set_src_rect(tile, tile_id) + end + end + + # For animated autotiles only + def refresh_tile_frame(tile, tile_id) + return if !tile.animated + @autotiles.set_src_rect(tile, tile_id) + end + + # x and y are the positions of tile within @tiles, not a map x/y + def refresh_tile_coordinates(tile, x, y) + tile.x = x * DISPLAY_TILE_WIDTH - @pixel_offset_x + tile.y = y * DISPLAY_TILE_HEIGHT - @pixel_offset_y + end + + def refresh_tile_z(tile, map, y, layer, tile_id) + if tile.shows_reflection + tile.z = -100 + elsif tile.bridge && $PokemonGlobal.bridge > 0 + tile.z = 0 + else + priority = tile.priority + tile.z = (priority == 0) ? 0 : y * DISPLAY_TILE_HEIGHT + priority * 32 + 32 + end + end + + def refresh_tile(tile, x, y, map, layer, tile_id) + refresh_tile_bitmap(tile, map, tile_id) + refresh_tile_coordinates(tile, x, y) + refresh_tile_z(tile, map, y, layer, tile_id) + tile.need_refresh = false + end + + #============================================================================= + + def check_if_screen_moved + ret = false + # Check for map change + if @current_map_id != $game_map.map_id + if MapFactoryHelper.hasConnections?(@current_map_id) + offsets = $MapFactory.getRelativePos(@current_map_id, 0, 0, $game_map.map_id, 0, 0) + if offsets + @tile_offset_x += offsets[0] + @tile_offset_y += offsets[1] + else + ret = true # Need a full refresh + end + end + @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 + new_tile_offset_x = current_map_display_x / DISPLAY_TILE_WIDTH + new_tile_offset_y = current_map_display_y / DISPLAY_TILE_HEIGHT + if new_tile_offset_x != @tile_offset_x + if new_tile_offset_x > @tile_offset_x + # Take tile stacks off the right and insert them at the beginning (left) + (new_tile_offset_x - @tile_offset_x).times do + c = @tiles.shift + @tiles.push(c) + c.each do |coord| + coord.each { |tile| tile.need_refresh = true } + end + end + else + # Take tile stacks off the beginning (left) and push them onto the end (right) + (@tile_offset_x - new_tile_offset_x).times do + c = @tiles.pop + @tiles.prepend(c) + c.each do |coord| + coord.each { |tile| tile.need_refresh = true } + end + end + end + @screen_moved = true + @tile_offset_x = new_tile_offset_x + end + if new_tile_offset_y != @tile_offset_y + if new_tile_offset_y > @tile_offset_y + # Take tile stacks off the bottom and insert them at the beginning (top) + @tiles.each do |col| + (new_tile_offset_y - @tile_offset_y).times do + c = col.shift + col.push(c) + c.each { |tile| tile.need_refresh = true } + end + end + else + # Take tile stacks off the beginning (top) and push them onto the end (bottom) + @tiles.each do |col| + (@tile_offset_y - new_tile_offset_y).times do + c = col.pop + col.prepend(c) + c.each { |tile| tile.need_refresh = true } + end + end + end + @screen_moved = true + @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 + if new_pixel_offset_x != @pixel_offset_x + @screen_moved = true + @pixel_offset_x = new_pixel_offset_x + end + if new_pixel_offset_y != @pixel_offset_y + @screen_moved = true + @screen_moved_vertically = true + @pixel_offset_y = new_pixel_offset_y + end + return ret + end + + #============================================================================= + + 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 } + end + end + @old_tone = @tone.clone + 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 } + end + end + @old_color = @color.clone + end + # 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 + map_display_y = (map.display_y.to_f / Game_Map::Y_SUBPIXELS).round + map_display_x_tile = map_display_x / DISPLAY_TILE_WIDTH + map_display_y_tile = map_display_y / DISPLAY_TILE_HEIGHT + start_x = [-map_display_x_tile, 0].max + start_y = [-map_display_y_tile, 0].max + end_x = @tiles_horizontal_count - 1 + end_x = [end_x, map.width - map_display_x_tile - 1].min + 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 + for j in start_y..end_y + tile_y = j + map_display_y_tile + @tiles[i][j].each_with_index do |tile, layer| + tile_id = map.data[tile_x, tile_y, layer] + if do_full_refresh || tile.need_refresh + refresh_tile(tile, i, j, map, layer, tile_id) + else + refresh_tile_frame(tile, tile_id) if tile.animated && @autotiles.changed + # Update tile's x/y coordinates + refresh_tile_coordinates(tile, i, j) if @screen_moved + # Update tile's z value + refresh_tile_z(tile, map, j, layer, tile_id) if @screen_moved_vertically + end + end + # Record x/y as visited + visited[i][j] = true + end + end + end + + # Clear all unvisited tile sprites + @tiles.each_with_index do |col, i| + col.each_with_index do |coord, j| + next if visited[i][j] + coord.each do |tile| + tile.set_bitmap("", false, false, 0, nil) + tile.shows_reflection = false + tile.bridge = false + 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