SLIP INTO RUBY: UNDER THE HOOD PART 6: BATTLERS!
Battlers battlers battlers
- Trihan
- 04/16/2015 03:20 PM
- 6160 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.
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.
These constants determine flag values for the Special Flag settings in features.
These constants determine the starting icon indexes for in-battle stat buffs/debuffs.
And now, public instance variables:
We've got three, all attr_readers (so they can be read but not written to directly): one each for HP, MP and TP.
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.
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!
@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.
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.
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.
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.
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.
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...
...1! This is of course changeable and you could even if you wanted have multiple death states with different effects.
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.
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).
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:
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.
This method simply returns states, which itself returns the data for each state the battler has.
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.
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.
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.
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.
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.
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.
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)
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.
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.
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.
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.
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.
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.
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.
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.
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%.
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%.
Exactly the same method, but for debuff rates instead of elements.
The same, but for state resistances.
Returns an array of all states the battler resists (those which they can't have added to them)
This method checks whether a particular state was resisting by seeing if it's included in the array returned by state_resist_set.
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).
Same as elements, but for states which attacks inflict.
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.
Determines attack speed modifier as the sum of all features which affect attack speed.
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)
Determines the skill types which are added as an array of the skill type IDs added by features.
Determines whether a skill type is sealed by checking whether the supplied skill type ID is included in features which seal skills.
Determines which individual skills are added by features.
Determines whether a supplied skill ID is sealed by checking whether its ID is included in features which seal skills.
Determines whether a particular weapon type can be equipped by checking whether its type ID is included in features which enable equipping weapon types.
Same thing, but for armour.
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.
Same thing, but checks for the type being sealed rather than fixed.
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.
Returns true if the slot type is 1 (there is a feature somewhere for which "Dual Wield" has been chosen)
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 .
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.
Returns the maximum value of the features which affect collapse type (Boss, Instant, and Not Disappear) or 0 if none are set.
Checks whether any party ability is set (Encounter Half, Encounter None, Cancel Surprise, Raise Preemptive, Gold Double or Drop Item Double).
Determines whether the battler is set to battle automatically by calling special_flag with the ID of the autobattle flag (0).
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.
Same thing, but for the substitute flag.
Same thing, but for the preserve TP flag.
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.
This is a setter method for HP.
Setter method for MP.
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.
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.
And here we see that max_tp returns 100. You could change this if you wanted more or less max TP in your game.
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.
Method for full recovery. Clears states and sets HP/MP to maximum values.
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.
Returns the percentage of MP if it's greater than 0, or 0 otherwise.
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.
Method for hiding the battler; simply sets @hidden to true.
Method for showing the battler; simply sets @hidden to false.
Returns the value of @hidden.
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)
Returns true if the battler exists AND has the death state.
Returns true if the battler exists AND does not have the death state.
Returns true if the battler exists AND has no action restrictions.
Returns true if normal is true AND the battler isn't set to auto battle.
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)
Sure enough, this one returns true if the battlers exists AND the restriction is between 1 and 3.
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.
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.
Same thing for determining whether the battler is an enemy.
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.
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")
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.
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.
Determines the MP cost for a particular skill. Returns the supplied skill's MP cost * mcr as an integer. (MCR being MP Cost Rate)
Determines the TP cost for a skill. Returns the supplied skill's TP cost.
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.
This method processes payment of skill costs. It reduces the battler's MP and TP by the associated costs calculated.
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.
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.
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!
Item conditions are a bit more forgiving: is it the appropriate situation? Does the party have one? Have at it!
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.
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.
Determines the skill ID for a normal physical attack, which in the default battle system is 1.
Determines the skill ID for the Guard command, which by default is 2.
Determines whether a normal attack can be used by calling usable? on the skill corresponding to attack_skill_id from the database.
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.
The constants for various effects should be fairly self-explanatory by this point.
There's also one for the special effect escape.
Public instance variables!
Again these should all be pretty self-explanatory.
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).
Clearing sprite effects involves setting @animation_id to 0, @animation_mirror to false, and @sprite_effect_type to nil.
Method for clearing actions; clear is a method which removes all elements and results in the array being empty.
Clearing states involves calling the same method of the superclass and also calling clear_status_effects in @result, a method we've already seen.
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)
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.
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.
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.
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.
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.
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)
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.
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)
Method for reviving a battler. Sets their HP to 1 if it's 0.
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.
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.
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.
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.
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.
Determines whether a given param_id is buffed. Returns true if its buff level is greater than 0, false otherwise.
Same thing, but for debuff.
Determines whether the buff for a given param_id is at maximum level. Returns true if the level is 2, false otherwise.
Same thing but for debuffs.
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)
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.
Same thing, but for the buff turn counts.
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).
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.
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.
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.
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.
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.
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.
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.
Returns the first element of the @actions array, which is naturally the current action being performed.
Removes the current action, which removes the first element of @actions and returns it.
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.
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.
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)
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.
Criticals are pretty simple: the damage is just multiplied by 3.
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.
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.
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.
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.
This is the method called above for consuming an item, and calls the method of the same name from $game_party.
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)
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.
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.
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.
Determines reflection rate of a skill/item. Returns MRF if it's magical, 0 otherwise.
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.
Determines the evasion chance for a skill/item. Returns EVA if it's physical, MEV if it's magical, 0 otherwise.
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.
Applies the effects of a normal attack; calls item_apply with attacker and the attack skill details as arguments.
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.
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.
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.
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.
This is pretty much identical to the HP recovery effect but for MP instead.
Similar method for gaining TP. Only works in absolute values and not percentages, so the calculation is easier.
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.
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.
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.
No modifiers for removing states, just a straight percentage chance.
This method simply adds the given buff.
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.
Removes the given buff if the battler has it, and sets @result's success flag to true.
Removes the given debuff if the battler has it, and sets @result's success flag to true.
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.
This method gives a bonus to the specified stat and sets @result's success flag to true.
This method teaches the specified skill if the battler is an actor, and sets @result's success flag to true.
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.
This method adds TP to the user equal to the item's TP gain * the TCR (TP Charge Rate) of the user.
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.
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.
This method doesn't actually do anything, but is overloaded by the method of the same name in Game_Actor.
Initialises TP; it'll end up being a value anywhere from 0 to 25.
Clears TP, which is to say sets it to 0.
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.
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.
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.
Almost the same principle as regenerating HP, but for MP and using MRG.
Regenerating TP adds 100 * TP Regen Rate to the battler.
This method calls all regeneration methods if the battler is alive.
This method initialises TP when battle starts unless the battler has the "preserve TP" flag set.
At the end of an action, clears @result, removes states set to automatically expire after actions, and removes auto-expiring buffs.
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.
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.
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.
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.
Pages:
1