// ShowItemIcon.js Ver.3.1.0
// MIT License (C) 2022 あわやまたな
// http://opensource.org/licenses/mit-license.php

/*:
* @target MV MZ
* @plugindesc  キャラクターの頭上にアイテムアイコンを表示できます。
* @author あわやまたな (Awaya_Matana)
* @url https://awaya3ji.seesaa.net/article/485335701.html
* @help［プラグインコマンド（MV）］
* showItemIcon イベントID アイコン番号
* アイコン番号が未入力、または0の時にアイコンを消去できます。
*
* showItemIcon キャラクターID 番号 種類
* 種類にitem, weapon, armor, skill, stateいずれかを入力する事で
* アイテム、武器、防具、スキル、ステートいずれかのアイコンを直接指定できます。
*
* showItemIcon offsets キャラクターID オフセットX オフセットY
* アイコンのオフセットを設定します。
*
* ［スクリプト］
* 【イベントエディター】
* this.character(キャラクターID).setIcon(アイコン番号);
* アイコン番号が未入力、または0の時にアイコンを消去できます。
*
* this.character(キャラクターID).setIcon(番号, "種類");
* 種類にitem, weapon, armor, skill, stateいずれかを入力する事で
* アイテム、武器、防具、スキル、ステートいずれかのアイコンを直接指定できます。
*
* this.character(キャラクターID).setIconOffsets(オフセットX, オフセットY);
* アイコンのオフセットを設定します。
*
* 【移動ルートの設定】
* this.setIcon(アイコン番号);
* アイコン番号が未入力、または0の時にアイコンを消去できます。
*
* this.setIcon(番号, "種類");
* 種類にitem, weapon, armor, skill, stateいずれかを入力する事で
* アイテム、武器、防具、スキル、ステートいずれかのアイコンを直接指定できます。
*
* this.setIconOffsets(オフセットX, オフセットY);
* アイコンのオフセットを設定します。
*
*［オプション］
* イベントの一行目に注釈を置き、以下の形式で入力します。
* するとページ切り替え時に最初からアイコンが表示されます。
* <itemIcon:50> //アイコン番号50のアイコンを表示する。
* <itemIcon:120> //アイコン番号120のアイコンを表示する。
* <itemIcon:1,weapon> //武器1のアイコンを表示する。
* <itemIcon:15,防具> //防具15のアイコンを表示する。
* <itemIconOffsets:0,-24> //アイコンのオフセットを0、-24にする。
*
*［Z座標］
* 0：下層タイル
* 1：下層キャラクター
* 3：通常キャラクター
* 4：上層タイル
* 5：上層キャラクター
* 6：飛行船の影
* 7：フキダシアイコン
* 8：アニメーション
* 9：タップエフェクト
*
* [更新履歴]
* 2022/01/24：Ver.1.0.0　公開
* 2022/01/24：Ver.1.0.1　イベントの一時消去時にアイコンが残留する不具合を修正。
* 2022/01/24：Ver.1.0.2　スクリプトで入力しやすいように関数を追加。
* 2022/01/24：Ver.1.0.3　アイコンの寸法取得方法を変更。
* 2022/12/09：Ver.2.0.0　PluginCommonBaseの判別方法を修正。ページ別設定をメモから注釈に変更。
* 2022/12/20：Ver.2.0.1　MZのv1.6.0の新機能に対応。
* 2023/02/24：Ver.3.0.0　アイテム、武器、防具、スキル、ステートアイコンの直接指定に対応。
* 2025/01/19：Ver.3.0.1　リファクタリング。
* 2025/01/21：Ver.3.0.2　アイコンのオフセットを追加。
* 2025/03/02：Ver.3.1.0　キャラクターの拡大率に応じて座標を変化させる機能を追加。
*
* @command showItemIcon
* @text アイテムアイコンの表示
* @desc キャラクターの頭上に、指定したアイコンを表示します。
*
* @arg character
* @text キャラクター
* @desc 対象とするキャラクターです。
* -1：プレイヤー　0：このイベント
* @default -1
* @type string
*
* @arg itemIcon
* @text アイコン番号
* @desc 表示するアイテムアイコンの番号です。
* 未入力、または0で消去。
* @default 
* @type icon
*
* @command showItem
* @text アイテムの表示
* @desc キャラクターの頭上に、指定したアイテムのアイコンを表示します。
*
* @arg character
* @text キャラクター
* @desc 対象とするキャラクターです。
* -1：プレイヤー　0：このイベント
* @default -1
* @type string
*
* @arg item
* @text アイテムID
* @desc 表示するアイテムのIDです。
* 未入力、または0で消去。
* @default 
* @type item
*
* @command showWeapon
* @text 武器の表示
* @desc キャラクターの頭上に、指定した武器のアイコンを表示します。
*
* @arg character
* @text キャラクター
* @desc 対象とするキャラクターです。
* -1：プレイヤー　0：このイベント
* @default -1
* @type string
*
* @arg item
* @text 武器ID
* @desc 表示する武器のIDです。
* 未入力、または0で消去。
* @default 
* @type weapon
*
* @command showArmor
* @text 防具の表示
* @desc キャラクターの頭上に、指定した防具のアイコンを表示します。
*
* @arg character
* @text キャラクター
* @desc 対象とするキャラクターです。
* -1：プレイヤー　0：このイベント
* @default -1
* @type string
*
* @arg item
* @text 防具ID
* @desc 表示する防具のIDです。
* 未入力、または0で消去。
* @default 
* @type armor
*
* @command showSkill
* @text スキルの表示
* @desc キャラクターの頭上に、指定したスキルのアイコンを表示します。
*
* @arg character
* @text キャラクター
* @desc 対象とするキャラクターです。
* -1：プレイヤー　0：このイベント
* @default -1
* @type string
*
* @arg item
* @text スキルID
* @desc 表示するスキルのIDです。
* 未入力、または0で消去。
* @default 
* @type skill
*
* @command showState
* @text ステートの表示
* @desc キャラクターの頭上に、指定したステートのアイコンを表示します。
*
* @arg character
* @text キャラクター
* @desc 対象とするキャラクターです。
* -1：プレイヤー　0：このイベント
* @default -1
* @type string
*
* @arg item
* @text ステートID
* @desc 表示するステートのIDです。
* 未入力、または0で消去。
* @default 
* @type state
*
* @command setIconOffsets
* @text アイコンのオフセット
* @desc アイコンのオフセットを設定します。
*
* @arg character
* @text キャラクター
* @desc 対象とするキャラクターです。
* -1：プレイヤー　0：このイベント
* @default -1
*
* @arg offsetX
* @text オフセットX
* @desc アイコンをずらす距離です。
* @default 0
*
* @arg offsetY
* @text オフセットY
* @desc アイコンをずらす距離です。
* @default 0
*
* @param iconY
* @text アイコンY座標
* @desc アイコンのY座標を設定値分ずらして表示します。
* @default 0
* @type number
* @min -999999
*
* @param iconZ
* @text アイコンZ座標
* @desc アイコンのZ座標を設定します。
* @default 6.5
* @type string
*
* @param autoShift
* @text 自動シフト
* @desc キャラクターの拡大率に応じて座標を変化させます。
* @type boolean
* @default true
*
*/

var AWY = AWY || {};
'use strict';
{
	const useMZ = Utils.RPGMAKER_NAME === "MZ";
	const pluginName = document.currentScript.src.match(/^.*\/(.*).js$/)[1];
	const parameters = PluginManager.parameters(pluginName);
	const iconY = Number(parameters['iconY']);
	const iconZ = Number(parameters['iconZ']);
	const autoShift = parameters['autoShift'] === "true";
	
	if (useMZ) {
		const _PluginManager = window.PluginManagerEx ?? PluginManager;
		const _pluginName = window.PluginManagerEx ? document.currentScript : pluginName;
		_PluginManager.registerCommand(_pluginName, "showItemIcon", function (args) {
			const character = this.character(+args.character);
			if (character) {
				character.setIcon(+args.itemIcon);
			}
		});
		_PluginManager.registerCommand(_pluginName, "showItem", function (args) {
			const character = this.character(+args.character);
			if (character) {
				character.setIcon(+args.item, "item");
			}
		});
		_PluginManager.registerCommand(_pluginName, "showWeapon", function (args) {
			const character = this.character(+args.character);
			if (character) {
				character.setIcon(+args.item, "weapon");
			}
		});
		_PluginManager.registerCommand(_pluginName, "showArmor", function (args) {
			const character = this.character(+args.character);
			if (character) {
				character.setIcon(+args.item, "armor");
			}
		});
		_PluginManager.registerCommand(_pluginName, "showSkill", function (args) {
			const character = this.character(+args.character);
			if (character) {
				character.setIcon(+args.item, "skill");
			}
		});
		_PluginManager.registerCommand(_pluginName, "showState", function (args) {
			const character = this.character(+args.character);
			if (character) {
				character.setIcon(+args.item, "state");
			}
		});
		_PluginManager.registerCommand(_pluginName, "setIconOffsets", function(args) {
			const character = this.character(+args.character);
			if (character) {
				character.setIconOffsets(+args.offsetX, +args.offsetY);
			}
		});
	}
	const _Game_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand;
	Game_Interpreter.prototype.pluginCommand = function(command, args) {
		_Game_Interpreter_pluginCommand.apply(this, arguments);
		if (command === "showItemIcon") {
			if (args[0] === "offsets") {
				const character = this.character(+args[1]);
				if (character) {
					character.setIconOffsets(Number(args[2] || 0), Number(args[3] || 0));
				}
				return;
			}
			const character = this.character(+args[0]);
			if (character) {
				character.setIcon(+args[1], args[2]);
			}
		}	
		
	};

	//-----------------------------------------------------------------------------
	// Sprite_ItemIcon

	function Sprite_ItemIcon() {
		this.initialize(...arguments);
	}

	Sprite_ItemIcon.prototype = Object.create(Sprite.prototype);
	Sprite_ItemIcon.prototype.constructor = Sprite_ItemIcon;

	Sprite_ItemIcon.prototype.initialize = function() {
		Sprite.prototype.initialize.call(this);
		this.initMembers();
		this.loadBitmap();
	};

	Sprite_ItemIcon.prototype.initMembers = function() {
		this._target = null;
		this._itemIconId = 0;
		this.anchor.x = 0.5;
		this.anchor.y = 1;
		this.z = iconZ;
	};

	Sprite_ItemIcon.prototype.loadBitmap = function() {
		this.bitmap = ImageManager.loadSystem("IconSet");
		this.setFrame(0, 0, 0, 0);
	};

	Sprite_ItemIcon.prototype.setup = function(targetSprite, itemIconId) {
		this._target = targetSprite;
		this._itemIconId = itemIconId;
		this.updateFrame();
	};

	Sprite_ItemIcon.prototype.update = function() {
		Sprite.prototype.update.call(this);
		this.updatePosition();
	};

	Sprite_ItemIcon.prototype.updatePosition = function() {
		const target = this._target;
		this.x = target.x;
		this.y = target.y - target.height + iconY;
		const character = target._character;
		if (character) {
			this.x += character._itemIconOffsetX || 0;
			this.y += character._itemIconOffsetY || 0;
		}
	};
	if (autoShift) {
		Sprite_ItemIcon.prototype.updatePosition = function() {
			const target = this._target;
			this.x = target.x;
			this.y = target.y - target.height * target.scale.y + iconY;
			const character = target._character;
			if (character) {
				this.x += character._itemIconOffsetX || 0;
				this.y += character._itemIconOffsetY || 0;
			}
		};
	}		

	Sprite_ItemIcon.prototype.updateFrame = function() {
		const w = useMZ ? ImageManager.iconWidth : Window_Base._iconWidth;
		const h = useMZ ? ImageManager.iconHeight : Window_Base._iconHeight;
		const sx = this._itemIconId %16 * w;
		const sy = Math.floor(this._itemIconId /16) * h;
		this.setFrame(sx, sy, w, h);
	};

	//-----------------------------------------------------------------------------
	// Spriteset_Map

	const _Spriteset_Map_initialize = Spriteset_Map.prototype.initialize;
	Spriteset_Map.prototype.initialize = function() {
		_Spriteset_Map_initialize.call(this);
		if (useMZ) this._itemIconSprites = [];
		this.restoreItemIcons();
	};
	//マップ復帰時に復元
	Spriteset_Map.prototype.restoreItemIcons = function() {
		for (const sprite of this._characterSprites) {
			const character = sprite._character;
			const itemIconId = character.itemIconId()
			if (itemIconId > 0) this.setItemIcon({target:character, itemIconId:itemIconId});
		}
	};
	//MZ
	const _Spriteset_Map_destroy = Spriteset_Map.prototype.destroy;
	Spriteset_Map.prototype.destroy = function(options) {
		this.removeAllItemIcons();
		_Spriteset_Map_destroy.call(this, options);
	};
	//MZ
	const _Spriteset_Map_loadSystemImages = Spriteset_Map.prototype.loadSystemImages;
	Spriteset_Map.prototype.loadSystemImages = function() {
		_Spriteset_Map_loadSystemImages.call(this);
		ImageManager.loadSystem("IconSet");
	};

	const _Spriteset_Map_update = Spriteset_Map.prototype.update;
	Spriteset_Map.prototype.update = function() {
		_Spriteset_Map_update.call(this);
		this.updateItemIcons();
	};

	Spriteset_Map.prototype.updateItemIcons = function() {
		this.processItemIconRequests();
	};

	Spriteset_Map.prototype.processItemIconRequests = function() {
		for (;;) {
			const request = $gameTemp.retrieveItemIcon();
			if (request) {
				this.setItemIcon(request);
			} else {
				break;
			}
		}
	};
	//MZ
	Spriteset_Map.prototype.setItemIcon = function(request) {
		const targetSprite = this.findTargetSprite(request.target);
		if (targetSprite) {
			//アイコンが既に表示されている場合は既存のスプライトに変更を加えるだけで終了
			const existingItemIcon = this.existingItemIcon(request);
			if (existingItemIcon) {
				if (request.itemIconId > 0) {
					existingItemIcon.setup(targetSprite, request.itemIconId);
				} else {
					//アイコン番号が0なら破棄
					this.removeItemIcon(existingItemIcon);
				}
			} else if (request.itemIconId > 0) {
				//アイコン番号が0より大きければスプライトを生成
				const sprite = new Sprite_ItemIcon();
				sprite.targetObject = request.target;
				sprite.setup(targetSprite, request.itemIconId);
				this._effectsContainer.addChild(sprite);
				this._itemIconSprites.push(sprite);
			}
			
		}
	};
	//MZ //既存のアイコンが存在するかどうか
	Spriteset_Map.prototype.existingItemIcon = function(request) {
		for (const sprite of this._itemIconSprites) {
			if (sprite.targetObject === request.target) {
				return sprite;
			}
		}
	};
	//MZ
	Spriteset_Map.prototype.removeItemIcon = function(sprite) {
		this._itemIconSprites.remove(sprite);
		this._effectsContainer.removeChild(sprite);
		sprite.destroy();
	};
	//MZ
	Spriteset_Map.prototype.removeAllItemIcons = function() {
		for (const sprite of this._itemIconSprites.clone()) {
			this.removeItemIcon(sprite);
		}
	};
	//MV
	if (!useMZ) {
		Spriteset_Map.prototype.setItemIcon = function(request) {
			const targetSprite = this.findTargetSprite(request.target);
			if (targetSprite) {
				if(request.itemIconId > 0) {
					targetSprite.createItemIcon();
				} else {
					targetSprite.removeItemIcon();
				}
			}
		};

		Spriteset_Map.prototype.findTargetSprite = function(target) {
			return this._characterSprites.find(sprite => sprite._character === target);
		};
	}

	//-----------------------------------------------------------------------------
	// Sprite_Character
	//MV
	if (!useMZ) {
		Sprite_Character.prototype.createItemIcon = function() {
			if (!this._itemIconSprite) this._itemIconSprite = new Sprite_ItemIcon();
			this._itemIconSprite.setup(this, this._character.itemIconId());
			this.parent.addChild(this._itemIconSprite);
		};

		Sprite_Character.prototype.removeItemIcon = function() {
			if (this._itemIconSprite) {
				this.parent.removeChild(this._itemIconSprite);
				this._itemIconSprite = null;
			}
		};
	}
	
	//-----------------------------------------------------------------------------
	// Game_Temp

	const _Game_Temp_initialize = Game_Temp.prototype.initialize;
	Game_Temp.prototype.initialize = function() {
		_Game_Temp_initialize.call(this);
		this._itemIconQueue = [];
	};

	Game_Temp.prototype.requestItemIcon = function(target, itemIconId = 0) {
		const request = { target: target, itemIconId: itemIconId };
		target.setItemIconId(itemIconId);
		this._itemIconQueue.push(request);
	};

	Game_Temp.prototype.retrieveItemIcon = function() {
		return this._itemIconQueue.shift();
	};

	//-----------------------------------------------------------------------------
	// Game_CharacterBase

	const _Game_CharacterBase_initMembers = Game_CharacterBase.prototype.initMembers;
	Game_CharacterBase.prototype.initMembers = function() {
		_Game_CharacterBase_initMembers.call(this);
		this._itemIconId = 0;
		this._itemIconOffsetX = 0;
		this._itemIconOffsetY = 0;
	};

	Game_CharacterBase.prototype.setIcon = function (itemIconId, type){
		$gameTemp.requestItemIcon(this, convertIconIndex(itemIconId, type));
	};

	Game_CharacterBase.prototype.setIconOffsets = function(x, y) {
		this._itemIconOffsetX = x;
		this._itemIconOffsetY = y;
	};

	function convertIconIndex(index, type) {
		if (index && type) {
			let data = null;
			switch (type) {
			case "item":
			case "アイテム":
				data = $dataItems;
				break;
			case "weapon":
			case "武器":
				data = $dataWeapons;
				break;
			case "armor":
			case "防具":
				data = $dataArmors;
				break;
			case "skill":
			case "スキル":
				data = $dataSkills;
				break;
			case "state":
			case "ステート":
				data = $dataStates;
				break;
			}
			if (data && data[index]) {
				return data[index].iconIndex;
			}
			return 0;
		}
		return index || 0;
	}

	Game_CharacterBase.prototype.setItemIconId = function(itemIconId) {
		this._itemIconId = itemIconId;
	};

	Game_CharacterBase.prototype.itemIconId = function() {
		return this._itemIconId;
	};

	//-----------------------------------------------------------------------------
	// Game_Event

	const _Game_Event_clearPageSettings = Game_Event.prototype.clearPageSettings;
	Game_Event.prototype.clearPageSettings = function() {
		_Game_Event_clearPageSettings.call(this);
		this.setIcon();
	};

	const _Game_Event_setupPageSettings = Game_Event.prototype.setupPageSettings;
	Game_Event.prototype.setupPageSettings = function() {
		_Game_Event_setupPageSettings.call(this);
		this.setupSettingsItemIcon();
	};

	Game_Event.prototype.setupSettingsItemIcon = function() {
		const page = this.page();
		this.setIcon(page.itemIcon);
		this.setIconOffsets(page.itemIconOffsetX || 0, page.itemIconOffsetY ||0);
	};

	//-----------------------------------------------------------------------------
	// DataManager
	//マップデータロード時の処理に追加

	if (!AWY.addPageSettings) {
		AWY._pageSettingHandlers = [];
		const _DataManager_onLoad = DataManager.onLoad;
		DataManager.onLoad = function(object) {
			_DataManager_onLoad.apply(this, arguments);
			AWY.addPageSettings(object);
		};

		AWY.addPageSettings = function(object) {
			if (DataManager.isMapObject(object) && Array.isArray(object.events)) {
				for (const event of object.events) {
					if (event && event.pages){
						extractMetadata(event);
					}
				}
			}
		};

		function extractMetadata(data) {
			for (const page of data.pages) {
				const comment = findComment(page);
				const commentData = {"note": comment};
				DataManager.extractMetadata(commentData);
				AWY._pageSettingHandlers.forEach(handler => handler(page, commentData.meta));
			}
		}

		function findComment(page) {
			const list = page.list;
			if (!list[0] && list[0].code !== 108) {
				return "";
			}
			let comment = list[0].parameters[0];
			for (let i = 1; list[i] && list[i].code === 408; i++) {
				comment += list[i].parameters[0];
			}
			return comment;
		}
	}

	if (!DataManager.isMapObject) {
		DataManager.isMapObject = function() {
			return !!(object.data && object.events);
		};
	}

	AWY._pageSettingHandlers.push(
		function(page, meta) {
			const data = (meta['itemIcon'] || "").split(",");
			page.itemIcon = convertIconIndex(Number(data[0] || 0), data[1]);
			const offsets = (meta['itemIconOffsets'] || "").split(",");
			page.itemIconOffsetX = Number(offsets[0] || 0);
			page.itemIconOffsetY = Number(offsets[1] || 0);
		}
	);
}