JUMP INTO JAVASCRIPT PART 11

In which we break down battlers.

  • Trihan
  • 06/02/2021 12:06 AM
  • 150 views
Hello sports fans! It's completely your imagination that I haven't done one of these since last October. Honest. But let's ignore how terrible I am at following schedules and buckle our seatbelts for another fun-filled edition of

Jump into Javascript

As I said last time, though you probably don't remember by now, we're hitting the ground running with Game_Battler today.

Game_Battler
Game_Battler is the superclass of Game_Actor and Game_Enemy. It mainly handles sprites and actions. I'm going to skip the prototype setup and initialize function since there's nothing new there from any other class.

Game_Battler.prototype.initMembers = function() {
    Game_BattlerBase.prototype.initMembers.call(this);
    this._actions = [];
    this._speed = 0;
    this._result = new Game_ActionResult();
    this._actionState = '';
    this._lastTargetIndex = 0;
    this._animations = [];
    this._damagePopup = false;
    this._effectType = null;
    this._motionType = null;
    this._weaponImageId = 0;
    this._motionRefresh = false;
    this._selected = false;
};


initMembers is setting up a number of things here. First we call the initMembers function of Game_BattlerBase, which sets up HP, MP, TP etc. After that, we're setting a bunch of properties on the instance: _actions is set to an empty array, which will contain instances of Game_Action representing actions the battler is taking in battle; _speed is set to 0, and will be used to determine the order in which battlers act; _result is set to a new instance of Game_ActionResult, which contains data on whether the battler was the target of an attack, whether it hit, how much damage/healing was dealt etc.; _actionState is set to an empty string, which tracks what part of their turn the battler is currently in; _lastTargetIndex is set to 0, and will be used to track the index of the battler's last target; _animations is set to an empty array, and will be used to store the requested battle animations which need to be played on the battler; _damagePopup is set to false, and will be used to determine whether a damage popup needs to be displayed on the battler; _effectType is set to null, and will be used to track whether the battler needs to blink/collapse/whiten etc.; _motionType is set to null, which tracks whether the battler should be displaying idle, attacking, damage, dead etc. motions; _weaponImageId is set to 0, and will be used to determine which weapon graphic needs to be displayed during a thrust, swing or missile attack; _motionRefresh is set to false, and will be used to determine whether the motion needs refreshed; and _selected is set to false, which will be used to determine whether the battler is currently being targeted.

Game_Battler.prototype.clearAnimations = function() {
    this._animations = [];
};

Game_Battler.prototype.clearDamagePopup = function() {
    this._damagePopup = false;
};

Game_Battler.prototype.clearWeaponAnimation = function() {
    this._weaponImageId = 0;
};

Game_Battler.prototype.clearEffect = function() {
    this._effectType = null;
};

Game_Battler.prototype.clearMotion = function() {
    this._motionType = null;
    this._motionRefresh = false;
};


I'll group the clear functions together, as they're all mostly similar. Basically all we're doing here is returning each respective property or set of properties to their initial value. clearAnimations() reverts _animations to an empty array; clearDamagePopup() reverts _damagePopup to false; clearWeaponAnimation() reverts _weaponImageId to 0; clearEffect() reverts _effectType to null; and clearMotion() reverts _motionType to null and _motionRefresh to false.

Game_Battler.prototype.requestEffect = function(effectType) {
    this._effectType = effectType;
};

Game_Battler.prototype.requestMotion = function(motionType) {
    this._motionType = motionType;
};

Game_Battler.prototype.requestMotionRefresh = function() {
    this._motionRefresh = true;
};


The request functions can be similarly grouped; requestEffect() and requestMotion() take a single parameter, effectType and motionType respectively, and set their corresponding instance variables to the passed-in value. requestMotionRefresh() doesn't need a parameter, as it just sets _motionRefresh to true.

Game_Battler.prototype.select = function() {
    this._selected = true;
};

Game_Battler.prototype.deselect = function() {
    this._selected = false;
};


These functions are just wrappers to set the value of _selected to true or false.

Game_Battler.prototype.isAnimationRequested = function() {
    return this._animations.length > 0;
};

Game_Battler.prototype.isDamagePopupRequested = function() {
    return this._damagePopup;
};

Game_Battler.prototype.isEffectRequested = function() {
    return !!this._effectType;
};

Game_Battler.prototype.isMotionRequested = function() {
    return !!this._motionType;
};

Game_Battler.prototype.isWeaponAnimationRequested = function() {
    return this._weaponImageId > 0;
};

Game_Battler.prototype.isMotionRefreshRequested = function() {
    return this._motionRefresh;
};

Game_Battler.prototype.isSelected = function() {
    return this._selected;
};

Game_Battler.prototype.effectType = function() {
    return this._effectType;
};

Game_Battler.prototype.motionType = function() {
    return this._motionType;
};

Game_Battler.prototype.weaponImageId = function() {
    return this._weaponImageId;
};


These functions can pretty much all be considered as a unit since, regardless of the function name, all they're doing is returning the value of an internal variable.

Game_Battler.prototype.shiftAnimation = function() {
    return this._animations.shift();
};


In the shiftAnimation function, we return the result of calling shift() on this._animations. As you may remember, shift() removes the first element from an array and returns it.

Game_Battler.prototype.startAnimation = function(animationId, mirror, delay) {
    var data = { animationId: animationId, mirror: mirror, delay: delay };
    this._animations.push(data);
};


This function is called when the developer wants to start an animation on the battler, taking three parameters: animationId (the ID of the animation as defined in the database), mirror (a boolean determining whether the animation will be flipped horizontally or not) and delay (the time in frames by which the animation appearing will be delayed). We create an object called data containing these three parameter values under the same names, and then push data to the _animations array.

Game_Battler.prototype.startDamagePopup = function() {
    this._damagePopup = true;
};


startDamagePopup is called when we want to display damage over the battler. All we do here is set _damagePopup to true.

Game_Battler.prototype.startWeaponAnimation = function(weaponImageId) {
    this._weaponImageId = weaponImageId;
};


This function starts playing a weapon animation on the battler, taking one parameter, weaponImageId (this will correspond to the position of each respective weapon in the weapons graphic file). All we're doing here is setting _weaponImageId to the passed-in value.

Game_Battler.prototype.action = function(index) {
    return this._actions[index];
};


This function gets the action at a given index, which is passed in as a parameter. We return the element of the _actions array at the provided index.

Game_Battler.prototype.setAction = function(index, action) {
    this._actions[index] = action;
};


This function sets an action at a given index in the battler's actions array to a given action. Here, we set the element of the array at the provided index to the provided action, which will of course be an instance of the Game_Action class.

Game_Battler.prototype.numActions = function() {
    return this._actions.length;
};


This function gets the battler's number of actions, and simply returns the length of the _actions array.

Game_Battler.prototype.clearActions = function() {
    this._actions = [];
};


clearActions does exactly what it says on the tin. In this function, we simply set _actions to an empty array.

Game_Battler.prototype.result = function() {
    return this._result;
};


result() is a wrapper function to encapsulate the internal _result variable, and correspondingly all we do in it is return _result's current value.

Game_Battler.prototype.clearResult = function() {
    this._result.clear();
};


This function clears the battler's results object (itself an instance of Game_ActionResult) and we do so by calling _result's clear() function.

Game_Battler.prototype.refresh = function() {
    Game_BattlerBase.prototype.refresh.call(this);
    if (this.hp === 0) {
        this.addState(this.deathStateId());
    } else {
        this.removeState(this.deathStateId());
    }
};


In the battler refresh method, first we call the prototype's refresh function. Then, if the battler's HP is equal to 0, we kill the battler by calling addState, passing in the result of calling deathStateId() (which, as you may remember from Game_BattlerBase, returns 1). Otherwise, we remove the death state by calling removeState instead.

Game_Battler.prototype.addState = function(stateId) {
    if (this.isStateAddable(stateId)) {
        if (!this.isStateAffected(stateId)) {
            this.addNewState(stateId);
            this.refresh();
        }
        this.resetStateCounts(stateId);
        this._result.pushAddedState(stateId);
    }
};


This is the function which adds a given state to a battler, taking one parameter, stateId, which as it indicates is the ID of the state we want to add.

First, we check whether we can even add the state to the battler, by calling isStateAddable() and passing in stateId. If this is the case, then we check to make sure the battler doesn't already *have* the state, using isStateAffected() and again passing in stateId. If that is also the case, we actually add the state, using addNewState() and passing in stateId, and then we call the battler's refresh() method to make sure any immediate effects of the state are reflected.

After the check for being affected by the state, we reset the turn counts for the state by calling resetStateCounts() and passing in stateId, and finally we call pushAddedState on _result, again passing in stateId. This will add the state ID to the result object's "addedStates" property.

Game_Battler.prototype.isStateAddable = function(stateId) {
    return (this.isAlive() && $dataStates[stateId] &&
            !this.isStateResist(stateId) &&
            !this._result.isStateRemoved(stateId) &&
            !this.isStateRestrict(stateId));
};


isStateAddable checks whether the battler is able to be afflicted by a given state, taking stateId as its parameter. This is a single return statement where we have several AND logic checks: we'll return true if the battler is alive AND $dataStates contains data at the element number equal to stateId AND the battler doesn't have resistance to the state AND the battler's result doesn't indicate that the state in question is currently being removed from it AND we're not trying to add a "remove by restriction" state to the battler while it's currently restricted. If any of these conditions fail, we'll return false.

Game_Battler.prototype.onRestrict = function() {
    Game_BattlerBase.prototype.onRestrict.call(this);
    this.clearActions();
    this.states().forEach(function(state) {
        if (state.removeByRestriction) {
            this.removeState(state.id);
        }
    }, this);
};


onRestrict is an "event" function which reacts to the battler being inflicted with a state which causes an action restriction (by which it means "attack ally", "cannot move" etc.).

First, we call the prototype function, which as you may remember from when we looked at the Game_BattlerBase implementation does nothing, but that's not to say a developer couldn't change that. We then call the battler's clearActions() method, since being afflicted by a restriction will mean they can't carry out any actions that had been chosen for them prior. Then we run a forEach loop for the battler's list of states, using the states() function. We pass to the forEach a function, using the iteration variable "state". If state has "remove by restriction" checked, we remove it using its ID.

Game_Battler.prototype.removeState = function(stateId) {
    if (this.isStateAffected(stateId)) {
        if (stateId === this.deathStateId()) {
            this.revive();
        }
        this.eraseState(stateId);
        this.refresh();
        this._result.pushRemovedState(stateId);
    }
};


removeState is effectively the opposite of addState, and also takes stateId as its parameter. First we check if the battler is affected by the state. If so, and the stateId corresponds to the death state ID, we call the battler's revive() function, since it needs to come back to life if death is being removed. Then we erase the state (which, among other things, removes the turn counter for it), refresh the battler, and push the state ID to _result's removedStates property.

Game_Battler.prototype.escape = function() {
    if ($gameParty.inBattle()) {
        this.hide();
    }
    this.clearActions();
    this.clearStates();
    SoundManager.playEscape();
};


This function is run when the battler successfully escapes battle. We check whether the party is actually in battle, and if so we call the battler's hide() function (so that it disappears). Then we clear the battler's actions and states, and play the escape sound using the playEscape() function of SoundManager.

Game_Battler.prototype.addBuff = function(paramId, turns) {
    if (this.isAlive()) {
        this.increaseBuff(paramId);
        if (this.isBuffAffected(paramId)) {
            this.overwriteBuffTurns(paramId, turns);
        }
        this._result.pushAddedBuff(paramId);
        this.refresh();
    }
};


The addBuff function adds a buff for a given parameter to the battler for a given number of turns, taking the parameters "paramId" and "turns". If the battler is alive, we increase their buff level for the specified paramId. Then, if they are affected by a buff for that param (they may not be if they were already debuffed), we overwrite the buff turns for it with the provided value. Finally, we push the param ID to _result's addedBuffs property, and refresh the battler.

Game_Battler.prototype.addDebuff = function(paramId, turns) {
    if (this.isAlive()) {
        this.decreaseBuff(paramId);
        if (this.isDebuffAffected(paramId)) {
            this.overwriteBuffTurns(paramId, turns);
        }
        this._result.pushAddedDebuff(paramId);
        this.refresh();
    }
};


addDebuff is almost identical, though obviously we're calling decreaseBuff instead of increaseBuff, isDebuffAffected instead of isBuffAffected, and pushAddedDebuff instead of pushAddedBuff. Everything else works the same way, for the same reason.

Game_Battler.prototype.removeBuff = function(paramId) {
    if (this.isAlive() && this.isBuffOrDebuffAffected(paramId)) {
        this.eraseBuff(paramId);
        this._result.pushRemovedBuff(paramId);
        this.refresh();
    }
};


This function removes a buff entirely, whether it's positive or negative, again taking paramId as a parameter. If the battler is alive and affected by either a buff or a debuff on the given paramId, we erase the buff, push the param ID to _result's removedBuff property, and refresh the battler.

Game_Battler.prototype.removeBattleStates = function() {
    this.states().forEach(function(state) {
        if (state.removeAtBattleEnd) {
            this.removeState(state.id);
        }
    }, this);
};


removeBattleStates is another pretty self-explanatory function; it removes any states which don't carry onto the map.

We call states() to get the array of states on the target, and run a forEach loop on it, passing in a function as we've seen a bunch of times already. Here, we're using "state" as the iteration variable. If the state has the "Remove at Battle End" checkbox checked in its database entry, we remove it via removeState, passing in its ID.

I'm not sure if I've explained this before, but the "this" being passed as a second parameter to the forEach tells it that when we refer to "this" in the call to removeState, we mean the battler. If we didn't have that, it would consider "this" to be the current state inside the loop, and obviously states don't have a removeState function so we'd get an error.

Game_Battler.prototype.removeAllBuffs = function() {
    for (var i = 0; i < this.buffLength(); i++) {
        this.removeBuff(i);
    }
};


This function is called removeAllBuffs. Guess what it does? If you guessed "it removes all buffs, you win a cookie!"

To do this, we have a for loop, using iteration variable "i" as we pretty much always do. The loop condition is i being less than the battler's buffLength() (which unless you change the buffs array, will always be 8) and then inside the loop we call removeBuff, passing in i. This will run through each param ID and remove any buffs or debuffs on it.

Game_Battler.prototype.removeStatesAuto = function(timing) {
    this.states().forEach(function(state) {
        if (this.isStateExpired(state.id) && state.autoRemovalTiming === timing) {
            this.removeState(state.id);
        }
    }, this);
};


This function removes any states which have an "Auto-removal Timing" setting in the database. It takes one parameter, "timing", which will be 1 for "Action End", and 2 for "Turn End".

We call the battler's states() function to get the states array, and run a forEach loop on it using state as our iteration variable, as before. In the loop, if the state has expired (reached 0 turns remaining) AND the state's autoRemovalTiming is equal to the passed-in timing value, we remove it with removeState passing in the state's ID.

Game_Battler.prototype.removeBuffsAuto = function() {
    for (var i = 0; i < this.buffLength(); i++) {
        if (this.isBuffExpired(i)) {
            this.removeBuff(i);
        }
    }
};


This function removes buffs automatically when they expire, via much the same logic as the previous function. As with removeAllBuffs we have a for loop running from 0 to the length of the buffs array, and if the buff has expired, we call removeBuff passing in the current param ID being looped through. Shouldn't be anything surprising there at this point.

Game_Battler.prototype.removeStatesByDamage = function() {
    this.states().forEach(function(state) {
        if (state.removeByDamage && Math.randomInt(100) < state.chanceByDamage) {
            this.removeState(state.id);
        }
    }, this);
};


This function is for removing states set to "Remove by Damage". As with the other functions above, we run a forEach loop on the states array passing in a function with iteration variable state. If the state has the Remove by Damage setting AND a random integer from 0 to 99 is less than the state's chanceByDamage percentage, we remove it. Again, we pass "this" as a second parameter to forEach to ensure it uses the battler when calling removeState rather than the iterated state object.

Game_Battler.prototype.makeActionTimes = function() {
    return this.actionPlusSet().reduce(function(r, p) {
        return Math.random() < p ? r + 1 : r;
    }, 1);
};


This function determines how many times a given action is going to execute.

If you think back to Game_BattlerBase last episode, actionPlusSet() returns the percentage values of each "Action Times+" trait applied to the battler as an array. We're taking that array and calling reduce on it with a function argument, passing in r as the accumulator and p as the current value. Then in that function, we're returning r + 1 if a random number from 0 to 1 is less than p, or r otherwise, starting with an initial value of 1.

It's been a while since I explained reduce, if I ever did, so here's a breakdown. It iterates through an array using an accumulator (running total) and current iterated value (it has two other possible parameters but we don't need them here). Each run through the loop adds to the accumulator, and when the loop ends the total is returned. If no initial value is set, it starts at 0.

Let's say there are 3 Action Times+ traits on the battler: one for 20%, one for 50% and one for 70%. The array we get from actionPlusSet will be . The reduce function starts at 1, and goes through each array element. It will generate a random number between 0 and 1 for each, and if it's less than the current value from the array, it'll add 1 to the final total.

Game_Battler.prototype.makeActions = function() {
    this.clearActions();
    if (this.canMove()) {
        var actionTimes = this.makeActionTimes();
        this._actions = [];
        for (var i = 0; i < actionTimes; i++) {
            this._actions.push(new Game_Action(this));
        }
    }
};


This function fills up the battler's actions list.

First, we call clearActions() to remove any that are already there. Then if the battler can move (is visible, is not restricted) we set a temporary variable called actionTimes to the result of calling makeActionTimes() (which we just looked at). Then we set _actions to an empty array...which is honestly kind of perplexing, since we literally just did that by calling clearActions and nothing's done anything to populate it since. We have a for loop running from 0 to actionTimes, where we push a new instance of Game_Action to the _actions array, passing in "this" as the subject argument to Game_Action's constructor.

Game_Battler.prototype.speed = function() {
    return this._speed;
};


speed() is just another wrapped function to encapsulate the internal _speed variable, nothing new or interesting there.

Game_Battler.prototype.makeSpeed = function() {
    this._speed = Math.min.apply(null, this._actions.map(function(action) {
        return action.speed();
    })) || 0;
};


makeSpeed determines the lowest speed value out of the battler's actions list.

We set the internal _speed variable to either: the result of applying Math.min, passing in null for the "this" value, and a map function for the _actions array, using iteration variable "action"; or 0. Inside that map function we return the current action's speed(), which reflects the "Speed" setting under the Invocation section of the Skills/Items database entry.

You may have forgotten the reason behind this, so I'll do a quick refresher. Math.min, by itself, expects distinct values, but we have an array, so we have to use .apply. The first argument will function as "this" if we use that in the function supplied to the second argument (which is why it's "null" here, since we don't need it). .map iterates through an array, runs a function on each element, and returns a new array consisting of the results of that function. So here we're basically taking the array of Game_Action instances in _actions and turning it into an array containing only the speed values of each action.

The || 0 is just a failsafe to account for the Math.min not returning a number.

Game_Battler.prototype.currentAction = function() {
    return this._actions[0];
};


This function returns the battler's current action; to do so, we simply return the element of _actions at index 0.

Game_Battler.prototype.removeCurrentAction = function() {
    this._actions.shift();
};


This function removes the current action. Here, we're calling shift() on this._actions, which as we've seen in other functions removes the first element of the array and returns it. Since we don't need to do anything with the removed action, the removal part is the only part that matters here.

Game_Battler.prototype.setLastTarget = function(target) {
    if (target) {
        this._lastTargetIndex = target.index();
    } else {
        this._lastTargetIndex = 0;
    }
};


In this function, we set the battler's last target, taking "target" as a parameter; this will also be an instance of Game_Battler.

If the target exists, we set _lastTargetIndex to the index of the target. Otherwise, we set it to 0. Fairly straightforward.

Game_Battler.prototype.forceAction = function(skillId, targetIndex) {
    this.clearActions();
    var action = new Game_Action(this, true);
    action.setSkill(skillId);
    if (targetIndex === -2) {
        action.setTarget(this._lastTargetIndex);
    } else if (targetIndex === -1) {
        action.decideRandomTarget();
    } else {
        action.setTarget(targetIndex);
    }
    this._actions.push(action);
};


This function is how we force the battler to take a particular action rather than choosing it for themselves. We're taking two parameters here: skillId, the ID of the skill we want to force the battler to execute; and targetIndex, the index of the desired target. Note that this doesn't need to be an integer, as we'll see in a second.

First, we clear the battler's actions, so that the forced action will be the only thing they do. Then, we create a new Game_Action, using "this" as the subject and true for the "forcing" argument. We set the action's skill to the passed-in skillId.

If the target index passed in was -2, we set the action's target to the battler's _lastTargetIndex; if it's -1, we decide on a target at random; otherwise, if it's an integer, we set the target to the value of targetIndex. Once the target is set, we push the new action to the _actions array.

Game_Battler.prototype.useItem = function(item) {
    if (DataManager.isSkill(item)) {
        this.paySkillCost(item);
    } else if (DataManager.isItem(item)) {
        this.consumeItem(item);
    }
};


This function processes the battler using an item (which covers skills and actual game items) taking "item" as a parameter, which will be an instance of Game_Item.

If DataManager's isSkill function returns true with item as the argument, we know it's a skill we're dealing with, so we all paySkillCost passing in item to remove any required resources from the battler. Otherwise, if the isItem function returns true, we know it's an item, so we call consumeItem to remove one of that item from the party's inventory if it's a consumable.

Game_Battler.prototype.consumeItem = function(item) {
    $gameParty.consumeItem(item);
};


This function consumes a given item, and is basically just a wrapper as all we're doing here is calling the consumeItem function of $gameParty and passing in the same item.

Game_Battler.prototype.gainHp = function(value) {
    this._result.hpDamage = -value;
    this._result.hpAffected = true;
    this.setHp(this.hp + value);
};


The gainHp function does what it says on the tin, and takes "value" as a parameter, which is a number representing how much HP the battler should gain. It can be negative, in which case the battler will take damage instead.

First, we set _result's hpDamage property to the negative of value (because if the value passed in is positive, it's healing and so we're logically taking negative damage; and if it's negative, we're gaining negative HP which is positive damage), and hpAffected to true to indicate that the HP property has been affected by whatever action is taking place. Then, we set the battler's HP to its current value plus the passed-in value.

Game_Battler.prototype.gainMp = function(value) {
    this._result.mpDamage = -value;
    this.setMp(this.mp + value);
};


This function is almost identical to gainHp, but we don't have an MP equivalent of hpAffected so that line is gone. It's otherwise the same, but with mpDamage instead of hpDamage, setMp instead of setHp, and this.mp rather than this.hp.

Game_Battler.prototype.gainTp = function(value) {
    this._result.tpDamage = -value;
    this.setTp(this.tp + value);
};


And the function for gaining TP is literally identical to MP but with the TP-related properties and functions instead.

Game_Battler.prototype.gainSilentTp = function(value) {
    this.setTp(this.tp + value);
};


Unlike HP and MP, the engine has a function which "silently" gains TP, which is to say it adds TP to the battler without the result reflecting it and without showing it. This is for TP gains from taking damage, regenerate effects and the innate TP gain set for the skill/item in the Invocation section. It's mostly identical to gainTp, the only difference is that we don't set _result's tpDamage property.

Game_Battler.prototype.initTp = function() {
    this.setTp(Math.randomInt(25));
};


This function initialises the battler's TP value, by setting it to a random integer from 0 to 24. There are several plugins which change this behaviour, as many developers dislike the random nature of it. If you're one of them, and wish to make your own code changes, this is the function you want to change with your plugin.

Game_Battler.prototype.clearTp = function() {
    this.setTp(0);
};


This function clears the battler's TP, and simply sets its value to 0.

Game_Battler.prototype.chargeTpByDamage = function(damageRate) {
    var value = Math.floor(50 * damageRate * this.tcr);
    this.gainSilentTp(value);
};


This function charges the battler's TP upon taking damage, taking damageRate as a parameter (as we'll see, this is based on the percentage of their HP they suffered in damage).

We set a temporary variable called value to the floor of 50 multiplied by damageRate multiplied by the battler's TCR property (TP Charge Rate) and then gain silent TP equal to the calculated value.

For example, if we pass in 0.29 (the battler took 29% of their HP in damage from an attack) and they have a TCR of 1.2 because they get 20% increased TP charge from an equipped accessory, the equation becomes Math.floor(50 * 0.29 * 1.2), which will result in 17. Therefore the battler will gain 17 TP from this attack.

This is an interesting balance observation; obviously in battle the less damage you take the better, but this also means that characters with high defences will charge less TP from being attacked, resulting in them having fewer resources for special abilities.

Game_Battler.prototype.regenerateHp = function() {
    var value = Math.floor(this.mhp * this.hrg);
    value = Math.max(value, -this.maxSlipDamage());
    if (value !== 0) {
        this.gainHp(value);
    }
};


This function is used to regenerate the battler's HP.

First, we set a temp variable value to the floor of the battler's max HP multiplied by their HRG (HP Regeneration Rate). We then set value to the max between itself and the negative of the maximum amount of slip damage (which we'll see in a second). If the value is then not equal to 0, the battler gains hP equal to the calculated value.

As with gainHp, this function is used whether the battler is going to gain or lose HP, which is why it works the way it does. With states like "poison", the effect is achieved by giving the afflicted battler a negative HRG percentage, which results in them losing HP in the regenerate phase.

For example, if the battler's overall HRG is -20%, and their max HP is 500, value will initially be (500 * -0.2) for a result of -100, so the target will gain -100 HP, equating to taking 100 damage.

Game_Battler.prototype.maxSlipDamage = function() {
    return $dataSystem.optSlipDeath ? this.hp : Math.max(this.hp - 1, 0);
};


This function determines the maximum amount of slip damage the battler is able to take. In our return value, we check the optSlipDeath property of $dataSystem (which maps to the "Knockout by Skip Damage" checkbox in the system tab of the database): if it's true, we return the battler's hp property. If false, we return the maximum value out of the battler's HP - 1, and 0. This means that if the battler can't die from slip damage, the maximum damage they can take is 1 less than whatever their current HP is (which obviously prevents it from becoming 0)

Game_Battler.prototype.regenerateMp = function() {
    var value = Math.floor(this.mmp * this.mrg);
    if (value !== 0) {
        this.gainMp(value);
    }
};


This is the function for regenerating MP, and is functionally identical to regenerateHp with the exception of not having the max slip damage check (since you don't take slip damage to MP).

Game_Battler.prototype.regenerateTp = function() {
    var value = Math.floor(100 * this.trg);
    this.gainSilentTp(value);
};


This function regenerates TP. We set temp variable value to 100 multiplied by the battler's trg (TP Regen Rate) property, rounded down. Then, we call gainSilentTp, passing in value. It's gainSilentTp rather than gainTp so that there's no message for the TP gain.

Game_Battler.prototype.regenerateAll = function() {
    if (this.isAlive()) {
        this.regenerateHp();
        this.regenerateMp();
        this.regenerateTp();
    }
};


This is just a wrapped function for the various regen functions: if the battler is alive (obviously, since a dead battler isn't going to regenerate anything), we call regenerateHp, regenerateMp and then regenerateTp.

Game_Battler.prototype.onBattleStart = function() {
    this.setActionState('undecided');
    this.clearMotion();
    if (!this.isPreserveTp()) {
        this.initTp();
    }
};


This function is called when battle starts. First, we call the battler's setActionState function passing in 'undecided' (we'll see the implementation of this in a couple of minutes) and then clearMotion(). Finally, is isPreserveTp() returns false (meaning the battler doesn't have the "Preserve TP" special flag), we call initTp().

Game_Battler.prototype.onAllActionsEnd = function() {
    this.clearResult();
    this.removeStatesAuto(1);
    this.removeBuffsAuto();
};


This function is called after all of the battler's turn actions have been decided. First we call clearResult() to empty their results object, then we call removeStatesAuto() passing in 1 as the parameter (for "Action End"), and then we call removeBuffsAuto() to remove any expired buffs.

Game_Battler.prototype.onTurnEnd = function() {
    this.clearResult();
    this.regenerateAll();
    if (!BattleManager.isForcedTurn()) {
        this.updateStateTurns();
        this.updateBuffTurns();
    }
    this.removeStatesAuto(2);
};


This function is called when the turn ends. Again, we call clearResult(), and also regenerateAll() to process HP, MP and TP regeneration. Then, if the isForcedTurn() function of BattleManager returns false (meaning the turn wasn't forced, or in other words the battler chose their own actions), we call updateStateTurns() and updateBuffTurns() to deduct a turn from each state and buff's duration. Then, we call removeStatesAuto, passing in 2 to remove the ones with their auto-removal timing set to "Turn End".

One interesting takeaway from this is that a forced turn (via the Force Action command, for example) doesn't affect state/buff durations for the purposes of auto-removal.

Game_Battler.prototype.onBattleEnd = function() {
    this.clearResult();
    this.removeBattleStates();
    this.removeAllBuffs();
    this.clearActions();
    if (!this.isPreserveTp()) {
        this.clearTp();
    }
    this.appear();
};


This function is called when the battle ends. We call clearResult(), then removeBattleStates() to remove any state set to "Remove at Battle End", then removeAllBuffs() to remove any buffs present on the battler, then clearActions() to clear out the battler's actions queue. If isPreserveTp() returns false, we call clearTp() to set it back to 0 (otherwise it'll remain at its current value for the next battle). And finally, we call appear() (which will bring back any party members who successfully escaped).

Game_Battler.prototype.onDamage = function(value) {
    this.removeStatesByDamage();
    this.chargeTpByDamage(value / this.mhp);
};


This function is called when the battler takes damage, and takes damage as a parameter (which surprising nobody is the amount of damage they took). First, we call removeStatesByDamage(), which processes removal of states set to "Remove by Damage". Then, we call chargeTpByDamage, passing in value divided by the battler's max HP (as we saw when we broke this function down, this increases TP based on the percentage of max HP the battler took in damage).

Game_Battler.prototype.setActionState = function(actionState) {

this._actionState = actionState;
this.requestMotionRefresh();
};


This function sets the battler's action state (the ones used by the default engine as 'undecided', which we saw above, 'inputting', 'waiting' and 'acting'), and takes actionState as a parameter. We set the _actionState property to the value passed in, and then call requestMotionRefresh() to indicate that we want to refresh the battler's motion.

Game_Battler.prototype.isUndecided = function() {

return this._actionState === 'undecided';
};

Game_Battler.prototype.isInputting = function() {
return this._actionState === 'inputting';
};

Game_Battler.prototype.isWaiting = function() {
return this._actionState === 'waiting';
};

Game_Battler.prototype.isActing = function() {
return this._actionState === 'acting';
};


This set of functions checks for _actionState being a particular value; there's a wrapped function for each possibility. isUndecided returns true if _actionState is 'undecided'; isInputting returns true if it's 'inputting'; isWaiting returns true if it's 'waiting' and isActing returns true if it's 'acting'.

Game_Battler.prototype.isChanting = function() {
    if (this.isWaiting()) {
        return this._actions.some(function(action) {
            return action.isMagicSkill();
        });
    }
    return false;
};


This function determines whether the battler is chanting (preparing to cast magic). We check whether isWaiting() returns true (meaning the battler's action state is 'waiting') and if so we call .some on the _actions array, providing it with a function that returns the result of its isMagicSkill() function. Or to put it another way, we'll return true if any of the actions in the battler's queue have a type included in the "Magic Skills" part of the system tab of the database. Otherwise, if the battler is not waiting, we return false.

Game_Battler.prototype.isGuardWaiting = function() {
    if (this.isWaiting()) {
        return this._actions.some(function(action) {
            return action.isGuard();
        });
    }
    return false;
};


This function determines whether the battler is going to be guarding; it's functionally identical to isChanting, only we're checking isGuard() instead of isMagicSkill().

Game_Battler.prototype.performActionStart = function(action) {
    if (!action.isGuard()) {
        this.setActionState('acting');
    }
};


This function is called when the battler starts performing an action, taking action as a parameter. If the action's isGuard() function returns true (if the action's skill ID matches the guard skill ID) we call setActionState, passing in the string 'acting'.

Game_Battler.prototype.performAction = function(action) {
};

Game_Battler.prototype.performActionEnd = function() {
    this.setActionState('done');
};

Game_Battler.prototype.performDamage = function() {
};

Game_Battler.prototype.performMiss = function() {
    SoundManager.playMiss();
};

Game_Battler.prototype.performRecovery = function() {
    SoundManager.playRecovery();
};

Game_Battler.prototype.performEvasion = function() {
    SoundManager.playEvasion();
};

Game_Battler.prototype.performMagicEvasion = function() {
    SoundManager.playMagicEvasion();
};

Game_Battler.prototype.performCounter = function() {
    SoundManager.playEvasion();
};

Game_Battler.prototype.performReflection = function() {
    SoundManager.playReflection();
};

Game_Battler.prototype.performSubstitute = function(target) {
};

Game_Battler.prototype.performCollapse = function() {
};


The last set of functions are for performing various types of action, and in the Game_Battler class are mostly blank (the functionality will be expanded in the child classes). performAction() takes action as a parameter, and in this class does nothing. performActionEnd() is called after an action is processed, and here just sets the action state to 'done'. performDamage is called when taking damage, and here is blank. performMiss is called when an attack misses, and here calls the playMiss() function of SoundManager. performRecovery is called when the battler is the target of a recovery effect, and calls playRecovery() in SoundManager. performEvasion is the same, but for evading attacks. performMagicEvasion is the same for evading magic attacks, performCounter the same but for countering an attack, performReflection for reflecting magic, performSubstitute for substituting for an ally, and finally performCollapse when the battler is killed.

And so ends our breakdown of Game_Battler! This was quite a big one, so I think I'll leave it there. Next time we'll look at the more specific implementations in Game_Actor and Game_Enemy.

Until next time!