SLIP INTO RUBY: UNDER THE HOOD PART 5: GAME OBJECT CONT.
In which we get into the exciting stuff.
- Trihan
- 04/15/2015 01:45 PM
- 5347 views
Attention, Sports Fans! My hair is telling me it's time for another exciting episode of
Let's just get right to it, shall we?
Game_BaseItem
This is a class that handles general stuff for skills, items, weapons and armour.
Nice easy constructor to begin with: just sets two instance variables, @class and @item_id, to nil and 0 respectively.
Here we have a set of methods to figure out what @class is currently. is_skill? returns true if its class is RPG::Skill, is_item? returns true if it's an RPG::Item, and so on. Finally, we have is_nil? which checks whether @class is in fact empty.
This is a getter for the current object. Returns the relevant piece of data from the database according to what class the object is. Note that given the default values, calling object before setting @class and @item_id is always going to return nil.
Setter for object, takes "item" as a parameter. Okay, so we set @class to the result of the above ternary operator: if item is not nil @class is set to its class, otherwise it's set to nil. Then we do the same thing for @item_id: if item is not nil, it's set to the item's ID parameter, or 0 otherwise.
Similar setter taking "is_weapon" and "item_id" as parameters. @class is set to RPG::Weapon if is_weapon is true, or RPG::Armor otherwise. @item_id is set to the supplied item_id value.
These methods all work pretty closely in tandem with each other like cogs in a machine. Let's say we create a "potion" in item database slot 1 and at some point in the code we have something like "@item = Game_BaseItem.new". We then do "@item.object = $data_items" to set object to the potion. @class is now RPG::Item and @item_id is now 1. is_item? is now going to return true (all the others will return false) and object will return the data of the item in question.
Game_Action
This class, as the name suggests, handles battle actions, and is used within Game_Battler, which we're getting ever-closer to covering.
Public instance variables ahoy!
Okay, so we've got an attr_reader for the subject of the action, an attr_reader as a flag to determine whether the action is being "forced", an attr_reader for the skill/item being used, an attr_accessor for the index of the action's target, and an attr_reader to determine action priority for autobattle. With me so far?
The constructor takes two arguments: subject, which is obviously going to be an instance of Game_Battler, and forcing, which defaults to false if no value is supplied. Sets the appropriate instance variables and then calls the clear method.
Simple enough. Sets @item to a new instance of Game_BaseItem, @target_index to -1 (generally if you have an index variable somewhere, the programmer's convention for resetting it to point to nothing is to make its value -1. There's a name for this but I can't remember it), and @value to 0.
Simply calls the method of the same name (which is a method of Game_Actor and Game_Enemy, as we'll see soon).
Same thing, but for enemies.
This method sets battle actions for enemies, and takes as its parameter "action", which must be of type RPG::Enemy::Action. You can look this up in the help file, but we'll be going over it when we get to Game_Enemy (it's gonna get a bit complicated).
This method sets the action to "attack"; simple enough, it calls set_skill supplying the attack_skill_id of the subject (which by default is 1 for every battler, though this can be changed to give different characters different default attack skills if that's something you want in your game) and returns self, which is the action calling the method.
Same thing but for guarding, which is 2 in the skill list by default (and can again be changed to give different characters different default guarding commands)
This is what we call in both of the preceding methods and for any skill being used that isn't attack or guard: it takes skill_id as a parameter, and sets @item.object to the data of the skill_idth skill in the database (remember my potion example?)
Same thing for for when an item is used. We already know from looking at Game_BaseItem that the behind-the-scenes stuff for checking classes and what kind of object has been set will be done there for us.
This is more of a shortcut method than anything else; means that anywhere in Game_Action we needed to refer to @item.object, we can just put item instead.
Such as here! This method returns true if item (which returns @item.object) is equal to the skill set as default attack in the database.
And again we're thankful for the item method. Okay, so.
If the item's scope is set to One Ally (Dead)...
set target to a random dead party member
Otherwise, if the item's scope is One Ally...
set target to a random living party member
Otherwise...
set target to a random enemy
If a target has been set...
set @target_index to the index of the target
Otherwise...
call clear
I guess now is a great time for me to explain a bit more about methods which exist solely to save time and typing for poor, weary programmers. Take the line
"target = friends_unit.random_dead_target"
We could just as easily have written
"target = subject.friends_unit.random_dead_target"
And same with
"if item.for_dead_friend?"
Could have been
"if @item.object.for_dead_friend?"
But obviously this results in more code and the more you use that particular object the more code you're going to have to write. Sometimes it'll give you a negligible benefit but for something like @item.object which is probably going to be used quite a few times it makes sense to make a method with a shorter name to point to it.
Note that for_dead_friend?, for_friend? etc. are built-in methods of RPG::UsableItem, which is the superclass of RPG::BaseItem. The full source code for this class is in the help file.
Simple enough, if set_confusion is called the action is set to a normal attack.
Method for action preparation. Calls set_confusion if the subject is confused and not being forced into an action.
Method to determine whether an action is valid. Returns true if EITHER the action is being forced and item has data in it OR the subject can use the item in question.
This is where we calculate action speed. Base speed is the subject's AGI stat + (a random integer between 0 and a 5 + (a quarter of the agi)). For example, given an initial agi of 20 the Soldier class has base speed 20 + (random number from 0 to 10). If there's an item or skill being used, we add the item's speed as set in the database. If the subject is attacking, we add the subject's attack speed (which will only be relevant if their character class has an attack speed set in features) and finally return the value of speed.
Here we're creating an array of possible targets. If we're not forcing a battle action and the subject is confused, return an array of the result from confusion_target. Otherwise, if the item's target is one opponent, return the result of targets_for_opponents. Otherwise, if the item's target is one ally, return the result of targets_for_friends. Otherwise, return an empty array.
Method for determining a confused battler's target. Checks the target's confusion level: if it's 1, just picks a random enemy. If it's 2, generate a random integer between 0 and 2 (so 0 or 1); if it's 0, pick a random enemy, otherwise pick a random ally. If the confusion level is anything else, pick a random ally. (note that confusion level corresponds to the "restriction" setting for the state that caused confusion.)
Determining enemy targets. If the item is for 1, 2, 3 or 4 random enemies, return a new array with (number of targets) elements. Each element will be set to a random opponent.
Otherwise, if the item is for 1 enemy:
set a temporary variable called num to 1 + the subject's number of additional attacks if they're attacking normally, 0 otherwise (so num will be equal to the number of attacks the subject would normally get in a turn)
If the @target_index is less than 0 (in other words, no targets have been chosen for the action) return an array of num random targets.
Otherwise, return an array of num targets smoothed out (the smooth_target method makes sure we're not trying to attack dead enemies and if so makes us attack the first living enemy instead, as you'll see soon).
Otherwise:
return the entire list of living enemies (if it's not for random and it's not for 1, it has to be for all enemies)
This one's almost exactly the same but has clauses for the user and a dead ally and lacks random (since that isn't a setting you can choose in the database for items or skills) If it's for the user it returns subject, if it's for 1 dead ally it returns the smoothed out target (making sure they're not alive and if so changes the target to the first dead ally) or all dead members otherwise (since it must be for all dead allies). If it's for 1 ally return the smoothed out target, otherwise return all living party members (since if it gets here it has to be set to all allies).
This is for evaluating value of actions for autobattle. We call evaluate_item if we have a valid action set, and then if @value is greater than 0 (which it may be after calling evaluate_item) we add to it a random number between 0 and 1, then return self.
For each possible candidate target for the current action (referred to via the block variable "target"):
value is set to the result of evaluate_item_with_target, which checks what the result of that action will end up being and returns the percentage of damage/healing.
If the item was for all targets, add value to @value
Otherwise, if value is greater than @value
set @value to value
set @target_index to the index of the target
Once I cover the last two methods, I think it might help to run an example.
If the item's target is enemies, return the living enemies. If it's for the user, return subject. If it's for a dead ally, return the dead allies. Otherwise, return the living allies.
Clear target's action result (every battler has one of these built-in when created) then call make_damage_value supplying subject and item (the user and the item/skill) which calculates how much damage the item or skill will do. If the item or skill's target is opponents, return the percentage of the target's HP this action would take away. Otherwise, calculate HP recovery and return what percentage of HP the target would recover.
Phew! Okay, let's step through this.
evaluate is called when a battler is putting together its list of candidate actions for autobattle, which we'll see when we get to Game_Battler. So let's say we're in autobattle with Isabelle, who has usable skills Attack, Guard, Fire and Sleep.
First Attack is evaluated. @value is set to 0, then we call evaluate_item.
item_target_candidates will end up being the list of living enemies; for sake of example, say we're fighting 2 Slimes.
First iteration will be dealing with slime #1:
value is set to the result of evaluate_item_with_target:
target (slime 1's) battle result is cleared.
we call make_damage_value, supplying Isabelle and Attack)
The actual damage algorithm doesn't matter, but say it determines that she's going to deal 20 damage. Slime 1 has 120HP, so we return (20.0 / 120), or 0.17 rounded.
Item is not for all, so we move to the else, which checks whether value > @value. 0.17 is greater than 0, so this is true. We set @value to 0.17 and @target_index to 1 (since it's the first enemy)
Now we're into the second iteration with Slime #2.
Let's say that make_damage_value now determines 23 damage will be done. The return value from evaluate_item_with_target is 0.19 rounded.
Again we're checking whether value > @value; 0.19 is greater than 0.17, so it's true. We set @value to 0.19 and @target_index to 2.
After this, since @value is greater than 0 it will have added to it a random number between 0 and 1.
We run through exactly the same process for Guard, Fire and Fire 2. The bottom line is that whichever action results in the greatest potential damage or recovery percentage is the one that will end up being chosen. (note that this does not reflect what the attack will -actually- do, just what a potential use of it resulted in). I didn't actually realise autobattle worked like this until picking through the code, so I learned something today too!
Game_ActionResult
This class handles the results of actions in battles, and as I said before is used internally within Game_Battler; on construction every battler receives its own new instance of Game_ActionResult.
On with the public instance variables!
I'm not going to dwell too much on these; they're pretty much all flags or values to show that certain things have happened in battle, and are for the most part self-explanatory.
Constructor takes battler as a parameter and sets @battler to the supplied value. This means that every battler has a Game_ActionResult with @battler set to the object of the battler it belongs to.
Pretty self-explanatory. clear clears all the flags so that results from a particular action aren't carried over to the next one.
This method clears the flags for an action being used, missing, being evaded, being a critical hit and being successful.
This method sets all damage and drain values to 0.
This method sets the damage values and takes two parameters: value, which is the damage done, and item, which is the skill or item that was used.
@critical is set to false if the damage value is 0 (you can't have a no-damage crit)
If the item's properties are set to damage HP, @hp_damage is set to value.
Likewise for MP damage (which is set to the minimum value between the MP damage and the battler's MP; we don't want to damage more MP than the target has), HP drain, and MP drain (which follows the same min damage principle)
@success is set to true if the item's damage settings have an effect on HP OR of the MP damage is not equal to 0.
Clears any added/removed states, buffs or debuffs.
Gets added states an an array of objects. The actual array only contains the state IDs.
Same principle for removed states.
Determines whether a status effect has happened. Returns true if the result is false to the logical check that ALL of the instance variables tracking states, buffs and debuffs are empty. (in other words, at least one of them isn't)
Determines whether the action hit. Returns true if the action was used AND the action didn't miss AND the action wasn't evaded.
Method for getting HP damage display text. If HP was drained, set fmt to Vocab::ActorDrain if the battler is an actor, Vocab::EnemyDrain otherwise. (which will either give us "%s was drained of %s %s!" or "Drained %s %s from %s!") then return sprintf(fmt, the name of the battler, Vocab::hp, and the value of the drain).
Example: If a Slime drains 21HP from Isabelle, it will say "Isabelle was drained of HP 21!" If you'd rather have a more natural-sounding drain message, change the string in Vocab to "%s had %s %s drained!" and then change the line here to sprintf(fmt, @battler.name, @hp_drain, Vocab::HP).
Exactly the same processes are followed for HP damage, HP recovery, and no damage.
The method for MP damage text is almost identical to the one for HP, with the exception of the final else: if there's no MP damage it just doesn't say anything, rather than saying there was no damage.
Another almost identical one for TP damage.
And that's it for this week! As always, comments are welcome, questions will be answered, and bagels will be eaten. Until next time.
Let's just get right to it, shall we?
Game_BaseItem
This is a class that handles general stuff for skills, items, weapons and armour.
def initialize @class = nil @item_id = 0 end
Nice easy constructor to begin with: just sets two instance variables, @class and @item_id, to nil and 0 respectively.
def is_skill?; @class == RPG::Skill; end def is_item?; @class == RPG::Item; end def is_weapon?; @class == RPG::Weapon; end def is_armor?; @class == RPG::Armor; end def is_nil?; @class == nil; end
Here we have a set of methods to figure out what @class is currently. is_skill? returns true if its class is RPG::Skill, is_item? returns true if it's an RPG::Item, and so on. Finally, we have is_nil? which checks whether @class is in fact empty.
def object return $data_skills[@item_id] if is_skill? return $data_items[@item_id] if is_item? return $data_weapons[@item_id] if is_weapon? return $data_armors[@item_id] if is_armor? return nil end
This is a getter for the current object. Returns the relevant piece of data from the database according to what class the object is. Note that given the default values, calling object before setting @class and @item_id is always going to return nil.
def object=(item) @class = item ? item.class : nil @item_id = item ? item.id : 0 end
Setter for object, takes "item" as a parameter. Okay, so we set @class to the result of the above ternary operator: if item is not nil @class is set to its class, otherwise it's set to nil. Then we do the same thing for @item_id: if item is not nil, it's set to the item's ID parameter, or 0 otherwise.
def set_equip(is_weapon, item_id) @class = is_weapon ? RPG::Weapon : RPG::Armor @item_id = item_id end
Similar setter taking "is_weapon" and "item_id" as parameters. @class is set to RPG::Weapon if is_weapon is true, or RPG::Armor otherwise. @item_id is set to the supplied item_id value.
These methods all work pretty closely in tandem with each other like cogs in a machine. Let's say we create a "potion" in item database slot 1 and at some point in the code we have something like "@item = Game_BaseItem.new". We then do "@item.object = $data_items" to set object to the potion. @class is now RPG::Item and @item_id is now 1. is_item? is now going to return true (all the others will return false) and object will return the data of the item in question.
Game_Action
This class, as the name suggests, handles battle actions, and is used within Game_Battler, which we're getting ever-closer to covering.
Public instance variables ahoy!
attr_reader :subject # action subject attr_reader :forcing # forcing flag for battle action attr_reader :item # skill/item attr_accessor :target_index # target index attr_reader :value # evaluation value for auto battle
Okay, so we've got an attr_reader for the subject of the action, an attr_reader as a flag to determine whether the action is being "forced", an attr_reader for the skill/item being used, an attr_accessor for the index of the action's target, and an attr_reader to determine action priority for autobattle. With me so far?
def initialize(subject, forcing = false) @subject = subject @forcing = forcing clear end
The constructor takes two arguments: subject, which is obviously going to be an instance of Game_Battler, and forcing, which defaults to false if no value is supplied. Sets the appropriate instance variables and then calls the clear method.
def clear @item = Game_BaseItem.new @target_index = -1 @value = 0 end
Simple enough. Sets @item to a new instance of Game_BaseItem, @target_index to -1 (generally if you have an index variable somewhere, the programmer's convention for resetting it to point to nothing is to make its value -1. There's a name for this but I can't remember it), and @value to 0.
def friends_unit subject.friends_unit end
Simply calls the method of the same name (which is a method of Game_Actor and Game_Enemy, as we'll see soon).
def opponents_unit subject.opponents_unit end
Same thing, but for enemies.
def set_enemy_action(action) if action set_skill(action.skill_id) else clear end end
This method sets battle actions for enemies, and takes as its parameter "action", which must be of type RPG::Enemy::Action. You can look this up in the help file, but we'll be going over it when we get to Game_Enemy (it's gonna get a bit complicated).
def set_attack set_skill(subject.attack_skill_id) self end
This method sets the action to "attack"; simple enough, it calls set_skill supplying the attack_skill_id of the subject (which by default is 1 for every battler, though this can be changed to give different characters different default attack skills if that's something you want in your game) and returns self, which is the action calling the method.
def set_guard set_skill(subject.guard_skill_id) self end
Same thing but for guarding, which is 2 in the skill list by default (and can again be changed to give different characters different default guarding commands)
def set_skill(skill_id) @item.object = $data_skills[skill_id] self end
This is what we call in both of the preceding methods and for any skill being used that isn't attack or guard: it takes skill_id as a parameter, and sets @item.object to the data of the skill_idth skill in the database (remember my potion example?)
def set_item(item_id) @item.object = $data_items[item_id] self end
Same thing for for when an item is used. We already know from looking at Game_BaseItem that the behind-the-scenes stuff for checking classes and what kind of object has been set will be done there for us.
def item @item.object end
This is more of a shortcut method than anything else; means that anywhere in Game_Action we needed to refer to @item.object, we can just put item instead.
def attack? item == $data_skills[subject.attack_skill_id] end
Such as here! This method returns true if item (which returns @item.object) is equal to the skill set as default attack in the database.
def decide_random_target if item.for_dead_friend? target = friends_unit.random_dead_target elsif item.for_friend? target = friends_unit.random_target else target = opponents_unit.random_target end if target @target_index = target.index else clear end end
And again we're thankful for the item method. Okay, so.
If the item's scope is set to One Ally (Dead)...
set target to a random dead party member
Otherwise, if the item's scope is One Ally...
set target to a random living party member
Otherwise...
set target to a random enemy
If a target has been set...
set @target_index to the index of the target
Otherwise...
call clear
I guess now is a great time for me to explain a bit more about methods which exist solely to save time and typing for poor, weary programmers. Take the line
"target = friends_unit.random_dead_target"
We could just as easily have written
"target = subject.friends_unit.random_dead_target"
And same with
"if item.for_dead_friend?"
Could have been
"if @item.object.for_dead_friend?"
But obviously this results in more code and the more you use that particular object the more code you're going to have to write. Sometimes it'll give you a negligible benefit but for something like @item.object which is probably going to be used quite a few times it makes sense to make a method with a shorter name to point to it.
Note that for_dead_friend?, for_friend? etc. are built-in methods of RPG::UsableItem, which is the superclass of RPG::BaseItem. The full source code for this class is in the help file.
def set_confusion set_attack end
Simple enough, if set_confusion is called the action is set to a normal attack.
def prepare set_confusion if subject.confusion? && !forcing end
Method for action preparation. Calls set_confusion if the subject is confused and not being forced into an action.
def valid? (forcing && item) || subject.usable?(item) end
Method to determine whether an action is valid. Returns true if EITHER the action is being forced and item has data in it OR the subject can use the item in question.
def speed speed = subject.agi + rand(5 + subject.agi / 4) speed += item.speed if item speed += subject.atk_speed if attack? speed end
This is where we calculate action speed. Base speed is the subject's AGI stat + (a random integer between 0 and a 5 + (a quarter of the agi)). For example, given an initial agi of 20 the Soldier class has base speed 20 + (random number from 0 to 10). If there's an item or skill being used, we add the item's speed as set in the database. If the subject is attacking, we add the subject's attack speed (which will only be relevant if their character class has an attack speed set in features) and finally return the value of speed.
def make_targets if !forcing && subject.confusion? [confusion_target] elsif item.for_opponent? targets_for_opponents elsif item.for_friend? targets_for_friends else [] end end
Here we're creating an array of possible targets. If we're not forcing a battle action and the subject is confused, return an array of the result from confusion_target. Otherwise, if the item's target is one opponent, return the result of targets_for_opponents. Otherwise, if the item's target is one ally, return the result of targets_for_friends. Otherwise, return an empty array.
def confusion_target case subject.confusion_level when 1 opponents_unit.random_target when 2 if rand(2) == 0 opponents_unit.random_target else friends_unit.random_target end else friends_unit.random_target end end
Method for determining a confused battler's target. Checks the target's confusion level: if it's 1, just picks a random enemy. If it's 2, generate a random integer between 0 and 2 (so 0 or 1); if it's 0, pick a random enemy, otherwise pick a random ally. If the confusion level is anything else, pick a random ally. (note that confusion level corresponds to the "restriction" setting for the state that caused confusion.)
def targets_for_opponents if item.for_random? Array.new(item.number_of_targets) { opponents_unit.random_target } elsif item.for_one? num = 1 + (attack? ? subject.atk_times_add.to_i : 0) if @target_index < 0 [opponents_unit.random_target] * num else [opponents_unit.smooth_target(@target_index)] * num end else opponents_unit.alive_members end end
Determining enemy targets. If the item is for 1, 2, 3 or 4 random enemies, return a new array with (number of targets) elements. Each element will be set to a random opponent.
Otherwise, if the item is for 1 enemy:
set a temporary variable called num to 1 + the subject's number of additional attacks if they're attacking normally, 0 otherwise (so num will be equal to the number of attacks the subject would normally get in a turn)
If the @target_index is less than 0 (in other words, no targets have been chosen for the action) return an array of num random targets.
Otherwise, return an array of num targets smoothed out (the smooth_target method makes sure we're not trying to attack dead enemies and if so makes us attack the first living enemy instead, as you'll see soon).
Otherwise:
return the entire list of living enemies (if it's not for random and it's not for 1, it has to be for all enemies)
def targets_for_friends if item.for_user? [subject] elsif item.for_dead_friend? if item.for_one? [friends_unit.smooth_dead_target(@target_index)] else friends_unit.dead_members end elsif item.for_friend? if item.for_one? [friends_unit.smooth_target(@target_index)] else friends_unit.alive_members end end end
This one's almost exactly the same but has clauses for the user and a dead ally and lacks random (since that isn't a setting you can choose in the database for items or skills) If it's for the user it returns subject, if it's for 1 dead ally it returns the smoothed out target (making sure they're not alive and if so changes the target to the first dead ally) or all dead members otherwise (since it must be for all dead allies). If it's for 1 ally return the smoothed out target, otherwise return all living party members (since if it gets here it has to be set to all allies).
def evaluate @value = 0 evaluate_item if valid? @value += rand if @value > 0 self end
This is for evaluating value of actions for autobattle. We call evaluate_item if we have a valid action set, and then if @value is greater than 0 (which it may be after calling evaluate_item) we add to it a random number between 0 and 1, then return self.
def evaluate_item item_target_candidates.each do |target| value = evaluate_item_with_target(target) if item.for_all? @value += value elsif value > @value @value = value @target_index = target.index end end end
For each possible candidate target for the current action (referred to via the block variable "target"):
value is set to the result of evaluate_item_with_target, which checks what the result of that action will end up being and returns the percentage of damage/healing.
If the item was for all targets, add value to @value
Otherwise, if value is greater than @value
set @value to value
set @target_index to the index of the target
Once I cover the last two methods, I think it might help to run an example.
def item_target_candidates if item.for_opponent? opponents_unit.alive_members elsif item.for_user? [subject] elsif item.for_dead_friend? friends_unit.dead_members else friends_unit.alive_members end end
If the item's target is enemies, return the living enemies. If it's for the user, return subject. If it's for a dead ally, return the dead allies. Otherwise, return the living allies.
def evaluate_item_with_target(target) target.result.clear target.make_damage_value(subject, item) if item.for_opponent? return target.result.hp_damage.to_f / [target.hp, 1].max else recovery = [-target.result.hp_damage, target.mhp - target.hp].min return recovery.to_f / target.mhp end end
Clear target's action result (every battler has one of these built-in when created) then call make_damage_value supplying subject and item (the user and the item/skill) which calculates how much damage the item or skill will do. If the item or skill's target is opponents, return the percentage of the target's HP this action would take away. Otherwise, calculate HP recovery and return what percentage of HP the target would recover.
Phew! Okay, let's step through this.
evaluate is called when a battler is putting together its list of candidate actions for autobattle, which we'll see when we get to Game_Battler. So let's say we're in autobattle with Isabelle, who has usable skills Attack, Guard, Fire and Sleep.
First Attack is evaluated. @value is set to 0, then we call evaluate_item.
item_target_candidates will end up being the list of living enemies; for sake of example, say we're fighting 2 Slimes.
First iteration will be dealing with slime #1:
value is set to the result of evaluate_item_with_target:
target (slime 1's) battle result is cleared.
we call make_damage_value, supplying Isabelle and Attack)
The actual damage algorithm doesn't matter, but say it determines that she's going to deal 20 damage. Slime 1 has 120HP, so we return (20.0 / 120), or 0.17 rounded.
Item is not for all, so we move to the else, which checks whether value > @value. 0.17 is greater than 0, so this is true. We set @value to 0.17 and @target_index to 1 (since it's the first enemy)
Now we're into the second iteration with Slime #2.
Let's say that make_damage_value now determines 23 damage will be done. The return value from evaluate_item_with_target is 0.19 rounded.
Again we're checking whether value > @value; 0.19 is greater than 0.17, so it's true. We set @value to 0.19 and @target_index to 2.
After this, since @value is greater than 0 it will have added to it a random number between 0 and 1.
We run through exactly the same process for Guard, Fire and Fire 2. The bottom line is that whichever action results in the greatest potential damage or recovery percentage is the one that will end up being chosen. (note that this does not reflect what the attack will -actually- do, just what a potential use of it resulted in). I didn't actually realise autobattle worked like this until picking through the code, so I learned something today too!
Game_ActionResult
This class handles the results of actions in battles, and as I said before is used internally within Game_Battler; on construction every battler receives its own new instance of Game_ActionResult.
On with the public instance variables!
attr_accessor :used # used flag attr_accessor :missed # missed flag attr_accessor :evaded # evaded flag attr_accessor :critical # critical flag attr_accessor :success # success flag attr_accessor :hp_damage # HP damage attr_accessor :mp_damage # MP damage attr_accessor :tp_damage # TP damage attr_accessor :hp_drain # HP drain attr_accessor :mp_drain # MP drain attr_accessor :added_states # added states attr_accessor :removed_states # removed states attr_accessor :added_buffs # added buffs attr_accessor :added_debuffs # added debuffs attr_accessor :removed_buffs # removed buffs/debuffs
I'm not going to dwell too much on these; they're pretty much all flags or values to show that certain things have happened in battle, and are for the most part self-explanatory.
def initialize(battler) @battler = battler clear end
Constructor takes battler as a parameter and sets @battler to the supplied value. This means that every battler has a Game_ActionResult with @battler set to the object of the battler it belongs to.
def clear clear_hit_flags clear_damage_values clear_status_effects end
Pretty self-explanatory. clear clears all the flags so that results from a particular action aren't carried over to the next one.
def clear_hit_flags @used = false @missed = false @evaded = false @critical = false @success = false end
This method clears the flags for an action being used, missing, being evaded, being a critical hit and being successful.
def clear_damage_values @hp_damage = 0 @mp_damage = 0 @tp_damage = 0 @hp_drain = 0 @mp_drain = 0 end
This method sets all damage and drain values to 0.
def make_damage(value, item) @critical = false if value == 0 @hp_damage = value if item.damage.to_hp? @mp_damage = value if item.damage.to_mp? @mp_damage = [@battler.mp, @mp_damage].min @hp_drain = @hp_damage if item.damage.drain? @mp_drain = @mp_damage if item.damage.drain? @hp_drain = [@battler.hp, @hp_drain].min @success = true if item.damage.to_hp? || @mp_damage != 0 end
This method sets the damage values and takes two parameters: value, which is the damage done, and item, which is the skill or item that was used.
@critical is set to false if the damage value is 0 (you can't have a no-damage crit)
If the item's properties are set to damage HP, @hp_damage is set to value.
Likewise for MP damage (which is set to the minimum value between the MP damage and the battler's MP; we don't want to damage more MP than the target has), HP drain, and MP drain (which follows the same min damage principle)
@success is set to true if the item's damage settings have an effect on HP OR of the MP damage is not equal to 0.
def clear_status_effects @added_states = [] @removed_states = [] @added_buffs = [] @added_debuffs = [] @removed_buffs = [] end
Clears any added/removed states, buffs or debuffs.
def added_state_objects @added_states.collect {|id| $data_states[id] } end
Gets added states an an array of objects. The actual array only contains the state IDs.
def removed_state_objects @removed_states.collect {|id| $data_states[id] } end
Same principle for removed states.
def status_affected? !(@added_states.empty? && @removed_states.empty? && @added_buffs.empty? && @added_debuffs.empty? && @removed_buffs.empty?) end
Determines whether a status effect has happened. Returns true if the result is false to the logical check that ALL of the instance variables tracking states, buffs and debuffs are empty. (in other words, at least one of them isn't)
def hit? @used && !@missed && !@evaded end
Determines whether the action hit. Returns true if the action was used AND the action didn't miss AND the action wasn't evaded.
def hp_damage_text if @hp_drain > 0 fmt = @battler.actor? ? Vocab::ActorDrain : Vocab::EnemyDrain sprintf(fmt, @battler.name, Vocab::hp, @hp_drain) elsif @hp_damage > 0 fmt = @battler.actor? ? Vocab::ActorDamage : Vocab::EnemyDamage sprintf(fmt, @battler.name, @hp_damage) elsif @hp_damage < 0 fmt = @battler.actor? ? Vocab::ActorRecovery : Vocab::EnemyRecovery sprintf(fmt, @battler.name, Vocab::hp, -hp_damage) else fmt = @battler.actor? ? Vocab::ActorNoDamage : Vocab::EnemyNoDamage sprintf(fmt, @battler.name) end end
Method for getting HP damage display text. If HP was drained, set fmt to Vocab::ActorDrain if the battler is an actor, Vocab::EnemyDrain otherwise. (which will either give us "%s was drained of %s %s!" or "Drained %s %s from %s!") then return sprintf(fmt, the name of the battler, Vocab::hp, and the value of the drain).
Example: If a Slime drains 21HP from Isabelle, it will say "Isabelle was drained of HP 21!" If you'd rather have a more natural-sounding drain message, change the string in Vocab to "%s had %s %s drained!" and then change the line here to sprintf(fmt, @battler.name, @hp_drain, Vocab::HP).
Exactly the same processes are followed for HP damage, HP recovery, and no damage.
def mp_damage_text if @mp_drain > 0 fmt = @battler.actor? ? Vocab::ActorDrain : Vocab::EnemyDrain sprintf(fmt, @battler.name, Vocab::mp, @mp_drain) elsif @mp_damage > 0 fmt = @battler.actor? ? Vocab::ActorLoss : Vocab::EnemyLoss sprintf(fmt, @battler.name, Vocab::mp, @mp_damage) elsif @mp_damage < 0 fmt = @battler.actor? ? Vocab::ActorRecovery : Vocab::EnemyRecovery sprintf(fmt, @battler.name, Vocab::mp, -@mp_damage) else "" end end
The method for MP damage text is almost identical to the one for HP, with the exception of the final else: if there's no MP damage it just doesn't say anything, rather than saying there was no damage.
def tp_damage_text if @tp_damage > 0 fmt = @battler.actor? ? Vocab::ActorLoss : Vocab::EnemyLoss sprintf(fmt, @battler.name, Vocab::tp, @tp_damage) elsif @tp_damage < 0 fmt = @battler.actor? ? Vocab::ActorGain : Vocab::EnemyGain sprintf(fmt, @battler.name, Vocab::tp, -@tp_damage) else "" end end
Another almost identical one for TP damage.
And that's it for this week! As always, comments are welcome, questions will be answered, and bagels will be eaten. Until next time.
Pages:
1