/*
 * --------------------------------------------------
 * MNKR_HzTimingBarMZ.js
 *   Ver.0.1.2
 * Copyright (c) 2022 Munokura
 * This software is released under the MIT license.
 * http://opensource.org/licenses/mit-license.php
 * --------------------------------------------------
 */

/*
Copyright (c) <2016> <hiz>
MITライセンスの下で公開されています。
*/

/*:
 * @target MZ
 * @url https://raw.githubusercontent.com/munokura/MNKR-MZ-plugins/master/MNKR_HzTimingBarMZ.js
 * @plugindesc タイミングを合わせてボタン入力するタイミングバーを実行します。
 * @author hiz (改変 munokura)
 * 
 * @help
 * タイミングを合わせてボタン入力するタイミングバーを実行します。
 * プラグインコマンドでタイミングバーを設定・実行します。
 * 
 * カーソルがバー終了地点に向かって動きます。
 * 何もない範囲で入力した時点で終了し、0が代入されます。
 * 終了までに、ヒット(1)・クリティカル(2)のどちらかを入力したポイントの高い方が
 * 変数に代入されます。
 * ただし、入力必須範囲がある場合、それを入力していないと0が代入されます。
 * 
 * バー末端に来るか、クリア条件（必須、ヒット、クリティカルの全てをヒット済）
 * を満たした時点で変数が代入され終了します。
 * 
 * 
 * コマンドヘルプ補足
 * [hit_area] 
 * ヒット範囲の最小値・最大値を0から100の間で設定。
 * min-max
 * 例）
 * 70-90
 *        
 * [critical_area]
 * クリティカル範囲の最小値・最大値を0から100の間で設定。
 * min-max
 * 例）
 * 90-95
 * 
 * [require_area]
 * 入力必須範囲（複数可）の最小値・最大値を0から100の間で設定。
 * min-max,min-max,...
 * 入力必須範囲で全てボタン入力しないとヒット範囲・クリティカル範囲で
 * ボタン入力してもミスになります。
 *    ※ 必ずヒット範囲・クリティカル範囲より前になるように設定して下さい。
 * 例）
 * 10-20        # 必須エリアは10から20の範囲
 * 20-30,50-60  # 必須エリアは20から30・50から60の範囲
 * 無入力        # 必須エリア無し
 * 
 * 
 * このプラグインについて
 *   RPGツクールMV用に作成されたプラグインをMZ用に移植したものです。
 *   お問い合わせは改変者へお願いいたします。
 * 
 * 
 * 利用規約:
 *   MITライセンスです。
 *   https://licenses.opensource.jp/MIT/MIT.html
 *   作者に無断で改変、再配布が可能で、
 *   利用形態（商用、18禁利用等）についても制限はありません。
 * 
 * 
 * Ver.0.0.1 by munokura (2022/4/11)
 * MZへ移植
 * 変数に値が代入されない不具合を修正
 * 
 * Ver.0.0.2 by munokura (2022/4/12)
 * プラグインコマンド後に文章の表示がない場合、無限ループになる不具合を修正
 * 必須エリアが他エリアの後ろにある場合、必ずミスになる不具合を修正
 * 
 * Ver.0.0.3 by munokura (2023/5/7)
 * クリティカルの後にヒットを取ると、ヒットの値を取得する不具合を修正
 * 
 * Ver.0.0.4 by munokura (2023/5/7)
 * 2回目以降に前回のスコアが影響してしまう不具合を修正
 * 
 * Ver.0.1.0 by munokura (2023/5/7)
 * ヒット時にクリア条件が揃っている場合、末端まで待たずにクリアする仕様変更
 * 
 * @param bar width
 * @type number
 * @text バーの幅
 * @desc バーの幅
 * @default 500
 *
 * @param required SE
 * @type file
 * @reauire 1
 * @dir audio/se/
 * @text 必須エリアヒット時SE
 * @desc 必須エリアヒット時のSE
 * @default Decision2
 *
 * @param hit SE
 * @type file
 * @reauire 1
 * @dir audio/se/
 * @text ヒットエリアヒット時SE
 * @desc ヒットエリアヒット時のSE
 * @default Attack2
 * 
 * @param critical SE
 * @type file
 * @reauire 1
 * @dir audio/se/
 * @text クリティカルエリアヒット時SE
 * @desc クリティカルエリアヒット時のSE
 * @default Attack3
 *
 * @param miss SE
 * @type file
 * @reauire 1
 * @dir audio/se/
 * @text 入力時（失敗）SE
 * @desc 入力時（失敗）のSE
 * @default Buzzer1
 * 
 * 
 * @command HzTimingBar
 * @text タイミングバー実行
 * @desc タイミングバーを実行します。
 *
 * @arg varNumber
 * @text 結果代入変数ID
 * @desc 結果を返す変数番号。ミスの場合は0・ヒットの場合は1・クリティカルの場合は2が返される。
 * @type variable
 * @default 0
 *
 * @arg hitArea
 * @text ヒット範囲
 * @desc ヒット範囲の最小値・最大値を0から100の間で設定。min-max
 * @default 70-90
 *
 * @arg criticalArea
 * @text クリティカル範囲
 * @desc クリティカル範囲の最小値・最大値を0から100の間で設定。min-max
 * 使用しない場合、無入力
 * @default 10-20
 *
 * @arg requireArea
 * @text 入力必須範囲
 * @desc 入力必須範囲（複数可）の最小値・最大値を0から100の間で設定。min-max,min-max。使用しない場合、無入力
 * @default 
 *
 * @arg x
 * @text コマンド位置X
 * @desc コマンドの表示位置を指定。-1で画面中央
 * @type number
 * @min -1
 * @default -1
 *
 * @arg y
 * @text コマンド位置Y
 * @desc コマンドの表示位置を指定。-1で画面中央
 * @type number
 * @min -1
 * @default -1
 */

// 必須エリア追加
// 必須エリアヒット時、効果音を出す
// プラグインコマンド
// TODO:アイテム・スキル使用時の実行
// TODO:状態異常の効果追加（エリア非表示・高速/低速）

(function () {
    var pluginName = document.currentScript.src.split("/").pop().replace(/\.js$/, "");
    var parameters = PluginManager.parameters(pluginName);

    var requiredSe = parameters['required SE'];
    var hitSe = parameters['hit SE'];
    var criticalSe = parameters['critical SE'];
    var missSe = parameters['miss SE'];
    var result = 0;
    var hitAreaHitted = false;
    var criticalAreaHitted = false;

    PluginManager.registerCommand(pluginName, "HzTimingBar", function (obj) {
        result = 0;
        var args = Object.entries(obj).map(([key, value]) => `${value}`);
        this.setWaitMode("hzTimingBar");
        var varNo = Number(args[0]);
        var hitAreaParm = String(args[1]);
        var criticalAreaParm = args[2] || false;
        var requiredAreaParm = args[3] ? '[' + args[3] + ']' : false;
        var x = Number(args[4] || -1) < 0 ? Graphics.width / 2 : Number(args[4]);
        var y = Number(args[5] || -1) < 0 ? Graphics.height / 2 : Number(args[5]);

        if (!hitAreaParm) {
            hitAreaHitted = true;
        }

        if (!criticalAreaParm) {
            criticalAreaHitted = true;
        }

        var hitArea = hitAreaParm.split("-").map(function (elm) { return Number(elm); });
        if (criticalAreaParm) {
            var criticalArea = criticalAreaParm.split("-").map(function (elm) { return Number(elm); });
        } else {
            var criticalArea = null;
        }
        if (requiredAreaParm) {
            var requiredAreas = requiredAreaParm.substring(1, requiredAreaParm.length - 1).split(",").map(function (requiredArea) {
                return requiredArea.split("-").map(function (elm) {
                    return Number(elm);
                });
            });
        } else {
            var requiredAreas = null;
        }
        // this._timingBar = new HzTimingBar(x, y, hitArea, criticalArea, requiredAreas);
        // 変数が代入されないバグ修正
        this._timingBar = new HzTimingBar(x, y, hitArea, criticalArea, requiredAreas, varNo);
    });

    // 待機状態の更新用関数に機能追加
    var _Game_Interpreter_updateWaitMode = Game_Interpreter.prototype.updateWaitMode;
    Game_Interpreter.prototype.updateWaitMode = function () {
        var waiting = null;
        switch (this._waitMode) {
            case 'hzTimingBar':
                // 待機状態の更新
                // ※ waitingには必ずtrueかfalseをセットすること！
                waiting = this._timingBar.update();
                if (!waiting) {
                    // 終了処理
                    this._timingBar.terminate();
                    this._timingBar = null;
                }
                break;
        }
        if (waiting !== null) {
            if (!waiting) {
                this._waitMode = '';
            }
            return waiting;
        }
        return _Game_Interpreter_updateWaitMode.call(this);
    };

    // コマンド入力実行用クラス
    function HzTimingBar() {
        this.initialize.apply(this, arguments);
    }

    HzTimingBar.DELAY = 20;
    HzTimingBar.maxFrame = 100;
    HzTimingBar.WIDTH = parameters['bar width'] ? Number(parameters['bar width']) : 500;
    HzTimingBar.HEIGHT = 35;
    HzTimingBar.RADIUS = 5;
    HzTimingBar.speed = 1.8;

    // 初期化処理（プロパティの初期化・スプライトの作成等を行う）
    // HzTimingBar.prototype.initialize = function (x, y, hitArea, criticalArea, requiredAreas) {
    //     this._varNo = 1;
    // 変数が代入されないバグ修正
    // 

    HzTimingBar.prototype.initialize = function (x, y, hitArea, criticalArea, requiredAreas, varNo) {
        this._hitArea = hitArea;
        this._varNo = varNo;
        $gameVariables.setValue(this._varNo, 0);
        // クリティカルエリアが無いとバーの左に表示されるのを修正
        // this._criticalArea = criticalArea != null ? criticalArea : [-1, 0];
        this._criticalArea = criticalArea != null ? criticalArea : [];
        this._requiredAreas = requiredAreas != null ? requiredAreas : [];
        this._frame = -HzTimingBar.DELAY;
        this._hitRequired = [];
        for (var i = 0; i < this._requiredAreas.length; i++) {
            this._hitRequired.push(this._requiredAreas[i].length != 2);
        }

        // 描画コンテナ
        this._container = new Sprite();
        this._container.position.set(x - HzTimingBar.WIDTH / 2, y - HzTimingBar.HEIGHT / 2);

        // 枠
        var barFrameBmp = new Bitmap(HzTimingBar.WIDTH + 4, HzTimingBar.HEIGHT + 4);
        // var framePath = roundedRectangle(barFrameBmp.context, 2, 2, HzTimingBar.WIDTH, HzTimingBar.HEIGHT, HzTimingBar.RADIUS);
        roundedRectangle(barFrameBmp.context, 2, 2, HzTimingBar.WIDTH, HzTimingBar.HEIGHT, HzTimingBar.RADIUS);
        barFrameBmp.context.fillStyle = "#000000";
        barFrameBmp.context.lineWidth = 2;
        barFrameBmp.context.strokeStyle = "#000000";
        barFrameBmp.context.fill();
        barFrameBmp.context.stroke();
        var barFrame = new Sprite(barFrameBmp);
        barFrame.position.set(-2, -2);
        this._container.addChild(barFrame);

        // 必須エリア
        for (var i = 0; i < this._requiredAreas.length; i++) {
            var requiredArea = this._requiredAreas[i];
            var reqWidth = HzTimingBar.WIDTH * (requiredArea[1] - requiredArea[0]) / HzTimingBar.maxFrame;
            var reqBmp = new Bitmap(reqWidth, HzTimingBar.HEIGHT - 1);
            reqBmp.context.fillStyle = "#000000";
            reqBmp.context.rect(0, 1, reqWidth, HzTimingBar.HEIGHT - 1);
            reqBmp.context.fill();
            var req = new Sprite(reqBmp);
            req.position.set(HzTimingBar.WIDTH * requiredArea[0] / HzTimingBar.maxFrame, 0);
            this._container.addChild(req);
        }

        // ヒットエリア
        var hitWidth = HzTimingBar.WIDTH * (this._hitArea[1] - this._hitArea[0]) / HzTimingBar.maxFrame;
        var hitBmp = new Bitmap(hitWidth, HzTimingBar.HEIGHT - 1);
        hitBmp.context.fillStyle = "#ffff00";
        hitBmp.context.rect(0, 1, hitWidth, HzTimingBar.HEIGHT - 1);
        hitBmp.context.fill();
        var hit = new Sprite(hitBmp);
        hit.position.set(HzTimingBar.WIDTH * this._hitArea[0] / HzTimingBar.maxFrame, 0);
        this._container.addChild(hit);

        // クリティカルエリア
        var criticalWidth = HzTimingBar.WIDTH * (this._criticalArea[1] - this._criticalArea[0]) / HzTimingBar.maxFrame;
        var criticalBmp = new Bitmap(criticalWidth, HzTimingBar.HEIGHT - 1);
        criticalBmp.context.fillStyle = "#ff00ff";
        criticalBmp.context.rect(0, 1, criticalWidth, HzTimingBar.HEIGHT - 1);
        criticalBmp.context.fill();
        var critical = new Sprite(criticalBmp);
        critical.position.set(HzTimingBar.WIDTH * this._criticalArea[0] / HzTimingBar.maxFrame, 0);
        this._container.addChild(critical);

        // カーソル
        var cursorBmp = new Bitmap(18, 32 + HzTimingBar.HEIGHT + 2);
        cursorBmp.context.fillStyle = "#ff1493";
        cursorBmp.context.strokeStyle = "#FFFFFF";
        cursorBmp.context.lineWidth = 3;
        cursorBmp.context.moveTo(1, 1);
        cursorBmp.context.lineTo(17, 1);
        cursorBmp.context.lineTo(9, 17);
        cursorBmp.context.lineTo(1, 1);
        cursorBmp.context.moveTo(9, HzTimingBar.HEIGHT + 17);
        cursorBmp.context.lineTo(17, HzTimingBar.HEIGHT + 33);
        cursorBmp.context.lineTo(1, HzTimingBar.HEIGHT + 33);
        cursorBmp.context.lineTo(9, HzTimingBar.HEIGHT + 17);
        cursorBmp.context.fill();
        cursorBmp.context.stroke();
        this._cursor = new Sprite(cursorBmp);
        this._cursor.opacity = 0;
        this._cursor.position.set(-9, -17);
        this._container.addChild(this._cursor);

        SceneManager._scene._spriteset.addChild(this._container);

    };

    // 更新処理（終了時はfalseを返す）
    HzTimingBar.prototype.update = function () {
        // タイマーによる時間制限
        if ($gameTimer.isWorking() && $gameTimer._frames === 0) {
            // 終了（失敗）
            if (missSe) {
                AudioManager.playSe({ name: missSe, volume: 90, pitch: 100, pan: 0 });
            }
            $gameVariables.setValue(this._varNo, 0);
            return false;
        }
        this._frame += HzTimingBar.speed;
        // カーソルアニメーション
        if (this._frame >= 0) {
            this._cursor.x = HzTimingBar.WIDTH * this._frame / HzTimingBar.maxFrame;
            this._cursor.opacity = 255;
        } else {
            this._cursor.x = 0;
            this._cursor.opacity = (HzTimingBar.DELAY + this._frame) / HzTimingBar.DELAY * 255;
        }
        // 時間経過でミス
        if (this._frame > HzTimingBar.maxFrame) {
            // 設計ミスと予想：タイムオーバーで必須エリア未クリア時にミス判定
            if (!this.allRequiredAreaHitted()) {
                $gameVariables.setValue(this._varNo, 0);
                if (missSe) {
                    AudioManager.playSe({ name: missSe, volume: 90, pitch: 100, pan: 0 });
                }
            }
            return false;
        }
        // ボタン押下時の判定
        // ボタン押下時の判定 (マウス対応追加)
        var inputCheck = Input.isTriggered('ok') || TouchInput.isTriggered();
        if (inputCheck && this._frame >= 0) {

            // 必須エリアチェック
            for (var i = 0; i < this._requiredAreas.length; i++) {
                var requiredArea = this._requiredAreas[i];
                if (requiredArea[0] <= this._frame && this._frame < requiredArea[1]) {
                    this._hitRequired[i] = true;
                    if (requiredSe) {
                        AudioManager.playSe({ name: requiredSe, volume: 90, pitch: 100, pan: 0 });
                    }
                    return true;
                }
            }

            if (this._criticalArea[0] <= this._frame && this._frame < this._criticalArea[1]) {
                // if (this.allRequiredAreaHitted()) {
                // result = 2;
                result = result > 2 ? result : 2;
                if (criticalSe) {
                    AudioManager.playSe({ name: criticalSe, volume: 90, pitch: 100, pan: 0 });
                }
                $gameVariables.setValue(this._varNo, result);
                criticalAreaHitted = true;
                if (this.allRequiredAreaHitted() && hitAreaHitted && criticalAreaHitted) {
                    return false;
                }
                return true;
                // }
            } else if (this._hitArea[0] <= this._frame && this._frame < this._hitArea[1]) {
                // if (this.allRequiredAreaHitted()) {
                // result = 1;
                result = result > 1 ? result : 1;
                if (hitSe) {
                    AudioManager.playSe({ name: hitSe, volume: 90, pitch: 100, pan: 0 });
                }
                $gameVariables.setValue(this._varNo, result);
                hitAreaHitted = true;
                if (this.allRequiredAreaHitted() && hitAreaHitted && criticalAreaHitted) {
                    return false;
                }
                return true;
                // }
            }
            // if (result === 0) {
            if (missSe) {
                AudioManager.playSe({ name: missSe, volume: 90, pitch: 100, pan: 0 });
            }
            result = 0;
            $gameVariables.setValue(this._varNo, result);
            this._frame = HzTimingBar.maxFrame;
            // }
            // $gameVariables.setValue(this._varNo, result);
            // return false;
        }
        return true;
    };

    HzTimingBar.prototype.allRequiredAreaHitted = function () {
        for (var i = 0; i < this._hitRequired.length; i++) {
            if (this._hitRequired[i] === false) {
                return false;
            }
        }
        return true;
    };

    // 終了処理
    HzTimingBar.prototype.terminate = function () {
        SceneManager._scene._spriteset.removeChild(this._container);
        this._container = null;
    };

    // 角丸長方形を描画
    function roundedRectangle(ctx, top, left, width, height, borderRadius) {
        ctx.beginPath();
        ctx.moveTo(left + borderRadius, top);
        ctx.lineTo(left + width - borderRadius, top);
        ctx.quadraticCurveTo(left + width, top, left + width, top + borderRadius);
        ctx.lineTo(left + width, top + height - borderRadius);
        ctx.quadraticCurveTo(left + width, top + height, left + width - borderRadius, top + height);
        ctx.lineTo(left + borderRadius, top + height);
        ctx.quadraticCurveTo(left, top + height, left, top + height - borderRadius);
        ctx.lineTo(left, top + borderRadius);
        ctx.quadraticCurveTo(left, top, left + borderRadius, top);
        ctx.closePath();
    }
})();
