//=============================================================================
// Yanfly Engine Plugins - Passive Aura Effects
// YEP_X_PassiveAuras.js
//=============================================================================

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

var Yanfly = Yanfly || {};
Yanfly.Aura = Yanfly.Aura || {};
Yanfly.Aura.version = 1.08;

//=============================================================================
 /*:
 * @plugindesc v1.08 (Requires YEP_AutoPassiveStates.js) Add aura effects
 * to various database objects.
 * @author Yanfly Engine Plugins
 *
 * @help
 * ============================================================================
 * Introduction
 * ============================================================================
 *
 * This plugin requires YEP_AutoPassiveStates. Make sure this plugin is located
 * under YEP_AutoPassiveStates in the plugin list.
 *
 * Passive Aura Effects are commonly found in many online multiplayer games
 * with RPG elements. When a battler can give out an aura, it will affect other
 * nearby battlers, too, either ally and/or foe. This plugin will allow states
 * to generate aura effects for other party members, opponents, or specifically
 * for actor and/or enemy parties.
 *
 * ============================================================================
 * Notetags
 * ============================================================================
 *
 * Use the following notetags to make a state generate auras.
 *
 * *NOTE* The notetags that affect alive members will affect members that have
 * at least 1 HP and not affected by the dead state. Even if they are immortal,
 * if they have 0 HP, they will not be affected by the alive aura effects as to
 * not conflict with the dead aura effects.
 *
 * *NOTE* The notetags that affect dead members will affect members that are
 * either affected by the death state or if their HP is at 0. The moment the
 * dead member has 1 HP or is no longer affected by the dead state, they will
 * no longer be affected by dead-only aura effects.
 *
 * State Notetags:
 *
 *   <Ally Aura: x>
 *   <Ally Aura: x, x, x>
 *   <Ally Aura: x through y>
 *   - This will cause the battler's allies to gain state(s) x (to y)
 *   while the battler is affected by the current state.
 *   *Note: A state cannot use itself in an aura effect.
 *
 *   <Alive Ally Aura: x>
 *   <Alive Ally Aura: x, x, x>
 *   <Alive Ally Aura: x through y>
 *   - This will cause the battler's alive allies to gain state(s) x (to y)
 *   while the battler is affected by the current state.
 *   *Note: A state cannot use itself in an aura effect.
 *
 *   <Dead Ally Aura: x>
 *   <Dead Ally Aura: x, x, x>
 *   <Dead Ally Aura: x through y>
 *   - This will cause the battler's dead allies to gain state(s) x (to y)
 *   while the battler is affected by the current state.
 *   *Note: A state cannot use itself in an aura effect.
 *
 *   <Foe Aura: x>
 *   <Foe Aura: x, x, x>
 *   <Foe Aura: x through y>
 *   - This will cause the battler's foes to gain state(s) x (to y)
 *   while the battler is affected by the current state.
 *   *Note: A state cannot use itself in an aura effect.
 *
 *   <Alive Foe Aura: x>
 *   <Alive Foe Aura: x, x, x>
 *   <Alive Foe Aura: x through y>
 *   - This will cause the battler's alive foes to gain state(s) x (to y)
 *   while the battler is affected by the current state.
 *   *Note: A state cannot use itself in an aura effect.
 *
 *   <Dead Foe Aura: x>
 *   <Dead Foe Aura: x, x, x>
 *   <Dead Foe Aura: x through y>
 *   - This will cause the battler's dead foes to gain state(s) x (to y)
 *   while the battler is affected by the current state.
 *   *Note: A state cannot use itself in an aura effect.
 *
 *   <Party Aura: x>
 *   <Party Aura: x, x, x>
 *   <Party Aura: x through y>
 *   - This will cause the Actor Party to gain state(s) x (to y)
 *   while the battler is affected by the current state.
 *   *Note: A state cannot use itself in an aura effect.
 *
 *   <Alive Party Aura: x>
 *   <Alive Party Aura: x, x, x>
 *   <Alive Party Aura: x through y>
 *   - This will cause the battler's alive Actor Party to gain state(s) x (to y)
 *   while the battler is affected by the current state.
 *   *Note: A state cannot use itself in an aura effect.
 *
 *   <Dead Party Aura: x>
 *   <Dead Party Aura: x, x, x>
 *   <Dead Party Aura: x through y>
 *   - This will cause the battler's dead Actor Party to gain state(s) x (to y)
 *   while the battler is affected by the current state.
 *   *Note: A state cannot use itself in an aura effect.
 *
 *   <Troop Aura: x>
 *   <Troop Aura: x, x, x>
 *   <Troop Aura: x through y>
 *   - This will cause the Enemy Troop to gain state(s) x (to y)
 *   while the battler is affected by the current state.
 *   *Note: A state cannot use itself in an aura effect.
 *
 *   <Alive Troop Aura: x>
 *   <Alive Troop Aura: x, x, x>
 *   <Alive Troop Aura: x through y>
 *   - This will cause the battler's alive Enemy Troop to gain state(s) x (to y)
 *   while the battler is affected by the current state.
 *   *Note: A state cannot use itself in an aura effect.
 *
 *   <Dead Troop Aura: x>
 *   <Dead Troop Aura: x, x, x>
 *   <Dead Troop Aura: x through y>
 *   - This will cause the battler's dead Enemy Troop to gain state(s) x (to y)
 *   while the battler is affected by the current state.
 *   *Note: A state cannot use itself in an aura effect.
 *
 *   <Everybody Aura: x>
 *   <Everybody Aura: x, x, x>
 *   <Everybody Aura: x through y>
 *   - This will cause the all active battlers to gain state(s) x (to y)
 *   while the battler is affected by the current state.
 *   *Note: A state cannot use itself in an aura effect.
 *
 *   <Alive Aura: x>
 *   <Alive Aura: x, x, x>
 *   <Alive Aura: x through y>
 *   - This will cause the all alive battlers to gain state(s) x (to y)
 *   while the battler is affected by the current state.
 *   *Note: A state cannot use itself in an aura effect.
 *
 *   <Dead Aura: x>
 *   <Dead Aura: x, x, x>
 *   <Dead Aura: x through y>
 *   - This will cause the all alive battlers to gain state(s) x (to y)
 *   while the battler is affected by the current state.
 *   *Note: A state cannot use itself in an aura effect.
 *
 * ============================================================================
 * Lunatic Mode - Custom Aura Conditions
 * ============================================================================
 *
 * For those with JavaScript experience and would like to make conditional aura
 * effects, you can use these notetags. Keep in mind, this conditional effect
 * is for the target delivered state and not the origin aura itself.
 *
 * State Notetags:
 *
 *   <Custom Aura Condition>
 *    if (user.hpRate() > 0.50) {
 *      condition = true;
 *    } else {
 *      condition = false;
 *    }
 *   </Custom Aura Condition>
 *   - The 'condition' variable will determine whether or not the target aura
 *   state will appear. If the 'condition' variable is 'true', then it will
 *   appear. If the 'condition' variable is 'false', then it will not appear.
 *   Remember, this notetag has to be placed in the target delivered state and
 *   not the origin aura itself.
 *
 * ============================================================================
 * Changelog
 * ============================================================================
 *
 * Version 1.08:
 * - Optimization update. There should be less lag spikes if there are more
 * passive conditions present on a battler.
 *
 * Version 1.07:
 * - Bypass the isDevToolsOpen() error when bad code is inserted into a script
 * call or custom Lunatic Mode code segment due to updating to MV 1.6.1.
 *
 * Version 1.06:
 * - Updated for RPG Maker MV version 1.6.1.
 *
 * Version 1.05:
 * - Updated for RPG Maker MV version 1.5.0.
 *
 * Version 1.04:
 * - Bug fixed where if an aura is applied as the last action of a turn, it
 * wouldn't take effect until the following turn.
 *
 * Version 1.03:
 * - Lunatic Mode fail safes added.
 *
 * Version 1.02:
 * - Added new notetags that affect specifically alive or dead allies. Alive
 * aura effects will affect only members with at least 1 HP and not affected by
 * the dead state. Dead aura effects will affect members with either 0 HP or
 * have the dead state.
 *
 * Version 1.01:
 * - Fixed a bug that would conflict with Taunt and Selection Core making some
 * aura effects randomly disappear.
 *
 * Version 1.00:
 * - Finished Plugin!
 */
//=============================================================================

if (Imported.YEP_AutoPassiveStates) {

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

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

  if (!Yanfly._loaded_YEP_X_PassiveAuras) {
    this.processAuraNotetags1($dataStates);
    Yanfly._loaded_YEP_X_PassiveAuras = true;
  }
  
  return true;
};

Yanfly.Aura.Types = [
  'friends', 'aliveFriends', 'deadFriends',
  'opponents', 'aliveOpponents', 'deadOpponents',
  'party', 'aliveParty', 'deadParty',
  'troop', 'aliveTroop', 'deadTroop',
  'all', 'aliveAll', 'deadAll'
];

DataManager.processAuraNotetags1 = function(group) {
  var note1 = /<(.*)[ ](?:AURA|STATE AURA):[ ]*(\d+(?:\s*,\s*\d+)*)>/i;
  var note2 = /<(.*)[ ](?:AURA|STATE AURA):[ ](\d+)[ ](?:THROUGH|to)[ ](\d+)>/i;
  for (var n = 1; n < group.length; n++) {
    var obj = group[n];
    var notedata = obj.note.split(/[\r\n]+/);

    obj.aura = {};
    for (a = 0; a < Yanfly.Aura.Types.length; ++a) {
      obj.aura[Yanfly.Aura.Types[a]] = [];
    }
    var evalMode = 'none';
    obj.auraConditionEval = '';

    for (var i = 0; i < notedata.length; i++) {
      var line = notedata[i];
      if (line.match(note1)) {
        var type = this.getNotetagAuraType(String(RegExp.$1));
        var array = JSON.parse('[' + RegExp.$2.match(/\d+/g) + ']');
        obj.aura[type] = obj.aura[type].concat(array);
        Yanfly.Util.removeArrayElement(obj.aura[type], obj.id);
      } else if (line.match(note2)) {
        var type = this.getNotetagAuraType(String(RegExp.$1));
        var range = Yanfly.Util.getRange(parseInt(RegExp.$2),
          parseInt(RegExp.$3));
        obj.aura[type] = obj.aura[type].concat(range);
        Yanfly.Util.removeArrayElement(obj.aura[type], obj.id);
      } else if (line.match(/<(?:CUSTOM AURA CONDITION)>/i)) {
        var evalMode = 'custom aura condition';
      } else if (line.match(/<\/(?:CUSTOM AURA CONDITION)>/i)) {
        var evalMode = 'none';
      } else if (evalMode === 'custom aura condition') {
        obj.auraConditionEval = obj.auraConditionEval + line + '\n';
      }
    }

    obj.auraConditionEval = new Function('condition','a','user','subject','b',
      'target','s','v', obj.auraConditionEval + '\nreturn condition;');
  }
};

DataManager.getNotetagAuraType = function(str) {
  switch (str.toUpperCase()) {
  case 'ALLY':
  case 'ALLIES':
  case 'FRIEND':
  case 'FRIENDS':
  case 'FRIENDLY':
    return 'friends';
    break;

  case 'ALIVE ALLY':
  case 'ALIVE ALLIES':
  case 'ALIVE FRIEND':
  case 'ALIVE FRIENDS':
  case 'ALIVE FRIENDLY':
    return 'aliveFriends';
    break;

  case 'DEAD ALLY':
  case 'DEAD ALLIES':
  case 'DEAD FRIEND':
  case 'DEAD FRIENDS':
  case 'DEAD FRIENDLY':
    return 'deadFriends';
    break;

  case 'FOE':
  case 'FOES':
  case 'OPPONENT':
  case 'OPPONENTS':
  case 'OPPOSING':
    return 'opponents';
    break;

  case 'ALIVE FOE':
  case 'ALIVE FOES':
  case 'ALIVE OPPONENT':
  case 'ALIVE OPPONENTS':
  case 'ALIVE OPPOSING':
    return 'aliveOpponents';
    break;

  case 'DEAD FOE':
  case 'DEAD FOES':
  case 'DEAD OPPONENT':
  case 'DEAD OPPONENTS':
  case 'DEAD OPPOSING':
    return 'deadOpponents';
    break;

  case 'ACTOR':
  case 'ACTORS':
  case 'PARTY':
  case 'PARTIES':
    return 'party';
    break;

  case 'ALIVE ACTOR':
  case 'ALIVE ACTORS':
  case 'ALIVE PARTY':
  case 'ALIVE PARTIES':
    return 'aliveParty';
    break;

  case 'DEAD ACTOR':
  case 'DEAD ACTORS':
  case 'DEAD PARTY':
  case 'DEAD PARTIES':
    return 'deadParty';
    break;

  case 'ENEMY':
  case 'ENEMIES':
  case 'TROOP':
  case 'TROOPS':
    return 'troop';
    break;

  case 'ALIVE ENEMY':
  case 'ALIVE ENEMIES':
  case 'ALIVE TROOP':
  case 'ALIVE TROOPS':
    return 'aliveTroop';
    break;

  case 'DEAD ENEMY':
  case 'DEAD ENEMIES':
  case 'DEAD TROOP':
  case 'DEAD TROOPS':
    return 'deadTroop';
    break;

  default:
    return 'all';
  }
};

DataManager.isAuraState = function(state) {
  if (!state) return false;
  if (!state.aura) return false;
  var array = Yanfly.Aura.Types;
  var length = array.length;
  for (var i = 0; i < length; ++i) {
    if (state.aura[array[i]].length > 0) return true;
  }
  return false;
};

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

BattleManager.refreshAllBattlers = function() {
  var members = $gameParty.members().concat($gameTroop.members());
  var length = members.length;
  for (var i = 0; i < length; ++i) {
    var member = members[i];
    if (member) member.refresh();
  }
};

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

Yanfly.Aura.Game_BattlerBase_passiveStatesRaw =
  Game_BattlerBase.prototype.passiveStatesRaw;
Game_BattlerBase.prototype.passiveStatesRaw = function() {
  var array = Yanfly.Aura.Game_BattlerBase_passiveStatesRaw.call(this);
  if ($gameParty.inBattle()) {
    array = array.concat(this.validAuraStateIds());
  }
  return array.filter(Yanfly.Util.onlyUnique);
};

Game_BattlerBase.prototype.checkAuraAlive = function() {
  return this.hp > 0 && this.isAlive();
};

Game_BattlerBase.prototype.checkAuraDead = function() {
  return this.hp <= 0 || this.isDead();
};

Game_BattlerBase.prototype.validAuraStateIds = function() {
  var array = [];
  var states = this.auraStateIds();
  var length = states.length;
  for (var i = 0; i < length; ++i) {
    var stateId = states[i];
    if (this.meetAuraStateCondition(stateId)) array.push(stateId);
  }
  return array;
};

Game_BattlerBase.prototype.auraStateIds = function() {
  if ($gameTemp._isGatheringAuraData) return [];
  var array = [];
  var friends = this.friendsUnit();
  var opponents = this.opponentsUnit();
  $gameTemp._isGatheringAuraData = true;
  array = array.concat(friends.auraStateTypeIds('all'));
  array = array.concat(opponents.auraStateTypeIds('all'));
  array = array.concat(friends.auraStateTypeIds('friends'));
  array = array.concat(opponents.auraStateTypeIds('opponents'));
  if (this.checkAuraAlive()) {
    array = array.concat(friends.auraStateTypeIds('aliveAll'));
    array = array.concat(opponents.auraStateTypeIds('aliveAll'));
    array = array.concat(friends.auraStateTypeIds('aliveFriends'));
    array = array.concat(opponents.auraStateTypeIds('aliveOpponents'));
  } else if (this.checkAuraDead()) {
    array = array.concat(friends.auraStateTypeIds('deadAll'));
    array = array.concat(opponents.auraStateTypeIds('deadAll'));
    array = array.concat(friends.auraStateTypeIds('deadFriends'));
    array = array.concat(opponents.auraStateTypeIds('deadOpponents'));
  }
  $gameTemp._isGatheringAuraData = false;
  return array;
};

Game_BattlerBase.prototype.getAuraStateTypeId = function(type) {
  var array = [];
  var states = this.states();
  var length = states.length;
  for (var i = 0; i < length; ++i) {
    var state = states[i];
    if (!state) continue;
    if (!DataManager.isAuraState(state)) continue;
    array = array.concat(state.aura[type]);
  }
  return array;
};

Game_BattlerBase.prototype.meetAuraStateCondition = function(stateId) {
  this._checkAuraStateCondition = this._checkAuraStateCondition || [];
  if (this._checkAuraStateCondition.contains(stateId)) return false;
  var state = $dataStates[stateId];
  if (!state) return false;
  if (state.auraConditionEval === '') return true;
  return this.auraStateConditionEval(state);
};

Game_BattlerBase.prototype.auraStateConditionEval = function(state) {
  this._checkAuraStateCondition = this._checkAuraStateCondition || [];
  this._checkAuraStateCondition.push(state.id);
  var condition = true;
  var a = this;
  var user = this;
  var subject = this;
  var b = this;
  var target = this;
  var s = $gameSwitches._data;
  var v = $gameVariables._data;
  var code = state.auraConditionEval;
  try {
      condition = state.auraConditionEval.call(this, condition, a, user,
        subject, b, target, s, v);
    } catch (e) {
      Yanfly.Util.displayError(e, code, 'PASSIVE AURA CUSTOM CONDITION ERROR');
    }
  var index = this._checkAuraStateCondition.indexOf(state.id);
  this._checkAuraStateCondition.splice(index, 1);
  return condition;
};

Yanfly.Aura.Game_BattlerBase_addNewState =
  Game_BattlerBase.prototype.addNewState;
Game_BattlerBase.prototype.addNewState = function(stateId) {
  Yanfly.Aura.Game_BattlerBase_addNewState.call(this, stateId);
  this.updateAuras(stateId);
};

Yanfly.Aura.Game_BattlerBase_eraseState =
  Game_BattlerBase.prototype.eraseState;
Game_BattlerBase.prototype.eraseState = function(stateId) {
  Yanfly.Aura.Game_BattlerBase_eraseState.call(this, stateId);
  this.updateAuras(stateId);
};

Game_BattlerBase.prototype.updateAuras = function(stateId) {
  var state = $dataStates[stateId];
  if (!state) return;
  aura = state.aura;
  if ((aura.all.length + aura.friends.length > 0) || aura.party.length > 0) {
    $gameParty.refreshMembers();
  }
  if ((aura.all.length + aura.opponents.length > 0) || aura.troop.length > 0) {
    $gameTroop.refreshMembers();
  }
};

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

Yanfly.Aura.Game_Battler_onTurnEnd = Game_Battler.prototype.onTurnEnd;
Game_Battler.prototype.onTurnEnd = function() {
  if ($gameParty.inBattle()) this.refresh();
  Yanfly.Aura.Game_Battler_onTurnEnd.call(this);
};

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

Game_Actor.prototype.auraStateIds = function() {
  if ($gameTemp._isGatheringAuraData) return [];
  var array = Game_Battler.prototype.auraStateIds.call(this);
  var friends = this.friendsUnit();
  var opponents = this.opponentsUnit();
  $gameTemp._isGatheringAuraData = true;
  array = array.concat(friends.auraStateTypeIds('party'));
  array = array.concat(opponents.auraStateTypeIds('party'));
  if (this.checkAuraAlive()) {
    array = array.concat(friends.auraStateTypeIds('aliveParty'));
    array = array.concat(opponents.auraStateTypeIds('aliveParty'));
  } else if (this.checkAuraDead()) {
    array = array.concat(friends.auraStateTypeIds('deadParty'));
    array = array.concat(opponents.auraStateTypeIds('deadParty'));
  }
  $gameTemp._isGatheringAuraData = false;
  return array;
};

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

Game_Enemy.prototype.auraStateIds = function() {
  if ($gameTemp._isGatheringAuraData) return [];
  var array = Game_Battler.prototype.auraStateIds.call(this);
  var friends = this.friendsUnit();
  var opponents = this.opponentsUnit();
  $gameTemp._isGatheringAuraData = true;
  array = array.concat(friends.auraStateTypeIds('troop'));
  array = array.concat(opponents.auraStateTypeIds('troop'));
  if (this.checkAuraAlive()) {
    array = array.concat(friends.auraStateTypeIds('aliveTroop'));
    array = array.concat(opponents.auraStateTypeIds('aliveTroop'));
  } else if (this.checkAuraDead()) {
    array = array.concat(friends.auraStateTypeIds('deadTroop'));
    array = array.concat(opponents.auraStateTypeIds('deadTroop'));
  }
  $gameTemp._isGatheringAuraData = false;
  return array;
};

//=============================================================================
// Game_Unit
//=============================================================================

Game_Unit.prototype.allAliveMembers = function() {
  return this.members().filter(function(member) {
      return member.isAlive();
  });
};

Game_Unit.prototype.auraStateTypeIds = function(type) {
  var array = [];
  var members = this.allAliveMembers();
  var length = members.length;
  for (var i = 0; i < length; ++i) {
    var member = members[i];
    if (member) {
      array = array.concat(member.getAuraStateTypeId(type));
    }
  }
  return array;
};

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

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

Yanfly.Util.displayError = function(e, code, message) {
  console.log(message);
  console.log(code || 'NON-EXISTENT');
  console.error(e);
  if (Utils.RPGMAKER_VERSION && Utils.RPGMAKER_VERSION >= "1.6.0") return;
  if (Utils.isNwjs() && Utils.isOptionValid('test')) {
    if (!require('nw.gui').Window.get().isDevToolsOpen()) {
      require('nw.gui').Window.get().showDevTools();
    }
  }
};

Yanfly.Util.removeArrayElement = function(array, element) {
  while (array.indexOf(element) >= 0) {
    array.splice(array.indexOf(element), 1);
  }
};

//=============================================================================
// End of File
//=============================================================================
}; // Imported.YEP_AutoPassiveStates