#==============================================================================
# ** Game_Map
#------------------------------------------------------------------------------
#  This class handles the map. It includes scrolling and passable determining
#  functions. Refer to "$game_map" for the instance of this class.
#==============================================================================

class Game_Map
  #--------------------------------------------------------------------------
  # * Public Instance Variables
  #--------------------------------------------------------------------------
  attr_accessor :tileset_name             # tileset file name
  attr_accessor :autotile_names           # autotile file name
  attr_accessor :panorama_name            # panorama file name
  attr_accessor :panorama_hue             # panorama hue
  attr_accessor :fog_name                 # fog file name
  attr_accessor :fog_hue                  # fog hue
  attr_accessor :fog_opacity              # fog opacity level
  attr_accessor :fog_blend_type           # fog blending method
  attr_accessor :fog_zoom                 # fog zoom rate
  attr_accessor :fog_sx                   # fog sx
  attr_accessor :fog_sy                   # fog sy
  attr_accessor :battleback_name          # battleback file name
  attr_accessor :display_x                # display x-coordinate * 128
  attr_accessor :display_y                # display y-coordinate * 128
  attr_accessor :display_clip_bottom
  attr_accessor :display_clip_left
  attr_accessor :display_clip_right
  attr_accessor :display_clip_top
  attr_accessor :display_x_fix
  attr_accessor :display_y_fix
  attr_accessor :wrap_x                   # display x-coordinate * 128 for wrap
  attr_accessor :wrap_y                   # display y-coordinate * 128 for wrap
  attr_accessor :need_refresh             # refresh request flag
  attr_accessor :bg_name                  # bg file name
  attr_accessor :particles_type           # particles name
  attr_accessor :clamped_x                # panorama is horizontally clamped?
  attr_accessor :clamped_y                # panorama is vertically clamped?
  attr_accessor :always_moving            # panorama is always moving
  attr_accessor :pan_move_offset          # panorama moving offset
  attr_accessor :pan_onetoone             # panorama is 1:1
  attr_accessor :pan_animate              # panorama is animated
  attr_accessor :pan_fade_animate         # panorama is fade animated
  attr_accessor :pan_zoom                 # panorama zoom
  attr_accessor :wrapping                 # map is wrapping?
  attr_accessor :pan_offset_y             # offset of y-coord of panorama
  attr_accessor :ambient                  # ambient light
  attr_reader   :passages                 # passage table
  attr_reader   :priorities               # prioroty table
  attr_reader   :terrain_tags             # terrain tag table
  attr_reader   :events                   # events
  attr_reader   :fog_ox                   # fog x-coordinate starting point
  attr_reader   :fog_oy                   # fog y-coordinate starting point
  attr_reader   :fog_tone                 # fog color tone
  #--------------------------------------------------------------------------
  # * List of clamped panorama images
  #--------------------------------------------------------------------------
  CLAMPED = [
    'red_distort',
    'codebg'
  ]
  CLAMPED_X = [
    ]
  CLAMPED_Y = [
    'red_obsdesk',
    ]
  ANIMATED = [
    'blue_water',
    ]
  FADE_ANIMATION_PANORAMA = []
  ONETOONE = [
    'blue_water',
    'green_water',
    'dark_water',
    ]
  NOZOOM = [
    'dark_water',
    'green_water',
    ]

  ALWAYS_MOVING = [
    'codebg'
  ]
  #--------------------------------------------------------------------------
  # * Object Initialization
  #--------------------------------------------------------------------------
  def initialize
    @map_id = 0
    @display_x = 0
    @display_y = 0
    @display_clip_bottom = 999999
    @display_clip_left = 0
    @display_clip_right = 999999
    @display_clip_top = 0
    @display_x_fix = nil
    @display_y_fix = nil
  end
  #--------------------------------------------------------------------------
  # * Setup
  #     map_id : map ID
  #--------------------------------------------------------------------------
  def setup(map_id)
    # Put map ID in @map_id memory
    @map_id = map_id
    # Load map from file and set @map
    @map = load_data(sprintf("Data/Map%03d.rxdata", @map_id))
    # set tile set information in opening instance variables
    tileset = $data_tilesets[@map.tileset_id]
    if @map.tileset_id > 0
      $game_temp.footstep_sfx = FOOTSTEP_SFX[@map.tileset_id - 1]
    else
      $game_temp.footstep_sfx = nil
    end
    @tileset_name = tileset.tileset_name
    @autotile_names = tileset.autotile_names
    @panorama_name = tileset.panorama_name
    @panorama_hue = tileset.panorama_hue
    @fog_name = tileset.fog_name
    @fog_hue = tileset.fog_hue
    @fog_opacity = tileset.fog_opacity
    @fog_blend_type = tileset.fog_blend_type
    @fog_zoom = tileset.fog_zoom
    @fog_sx = tileset.fog_sx
    @fog_sy = tileset.fog_sy
    @battleback_name = tileset.battleback_name
    @passages = tileset.passages
    @priorities = tileset.priorities
    @terrain_tags = tileset.terrain_tags
    # Initialize displayed coordinates
    @display_x = 0
    @display_y = 0
    @display_clip_bottom = 999999
    @display_clip_left = 0
    @display_clip_right = 999999
    @display_clip_top = 0
    @display_x_fix = nil
    @display_y_fix = nil
    @wrap_x = 0
    @wrap_y = 0
    # Clear refresh request flag
    @need_refresh = false
    # Set map event data
    @events = {}
    for i in @map.events.keys
      @events[i] = Game_Event.new(@map_id, @map.events[i])
    end
    # Set common event data
    @common_events = {}
    for i in 1...$data_common_events.size
      @common_events[i] = Game_CommonEvent.new(i)
    end
    # Initialize all fog information
    @fog_ox = 0
    @fog_oy = 0
    @fog_tone = Tone.new(0, 0, 0, 0)
    @fog_tone_target = Tone.new(0, 0, 0, 0)
    @fog_tone_duration = 0
    @fog_opacity_duration = 0
    @fog_opacity_target = 0
    # Initialize scroll information
    @scroll_direction = 2
    @scroll_rest = 0
    @scroll_speed = 4
    # Clear BG
    @bg_name = ""
    # Clear particles
    @particles_type = nil
    # Unclamp panorama
    if CLAMPED.include? @panorama_name
      @clamped_x = true
      @clamped_y = true
    elsif CLAMPED_X.include? @panorama_name
      @clamped_x = true
      @clamped_y = false
    elsif CLAMPED_Y.include? @panorama_name
      @clamped_x = false
      @clamped_y = true
    else
      @clamped_x = false
      @clamped_y = false
    end

    @always_moving = ALWAYS_MOVING.include? @panorama_name
    @pan_move_offset = 0

    # Animated/One-to-one/zoom
    @pan_animate = ANIMATED.include? @panorama_name
    @pan_fade_animate = FADE_ANIMATION_PANORAMA.include? @panorama_name
    @pan_onetoone = ONETOONE.include? @panorama_name
    @pan_zoom = NOZOOM.include?(@panorama_name) ? 1 : 2
    # Unwrap map
    @wrapping = false
    @pan_offset_y = 0
    # Full bright ambient light
    @ambient = Tone.new(0, 0, 0, 0)

    # Construct map name path
    mapinfo = load_data("Data/MapInfos.rxdata")
    @map_name = ""
    i = @map_id
    begin
      @map_name.insert(0, "/" + mapinfo[i].name)
      i = mapinfo[i].parent_id
    end while i > 0
  end
  #--------------------------------------------------------------------------
  # * Get Map ID
  #--------------------------------------------------------------------------
  def map_id
    return @map_id
  end
  #--------------------------------------------------------------------------
  # * Get Map Name
  #--------------------------------------------------------------------------
  def map_name
    return @map_name
  end
  #--------------------------------------------------------------------------
  # * Get Width
  #--------------------------------------------------------------------------
  def width
    return @map.width
  end
  #--------------------------------------------------------------------------
  # * Get Height
  #--------------------------------------------------------------------------
  def height
    return @map.height
  end
  #--------------------------------------------------------------------------
  # * Get Encounter List
  #--------------------------------------------------------------------------
  def encounter_list
    return @map.encounter_list
  end
  #--------------------------------------------------------------------------
  # * Get Encounter Steps
  #--------------------------------------------------------------------------
  def encounter_step
    return @map.encounter_step
  end
  #--------------------------------------------------------------------------
  # * Get Map Data
  #--------------------------------------------------------------------------
  def data
    return @map.data
  end
  #--------------------------------------------------------------------------
  # * Automatically Change Background Music and Backround Sound
  #--------------------------------------------------------------------------
  def autoplay
    if @map.autoplay_bgm
      $game_system.bgm_play(@map.bgm)
    end
    if @map.autoplay_bgs
      $game_system.bgs_play(@map.bgs)
    end
  end
  #--------------------------------------------------------------------------
  # * Refresh
  #--------------------------------------------------------------------------
  def refresh
    # If map ID is effective
    if @map_id > 0
      # Refresh all map events
      for event in @events.values
        event.refresh
      end
      # Refresh all common events
      for common_event in @common_events.values
        common_event.refresh
      end
    end
    # Clear refresh request flag
    @need_refresh = false
  end

  #--------------------------------------------------------------------------
  # * Scroll Down
  #     distance : scroll distance
  #--------------------------------------------------------------------------
  def scroll_down(distance)
    return if @display_y_fix

    @display_y = [@display_y + distance, (self.height - Graphics.height / 32.0).round * 128].min
    @display_y = @display_y.clamp((@display_clip_top * 128.0).to_i, (@display_clip_bottom * 128.0).to_i - Graphics.height * 4)
  end
  #--------------------------------------------------------------------------
  # * Scroll Left
  #     distance : scroll distance
  #--------------------------------------------------------------------------
  def scroll_left(distance)
    return if @display_x_fix
    
    @display_x = [@display_x - distance, 0].max
    @display_x = @display_x.clamp((@display_clip_left * 128.0).to_i, (@display_clip_right * 128.0).to_i - Graphics.width * 4)
  end
  #--------------------------------------------------------------------------
  # * Scroll Right
  #     distance : scroll distance
  #--------------------------------------------------------------------------
  def scroll_right(distance)
    return if @display_x_fix
    
  	@display_x = [@display_x + distance, (self.width - (Graphics.width / 32.0).round) * 128].min
    @display_x = @display_x.clamp((@display_clip_left * 128.0).to_i, (@display_clip_right * 128.0).to_i - Graphics.width * 4)
  end
  #--------------------------------------------------------------------------
  # * Scroll Up
  #     distance : scroll distance
  #--------------------------------------------------------------------------
  def scroll_up(distance)
    return if @display_y_fix

   	@display_y = [@display_y - distance, 0].max
    @display_y = @display_y.clamp((@display_clip_top * 128.0).to_i, (@display_clip_bottom * 128.0).to_i - Graphics.height * 4)
  end
  #--------------------------------------------------------------------------
  # * Determine Valid Coordinates
  #     x          : x-coordinate
  #     y          : y-coordinate
  #--------------------------------------------------------------------------
  def valid?(x, y)
    return true if @wrapping
    return (x >= 0 and x < width and y >= 0 and y < height)
  end
  #--------------------------------------------------------------------------
  # * Determine if Passable
  #     x          : x-coordinate
  #     y          : y-coordinate
  #     d          : direction (0,2,4,6,8,10)
  #                  *  0,10 = determine if all directions are impassable
  #     self_event : Self (If event is determined passable)
  #--------------------------------------------------------------------------
  def passable?(x, y, d, self_event = nil)
    # If coordinates given are outside of the map
    unless valid?(x, y)
      # impassable
      return false
    end
    x %= self.width
    y %= self.height
    # Change direction (0,2,4,6,8,10) to obstacle bit (0,1,2,4,8,0)
    bit = (1 << (d / 2 - 1)) & 0x0f
    # Loop in all events
    for event in events.values
      # If tiles other than self are consistent with coordinates
      if event.tile_id >= 0 and event != self_event and
         event.x == x and event.y == y and not event.through
        # If blank tile
        if event.tile_id == 0 && event.character_name.empty?
          return false
          # If obstacle bit is set
        elsif @passages[event.tile_id] & bit != 0
          # impassable
          return false
          # If obstacle bit is set in all directions
        elsif @passages[event.tile_id] & 0x0f == 0x0f
          # impassable
          return false
          # If priorities other than that are 0
        elsif @priorities[event.tile_id] == 0
          # passable
          return true
        end
      end
    end
    # Loop searches in order from top of layer
    blank = 0
    [2, 1, 0].each do |i|
      # Get tile ID
      tile_id = data[x, y, i]
      # Tile ID acquistion failure
      next if tile_id == nil

      # Only handle blank if all three layers are blank
      if tile_id < 48 && i > 0
        blank += 1
        next if blank < 3
      end

      # If obstacle bit is set
      if @passages[tile_id] & bit != 0
        # impassable
        return false
        # If obstacle bit is set in all directions
      elsif @passages[tile_id] & 0x0f == 0x0f
        # impassable
        return false
        # If priorities other than that are 0
      elsif @priorities[tile_id] == 0
        # passable
        return true
      end
    end
    # passable
    return true
  end
  #--------------------------------------------------------------------------
  # * Determine Thicket
  #     x          : x-coordinate
  #     y          : y-coordinate
  #--------------------------------------------------------------------------
  def bush?(x, y)
    if @map_id != 0
      for i in [2, 1, 0]
        tile_id = data[x, y, i]
        if tile_id == nil
          return false
        elsif @passages[tile_id] & 0x40 == 0x40
          return true
        end
      end
    end
    return false
  end
  #--------------------------------------------------------------------------
  # * Determine Counter
  #     x          : x-coordinate
  #     y          : y-coordinate
  #--------------------------------------------------------------------------
  def counter?(x, y)
    if @map_id != 0
      for i in [2, 1, 0]
        tile_id = data[x, y, i]
        if tile_id == nil
          return false
        elsif @passages[tile_id] & 0x80 == 0x80
          return true
        end
      end
    end
    return false
  end
  #--------------------------------------------------------------------------
  # * Get Terrain Tag
  #     x          : x-coordinate
  #     y          : y-coordinate
  #--------------------------------------------------------------------------
  def terrain_tag(x, y)
    if @map_id != 0
      for i in [2, 1, 0]
        tile_id = data[x, y, i]
        if tile_id == nil
          next #return 0
        elsif @terrain_tags[tile_id] && @terrain_tags[tile_id] > 0
          return @terrain_tags[tile_id]
        end
      end
    end
    return 0
  end
  #--------------------------------------------------------------------------
  # * Get Designated Position Event ID
  #     x          : x-coordinate
  #     y          : y-coordinate
  #--------------------------------------------------------------------------
  def check_event(x, y)
    for event in $game_map.events.values
      if event.x == x and event.y == y
        return event.id
      end
    end
  end
  #--------------------------------------------------------------------------
  # * Start Scroll
  #     direction : scroll direction
  #     distance  : scroll distance
  #     speed     : scroll speed
  #--------------------------------------------------------------------------
  def start_scroll(direction, distance, speed)
    @scroll_direction = direction
    @scroll_rest = distance * 128
    @scroll_speed = speed
  end
  #--------------------------------------------------------------------------
  # * Determine if Scrolling
  #--------------------------------------------------------------------------
  def scrolling?
    return @scroll_rest > 0
  end
  #--------------------------------------------------------------------------
  # * Start Changing Fog Color Tone
  #     tone     : color tone
  #     duration : time
  #--------------------------------------------------------------------------
  def start_fog_tone_change(tone, duration)
    @fog_tone_target = tone.clone
    @fog_tone_duration = duration
    if @fog_tone_duration == 0
      @fog_tone = @fog_tone_target.clone
    end
  end
  #--------------------------------------------------------------------------
  # * Start Changing Fog Opacity Level
  #     opacity  : opacity level
  #     duration : time
  #--------------------------------------------------------------------------
  def start_fog_opacity_change(opacity, duration)
    @fog_opacity_target = opacity * 1.0
    @fog_opacity_duration = duration
    if @fog_opacity_duration == 0
      @fog_opacity = @fog_opacity_target
    end
  end
  #--------------------------------------------------------------------------
  # * Frame Update
  #--------------------------------------------------------------------------
  def update
    @display_clip_top = @display_clip_top || 0
    @display_clip_left = @display_clip_left || 0
    @display_clip_right = @display_clip_right || 999999
    @display_clip_bottom = @display_clip_bottom || 999999

    # Refresh map if necessary
    if $game_map.need_refresh
      refresh
    end
    # If scrolling
    if @scroll_rest > 0
      # Change from scroll speed to distance in map coordinates
      distance = 2 ** @scroll_speed
      # Execute scrolling
      case @scroll_direction
      when 2  # Down
        scroll_down(distance)
      when 4  # Left
        scroll_left(distance)
      when 6  # Right
        scroll_right(distance)
      when 8  # Up
        scroll_up(distance)
      end
      # Subtract distance scrolled
      @scroll_rest -= distance
    end
    # Update map event
    for event in @events.values
      event.update
    end
    # Update common event
    for common_event in @common_events.values
      common_event.update
    end
    # Manage fog scrolling
    @fog_ox -= @fog_sx / 8.0
    @fog_oy -= @fog_sy / 8.0
    # Manage change in fog color tone
    if @fog_tone_duration >= 1
      d = @fog_tone_duration
      target = @fog_tone_target
      @fog_tone.red = (@fog_tone.red * (d - 1) + target.red) / d
      @fog_tone.green = (@fog_tone.green * (d - 1) + target.green) / d
      @fog_tone.blue = (@fog_tone.blue * (d - 1) + target.blue) / d
      @fog_tone.gray = (@fog_tone.gray * (d - 1) + target.gray) / d
      @fog_tone_duration -= 1
    end
    # Manage change in fog opacity level
    if @fog_opacity_duration >= 1
      d = @fog_opacity_duration
      @fog_opacity = (@fog_opacity * (d - 1) + @fog_opacity_target) / d
      @fog_opacity_duration -= 1
    end
    
    if @display_x_fix
      @display_x = (@display_x_fix * 128.0).to_i - Graphics.width * 2
    else
      @display_x = @display_x.clamp((@display_clip_left * 128.0).to_i, (@display_clip_right * 128.0).to_i - Graphics.width * 4)
    end
    if @display_y_fix
      @display_y = (@display_y_fix * 128.0).to_i - Graphics.height * 2
    else
      @display_y = @display_y.clamp((@display_clip_top * 128.0).to_i, (@display_clip_bottom * 128.0).to_i - Graphics.height * 4)
    end
  end

  def set_tile(x, y, z, tile)
    if z < 0 || z > 2 || !valid?(x, y)
      return
    end
    @map.data[x, y, z] = tile
  end
  def get_tile(x, y, z)
    if z < 0 || z > 2 || !valid?(x, y)
      return 0
    end
    @map.data[x, y, z]
  end
  def resize(x, y, z)
    @map.data.resize(x, y, z)
    @map.width = x
    @map.height = y
  end
end
