JUMP INTO JAVASCRIPT PART 9

In which a year later Trihan actually updates with the stuff we should have looked at back then.

  • Trihan
  • 06/06/2019 02:02 AM
  • 5397 views
Hello sports fans! Welcome to the "a year isn't that long, right?" update to

Jump into Javascript

Today, we FINALLY continue with rpg_objects.js.

Game_Action
This is the class that handles battle actions, and is probably one a lot of people will appreciate a breakdown of because this is the meat and potatoes of every item and skill you'll use in your game. It has the standard constructor but since it's been a while, let's go over it for posterity. Before we begin, for the sake of avoiding repetition, any time you see "this" consider it to say "the current instance of the class the function is being written for".

function Game_Action() {
    this.initialize.apply(this, arguments);
}


So when we create a new Game_Action, it calls the apply function for this.initialize, passing in "this" and "arguments" as the parameters (basically, the current instance of the class and whatever arguments were provided in the call to construct it).

This class also has a number of constants:

Game_Action.EFFECT_RECOVER_HP       = 11;
Game_Action.EFFECT_RECOVER_MP       = 12;
Game_Action.EFFECT_GAIN_TP          = 13;
Game_Action.EFFECT_ADD_STATE        = 21;
Game_Action.EFFECT_REMOVE_STATE     = 22;
Game_Action.EFFECT_ADD_BUFF         = 31;
Game_Action.EFFECT_ADD_DEBUFF       = 32;
Game_Action.EFFECT_REMOVE_BUFF      = 33;
Game_Action.EFFECT_REMOVE_DEBUFF    = 34;
Game_Action.EFFECT_SPECIAL          = 41;
Game_Action.EFFECT_GROW             = 42;
Game_Action.EFFECT_LEARN_SKILL      = 43;
Game_Action.EFFECT_COMMON_EVENT     = 44;
Game_Action.SPECIAL_EFFECT_ESCAPE   = 0;
Game_Action.HITTYPE_CERTAIN         = 0;
Game_Action.HITTYPE_PHYSICAL        = 1;
Game_Action.HITTYPE_MAGICAL         = 2;


These are code IDs for the different effects you can set for an action in the database, corresponding to the different radio buttons that are available for such effects. They appear to be based on a convention of (tab number)(button number); that is to say RECOVER_HP is the 1st radio button on the first tab, so its ID is 11. The SPECIAL_EFFECT_ESCAPE constant is specific to the only option under "Special Effect" in the "Other" tab, and the last three HITTYPE constants correspond to the "Hit Type" drop down in the Skills/Items tabs.

We could have just used the numbers in their appropriate place in the code, but just having an ID can get really confusing for readability. "return this.item().hitType === Game_Action.HITTYPE_CERTAIN;" is a lot easier to understand the function of than "return this.item().hitType === 0;".

Game_Action.prototype.initialize = function(subject, forcing) {
    this._subjectActorId = 0;
    this._subjectEnemyIndex = -1;
    this._forcing = forcing || false;
    this.setSubject(subject);
    this.clear();
};


The initialize function takes two parameters: "subject" and "forcing". subject will be an instance of Game_Actor or Game_Enemy, while forcing is a boolean flag that determines whether the action is being forced or not (an important distinction when the subject is confused, because an action can still be forced and won't be subject to the same restrictions as confusion would usually cause).

First, we set a property called _subjectActorId to 0. Then we set _subjectEnemyIndex to -1 (it can't be 0 because that's what enemy indexes start from; if it were 0 by default then an action would be considering itself to be targeting the first enemy in battle even if it doesn't have a target yet). Then we set _forcing to the value of forcing or false (meaning it will only be true if true was passed in to the constructor). Then we call the function setSubject, passing in the subject parameter. Finally, we call the clear function.

Game_Action.prototype.clear = function() {
    this._item = new Game_Item();
    this._targetIndex = -1;
};


The clear function takes no parameters. First, we set the _item property to a new instance of Game_Item (which will initialise it with the default parameters of an item that has no specific data yet) and _targetIndex to -1, clearing any index that might have existed for it previously.

Game_Action.prototype.setSubject = function(subject) {
    if (subject.isActor()) {
        this._subjectActorId = subject.actorId();
        this._subjectEnemyIndex = -1;
    } else {
        this._subjectEnemyIndex = subject.index();
        this._subjectActorId = 0;
    }
};


setSubject, as called in the initialize function, takes one parameter, subject.

If the subject is an actor (in which case calling isActor() will return true), we set _subjectActorId to the result of calling the subject's actorId function, and _subjectEnemyIndex to -1 (it isn't an enemy, so it won't have one).

Otherwise (meaning the subject is an enemy) we set _subjectEnemyIndex to the result of calling the subject's index function, and _subjectActorId to 0, since it isn't an actor (note that 0 is fine for this, because actor IDs start at 1; the engine is intentionally set up so that database-driven arrays are 1-indexed so that the internal index always matches the database one)

Game_Action.prototype.subject = function() {
    if (this._subjectActorId > 0) {
        return $gameActors.actor(this._subjectActorId);
    } else {
        return $gameTroop.members()[this._subjectEnemyIndex];
    }
};


The subject function will give us the actual actor object associated with the action. If _subjectActorId is greater than 0 (meaning the subject is an actor) we return the result of calling the actor() function of $gameActors, passing in the action instance's _subjectActorId. For example, if _subjectActorId is 2 (the 2nd actor in the database), we would call $gameActors.actor(2) which would return the Game_Actor object corresponding to actor 2. Otherwise, in which case we know the subject is an enemy, we call the members function of $gameTroop and return the element with index _subjectEnemyIndex. For example, if the subject is enemy #5 in the troop lineup, and 5 is a Slime, we will return that element of the members() array, which will be the Game_Enemy object corresponding to the Slime in slot 5.

Game_Action.prototype.friendsUnit = function() {
    return this.subject().friendsUnit();
};


The action's friendsUnit function is essentially just a wrapper for the subjects, and simply calls the friendsUnit function of the result from calling subject(). This will be $gameParty if the subject is an actor, and $gameTroop if the subject is an enemy, which we'll see once we start looking at Game_Actor and Game_Enemy.

Game_Action.prototype.opponentsUnit = function() {
    return this.subject().opponentsUnit();
};


The same thing, but for opponentsUnit. This will return $gameParty if the subject is an enemy, and $gameTroop if the subject is an actor.

Game_Action.prototype.setEnemyAction = function(action) {
    if (action) {
        this.setSkill(action.skillId);
    } else {
        this.clear();
    }
};


This function is only used by enemies, as we'll see in Game_Enemy, and takes one parameter, "action". This will be an instance of Game_Action, but not necessarily the one calling the function (which is why it doesn't use 'this').

If the action passed in is not null, we call the setSkill function, passing in the action's skillId property. Otherwise, we call clear. This means you can call setEnemyAction with no argument to clear it.

Game_Action.prototype.setAttack = function() {
    this.setSkill(this.subject().attackSkillId());
};


This function sets the action to "physical attack" by calling setSkill and passing in the result of calling the subject's attackSkillId function. (which as we'll see later defaults to 1, or in other words the first skill in the database is hardcoded to be the one that is used as the "Attack" command. As with everything else in the engine, this can be changed if you wish)

Game_Action.prototype.setGuard = function() {
    this.setSkill(this.subject().guardSkillId());
};


This is the same thing but for the guardSkillId function, which defaults to 2.

Game_Action.prototype.setSkill = function(skillId) {
    this._item.setObject($dataSkills[skillId]);
};


The setSkill function, called in the previous two functions, takes one parameter, "skillId", which is obviously the ID of the skill you want to associate with the action. In it, we call the setObject function of the _item object, passing in the element of $dataSkills with an index corresponding with SkillId. Taking the call in setAttack as an example, this ends up being "this.setSkill(1)", which then calls "this._item.setObject($dataSkills[1])". We looked at Game_Item back in part 8, but you may not remember.

Game_Action.prototype.setItem = function(itemId) {
    this._item.setObject($dataItems[itemId]);
};


This function is used when we want the action to be associated with an item instead of a skill, and takes one parameter, "itemId". We have an almost identical call to setObject, but this time we pass in the corresponding element of $dataItems instead.

Game_Action.prototype.setItemObject = function(object) {
    this._item.setObject(object);
};


This is a similar functon to the previous two, but rather than taking an ID as the parameter, it takes an actual object and passes that object to the setObject call. (this is used in Scene_ItemBase, which we covered back in part 4)

Game_Action.prototype.setTarget = function(targetIndex) {
    this._targetIndex = targetIndex;
};


The setTarget function takes one parameter, "targetIndex" (which is, as the name suggests, the index of the desired target) and simply sets the instance's _targetIndex property to the value passed to it.

Game_Action.prototype.item = function() {
    return this._item.object();
};


This function returns the item's associated object instance by calling the object function of the item. This will give us the data or whichever skill or item the action relates to.

Game_Action.prototype.isSkill = function() {
    return this._item.isSkill();
};

Game_Action.prototype.isItem = function() {
    return this._item.isItem();
};


These are two very similar functions intended to determine whether the action is a skill or an item. isSkill will return true if calling isSkill on its _item returns true, and isItem will do the same for the _item's isItem function.

Game_Action.prototype.numRepeats = function() {
    var repeats = this.item().repeats;
    if (this.isAttack()) {
        repeats += this.subject().attackTimesAdd();
    }
    return Math.floor(repeats);
};


This function determines how many times the action will repeat when used. First we set a temporary variable called "repeats" to the repeats property of the action's item (corresponding to the value entered in the "Repeat" box of the database entry). Then if isAttack() returns true (meaning we're looking at a physical attack) we add the result of calling the subject's attackTimesAdd function (which gives us the total of any Attack Times + traits that affect the battler using the action from inherent traits or equipment). Then we return the floor of repeats, meaning it'll round down if the result is a decimal. The Math.floor doesn't make sense, honestly, because the value will always be an integer.

Game_Action.prototype.checkItemScope = function(list) {
    return list.contains(this.item().scope);
};


This function checks whether the action's item has a given scope, taking one parameter, "list", which is an array of scope IDs. We return true if the list contains the item's scope, and false otherwise. The values used correspond to the "Scope" dropdown in the database.

Game_Action.prototype.isForOpponent = function() {
    return this.checkItemScope([1, 2, 3, 4, 5, 6]);
};

Game_Action.prototype.isForFriend = function() {
    return this.checkItemScope([7, 8, 9, 10, 11]);
};

Game_Action.prototype.isForDeadFriend = function() {
    return this.checkItemScope([9, 10]);
};

Game_Action.prototype.isForUser = function() {
    return this.checkItemScope([11]);
};

Game_Action.prototype.isForOne = function() {
    return this.checkItemScope([1, 3, 7, 9, 11]);
};

Game_Action.prototype.isForRandom = function() {
    return this.checkItemScope([3, 4, 5, 6]);
};

Game_Action.prototype.isForAll = function() {
    return this.checkItemScope([2, 8, 10]);
};

Game_Action.prototype.needsSelection = function() {
    return this.checkItemScope([1, 7, 9]);
};


This set of functions determines whether the action is intended for a certain type of target. isForOpponent determines whether it's targeting an enemy (list consists of "1 Enemy", "All Enemies", "1 Random Enemy", "2 Random Enemies", "3 Random Enemies" and "4 Random Enemies"). isForFriend determines whether it targets a friendly unit (list consists of "1 Ally", "All Allies", "1 Ally (Dead)", "All Allies (Dead)" and "The User"). isForDeadFriend determines whether it targets a dead friendly unit (using "1 Ally (Dead)" and "All Allies (Dead)". isForUser just passes "The User" but still has to do so as an array or the checkItemScope function will error out. isForOne determines whether it targets a single target (list consists of "1 Enemy", "1 Random Enemy", "1 Ally", "1 Ally (Dead)" and "The User"). isForRandom determines whether it's for a random target ("1 Random Enemy", "2 Random Enemies", "3 Random Enemies", "4 Random Enemies"). isForAll determines whether it targets all of a group ("All Enemies", "All Alies" or "All Allies (Dead)"). needsSelection determines whether the player needs to choose a target ("1 Enemy", "1 Ally", "1 Ally (Dead)").

None of these functions are strictly needed, per se, but it's a lot easier than calling checkItemScope and passing in all those IDs every time you want to check whether the scope is for one of those groups.

Game_Action.prototype.numTargets = function() {
    return this.isForRandom() ? this.item().scope - 2 : 0;
};


This function determines how many targets the action has and is only used for random scopes. It's actually quite clever: if isForRandom() returns true, we return the item's scope ID minus 2. Otherwise, we return 0. Since "1 Random Enemy" is ID 3 in the scope list, this will return "1". Likewise for "2 Random Enemies" being ID 4 and so returning 2, and so on.

Game_Action.prototype.checkDamageType = function(list) {
    return list.contains(this.item().damage.type);
};


This function performs a similar check to checkItemScope but for damage types, taking a similar array list of IDs as its parameter. We return true if the list contain's the item's damage type, and false otherwise. The IDs used here correspond to the items in the "Type" dropdown under Damage in the database tab.

Game_Action.prototype.isHpEffect = function() {
    return this.checkDamageType([1, 3, 5]);
};

Game_Action.prototype.isMpEffect = function() {
    return this.checkDamageType([2, 4, 6]);
};

Game_Action.prototype.isDamage = function() {
    return this.checkDamageType([1, 2]);
};

Game_Action.prototype.isRecover = function() {
    return this.checkDamageType([3, 4]);
};

Game_Action.prototype.isDrain = function() {
    return this.checkDamageType([5, 6]);
};

Game_Action.prototype.isHpRecover = function() {
    return this.checkDamageType([3]);
};

Game_Action.prototype.isMpRecover = function() {
    return this.checkDamageType([4]);
};


This set of functions checks to see whether the damage type is in a particular category. isHpEffect determines whether the damage type affects the target's HP ("HP Damage", "HP Recover" and "HP Drain"). isMpEffect determines whether the damage type affects the target's MP ("MP Damage", MP Recover" and "MP Drain"). isDamage determines whether the damage type causes points to decrease ("HP Damage", "MP Damage"). isRecover determines whether the damage type causes points to increase ("HP Recover", "MP Recover"). isDrain determines whether the damage type causes points to be removed from the target and restored to the subject ("HP Drain", "MP Drain"). isHpRecover and isMpRecover just check whether the damage type is "HP Damage" or "MP Damage" respectively.

Game_Action.prototype.isCertainHit = function() {
    return this.item().hitType === Game_Action.HITTYPE_CERTAIN;
};

Game_Action.prototype.isPhysical = function() {
    return this.item().hitType === Game_Action.HITTYPE_PHYSICAL;
};

Game_Action.prototype.isMagical = function() {
    return this.item().hitType === Game_Action.HITTYPE_MAGICAL;
};


This next set of functions determine whether the hit type of the action is a particular kind. isCertainHit returns true if the item's hitType is equal to the HITTYPE_CERTAIN constant, which is 0. isPhysical returns true if it's HITTYPE_PHYSICAL (1) and isMagical returns true if it's HITTYPE_MAGICAL (2).

Game_Action.prototype.isAttack = function() {
    return this.item() === $dataSkills[this.subject().attackSkillId()];
};

Game_Action.prototype.isGuard = function() {
    return this.item() === $dataSkills[this.subject().guardSkillId()];
};

Game_Action.prototype.isMagicSkill = function() {
    if (this.isSkill()) {
        return $dataSystem.magicSkills.contains(this.item().stypeId);
    } else {
        return false;
    }
};


This set checks whether the skill is an attack command, guard command or magic. isAttack returns true if item() returns the object that matches the element of $dataSkills corresponding to the subject's attackSkillId. isGuard does the same with guardSkillId. isMagicSkill is a little more complex: first we check whether the action is a skill at all. If not it's an item and can't be magic, so we return false. If so, we return true if the magicSkills property of $dataSystem contains the item's skill type ID (stypeId property). This draws from the "[SV] Magic Skills" section of the System database tab. Basically if the skill's type is listed in that list, it's considered a magic skill. (if it's not, it won't be, even if the skill's type is called "blatantly magical things" and the hit type is "magical attack")

Game_Action.prototype.decideRandomTarget = function() {
    var target;
    if (this.isForDeadFriend()) {
        target = this.friendsUnit().randomDeadTarget();
    } else if (this.isForFriend()) {
        target = this.friendsUnit().randomTarget();
    } else {
        target = this.opponentsUnit().randomTarget();
    }
    if (target) {
        this._targetIndex = target.index();
    } else {
        this.clear();
    }
};


This function decides on a random target. First we dimension a null variable called target. We check whether the action is for a dead friend; if so, target is set to the result of calling randomDeadTarget on the subject's friendsUnit(). Otherwise, if it's for a living friend, we call randomTarget instead. Otherwise (meaning we're targeting an enemy) we call randomTarget on the subject's opponentsUnit().

Then, if target is not null, we set _targetIndex to the result of calling the target's index function. Otherwise, if target is null, we call the clear function.

Game_Action.prototype.setConfusion = function() {
    this.setAttack();
};


This function sets up the state for a confused battler, and simply calls setAttack (because confused battlers only use physical attacks).

Game_Action.prototype.prepare = function() {
    if (this.subject().isConfused() && !this._forcing) {
        this.setConfusion();
    }
};


The prepare function is part of setting up an action during turn processing (which we'll see when we start looking at BattleManager). If the result of calling isConfused on the subject is true AND _forcing is false, we call setConfusion. Basically we set up confusion if the subject is confused and not being forced to act.

Game_Action.prototype.isValid = function() {
    return (this._forcing && this.item()) || this.subject().canUse(this.item());
};


This function determines whether the action is valid. There are two criteria under which the function will return true: if the action is being forced and an item is set OR if the result of calling the subject's canUse function is true when the action's item is passed in.

Game_Action.prototype.speed = function() {
    var agi = this.subject().agi;
    var speed = agi + Math.randomInt(Math.floor(5 + agi / 4));
    if (this.item()) {
        speed += this.item().speed;
    }
    if (this.isAttack()) {
        speed += this.subject().attackSpeed();
    }
    return speed;
};


This function determines the speed of the action, used to determine the order in which battlers will act during a turn. First we set a temp variable called agi to the agility stat of the subject. Then we set a temp variable called speed to agi plus a random integer from 0 up to 5 more than a quarter of agi, rounded down. For example, say the subject has 27 agility. speed will end up being "27 + Math.randomInt(Math.floor(5 + 27 / 4))". The random integer will be 0 to 10 (upper bound is 11, but it's non-inclusive) meaning speed will have a final value of 27 to 37.

If the action has an associated item, we then add the item's speed property to speed (which can be -2000 to 2000). Then we check whether this action is a physical attack, and if so we add the result of calling the subject's attackSpeed function.

Finally, we return speed.

A few obvious conclusions to draw from this: first, having higher agi makes it more likely that a battler will act first. The item or skill having a higher speed adds to that likelihood, and any attack speed traits in the battler's class or equipment will also help the chances. Of course, this also means you can set negative speed values to make more powerful effects that are more likely to be executed later in the turn.

Game_Action.prototype.makeTargets = function() {
    var targets = [];
    if (!this._forcing && this.subject().isConfused()) {
        targets = [this.confusionTarget()];
    } else if (this.isForOpponent()) {
        targets = this.targetsForOpponents();
    } else if (this.isForFriend()) {
        targets = this.targetsForFriends();
    }
    return this.repeatTargets(targets);
};


This function determines the list of targets for the action. First, we dimension an empty temporary array called targets. If _forcing is false (the action is not being forced) AND the subject's isConfused function returns true (the subject is confused) we set targets to an array consisting of the result from calling the confusionTarget function, which we'll see in a second. Otherwise, if isForOpponent returns true (the action is targeting enemies), we set targets to the result of calling targetsForOpponents. Otherwise, if the action is for a friendly unit, we set targets to the result of calling targetsForFriends. Finally, we return the result of calling repeatTargets passing in the targets array.

Game_Action.prototype.repeatTargets = function(targets) {
    var repeatedTargets = [];
    var repeats = this.numRepeats();
    for (var i = 0; i < targets.length; i++) {
        var target = targets[i];
        if (target) {
            for (var j = 0; j < repeats; j++) {
                repeatedTargets.push(target);
            }
        }
    }
    return repeatedTargets;
};


This is the function we called at the end of the last one. It takes one parameter, targets, which is an array of target objects. First we dimension an empty temp array called repeatedTargets and set a temp variable called repeats to the result of calling the numRepeats function. (which gets how many times the action will be performed)

Then we have a for loop using iteration variable i, starting at 0 and looping as long as the iteration variable is less than the number of elements in the targets array. Inside the loop, we set a temp variable called target to the element of targets with an index of "i". If the target is not null, we have another for loop using iteration variable j, starting at 0 and looping as long as the iteration variable is less than repeats. Inside THIS loop, we push target to repeatedTargets.

Finally, we return repeatedTargets.

This will result in an array that has each target repeated as many times as there are repetitions of the skill.

Game_Action.prototype.confusionTarget = function() {
    switch (this.subject().confusionLevel()) {
    case 1:
        return this.opponentsUnit().randomTarget();
    case 2:
        if (Math.randomInt(2) === 0) {
            return this.opponentsUnit().randomTarget();
        }
        return this.friendsUnit().randomTarget();
    default:
        return this.friendsUnit().randomTarget();
    }
};


This function gets a confusion target. We set up a switch statement using the result of calling the subject's confusionLevel function, which is going to return a value from 1-3 depending on the restriction level of the confusion state. If it's 1 ("Attack an enemy") we return the result of calling randomTarget on the opponents unit. If it's 2 ("Attack anyone") we check whether a random integer from 0-2 exclusive (giving us either 0 or 1) is equal to 0, and if so we again call randomTarget on opponentsUnit (giving us a random enemy). If the random int was 1, we instead return randomTarget called on friendsUnit (giving us a random ally). The default case is a random target from friendsUnit. (which also covers the case where confusionLevel returns 2)

Game_Action.prototype.targetsForOpponents = function() {
    var targets = [];
    var unit = this.opponentsUnit();
    if (this.isForRandom()) {
        for (var i = 0; i < this.numTargets(); i++) {
            targets.push(unit.randomTarget());
        }
    } else if (this.isForOne()) {
        if (this._targetIndex < 0) {
            targets.push(unit.randomTarget());
        } else {
            targets.push(unit.smoothTarget(this._targetIndex));
        }
    } else {
        targets = unit.aliveMembers();
    }
    return targets;
};


This function gets the list of targets when we're targeting enemies of the subject. Firts, we dimension an empty array called targets. Then set a temporary variable called unit to the return value of opponentsUnit(). (which will either be $gameParty if the subject is an enemy and $gameTroop if the subject is an actor)

If the action is for a random target, we have a for loop using iteration variable i, starting at 0 and looping as long as i is less than the return value of numTargets (the number of targets for the action). Inside this loop, we push the result of calling randomTarget from unit to the targets array.

Otherwise, if the action is for a single target, we check whether _targetIndex is less than 0 (meaning the target needs to be random) we push the result of randomTarget again. Otherwise (meaning the player selected their own target), we push to targets the result of calling smoothTarget passing in _targetIndex. We'll see this later, but smoothTarget basically just makes sure that a new target is chosen if the original one dies before the action executes. The alternative is a wasted turn, and nobody has time for that.

If the action is neither random nor for a single target, it must be for all enemies, so we just set targets to the result of calling unit's aliveMembers function, which will return any enemy that's not dead yet.

Finally, we return the targets array.

Game_Action.prototype.targetsForFriends = function() {
    var targets = [];
    var unit = this.friendsUnit();
    if (this.isForUser()) {
        return [this.subject()];
    } else if (this.isForDeadFriend()) {
        if (this.isForOne()) {
            targets.push(unit.smoothDeadTarget(this._targetIndex));
        } else {
            targets = unit.deadMembers();
        }
    } else if (this.isForOne()) {
        if (this._targetIndex < 0) {
            targets.push(unit.randomTarget());
        } else {
            targets.push(unit.smoothTarget(this._targetIndex));
        }
    } else {
        targets = unit.aliveMembers();
    }
    return targets;
};


targetsForFriends is almost identical. The main difference is the check for isForUser, which returns the action's subject as the target. Instead of isForRandom, we're checking isForDeadFriend. In that case, we check whether isForOne returns true, in which case we return the result of calling unit's smoothDeadTarget passing in _targetIndex (this makes sure we don't end up targeting a dead ally who's already been revived before the action executes) and otherwise it has to be for all dead allies, so we push the result of deadMembers, which returns all party members who are no longer alive.

If it's not for a dead friend, we check whether isForOne returns true. If so, we check whether _targetIndex is less than 0, in which case the target has to be random and we put the result of randomTarget. Otherwise, the player must have picked an ally, so we push the result of smoothTarget with _targetIndex passed in.

Otherwise, the action must target all allies, so we just set targets to the result of aliveMembers.

And, again, finally we return targets.

Game_Action.prototype.evaluate = function() {
    var value = 0;
    this.itemTargetCandidates().forEach(function(target) {
        var targetValue = this.evaluateWithTarget(target);
        if (this.isForAll()) {
            value += targetValue;
        } else if (targetValue > value) {
            value = targetValue;
            this._targetIndex = target.index();
        }
    }, this);
    value *= this.numRepeats();
    if (value > 0) {
        value += Math.random();
    }
    return value;
};


evaluate is an interesting function: it basically simulates the action on each target it can be applied to, and targets the most effective viable target.

First we set a temp variable called value to 0. Then start a forEach loop on the return value of itemTargetCandidates (which as we'll see in a sec, gets all targets that *can* be chosen for the action), passing in a function where the current candidate is stored in the variable "target" and using "this" as the "thisValue" argument, meaning we can refer to "this" in the loop and mean the current class instance.

Inside this loop, we set a temp variable called targetValue to the result of calling evaluateWithTarget with target as the argument. If the action is for all targets, we add targetValue to value. Otherwise, if targetValue is greater than value (meaning we've simulated a more effective attack/heal than the last one) we set value to targetValue, and the action's _targetIndex to the index of the current target. This keeps a running tally of the candidate that caused the most desirable effect. (bearing in mind dealing more damage or healing more is rarely a bad thing)

After the loop, we multiply value by the number of repeats (to get the total "potential" value). If value is greater than 0, we add a random value to it from 0 to 1 (for a bit of variance in the event there is none in the action itself; this ensures that skills with no innate variance won't always randomly target the first viable target)

Finally we return the value variable, which will contain the highest amount of damage/healing it was able to simulate for the available targets.

Game_Action.prototype.itemTargetCandidates = function() {
    if (!this.isValid()) {
        return [];
    } else if (this.isForOpponent()) {
        return this.opponentsUnit().aliveMembers();
    } else if (this.isForUser()) {
        return [this.subject()];
    } else if (this.isForDeadFriend()) {
        return this.friendsUnit().deadMembers();
    } else {
        return this.friendsUnit().aliveMembers();
    }
};


This function gets the possible candidates that the action can be used on (as we've just seen used in the function above).

First of all, if calling isValid() returns false, we just return an empty array. If the item can't be used, there are no viable targets for it, obviously.

Otherwise, if the action is for opponents, we return the aliveMembers() array from opponentsUnit(). This ensures we don't consider dead enemies to be valid targets (there may be some interesting applications of coding in exceptions to this. Imagine an enemy with a skill that can target dead party members to remove them from battle completely!)

Otherwise, if the action is for its user, we just return an array with subject() as the only element.

Otherwise, if the action is for a dead ally, we return the deadMembers() array from friendsUnit().

And in all other cases, we return the aliveMembers() array from friendsUnit(), as the only other possibility is that the action is intended for an ally.

Game_Action.prototype.evaluateWithTarget = function(target) {
    if (this.isHpEffect()) {
        var value = this.makeDamageValue(target, false);
        if (this.isForOpponent()) {
            return value / Math.max(target.hp, 1);
        } else {
            var recovery = Math.min(-value, target.mhp - target.hp);
            return recovery / target.mhp;
        }
    }
};


evaluateWithTarget is another function we've seen called earlier; it takes one parameter, target.

First we check whether the action has an HP effect (HP damage, HP recovery or HP drain). If so, we set a temp variable called value to the result of calling makeDamageValue, passing in "target" and "false" as arguments. We'll see what this does soon.

Then we check whether the action is for opponents. If so, we return value divided by the maximum out of the target's HP and 1 (to ensure that in the event that the target's HP has already been reduced to 0 or less by the time we evaluate this, we don't end up dividing by 0 or a negative number). Otherwise (if the action is for a friendly unit, we set a temp variable called "recovery" to the minimum value out of the inverse of value, or the target's maximum HP minus its current HP (in other words, the damage converted to healing and how much HP the target has actually lost; this avoids overheals). Finally we return recovery divided by the target's max HP.

Let's consider a skill that deals a flat 20 points of damage, being evaluated against an enemy with 50HP which has already been damaged by 12, so its current HP is 38. The return value would end up being 20 / 38, or 0.53 (basically the enemy would be losing 53% of its health in this simulation)

Game_Action.prototype.testApply = function(target) {
    return (this.isForDeadFriend() === target.isDead() &&
            ($gameParty.inBattle() || this.isForOpponent() ||
            (this.isHpRecover() && target.hp < target.mhp) ||
            (this.isMpRecover() && target.mp < target.mmp) ||
            (this.hasItemAnyValidEffects(target))));
};


testApply is just one big long logic test. It will return true if ((the action being intended for a dead friend returns the same value as calling isDead on the target) AND (the party is in battle OR the action is for opponents OR (the action recovers HP AND the target's HP is less than its maximum) OR (the action recovers MP AND the target's MP is less than its maximum) OR the target has any applicable effects for the target).

Game_Action.prototype.hasItemAnyValidEffects = function(target) {
    return this.item().effects.some(function(effect) {
        return this.testItemEffect(target, effect);
    }, this);
};


This function checks whether the item has any affects that are applicable to the target, and takes "target" as a parameter.

You may remember this from previous articles, but .some is an array function which returns true if at least one of the elements meets the criteria supplied in the code block.

What we're returning here is the result of calling .some on the effects property of item(), which gives us an array of effect objects. To the some function, we pass a function, storing each element of effects in an iteration variable called "effect" and inside that function return the result of calling testItemEffect with "target" and "effect" passed as arguments.

Game_Action.prototype.testItemEffect = function(target, effect) {
    switch (effect.code) {
    case Game_Action.EFFECT_RECOVER_HP:
        return target.hp < target.mhp || effect.value1 < 0 || effect.value2 < 0;
    case Game_Action.EFFECT_RECOVER_MP:
        return target.mp < target.mmp || effect.value1 < 0 || effect.value2 < 0;
    case Game_Action.EFFECT_ADD_STATE:
        return !target.isStateAffected(effect.dataId);
    case Game_Action.EFFECT_REMOVE_STATE:
        return target.isStateAffected(effect.dataId);
    case Game_Action.EFFECT_ADD_BUFF:
        return !target.isMaxBuffAffected(effect.dataId);
    case Game_Action.EFFECT_ADD_DEBUFF:
        return !target.isMaxDebuffAffected(effect.dataId);
    case Game_Action.EFFECT_REMOVE_BUFF:
        return target.isBuffAffected(effect.dataId);
    case Game_Action.EFFECT_REMOVE_DEBUFF:
        return target.isDebuffAffected(effect.dataId);
    case Game_Action.EFFECT_LEARN_SKILL:
        return target.isActor() && !target.isLearnedSkill(effect.dataId);
    default:
        return true;
    }
};


This is the function we used in the previous one, taking target and effect as parameters. We're using a switch statement passing in the effect's code.

When it's EFFECT_RECOVER_HP, we return true if the target's HP is less than its maximum OR value1 of the effect is less than 0 OR value2 of the effect is less than 0 (this would mean that the developer has set an effect that reduces HP by a percentage, flat value, or both).

For EFFECT_RECOVER_MP, we do the same checks but for current and max MP instead.

For EFFECT_ADD_STATE, we check whether the target calling isStateAffected returns false when we pass in the effect's dataId (that is to say the target is *not* already afflicted by the state the effect inflicts)

For EFFECT_REMOVE_STATE, we perform the opposite check, making sure the target *is* afflicted by the state the effect removes.

For EFFECT_ADD_BUFF, we have a similar check to the state one, but we're calling isMaxBuffAffected to make sure the target doesn't already have the maximum buff stacks.

For EFFECT_ADD_DEBUFF, we're calling isMaxDebuffAffected to make sure the target doesn't already have the maximum debuff stacks.

EFFECT_REMOVE_BUFF and EFFECT_REMOVE_DEBUFF call the isBuffAffected and isDebuffAffected functions instead, as we want to check whether the target has at least one stack of the buff/debuff in question.

For EFFECT_LEARN_SKILL, we return the result of checking whether the target is an actor AND calling isLearnedSkill returns false (meaning they haven't learned the skill yet). Obviously enemies don't learn skills, which is why the first check is needed.

Finally, we return true as the default case.

Game_Action.prototype.itemCnt = function(target) {
    if (this.isPhysical() && target.canMove()) {
        return target.cnt;
    } else {
        return 0;
    }
};

Game_Action.prototype.itemMrf = function(target) {
    if (this.isMagical()) {
        return target.mrf;
    } else {
        return 0;
    }
};

Game_Action.prototype.itemHit = function(target) {
    if (this.isPhysical()) {
        return this.item().successRate * 0.01 * this.subject().hit;
    } else {
        return this.item().successRate * 0.01;
    }
};

Game_Action.prototype.itemEva = function(target) {
    if (this.isPhysical()) {
        return target.eva;
    } else if (this.isMagical()) {
        return target.mev;
    } else {
        return 0;
    }
};

Game_Action.prototype.itemCri = function(target) {
    return this.item().damage.critical ? this.subject().cri * (1 - target.cev) : 0;
};


These almost-identical functions return the action's counterattack chance, magic reflection chance, hit rate, evasion chance and critical chance respectively, each taking target as their parameter.

For itemCnt, if isPhysical() AND the target's canMove() return true (meaning the action is a physical attack and the target's movement is unrestricted) we return the target's cnt value (counterattack rate). Otherwise, we return 0.
itemMrf makes a similar check, but we're just checking the result of isMagical(), as the only requirement for magic reflection is that the action be a magical attack. If this is the case, we return the target's mrf value (magic reflection rate).

itemHit checks whether isPhysical() returns true; if so, we return the item's success rate multiplied by 0.01 multiplied by the subject's hit value. Otherwise, we just return successRate multiplied by 0.01. The multiplication is because the chances are stored as percentages and when calculating with them we need the decimal representation (that is to say a hit rate of 50% is stored as 50, so we need to multiply it by 0.01 to get 0.5).

In itemEva, we check whether isPhysical() returns true; if so, we return the target's eva value (evasion). Otherwise, we check whether isMagical() returns true, in which case we return the target's mev (magic evasion). Otherwise we return 0.

Finally, in itemCri we get the action's critical chance. We're returning the result of an inline if: if the critical flag of the item's damage property is true (meaning we scored a critical hit), the return value is the subject's cri (crit chance) multiplied by (1 - target's cev). Otherwise, we return 0.

Let's say the user has a crit rate of 4% and the target has a critical evasion of 5%; the equation becomes 0.04 * (1 - 0.05) or 0.04 * 0.95, which results in 0.038, effectively reducing the chance of a successful critical attack to 3.8%.

Game_Action.prototype.apply = function(target) {
    var result = target.result();
    this.subject().clearResult();
    result.clear();
    result.used = this.testApply(target);
    result.missed = (result.used && Math.random() >= this.itemHit(target));
    result.evaded = (!result.missed && Math.random() < this.itemEva(target));
    result.physical = this.isPhysical();
    result.drain = this.isDrain();
    if (result.isHit()) {
        if (this.item().damage.type > 0) {
            result.critical = (Math.random() < this.itemCri(target));
            var value = this.makeDamageValue(target, result.critical);
            this.executeDamage(target, value);
        }
        this.item().effects.forEach(function(effect) {
            this.applyItemEffect(target, effect);
        }, this);
        this.applyItemUserEffect(target);
    }
};


The apply function is what we call to actually manifest the effects of the action, and takes target as its parameter.

First we set the temp variable result to the return value from calling the target's result() function, which gives us that battler's instance of Game_ActionResult. We call clearResult on the subject to clear out their own instance, and clear() on result. This starts us out with completely fresh action results.

result.used is set to the return value of testApply passing in target, which as we've already covered will return true if the action is applicable to the target, and false otherwise.

result.missed will return true if result.used is true AND a random value from 0 to 1 is greater than or equal to the result of calling itemHit. (for example, with a hit rate of 80%, 0.8, missed will be false if the random number is 0 to 0.7999... and true if it's 0.8 or above. Obviously the lower the hit rate, the easier it is for a random number to exceed it).

result.evaded will return true if result.missed is false AND a random value from 0 to 1 is less than the result of calling itemEva.

result.physical is set to the result of isPhysical(), so it'll be true if a physical attack and false if not.

Then we check whether result's isHit() function returns true. If so (the action didn't miss and wasn't evaded) we check whether the item's damage type is greater than 0 (0 being "none"); if so, we set result.critical to whether a random value from 0 to 1 is less than the result of calling itemCri (this will tell us whether we've scored a critical). Then we set temp variable value to the result of calling makeDamageValue, passing in target and result.critical as the arguments. And then we call executeDamage, passing in target and value. We'll see shortly how these work.

Following the damage type check, we run through a forEach loop with the item's effects using iteration variable "effect", and call applyItemEffect passing in "target" and "effect" each time.

Finally, we call applyItemUserEffect to update user resources, which we'll see soon as well.

Game_Action.prototype.makeDamageValue = function(target, critical) {
    var item = this.item();
    var baseValue = this.evalDamageFormula(target);
    var value = baseValue * this.calcElementRate(target);
    if (this.isPhysical()) {
        value *= target.pdr;
    }
    if (this.isMagical()) {
        value *= target.mdr;
    }
    if (baseValue < 0) {
        value *= target.rec;
    }
    if (critical) {
        value = this.applyCritical(value);
    }
    value = this.applyVariance(value, item.damage.variance);
    value = this.applyGuard(value, target);
    value = Math.round(value);
    return value;
};


makeDamageValue is one of the cornerstones of your battle system, and calculates how much damage/healing is actually done. It takes two parameters, "target" and "critical".

First we set temp variable item to this.item() just for convenience. Then another temp variable called baseValue to the result of calling evalDamageFormula with target passed in (again for convenience) and another called value which is set to baseValue multiplied by the result of calling calcElementRate with target passed in (this will modify the value by elemental resistances, as we'll see soon).

If isPhysical() returns true, we multiply value by the target's pdr (physical damage resistance). If isMagical() returns true, we multiply value by the target's mdr (magic resistance). Then, if baseValue is less than 0 (meaning we're healing rather than damaging) we multiply value by the target's rec (recovery rate). If critical is true, we set value to the result of calling applyCritical with value passed in (this will modify the value to be critical damage/healing, as we'll see).

Then, we set value to the result of applyVariance, passing in value and the variance property of the item's damage object (which will be whatever variance we set for the action in the database), and then to the result of applyGuard, passing in value and target (which will reduce damage if guarding). Finally, we round value and return it.

Game_Action.prototype.evalDamageFormula = function(target) {
    try {
        var item = this.item();
        var a = this.subject();
        var b = target;
        var v = $gameVariables._data;
        var sign = ([3, 4].contains(item.damage.type) ? -1 : 1);
        var value = Math.max(eval(item.damage.formula), 0) * sign;
		if (isNaN(value)) value = 0;
		return value;
    } catch (e) {
        return 0;
    }
};


This is the function that converts your damage formula into an actual value and takes "target" as a parameter.

First of all we set up some temp variables: "item" is set to this.item(), "a" to this.subject(), "b" to target, and "v" to $gameVariables._data. This is actually why, in the damage formula box, you can do stuff like a.atk and v.

We also have a variable to determine the sign: if the item's damage type is 3 or 4 (HP/MP Recover) it's set to -1, or 1 otherwise.

We then set value to the maximum value between the evaluation of the item's damage formula and 0, multiplied by sign. If the value ends up being isNaN (not a number) we set it to 0 instead to avoid crashes, and then return value.

Note that this whole thing is in what's called a try/catch block. If any line of code inside the "try" causes an error, execution will immediately jump to the "catch" block. In this case, if we encounter an error, we'll simply return 0.

eval is a fun little function. Basically it evaluates an equation provided to it and returns the result. So taking the default damage formula as an example, it's "a.atk / 2 - b.def / 4" or "half of subject's atk minus a quarter of target's def". If the subject has 20 atk and the target has 12 def, this will work out to 20 / 2 - 12 / 4 -> 10 - 3 -> 7 base damage.

Game_Action.prototype.calcElementRate = function(target) {
    if (this.item().damage.elementId < 0) {
        return this.elementsMaxRate(target, this.subject().attackElements());
    } else {
        return target.elementRate(this.item().damage.elementId);
    }
};


This function determines the effect elements will have on the damage dealt, taking target as a parameter.

If the element ID of the item's damage is less than 0 (meaning it's a Normal Attack, which has an ID of -1) we return the result of calling elementsMaxRate, passing in target and the result of calling attackElements() on the subject. Otherwise (meaning the attack has an element besides Normal Attack) we return the result of calling elementRate on the target, passing in the element ID of the item's damage.

Game_Action.prototype.elementsMaxRate = function(target, elements) {
    if (elements.length > 0) {
        return Math.max.apply(null, elements.map(function(elementId) {
            return target.elementRate(elementId);
        }, this));
    } else {
        return 1;
    }
};


This function returns the highest rate among all elements contained in the attack, take "target" and "elements" as parameters.

First we check whether elements.length is greater than 0 (in other words, whether there were any attack elements at all). If so, we return...oh, that looks scary. But as with anything else, it's not!

Okay, so Math.max is easy, we know that. We've look at map before, but in case you need a refresher, it basically convert one array into a new one using the provided code block. In this case, the block returns the result of calling the target's elementRate() passing in the elementId block variable. This will give us an array of the effectiveness values of each attack element on the target. .apply lets us provide our arguments to the calling function as an array, which is why we're adding that on to Max.max. Max.max requires two numerical arguments, but we have an array so we need to go a bit beyond that. The "null" for the first parameter is what we want to consider "this" to be in the code block, but we don't need anything there as "this" isn't referenced inside the block here.

Otherwise, if there are no elements, we just return 1 (100% effectiveness).

Game_Action.prototype.applyCritical = function(damage) {
    return damage * 3;
};


This function applies the critical hit effect and takes "damage" as its parameter. It simply returns damage multiplied by 3. If you ever want to change the damage multiplier for a critical hit, this is the place to do it.

Game_Action.prototype.applyVariance = function(damage, variance) {
    var amp = Math.floor(Math.max(Math.abs(damage) * variance / 100, 0));
    var v = Math.randomInt(amp + 1) + Math.randomInt(amp + 1) - amp;
    return damage >= 0 ? damage + v : damage - v;
};


This function applies variance to the action, oddly enough, and takes "damage" and "variance" as parameters.

We set a temp variable called amp to the floor of the maximum value between the absolute value of damage multiplied by variance divide by 100, and 0.

Uh...what?

Okay, so. Let's say we have 32 damage and the skill's variance was set to 15%. We end up with

Math.floor(Math.max(Math.abs(32) * 15 / 100, 0)) -> Math.floor(Math.max(32 * 15 / 100, 0)) -> Math.floor(Math.max(4.8, 0)) -> Math.floor(4.8) = 4

So our amp will be 4.

Then we set a temp variable called v to a random integer from 0 to amp plus a random integer from 0 to amp minus amp.

So our random number will be 0-4. Let's say the first one is 4, and the second is 3. That gives us 4 + 3 - 4 = 3, so v is 3.

We return damage plus v if damage is greater than or equal to 0, or damage minus v otherwise. In our example case, that ends up being 32 + 3, so our variant damage comes out to 35.

Game_Action.prototype.applyGuard = function(damage, target) {
    return damage / (damage > 0 && target.isGuard() ? 2 * target.grd : 1);
};


This function applies the guard effect, taking "damage" and "target" as parameters. We return damage divided by 2 multiplied by target's GRD (Guard Effect) if the damage is greater than 0 and the target is guarding, or damage divided by 1 otherwise.

Taking our previous example, damage is 35. If the target isn't guarding, we just get 35 / 1, so still 35. If the target is guarding, let's say they have a shield on that sets Guard Effect to 130%. Then we'll have a return value of 35 / 2 * 1.3 -> 35 / 2.6 = 13.46. You can see that GRD is pretty handy, as without that the return value would have been 17.5. 4 damage might not seem like a lot but depending on the balance of the battle system it can be the difference between life and death.

Game_Action.prototype.executeDamage = function(target, value) {
    var result = target.result();
    if (value === 0) {
        result.critical = false;
    }
    if (this.isHpEffect()) {
        this.executeHpDamage(target, value);
    }
    if (this.isMpEffect()) {
        this.executeMpDamage(target, value);
    }
};


This function executes the damage for the action, taking "target" and "value" as parameters.

We set a temp variable called result to target.result(). Then if value is 0, we set result.critical to false.

If the action has an HP effect (HP Damage, HP Recover, HP Drain) we call executeHpDamage, passing in target and value. If the action has an MP effect (MP Damage, MP Recover, MP Drain) we call executeMpDamage instead, passing in the same values.

Game_Action.prototype.executeHpDamage = function(target, value) {
    if (this.isDrain()) {
        value = Math.min(target.hp, value);
    }
    this.makeSuccess(target);
    target.gainHp(-value);
    if (value > 0) {
        target.onDamage(value);
    }
    this.gainDrainedHp(value);
};


executeHpDamge applies...well, HP damage. It takes the same parameters as above.

If isDrain() returns true (in other words, HP Drain was selected) we set value to the minimum between the target's HP and value (because you can't drain more HP than the target has). We call makeSuccess, passing in target (it'll be a while before we see this one, but it basically just sets the "success" flag of the action result to true) and call gainHp on the target, passing in the negative of value (it has to be negated to be treated as damage rather than healing). If value is greater than 0, we call the target's onDamage function passing in value (we'll see this at a later date) and finally we call gainDrainedHp, again passing in value.

Game_Action.prototype.executeMpDamage = function(target, value) {
    if (!this.isMpRecover()) {
        value = Math.min(target.mp, value);
    }
    if (value !== 0) {
        this.makeSuccess(target);
    }
    target.gainMp(-value);
    this.gainDrainedMp(value);
};


executeMpDamage is almost identical, but with a couple of interesting differences. First of all, instead of checking isDrain, we check whether isMpRecover returns false. This is because we want to reduce value to at most the target's current MP whether it's MP damage or MP drain. Why not do this with HP damage? Well, if the target's HP is reduced below 0, it'll die just as much. MP being 0 doesn't kill the enemy though, and they might regain MP at some point, so being able to reduce the enemy to negative MP would be an issue.

Secondly, we have an additional if statement for value not being 0 in order to call makeSuccess. This is because an MP attack that does no damage isn't considered successful (I'm honestly not sure why a 0-damage HP damage *is* considered a success, but ours is not to reason why). We also don't have the call to onDamage (it's going to be a while before we see this, but basically it's because that function removes states that are removed by taking damage, and adds damage-based TP neither of which MP affect, so there's no need to call it).

Game_Action.prototype.gainDrainedHp = function(value) {
    if (this.isDrain()) {
       var gainTarget = this.subject();
       if (this._reflectionTarget !== undefined) {
            gainTarget = this._reflectionTarget;
        }
       gainTarget.gainHp(value);
    }
};

Game_Action.prototype.gainDrainedMp = function(value) {
    if (this.isDrain()) {
       var gainTarget = this.subject();
       if (this._reflectionTarget !== undefined) {
           gainTarget = this._reflectionTarget;
       }
       gainTarget.gainMp(value);
    }
};


These two are together because they basically are identical aside from which function is called at the end. We check whether isDrain() returns true, and if so: we set temp variable gainTarget to the subject, then check whether _reflectionTarget is not undefined and if so sets gainTarget to _reflectionTarget, then calls gainHP/gainMP on gainTarget, passing in value. Interestingly, this tells us that drain effects are subject to magic reflection.

Game_Action.prototype.applyItemEffect = function(target, effect) {
    switch (effect.code) {
    case Game_Action.EFFECT_RECOVER_HP:
        this.itemEffectRecoverHp(target, effect);
        break;
    case Game_Action.EFFECT_RECOVER_MP:
        this.itemEffectRecoverMp(target, effect);
        break;
    case Game_Action.EFFECT_GAIN_TP:
        this.itemEffectGainTp(target, effect);
        break;
    case Game_Action.EFFECT_ADD_STATE:
        this.itemEffectAddState(target, effect);
        break;
    case Game_Action.EFFECT_REMOVE_STATE:
        this.itemEffectRemoveState(target, effect);
        break;
    case Game_Action.EFFECT_ADD_BUFF:
        this.itemEffectAddBuff(target, effect);
        break;
    case Game_Action.EFFECT_ADD_DEBUFF:
        this.itemEffectAddDebuff(target, effect);
        break;
    case Game_Action.EFFECT_REMOVE_BUFF:
        this.itemEffectRemoveBuff(target, effect);
        break;
    case Game_Action.EFFECT_REMOVE_DEBUFF:
        this.itemEffectRemoveDebuff(target, effect);
        break;
    case Game_Action.EFFECT_SPECIAL:
        this.itemEffectSpecial(target, effect);
        break;
    case Game_Action.EFFECT_GROW:
        this.itemEffectGrow(target, effect);
        break;
    case Game_Action.EFFECT_LEARN_SKILL:
        this.itemEffectLearnSkill(target, effect);
        break;
    case Game_Action.EFFECT_COMMON_EVENT:
        this.itemEffectCommonEvent(target, effect);
        break;
    }
};


The applyItemEffect function takes two parameters, "target" and "effect" and is essentially just a giant switch statement for the effect's code which calls the relevant function for whatever the effect is. I'm not going to go through the whole thing because by now this function should be pretty self-explanatory. Rememeber what I said before about the constants making this way easier to understand? Imagine how confusing it would be if the cases were just numbers.

Game_Action.prototype.itemEffectRecoverHp = function(target, effect) {
    var value = (target.mhp * effect.value1 + effect.value2) * target.rec;
    if (this.isItem()) {
        value *= this.subject().pha;
    }
    value = Math.floor(value);
    if (value !== 0) {
        target.gainHp(value);
        this.makeSuccess(target);
    }
};

Game_Action.prototype.itemEffectRecoverMp = function(target, effect) {
    var value = (target.mmp * effect.value1 + effect.value2) * target.rec;
    if (this.isItem()) {
        value *= this.subject().pha;
    }
    value = Math.floor(value);
    if (value !== 0) {
        target.gainMp(value);
        this.makeSuccess(target);
    }
};


itemEffectRecoverHp and itemEffectRecoverMp are another two that are almost identical with the exception of which stats they check and which gain function they call. They each take "target" and "effect" as parameters.

We set temp variable value to (target's max HP/MP multiplied by effect's value1 plus effect's value2) multiplied by the target's REC (Recovery Effect). If isItem() returns true, we multiply value by the subject's PHA (Pharmacology). We set value to the floor of value. Then if value is not 0, we call gainHP/gainMP passing in value, and call makeSuccess passing in the target.

Okay, so let's do an example to unwind this a bit. Let's say the effect on an item is "Recover HP" and in the database you've set 10% and +20. The target's REC is 150% and the user of the item has a Pharmacology of 60% as they have a state that makes recovery items less effective when used by the character that has it. We'll say the target's max HP is 200.

This gives us value = (200 * 0.1 + 20) * 1.5 -> 40 * 1.5 = 60

Then because it's an item, we have 60 *= 0.6 = 36. So in the end, the item will restore 36 HP.

Game_Action.prototype.itemEffectGainTp = function(target, effect) {
    var value = Math.floor(effect.value1);
    if (value !== 0) {
        target.gainTp(value);
        this.makeSuccess(target);
    }
};


itemEffectGainTp also takes target and effect as parameters. In fact, all of the item effect functions do so this is the last time I'll specify that.

We set temp variable value to the floor of effect's value1 (I'm not sure why Math.floor is necessary as you're only able to enter integers in the effect interface). Then, if value is not equal to 0, we call gainTp on the target, passing in value, and also call makeSuccess passing in target.

Game_Action.prototype.itemEffectAddState = function(target, effect) {

if (effect.dataId === 0) {
this.itemEffectAddAttackState(target, effect);
} else {
this.itemEffectAddNormalState(target, effect);
}
};


This one deals with "Add State" effects. We check whether dataId of the effect is 0 (Normal Attack); if so, we call itemEffectAddAttackState passing in target and effect. Otherwise we call itemEffectAddNormalState passing in target and effect.

Game_Action.prototype.itemEffectAddAttackState = function(target, effect) {

this.subject().attackStates().forEach(function(stateId) {
var chance = effect.value1;
chance *= target.stateRate(stateId);
chance *= this.subject().attackStatesRate(stateId);
chance *= this.lukEffectRate(target);
if (Math.random() < chance) {
target.addState(stateId);
this.makeSuccess(target);
}
}.bind(this), target);
};


This function adds the normal attack state. A lot of people misunderstand this one and don't understand what it is, or indeed how *powerful* it is. What this option does is gives the effect a chance of inflicting *any state the user could normally inflict with an attack*. So if your hero has a Poison Sword, and the Crown of Blinding, and a Ring of Inducing Rage, and he uses a skill that has "Add State: Normal Attack" as an effect, it could end up poisoning, blinding *and* enraging the enemy!

So how does it work? Well first of all we're running a forEach loop on the subject's attackStates(), using stateId as the iteration variable in the block.

We set temp variable "chance" to effect's value1, which is the percentage chance of inflicting the Normal Attack state. Then we multiply chance by the return value of stateRate on the target with stateId passed in. Then we multiply chance by the return value of attackStatesRate on the subject passing in stateId. Then we multiply chance by the return value of lukEffectRate passing in target.

If a random value from 0 to 1 is less than chance, we add the current state to the target and call makeSuccess with target as the argument.

Note that the .bind(this) on the function allows "this" to be referenced inside the block and still refer to the game action.

Game_Action.prototype.itemEffectAddNormalState = function(target, effect) {
    var chance = effect.value1;
    if (!this.isCertainHit()) {
        chance *= target.stateRate(effect.dataId);
        chance *= this.lukEffectRate(target);
    }
    if (Math.random() < chance) {
        target.addState(effect.dataId);
        this.makeSuccess(target);
    }
};


This function adds any other state than the normal attack one. It's almost identical except it doesn't have the forEach loop for attack states (since we're only trying to inflict one) and there's no call to attackStatesRate because it doesn't apply here. There is an additional if statement to make sure isCertainHit() is false before calling the modification functions, because if it *is* certain hit we just want to apply the percentage directly without worrying about the target's actual state rate for the state in question or their luck modifier. Other than that, we're doing the same thing as before.

Game_Action.prototype.itemEffectRemoveState = function(target, effect) {
    var chance = effect.value1;
    if (Math.random() < chance) {
        target.removeState(effect.dataId);
        this.makeSuccess(target);
    }
};


The function for removing a state is just a simplified version of the function for adding one. In fact, it's identical besides taking out the "isCertainHit" check and calling removeState instead of addState.

Interestingly, this means that *adding* a state with an item or skill in the default battle system that is set to physical or magic attack will take the target's state rate and luck effect into account, while removing one never will.

Game_Action.prototype.itemEffectAddBuff = function(target, effect) {
    target.addBuff(effect.dataId, effect.value1);
    this.makeSuccess(target);
};


This function adds a buff effect. Pretty simple really, it just calls addBuff on the target passing in the effect's dataId (the ID of the stat being buffed) and value1 (the number of turns to buff for) and then calls makeSuccess like the other functions do.

Game_Action.prototype.itemEffectAddDebuff = function(target, effect) {
    var chance = target.debuffRate(effect.dataId) * this.lukEffectRate(target);
    if (Math.random() < chance) {
        target.addDebuff(effect.dataId, effect.value1);
        this.makeSuccess(target);
    }
};


The one for adding a debuff is similar, but adds a chance of failure. We set chance to the target's debuffRate passing in the effect's dataId (the stat to debuff) multiplied by lukEffectRate passing in the target. Then if a random number from 0 to 1 is less than chance, we call addDebuff on the target and call makeSuccess.

Game_Action.prototype.itemEffectRemoveBuff = function(target, effect) {
    if (target.isBuffAffected(effect.dataId)) {
        target.removeBuff(effect.dataId);
        this.makeSuccess(target);
    }
};

Game_Action.prototype.itemEffectRemoveDebuff = function(target, effect) {
    if (target.isDebuffAffected(effect.dataId)) {
        target.removeBuff(effect.dataId);
        this.makeSuccess(target);
    }
};


itemEffectRemoveBuff and itemEffectRemoveDebuff are pretty much identical with the exception of the function which is called to check whether the target is affected. Interestingly, in both cases we call removeBuff; this is because internally a debuff is just negative stacks of a buff.

Game_Action.prototype.itemEffectSpecial = function(target, effect) {
    if (effect.dataId === Game_Action.SPECIAL_EFFECT_ESCAPE) {
        target.escape();
        this.makeSuccess(target);
    }
};


This function applies special effects, which by default there is only one: escaping battle. So if effect's dataId is SPECIAL_EFFECT_ESCAPE, we call the escape() function on the target and then makeSuccess.

Game_Action.prototype.itemEffectGrow = function(target, effect) {
    target.addParam(effect.dataId, Math.floor(effect.value1));
    this.makeSuccess(target);
};


This function handles the "Grow" effect. We call addParam on the target, passing in the effect's dataId (the stat being grown) and the floor of effect's value1. Unlike some other functions, the floor here actually is needed because for some reason you're able to enter decimal values in the input box for this one. And then, you guessed it, we call makeSuccess.

Game_Action.prototype.itemEffectLearnSkill = function(target, effect) {
    if (target.isActor()) {
        target.learnSkill(effect.dataId);
        this.makeSuccess(target);
    }
};


This function handles skill-learning effects. First we check whether isActor() on the target returns true (because enemies can't learn skills) and if so we call learnSkill passing in the effect's dataId (The ID of the skill to learn) and makeSuccess.

Game_Action.prototype.itemEffectCommonEvent = function(target, effect) {
};


And here we have an empty function, because common events are handled a bit differently as we'll see in a second. I mean there's nothing stopping you from adding code here if you want to have a particular thing happen whenever an item or skill calls a common event, but by default there's nothing.

Game_Action.prototype.makeSuccess = function(target) {
    target.result().success = true;
};


Finally, we see what winning looks like! And...it's kinda underwhelming, but I did warn you earlier. This makeSuccess function we've been calling all over the place just sets the success flag of the target's result() to true.

Game_Action.prototype.applyItemUserEffect = function(target) {
    var value = Math.floor(this.item().tpGain * this.subject().tcr);
    this.subject().gainSilentTp(value);
};


applyItemUserEffect is the function that...well, applies user effects from the item. It only takes target as a parameter. First we set temp variable value to the floor of multiplying the item's TP gain by the subject's TCR (TP Charge Rate) and then we call gainSilentTyp on subject(), passing in value.

For example, if we have a skill that has TP Gain set to 5, and our user has a TCR of 120%, this becomes Math.floor(5 * 1.2) = 6. So we get a whole extra TP! gainSilentTP, as we'll see when we get into the battler classes, just adds to TP without having a notification message about it.

Game_Action.prototype.lukEffectRate = function(target) {
    return Math.max(1.0 + (this.subject().luk - target.luk) * 0.001, 0.0);
};


lukEffectRate is the function we use to apply the luck effect to a calculation, and by default we take the maximum value between (1.0 plus (subject's luk minus target's luk) multiplied by 0.001), and 0.0, as we don't want to apply a negative modifier.

Let's say our subject has 22 luk, and their target has 10. This becomes Math.max(1.0 + (22 - 10) * 0.001, 0.0) -> Math.max(1.0 + 12 * 0.001, 0.0) -> Math.max(1.0 + 0.012, 0.0) -> Math.max(1.012, 0.0) -> 1.012. So in this case, luk will increase our chances by 1.2%.

Game_Action.prototype.applyGlobal = function() {
    this.item().effects.forEach(function(effect) {
        if (effect.code === Game_Action.EFFECT_COMMON_EVENT) {
            $gameTemp.reserveCommonEvent(effect.dataId);
        }
    }, this);
};


We're onto the last function in the behemoth that is Game_Action! applyGlobal is what handles common events.

We have a forEach function on the item's effects, using iteration variable "effect". If the effect's code is the same as EFFECT_COMMON_EVENT, we call the reserveCommonEvent function of $gameTemp, passing in the effect's dataId (the ID of the common event we want the item/skill to call). The actual processing of this is handled by a combination of classes, but we'll see more of the flow of that when we get to the relevant ones.

This is getting quite long, and I would have called it there but the next class is Game_ActionResult, which is quite closely connected to Game_Action and also quite a short class, so let's get it out of the way so we can start out next one with the way more exciting battler classes!

Game_ActionResult
The class, as the name suggests, deals with the results of battle actions.

Game_ActionResult.prototype.initialize = function() {
    this.clear();
};


All the initialize function does is call the clear() function.

Game_ActionResult.prototype.clear = function() {
    this.used = false;
    this.missed = false;
    this.evaded = false;
    this.physical = false;
    this.drain = false;
    this.critical = false;
    this.success = false;
    this.hpAffected = false;
    this.hpDamage = 0;
    this.mpDamage = 0;
    this.tpDamage = 0;
    this.addedStates = [];
    this.removedStates = [];
    this.addedBuffs = [];
    this.addedDebuffs = [];
    this.removedBuffs = [];
};


Which does a considerable amount! Okay, so we're setting the "used", "missed", "evaded", "physical", "drain", "critical", "success" and "hpAffected" flags to false; setting the "hpDamage", "mpDamage" and "tpDamage" properties to 0; and setting the "addedStates", "removedStates", "addedBuffs", "addedDebuffs" and "removedBuffs" properties to empty arrays.

Game_ActionResult.prototype.addedStateObjects = function() {
    return this.addedStates.map(function(id) {
        return $dataStates[id];
    });
};

Game_ActionResult.prototype.removedStateObjects = function() {
    return this.removedStates.map(function(id) {
        return $dataStates[id];
    });
};


The addedStateObjects function gets an array of state objects. We return a map of the addedStates property, using id as the iteration variable, and in the inner code block we return the element of $dataStates with an index of "id". In other words, for each state we're adding in the action result, get the object data for that state and bung it in the returned array.

removedStateObjects is just the same but for removedStates instead.

Game_ActionResult.prototype.isStatusAffected = function() {
    return (this.addedStates.length > 0 || this.removedStates.length > 0 ||
            this.addedBuffs.length > 0 || this.addedDebuffs.length > 0 ||
            this.removedBuffs.length > 0);
};


isStatusEffected is a function that determines whether the result contains any kind of status change: we return true if addedStates has elements OR if removedStates has elements OR if addedBuffs has elements OR if addedDebuffs has elements OR if removedBuffs has elements.

Game_ActionResult.prototype.isHit = function() {
    return this.used && !this.missed && !this.evaded;
};


isHit determines whether the result was a successful hit or not. It returns true if the used flag is true AND the missed flag is false AND the evaded flag is false. In other words, an action was used that didn't miss and wasn't evaded.

Game_ActionResult.prototype.isStateAdded = function(stateId) {
    return this.addedStates.contains(stateId);
};


This function determines whether a given state is being added by the action result, taking stateId as a parameter. We return true if addedStates contains the given stateId.

Game_ActionResult.prototype.pushAddedState = function(stateId) {
    if (!this.isStateAdded(stateId)) {
        this.addedStates.push(stateId);
    }
};


This function pushes a given state to the added states array, taking stateId as a parameter. If calling isStateAdded with stateId as the argument returns false (meaning that ID isn't already in the array), we push stateId to the addedStates array. This ensures that each ID is only in the array once.

Game_ActionResult.prototype.isStateRemoved = function(stateId) {
    return this.removedStates.contains(stateId);
};

Game_ActionResult.prototype.pushRemovedState = function(stateId) {
    if (!this.isStateRemoved(stateId)) {
        this.removedStates.push(stateId);
    }
};

Game_ActionResult.prototype.isBuffAdded = function(paramId) {
    return this.addedBuffs.contains(paramId);
};

Game_ActionResult.prototype.pushAddedBuff = function(paramId) {
    if (!this.isBuffAdded(paramId)) {
        this.addedBuffs.push(paramId);
    }
};

Game_ActionResult.prototype.isDebuffAdded = function(paramId) {
    return this.addedDebuffs.contains(paramId);
};

Game_ActionResult.prototype.pushAddedDebuff = function(paramId) {
    if (!this.isDebuffAdded(paramId)) {
        this.addedDebuffs.push(paramId);
    }
};

Game_ActionResult.prototype.isBuffRemoved = function(paramId) {
    return this.removedBuffs.contains(paramId);
};

Game_ActionResult.prototype.pushRemovedBuff = function(paramId) {
    if (!this.isBuffRemoved(paramId)) {
        this.removedBuffs.push(paramId);
    }
};


The other functions in this vein are exactly the same process, just using different verification functions, but the idea is identical.

And we're done! Hopefully this is a good amount of content for having been 3 years in the coming, and I promise the next one won't take quite so long.

Until next time!

Posts

Pages: 1
Trihan
"It's more like a big ball of wibbly wobbly...timey wimey...stuff."
3359
Part 9 has now been updated with the correct content!
Thank you for these. I am excited but slightly nauseous to do some jumping.
Trihan
"It's more like a big ball of wibbly wobbly...timey wimey...stuff."
3359
Note that I screwed this up a bit and inadvertently covered the same classes I already did 2 years ago. In the process of writing the proper instalment.
Pages: 1