code:
//=============================================================================
// NRP_EnemyRoutineKai.js
//=============================================================================
/*:
 * @target MV MZ
 * @plugindesc v1.081 Improve the enemy's action routine.
 * @author Takeshi Sunagawa (http://newrpg.seesaa.net/)
 * @url http://newrpg.seesaa.net/article/473218336.html
 *
 * @help Improve the enemy's action routine as follows.
 *
 * - Re-select actions not only at the start of the turn
 *   but also just before the action.
 * - Select the target with the least amount of HP & MP recovery.
 * - Do not use the MP drain skill on an opponent with 0 MP.
 * - Do not overlay already active states.
 * - Do not overlay already limited buffs or debuffs.
 * - Do not heal, cure, or revive if there is no valid target.
 * - If there is only one, the substitution skill is not used.
 *
 * [Individual Settings]
 * You can also make individual settings
 * by entering the following in the note of the enemy.
 *
 * - Re-select just before action?  : <RoutineReset:[true or false]>
 * - Pointless action or not?       : <RoutineTest:[true or false]>
 * - Control recovery actions?      : <RoutineRecover:[true or false]>
 * - Condition for HP recovery      : <RoutineIfHp:[condition]>.
 * - Condition for MP recovery      : <RoutineIfMp:[condition]>.
 * - Control MP drain               ：<RoutineMpDrain:[true or false]>
 * - Control revival action?        : <RoutineForDead:[true or false]>
 * - Control the effect for actors? : <RoutineActorEffect:[true or false]>
 * - Control the effect for enemys  : <RoutineEnemyEffect:[true or false]>
 * - Watch for state resistance?    : <RoutineWatchResist:[true or false]>
 *
 * [Notes]
 * - Only recovery type skills can be targeted for recovery control.
 * - Effects such as state are only determined by "none" type skills.
 * - Skills that belong to the HP damage/drain type
 *   are not controlled regardless of their effect.
 * - If a speed compensation skill is selected at the start of a turn,
 *   the action will not be reselected.
 * - Also, when you re-select an action,
 *   you will not select a speed compensation skill.
 * - In the case of "Force Action", if the target is "Random",
 *   the control will also be performed.
 *
 * [Terms]
 * There are no restrictions.
 * Modification, redistribution freedom, commercial availability,
 * and rights indication are also optional.
 * The author is not responsible,
 * but will deal with defects to the extent possible.
 *
 * @param <Base Setting>
 *
 * @param resetAction
 * @parent <Base Setting>
 * @type boolean
 * @default false
 * @desc If ON, it will make another action decision just before the action.
 * The action is determined one at a time, even when there are multiple actions.
 *
 * @param testApply
 * @parent <Base Setting>
 * @type boolean
 * @default true
 * @desc Execute an action judgment.
 * If ON, you will no longer use ineffective skills.
 *
 * @param <Heal>
 *
 * @param controlRecover
 * @parent <Heal>
 * @type boolean
 * @default true
 * @desc If it is on, it controls the recovery skill.
 *
 * @param ifHpRecover
 * @parent <Heal>
 * @type text
 * @default b.hpRate() < 1.0
 * @desc Set the condition for HP recovery.
 * When someone's HP drops below 100% with "b.hpRate() < 1.0".
 *
 * @param ifMpRecover
 * @parent <Heal>
 * @type text
 * @default b.mpRate() < 1.0
 * @desc Set the condition for MP recovery.
 * When someone's MP drops below 100% with "b.mpRate() < 1.0".
 *
 * @param controlForDead
 * @parent <Heal>
 * @type boolean
 * @default true
 * @desc If it's on, it gives you control over skills that target the dead.
 *
 * @param <MP Drain>
 *
 * @param controlMpDrain
 * @parent <MP Drain>
 * @type boolean
 * @default true
 * @desc If ON, you control related to MP drain/damage skills.
 *
 * @param <Effect>
 *
 * @param controlEnemyEffect
 * @parent <Effect>
 * @type boolean
 * @default true
 * @desc If ON, you control the effect skill for the enemy.
 * (e.g. support states)
 *
 * @param controlActorEffect
 * @parent <Effect>
 * @type boolean
 * @default true
 * @desc If ON, you control the effect skill for the actor.
 * (e.g. abnormal states)
 *
 * @param watchResist
 * @parent <Effect>
 * @type boolean
 * @default false
 * @desc If ON, the target's resistance is checked
 * and the invalid state is not used.
 *
 * @param improveSubstitute
 * @parent <Effect>
 * @type boolean
 * @default true
 * @desc If ON, it will not use skills with substitute state with only one.
 */

/*:ja
 * @target MV MZ
 * @plugindesc v1.081 敵行動ルーチンを改善します。
 * @author 砂川赳（http://newrpg.seesaa.net/）
 * @url http://newrpg.seesaa.net/article/473218336.html
 *
 * @help 敵の行動ルーチンを以下の通り改善します。
 *
 * ・ターン開始時だけでなく行動直前にも行動再選択を行う。
 * ・ＨＰ＆ＭＰ回復時に最も減っている対象を選択する。
 * ・ＭＰが０の相手にＭＰ吸収スキルを使わない。
 * ・既にかかっているステートを重ねがけしない。
 * ・既に限界となっている能力変化を重ねがけしない。
 * ・有効な対象が存在しない場合、回復や治療、蘇生を行わない。
 * ・一人しかいない場合、身代わりスキルを使用しない。
 *
 * ■個別設定
 * 敵キャラのメモ欄に以下を記入すれば個別設定も可能です。
 *
 * ・行動直前の再設定を行うか？     ：<RoutineReset:[true or false]>
 * ・無意味な行動をしないか？       ：<RoutineTest:[true or false]>
 * ・回復系行動を制御するか？       ：<RoutineRecover:[true or false]>
 * ・HP回復を行う条件              ：<RoutineIfHp:[condition]>
 * ・MP回復を行う条件              ：<RoutineIfMp:[condition]>
 * ・MP吸収を制御するか？          ：<RoutineMpDrain:[true or false]>
 * ・蘇生系行動を制御するか？       ：<RoutineForDead:[true or false]>
 * ・対アクター使用効果を制御するか？：<RoutineActorEffect:[true or false]>
 * ・対エネミー使用効果を制御するか？：<RoutineEnemyEffect:[true or false]>
 * ・ステート耐性を見るか？         ：<RoutineWatchResist:[true or false]>
 *
 * ■注意点
 * ・回復時の対象制御はタイプが回復系のスキルのみ行います。
 * ・ステートなど使用効果の有効判定は、タイプが『なし』のスキルのみ行います。
 * ・タイプがＨＰダメージ・吸収に該当するスキルは使用効果に依らず制御しません。
 * ・ターン制かつターン開始時に速度補正技が選ばれた場合、行動再選択を行いません。
 * ・また、行動再選択時に速度補正技を選ぶこともありません。
 * ・『戦闘行動の強制』を使用した場合も、対象がランダムだと制御が行われます。
 *
 * ■利用規約
 * 特に制約はありません。
 * 改変、再配布自由、商用可、権利表示も任意です。
 * 作者は責任を負いませんが、不具合については可能な範囲で対応します。
 *
 * @param <Base Setting>
 * @text ＜基本設定＞
 * @desc 見出しです。
 *
 * @param resetAction
 * @text 行動直前の再設定
 * @parent <Base Setting>
 * @type boolean
 * @default false
 * @desc ONならば、行動直前にも再度の行動決定を行います。
 * 複数回行動時も一回毎に行動決定を行います。
 *
 * @param testApply
 * @text 行動判定の実行
 * @parent <Base Setting>
 * @type boolean
 * @default true
 * @desc ONならば効果の得られないスキルは使用しなくなります。
 *
 * @param <Heal>
 * @text ＜回復関連＞
 * @desc 見出しです。
 *
 * @param controlRecover
 * @text 回復系行動の制御
 * @parent <Heal>
 * @type boolean
 * @default true
 * @desc ONならば回復系スキルの制御を行います。
 *
 * @param ifHpRecover
 * @text HP回復行動の条件
 * @parent <Heal>
 * @type text
 * @default b.hpRate() < 1.0
 * @desc 指定した条件でHP回復を行います。
 * 『b.hpRate() < 1.0』で誰かのHPが100%未満になった時です。
 *
 * @param ifMpRecover
 * @text MP回復行動の条件
 * @parent <Heal>
 * @type text
 * @default b.mpRate() < 1.0
 * @desc MP回復系行動を行う条件を設定します。
 * 『b.mpRate() < 1.0』で誰かのMPが100%未満になった時です。
 *
 * @param controlForDead
 * @text 蘇生系行動の制御
 * @parent <Heal>
 * @type boolean
 * @default true
 * @desc ONならば戦闘不能者を対象にしたスキルの制御を行います。
 *
 * @param <MP Drain>
 * @text ＜MP吸収関連＞
 * @desc 見出しです。
 *
 * @param controlMpDrain
 * @text MP吸収の制御
 * @parent <MP Drain>
 * @type boolean
 * @default true
 * @desc ONならばMP吸収／ダメージスキルに関する制御を行います。
 *
 * @param <Effect>
 * @text ＜使用効果関連＞
 * @desc 見出しです。
 *
 * @param controlEnemyEffect
 * @text 対エネミーの使用効果制御
 * @parent <Effect>
 * @type boolean
 * @default true
 * @desc ONならば仲間（エネミー）サイドに対する使用効果系スキルの制御を行います。（支援系ステートなど）
 *
 * @param controlActorEffect
 * @text 対アクターの使用効果制御
 * @parent <Effect>
 * @type boolean
 * @default true
 * @desc ONならば相手（アクター）サイドに対する使用効果系スキルの制御を行います。（異常系ステートなど）
 *
 * @param watchResist
 * @text ステート耐性を見るか
 * @parent <Effect>
 * @type boolean
 * @default false
 * @desc ONならば対象の耐性を確認し、無効なステートは使用しません。
 *
 * @param improveSubstitute
 * @text 身代わりを改善
 * @parent <Effect>
 * @type boolean
 * @default true
 * @desc ONならば一人しかいない状態で身代わりステート付きのスキルを使用しません。
 */
(function () {
  "use strict";

  function setDefault(str, def) {
    return str ? str : def;
  }
  function toBoolean(val, def) {
    // 空白なら初期値を返す
    if (val === "" || val === undefined) {
      return def;
    }
    // 文字列ならboolean型に変換して返す
    return val.toLowerCase() == "true";
  }

  const parameters = PluginManager.parameters("NRP_EnemyRoutineKai");

  const pResetAction = toBoolean(parameters["resetAction"], true);
  const pTestApply = toBoolean(parameters["testApply"], true);

  const pControlRecover = toBoolean(parameters["controlRecover"], true);
  const pIfHpRecover = setDefault(
    parameters["ifHpRecover"],
    "b.hpRate() < 1.0"
  );
  const pIfMpRecover = setDefault(
    parameters["ifMpRecover"],
    "b.mpRate() < 1.0"
  );
  const pControlMpDrain = toBoolean(parameters["controlMpDrain"], true);
  const pControlForDead = toBoolean(parameters["controlForDead"], true);

  const pControlActorEffect = toBoolean(parameters["controlActorEffect"], true);
  const pControlEnemyEffect = toBoolean(parameters["controlEnemyEffect"], true);
  const pWatchResist = toBoolean(parameters["watchResist"], true);
  const pImproveSubstitute = toBoolean(parameters["improveSubstitute"], true);

  /**
   * ●行動再決定を行うか？
   */
  function isResetAction(subject) {
    // メモ欄のmeta情報を確認
    var flg = eval(subject.enemy().meta.RoutineReset);
    // 設定がある場合
    if (flg == true || flg == false) {
      return flg;
    }
    // 未定義の場合
    return pResetAction;
  }

  /**
   * ●行動判定の実行を行うか？
   */
  function isTestApply(subject) {
    var flg = eval(subject.enemy().meta.RoutineTest);
    if (flg == true || flg == false) {
      return flg;
    }
    return pTestApply;
  }

  /**
   * ●回復系行動の制御を行うか？
   */
  function isControlRecover(subject) {
    var flg = eval(subject.enemy().meta.RoutineRecover);
    if (flg == true || flg == false) {
      return flg;
    }
    return pControlRecover;
  }

  /**
   * ●HP回復を行うかを判定する。
   * @param a 行動主体
   * @param b 対象
   */
  function isRoutineHpRecover(a, b) {
    var flg = eval(a.enemy().meta.RoutineIfHp);
    if (flg == true || flg == false) {
      return flg;
    }
    return eval(pIfHpRecover);
  }

  /**
   * ●MP回復を行うかを判定する。
   * @param a 行動主体
   * @param b 対象
   */
  function isRoutineMpRecover(a, b) {
    var flg = eval(a.enemy().meta.RoutineIfMp);
    if (flg == true || flg == false) {
      return flg;
    }
    return eval(pIfMpRecover);
  }

  /**
   * ●MP吸収制御を行うかを判定する。
   */
  function isControlMpDrain(subject) {
    var flg = eval(subject.enemy().meta.RoutineMpDrain);
    if (flg == true || flg == false) {
      return flg;
    }
    return pControlMpDrain;
  }

  /**
   * ●蘇生系行動の制御を行うか？
   */
  function isControlForDead(subject) {
    var flg = eval(subject.enemy().meta.RoutineForDead);
    if (flg == true || flg == false) {
      return flg;
    }
    return pControlForDead;
  }

  /**
   * ●対エネミーの使用効果制御を行うか？
   */
  function isControlEnemyEffect(subject) {
    var flg = eval(subject.enemy().meta.RoutineEnemyEffect);
    if (flg == true || flg == false) {
      return flg;
    }
    return pControlEnemyEffect;
  }

  /**
   * ●対アクターの使用効果制御を行うか？
   */
  function isControlActorEffect(subject) {
    var flg = eval(subject.enemy().meta.RoutineActorEffect);
    if (flg == true || flg == false) {
      return flg;
    }
    return pControlActorEffect;
  }

  /**
   * ●無効なステートを使用しないか？
   */
  function isRoutineWatchResist(subject) {
    var flg = eval(subject.enemy().meta.RoutineWatchResist);
    if (flg == true || flg == false) {
      return flg;
    }
    return pWatchResist;
  }

  /**
   * ●速度補正技の除外処理を行うか？
   */
  function isSpeedException(action) {
    // CTB, CTTB,なら速度補正の意味が異なるので除外しない
    // _cttbCountの有無で判定
    if (
      BattleManager._cttbCount ||
      BattleManager._isCttb ||
      BattleManager._isCtb
    ) {
      return false;

      // MZのTPBも同じく除外
    } else if (Utils.RPGMAKER_NAME == "MZ" && BattleManager.isTpb()) {
      return false;
    }

    // 以下はターン制
    // 速度補正技なので除外する
    if (action.item().speed != 0) {
      return true;
    }
    return false;
  }

  /**
   * ●ターン開始入力
   */
  var _BattleManager_startInput = BattleManager.startInput;
  BattleManager.startInput = function () {
    // 行動確定前にフラグ初期化
    clearFirstActionDone();

    _BattleManager_startInput.apply(this, arguments);
  };

  /**
   * ●ターン開始
   */
  var _BattleManager_startTurn = BattleManager.startTurn;
  BattleManager.startTurn = function () {
    _BattleManager_startTurn.apply(this, arguments);

    // TPBではstartInputを通らないためこちらでクリア
    if (Utils.RPGMAKER_NAME == "MZ" && this.isTpb()) {
      // 行動確定前にフラグ初期化
      clearFirstActionDone();
    }
  };

  /**
   * ●行動確定前にフラグ初期化
   */
  function clearFirstActionDone() {
    for (let enemy of $gameTroop.members()) {
      enemy._firstActionDone = undefined;
    }
  }

  // 行動再設定を判定するフラグ
  let mResetActionFlg = false;

  /**
   * ●戦闘行動の処理
   */
  const _BattleManager_processTurn = BattleManager.processTurn;
  BattleManager.processTurn = function () {
    const subject = this._subject;

    // 敵の場合、行動直前の再設定を実行
    if (subject.isEnemy() && isResetAction(subject)) {
      const action = subject.currentAction();
      // 行動前のタイミングでアクション再設定
      if (action) {
        // ただし、速度補正技は除く
        if (!action.item() || !isSpeedException(action)) {
          mResetActionFlg = true;
          subject.makeActions();
          mResetActionFlg = false;
        }

        // 既に一度行動した。
        subject._firstActionDone = true;
      }
    }

    // 元処理実行
    _BattleManager_processTurn.apply(this);
  };

  /**
   * ●アクション生成
   * ※Game_Enemy.prototype.makeActionTimesをオーバライド
   */
  const _Game_Enemy_makeActions = Game_Enemy.prototype.makeActions;
  Game_Enemy.prototype.makeActions = function () {
    // クリアする前に行動回数を保持しておく。
    // 下のmakeActionTimesに使うだけの使い捨て
    this._keepActionTimes = this._actions.length;

    _Game_Enemy_makeActions.apply(this, arguments);
  };

  /**
   * ●行動回数計算
   * ※Game_Battler.prototype.makeActionTimesをオーバライド
   */
  const _Game_Enemy_makeActionTimes = Game_Enemy.prototype.makeActionTimes;
  Game_Enemy.prototype.makeActionTimes = function () {
    // 既に一度行動済の場合は、行動回数を加算しない。
    if (this._firstActionDone) {
      // 現在の残り行動回数をそのまま取得
      return this._keepActionTimes;
    }
    return _Game_Enemy_makeActionTimes.apply(this, arguments);
  };

  /**
   * ●敵のターン参照
   */
  const _Game_Troop_turnCount = Game_Troop.prototype.turnCount;
  Game_Troop.prototype.turnCount = function () {
    let turnCount = _Game_Troop_turnCount.apply(this);

    // 行動再設定時はターン-1
    // そうしないとターン最初の行動決定と条件が一致しない
    if (mResetActionFlg) {
      turnCount -= 1;
    }
    return turnCount;
  };

  /**
   * ●仲間への対象選択
   */
  var _Game_Action_targetsForFriends = Game_Action.prototype.targetsForFriends;
  Game_Action.prototype.targetsForFriends = function () {
    var subject = this.subject();

    /*
     * 以下の条件を全て満たす場合が対象
     * ・行動主体が敵
     * ・死亡者向けではない
     * ・対象が使用者ではない
     * ・単体が対象
     * ・対象がランダム（敵の単体対象は原則ランダム扱い）
     * ・ダメージ・吸収ではない
     */
    if (
      subject.isEnemy() &&
      !this.isForDeadFriend() &&
      !this.isForUser() &&
      this.isForOne() &&
      this._targetIndex < 0 &&
      !this.isDamage() &&
      !this.isDrain()
    ) {
      var targets = [];
      var target;
      var unit = this.friendsUnit();

      // 回復制御を行う場合
      if (isControlRecover(subject)) {
        // HP回復の場合
        if (this.isHpRecover()) {
          // HPが最も減っている対象を選択
          target = unit.aliveMembers().reduce(function (a, b) {
            return a.hpRate() < b.hpRate() ? a : b;
          });
          targets.push(target);
          return targets;

          // MP回復の場合
        } else if (this.isMpRecover()) {
          // MPが最も減っている対象を選択
          target = unit.aliveMembers().reduce(function (a, b) {
            return a.mpRate() < b.mpRate() ? a : b;
          });
          targets.push(target);
          return targets;
        }
      }

      // ダメージタイプがなしで、かつ使用効果がある場合
      if (this.checkDamageType([0]) && this.item().effects.length > 0) {
        // 効果のある対象だけに候補を絞る
        const filterTargets = unit.aliveMembers().filter(function (m) {
          return this.testApplyEnemy(m);
        }, this);

        // 有効な対象が存在する場合
        if (filterTargets.length > 0) {
          // 絞った候補からさらにランダムで選択する。
          targets.push(randomTargetFilter(filterTargets));
          return targets;
        }
      }
    }

    // 条件を満たさない場合は元処理実行
    return _Game_Action_targetsForFriends.apply(this);
  };

  /**
   * ●相手サイドへの対象選択
   */
  var _Game_Action_targetsForOpponents =
    Game_Action.prototype.targetsForOpponents;
  Game_Action.prototype.targetsForOpponents = function () {
    var subject = this.subject();

    /*
     * 以下の条件を全て満たす場合が対象
     * ・行動主体が敵
     * ・単体が対象
     * ・対象がランダム（敵の単体対象は原則ランダム扱い）
     */
    if (subject.isEnemy() && this.isForOne() && this._targetIndex < 0) {
      // 制御対象かどうかの判定
      let controlFlg = false;

      /*
       * ・ダメージタイプがなし
       * ・使用効果が存在する
       */
      if (this.checkDamageType([0]) && this.item().effects.length > 0) {
        controlFlg = true;

        /*
         * ・ダメージタイプがＭＰダメージ／ＭＰ吸収
         */
      } else if (this.isMpEffect() && (this.isDamage() || this.isDrain())) {
        controlFlg = true;
      }

      // 制御対象として処理する。
      if (controlFlg) {
        const targets = [];
        const unit = this.opponentsUnit();

        // 効果のある対象だけに候補を絞る
        const filterTargets = unit.aliveMembers().filter(function (m) {
          return this.testApplyEnemy(m);
        }, this);

        // 有効な対象が存在する場合
        if (filterTargets.length > 0) {
          // 絞った候補からさらにランダムで選択する。
          targets.push(randomTargetFilter(filterTargets));
          return targets;
        }
      }
    }

    // 条件を満たさない場合は元処理実行
    return _Game_Action_targetsForOpponents.apply(this);
  };

  /**
   * 【独自定義】指定した対象集団の狙われ率合計を求める。
   */
  function tgrSumFilter(targets) {
    return targets.reduce(function (r, member) {
      return r + member.tgr;
    }, 0);
  }

  /**
   * 【独自定義】指定した対象集団から狙われ率に従ってランダムで選択する。
   */
  function randomTargetFilter(targets) {
    var tgrRand = Math.random() * tgrSumFilter(targets);
    var target = null;
    targets.forEach(function (member) {
      tgrRand -= member.tgr;
      if (tgrRand <= 0 && !target) {
        target = member;
      }
    });
    return target;
  }

  /**
   * ●敵の行動有効判定
   * ここで無効にした行動は取らない。
   */
  var Game_Enemy_isActionValid = Game_Enemy.prototype.isActionValid;
  Game_Enemy.prototype.isActionValid = function (action) {
    // 元処理実行（通常の使用条件判定）
    var ret = Game_Enemy_isActionValid.apply(this, arguments);
    // 使用不可なら、そこで終了
    if (ret == false) {
      return ret;
    }

    // 行動判定の実行を行う場合のみ
    if (isTestApply(this)) {
      // 引数のactionはJSONのパラメータそのままなので、
      // Game_Actionに変換する。
      var gameAction = new Game_Action(this);
      gameAction.setSkill(action.skillId);

      // 既にターン実行中ならば、速度補正技は使用候補から外す。
      if (isResetAction(this) && BattleManager.isInTurn()) {
        if (gameAction.item() && isSpeedException(gameAction)) {
          return false;
        }
      }

      // 使用者が対象
      if (gameAction.isForUser()) {
        // 効果が無効ならば、使用候補から外す。
        if (!gameAction.testApplyEnemy(this)) {
          return false;
        }

        // それ以外
      } else {
        // 対象がアクター向けかつ全員無効ならば、使用候補から外す。
        if (
          gameAction.isForOpponent() &&
          $gameParty.members().every(function (m) {
            return !gameAction.testApplyEnemy(m);
          })
        ) {
          return false;

          // 対象がエネミー向けかつ全員無効ならば、使用候補から外す。
        } else if (
          gameAction.isForFriend() &&
          $gameTroop.members().every(function (m) {
            return !gameAction.testApplyEnemy(m);
          })
        ) {
          return false;
        }
      }
    }

    // 使用可能と認定
    return true;
  };

  /**
   * 【独自】敵行動の有効判定
   * ここでtrueを返した場合に使用可能な行動となる。
   */
  Game_Action.prototype.testApplyEnemy = function (target) {
    const subject = this.subject();

    // 対象が戦闘不能の仲間向け
    if (this.isForDeadFriend()) {
      // 蘇生制御を行わない場合は常に有効
      if (!isControlForDead(subject)) {
        return true;
      }
      // 対象が戦闘不能なら有効
      if (target.isDead()) {
        return true;
      }
      // それ以外は無効
      return false;

      // それ以外で対象が戦闘不能（逃走も含む）なら無効
    } else if (!target.isAlive()) {
      return false;
    }

    // ダメージまたは吸収
    if (this.isDamage() || this.isDrain()) {
      // ＭＰダメージ／吸収制御を行う場合、
      // ＭＰ効果の場合、対象のＭＰが０なら無効
      if (isControlMpDrain(subject) && this.isMpEffect() && target.mp == 0) {
        return false;
      }

      // それ以外は有効
      return true;

      // 回復系の場合
    } else if (this.isRecover()) {
      // 回復制御を行わない場合は常に有効
      if (!isControlRecover(subject)) {
        return true;
      }

      // 効果がHP回復、かつ対象のHPが減っている。
      if (this.isHpRecover() && isRoutineHpRecover(subject, target)) {
        return true;
        // 効果がMP回復、かつ対象のMPが減っている。
      } else if (this.isMpRecover() && isRoutineMpRecover(subject, target)) {
        return true;
      }

      // ダメージタイプがなし
    } else if (this.checkDamageType([0])) {
      // 対象がアクター、かつ使用効果制御を行わない場合は有効
      if (target.isActor() && !isControlActorEffect(subject)) {
        return true;
        // 対象がエネミー、かつ使用効果制御を行わない場合は有効
      } else if (target.isEnemy() && !isControlEnemyEffect(subject)) {
        return true;
      }

      // 使用効果が空（様子を見るなどの何もしない行動）なら有効
      if (this.item().effects.length == 0) {
        return true;
        // 何らかの使用効果が得られるなら有効
      } else if (this.hasItemAnyValidEffects(target)) {
        // ただし、身代わりスキルの場合
        if (
          pImproveSubstitute &&
          this.isForFriend() &&
          this.isSubstituteSkill()
        ) {
          // 人数が一人だと無効
          if (this.friendsUnit().aliveMembers().length == 1) {
            return false;
          }
        }
        // それ以外は有効
        return true;
      }
    }

    // それ以外は無効と判定
    return false;
  };

  /**
   * ●使用効果を得られるか？
   */
  var _Game_Action_testItemEffect = Game_Action.prototype.testItemEffect;
  Game_Action.prototype.testItemEffect = function (target, effect) {
    // 敵限定
    if (this.subject().isEnemy()) {
      // ステート追加の場合のみ、無効かどうかを判定
      switch (effect.code) {
        case Game_Action.EFFECT_ADD_STATE:
          // 対象の耐性を参照する。
          if (isRoutineWatchResist(this.subject())) {
            // 対象がステート無効である。
            if (target.isStateResist(effect.dataId)) {
              return false;
              // 行動が必中ではない。かつ、ステート成功率が０
            } else if (
              !this.isCertainHit() &&
              target.stateRate(effect.dataId) == 0
            ) {
              return false;
            }
          }
      }
    }

    // 元処理実行
    return _Game_Action_testItemEffect.apply(this, arguments);
  };

  /**
   * 【独自】身代わりスキルかどうか？
   */
  Game_Action.prototype.isSubstituteSkill = function () {
    // 使用効果に身代わりステートがあるか確認
    return this.item().effects.some((effect) => isEffectSubstitute(effect));
  };

  /**
   * ●使用効果が身代わりかどうか？
   */
  function isEffectSubstitute(effect) {
    // ステート付加である
    if (effect.code == Game_Action.EFFECT_ADD_STATE) {
      // さらにステート情報を確認
      const dataState = $dataStates[effect.dataId];
      // 特徴が特殊フラグかつ身代わりである。
      if (
        dataState.traits.some(
          (trait) =>
            trait.code == Game_BattlerBase.TRAIT_SPECIAL_FLAG &&
            trait.dataId == Game_BattlerBase.FLAG_ID_SUBSTITUTE
        )
      ) {
        return true;
      }
    }
    return false;
  }

  /**
   * ●エネミーに対する戦闘行動の強制
   */
  const Game_Enemy_forceAction = Game_Enemy.prototype.forceAction;
  Game_Enemy.prototype.forceAction = function (skillId, targetIndex) {
    // 対象が-1（ランダム）の場合、decideRandomTarget()を経由せず、
    // そのまま返すように修正。
    // ここで確定してしまうと対象の補正を行えなくなる。
    if (targetIndex === -1) {
      this.clearActions();
      var action = new Game_Action(this, true);
      action.setSkill(skillId);
      action.setTarget(targetIndex);
      this._actions.push(action);
      return;
    }

    // 元処理実行
    Game_Enemy_forceAction.apply(this, arguments);
  };
})();


code:
//=============================================================================
// NRP_EquipSlot.js
//=============================================================================
/*:
 * @target MV MZ
 * @plugindesc v1.04 Change the equipment slots at will.
 * @author Takeshi Sunagawa (http://newrpg.seesaa.net/)
 * @url https://newrpg.seesaa.net/article/489626316.html
 *
 * @help Change the equipment slots at will.
 *
 * By default, Equipment Type and Actor's equipment slots
 * are one-to-one, but can be changed freely.
 * For example, it will be possible to equip multiple accessories.
 *
 * Furthermore, you can set up additional equipment slots
 * for each actor, class, weapon, armor, state, and skill.
 * Specifically, the following system can be implemented.
 *
 * - Add a slot for an accessory for each equipment.
 *   (so-called materia)
 * - Only mages can equip multiple grimoires.
 * - A passive skill that increases the number of equipment slots
 *   when learned.
 *
 * -------------------------------------------------------------------
 * [Usage]
 * -------------------------------------------------------------------
 * Fill in the following in the notes field
 * for the Actor, Class, Weapon, Armor, State, or Skill.
 *
 * <AddEquipSlot:[Equipment Type]>
 *
 * A slot of the specified Equipment Type is added.
 * For example, <AddEquipSlot:5> for "05:Accessories".
 *
 * You can further specify the number of slots
 * by adding a comma-separated list of numbers.
 *
 * <AddEquipSlot:5, 2>
 *
 * This will add two slots for accessories.
 * ※The number of slots can also be negative.
 *   However, the behavior of a combination
 *   of plus and minus is not guaranteed.
 *
 * If you want to add multiple Equipment Types, you can write
 *
 * <AddEquipSlot:5, 2>
 * <AddEquipSlot2:6, 2>
 * <AddEquipSlot3:7>
 *
 * ※Please note that the order is: unmarked, 2, 3.
 *
 * -------------------------------------------------------------------
 * [Show on another page]
 * -------------------------------------------------------------------
 * The equipment window can be split into multiple pages.
 * For example, if a detachable skill system
 * is to be implemented with this plugin,
 * it would be easier to understand if it is
 * on a separate page from the normal equipment.
 *
 * ◆Procedure
 * Set the equipment type in "PagingEquipmentType" as a numerical value.
 * If 5 is specified, equipment types after 5
 * will be displayed on a separate page.
 *
 * Multiple specifications, such as "4,6", are also possible.
 * In this case, the equipment types will be displayed
 * on three separate pages: "1~3", "4~5", and "6~".
 *
 * Note that the page can be switched from left to right.
 * With the mouse, you can switch between them with the wheel.
 *
 * However, it may be difficult for the player
 * to notice in the standard way.
 * Also, the display is a little suspect in MV.
 *
 * If combined with a plugin that paginates the window (NRP_PageWindow.js),
 * it is possible to display the switching symbol.
 * https://newrpg.seesaa.net/article/475347742.html
 *
 * -------------------------------------------------------------------
 * [Acknowledgements]
 * -------------------------------------------------------------------
 * The following plugin was used as a reference
 * in the creation of this plugin.
 *
 * ◆EquipSlotAddTrait.js (Yana)
 * https://w.atwiki.jp/pokotan/pages/3.html
 *
 * -------------------------------------------------------------------
 * [Terms]
 * -------------------------------------------------------------------
 * There are no restrictions.
 * Modification, redistribution freedom, commercial availability,
 * and rights indication are also optional.
 * The author is not responsible,
 * but will deal with defects to the extent possible.
 *
 * @------------------------------------------------------------------
 * @ [Plugin Parameters]
 * @------------------------------------------------------------------
 *
 * @param DefaultEquipSlots
 * @type number[]
 * @desc Initial configuration of slots (Equipment Type).
 * Multiple equipment can be equipped by specifying the same number.
 *
 * @param AdjustInitEquip
 * @type boolean
 * @default true
 * @desc If there is a mismatch between equipment type and DefaultEquipSlots order, also allow the initial equipment to take effect.
 *
 * @param DualWieldPosition
 * @type number
 * @default 2
 * @desc The position where the weapon is equipped during Dual Wield.
 * The standard position is second.
 *
 * @param PagingEquipmentType
 * @type string
 * @desc Display the specified Equipment Type or later on a separate page.
 * Multiple items can be specified. (e.g.: 4,6)
 *
 * @param StatusShowSlots
 * @type number[]
 * @desc Equipment type to be displayed on the status screen.
 * If blank, it will be displayed until it fits in the window.
 */

/*:ja
 * @target MV MZ
 * @plugindesc v1.04 装備スロットを自由に変更。
 * @author 砂川赳（http://newrpg.seesaa.net/）
 * @url https://newrpg.seesaa.net/article/489626316.html
 *
 * @help 装備スロットを自由に変更します。
 *
 * 標準では装備タイプとアクターの装備スロットは１対１ですが、
 * 自由に変更できるようにします。
 * 例えば、装飾品を複数装備できるようになります。
 *
 * さらにはアクター、職業、武器、防具、ステート、スキル毎に、
 * 装備スロットを追加する設定が可能です。
 * 具体的には以下のようなシステムを実現できます。
 *
 * ・装備毎に装飾品スロットを追加する。（いわゆるマテリア）
 * ・魔道士のみが魔道書を複数装備できる。
 * ・習得すると装備スロットが増えるパッシブスキル。
 *
 * -------------------------------------------------------------------
 * ■使用法
 * -------------------------------------------------------------------
 * 以下をアクター、職業、武器、防具、ステート、スキルの
 * いずれかのメモ欄に記入してください。
 *
 * <AddEquipSlot:[装備タイプ]>
 *
 * 指定した装備タイプのスロットが追加されます。
 * 例えば『05:装飾品』ならば<AddEquipSlot:5>です。
 *
 * さらにカンマ区切りで数値を追加すれば、スロット数を指定できます。
 *
 * <AddEquipSlot:5, 2>
 *
 * これで装飾品のスロットが２つ追加されます。
 * ※スロット数はマイナスも可能です。
 * 　ただし、プラスとマイナスを組み合わせた場合の挙動は保証しません。
 *
 * 複数の装備タイプを追加したい場合は以下のように記述できます。
 *
 * <AddEquipSlot:5, 2>
 * <AddEquipSlot2:6, 2>
 * <AddEquipSlot3:7>
 *
 * ※無印, 2, 3の順序であることにご注意ください。
 *
 * -------------------------------------------------------------------
 * ■別ページに表示
 * -------------------------------------------------------------------
 * 装備画面を複数のページに分割することが可能です。
 * 例えば、着脱可能なスキルシステムを当プラグインで実現する場合など、
 * 通常の装備とは別ページにすると分かりやすいです。
 *
 * ◆手順
 * 『ページ切替する装備タイプ』に装備タイプを数値で設定してください。
 * 5を指定した場合、5以降の装備タイプが別ページに表示されます。
 *
 * 「4,6」というように複数指定も可能です。
 * この場合「1~3」「4~5」「6~」の３ページに装備タイプが分かれます。
 *
 * なお、ページは左右で切り替えできます。
 * マウス操作ではホイールで切り替えできます。
 *
 * ただし、標準ではプレイヤーが気づきにくいかもしれません。
 * また、ＭＶでは少し表示が怪しいです。
 *
 * ウィンドウをページ化するプラグイン（NRP_PageWindow.js）と
 * 組み合わせれば、切替記号を表示することも可能です。
 * https://newrpg.seesaa.net/article/475347742.html
 *
 * -------------------------------------------------------------------
 * ■謝辞
 * -------------------------------------------------------------------
 * このプラグインの作成に当たり、以下のプラグインを参考にさせて頂きました。
 *
 * ◆EquipSlotAddTrait.js（装備スロット追加特徴）（Yana様）
 * https://w.atwiki.jp/pokotan/pages/3.html
 *
 * -------------------------------------------------------------------
 * ■利用規約
 * -------------------------------------------------------------------
 * 特に制約はありません。
 * 改変、再配布自由、商用可、権利表示も任意です。
 * 作者は責任を負いませんが、不具合については可能な範囲で対応します。
 *
 * @------------------------------------------------------------------
 * @ プラグインパラメータ
 * @------------------------------------------------------------------
 *
 * @param DefaultEquipSlots
 * @text 初期スロット構成
 * @type number[]
 * @desc 装備スロット（装備タイプ）の初期構成です。
 * 同じ番号を複数指定すれば重複装備も可能です。
 *
 * @param AdjustInitEquip
 * @text 初期装備を自動調整
 * @type boolean
 * @default true
 * @desc 装備タイプと初期スロット構成の順序が不一致な場合、初期装備が有効にならない問題に対処します。
 *
 * @param DualWieldPosition
 * @text 二刀流の位置
 * @type number
 * @default 2
 * @desc 二刀流の際に武器を装備する位置です。
 * 標準では二番目になります。
 *
 * @param PagingEquipmentType
 * @text ページ切替する装備タイプ
 * @type string
 * @desc 指定の装備タイプ以降を別ページに表示します。
 * 複数指定可能です。（例：4,6）
 *
 * @param StatusShowSlots
 * @text ステータスの表示スロット
 * @type number[]
 * @desc ステータス画面に表示する装備スロット（装備タイプ）です。
 * 空白時はウィンドウに収まるまで全表示します。
 */

(function () {
  "use strict";

  /**
   * ●構造体をJSで扱えるように変換
   */
  function parseStruct1(arg) {
    if (arg == undefined || arg == "") {
      return undefined;
    }
    const ret = [];
    for (const str of JSON.parse(arg)) {
      ret.push(str);
    }
    return ret;
  }
  function toBoolean(str, def) {
    if (str === true || str === "true") {
      return true;
    } else if (str === false || str === "false") {
      return false;
    }
    return def;
  }
  function toNumber(str, def) {
    if (str == undefined || str == "") {
      return def;
    }
    return isNaN(str) ? def : +(str || def);
  }
  function setDefault(str, def) {
    if (str == undefined || str == "") {
      return def;
    }
    return str;
  }

  const PLUGIN_NAME = "NRP_EquipSlot";
  const parameters = PluginManager.parameters(PLUGIN_NAME);
  const pDefaultEquipSlots = parseStruct1(parameters["DefaultEquipSlots"]);
  const pAdjustInitEquip = toBoolean(parameters["AdjustInitEquip"], false);
  const pDualWieldPosition = toNumber(parameters["DualWieldPosition"], 2);
  const pPagingEquipmentType = parameters["PagingEquipmentType"];
  const pStatusShowSlots = parseStruct1(parameters["StatusShowSlots"]);

  //-----------------------------------------------------------------------------
  // Game_Actor
  //-----------------------------------------------------------------------------

  if (pAdjustInitEquip) {
    /**
     * 【上書】装備の初期化
     * ※初期状態では装備タイプと装備スロットがほぼ１対１で対応しているが、
     * 　スロット構造が変更された場合に対処できなくなる。
     * 　そこで改めて再設定を行う。
     */
    Game_Actor.prototype.initEquips = function (equips) {
      const slots = this.equipSlots();
      const maxSlots = slots.length;
      this._equips = [];

      // 初期装備の装備タイプとＩＤの組を作成
      const initEquipsMap = [];
      for (let i = 0; i < equips.length; i++) {
        let etype = i + 1;
        // 二刀流の位置かつアクターが二刀流の場合
        if (i == pDualWieldPosition - 1 && this.isDualWield()) {
          // 盾 -> 武器に変更
          etype = 1;
        }
        initEquipsMap.push({ etype: etype, equipId: equips[i], set: false });
      }

      // 装備スロット毎のループ
      for (let i = 0; i < maxSlots; i++) {
        // 装備スロットを初期化する。
        this._equips[i] = new Game_Item();

        // スロットに対応する装備タイプを取得
        const slotEType = slots[i];

        // 初期装備の装備情報でループ
        for (const initEquipMap of initEquipsMap) {
          const initEtype = initEquipMap.etype;
          const initEquipId = initEquipMap.equipId;
          const initSet = initEquipMap.set;

          // 装備タイプが一致する場合
          // ※ただし、設定済の場合は除外
          if (!initSet && initEtype == slotEType) {
            // 初期装備を設定
            this._equips[i].setEquip(slotEType === 1, initEquipId);
            // 設定済のフラグを立てる。
            initEquipMap.set = true;
            break;
          }
        }
      }

      // 無効な装備を外す。
      this.releaseUnequippableItems(true);
      // リフレッシュ
      this.refresh();
    };
  }

  if (pDefaultEquipSlots) {
    /**
     * 【上書】スロットの生成
     * ※装備タイプとは独立して初期スロットを設定
     */
    Game_Actor.prototype.equipSlots = function () {
      const slots = [];
      // 初期スロットを設定
      for (const etype of pDefaultEquipSlots) {
        slots.push(eval(etype));
      }
      // 二刀流
      if (slots.length >= 2 && this.isDualWield()) {
        slots[1] = 1;
      }
      return slots;
    };
  }

  /**
   * ●スロットの生成
   */
  const _Game_Actor_equipSlots = Game_Actor.prototype.equipSlots;
  Game_Actor.prototype.equipSlots = function () {
    const slots = _Game_Actor_equipSlots.apply(this, arguments);

    // メモ欄を参照するオブジェクト
    // 特徴系＋スキル
    const objects = this.traitObjects().concat(this.skills());
    for (const object of objects) {
      const addEquipSlot = object.meta.AddEquipSlot;
      changeSlots(slots, addEquipSlot);

      // <AddEquipSlot2>, <AddEquipSlot3>というように数値が尽きるまで処理
      // ※最大１００までループ
      for (let i = 2; i <= 100; i++) {
        const addEquipSlotNumber = object.meta["AddEquipSlot" + i];
        // 該当のタグがなければ終了
        if (!addEquipSlotNumber) {
          break;
        }
        // 該当のタグがあれば処理
        changeSlots(slots, addEquipSlotNumber);
      }
    }

    // スロットを数値順でソート
    slots.sort((a, b) => a - b);

    // nullのスロットは他の処理でエラーになるため初期化
    for (let i = 0; i < slots.length; i++) {
      if (this._equips[i] == null) {
        this._equips[i] = new Game_Item();
      }
    }

    return slots;
  };

  /**
   * ●スロット情報を変更
   */
  function changeSlots(slots, addEquipSlot) {
    // タグが存在しない場合
    if (!addEquipSlot) {
      return;
    }

    // カンマ区切りを分解
    const addEquipSlotArray = addEquipSlot.split(",");
    const etype = eval(addEquipSlotArray[0]);
    const count = eval(addEquipSlotArray[1]) || 1;
    // 値が＋の場合はスロット追加
    if (count > 0) {
      for (let i = 0; i < count; i++) {
        slots.push(etype);
      }
      // 値が－の場合はスロット除去
    } else if (count < 0) {
      for (let i = 0; i > count; i--) {
        const deleteIndex = slots.indexOf(etype);
        if (deleteIndex >= 0) {
          slots.splice(deleteIndex, 1);
        }
      }
    }
  }

  //-----------------------------------------------------------------------------
  // ページ切替機能（Scene_Equip & Window_EquipSlot）
  //-----------------------------------------------------------------------------

  if (pPagingEquipmentType) {
    // ページ切替する装備タイプを配列化
    const mPagingEquipmentTypes = pPagingEquipmentType.split(",");

    /**
     * ●装備選択
     */
    const _Scene_Equip_commandEquip = Scene_Equip.prototype.commandEquip;
    Scene_Equip.prototype.commandEquip = function () {
      _Scene_Equip_commandEquip.apply(this, arguments);

      // ページの先頭を選択
      const topIndex = this._slotWindow.topIndex();
      this._slotWindow.select(topIndex);
    };

    /**
     * ●初期化
     */
    const _Window_EquipSlot_initialize = Window_EquipSlot.prototype.initialize;
    Window_EquipSlot.prototype.initialize = function (rect) {
      _Window_EquipSlot_initialize.apply(this, arguments);

      // 現在のページを設定
      this._pageNo = 0;
    };

    /**
     * 【独自】最大ページ番号（0始まり）
     */
    Window_EquipSlot.prototype.maxPageNo = function () {
      let maxPageNo = 0;

      // ２ページ目以降の存在をチェック
      for (let i = 0; i < mPagingEquipmentTypes.length; i++) {
        if (this.pageItemsCount(i + 1) > 0) {
          maxPageNo++;
        }
      }

      return maxPageNo;
    };

    /**
     * ●上キー
     */
    const _Window_EquipSlot_cursorUp = Window_EquipSlot.prototype.cursorUp;
    Window_EquipSlot.prototype.cursorUp = function (wrap) {
      // 移動先が現在のページの最小要素を下回っていた場合
      if (this._index - 1 < this.pageTopIndex()) {
        // 連続移動でない場合は末尾に移動
        if (wrap) {
          this.select(this.pageLastIndex());
        }
        // 連続移動の場合は停止
        return;
      }
      _Window_EquipSlot_cursorUp.apply(this, arguments);
    };

    /**
     * ●下キー
     */
    const _Window_EquipSlot_cursorDown = Window_EquipSlot.prototype.cursorDown;
    Window_EquipSlot.prototype.cursorDown = function (wrap) {
      // 移動先が現在のページの最大要素を超えていた場合
      if (this._index + 1 > this.pageLastIndex()) {
        // 連続移動でない場合は先頭に移動
        if (wrap) {
          this.select(this.pageTopIndex());
        }
        // 連続移動の場合は停止
        return;
      }
      _Window_EquipSlot_cursorDown.apply(this, arguments);
    };

    /**
     * ●右キー
     */
    Window_EquipSlot.prototype.cursorRight = function (wrap) {
      // ページ切替前に表示上の位置を取得
      const posIndex = this.posIndex();
      // 次のページへ
      this._pageNo++;
      // 最大ページを超えたら0ページへループ
      if (this._pageNo > this.maxPageNo()) {
        this._pageNo = 0;
      }
      // 現在値を更新
      let index = posIndex + this.topIndex();
      // カーソルが領域外になった場合は調整
      if (posIndex >= this.maxVisibleItems()) {
        index = this.topIndex() + this.maxVisibleItems() - 1;
      }
      this.select(index);
      // 再描画
      this.paint();
    };

    /**
     * ●左キー
     */
    Window_EquipSlot.prototype.cursorLeft = function (wrap) {
      // ページ切替前に表示上の位置を取得
      const posIndex = this.posIndex();
      // 前のページヘ
      this._pageNo--;
      // マイナスになったら最大ページループ
      if (this._pageNo < 0) {
        this._pageNo = this.maxPageNo();
      }
      // 現在値を更新
      let index = posIndex + this.topIndex();
      // カーソルが領域外になった場合は調整
      if (posIndex >= this.maxVisibleItems()) {
        index = this.topIndex() + this.maxVisibleItems() - 1;
      }
      this.select(index);
      // 再描画
      this.paint();
    };

    /**
     * 【独自】画面上のインデックス
     */
    Window_EquipSlot.prototype.posIndex = function () {
      return this.index() - this.topIndex();
    };

    /**
     * 【上書】先頭のインデックス
     */
    Window_EquipSlot.prototype.topIndex = function () {
      if (!this._actor || this._pageNo == 0) {
        return 0;
      }
      //-----------------------------------------------
      // ページ数を元に先頭のインデックスを求める。
      //-----------------------------------------------
      let index = 0;
      // 各ページ毎の項目数を加算
      for (let p = 0; p < this._pageNo; p++) {
        index += this.pageItemsCount(p);
      }
      return index;
    };

    /**
     * 【独自】ページ先頭要素のインデックス
     * ※topIndexと分ける必要があったかは謎……。
     */
    Window_EquipSlot.prototype.pageTopIndex = function () {
      return this.topIndex();
    };

    /**
     * 【独自】ページ末尾要素のインデックス
     */
    Window_EquipSlot.prototype.pageLastIndex = function () {
      return this.topIndex() + this.maxPageAllRows() - 1;
    };

    /**
     * 【上書】スクロール計算用の縦幅
     */
    Window_EquipSlot.prototype.overallHeight = function () {
      // ページ毎の行数で計算
      return this.maxPageAllRows() * this.itemHeight();
    };

    /**
     * ●選択
     */
    const _Window_EquipSlot_select = Window_EquipSlot.prototype.select;
    Window_EquipSlot.prototype.select = function (index) {
      // 現在のページの最大要素を超えていた場合は末尾に移動
      if (index > this.pageLastIndex()) {
        index = this.pageLastIndex();
      }
      _Window_EquipSlot_select.call(this, index);
    };

    /**
     * ●スムース選択
     */
    Window_EquipSlot.prototype.smoothSelect = function (index) {
      this.select(index);
      // ↓相性が悪いようなので注釈化
      // this.ensureCursorVisible(true);
    };

    /**
     * 【上書】装備項目を全描画
     */
    Window_EquipSlot.prototype.drawAllItems = function () {
      const topIndex = this.topIndex();
      for (let i = 0; i < this.maxVisibleItems(); i++) {
        // 実インデックスを取得
        const index = topIndex + i;
        if (index < this.maxItems()) {
          // ＭＺのみの関数
          if (this.drawItemBackground) {
            this.drawItemBackground(index);
          }
          this.drawItem(index);
        }
      }
    };

    /**
     * 【上書】項目描画範囲を取得
     */
    Window_EquipSlot.prototype.itemRect = function (index) {
      // 表示上の位置
      const posIndex = index - this.topIndex();

      const maxCols = this.maxCols();
      const itemWidth = this.itemWidth();
      const itemHeight = this.itemHeight();
      const colSpacing = this.colSpacing();
      const rowSpacing = this.rowSpacing();
      const col = index % maxCols;
      const row = Math.floor(posIndex / maxCols);
      const x = col * itemWidth + colSpacing / 2 - this.scrollBaseX();

      // スクロール分を加算しない。
      // const y = row * itemHeight + rowSpacing / 2 - this.scrollBaseY();
      const y = row * itemHeight + rowSpacing / 2;

      const width = itemWidth - colSpacing;
      const height = itemHeight - rowSpacing;
      return new Rectangle(x, y, width, height);
    };

    /**
     * 【ＭＶ対応】列のスペース
     */
    if (!Window_EquipSlot.prototype.colSpacing) {
      Window_EquipSlot.prototype.colSpacing = function () {
        return 0;
      };
    }

    /**
     * 【ＭＶ対応】行のスペース
     */
    if (!Window_EquipSlot.prototype.rowSpacing) {
      Window_EquipSlot.prototype.rowSpacing = function () {
        return 0;
      };
    }

    /**
     * 【ＭＶ対応】横スクロールの基本値
     */
    if (!Window_EquipSlot.prototype.scrollBaseX) {
      Window_EquipSlot.prototype.scrollBaseX = function () {
        return 0;
      };
    }

    /**
     * 【ＭＶ対応】横スクロールの基本値描画
     */
    if (!Window_EquipSlot.prototype.paint) {
      Window_EquipSlot.prototype.paint = function () {
        return this.refresh();
      };
    }

    /**
     * 【独自】スクロール分を含めたページ内の全行数
     */
    Window_EquipSlot.prototype.maxPageAllRows = function () {
      if (!this._actor) {
        return 0;
      }

      const topIndex = this.topIndex();
      const slots = this._actor.equipSlots();

      let i = 0;
      for (i = topIndex; i < slots.length; i++) {
        const slotId = slots[i];

        // ページ先頭のスロットＩＤは除外
        if (slotId == slots[topIndex]) {
          continue;

          // 該当のスロットＩＤに対してページ切替を行うかどうか？
        } else if (existPagingEquipmentType(this._pageNo, slotId)) {
          // 切り替えるタイミングでのインデックス（i）を保持
          break;
        }
      }
      return i - topIndex;
    };

    /**
     * 【上書】画面内に表示する項目数
     */
    Window_EquipSlot.prototype.maxVisibleItems = function () {
      return this.maxPageAllRows();

      // ↓スクロール対応用（未実装）
      // ページ内に収まる行数とページ内の全行数の小さいほう
      // return Math.min(this.maxPageRows(), this.maxPageAllRows());
    };

    /**
     * ●該当のスロットＩＤに対してページ切替を行うかどうか？
     */
    function existPagingEquipmentType(pageNo, slotId) {
      // 現在ページから最終ページまでをチェック
      for (let i = pageNo; i < mPagingEquipmentTypes.length; i++) {
        // ページ切替する装備タイプがあれば、そこでインデックスを返す
        if (mPagingEquipmentTypes[i] && mPagingEquipmentTypes[i] == slotId) {
          return true;
        }
      }
      return false;
    }

    /**
     * 【独自】指定ページに表示する項目数
     */
    Window_EquipSlot.prototype.pageItemsCount = function (pageNo) {
      if (!this._actor) {
        return 0;
      }

      const slots = this._actor.equipSlots();
      // slotsを各ページに分割
      const pageSlots = [];
      // ページ数分の配列を初期化
      for (let i = 0; i <= mPagingEquipmentTypes.length; i++) {
        pageSlots[i] = [];
      }
      // アクターのスロット毎にループ
      for (const slot of slots) {
        let pushFlg = false;

        for (let i = 0; i < mPagingEquipmentTypes.length; i++) {
          if (slot < mPagingEquipmentTypes[i]) {
            pageSlots[i].push(slot);
            pushFlg = true;
            break;
          }
        }

        // 該当がなかった場合は最後のページへ追加
        if (!pushFlg) {
          pageSlots[mPagingEquipmentTypes.length].push(slot);
        }
      }

      // 空のページは除去
      const filterPageSlots = pageSlots.filter(
        (pageSlot) => pageSlot.length > 0
      );

      // 有効なページなら指定ページの件数を取得
      if (filterPageSlots[pageNo]) {
        return filterPageSlots[pageNo].length;
      }
      return 0;
    };

    /**
     * 【独自】ページが存在するかどうか？
     */
    Window_EquipSlot.prototype.existPage = function () {
      // ページ処理対象外の場合
      if (!this.isUsePage()) {
        return false;
      }
      // 最大ページ数が０の場合
      if (this.maxPageNo() == 0) {
        return false;
      }
      return true;
    };

    /**
     * 【上書】ホイールでページ切替
     */
    Window_EquipSlot.prototype.processWheelScroll = function () {
      if (
        this.isOpenAndActive() &&
        this.isWheelScrollEnabled() &&
        this.isTouchedInsideFrame()
      ) {
        const threshold = 20;
        if (TouchInput.wheelY >= threshold) {
          this.cursorRight();
        }
        if (TouchInput.wheelY <= -threshold) {
          this.cursorLeft();
        }
      }
    };
  }

  //-----------------------------------------------------------------------------
  // Window_StatusEquip
  //-----------------------------------------------------------------------------

  if (pStatusShowSlots) {
    /**
     * 【上書】１ページ内に表示する項目数
     */
    Window_StatusEquip.prototype.maxVisibleItems = function () {
      const slots = this._actor.equipSlots();
      const filterSlots = slots.filter((slot) =>
        pStatusShowSlots.includes(String(slot))
      );
      return filterSlots.length;
    };

    /**
     * 【上書】全体の項目数
     */
    Window_StatusEquip.prototype.maxItems = function () {
      // 表示する項目数と一致させる。
      return this.maxVisibleItems();
    };
  }
})();

repo_name: dazed/translations

license: unknown


code:
//=============================================================================
// NRP_EventFastForward.js
//=============================================================================
/*:
 * @target MV MZ
 * @plugindesc v1.01 Extends event acceleration feature.
 * @author Takeshi Sunagawa (http://newrpg.seesaa.net/)
 * @url http://newrpg.seesaa.net/article/483801702.html
 *
 * @help Extends event acceleration feature.
 *
 * RPG Maker MV~MZ has a feature
 * that allows you to double the speed of an event
 * by holding down the decision key while the event is running.
 * I will make some extensions to that feature.
 *
 * ◆Main features.
 * - Faster sprite processing (animations, balloons, etc.)
 * - Change of keys to be used
 * - Change of execution speed
 * - Change the time to hold down the key before execution
 * - Touch UI side setting can be specified separately.
 *
 * For example, it is possible to speed up the entire event
 * by combining the key with a message skipping plugin.
 *
 * ■Acknowledgements
 * This plugin is inspired
 * by FastForwardCustomize.js created by Triacontane.
 *
 * [Terms]
 * There are no restrictions.
 * Modification, redistribution freedom, commercial availability,
 * and rights indication are also optional.
 * The author is not responsible,
 * but will deal with defects to the extent possible.
 *
 * @param FastKey
 * @default ok
 * @type select
 * @option ok
 * @option cancel
 * @option shift
 * @option menu
 * @option pageup
 * @option pagedown
 * @option control
 * @option tab
 * @desc The key to perform event acceleration.
 *
 * @param SpeedMultiply
 * @type number
 * @default 2
 * @desc This is the event speed multiplier for speedup.
 * If 1 or less, the acceleration will be disabled.
 *
 * @param SpeedVariableId
 * @type variable
 * @desc Variable to set the event acceleration multiplier. If 1 or less, acceleration is disabled. Precedence over SpeedMultiply.
 *
 * @param PressWait
 * @type number
 * @default 24
 * @desc This is the wait time before starting the acceleration.
 * Specify in units of 1/60th of a second.
 *
 * @param FastSprite
 * @type boolean
 * @default false
 * @desc Speeds up sprite drawing.
 * Animations, balloons, etc. are also accelerated.
 *
 * @param <Touch>
 * @desc This is the event acceleration setting for touch operations.
 * If not specified, the same settings as above will be used.
 *
 * @param TouchSpeedMultiply
 * @parent <Touch>
 * @type number
 * @desc This is the event speed multiplier for speedup.
 * If 1 or less, the acceleration will be disabled.
 *
 * @param TouchSpeedVariableId
 * @parent <Touch>
 * @type variable
 * @desc Variable to set the event acceleration multiplier. If 1 or less, acceleration is disabled. Precedence over SpeedMultiply.
 *
 * @param TouchPressWait
 * @parent <Touch>
 * @type number
 * @desc This is the wait time before starting the acceleration.
 * Specify in units of 1/60th of a second.
 */

/*:ja
 * @target MV MZ
 * @plugindesc v1.01 イベント高速化機能を拡張します。
 * @author 砂川赳（http://newrpg.seesaa.net/）
 * @url http://newrpg.seesaa.net/article/483801702.html
 *
 * @help イベント高速化機能を拡張します。
 *
 * ツクールＭＶ～ＭＺにはイベント実行中に、
 * 決定キーを押しっぱなしにすると動作が倍速化する機能があります。
 * その機能に対していくつかの拡張を行います。
 *
 * ◆主な機能
 * ・描画処理（アニメーション、フキダシなど）の高速化
 * ・使用するキーの変更
 * ・実行速度の変更
 * ・実行までの押しっぱなし時間の変更
 * ・タッチＵＩ側の設定も別途指定可
 *
 * 例えば、メッセージスキップ系のプラグインとキーを合わせることで、
 * イベント全体を高速化することも可能です。
 *
 * ■謝辞
 * 当プラグインはトリアコンタン様作成の
 * FastForwardCustomize.jsを参考にさせて頂きました。
 *
 * ■利用規約
 * 特に制約はありません。
 * 改変、再配布自由、商用可、権利表示も任意です。
 * 作者は責任を負いませんが、不具合については可能な範囲で対応します。
 *
 * @param FastKey
 * @text 高速化キー
 * @default ok
 * @type select
 * @option ok
 * @option cancel
 * @option shift
 * @option menu
 * @option pageup
 * @option pagedown
 * @option control
 * @option tab
 * @desc イベント高速化を実行するキーです。
 *
 * @param SpeedMultiply
 * @text 速度倍率
 * @type number
 * @default 2
 * @desc 高速化時のイベント速度の倍率です。
 * 1以下なら高速化は無効になります。
 *
 * @param SpeedVariableId
 * @text 速度倍率の変数
 * @type variable
 * @desc イベント高速化の倍率を設定する変数です。
 * 値が1以下なら高速化無効。速度倍率より優先されます。
 *
 * @param PressWait
 * @text 開始プレス時間
 * @type number
 * @default 24
 * @desc 高速化を開始するまでのウェイト時間です。
 * 1/60秒単位で指定してください。
 *
 * @param FastSprite
 * @text 描画処理を高速化
 * @type boolean
 * @default false
 * @desc 描画処理を高速化します。
 * アニメーションやフキダシなども高速化されます。
 *
 * @param <Touch>
 * @text ＜タッチ関連＞
 * @desc タッチ操作時のイベント高速化設定です。
 * 特に指定しなければ、上記と同じ設定を使用します。
 *
 * @param TouchSpeedMultiply
 * @parent <Touch>
 * @text 速度倍率（タッチ）
 * @type number
 * @desc 高速化時のイベント速度の倍率です。
 * 1以下なら高速化は無効になります。
 *
 * @param TouchSpeedVariableId
 * @parent <Touch>
 * @text 速度倍率の変数（タッチ）
 * @type variable
 * @desc イベント高速化の倍率を設定する変数です。
 * 値が1以下なら高速化無効。速度倍率より優先されます。
 *
 * @param TouchPressWait
 * @parent <Touch>
 * @text 開始プレス時間（タッチ）
 * @type number
 * @desc 高速化を開始するまでの待ち時間です。
 * 1/60秒単位で指定してください。
 */
(function () {
  "use strict";

  function toBoolean(str, def) {
    if (str === true || str === "true") {
      return true;
    } else if (str === false || str === "false") {
      return false;
    }
    return def;
  }
  function toNumber(str, def) {
    if (str == undefined || str == "") {
      return def;
    }
    return isNaN(str) ? def : +(str || def);
  }

  const PLUGIN_NAME = "NRP_EventFastForward";
  const parameters = PluginManager.parameters(PLUGIN_NAME);
  const pFastKey = parameters["FastKey"].toLowerCase();
  const pSpeedMultiply = toNumber(parameters["SpeedMultiply"], 2);
  const pSpeedVariableId = toNumber(parameters["SpeedVariableId"]);
  const pPressWait = toNumber(parameters["PressWait"], 24);
  const pFastSprite = toBoolean(parameters["FastSprite"], false);
  const pTouchSpeedMultiply = toNumber(
    parameters["TouchSpeedMultiply"],
    pSpeedMultiply
  );
  const pTouchSpeedVariableId = toNumber(
    parameters["TouchSpeedVariableId"],
    pSpeedVariableId
  );
  const pTouchPressWait = toNumber(parameters["TouchPressWait"], pPressWait);

  /**
   * ●高速化倍率の調整
   */
  const _Scene_Map_updateMainMultiply = Scene_Map.prototype.updateMainMultiply;
  Scene_Map.prototype.updateMainMultiply = function () {
    _Scene_Map_updateMainMultiply.apply(this, arguments);
    if (this.isFastForward()) {
      this.updateMainForMoreFast();
    }
  };

  /**
   * 【独自】高速化倍率に応じてupdateMainを呼び出す。
   */
  Scene_Map.prototype.updateMainForMoreFast = function () {
    let updateCount;

    // 通常入力の場合
    if (isLongPressed.bind(Input)(pFastKey)) {
      updateCount = getSpeedMultiply();

      // タッチ操作の場合
    } else if (isTouchLongPressed.bind(TouchInput)()) {
      updateCount = getTouchSpeedMultiply();
    }

    // 既に一度updateMainが呼び出されている想定なので、
    // ２を基準とする。
    updateCount -= 2;

    for (let i = 0; i < updateCount; i++) {
      this.updateMain();

      // スプライトセットも更新
      if (pFastSprite) {
        this._spriteset.update();
      }
    }
  };

  /**
   * 【上書】イベント高速化の実行中かどうか？
   */
  Scene_Map.prototype.isFastForward = function () {
    // イベント実行中ではない。
    // または、シーン変更時は無効。
    if (!$gameMap.isEventRunning() || SceneManager.isSceneChanging()) {
      return false;
    }

    // 通常入力の場合
    if (isLongPressed.bind(Input)(pFastKey)) {
      // 速度倍率が２以上なら有効
      return getSpeedMultiply() >= 2;

      // タッチ操作の場合
    } else if (isTouchLongPressed.bind(TouchInput)()) {
      // 速度倍率が２以上なら有効
      return getTouchSpeedMultiply() >= 2;
    }

    return false;
  };

  /**
   * ●高速化倍率
   */
  function getSpeedMultiply() {
    if (pSpeedVariableId) {
      return $gameVariables.value(pSpeedVariableId);
    }
    return pSpeedMultiply;
  }

  /**
   * ●高速化倍率（タッチＵＩ）
   */
  function getTouchSpeedMultiply() {
    if (pTouchSpeedVariableId) {
      return $gameVariables.value(pTouchSpeedVariableId);
    }
    return pTouchSpeedMultiply;
  }

  /**
   * ●押しっぱなし判定
   * ※coreのInput.isLongPressedを改造
   * ※Inputを渡して使用
   */
  function isLongPressed(keyName) {
    if (this._isEscapeCompatible(keyName) && this.isLongPressed("escape")) {
      return true;
    } else {
      return this._latestButton === keyName && this._pressedTime >= pPressWait;
    }
  }

  /**
   * ●タッチＵＩ用の押しっぱなし判定
   * ※coreのTouchInput.isLongPressedを改造
   * ※TouchInputを渡して使用
   */
  function isTouchLongPressed() {
    return this.isPressed() && this._pressedTime >= pTouchPressWait;
  }
})();