Saturday, 6 May 2017

Autonomous Movement

Just a reminder:

Events with Autonomous Movements will stop moving when the Player directly interacts with them, and will move again from where they left off afterwards (if bug fix in previous post is implemented).

Events with Autonomous Movements will keep moving during Autorun or Parallel Process events, and if you change their movement via a Set Move Route call, they will go back to their Autonomous route immediately after Set Move Route is over.

To prevent this, leave Autonomous Movement on Fixed. Or if you require them to move autonomously outside the Autorun event, have two event pages - one with the custom Autonomous route, and one with the Fixed route - and ensure the page with the Fixed route is active during Autorun.

Restore Move Route Bug Fix

I found a bug in RPG Maker VX Ace that I am really surprised I haven't come across a fix for already on the internet.

It's something I noticed before, but didn't really look into until now.

If you have an event with a repeating Custom Autonomous Movement, and some logic when you interact with them that makes a Set Move Route call, once the interaction is over one of two things may happen:

  1. they return to their autonomous movement, but not where they left off when they were interrupted (meaning their original route gets totally screwed up), or
  2. they stop moving completely.
It turns out, which one happens depends on where they were in their move route when you interacted with them. If you interact with them at the end of their Custom Autonomous Move Route cycle, the second one happens. Any other time, the first one happens.


The Way Movement Works

Events move around via an update_routine_move method that is called continuously (until you interact with them). The move route is the list of move commands, and it keeps track of the current movement via an index that is manually incremented. 

In other words, the code retrieves the current move command from the route via the index, processes the command, then advances the index. Pretty standard stuff. This method is also called for Set Move Route commands within the event logic, which actually overwrites the original move route.

But once Player interaction ends, the event is supposed to go back to its original move route. So the code stores (or "memorises") the Custom Autonomous Move Route prior to any Set Move Route call. It also stores the index so it can "remember" where it was up to when interrupted.

Then, when Set Move Route is done, it simply restores the original move route and the original index from memory. 

The bug is: it doesn't restore this index correctly.

The problem code is in one of two places, depending how you look at it.

Either:

class Game_Character
  #--------------------------------------------------------------
  # * Restore Move Route
  #--------------------------------------------------------------
  def restore_move_route
    @move_route           = @original_move_route
    @move_route_index     = @original_move_route_index
    @original_move_route  = nil
  end
end

OR

class Game_Character
  #--------------------------------------------------------------
  # * Process Move Route End
  #--------------------------------------------------------------
  def process_route_end
    if @move_route.repeat
      @move_route_index = -1
    elsif @move_route_forcing
      @move_route_forcing = false
      restore_move_route
    end
  end
end

It all looks correct and innocent enough, but the giveaway is that the index is set back to -1 and not 0 if the move route is set to repeat.

The reason for this is, the code processes the end of the move route as another command in the list. And as we discovered earlier, after a command is processed, it then advances the index. So when it goes back to the start, the index has been advanced back to 0 and we're good to go.

When it restores the move route, however, it doesn't take this into account. Therefore, the index is out by 1 when the original route is restored. 

What this means is if the event was in the middle of the move route when interrupted, it skips the command it was up to and goes to the next one, which is why the original route gets messed up. If the event was at the end of its route when interrupted, the index is then increased to be greater than the number of commands in the list, so when the code goes to process the next command, it simply doesn't find one and the event stops moving.

The Fix

For all that, the fix is very simple. The move route index simply needs to be decreased by 1 when restoring it back to the original route. This can be done in either of the two methods above. It makes more sense to me to do it in process_route_end but it's easier with aliasing to do it in restore_move_route

IMPORTANT: Choose one or the other, not both.

Either:

class Game_Character
  #--------------------------------------------------------------
  # * Alias method - Restore Move Route
  #   Only needed if the overwritten method below isn't used
  #--------------------------------------------------------------
  alias rtp_bug_fix_restore_move_route restore_move_route
  def restore_move_route
    rtp_bug_fix_restore_move_route
    @move_route_index -= 1
  end
end

OR

class Game_Character
  #----------------------------------------------------------------
  # * Overwrite Method - Process Move Route End
  #   Only needed if the alias method above isn't used
  #--------------------------------------------------------------
  def process_route_end
    if @move_route.repeat
      @move_route_index = -1
    elsif @move_route_forcing
      @move_route_forcing = false
      restore_move_route
      @move_route_index -= 1
    end
  end
end