[Tutorial Series] Hime ATB #5: Bug Fixes 1

Help spread the word!Share on FacebookShare on TumblrTweet about this on TwitterShare on Google+Share on LinkedInShare on RedditFlattr the author

This tutorial is part of a tutorial series on Active Time Battle (ATB) System development. In this series, you will learn how to build your own ATB system from scratch, and add new functionality to create a unique battle system based on ATB.

In this tutorial, we are going through refactoring as well as some bug fixes. We have completed 4 tutorials so far, and while we test new functionality, we might realize that certain things aren’t actually working as we expected.

So far, I have uncovered several bugs as a result of implementing state turns, and will be discussing what the bugs are, and how to fix them. If you had caught these issues early on, congratulations!

Preparation

This is the fifth tutorial in the series. If you have not read the previous tutorials, I highly recommend doing so, starting from the first tutorial.

In this tutorial, we will be working with the results from the fourth tutorial.

Anyways, let’s start looking at some bugs.

Bug #1: AT Bars Progress when Battler Can’t Move

Depending on your requirements, AT bars might not be allowed to increase if the battler cannot move, such as if they’re dead or they’re asleep.

If you look at where we implemented battler frame updates, we did something like this

Game_Unit.prototype.updateFrame = function() {
  var members = this.members();
  for (var i = 0; i < members.length; i++) {
    members[i].updateFrame();
  }
};

Game_Battler.prototype.updateFrame = function() {
  this.updateAtb();
  this.updateStateFrames();
};

Game_Battler.prototype.updateAtb = function() {
  this._atb = Math.min(this._atb + this.atbFillRate(), this.atbMax());
};

You can see that we have no logic to check for whether the ATB can actually be increased.

States have a restriction called “cannot move”, which we could use to determine whether the ATB can progress or not. There may also be other cases where we want to explicitly prevent the ATB from updating even if the battler can still move, but we will consider that separately.

A simple fix might be to do something like this

Game_Battler.prototype.updateAtb = function() {
  if (this.canMove()) {
    this._atb = Math.min(this._atb + this.atbFillRate(), this.atbMax());
  }
};

And now the ATB only updates if the battler can move. Personally, I would prefer to write it like this, since whether the battler can move or not is not necessarily the only condition for ATB update:

Game_Battler.prototype.updateAtb = function() {
  if (this.canUpdateAtb()) {
    this._atb = Math.min(this._atb + this.atbFillRate(), this.atbMax());
  }
};

Game_Battler.prototype.canUpdateAtb = function() {
  if (!this.canMove()) {
    return false;
  }
  return true;
};

This will address the ATB bug. Now, when a battler dies, their ATB will simply freeze up.

Bug #2: Confusion state doesn’t work

When a battler is confused, they will simply pick a random action and a random target when they can perform an action. Upon testing, I’ve discovered that when a battler is confused, they don’t perform any actions even when their ATB is full.

Taking a look at the “updateFrame” method in our BattleManager, we have this

BattleManager.updateFrame = function() {
  var members = this.allBattleMembers();
  for (var i = 0; i < members.length; i++) {        
    if (members[i].canInput()) {
      this._subject = members[i];
      this.startInput();
      break;
    }
  }
  if (!this._subject) {
    this._frames++;
    $gameParty.updateFrame();
    $gameTroop.updateFrame();    
    this.updateTurnCount();
  }
}

Notice that it uses the method “canInput” to determine whether a battler is ready to perform an action or not.

This was written in the first tutorial, where we assumed that if you “can input”, then you can perform an action. Turns out, this assumption is inaccurate, because this is how “canInput” is defined

Game_BattlerBase.prototype.canInput = function() {
  return this.isAppeared() && !this.isRestricted() && !this.isAutoBattle();
};

Which means if you’re restricted (eg: confused) or has the “auto-battle” flag, you can’t provide an input. But that doesn’t mean you can’t make a move.

So our approach was wrong. What exactly do we want to say when we’re checking if someone is ready to perform an action?

  • Their ATB is full

Is there anything else? Not really. Let’s go with this then:

Game_Battler.prototype.isAtbFull = function() {
  return this._atb === this.atbMax();
}

BattleManager.updateFrame = function() {
  var members = this.allBattleMembers();
  for (var i = 0; i < members.length; i++) {        
    if (members[i].isAtbFull()) {
      this._subject = members[i];
      this.startInput();
      break;
    }
  }
  if (!this._subject) {
    this._frames++;
    $gameParty.updateFrame();
    $gameTroop.updateFrame();    
    this.updateTurnCount();
  }
}

This should take care of the confusion bug, as well as the auto-battle bug that we just discovered.

Bug #3: “State Persist Message” not displayed

This issue is something I noticed when I was testing the “State persists” message.

tut_atb5_1

During testing, when a state was added or removed, the appropriate message would be displayed. However, if the state continued to persist, there was no message reflecting that, which is a bug. Why does this happen? And when does that message get displayed by default?

In BattleManager, there’s a method called “processTurn”, which is used to process a battler actions. Here is our updateTurn method

BattleManager.updateTurn = function() {    
  if (this._subject) {
    if (this._subject.currentAction()) {
      this.processTurn();
    }
    else {
      this.endSubjectTurn();        
    }
  }
  else {
    this.updateFrame();
  }
};

So basically, if a battler is ready to perform an action and it has a current action, then we call “processTurn”.

This method is already defined in the default battle system. Here is how it looks:

BattleManager.processTurn = function() {
  var subject = this._subject;
  var action = subject.currentAction();
  if (action) {
      action.prepare();
      if (action.isValid()) {
          this.startAction();
      }
      subject.removeCurrentAction();
  } else {
      subject.onAllActionsEnd();
      this.refreshStatus();
      this._logWindow.displayAutoAffectedStatus(subject);
      this._logWindow.displayCurrentState(subject);
      this._logWindow.displayRegeneration(subject);
      this._subject = this.getNextSubject();
  }
};

The method of interest is “displayCurrentState”, which will pick the highest priority state on the battler and display the “persist” message.

It appears that the “else” block is never reached, because in our “updateTurn” method, we decided to check for a current action BEFORE processing the turn. This leads to another bug.

Bug #4: “All Actions End” not being called after performing actions

There is a method defined in Game_Battler that looks like this

Game_Battler.prototype.onAllActionsEnd = function() {
   this.clearResult();
   this.removeStatesAuto(1);
   this.removeBuffsAuto();
 };

This method is pretty important, since you can have states wear off after the battler performs a set of actions. So it would be important for us to retain this.
Let’s fix the “updateTurn” method

BattleManager.updateTurn = function() {  
  if (this._subject) {
    this.processTurn();
  }
  else {
    this.updateFrame();
  }
};

Now, when do we actually end the subject’s turn? If you refer back to the “processTurn” method, you’ll see that after all actions have been performed by that battler, it makes a call to “getNextSubject”.

In the default battle system, this would just get the next battler that is ready to perform an action. In our case, once a battler has performed their actions, we should just null out the current subject so that game can proceed to update frames.

BattleManager.getNextSubject = function() {
  this.endSubjectTurn();
};

Now, whenever a battler’s action comes up, they will perform their action, and then they will run “all actions end” and the game will resume frame updates.

This is fine…but what you’ll notice is that when the battler is sleeping, the “still sleeping!” message is still not displayed. Why does this happen?

Battler Action never comes up

Let’s say your battler is currently asleep, and therefore cannot move.
Because they cannot move, their ATB never increases.
And because of this, they can never perform an action.
If they never perform an action, the game will never call processTurn for them.
And thus, they would never display the state persist message.

Is this a bug? Maybe. Maybe not.

In the default battle system, every battler will have an action every turn, even if they cannot move. This means all battlers will always have actions every battle turn.
In our ATB, battlers will only have an action when their AT bar fills up. This means battlers may not be able to perform an action every battle turn.

Based  on our specifications, it would not be a bug if a battler’s action never comes up.

But thinking about turn end and action end leads to another question that might be worth looking over.

State Auto Removal Conditions

By default, there are two timings where a state is removed automatically:

tut_atb5_2

  1. On Action End
  2. On (Battle) Turn End

Note that this “timing” has nothing to do with the duration in turns.

If a state disappears on action end, that means if the state has 0 turns left, it will be removed after you perform your action.
On the other hand, if the state disappears on turn end, then that means if the state has 0 turns left, it will be removed after the turn is over.

In our ATB, we update states every frame, and once a full “state turn” has passed, we will decrement state turn count by 1. At this point, if the state is removed on turn end, we will remove the state.

If the state is instead removed on action end, then the state would continue to have 0 turns until the battler performs an action.

And, as we said earlier, if the battler never performs an action, the state will never be removed. This would be the expected behavior based on what it means for a state to be removed “on action end”.

Summary

After implementing a number of features, we did some in-depth testing and found out there were some bugs.
At the time of writing, I have found four bugs based on my requirements:

  1. ATB still updating if battler cannot move
  2. Battlers did nothing when confused/auto-battle
  3. State persist message not being displayed
  4. All Actions End not being called after performing an action

The fixes were not too difficult, and provided more insight into the default battle system. Is it actually necessary to base our code on the default battle system? Not really, but we just happen to share the same logic.

Two issues were discussed, where in the end I concluded they were not actually bugs, so we will leave them as they are.

It is possible that there are more bugs in the code that I haven’t thought of yet, but that is the nature of development: you can’t solve a problem if you don’t know about it, but you can try to find as many problems as you can through testing various scenarios.

Feedback

Do you have any questions or comments? Perhaps there is something that is unclear, or could be implemented in a better way? Let me know in the comments!

Share the Tutorial

If you know anyone that is interested in writing battle systems for RPG Maker, share this with them!
You can also follow me on TwitterFacebook, or Youtube  for the latest posts and videos.

Support HimeWorks

If you would like to support me in writing these tutorials, you can put in a monthly pledge on Patreon! Every little bit helps, and becoming a supporter allows you to gain access to some exclusive content like development logs/rants, closed beta tests, and other things that I offer.

Help spread the word!Share on FacebookShare on TumblrTweet about this on TwitterShare on Google+Share on LinkedInShare on RedditFlattr the author

You may also like...

Leave a Reply

Your email address will not be published.

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax