SLIP INTO RUBY: UNDER THE HOOD PART 6: BATTLERS!

Battlers battlers battlers

  • Trihan
  • 04/16/2015 03:20 PM
  • 6098 views
Hello Sports Fans! I was feeling generous and I've just started sharing these with RMRK and rpgmakerweb, so you get a bonus early edition of



It's finally the time all (most (some (a few (none (because nobody reads these))))) of you have been waiting for: battlers! We're going to delve in and get all nitty-gritty with some of the most complicated code you're going to see in the default scripts, so buckle your seatbelts, put on some fresh pants and pack a lunch!

Game_BattlerBase
This is the base class for battlers. It mainly handles stuff like parameter calculations, and is the superclass of Game_Battler.

First, we have a bunch of constants. Constants are, as the name suggests, variable values which will never change. We set them here so we can refer to them later by name, and if we ever DO decide to change a constant's value we only have to change it in the constant declaration rather than hunting down every instance of a number in the code. By convention, constants are always named in block capitals.

FEATURE_ELEMENT_RATE  = 11              # Element Rate
  FEATURE_DEBUFF_RATE   = 12              # Debuff Rate
  FEATURE_STATE_RATE    = 13              # State Rate
  FEATURE_STATE_RESIST  = 14              # State Resist
  FEATURE_PARAM         = 21              # Parameter
  FEATURE_XPARAM        = 22              # Ex-Parameter
  FEATURE_SPARAM        = 23              # Sp-Parameter
  FEATURE_ATK_ELEMENT   = 31              # Atk Element
  FEATURE_ATK_STATE     = 32              # Atk State
  FEATURE_ATK_SPEED     = 33              # Atk Speed
  FEATURE_ATK_TIMES     = 34              # Atk Times+
  FEATURE_STYPE_ADD     = 41              # Add Skill Type
  FEATURE_STYPE_SEAL    = 42              # Disable Skill Type
  FEATURE_SKILL_ADD     = 43              # Add Skill
  FEATURE_SKILL_SEAL    = 44              # Disable Skill
  FEATURE_EQUIP_WTYPE   = 51              # Equip Weapon
  FEATURE_EQUIP_ATYPE   = 52              # Equip Armor
  FEATURE_EQUIP_FIX     = 53              # Lock Equip
  FEATURE_EQUIP_SEAL    = 54              # Seal Equip
  FEATURE_SLOT_TYPE     = 55              # Slot Type
  FEATURE_ACTION_PLUS   = 61              # Action Times+
  FEATURE_SPECIAL_FLAG  = 62              # Special Flag
  FEATURE_COLLAPSE_TYPE = 63              # Collapse Effect
  FEATURE_PARTY_ABILITY = 64              # Party Ability


These constants determine the code numbers of the various features you can set for things in the database. We'll look at this in more detail shortly.

FLAG_ID_AUTO_BATTLE   = 0               # auto battle
  FLAG_ID_GUARD         = 1               # guard
  FLAG_ID_SUBSTITUTE    = 2               # substitute
  FLAG_ID_PRESERVE_TP   = 3               # preserve TP


These constants determine flag values for the Special Flag settings in features.

ICON_BUFF_START       = 64              # buff (16 icons)
  ICON_DEBUFF_START     = 80              # debuff (16 icons)


These constants determine the starting icon indexes for in-battle stat buffs/debuffs.

And now, public instance variables:

attr_reader   :hp                       # HP
  attr_reader   :mp                       # MP
  attr_reader   :tp                       # TP


We've got three, all attr_readers (so they can be read but not written to directly): one each for HP, MP and TP.

def mhp;  param(0);   end               # MHP  Maximum Hit Points
  def mmp;  param(1);   end               # MMP  Maximum Magic Points
  def atk;  param(2);   end               # ATK  ATtacK power
  def def;  param(3);   end               # DEF  DEFense power
  def mat;  param(4);   end               # MAT  Magic ATtack power
  def mdf;  param(5);   end               # MDF  Magic DeFense power
  def agi;  param(6);   end               # AGI  AGIlity
  def luk;  param(7);   end               # LUK  LUcK
  def hit;  xparam(0);  end               # HIT  HIT rate
  def eva;  xparam(1);  end               # EVA  EVAsion rate
  def cri;  xparam(2);  end               # CRI  CRItical rate
  def cev;  xparam(3);  end               # CEV  Critical EVasion rate
  def mev;  xparam(4);  end               # MEV  Magic EVasion rate
  def mrf;  xparam(5);  end               # MRF  Magic ReFlection rate
  def cnt;  xparam(6);  end               # CNT  CouNTer attack rate
  def hrg;  xparam(7);  end               # HRG  Hp ReGeneration rate
  def mrg;  xparam(8);  end               # MRG  Mp ReGeneration rate
  def trg;  xparam(9);  end               # TRG  Tp ReGeneration rate
  def tgr;  sparam(0);  end               # TGR  TarGet Rate
  def grd;  sparam(1);  end               # GRD  GuaRD effect rate
  def rec;  sparam(2);  end               # REC  RECovery effect rate
  def pha;  sparam(3);  end               # PHA  PHArmacology
  def mcr;  sparam(4);  end               # MCR  Mp Cost Rate
  def tcr;  sparam(5);  end               # TCR  Tp Charge Rate
  def pdr;  sparam(6);  end               # PDR  Physical Damage Rate
  def mdr;  sparam(7);  end               # MDR  Magical Damage Rate
  def fdr;  sparam(8);  end               # FDR  Floor Damage Rate
  def exr;  sparam(9);  end               # EXR  EXperience Rate


Abbreviated methods for accessing the various parameters, as denoted by their comments. Every parameter you could ever need condensed to a handy three-letter acronym. As you'll see soon, param, xparam and sparam are all methods themselves.

def initialize
    @hp = @mp = @tp = 0
    @hidden = false
    clear_param_plus
    clear_states
    clear_buffs
  end


Okay, so the constructor sets HP, MP and TP to 0, the @hidden flag to false (we don't want invisible battlers by default, do we?) and calls a bunch of flag/value-clearing methods to give us a fresh, blank battler ready for action!

def clear_param_plus
    @param_plus = [0] * 8
  end


@param_plus is an instance variable which tracks bonuses for the 8 main stats; clearing it is a simple matter of setting the variable to an array of eight zeroes.

def clear_states
    @states = []
    @state_turns = {}
    @state_steps = {}
  end


Clearing states involves setting @states to an empty array, and @state_turns/@state_steps to empty hashes.

A hash is pretty similar to an array, but refers to its members with a unique key-value pair instead of an indexed value. The reason it's done like this is that although @states is only ever going to contain a finite array of each state from the database in sequence, @state_turns and @state_steps are constantly going to be filled with a mishmash of references to the different states the battler currently has, which can't be indexed with a standard array.

def erase_state(state_id)
    @states.delete(state_id)
    @state_turns.delete(state_id)
    @state_steps.delete(state_id)
  end


This method is for erasing one particular state, and involves deleting the state_idth element of @states, and the key state_id from both @state_turns and @state_steps.

def clear_buffs
    @buffs = Array.new(8) { 0 }
    @buff_turns = {}
  end


Clearing buffs involves declaring a new array with 8 elements, each of which is 0, and setting @buff_turns to a blank hash. Note that as far as I'm aware "Array.new(8) { 0 }" is completely functionally identical to "= [0] * 8", so I'm not sure why they used two different ways of doing the same thing.

def state?(state_id)
    @states.include?(state_id)
  end


This method checks whether a battler has the given state; it returns true if the @states array includes the parameter state_id in its values.

def death_state?
    state?(death_state_id)
  end


This method checks whether a battler is dead by calling state? and supplying death_state_id as the parameter. This is itself a method which returns...

def death_state_id
    return 1
  end


...1! This is of course changeable and you could even if you wanted have multiple death states with different effects.

def states
    @states.collect {|id| $data_states[id] }
  end


This method gets the states as an object array by returning each ID's associated data. You've seen .collect before, nothing new to see here.

def state_icons
    icons = states.collect {|state| state.icon_index }
    icons.delete(0)
    icons
  end


This method gets the list of current states as an array of icon indexes and deletes any zeroes (since a 0 would mean there's no icon there).

def buff_icons
    icons = []
    @buffs.each_with_index {|lv, i| icons.push(buff_icon_index(lv, i)) }
    icons.delete(0)
    icons
  end


This method gets the current battle buffs/debuffs as an array of their icon numbers. It starts with a blank array. each_with_index is similar to each but also provides an index as a block variable: the block variables in this case are lv, which is the value of the current element (which in this case is the level of buff/debuff the battler has) and i, the index. The block pushes to the icons array the result of calling buff_icon_index with lv and i as parameters:

def buff_icon_index(buff_level, param_id)
    if buff_level > 0
      return ICON_BUFF_START + (buff_level - 1) * 8 + param_id
    elsif buff_level < 0
      return ICON_DEBUFF_START + (-buff_level - 1) * 8 + param_id 
    else
      return 0
    end
  end


Okay, so this might need an example. Let's say the battler has a level 2 defence buff. The buff_level is greater than 0, so we run the first return statement. It returns ICON_BUFF_START (which from the constants above is 64) + (2 - 1) * 8 + 3 (the ID for def), which simplifies to 64 + 1 * 8 + 3, which results in 75. If you look up 75 in the icon sheet (bearing in mind it's zero-indexed) you'll see the def buff icon with two arrows.

If we have, say, a level 2 defence debuff on the other hand, the buff_level is less than 0 so we go with the second return statement:

ICON_DEBUFF_START + (-buff_level - 1) * 8 + param_id
80 + (-2 - 1) * 8 + 3
80 + -3 * 8 + 3
80 - 24 + 3
59

And again, looking up icon 59 shows the defence debuff icon with two arrows.

def feature_objects
    states
  end


This method simply returns states, which itself returns the data for each state the battler has.

def all_features
    feature_objects.inject([]) {|r, obj| r + obj.features }
  end


This method uses the inject method, which we've seen before. In this case, the initial value of the returned array is a blank array, the memo variable is r, and the object in the current iteration is obj. The features property of obj is added to r for each element in the array.

features is a built-in property of RPG::BaseItem, which is the superclass of RPG::State, which is the class of the states in the database. Features is itself an array of RPG::BaseItem::Feature, instances of which consist of a feature code, a data_id, and a value.

That seems like a lot to take in, so let's break it down. Take a state like Ironbody. Its only feature is Sp-Parameter: PDR * 10%, which means the battler with this state takes 10% of normal physical damage. Looking again at the constants, the feature code for Sp-Parameters is 23. PDR is the 7th parameter in the list, so the data_id is 6 (remember arrays are 0-indexed) and the value is 0.1, as that's the decimal representation of 10%. So the feature list for $data_states[17] (Ironbody) will be a 1-element array with an instance of RPG::BaseItem::Feature which has @code 23, @data_id 6, and @value 0.1.

def features(code)
    all_features.select {|ft| ft.code == code }
  end


This is a slightly more discerning method for getting a list of state features; select is a method which iterates through the calling array and returns a new array of all elements for which the supplied block returned true. In this case, the block is that ft (block variable containing the currently-iterating element) has the same code as the supplied parameter. For instance, calling features(FEATURE_ATK_TIMES) would return all features which affect number of additional attacks.

def features_with_id(code, id)
    all_features.select {|ft| ft.code == code && ft.data_id == id }
  end


This is an even stricter getter for features, and requires additionally that the element's ID is also equal to the supplied ID. For instance, calling features_with_id(FEATURE_PARAM, 3) would return any features which affect defence.

def features_pi(code, id)
    features_with_id(code, id).inject(1.0) {|r, ft| r *= ft.value }
  end


This method calculates the complement of feature values, and takes two parameters, a code and an ID. Starting with an initial r value of 1.0, it loops through each feature matching that code/ID and multiplies r by its value. For example, features_pi(FEATURE_PARAM, 0) when the battler has two states reducing max HP to 25%:

In the first iteration of .inject, r is 1.0 and ft is the first state. r is multiplied by ft's value, 0.25, and becomes 0.25. In the second iteration, r is 0.25 and ft is the second state. r is again multiplied by ft's value, 0.25, and becomes 0.0625, which is the complement of the two states. As we'll see soon, this creates a curve for the bonus/penalty on stacked features.

def features_sum(code, id)
    features_with_id(code, id).inject(0.0) {|r, ft| r += ft.value }
  end


Similar to the above, this method instead calculates the sum of the feature values. In our example, this will result in r being 0.5.

def features_sum_all(code)
    features(code).inject(0.0) {|r, ft| r += ft.value }
  end


Similar to the above again, but this method calculates the sum of all features for a particular code, without worrying about the ID. So it would return the value of all features affecting x-parameters, for instance.

def features_set(code)
    features(code).inject([]) {|r, ft| r |= [ft.data_id] }
  end


This method takes code as a parameter and then calls inject, with r initially being an empty array, on features(code), which we know returns a list of all features matching that code. It adds the array element ft.data_id but only if it isn't already part of r. |= is a useful operator for uniquely adding elements to arrays; it's essentially a shorthand way of writing "r = r | " (look up bitwise OR assignment for more details)

def param_base(param_id)
    return 0
  end


Method for getting the base value of parameters; it always returns 0. This method is overridden by param_base of the Game_Actor and Game_Enemy classes to get the actual values.

def param_plus(param_id)
    @param_plus[param_id]
  end


This method returns the param_idth element of the @param_plus array, which will correspond to the param_id supplied as a parameter to the method. For example param_plus(3) will return @param_plus which is the bonus value to defence.

def param_min(param_id)
    return 0 if param_id == 1  # MMP
    return 1
  end


This method returns the minimum possible value for a parameter. It returns 0 if the ID is 1 (max MP) but 1 otherwise. You can change this if you want minimum max MP to be 1, or have stats that can go down to 0.

def param_max(param_id)
    return 999999 if param_id == 0  # MHP
    return 9999   if param_id == 1  # MMP
    return 999
  end


This method returns the maximum possible value for a parameter. Returns 999,999 if the ID is 0 (max HP), 9,999 if the ID is 1 (max MP) or 999 otherwise.

def param_rate(param_id)
    features_pi(FEATURE_PARAM, param_id)
  end


This is a shortcut method that calls features_pi on the given param_id. In the case of my example from that method, param_rate(0) would return 0.0625 for the battler with two +25% max HP states.

def param_buff_rate(param_id)
    @buffs[param_id] * 0.25 + 1.0
  end


This method returns the multiplier from buffs and debuffs for the given parameter. For example, a level 3 atk buff would result in param_buff_rate(2) returning 3 * 0.25 + 1.0, or 1.75. Note that it adds 1.0 and not 1 because that automatically makes the return value a float instead of an integer; if it had just been 1, the return value would have been 1 or 2 depending on which way it was rounded, which would be useless for a multiplier.

def param(param_id)
    value = param_base(param_id) + param_plus(param_id)
    value *= param_rate(param_id) * param_buff_rate(param_id)
    [[value, param_max(param_id)].min, param_min(param_id)].max.to_i
  end


Okay, so here is where we return the actual values for parameters.

value is set to the param_base of the given param_id plus param_plus for that ID.
It's then multiplied by param_rate * param_buff_rate
Finally, the return value is the maximum of an array consisting of the minimum between value and param_max, and param_min.

Examples ahoy!

Okay, so let's say we've called param(2) to get attack power on a level 1 Eric. Level 1 Soldier has 20 ATK. He currently has no stat bonuses but a level 2 atk buff.

value = 20 + 0
value *= 1.0 * 1.5 (it's now 30 at this point)
[.min, 1].max.to_i

The minimum value between 30 and 999 is obviously 30, and the maximum between 30 and 1 is 30, so the return value is 30.

def xparam(xparam_id)
    features_sum(FEATURE_XPARAM, xparam_id)
  end


Getting the value of an x-parameter involves calling features_sum with FEATURE_XPARAM and the given ID. For example xparam(2) would return the sum of all modifiers to critical hit rate.

def sparam(sparam_id)
    features_pi(FEATURE_SPARAM, sparam_id)
  end


Special parameters use the complement of the values rather than the sum. So for example sparam(0) would return the complement of all modifiers to target rate.

Let's say we have two effects: one that decreases physical damage to 10% and one that reduces it to 20%.

r will initially be 1.0. In the first iteration, it's multiplied by 0.1 and becomes 0.1. On the second iteration, 0.1 is multiplied by 0.2 to become 0.02, so damage is reduced to 2% instead of 10%.

def element_rate(element_id)
    features_pi(FEATURE_ELEMENT_RATE, element_id)
  end


This method follows a similar principle for elemental damage multipliers. Let's say Eric currently has two states which reduce fire damage to 10%. First iteration becomes 0.1, second becomes 0.01, so it's reduced to 1%.

def debuff_rate(param_id)
    features_pi(FEATURE_DEBUFF_RATE, param_id)
  end


Exactly the same method, but for debuff rates instead of elements.

def state_rate(state_id)
    features_pi(FEATURE_STATE_RATE, state_id)
  end


The same, but for state resistances.

def state_resist_set
    features_set(FEATURE_STATE_RESIST)
  end


Returns an array of all states the battler resists (those which they can't have added to them)

def state_resist?(state_id)
    state_resist_set.include?(state_id)
  end


This method checks whether a particular state was resisting by seeing if it's included in the array returned by state_resist_set.

def atk_elements
    features_set(FEATURE_ATK_ELEMENT)
  end


Gets the elements of an attack by returning all of the attack element features. As we'll see later, if the user has multiple attack elements, the engine will use whichever one the enemy is weakest to (for example, if an enemy takes 0% damage from fire and 200% from ice, and the user has both fire and ice attack elements, it will be treated as if the user's element were ice).

def atk_states
    features_set(FEATURE_ATK_STATE)
  end


Same as elements, but for states which attacks inflict.

def atk_states_rate(state_id)
    features_sum(FEATURE_ATK_STATE, state_id)
  end


Calculates attack state infliction rate as the sum of all features which inflict it. For example, if you have two states that give a 50% chance to poison on attack, you'll poison on attack 100% of the time.

def atk_speed
    features_sum_all(FEATURE_ATK_SPEED)
  end


Determines attack speed modifier as the sum of all features which affect attack speed.

def atk_times_add
    [features_sum_all(FEATURE_ATK_TIMES), 0].max
  end


Determines the number of additional attacks as the sum of all features which add or remove attacks (picking the maximum value between that one and 0, so you can't have negative attacks in a turn)

def added_skill_types
    features_set(FEATURE_STYPE_ADD)
  end


Determines the skill types which are added as an array of the skill type IDs added by features.

def skill_type_sealed?(stype_id)
    features_set(FEATURE_STYPE_SEAL).include?(stype_id)
  end


Determines whether a skill type is sealed by checking whether the supplied skill type ID is included in features which seal skills.

def added_skills
    features_set(FEATURE_SKILL_ADD)
  end


Determines which individual skills are added by features.

def skill_sealed?(skill_id)
    features_set(FEATURE_SKILL_SEAL).include?(skill_id)
  end


Determines whether a supplied skill ID is sealed by checking whether its ID is included in features which seal skills.

def equip_wtype_ok?(wtype_id)
    features_set(FEATURE_EQUIP_WTYPE).include?(wtype_id)
  end


Determines whether a particular weapon type can be equipped by checking whether its type ID is included in features which enable equipping weapon types.

def equip_atype_ok?(atype_id)
    features_set(FEATURE_EQUIP_ATYPE).include?(atype_id)
  end


Same thing, but for armour.

def equip_type_fixed?(etype_id)
    features_set(FEATURE_EQUIP_FIX).include?(etype_id)
  end


Determines whether the supplied equipment type is fixed (can't be unequipped) by checking whether its type ID is included in features which fix equipment.

def equip_type_sealed?(etype_id)
    features_set(FEATURE_EQUIP_SEAL).include?(etype_id)
  end


Same thing, but checks for the type being sealed rather than fixed.

def slot_type
    features_set(FEATURE_SLOT_TYPE).max || 0
  end


This method determines slot type (for which there is only one option: Dual Wield). Returns the maximum value from features which affect slot type, or 0 if there are none.

def dual_wield?
    slot_type == 1
  end


Returns true if the slot type is 1 (there is a feature somewhere for which "Dual Wield" has been chosen)

def action_plus_set
    features(FEATURE_ACTION_PLUS).collect {|ft| ft.value }
  end


This one gets an array of the probabilities for which an additional action will be granted to the battler. For example, having two effects with the Action Times+ 25% feature will result in an array consisting of .

def special_flag(flag_id)
    features(FEATURE_SPECIAL_FLAG).any? {|ft| ft.data_id == flag_id }
  end


any? returns true if the supplied block ever returns true on any element passed to it. In this case, each feature which affects special flags (Auto Battle, Guard, Substitute and Preserve TP) is compared to the supplied flag_id, and returns true if they match.

In short, this method returns true if any special flag is set for the battler.

def collapse_type
    features_set(FEATURE_COLLAPSE_TYPE).max || 0
  end


Returns the maximum value of the features which affect collapse type (Boss, Instant, and Not Disappear) or 0 if none are set.

def party_ability(ability_id)
    features(FEATURE_PARTY_ABILITY).any? {|ft| ft.data_id == ability_id }
  end


Checks whether any party ability is set (Encounter Half, Encounter None, Cancel Surprise, Raise Preemptive, Gold Double or Drop Item Double).

def auto_battle?
    special_flag(FLAG_ID_AUTO_BATTLE)
  end


Determines whether the battler is set to battle automatically by calling special_flag with the ID of the autobattle flag (0).

def guard?
    special_flag(FLAG_ID_GUARD) && movable?
  end


Determines whether the battler is set to guard by checking whether the guard flag (1) is set, and also that the battler is able to act.

def substitute?
    special_flag(FLAG_ID_SUBSTITUTE) && movable?
  end


Same thing, but for the substitute flag.

def preserve_tp?
    special_flag(FLAG_ID_PRESERVE_TP)
  end


Same thing, but for the preserve TP flag.

def add_param(param_id, value)
    @param_plus[param_id] += value
    refresh
  end


This method allows a battler's bonus parameters to be increased given a param_id and value (for example, add_param(0, 25) will add 25 to max HP bonus). Refresh updates stats, as we'll see shortly.

def hp=(hp)
    @hp = hp
    refresh
  end


This is a setter method for HP.

def mp=(mp)
    @mp = mp
    refresh
  end


Setter method for MP.

def change_hp(value, enable_death)
    if !enable_death && @hp + value <= 0
      self.hp = 1
    else
      self.hp += value
    end
  end


HP change method for actors; allows HP to be modified by an amount rather than set to a specific one. Takes two parameters: value, and a flag for whether the actor can die as a result of the change.

If the enable_death flag is false AND adding the value to HP would result in it being less than 0, set it to 1. Otherwise, add value to HP.

def tp=(tp)
    @tp = [[tp, max_tp].min, 0].max
  end


Setter method for TP. Sets @tp to the maximum value between [minimum of TP and max TP] and 0. This prevents TP going below 0 or above max.

def max_tp
    return 100
  end


And here we see that max_tp returns 100. You could change this if you wanted more or less max TP in your game.

def refresh
    state_resist_set.each {|state_id| erase_state(state_id) }
    @hp = [[@hp, mhp].min, 0].max
    @mp = [[@mp, mmp].min, 0].max
    @hp == 0 ? add_state(death_state_id) : remove_state(death_state_id)
  end


Okay, refresh does a few things. First, it goes through each state the battler resists and calls a block on it, supplying state_id as the block variable. It then calls erase_state on that ID (the logic being that if the battler resists a state we don't want them to have it)

HP is set to the maximum value between [minimum of HP and max HP] and 0. As with TP, this prevents HP going below 0 or above max. Same thing happens with MP.
Finally, if HP is 0 we add the death state; if it isn't, we remove the death state.

def recover_all
    clear_states
    @hp = mhp
    @mp = mmp
  end


Method for full recovery. Clears states and sets HP/MP to maximum values.

def hp_rate
    @hp.to_f / mhp
  end


Returns the percentage of HP the battler has, by, funnily enough, dividing HP by max HP. Note that it's cast to a float so that the return value is a float and not an integer. You could actually achieve the same effect by doing @hp / mhp * 1.0, but it doesn't really make a difference.

def mp_rate
    mmp > 0 ? @mp.to_f / mmp : 0
  end


Returns the percentage of MP if it's greater than 0, or 0 otherwise.

def tp_rate
    @tp.to_f / 100
  end


Returns the percentage of TP. Oddly enough, I would have expected them to do @tp.to_f / max_tp in case you decide to change the max value. Not sure why they used 100 as a literal here. Be aware that if you do change the max, you'll need to change it here too.

def hide
    @hidden = true
  end


Method for hiding the battler; simply sets @hidden to true.

def appear
    @hidden = false
  end


Method for showing the battler; simply sets @hidden to false.

def hidden?
    @hidden
  end


Returns the value of @hidden.

def exist?
    !hidden?
  end


Returns true if the battler is not hidden, false otherwise (the logic being that battlers which aren't visible don't "exist" at that moment in time)

def dead?
    exist? && death_state?
  end


Returns true if the battler exists AND has the death state.

def alive?
    exist? && !death_state?
  end


Returns true if the battler exists AND does not have the death state.

def normal?
    exist? && restriction == 0
  end


Returns true if the battler exists AND has no action restrictions.

def inputable?
    normal? && !auto_battle?
  end


Returns true if normal is true AND the battler isn't set to auto battle.

def movable?
    exist? && restriction < 4
  end


Returns true if the battler exists AND restriction is less than 4 (Cannot move, as seen in the database. The other possibilities are None, Attack an enemy, Attack anyone, or Attack an ally)

def confusion?
    exist? && restriction >= 1 && restriction <= 3
  end


Sure enough, this one returns true if the battlers exists AND the restriction is between 1 and 3.

def confusion_level
    confusion? ? restriction : 0
  end


You may remember confusion_level from looking at Game_Action; here we see that its return value is the value of retriction if the battler is confused, or 0 otherwise. This means a confused battler will have a confusion level of 1, 2 or 3, which we saw in the part of Game_Action that deals with confusion.

def actor?
    return false
  end


This method always returns false. I guess this is a good point to tell you that quite a few of these methods (this one, the ones dealing with features, etc.) will be overloaded by the child classes for actors and enemies. We'll see this in practice soon.

def enemy?
    return false
  end


Same thing for determining whether the battler is an enemy.

def sort_states
    @states = @states.sort_by {|id| [-$data_states[id].priority, id] }
  end


This method sorts the states, putting the ones with higher priority first. It uses an array to sort by both priority and ID, so when two states have the same priority the one higher in the list (lower ID) will be first.

def restriction
    states.collect {|state| state.restriction }.push(0).max
  end


This method takes the array of states, runs through it creating a new array of the restrictions for those states, adds 0 to the array, and takes the maximum value. In other words, it gets the highest restriction value of all states the battler has (the logic being that "cannot move" is higher priority than "attack an ally" which is higher priority than "attack anyone" which is higher priority than "attack an enemy")

def most_important_state_text
    states.each {|state| return state.message3 unless state.message3.empty? }
    return ""
  end


This method goes through each state and if that state has a message3 set (the message for the state continuing to be active) it returns that message. If all are empty, it returns a blank string. As the states are sorted every time a new state is added, this will prioritise the highest-priority state that has a continuation message.

def skill_wtype_ok?(skill)
    return true
  end


Determines whether a weapon type required for a skill is equipped. The superclass method always returns true; this will be overloaded by the child classes.

def skill_mp_cost(skill)
    (skill.mp_cost * mcr).to_i
  end


Determines the MP cost for a particular skill. Returns the supplied skill's MP cost * mcr as an integer. (MCR being MP Cost Rate)

def skill_tp_cost(skill)
    skill.tp_cost
  end


Determines the TP cost for a skill. Returns the supplied skill's TP cost.

def skill_cost_payable?(skill)
    tp >= skill_tp_cost(skill) && mp >= skill_mp_cost(skill)
  end


Determines whether the cost of a skill is payable. Returns true if the battler's TP is greater than or equal to the skill's TP cost AND their MP is greater than or equal to its MP cost.

def pay_skill_cost(skill)
    self.mp -= skill_mp_cost(skill)
    self.tp -= skill_tp_cost(skill)
  end


This method processes payment of skill costs. It reduces the battler's MP and TP by the associated costs calculated.

def occasion_ok?(item)
    $game_party.in_battle ? item.battle_ok? : item.menu_ok?
  end


This method checks whether the current situation is an appropriate one in which to use a particular item or skill. If the party is in battle, called the battle_ok? method of the item. Otherwise calls the menu_ok? method of the item. These are built-in methods of RPG::UseableItem which check whether the item/skill is set to Always/Only in Battle or Always/Only from the Menu respectively.

def usable_item_conditions_met?(item)
    movable? && occasion_ok?(item)
  end


This method checks whether the conditions are met to use a particular item or skill. Returns true if the battler is capable of acting and it's the appropriate situation.

def skill_conditions_met?(skill)
    usable_item_conditions_met?(skill) &&
    skill_wtype_ok?(skill) && skill_cost_payable?(skill) &&
    !skill_sealed?(skill.id) && !skill_type_sealed?(skill.stype_id)
  end


Determines whether conditions are met to use a particular skill. Returns true if its conditions are met AND the user has the appropriate weapon type required for the skill AND the user can pay the skill's cost AND the user doesn't have that skill sealed AND the user doesn't have the skill's type sealed. Phew!

def item_conditions_met?(item)
    usable_item_conditions_met?(item) && $game_party.has_item?(item)
  end


Item conditions are a bit more forgiving: is it the appropriate situation? Does the party have one? Have at it!

def usable?(item)
    return skill_conditions_met?(item) if item.is_a?(RPG::Skill)
    return item_conditions_met?(item)  if item.is_a?(RPG::Item)
    return false
  end


Determines whether an item or skill is useable. Checks whether it's of type RPG::Skill or RPG::Item and calls the appropriate conditions_met? method. Returns false if the item is neither of those things.

def equippable?(item)
    return false unless item.is_a?(RPG::EquipItem)
    return false if equip_type_sealed?(item.etype_id)
    return equip_wtype_ok?(item.wtype_id) if item.is_a?(RPG::Weapon)
    return equip_atype_ok?(item.atype_id) if item.is_a?(RPG::Armor)
    return false
  end


Determines whether a given item is equippable. Returns false unless it's of type RPG::EquipItem (you can't equip something that isn't equipment after all). Return false if the battler has that equipment type sealed. Return the result of checking whether the battler can equip the item's weapon type if it's a weapon, or its armour type if it's armour. Otherwise, return false.

def attack_skill_id
    return 1
  end


Determines the skill ID for a normal physical attack, which in the default battle system is 1.

def guard_skill_id
    return 2
  end


Determines the skill ID for the Guard command, which by default is 2.

def attack_usable?
    usable?($data_skills[attack_skill_id])
  end


Determines whether a normal attack can be used by calling usable? on the skill corresponding to attack_skill_id from the database.

def guard_usable?
    usable?($data_skills[guard_skill_id])
  end


Same thing, but for Guard.

Okay, so that's finally it for Game_BattlerBase! I don't know about you but I'm exhausted after all that. Take a breather. Go have a cup of tea. I'll be here waiting.

All refreshed? Great, let's continue.

Game_Battler
Child class of Game_BattlerBase, this one handles stuff like sprite manipulation and processing actions. It's the superclass for Game_Actor and Game_Enemy. It's almost like we're evolving Pokémon here!

First, constants.

EFFECT_RECOVER_HP     = 11              # HP Recovery
  EFFECT_RECOVER_MP     = 12              # MP Recovery
  EFFECT_GAIN_TP        = 13              # TP Gain
  EFFECT_ADD_STATE      = 21              # Add State
  EFFECT_REMOVE_STATE   = 22              # Remove State
  EFFECT_ADD_BUFF       = 31              # Add Buff
  EFFECT_ADD_DEBUFF     = 32              # Add Debuff
  EFFECT_REMOVE_BUFF    = 33              # Remove Buff
  EFFECT_REMOVE_DEBUFF  = 34              # Remove Debuff
  EFFECT_SPECIAL        = 41              # Special Effect
  EFFECT_GROW           = 42              # Raise Parameter
  EFFECT_LEARN_SKILL    = 43              # Learn Skill
  EFFECT_COMMON_EVENT   = 44              # Common Events


The constants for various effects should be fairly self-explanatory by this point.

SPECIAL_EFFECT_ESCAPE = 0               # Escape


There's also one for the special effect escape.

Public instance variables!

attr_reader   :battler_name             # battle graphic filename
  attr_reader   :battler_hue              # battle graphic hue
  attr_reader   :action_times             # action times
  attr_reader   :actions                  # combat actions (action side)
  attr_reader   :speed                    # action speed
  attr_reader   :result                   # action result (target side)
  attr_accessor :last_target_index        # last target
  attr_accessor :animation_id             # animation ID
  attr_accessor :animation_mirror         # animation flip horizontal flag
  attr_accessor :sprite_effect_type       # sprite effect
  attr_accessor :magic_reflection         # reflection flag


Again these should all be pretty self-explanatory.

def initialize
    @battler_name = ""
    @battler_hue = 0
    @actions = []
    @speed = 0
    @result = Game_ActionResult.new(self)
    @last_target_index = 0
    @guarding = false
    clear_sprite_effects
    super
  end


Okay, so on construction we're setting @battler_name to a blank string, @battler_hue to 0, @actions to an empty array, @speed to 0, @result to a new instance of Game_ActionResult (pointing to the battler it belongs to, as we discussed before), @last_target_index to 0, @guarding to false, calling clear_sprite_effects, and calling super (which calls the Initialize method of the superclass, in this case Game_BattlerBase).

def clear_sprite_effects
    @animation_id = 0
    @animation_mirror = false
    @sprite_effect_type = nil
  end


Clearing sprite effects involves setting @animation_id to 0, @animation_mirror to false, and @sprite_effect_type to nil.

def clear_actions
    @actions.clear
  end


Method for clearing actions; clear is a method which removes all elements and results in the array being empty.

def clear_states
    super
    @result.clear_status_effects
  end


Clearing states involves calling the same method of the superclass and also calling clear_status_effects in @result, a method we've already seen.

def add_state(state_id)
    if state_addable?(state_id)
      add_new_state(state_id) unless state?(state_id)
      reset_state_counts(state_id)
      @result.added_states.push(state_id).uniq!
    end
  end


Method for adding a new state to the battler, given a state_id as parameter. Checks to see whether the state is addable: if it is, call add_new_state with that ID unless the battler has it already. Reset the state counts for that state, and uniquely push that ID to the added_states property of @result. (uniq! in this case meaning to add the element in place to the @result array rather than returning a new one, and only adding the element if it isn't already there)

def state_addable?(state_id)
    alive? && $data_states[state_id] && !state_resist?(state_id) &&
      !state_removed?(state_id) && !state_restrict?(state_id)
  end


This is the method which determines whether the state can be added. Okay, so it'll return true if the battler is alive AND there is a database entry for the given state ID AND the battler doesn't resist the given state AND the battler hasn't had the same state removed during the same action AND the given state isn't removed by the action restrictions the battler currently has in place.

def state_removed?(state_id)
    @result.removed_states.include?(state_id)
  end


Determines whether the given state was already removed during the same action by checking whether the removed_states property of @result includes the state_id supplied.

def state_restrict?(state_id)
    $data_states[state_id].remove_by_restriction && restriction > 0
  end


Determines whether the given state would be removed by the battler's current restrictions (if it is, there's no point in adding it). Checks whether the state is set to "remove by restriction" AND the battler's restriction level is greater than 0.

def add_new_state(state_id)
    die if state_id == death_state_id
    @states.push(state_id)
    on_restrict if restriction > 0
    sort_states
    refresh
  end


Adds a new state. If the supplied ID is the death state ID, call die. Push the ID to the @states variable, call on_restrict if the battler's restriction is now greater than 0, sort the states, and refresh.

def on_restrict
    clear_actions
    states.each do |state|
      remove_state(state.id) if state.remove_by_restriction
    end
  end


This method clears any actions the battler currently has queued, and then for each state they have, remove it from their states if that state is removed by restrictions.

def reset_state_counts(state_id)
    state = $data_states[state_id]
    variance = 1 + [state.max_turns - state.min_turns, 0].max
    @state_turns[state_id] = state.min_turns + rand(variance)
    @state_steps[state_id] = state.steps_to_remove
  end


This method resets the turn and step counts for a given state. state is set to the database entry for the state_idth state. Variance is set to 1 + the maximum value between (the maximum turns the state lasts - the minimum turns the state lasts) and 0.

Example: Blind in the default database is set to expire between 3 to 5 turns. In this case, variance will be 1 + [5 - 3, 0].max = 1 + 2 = 3.
@state_turns for the supplied ID is then set to the minimum turns (in this case 3) + a random number between 0 and the variance (0-2) so Blind will last between 3 and 5 turns.
Finally, @state_steps for the supplied ID is set to the number of steps set for removing the state (which will only be relevant if "remove by walking" has been set for the state)

def remove_state(state_id)
    if state?(state_id)
      revive if state_id == death_state_id
      erase_state(state_id)
      refresh
      @result.removed_states.push(state_id).uniq!
    end
  end


Removes a state from the battler. First checks to see if they actually have the state in question: if they do and the state is death, call revive. Erase the state from the battler's list of states, refresh their stats, and uniquely push the state ID to @result's removed_states property.

def die
    @hp = 0
    clear_states
    clear_buffs
  end


Method for killing a battler. Sets their HP to 0 and clears their states/buffs. (interesting change you could make to this: have status ailments that persist through death)

def revive
    @hp = 1 if @hp == 0
  end


Method for reviving a battler. Sets their HP to 1 if it's 0.

def escape
    hide if $game_party.in_battle
    clear_actions
    clear_states
    Sound.play_escape
  end


Method for the battler escaping from battle. Hide them if the party is in battle, clear their actions and states, and finally play the escape sound effect.

def add_buff(param_id, turns)
    return unless alive?
    @buffs[param_id] += 1 unless buff_max?(param_id)
    erase_buff(param_id) if debuff?(param_id)
    overwrite_buff_turns(param_id, turns)
    @result.added_buffs.push(param_id).uniq!
    refresh
  end


Method for adding a buff to the battler. Return unless the battler is alive (you can't buff a dead character). Add 1 to the param_idth element of @buffs unless that parameter is already buffed to its maximum level. Reset the buff state if that parameter was debuffed. Overwrite the duration in turns, uniquely push the param_id to @result's added_buffs property, and refresh.

def add_debuff(param_id, turns)
    return unless alive?
    @buffs[param_id] -= 1 unless debuff_max?(param_id)
    erase_buff(param_id) if buff?(param_id)
    overwrite_buff_turns(param_id, turns)
    @result.added_debuffs.push(param_id).uniq!
    refresh
  end


Method for adding a debuff. Works almost identically to add_buff but subtracts one from @buffs instead of adding and erases if the character was already buffed. And obviously it pushes to added_debuffs instead of added_buffs.

Interesting note about the way buffs/debuffs work by default: if you have, say, a level 2 buff to atk, any atk debuff at all will reset it to 0. Likewise if you have a level 2 debuff, any buff will reset it. You could change this if you wanted so that buffing a debuff or debuffing a buff only adds/removes one level.

def remove_buff(param_id)
    return unless alive?
    return if @buffs[param_id] == 0
    erase_buff(param_id)
    @buff_turns.delete(param_id)
    @result.removed_buffs.push(param_id).uniq!
    refresh
  end


Method for removing a buff to a given param_id. Return unless alive again, obviously. Return if the level of buff for that param_id is 0 (which means no buff). erase the buff, delete the turns entry for it from @buff_turns, uniquely push the ID to @result's removed-buffs, and refresh.

def erase_buff(param_id)
    @buffs[param_id] = 0
    @buff_turns[param_id] = 0
  end


Method for erasing a buff. Simply sets the param_idth element of @buffs to 0, and also sets the param_id key for @buff_turns to 0.

def buff?(param_id)
    @buffs[param_id] > 0
  end


Determines whether a given param_id is buffed. Returns true if its buff level is greater than 0, false otherwise.

def debuff?(param_id)
    @buffs[param_id] < 0
  end


Same thing, but for debuff.

def buff_max?(param_id)
    @buffs[param_id] == 2
  end


Determines whether the buff for a given param_id is at maximum level. Returns true if the level is 2, false otherwise.

def debuff_max?(param_id)
    @buffs[param_id] == -2
  end


Same thing but for debuffs.

def overwrite_buff_turns(param_id, turns)
    @buff_turns[param_id] = turns if @buff_turns[param_id].to_i < turns
  end


Sets the param_id key of @buff_turns to the supplied turns value but only if the current number of turns is less than the new value. (doesn't overwrite if calling the method would result in a shorter buff)

def update_state_turns
    states.each do |state|
      @state_turns[state.id] -= 1 if @state_turns[state.id] > 0
    end
  end


Updates the turns left on states. Runs through each state and subtracts 1 from its turn count if the turn count is greater than 0.

def update_buff_turns
    @buff_turns.keys.each do |param_id|
      @buff_turns[param_id] -= 1 if @buff_turns[param_id] > 0
    end
  end


Same thing, but for the buff turn counts.

def remove_battle_states
    states.each do |state|
      remove_state(state.id) if state.remove_at_battle_end
    end
  end


Runs through each state and removes it if it's able to be removed at the end of battle (this method is only called when battle ends).

def remove_all_buffs
    @buffs.size.times {|param_id| remove_buff(param_id) }
  end


Ah, we haven't seen the times method for a while. This one basically loops through however many elements @buffs has at the time and removes each one.

def remove_states_auto(timing)
    states.each do |state|
      if @state_turns[state.id] == 0 && state.auto_removal_timing == timing
        remove_state(state.id)
      end
    end
  end


This method removes states set to expire automatically. The supplied timing value will either be 1 (end of action) or 2 (end of turn).

Runs through each state and if the number of turns on it is 0 and that state's auto-removal timing is equal to the timing value supplied, remove it.

def remove_buffs_auto
    @buffs.size.times do |param_id|
      next if @buffs[param_id] == 0 || @buff_turns[param_id] > 0
      remove_buff(param_id)
    end
  end


Automatically removes buffs that have expired. Runs through each buff present; goes to the next element if there's no buff OR the number of turns left on it is greater than 0. If it passes that check, removes the buff in question.

def remove_states_by_damage
    states.each do |state|
      if state.remove_by_damage && rand(100) < state.chance_by_damage
        remove_state(state.id)
      end
    end
  end


Removes states that expire on damage. Runs through each state, and if it's set to remove by damage AND a random number from 0-99 is less than the percentage chance set for the state being removed, remove it.

def make_action_times
    action_plus_set.inject(1) {|r, p| rand < p ? r + 1 : r }
  end


Wow, this one looks a bit more complicated than we're used to. Okay. We already know from Game_BattlerBase that action_plus_set is an array of the percentage chances for getting an extra action. Let's say we've got two effects, one for 25% and one for 15%. We're calling inject, with an initial value of 1, a memo variable r, and obj variable p.

If a random number between 0 and 1 is less than p, return r + 1, otherwise return r.

Let's say that the random number for the first iteration is 0.593848blah. It's not less than 0.25, so we return r, which is 1. The second iteration gives 0.0483983, which is less than 0.15 so we return r + 1, 2. The battler will get 2 actions this turn.

def make_actions
    clear_actions
    return unless movable?
    @actions = Array.new(make_action_times) { Game_Action.new(self) }
  end


This method creates an array of actions for the battler. First, actions are cleared. We return unless the battler is movable, as there's no point in setting up actions for a battler that can't act. Then @actions is set to a new array with make_action_times elements, and the block sets each element to a new instance of Game_Action with the battler as the subject. In our example where the battler gets 2 actions, this will result in @actions being a 2-element array containing two instance of Game_Action.

def make_speed
    @speed = @actions.collect {|action| action.speed }.min || 0
  end


This method sets @speed to the minimum value of an array consisting of the speeds of all actions being taken, or 0 if there are no actions to collect.

def current_action
    @actions[0]
  end


Returns the first element of the @actions array, which is naturally the current action being performed.

def remove_current_action
    @actions.shift
  end


Removes the current action, which removes the first element of @actions and returns it.

def force_action(skill_id, target_index)
    clear_actions
    action = Game_Action.new(self, true)
    action.set_skill(skill_id)
    if target_index == -2
      action.target_index = last_target_index
    elsif target_index == -1
      action.decide_random_target
    else
      action.target_index = target_index
    end
    @actions.push(action)
  end


This method forces an action on the battler and takes two parameters: a skill ID and a target index.
First the battler's actions are cleared, then action is set to a new instance of Game_Action and its set_skill method is called with skill_id as the argument. If the target index is -2, the target index of the action is set to last_target_index (as -2 denotes the most recent target of an action). If target index is -1, decide on a random target. Otherwise, set the action's target index to the argument given. Either way, push action to the @actions array.

def make_damage_value(user, item)
    value = item.damage.eval(user, self, $game_variables)
    value *= item_element_rate(user, item)
    value *= pdr if item.physical?
    value *= mdr if item.magical?
    value *= rec if item.damage.recover?
    value = apply_critical(value) if @result.critical
    value = apply_variance(value, item.damage.variance)
    value = apply_guard(value)
    @result.make_damage(value.to_i, item)
  end


Okay, so this is the method which determines how much damage a given skill or item does and takes two parameters: the user and the item in question.

item.damage is a built-in instance of RPG::UsableItem::Damage and its eval method is as follows: eval(a, b, v) (a is the user, b is the target, and v is the in-game variable array)

Looking at the source for the eval method, we see that it processes thusly:

[Kernel.eval(@formula), 0].max * sign rescue 0

Kernel is a module defining methods which can be referred to by any class. Its eval method takes the string (expr) as an argument and evaluates it as a Ruby program, returning the result. What we're basically saying here is "evaluate the damage formula as a program and return the maximum value between its return value and 0, multiplied by the damage sign (-1 if recovery, 1 otherwise) with 0 as a rescue return value (in other words, if evaluating the formula results in an error).

Take as an example the formula for a standard attack: a.atk * 4 - b.def * 2

We already know that in eval a is the user and b is the target. In this case the target is self, as the battler is finding out how much damage is being done to itself by an external source. $game_variables is passed I believe so that you can use v[n] in the formulae. So Kernel.eval will calculate the user's attack * 4 - the target's defence * 2 and return the result, which will be stored in value.

After this, the value is multiplied by the result of item_element_rate with the user and item as arguments (which will give an elemental multiplier based on the elements used in the attack, as we'll see shortly).

Then the value is multiplied by PDR if it's a physical attack or MDR if it's a magical attack (it can't be both), then multiplied by REC if it's set to recover HP.

Value is then set to the result of apply_critical with value as the argument if @result.critical is true. It is then set to the result of apply_variance with value and the damage variance as arguments. It is then set to the result of apply_guard with value as the argument. Finally, make_damage is called for @result, with the integer representation of value and item as arguments.

def item_element_rate(user, item)
    if item.damage.element_id < 0
      user.atk_elements.empty? ? 1.0 : elements_max_rate(user.atk_elements)
    else
      element_rate(item.damage.element_id)
    end
  end


This is the method that determines an item's elemental multiplier. If the element ID of the item itself is less than 0, we check whether the user's attack elements list is empty. If it is, we return 1.0, otherwise we return the result of elements_max_rate on the user's attack elements. If the item's element ID is NOT 0, we return the result of element_rate on the item's element ID (this shows us that having a specific element for an item or skill overrides any intrinsic elements the user has)

def elements_max_rate(elements)
    elements.inject([0.0]) {|r, i| r.push(element_rate(i)) }.max
  end


This is the method that determines the maximum elemental multiplier, given an array of element IDs. We've seen inject several times now so what this is doing should be obvious, but the bottom line is that it's starting with 0.0, looping through each element passed to it and adding it to an array, then taking the maximum value from the array as the return value.

def apply_critical(damage)
    damage * 3
  end


Criticals are pretty simple: the damage is just multiplied by 3.

def apply_variance(damage, variance)
    amp = [damage.abs * variance / 100, 0].max.to_i
    var = rand(amp + 1) + rand(amp + 1) - amp
    damage >= 0 ? damage + var : damage - var
  end


Applying variance is fun! I've had to examine this one before, so let's see if I remember the mistake I made last time. First of all we're supplying two parameters, damage and variance. Variance is pulled from the database setting.

amp is set to the integer form of the maximum value between (the absolute form of damage * variance / 100) and 0. So say we've got 500 damage and variance is 20. This results in amp = .max.to_i = .max_to_i. The result of which is obviously 100. The .max part is just there to make sure variance isn't negative (though I don't see how it can be as variance can't be set below 0 and .abs ensures we're never dealing with negative damage values)

var is then set to a random number between 0 and amp+1 + another random number in the same range - amp

Let's say we get 34 and 95, var will be 29.

If damage is greater than or equal to 0, var is added to it. Otherwise, var is subtracted from it. In the example case, damage is now 529.

def apply_guard(damage)
    damage / (damage > 0 && guard? ? 2 * grd : 1)
  end


If damage is greater than 0 AND the battler has the guard special flag, return damage / (2 * GRD (GuaRD effect rate)); otherwise return damage / 1. In our example, if the battler had the guard flag and their GRD was 125%, the damage would be reduced to 423.

def execute_damage(user)
    on_damage(@result.hp_damage) if @result.hp_damage > 0
    self.hp -= @result.hp_damage
    self.mp -= @result.mp_damage
    user.hp += @result.hp_drain
    user.mp += @result.mp_drain
  end


This is the method that actually does the damage, taking user as a parameter. Calls on_damage with @result.hp_damage as the argument if the damage was greater than 0, subtracts the HP damage from the battler's HP, the MP damage from the battler's MP, and adds to the user's HP/MP if any was drained.

def use_item(item)
    pay_skill_cost(item) if item.is_a?(RPG::Skill)
    consume_item(item)   if item.is_a?(RPG::Item)
    item.effects.each {|effect| item_global_effect_apply(effect) }
  end


Method for using a skill or item. First pays the skill cost if it's a skill, or consumes the item if it's an item. Then for each effect in the item's effects, it calls item_global_effect_apply, with effect as the argument.

def consume_item(item)
    $game_party.consume_item(item)
  end


This is the method called above for consuming an item, and calls the method of the same name from $game_party.

def item_global_effect_apply(effect)
    if effect.code == EFFECT_COMMON_EVENT
      $game_temp.reserve_common_event(effect.data_id)
    end
  end


If the effect's code is EFFECT_COMMON_EVENT (44), call $game_temp.reserve_common_event supplying the effect's data ID as the argument (which will be the ID of the common event set in the database)

def item_test(user, item)
    return false if item.for_dead_friend? != dead?
    return true if $game_party.in_battle
    return true if item.for_opponent?
    return true if item.damage.recover? && item.damage.to_hp? && hp < mhp
    return true if item.damage.recover? && item.damage.to_mp? && mp < mmp
    return true if item_has_any_valid_effects?(user, item)
    return false
  end


This method tests various scenarios to see if it actually makes sense to use an item or skill. Returns false if the item/skill is for a dead ally and the battler isn't dead. Returns true if in battle. Returns true if the item/skill is for an enemy. Returns true if the item/skill has recovery AND affects HP AND the battler's HP is less than their max HP. Returns true if the item/skill has recovery AND affects MP AND the battle's MP is less than their max MP. Returns true if the result of calling item_has_any_valid_effects? with user and item as arguments returns true. Otherwise, return false.

def item_has_any_valid_effects?(user, item)
    item.effects.any? {|effect| item_effect_test(user, item, effect) }
  end


This method determines whether a given item has any valid effects. Runs through each effect and returns true if any of them return true for the block calling item_effect_test with user, item and effect as arguments.

def item_cnt(user, item)
    return 0 unless item.physical?          # Hit type is not physical
    return 0 unless opposite?(user)         # No counterattack on allies
    return cnt                              # Return counterattack rate
  end


Determines a skill/item's counterattack chance. Returns 0 unless it's a physical attack. Returns 0 unless opposite?(user) returns true, which checks that the effect came from an enemy. Otherwise, returns CNT.

def item_mrf(user, item)
    return mrf if item.magical?     # Return magic reflection if magic attack
    return 0
  end


Determines reflection rate of a skill/item. Returns MRF if it's magical, 0 otherwise.

def item_hit(user, item)
    rate = item.success_rate * 0.01     # Get success rate
    rate *= user.hit if item.physical?  # Physical attack: Multiply hit rate
    return rate                         # Return calculated hit rate
  end


Determines hit rate of a skill/item. rate is set to the success rate of the item * 0.01 (so 100% becomes 1) and multiplied by the user's hit rate if it's physical (which by default in most cases is 95%, so 1 would become 0.95) and the calculated rate is returned.

def item_eva(user, item)
    return eva if item.physical?    # Return evasion if physical attack
    return mev if item.magical?     # Return magic evasion if magic attack
    return 0
  end


Determines the evasion chance for a skill/item. Returns EVA if it's physical, MEV if it's magical, 0 otherwise.

def item_cri(user, item)
    item.damage.critical ? user.cri * (1 - cev) : 0
  end


Calculates critical chance for a skill/item. If the item is set to allow critical hits, return the user's CRI * (1 - CEV), otherwise 0.

For example, most characters by default have a crit rate of 4% and no critical evasion. This would result in a critical rate of 0.04 * (1 - 0) = 0.04.

def attack_apply(attacker)
    item_apply(attacker, $data_skills[attacker.attack_skill_id])
  end


Applies the effects of a normal attack; calls item_apply with attacker and the attack skill details as arguments.

def item_apply(user, item)
    @result.clear
    @result.used = item_test(user, item)
    @result.missed = (@result.used && rand >= item_hit(user, item))
    @result.evaded = (!@result.missed && rand < item_eva(user, item))
    if @result.hit?
      unless item.damage.none?
        @result.critical = (rand < item_cri(user, item))
        make_damage_value(user, item)
        execute_damage(user)
      end
      item.effects.each {|effect| item_effect_apply(user, item, effect) }
      item_user_effect(user, item)
    end
  end


Okay, first we clear @result, then set its used flag to the result of item_test with user and item as arguments. We then set its missed flag to the result of checking whether used is true AND a random number between 0 and 1 is greater than or equal to the user's hit chance. We then set its evaded flag to the result of checking whether it did NOT miss AND a random number between 0 and 1 is less than the evasion chance.

If it hit, unless the item/skill does no damage, we set @result's critical flag to the result of checking whether a random number between 0 and 1 is less than the critical hit chance. We then call make_damage_value with user and item as arguments, and execute_damage with user as the argument.

After that's done, we call item_effect_apply for each item effect with user, item and effect as arguments, and finally call item_user_effect with user and item as arguments.

def item_effect_test(user, item, effect)
    case effect.code
    when EFFECT_RECOVER_HP
      hp < mhp || effect.value1 < 0 || effect.value2 < 0
    when EFFECT_RECOVER_MP
      mp < mmp || effect.value1 < 0 || effect.value2 < 0
    when EFFECT_ADD_STATE
      !state?(effect.data_id)
    when EFFECT_REMOVE_STATE
      state?(effect.data_id)
    when EFFECT_ADD_BUFF
      !buff_max?(effect.data_id)
    when EFFECT_ADD_DEBUFF
      !debuff_max?(effect.data_id)
    when EFFECT_REMOVE_BUFF
      buff?(effect.data_id)
    when EFFECT_REMOVE_DEBUFF
      debuff?(effect.data_id)
    when EFFECT_LEARN_SKILL
      actor? && !skills.include?($data_skills[effect.data_id])
    else
      true
    end
  end


This method tests the result of using an item or skill, with a given user/item/effect.
First we check what the effect's code is:
If it involves recovering HP, the return value is the result of checking whether HP is less than max HP OR the effect's value is less than 0 OR the effect's second value is less than 0.
If it involves recovering MP, the return value is the result of checking whether MP is less than max MP OR the effect's value is less than 0 OR the effect's second value is less than 0.
If it involves adding a state, the return value is whether the battler does NOT have the state in question.
If it involves removing a state, the return value is whether the battler has the state in question.
If it involves adding a buff, the return value is whether the battler already has the max level buff for the given stat.
If it involves adding a debuff, the return value is whether the battler already has the max level debuff for the given stat.
If it involves removing a buff, the return value is whether the battler has that buff.
If it involves removing a debuff, the return value is whether the battler has that debuff.
If it involves learning a skill, the return value is whether the battler is an actor AND their skills don't already include the one in question.
Otherwise, return true.

def item_effect_apply(user, item, effect)
    method_table = {
      EFFECT_RECOVER_HP    => :item_effect_recover_hp,
      EFFECT_RECOVER_MP    => :item_effect_recover_mp,
      EFFECT_GAIN_TP       => :item_effect_gain_tp,
      EFFECT_ADD_STATE     => :item_effect_add_state,
      EFFECT_REMOVE_STATE  => :item_effect_remove_state,
      EFFECT_ADD_BUFF      => :item_effect_add_buff,
      EFFECT_ADD_DEBUFF    => :item_effect_add_debuff,
      EFFECT_REMOVE_BUFF   => :item_effect_remove_buff,
      EFFECT_REMOVE_DEBUFF => :item_effect_remove_debuff,
      EFFECT_SPECIAL       => :item_effect_special,
      EFFECT_GROW          => :item_effect_grow,
      EFFECT_LEARN_SKILL   => :item_effect_learn_skill,
      EFFECT_COMMON_EVENT  => :item_effect_common_event,
    }
    method_name = method_table[effect.code]
    send(method_name, user, item, effect) if method_name
  end


This method determines what effect is happening and calls the appropriate method for processing it. method_table is set to a hash table where the keys are constants and the values are symbols pointing to the methods corresponding to that effect. For example, EFFECT_RECOVER_HP equates to 11 (which is the key) and the value is the symbol :item_effect_recover_hp.

method_name is set to the key in method_table corresponding to the supplied effect's code.
Then, if method_name is not nil, we call send with the arguments method_name, user, item and effect.

send allows you to invoke another method by name (in this case whichever symbol matches) and what follows determines the parameters the method receives.

def item_effect_recover_hp(user, item, effect)
    value = (mhp * effect.value1 + effect.value2) * rec
    value *= user.pha if item.is_a?(RPG::Item)
    value = value.to_i
    @result.hp_damage -= value
    @result.success = true
    self.hp += value
  end


This method recovers some amount of HP. The value is set to (max HP * the percentage HP recovery + the static HP recovery) * REC (RECovery effect rate). Let's say we have a potion that recovers 10% + 20 HP, and use it on a character with 200 max HP and 150% REC:

value = (200 * 0.1 + 20) * 1.5 = (20 + 20) * 1.5 = 60

value is then multiplied by the user's PHA (PHArmacology) if the item is an actual item and not a skill. It's finally converted to an integer.

@result's HP damage has value subtracted from it; the success flag is set to true, and the battler's HP is increased by value.

def item_effect_recover_mp(user, item, effect)
    value = (mmp * effect.value1 + effect.value2) * rec
    value *= user.pha if item.is_a?(RPG::Item)
    value = value.to_i
    @result.mp_damage -= value
    @result.success = true if value != 0
    self.mp += value
  end


This is pretty much identical to the HP recovery effect but for MP instead.

def item_effect_gain_tp(user, item, effect)
    value = effect.value1.to_i
    @result.tp_damage -= value
    @result.success = true if value != 0
    self.tp += value
  end


Similar method for gaining TP. Only works in absolute values and not percentages, so the calculation is easier.

def item_effect_add_state(user, item, effect)
    if effect.data_id == 0
      item_effect_add_state_attack(user, item, effect)
    else
      item_effect_add_state_normal(user, item, effect)
    end
  end


This method adds states set in the item's effects. If the ID is 0 (normal attack) the item will try to inflict any states that the user's normal attack is capable of. Otherwise, it will add the state specified.

def item_effect_add_state_attack(user, item, effect)
    user.atk_states.each do |state_id|
      chance = effect.value1
      chance *= state_rate(state_id)
      chance *= user.atk_states_rate(state_id)
      chance *= luk_effect_rate(user)
      if rand < chance
        add_state(state_id)
        @result.success = true
      end
    end
  end


For each of the user's potential attack states (states it can add with physical attacks), set chance to the effect's value1. Multiply it by the battler's state rate for that state. Multiply it by the user's attack state rate for that state. Multiply it by the user's luck effect rate. If a random number between 0 and 1 is less than chance, inflict the state and set @result's success flag to true.

def item_effect_add_state_normal(user, item, effect)
    chance = effect.value1
    chance *= state_rate(effect.data_id) if opposite?(user)
    chance *= luk_effect_rate(user)      if opposite?(user)
    if rand < chance
      add_state(effect.data_id)
      @result.success = true
    end
  end


This method adds a specified state. Follows a similar chance calculation to the previous one, but obviously without the user's state rates coming into play, and state_rate/luck effect are only taken into account if the state is added by an enemy.

def item_effect_remove_state(user, item, effect)
    chance = effect.value1
    if rand < chance
      remove_state(effect.data_id)
      @result.success = true
    end
  end


No modifiers for removing states, just a straight percentage chance.

def item_effect_add_buff(user, item, effect)
    add_buff(effect.data_id, effect.value1)
    @result.success = true
  end


This method simply adds the given buff.

def item_effect_add_debuff(user, item, effect)
    chance = debuff_rate(effect.data_id) * luk_effect_rate(user)
    if rand < chance
      add_debuff(effect.data_id, effect.value1)
      @result.success = true
    end
  end


This method adds the given debuff. chance is determined as the battler's debuff rate for the specific stat, multiplied by the user's luck effect rate.

def item_effect_remove_buff(user, item, effect)
    remove_buff(effect.data_id) if @buffs[effect.data_id] > 0
    @result.success = true
  end


Removes the given buff if the battler has it, and sets @result's success flag to true.

def item_effect_remove_debuff(user, item, effect)
    remove_buff(effect.data_id) if @buffs[effect.data_id] < 0
    @result.success = true
  end


Removes the given debuff if the battler has it, and sets @result's success flag to true.

def item_effect_special(user, item, effect)
    case effect.data_id
    when SPECIAL_EFFECT_ESCAPE
      escape
    end
    @result.success = true
  end


The only special effect is "escape" as you can see from the code. The way they've set this and some other fields up makes me wonder if they intend on adding more in a patch or something.

def item_effect_grow(user, item, effect)
    add_param(effect.data_id, effect.value1.to_i)
    @result.success = true
  end


This method gives a bonus to the specified stat and sets @result's success flag to true.

def item_effect_learn_skill(user, item, effect)
    learn_skill(effect.data_id) if actor?
    @result.success = true
  end


This method teaches the specified skill if the battler is an actor, and sets @result's success flag to true.

def item_effect_common_event(user, item, effect)
  end


This doesn't actually do anything as common event handling was done by earlier code. I think it's just here to catch the effect code for common event.

def item_user_effect(user, item)
    user.tp += item.tp_gain * user.tcr
  end


This method adds TP to the user equal to the item's TP gain * the TCR (TP Charge Rate) of the user.

def luk_effect_rate(user)
    [1.0 + (user.luk - luk) * 0.001, 0.0].max
  end


This method determines the user's luck effect rate. Let's say the user has 15 luck and the target has 1:

[1.0 + (15 - 1) * 0.001, 0.0].max =
[1.0 + (14) * 0.001, 0.0].max =
[1.0 + 0.014, 0.0].max =
[1.014, 0.0].max =
1.014

So the luck modifier will give an additional 1.4% to the calculation.

def opposite?(battler)
    actor? != battler.actor? || battler.magic_reflection
  end


Determines if the relation is hostile: returns true if the result of checking whether the current battler is an actor is not the same as checking whether the supplied battler is (so either we'll get an actor and a not-actor, or a not-actor and an actor) OR if the battler is currently reflecting magic.

def perform_map_damage_effect
  end


This method doesn't actually do anything, but is overloaded by the method of the same name in Game_Actor.

def init_tp
    self.tp = rand * 25
  end


Initialises TP; it'll end up being a value anywhere from 0 to 25.

def clear_tp
    self.tp = 0
  end


Clears TP, which is to say sets it to 0.

def charge_tp_by_damage(damage_rate)
    self.tp += 50 * damage_rate * tcr
  end


Charges TP by damage taken; damage_rate, as we'll see shortly, is the percentage of HP that was taken off by the attack.

Let's say for example we have a battler with 500 HP that's just taken 20 damage. damage_rate will be 0.04. With a TCR of 0, they will gain 50 * 0.04 = 2 TP.

def regenerate_hp
    damage = -(mhp * hrg).to_i
    perform_map_damage_effect if $game_party.in_battle && damage > 0
    @result.hp_damage = [damage, max_slip_damage].min
    self.hp -= @result.hp_damage
  end


This method regenerates HP.

damage is set to the integer form of the negative of (mhp * hrg (HP ReGeneration rate)

Let's say a battler has an HP regeneration rate of +10% and max HP of 500; damage will be -50.

We call perform_map_damage_effect if the party is in battle and damage is greater than 0.
@result's HP damage is set to the minimum value between damage and max_slip_damage.
The battler's HP is then reduced by @result's HP damage.

def max_slip_damage
    $data_system.opt_slip_death ? hp : [hp - 1, 0].max
  end


If "KO by slip damage" is checked in the database, we return HP, otherwise we return the maximum value between (HP - 1) and 0. In the case of our regen example above, @result's HP damage is set to -50, and we remove -50 HP from the battler, so they regain 50 HP.

def regenerate_mp
    @result.mp_damage = -(mmp * mrg).to_i
    self.mp -= @result.mp_damage
  end


Almost the same principle as regenerating HP, but for MP and using MRG.

def regenerate_tp
    self.tp += 100 * trg
  end


Regenerating TP adds 100 * TP Regen Rate to the battler.

def regenerate_all
    if alive?
      regenerate_hp
      regenerate_mp
      regenerate_tp
    end
  end


This method calls all regeneration methods if the battler is alive.

def on_battle_start
    init_tp unless preserve_tp?
  end


This method initialises TP when battle starts unless the battler has the "preserve TP" flag set.

def on_action_end
    @result.clear
    remove_states_auto(1)
    remove_buffs_auto
  end


At the end of an action, clears @result, removes states set to automatically expire after actions, and removes auto-expiring buffs.

def on_turn_end
    @result.clear
    regenerate_all
    update_state_turns
    update_buff_turns
    remove_states_auto(2)
  end


At the end of a turn, clears @result, regenerates HP/MP/TP, updates the state/buff turns, and removes any states set to expire at the end of a turn.

def on_battle_end
    @result.clear
    remove_battle_states
    remove_all_buffs
    clear_actions
    clear_tp unless preserve_tp?
    appear
  end


At the end of a battle, clear @result, remove battle states, remove buffs, clear actions, clear TP (unless the flag is checked) and call appear, which unhides the battler if they were hidden.

def on_damage(value)
    remove_states_by_damage
    charge_tp_by_damage(value.to_f / mhp)
  end


Method called when taking damage; removes states that fall off when damaged and charges TP.

Phew! We've covered a LOT of material in just two classes. I was going to do Game_Actor, Game_Enemy, Game_Actors, Game_Unit, Game_Party and Game_Troop as well but I think we've gotten into ludicrous length territory as-is so I'll leave those for next week.

As always, all comments and criticisms welcome.

Until next time.

Posts

Pages: 1
I challenge this script to a robattle!
Pages: 1