/**
 * Copyright(c) Live2D Inc. All rights reserved.
 *
 * Use of this source code is governed by the Live2D Open Software license
 * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html.
 */

/* 更新履歴
 * Slip 2021/07/11 ツクールMZ用プラグイン向けに作成（ベースはツクールMV用のLive2Dプラグイン）
 * Slip 2021/08/14 表情変更機能を追加
 *
 * 本プラグインの再配布、改変はLive2D Open Software licenseに準拠します。
 *
 * Live2D Open Software 使用許諾契約書
 * https://www.live2d.com/eula/live2d-open-software-license-agreement_jp.html
 */

/*:
 * @plugindesc Plug-in that displays live2d on Maker MV
 * ツクールMZ上でlive2dを立ち絵表示するプラグイン
 * @author Slip
 *
 * @param Modelcondition
 * @desc live2d model individual setting
 * live2dモデル個別設定
 * @default
 * @type struct<ModelconditionData>[]
 *
 * @param ModelPosition
 * @type note
 * @default Model placement
 * @desc Model placement
 * モデル配置
 *
 * @param ModelScaling
 * @type note
 * @default Scale model
 * @desc Scale model
 * モデルの拡大・縮小
 *
 * @param System
 * @type note
 * @default System
 * @desc System
 * システム関連
 *
 * @param vertical
 * @type number
 * @desc Vertical display position
 * 縦の表示位置
 * @default 320
 * @min 0
 * @max 640
 * @parent ModelPosition
 *
 * @param left
 * @type number
 * @desc Horizontal display position (left)
 * 横の表示位置（左）
 * @default 100
 * @min 0
 * @max 816
 * @parent ModelPosition
 *
 * @param middle
 * @type number
 * @desc Horizontal display position (center)
 * 横の表示位置（中央）
 * @default 408
 * @min 0
 * @max 1200
 * @parent ModelPosition
 *
 * @param right
 * @type number
 * @desc Horizontal display position (right)
 * 横の表示位置（右）
 * @default 716
 * @min 0
 * @max 816
 * @parent ModelPosition
 *
 * @param DefaultScale
 * @type string
 * @desc Model size initial value
 * モデルの大きさ初期値
 * @default 1.0
 * @parent ModelScaling
 *
 * @param pictpriority
 * @type number
 * @desc The live2d model is displayed before the picture below this ID.
 * このID以下のピクチャよりも手前にlive2dのモデルを表示します。
 * @min 0
 * @max 100
 * @default 10
 * @parent System
 *
 * @param includesave
 * @desc Include the display status of live2d in save data
 * live2dの表示状況をセーブデータに含める
 * @default true
 * @type boolean
 * @parent System
 *
 * @param useinbattle
 * @desc Live2d model use flag in battle scene
 * 戦闘画面でのlive2dモデル使用フラグ
 * @default false
 * @type boolean
 * @parent System
 *
 *
 * @command show
 * @text 表示
 * @desc 指定したモデルを表示します
 *
 * @arg modelName
 * @type string
 * @default
 *
 * @command hide
 * @text 消去
 * @desc 指定したモデルを消去します
 *
 * @arg modelName
 * @type string
 * @default
 *
 * @command motion
 * @text モーション再生
 * @desc 指定したモデルのモーションを再生します
 *
 * @arg modelName
 * @type string
 * @desc モデル名
 * @default
 *
 * @arg motionName
 * @type string
 * @desc モーション名
 * @default
 *
 * @arg loop
 * @type boolean
 * @desc ループする/しない
 * @default false
 *
 * @command expression
 * @text 表情変更
 * @desc 指定したモデルの表情を変更します
 *
 * @arg modelName
 * @type string
 * @desc モデル名
 * @default
 *
 * @arg expressionName
 * @type string
 * @desc model3.json "Expressions"のName
 * @default 1
 *
 * @command pos
 * @text 位置変更
 * @desc 指定したモデルの位置を変更します
 *
 * @arg modelName
 * @type string
 * @default
 * @desc モデル名
 *
 * @arg x
 * @type number
 * @default 408
 * @desc X位置
 *
 * @arg y
 * @type number
 * @default 312
 * @desc Y位置
 *
 * @arg dur
 * @type number
 * @default 1
 * @desc 移動時間
 *
 * @arg wait
 * @type boolean
 * @default false
 * @desc ウェイトありなし
 *
 * @command left
 * @text 左
 * @desc 指定したモデル位置を左にします。
 *
 * //引数
 * @arg modelName
 * @type string
 * @default
 *
 * @command middle
 * @text 真ん中
 * @desc 指定したモデル位置を真ん中にします。
 *
 * //引数
 * @arg modelName
 * @type string
 * @default
 *
 * @command right
 * @text 右
 * @desc 指定したモデル位置を右にします。
 *
 * //引数
 * @arg modelName
 * @type string
 * @default
 *
 * @command scale
 * @text 倍率変更
 * @desc 指定したモデルの倍率を変更します
 *
 * @arg modelName
 * @type string
 * @default
 *
 * @arg scale
 * @type Number
 * @default
 *
 * @command paraminitskip
 * @text モーション強制初期化
 * @desc モーション再生時にモデルパラメータを初期化します
 *
 *
 * @command cloth_act
 * @text 服装反映させる
 * @desc モーション再生時にパラメータ24の値を変数によって変更します
 *
 * @arg modelName
 * @type string
 * @default
 *
 * @arg IsInit
 * @type boolean
 * @default true
 *
 * @arg actor
 * @type string
 * @default
 * @desc 白雪:sira 夜鶴:yozu
 *
 *
 * @command bote_act
 * @text ボテ腹を反映させる（エロステ）
 * @desc モーション再生時にパラメータ24の値を変数によって変更します
 *
 * @arg modelName
 * @type string
 * @default
 *
 * @arg IsInit
 * @type boolean
 * @default true
 *
 * @arg actor
 * @type string
 * @default
 * @desc 白雪:sira 夜鶴:yozu
 *
 *
 * @help
 * [English]
 * live2d standing picture display plugin　ver1.0.0
 *
 * It is a plug-in that displays a live2d (cubism 4.0) model while standing and talking.
 *
 * Usage:
 * ↓ It is described here.
 * https://github.com/MrSlip777/plugins_talk_live2d_cubism4/blob/master/README_en.md
 *
 * [日本語]
 * live2d立ち絵表示プラグイン　ver1.0.0
 *
 * live2d(cubism4.0)のモデルを会話中に立ち絵表示するプラグインです。
 *
 * 使い方：
 * ↓こちらに記載しています。
 * https://github.com/MrSlip777/plugins_talk_live2d_cubism4/blob/master/README.md
 */

/*~struct~ModelconditionData:
 * @param Modelname
 * @desc The name of the model used in the plugin command
 * プラグインコマンドで使用するモデルの名前
 * @default
 *
 * @param folderpath
 * @desc folder path of model3.json file
 * model3.jsonファイルのフォルダパス
 * @default
 *
 * @param motiongroupnames
 * @desc You can set the name of the motion.
 * モーションの名称を設定できます。
 * @type note
 * @default "<basic:Idle_1,Idle_3>\n<angry:Idle_4>"
 *
 */

/**
* //一旦保留
*
* @param PlayBack
* @type note
* @default Motion playback
* @desc Motion playback
* モーション再生
*
* @param LinkEquipment
* @type note
* @default Linked setting with equipment
* @desc Linked setting with equipment.
* 装備品との連動設定
*
* @param upsidedown
* @type boolean
* @desc Upside down display
* 上下反転表示
* @default false
* @parent Screen
*
* @param playbackSpeed
* @type number
* @desc Playback speed
* 再生速度
* @default 8
* @parent PlayBack
*
* @param AdjustWeight
* @type number
* @desc Deformation weight adjustment parameter
* 変形の重み調整パラメータ
* @default 8
* @parent PlayBack

* @param Scene
* @type note
* @default Scenes such as maps and battle screens
* @desc Scenes such as maps and battle screens
* マップ、戦闘画面等のシーン
*
* @param SaveData
* @type note
* @default Save data
* @desc Save data
* セーブデータ
*
* @param ScaleGain
* @type number
* @desc Scaling growth rate
* 拡大縮小の増加率
* @default 2
* @parent ModelScaling
*
*
* @param useLinkEquipment
* @desc use flag LinkEquipment.
* 装備品との連動フラグ
* @default false
* @type boolean
* @parent LinkEquipment
*

 * @param individual_upsidedown
 * @type boolean
 * @desc Individual upside down flag
 * 個々の上下反転フラグ
 * @default false
*
 * @param SettingLinkEquipment
 * @type note
 * @default 装備とのモーション連動設定
 * @desc Linked setting with equipment.
 * 装備とのモーション連動設定
 * 
 * @param noWeapon
 * @desc Motion without weapon.
 * 武器なし時のモーション
 * @default
 * @parent SettingLinkEquipment
 *
 * @param noShield
 * @desc Motion without shield.
 * 盾なし時のモーション
 * @default
 * @parent SettingLinkEquipment
 * 
 * @param noHead
 * @desc Motion without head armor.
 * 頭防具なし時のモーション
 * @default
 * @parent SettingLinkEquipment
 *
 * @param noBody
 * @desc Motion without body armor.
 * 体防具なし時のモーション
 * @default
 * @parent SettingLinkEquipment
 * 
 * @param noOrnament
 * @desc Motion without ornaments.
 * 装飾品なし時のモーション
 * @default
 * @parent SettingLinkEquipment


*/

// ----------------------------
/**
 * 共通関数
 * 概要：ツクールエディタのプラグインで設定した変数をやりとりする関数群
 */
// ----------------------------

function parseStrToBoolean(str) {
	// 文字列を判定
	return str == "true" ? true : false;
}

const L2DINstringifyReplacer = function (key, value) {
	if (value === "null") return value;
	try {
		return JSON.parse(value);
	} catch (e) {
		return value;
	}
};

//設定値がある場合にはそれは配列になる。
const L2DINextractmeta = function (data) {
	const req = /<([^<>:]+)(:?)([^>]*)>/g;
	data.meta = {};
	for (var i = 0; i < 1000; i++) {
		var match = req.exec(data.motiongroupnames);
		if (!!match) {
			if (match[2] === ":") {
				data.meta[match[1]] = match[3].split(", ");
			} else {
				data.meta[match[1]] = true;
			}
		} else {
			break;
		}
	}
};

const L2DINfindnameindex = function (name) {
	return L2DINmodels.findIndex(function (data) {
		return data.Modelname === name;
	});
};

const L2DINgetmetaarray = function (data, tag) {
	const meta = data.meta[tag];
	if (!meta || meta === true) {
		return [];
	}
	return meta;
};

const L2DINPP = PluginManager.parameters("Live2DInterfaceMZ");

const L2DINleft = Number(L2DINPP["left"]) || 0;
const L2DINmiddle = Number(L2DINPP["middle"]) || 0;
const L2DINright = Number(L2DINPP["right"]) || 0;
const L2DINvertical = Number(L2DINPP["vertical"]) || 0;
const L2DINscaleX = Number(L2DINPP["scale_V"]) / 100;
const L2DINscaleY = Number(L2DINPP["scale_H"]) / 100;

const L2DINincludesave = L2DINPP["includesave"] === "true";
const L2DINuseinbattle = L2DINPP["useinbattle"] === "true";
const L2DINuseLinkEquipment = L2DINPP["useLinkEquipment"] === "true";
const L2DINpictpriority = Number(L2DINPP["pictpriority"]) || 0;
//Array型、各要素はobject
const L2DINmodels = JSON.parse(JSON.stringify(L2DINPP["Modelcondition"], L2DINstringifyReplacer));
L2DINmodels.forEach(function (Ldata) {
	L2DINextractmeta(Ldata);
});

//共通変数(Live2Dファイル拡張子、画面解像度)
const model3expend = ".model3.json";
const motion3expend = ".motion3.json";
const L2DINgrawidth = 816;
const L2DINgraheight = 640;

//再生速度
const L2DINPlaybackSpeed = Number(L2DINPP["playbackSpeed"]) / 10;

//重み調整
const AdjustWeight = Number(L2DINPP["AdjustWeight"]) / 10;

const ScaleGain = Number(L2DINPP["ScaleGain"]);

//-----------------------------------------------------------------------------
//MotionElement
//モーションのグループ、番号のまとまり
//Slip 2021/07/03
//-----------------------------------------------------------------------------
function MotionElement() {
	this.number = -1;
	this.groupName = "none";
	this.Isloop = false;
}

// ----------------------------
/* クラス：Game_Live2d
 * 概要：Live2Dモデルをツクール上で動作させるためのデータを持つクラス。
 * 扱いはgame_xxxと同等($gameLive2dをインスタンスとして、メソッドを呼び出す)
 */
//----------------------------

var $gameLive2d = null;

function Game_Live2d() {
	this.initialize.apply(this, arguments);
}

Game_Live2d.prototype.initialize = function () {
	this.clear();
};

//マジックナンバーだと分かりにくいのでパラメータ設定のモデル名をkeyにする。
Game_Live2d.prototype.clear = function () {
	var parameters = PluginManager.parameters("Live2DInterfaceMZ");

	//エディタ(プラグインの設定)から取得する設定値
	this._folder = {};
	this._name = {};
	this._model = {};
	this._pos_left = Number(parameters["left"]);
	this._pos_middle = Number(parameters["middle"]);
	this._pos_right = Number(parameters["right"]);
	this._pos_vertical = Number(parameters["vertical"]);
	this._IsUpsidedown = parseStrToBoolean(parameters["upsidedown"]);

	//コマンド経由の設定値（ここでは初期値を設定する）
	this.visible = {}; //モデルの表示、非表示
	this.pos_x = {}; //モデルのX位置
	this.pos_y = {}; //モデルのY位置
	this._targetX = {}; //モデルのX目標位置
	this._targetY = {}; //モデルのY目標位置
	this._duration = {}; //移動する時間
	this._live2dmodelsaveonly = {}; //セーブ用データ

	this.CBR_fadeMode = {}; //
	this.CBR_fadeDuration = {}; //
	this.CBR_DURATION = 5;

	//モーション関係のデータ　（モーション再生、セーブデータの保存、読み込みとして必要）
	this.motionGroup = {};
	this.motionNumber = {};
	this.motionElements = {};

	//モーションの強制実行フラグ
	this.IsForcedExe_NextMotion = {};

	//表情データ
	this.expressionName = {};

	//表情変更の強制実行フラグ
	this.IsForcedExe_ChangeExpression = {};

	this.motionLoop = {};
	this.paraminitskip = {};
	this.cloth_act = {};
	this.bote_act = {};

	this.scale = {}; //スケール
	this.A = {};
	this.R = {};
	this.G = {};
	this.B = {};

	this._waitCount = 0;

	//個々の上下反転フラグ（全体のフラグ、または個々のフラグがtrueになっていると上下反転の表示）
	this.individual_upsidedown = {};

	//装備品、衣装変更時に使用。装備なし時のモーション
	this.linkEquip_None = {};

	this.InitializeModelSetting();
};

Game_Live2d.prototype.CBR_getDuration = function () {
	return this.CBR_DURATION;
};

//各モデル設定値の初期化
Game_Live2d.prototype.InitializeModelSetting = function () {
	var parameters = PluginManager.parameters("Live2DInterfaceMZ");

	var i = 1;

	L2DINmodels.forEach(function (data) {
		this._folder[i] = data.folderpath;
		this._name[i] = data.Modelname;
		var strCopy = data.folderpath.split("/");
		this._model[i] = strCopy[strCopy.length - 2];
		this.visible[i] = false;
		this.scale[i] = Number(parameters["DefaultScale"]);
		this.A[i] = 1.0;
		this.R[i] = 1.0;
		this.G[i] = 1.0;
		this.B[i] = 1.0;
		this._duration[i] = 0;
		this.pos_x[i] = this._pos_middle;
		this.pos_y[i] = this._pos_vertical;
		this._live2dmodelsaveonly[this._name[i]] = {};
		this.motionGroup[i] = "Idle";
		this.motionNumber[i] = 1;
		this.motionElements[i] = [];

		this.IsForcedExe_NextMotion[i] = false;

		this.expressionName[i] = "";
		this.IsForcedExe_ChangeExpression[i] = false;

		this.motionLoop[i] = false;
		this.paraminitskip[i] = false;
		this.cloth_act[i] = false;
		this.bote_act[i] = false;

		//個々の上下反転フラグ（全体のフラグ、または個々のフラグがtrueになっていると上下反転の表示）
		this.individual_upsidedown[i] = data.individual_upsidedown;

		//装備品、衣装変更時に使用。装備なし時のモーション
		this.linkEquip_None[i] = {};
		this.linkEquip_None[i][0] = data.noWeapon;
		this.linkEquip_None[i][1] = data.noShield;
		this.linkEquip_None[i][2] = data.noHead;
		this.linkEquip_None[i][3] = data.noBody;
		this.linkEquip_None[i][4] = data.noOrnament;
		i++;
	}, this);

	this.MAXNUMBER = i - 1;
};

//セーブデータの設定値をモデルに反映
Game_Live2d.prototype.ReflectSavedataToModels = function () {
	var i = 1;
	L2DINmodels.forEach(function (data) {
		var saveobj = this._live2dmodelsaveonly[data.Modelname];
		if (saveobj.vis) {
			this.visible[i] = saveobj.vis;
			this.scale[i] = saveobj.scx;
			this.pos_x[i] = saveobj.px;
			this.pos_y[i] = saveobj.py;
			$gameLive2d.motionGroup[i] = saveobj.motionGroup;
			$gameLive2d.motionNumber[i] = saveobj.motionNumber;
			$gameLive2d.motionLoop[i] = saveobj.motionLoop;

			//モーションを強制実行する
			$gameLive2d.IsForcedExe_NextMotion[i] = true;

			//表情変更を強制実行する
			$gameLive2d.IsForcedExe_ChangeExpression[i] = true;

			$gameLive2d.motionElements[i].splice(0);

			var motionElement = new MotionElement();
			motionElement.groupName = saveobj.motionGroup;
			motionElement.number = saveobj.motionNumber;
			motionElement.Isloop = saveobj.motionLoop;

			$gameLive2d.motionElements[i].push(motionElement);

			$gameLive2d.paraminitskip[i] = saveobj.paraminitskip;
			$gameLive2d.cloth_act[i] = saveobj.cloth_act;
			$gameLive2d.bote_act[i] = saveobj.bote_act;
		}
		i++;
	}, this);
};

//ニューゲームの時に前回セーブしたときの影響が残らないように
Game_Live2d.prototype.newgamefix = function () {
	L2DINmodels.forEach(function (data) {
		var name = data.Modelname;
		var saveobj = this._live2dmodelsaveonly[name];
		if (saveobj) {
			saveobj.isneedrestore = false;
		}
	}, this);

	this.InitializeModelSetting();
};

//モデルの全ての情報をセーブするわけではないことに注意。
Game_Live2d.prototype.Createsavemodel = function () {
	var i = 1;

	L2DINmodels.forEach(function (data) {
		var name = data.Modelname;
		var saveobj = this._live2dmodelsaveonly[name];

		saveobj.isneedrestore = true;
		saveobj.px = $gameLive2d.pos_x[i];
		saveobj.py = $gameLive2d.pos_y[i];
		saveobj.scx = $gameLive2d.scale[i];
		saveobj.vis = $gameLive2d.visible[i];
		saveobj.motionGroup = $gameLive2d.motionGroup[i];
		saveobj.motionNumber = $gameLive2d.motionNumber[i];
		saveobj.motionLoop = $gameLive2d.motionLoop[i];
		saveobj.paraminitskip = $gameLive2d.paraminitskip[i];
		saveobj.cloth_act = $gameLive2d.cloth_act[i];
		saveobj.bote_act = $gameLive2d.bote_act[i];
		i++;
	}, this);
	return this._live2dmodelsaveonly;
};

//loaderより先に実行されることに注意。
Game_Live2d.prototype.Restoresavemodel = function (saveobj) {
	this._live2dmodelsaveonly = saveobj;
};

//各シーンのupdateで呼ぶ
Game_Live2d.prototype.live2dupdate = function () {
	L2DINmodels.forEach(function (targetobj) {
		var model = this._live2dmodel[targetobj.Modelname];
		if (model !== null) {
			this.live2dupdateMove(model);
		}
	}, this);
};

//Cubism4対応 Slip 2020/01/13
Game_Live2d.prototype.getTextureManager = function () {
	return this._textureManager;
};

//各シーンのupdateで呼ぶ
Game_Live2d.prototype.live2dupdate = function () {
	L2DINmodels.forEach(function (targetobj) {
		var model_no = 1;
		for (var number in $gameLive2d._name) {
			if ($gameLive2d._name[number] == targetobj.Modelname) {
				break;
			}
			model_no++;
		}

		var model = null;
		if (model !== null) {
			this.live2dupdateMove(model_no);
		}
	}, this);
};

Game_Live2d.prototype.wait = function (dur) {
	this._waitCount = dur;
};

//移動する
Game_Live2d.prototype.live2dupdateMove = function (model_no) {
	var d = $gameLive2d._duration[model_no];
	if (d > 0) {
		$gameLive2d.pos_x[model_no] = ($gameLive2d.pos_x[model_no] * (d - 1) + $gameLive2d._targetX[model_no]) / d;
		$gameLive2d.pos_y[model_no] = ($gameLive2d.pos_y[model_no] * (d - 1) + $gameLive2d._targetY[model_no]) / d;
		$gameLive2d._duration[model_no]--;
	}
};

//内部のモーション名
Game_Live2d.prototype.InnerMotionName = function (name, group) {
	const index = L2DINfindnameindex(name);

	if (index >= 0) {
		const innerMotionName = L2DINgetmetaarray(L2DINmodels[index], group);

		return innerMotionName;
	} else {
		return null;
	}
};

//-----------------------------------------------------------------------------
//Live2DManager
//会話上にLive2Dモデルを表示するクラス
// Slip 2016/12/25
//-----------------------------------------------------------------------------
function Live2DManager() {
	this.initialize.apply(this, arguments);
}

var live2dmodel = {};

Live2DManager.initialize = function () {
	for (var i = 1; i <= $gameLive2d.MAXNUMBER; i++) {
		live2dmodel[i] = null;
	}
};

Live2DManager.getNumberFromName = function (modelName) {
	var gameLive2d_no = 1;
	for (var number in $gameLive2d._name) {
		if ($gameLive2d._name[number] == modelName) {
			break;
		}
		gameLive2d_no++;
	}

	return gameLive2d_no;
};

//表示フラグ
Live2DManager.live2dVisible = function (model_no, flag) {
	$gameLive2d.visible[model_no] = flag;
};

//モーション設定
Live2DManager.live2dMotion = function (model_no, motionGroup, motion_no, loop) {
	$gameLive2d._lappLive2dManager._models.at(model_no - 1).changeMotion(motionGroup, motion_no - 1, loop);

	//セーブデータ保存用
	$gameLive2d.motionGroup[model_no] = motionGroup;
	$gameLive2d.motionNumber[model_no] = motion_no;
	$gameLive2d.motionLoop[model_no] = loop;

	this.Modelparaminit(model_no);
};

//すべてのモデルの設定されたモーションを再生する（表示されたモーションのみ。セーブデータのロード用）
Live2DManager.PlayBackAllModel = function () {
	for (var i = 1; i <= $gameLive2d.MAXNUMBER; i++) {
		if ($gameLive2d.visible[i] == true) {
			this.live2dMotion(i, $gameLive2d.motionGroup[i], $gameLive2d.motionNumber[i], $gameLive2d.motionLoop[i]);
		}
	}
};

//モーション再生
Live2DManager.live2dSequenceMotion = function (model_no, motions, loop) {
	var count_inLoop = 1;
	var motionNames = String(motions).split(",");
	var count_Max = motionNames.length;

	//スタックされたモーションがあるかもしれないので一掃する
	$gameLive2d.motionElements[model_no].splice(0);

	//強制実行
	$gameLive2d.IsForcedExe_NextMotion[model_no] = true;

	motionNames.forEach(function (motionName) {
		var data = String(motionName).split("_");

		var motionElement = new MotionElement();
		motionElement.groupName = data[0];
		motionElement.number = Number(data[1]) - 1;

		if (count_Max == count_inLoop) {
			motionElement.Isloop = loop;
			//セーブデータ保存用
			$gameLive2d.motionLoop[model_no] = loop;
		} else {
			motionElement.Isloop = false;
		}

		$gameLive2d.motionElements[model_no].push(motionElement);

		//セーブデータ保存用
		$gameLive2d.motionGroup[model_no] = data[0];
		$gameLive2d.motionNumber[model_no] = Number(data[1]) - 1; //実際にあつかうモーションNoは-1されている

		count_inLoop = count_inLoop + 1;
	});
};

//表情設定
Live2DManager.live2dExpression = function (model_no, expressionName) {
	//強制実行
	$gameLive2d.IsForcedExe_ChangeExpression[model_no] = true;

	$gameLive2d.expressionName[model_no] = expressionName;
};

//位置変更（移動可能）
Live2DManager.live2dSetPosition = function (model_no, x, y, dur, wait) {
	if (dur > 0) {
		$gameLive2d._targetX[model_no] = x;
		$gameLive2d._targetY[model_no] = y;
	} else {
		$gameLive2d.pos_x[model_no] = x;
		$gameLive2d.pos_y[model_no] = y;
	}
	$gameLive2d._duration[model_no] = dur;
	if (wait) {
		$gameLive2d._waitCount = dur;
	}
};

//表示位置変更
Live2DManager.live2dSetPosition_X = function (model_no, pos_x) {
	if (pos_x < 1) {
		pos_x = 1;
	} else if (pos_x > Graphics.width) {
		pos_x = Graphics.width;
	} else {
		pos_x = pos_x;
	}

	$gameLive2d.pos_x[model_no] = pos_x;
};

//表示位置変更
Live2DManager.live2dSetPosition_Y = function (model_no, pos_y) {
	if (pos_y < 1) {
		pos_y = 1;
	} else if (pos_y > Graphics.height) {
		pos_y = Graphics.height;
	} else {
		pos_y = pos_y;
	}

	$gameLive2d.pos_y[model_no] = pos_y;
};

//倍率変更
Live2DManager.live2dSetScale = function (model_no, scale) {
	$gameLive2d.scale[model_no] = scale;
};

//初期化スキップ
Live2DManager.live2dsetparaminitskip = function (model_no, flag) {
	$gameLive2d.paraminitskip[model_no] = flag;
};
Live2DManager.live2dsetcloth_act = function (model_no, flag, actor) {
	$gameLive2d.cloth_act[model_no] = actor;
};

Live2DManager.live2dsetbote_act = function (model_no, flag, actor) {
	$gameLive2d.bote_act[model_no] = actor;
};

//グレースケール変更
Live2DManager.live2dSetGrayscale = function (model_no, grayscale) {
	$gameLive2d.R[model_no] = grayscale;
	$gameLive2d.G[model_no] = grayscale;
	$gameLive2d.B[model_no] = grayscale;
};

//上下反転
Live2DManager.live2dDisplayDirection = function (flag) {
	$gameLive2d._IsUpsidedown = flag;
};

//衣装変更
Live2DManager.live2dChangeCloth = function (model_no, Id) {
	$gameLive2d._lappLive2dManager._models.at(model_no - 1).setClothMotion(Id);
};

//装備とモデルの連動
Live2DManager.live2dClothLinkEquipment = function () {
	//衣装変更用
	for (var i = 0; i < $gameParty._actors.length; i++) {
		for (var number in $gameLive2d._name) {
			if ($gameParty.members()[i]._name == $gameLive2d._name[number]) {
				for (var j = 0; j < 5; j++) {
					//装備スロット
					if ($gameLive2d.linkEquip_None[number][j] != "") {
						var index = $gameParty.members()[i]._equips[j]._itemId;
						var cloth_name = null;

						if ($dataArmors[index]) {
							cloth_name = $dataArmors[index].name;
						} else {
							cloth_name = $gameLive2d.linkEquip_None[number][j];
						}

						var innerMotionName = null;

						innerMotionName = $gameLive2d.InnerMotionName($gameLive2d._name[number], cloth_name);
						var modelNo = Live2DManager.getNumberFromName($gameLive2d._name[number]);

						$gameLive2d._lappLive2dManager._models.at(modelNo - 1).setClothMotion(innerMotionName);
					}
				}
			}
		}
	}
};

// ----------------------------
// Sprite_Live2d
// ----------------------------
function Sprite_Live2d() {
	this.initialize.apply(this, arguments);
}
Sprite_Live2d.prototype = Object.create(PIXI.Container.prototype);
Sprite_Live2d.prototype.constructor = Sprite_Live2d;

Sprite_Live2d.prototype.initialize = function () {
	PIXI.Container.call(this);
	this._model = null;

	var filter = new PIXI.filters.OpacityFilter();
	filter.uniforms.opa = 1.0;
	this.filters = [filter];

	this.CBR_kousoku = new Sprite();
	this.CBR_kousoku.x = -300;
	this.CBR_kousoku.y = -700;
	this.addChild(this.CBR_kousoku);
};

Sprite_Live2d.prototype.update = function () {
	if (this._model) {
		// live2dのスピード
		this._model.update(16);
	}
};

Sprite_Live2d.prototype.showImg = function (img) {
	this._model.visible = false;
	this.CBR_kousoku.bitmap = ImageManager.loadPicture(img);
	this.CBR_kousoku.visible = true;
};

Sprite_Live2d.prototype.hideImg = function () {
	this.CBR_kousoku.visible = false;
	this._model.visible = true;
};

Sprite_Live2d.prototype.loadModel = function (name) {
	this.clear();
	const option = { autoInteract: false, autoUpdate: false, motionPreload: PIXI.live2d.MotionPreloadStrategy.ALL };
	PIXI.live2d.Live2DModel.from("./" + name, option)
		.then((model) => {
			if (!this._model) {
				this._model = model;
				this.addChild(model);
				model.anchor.set(0.5, 0.5);
			}
		})
		.catch((error) => {
			console.error(error);
		});
};

Sprite_Live2d.prototype.clear = function () {
	if (this._model) {
		this.removeChild(this._model);
		this._model = null;
	}
};

var CBR_forceShow = function (name) {
	setTimeout(
		function (name) {
			var model_no = Live2DManager.getNumberFromName(name);
			Live2DManager.live2dVisible(model_no, true);
		},
		0,
		name
	);
};
var CBR_forceHide = function (name) {
	setTimeout(
		function (name) {
			var model_no = Live2DManager.getNumberFromName(name);
			Live2DManager.live2dVisible(model_no, false);
		},
		0,
		name
	);
};
var CBR_forceShowHide = function (name1, name2) {
	setTimeout(
		function (name1, name2) {
			var model_no = Live2DManager.getNumberFromName(name1);
			Live2DManager.live2dVisible(model_no, true);
			var model_no = Live2DManager.getNumberFromName(name2);
			Live2DManager.live2dVisible(model_no, false);
		},
		0,
		name1,
		name2
	);
};

(() => {
	"use strict";

	const Scene_Base_initialize = Scene_Base.prototype.initialize;
	Scene_Base.prototype.initialize = function () {
		Scene_Base_initialize.call(this);

		this.CBR_live2D_list = [];
	};

	//---Scene_Baseの上書き---
	//---シーン上にlive2dモデル用スプライトを作成する---
	Scene_Base.prototype.createlive2d = function () {
		this.live2dSprite = {};

		for (var k = 0; k < this.CBR_live2D_list.length; k++) {
			var i = this.CBR_live2D_list[k];

			var fileFullPath = $gameLive2d._folder[i] + $gameLive2d._model[i] + ".model3.json";

			this.live2dSprite[i] = new Sprite_Live2d();
			this.live2dSprite[i].loadModel(fileFullPath);
			this.live2dSprite[i].x = $gameLive2d.pos_x[i];
			this.live2dSprite[i].y = $gameLive2d.pos_y[i];
			this.live2dSprite[i].scale.set(0.2, 0.2);

			//非表示で描画する
			//this.live2dSprite[i].alpha = 0;
			this.live2dSprite[i].visible = false;

			SceneManager._scene._spriteset.addChildlive2d(this.live2dSprite[i]);

			//セーブデータの設定値をモデルに反映
			$gameLive2d.ReflectSavedataToModels();
		}

		if (L2DINuseLinkEquipment == true) {
			Live2DManager.live2dClothLinkEquipment();
		}
	};

	Scene_Base.prototype.resetLive2d = function (i, name) {
		var fileFullPath = $gameLive2d._folder[i] + $gameLive2d._model[i] + ".model3.json";

		this.live2dSprite[i].loadModel(fileFullPath);

		var motionElement = new MotionElement();
		motionElement.groupName = name;
		motionElement.number = 0;

		motionElement.Isloop = true;
		//セーブデータ保存用
		$gameLive2d.motionLoop[i] = true;

		$gameLive2d.motionElements[i].push(motionElement);

		//セーブデータの設定値をモデルに反映
		$gameLive2d.ReflectSavedataToModels();
	};

	Scene_Base.prototype.updatelive2d = function () {
		for (var k = 0; k < this.CBR_live2D_list.length; k++) {
			var i = this.CBR_live2D_list[k];

			//表示非表示の更新
			this.live2dSprite[i].visible = $gameLive2d.visible[i];

			if (this.live2dSprite[i]._model) {
				//位置を更新する
				$gameLive2d.live2dupdateMove(i);
				this.live2dSprite[i].x = $gameLive2d.pos_x[i];
				if (this.live2dSprite[i].x == 840) {
					this.live2dSprite[i].y = 740;
				} else {
					this.live2dSprite[i].y = $gameLive2d.pos_y[i];
				}
				this.live2dSprite[i].scale.set(0.2 * $gameLive2d.scale[i], 0.2 * $gameLive2d.scale[i]);

				if ($gameLive2d.CBR_fadeMode[i] == "hide") {
					this.live2dSprite[i].filters[0].uniforms.opa = $gameLive2d.CBR_fadeDuration[i] / $gameLive2d.CBR_getDuration();
					this.live2dSprite[i].x += 8 * ($gameLive2d.CBR_getDuration() - $gameLive2d.CBR_fadeDuration[i]);

					if ($gameLive2d.CBR_fadeDuration[i] <= 0) {
						$gameLive2d.CBR_fadeMode[i] = "";
						$gameLive2d.visible[i] = false;
						this.live2dSprite[i].filters[0].uniforms.opa = 1;
						$gameLive2d.pos_x[i] = 300;
					} else {
						$gameLive2d.CBR_fadeDuration[i]--;
					}
				}

				if ($gameLive2d.CBR_fadeMode[i] == "show") {
					$gameLive2d.visible[i] = true;
					this.live2dSprite[i].filters[0].uniforms.opa = 1 - $gameLive2d.CBR_fadeDuration[i] / $gameLive2d.CBR_getDuration();
					this.live2dSprite[i].x -= 8 * $gameLive2d.CBR_fadeDuration[i];

					if ($gameLive2d.CBR_fadeDuration[i] <= 0) {
						$gameLive2d.CBR_fadeMode[i] = "";
						this.live2dSprite[i].filters[0].uniforms.opa = 1;
					} else {
						$gameLive2d.CBR_fadeDuration[i]--;
					}
				}

				if (this.live2dSprite[i]._model.internalModel) {
					//モーションの開始条件は以下いずれかが満たされていること
					//1:モーション終了している
					//2:プラグインコマンドからの実行（強制実行フラグ：true）
					if (this.live2dSprite[i]._model.internalModel.motionManager.isFinished() || $gameLive2d.IsForcedExe_NextMotion[i] == true) {
						var motionElement = $gameLive2d.motionElements[i].shift();

						if (motionElement) {
							//初期化スキップ（モデルのモーションパラメータを0にする）
							if ($gameLive2d.paraminitskip[i]) {
								this.live2dSprite[i]._model.internalModel.coreModel._parameterValues.fill(0);
							}

							// if ($gameLive2d.bote_act[i]) {
							// 	var idx = 0;
							// 	if ($gameLive2d.bote_act[i] == "sira") {
							// 		idx = 204;

							// 		// ボテ腹着衣＝2
							// 		// ボテ腹裸＝3
							// 		var flag = $gameVariables.value(idx);
							// 		this.live2dSprite[i]._model.internalModel.coreModel._parameterValues[43] = flag ? 1 : 0;
							// 		this.live2dSprite[i]._model.internalModel.coreModel._parameterValues[44] = flag ? 1 : 0;
							// 	} else if ($gameLive2d.bote_act[i] == "yozu") {
							// 		idx = 205;
							// 		this.live2dSprite[i]._model.internalModel.coreModel._parameterValues[34] = flag ? 1 : 0;
							// 	}
							// }

							this.live2dSprite[i]._model.internalModel.motionManager.stopAllMotions();

							//エラー回避　2023/01/25 slip
							if (
								this.live2dSprite[i]._model.internalModel.motionManager.motionGroups[motionElement.groupName][
									motionElement.number
								] !== undefined
							) {
								//モーションのループ設定
								this.live2dSprite[i]._model.internalModel.motionManager.motionGroups[motionElement.groupName][
									motionElement.number
								].setIsLoop(motionElement.Isloop);
							}

							// //モーションのループ設定
							// if (this.live2dSprite[i]._model.internalModel.motionManager.motionGroups[motionElement.groupName][motionElement.number]) {
							// 	this.live2dSprite[i]._model.internalModel.motionManager.motionGroups[motionElement.groupName][
							// 		motionElement.number
							// 	].setIsLoop(motionElement.Isloop);
							// }

							this.live2dSprite[i]._model.motion(motionElement.groupName, motionElement.number, 3);

							//強制実行フラグをオフ（デフォルトに戻す）
							$gameLive2d.IsForcedExe_NextMotion[i] = false;
						}
					}

					//表情変更
					if ($gameLive2d.IsForcedExe_ChangeExpression[i] == true) {
						var expressions = this.live2dSprite[i]._model.internalModel.settings.expressions;
						var expressionId = 0;
						//エラー回避　2023/01/25 slip
						if (expressions !== undefined) {
							for (var j = 0; j < expressions.length; j++) {
								if (expressions[j].Name == $gameLive2d.expressionName[i]) {
									expressionId = j;
									break;
								}
							}
						}

						this.live2dSprite[i]._model.expression(expressionId);

						//強制実行フラグをオフ（デフォルトに戻す）
						$gameLive2d.IsForcedExe_ChangeExpression[i] = false;
					}

					if ($gameLive2d.cloth_act[i]) {
						var idx = 0;
						if ($gameLive2d.cloth_act[i] == "sira") {
							idx = 237;
						} else if ($gameLive2d.cloth_act[i] == "yozu") {
							idx = 238;
						}
						// ONの場合裸
						this.live2dSprite[i]._model.internalModel.coreModel._parameterValues[24] = $gameSwitches.value(idx) ? 1 : -1;

						$gameLive2d.cloth_act[i] = false;
					}
				}
			}
		}
	};

	Spriteset_Base.prototype.addChildlive2d = function (Sprite) {
		if (L2DINpictpriority >= 100) {
			this.addChild(Sprite);
		} else {
			this._pictureContainer.addChildAt(Sprite, L2DINpictpriority);
		}
	};

	Scene_Base.prototype.terminatelive2d = function () {
		for (var i = 1; i <= $gameLive2d.MAXNUMBER; i++) {
			//this.live2dSprite[i].clear();
		}
	};

	const DataManager_createGameObjects = DataManager.createGameObjects;
	DataManager.createGameObjects = function () {
		DataManager_createGameObjects.call(this);
		if ($gameLive2d == null) {
			$gameLive2d = new Game_Live2d();
		}
	};

	//Scene_Mapの上書き
	/*
	const Scene_Map_terminate = Scene_Map.prototype.terminate;
	Scene_Map.prototype.terminate = function () {
		this.terminatelive2d();
		Scene_Map_terminate.call(this);
	};

	const Scene_Map_start = Scene_Map.prototype.start;
	Scene_Map.prototype.start = function () {
		Scene_Map_start.call(this);
		this.createlive2d();
	};

	const Scene_Map_update = Scene_Map.prototype.update;
	Scene_Map.prototype.update = function () {
		Scene_Map_update.call(this);
		this.updatelive2d();
	};
    */

	//---Live2dモデルの戦闘画面表示機能---
	if (L2DINuseinbattle) {
		Scene_Battle.prototype.istickerupd = function () {
			return true;
		};

		const Battle_L2DINupdate = Scene_Battle.prototype.update;
		Scene_Battle.prototype.update = function () {
			Battle_L2DINupdate.call(this);
			this.updatelive2d();
		};

		const Battle_L2DINterminate = Scene_Battle.prototype.terminate;
		Scene_Battle.prototype.terminate = function () {
			Battle_L2DINterminate.call(this);
			this.terminatelive2d();
		};

		const Battle_L2DINcreateWindowLayer = Scene_Battle.prototype.createWindowLayer;
		Scene_Battle.prototype.createWindowLayer = function () {
			this.CBR_live2D_list = [1, 2, 3, 4, 5, 6, 7, 8, 9];

			this.createlive2d();
			Battle_L2DINcreateWindowLayer.call(this);
		};
	} else {
		console.log("Live2d表示はマップシーンのみです。");
	}

	//---Live2dモデルの設定をセーブデータに含める機能---
	if (L2DINincludesave) {
		const L2DINmakeSaveContents = DataManager.makeSaveContents;
		DataManager.makeSaveContents = function () {
			const contents = L2DINmakeSaveContents.call(this);
			contents.Live2d = $gameLive2d.Createsavemodel();
			return contents;
		};

		const L2DINextractSaveContents = DataManager.extractSaveContents;
		DataManager.extractSaveContents = function (contents) {
			L2DINextractSaveContents.call(this, contents);
			if (!!contents && !!contents.Live2d) {
				$gameLive2d.Restoresavemodel(contents.Live2d);
			}
		};

		const L2DINsetupNewGame = DataManager.setupNewGame;
		DataManager.setupNewGame = function () {
			L2DINsetupNewGame.call(this);
			$gameLive2d.newgamefix();
		};
	} else {
		console.log("Live2d表示はセーブ・ロードしません。");
	}

	//---プラグインコマンドの処理---
	const Game_Interpreter_updateWaitCount = Game_Interpreter.prototype.updateWaitCount;
	Game_Interpreter.prototype.updateWaitCount = function () {
		if ($gameLive2d._waitCount > 0) {
			$gameLive2d._waitCount--;
			return true;
		}
		return Game_Interpreter_updateWaitCount.call(this);
	};

	// プラグインコマンド
	const pluginName = "Live2DInterfaceMZ";

	PluginManager.registerCommand(pluginName, "show", (args) => {
		var model_no = Live2DManager.getNumberFromName(args.modelName);
		Live2DManager.live2dVisible(model_no, true);
	});

	PluginManager.registerCommand(pluginName, "hide", (args) => {
		var model_no = Live2DManager.getNumberFromName(args.modelName);
		Live2DManager.live2dVisible(model_no, false);
	});

	PluginManager.registerCommand(pluginName, "motion", (args) => {
		var model_no = Live2DManager.getNumberFromName(args.modelName);
		var loop = false;
		if (args.loop == "false") {
			loop = false;
		} else {
			loop = true;
		}

		var innerMotionName = $gameLive2d.InnerMotionName(args.modelName, args.motionName);

		Live2DManager.live2dSequenceMotion(model_no, innerMotionName, loop);
	});

	//表情変更
	PluginManager.registerCommand(pluginName, "expression", (args) => {
		var model_no = Live2DManager.getNumberFromName(args.modelName);
		Live2DManager.live2dExpression(model_no, args.expressionName);
	});

	//位置変更
	//左
	PluginManager.registerCommand(pluginName, "left", (args) => {
		var model_no = Live2DManager.getNumberFromName(args.modelName);
		Live2DManager.live2dSetPosition_X(model_no, $gameLive2d._pos_left);
	});

	//真ん中
	PluginManager.registerCommand(pluginName, "middle", (args) => {
		var model_no = Live2DManager.getNumberFromName(args.modelName);
		Live2DManager.live2dSetPosition_X(model_no, $gameLive2d._pos_middle);
	});

	//右
	PluginManager.registerCommand(pluginName, "right", (args) => {
		var model_no = Live2DManager.getNumberFromName(args.modelName);
		Live2DManager.live2dSetPosition_X(model_no, $gameLive2d._pos_right);
	});

	//位置移動
	PluginManager.registerCommand(pluginName, "pos", (args) => {
		var model_no = Live2DManager.getNumberFromName(args.modelName);
		const x = Number(args.x) || 0;
		const y = Number(args.y) || 0;
		const dur = Number(args.dur) || 0;
		var wait = args.wait;

		Live2DManager.live2dSetPosition(model_no, x, y, dur, wait);
	});

	//大きさ変更
	PluginManager.registerCommand(pluginName, "scale", (args) => {
		var model_no = Live2DManager.getNumberFromName(args.modelName);
		Live2DManager.live2dSetScale(model_no, args.scale);
	});

	//初期化スキップ（強制モーション初期化）
	PluginManager.registerCommand(pluginName, "paraminitskip", (args) => {
		var model_no = Live2DManager.getNumberFromName(args.modelName);
		Live2DManager.live2dsetparaminitskip(model_no, args.IsInit);
	});

	//初期化スキップ（強制モーション初期化）
	PluginManager.registerCommand(pluginName, "cloth_act", (args) => {
		var model_no = Live2DManager.getNumberFromName(args.modelName);
		Live2DManager.live2dsetcloth_act(model_no, args.IsInit, args.actor);
	});

	//初期化スキップ（強制モーション初期化）
	PluginManager.registerCommand(pluginName, "bote_act", (args) => {
		var model_no = Live2DManager.getNumberFromName(args.modelName);
		Live2DManager.live2dsetbote_act(model_no, args.IsInit, args.actor);
	});

	//以下は実装するか検討中　slip 2021/07/14
	PluginManager.registerCommand(pluginName, "set", (args) => {
		var model_no = Live2DManager.getNumberFromName(args.modelName);

		switch (args[1]) {
			case "expression":
			case "表情":
				Live2DManager.live2dExpression(model_no, args[2]);
				break;
			case "grayscale":
			case "グレースケール":
				Live2DManager.live2dSetGrayscale(model_no, args[2]);
				break;
			case "衣装変更":
			case "changecloth":
				Live2DManager.live2dChangeCloth(model_no, args[2]);
		}
	});
})();

//live2d用半透明フィルター
PIXI.filters.OpacityFilter = function () {
	var fragmentSrc = [
		"precision lowp float;",
		"uniform sampler2D uSampler;",
		"varying vec2 vTextureCoord;",
		"uniform float opa;",

		"vec2 mapCoord( vec2 coord ){",
		"return coord;",
		"}",

		"void main (void) {",
		"vec2 coord = mapCoord(vTextureCoord);",
		"gl_FragColor = texture2D(uSampler, coord );",

		"gl_FragColor = vec4(gl_FragColor.r, gl_FragColor.g, gl_FragColor.b, gl_FragColor.a * opa);",

		"}"
	];
	PIXI.Filter.call(
		this,
		null, // vertex shader
		fragmentSrc.join("\n"), // fragment shader
		{ opa: 0.0 } // uniforms
	);
};

PIXI.filters.OpacityFilter.prototype = Object.create(PIXI.Filter.prototype);
PIXI.filters.OpacityFilter.prototype.constructor = PIXI.filters.OpacityFilter;
