HOW TO WRITE AN ATB SYSTEM SCRIPT

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

  • DoubleX
  • 04/25/2015 04:37 AM
  • 3523 views
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 execute 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

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?
  end

end

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
  end

end


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 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 }
    @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) }
  end

end

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)
  end

end


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

def atb_bar_refresh
    item_max.times { |index| draw_atb_bar(index) }
  end

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

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.

Posts

Pages: 1
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).
Pages: 1