Chaos Project

RPG Maker => RPG Maker Scripts => RMXP Script Database => Topic started by: mad.array on January 16, 2012, 08:22:18 pm

Title: [XP] TOA Caterpillar & Pathfinding
Post by: mad.array on January 16, 2012, 08:22:18 pm
TOA Caterpillar, Move Routes & Pathfinding
Authors: mad.array, using Advanced Pathfinding by ForeverZer0
Version: 0.5a
Type: Movement Addon
Key Term: Movement Addon



Introduction

Well, I've gone and mucked about with a perfectly good script. Again. Two perfectly good scripts all be told. This script allows you to use the in built move route commands with the tons_of_addons caterpillar feature. It also allows caterpillar actors to behave like events whilst they are away from the player. It is in a very rough stage at the moment so some testing and feedback will be required.

It uses my modified Advanced Pathfinding & Carrots script, which has been modified some more. I'm surprised no-one has asked about the carrot thing, but it's gone 1 in the morning so I'm surprised by a lot of things.


Features




Screenshots

Not really a screenshot script.



Script

Goes below toa scripts (required). Not compatible with other Pathfinding scripts.

Spoiler: ShowHide

#=begin
#+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
# Advanced Pathfinding + Carrots with Caterpillar Support
# Author: ForeverZer0, mad.array
# Version: 1.0b
# Date: 10.12.2011
#+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
#
# Introduction:
#   This is an advanced an highly inteligent pathfinding system. It allows for
#   the user to either through script or script call quickly and easily have
#   events or the game player automatically walk a path to a given set of
#   coordinates. The system is smart enough to quickly find paths through
#   relatively complex areas, and asjust on the fly for any obstacle that moves
#   to block its path. I used the A* algorithm, basic search algorithm used
#   often for robotics. More on this algorithm can be read about here:
#
#               http://en.wikipedia.org/wiki/A*_search_algorithm
#
#
#   In addition, the modifications to the script allow for events to navigate to
#   other events via their event name. If more than one event with a name exists
#   it will pick one at random. You can also define multiple event names to pick
#   from in an array.
#
#   In a further addition, caterpillar actors (henceforthe referred to as
#   members) can also be moved by 'syncing' them to an event on the map. When
#   this is done, they will move instead of that event. They will also perform
#   the actions of that event when the trigger conditions are met.
#
# Features:
#   - Fast and intelligent pathfinding
#   - Easy to use script calls
#   - Optional "range" parameter can have character find alternate locations
#     if the preferred one is blocked and they are within the given range.
#   - Optional callbacks can be given to have something execute if when the
#     character reaches its goal, or when it fails to do so.
#
# Instructions:
#   - Place script below default scripts and tons_of_addons, and above "Main".
#   - Use the following script call:
#
#     pathfind(X, Y, CHARACTER, RANGE, SUCCESS_PROC, FAIL_PROC)
#    
#     The X and Y are the only required arguments. The others can be omitted.
#    
#     X - The x-coordinate to pathfind to. If X is a string or array then
#         strings must be contained within "quotation marks" and the array
#         within [square brackets].
#     Y - The y-coordinate to pathfind to. If using a string or array, DO NOT
#         leave Y blank. It will cause the script to crash.
#
#     CHARACTER - Either an instance of the character ($game_player,
#                 $game_map.events[ID], etc) or the ID of a character. The ID
#                 will be the event ID. Use -1 for the game player.
#
#     SUCCESS_PROC - A Proc object that will be executed when the player
#                    reaches the defined coordinates.
#     FAILURE_PROC - A Proc object that will be executed when the player
#                    cannot reach the defined coordinates.
#
#   - As default, the pathfinder will make 35 attempts to recalculate a route
#     that gets blocked. This value can be changed in game with the script
#     call:
#           $game_map.collision_retry = NUMBER
#
#     You can change the default value if desired by looking down to the first
#     class below in the main script.
#   - For longer pathfind routes, it is sometimes necessary to reset the
#     search limiter. This may cause increased lag when an object blocks the
#     character from being able to move, but will increase the range that the
#     system can work with. Use the following script call:
#
#         $game_map.search_limiter = NUMBER  (Default 1000)
#
#   - If you are experiencing any compatibility problems, go to the Game_Map
#     class below and set @recalculate_paths to false. This will take away some
#     of the efficiency of recalculating collisions, but will improve may fix
#     your problem.
#   - When you want a member to move, you have to sync them to an event. This
#     can be done by using the following script call:
#
#         $game_player.members[i].synced = i
#
#     Where i is the number of the event your member will use for move commands
#     and interaction.
#
#   - When returning a member to the train, have an event commands like this;
#    
#       Script: z = $game_player.calc_return_path(i)
#               pathfind(z[0],z[1],2,0)
#               Wait for moves completion
#       Script: $game_player.members[i].synced = nil
#
#     Where i is the index of the member(0 to 2)
#      
#
# Compatibility:
#   Highly compatible. May experience issues with Custom Movement scripts,
#   but even then, it is unlikely. Also modifies Interpreter command 209... as
#   in not aliased, I had to copy and paste and change modified. Sorry!
#
# Credits/Thanks:
#   - ForeverZer0, for the AP script
#   - Blizzard, for the caterpillar scipt and general awesomeness.
#   - Special thanks to Jragyn for help making the big maze for the demo and
#     help testing.
#   - Credit goes to the Peter Hart, Nils Nilsson and Bertram Raphael for the
#     original search algorithm that was implemented
#
#+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+

class Game_System
 
 alias init_toa_later initialize
 def initialize
   init_toa_later
   @CATERPILLAR                = true
end

 attr_accessor :CATERPILLAR
end

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

class Game_Map
 
 attr_accessor :collision_retry
 attr_accessor :recalculate_paths
 attr_accessor :search_limiter
 
 alias zer0_pathfinding_init initialize
 def initialize
   # Initialize instance variables used for pathfinding.
   @collision_retry = 35
   @recalculate_paths = true
   @search_limiter = 1000
   # Original method
   zer0_pathfinding_init
 end
end

#===============================================================================
# ** Interpreter
#===============================================================================

class Interpreter
 
 def pathfind(x, y, *args)
   @x = x
   @y = y
   args[0] = @event_id if args[0] == nil
   args[1] = 0 if args[1] == nil
   if @x.is_a?(Array)
     decidepath(@x, @y, @args)
   end
   findpathevent(@x,@y, args)
   Pathfind.new(Node.new(@x, @y), *args)
 end

 def decidepath(x, y, args)
   r = rand(100) + 1
   for i in 0...x.size
     targ1 = 1 + (100/x.size) * i
     targ2 = (100/x.size) * (i +1)
     if r >= targ1 && r <=targ2
       @x=x[i]
     end
   end
 end

 def findpathevent(x, y, args)
   xs = []
   ys = []
   proxx = []
   proxy = []
   distance = []
   chosen = nil
   if x.is_a?(String)
     for event in $game_map.events.values
       if event.name == x
         xs.push(event.x)
         ys.push(event.y)
       end
     end
     if args[0].is_a?(Integer)
     if args[0] >  0
       runnerx = $game_map.events[args[0]].x
       runnery = $game_map.events[args[0]].y
     else
       runnerx = $game_player.x
       runnery = $game_player.y
     end
   end
   if args[0].is_a?(Game_Character)
     char = args[0]
     runnerx = char.x
     runnery = char.y
   end
     if xs.size > 1
       for i in 0...xs.size
         proxx[i] = runnerx - xs[i]
         proxy[i] = runnery - ys[i]
         proxx[i] *= -1 if proxx[i] <0
         proxy[i] *= -1 if proxy[i] <0
         if proxx[i] > proxy[i]
           distance[i] = proxx[i] - proxy[i]
         elsif proxx[i] < proxy[i]
           distance[i] = proxy[i] - proxx[i]
         elsif proxx[i]==0
           distance[i] = proxy[i]
         elsif proxy[i]==0 || proxy[i] == proxx[i]
           distance[i] = proxx[i]
         end
       end
       j = 0
       while chosen == nil
         chosen = distance.index(j)
         j+=1
       end
         @x = xs[chosen]
         @y = ys[chosen]
     else
       @x = xs[0]
       @y = ys[0]
     end
   end
 end
end


class Game_Event
 
 attr_accessor :direction_fix
 attr_accessor :step_anime
 attr_accessor :walk_anime
 attr_accessor :always_on_top
 
def name
  return @event.name
end
end

#==============================================================================
# ** Pathfind
#==============================================================================

class Pathfind

 attr_reader   :route                  
 attr_accessor :range  
 attr_reader   :goal
 attr_reader   :found
 attr_reader   :character                                
 attr_accessor :success_proc          
 attr_accessor :failure_proc        
 attr_accessor :target    
 attr_accessor :collisions      
 
 def initialize(node, char = -1, range = 0, *callbacks)
   # Set the character. Can either us an ID or an instance of a Game_Character.
   # A value of -1, which is default, is the Game_Player.
   if char.is_a?(Integer)
     @character = (char == -1) ? $game_player : $game_map.events[char]
     $game_player.members.each {|member|
       if member.synced==char
         member.frozen = false
         @character = member
       end}
   elsif char.is_a?(Game_Character)
     @character = char
   end
   # Set forcing flag. Will be disabled for recalculating on the fly.
   @forcing = true
   # Call a public method, since this method may need to be used again,
   # and "initialize" is private.
   setup(node, range, *callbacks)
 end
 
 
 def setup(node, range = 0, *callbacks)
   # Initialize the node we are trying to get to.
   @target = Node.new(node.x, node.y)
   @goal = @target.clone
   # Set beginning nodes and required variables.
   @start_node = Node.new(@character.x, @character.y)
   @nearest = Node.new(0, 0, 0, -1)
   @range, @found, @collisions = range, false, 0
   # Set callbacks for success and failure if included, else nil.
   @success_proc = callbacks[0]
   @failure_proc= callbacks[1]
   # Initialize sets to track open and closed nodes
   @open_set, @close_set = [@start_node], {}  
   # Find the optimal path
   calculate_path  
 end

 def calculate_path
   # Only do calculation if goal is actually passable, unless we only
   # need to get close or within range
   if @character.passable?(@goal.x, @goal.y, 0) || @range > 0 || @character.is_a?(Game_Member)
     # Initialize counter
     counter, wait = 0, 0
     until @open_set.empty?
       counter += 1
       # Give up if pathfinding is taking more than 500 iterations
       if counter >= $game_map.search_limiter
         @found = false
         break
       end
       # Get the node with lowest cost and add it to the closed list
       @current_node = get_current
       @close_set[[@current_node.x, @current_node.y]] = @current_node
       if @current_node == @goal ||
          (@range > 0 && @goal.in_range?(@current_node, @range) &&
          !@character.passable?(@goal.x, @goal.y,0))
         # We reached goal, exit the loop!
         @target = @goal
         @goal, @found = @current_node, true
         break
       else # if not goal
         # Keep track of the node with the lowest cost so far
         if @current_node.heuristic < @nearest.heuristic ||
           @nearest.heuristic < 1
           @nearest = @current_node
         end
         # Get adjacent nodes and check if they can be added to the open list
         neighbor_nodes(@current_node).each {|neighbor|
           # Skip Node if it already exists in one of the lists.
           next if can_skip?(neighbor)
           # Add node to open list following the binary heap conventions
           @open_set.push(neighbor)
           arrange(@open_set.size - 1)
         }
       end
     end
   end
   # If no path was found, see if we can get close to goal
   unless @found
     if @range > 0 && @nearest.heuristic > 0  
       # Create an alternate path.
       setup(@nearest, @range, @success_proc, @failure_proc)
     elsif @failure_proc != nil && (($game_map.collision_retry == 0) ||
       (@collisions > $game_map.collision_retry))
       # If out of retries, call the Proc for failure if defined
       @failure_proc.call
     end
   end
   # Create the move route using the generated path
   create_move_route
 end

 def create_move_route
   # There's no path to generate if no path was found
   return if !@found
   # Create a new move route that isn't repeatable
   @route = RPG::MoveRoute.new
   @route.repeat = false
   # Generate path by starting from goal and following parents
   node = @goal
   while node.parent
     # Get direction from parent to node as RPG::MoveCommand
     code = case direction(node.parent.x, node.parent.y, node.x, node.y)
     when 2 then 4 # Up
     when 4 then 3 # Left
     when 6 then 2 # Right
     when 8 then 1 # Down
     else; 0
     end
     # Add movement code to the start of the array
     @route.list.unshift(RPG::MoveCommand.new(code)) if code != 0
     node = node.parent
   end
   # If the path should be assigned to the character
   if (@forcing && !@route.list.empty?)
     @collisions = 0
     @character.paths.push(self)
     @character.force_move_route(@route) if @character.paths.size == 1
   end
   # Reset forcing flag if needed
   @forcing = true
   # Return the constructed RPG::MoveRoute
   return @route
 end
 
 def arrange(index)
   # Rearrange nodes in the open_set
   while index > 0
     # Break loop unless current item's cost is less than parent's
     break if @open_set[index].score > @open_set[index / 2].score
     # Bring lowest value to the top.
     temp = @open_set[index / 2]
     @open_set[index / 2] = @open_set[index]
     @open_set[index] = temp
     index /= 2
   end
 end
 
 def get_current
   return if @open_set.empty?
   return @open_set[0] if @open_set.size == 1
   # Set current node to local variable and replace it with the last
   current = @open_set[0]
   @open_set[0] = @open_set.pop
   # Loop and rearrange array according to the A* algorithm until done.
   y = 0  
   loop {
     x = y
     # If two children exist
     if 2 * x + 1 < @open_set.size
       if @open_set[2 * x].score <= @open_set[x].score
         y = 2 * x
         if @open_set[2 * x + 1].score <= @open_set[y].score
           y = 2 * x + 1
         end
       end
     # If only one child exists
     elsif 2 * x < @open_set.size &&
       @open_set[2 * x].score <= @open_set[x].score
       y = 2 * x
     end
     # Swap a child if it is less than the parent.
     break if x == y
     temp = @open_set[x]
     @open_set[x] = @open_set[y]
     @open_set[y] = temp
   }
   # Return the original first node (which was removed)
   return current
 end

 def direction(x1, y1, x2, y2)
   # Return the numerical direction between coordinates.
   return 6 if x1 > x2 # Right
   return 4 if x1 < x2 # Left
   return 2 if y1 > y2 # Bottom
   return 8 if y1 < y2 # Top
   return 0            
 end
 
 def neighbor_nodes(node)
   # Create array to hold the nodes, then check each direction.
   nodes = []
   nodes.push(get_neighbor(node.x + 1, node.y, node)) # Right
   nodes.push(get_neighbor(node.x - 1, node.y, node)) # Left
   nodes.push(get_neighbor(node.x, node.y + 1, node)) # Down
   nodes.push(get_neighbor(node.x, node.y - 1, node)) # Up
   # Remove any nil elements, then return results.
   return nodes.compact
 end
 
 def get_neighbor(x, y, parent)
   # Calculate direction, return new node if passable.
   direction = direction(x, y, parent.x, parent.y)
   if @character.passable?(parent.x, parent.y, direction)
     # The heuristic is simply the distance
     heuristics = ((x - @goal.x).abs + (y - @goal.y).abs)
     return Node.new(x, y, parent, parent.cost + 1, heuristics)
   end
 end
 
 def can_skip?(node)
   # Branch by if node is in either the open or closed set.
   if @open_set.include?(node)
     index = @open_set.index(node)
     return true if @open_set[index].score <= node.score
     # Swap them and update list order
     @open_set[index] = node
     arrange(index)
     return true
   elsif @close_set[[node.x, node.y]] != nil
     # If the existing passed node has a lower score than this one.
     return true if @close_set[[node.x, node.y]].score <= node.score
     # Update the existing node
     @close_set[[node.x, node.y]] = node
   end
   # Return false if no criteria was met.
   return false
 end
end

#==============================================================================
# ** Game_Character
#==============================================================================

class Game_Character
 
 attr_accessor :paths
 attr_accessor :move_route_forcing
 attr_accessor :move_route

 alias zer0_pathfinding_init initialize
 def initialize
   # Add public instance variable for paths
   @paths = []
   # Original method
   zer0_pathfinding_init
 end
 
 def next_route
   # Stop any custom move route that may be occuring.
   if @move_route != nil
     # Set index and disable forcing of current route
     @move_route_index = @move_route.list.size
     @move_route_forcing = false
     # Reset to what it was originally
     @move_route = @original_move_route
     @move_route_index = @original_move_route_index
     @original_move_route = nil
   end
   # Remove first path from the paths array.
   @paths.shift
   # If there is another path to follow...
   if @paths[0] != nil
     # Setup path again to reflect any changes since original creation
     @forcing = false
     @paths[0].setup(@paths[0].target, @paths[0].range,
       @paths[0].success_proc, @paths[0].failure_proc)
     force_move_route(@paths[0].route) if @paths[0].found
   end
 end
 
 alias zer0_recalculate_paths_move move_type_custom
 def move_type_custom
   if $game_map.recalculate_paths
     # Interrupt if not stopping
     return if jumping? || moving?
     # Loop until finally arriving at move command list
     while @move_route_index < @move_route.list.size
       # Get the move command at index
       command = @move_route.list[@move_route_index]
       # If command code is 0 (end of list)
       if command.code == 0
         # If [repeat action] option is ON
         if @move_route.repeat
           # Reset move route index to the top of the list
           @move_route_index = 0
         end
         # If [repeat action] option is OFF
         unless @move_route.repeat
           # If move route is forced and not repeating
           if @move_route_forcing and not @move_route.repeat
             # The move route is no longer forced (moving ended)
             @move_route_forcing = false
             # Restore original move route
             @move_route = @original_move_route
             @move_route_index = @original_move_route_index
             @original_move_route = nil
             # If there was a path to follow and we reached goal
             if @paths[0] != nil
               if self.x == @paths[0].goal.x &&
                 y == @paths[0].goal.y && @paths[0].success_proc
                 # Call success Proc if goal is reached and it is defined.
                 @paths[0].success_proc.call
               end
               next_route
             end
           end
           # Clear stop count
           @stop_count = 0
         end
         return
       end # if command.code == 0
       # For move commands (from move down to jump)
       if command.code <= 14
         # Branch by command code
         case command.code
         when 1 then move_down                 # Move down
         when 2 then move_left                 # Move left
         when 3 then move_right                # Move right
         when 4 then move_up                   # Move up
         when 5 then move_lower_left           # Move lower left
         when 6 then move_lower_right          # Move lower right
         when 7 then move_upper_left           # Move upper left
         when 8 then move_upper_right          # Move upper right
         when 9 then move_random               # Move random
         when 10 then move_toward_player       # Move toward player
         when 11 then move_away_from_player    # Move away from player
         when 12 then move_forward             # Step forward
         when 13 then move_backward            # Step backward
         when 14 then jump(command.parameters[0], command.parameters[1]) # Jump
         end
         # If movement failure occurs when "Ignore If Can't Move" is unchecked.
         if !@move_route.skippable && !moving? && !jumping?
           # If path is current and collision limit is not reached
           if @paths[0] != nil &&
             @paths[0].collisions < $game_map.collision_retry
             # Setup path again to update starting location.
             # original goal node is used because pathfinding changes
             # the goal node to current node
             goal, range = @paths[0].target, @paths[0].range
             reach = @paths[0].success_proc
             fail = @paths[0].failure_proc
             counter = @paths[0].collisions + 1
             # Find another path to goal
             @paths[0] = Pathfind.new(goal, self, range, reach, fail)
             @paths[0].collisions = counter
             force_move_route(@paths[0].route) if @paths[0].found
             # Wait a bit before starting to follow the new path
             @wait_count = 6
             return
           elsif paths[0] != nil
             # Call failure Proc if defined and set move index.
             @move_route_index = @move_route.list.size
             @paths[0].failure_proc.call if @paths[0].failure_proc != nil
             next_route
           end
           # End method
           return
         end
         # Advance index
         @move_route_index += 1
         return
       end # if command.code <= 14
       # If waiting
       if command.code == 15
         # Set wait count (from provided parameter)
         @wait_count = command.parameters[0] * 2 - 1
         @move_route_index += 1
         return
       end # if command.code == 15
       # If direction change (turning) command
       if command.code >= 16 and command.code <= 26
         # Branch by command code
         case command.code
         when 16 then turn_down                      # Turn down
         when 17 then turn_left                      # Turn left
         when 18 then turn_right                     # Turn right
         when 19 then turn_up                        # Turn up
         when 20 then turn_right_90                  # Turn 90° right
         when 21 then turn_left_90                   # Turn 90° left
         when 22 then turn_180                       # Turn 180°
         when 23 then turn_right_or_left_90          # Turn 90° right or left
         when 24 then turn_random                    # Turn at Random
         when 25 then turn_toward_player             # Turn toward player
         when 26 then turn_away_from_player          # Turn away from player
         end
         @move_route_index += 1
         return
       end
       # If other command (commands that don't 'return')
       if command.code >= 27
         # Branch by command code
         case command.code
         when 27                                              # Switch ON
           $game_switches[command.parameters[0]] = true
           $game_map.need_refresh = true
         when 28                                              # Switch OFF
           $game_switches[command.parameters[0]] = false
           $game_map.need_refresh = true
         when 29 then @move_speed = command.parameters[0]     # Change speed
         when 30 then @move_frequency = command.parameters[0] # Change freq
         when 31 then @walk_anime = true                      # Move ON
         when 32 then @walk_anime = false                     # Move OFF
         when 33 then @step_anime = true                      # Stop ON
         when 34 then @step_anime = false                     # Stop OFF
         when 35 then @direction_fix = true                   # Direction ON
         when 36 then @direction_fix = false                  # Direction OFF
         when 37 then @through = true                         # Through ON
         when 38 then @through = false                        # Through OFF
         when 39 then @always_on_top = true                   # On top ON
         when 40 then @always_on_top = false                  # On top OFF
         when 41                                              # Change Graphic
           # Can't change into a tile
           @tile_id = 0
           @character_name = command.parameters[0]
           @character_hue = command.parameters[1]
           # Update direction
           if @original_direction != command.parameters[2]
             @direction = command.parameters[2]
             @original_direction = @direction
             @prelock_direction = 0
           end
           # Update frame
           if @original_pattern != command.parameters[3]
             @pattern = command.parameters[3]
             @original_pattern = @pattern
           end
         when 42 then @opacity = command.parameters[0]        # Change Opacity
         when 43 then @blend_type = command.parameters[0]     # Change Blending
         when 44 then $game_system.se_play(command.parameters[0]) # Play SE
         when 45 then result = eval(command.parameters[0])    # Script
         end
         # Increment move index.
         @move_route_index += 1
       end
     end
   else
     # Original method
     zer0_recalculate_paths_move
   end
 end
end

#==============================================================================
# ** Node
#==============================================================================

class Node

 attr_accessor :x                      
 attr_accessor :y                      
 attr_accessor :parent                  
 attr_accessor :cost                
 attr_accessor :heuristic                  

 def initialize(x, y, parent = nil, cost = 0, heuristic = 0)
   # Set public instance variables.
   @x, @y, @parent, @cost, @heuristic = x, y, parent, cost, heuristic
 end

 def score
   # Return the current "score" of this node
   return @cost + @heuristic
 end
 
 def in_range?(node, range)
   # Return true/false if Nodes are within RANGE of each other.
   return (@x - node.x).abs + (@y - node.y).abs <= range
 end

 def ==(node)
   # Returns true/false of whether self and other are equal.
   return ((node.is_a?(Node)) && (node.x == @x) && (node.y == @y))
 end
end

#:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=
# Caterpillar by Blizzard
# Version: 2.2b
# Type: Game Experience Improvement
# Date: 7.3.2007
# Date v1.01b: 7.3.2007
# Date v2.0: 7.8.2007
# Date v2.0b: 8.8.2007
# Date v2.1b: 17.2.2008
# Date v2.11b: 22.2.2008
# Date v2.2b: 1.11.2008
#:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=
#
# new in v1.01b:
#   - now events can't go through your party members anymore
#
# new in v2.0:
#   - completely overworked and improved
#
# new in v2.0b:
#   - improved coding and made it work more convenient
#   - fixed a few glitches
#   - added DEAD_DISPLAY option
#   - removed stop animation override upon the player character
#
# new in v2.1b:
#   - fixed bug where an empty party would cause a crash
#
# new in v2.11b:
#   - fixed a minor compatibility glitch with Blizz-ABS
#
# new in v2.2b:
#   - improved coding and made it work more convenient
#   - fixed the bug where deactivating the caterpillar wouldn't work properly
#
#
# Compatibility:
#
#   93% compatible with SDK v1.x. 60% compatible with SDK v2.x. You might
#   experience problems with pixel movement scripts or map graphic manipulating
#   scripts. Blizz-ABS disables this add-on automatically and uses the
#   Blizz-ABS Caterpillar system.
#
#
# Features:
#
#   - your party members follow you on the map
#   - less code than other caterpillar scripts
#   - use $game_player.update_buffer('reset') if you need all party members to
#     gather around the player
#:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=

#::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
# START Configuration
#::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

# max number of party members
MAX_PARTY = 4
# actor IDs where the actor is animated even when not walking
ANIMATED_IDS = []
# 0 - shows all characters; 1 - shows "ghosts"; 2 - removes from caterpillar
DEAD_DISPLAY = 0

#::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
# END Configuration
#::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

#==============================================================================
# Game_Character
#==============================================================================
   
class Game_Character
 
 alias passable_caterpillar_later? passable?
 def passable?(x, y, d)
   result = passable_caterpillar_later?(x, y, d)
   new_x = x + (d == 6 ? 1 : d == 4 ? -1 : 0)
   new_y = y + (d == 2 ? 1 : d == 8 ? -1 : 0)
   unless @through
     $game_player.members.each {|member|
         if member.character_name != '' && member.x == new_x && member.y == new_y && member.through==false
           return false
         end}
   end
   return result if $BlizzABS && BlizzABS::VERSION >= 1.01
   return result if self.is_a?(Game_Player) || self.is_a?(Game_Member)
   new_x = x + (d == 6 ? 1 : d == 4 ? -1 : 0)
   new_y = y + (d == 2 ? 1 : d == 8 ? -1 : 0)
   unless @through
     $game_player.members.each {|member|
         if member.character_name != '' && member.x == new_x && member.y == new_y
           return false
         end}
   end
   return result
 end
 
end

#==============================================================================
# Game_Player
#==============================================================================
   
class Game_Player
 
 attr_reader :members
 attr_reader :move_speed
 attr_accessor :active_member
 attr_accessor :return_buffer
 
 alias init_caterpillar_later initialize
 def initialize
   init_caterpillar_later
   @active_member = true
   @members = []
   @return_buffer = []
   @lastdirection = 0
   @directionpause = 4
   @directionframe = 0
   (1...MAX_PARTY).each {|i| @members.push(Game_Member.new(i))} unless $BlizzABS
 end
 
 alias upd_caterpillar_later update
 def update
   upd_caterpillar_later
   refresh if DEAD_DISPLAY > 0
   @members.each {|member| member.update}
   if $game_system.CATERPILLAR && ANIMATED_IDS.include?(actor.id)
     @step_anime = true
   end
 end
 
 alias straighten_caterpillar_later straighten
 def straighten
   straighten_caterpillar_later
   @members.each {|member| member.straighten}
 end
 
 alias refresh_caterpillar refresh
 def refresh
   unless $game_system.CATERPILLAR
     refresh_caterpillar
     return
   en d
   act, $game_party.actors[0] = $game_party.actors[0], actor
   $game_party.actors.pop if $game_party.actors[0] == nil
   refresh_caterpillar
   return if actor == nil
   $game_party.actors[0] = act
   if actor.dead? && DEAD_DISPLAY == 1
     @opacity = Graphics.frame_count % 4 / 2 * 255
     @blend_type = 1
   end
 end
 
 def actor
   if DEAD_DISPLAY > 0
     $game_party.actors.each {|actor| return actor unless actor.dead?}
   end
   return $game_party.actors[0]
 end
 
 def update_buffer(next_move)
   if next_move == nil
     @members.each {|member| member.buffer = []}
   else
     @members.each {|member| member.update_buffer(
         next_move == 'reset' ? nil : next_move)}
   end
 end
 
 def update_return_buffer(move)
   return_buffer.push(move)
   if return_buffer.size > 3
     return_buffer.delete_at(0)
   end
 end
 
 alias move_down_caterpillar_later move_down
 def move_down(turn_enabled = true)
   update_buffer(2) if passable?(@x, @y, 2)
   update_return_buffer(2) if passable?(@x, @y, 2)
   move_down_caterpillar_later
 end
 
 alias move_left_caterpillar_later move_left
 def move_left(turn_enabled = true)
   update_buffer(4) if passable?(@x, @y, 4)
   update_return_buffer(4) if passable?(@x, @y, 4)
   move_left_caterpillar_later
 end
 
 alias move_right_caterpillar_later move_right
 def move_right(turn_enabled = true)
   update_buffer(6) if passable?(@x, @y, 6)
   update_return_buffer(6) if passable?(@x, @y, 6)
   move_right_caterpillar_later
 end
 
 alias move_up_caterpillar_later move_up
 def move_up(turn_enabled = true)
   update_buffer(8) if passable?(@x, @y, 8)
   update_return_buffer(8) if passable?(@x, @y, 8)
   move_up_caterpillar_later
 end
 
 alias move_lower_left_caterpillar_later move_lower_left
 def move_lower_left
   if passable?(@x, @y, 2) && passable?(@x, @y + 1, 4) ||
      passable?(@x, @y, 4) && passable?(@x - 1, @y, 2)
     update_buffer(1)
   end
   move_lower_left_caterpillar_later
 end
 
 alias move_lower_right_caterpillar_later move_lower_right
 def move_lower_right
   if passable?(@x, @y, 2) && passable?(@x, @y + 1, 6) ||
      passable?(@x, @y, 6) && passable?(@x + 1, @y, 2)
     update_buffer(3)
   end
   move_lower_right_caterpillar_later
 end
 
 alias move_upper_left_caterpillar_later move_upper_left
 def move_upper_left
   if passable?(@x, @y, 8) && passable?(@x, @y - 1, 4) ||
      passable?(@x, @y, 4) && passable?(@x - 1, @y, 8)
     update_buffer(7)
   end
   move_upper_left_caterpillar_later
 end
 
 alias move_upper_right_caterpillar_later move_upper_right
 def move_upper_right
   if passable?(@x, @y, 8) && passable?(@x, @y - 1, 6) ||
      passable?(@x, @y, 6) && passable?(@x + 1, @y, 8)
     update_buffer(9)
   end
   move_upper_right_caterpillar_later
 end
 
 alias jump_caterpillar_later jump
 def jump(x_plus, y_plus)
   if (x_plus != 0 || y_plus != 0) && passable?(@x + x_plus, @y + y_plus, 0)
     update_buffer([x_plus, y_plus])
   end
   jump_caterpillar_later(x_plus, y_plus)
 end
 
 alias moveto_caterpillar moveto
 def moveto(x, y)
   update_buffer(nil)
   moveto_caterpillar(x, y)
   @members.each {|member|
   unless member.frozen==true
       member.moveto(x, y)
       case @direction
         when 2 then member.turn_down
         when 4 then member.turn_left
         when 6 then member.turn_right
         when 8 then member.turn_up
       end
   end}
 end
 
 def syncevent(member,eventid)
   mevent = $game_player.members[member]
   event = $game_map.events[eventid]
   mevent.synced=eventid
 end
 
 alias check_member_event_there check_event_trigger_there
 def check_event_trigger_there(triggers)
   result = false
   # If event is running
   if $game_system.map_interpreter.running?
     return result
   end
   result = check_member_event_there(triggers)
   new_x = @x + (@direction == 6 ? 1 : @direction == 4 ? -1 : 0)
   new_y = @y + (@direction == 2 ? 1 : @direction == 8 ? -1 : 0)
   for member in $game_player.members
     # If event coordinates and triggers are consistent
     if member.synced != nil
       event = $game_map.events[member.synced]
       if member.x == new_x && member.y == new_y && triggers.include?(event.trigger)
         # If starting determinant is front event (other than jumping)
         #if not member.jumping? and not member.over_trigger?
           member.lock
           event.start
           result = true
         #end
       end
     end
   end
 return result
end

 def calc_return_path(index)
   nodex=$game_player.x
   nodey=$game_player.y
   b = $game_player.members[index]
   rb = return_buffer.reverse
   if index > 0
   for i in 0...index
     move = rb[i]
     nodex +=1 if move == 4
     nodex -=1 if move == 6
     nodey -=1 if move == 2
     nodey +=1 if move == 8
   end
 else
     move = rb[0]
     nodex +=1 if move == 4
     nodex -=1 if move == 6
     nodey -=1 if move == 2
     nodey +=1 if move == 8
   end
   b.buffer = []
   if index > 0
     for i in 0...index
       b.buffer.push(rb[i])
     end
   else
     b.buffer.push(rb[0])
   end
   return [nodex, nodey]
 end
 
end
 
#==============================================================================
# Game_Member
#==============================================================================

class Game_Member < Game_Character
 
 attr_accessor :buffer
 attr_accessor :frozen
 attr_accessor :synced
 attr_accessor :direction_fix
 attr_accessor :step_anime2
 attr_accessor :walk_anime
 attr_accessor :always_on_top
 
 def initialize(index)
   super()
   @frozen = false
   @synced = nil
   @direction_fix = false
   @step_anime2 = false
   @walk_anime = true
   @always_on_top = false
   @index, @force_movement, @buffer, @through = index, 0, [], true
 end
 
 def refresh
   unless $game_system.CATERPILLAR && actor != nil
     @character_name, @character_hue = '', 0
     return
   end
   @character_name = actor.character_name
   @character_hue = actor.character_hue
   if actor.dead? && DEAD_DISPLAY == 1
     @opacity, @blend_type = Graphics.frame_count % 4 / 2 * 255, 1
   else
     @opacity, @blend_type = 255, 0
   end
 end
 
 def actor
   case DEAD_DISPLAY
   when 0 then return $game_party.actors[@index]
   when 1
     alive = 0
     $game_party.actors.each {|actor| alive += 1 unless actor.dead?}
     if @index >= alive
       ind, flag = @index - alive, true
     else
       ind, flag = @index, false
     end
     $game_party.actors.each_index {|i|
         ind -= 1 if (flag == $game_party.actors[i].dead?)
         return $game_party.actors[i] if ind < 0}
   when 2
     ind = @index
     $game_party.actors.each_index {|i|
         ind -= 1 unless $game_party.actors[i].dead?
         return $game_party.actors[i] if ind < 0}
   end
   return nil
 end
 
 def update
   refresh
   @transparent = $game_player.transparent
   @move_speed = $game_player.move_speed
   @through = !@frozen
   unless moving? || @buffer.size <= @index && @force_movement <= 0 || @frozen == true
     if @buffer.size > 0
       move = @buffer.shift
       if move.is_a?(Array)
         jump(move[0], move[1])
       else
         case move
         when 1 then move_lower_left
         when 2 then move_down(true)
         when 3 then move_lower_right
         when 4 then move_left(true)
         when 6 then move_right(true)
         when 7 then move_upper_left
         when 8 then move_up(true)
         when 9 then move_upper_right
         end
       end
       @force_movement -= 1 if @force_movement > 0
     end
   end
   super
   @step_anime2 = @step_anime
   @step_anime = (ANIMATED_IDS.include?($game_party.actors[@index].id) || @step_anime2)
 end
 
 def update_buffer(next_move)
   if next_move == nil
     @force_movement = @buffer.size
   else
     @buffer.push(next_move)
     @force_movement = @buffer.size if next_move.is_a?(Array)
   end
 end
 
 def check_event_trigger_touch(x, y) # don't remove this, it's necessary...
 end
 
 def screen_z(height = 0)
   return (super - @index)
 end
 
end

#==============================================================================
# Spriteset_Map
#==============================================================================

class Spriteset_Map
 
 alias init_caterpillar_later initialize
 def initialize
   init_caterpillar_later
   return if $BlizzABS && BlizzABS::VERSION >= 1.01
   $game_player.members.each {|member|
       sprite = Sprite_Character.new(@viewport1, member)
       sprite.update
       @character_sprites.push(sprite)}
 end
 
end

#==============================================================================
# Scene_Map
#==============================================================================

class Scene_Map
 
 alias transfer_player_caterpillar_later transfer_player
 def transfer_player
   transfer_player_caterpillar_later
   return if $BlizzABS
   case $game_temp.player_new_direction
   when 2 then $game_player.members.each {|member| member.turn_down}
   when 4 then $game_player.members.each {|member| member.turn_left}
   when 6 then $game_player.members.each {|member| member.turn_right}
   when 8 then $game_player.members.each {|member| member.turn_up}
   end
 end
 
end

class Interpreter
 
 def command_209
   character = nil
   $game_player.members.each {|member|
     if member.synced == @parameters[0]
       character = member
       character.frozen = true
     end}
   # If no character exists
   if character == nil
     # Get character
     character = get_character(@parameters[0])
     # Continue
   end
   if character == nil
     return true
   end
   # Force move route
   character.force_move_route(@parameters[1])
   # Continue
   return true
 end
 
 alias command_end_member command_end
 def command_end
   command_end_member
   $game_player.members.each {|member|
   if member.synced == @event_id
     member.unlock
   end}
 end
 
end



Instructions

Instructions in script.


Compatibility





Credits and Thanks




Author's Notes

Feel free to post any feedback/complaints/improvements. I know this thing is far from complete!

Enjoy!
Title: Re: [XP]TOA Caterpillar & Pathfinding
Post by: ForeverZer0 on January 16, 2012, 08:31:03 pm
Nice job! :D
That should be a feature a lot of people will find very handy. 
Title: Re: [XP]TOA Caterpillar & Pathfinding
Post by: mad.array on January 16, 2012, 08:37:59 pm
Thanks. I had a few problems posting. Something is suffering from the crazies. Not sure whether it's my end or not but my post got sliced halfway and then while I was editing it it jumped from RGSS to General. crazy!
Title: Re: [XP] TOA Caterpillar & Pathfinding
Post by: G_G on January 16, 2012, 08:39:37 pm
*coughs* *blames it on G_G* It was totally G_G. Wait...shit.
Title: Re: [XP] TOA Caterpillar & Pathfinding
Post by: mad.array on January 16, 2012, 08:41:27 pm
lol, at least I know I'm not going senile just yet...

At least, not as senile as 'they' keep insisting :wacko: