//=============================================================================
// SubstituteExtend.js
// ----------------------------------------------------------------------------
// (C)2017 Triacontane
// This software is released under the MIT License.
// http://opensource.org/licenses/mit-license.php
// ----------------------------------------------------------------------------
// Version
// 1.4.1 2020/06/10 1.4.0の修正でパラメータ「身代わり条件_必中以外」が消えていた問題を修正
// 1.4.0 2020/05/19 行動制約「行動できない」状態でも身代わりが発動するように既存仕様を変更する設定を追加
// 1.3.0 2020/04/14 混乱系のステート有効時は身代わりを発動しないように既存仕様を変更する設定を追加
// 1.2.0 2019/12/22 身代わりの判定仕様を変更し、身代わり後の反撃や魔法反射できる機能を追加
// 1.1.0 2018/09/09 身代わりを無効にするスキルやアイテムを直接指定できる機能を追加
// 1.0.2 2017/08/22 身代わり条件「必中以外」を無効にした場合でも必中攻撃に対する身代わりが発動しない場合がある問題を修正
// 1.0.1 2017/02/07 端末依存の記述を削除
// 1.0.0 2017/02/05 初版
// ----------------------------------------------------------------------------
// [Blog]   : https://triacontane.blogspot.jp/
// [Twitter]: https://twitter.com/triacontane/
// [GitHub] : https://github.com/triacontane/
//=============================================================================

/*:
 * @plugindesc SubstituteExtendPlugin
 * @author triacontane
 *
 * @param CondDying
 * @desc デフォルトの身代わり条件である「瀕死」を有効にします。OFFにすると無効になります。(ON/OFF)
 * @default true
 * @type boolean
 *
 * @param CondNonCertainHit
 * @desc デフォルトの身代わり条件である「必中以外」を有効にします。OFFにすると無効になります。(ON/OFF)
 * @default true
 * @type boolean
 *
 * @param SubstituteCounter
 * @desc 身代わりの判定仕様を変更し、身代わり後の反撃や魔法反射が有効になります。
 * @default false
 * @type boolean
 *
 * @param InvalidConfused
 * @desc 混乱（行動制約が「〇〇を攻撃」となっているステート）状態の場合、身代わりの発動を無効化します。
 * @default false
 * @type boolean
 *
 * @param ValidRestriction
 * @desc 行動不能（行動制約が「行動できない」のステート）状態の場合でも、身代わりの発動を有効化します。
 * @default false
 * @type boolean
 *
 * @help 身代わりの仕様を変更します。
 * まずRPGツクールMV本体の身代わり仕様について説明します。
 * ・身代わりする側
 * 　　生存している
 * 　　行動制約『行動できない』のステートに掛かっていない
 * 　　特徴『身代わり』を保持している
 * ・身代わりされる側
 * 　　瀕死（HPが1/4以下）
 * 　　身代わりする側と同一バトラーでない
 * ・身代わりが発動するスキル
 * 　　命中タイプが『必中』でない
 * ・身代わりの優先度
 * 　　パーティの先頭から順番に『身代わりするバトラー』を判定
 * ・勘違いされやすい仕様
 * 　　行動制約『〇〇を攻撃』のステートに掛かっていても身代わり可能
 * 　　スキルの『範囲』およびスキルの使用者は、身代わり判定とは一切関係ない
 * 　　（命中タイプが必中でないと回復や防御にも身代わりが発動する）
 * 　　判定は一度だけなので、以下のケースでは身代わりは発動しない
 * 　　　・1番目と2番目のバトラーが身代わり可能
 * 　　　・1番目のバトラーが身代わり対象
 *
 * 1. デフォルトの身代わり条件である以下を無効化できます。
 *  ・瀕死（HPが1/4以下）
 *  ・命中タイプが『必中』でない
 *  ・行動制約『行動できない』のステートに掛かっていない
 *
 * なお「命中タイプが『必中』でない」の条件を外すと、防御など無関係の
 * スキルに対しても無差別に身代わりが発動するようになります。
 * 注意して設定してください。
 *
 * 2. 身代わりの詳細な発動条件を細かく設定できます。
 * 特徴を有するデータベースのメモ欄に、以下の通り記述してください。
 * メモ欄の「全ての条件」を満たした場合に身代わりが発動します。
 * 基本的に「特徴」の「身代わり」とセットで記述します。
 *
 * なお、特徴を有するデータベースのメモ欄とは
 * アクター、職業、武器、防具、ステート、敵キャラのいずれかのメモ欄です。
 *
 * <SE_実行者HP率:50>       # 実行者のHPが50%以上のときのみ発動します。
 * <SE_SubjectHPRate:50>    # 同上
 * <SE_対象者HP率:50>       # 対象者のHPが50%以下のときのみ発動します。(※1)
 * <SE_TargetHPRate:50>     # 同上
 * <SE_身代わり対象限定:1>  # 身代わりの対象者を[1]に限定します。(※2)
 * <SE_TargetRestriction:1> # 同上
 * <SE_身代わりスイッチ:4>  # スイッチ[4]がONのときのみ発動します。
 * <SE_SubstituteSwitch:4>  # 同上
 * <SE_身代わり計算式:f>    # 計算式[f]の結果がtrueのときのみ発動します。(※3)
 * <SE_SubstituteFormula:f> # 同上
 *
 * ※1 パラメータからデフォルトの身代わり条件である「瀕死」を
 * 無効にした場合のみ判定します。
 *
 * ※2 <SE_身代わり対象限定:n>のタグの対象者を設定したい場合、
 * 特徴を有するデータベースのメモ欄に以下の通り記述します。
 *
 * <SE_身代わり対象者:1>   # <SE_身代わり対象限定:1>の対象になります。
 * <SE_SubstituteTarget:1> # 同上
 *
 * 「身代わり対象限定」で指定したパラメータと一致する場合に身代わり対象になります。
 * 例えば、メモ欄に<SE_身代わり対象限定:2>と記入した場合、同じくメモ欄に
 * <SE_身代わり対象者:2>と記入されている特徴を持つアクターに対してのみ
 * 身代わりを実行します。
 *
 * ※3 上級者向け機能です。
 * また、計算式中で不等号を使いたい場合、以下のように記述してください。
 * < → &lt;
 * > → &gt;
 *
 * 例：<SE_身代わり計算式:\v[2] &gt; 3> # 変数[2]が[3]より大きい場合、発動します。
 *
 * 計算式中では「action」で対象スキルのActionオブジェクトを参照できます。
 * うまく利用すれば身代わりが発動するスキルを細かく限定できます。
 *
 * 以下が記入例です。
 * <SE_身代わり計算式:action.isAttack()>    # 通常攻撃のみ身代わり発動
 * <SE_身代わり計算式:action.isPhysical()>  # 物理攻撃のみ身代わり発動
 * <SE_身代わり計算式:action.isForOne()>    # 単体対象のみ身代わり発動
 *
 * 3. 身代わり発動時に、指定したIDのスキル効果を身代わり実行者に
 * 適用させることができます。
 * <SE_身代わりスキルID:5>  # 身代わり発動時にスキル[5]を実行者に適用。(※1)
 * <SE_SubstituteSkillId:5> # 同上
 *
 * ※1 ダメージポップアップやアニメーション等の演出は表示されません。
 *
 * 4. 身代わりを無効にするスキルを個別指定できます。
 * スキルもしくはアイテムのメモ欄に以下の通り指定してください。
 * <SE_身代わり無効>
 * <SE_SubstituteInvalid>
 *
 * このプラグインにはプラグインコマンドはありません。
 *
 * 利用規約：
 *  作者に無断で改変、再配布が可能で、利用形態（商用、18禁利用等）
 *  についても制限はありません。
 *  このプラグインはもうあなたのものです。
 *
 * This plugin is released under the MIT License.
 */
/*:ja
 * @plugindesc 身代わり拡張プラグイン
 * @author トリアコンタン
 *
 * @param 身代わり条件_瀕死
 * @desc デフォルトの身代わりされる条件である「瀕死」を有効にします。OFFにすると無効になります。(ON/OFF)
 * @default true
 * @type boolean
 *
 * @param 身代わり条件_必中以外
 * @desc デフォルトの身代わり条件である「必中以外」を有効にします。OFFにすると無効になります。(ON/OFF)
 * @default true
 * @type boolean
 *
 * @param 身代わり反撃
 * @desc 身代わりの判定仕様を変更し、身代わり後の反撃や魔法反射が有効になります。
 * @default false
 * @type boolean
 *
 * @param 混乱時の身代わり無効
 * @desc 混乱（行動制約が「〇〇を攻撃」のステート）状態の場合、身代わりの発動を無効化します。
 * @default false
 * @type boolean
 *
 * @param 行動不能時の身代わり有効
 * @desc 行動不能（行動制約が「行動できない」のステート）状態の場合でも、身代わりの発動を有効化します。
 * @default false
 * @type boolean
 *
 * @help 身代わりの仕様を変更します。
 * まずRPGツクールMV本体の身代わり仕様について説明します。
 * ・身代わりする側
 * 　　生存している
 * 　　行動制約『行動できない』のステートに掛かっていない
 * 　　特徴『身代わり』を保持している
 * ・身代わりされる側
 * 　　瀕死（HPが1/4以下）
 * 　　身代わりする側と同一バトラーでない
 * ・身代わりが発動するスキル
 * 　　命中タイプが『必中』でない
 * ・身代わりの優先度
 * 　　パーティの先頭から順番に『身代わりするバトラー』を判定
 * ・勘違いされやすい仕様
 * 　　行動制約『〇〇を攻撃』のステートに掛かっていても身代わり可能
 * 　　スキルの『範囲』およびスキルの使用者は、身代わり判定とは一切関係ない
 * 　　（命中タイプが必中でないと回復や防御にも身代わりが発動する）
 * 　　判定は一度だけなので、以下のケースでは身代わりは発動しない
 * 　　　・1番目と2番目のバトラーが身代わり可能
 * 　　　・1番目のバトラーが身代わり対象
 *
 * 1. デフォルトの身代わり条件である以下を無効化できます。
 *  ・瀕死（HPが1/4以下）
 *  ・命中タイプが『必中』でない
 *  ・行動制約『行動できない』のステートに掛かっていない
 *
 * なお「命中タイプが『必中』でない」の条件を外すと、防御など無関係の
 * スキルに対しても無差別に身代わりが発動するようになります。
 * 注意して設定してください。
 *
 * 2. 身代わりの詳細な発動条件を細かく設定できます。
 * 特徴を有するデータベースのメモ欄に、以下の通り記述してください。
 * メモ欄の「全ての条件」を満たした場合に身代わりが発動します。
 * 基本的に「特徴」の「身代わり」とセットで記述します。
 *
 * なお、特徴を有するデータベースのメモ欄とは
 * アクター、職業、武器、防具、ステート、敵キャラのいずれかのメモ欄です。
 *
 * <SE_実行者HP率:50>       # 実行者のHPが50%以上のときのみ発動します。
 * <SE_SubjectHPRate:50>    # 同上
 * <SE_対象者HP率:50>       # 対象者のHPが50%以下のときのみ発動します。(※1)
 * <SE_TargetHPRate:50>     # 同上
 * <SE_身代わり対象限定:1>  # 身代わりの対象者を[1]に限定します。(※2)
 * <SE_TargetRestriction:1> # 同上
 * <SE_身代わりスイッチ:4>  # スイッチ[4]がONのときのみ発動します。
 * <SE_SubstituteSwitch:4>  # 同上
 * <SE_身代わり計算式:f>    # 計算式[f]の結果がtrueのときのみ発動します。(※3)
 * <SE_SubstituteFormula:f> # 同上
 *
 * ※1 パラメータからデフォルトの身代わり条件である「瀕死」を
 * 無効にした場合のみ判定します。
 *
 * ※2 <SE_身代わり対象限定:n>のタグの対象者を設定したい場合、
 * 特徴を有するデータベースのメモ欄に以下の通り記述します。
 *
 * <SE_身代わり対象者:1>   # <SE_身代わり対象限定:1>の対象になります。
 * <SE_SubstituteTarget:1> # 同上
 *
 * 「身代わり対象限定」で指定したパラメータと一致する場合に身代わり対象になります。
 * 例えば、メモ欄に<SE_身代わり対象限定:2>と記入した場合、同じくメモ欄に
 * <SE_身代わり対象者:2>と記入されている特徴を持つアクターに対してのみ
 * 身代わりを実行します。
 *
 * ※3 上級者向け機能です。
 * また、計算式中で不等号を使いたい場合、以下のように記述してください。
 * < → &lt;
 * > → &gt;
 *
 * 例：<SE_身代わり計算式:\v[2] &gt; 3> # 変数[2]が[3]より大きい場合、発動します。
 *
 * 計算式中では「action」で対象スキルのActionオブジェクトを参照できます。
 * うまく利用すれば身代わりが発動するスキルを細かく限定できます。
 *
 * 以下が記入例です。
 * <SE_身代わり計算式:action.isAttack()>    # 通常攻撃のみ身代わり発動
 * <SE_身代わり計算式:action.isPhysical()>  # 物理攻撃のみ身代わり発動
 * <SE_身代わり計算式:action.isForOne()>    # 単体対象のみ身代わり発動
 *
 * 3. 身代わり発動時に、指定したIDのスキル効果を身代わり実行者に
 * 適用させることができます。
 * <SE_身代わりスキルID:5>  # 身代わり発動時にスキル[5]を実行者に適用。(※1)
 * <SE_SubstituteSkillId:5> # 同上
 *
 * ※1 ダメージポップアップやアニメーション等の演出は表示されません。
 *
 * 4. 身代わりを無効にするスキルを個別指定できます。
 * スキルもしくはアイテムのメモ欄に以下の通り指定してください。
 * <SE_身代わり無効>
 * <SE_SubstituteInvalid>
 *
 * このプラグインにはプラグインコマンドはありません。
 *
 * 利用規約：
 *  作者に無断で改変、再配布が可能で、利用形態（商用、18禁利用等）
 *  についても制限はありません。
 *  このプラグインはもうあなたのものです。
 */

(function() {
    'use strict';
    var pluginName    = 'SubstituteExtend';
    var metaTagPrefix = 'SE_';

    //=============================================================================
    // ローカル関数
    //  プラグインパラメータやプラグインコマンドパラメータの整形やチェックをします
    //=============================================================================
    var getParamString = function(paramNames) {
        if (!Array.isArray(paramNames)) paramNames = [paramNames];
        for (var i = 0; i < paramNames.length; i++) {
            var name = PluginManager.parameters(pluginName)[paramNames[i]];
            if (name) return name;
        }
        return '';
    };

    var getParamBoolean = function(paramNames) {
        var value = getParamString(paramNames);
        return value.toUpperCase() === 'ON' || value.toUpperCase() === 'TRUE';
    };

    var getMetaValue = function(object, name) {
        var metaTagName = metaTagPrefix + name;
        return object.meta.hasOwnProperty(metaTagName) ? convertEscapeCharacters(object.meta[metaTagName]) : undefined;
    };

    var getMetaValues = function(object, names) {
        for (var i = 0, n = names.length; i < n; i++) {
            var value = getMetaValue(object, names[i]);
            if (value !== undefined) return value;
        }
        return undefined;
    };

    var convertEscapeCharacters = function(text) {
        if (text == null) {
            text = '';
        }
        if (text === true) {
            return text;
        }
        var windowLayer = SceneManager._scene._windowLayer;
        return windowLayer ? windowLayer.children[0].convertEscapeCharacters(text) : text;
    };

    var convertEscapeTags = function(text) {
        if (text == null || text === true) text = '';
        text = text.replace(/&gt;?/gi, '>');
        text = text.replace(/&lt;?/gi, '<');
        return text;
    };

    //=============================================================================
    // パラメータの取得と整形
    //=============================================================================
    var param             = {};
    param.condDying         = getParamBoolean(['CondDying', '身代わり条件_瀕死']);
    param.condNonCertainHit = getParamBoolean(['CondNonCertainHit', '身代わり条件_必中以外']);
    param.substituteCounter = getParamBoolean(['SubstituteCounter', '身代わり反撃']);
    param.invalidConfused   = getParamBoolean(['InvalidConfused', '混乱時の身代わり無効']);
    param.validRestriction  = getParamBoolean(['ValidRestriction', '行動不能時の身代わり有効']);

    //=============================================================================
    // Game_BattlerBase
    //  身代わりを実行するかどうかの判定を拡張します。
    //=============================================================================
    var _Game_BattlerBase_isSubstitute      = Game_BattlerBase.prototype.isSubstitute;
    Game_BattlerBase.prototype.isSubstitute = function() {
        if (param.validRestriction) {
            this._suppressRestriction = true;
        }
        var result = _Game_BattlerBase_isSubstitute.apply(this, arguments) && this.isSubstituteExtend();
        this._suppressRestriction = false;
        return result;
    };

    var _Game_BattlerBase_restriction = Game_BattlerBase.prototype.restriction;
    Game_BattlerBase.prototype.restriction = function() {
        return this._suppressRestriction ? 0 : _Game_BattlerBase_restriction.apply(this, arguments);
    };

    Game_BattlerBase.prototype.isSubstituteExtend = function() {
        if (param.invalidConfused && this.isConfused()) {
            return false;
        }
        return this.isValidSubstituteHpRate() &&
            (param.condDying || this.isValidSubstituteTargetHpRate()) &&
            this.isValidSubstituteSwitch() &&
            this.isValidSubstituteRestriction() &&
            this.isValidSubstituteFormula() &&
            this.isValidSubstituteSkill();
    };

    Game_BattlerBase.prototype.isValidSubstituteHpRate = function() {
        var subjectHpRate = this.getSubstituteMetaInfo(['SubjectHPRate', '実行者HP率'], true);
        if (subjectHpRate) {
            return this.hpRate() >= subjectHpRate / 100;
        }
        return true;
    };

    Game_BattlerBase.prototype.isValidSubstituteTargetHpRate = function() {
        var targetHpRate = this.getSubstituteMetaInfo(['TargetHPRate', '対象者HP率'], true);
        if (targetHpRate) {
            return BattleManager.checkSubstituteTargetHpRate(targetHpRate);
        }
        return true;
    };

    Game_BattlerBase.prototype.isValidSubstituteRestriction = function() {
        var restrictionId = this.getSubstituteMetaInfo(['TargetRestriction', '身代わり対象限定'], true);
        if (restrictionId) {
            return BattleManager.checkSubstituteRestriction(restrictionId);
        }
        return true;
    };

    Game_BattlerBase.prototype.isValidSubstituteSwitch = function() {
        var switchId = this.getSubstituteMetaInfo(['SubstituteSwitch', '身代わりスイッチ'], true);
        if (switchId) {
            return $gameSwitches.value(switchId);
        }
        return true;
    };

    Game_BattlerBase.prototype.isValidSubstituteFormula = function() {
        var formula = this.getSubstituteMetaInfo(['SubstituteFormula', '身代わり計算式'], false);
        if (formula) {
            var action = BattleManager.getSubstituteAction();
            return eval(convertEscapeTags(formula));
        }
        return true;
    };

    Game_BattlerBase.prototype.isValidSubstituteSkill = function() {
        return !getMetaValues(BattleManager.getSubstituteAction().item(), ['SubstituteInvalid', '身代わり無効']);
    };

    Game_BattlerBase.prototype.isEqualSubstituteRestrictionId = function(restrictionId) {
        var restrictionTargetId = this.getSubstituteMetaInfo(['SubstituteTarget', '身代わり対象者'], true);
        return restrictionTargetId === restrictionId;
    };

    Game_BattlerBase.prototype.getSubstituteSkillId = function() {
        return this.getSubstituteMetaInfo(['SubstituteSkillId', '身代わりスキルID'], true);
    };

    Game_BattlerBase.prototype.getSubstituteMetaInfo = function(tagNames, isNumber) {
        var metaValue;
        this.traitObjects().some(function(traitObject) {
            metaValue = getMetaValues(traitObject, tagNames);
            return !!metaValue;
        });
        return (metaValue && isNumber) ? parseInt(metaValue) : metaValue;
    };

    //=============================================================================
    // BattleManager
    //  身代わり対象者の情報を保持して必要に応じて評価します。
    //=============================================================================
    var _BattleManager_invokeAction = BattleManager.invokeAction;
    BattleManager.invokeAction = function(subject, target) {
        if (param.substituteCounter) {
            var realTarget = this.applySubstitute(target);
            _BattleManager_invokeAction.call(this, subject, realTarget);
        } else {
            _BattleManager_invokeAction.apply(this, arguments);
        }
    };

    var _BattleManager_applySubstitute = BattleManager.applySubstitute;
    BattleManager.applySubstitute      = function(target) {
        this._substituteTarget = target;
        var substitute = _BattleManager_applySubstitute.apply(this, arguments);
        this._substituteTarget = null;
        if (substitute !== target) {
            this.applySubstituteEffect(substitute);
        }
        return substitute;
    };

    BattleManager.applySubstituteEffect = function(substitute) {
        var skillId = substitute.getSubstituteSkillId();
        if (!skillId) return;
        var action = new Game_Action(substitute);
        action.setSkill(skillId);
        action.apply(substitute);
    };

    BattleManager.checkSubstituteTargetHpRate = function(hpRate) {
        return this._substituteTarget ? this._substituteTarget.hpRate() <= hpRate / 100 : true;
    };

    BattleManager.checkSubstituteRestriction = function(restrictionId) {
        return this._substituteTarget ? this._substituteTarget.isEqualSubstituteRestrictionId(restrictionId) : true;
    };

    BattleManager.getSubstituteAction = function() {
        return this._action;
    };

    var _BattleManager_checkSubstitute = BattleManager.checkSubstitute;
    BattleManager.checkSubstitute      = function(target) {
        var resultSubstitute = _BattleManager_checkSubstitute.apply(this, arguments);
        if (!resultSubstitute) {
            return this.checkSubstituteDefault(target);
        }
        return resultSubstitute;
    };

    BattleManager.checkSubstituteDefault = function(target) {
        return this.isValidSubstituteDying(target) && this.isValidSubstituteCertainHit();
    };

    BattleManager.isValidSubstituteDying = function(target) {
        return !param.condDying || target.isDying();
    };

    BattleManager.isValidSubstituteCertainHit = function() {
        return !param.condNonCertainHit || !this._action.isCertainHit();
    };
})();

