#==============================================================================
# Listra Pathfinder Module by Bunga Tepi Jalan
# for RPG Maker
# Version 1.0
#==============================================================================
#
# PART 1
#
#==============================================================================
# Copyrighted by Bunga Tepi Jalan.
# * Don't forget to credit me if you want to use this work
# * You are free to Share - to copy, distribute and transmit the work
# * You are free to Remix - to adapt the work
# * You may not use this work for commercial purposes
#
# Credits
# Wikipedia
# Amit's A* Pages
#
#==============================================================================
# Information:
# This script improves events' Autonomous Movement of type Approach
# such that they will perform smart pathfinding to move towards the player,
# by overriding predefined move_type_toward_player and
# move_toward_player methods in class Game_Character with pathfinding
# algorithm (either Dijkstra's or A*).
#
# To learn about A* and Dijkstra's algorithm, visit the following articles:
# - http://en.wikipedia.org/wiki/Dijkstra's_algorithm
# - http://en.wikipedia.org/wiki/A*_search_algorithm
# - http://www-cs-students.stanford.edu/~amitp/gameprog.html
#
# If you find any bugs or you have any suggestions, please report them via
# e-mail (listra92@gmail.com), or either my blog or these forums:
# - http://bungatepijalan.wordpress.com
# - http://rpgmakerid.com
# - http://prodig.forumotion.net
# - http://vixep.forumotion.com
#==============================================================================
#==============================================================================
# ** PQueue
#------------------------------------------------------------------------------
# This class, as derivation of Array, provides additional operations, such
# as insert and remove element such that the array behaves like a priority
# queue, and also search value in the array. This will be used on
# pathfinding algorithms in MGraph class.
#==============================================================================
class PQueue < Array
#--------------------------------------------------------------------------
# * Add Element, the result array will be always ordered ascendingly
# ii : element to be added into the array
#--------------------------------------------------------------------------
def addEl(ii)
iii = 0
while iii < self.length && self[iii] < ii
iii += 1
end
self.insert(iii,ii)
end
#--------------------------------------------------------------------------
# * Remove Element
# ii : element to be removed from the array, the result remains if
# ii isn't found in the array
#--------------------------------------------------------------------------
def remEl(ii)
iii = 0
while iii < self.length && self[iii] < ii
iii += 1
end
self.delete_at(iii)
end
#--------------------------------------------------------------------------
# * Is Element Found?
# ii : element to be searched in the array (using binary search)
# Returns true if ii found in the array
#--------------------------------------------------------------------------
def found?(ii)
i = 0
j = self.length-1
ff = false
while (not ff) && i <= j
mid = (i+j)/2
if self[mid] == ii
ff = true
else
if self[mid] < ii
i = mid+1
else
j = mid-1
end
end
end
return ff
end
end
#==============================================================================
# ** MGraph
#------------------------------------------------------------------------------
# This class generates a passage graph of given 2-dimensional map and uses it
# for pathfinding analysis with Dijkstra's algorithm and A*. Refer to
# "$mgraph" for the instance of this class.
#==============================================================================
class MGraph
#--------------------------------------------------------------------------
# * Public Instance Variables
#--------------------------------------------------------------------------
attr_accessor :w, :h
attr_accessor :neighbors
attr_accessor :passage_table
#--------------------------------------------------------------------------
# * Map Graph Initialization
#--------------------------------------------------------------------------
def initialize(nh)
@w = $game_map.width
@h = $game_map.height
@neighbors = nh
# Passage table for each map nodes
@passage_table = Table.new(@w,@h,nh)
for i in 0..@w
for j in 0..@h
for k in 1..nh
@passage_table[i,j,k-1] = $game_map.passable2?(i,j,k*2) ? 1 : 0
if not neighborExist?(nodeOf(i,j),k)
@passage_table[i,j,k-1] = 0
end
end
end
end
end
#--------------------------------------------------------------------------
# * Node/Vertex Of
# x : x-position
# y : y-position
#--------------------------------------------------------------------------
def nodeOf(x,y)
return y*@w+x+1
end
#--------------------------------------------------------------------------
# * xNode, yNode
# idxNode : index of node
#--------------------------------------------------------------------------
def xNode(idxNode)
return (idxNode-1)%@w
end
def yNode(idxNode)
return (idxNode-1)/@w
end
#--------------------------------------------------------------------------
# * Neighbor Of
# idxNode : index of node
# dir : down(1), left(2), right(3), or up(4)
# Returns index of adjacent node idxNode
#--------------------------------------------------------------------------
def neighborOf(idxNode,dir)
case dir
when 1
return (idxNode+@w)
when 2
return (idxNode-1)
when 3
return (idxNode+1)
when 4
return (idxNode-@w)
end
end
#--------------------------------------------------------------------------
# * Is Neighbor Exist?
# idxNode : index of node
# dir : down(1), left(2), right(3), or up(4)
# Returns if adjacent node of idxNode exists
#--------------------------------------------------------------------------
def neighborExist?(idxNode,dir)
case dir
when 1
return (yNode(idxNode)<@h-1)
when 2
return (xNode(idxNode)>0)
when 3
return (xNode(idxNode)<@w-1)
when 4
return (yNode(idxNode)>0)
end
end
#--------------------------------------------------------------------------
# * Reconstruct Path
# s : source node
# t : target node
# vertices_prev : map of navigated nodes
# Returns movement direction 1(down), 2(left), 3(right), or 4(up)
#--------------------------------------------------------------------------
def reconstruct_path(s,t,vertices_prev)
u=t
while vertices_prev[u] != s && vertices_prev[u] != 0
u = vertices_prev[u]
end
case u
when s+@w
return 1
when s-1
return 2
when s+1
return 3
when s-@w
return 4
end
return 0
end
#--------------------------------------------------------------------------
# * Heuristic Distance
# u : node 1
# v : node 2
#--------------------------------------------------------------------------
def heuristic_dist(u,v)
dx = xNode(v)-xNode(u)
dy = yNode(v)-yNode(u)
# Manhattan distance heuristic
return (dx.abs+dy.abs)
end
#--------------------------------------------------------------------------
# * Dijkstra's Algorithm
# x1, y1 : source coordinate
# x2, y2 : target coordinate
# Returns movement towards target 1(down), 2(left), 3(right), or 4(up)
#--------------------------------------------------------------------------
def Dijkstra(x1, y1, x2, y2)
# Initializations
s = nodeOf(x1,y1)
t = nodeOf(x2,y2)
# Create open list q (as priority queue)
q = PQueue.new(1,0)
# Add s into open list q
q.addEl(s)
# Unknown distance function from source to v
vertices_dist = Array.new(@w*@h+1,9999)
# Previous node in optimal path from source
vertices_prev = Array.new(@w*@h+1,0)
# Distance from source to source
vertices_dist[s] = 0
# The main loop
while q.length > 1
# Finds vertex u with least value of path distance
d = vertices_dist[q[1]]
u = q[1]
if q.length>2
for ii in 2..q.length-1
if vertices_dist[q[ii]]<d
d = vertices_dist[q[ii]]
u = q[ii]
end
end
end
# Search completed
if u == t
return reconstruct_path(s,t,vertices_prev)
end
# Remove u from open list q
q.remEl(u)
for i in 1..@neighbors
if @passage_table[xNode(u),yNode(u),i-1] == 1
v = neighborOf(u,i)
alt = vertices_dist[u]+1
if alt < vertices_dist[v]
# Relax (u,v)
q.addEl(v)
vertices_dist[v]=alt
vertices_prev[v]=u
end
end
end
end
return 0
end
#--------------------------------------------------------------------------
# * A* Algorithm
# x1, y1 : source coordinate
# x2, y2 : target coordinate
# Returns movement towards target 1(down), 2(left), 3(right), or 4(up)
#--------------------------------------------------------------------------
def AStar(x1, y1, x2, y2)
# Initializations
s = nodeOf(x1,y1)
t = nodeOf(x2,y2)
# Create open list q (as priority queue)
q = PQueue.new(1,0)
# Add s into open list q
q.addEl(s)
# Create closed list q1, The list of nodes already evaluated.
q1 = Array.new(@w*@h+1,false)
# The map of navigated nodes.
vertices_prev = Array.new(@w*@h+1,0)
# Unknown distance function from source to v
vertices_dist = Array.new(@w*@h+1,9999)
h_score = Array.new(@w*@h+1,0)
f_score = Array.new(@w*@h+1,9999)
# Distance from source to source
vertices_dist[s] = 0
h_score[s] = heuristic_dist(s,t)
f_score[s] = h_score[s]
# The main loop
while q.length > 1
# Finds vertex u with least value of f_score
d = f_score[q[1]]
u = q[1]
if q.length>2
for ii in 2..q.length-1
if f_score[q[ii]]<d
d = f_score[q[ii]]
u = q[ii]
end
end
end
# Search completed
if u == t
return reconstruct_path(s,t,vertices_prev)
end
# Move current node from open list to the closed list
q.remEl(u)
q1[u] = true
for i in 1..@neighbors
if @passage_table[xNode(u),yNode(u),i-1] == 1
v = neighborOf(u,i)
if !q1[v]
tentative_g_score = vertices_dist[u]+1
if !q.found?(v)
q.addEl(v)
tentative_is_better = true
elsif tentative_g_score < vertices_dist[v]
tentative_is_better = true
else
tentative_is_better = false
end
if tentative_is_better
if vertices_prev[v] != 0
if f_score[u] < f_score[vertices_prev[v]]
vertices_prev[v] = u
end
else
vertices_prev[v] = u
end
vertices_dist[v] = tentative_g_score
h_score[v] = heuristic_dist(v,t)
f_score[v] = vertices_dist[v]+h_score[v]
end
end
end
end
end
return 0
end
end