Old VX ACE pathfinding script edited for single move

I’ve been struggling to find a pathfinding script that only fires one movement each time the event is called. After a lot of trial and error and sadly some assistance from chatbots I’ve managed to turn this classic pathfinder from 2011 into what I’ve been looking for.

All the credits to the original scripters cozziekuns (and Modern Albegra). Also most of the description has been written by cozziekuns with minor edits where applicable.

Shout out to johantnjl for having requested this feature/modification to the script in January of 2012. I hope this helps you in your project :slightly_smiling_face:

Version History


  • <Version 1.0> 2011.12.31 - Original Release (minor modification by myhgis 19.07.2026)

Description
This script allows you to move any character to another tile using the shortest path possible. It takes a significantly longer time on large maps.

Features

  • Incredibly fast and accurate pathfinding

  • Easy script calls

  • Almost no configuration

Instructions

To use the script as originally intended without taking one step at a time, create a move route event and as a script command use:

find_path(target_x, target_y)

Additionally, one can force a path through a script using the following call, which is the one that has been edited to only move once each time the script has been called:

force_path(target_x, target_y)

There is a known bug that is present in Modern Algebra’s script regarding the recalculation of a path once found. Since some of this script is simply a convert from his VX version (only the algorithm is different by being slightly slower and sloppier), the bug is also found in this script.

Credit


  • cozziekuns
  • Modern Algebra, for his VX version of the script

Thanks

  • Patrick Lester, and his well known A* Tutorials which were immensely helpful.

#===============================================================================
# [VXA] Pathfinding
#-------------------------------------------------------------------------------
# Version: 1.0
# Author: cozziekuns (rmrk)
# Last Date Updated: 12/31/2011 (MM/DD/YYYY)
#===============================================================================
# Description:
#-------------------------------------------------------------------------------
# This script allows you to move any character to another tile using the 
# shortest path possible. It takes a significantly longer time on large maps.
#===============================================================================
# Updates
# ------------------------------------------------------------------------------
# o 12/31/2011 - Started Script
#===============================================================================
# To-do List
#-------------------------------------------------------------------------------
# o Allow for dynamic recalculation if one wants to repeat the pathfinding
#   process for any reason.
#===============================================================================
# Instructions
#-------------------------------------------------------------------------------
# To use, create a move route event and as a script command use:
#
# find_path(target_x, target_y)
#
# Additionally, one can force a path through a script using the following call:
#
# force_path(target_x, target_y)
#
# There is a known bug that is present in Modern Algebra's script regarding the
# recalculation of a path once found. Since some of this script is simply
# a convert from his VX version (only the algorithm is different by being
# slightly slower and sloppier :P), the bug is also found in this script.
#===============================================================================

#==============================================================================
# ** Game_Map
#==============================================================================

class Game_Map
 
  def find_path(target_x, target_y, sx, sy, passable, char)
    path = []
    max_elements = width * height + 2
    checked_items = 0
    @open_list_items = 0
    @open_list = Table.new(max_elements)
    @nodes = Table.new(max_elements, 2)
    open = Table.new(width, height)
    closed = Table.new(width, height)
    parent = Table.new(width, height, 3)
    @f_cost = Table.new(width, height)
    @open_list[0] = 0
    @nodes[0, 0] = sx
    @nodes[0, 1] = sy
    next_point = [sx, sy]
    closed[sx, sy] = 1

    loop do
      next_point = delete_from_heap if not next_point == [sx, sy]
      
      # Target is unreachable — cancel search and stay idle
      return [] if next_point.nil?
      
      open[next_point[0], next_point[1]] = 0
      closed[next_point[0], next_point[1]] = 2 if not next_point == [sx, sy]
      parent_x, parent_y = next_point[0], next_point[1]

      for i in 1..4
        dir = i * 2
        x, y = case dir
        when 2; [parent_x, parent_y + 1]
        when 4; [parent_x - 1, parent_y]
        when 6; [parent_x + 1, parent_y]
        when 8; [parent_x, parent_y - 1]
        end

        next unless $game_map.valid?(x, y)
        next if closed[x, y] == 2
        next unless custom_passable?(char, parent_x, parent_y, dir, x, y, target_x, target_y)

        if not open[x, y] == 1
          open[x, y] = 1
          parent[x, y, 0] = parent_x
          parent[x, y, 1] = parent_y
          parent[x, y, 2] = parent[parent_x, parent_y, 2] + 10
          g = parent[x, y, 2] + 10
          h = ((target_x - x).abs + (target_y - y).abs) * 10
          @f_cost[x, y] = g
          checked_items += 1
          @open_list_items += 1
          @nodes[checked_items, 0] = x
          @nodes[checked_items, 1] = y
          add_to_heap(checked_items)
        else
          old_g = parent[x, y, 2] + 10
          new_g = parent[parent_x, parent_y, 2] + 20
          next if old_g < new_g
          parent[x, y, 0] = parent_x
          parent[x, y, 1] = parent_y
          parent[x, y, 2] = new_g
          g = parent[x, y, 2] + 10
          h = ((target_x - x).abs + (target_y - y).abs) * 10
          @f_cost[x, y] = g
        end
      end

      next_point = nil
      break if closed[target_x, target_y] == 2
    end   

    # Double-check that destination was reached
    return [] unless closed[target_x, target_y] == 2

    path_x, path_y = target_x, target_y
    loop do   
      parent_x = parent[path_x, path_y, 0]
      parent_y = parent[path_x, path_y, 1]

      dx = path_x - parent_x
      dy = path_y - parent_y

      if dx == 1
        code = 6 # right
      elsif dx == -1
        code = 4 # left
      elsif dy == 1
        code = 2 # down
      elsif dy == -1
        code = 8 # up
      else
        code = 0
      end
        
      path.push(RPG::MoveCommand.new(code))
      path_x, path_y = parent_x, parent_y     
      break if path_x == sx and path_y == sy
    end

    return path
  end

  def custom_passable?(char, x, y, d, nx, ny, target_x, target_y)
    return false unless char.map_passable?(x, y, d)
    return true if nx == target_x && ny == target_y
    return !char.collide_with_characters?(nx, ny)
  end

  def add_to_heap(value)
    m = @open_list_items
    @open_list[m] = value
    while m != 1
      if fcost(@open_list[m]) < fcost(@open_list[m / 2])
        temp = @open_list[m / 2]
        @open_list[m / 2] = @open_list[m]
        @open_list[m] = temp
        m /= 2
      else
        break
      end
    end
  end
 
  def delete_from_heap
    return nil if @open_list_items <= 0
    
    next_point = @open_list[0]
    @open_list[0] = @open_list[@open_list_items]
    @open_list_items -= 1
    v = 1
    loop do
      u = v
      w = 2 * u
      if w + 1 <= @open_list_items
        v = w if fcost(@open_list[u - 1]) >= fcost(@open_list[w - 1])
        v = w + 1 if fcost(@open_list[v - 1]) >= fcost(@open_list[w])
      elsif w <= @open_list_items
        v = w if fcost(@open_list[u - 1]) >= fcost(@open_list[w - 1])
      end
      if u != v
        temp = @open_list[u - 1]
        @open_list[u - 1] = @open_list[v - 1]
        @open_list[v - 1] = temp
      else
        break
      end
    end
    
    return nil if next_point.nil?
    return @nodes[next_point, 0], @nodes[next_point, 1]
  end
 
  def fcost(point)
    x = @nodes[point, 0]
    y = @nodes[point, 1]
    return @f_cost[x, y]
  end

end

#==============================================================================
# ** Game_CharacterBase
#==============================================================================
class Game_CharacterBase

  def find_path(target_x, target_y)
    path = $game_map.find_path(target_x, target_y, @x, @y, false, self)
    path = path[0, 1] || []

    @move_route.list.delete_at(@move_route_index)
    path.each { |cmd| @move_route.list.insert(@move_route_index, cmd) }
    @move_route_index -= 1
  end

  def force_path(target_x, target_y)
    return if @x == target_x && @y == target_y
    
    path = $game_map.find_path(target_x, target_y, @x, @y, false, self)
    return if path.nil? || path.empty?

    next_step = path.last
    move_straight(next_step.code)
  end

  def count_iterations(target_x, target_y)
    path = $game_map.find_path(target_x, target_y, @x, @y, true, self)
    return path.size
  end

end
1 Like