class Game_Character
  attr_reader   :id
  attr_reader   :original_x
  attr_reader   :original_y
  attr_reader   :x
  attr_reader   :y
  attr_reader   :real_x
  attr_reader   :real_y
  attr_writer   :x_offset   # In pixels, positive shifts sprite to the right
  attr_writer   :y_offset   # In pixels, positive shifts sprite down
  attr_accessor :width
  attr_accessor :height
  attr_accessor :sprite_size
  attr_reader   :tile_id
  attr_accessor :character_name
  attr_accessor :character_hue
  attr_accessor :opacity
  attr_reader   :blend_type
  attr_accessor :direction
  attr_accessor :pattern
  attr_accessor :pattern_surf
  attr_accessor :lock_pattern
  attr_reader   :move_route_forcing
  attr_accessor :through
  attr_accessor :animation_id
  attr_accessor :transparent
  attr_reader   :move_speed
  attr_reader   :jump_speed
  attr_accessor :walk_anime
  attr_writer   :bob_height

  def initialize(map = nil)
    @map                       = map
    @id                        = 0
    @original_x                = 0
    @original_y                = 0
    @x                         = 0
    @y                         = 0
    @real_x                    = 0
    @real_y                    = 0
    @x_offset                  = 0
    @y_offset                  = 0
    @width                     = 1
    @height                    = 1
    @sprite_size               = [Game_Map::TILE_WIDTH, Game_Map::TILE_HEIGHT]
    @tile_id                   = 0
    @character_name            = ""
    @character_hue             = 0
    @opacity                   = 255
    @blend_type                = 0
    @direction                 = 2
    @pattern                   = 0
    @pattern_surf              = 0
    @lock_pattern              = false
    @move_route_forcing        = false
    @through                   = false
    @animation_id              = 0
    @transparent               = false
    @original_direction        = 2
    @original_pattern          = 0
    @move_type                 = 0
    self.move_speed            = 3
    self.move_frequency        = 6
    self.jump_speed            = 3
    @move_route                = nil
    @move_route_index          = 0
    @original_move_route       = nil
    @original_move_route_index = 0
    @walk_anime                = true    # Whether character should animate while moving
    @step_anime                = false   # Whether character should animate while still
    @direction_fix             = false
    @always_on_top             = false
    @anime_count               = 0   # Time since pattern was last changed
    @stop_count                = 0   # Time since character last finished moving
    @bumping                   = false   # Used by the player only when walking into something
    @jump_peak                 = 0   # Max height while jumping
    @jump_distance             = 0   # Total distance of jump
    @jump_fraction             = 0   # How far through a jump we currently are (0-1)
    @jumping_on_spot           = false
    @bob_height                = 0
    @wait_count                = 0
    @wait_start                = nil
    @moved_this_frame          = false
    @moveto_happened           = false
    @locked                    = false
    @prelock_direction         = 0
  end

  def x_offset; return @x_offset || 0; end
  def y_offset; return @y_offset || 0; end

  def at_coordinate?(check_x, check_y)
    return check_x >= @x && check_x < @x + @width &&
           check_y > @y - @height && check_y <= @y
  end

  def in_line_with_coordinate?(check_x, check_y)
    return (check_x >= @x && check_x < @x + @width) ||
           (check_y > @y - @height && check_y <= @y)
  end

  def each_occupied_tile
    (@x...(@x + @width)).each do |i|
      ((@y - @height + 1)..@y).each do |j|
        yield i, j
      end
    end
  end

  def move_speed=(val)
    @move_speed = val
    # Time taken to traverse one tile (in seconds) for each speed:
    #   1 => 1.0
    #   2 => 0.5
    #   3 => 0.25    # Walking speed
    #   4 => 0.125   # Running speed (2x walking speed)
    #   5 => 0.1     # Cycling speed (1.25x running speed)
    #   6 => 0.05
    case val
    when 6 then @move_time = 0.05
    when 5 then @move_time = 0.1
    else        @move_time = 2.0 / (2**val)
    end
  end

  # Takes the same values as move_speed above.
  def jump_speed=(val)
    @jump_speed = val
    case val
    when 6 then @jump_time = 0.05
    when 5 then @jump_time = 0.1
    else        @jump_time = 2.0 / (2**val)
    end
  end

  # Returns time in seconds for one full cycle (4 frames) of an animating
  # charset to show. Two frames are shown per movement across one tile.
  def pattern_update_speed
    return @jump_time * 2 if jumping?
    ret = @move_time * 2
    ret *= 2 if @move_speed >= 5   # Cycling speed or faster; slower animation
    return ret
  end

  def move_frequency=(val)
    return if val == @move_frequency
    @move_frequency = val
    # Time in seconds to wait between each action in a move route (not forced).
    # Specifically, this is the time to wait after the character stops moving
    # because of the previous action.
    #   1 => 4.75 seconds
    #   2 => 3.6 seconds
    #   3 => 2.55 seconds
    #   4 => 1.6 seconds
    #   5 => 0.75 seconds
    #   6 => 0 seconds, i.e. continuous movement
    @command_delay = (40 - (val * 2)) * (6 - val) / 40.0
  end

  def bob_height
    @bob_height = 0 if !@bob_height
    return @bob_height
  end

  def lock
    return if @locked
    @prelock_direction = 0   # Was @direction but disabled
    turn_toward_player
    @locked = true
  end

  def minilock
    @prelock_direction = 0   # Was @direction but disabled
    @locked = true
  end

  def lock?
    return @locked
  end

  def unlock
    return unless @locked
    @locked = false
    @direction = @prelock_direction if !@direction_fix && @prelock_direction != 0
  end

  #=============================================================================
  # Information from map data
  #=============================================================================
  def map
    return (@map) ? @map : $game_map
  end

  def terrain_tag
    return self.map.terrain_tag(@x, @y)
  end

  def bush_depth
    return @bush_depth || 0
  end

  def calculate_bush_depth
    if @tile_id > 0 || @always_on_top || jumping?
      @bush_depth = 0
      return
    end
    this_map = (self.map.valid?(@x, @y)) ? [self.map, @x, @y] : $map_factory&.getNewMap(@x, @y, self.map.map_id)
    if this_map && this_map[0].deepBush?(this_map[1], this_map[2])
      xbehind = @x + (@direction == 4 ? 1 : @direction == 6 ? -1 : 0)
      ybehind = @y + (@direction == 8 ? 1 : @direction == 2 ? -1 : 0)
      if moving?
        behind_map = (self.map.valid?(xbehind, ybehind)) ? [self.map, xbehind, ybehind] : $map_factory&.getNewMap(xbehind, ybehind, self.map.map_id)
        @bush_depth = Game_Map::TILE_HEIGHT if behind_map[0].deepBush?(behind_map[1], behind_map[2])
      else
        @bush_depth = Game_Map::TILE_HEIGHT
      end
    elsif this_map && this_map[0].bush?(this_map[1], this_map[2]) && !moving?
      @bush_depth = 12
    else
      @bush_depth = 0
    end
  end

  def fullPattern
    case self.direction
    when 2 then return self.pattern
    when 4 then return self.pattern + 4
    when 6 then return self.pattern + 8
    when 8 then return self.pattern + 12
    end
    return 0
  end

  #=============================================================================
  # Passability
  #=============================================================================
  def passable?(x, y, d, strict = false)
    new_x = x + (d == 6 ? 1 : d == 4 ? -1 : 0)
    new_y = y + (d == 2 ? 1 : d == 8 ? -1 : 0)
    return false unless self.map.valid?(new_x, new_y)
    return true if @through
    if strict
      return false unless self.map.passableStrict?(x, y, d, self)
      return false unless self.map.passableStrict?(new_x, new_y, 10 - d, self)
    else
      return false unless self.map.passable?(x, y, d, self)
      return false unless self.map.passable?(new_x, new_y, 10 - d, self)
    end
    self.map.events.each_value do |event|
      next if self == event || !event.at_coordinate?(new_x, new_y) || event.through
      return false if self != $game_player || event.character_name != ""
    end
    if $game_player.x == new_x && $game_player.y == new_y &&
       !$game_player.through && @character_name != ""
      return false
    end
    return true
  end

  def can_move_from_coordinate?(start_x, start_y, dir, strict = false)
    case dir
    when 2, 8   # Down, up
      y_diff = (dir == 8) ? @height - 1 : 0
      (start_x...(start_x + @width)).each do |i|
        return false if !passable?(i, start_y - y_diff, dir, strict)
      end
      return true
    when 4, 6   # Left, right
      x_diff = (dir == 6) ? @width - 1 : 0
      ((start_y - @height + 1)..start_y).each do |i|
        return false if !passable?(start_x + x_diff, i, dir, strict)
      end
      return true
    when 1, 3   # Down diagonals
      # Treated as moving down first and then horizontally, because that
      # describes which tiles the character's feet touch
      (start_x...(start_x + @width)).each do |i|
        return false if !passable?(i, start_y, 2, strict)
      end
      x_diff = (dir == 3) ? @width - 1 : 0
      ((start_y - @height + 1)..start_y).each do |i|
        return false if !passable?(start_x + x_diff, i + 1, dir + 3, strict)
      end
      return true
    when 7, 9   # Up diagonals
      # Treated as moving horizontally first and then up, because that describes
      # which tiles the character's feet touch
      x_diff = (dir == 9) ? @width - 1 : 0
      ((start_y - @height + 1)..start_y).each do |i|
        return false if !passable?(start_x + x_diff, i, dir - 3, strict)
      end
      x_tile_offset = (dir == 9) ? 1 : -1
      (start_x...(start_x + @width)).each do |i|
        return false if !passable?(i + x_tile_offset, start_y - @height + 1, 8, strict)
      end
      return true
    end
    return false
  end

  def can_move_in_direction?(dir, strict = false)
    return can_move_from_coordinate?(@x, @y, dir, strict)
  end

  #=============================================================================
  # Screen position of the character
  #=============================================================================
  def screen_x
    ret = ((@real_x.to_f - self.map.display_x) / Game_Map::X_SUBPIXELS).round
    ret += @width * Game_Map::TILE_WIDTH / 2
    ret += self.x_offset
    return ret
  end

  def screen_y_ground
    ret = ((@real_y.to_f - self.map.display_y) / Game_Map::Y_SUBPIXELS).round
    ret += Game_Map::TILE_HEIGHT
    return ret
  end

  def screen_y
    ret = screen_y_ground
    if jumping?
      jump_progress = (@jump_fraction - 0.5).abs   # 0.5 to 0 to 0.5
      ret += @jump_peak * ((4 * (jump_progress**2)) - 1)
    end
    ret += self.y_offset
    return ret
  end

  def screen_z(height = 0)
    return 999 if @always_on_top
    z = screen_y_ground
    if @tile_id > 0
      begin
        return z + (self.map.priorities[@tile_id] * 32)
      rescue
        raise "Event's graphic is an out-of-range tile (event #{@id}, map #{self.map.map_id})"
      end
    end
    # Add z if height exceeds 32
    return z + ((height > Game_Map::TILE_HEIGHT) ? Game_Map::TILE_HEIGHT - 1 : 0)
  end

  #=============================================================================
  # Movement
  #=============================================================================
  def moving?
    return !@move_timer.nil?
  end

  def jumping?
    return !@jump_timer.nil?
  end

  def straighten
    @pattern = 0 if @walk_anime || @step_anime
    @anime_count = 0
    @prelock_direction = 0
  end

  def force_move_route(move_route)
    if @original_move_route.nil?
      @original_move_route       = @move_route
      @original_move_route_index = @move_route_index
    end
    @move_route         = move_route
    @move_route_index   = 0
    @move_route_forcing = true
    @prelock_direction  = 0
    @wait_count         = 0
    @wait_start         = nil
    move_type_custom
  end

  def moveto(x, y)
    @x = x % self.map.width
    @y = y % self.map.height
    @real_x = @x * Game_Map::REAL_RES_X
    @real_y = @y * Game_Map::REAL_RES_Y
    @prelock_direction = 0
    @moveto_happened = true
    calculate_bush_depth
    triggerLeaveTile
  end

  def triggerLeaveTile
    if @oldX && @oldY && @oldMap &&
       (@oldX != self.x || @oldY != self.y || @oldMap != self.map.map_id)
      EventHandlers.trigger(:on_leave_tile, self, @oldMap, @oldX, @oldY)
    end
    @oldX = self.x
    @oldY = self.y
    @oldMap = self.map.map_id
  end

  def increase_steps
    @stop_count = 0
    triggerLeaveTile
  end

  #=============================================================================
  # Movement commands
  #=============================================================================
  def move_type_random
    case rand(6)
    when 0..3 then move_random
    when 4    then move_forward
    when 5    then @stop_count = 0
    end
  end

  def move_type_toward_player
    sx = @x + (@width / 2.0) - ($game_player.x + ($game_player.width / 2.0))
    sy = @y - (@height / 2.0) - ($game_player.y - ($game_player.height / 2.0))
    if sx.abs + sy.abs >= 20
      move_random
      return
    end
    case rand(6)
    when 0..3 then move_toward_player
    when 4    then move_random
    when 5    then move_forward
    end
  end

  def move_type_custom
    return if jumping? || moving?
    return if @move_route.list.size <= 1   # Empty move route
    start_index = @move_route_index
    (@move_route.list.size - 1).times do
      command = @move_route.list[@move_route_index]
      if command.code == 0
        if @move_route.repeat
          @move_route_index = 0
          command = @move_route.list[@move_route_index]
        else
          if @move_route_forcing
            @move_route_forcing = false
            @move_route       = @original_move_route
            @move_route_index = @original_move_route_index
            @original_move_route = nil
          end
          @stop_count = 0
          return
        end
      end
      done_one_command = true
      # The below move route commands wait for a frame (i.e. return) after
      # executing them
      if command.code <= 14
        case command.code
        when 1  then move_down
        when 2  then move_left
        when 3  then move_right
        when 4  then move_up
        when 5  then move_lower_left
        when 6  then move_lower_right
        when 7  then move_upper_left
        when 8  then move_upper_right
        when 9  then move_random
        when 10 then move_toward_player
        when 11 then move_away_from_player
        when 12 then move_forward
        when 13 then move_backward
        when 14 then jump(command.parameters[0], command.parameters[1])
        end
        @move_route_index += 1 if @move_route.skippable || moving? || jumping?
        return
      end
      # The below move route commands wait for a frame (i.e. return) after
      # executing them
      if command.code >= 15 && command.code <= 26
        case command.code
        when 15   # Wait
          @wait_count = command.parameters[0] / 20.0
          @wait_start = System.uptime
        when 16 then turn_down
        when 17 then turn_left
        when 18 then turn_right
        when 19 then turn_up
        when 20 then turn_right_90
        when 21 then turn_left_90
        when 22 then turn_180
        when 23 then turn_right_or_left_90
        when 24 then turn_random
        when 25 then turn_toward_player
        when 26 then turn_away_from_player
        end
        @move_route_index += 1
        return
      end
      # The below move route commands don't wait for a frame (i.e. return) after
      # executing them
      if command.code >= 27
        case command.code
        when 27
          $game_switches[command.parameters[0]] = true
          self.map.need_refresh = true
        when 28
          $game_switches[command.parameters[0]] = false
          self.map.need_refresh = true
        when 29 then self.move_speed = command.parameters[0]
        when 30 then self.move_frequency = command.parameters[0]
        when 31 then @walk_anime = true
        when 32 then @walk_anime = false
        when 33 then @step_anime = true
        when 34 then @step_anime = false
        when 35 then @direction_fix = true
        when 36 then @direction_fix = false
        when 37 then @through = true
        when 38 then @through = false
        when 39
          old_always_on_top = @always_on_top
          @always_on_top = true
          calculate_bush_depth if @always_on_top != old_always_on_top
        when 40
          old_always_on_top = @always_on_top
          @always_on_top = false
          calculate_bush_depth if @always_on_top != old_always_on_top
        when 41
          old_tile_id = @tile_id
          @tile_id = 0
          @character_name = command.parameters[0]
          @character_hue = command.parameters[1]
          if @original_direction != command.parameters[2]
            @direction = command.parameters[2]
            @original_direction = @direction
            @prelock_direction = 0
          end
          if @original_pattern != command.parameters[3]
            @pattern = command.parameters[3]
            @original_pattern = @pattern
          end
          calculate_bush_depth if @tile_id != old_tile_id
        when 42 then @opacity = command.parameters[0]
        when 43 then @blend_type = command.parameters[0]
        when 44 then pbSEPlay(command.parameters[0])
        when 45
          eval(command.parameters[0])
          if command.parameters[0][/^move_random_range/] ||
             command.parameters[0][/^move_random_UD/] ||
             command.parameters[0][/^move_random_LR/]
            @move_route_index += 1
            return
          end
        end
        @move_route_index += 1
      end
    end
  end

  def move_generic(dir, turn_enabled = true)
    turn_generic(dir) if turn_enabled
    if can_move_in_direction?(dir)
      turn_generic(dir)
      @move_initial_x = @x
      @move_initial_y = @y
      @x += (dir == 4) ? -1 : (dir == 6) ? 1 : 0
      @y += (dir == 8) ? -1 : (dir == 2) ? 1 : 0
      @move_timer = 0.0
      increase_steps
    else
      check_event_trigger_touch(dir)
    end
  end

  def move_down(turn_enabled = true)
    move_generic(2, turn_enabled)
  end

  def move_left(turn_enabled = true)
    move_generic(4, turn_enabled)
  end

  def move_right(turn_enabled = true)
    move_generic(6, turn_enabled)
  end

  def move_up(turn_enabled = true)
    move_generic(8, turn_enabled)
  end

  def move_upper_left
    unless @direction_fix
      @direction = (@direction == 6 ? 4 : @direction == 2 ? 8 : @direction)
    end
    if can_move_in_direction?(7)
      @move_initial_x = @x
      @move_initial_y = @y
      @x -= 1
      @y -= 1
      @move_timer = 0.0
      increase_steps
    end
  end

  def move_upper_right
    unless @direction_fix
      @direction = (@direction == 4 ? 6 : @direction == 2 ? 8 : @direction)
    end
    if can_move_in_direction?(9)
      @move_initial_x = @x
      @move_initial_y = @y
      @x += 1
      @y -= 1
      @move_timer = 0.0
      increase_steps
    end
  end

  def move_lower_left
    unless @direction_fix
      @direction = (@direction == 6 ? 4 : @direction == 8 ? 2 : @direction)
    end
    if can_move_in_direction?(1)
      @move_initial_x = @x
      @move_initial_y = @y
      @x -= 1
      @y += 1
      @move_timer = 0.0
      increase_steps
    end
  end

  def move_lower_right
    unless @direction_fix
      @direction = (@direction == 4 ? 6 : @direction == 8 ? 2 : @direction)
    end
    if can_move_in_direction?(3)
      @move_initial_x = @x
      @move_initial_y = @y
      @x += 1
      @y += 1
      @move_timer = 0.0
      increase_steps
    end
  end

  # Anticlockwise.
  def moveLeft90
    case self.direction
    when 2 then move_right   # down
    when 4 then move_down    # left
    when 6 then move_up      # right
    when 8 then move_left    # up
    end
  end

  # Clockwise.
  def moveRight90
    case self.direction
    when 2 then move_left    # down
    when 4 then move_up      # left
    when 6 then move_down    # right
    when 8 then move_right   # up
    end
  end

  def move_random
    case rand(4)
    when 0 then move_down(false)
    when 1 then move_left(false)
    when 2 then move_right(false)
    when 3 then move_up(false)
    end
  end

  def move_random_range(xrange = -1, yrange = -1)
    dirs = []   # 0=down, 1=left, 2=right, 3=up
    if xrange < 0
      dirs.push(1)
      dirs.push(2)
    elsif xrange > 0
      dirs.push(1) if @x > @original_x - xrange
      dirs.push(2) if @x < @original_x + xrange
    end
    if yrange < 0
      dirs.push(0)
      dirs.push(3)
    elsif yrange > 0
      dirs.push(0) if @y < @original_y + yrange
      dirs.push(3) if @y > @original_y - yrange
    end
    return if dirs.length == 0
    case dirs[rand(dirs.length)]
    when 0 then move_down(false)
    when 1 then move_left(false)
    when 2 then move_right(false)
    when 3 then move_up(false)
    end
  end

  def move_random_UD(range = -1)
    move_random_range(0, range)
  end

  def move_random_LR(range = -1)
    move_random_range(range, 0)
  end

  def move_toward_player
    sx = @x + (@width / 2.0) - ($game_player.x + ($game_player.width / 2.0))
    sy = @y - (@height / 2.0) - ($game_player.y - ($game_player.height / 2.0))
    return if sx == 0 && sy == 0
    abs_sx = sx.abs
    abs_sy = sy.abs
    if abs_sx == abs_sy
      (rand(2) == 0) ? abs_sx += 1 : abs_sy += 1
    end
    if abs_sx > abs_sy
      (sx > 0) ? move_left : move_right
      if !moving? && sy != 0
        (sy > 0) ? move_up : move_down
      end
    else
      (sy > 0) ? move_up : move_down
      if !moving? && sx != 0
        (sx > 0) ? move_left : move_right
      end
    end
  end

  def move_away_from_player
    sx = @x + (@width / 2.0) - ($game_player.x + ($game_player.width / 2.0))
    sy = @y - (@height / 2.0) - ($game_player.y - ($game_player.height / 2.0))
    return if sx == 0 && sy == 0
    abs_sx = sx.abs
    abs_sy = sy.abs
    if abs_sx == abs_sy
      (rand(2) == 0) ? abs_sx += 1 : abs_sy += 1
    end
    if abs_sx > abs_sy
      (sx > 0) ? move_right : move_left
      if !moving? && sy != 0
        (sy > 0) ? move_down : move_up
      end
    else
      (sy > 0) ? move_down : move_up
      if !moving? && sx != 0
        (sx > 0) ? move_right : move_left
      end
    end
  end

  def move_forward
    case @direction
    when 2 then move_down(false)
    when 4 then move_left(false)
    when 6 then move_right(false)
    when 8 then move_up(false)
    end
  end

  def move_backward
    last_direction_fix = @direction_fix
    @direction_fix = true
    case @direction
    when 2 then move_up(false)
    when 4 then move_right(false)
    when 6 then move_left(false)
    when 8 then move_down(false)
    end
    @direction_fix = last_direction_fix
  end

  def jump(x_plus, y_plus)
    if x_plus != 0 || y_plus != 0
      if x_plus.abs > y_plus.abs
        (x_plus < 0) ? turn_left : turn_right
      else
        (y_plus < 0) ? turn_up : turn_down
      end
      each_occupied_tile { |i, j| return if !passable?(i + x_plus, j + y_plus, 0) }
    end
    @jump_initial_x = @x
    @jump_initial_y = @y
    @x += x_plus
    @y += y_plus
    @jump_timer = 0.0
    real_distance = Math.sqrt((x_plus**2) + (y_plus**2))
    distance = [1, real_distance].max
    @jump_peak = distance * Game_Map::TILE_HEIGHT * 3 / 8   # 3/4 of tile for ledge jumping
    @jump_distance = [x_plus.abs * Game_Map::REAL_RES_X, y_plus.abs * Game_Map::REAL_RES_Y].max
    @jumping_on_spot = (real_distance == 0)
    increase_steps
  end

  def jumpForward(distance = 1)
    return false if distance == 0
    old_x = @x
    old_y = @y
    case self.direction
    when 2 then jump(0, distance)    # down
    when 4 then jump(-distance, 0)   # left
    when 6 then jump(distance, 0)    # right
    when 8 then jump(0, -distance)   # up
    end
    return @x != old_x || @y != old_y
  end

  def jumpBackward(distance = 1)
    return false if distance == 0
    old_x = @x
    old_y = @y
    case self.direction
    when 2 then jump(0, -distance)   # down
    when 4 then jump(distance, 0)    # left
    when 6 then jump(-distance, 0)   # right
    when 8 then jump(0, distance)    # up
    end
    return @x != old_x || @y != old_y
  end

  def turn_generic(dir)
    return if @direction_fix
    oldDirection = @direction
    @direction = dir
    @stop_count = 0
    check_event_trigger_after_turning if dir != oldDirection
  end

  def turn_down;  turn_generic(2); end
  def turn_left;  turn_generic(4); end
  def turn_right; turn_generic(6); end
  def turn_up;    turn_generic(8); end

  def turn_right_90
    case @direction
    when 2 then turn_left
    when 4 then turn_up
    when 6 then turn_down
    when 8 then turn_right
    end
  end

  def turn_left_90
    case @direction
    when 2 then turn_right
    when 4 then turn_down
    when 6 then turn_up
    when 8 then turn_left
    end
  end

  def turn_180
    case @direction
    when 2 then turn_up
    when 4 then turn_right
    when 6 then turn_left
    when 8 then turn_down
    end
  end

  def turn_right_or_left_90
    (rand(2) == 0) ? turn_right_90 : turn_left_90
  end

  def turn_random
    case rand(4)
    when 0 then turn_up
    when 1 then turn_right
    when 2 then turn_left
    when 3 then turn_down
    end
  end

  def turn_toward_player
    sx = @x + (@width / 2.0) - ($game_player.x + ($game_player.width / 2.0))
    sy = @y - (@height / 2.0) - ($game_player.y - ($game_player.height / 2.0))
    return if sx == 0 && sy == 0
    if sx.abs > sy.abs
      (sx > 0) ? turn_left : turn_right
    else
      (sy > 0) ? turn_up : turn_down
    end
  end

  def turn_away_from_player
    sx = @x + (@width / 2.0) - ($game_player.x + ($game_player.width / 2.0))
    sy = @y - (@height / 2.0) - ($game_player.y - ($game_player.height / 2.0))
    return if sx == 0 && sy == 0
    if sx.abs > sy.abs
      (sx > 0) ? turn_right : turn_left
    else
      (sy > 0) ? turn_down : turn_up
    end
  end

  #=============================================================================
  # Updating
  #=============================================================================
  def update
    return if $game_temp.in_menu
    time_now = System.uptime
    @last_update_time = time_now if !@last_update_time || @last_update_time > time_now
    @delta_t = time_now - @last_update_time
    @last_update_time = time_now
    return if @delta_t > 0.25   # Was in a menu; delay movement
    @moved_last_frame = @moved_this_frame
    @stopped_last_frame = @stopped_this_frame
    @moved_this_frame = false
    @stopped_this_frame = false
    # Update command
    update_command
    # Update movement
    (moving? || jumping?) ? update_move : update_stop
    # Update animation
    update_pattern
  end

  def update_command
    if @wait_count > 0
      return if System.uptime - @wait_start < @wait_count
      @wait_count = 0
      @wait_start = nil
    end
    if @move_route_forcing
      move_type_custom
    elsif !@starting && !lock? && !moving? && !jumping?
      update_command_new
    end
  end

  def update_command_new
    if @stop_count >= @command_delay
      case @move_type
      when 1 then move_type_random
      when 2 then move_type_toward_player
      when 3 then move_type_custom
      end
    end
  end

  def update_move
    if @move_timer
      @move_timer += @delta_t
      # Move horizontally
      if @x != @move_initial_x
        dist = (@move_initial_x - @x).abs
        @real_x = lerp(@move_initial_x, @x, @move_time * dist, @move_timer) * Game_Map::REAL_RES_X
      end
      # Move vertically
      if @y != @move_initial_y
        dist = (@move_initial_y - @y).abs
        @real_y = lerp(@move_initial_y, @y, @move_time * dist, @move_timer) * Game_Map::REAL_RES_Y
      end
    elsif @jump_timer
      self.jump_speed = 3 if !@jump_time
      @jump_timer += @delta_t
      dist = [(@x - @jump_initial_x).abs, (@y - @jump_initial_y).abs].max
      dist = 1 if dist == 0   # Jumping on spot
      # Move horizontally
      if @x != @jump_initial_x
        @real_x = lerp(@jump_initial_x, @x, @jump_time * dist, @jump_timer) * Game_Map::REAL_RES_X
      end
      # Move vertically
      if @y != @jump_initial_y
        @real_y = lerp(@jump_initial_y, @y, @jump_time * dist, @jump_timer) * Game_Map::REAL_RES_Y
      end
      # Calculate how far through the jump we are (from 0 to 1)
      @jump_fraction = @jump_timer / (@jump_time * dist)
    end
    # Snap to end position if close enough
    @real_x = @x * Game_Map::REAL_RES_X if (@real_x - (@x * Game_Map::REAL_RES_X)).abs < Game_Map::X_SUBPIXELS / 2
    @real_y = @y * Game_Map::REAL_RES_Y if (@real_y - (@y * Game_Map::REAL_RES_Y)).abs < Game_Map::Y_SUBPIXELS / 2
    # End of move
    if moving? && @move_timer >= @move_time &&
       @real_x == @x * Game_Map::REAL_RES_X && @real_y == @y * Game_Map::REAL_RES_Y
      @move_timer = nil
      @bumping = false
    end
    # End of jump
    if jumping? && @jump_fraction >= 1
      @jump_timer = nil
      @jump_peak = 0
      @jump_distance = 0
      @jump_fraction = 0
      @jumping_on_spot = false
    end
    # End of a step, so perform events that happen at this time
    if !jumping? && !moving?
      EventHandlers.trigger(:on_step_taken, self)
      calculate_bush_depth
      @stopped_this_frame = true
    elsif !@moved_last_frame || @stopped_last_frame   # Started a new step
      calculate_bush_depth
    end
    # Increment animation counter
    @anime_count += @delta_t if @walk_anime || @step_anime
    @moved_this_frame = true
  end

  def update_stop
    @anime_count += @delta_t if @step_anime
    @stop_count += @delta_t if !@starting && !lock?
  end

  def update_pattern
    return if @lock_pattern
#    return if @jumping_on_spot   # Don't animate if jumping on the spot
    # Character has stopped moving, return to original pattern
    if @moved_last_frame && !@moved_this_frame && !@step_anime
      @pattern = @original_pattern
      @anime_count = 0
      return
    end
    # Character has started to move, change pattern immediately
    if !@moved_last_frame && @moved_this_frame && !@step_anime
      @pattern = (@pattern + 1) % 4 if @walk_anime
      @anime_count = 0
      return
    end
    # Calculate how many frames each pattern should display for, i.e. the time
    # it takes to move half a tile (or a whole tile if cycling). We assume the
    # game uses square tiles.
    pattern_time = pattern_update_speed / 4   # 4 frames per cycle in a charset
    return if @anime_count < pattern_time
    # Advance to the next animation frame
    @pattern = (@pattern + 1) % 4
    @anime_count -= pattern_time
  end
end
