//=============================================================================
// Yanfly Engine Plugins - Element Core
// YEP_ElementCore.js
//=============================================================================

var Imported = Imported || {};
Imported.YEP_ElementCore = true;

var Yanfly = Yanfly || {};
Yanfly.Ele = Yanfly.Ele || {};
Yanfly.Ele.version = 1.03;

//=============================================================================
 /*:
 * @plugindesc v1.02 Manage the way elements work in this game from
 * absorbing elements, reflecting elements, and more!
 * @author Yanfly Engine Plugins
 *
 * @param Multi-Element Rulings
 * @type select
 * @option Lowest
 * @value 0
 * @option Add
 * @value 1
 * @option Multiply
 * @value 2
 * @option Highest
 * @value 3
 * @option Average
 * @value 4
 * @desc If a skill/item has multiple elements, then use these rules:
 * 0 - Lowest; 1 - Add; 2 - Multiply; 3 - Highest; 4 - Average
 * @default 2
 *
 * @help
 * ============================================================================
 * Introduction
 * ============================================================================
 *
 * Elemental control in RPG Maker MV is pretty lacking. The calculation of how
 * multiple elements are handled aren't very clear nor are they too intuitive
 * when it comes to certain aspects. This plugin also gives way to skills and
 * items having more than one element, battlers being able to absorb, reflect,
 * amplify elemental damage, and more!
 *
 * * Note: If you are using the Battle Engine Core, place this plugin under the
 * Battle Engine Core in the plugin list for additional features.
 *
 * * Note: If you are using the Damage Core, place this plugin underneath the
 * Damage Core plugin in the list for maximum compatibility.
 *
 * ============================================================================
 * Notetags
 * ============================================================================
 *
 * Use these notetags if you wish to modify various aspects of elements for
 * your database objects.
 *
 * Skill and Item Notetags
 *
 *   <Bypass Element Reflect>
 *   - Allows this skill/item to ignore elemental reflect properties. This will
 *   not bypass reflect properties as a whole, however.
 *
 *   <Multiple Elements: x>
 *   <Multiple Elements: x to y>
 *   <Multiple Elements: x, x, x>
 *   <Multiple Elements: name, name, name>
 *   - This adds elements x (or name) to the skill/item in addition to the
 *   skill/item's current element. Skills and items with multiple elements will
 *   follow the Multi-Element Rule when calculating damage rate. Insert more of
 *   this notetag to insert more elements.
 *
 *   <Multi-Element Rule: Lowest>
 *   <Multi-Element Rule: Add>
 *   <Multi-Element Rule: Multiply>
 *   <Multi-Element Rule: Highest>
 *   <Multi-Element Rule: Average>
 *   - This allows you to set the rule for this skill/item if it has multiple
 *   elements. Either the lowest rate, the additive sum of all rates, the
 *   multiplicative product of all rates, or the highest rate will be used. If
 *   average is used, it will be the average of all element rates.
 *
 * Actor, Class, Enemy, Weapon, Armor, and State Notetags:
 *
 *   <Element Absorb: x>
 *   <Element Absorb: x, x, x>
 *   <Element Absorb: name>
 *   <Element Absorb: name, name, name>
 *   - Causes element x to be absorbed and heals the battler. When an element
 *   is absorbed, the rate goes down by 200% instead of being just an inverse.
 *   This is so that battlers that are originally resistant to the element will
 *   absorb more of the element while battlers that are originally vulnerable
 *   to the element will absorb less of the element. The minimum amount
 *   absorbed is 0.01%.
 *
 *   <Element Reflect x: +y%>
 *   <Element Reflect x: -y%>
 *   <Element Reflect name: +y%>
 *   <Element Reflect name: -y%>
 *   - Increases or decreases the rate to reflect element x by y%. If a skill
 *   or item has multiple elements, the reflect rate is added for each element
 *   used by the skill/item.
 *
 *   <Element Magnify x: +y%>
 *   <Element Magnify x: -y%>
 *   <Element Magnify name: +y%>
 *   <Element Magnify name: -y%>
 *   - If the user performs a skill or item that utilizes element x (or name),
 *   increase or decrease its damage by y%. If a skill or item has multiple
 *   elements, the rate is increased additively for each element and adjusted
 *   multiplicatively with base rate. This bottoms out at 0%.
 *
 *   <Element Amplify x: +y%>
 *   <Element Amplify x: -y%>
 *   <Element Amplify name: +y%>
 *   <Element Amplify name: -y%>
 *   - If the user performs a skill or item that utilizes element x (or name),
 *   increase or decrease its damage by y%. If a skill or item has multiple
 *   elements, the rate is increased additively for each element and adjusted
 *   additively for base rate.
 *
 *   <Element Null>
 *   - This will cause the battler to not have elemental attacks when using
 *   skills and items. However, this will not bypass the 'Force Element' action
 *   sequence effect.
 *
 *   <Force Element x Rate: y%>
 *   <Force Element name Rate: y%>
 *   - This forces the battler's elemental rate for x (or named) to be y%. This
 *   will work in a priority setting of states (highest priority to lowest),
 *   equips (first to last), then class, then actor/enemy if more than one
 *   notetag is used for the same element. If y is negative, the element is
 *   absorbed.
 *
 * ============================================================================
 * Yanfly Engine Plugins - Battle Engine Extension - Action Sequence Commands
 * ============================================================================
 *
 * If you have YEP_BattleEngineCore.js installed with this plugin located
 * underneath it in the Plugin Manager, you can make use of these extra
 * damage related action sequences.
 *
 *=============================================================================
 * ADD ELEMENT: X
 * ADD ELEMENT: X, X, X
 * ADD ELEMENT: NAME
 * ADD ELEMENT: NAME, NAME, NAME
 *- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 * This will add more elements to the currently existing elements. This will
 * not include forced elements.
 *- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 * Usage Example: add element: 4
 *                add element: 5, 6, 7
 *                add element: fire
 *                add element: ice, wind, water
 *=============================================================================
 *
 *=============================================================================
 * CLEAR ELEMENT
 *- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 * This will clear any ___ Element action sequence settings and revert element
 * calculation back to normal.
 *- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 * Usage Example: clear element
 *=============================================================================
 *
 *=============================================================================
 * FORCE ELEMENT: X
 * FORCE ELEMENT: X, X, X
 * FORCE ELEMENT: NAME
 * FORCE ELEMENT: NAME, NAME, NAME
 *- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 * This will override elemental settings for elemental damage rate calculations
 * except for customized calculations that aim at specific elements.  This will
 * ignore all other elements.
 *- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 * Usage Example: force element: 4
 *                force element: 5, 6, 7
 *                force element: fire
 *                force element: ice, wind, water
 *=============================================================================
 *
 *=============================================================================
 * NULL ELEMENT
 *- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 * This will force the elements to null. Calculated elemental damage will
 * always return neutral rate. Using this will clear the Force Element action
 * sequence effect.
 *- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 * Usage Example: null element
 *=============================================================================
 *
 * ============================================================================
 * Changelog
 * ============================================================================
 *
 * Version 1.03:
 * - Updated for RPG Maker MV version 1.5.0.
 *
 * Version 1.02:
 * - Added <Element x Magnify: +y%>, <Element x Magnify: -y%> notetags. These
 * notetags different from the Amplify counterparts in a way where the Amplify
 * notetags will shift the element rate additively. These will alter the rate
 * multiplicatively.
 *
 * Version 1.01:
 * - Optimized element rate calculation where if no elements are present, then
 * damage rate will default to 100%.
 *
  * Version 1.00:
 * - Finished Plugin!
 */
//=============================================================================

//=============================================================================
// Parameter Variables
//=============================================================================

Yanfly.Parameters = PluginManager.parameters('YEP_ElementCore');
Yanfly.Param = Yanfly.Param || {};

Yanfly.Param.EleMultiRule = Number(Yanfly.Parameters['Multi-Element Rulings']);

//=============================================================================
// DataManager
//=============================================================================

Yanfly.Ele.DataManager_isDatabaseLoaded = DataManager.isDatabaseLoaded;
DataManager.isDatabaseLoaded = function() {
  if (!Yanfly.Ele.DataManager_isDatabaseLoaded.call(this)) return false;

  if (!Yanfly._loaded_YEP_ElementCore) {
    this.processElementNotetagsSys($dataSystem);
    this.processElementNotetags1($dataSkills);
    this.processElementNotetags1($dataItems);
    this.processElementNotetags2($dataActors);
    this.processElementNotetags2($dataClasses);
    this.processElementNotetags2($dataEnemies);
    this.processElementNotetags2($dataWeapons);
    this.processElementNotetags2($dataArmors);
    this.processElementNotetags2($dataStates);
    Yanfly._loaded_YEP_ElementCore = true;
  }
  
  return true;
};

DataManager.processElementNotetagsSys = function(group) {
  Yanfly.ElementIdRef = {};
  for (var i = 1; i < group.elements.length; ++i) {
    var name = group.elements[i].toUpperCase();
    name = name.replace(/\\I\[(\d+)\]/gi, '');
    Yanfly.ElementIdRef[name] = i;
  }
};

DataManager.processElementNotetags1 = function(group) {
  var noteA1 = /<MULTIPLE ELEMENTS:[ ]*(\d+(?:\s*,\s*\d+)*)>/i;
  var noteA2 = /<MULTIPLE ELEMENTS:[ ](\d+)[ ](?:THROUGH|to)[ ](\d+)>/i;
  var noteA3 = /<MULTIPLE ELEMENTS:[ ](.*)>/i;
  for (var n = 1; n < group.length; n++) {
    var obj = group[n];
    var notedata = obj.note.split(/[\r\n]+/);

    obj.elementMultiRule = Yanfly.Param.EleMultiRule;
    obj.multipleElements = [];
    obj.bypassElementReflect = false;

    for (var i = 0; i < notedata.length; i++) {
      var line = notedata[i];
      if (line.match(/<MULTI-ELEMENT RULE:[ ]LOWEST>/i)) {
        obj.elementMultiRule = 0;
      } else if (line.match(/<MULTI-ELEMENT RULE:[ ]ADD>/i)) {
        obj.elementMultiRule = 1;
      } else if (line.match(/<MULTI-ELEMENT RULE:[ ]MULTIPLY>/i)) {
        obj.elementMultiRule = 2;
      } else if (line.match(/<MULTI-ELEMENT RULE:[ ]HIGHEST>/i)) {
        obj.elementMultiRule = 3;
      } else if (line.match(/<MULTI-ELEMENT RULE:[ ]AVERAGE>/i)) {
        obj.elementMultiRule = 4;
      } else if (line.match(/<MULTI-ELEMENT RULE:[ ](.*)>/i)) {
        obj.elementMultiRule = String(RegExp.$1);
      } else if (line.match(noteA1)) {
        var array = JSON.parse('[' + RegExp.$1.match(/\d+/g) + ']');
        obj.multipleElements = obj.multipleElements.concat(array);
      } else if (line.match(noteA2)) {
        var range = Yanfly.Util.getRange(parseInt(RegExp.$1),
          parseInt(RegExp.$2));
        obj.multipleElements = obj.multipleElements.concat(range);
      } else if (line.match(noteA3)) {
        var text = String(RegExp.$1);
        var array = text.split(',');
        var length = array.length;
        for (var j = 0; j < length; ++j) {
          var name = array[j].toUpperCase().trim();
          if (Yanfly.ElementIdRef[name]) {
            var id = Yanfly.ElementIdRef[name];
            obj.multipleElements.push(id);
          }
        }
      } else if (line.match(/<(?:BYPASS ELEMENT REFLECT)>/i)) {
        obj.bypassElementReflect = true;
      }
    }
  }
};

DataManager.processElementNotetags2 = function(group) {
  var noteA1 = /<(?:ELEMENT ABSORB):[ ](\d+)[ ](?:THROUGH|to)[ ](\d+)>/i;
  var noteB1 = /<(?:ELEMENT REFLECT)[ ](\d+):[ ]([\+\-]\d+)([%％])>/i;
  var noteB2 = /<(?:ELEMENT REFLECT)[ ](.*):[ ]([\+\-]\d+)([%％])>/i;
  var noteC1 = /<(?:ELEMENT AMPLIFY)[ ](\d+):[ ]([\+\-]\d+)([%％])>/i;
  var noteC2 = /<(?:ELEMENT AMPLIFY)[ ](.*):[ ]([\+\-]\d+)([%％])>/i;
  var noteC3 = /<(?:ELEMENT MAGNIFY)[ ](\d+):[ ]([\+\-]\d+)([%％])>/i;
  var noteC4 = /<(?:ELEMENT MAGNIFY)[ ](.*):[ ]([\+\-]\d+)([%％])>/i;
  var noteD1 = /<FORCE ELEMENT[ ](\d+)[ ]RATE:[ ](\d+)([%％])>/i;
  var noteD2 = /<FORCE ELEMENT[ ](\d+)[ ]RATE:[ ]-(\d+)([%％])>/i;
  var noteD3 = /<FORCE ELEMENT[ ](.*)[ ]RATE:[ ](\d+)([%％])>/i;
  var noteD4 = /<FORCE ELEMENT[ ](.*)[ ]RATE:[ ]-(\d+)([%％])>/i;
  for (var n = 1; n < group.length; n++) {
    var obj = group[n];
    var notedata = obj.note.split(/[\r\n]+/);

    obj.elementAbsorb = [];
    obj.elementReflect = {};
    obj.elementAmplify = {};
    obj.elementMagnify = {};
    obj.elementNull = false;
    obj.elementForcedRate = {};

    for (var i = 0; i < notedata.length; i++) {
      var line = notedata[i];
      if (line.match(/<(?:ELEMENT ABSORB):[ ]*(\d+(?:\s*,\s*\d+)*)>/i)) {
        var array = JSON.parse('[' + RegExp.$1.match(/\d+/g) + ']');
        obj.elementAbsorb = obj.elementAbsorb.concat(array);
      } else if (line.match(noteA1)) {
        var range = Yanfly.Util.getRange(parseInt(RegExp.$1),
          parseInt(RegExp.$2));
        obj.elementAbsorb = obj.elementAbsorb.concat(range);
      } else if (line.match(/<(?:ELEMENT ABSORB):[ ](.*)>/i)) {
        var text = String(RegExp.$1);
        var array = text.split(',');
        var length = array.length;
        for (var j = 0; j < length; ++j) {
          var name = array[j].toUpperCase().trim();
          if (Yanfly.ElementIdRef[name]) {
            var id = Yanfly.ElementIdRef[name];
            obj.elementAbsorb.push(id);
          }
        }
      } else if (line.match(noteB1)) {
        var elementId = parseInt(RegExp.$1);
        var rate = parseFloat(RegExp.$2 * 0.01);
        obj.elementReflect[elementId] = rate;
      } else if (line.match(noteB2)) {
        var name = String(RegExp.$1).toUpperCase().trim();
        var rate = parseFloat(RegExp.$2 * 0.01);
        if (Yanfly.ElementIdRef[name]) {
          var id = Yanfly.ElementIdRef[name];
          obj.elementReflect[id] = rate;
        }
      } else if (line.match(noteC1)) {
        var elementId = parseInt(RegExp.$1);
        var rate = parseFloat(RegExp.$2 * 0.01);
        obj.elementAmplify[elementId] = rate;
      } else if (line.match(noteC2)) {
        var name = String(RegExp.$1).toUpperCase().trim();
        var rate = parseFloat(RegExp.$2 * 0.01);
        if (Yanfly.ElementIdRef[name]) {
          var id = Yanfly.ElementIdRef[name];
          obj.elementAmplify[id] = rate;
        }
      } else if (line.match(noteC3)) {
        var elementId = parseInt(RegExp.$1);
        var rate = parseFloat(RegExp.$2 * 0.01);
        obj.elementMagnify[elementId] = rate;
      } else if (line.match(noteC4)) {
        var name = String(RegExp.$1).toUpperCase().trim();
        var rate = parseFloat(RegExp.$2 * 0.01);
        if (Yanfly.ElementIdRef[name]) {
          var id = Yanfly.ElementIdRef[name];
          obj.elementMagnify[id] = rate;
        }
      } else if (line.match(/<(?:ELEMENT NULL)>/i)) {
        obj.elementNull = true;
      } else if (line.match(noteD1)) {
        var elementId = parseInt(RegExp.$1);
        var rate = parseFloat(RegExp.$2 * 0.01);
        obj.elementForcedRate[elementId] = rate;
      } else if (line.match(noteD2)) {
        var elementId = parseInt(RegExp.$1);
        var rate = parseFloat(RegExp.$2 * 0.01);
        obj.elementForcedRate[elementId] = rate * -1;
      } else if (line.match(noteD3)) {
        var name = String(RegExp.$1).toUpperCase().trim();
        var rate = parseFloat(RegExp.$2 * 0.01);
        if (Yanfly.ElementIdRef[name]) {
          var id = Yanfly.ElementIdRef[name];
          obj.elementForcedRate[id] = rate;
        }
      } else if (line.match(noteD4)) {
        var name = String(RegExp.$1).toUpperCase().trim();
        var rate = parseFloat(RegExp.$2 * 0.01);
        if (Yanfly.ElementIdRef[name]) {
          var id = Yanfly.ElementIdRef[name];
          obj.elementForcedRate[id] = rate * -1;
        }
      }
    }
  }
};

//=============================================================================
// BattleManager
//=============================================================================

if (Imported.YEP_BattleEngineCore) {

Yanfly.Ele.BattleManager_processActionSequence =
    BattleManager.processActionSequence;
BattleManager.processActionSequence = function(actionName, actionArgs) {
  // ADD ELEMENT: X
  if (actionName === 'ADD ELEMENT') {
    return this.actionAddElement(actionArgs);
  }
  // CLEAR ELEMENT
  if (actionName === 'CLEAR ELEMENT') {
    return this.actionClearElement();
  }
  // FORCE ELEMENT: X
  if (actionName === 'FORCE ELEMENT') {
    return this.actionForceElement(actionArgs);
  }
  // FORCE ELEMENT
  if (actionName === 'NULL ELEMENT') {
    return this.actionNullElement();
  }
  return Yanfly.Ele.BattleManager_processActionSequence.call(this,
    actionName, actionArgs);
};

BattleManager.actionAddElement = function(actionArgs) {
    if (!actionArgs) return;
    var array = [];
    var length = actionArgs.length;
    for (var i = 0; i < length; ++i) {
      var ele = actionArgs[i].toUpperCase().trim();
      if (ele.match(/(\d+)/i)) {
        array.push(parseInt(RegExp.$1));
      } else if (Yanfly.ElementIdRef[ele]) {
        array.push(Yanfly.ElementIdRef[ele]);
      }
    }
    $gameTemp._addedElements = array;
    return true;
};

BattleManager.actionClearElement = function() {
    $gameTemp._addedElements = undefined;
    $gameTemp._forcedElements = undefined;
    return true;
};

BattleManager.actionForceElement = function(actionArgs) {
    if (!actionArgs) return;
    var array = [];
    var length = actionArgs.length;
    for (var i = 0; i < length; ++i) {
      var ele = actionArgs[i].toUpperCase().trim();
      if (ele.match(/(\d+)/i)) {
        array.push(parseInt(RegExp.$1));
      } else if (Yanfly.ElementIdRef[ele]) {
        array.push(Yanfly.ElementIdRef[ele]);
      }
    }
    $gameTemp._forcedElements = array;
    return true;
};

BattleManager.actionNullElement = function() {
    $gameTemp._forcedElements = [];
    return true;
};

}; // Imported.YEP_BattleEngineCore

//=============================================================================
// Game_BattlerBase
//=============================================================================

Yanfly.Ele.Game_BtlrBase_elementRate =
    Game_BattlerBase.prototype.elementRate;
Game_BattlerBase.prototype.elementRate = function(elementId) {
  var rate = this.forcedElementRate(elementId);
  if (rate !== undefined) return rate;
  var result = Yanfly.Ele.Game_BtlrBase_elementRate.call(this, elementId);
  if (this.isAbsorbElement(elementId) && result > 0) {
    result = Math.min(result - 2.0, -0.01);
  }
  return result;
};

Game_BattlerBase.prototype.getObjElementReflectRate = function(obj, elementId) {
  if (!obj) return 0;
  if (!obj.elementReflect) return 0;
  return obj.elementReflect[elementId] || 0;
};

Game_BattlerBase.prototype.getObjElementAmplifyRate = function(obj, elementId) {
  if (!obj) return 0;
  if (!obj.elementAmplify) return 0;
  return obj.elementAmplify[elementId] || 0;
};

Game_BattlerBase.prototype.getObjElementMagnifyRate = function(obj, elementId) {
  if (!obj) return 0;
  if (!obj.elementMagnify) return 0;
  return obj.elementMagnify[elementId] || 0;
};

Game_BattlerBase.prototype.getObjElementForcedRate = function(obj, elementId) {
  if (!obj) return undefined;
  if (!obj.elementForcedRate) return undefined;
  return obj.elementForcedRate[elementId] || undefined;
};

//=============================================================================
// Game_Battler
//=============================================================================

Game_Battler.prototype.isAbsorbElement = function(elementId) {
  var length = this.states().length;
  for (var i = 0; i < length; ++i) {
    var state = this.states()[i];
    if (!state) continue;
    if (!state.elementAbsorb) continue;
    if (state.elementAbsorb.contains(elementId)) return true;
  }
  return false;
};

Game_Battler.prototype.elementReflectRate = function(elementId) {
  var rate = 0;
  var length = this.states().length;
  for (var i = 0; i < length; ++i) {
    var obj = this.states()[i];
    rate += this.getObjElementReflectRate(obj, elementId);
  }
  return rate;
};

Game_Battler.prototype.elementAmplifyRate = function(elementId) {
  var rate = 0;
  var length = this.states().length;
  for (var i = 0; i < length; ++i) {
    var obj = this.states()[i];
    rate += this.getObjElementAmplifyRate(obj, elementId);
  }
  return rate;
};

Game_Battler.prototype.elementMagnifyRate = function(elementId) {
  var rate = 1;
  var length = this.states().length;
  for (var i = 0; i < length; ++i) {
    var obj = this.states()[i];
    rate += this.getObjElementMagnifyRate(obj, elementId);
  }
  return rate;
};

Game_Battler.prototype.isNullElement = function() {
  var length = this.states().length;
  for (var i = 0; i < length; ++i) {
    var state = this.states()[i];
    if (state && state.elementNull) return true;
  }
  return false;
};

Game_Battler.prototype.forcedElementRate = function(elementId) {
  var length = this.states().length;
  for (var i = 0; i < length; ++i) {
    var state = this.states()[i];
    var rate = this.getObjElementForcedRate(state, elementId);
    if (rate !== undefined) return rate;
  }
  return undefined;
};

//=============================================================================
// Game_Actor
//=============================================================================

Game_Actor.prototype.isAbsorbElement = function(elementId) {
  if (this.actor().elementAbsorb.contains(elementId)) return true;
  if (this.currentClass().elementAbsorb.contains(elementId)) return true;
  var length = this.equips().length;
  for (var i = 0; i < length; ++i) {
    var equip = this.equips()[i];
    if (!equip) continue;
    if (!equip.elementAbsorb) continue;
    if (equip.elementAbsorb.contains(elementId)) return true;
  }
  return Game_Battler.prototype.isAbsorbElement.call(this, elementId);
};

Game_Actor.prototype.elementReflectRate = function(elementId) {
  var rate = Game_Battler.prototype.elementReflectRate.call(this, elementId);
  var length = this.equips().length;
  for (var i = 0; i < length; ++i) {
    var obj = this.equips()[i];
    rate += this.getObjElementReflectRate(obj, elementId);
  }
  rate += this.getObjElementReflectRate(this.actor(), elementId);
  rate += this.getObjElementReflectRate(this.currentClass(), elementId);
  return rate;
};

Game_Actor.prototype.elementAmplifyRate = function(elementId) {
  var rate = Game_Battler.prototype.elementAmplifyRate.call(this, elementId);
  var length = this.equips().length;
  for (var i = 0; i < length; ++i) {
    var obj = this.equips()[i];
    rate += this.getObjElementAmplifyRate(obj, elementId);
  }
  rate += this.getObjElementAmplifyRate(this.actor(), elementId);
  rate += this.getObjElementAmplifyRate(this.currentClass(), elementId);
  return rate;
};

Game_Actor.prototype.elementMagnifyRate = function(elementId) {
  var rate = Game_Battler.prototype.elementMagnifyRate.call(this, elementId);
  var length = this.equips().length;
  for (var i = 0; i < length; ++i) {
    var obj = this.equips()[i];
    rate += this.getObjElementMagnifyRate(obj, elementId);
  }
  rate += this.getObjElementMagnifyRate(this.actor(), elementId);
  rate += this.getObjElementMagnifyRate(this.currentClass(), elementId);
  return rate;
};

Game_Actor.prototype.isNullElement = function() {
  if (this.actor().elementNull) return true;
  if (this.currentClass().elementNull) return true;
  var length = this.equips().length;
  for (var i = 0; i < length; ++i) {
    var equip = this.equips()[i];
    if (equip && equip.elementNull) return true;
  }
  return Game_Battler.prototype.isNullElement.call(this);
};

Game_Actor.prototype.forcedElementRate = function(elementId) {
  var rate = Game_Battler.prototype.forcedElementRate.call(this, elementId);
  if (rate !== undefined) return rate;
  var length = this.equips().length;
  for (var i = 0; i < length; ++i) {
    var equip = this.equips()[i];
    rate = this.getObjElementForcedRate(equip, elementId);
    if (rate !== undefined) return rate;
  }
  rate = this.getObjElementForcedRate(this.currentClass(), elementId);
  if (rate !== undefined) return rate;
  rate = this.getObjElementForcedRate(this.actor(), elementId);
  if (rate !== undefined) return rate;
  return undefined;
};

//=============================================================================
// Game_Enemy
//=============================================================================

Game_Enemy.prototype.isAbsorbElement = function(elementId) {
  if (this.enemy().elementAbsorb.contains(elementId)) return true;
  return Game_Battler.prototype.isAbsorbElement.call(this, elementId);
};

Game_Enemy.prototype.elementReflectRate = function(elementId) {
  var rate = Game_Battler.prototype.elementReflectRate.call(this, elementId);
  rate += this.getObjElementReflectRate(this.enemy(), elementId);
  return rate;
};

Game_Enemy.prototype.elementAmplifyRate = function(elementId) {
  var rate = Game_Battler.prototype.elementAmplifyRate.call(this, elementId);
  rate += this.getObjElementAmplifyRate(this.enemy(), elementId);
  return rate;
};

Game_Enemy.prototype.elementMagnifyRate = function(elementId) {
  var rate = Game_Battler.prototype.elementMagnifyRate.call(this, elementId);
  rate += this.getObjElementMagnifyRate(this.enemy(), elementId);
  return rate;
};

Game_Enemy.prototype.isNullElement = function() {
  if (this.enemy().elementNull) return true;
  return Game_Battler.prototype.isNullElement.call(this);
};

Game_Enemy.prototype.forcedElementRate = function(elementId) {
  var rate = Game_Battler.prototype.forcedElementRate.call(this, elementId);
  if (rate !== undefined) return rate;
  rate = this.getObjElementForcedRate(this.enemy(), elementId);
  if (rate !== undefined) return rate;
  return undefined;
};

//=============================================================================
// Game_Action
//=============================================================================

Game_Action.prototype.getItemElements = function() {
  if ($gameTemp._forcedElements !== undefined) {
    return $gameTemp._forcedElements.filter(Yanfly.Util.onlyUnique);
  }
  if (this.subject().isNullElement()) return [];
  var elements = [];
  if (this.item().damage.elementId < 0) {
    Yanfly.Util.extend(elements, this.subject().attackElements());
  } else if (this.item().damage.elementId > 0) {
    elements.push(this.item().damage.elementId);
  }
  Yanfly.Util.extend(elements, this.item().multipleElements);
  if ($gameTemp._addedElements !== undefined) {
    Yanfly.Util.extend(elements, $gameTemp._addedElements);
  }
  return elements.filter(Yanfly.Util.onlyUnique);
};

Game_Action.prototype.calcElementRate = function(target) {
  if (!this.item()) return 1;
  var finalRate;
  var finalRateRaw;
  var elements = this.getItemElements();
  if (elements.length < 1) return 1.00;
  var rule = this.item().elementMultiRule;
  var average = 0;
  while (elements.length > 0) {
    var elementId = elements.shift();
    var eleRateRaw = target.elementRate(elementId);
    var eleRate = eleRateRaw;
    eleRate *= Math.max(0, this.subject().elementMagnifyRate(elementId));
    var absorbed = eleRate < 0;

    eleRate += this.subject().elementAmplifyRate(elementId);
    if (rule === 0) { // Lowest Rate
      finalRate = finalRate || eleRate;
      finalRate = Math.min(finalRate, eleRate);
    } else if (rule === 1) { // Additive
      finalRate = finalRate || 1.00;
      eleRate -= 1.00;
      finalRate += eleRate;
    } else if (rule === 2) { // Multiplicative
      finalRate = (finalRate === undefined) ? 1.00 : finalRate;
      finalRate *= Math.abs(eleRate);
      if (eleRate < 0) finalRate = Math.abs(finalRate) * -1;
      finalRateRaw = (finalRateRaw === undefined) ? 1.00 : finalRateRaw;
      finalRateRaw *= Math.abs(eleRateRaw);
      if (eleRateRaw < 0) finalRateRaw = Math.abs(finalRateRaw) * -1;
    } else if (rule === 3) { // Highest
      finalRate = finalRate || eleRate;
      finalRate = Math.max(finalRate, eleRate);
    } else if (rule === 4) { // Average
      finalRate = finalRate || 0;
      finalRate += eleRate;
      average += 1;
    } else {
      finalRate = this.calcElementRateRule(target, elementId, finalRate,
        eleRate, rule);
    }
  }
  if (rule === 4) finalRate /= Math.max(1, average);
  if (finalRate === undefined) finalRate = 1.00;
  if (finalRateRaw === undefined) finalRateRaw = 1.00;
  if (Imported.BattleEffectPopup) {
    this.popupElementRate(target, finalRateRaw);
  }
  return finalRate;
};

Game_Action.prototype.calcElementRateRule = function(target, elementId,
finalRate, eleRate, rule) {
  return finalRate;
};

Yanfly.Ele.Game_Action_itemMrf = Game_Action.prototype.itemMrf;
Game_Action.prototype.itemMrf = function(target) {
  var rate = Yanfly.Ele.Game_Action_itemMrf.call(this, target);
  if (this.item().bypassElementReflect) return rate;
  var elements = this.getItemElements();
  while (elements.length > 0) {
    var elementId = elements.shift();
    rate += target.elementReflectRate(elementId);
  }
  return rate;
};

//=============================================================================
// Utilities
//=============================================================================

Yanfly.Util = Yanfly.Util || {};

Yanfly.Util.getRange = function(n, m) {
    var result = [];
    for (var i = n; i <= m; ++i) result.push(i);
    return result;
};

Yanfly.Util.extend = function (mainArray, otherArray) {
    otherArray.forEach(function(i) {
      mainArray.push(i)
    }, this);
}

Yanfly.Util.onlyUnique = function(value, index, self) {
    return self.indexOf(value) === index;
};

//=============================================================================
// End of File
//=============================================================================
