#==============================================================================
# ■ RMVA A+自动寻路 系统
#------------------------------------------------------------------------------
#  A*自動尋路 部分 By 神隱不器
#     其他腳本 部分 By 釣到一隻猴子@_@
#==============================================================================
 
#==============================================================================
# ■ PathFinding
#------------------------------------------------------------------------------
#  A*自動尋路 二叉堆版。
#     By 神隱不器
#==============================================================================
 
module PathFinding
  #--------------------------------------------------------------------------
  # ● 参数
  #--------------------------------------------------------------------------
  Directions = 8  # 寻路方向(4或8)
  PathType = 1    # 返回值的类型(0:节点坐标, 1:移动方向)
  #--------------------------------------------------------------------------
  Node = Struct.new(:x, :y, :d, :parent, :h, :g, :f)
  #--------------------------------------------------------------------------
  # ● 搜索路径
  #--------------------------------------------------------------------------
  def self.find_short_path(self_x, self_y, trg_x, trg_y)
    return find_path(self_x, self_y, trg_x, trg_y)
  end
  #--------------------------------------------------------------------------
  # ● 寻找角色的最短路径
  #--------------------------------------------------------------------------
  def self.find_player_short_path(trg_x, trg_y)
    if Input.mouse_icon != 0
      @possible_x = trg_x
      @possible_y = trg_y
    end
    self_x = $game_player.x
    self_y = $game_player.y
    return find_path(self_x, self_y, trg_x, trg_y)
  end
  #--------------------------------------------------------------------------
  # ● 开始
  #--------------------------------------------------------------------------
  def self.find_path(start_x, start_y, dest_x, dest_y)
    @dest_pos = [dest_x, dest_y]
    ## 加入起始节点
    size = [$game_map.width, $game_map.height]
    @open = BinaryHeap.new(size, make_node(start_x, start_y, nil)) {|a,b| a.f <=> b.f }
    @link = @open.link
    path = []
    while !@open.data.empty?
      node = @open.dequeue
      if [node.x,node.y] == @dest_pos
        path = make_path(node,PathType)
        break
      end
      self.check(node)
    end
    @possible_x = nil
    @possible_y = nil
    return path
  end
  #--------------------------------------------------------------------------
  # ● 生成路径
  # type: 返回的形式 (0:节点坐标, 1:移动方向)
  #--------------------------------------------------------------------------
  def self.make_path(node,type)
    path = []
    loop do
      case type
      when 0
        path.unshift [node.x, node.y]
      when 1
        path.unshift direction(node.x,node.y,node.parent)
      end
      ## 循环条件
      node = node.parent
      break if node.nil? || node.parent.nil?
    end
    return path
  end
  #--------------------------------------------------------------------------
  # ● 生成节点
  #--------------------------------------------------------------------------
  def self.make_node(x, y, parent)
    buf = Node.new(x,y,direction(x,y,parent),parent)
    buf.h = self.h(buf)
    buf.g = self.g(buf)
    buf.f = buf.h + buf.g
    return buf
  end
  #--------------------------------------------------------------------------
  # ● 寻路
  #--------------------------------------------------------------------------
  def self.check(node)
    px = $game_map.round_x(node.x)
    py = $game_map.round_y(node.y)
    @link[px, py] = -2 ## closed
    self.child_nodes(node).each do |n|
      x = $game_map.round_x(n.x)
      y = $game_map.round_y(n.y)
      next if @link[x, y] == -2 ## closed
      next if !passable?(px, py, n.d)
      ## 检查更新
      if @link[x, y] - 1 >= 0 && @open.data[@link[x, y] - 1]
        @open.replace(@link[x, y] - 1, n) if @open.data[@link[x, y] - 1].g > n.g
      else
        @open.enqueue(n)
      end
    end
  end
  #--------------------------------------------------------------------------
  # ● h值
  #--------------------------------------------------------------------------
  def self.h(node)
    return ((node.x-@dest_pos[0]).abs + (node.y-@dest_pos[1]).abs)*20
  end
  #--------------------------------------------------------------------------
  # ● g值
  #--------------------------------------------------------------------------
  def self.g(node)
    parent = node.parent
    return 0 if parent.nil?
    dg = parent.d == node.d ? 0 : 14
                                  ## 改变方向的附加开销
    dx = node.x - parent.x
    dy = node.y - parent.y
    ## 判断相对坐标
    if dx != 0 && dy != 0
      return parent.g + 14 + dg
    else
      return parent.g + 10 + dg
    end
  end
  #--------------------------------------------------------------------------
  # ● 方向
  #--------------------------------------------------------------------------
  def self.direction(x,y,parent)
    return parent.nil? ? 5 : (5 + (x-parent.x)*1 + (y-parent.y)*-3)
  end
  #--------------------------------------------------------------------------
  # ● 子节点
  #--------------------------------------------------------------------------
  def self.child_nodes(node)
    nodes = []
    case Directions
    when 4 # 4方向寻路
      nodes<<make_node(node.x,node.y-1,node)<<make_node(node.x,node.y+1,node)
      nodes<<make_node(node.x-1,node.y,node)<<make_node(node.x+1,node.y,node)
    when 8 # 8方向寻路
      for x in -1..1
        for y in -1..1
          next if x == 0 && y == 0
          nodes << make_node(node.x+x, node.y+y, node)
        end
      end
    end
    return nodes
  end
  #--------------------------------------------------------------------------
  # ● 通行? 
  #--------------------------------------------------------------------------
  def self.passable?(x, y, d)
    if @possible_x and @possible_y
      tx, ty = x, y
      case d
      when 1 # 左下
        tx -= 1
        ty += 1
      when 2 # 下
        ty += 1
      when 3 # 右下
        tx += 1
        ty += 1
      when 4 # 左
        tx -= 1
      when 6 # 右
        tx += 1
      when 7 # 左上
        tx -= 1
        ty -= 1
      when 8 # 上
        ty -= 1
      when 9 # 右上
        tx += 1
        ty -= 1
      end
      return true if @possible_x == tx and @possible_y == ty
    end
    x = $game_map.round_x(x)
    y = $game_map.round_y(y)
    case d
    when 0, 2, 4, 6, 8
      return $game_player.passable?(x, y, d)
    when 1
      return $game_player.diagonal_passable?(x, y, 4, 2)
    when 3
      return $game_player.diagonal_passable?(x, y, 6, 2)
    when 7
      return $game_player.diagonal_passable?(x, y, 4, 8)
    when 9
      return $game_player.diagonal_passable?(x, y, 6, 8)
    end
  end
  #--------------------------------------------------------------------------
  # ● 
  #--------------------------------------------------------------------------
  def self.open
    @open.data
  end
  #--------------------------------------------------------------------------
  # ● 
  #--------------------------------------------------------------------------
  def self.close
    result = []
    for i in [email]0...@link.xsize[/email]
      for j in [email]0...@link.ysize[/email]
        result<<[i,j] if @link[i,j]==-2
      end
    end
    return result
  end
end
 
#==============================================================================
# ■ BinaryHeap
#------------------------------------------------------------------------------
#  二叉堆 寻路用。
#==============================================================================
 
class BinaryHeap
  #--------------------------------------------------------------------------
  # ● 定义实例变量
  #--------------------------------------------------------------------------
  attr_reader :data
  attr_reader :link
  #--------------------------------------------------------------------------
  # ● 初始化
  #--------------------------------------------------------------------------
  def initialize(link,*val,&cmp)
    @link = Table.new(*link)
    @data = val.sort
    @cmp = cmp
    @cmp = Proc.new{|a,b| a<=>b} if @cmp.nil?
    @data.each_index{|i| @link[@data[i].x,@data[i].y] = i+1}
  end
  #--------------------------------------------------------------------------
  # ● 入列
  #--------------------------------------------------------------------------
  def enqueue(val)
    return replace(@data.size,val)
  end
  #--------------------------------------------------------------------------
  # ● 出列
  #--------------------------------------------------------------------------
  def dequeue
    shift = @data[0]
    if @data.size <= 1
      @data.clear
      return shift
    end
    @data[0] = @data.pop
    linkto(0)
    fid,cid = 0,[1,2]
    while @data[cid[0]]     ## 左枝存在时
      if @data[cid[1]].nil? ## 右枝不存在时
        min = cid[0] 
      else
        min = @cmp.call(@data[cid[0]],@data[cid[1]]) < 0 ? cid[0] : cid[1]
      end
      if @cmp.call(@data[min],@data[fid]) < 0
        @data[min],@data[fid] = @data[fid],@data[min]
        linkto(min,fid)
        fid = min
        cid = [fid*2+1,fid*2+2]
      else
        break
      end
    end
    return shift
  end
  #--------------------------------------------------------------------------
  # ● 修改
  #--------------------------------------------------------------------------
  def replace(pos, val)
    @data[pos] = val
    linkto(pos)
    cid,fid = pos,(pos-1)/2
    while cid > 0 ## 大于顶层时
      if @cmp.call(val,@data[fid]) < 0
        @data[cid] = @data[fid]
        linkto(cid)
        cid = fid
        fid = (cid-1)/2
      else
        break
      end
    end
    @data[cid] = val
    linkto(cid)
    return cid
  end
  #--------------------------------------------------------------------------
  # ● 关联
  #--------------------------------------------------------------------------
  def linkto(*id)
    id.each{|i| @link[@data[i].x,@data[i].y]=i+1}
  end
end
 
#==============================================================================
# ■ Game_Player
#------------------------------------------------------------------------------
#  处理玩家人物的类。拥有事件启动的判定、地图的卷动等功能。
#   本类的实例请参考 $game_player 。
#==============================================================================
 
class Game_Player < Game_Character
  #--------------------------------------------------------------------------
  # ● 初始化对象
  #--------------------------------------------------------------------------
  alias :d8_ori_init :initialize unless defined? d8_ori_init
  def initialize
    d8_ori_init
    init_mouse_control_info
  end
  #--------------------------------------------------------------------------
  # ● 初始化滑鼠訊息
  #--------------------------------------------------------------------------
  def init_mouse_control_info
    @mouse_drag = false
    @mouse_count = 0
    @mouse_speeding = false
    @find_path = 0
    @mpath ? @mpath.clear : @mpath = []
  end
  #--------------------------------------------------------------------------
  # ● 由玩家控制移动
  #--------------------------------------------------------------------------
  def move_by_input
    return if !movable? || $game_map.interpreter.running?
    if Input.dir8 > 0
      init_mouse_control_info
      move_direction(Input.dir4)
    end
    update_mouse_control
  end
  #--------------------------------------------------------------------------
  # ● 更新滑鼠控制
  #--------------------------------------------------------------------------
  def update_mouse_control
    # Count refresh
    @find_path += 1 if @find_path != 0
    # Mouse
    mouse_x, mouse_y = Input.get_mouse_pos
    # 滑鼠連點狀態
    if Input.dbclick?(Input::M_L)
      @mouse_speeding = true
      @find_path = 1
      @mouse_count = 0
    elsif Input.trigger?(Input::M_L)
      @find_path = 1
      @mouse_count = 0
    elsif Input.press?(Input::M_L)
      if @mouse_count > 10
        @mouse_drag = true
        @find_path = 0
      else
        @mouse_count += 1
      end
    else
      @mouse_count = 0
      @mouse_speeding = false if @mpath.empty? and @find_path == 0
      @mouse_drag = false
    end
    # 自動尋路
    if @find_path > 12
      @find_path = 0
      if Input.mouse_icon != 0
        if @target_target_event != 0
          @mouse_trigger_event = false
          @target_target_event = 0
        else
          @mouse_trigger_event = true
          @target_target_event = @target_event
        end
      end
      # 計算滑鼠所在地圖座標
      if $game_map.loop_horizontal? and $game_map.display_x + Graphics.width / 32 > $game_map.width and x < $game_map.display_x
        dx = $game_map.display_x - $game_map.width
      else
        dx = $game_map.display_x
      end
      if $game_map.loop_vertical? and $game_map.display_y + Graphics.height / 32 > $game_map.height and y < $game_map.display_y
        dy = $game_map.display_y - $game_map.height
      else
        dy = $game_map.display_y
      end
      mx = dx + mouse_x / 32
      my = dy + mouse_y / 32
      mx = mx <= mx.floor + 0.5 ? mx.ceil : mx.floor
      my = my >= my.floor + 0.5 ? my.ceil : my.floor
      if passable?(mx, my, 0) or Input.mouse_icon != 0
        @mpath = PathFinding.find_player_short_path(mx, my)
        @mouse_drag = false
      end
    end
    if @mouse_drag
      @mpath.clear unless @mpath.empty?
      dir = -1
      # 滑鼠移動控制
      dir = 6 if mouse_x > self.screen_x + 30
      dir = 4 if mouse_x < self.screen_x - 30
      if mouse_y > self.screen_y + 10
        case dir
        when 4
          dir = 1
        when 6
          dir = 3
        else
          dir = 2
        end
      end
      if mouse_y < self.screen_y - 48
        case dir
        when 4
          dir = 7
        when 6
          dir = 9
        else
          dir = 8
        end
      end
      move_direction(dir) if dir > 0
    end
    # 依自動尋路路徑行走
    move_direction(@mpath.shift) if !@mpath.empty?
  end
  #--------------------------------------------------------------------------
  # ● 依方向移動
  #--------------------------------------------------------------------------
  def move_direction(d)
    case d
    when 2, 4, 6, 8
      move_straight(d)
    # 斜向需作修正
    when 1
      return move_diagonal(4, 2) if diagonal_passable?(@x, @y, 4, 2)
      move_straight(4) if passable?(@x, @y, 4)
      move_straight(2) if passable?(@x, @y, 2)
    when 3
      return move_diagonal(6, 2) if diagonal_passable?(@x, @y, 6, 2)
      move_straight(6) if passable?(@x, @y, 6)
      move_straight(2) if passable?(@x, @y, 2)
    when 7
      return move_diagonal(4, 8) if diagonal_passable?(@x, @y, 4, 8)
      move_straight(4) if passable?(@x, @y, 4)
      move_straight(8) if passable?(@x, @y, 8)
    when 9
      return move_diagonal(6, 8) if diagonal_passable?(@x, @y, 6, 8)
      move_straight(6) if passable?(@x, @y, 6)
      move_straight(8) if passable?(@x, @y, 8)
    end
  end
end