How to write an atb system script

DoubleX

Just a nameless weakling
Veteran
Joined
Jan 2, 2014
Messages
1,787
Reaction score
939
First Language
Chinese
Primarily Uses
N/A
Edit: #1 to #19 cover stuffs that can be handled with some scripting proficiency; The later replies will probably cover stuffs that need decent scripting proficiency to be handled.

This post mainly targets those wanting to write an atb system script or understand how the existing ones work, or those just interested in the atb concept. What I'm going to share are just my opinions, so don't automatically take them as hard facts.

Before I start, I want to state that writing an atb system script or understanding existing ones likely needs at least some scripting proficiency. Specifically, you need to at least have:

1. A solid understanding of how the default RMVXA battle system works on the player level

2. A basic understanding of how the battle related parts of the default RMVXA script works

3. Written few basic battle related scripts

Also, you need to at least have a basic understanding of the atb concept.

Nevertheless, you might still be able to follow what I'll going to say even if you don't all the above prerequisites. You can ask me if you do fail to follow. So let's get started.

Understanding how a basic atb system works

While some of you might want to write an extremely powerful atb system script already, you need to be able to understand how a basic atb system works first. By being basic, it only has the minimum feature set that's essential to any atb system.

1. ATB frame update and wait conditions

Unless one can flawlessly implement executing more than 1 actions at the same time, no atb system can have absolutely no waits, meaning every atb system has wait conditions determining if a frame should execute the atb frame update, which updates the global and battler atb clock(to be explained later) and executes battlers' actions that should be executed.

Usually, the wait conditions are 1 of the below:

- Wait when an action's executing or a message's displaying

- Wait when an action's animation's playing as well

- Wait when an actor's picking the action's targets as well

- Wait when an actor's picking a skill/item as well

- Wait when an actor's can input actions as well

2. Global and battler atb clock

Unless you're going to abandon the whole battle turn number concept entirely, you'll need a global atb clock to run it, as its implementation used by the default RMVXA battle system won't work in any atb system anymore. It's because both the party and troop have the action input phase and action execution phase in battles, and its turn number mechanics's built around it, while no atb system will ever divide a battle that way. Instead, each battler has an independent action input phase and action execution phase, as well as an atb refill phase needed in any atb system, meaning each battler needs to have an independent atb clock. As different battler atb clocks are extremely likely to be different from each other(otherwise the essence of the atb system would be lost), they can't be used to run the battle turn number, meaning a global atb clock's needed to run it.

The global atb clock normally uses 1 one of the below 2 units:

- Frames. Each battle turn consists of a specific number of frames with the atb frame update passed

- Actions. Each battle turn consists of a specific number of executed actions

For both units, when that specific number's reached in a battle turn, the battle turn number will be increased by 1.

The battler's atb clock's usually used in 1 one of the below 2 ways:

- Checks if the battler should be able to act. When it's true, that battler can input, then execute actions, and the battler atb clock will be reset afterwards; When it's false, that battler will need to wait until the aforementioned check returns true.

- Checks if the battler's inputted actions should be executed. When it's true, that action will be executed and then that battler can input actions again; When it's false, that action will need to wait until the aforementioned check returns true.

For both ways, the battler's speed must be used to run that battler's atb clock, as the essence of an atb system is to make the some battlers act faster and more frequently than the others. Using the default RMVXA settings, the battler's speed's determined by that battler's agi. This also implies that the skill/item invocation speed is mostly useless in the 1st way of using the battler atb clock.
If you understand how a basic atb system works, you should be able to proceed to be below next part. Bear in mind that it only briefly describes the essential basics, so you need to digest those core ideas an experiment them with your own scripting proficiency.

Understanding how to write a basic atb system script

1. ATB frame update

If the atb wait condition's always true, the atb system will never run, as no atb frame update ever takes place. So the first thing you should understand is how to implement it.

As at most 1 atb frame update can take place at any frame, its implementation must be linked to methods that exactly 1 of them is called exactly once at any frame. Only Scene_Battle's battle related class/module having such methods:

#-------------------------------------------------------------------------- # * Frame Update #-------------------------------------------------------------------------- def update super if BattleManager.in_turn? process_event process_action end BattleManager.judge_win_loss end #-------------------------------------------------------------------------- # * Update Frame (Basic) #-------------------------------------------------------------------------- def update_basic super $game_timer.update $game_troop.update @spriteset.update update_info_viewport update_message_open end
While BattleManager.in_turn?, BattleManager.judge_win_loss, $game_timer.update, $game_troop.update, @spriteset.update, update_info_viewport and update_message_open are clearly undesirable choices, all the others should be at least barely acceptable in most cases, but you should still think about each choice thoroughly and compare them carefully using a case by case approach. Bear in mind that sometimes you can choose more than 1 methods.

Now suppose you've made your choice and aliased that methods to let it call your new method handling the atb frame update unless the atb wait condition's met. For example:

alias atb_chosen_def chosen_def def chosen_def atb_chosen_def atb_frame_update unless atb_wait_cond? end
Then you'll have to write both atb_frame_update and atb_wait_cond?. For example:

def atb_frame_update update_global_atb_clock if Script_Config::GLOBAL_ATB_CLOCK_UNIT == :frame all_battle_members.each { |battler| battler.update_battler_atb_clock } end
Code:
  def atb_wait_cond?    return true if scene_changing? || !SceneManager.scene_is?(Scene_Battle) || $game_message.visible    return false if Script_Config::ATB_WAIT_COND == :none    return false if Script_Config::ATB_WAIT_COND == :ani && @spriteset.animation?    return false unless Script_Config::ATB_WAIT_COND != :target || @actor_window.active || @enemy_window.active    return false unless Script_Config::ATB_WAIT_COND != :item || @skill_window.active || @item_window.active    @actor_command_window.active || @party_command_window.active  end
Clearly, update_battler_atb_clock should be written under either Game_BattlerBase or Game_Battler. For example, assuming the battler atb clock's used to check if the battler can input and then execute actions:

def update_battler_atb_clock return unless movable? && @battler_atb_clock < MAX_BATTLER_ATB_CLOCK @battler_atb_clock += agi / BattleManager.all_battlers_avg_agi return unless @battler_atb_clock >= MAX_BATTLER_ATB_CLOCK @battler_atb_clock = MAX_BATTLER_ATB_CLOCK make_actions end
Also, when the global atb clock uses frame as the unit and the specific number of frame's reached in a battle turn, the battle turn number should be increased by 1. For example:

def update_global_atb_clock @global_atb_clock += 1 if @global_atb_clock >= User_Config::GLOBAL_ATB_CLOCK_MAX_UNIT[:frame] @global_atb_clock = 0 increase_battle_turn_number end
2. Action execution

As either of the battler atb clock check(if the battler can input and then execute actions, or if the battler's action can be executed) can return true at any frame and an action needs to be executed when either check returns true, an action can be executed at any frame. Also, as at most 1 action can be executed at the same time(you're you're a scripting prodigy), its implementation must be linked to methods that exactly 1 of them is called exactly once at any frame.

As process_action is the only method that execute actions but it's only called when BattleManager.in_turn? returns true, you need to rewrite @phase under BattleManager and/or BattleManager.in_turn? to return true whenever an action can be executed.

Also, right after executing an action, its user's battler atb clock must be reset. For example:

alias atb_on_action_end on_action_end  def on_action_end    atb_on_action_end @battler_atb_clock = 0  end
3. Action validity

In the default RMVXA setting, an action will only be validated right before its execution. An action's valid if it's a force action with a skill/item, or its user can use its skill/item.

Such validation won't work on an atb system with its atb wait condition being bare minimum or true when an action's executing, however. It's because when an actor finished inputting a skill/item needing target selection, that actor will proceed to inputting its targets. At that moment, process_action will be run as action execution can take place now. This causes the actor's action to be executed before its targets are selected, as it's considered to be valid in the default RMVXA setting.

To counter this, the action should be considered as valid only if it's selected its targets. For example:

class Game_Action alias atb_valid? valid? def valid? (subject.enemy || @atb_confirm || subject.auto_battle? || subject.confusion) && atb_valid? endend
Code:
class Scene_Battle < Scene_Base  alias atb_command_guard command_guard  def command_guard    BattleManager.actor.input.atb_confirm = true    atb_command_guard  end  alias atb_on_actor_ok on_actor_ok  def on_actor_ok    BattleManager.actor.input.atb_confirm = true    atb_on_actor_ok  end  alias atb_on_enemy_ok on_enemy_ok  def on_enemy_ok    BattleManager.actor.input.atb_confirm = true    atb_on_enemy_ok  endend
4. Actor command window setup and deactivation

In the default RMVXA setting, the actor command window will be setup only right after choosing the Fight command in the party window.

Such mechanism won't work on any atb system, however, as the actor command window of an actor needs to be setup right after that actor becomes able to input actions. This means its implementation must be linked to the atb update frame method, as "an actor becomes able to input actions" can only be triggered by an atb frame update. For example:

def atb_frame_update update_global_atb_clock if Script_Config::GLOBAL_ATB_CLOCK_UNIT == :frame all_battle_members.each { |battler| battler.update_battler_atb_clock } setup_actor_command_window if @status_window.index < 0 end def setup_actor_command_window actor = $game_party.alive_members.find { |member| member.actions.size > 0 } return unless actor BattleManager.actor_index = actor.index @status_window.select(BattleManager.actor_index) @actor_command_window.setup(BattleManager.actor).show end
The same applies to deactivating the actor command window. In the default RMVXA setting, the actor command window will be deactivated only right after choosing the cancel command or entering the action execution phase.

Again, such mechanism won't work on any atb system, as the actor command window of an actor needs to be deactivated right after that actor becomes unable to input actions. This means its implementation must be linked to the atb update frame method, as "an actor becomes unable to input actions" can only be triggered by an atb frame update. For example:

def atb_frame_update update_global_atb_clock if Script_Config::GLOBAL_ATB_CLOCK_UNIT == :frame all_battle_members.each { |battler| battler.update_battler_atb_clock } @status_window.index < 0 ? setup_actor_command_window : deactivate_actor_command_window end  def deactivate_actor_command_window actor = $game_party.members[@status_window.index]    return if actor && actor.actions.size > 0 && !actor.current_action.atb_confirm if @skill_window.visible || @item_window.visible || @actor_window.visible || @enemy_window.visible @status_window.open.show      @status_aid_window.hide end    [@actor_window, @enemy_window, @skill_window, @item_window,@actor_command_window].each { |window|      window.hide.deactivate.close if window.active } @status_window.unselect  end
5. Starting battler atb clock status

As in the default RMVXA setting, a battle can start normally, preemptively or under surprise, the starting battler atb clock status should be different in those different cases, meaning a battler method handling that must be called at the start of a battle. For example:

class << BattleManager alias atb_battle_start battle_start def battle_start atb_battle_start start_type = @preemptive ? :preemptive : @surprise ? :surprise : :normal $game_party.members.each { |member| member.set_start_battler_atb_clock(start_type) } endend
Code:
class Game_Battler < Game_BattlerBase  def set_starting_battler_atb_clock(start_type)    return @battler_atb_clock = 0 unless start_type != :surprise && :movable?    return @battler_atb_clock = MAX_BATTLER_ATB_CLOCK if start_type == :preemptive    @battler_atb_clock = agi * MAX_BATTLER_ATB_CLOCK / param_max(6)  endend
6. Battler atb clock display

Strictly speaking, it isn't necessary if only the minimum functionality of the atb system's to be considered. But as an atb system without letting players know the battler atb clock status is generally considered to be broken, even a basic atb system script should implement the battler atb clock display.

As the battler atb clock display can only be changed when the battler atb clock changes, which can only be triggered by an atb frame update, its implementation must be linked to the atb frame update method. For example:

def atb_frame_update update_global_atb_clock if Script_Config::GLOBAL_ATB_CLOCK_UNIT == :frame all_battle_members.each { |battler| battler.update_battler_atb_clock } @status_window.index < 0 ? setup_actor_command_window : deactivate_actor_command_window @status_window.atb_bar_refresh end
Code:
  def atb_bar_refresh    item_max.times { |index| draw_atb_bar(index) }  end
Code:
  def draw_atb_bar(index)    actor = $game_party.members[index]    return unless actor    rect = item_rect(index)    draw_gauge(rect.x, line_height, rect.width, actor.battler_atb_clock / MAX_BATTLER_ATB_CLOCK, User_Config::BATTLER_ATB_CLOCK_COLOR[0],    User_Config::BATTLER_ATB_CLOCK_COLOR[1])  end
Never refresh the entire status window when only the battler atb clock display needs to be updated, as doing so per frame will most likely cause at least significant average fps drops on most but the most powerful machines. The average fps drop can be so severe that it can effectively kill the atb system.


If you understand how to write a basic atb system script, you should be able to write one. If you want it to be more powerful, you may want to proceed to the next part.

Understanding how to add features to an atb system script

While some of you might want to write the codes already, you should first make sure you fully comprehend your own basic atb system script on both the user level and implementation level.

1. The new feature clarifications

As always, before adding a new feature, you should first fully understand what it means. For example, you might want to add a cooldown feature, which makes some skills/items to force their users' battler atb clock to freeze for a specified amount of time right after using them.

2. The interaction between the new feature and the existing atb system

Understanding what a new feature mean alone isn't enough, as you also need to understand how it'll change your existing script on both the user level and the implementation level. For example, with the cooldown feature added, users need some ways to specify some skills/items to use the cooldown feature and specify the cooldown duration for each of those skill/item. The implementation also needs to add a new cooldown method handling the battler's cooldown, and that method should be called if the last skill/item used uses the cooldown feature and the cooldown duration isn't reached yet.

3. The implementation of the new feature

Using the cooldown feature as an example:

As the cooldown happens after executing an action, when the former happens, the battler has no way to read the last used skill/item, meaning there's no way to know the cooldown duration, causing the cooldown feature to fail. To counter this, the last use skill/item's cooldown duration must be temporarily stored by its user after inputting but before executing it. As cooldown can't take place without using a skill/item, in which its cooldown duration temporary store of its user will always replace the old one, there's no need to clear the old one after finishing cooldown. For example:

alias atb_use_item use_item def use_item atb_use_item @subject.set_battler_atb_clock_cooldown_time end
Code:
  def set_battler_atb_clock_cooldown_time    @battler_atb_clock_cooldown_time = eval(current_action.item.battler_atb_clock_cooldown_time)  end
Also, the cooldown starting flag needs to be raised right after executing an action needing user cooldown. For example:

alias atb_on_action_end on_action_end def on_action_end atb_on_action_end @battler_atb_clock = 0 @battler_atb_clock_cooldown = MAX_BATTLER_ATB_CLOCK if @battler_atb_clock_cooldown_time > 0 end
Then, that flag needs to be used by the cooldown handling method, which needs to be called per frame. For example:

def update_battler_atb_clock return unless movable? if @battler_atb_clock_cooldown > 0 @battler_atb_clock_cooldown -= MAX_BATTLER_ATB_CLOCK / @battler_atb_clock_cooldown_time @battler_atb_clock_cooldown = 0 if @battler_atb_clock_cooldown < 0 return end @battler_atb_clock += agi / BattleManager.all_battlers_avg_agi return unless @battler_atb_clock >= MAX_BATTLER_ATB_CLOCK @battler_atb_clock = MAX_BATTLER_ATB_CLOCK make_actions end

Summary

When writing an atb system script, you should:

1. Understand how the atb fream update and wait conditions, and the global and the battler atb clock work

2. Understand how to implement the atb frame update, the action execution and validation, the actor command window setup and deactivation, the starting battler atb clock work status and the battler atb clock display

3. Understand how to integrate a new atb feature into an existing atb system script

That's all for now. I hope you've at least a slightly better understanding of an atb system. Those familiar with atb system scripts may want to correct me if there's anything wrong, or share their understandings here. After all, all I've shared is only my own understanding, and I won't claim them as absolute truths.
 
Last edited by a moderator:

Warpmind

Twisted Genius
Veteran
Joined
Mar 13, 2012
Messages
936
Reaction score
578
First Language
Norwegian
Primarily Uses
Bookmark'd. :D
 

DoubleX

Just a nameless weakling
Veteran
Joined
Jan 2, 2014
Messages
1,787
Reaction score
939
First Language
Chinese
Primarily Uses
N/A
If one can follow what the OP post said, one should also be able to fully comprehend how one of the most basic atb system I know so far works - Fomar0153's Customisable ATB/Stamina Based Battle System:

http://www.rpgmakervxace.net/topic/467-customisable-atbstamina-based-battle-system/

That's because

- It's a completely standalone script

- It's written in English

- Its author's still active so you can ask him/her(Deathsia and Maliki's also supporting it so you can ask them as well)

- It's one of the shortest atb system script I know so far

- It's one of those I know so far having the least features(i.e., closest to a basic atb system)

Fully comprehending that script probably implies fully comprehending what a basic atb system does, how it works and how to write one :)
 
Last edited by a moderator:

DoubleX

Just a nameless weakling
Veteran
Joined
Jan 2, 2014
Messages
1,787
Reaction score
939
First Language
Chinese
Primarily Uses
N/A
While the basic atb system I mentioned can work(not the Fomar153's one of course), it has obvious bugs - There's no battler atb clock reset upon becoming unmovable nor failed escape cost. You probably have already noticed these.

Battler Atb Clock Reset Upon Becoming Unmovable

When a battler becomes unmovable, the associated battler atb clock should be reset to 0 and all action slots should be cleared:

#------------------------------------------------------------------------------|# * Edit class: Game_Battler |#------------------------------------------------------------------------------|class Game_Battler < Game_BattlerBase #----------------------------------------------------------------------------| # Alias method: on_restrict | #----------------------------------------------------------------------------| alias atb_on_restrict on_restrict def on_restrict atb_on_restrict # Added to reset the battler atb clock reset_battler_atb_clock # end # atb_on_restrict  #----------------------------------------------------------------------------|  #  Alias method: on_action_end                                               |  #----------------------------------------------------------------------------|  alias atb_on_action_end on_action_end  def on_action_end    atb_on_action_end # Added to reset the battler atb clock    reset_battler_atb_clock #  end # on_action_end  #----------------------------------------------------------------------------|  #  New method: reset_battler_atb_clock                                       |  #  - Updates the battler atb clock and makes actions when the battler can act|  #----------------------------------------------------------------------------|  def update_battler_atb_clock return reset_battler_atb_clock unless movable?    return if @battler_atb_clock >= MAX_BATTLER_ATB_CLOCK    @battler_atb_clock += agi / BattleManager.all_battlers_avg_agi    return unless @battler_atb_clock >= MAX_BATTLER_ATB_CLOCK    @battler_atb_clock = MAX_BATTLER_ATB_CLOCK    make_actions  end # update_battler_atb_clock #----------------------------------------------------------------------------| # New method: reset_battler_atb_clock | # - Resets the battler atb clock as 0 and clears all battler's actions | #----------------------------------------------------------------------------| def reset_battler_atb_clock @battler_atb_clock = 0 clear_actions BattleManager.clear_actor if actor? && BattleManager.actor == self end # reset_battler_atb_clockend # Game_Battler

Failed Escape Cost

It's quite straightforward. Just resets all party member's battler atb clock upon failed escape:

#------------------------------------------------------------------------------|# * Edit class: Game_Party |#------------------------------------------------------------------------------|class Game_Party < Game_Unit #----------------------------------------------------------------------------| # New method: clear_actions | # - Resets all party members' battler atb clock upon failed escape | #----------------------------------------------------------------------------| def clear_actions super # Resets all party members' battler atb clock as 0 members.each {|member| member.reset_battler_atb_clock } # end # clear_actionsend # Game_Party

P.S.: For the escape conditions, if you want to give users lots of control and freedom about that, and the battler atb clock uses the 2nd way, which is:

- Checks if the battler's inputted actions should be executed. When it's true, that action will be executed and then that battler can input actions again; When it's false, that action will need to wait until the aforementioned check returns true.

Then handling escape conditions can be rather complicated even in a basic atb system(up to needing decent scripting proficiency to implement it), so I decided to not talking about that for now lol
 
Last edited by a moderator:

DoubleX

Just a nameless weakling
Veteran
Joined
Jan 2, 2014
Messages
1,787
Reaction score
939
First Language
Chinese
Primarily Uses
N/A
Now you should be able to write a basic atb system script. But soon you'll probably realize a major tough spot of its maintenance - debugging. An atb system script having tons of nontrivial bugs is seldom excellent.

While you might think that doing so is just similar to debugging a simple battle related script, the reality is that it's likely not so simple. ATB system scripts are generally especially bug prone, as there are lots of troublesome edge cases that are specific to them.

Debug all atb wait conditions

Normally, the harder the atb wait condition can be met, the more errors(with higher likelihoods) can come into play. For instance, consider the below edge cases under the minimum atb wait condition(executing actions or displaying messages):

- A skill window is opened for an actor to pick skills. What should/would happen if that actor becomes not inputable when the skill window is still opened?

- A skill window is opened for an actor to pick skills. What should/would happen if a skill becomes usable/unusable when the skill window is still opened?

- A target window is opened for an actor to pick targets. What should/would happen if the selected target becomes out of the target scope when the target window is still opened?

- A battler is executing an action. What should/would happen if the party escape command is selected while the battler is still executing an action?

The above are just few examples from a potentially endless list. Depending on the actual implementation of a basic atb system script, it can have some unique problems as well. You'll want to train yourself to be excellent at detecting possibly problematic edge cases, and being so needs a deliberate and meticulous mind.

Bear in mind the more features and/or control and freedom a basic atb system script gives to its users, the more possible edge cases(with higher likelihoods) can come into play, increasing the difficulty and cost of debugging.

To have the best bet of fixing as many bugs as possible, debug from the strictest(hardest to be met) to the loosest(easiest to be met) atb wait conditions. It's because a bug fix working under a stricter atb wait condition likely also works under a looser atb wait condition, while the opposite is usually not fully the case. So unless the script's really poorly written, if it has almost no bugs under the minimum atb wait condition, there's an extremely high chance that it has almost no bugs under any other atb wait condition as well. But to be sure, you'll still want to debug all atb wait conditions.
Debug the existing parts as well after adding new features

When debugging a atb system script, it's likely a wishful thinking(unless the script is incredibly modular) to think that after adding new features, only those new features need debugging. In reality, adding new features can introduce new bugs to the existing parts of the script. For instance, if the latters assume something to be true and adding the formers can break those assumptions, the latters can have new bugs. That's why it's generally a good practice to think of how the new features will change the existing parts of the script before actually implementing those new features.
 
Last edited by a moderator:

Andar

Veteran
Veteran
Joined
Mar 5, 2013
Messages
31,365
Reaction score
7,676
First Language
German
Primarily Uses
RMMV
How would you scale the clocks? I'm talking about the internal numbers here, it's obvious that the display for the player has to be scaled down for readability.


If you set the average speed to 10, then the next difference (9 or 11) would have a change of 10% more or less. That will limit your options both in balancing as well as in leveling.


If you set the average speed to 100, then you have a much finer control over the results.


However, the total numbers will go up and become more difficult to handle. Let's assume the average action time should be five seconds, that will result in a total of 300 frames.


With an average of 10, that would be 3000 "time points" for a single action, with an average of 100 that would become 30000 "time points" for the scale.


For the player that would make no difference as the numbers would be scaled to a bar of probably 100 pixels anyway and he doesn't know if a single pixel would stand for 30 or 300 time points, but the developer would have to handle those numbers in the database.


So where would you place the basic scale to allow for finer balancing on one side without the numbers becoming too large and cumbersome on the other side?
 
Last edited by a moderator:

DoubleX

Just a nameless weakling
Veteran
Joined
Jan 2, 2014
Messages
1,787
Reaction score
939
First Language
Chinese
Primarily Uses
N/A
How would you scale the clocks? I'm talking about the internal numbers here, it's obvious that the display for the player has to be scaled down for readability.

If you set the average speed to 10, then the next difference (9 or 11) would have a change of 10% more or less. That will limit your options both in balancing as well as in leveling.

If you set the average speed to 100, then you have a much finer control over the results.

However, the total numbers will go up and become more difficult to handle. Let's assume the average action time should be five seconds, that will result in a total of 300 frames.

With an average of 10, that would be 3000 "time points" for a single action, with an average of 100 that would become 30000 "time points" for the scale.

For the player that would make no difference as the numbers would be scaled to a bar of probably 100 pixels anyway and he doesn't know if a single pixel would stand for 30 or 300 time points, but the developer would have to handle those numbers in the database.

So where would you place the basic scale to allow for finer balancing on one side without the numbers becoming too large and cumbersome on the other side?
If you use atb fill percentages instead of atb values, and use floating point numbers instead of integers, to think about the issue, you would likely come up with very different answers.

First and foremost, the clock isn't restricted to use integers, it can(and should imo) use floating point numbers instead.

Regardless of the clock scale, the atb fill percentage always ranges from 0 to 100%. What changes is the atb fill percentage rate of change(per frame), i.e., atb fill rate. It's the atb fill percentage and rate that really matters.

This will likely cause those floating point numbers to have more than 10 digits frequently, but in my experience, it's almost always a non-issue.

Second, to reflect the battler speed, you can either alter the atb fill rate and/or the maximum atb fill percentage(the minimum atb fill percentage needed to acquire actions). Although I highly recommend the former way, and latter way can work too(If you feel you mastered the battler atb clock, you can even try both).

The faster the battler, the higher the atb fill rate and/or the lower the maximum atb fill percentage.
 
Last edited by a moderator:

Andar

Veteran
Veteran
Joined
Mar 5, 2013
Messages
31,365
Reaction score
7,676
First Language
German
Primarily Uses
RMMV
That way only works if you decide to frame the ATB scale between zero and action (100%). But that is something I want to change in my own variant (as described in my development topic).


If I break that frame and have actions at zero, I don't have a top fixure and will not be able to use percentages, but need to use fixed numbers (there is no point in using floats if you don't need to enforce a top barrier).


I admit that it will create a few additional problems in handling (like no percentage rates and a few others), but it will also eliminate the need for other points that your principles consider advanced parts (like no need for freezing the scale).
 

DoubleX

Just a nameless weakling
Veteran
Joined
Jan 2, 2014
Messages
1,787
Reaction score
939
First Language
Chinese
Primarily Uses
N/A
That way only works if you decide to frame the ATB scale between zero and action (100%). But that is something I want to change in my own variant (as described in my development topic).

If I break that frame and have actions at zero, I don't have a top fixure and will not be able to use percentages, but need to use fixed numbers (there is no point in using floats if you don't need to enforce a top barrier).

I admit that it will create a few additional problems in handling (like no percentage rates and a few others), but it will also eliminate the need for other points that your principles consider advanced parts (like no need for freezing the scale).
The top fixture is always there - It's the maximum possible starting point of the delay drop in your case. You've to give it a cap as you can't have infinite delays.

For instance, let's say your maximum possible starting point of the delay is 300,000,000 points(insanely large) and the delay can reach to 0 points by decrements with 1 point as the unit. You can just divide the scale by, say, 100,000, so the maximum becomes 3,000(much more manageable) points, and the unit of delay drop is 0.00001 point, which is a floating point number without too much digits(again, floating point numbers with 10+ digits are completely ok and normal).

On a side note, if you want, the atb fill percentage can go beyond 100%(up to 1000% or whatever), although I'd say it'd be quite difficult to implement(just think of the command synergy battle as a variant of atb).

Also, if you wish, you can always reduce the atb fill percentage by certain amounts if certain movement restriction states are applied. Freezing is just the closest replication of the default RMVXA movement restriction behaviors.

As I'm still talking about basic atb systems, I don't want to introduce too much changes yet. So freezing is just the default but not the only option, and introducing other options can be done via adding features.

P.S.: I've reread what I said several times, and I still failed to understand why you think I've ever said freezing is needed. You might mixed resetting the battler atb clock with freezing, as freezing is just 1 way of resetting the battler atb clock :)
 
Last edited by a moderator:

Andar

Veteran
Veteran
Joined
Mar 5, 2013
Messages
31,365
Reaction score
7,676
First Language
German
Primarily Uses
RMMV
Nope, there is no need for a top fixture - because that is NOT a starting point in my definition.


Without surprise effects, the starting point is zero in my variant - the player can act at battlestart.


Let's say the regular action has a delay of 3000 like you think manageable, so the player makes that action, the delay goes to 3000 and drops at his fixed rate from there.


Next action the player chooses a special heavy damage skill that has a delay of 10,000 - so then the player has to wait three turns for the next action if the enemy survives.


Or let's say that the player is hit by paralysis - in your base ATB such effects would require a counter on how long to stop the ATB-refill. In my variant, it would simply add 30,000 delay for a 10-turn-paralysis.


But that 30,000 would never be reachable with regular actions or skills.
 

DoubleX

Just a nameless weakling
Veteran
Joined
Jan 2, 2014
Messages
1,787
Reaction score
939
First Language
Chinese
Primarily Uses
N/A
Nope, there is no need for a top fixture - because that is NOT a starting point in my definition.

Without surprise effects, the starting point is zero in my variant - the player can act at battlestart.

Let's say the regular action has a delay of 3000 like you think manageable, so the player makes that action, the delay goes to 3000 and drops at his fixed rate from there.

Next action the player chooses a special heavy damage skill that has a delay of 10,000 - so then the player has to wait three turns for the next action if the enemy survives.

Or let's say that the player is hit by paralysis - in your base ATB such effects would require a counter on how long to stop the ATB-refill. In my variant, it would simply add 30,000 delay for a 10-turn-paralysis.

But that 30,000 would never be reachable with regular actions or skills.
I think I've to elaborate this time.

1. Even in a basic atb system(which is nowhere near truly powerful ones with lots of features added), the battler atb clock can be used in this way:

- Checks if the battler's inputted actions should be executed. When it's true, that action will be executed and then that battler can input actions again; When it's false, that action will need to wait until the aforementioned check returns true.

It means before inputting actions, the atb fill percent is always 0. After inputting actions, it starts to fill from 0 to 100%. When it reached 100%, the action will be executed.

However, a feature can be added to let different battlers/actions to have different starting atb fill percentage right after inputting actions. 0% is just the closest replication of the default RMVXA battle behaviors.

Let's say one's 50% and another is 75%. Suppose filling from 50% to 100% needs 2 turns, then filling from 75% to 100% needs 1 turn.

A cap can also be added to ensure that the starting atb fill percentage right after inputting actions is always at least, say, 50%, meaning there's always at least 50% room for movement restriction states to be reduced.

2. No additional counters are needed for the atb refill stoppage(although they can be used instead) - It's counted by the state duration in turns. If a feature's added, the state duration can be changed to seconds, number of actions the state owner executed while having the state, or other units.

3. The top fixture is about your's delay itself. Let's say without getting hit by movement restriction states, it can only go up to 300,000, and with that, it can go up to 300,000,000. Either number can't be infinite, it has to be capped somewhere. Also, let's say each hit of paralysis adds the delay by 300. You can't let the delay additions go infinitely(once per paralysis hit of course), they've to be stopped somewhere.

4. Basic atb systems aren't restricted from adding features, as long as they're well-written. Truly powerful ones can have up to 20+ additional features, or even more.
 
Last edited by a moderator:

Milena

The woman of many questions
Veteran
Joined
Jan 26, 2014
Messages
1,281
Reaction score
106
First Language
Irish
Primarily Uses
N/A
By any chance is there a way you could give us a template or an example how to start it though?
 

DoubleX

Just a nameless weakling
Veteran
Joined
Jan 2, 2014
Messages
1,787
Reaction score
939
First Language
Chinese
Primarily Uses
N/A
By any chance is there a way you could give us a template or an example how to start it though?
Have you tried to comprehend Fomar0153's Customisable ATB/Stamina Based Battle System(read Scene_Battle first to grasp the overall picture before reading other details)? I think it's pretty close to a basic atb system:

http://www.rpgmakervxace.net/topic/467-customisable-atbstamina-based-battle-system/?hl=+atb%20+system

If that's not enough though, I might consider writing a basic atb system myself :)
 
Last edited by a moderator:

DoubleX

Just a nameless weakling
Veteran
Joined
Jan 2, 2014
Messages
1,787
Reaction score
939
First Language
Chinese
Primarily Uses
N/A
When reading an atb system script, you'll first want to check its feature set to know what its implementations need to do. Then when reading those implementations, you'll want to read the atb frame update methods first to grasp the overall picture, as any atb system script needs them to work and many other atb features are linked to them.

Now let's use an existing atb system script that's close to a basic atb system - Fomar0153's Customisable ATB/Stamina Based Battle System, to share my opinions on how to read existing ones.

Brief analysis of Fomar0153's Customisable ATB/Stamina Based Battle System

1. Feature Set

MAX_STAMINA = 1000 RESET_STAMINA = true # If ATB is set to false then the bars won't appear and # the pauses where the bars would be filling up are removed # effectively turning this into a CTB system ATB = false SAMINA_GAUGE_NAME = "ATB" ENABLE_PASSING = true PASSING_COST = 200 # If seconds per turn is set to 0 then a turn length will be # decided on number of actions TURN_LENGTH = 4 ESCAPE_COST = 500 # If reset stamina is set to true then all characters # will start with a random amount of stamina capped at # the percentage you set. # If reset stamina is set to false then this just # affects enemies. STAMINA_START_PERCENT = 20 # Default skill cost # If you want to customise skill costs do it like this # SKILL_COST[skill_id] = cost SKILL_COST = [] SKILL_COST[0] = 1000 # Attack SKILL_COST[1] = 1000 # Guard SKILL_COST[2] = 500 ITEM_COST = 1000 # If you prefer to have states handle agility buffs then set STATES_HANDLE_AGI to true STATES_HANDLE_AGI = false # In the following section mult means the amount you multiply stamina gains by # if STATES_HANDLE_AGI is set to true then it is only used to determine bar color # with debuffs taking precedence over buffs STAMINA_STATES = [] # Default colour STAMINA_STATES[0] = [1,31,32] # in the form # STAMINA_STATES[STATE_ID] = [MULT,FILL_COLOUR,EMPTY_COLOR] # e.g. Haste STAMINA_STATES[10] = [2,4,32] # e.g. Stop STAMINA_STATES[11] = [0,8,32] # e.g. Slow STAMINA_STATES[12] = [0.5,8,32] #-------------------------------------------------------------------------- # ● New Method stamina_gain #-------------------------------------------------------------------------- def self.stamina_gain(battler) return ((2 + [0, battler.agi / 10].max) * self.stamina_mult(battler)).to_i end #-------------------------------------------------------------------------- # ● New Method stamina_gain #-------------------------------------------------------------------------- def self.stamina_mult(battler) return 1 if STATES_HANDLE_AGI mult = STAMINA_STATES[0][0] for state in battler.states unless STAMINA_STATES[state.id].nil? mult *= STAMINA_STATES[state.id][0] end end return mult end #-------------------------------------------------------------------------- # ● New Method stamina_gain #-------------------------------------------------------------------------- def self.stamina_colors(battler) colors = STAMINA_STATES[0] for state in battler.states unless STAMINA_STATES[state.id].nil? if STAMINA_STATES[state.id][0] < colors[0] or colors[0] == 1 colors = STAMINA_STATES[state.id] end end end return colors end #-------------------------------------------------------------------------- # ● New Method stamina_start #-------------------------------------------------------------------------- def self.stamina_start(battler) battler.stamina = rand(MAX_STAMINA * STAMINA_START_PERCENT / 100) end
2. ATB Frame Update

#-------------------------------------------------------------------------- # ● New Method process_stamina #-------------------------------------------------------------------------- def process_stamina @actor_command_window.close return if @subject BattleManager.advance_turn all_battle_members.each do |battler| battler.stamina_gain end @status_window.refresh if @status_window.close? @status_window.open end if BattleManager.escaping? $game_party.battle_members.each do |battler| if battler.stamina < CBS::MAX_STAMINA $game_troop.members.each do |enemy| if enemy.stamina == CBS::MAX_STAMINA enemy.make_actions @subject = enemy end end return end end unless BattleManager.process_escape $game_party.battle_members.each do |actor| actor.stamina -= CBS::ESCAPE_COST end BattleManager.set_escaping(false) unless CBS::ATB end end all_battle_members.each do |battler| if battler.stamina == CBS::MAX_STAMINA battler.make_actions @subject = battler if @subject.inputable? and battler.is_a?(Game_Actor) @actor_command_window.setup(@subject) BattleManager.set_actor(battler) end return end end end
In order to grasp the overall picture, when reading the atb frame update methods, you should focus on what they do first, before trying to figure out how they're actually done. For instance:

- @actor_command_window.close closes the actor command window when the global atb clock runs.

- return if @subject stops the atb frame update when a battler's executing an action.

- @status_window.refresh refreshes the entire status window(including the atb bars) when the global atb clock runs.

- BattleManager.advance_turn increases the battle turn number when the battle turn clock reaches its max.

- battler.stamina_gain increases the battler atb clock unless the max's reached or the battler's unmovable.

- This part:

    if BattleManager.escaping?      $game_party.battle_members.each do |battler|        if battler.stamina < CBS::MAX_STAMINA          $game_troop.members.each do |enemy|            if enemy.stamina == CBS::MAX_STAMINA              enemy.make_actions              @subject = enemy            end          end          return        end      end      unless BattleManager.process_escape        $game_party.battle_members.each do |actor|          actor.stamina -= CBS::ESCAPE_COST        end        BattleManager.set_escaping(false) unless CBS::ATB      end    end
First check if the party's trying to escape while the global atb clock's running. If that's the case, then for each actor not being able to act, an enemy will act if that enemy can act. Also, unless the party escape succeeds, all actors will have to pay the failed escape cost by reducing their battler atb clocks.

- This part:

if battler.stamina == CBS::MAX_STAMINA battler.make_actions @subject = battler if @subject.inputable? and battler.is_a?(Game_Actor) @actor_command_window.setup(@subject) BattleManager.set_actor(battler) end return end
Checks if a battler can act. If that's the case, for an enemy or uninputable actor, that enemy will act; for an inputable actor, that actor's command window will be setup to let players input actions.

To sum up:

The atb frame update method always closes the actor command window, but only continues to run when no battler's executing actions. When it continues to run, it first redraws the entire status window(including the atb bars), then updates the battle turn clock, then the battler atb clocks, then party escape processing, and finally battler's action input and execution setups.

3. ATB Wait Condition

ATB wait condition methods can only be called by ATB frame update methods or their callers. As thiat script's atb frame update method doesn't call atb wait condition methods(except the necessary return when a battler's executing an action), they must be called by atb frame update method callers:

#-------------------------------------------------------------------------- # ● Rewrote update #-------------------------------------------------------------------------- def update super if (CBS::ENABLE_PASSING and @actor_command_window.active) and Input.press?:)A) command_pass end if BattleManager.in_turn? and !inputting? while @subject.nil? and !CBS::ATB process_stamina end if CBS::ATB process_stamina end process_event process_action end BattleManager.judge_win_loss end
For the atb mode of that script, the atb wait condition is "BattleManager.in_turn? and !inputting?":

def inputting? return @actor_command_window.active || @skill_window.active || @item_window.active || @actor_window.active || @enemy_window.active end
So the atb frame update will never trigger when the actor command, skill, item, actor or enemy window's active, meaning the loosest(easiest to be met) atb wait condition's used(wait whenever players can input actions).

4. Action Execution

  #--------------------------------------------------------------------------  # ● Aliases use_item  #--------------------------------------------------------------------------  alias cbs_use_item use_item  def use_item    cbs_use_item    @subject.stamina_loss  end
Code:
  #--------------------------------------------------------------------------  # ● New Method on_turn_end  #--------------------------------------------------------------------------  def stamina_loss    if self.actor?      @stamina -= input.stamina_cost    else      @stamina -= @actions[0].stamina_cost    end    BattleManager.add_action  end
Code:
  #--------------------------------------------------------------------------  # ● New Increase action counter  #--------------------------------------------------------------------------  def self.add_action    return if @actions_per_turn.nil?    @turn_counter += 1    if @turn_counter == @actions_per_turn and CBS::TURN_LENGTH == 0      $game_troop.increase_turn      SceneManager.scene.turn_end      @turn_counter = 0    end  end
Right after executing an action, the battler atb clock will be reduced by a cost specified by the action. If the battle turn clock uses number of actions as the unit, it'll be increased by 1 now. When it reaches its max, the battle turn will increase by 1.

5. Action Validity

It uses the default RMVXA action validity check, which works here as the atb clock will only run when the players can't input actions.

6. Starting battler atb clock status

#-------------------------------------------------------------------------- # ● Rewrote setup #-------------------------------------------------------------------------- def self.setup(troop_id, can_escape = true, can_lose = false) init_members $game_troop.setup(troop_id) @can_escape = can_escape @can_lose = can_lose make_escape_ratio @escaping = false @turn_counter = 0 @actions_per_turn = $game_party.members.size + $game_troop.members.size ($game_party.members + $game_troop.members).each do |battler| if battler.is_a?(Game_Enemy) or CBS::RESET_STAMINA CBS.stamina_start(battler) end end end
Code:
  #--------------------------------------------------------------------------  # ● New Method stamina_start  #--------------------------------------------------------------------------  def self.stamina_start(battler)    battler.stamina = rand(MAX_STAMINA * STAMINA_START_PERCENT / 100)  end
As self.stamina_start is within the user editable region, it lets users set the starting battler atb clock.

7. Battler atb clock display

#-------------------------------------------------------------------------- # ● Rewrote draw_gauge_area_with_tp #-------------------------------------------------------------------------- def draw_gauge_area_with_tp(rect, actor) draw_actor_hp(actor, rect.x + 0, rect.y, 72) draw_actor_mp(actor, rect.x + 82, rect.y, 64) draw_actor_tp(actor, rect.x + 156, rect.y, 64) draw_actor_stamina(actor, rect.x + 240, rect.y, 108) if CBS::ATB end #-------------------------------------------------------------------------- # ● Rewrote draw_gauge_area_without_tp #-------------------------------------------------------------------------- def draw_gauge_area_without_tp(rect, actor) draw_actor_hp(actor, rect.x + 0, rect.y, 134) draw_actor_mp(actor, rect.x + 144, rect.y, 76) draw_actor_stamina(actor, rect.x + 240, rect.y, 108) if CBS::ATB end #-------------------------------------------------------------------------- # ● New Method draw_actor_stamina #-------------------------------------------------------------------------- def draw_actor_stamina(actor, x, y, width = 124) draw_gauge(x, y, width, actor.stamina_rate, text_color(CBS.stamina_colors(actor)[2]),text_color(CBS.stamina_colors(actor)[1])) change_color(system_color) draw_text(x, y, 30, line_height, CBS::SAMINA_GAUGE_NAME) end
Code:
  #--------------------------------------------------------------------------  # ● New Method stamina_rate  #--------------------------------------------------------------------------  def stamina_rate    @stamina.to_f / CBS::MAX_STAMINA  end
As that script always redraws the entire status window and never redraws the atb bars only, draw_actor_stamina won't be called directly by the atb frame update method. Instead, it's linked to @status_window.refresh.

stamina_rate, which is the ratio of the current battler atb clock over the maximum battler atb clock, is used to set the atb bar fill rate when drawing the atb bars.

8. Battler Atb Clock Reset Upon Becoming Unmovable

def stamina_gain return if not movable? @stamina = [CBS::MAX_STAMINA, @stamina + CBS.stamina_gain(self)].min end
Instead of resetting the battler atb clock to any value, that script just freeze it.

If you've read the whole script, and can follow up so far, you should've a solid understanding of that script's implementations as a whole. Reading the remaining parts to complete the whole picture shouldn't be hard, as long as you've played with that script for a while.
 
Last edited by a moderator:

DoubleX

Just a nameless weakling
Veteran
Joined
Jan 2, 2014
Messages
1,787
Reaction score
939
First Language
Chinese
Primarily Uses
N/A
DoubleX RMVXA Basic ATB has just been written. I hope it can help you better understand how an atb script can be written(later I might elaborate its implementations) :)
 

DoubleX

Just a nameless weakling
Veteran
Joined
Jan 2, 2014
Messages
1,787
Reaction score
939
First Language
Chinese
Primarily Uses
N/A
Now let's have a closer look on some important details of implementing an atb system script. Again, you're assumed to have basic knowledge to the default RMVXA battle flow implementations.

You may have noticed the main difference between the default RMVXA battle system and an atb system is their battle flows, so you'll want to pay special attention to the implementations of that of the former.

Action Input Phase

Examine the implementations of the next and prior input commands in the action input phase:

#-------------------------------------------------------------------------- # * To Next Command Input #-------------------------------------------------------------------------- def self.next_command begin if !actor || !actor.next_command @actor_index += 1 return false if @actor_index >= $game_party.members.size end end until actor.inputable? return true end #-------------------------------------------------------------------------- # * To Previous Command Input #-------------------------------------------------------------------------- def self.prior_command begin if !actor || !actor.prior_command @actor_index -= 1 return false if @actor_index < 0 end end until actor.inputable? return true end
Code:
  #--------------------------------------------------------------------------  # * To Next Command Input  #--------------------------------------------------------------------------  def next_command    return false if @action_input_index >= @actions.size - 1    @action_input_index += 1    return true  end  #--------------------------------------------------------------------------  # * To Previous Command Input  #--------------------------------------------------------------------------  def prior_command    return false if @action_input_index <= 0    @action_input_index -= 1    return true  end
Code:
  #--------------------------------------------------------------------------  # * To Next Command Input  #--------------------------------------------------------------------------  def next_command    if BattleManager.next_command      start_actor_command_selection    else      turn_start    end  end  #--------------------------------------------------------------------------  # * To Previous Command Input  #--------------------------------------------------------------------------  def prior_command    if BattleManager.prior_command      start_actor_command_selection    else      start_party_command_selection    end  end
They traverse from the party command window to the action execution phase via all inputable actors' action slots(from the 1st action slot of the 1st inputable actor to the last action slot of the last inputable actor). Those Scene_Battle methods defines "what the next and prior input command does", while those BattleManager ones defines "how the next and prior input command works"(Game_Actor ones manipulates the action input index of the actor).

Clearly, such battle flow won't work in any atb system, as it's built upon battles having a global action input and execution phase applied to all battlers, while each battler in an atb system has their own action input and execution phase, meaning some of those method contents or those called by them needs to be addressed.

Upon more detailed inspection, however, you'll realize you only have to change a little, although you can redefine them all if you want. For example, in DoubleX RMVXA Basic ATB, inputable? is changed to something like this:

#----------------------------------------------------------------------------| # Alias method: inputable? | #----------------------------------------------------------------------------| alias inputable_batb? inputable? def inputable? # Rewritten to check if there are action slots and they're all unconfirmed return false unless inputable_batb? && @actions.size > 0 actor? && !@actions.all? { |act| act.batb_confirm } # end # inputable?(batb_confirm is true if and only if the action slot storing that action has been finished inputting)
Suddenly you might find that you don't have to change the rest of the above methods, although you'll have to change something else. It's because in DoubleX RMVXA Basic ATB:

1. Battlers makes actions if and only if they become able to act

2. Battlers clears actions if they become unable to act

3. (Implied by 1. and 2.)inputable? will return true for an actor only if he/she/it's full atb, no auto battle nor restrictions, and has at least 1 action slots that aren't inputted

So when an actor becomes able to act, inputable? returns true and his/her/its actor command window will be setup(via new methods calling start_actor_command_selection). When the currently pointed action slot of that actor's inputted, it'll be confirmed and his/her/its next action slot will be selected via BattleManager.next_command(which calls actor.next_command to add the action input index by 1) if the current action slot isn't the last one, or he/she/it'll proceed to his/her/its own action execution phase(executes instantly or charge for a while first).

Similarly, when the prior input command(cancel) of that actor command window's called, the prior action slot of that actor will be selected via BattleManager.prior_command(which calls actor.prior_command to minus the action input index by 1) if the current action slot isn't the 1st one, or the party command window will be setup instead.


Action Execution Phase

Examine the implementations of the action order queue and action executions in the action execution phase:

#-------------------------------------------------------------------------- # * Create Action Order #-------------------------------------------------------------------------- def self.make_action_orders @action_battlers = [] @action_battlers += $game_party.members unless @surprise @action_battlers += $game_troop.members unless @preemptive @action_battlers.each {|battler| battler.make_speed } @action_battlers.sort! {|a,b| b.speed - a.speed } end
Code:
  #--------------------------------------------------------------------------  # * Get Next Action Subject  #    Get the battler from the beginning of the action order list.  #    If an actor not currently in the party is obtained (occurs when index  #    is nil, immediately after escaping in battle events etc.), skip them.  #--------------------------------------------------------------------------  def self.next_subject    loop do      battler = @action_battlers.shift      return nil unless battler      next unless battler.index && battler.alive?      return battler    end  end
Code:
  #--------------------------------------------------------------------------  # * Battle Action Processing  #--------------------------------------------------------------------------  def process_action    return if scene_changing?    if !@subject || !@subject.current_action      @subject = BattleManager.next_subject    end    return turn_end unless @subject    if @subject.current_action      @subject.current_action.prepare      if @subject.current_action.valid?        @status_window.open        execute_action      end      @subject.remove_current_action    end    process_action_end unless @subject.current_action  end
In the default RMVXA battle system, when the battle enters the global action execution phase, make_action_orders will be called to make the action order queue(FIFO data structure) for all battlers. While it's not empty, the most up front battler will process all his/her/its actions and leave that queue(or just leave the queue if he/she/it isn't alive). Eventually, that queue will become empty and the battle will enter the global action input phase.

As mentioned, such setup won't work in any atb system. At the very least, the action order queue needs to go or battlers in that queue could frequently shift positions in that queue due to the different speeds and action execution phase entrance timings among them.

A better way to utilize @action_battlers is to make it a simple array storing all battlers having action slots. As there could be at least 1 battler being ready to execute actions at any frame and a battler needs to have action slots to execute actions, all battlers in @action_battlers should be checked per frame to see if their atb reaches the max(and all action slots are confirmed for actors). A battler in @action_battlers will be removed from that array right after executing all his/her/its actions.

For example, in DoubleX RMVXA Basic ATB:

#----------------------------------------------------------------------------| # Alias method: make_actions | #----------------------------------------------------------------------------| alias make_actions_batb make_actions def make_actions make_actions_batb # Added to mark that this battler has action slots BattleManager.action_battlers << self # end # make_actions
Code:
  #----------------------------------------------------------------------------|  #  New method: reset_batb_val                                                |  #  - Resets the battler's atb value                                          |  #----------------------------------------------------------------------------|  # reset: The battler action reset flag  def reset_batb_val(reset = true)    # Sets the battler's atb value as its min and clear all battler's actions    if reset      @batb_val = 0.0      @batb_val_change = true    end    clear_actions    BattleManager.action_battlers.delete(self)    BattleManager.clear_actor if actor? && BattleManager.actor == self    #  end # reset_batb_val
Code:
  #----------------------------------------------------------------------------|  #  Rewrite method: process_action                                            |  #----------------------------------------------------------------------------|  def process_action    return if scene_changing?    # Rewritten to execute all battler's actions unless there are forced ones    return process_batb_act if @subject    BattleManager.action_battlers.each { |battler|      next if battler.batb_val < 100.0 || battler.inputable?      process_batb_act while (@subject = battler if battler.current_action)      @subject = nil    }    #  end # process_action
Whenever a battler becomes able to act, make_actions will be called and that battler will join @action_battlers; Whenever a battler becomes unable to act, reset_batb_val will be called and that battler will leave @action_battlers.

Whenever process_action is called(which is called per frame), each battler in @action_battlers will be checked to see if they've full atb(and not inputable for actors). If there's any, the action execution subject will be set as that battler and all his/her/its actions will be processed. After that, the action execution subject will be cleared. This repeats until @action_battlers is empty or no battler in @action_battlers passes the check.


Turn Start/End

Examine the implementations of the start and the end of the action execution phase:

#-------------------------------------------------------------------------- # * Start Turn #-------------------------------------------------------------------------- def turn_start @party_command_window.close @actor_command_window.close @status_window.unselect @subject = nil BattleManager.turn_start @log_window.wait @log_window.clear end
Code:
  #--------------------------------------------------------------------------  # * End Turn  #--------------------------------------------------------------------------  def turn_end    all_battle_members.each do |battler|      battler.on_turn_end      refresh_status      @log_window.display_auto_affected_status(battler)      @log_window.wait_and_clear    end    BattleManager.turn_end    process_event    start_party_command_selection  end
Code:
  #--------------------------------------------------------------------------  # * Start Turn  #--------------------------------------------------------------------------  def self.turn_start    @phase = :turn    clear_actor    $game_troop.increase_turn    make_action_orders  end
Code:
  #--------------------------------------------------------------------------  # * End Turn  #--------------------------------------------------------------------------  def self.turn_end    @phase = :turn_end    @preemptive = false    @surprise = false  end
If untouched, some problems would come into play in an atb system:

1. Scene_Battle turn_start is called by next_command when the last action slot of the last inputable actor's reached. As consecutive action input for more than 1 actors are likely rare, whenever that's not the case, after finishing inputting an actor, turn_start would be called, calling battle_start and increasing the battle turn number by 1. Also, the same doesn't apply to enemies, leading to seemingly inconsistent battle turn starts(in some players' view).

2. Scene_Battle turn_end is called by process_action when no battler can act at that moment. As process_action needs to be called per frame to ensure all actions will be executed at the correct moment, it's extremely rare that there'll be always at least 1 battler that can process actions whenever process_action is called. When that's not the case, turn_end will be called, calling battle events with condition "at the end of a turn" and each battler's on_turn_end(triggering stuffs triggered on turn end), leading to seemingly inconsistent battle turn starts(in some players' view).

3. Unless it's intended to define a battle turn as action executions of a battler(so the battle turn always starts/ends right before/after executing all actions of a battler), there's no way to synchronize battle turn start and turn end(Each subsequent battle turn start should only be triggered after triggering each battle turn end and vice versa).

One way to solve this is to make the battle turn start and end independent of action execution triggerings. A separate counter, with units as seconds or number of executed actions(or battlers)or something else, should be implemented to increase the battle turn count whenever that counter reaches its max. For example, DoubleX RMVXA Basic ATB made one:

#----------------------------------------------------------------------------| # New method: batb_update | # - Runs the atb frame update | #----------------------------------------------------------------------------| def batb_update # Updates the turn and all battlers' atb clocks batb_update_turn(0) BattleManager.batb_update_battlers list = BattleManager.action_battlers.select { |battler| battler.actor? && battler.inputable? }.collect { |battler| battler.index } batb_update_windows(list) unless list.include?(@status_window.index) @status_window.refresh_batb_bars # end # batb_update
Code:
  #----------------------------------------------------------------------------|  #  New method: exec_batb_act                                                 |  #  - Executes all valid actions of the action execution subject              |  #----------------------------------------------------------------------------|  def exec_batb_act    # Disables party escape during executions and updates the turn atb clock    @status_window.open    BattleManager.batb_can_esc = false    execute_action    BattleManager.batb_can_esc = true    batb_update_turn(1)    #  end # exec_batb_act
Code:
  #----------------------------------------------------------------------------|  #  New method: batb_update_turn                                              |  #  - Updates the turn atb clock                                              |  #----------------------------------------------------------------------------|  # code: The current turn atb clock unit code  def batb_update_turn(code)    # Increases the turn number and resets its atb clock when its max's reached    return unless code == DoubleX_RMVXA::BATB.batb_turn_unit_code    max_clock = DoubleX_RMVXA::BATB.batb_max_turn_unit    max_clock *= Graphics.frame_rate if code == 0    return unless (@batb_turn_clock[code] += 1) >= max_clock    @batb_turn_clock[code] = 0    turn_end    #  end # batb_update_turn
The counter here can have either seconds or number of executed actions as the unit. batb_update_turn(battle turn counter update method) will be called per batb_update(atb frame update method) call in the former case and per exec_batb_act(action execution method) call in the latter case respectively. When the counter reaches it max, the battle turn end will be called:

#----------------------------------------------------------------------------| # Rewrite method: turn_end | #----------------------------------------------------------------------------| def turn_end ($game_party.battle_members + $game_troop.members).each { |battler| battler.on_turn_end refresh_status @log_window.display_auto_affected_status(battler) @log_window.wait_and_clear } # Added to increase the battle turn number by 1 $game_troop.increase_turn # BattleManager.turn_end process_event # Removed to stop opening the party command window on turn end # end # turn_end
It increases the battle turn count by 1, making battle turn start and battle turn end effectively always trigger at the exactly same frame.
 
Last edited by a moderator:

DoubleX

Just a nameless weakling
Veteran
Joined
Jan 2, 2014
Messages
1,787
Reaction score
939
First Language
Chinese
Primarily Uses
N/A
The below is my steps of writing DoubleX RMVXA Basic ATB:

The feature set

#==============================================================================|# ** Script Call Info |#------------------------------------------------------------------------------|# * Battler manipulations |# 1. batb_val |# - Returns the battler's atb fill percentage |# 2. reset_batb_val(reset) |# - Clears all battler's actions |# - Empties the battler's atb bar as well if reset is true |#==============================================================================|
Code:
    # Sets the base atb fill time from empty to full as BASE_FILL_T seconds    # If BASE_FILL_T_VAR_ID is a natural number, the value of variable with id    # BASE_FILL_T_VAR_ID will be used instead of using BASE_FILL_T    BASE_FILL_T = 5    BASE_FILL_T_VAR_ID = 0    # Sets the atb wait condition code as WAIT_COND_CODE    # If WAIT_COND_CODE_VAR_ID is a natural number, the value of variable with    # id WAIT_COND_CODE_VAR_ID will be used instead of using WAIT_COND_CODE    # Available atb wait condition codes:    # 0 - Wait only when an action's executing or a message's showing    # 1 - Wait when an animation's playing as well    # 2 - Wait when the target selection window's shown as well    # 3 - Wait when the skill/item selection window's shown as well    # 4 - Wait when players can input actions as well    WAIT_COND_CODE = 0    WAIT_COND_CODE_VAR_ID = 0    # Sets the code of the unit of the turn clock as TURN_UNIT_CODE    # If TURN_UNIT_CODE_VAR_ID is a natural number, the value of variable with    # id TURN_UNIT_CODE_VAR_ID will be used instead of using TURN_UNIT_CODE    # Available codes of the unit of the turn clock:    # 0 - Seconds    # 1 - Number of executed actions in the same turn    TURN_UNIT_CODE = 0    TURN_UNIT_CODE_VAR_ID = 0    # Sets the maximum turn clock unit as MAX_TURN_UNIT    # If MAX_TURN_UNIT_VAR_ID is a natural number, the value of variable with id    # MAX_TURN_UNIT_VAR_ID will be used instead of using MAX_TURN_UNIT    MAX_TURN_UNIT = 5    MAX_TURN_UNIT_VAR_ID = 0    # Sets the atb fill mode as ATB_FILL_MODE    # If ATB_FILL_MODE_VAR_ID is a natural number, the value of variable with id    # ATB_FILL_MODE_VAR_ID will be used instead of using ATB_FILL_MODE    # Available atb fill modes:    # 0 - Lets battlers input and execute actions when that battler's atb's full    #     Empties and refills that battler's atb right after executing actions    # 1 - Lets battlers input actions when that battler's atb's empty    #     Refills that battler's atb right after inputting actions    #     Let battlers execute actions when that battler's atb's full    #     Empties and that battler's atb right after executing actions    #     The sum of invocation speeds of all inputted skills/items will be    #     added to that battler's agi in that battler's atb refill rate    # The value of variable with id ATB_FILL_MODE_VAR_ID should remain the same    # during battles to ensure proper atb fillings and action executions    ATB_FILL_MODE = 0    ATB_FILL_MODE_VAR_ID = 0    # (v1.01a+)Sets the starting atb value mode as ATB_START_MODE    # If ATB_START_MODE_VAR_ID is a natural number, the value of variable with    # id ATB_START_MODE_VAR_ID will be used instead of using ATB_START_MODE    # The starting atb value is always 0.0 for:    # - unmovable battlers    # - atb fill mode as 1    # - actors with start type surprise    # - enemies with start type preemptive    # The starting atb value is always 100.0 for:    # - movable actors with atb fill mode as 0 and start type preemptive    # - movable enemies with atb fill mode as 0 and start type surprise    # Available starting atb value mode for normal start:    # - agi is the battler's agi    # - param_max(6) is the maximum value of any battler's agi    # 0 - 0.0    # 1 - 100.0 * agi / param_max(6)    ATB_START_MODE = 0    ATB_START_MODE_VAR_ID = 0    # (v1.02a+)Sets the party escape invocation speed as PARTY_ESC_SPEED    # It'll only be used with ATB_FILL_MODE being 1    # With ATB_FILL_MODE being 1, if no actor would be able to act without the    # party escape attempt, that attempt will fail immediately    # If PARTY_ESC_SPEED_VAR_ID is a natural number, the value of variable with    # id PARTY_ESC_SPEED_VAR_ID will be used instead of using PARTY_ESC_SPEED    PARTY_ESC_SPEED = 0    PARTY_ESC_SPEED_VAR_ID = 0    # Sets the atb refill rate code as ATB_RATE_CODE    # If ATB_RATE_CODE_VAR_ID is a natural number, the value of variable with id    # ATB_RATE_CODE_VAR_ID will be used instead of using ATB_RATE_CODE    # Available atb refill rate code:    # - speed is    #   agi for atb fill mode 0    #   agi + batb_item_speed_sum for atb fill mode 1    # - agi is the battler's agi    # - batb_item_speed_sum is the sum of invocation speeds(+ attack speed for     #   the attack skill) of all inputted skills/items    # - DoubleX_RMVXA::BATB.base_batb_fill_t is the base atb fill time    # - Graphics.frame_rate is the number of frames per second    # - BattleManager.batb_avg_agi is the average of all battler's agi    # 0 - speed * 100.0 / DoubleX_RMVXA::BATB.base_batb_fill_t /     #     Graphics.frame_rate    # 1 - (formula in code 0) / BattleManager.batb_avg_agi    # 2 - Same as 1, except BattleManager.batb_avg_agi will always be    #     reevalauted per frame instead of just at the start of a battle    ATB_RATE_CODE = 1    ATB_RATE_CODE_VAR_ID = 0    # Sets the code of the reset atb mechanism as ATB_RESET_CODE    # If ATB_RESET_CODE_VAR_ID is a natural number, the value of variable with    # id ATB_RESET_CODE_VAR_ID will be used instead of using ATB_RESET_CODE    # Available codes of the atb reset mechanism:    # 0 - Freezes the atb refill while not movable    # 1 - Same as 0, except the atb will be emptied when becoming unmovable    ATB_RESET_CODE = 1    ATB_RESET_CODE_VAR_ID = 0    # Sets the 1st atb bar color as text color ATB_BAR_COLOR1    # If ATB_BAR_COLOR1_VAR_ID is a natural number, the value of variable with    # id ATB_BAR_COLOR1_VAR_ID will be used instead of using ATB_BAR_COLOR1    # The value of variable with id ATB_BAR_COLOR1_VAR_ID should remain the same    # during battles to ensure proper atb bar color displays    ATB_BAR_COLOR1 = 7    ATB_BAR_COLOR1_VAR_ID = 0    # Sets the 2nd atb bar color as text color ATB_BAR_COLOR2    # If ATB_BAR_COLOR2_VAR_ID is a natural number, the value of variable with    # id ATB_BAR_COLOR2_VAR_ID will be used instead of using ATB_BAR_COLOR2    # The value of variable with id ATB_BAR_COLOR2_VAR_ID should remain the same    # during battles to ensure proper atb bar color displays    ATB_BAR_COLOR2 = 8    ATB_BAR_COLOR2_VAR_ID = 0    # Sets the atb bar description text as ATB_BAR_TEXT    # If ATB_BAR_TEXT_VAR_ID is a natural number, the value of variable with id    # ATB_BAR_TEXT_VAR_ID will be used instead of using ATB_BAR_TEXT    # The value of variable with id ATB_BAR_TEXT_VAR_ID should remain the same    # during battles to ensure proper atb bar text displays    ATB_BAR_TEXT = "AP"    ATB_BAR_TEXT_VAR_ID = 0
I actually never planned to include any script call, but as I write that script, I realized those accessors/methods can be used as script calls, even by users with just little scripting proficiency.

It's what I think the minimum feature set for any complete ATB system script conforming to the default RMVXA battle features. It lets users set:

- The base battler atb clock fill time applied to all battlers

- The atb wait conditions

- The global battle turn clock unit and its maximum value

- The battler atb clock fill mode applied to all battlers

- The value of the battler atb clock at the start of a battle applied to all battlers

- The party escape "invocation speed" for the reverse battler atb clock fill mode applied to all battlers(The atb bars will charge from empty to full and the party escape attempt will be executed when at least 1 battler reaches full atb)

- The battler atb clock fill rate applied to all battlers(Whether the inputted skills/items' invocation speeds will be factored in, and whether the final rate will be divided by the average of all battlers' agi)

- The battler atb clock reset mechanism applied ty all battlers(Whether it'll just be freezed upon unmovable or it'll be reset as 0 as well)

- The actors' atb bar colors and text applied to all those bars

Also, states' Auto-removal Timing Action End will be triggered right after their owners executed all their actions. It's to conform to the default RMVXA battle features.


The atb frame update

First, I alias update and update_for_wait under Scene_Battle:

#----------------------------------------------------------------------------| # Alias method: update | #----------------------------------------------------------------------------| alias update_batb update def update update_batb # Added to run the atb frame update unless the atb wait condition's met batb_update if !@spriteset.animation? && batb_update? # end # update #----------------------------------------------------------------------------| # Alias method: update_for_wait | #----------------------------------------------------------------------------| alias update_for_wait_batb update_for_wait def update_for_wait update_for_wait_batb # Added to run the atb frame update unless the atb wait condition's met batb_update if @spriteset.animation? && batb_update? # end # update_for_wait
batb_update is the atb frame update method while batb_update? checks against the atb wait conditions.

Normally, batb_update is called by update, but when an animation's playing, it's called by update_for_wait instead. To prevent duplicate batb_update call in the same frame, @spriteset.animation is used to check if an animation's playing, so exactly 1 of those aliased methods will be called per frame update.

Next, I write batb_update under Scene_Battle:

#----------------------------------------------------------------------------| # New method: batb_update | # - Runs the atb frame update | #----------------------------------------------------------------------------| def batb_update # Updates the turn and all battlers' atb clocks batb_update_turn(0) BattleManager.batb_update_battlers list = BattleManager.action_battlers list = list.select { |battler| battler.actor? && battler.inputable? } list.collect! { |battler| battler.index } batb_update_windows(list) unless list.include?(@status_window.index) @status_window.refresh_batb_bars # end # batb_update
It controls what needs to be updated in an atb frame update.

batb_update_turn updates the global battle turn clock with seconds as the unit.

BattleManager.batb_update_battlers updates all battler atb clocks(and the party escape status in the reverse atb mode).

batb_update_windows uses the list of indices of all actors that can input actions to update all windows used by actors(status window, party command window, actor command window, skill/item window, target window).

@status_window.refresh_batb_bars refreshes all actors' atb bars.

To sum up, it updates the underlying data before updating their visuals to ensure those visuals always correctly displays those underlying data.


Tthe ATB wait condition

I write batb_update?(which is called by update and update_for_wait under Scene_Battle) under Scene_Battle:

#----------------------------------------------------------------------------| # New method: batb_update? | # - Checks if the atb frame update should be run | #----------------------------------------------------------------------------| def batb_update? # Checks if the conditions in the current atb wait condition code aren't met return false if scene_changing? || !BattleManager.batb_update? code = DoubleX_RMVXA::BATB.batb_wait_cond_code return true if code == 0 return false if @spriteset.animation? return true if code == 1 return false if @actor_window.active || @enemy_window.active return true if code == 2 return false if @skill_window.active || @item_window.active return true if code == 3 !@actor_command_window.active && !@party_command_window.active # end # batb_update?
I use the atb wait condition code set by users to determine what conditions need to be checked. I check from the one hardest to be met to the one easiest to be met.

Before using that, however, I first check against the always wait cases, and that check's outsourced to batb_update? under BattleManager(regardless of the atb wait conditions):

#----------------------------------------------------------------------------| # New method: batb_update? | # - Checks if the atb frame update can be run | #----------------------------------------------------------------------------| def batb_update? # Returns false for all cases always forbidding atb frame updates return false if $game_party.all_dead? || $game_troop.all_dead? @phase && @phase != :init && !$game_message.visible # end # batb_update?
When the game party or troop's all dead, or the battle's about to be aborted, the battle will end, causing further atb frame updates to be redundant(and harmful in some cases).

At the start of a battle, some extra preparations needs to be done(like applying the battler atb clock values at the start of a battle) before the atb frame update can run.

As updating the atb frame can trigger animation displays and/or open windows, and doing those can interfere with the game messages(or even just crash or freeze the game), updating the atb frame at that moment can be bug prone.


The battler atb clock update

I write batb_update_battlers under Scene_Batlte to update all alive battlers' atb clock:

#----------------------------------------------------------------------------| # New method: batb_update_battlers | # - Updates all alive battlers' atb values and the party escape's statuses | #----------------------------------------------------------------------------| def batb_update_battlers alive_members = $game_party.alive_members (alive_members + $game_troop.alive_members).each { |battler| battler.batb_update } # Checks the atb fill mode and the existence of actors that can act return unless @batb_can_esc && @batb_esc return batb_esc_fail if DoubleX_RMVXA::BATB.batb_fill_mode == 0 return batb_esc_fail unless @action_battlers.any? { |b| b.actor? } batb_esc_end if alive_members.any? { |battler| battler.batb_val >= 100.0 } # end # batb_update_battlers
(It also checks if the party escape attempt should be interrupted or triggered in the reverse atb mode by checking if the atb fill mode changes and at least 1 actor can act and have full atb)

Each battler updates his/her/its atb clock in batb_update under Game_Battler:

#----------------------------------------------------------------------------| # New method: batb_update | # - Updates the battler's atb value | #----------------------------------------------------------------------------| def batb_update # Increases the battler's atb value by its rate and make actions accordingly return if @batb_val >= 100.0 || inputable? || restriction > 3 mode = DoubleX_RMVXA::BATB.batb_fill_mode return make_actions if mode == 1 && @batb_val == 0.0 && @actions.empty? last_batb_val = @batb_val @batb_val += batb_rate @batb_val_change = last_batb_val != @batb_val return unless @batb_val >= 100.0 @batb_val = 100.0 make_actions if mode == 0 # end # batb_update
If the battler's full atb or can input actions or can't move, there's no need to update the battler atb clock, as that battler will either execute or input actions at that moment, or just can't update atb due to being restricted.

In the reverse atb bar mode, if the battler has empty atb and can't act, new empty action slots should be made so that battler can input actions.

@batb_val_change is used to notify the battler's atb bar to be updated, which should be updated if and only if the atb value's changed(Updating an atb bar is relatively expensive so it should only be updated if it's to).

If the battler's atb becomes full and the default atb fill mode is used, new empty action slots should be made so that battler can input actions.

The battler's atb gain rate is batb_rate under Game_Battler:

#----------------------------------------------------------------------------| # New method: batb_rate | # - Returns the battler's atb rate | #----------------------------------------------------------------------------| def batb_rate # Uses the atb rate formula in the current atb rate formula code speed = agi speed += batb_speed if DoubleX_RMVXA::BATB.batb_fill_mode == 1 return 0 if speed < 0 rate = speed * 100.0 / DoubleX_RMVXA::BATB.base_batb_fill_t return rate / Graphics.frame_rate if DoubleX_RMVXA::BATB.batb_rate_code == 0 rate / Graphics.frame_rate / BattleManager.batb_avg_agi # end # batb_rate
The battler's atb gain rate's determined by that battler's agi(and the sum of all inputted skills/items' invocation speeds, batb_speed for the reverse atb mode) to conform to the default RMVXA battle features.

It's then multiplied by the base atb fill rate applied to all battlers. It'll also be divided by the average of all battlers' agi if users want to.

Actually, batb_speed should be cached as skills/items' invocation speeds are constants and those skills/items won't change as long as the battler's gaining atb, but as it'd only be expensive when the battler's ridiculously many action slots and caching might harm that script's readability for less able scripters, which is highly prioritized in that script, I decided not to cache it unless the average fps speaks otherwise.


The action execution

I rewrite process_action under Scene_Battle:

  #----------------------------------------------------------------------------|  #  Rewrite method: process_action                                            |  #----------------------------------------------------------------------------|  def process_action    return if scene_changing?    # Rewritten to execute all battler's actions unless there are forced ones    return process_batb_act(false) if @subject    esc = BattleManager.batb_esc    BattleManager.action_battlers.each { |battler|      next if esc && battler.actor?      next if battler.batb_val < 100.0 || battler.inputable?      @subject = battler      process_batb_act      @subject = nil    }    #  end # process_action
It checks if any battler can process actions while process_batb_act actually processes those actions. Only battlers that can act are checked.

If the battler is an actor trying to execute a party escape attempt, or if his/her/its atb isn't full or is inputable, that battler won't process actions, otherwise that battler will process each action sequentially until all actions are processed.

process_batb_act under Scene_Battle will also call process_action_end under Scene_Battle upon executing all the battler's actions:

#----------------------------------------------------------------------------| # New method: process_batb_act | # - Processes all actions of the action execution subject | #----------------------------------------------------------------------------| # all: Whether all actions will be processed def process_batb_act(all = false) # Executes each valid action and remove each action until there's none left while @subject.current_action @subject.current_action.prepare exec_batb_act if @subject.current_action.valid? @subject.remove_current_action break unless all end process_action_end unless @subject.current_action # end # process_batb_act
All valid actions will be executed in exec_batb_act under Scene_Battle:

#----------------------------------------------------------------------------| # New method: exec_batb_act | # - Executes all valid actions of the action execution subject | #----------------------------------------------------------------------------| def exec_batb_act # Disables party escape during executions and updates the turn atb clock @status_window.open BattleManager.batb_can_esc = false execute_action BattleManager.batb_can_esc = true batb_update_turn(1) # end # exec_batb_act
BattleManager.batb_can_esc is set as false and true right before and after executing an action respectively to prevent calling a party escape attempt when an action's executing, which can crash the game.

batb_update_turn updates the global battle turn clock with number of actions as the unit.

After executing all the battler's actions, that battler's states with auto removal timing action end needs to be updated and his/her/its atb clock needs to be reset to conform to the default RMVXA battle features. So I aliased on_action_end:

#----------------------------------------------------------------------------| # Alias method: on_action_end | #----------------------------------------------------------------------------| alias on_action_end_batb on_action_end def on_action_end on_action_end_batb # Added to update the state turns with timing action and reset the atb value update_state_turns(1) reset_batb_val # end # on_action_end
update_state_turns under Game_Battler is rewritten to this:

#----------------------------------------------------------------------------| # (v1.02a+)Rewrite method: update_state_turns | #----------------------------------------------------------------------------| def update_state_turns(timing = 2) # Rewritten to update state turns according to their auto removal timings states.each { |state| next if state.auto_removal_timing != timing @state_turns[state.id] -= 1 if @state_turns[state.id] > 0 } # end # update_state_turns
reset_batb_val under Game_Battler resets the battler's atb clock, removes that battler's action slots, and removes that battler from the actable battler list:

#----------------------------------------------------------------------------| # New method: reset_batb_val | # - Resets the battler's atb value | #----------------------------------------------------------------------------| # reset: The battler action reset flag def reset_batb_val(reset = true) # Sets the battler's atb value as its min and clear all battler's actions if reset @batb_val = 0.0 @batb_val_change = true end clear_actions BattleManager.action_battlers.delete(self) BattleManager.clear_actor if actor? && BattleManager.actor == self # end # reset_batb_val

Action Validity

I added @batb_confirm under Game_Action as the actor input confirmation flag:

#----------------------------------------------------------------------------| # New public instance variable | #----------------------------------------------------------------------------| attr_accessor :batb_confirm # The action's confirmation flag
It'll be raised upon selecting a skill/item not needing target selections by aliasing set_skill and set_item under Game_Action, and command_guard under Scene_Battle:

#----------------------------------------------------------------------------| # Alias method: set_skill | #----------------------------------------------------------------------------| alias set_skill_batb set_skill def set_skill(skill_id) set_skill_batb(skill_id) # Added to confirm inputs not needing targeting selections @batb_confirm ||= !@item.object.need_selection? self # end # set_skill #----------------------------------------------------------------------------| # Alias method: set_item | #----------------------------------------------------------------------------| alias set_item_batb set_item def set_item(item_id) set_item_batb(item_id) # Added to confirm inputs not needing targeting selections @batb_confirm ||= !@item.object.need_selection? self # end # set_item
Code:
  #----------------------------------------------------------------------------|  #  Alias method: command_guard                                               |  #----------------------------------------------------------------------------|  alias command_guard_batb command_guard  def command_guard    # Added to confirm the actor's input before finishing inputting it    actor = BattleManager.actor    return unless actor    actor.input.batb_confirm = true    #    command_guard_batb  end # command_guard
It'll also be raised upon finishing the target selections by aliasing on_actor_ok and on_enemy_ok under Scene_Battle:

#----------------------------------------------------------------------------| # Alias method: on_actor_ok | #----------------------------------------------------------------------------| alias on_actor_ok_batb on_actor_ok def on_actor_ok # Added to confirm the actor's input before finishing inputting it actor = BattleManager.actor return unless actor actor.input.batb_confirm = true # on_actor_ok_batb end # on_actor_ok #----------------------------------------------------------------------------| # Alias method: on_enemy_ok | #----------------------------------------------------------------------------| alias on_enemy_ok_batb on_enemy_ok def on_enemy_ok # Added to confirm the actor's input before finishing inputting it actor = BattleManager.actor return unless actor actor.input.batb_confirm = true # on_enemy_ok_batb end # on_enemy_ok
An actor is inputable only if that actor has action slots and at least 1 of them aren't confirmed(and that actor isn't trying to execute a party escape attempt). So I aliased inputable? under Game_Battler:

#----------------------------------------------------------------------------| # Alias method: inputable? | #----------------------------------------------------------------------------| alias inputable_batb? inputable? def inputable? # Rewritten to check if there are action slots and they're all unconfirmed return false unless inputable_batb? && @actions.size > 0 && actor? !BattleManager.batb_esc && !@actions.all? { |act| act.batb_confirm } # end # inputable?

Actor command window setup and deactivation

I write batb_update_windows under Scene_Battle to handle most window setups and deactivations:

#----------------------------------------------------------------------------| # New method: batb_update_windows | # - Keeps all info and states of all windows to be up to date | #----------------------------------------------------------------------------| # list: The currently inputable actor list def batb_update_windows(list) # Updates status, party, actor command, item and target selection windows if @status_window.index >= 0 @status_window.open.show if @skill_window.visible || @item_window.visible || @actor_window.visible || @enemy_window.visible [@actor_window, @enemy_window, @skill_window, @item_window, @actor_command_window].each { |w| w.hide.deactivate.close if w.active } @status_window.unselect elsif list.empty? @party_command_window.deactivate.close if @party_command_window.active return end return if @party_command_window.active || list.empty? BattleManager.actor_index = list[0] start_actor_command_selection # end # batb_update_windows
As the passed list doesn't include the current status window index, if it's greater than 0(selecting an actor), it'll be certainly wrong, as it should only select an actor with index included in the passed list. It means the skill/item and target selection windows are also wrong as they depend on the selected actor opening them, and that actor's going to be changed. So those windows should be deactivated and the status window should be unselected.

If the list's empty, it means no actor can input actions, so the party command window should be closed.

If the list isn't empty and the party command window isn't active, then the 1st actor in the list will be selected with his/her/its actor command window being setup.


Starting battler atb clock status

I aliased battle_start under BattleManager:

#----------------------------------------------------------------------------| # Alias method: battle_start | #----------------------------------------------------------------------------| alias battle_start_batb battle_start def battle_start battle_start_batb # Added to set all battlers' atb and escape status at the start of a battle batb_battle_start # end # battle_start
batb_battle_start under BattleManager sets all battlers' starting atb value:

#----------------------------------------------------------------------------| # New method: batb_battle_start | # - Sets all battlers' atb and escape status at the start of a battle | #----------------------------------------------------------------------------| def batb_battle_start # Sets the party escape flags and all battlers' starting atb values @batb_can_esc = true @batb_esc = false start = @preemptive ? :preempt : @surprise ? :surprise : :norm ($game_party.battle_members + $game_troop.members).each { |member| member.batb_start(start) } # end # batb_battle_start
Each battler's batb_start under Game_Battler actually sets his/her/its starting atb value:

#----------------------------------------------------------------------------| # New method: batb_start | # - Sets the battler's starting atb value | #----------------------------------------------------------------------------| # start: The battle start type def batb_start(start) # Sets the battler's starting atb value according to the battle start type @batb_val_change = true return @batb_val = 0.0 unless movable? if DoubleX_RMVXA::BATB.batb_fill_mode == 1 @batb_val = 0.0 return make_actions elsif start == :preempt && actor? || start == :surprise && enemy? @batb_val = 100.0 return make_actions end return @batb_val = 0.0 if DoubleX_RMVXA::BATB.batb_start_mode == 0 || start == :surprise && actor? || start == :preempt && enemy? @batb_val = 100.0 * agi / param_max(6) make_actions if @batb_val >= 100.0 # end # batb_start
If the battler's isn't movable, the starting atb value will be 0.

If the reverse atb mode's used, all battlers' starting atb value will be 0, and they'll all have their new action slots.

If the start is preemptive, actors' atb will be full and new action slots will be acquired, while enemies' atb will be empty, and vice versa.

Otherwise the starting atb value formula will be used(which will be used depends on the users settings).


Battler atb clock display

I rewrite draw_gauge_area_with_tp and draw_gauge_area_without_tp under Window_BattleStatus:

#----------------------------------------------------------------------------| # Rewrite method: draw_gauge_area_with_tp | #----------------------------------------------------------------------------| def draw_gauge_area_with_tp(rect, actor) # Rewritten to draw all actors' atb bars as well draw_actor_hp(actor, rect.x + 0, rect.y, 60) draw_actor_mp(actor, rect.x + 64, rect.y, 60) draw_actor_tp(actor, rect.x + 128, rect.y, 52) draw_batb_bar(rect, actor, true) if actor # end # draw_gauge_area_with_tp #----------------------------------------------------------------------------| # Rewrite method: draw_gauge_area_without_tp | #----------------------------------------------------------------------------| def draw_gauge_area_without_tp(rect, actor) # Rewritten to draw the actor's atb bar as well draw_actor_hp(actor, rect.x + 0, rect.y, 72) draw_actor_mp(actor, rect.x + 80, rect.y, 72) draw_batb_bar(rect, actor, false) if actor # end # draw_gauge_area_without_tp
I also write refresh_batb_bars under Window_BattleStatus to refresh all actors' atb bars:

#----------------------------------------------------------------------------| # New method: refresh_batb_bars | # - Draws all actors' atb bars | #----------------------------------------------------------------------------| def refresh_batb_bars # Draws each actor's atb bar using its rect if the actor's atb value changed display_tp = $data_system.opt_display_tp item_max.times { |index| actor = $game_party.battle_members[index] next unless actor && actor.batb_val_change draw_batb_bar(gauge_area_rect(index), actor, display_tp) } # end # refresh_batb_bars
The actor's atb bar will be refreshed only if that actor's atb value changed.

I write draw_batb_bar under Window_BattleStatus to actually draw the actor's atb bar:

#----------------------------------------------------------------------------| # New method: draw_batb_bar | # - Draws the actor's atb bar | #----------------------------------------------------------------------------| # rect: The atb bar's rect # actor: The atb bar's owner # display_tp: The tp bar display flag def draw_batb_bar(rect, actor, display_tp) # Sets atb bar x position and width according to the display tp flag if display_tp x = rect.x + 184 w = 36 else x = rect.x + 160 w = 60 end draw_gauge(x, rect.y, w, actor.batb_val / 100.0, text_color(DoubleX_RMVXA::BATB.batb_bar_color1), text_color(DoubleX_RMVXA::BATB.batb_bar_color2)) change_color(system_color) draw_text(x, rect.y, 30, line_height, DoubleX_RMVXA::BATB.batb_bar_text) actor.batb_val_change = false # end # draw_batb_bar
The actor's atb value change flag will be reset as false right after drawing the atb bar to ensure it'll be drawn only if the atb value's changed.

Again, never refreshes the entire status window per frame when only the atb bars need to be redrawn per frame, as doing so would be extremely expensive. An even better way is to redraw an atb bar only if its value changed, as redrawing even a single atb bar is still relatively expensive.


Battle Turn

I rewrite in_turn? under BattleManager as always returning true:

#----------------------------------------------------------------------------| # Rewrite method: in_turn? | #----------------------------------------------------------------------------| def in_turn? # Rewritten to always return true true # end # in_turn?
Now both process_event and process_action will always be called per frame update.

I alias input_start under BattleManager to stop recreating new action slots are battlers and opening the party command window:

#----------------------------------------------------------------------------| # Alias method: input_start | #----------------------------------------------------------------------------| alias input_start_batb input_start def input_start # Added to stop making battler actions when opening the party command window @phase = :input # input_start_batb end # input_start
I rewrite turn_start under Scene_Battle to stop shifting battle phase, clearing subject, or clearing the log window:

#----------------------------------------------------------------------------| # Rewrite method: turn_start | #----------------------------------------------------------------------------| def turn_start @party_command_window.close @actor_command_window.close @status_window.unselect # Removed to stop shifting battle phase nor clearing subject # @log_window.wait # (v1.01a+)Removed to stop clearing the log window # end # turn_start
I rewrite turn_end under Scene_Battle to increase the battle turn number by 1 and stop opening thew party command window:

#----------------------------------------------------------------------------| # Rewrite method: turn_end | #----------------------------------------------------------------------------| def turn_end ($game_party.battle_members + $game_troop.members).each { |battler| battler.on_turn_end refresh_status @log_window.display_auto_affected_status(battler) @log_window.wait_and_clear } # Added to increase the battle turn number by 1 $game_troop.increase_turn # BattleManager.turn_end process_event # Removed to stop opening the party command window on turn end # end # turn_end
turn_end is now called by batb_update_turn under Scene_Battle:

#----------------------------------------------------------------------------| # New method: batb_update_turn | # - Updates the turn atb clock | #----------------------------------------------------------------------------| # code: The current turn atb clock unit code def batb_update_turn(code) # Increases the turn number and resets its atb clock when its max's reached return unless code == DoubleX_RMVXA::BATB.batb_turn_unit_code max_clock = DoubleX_RMVXA::BATB.batb_max_turn_unit max_clock *= Graphics.frame_rate if code == 0 @batb_turn_clock
Code:
 += 1    return unless @batb_turn_clock[code] >= max_clock    @batb_turn_clock[code] = 0    turn_end    #  end # batb_update_turn
[/SPOILER]When the turn clock reaches its max, it'll be reset and the turn end will be triggered.


[B]Party Escape[/B]

[SPOILER]In both atb fill modes, a failed party escape attempt should empty all actors' atb, remove all their action slots, and remove them from the actable battler list. So I aliased clear_actions under Game_Party:

  #----------------------------------------------------------------------------|  #  Alias method: clear_actions                                               |  #----------------------------------------------------------------------------|  alias clear_actions_batb clear_actions  def clear_actions    clear_actions_batb    # Added to clear all party members' atb value and actions    members.each {|member| member.reset_batb_val }    #  end # clear_actions
[/SPOILER]
[B]In the reverse atb filling mode, party escape attempts can't be triggered immediately or the party would cost nothing for a failed party escape, as penalties should be all actors' actions. So party escape attempts need to be charged just like every other actions in the reverse atb filling mode.[/B] [B]Also, conforming to the default RMVXA battle features, the party escape attempt can only be executed when at least 1 actor can act, so when that's not the case during charging the party escape attempt, it should be interrupted and interpreted as failed, hence triggering the penalties.[/B]

I alias process_escape under BattleManager to initiate the charging of a party escape attempt:

[SPOILER]  #----------------------------------------------------------------------------|  #  Alias method: process_escape                                              |  #----------------------------------------------------------------------------|  alias process_escape_batb process_escape  def process_escape    # Rewritten to process party escape only if it's allowed and ready    return false unless @batb_can_esc    return process_escape_batb unless DoubleX_RMVXA::BATB.batb_fill_mode == 1    batb_esc_start    #  end # process_escape
[/SPOILER]If escaping isn't allowed at that moment, the whole party escape attempt won't be triggered at all.

If the default atb mode's used, the party escape attempt will be executed right away.

I write batb_esc_start under BattleManager to mark that a party escape attempt's initiated:

[SPOILER]  #----------------------------------------------------------------------------|  #  (v1.02a+)New method: batb_esc_start                                       |  #  - Begins the party escape attempt preparation for atb fill mode 1         |  #----------------------------------------------------------------------------|  def batb_esc_start    # Raises the party escape flag for all battlers and shows the escape message    $game_message.add(sprintf(Vocab::EscapeStart, $game_party.name))    Sound.play_escape    wait_for_message    @batb_esc = true    false    #  end # batb_esc_start
[/SPOILER]
If the party escape attempt can be executed, batb_esc_end under BattleManager will be called:

[SPOILER]  #----------------------------------------------------------------------------|  #  (v1.02a+)New method: batb_esc_end                                         |  #  - Executes the party escape attempt for atb fill mode 1                   |  #----------------------------------------------------------------------------|  def batb_esc_end    # Proceeds to successful and failed attempts according to the success chance    @preemptive || rand < @escape_ratio ? process_abort : batb_esc_fail    wait_for_message    #  end # batb_esc_end
[/SPOILER]
If the party escape attempt failed during charging or after execution, batb_esc_fail under BattleManager will be called to apply the penalties:

[SPOILER]  #----------------------------------------------------------------------------|  #  (v1.02a+)New method: batb_esc_fail                                        |  #  - Processes the failed party escape attempt for atb fill mode 1           |  #----------------------------------------------------------------------------|  def batb_esc_fail    # Adds the escape ratio, shows the failed message and resets the escape flag    @escape_ratio += 0.1    $game_message.add('\.' + Vocab::EscapeFailure)    $game_party.clear_actions    @batb_esc = false    #  end # batb_esc_fail
[/SPOILER]

The rest of the script is mostly fixing bugs, and you should be able to comprehend them yourselves  :)
 
Last edited by a moderator:

DoubleX

Just a nameless weakling
Veteran
Joined
Jan 2, 2014
Messages
1,787
Reaction score
939
First Language
Chinese
Primarily Uses
N/A
Now lets use an example, DoubleX RMVXA BATB Delay, to show how an atb system addon can be written.

The feature set

# Sets the scale applied to the skill/item's invocation speed # The sum of all inputted skills/items' invocation speeds, multiplied by # this scale, will be subtracted from the battler's atb value right after # that battler finished inputting all that battler's action slots # If INVOCATION_SPEED_SCALE_VAR_ID is a natural number, the value of # variable with id INVOCATION_SPEED_SCALE_VAR_ID will be used instead of # using INVOCATION_SPEED_SCALE # The value of variable with id INVOCATION_SPEED_SCALE_VAR_ID should remain # the same during the same battle to ensure proper invocation speed scaling INVOCATION_SPEED_SCALE = 10 INVOCATION_SPEED_SCALE_VAR_ID = 0 # Sets the maximum atb value displayed on the atb bars # If the actual atb value's greater than this, the atb bar's overlaid # If MAX_ATB_VAL_VAR_ID is a natural number, the value of variable with id # MAX_ATB_VAL_VAR_ID will be used instead of using MAX_ATB_VAL # The value of variable with id MAX_ATB_VAL_VAR_ID should remain the same # during the same battle to ensure proper atb bar display MAX_ATB_VAL = 60000 MAX_ATB_VAL_VAR_ID = 0 # Sets the atb value added when the battler's atb's reset # If RESET_ATB_VAL_VAR_ID is a natural number, the value of variable with id # RESET_ATB_VAL_VAR_ID will be used instead of using RESET_ATB_VAL RESET_ATB_VAL = 60000 RESET_ATB_VAL_VAR_ID = 0 # Sets the maximum atb value at the start of a battle # If START_ATB_VAL_VAR_ID is a natural number, the value of variable with id # START_ATB_VAL_VAR_ID will be used instead of using START_ATB_VAL START_ATB_VAL = 60000 START_ATB_VAL_VAR_ID = 0 # Sets the 1st atb bar overlay color as text color ATB_OVERLAY_COLOR1 # It'll be used when the atb bar's overlaid # If ATB_OVERLAY_COLOR1_VAR_ID is a natural number, the value of variable # with id ATB_OVERLAY_COLOR1_VAR_ID will be used instead of using # ATB_OVERLAY_COLOR1 # The value of variable with id ATB_OVERLAY_COLOR1_VAR_ID should remain the # same during the same battle to ensure proper atb bar color displays ATB_OVERLAY_COLOR1 = 19 ATB_OVERLAY_COLOR1_VAR_ID = 0 # Sets the 2nd atb bar overlay color as text color ATB_OVERLAY_COLOR2 # It'll be used when the atb bar's overlaid # If ATB_OVERLAY_COLOR2_VAR_ID is a natural number, the value of variable # with id ATB_OVERLAY_COLOR2_VAR_ID will be used instead of using # ATB_OVERLAY_COLOR2 # The value of variable with id ATB_OVERLAY_COLOR2_VAR_ID should remain the # same during the same battle to ensure proper atb bar color displays ATB_OVERLAY_COLOR2 = 26 ATB_OVERLAY_COLOR2_VAR_ID = 0
It basically changes the atb from using a closed percentage scale(0 - 100%) to an open integer scale(0 - maximum integer Ruby/the machines can handle), and from filling from empty to full to dropping from the current delay point to empty.


The interaction between the new features and the existing atb system

This feature obviously conflicts with the delay addon and has to be abandoned when that addon's used:

# Sets the atb fill mode as ATB_FILL_MODE # If ATB_FILL_MODE_VAR_ID is a natural number, the value of variable with id # ATB_FILL_MODE_VAR_ID will be used instead of using ATB_FILL_MODE # Available atb fill modes: # 0 - Lets battlers input and execute actions when that battler's atb's full # Empties and refills that battler's atb right after executing actions # 1 - Lets battlers input actions when that battler's atb's empty # Refills that battler's atb right after inputting actions # Let battlers execute actions when that battler's atb's full # Empties that battler's atb right after executing actions # The value of variable with id ATB_FILL_MODE_VAR_ID should remain the same # during battles to ensure proper atb fillings and action executions ATB_FILL_MODE = 0 ATB_FILL_MODE_VAR_ID = 0
These features clearly needs to be redefined to work with the delay addon:

# (v1.01a+)Sets the starting atb value mode as ATB_START_MODE # If ATB_START_MODE_VAR_ID is a natural number, the value of variable with # id ATB_START_MODE_VAR_ID will be used instead of using ATB_START_MODE # The starting atb value is always 0.0 for: # - unmovable battlers # - atb fill mode as 1 # - actors with start type surprise # - enemies with start type preemptive # The starting atb value is always 100.0 for: # - movable actors with atb fill mode as 0 and start type preemptive # - movable enemies with atb fill mode as 0 and start type surprise # Available starting atb value mode for normal start: # - agi is the battler's agi # - param_max(6) is the maximum value of any battler's agi # 0 - 0.0 # 1 - 100.0 * agi / param_max(6) ATB_START_MODE = 0 ATB_START_MODE_VAR_ID = 0
Code:
    # Sets the atb refill rate code as ATB_RATE_CODE    # If ATB_RATE_CODE_VAR_ID is a natural number, the value of variable with id    # ATB_RATE_CODE_VAR_ID will be used instead of using ATB_RATE_CODE    # Available atb refill rate code:    # - speed is    #   Battler's agi for atb fill mode 0    #   Battler's agi + batb_item_speed_sum for atb fill mode 1    # - batb_item_speed_sum is the sum of invocation speeds(+ attack speed for     #   the attack skill) of all inputted skills/items    # - DoubleX_RMVXA::BATB.base_batb_fill_t is the base atb fill time    # - Graphics.frame_rate is the number of frames per second    # - BattleManager.batb_avg_agi is the average of all battler's agi    # 0 - speed * 100.0 / DoubleX_RMVXA::BATB.base_batb_fill_t /     #     Graphics.frame_rate    # 1 - (formula in code 0) / BattleManager.batb_avg_agi    # 2 - Same as 1, except BattleManager.batb_avg_agi will always be    #     reevalauted per frame instead of just at the start of a battle    ATB_RATE_CODE = 1    ATB_RATE_CODE_VAR_ID = 0    # Sets the code of the reset atb mechanism as ATB_RESET_CODE    # If ATB_RESET_CODE_VAR_ID is a natural number, the value of variable with    # id ATB_RESET_CODE_VAR_ID will be used instead of using ATB_RESET_CODE    # Available codes of the atb reset mechanism:    # 0 - Freezes the atb refill while not movable    # 1 - Same as 0, except the atb will be emptied when becoming unmovable    ATB_RESET_CODE = 1    ATB_RESET_CODE_VAR_ID = 0
Redefining ATB_START_MODE is relatively straightforward. Just revert 0.0 with START_ATB_VAL, 100.0 as 0, and 100.0 * agi / param_max(6) as START_ATB_VAL * (param_max(6) - agi) / param_max(6).

The same applies to ATB_RESET_CODE, except that the atb reset value will be added by RESET_ATB_VAL instead of set to RESET_ATB_VAL.

Redefining ATB_RATE_CODE is just getting rid of its speed parts.


The implementation of the new features

batb_start under both Game_BattlerBase and Game_Battler are rewritten to redefine ATB_START_MODE and add START_ATB_VAL into the play:

#----------------------------------------------------------------------------| # Uses the atb decrement mechanics and open atb value scale instead | #----------------------------------------------------------------------------| def batb_start(start) # Rewrite # Rewritten @batb_val += DoubleX_RMVXA::BATB_Delay.start_atb_val return if DoubleX_RMVXA::BATB.atb_start_mode == 0 return if start == :surprise && actor? || start == :preempt && enemy? @batb_val = (@batb_val * ((param_max(6) - agi) * 1.0 / param_max(6))).to_i make_actions if @batb_val <= 0 # end # batb_start
Code:
  #----------------------------------------------------------------------------|  #  Gets rid of the atb fill mode and uses the atb decrement mechanics instead|  #----------------------------------------------------------------------------|  def batb_start(start) # Rewrite    @batb_val_change = true    # Rewritten    @last_batb_inputable = false    return @batb_val = DoubleX_RMVXA::BATB_Delay.start_atb_val unless movable?    if start == :preempt && actor? || start == :surprise && enemy?      @batb_val = 0      return make_actions    end    #    super(start)  end # batb_start
reset_batb_val under Game_Battler is rewritten to redefine ATB_RESET_CODE:

#----------------------------------------------------------------------------| # Uses the atb decrement mechanics and open atb value scale instead | #----------------------------------------------------------------------------| def reset_batb_val(reset = true) # Rewrite # Rewritten if reset @batb_val += DoubleX_RMVXA::BATB_Delay.reset_atb_val @batb_val_change = true end @last_batb_inputable = false # clear_actions BattleManager.action_battlers.delete(self) BattleManager.clear_actor if actor? && BattleManager.actor == self end # reset_batb_val
batb_rate under Game_Battler is rewritten to redefine ATB_RATE_CODE:

#----------------------------------------------------------------------------| # Gets rid of the atb fill mode and uses integer instead of floating points | #----------------------------------------------------------------------------| def batb_rate # Rewrite # Rewritten return agi if DoubleX_RMVXA::BATB.atb_rate_code == 0 (agi * (agi * 1.0 / BattleManager.batb_avg_agi)).to_i # end # batb_rate
batb_speed is aliased to add INVOCATION_SPEED_SCALE into the play:

#----------------------------------------------------------------------------| # Gets rid of the party escape speed and uses the invocation speed scale | #----------------------------------------------------------------------------| alias batb_speed_delay batb_speed def batb_speed # Rewritten batb_speed_delay * DoubleX_RMVXA::BATB_Delay.invocation_speed_scale # end # batb_speed
batb_update under Game_Battler is the main focus of that addon and this reply. It needs to be rewritten to deal with the following:

- Drops the atb delay points from the current point to 0, instead of filling the atb value from 0% to 100%

- Adds the sum of delays from all the confirmed skills/items right after all action slots are confirmed(Actually there are better ways to solve this without using @last_batb_inputable nor touching the battler atb clock update method, but this one's more straightforward)

- Makes new action slots when the atb delay becomes 0, instead of becoming full with ATB_FILL_MODE being 0

So it's rewritten into this:

#----------------------------------------------------------------------------| # Gets rid of the atb fill mode and uses the atb decrement mechanics instead| #----------------------------------------------------------------------------| def batb_update # Rewrite # Rewritten return if restriction > 3 if @batb_val > 0 @batb_val_change = @batb_val != @batb_val -= batb_rate elsif @last_batb_inputable && @actions.all? { |act| act.batb_confirm } @batb_val_change = @batb_val != @batb_val -= batb_speed @last_batb_inputable = false end return unless @batb_val <= 0 @batb_val = 0 make_actions if @actions.empty? # end # batb_update
@last_batb_inputable is a flag used to detect if the battler becomes to have all action slots confirmed. A better way will be adding the delay sum of all skills/items in a new method called by command_guard, on_actor_ok, on_enemy_ok, make_actions under Game_Enemy, make_autobattle_actions and make_confusion_actions instead, with whether all action slots are confirmed checked.

On a side note, @batb_val_change = @batb_val != @batb_val -= batb_rate stores the comparison result of whether the old @batb_val value is the same as the new one, while updating @batb_val to its new value at the same time. The same applies to @batb_val_change = @batb_val != @batb_val -= batb_speed. I hope they're still readable enough for you though, as readability is almost the highest priority in that addon.

draw_batb_bar under Window_BattleStatus is rewritten to display the atb drop instead of increment, and display the atb bar with the atb overlay colors if the atb bar's overlaid(the current atb value >= MAX_ATB_VAL):

# rect: The atb bar's rect # actor: The atb bar's owner # display_tp: The tp bar display flag def draw_batb_bar(rect, actor, display_tp) # Rewrite display_tp ? (x, w = rect.x + 184, 36) : (x, w = rect.x + 160, 60) # Rewritten colors = set_batb_bar_colors(actor.batb_val) max = DoubleX_RMVXA::BATB_Delay.max_atb_val batb_val = actor.batb_val > max ? max : actor.batb_val draw_gauge(x, rect.y, w, batb_val * 1.0 / max, colors[0], colors[1]) # change_color(system_color) draw_text(x, rect.y, 30, line_height, DoubleX_RMVXA::BATB.atb_bar_text) actor.batb_val_change = false end # draw_batb_bar
set_batb_bar_colors is a new method under Window_BattleStatus to use ATB_OVERLAY_COLOR1 and ATB_OVERLAY_COLOR2 when the atb bar's overlaid:

# batb_val: The atb value def set_batb_bar_colors(batb_val) # New if batb_val <= DoubleX_RMVXA::BATB_Delay.max_atb_val batb = DoubleX_RMVXA::BATB return [text_color(batb.atb_bar_color1), text_color(batb.atb_bar_color2)] end delay = DoubleX_RMVXA::BATB_Delay [text_color(delay.atb_overlay_color1), text_color(delay.atb_overlay_color2)] end # set_batb_bar_colors
The rest is just fixing bugs and edge cases, and you should be able to get them yourselves :)


If you have written at least 1 atb system addon and feel good about yourself, you may want to try to write multiple addons to be same atb system. You'll have to manage the compatibility and relative positioning rules between those addons as well, which can be quite challenging if some of them have conceptual conflicts(probably needing decent scripting proficiency to handle) :D
 
Last edited by a moderator:

DoubleX

Just a nameless weakling
Veteran
Joined
Jan 2, 2014
Messages
1,787
Reaction score
939
First Language
Chinese
Primarily Uses
N/A
Before moving up 1 level to decent scripting proficiency, I want to use this example, DoubleX RMVXA Basic ATB Bar, to show how to deal with dependencies among the atb system addons themselves.

Although it generally needs at least decent scripting proficiency to be dealt with, there are some really simple cases where just some scripting proficiency will suffice(although I've thought for entire week just to come up with this simple example).

The feature set

# Sets the actor atb bars to be shown at their sprites instead of the # status window if ATB_BAR_ACTOR returns true # It should only return true if the actor sprites are already created # If ATB_BAR_WIDTH_SWITCH_ID is a natural number, the state of switch with # id ATB_BAR_WIDTH_SWITCH_ID will be used instead of using ATB_BAR_ACTOR # The state of switch with id ATB_BAR_WIDTH_SWITCH_ID should remain the same # during the same battle to ensure proper atb bar displays ATB_BAR_ACTOR = false ATB_BAR_WIDTH_SWITCH_ID = 0 # Sets the maximum atb bar width as ATB_BAR_W # If ATB_BAR_W_VAR_ID is a natural number, the value of variable with id # ATB_BAR_W_VAR_ID will be used instead of using ATB_BAR_W # The value of variable with id ATB_BAR_W_VAR_ID should remain the same # during the same battle to ensure proper atb bar displays ATB_BAR_W = 48 ATB_BAR_W_VAR_ID = 0 # Sets the atb bar height as ATB_BAR_H # If ATB_BAR_H_VAR_ID is a natural number, the value of variable with id # ATB_BAR_H_VAR_ID will be used instead of using ATB_BAR_H # The value of variable with id ATB_BAR_H_VAR_ID should remain the same # during the same battle to ensure proper atb bar displays ATB_BAR_H = 12 ATB_BAR_H_VAR_ID = 0 # Sets the atb bar x offset from the battler sprite as ATB_BAR_X # If ATB_BAR_X_VAR_ID is a natural number, the value of variable with id # ATB_BAR_X_VAR_ID will be used instead of using ATB_BAR_X # The value of variable with id ATB_BAR_X_VAR_ID should remain the same # during the same battle to ensure proper atb bar displays ATB_BAR_X = -12 ATB_BAR_X_VAR_ID = 0 # Sets the atb bar y offset from the battler sprite as ATB_BAR_Y # If ATB_BAR_Y_VAR_ID is a natural number, the value of variable with id # ATB_BAR_Y_VAR_ID will be used instead of using ATB_BAR_Y # The value of variable with id ATB_BAR_Y_VAR_ID should remain the same # during the same battle to ensure proper atb bar displays ATB_BAR_Y = -8 ATB_BAR_Y_VAR_ID = 0 # Sets the atb bar z position as ATB_BAR_Z # If ATB_BAR_Z_VAR_ID is a natural number, the value of variable with id # ATB_BAR_Z_VAR_ID will be used instead of using ATB_BAR_Z # The value of variable with id ATB_BAR_Z_VAR_ID should remain the same # during the same battle to ensure proper atb bar displays ATB_BAR_Z = 125 ATB_BAR_Z_VAR_ID = 0 # Sets the atb back bar color 1 as ATB_BACK_COLOR1 # If ATB_BACK_COLOR1_VAR_ID is a natural number, the value of variable with # id ATB_BACK_COLOR1_VAR_ID will be used instead of using ATB_BACK_COLOR1 # The value of variable with id ATB_BACK_COLOR1_VAR_ID should remain the # same during the same battle to ensure proper atb back bar color displays ATB_BACK_COLOR1 = 15 ATB_BACK_COLOR1_VAR_ID = 0 # Sets the atb back bar color 2 as ATB_BACK_COLOR2 # If ATB_BACK_COLOR2_VAR_ID is a natural number, the value of variable with # id ATB_BACK_COLOR2_VAR_ID will be used instead of using ATB_BACK_COLOR2 # The value of variable with id ATB_BACK_COLOR2_VAR_ID should remain the # same during the same battle to ensure proper atb back bar color displays ATB_BACK_COLOR2 = 15 ATB_BACK_COLOR2_VAR_ID = 0
The interaction between the new features and the existing atb system and its addons

As the bar addon just adds something new - displaying the battlers' atb bars on their sprites if any, it doesn't change any part of the existing atb system nor any of its addons.

Nevertheless, if you still remember how the delay addon changed how the actor atb bar works, you'll probably realize the same changes should apply to the bar addon too.

This leads to atb system addon dependencies - The bar addon needs to check if the delay addon's used as well in order to decide whether the normal or delay version of the atb bars should be displayed.

Fortunately, that's just a 1 way dependency, as the delay addon doesn't have to change anything in order to work with the bar addon.

On a side note: In general, 2 way dependencies are probably significantly harder to deal with, and decent scripting proficiency are likely needed, especially when most of them come from conceptual conflicts.
The addon dependency solutions

Usually, there are 4 ways to solve addon dependencies:

1. Abandons the addon approach and integrates everything into the base script instead

- Decent scripting proficiency are mostly needed to be able to write such scripts having decent feature sets and code qualities(If the feature sets are exceptionally massive and the script qualities have to be excellent, advanced scripting proficiency might be needed instead as they can end up being advanced complex scripts).

2. Sets 1 addon to need another addon to be used as well

- This changes addon hierarchies(among the addons themselves) from purely parallel to be partially level-based.

- Decent scripting proficiency are mostly needed to have a solid understanding on addon hierarchies.

3. Check which involved addons are used at compilation time(when the ASTs are generated; You don't have to know what it's if you're just concerning how to write atb system scripts)

- This gives better time performance, especially when the involved codes are run many times per frame.

- This almost always restricts the orderings among the involved addons.

Example:

if $doublex_rmvxa[:BATB_Delay] def update super return unless self.visible = @battler.alive? if @last_x != @battler.screen_x || @last_y != @battler.screen_y @last_x = @battler.screen_x @last_y = @battler.screen_y rect.x = @last_x + DoubleX_RMVXA::BATB_Bar.atb_bar_x rect.y = @last_y + DoubleX_RMVXA::BATB_Bar.atb_bar_y end update_bar_w if @type == :atb && @battler.batb_val_change end # update def update_bar_w max = DoubleX_RMVXA::BATB_Delay.max_atb_val batb_val = @battler.batb_val > max ? max : @battler.batb_val rect.width = DoubleX_RMVXA::BATB_Bar.atb_bar_w * batb_val * 1.0 / max end # update_bar_w def draw_bars dw = rect.width dh = rect.height colors = set_bar_colors @plane.bitmap.gradient_fill_rect(0, 0, dw, dh, colors[0], colors[1]) end # draw_bars def set_bar_colors if @type == :back bar = DoubleX_RMVXA::BATB_Bar return [bar_color(bar.atb_back_color1), bar_color(bar.atb_back_color2)] end if @battler.batb_val <= DoubleX_RMVXA::BATB_Delay.max_atb_val batb = DoubleX_RMVXA::BATB return [bar_color(batb.atb_bar_color1), bar_color(batb.atb_bar_color2)] end delay = DoubleX_RMVXA::BATB_Delay [bar_color(delay.atb_overlay_color1), bar_color(delay.atb_overlay_color2)] end # set_bar_colors else def update super return unless self.visible = @battler.alive? if @last_x != @battler.screen_x || @last_y != @battler.screen_y @last_x = @battler.screen_x @last_y = @battler.screen_y rect.x = @last_x + DoubleX_RMVXA::BATB_Bar.atb_bar_x rect.y = @last_y + DoubleX_RMVXA::BATB_Bar.atb_bar_y end return unless @type == :atb && @battler.batb_val_change rect.width = DoubleX_RMVXA::BATB_Bar.atb_bar_w * @battler.batb_val / 100.0 end # update def draw_bars dw = rect.width dh = rect.height if @type == :atb color1 = bar_color(DoubleX_RMVXA::BATB.atb_bar_color1) color2 = bar_color(DoubleX_RMVXA::BATB.atb_bar_color2) else color1 = bar_color(DoubleX_RMVXA::BATB_Bar.atb_back_color1) color2 = bar_color(DoubleX_RMVXA::BATB_Bar.atb_back_color2) end @plane.bitmap.gradient_fill_rect(0, 0, dw, dh, color1, color2) end # draw_bars end # if $doublex_rmvxa[:BATB_Delay]
In this case, the delay addon must be placed above the bar addon in order for them to work well together.

4. Check which involved addons are used at runtime(when the involved codes are interpreted)

- This gives worse time performance, especially when the involved codes are run many times per frame.

- This has a reasonable chance to avoid restricting the orderings among the involved addons.

Would-be example:

  def update    super    return unless self.visible = @battler.alive?    if @last_x != @battler.screen_x || @last_y != @battler.screen_y      @last_x = @battler.screen_x      @last_y = @battler.screen_y      rect.x = @last_x + DoubleX_RMVXA::BATB_Bar.atb_bar_x      rect.y = @last_y + DoubleX_RMVXA::BATB_Bar.atb_bar_y    end    update_bar_w if @type == :atb && @battler.batb_val_change  end # update  def update_bar_w    if $doublex_rmvxa[:BATB_Delay]      max = DoubleX_RMVXA::BATB_Delay.max_atb_val      batb_val = @battler.batb_val > max ? max : @battler.batb_val      return rect.width = DoubleX_RMVXA::BATB_Bar.atb_bar_w * batb_val * 1.0 / max    end    rect.width = DoubleX_RMVXA::BATB_Bar.atb_bar_w * @battler.batb_val / 100.0  end # update_bar_w  def draw_bars    dw = rect.width    dh = rect.height    colors = set_bar_colors    @plane.bitmap.gradient_fill_rect(0, 0, dw, dh, colors[0], colors[1])  end # draw_bars  def set_bar_colors    if @type == :back      bar = DoubleX_RMVXA::BATB_Bar      return [bar_color(bar.atb_back_color1), bar_color(bar.atb_back_color2)]    end    if $doublex_rmvxa[:BATB_Delay]      if @battler.batb_val > DoubleX_RMVXA::BATB_Delay.max_atb_val        d = DoubleX_RMVXA::BATB_Delay        return [bar_color(d.atb_overlay_color1), bar_color(d.atb_overlay_color2)]      end    end    batb = DoubleX_RMVXA::BATB    [bar_color(batb.atb_bar_color1), bar_color(batb.atb_bar_color2)]  end # set_bar_colors
In this case, either placing the bar addon above or below the delay addon will work.


If you've a basic knowledge of Bitmap, Plane and Viewport, the rest of the addon should be so simple for you that you can figure them out yourselves :)

On a side note: The @last_x and @last_y are to increase time performance, by updating the atb bar positions only when their battler sprites change positions.

I think that's the final important piece that can be handled with just some scripting proficiency, so be prepared to face next-level stuffs where you'll be assumed to have at least decent scripting proficiency.

While writing a basic atb system script that works without nontrivial bugs just needs some scripting proficiency, writing a decent one(with decent code qualities) that works decently probably needs decent scripting proficiency.

Nevertheless, you might still stand a remote chance to have a solid understanding on the upcoming important pieces even if you just have some scripting proficiency :D

One upcoming major piece is notetag management(without decent scripting proficiency it'd be really tough to have a solid understanding on it), and unfortunately, an atb system is really hard to be even decent without notetags ;)
 
Last edited by a moderator:

DoubleX

Just a nameless weakling
Veteran
Joined
Jan 2, 2014
Messages
1,787
Reaction score
939
First Language
Chinese
Primarily Uses
N/A
From now on, I'll move up to 1 level higher. You'll be assumed to have at least decent scripting proficiency. Specifically, you need to at least have:

1. A solid understanding of how the atb system concepts work on the player level

2. A solid understanding of how the battle related parts of the default RMVXA script works

3. Written dozens of decent battle related scripts

If you just want to stay with writing basic atb system scripts that just work without nontrivial bugs, you don't have to continue to follow; If you want to write a decent one, my following replies will likely help :)

In my opinion, any decent atb system script needs decent time performance, as those consistently dropping the average fps are effectively broken, even if that only happens on the less powerful machines.

Also, some time performance optimizations can be applied to many other aspects I'm going to cover and I think it's better to build based on the basic ptototype, so I'll talk about time performance first.

We should first know where to optimize for time performance, and then think of how to optimize those spots.

ATB System Hot spots

Having a solid understanding on hot spots in atb systems are vital to make those scripts to have decent time performance by using such optimizations there. An easy and simple rule to judge whether a piece of code's a hot spot is to check how frequent it'll likely run and how expensive each run is.

For example, the below pieces are definitely hot spots:

- The atb wait condition methods(always run per frame if the atb frame update method could run without checking the atb wait conditions)

- The atb frame update methods(always run per frame if it's allowed to run at that frame)

- The global atb clock update methods running the time based clock(always linked to the atb frame update method)

- The battler atb clock update methods per battler(always linked to the atb frame update method)

- The battler atb bar update method per battler(always linked to the atb frame update method)

The below pieces can be hot spots too, even though they aren't supposed to be:

- The window update methods(party command window, status window, actor command window, skill window, item window, actor window, enemy window, etc)

The below pieces are definitely not hot spots:

- The battler turn end methods per battler(always run upon turn end)

- The battle turn end methods

- The battler battle start/end methods per battler(always run upon battle start/end)

- The battle start/end methods

- The action execution methods(always run when an existing valid action execution subject executes an valid action)

Of course these examples are by no means exhaustive :D
ATB System Invariants

Invariants can be used to boost the time performance, and some such optimization tricks need to use them. The Time performance optimizations section will illustrate how invariants can be used.

In general, the more specific the invariants are, the more helpful they'll likely be when it comes to boosting the time performance. So it's usually advantageous to assert the invariants as strict as you can while still ensuring that they'll always hold. For example, the below are some invariants that should hold for any atb system script:

1. The atb bars needs to be redrawn only if the atb frame update's run

2. The atb bars needs to be redrawn only if their associated battlers' atb clocks' change

3. The atb bars needs to be redrawn only if their lengths needs to be changed

It's crystal clear and obvious that the 1st one's loosest while the 3rd one's strictest. In fact, the 3rd one's also the most helpful one as it minimizes redundant atb bar redraws.

However, bear in mind that the stricter the invariants are, the more probable they'll break when compatibility issues come into play as well. Even though you can always write separate compatibility fixes to address them, you should still consider the potential compatibility trade offs before making any invariant stricter.
Time performance optimizations

Actually, I've already briefly covered some optimization tricks back when I was talking about how to write a basic atb system script. The principle behind is crystal clear and obvious: Runs expensive codes only when they're needed to be run.

For instance, I've stated before that:

- Never refresh the entire status window when only the battler atb clock display needs to be updated

- Updating an atb bar is relatively expensive so it should only be updated if it's to

- batb_speed should be cached as skills/items' invocation speeds are constants and those skills/items won't change as long as the battler's gaining atb

- @last_x and @last_y are to increase time performance, by updating the atb bar positions only when their battler sprites change positions

Although I think they're too advanced to be talked about in basic atb system script writing tutorials, their corresponding time performance pitfalls are so significant that the time performance will probably be unacceptable in the less powerful machines even when we're talking about basic atb system scripts that just work without nontrivial bugs.

Back in their essences, the question is how to determine whether a piece of code's expensive and when they're needed to be run.

For example, consider an atb bar that only its length can be changed:

Q1 - Are redrawing an atb bar expensive?

A1 - Yes, at least in the less powerful machines, as it alone can cause noticeable average fps drops if it's poorly optimized in terms of time performance

Q2 - When an atb bar needs to be redrawn?

A2 -

Right after the atb frame update's run or the corresponding battler gets hit by skills/items(please try harder :p )

Right after the corresponding battler's atb clock's changed(acceptable but there's an even better answer ;) )

When the atb bar's length needs to be changed(I'd glad that you come up with this great answer :guffaw: )

The answers for these questions means that the atb bar update methods needs to be optimized in terms of time performance by only redrawing those needing to have their lengths changed.

Note that "an atb bar needs to be redrawn only when its length needs to be changed" is an invariant that's used here to boost the time performance, so Q2 is basically asking for invariants while A2 should be as strict as you can.

Memoization is 1 of your best friends when optimizing for time performance. Caches the results of the expensive code executions and only reevaluates them when they can be changed.

Using atb_speed under Game_Battler as an example(DoubleX RMVXA Basic ATB):

#----------------------------------------------------------------------------| # Returns the invocation speed sum of all skills/items to be executed | #----------------------------------------------------------------------------| def batb_speed # v1.02a+; New return DoubleX_RMVXA::BATB.party_esc_speed if BattleManager.batb_esc @actions.inject(0) { |sum, act| act.item ? sum + act.item.speed + (act.attack? ? atk_speed : 0) : 0 } end # batb_speed
It can be an expensive method if the battler has dozens or even hundreds of action slots(albeit ridiculously unlikely). However, as none of those action slots can be changed before the battler's atb's reset in that script, its evaluated result, which is the skill/item invocation speed sum, must be constant during that time interval, meaning it can be cached and reused until the battler's atb's reset:

#----------------------------------------------------------------------------| # Returns the atb rate using the current rate formula and cached speed sum | #----------------------------------------------------------------------------| def batb_rate # New rate = agi @batb_speed ||= batb_speed # Caches the sum until the actions are cleared rate += @batb_speed if DoubleX_RMVXA::BATB.atb_fill_mode == 1 rate < 0 ? 0 : rate * 100.0 / BattleManager.batb_base end # batb_rate
Code:
  #----------------------------------------------------------------------------|  #  Resets the battler's atb value to its min and clears all battler's actions|  #----------------------------------------------------------------------------|  # reset: The battler action reset flag  def reset_batb_val(reset = true) # New    if reset      @batb_val = 0.0      @batb_val_change = true    end    clear_actions    @batb_speed = nil # Clears the cached sum to be ready to cache new ones    BattleManager.action_battlers.delete(self)    BattleManager.clear_actor if actor? && BattleManager.actor == self  end # reset_batb_val
Note that "the skill/item invocation speed sum must be constant during that time interval" is an invariant that's used here to boost the time performance.

Another 2 of your best friends are:

1. Add much cheaper pieces of code to check if the original and much more expensive ones need to be run. This approaches suffers from the rare worst cases but works well in all the other cases.

2. Use notification flags to explicitly indicate the needs for change(although I think the observer pattern will likely be overkill here). It's analogous to this forum's user notifications.

Using update_pos under BATB_Bar as an example(DoubleX RMVXA Basic ATB Bar):

  def update_pos # v1.00b+    pos_change = @last_x != @battler.screen_x || @last_y != @battler.screen_y    return unless BattleManager.batb_offset_change || pos_change    @last_x = @battler.screen_x    @last_y = @battler.screen_y    rect.x = @last_x + DoubleX_RMVXA::BATB_Bar.atb_bar_x    rect.y = @last_y + DoubleX_RMVXA::BATB_Bar.atb_bar_y    BattleManager.batb_offset_change = false  end # update_pos
The atb bar positions are determined by their battler sprites' positions and the x and y offsets from their battlers. As those offsets can only be changed by changing the values of their associated variables, which must be done by users explicitly, there are only 2 possible cases for the atb bar positions to be changed:

1. The battler sprites' positions change

- In this case pos_change will ensure that the atb bar positions will be changed.

2. The users changed the values of those associated variables

- In this case BattleManager.batb_offset_change, which can be set as true by users(they should do so right after changing the values of those associated variables), will ensure that the atb bar positions will be changed.

Bear in mind that you'll have to reset the notification flags right after they're used(just like how the user notification in this forum works), otherwise they'll remain falsely raised and thus nullifying the benefits of those time performance optimizations.

Note that "The atb bar positions can only be changed by changing the battler sprites' positions or changing the values of variables associated to the x and y offsets" is an invariant that's used here to boost the time performance.

There are some more time performance optimization tricks, but I'll talk about them later when notetag management are covered.


To play safe, we'll also want to really benchmark the time performance to ensure we're indeed optimizing it.

Time performance benchmarks

Time performance benchmarks are basically about the average fps. It seems to me that there are at least 2 such benchmarks:

1. The number of graphics redraws per frame

- It can be shown by pressing F2

2. The number of graphics updates per frame

- It can be shown by some custom scripts like this

As the default fps is 60, you'll want the average fps of your atb system scripts to seldom drop below it even in the less powerful machines. At the very least, if the average fps of yours can ever drop below 20, which is the default minimum, yours will likely be considered to be broken even if such drops only happens in the less powerful machines.

If you really want to aim high, you might want to aim for staying at 120 on machines that can run 120 fps when idle, as 120 is the default maximum.

Recall that any such benchmark depends on the machines, so you should know how relatively powerful your machines are as well in order to have a reasonable estimate on what those benchmarks would be on the less powerful ones.

For example, the average benchmark of these cases in my machine(WinXP 32 bit + i7-3820 without overclock + ASUS GTX550 Ti without overclock + 2 * 2GB DDR3-1333 without overclock) are as follows(Both the Graphics.frame_rate and the monitor refresh rate are set as 120, 4 actors and 8 enemies are included and only the time interval where no battler can act are tested):

1. Customisable ATB/Stamina Based Battle System(without implementation edits)

- About 20 graphics redraws per frame(average)

- About 100 graphics updates per frame(average)

2. Customisable ATB/Stamina Based Battle System(changed from refreshing the whole status window per frame to refreshing only the atb bars per frame)

- About 108 graphics redraws per frame(average)

- About 120 graphics updates per frame(average)

3. dbchest's ATB(without implementation edits)

- About 24 graphics redraws per frame(average)

- About 120 graphics updates per frame(average)

4. dbchest's ATB(changed from refreshing the whole status window per frame to refreshing only the atb bars per frame)

- About 114 graphics redraws per frame(average)

- About 120 graphics updates per frame(average)

5. DoubleX RMVXA Basic ATB(without implementation edits)

- About 114 graphics redraws per frame(average)

- About 120 graphics updates per frame(average)

6. Akea Active Time Battle(setting Atb_Bar as a nonempty string)

- About 120 graphics redraws per frame(average)

- About 120 graphics updates per frame(average)

To make the comparisons as fair as I can, I decided not to include ATB system scripts that need some other scripts to be used nor have significantly larger feature sets.

These data suggest that:

1. Refreshing the whole status window per frame generally drops the average fps drastically

- There's at least 1 understandable reason for doing so: Those atb bars are displayed along with their corresponding texts. Without refreshing the whole status window, those texts can look rather ugly to some users.

2. Redrawing the atb bars only when they're needed to can at least slightly boost the average fps

- Bear in mind that the overall average fps boost can become significant if there are many such slight boosts.

3. Using separate sprites to draw the atb bars instead of drawing them on the status window directly can boost the average fps even more

- That's because the sprites are never redrawn. They just use zoom_x to do the trick.
You should be able to reason about average fps performance for any atb system script decently if you've a solid understanding on the aforementioned contents.

Finally, remember that time performance isn't everything, as you'll have to take all the other important programming aspects into account too, so you shouldn't sacrifice any of them too much unless you can justify that with very, very solid reasons.
 
Last edited by a moderator:

Users Who Are Viewing This Thread (Users: 0, Guests: 1)

Latest Threads

Latest Profile Posts

so hopefully tomorrow i get to go home from the hospital i've been here for 5 days already and it's driving me mad. I miss my family like crazy but at least I get to use my own toiletries and my own clothes. My mom is coming to visit soon i can't wait to see her cause i miss her the most. :kaojoy:
Couple hours of work. Might use in my game as a secret find or something. Not sure. Fancy though no? :D
Holy stink, where have I been? Well, I started my temporary job this week. So less time to spend on game design... :(
Cartoonier cloud cover that better fits the art style, as well as (slightly) improved blending/fading... fading clouds when there are larger patterns is still somewhat abrupt for some reason.
Do you Find Tilesetting or Looking for Tilesets/Plugins more fun? Personally I like making my tileset for my Game (Cretaceous Park TM) xD

Forum statistics

Threads
105,868
Messages
1,017,074
Members
137,578
Latest member
JamesLightning
Top