//=============================================================================
// RPG Maker MZ - IM_BattleLayout
//
// Copyright 2025 I'm moralist/min-pub.com All rights reserved.
// This source code or any portion thereof must not be
// reproduced or used without licensed in any manner whatsoever.
//=============================================================================
/*:
 * @target MZ
 * @plugindesc [v1.0.9] バトル画面レイアウト一元管理 + シングルステータスウィンドウ。ステートウィンドウ機能は廃止しました。
 * @author
 *
 * @help
 * =============================================================================
 * ■ 概要
 * =============================================================================
 * - バトル画面のウィンドウ配置を一元管理するプラグインです。
 * - 1) 標準ステータスウィンドウは削除せず、画面外(-9999,-9999)に移動して非表示。
 * - 2) 先頭アクター用のHP/MP/TPウィンドウ(幅600)を左下に配置（名前/ステート非表示）。
 * - 3) ver1.06でステート専用ウィンドウ関連機能を廃止しました。
 * - 4) 「this.drawGauge is not a function」エラー回避のフォールバックを冒頭で定義。
 *
 * ■ バージョン情報
 *  v1.0.9 (2025/6/05) : 全体をリファクタリング＆IM_BattleLayoutMarginをマージ
 *  v1.0.8 (2025/3/18) : プレイヤー行動時ゲージ再描画 + TPゲージ75%超過時にアニメ点滅 : プレイヤー行動時のゲージ再描画を追加
 *  v1.0.6 (2025/3/18) : ステートウィンドウ機能を廃止
 *  v1.0.5 (2025/3/18) : 左側メニューに修正
 *  （以下略）
 */

(() => {
    "use strict";

    //========================================================================
    // プラグイン定義
    //========================================================================
    const pluginName = "IM_BattleLayout";
    const script = document.currentScript;
    const param  = PluginManagerEx.createParameter(script);

    //========================================================================
    // 定数定義
    //========================================================================
    const PARTY_CMD_ROWS       = 6;  
    const ACTOR_CMD_ROWS       = 6;  
    const CMD_WINDOW_WIDTH     = 240;
    const SINGLE_STATUS_LINES  = 3;
    const SINGLE_STATUS_WIDTH  = 592;
    const SKILLITEM_ROWS       = 6;
    const SKILLITEM_WIDTH      = 960;

    const GAUGE_MARGIN_X       = 40;
    const LABEL_WIDTH          = 60;
    const LABEL_GAP            = 10;
    const TP_GAUGE_MARGIN_X    = 40;
    const TP_LABEL_WIDTH       = 60;
    const TP_LABEL_GAP         = 10;
    const GAUGE_HEIGHT         = 6;
    const TP_GAUGE_HEIGHT      = 9;

    // 横方向のマージン量
    const LEFT_MARGIN          = 50;
    const RIGHT_MARGIN         = 50;

    const LARGER_FONT_SIZE     = 36;

    //========================================================================
    // 共通基盤
    //========================================================================

    //========================================================================
    // コアスクリプト変更部 (Window_Base)
    //========================================================================
    // drawGauge フォールバック
    if (typeof Window_Base.prototype.drawGauge !== "function") {
        if (typeof Window_Base.prototype.gaugeBackColor !== "function") {
            Window_Base.prototype.gaugeBackColor = function() {
                return "#808080";
            };
        }
        Window_Base.prototype.drawGauge = function(x, y, width, rate, color1, color2) {
            const gaugeH = 6;
            const gaugeY = y + this.lineHeight() - gaugeH - 2;
            this.contents.fillRect(x, gaugeY, width, gaugeH, this.gaugeBackColor());
            const fillW = Math.floor(width * rate);
            const c1 = color1 || "#ff6060";
            const c2 = color2 || "#ffc0c0";
            this.contents.gradientFillRect(x, gaugeY, fillW, gaugeH, c1, c2, false);
        };
    }

    //========================================================================
    // コアスクリプト変更部 (Window_PartyCommand)
    //========================================================================
    // パーティコマンド＆アクターコマンドのウィンドウ変更
    const _Window_PartyCommand_prototype_numVisibleRows = Window_PartyCommand.prototype.numVisibleRows;
    Window_PartyCommand.prototype.numVisibleRows = function() {
        return PARTY_CMD_ROWS;
    };

    const _Window_PartyCommand_prototype_hide = Window_PartyCommand.prototype.hide;
    Window_PartyCommand.prototype.hide = function() {
        // do nothing
    };

    //========================================================================
    // コアスクリプト変更部 (Window_ActorCommand)
    //========================================================================
    // パーティコマンド＆アクターコマンドのウィンドウ変更
    const _Scene_Battle_prototype_createActorCommandWindow = Scene_Battle.prototype.createActorCommandWindow;
    Scene_Battle.prototype.createActorCommandWindow = function() {
        const rect = this.actorCommandWindowRect();
        const commandWindow = new Window_ActorCommand(rect);
        commandWindow.y = Graphics.boxHeight - commandWindow.height;

        commandWindow.setHandler("skill", this.commandSkill.bind(this));
        commandWindow.setHandler("guard", this.commandGuard.bind(this));

        // キャンセル禁止（戦う・逃げるに戻さない）

        this.addWindow(commandWindow);
        this._actorCommandWindow = commandWindow;
    };

    const _Window_ActorCommand_prototype_numVisibleRows = Window_ActorCommand.prototype.numVisibleRows;
    Window_ActorCommand.prototype.numVisibleRows = function() {
        return ACTOR_CMD_ROWS;
    };

    const _Window_ActorCommand_prototype_hide = Window_ActorCommand.prototype.hide;
    Window_ActorCommand.prototype.hide = function() {
        // do nothing
    };

    // 通常攻撃は使わない
    const _Window_ActorCommand_prototype_addAttackCommand = Window_ActorCommand.prototype.addAttackCommand;
    Window_ActorCommand.prototype.addAttackCommand = function() {
        // do noghing
    };

    // 無効になってるコマンドは表示しないように
    const _Window_ActorCommand_prototype_addGuardCommand = Window_ActorCommand.prototype.addGuardCommand;
    Window_ActorCommand.prototype.addGuardCommand = function() {
        if (this._actor.canGuard()) {
            this.addCommand(TextManager.guard, "guard", this._actor.canGuard());
        }
    };

    const _Window_ActorCommand_prototype_addItemCommand = Window_ActorCommand.prototype.addItemCommand;
    Window_ActorCommand.prototype.addItemCommand = function() {
        // アイテムは今のところ使用予定無いので非表示
        // do nothing
    };

    const _Window_ActorCommand_prototype_selectLast = Window_ActorCommand.prototype.selectLast;
    Window_ActorCommand.prototype.selectLast = function() {
        if ($gameSwitches.value(15) === false) {
            _Window_ActorCommand_prototype_selectLast.apply(this, arguments);
        } else {
            this.forceSelect(0);
            $gameSwitches.setValue(15, false);
        }
    };

    //========================================================================
    // コアスクリプト変更部 (Window_BattleLog)
    //========================================================================
    const _Window_BattleLog_prototype_resetFontSettings = Window_BattleLog.prototype.resetFontSettings;
    Window_BattleLog.prototype.resetFontSettings = function() {
        this.contents.fontFace = $gameSystem.mainFontFace();
        this.contents.fontSize = LARGER_FONT_SIZE;
        this.resetTextColor();
    };

    const _Window_BattleLog_prototype_lineHeight = Window_BattleLog.prototype.lineHeight;
    Window_BattleLog.prototype.lineHeight = function() {
        return 38;
    };

    const _Window_BattleLog_prototype_itemHeight = Window_BattleLog.prototype.itemHeight;
    Window_BattleLog.prototype.itemHeight = function() {
        return this.lineHeight() + 2;
    };

    //========================================================================
    // コアスクリプト変更部 (Scene_Battle)
    //========================================================================
    const _Scene_Battle_prototype_partyCommandWindowRect = Scene_Battle.prototype.partyCommandWindowRect;
    Scene_Battle.prototype.partyCommandWindowRect = function() {
        const ww = CMD_WINDOW_WIDTH;
        const wh = this.calcWindowHeight(PARTY_CMD_ROWS, true);
        const wx = 0;
        const wy = Graphics.boxHeight - wh;
        return new Rectangle(wx, wy, ww, wh);
    };

    const _Scene_Battle_prototype_actorCommandWindowRect = Scene_Battle.prototype.actorCommandWindowRect;
    Scene_Battle.prototype.actorCommandWindowRect = function() {
        const ww = CMD_WINDOW_WIDTH;
        const wh = this.calcWindowHeight(ACTOR_CMD_ROWS, true);
        const wx = 0;
        const wy = Graphics.boxHeight - wh;
        return new Rectangle(wx, wy, ww, wh);
    };

    // バトルログの修正
    const _Scene_Battle_prototype_logWindowRect = Scene_Battle.prototype.logWindowRect;
    Scene_Battle.prototype.logWindowRect = function() {
        const cmdRect = this.partyCommandWindowRect();
        const wx = cmdRect.x + cmdRect.width + 10;
        const wy = 0;
        const ww = Graphics.boxWidth - ((cmdRect.x + cmdRect.width + 10) * 2);
        const wh = this.calcWindowHeight(14, false);
        return new Rectangle(wx, wy, ww, wh);
    };

    // シングルステータスウィンドウ (HP/MP/TP)
    const _Scene_Battle_createAllWindows = Scene_Battle.prototype.createAllWindows;
    Scene_Battle.prototype.createAllWindows = function() {
        _Scene_Battle_createAllWindows.apply(this, arguments);

        // メッセージウィンドウのフォントサイズ変更
        this._messageWindow.contents.fontSize += 12;

        // 旧ステータスウィンドウを画面外へ
        if (this._statusWindow) {
            this._statusWindow.x = -9999;
            this._statusWindow.y = -9999;
            this._statusWindow.visible = false;
        }

        // 新ステータス(HP/MP/TP)
        this.createSingleActorWindow();
    };

    Scene_Battle.prototype.createSingleActorWindow = function() {
        const rect = this.singleActorWindowRect();
        this._singleActorWindow = new Window_SingleActorStatus(rect);
        this.addWindow(this._singleActorWindow);
    };

    Scene_Battle.prototype.singleActorWindowRect = function() {
        const ww = SINGLE_STATUS_WIDTH;
        const wh = this.calcWindowHeight(SINGLE_STATUS_LINES, false);
        const wx = Graphics.boxWidth - ww;
        const wy = Graphics.boxHeight - wh;
        return new Rectangle(wx, wy, ww, wh);
    };

    // スキル等の説明ウィンドウ
    const _Scene_Battle_prototype_helpWindowRect = Scene_Battle.prototype.helpWindowRect;
    Scene_Battle.prototype.helpWindowRect = function() {
        const cmdRect = this.partyCommandWindowRect();
        const ww = Graphics.boxWidth - ((cmdRect.x + cmdRect.width + 10) * 2);
        const wh = this.helpAreaHeight();
        const wx = cmdRect.x + cmdRect.width + 10;
        const wy = this.helpAreaTop();
        return new Rectangle(wx, wy, ww, wh);
    };

    const _Scene_Battle_prototype_helpAreaHeight = Scene_Battle.prototype.helpAreaHeight;
    Scene_Battle.prototype.helpAreaHeight = function() {
        const numLines = 2;
        return numLines * (LARGER_FONT_SIZE + 4) + $gameSystem.windowPadding() * 2;
    };

    const _Scene_Battle_prototype_createHelpWindow = Scene_Battle.prototype.createHelpWindow;
    Scene_Battle.prototype.createHelpWindow = function() {
        const rect = this.helpWindowRect();
        this._helpWindow = new Window_HelpInBattle(rect);
        this._helpWindow.hide();
        this.addWindow(this._helpWindow);
    };

    // スキル/アイテム/敵選択ウィンドウ
    Scene_Battle.prototype.skillItemEnemyWindowRect = function() {
        const cmdRect = this.partyCommandWindowRect();
        const ww = SKILLITEM_WIDTH;
        const wh = this.calcWindowHeight(SKILLITEM_ROWS, true);
        const wx = cmdRect.x + cmdRect.width + 10;
        const wy = cmdRect.y + cmdRect.height - wh;
        return new Rectangle(wx, wy, ww, wh);
    };

    const _Scene_Battle_prototype_skillWindowRect = Scene_Battle.prototype.skillWindowRect;
    Scene_Battle.prototype.skillWindowRect = function() {
        return this.skillItemEnemyWindowRect();
    };

    const _Scene_Battle_prototype_itemWindowRect = Scene_Battle.prototype.itemWindowRect;
    Scene_Battle.prototype.itemWindowRect = function() {
        return this.skillItemEnemyWindowRect();
    };

    const _Scene_Battle_prototype_enemyWindowRect = Scene_Battle.prototype.enemyWindowRect;
    Scene_Battle.prototype.enemyWindowRect = function() {
        return this.skillItemEnemyWindowRect();
    };

    // バトルシーンに入ったときだけバトルウィンドウ類が生成される時点（createDisplayObjects）で
    // boxWidth を書き換えて左右マージンを適用する
    const _Scene_Battle_createDisplayObjects = Scene_Battle.prototype.createDisplayObjects;
    Scene_Battle.prototype.createDisplayObjects = function() {
        // バトルシーン開始処理（ウィンドウ生成前）

        // いまの boxWidth を保持
        this._originalBoxWidthIM = Graphics.boxWidth;

        // boxWidth を狭める
        const totalMargin = LEFT_MARGIN + RIGHT_MARGIN;
        Graphics.boxWidth = Math.max(1, Graphics.boxWidth - totalMargin);

        // ADV バックログ抑止
        if (!!$gameTemp.adv) {
            $gameTemp.adv.enableBackLog(false);
        }

        // 元メソッド実行 → バトルウィンドウ類は「狭めたwidth」で初期化される
        _Scene_Battle_createDisplayObjects.call(this);
    };

    const _Scene_Battle_terminate = Scene_Battle.prototype.terminate;
    Scene_Battle.prototype.terminate = function() {
        // バトルシーン終了処理

        // boxWidth を元に戻す
        if (this._originalBoxWidthIM != null) {
            Graphics.boxWidth = this._originalBoxWidthIM;
        }

        // ADV バックログ復帰
        if (!!$gameTemp.adv) {
            $gameTemp.adv.enableBackLog(true);
        }

        // 元メソッド実行
        _Scene_Battle_terminate.call(this);
    };

    // タッチUI設定の再表示されるバックボタンの生成を抑止
    const _Scene_Battle_prototype_createCancelButton = Scene_Battle.prototype.createCancelButton;
    Scene_Battle.prototype.createCancelButton = function() {
        // do nothing
    };

    //========================================================================
    // コアスクリプト変更部 (BattleManager)
    //========================================================================
    // ★ ver1.07: プレイヤー行動終了時に再描画
    const _BattleManager_endAction = BattleManager.endAction;
    BattleManager.endAction = function() {
        _BattleManager_endAction.apply(this, arguments);
        if (!!SceneManager._scene && !!SceneManager._scene._singleActorWindow) {
            SceneManager._scene._singleActorWindow.refresh();
        }
    };

    //========================================================================
    // コアスクリプト変更部 (Game_BattlerBase)
    //========================================================================
    // HP/MP/TP変動で先頭アクターのSingleActor再描画
    const _Game_BattlerBase_setHp = Game_BattlerBase.prototype.setHp;
    Game_BattlerBase.prototype.setHp = function(hp) {
        const prevHp = this._hp;
        _Game_BattlerBase_setHp.apply(this, arguments);
        if (this === $gameParty.members()[0] && this._hp !== prevHp) {
            if (SceneManager._scene && SceneManager._scene._singleActorWindow) {
                SceneManager._scene._singleActorWindow.refresh();
            }
        }
    };

    const _Game_BattlerBase_setMp = Game_BattlerBase.prototype.setMp;
    Game_BattlerBase.prototype.setMp = function(mp) {
        const prevMp = this._mp;
        _Game_BattlerBase_setMp.apply(this, arguments);
        if (this === $gameParty.members()[0] && this._mp !== prevMp) {
            if (SceneManager._scene && SceneManager._scene._singleActorWindow) {
                SceneManager._scene._singleActorWindow.refresh();
            }
        }
    };

    const _Game_BattlerBase_setTp = Game_BattlerBase.prototype.setTp;
    Game_BattlerBase.prototype.setTp = function(tp) {
        const prevTp = this._tp;
        _Game_BattlerBase_setTp.apply(this, arguments);
        if (this === $gameParty.members()[0] && this._tp !== prevTp) {
            if (SceneManager._scene && SceneManager._scene._singleActorWindow) {
                SceneManager._scene._singleActorWindow.refresh();
            }
        }
    };

    //========================================================================
    // コアスクリプト変更部 (Sprite_Enemy)
    //========================================================================
    // 敵消滅時のエフェクトをモノクロ化
    const _Sprite_Enemy_prototype_updateCollapse = Sprite_Enemy.prototype.updateCollapse;
    Sprite_Enemy.prototype.updateCollapse = function() {
        this.blendMode = 1;
        this.setBlendColor([255, 255, 255, 128]);
        this.opacity *= this._effectDuration / (this._effectDuration + 1);
    };
    
    //========================================================================
    // クラス定義 (Window_HelpInBattle)
    //========================================================================
    // スキル説明ウィンドウの文字サイズを変更
    class Window_HelpInBattle extends Window_Help {
        constructor() {
            super(...arguments);
        }

        resetFontSettings() {
            this.contents.fontFace = $gameSystem.mainFontFace();
            this.contents.fontSize = LARGER_FONT_SIZE;
            this.resetTextColor();
        }

        lineHeight() {
            return LARGER_FONT_SIZE + 6;
        }

        calcTextHeight(textState) {
            const lineSpacing = this.lineHeight() - LARGER_FONT_SIZE;
            const lastFontSize = this.contents.fontSize;
            const lines = textState.text.slice(textState.index).split("\n");
            const textHeight = this.maxFontSizeInLine(lines[0]) + lineSpacing;
            this.contents.fontSize = lastFontSize;
            return textHeight;
        }
    };

    //========================================================================
    // クラス定義 (Window_SingleActorStatus)
    //========================================================================
    // Window_SingleActorStatus : 先頭アクターHP/MP/TPのみ表示

    // ★ アニメ用フレームカウンタを保持する拡張
    //   Window_SingleActorStatus に _tpBlinkCount を追加し、
    //   毎フレームアップデートして再描画を行う。

    // 先にWindow_SingleActorStatus拡張のため、クラス定義の後にupdateメソッドを追加
    class Window_SingleActorStatus extends Window_Base {
        initialize(rect) {
            super.initialize(rect);
            this.openness = 0;
            this.open();
            // ★TPゲージ点滅用カウンタ
            this._tpBlinkCount = 0;
            this.refresh();
        }

        update() {
            super.update();
            // ゲージを毎フレームアニメするためにTPレートを監視
            const actor = $gameParty.members()[0];
            if (!actor) return;
            const rate = actor.tpRate();
            // TP 75%超えならカウンタを進め、一定間隔で再描画
            if (rate >= 0.75) {
                this._tpBlinkCount++;
                // 例えば 4フレームごとに再描画
                if (this._tpBlinkCount % 4 === 0) {
                    this.refresh();
                }
            } else {
                // 75%未満になったら点滅をリセット
                if (this._tpBlinkCount !== 0) {
                    this._tpBlinkCount = 0;
                    this.refresh();
                }
            }
        }

        refresh() {
            this.contents.clear();
            const actor = $gameParty.members()[0];
            if (!actor) return;

            let yy = 0;
            const lh = this.lineHeight();

            // HP
            this.drawParamLineEx(TextManager.hpA, actor.hp, actor.mhp, actor.hpRate(), yy);
            yy += lh;
            // MP
            this.drawParamLineEx(TextManager.mpA, actor.mp, actor.mmp, actor.mpRate(), yy);
            yy += lh;
            // TP
            this.drawParamLineEx(TextManager.tpA, actor.tp, actor.maxTp(), actor.tpRate(), yy);
        }

        drawParamLineEx(label, cur, max, rate, y) {
            this.contents.fontSize = this.lineHeight() - 8;
            this.contents.textColor = ColorManager.normalColor();

            let gaugeMarginX = GAUGE_MARGIN_X;
            let labelWidth   = LABEL_WIDTH;
            let labelGap     = LABEL_GAP;
            let gaugeHeight  = GAUGE_HEIGHT;
            let color1       = ColorManager.hpGaugeColor1();
            let color2       = ColorManager.hpGaugeColor2();

            // TPゲージ用の設定
            if (label === TextManager.tpA) {
                gaugeMarginX = TP_GAUGE_MARGIN_X;
                labelWidth   = TP_LABEL_WIDTH;
                labelGap     = TP_LABEL_GAP;
                gaugeHeight  = TP_GAUGE_HEIGHT;
                color1       = ColorManager.tpGaugeColor1();
                color2       = ColorManager.tpGaugeColor2();
                // ★TPゲージが75%以上ならアニメ点滅
                if (rate >= 0.75) {
     
               // 例: 60フレームで心臓ドキドキ風 (灰色→白→灰色)
               const blinkPeriod = 40;
               const c = this._tpBlinkCount % blinkPeriod;
               // 0.0～1.0に正規化
               const baseFrac = c / blinkPeriod;
                // サイン波(1周期)で 0→1→0 と変化 => ドキドキ感を演出
                const fraction = 0.5 - 0.5 * Math.cos(2 * Math.PI * baseFrac);

               // 0x80(=128)～0xFF(=255)を fraction で補間 => 灰色→白
               const grayVal = Math.round(0x80 + (0xFF - 0x80) * fraction);
               const hexStr = grayVal.toString(16).padStart(2, "0");
            color1 = "#" + hexStr + hexStr + hexStr;
            // color2は常に白
            color2 = "#FFFFFF";
}
            } else if (label === TextManager.mpA) {
                color1 = ColorManager.mpGaugeColor1();
                color2 = ColorManager.mpGaugeColor2();
            }

            const totalW    = this.contentsWidth();
            const xLeft     = gaugeMarginX;
            const gaugeAll  = totalW - gaugeMarginX * 2;
            const gaugeW    = gaugeAll - (labelWidth + labelGap);

            this.drawText(label, xLeft, y, labelWidth, "left");

            const gx = xLeft + labelWidth + labelGap;
            this.drawCustomGauge(gx, y, gaugeW, rate, color1, color2, gaugeHeight);

            const text = `${cur}/${max}`;
            this.drawText(text, gx, y, gaugeW, "right");
        }

        drawCustomGauge(x, y, width, rate, color1, color2, gaugeH) {
            const gaugeY = y + this.lineHeight() - gaugeH - 2;
            this.contents.fillRect(x, gaugeY, width, gaugeH, this.gaugeBackColor());
            const fillW = Math.floor(width * rate);
            this.contents.gradientFillRect(x, gaugeY, fillW, gaugeH, color1, color2, false);
        }

        hide() {
            // hide()無視
            // do nothing
        }
    }
    window.Window_SingleActorStatus = Window_SingleActorStatus;
})();


