[Tutorial Series] Hime ATB #4: State Turns and Effect Timings

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.

At the end of this tutorial, you will be able to create states that will wear off after a certain number of turns, even if the state was added during the middle of a turn.

Preparation

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

This section requires the base ATB code from the first tutorial, along with an implementation of “turn count” from the third tutorial.

We will base our code on the results of the third tutorial, which you can find here.

Re-Cap

So far, we have a simple working implementation of ATB from tutorial #1.

We then implemented custom fill rates in tutorial #2 so that ATB fill rates are based on the battler’s speeds.

Finally, in tutorial #3, we implemented turn counts so that we can use turn-count based mechanics such as running events during certain turns, or enemies performing during certain turns, and so on.

This also means that states that disappear after a certain amount of turns, will actually disappear. If you are not familiar with states, I recommend taking some time to explore how states work in the default system.

State Effect Timings

States can be used to provide temporary effects, such as preventing a battler from moving when they are asleep, or losing a certain amount of HP every turn due to poison.

The word “turn” comes up again. Remember that in the tutorial #3, I said it is important to distinguish the phrase “Battle Turn” from “Action Turn”.

This becomes especially important when a state effect occurs.

Let’s say you had a “Regen” state that “restores 5% HP every turn for 3 turns”.

Does that mean that the battler will restore HP whenever they can perform an action, and after 3 actions are over, the state will disappear?

Or does that mean the battler will restore HP whenever the battle turn changes, and when 3 battle turns have elapsed, the state will disappear?

Both are entirely possible, depending on how you wish to design your state. It is possible that you have a special poison that will cause damage whenever you try to move, which means everytime you perform an action, you will receive some damage.

Now, by default, all state effects occur at the end of the battle turn, so there we don’t have to do anything extra if that is enough.

State Countdown Timings

States have some auto-removal methods related to turn count. You can specify how many turns a state will last from the moment it is applied.

For example, if a state should last between 2 to 2 turns, then it will last 2 turns. If a state should last between 3 to 6 turns, then it will last between some random integer between 3 to 6.

Once the duration has been picked, the state will begin to countdown. At the end of every battle turn, every state that can be removed automatically by turn count, will have their durations decreased by 1.

So if you added a state at turn 1 and the state lasts for 2 turns, it will be removed at the end of state 3.

What does this mean in terms of frames?

Assuming we have 100 frames per turn, this means that a state that lasts for 2 turns should IN THEORY last 200 frames, right?

Unfortunately, that is not always the case. The key here is to realize when state durations are updated: at the end of the battle turn.

Let’s say it is currently frame 150, and you have a fast battler that can perform actions every 50 frames (ie: twice per turn). Now, at this exact frame, the fast battler decides to cast a poison spell on another battler, and the poison lasts two turns.

What we expect to happen is this:

  • Frame 150: state added, 200 frames or 2 turns remain
  • Frame 250: 100 frames or 1 turn remain
  • Frame 350: 0 frames or 0 turns remain, state is removed.

However, keeping in mind that state durations are updated at the end of the each battle turn, this is what ends up happening

  • Frame 150: state added, 200 frames or 2 turns remain
  • Frame 199: battle turn ends, 151 frames remain, but 1 turn remain
  • Frame 299: battle turn ends, 51 frames remain, but 0 turns remain

State will be removed, because the duration is tracked as turns, not frames.

Is Duration Timing Really an Issue?

Now, this might not be too big of a problem. The poison state is supposed to last 2 turns, and the target will receive poison damage twice anyways since poison damage by default occurs at the end of the battle turn, so is it really that big of a deal? It’s not like the target will lose any more HP anyways if you let it drag on for an extra 51 frames.

Well, depending on the effect, it might.

Let’s say that instead of receiving poison damage at the end of every battle turn, you receive 1 poison damage every frame. Literally, as long as you are poisoned, your HP will be continuously sapped away as long as the state is in effect.

Or if you had a state that gave you a strength boost allowing you to deal double damage. Every frame might count, and if the player is short-changed because the duration of your states in turns doesn’t match the duration in frames, that might not be a good outcome.

More Accurate Duration Checks

So let’s think of a way to solve this problem. Can we, instead of checking state durations at the end of the turn, check them every frame?

Option 1: State Duration as Frames

Let’s say that instead of storing turns, we decided to store the state duration as frames instead. This means that on every frame update, the game would update your ATB, and update all your states’ durations as well.

Does your state last 237 frames? No problem, it will be removed 237 frames later. No more, no less. It will be absolutely precise.

Option 2: Turns with Decimals

There is no absolute requirement that turns must be integers. It just makes it a bit easier because how many frames is equal to 1.27? Well, if there are 100 frames per turn, then that means there are 127 frames left, but if the number was a bit more complicated like maybe 67, things get messy.

If we’re working with a “nice” constant for the frames per turn, then we could potentially get away with decimal numbers.

However, this is no different from option 1 except instead of working with integers, we’re working with reals. I think overall it might be easier to just work with frames in that case.

Option 3: Update Turn Count based on Frame Count

Rather than thinking about how to change the way turns are tracked, perhaps we could instead store frame count separately, and use THAT to update the turn count.

For example, if a state was added at frame 50 and the state lasts for 2 turns, then assuming 100 frames per turn, our state will be removed at frame 250.

Here is a summary of how the state will look as time passes:

  • Frame 50, state added, 200 frames left, 2 turns left.
  • Frame 150, 100 frames left, a full “turn” has passed for the state, so 1 turn left
  • Frame 250, 0 frames left, another “turn” has passed for the state, so 0 turns left

And finally, the state will be removed at frame 250.

Notice that the “turn” for the state is based on when it was added. After every 100 frames, which is our frames per turn, we say that a “state turn” has passed.

The State Turn

We define the “State Turn” to be an independent turn counter for each and every state that you have. A state’s turn lasts a full “frames per turn”, which would be relative to its own frame counter.

When a state is added, we will initialize its frame count to be equal to the number of turns it lasts, multiplied by the number of frames per turn. For example, if a state lasts 2 turns, and there’s 100 frames per turn, then it will last 200 frames.

When a state turn has passed a full “frames per turn”, it will decrement the turn duration by 1, and then perform a check to see if any states should be removed automatically. So after 100 frames, the turn count will be decreased by 1.

Now we have a way to accurately keep track of state durations.

State Turn Effects

We re-visit the problem of effect timings, with our new “State Turn” concept.

Let’s say you had a regen spell that “restores 5% HP every turn”. Now, instead of restoring 5% HP at the end of every battle turn, we can simply trigger this effect at the end of every state turn.

Now let’s look at the example of a state that reduces your HP by 1 every frame. Now, because we are updating our state durations every frame, we can now execute effects that should occur every frame.

Because RPG Maker does not support frame-based effect timings, we would need to implement this ourselves, but since we already have the ATB set up, this is entirely possible now.

Implementing State Turns

State turns seems like a pretty good solution so far. It uses a frame-based duration approach, and correctly updates the number of turns remaining after a certain number of frames have passed.

So let’s start by adding our state frame count.

var TH_GameBattlerBase_clearStates = Game_BattlerBase.prototype.clearStates;
Game_BattlerBase.prototype.clearStates = function() {
  TH_GameBattlerBase_clearStates.call(this);
  this._stateFrames = {}
};

Now, when a new state is added, we want to initialize the state counter:

var TH_GameBattlerBase_resetStateCounts = Game_BattlerBase.prototype.resetStateCounts;
Game_BattlerBase.prototype.resetStateCounts = function(stateId) {
  TH_GameBattlerBase_resetStateCounts.call(this, stateId);
  this._stateFrames[stateId] = 0;
};

Note that the counter does not keep track of how many frames left. Instead, it keeps track of how many frames have passed since it was added.

When the state is removed, we want to clear out state frames as well, for consistency.

var TH_GameBattlerBase_eraseState = Game_BattlerBase.prototype.eraseState;
Game_BattlerBase.prototype.eraseState = function(stateId) {
  TH_GameBattlerBase_eraseState.call(this, stateId);
  delete this._stateFrames[stateId];
};

So far, we’ve just been going through the motions of handling the usual “initialize”, “create”, and “delete” operations. Now we need to implement the “update” operation.

Let’s begin with the frame update method we defined back in the first tutorial:

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

According to our design, all states will have their frame counters updated. If any of the counts equals the frames per turn, then we need to update the state turns, and reset the frame count back to 0.

OR, we could choose not to reset it and just check whether the counter is a multiple of the frames per turn…it might be useful to know how long the state has lasted in frames. I like to believe having more information may be useful? It’s hard to tell at this point.

Let’s just reset it to 0 since it’s easier. It wouldn’t be difficult to modify if we needed that information.

Game_Battler.prototype.updateStateFrames = function() {
  var stateIds = this._states;  
  for (var i = 0; i < stateIds.length; i++) {
     var id = stateIds[i];
     this._stateFrames[id] += 1;
     if (this._stateFrames[id] >= BattleManager._framesPerTurn) {
        this._stateFrames[id] = 0;
        this.updateStateTurn(id);
     }
  }
};

Game_Battler.prototype.updateStateTurn = function(stateId) {
  if (this._stateTurns[stateId] > 0) {
    this._stateTurns[stateId]--;
  }
};

Note: we are defining a new method called “updateStateTurn”, which is in singular form. There is another method called “updateStateTurns” which basically updates every state’s turns.

It is used here:

Game_Battler.prototype.onTurnEnd = function() {
    this.clearResult();
    this.regenerateAll();
    this.updateStateTurns();
    this.updateBuffTurns();
    this.removeStatesAuto(2);
};

Now, let’s take a closer look at what this method is doing whenever a battle turn ends.

It begins by clearing out previous results. Presumably this is because when it performs the “regenerateAll” method which will apply any regen/poison damage, the game needs to be able to know those values without any previous results mixed in.

Then it updates all state turns, all buff turns, and removes any states with a turn count of 0.

First, our design requires us to perform state updates on every frame, not every turn, so we need to remove the state turns update. We should probably remove buffs as well, and switch that over to a frames-based approach similar to our states. It is not necessary to check for state removal at this point because states will be removed when we perform an “updateStateTurn” during the frame update.

Second, our design requires us to perform state effects at the end of a state turn. Again, not at the end of a battle turn.

Let’s go and update our “updateStateTurn” method to execute state effects and auto-removal

/* Overwrite for now */
Game_Battler.prototype.onTurnEnd = function() {
  this.clearResult();
  this.updateBuffTurns(); // deal with this later
};

Game_Battler.prototype.updateStateTurn = function(stateId) {
  if (this._stateTurns[stateId] > 0) {
    this._stateTurns[stateId]--;    
  }  
  this.clearResult();
  this.regenerateAll();
  this.removeStatesAuto(2);
  BattleManager.displayStateUpdate(this);
};

Notice the last line. Even though your battlers may have received some damage or recovery due from the state, the game won’t display that automatically since it doesn’t know that it should display it, so we tell the battle manager to explicitly display any changes due to a state update.

BattleManager.displayStateUpdate = function(battler) {
   this._logWindow.displayAutoAffectedStatus(battler);
   this._logWindow.displayRegeneration(battler);
}

Now, our states will be updated every frame, and when a state turn has passed, the state will execute any state effects that are performed at the end of each state turn, decrements turn count by 1, and then auto-removes any states with a turn count of 0.

I chose to overwrite “onTurnEnd” because it is a method that is called at the end of every battle turn, and there may be logic that should still run at this time.

Anyways, at this point, you should have properly functioning states! No matter what frame you add them, their turn effects will occur at the end of their own state turns, and their turn count would be updated when a state turn ends. We didn’t have to change the way state turns were stored, so compatibility with other plugins should be pretty good.

Summary

This tutorial was fairly long, mostly because of the design step. You may notice that the actual implementation was fairly straightforward after coming up with a solution that sounded alright.

We started by discussing state effects like poison, and when the poison damage should occur. We then looked at state duration in turns, and when the duration should be decreased.

We decided to implement frame-based state updates, where all states are updated every frame.

We defined something called a “State Turn”, which is basically the number of frames before a turn passes for a state, except it is based on when the state was added, not when the battle turn ends. Using this mechanic, we could then have each state keep track of when their turns should be updated, based on the frame count.

We then updated when state effects should be executed to reflect state turns as well, instead of battle turns.

At this point, we have properly functioning states, which brings us closer to a finished RPG Maker battle system.

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.

You may also like...

Leave a Reply

Your email address will not be published. Required fields are marked *