//============================================================================= // iiiBattleStateHelp.js // ---------------------------------------------------------------------------- // (C) 2024 iii // This software is released under the MIT License. // http://opensource.org/licenses/mit-license.php // ---------------------------------------------------------------------------- /*:ja * @target MZ * @plugindesc ステートアイコン上にマウスホバーでヘルプバルーン表示 * @author iii * @help * ■ マウスホバーでのバルーン表示 * ステートのメモ欄に * * と記載したステートのアイコンにマウスカーソルを合わせると説明文が表示されます。 * * ■ ヘルプ画像表示コマンドの追加(パーティコマンド) * プラグインパラメータのhelpImagesに表示したい画像を指定してください。 * (指定が無ければコマンド自体表示されません) * * ----- * @param helpCommandName * @text バトル中ヘルプのコマンド名 * @desc 空白の場合、ヘルプコマンド自体が表示されなくなります * @type string * @default ヘルプ * * @param helpImages * @text バトル中ヘルプ画像 * @desc 設定されていない場合、ヘルプ画像コマンド自体が表示されなくなります * @type file[] * */ (() => { "use strict"; const script = document.currentScript; const pluginParam = PluginManagerEx.createParameter(script); const needShowHelp = pluginParam.helpCommandName && pluginParam.helpImages.length > 0; /* ============================================================================= 以下、バトル中のマウスホバーでのヘルプバルーン表示対応 ============================================================================= */ /******************************************************* * Scene_Battle */ const _Scene_Battle_prototype_createDisplayObjects = Scene_Battle.prototype.createDisplayObjects; Scene_Battle.prototype.createDisplayObjects = function () { _Scene_Battle_prototype_createDisplayObjects.call(this); this._helpBalloon = new StateHelpBalloonSprite(); this._helpBalloon.x = Graphics.width / 2; this._helpBalloon.y = Graphics.height / 2; this.addChild(this._helpBalloon); this._helpBalloon.hide(); if (needShowHelp) { this._helpSprite = new Sprite(); this._helpSprite.bitmap = new Bitmap(Graphics.width, Graphics.height); this._helpSprite.hide(); this.addChild(this._helpSprite); } }; // 毎フレーム更新 const _Scene_Battle_prototype_update = Scene_Battle.prototype.update; Scene_Battle.prototype.update = function () { _Scene_Battle_prototype_update.call(this); this.updateStateIconHelpBalloon(); }; // バルーン表示判定 Scene_Battle.prototype.updateStateIconHelpBalloon = function () { let battlers = this._spriteset.battlerSprites(); let targetBattler = null; // Sprite_Battler(にくっついているアイコンのマウスホバー)をチェック var battlerSprite = battlers.find((bs) => { const battler = bs._battler; if (!battler) return false; const stateSprite = bs._stateIconSprite; if (!stateSprite) return false; const icons = bs._battler.allIcons(); if (!icons || icons.length <= 0) return false; if (!stateSprite._hovered) return false; return true; }); targetBattler = battlerSprite?._battler; // ボス用UIにくっついているアイコンのマウスホバーをチェック if (!targetBattler && this._currentEnemyInfoWindow) { const gameEnemy = this._currentEnemyInfoWindow._gameEnemy; const stateSprite = this._currentEnemyInfoWindow._stateIconSprite; if (gameEnemy && stateSprite) { const icons = gameEnemy.allIcons(); if (icons?.length > 0 && stateSprite._hovered) { targetBattler = gameEnemy; } } } // ↑の対象がいなければ、Window_BattleStatus(にくっついているアイコンのマウスホバー)をチェック if (!targetBattler) { for (let i = 0; i < this._statusWindow.maxItems(); i++) { const actor = this._statusWindow.actor(i); if (!actor) continue; // Window_StatusBase.prototype.placeStateIcon 参照 const key = "actor%1-stateIcon".format(actor.actorId()); const dict = this._statusWindow._additionalSprites; if (!dict[key]) continue; const stateSprite = dict[key]; if (!stateSprite._hovered) continue; targetBattler = actor; } } // マウスホバー中のBattler存在しないので終了 if (!targetBattler) { this._helpBalloon.hide(); return; } // ステートアイコン番号からはステートIDが逆引きできないので、メタデータ設定されているステートをすべて表示 const states = targetBattler .traitObjects() .filter((sData) => sData.meta.ShowHoverState); if (states.length <= 0) { this._helpBalloon.hide(); return; } this._helpBalloon.ShowBalloon(states); }; /******************************************************* * Sprite_StateIconの拡張 * マウスホバー反応するようにしたもの */ const Sprite_StateIcon_prototype_initialize = Sprite_StateIcon.prototype.initialize; Sprite_StateIcon.prototype.initialize = function () { Sprite_StateIcon_prototype_initialize.call(this); this._hovered = false; }; const Sprite_StateIcon_prototype_update = Sprite_StateIcon.prototype.update; Sprite_StateIcon.prototype.update = function () { Sprite_StateIcon_prototype_update.call(this); this.processTouch(); }; Sprite_StateIcon.prototype.processTouch = function () { if (this.isClickEnabled()) { if (this.isBeingTouched()) { if (!this._hovered && TouchInput.isHovered()) { this._hovered = true; } } else { this._hovered = false; } } else { this._hovered = false; } }; Sprite_StateIcon.prototype.isClickEnabled = function () { return this.worldVisible; }; Sprite_StateIcon.prototype.isBeingTouched = function () { const touchPos = new Point(TouchInput.x, TouchInput.y); const localPos = this.worldTransform.applyInverse(touchPos); return this.hitTest(localPos.x, localPos.y); }; Sprite_StateIcon.prototype.hitTest = function (x, y) { const rect = new Rectangle( -this.anchor.x * this.width, -this.anchor.y * this.height, this.width, this.height ); return rect.contains(x, y); }; /******************************************************* * 状態異常ヘルプ表示用バルーンスプライト */ class StateHelpBalloonSprite extends Sprite_Clickable { #mergin = 8; #stateCache = null; constructor() { super(); this.bitmap = new Bitmap(300, 24); // 幅は固定。高さは動的に変わる } ShowBalloon(states) { const lineHei = 24; let totalHei = this.#mergin * 2; states.forEach((sData) => { if (!sData.meta.ShowHoverState) return; totalHei += lineHei; }); this.bitmap.resize(this.width, totalHei); this.height = totalHei; this.bitmap.clear(); this.bitmap.fillAll("#00000088"); states.forEach((sData, idx) => { if (!sData.meta.ShowHoverState) return; this.#drawTextAndIcon( sData.iconIndex, sData.meta.ShowHoverState, this.#mergin, this.#mergin + idx * lineHei, this.width, lineHei ); }, this); this.x = TouchInput.x + this.#mergin; this.y = TouchInput.y + this.#mergin; this.show(); } #drawTextAndIcon(iconIndex, text, x, y, w, h) { const iconW = ImageManager.iconWidth + 4; this.#drawIcon(iconIndex, x, y); this.bitmap.drawText( this.#convertEscapeCharacters(text), x + iconW, y, w - iconW, h ); } #drawIcon = function (iconIndex, x, y, w, h) { const bitmap = ImageManager.loadSystem("IconSet"); const pw = ImageManager.iconWidth; const ph = ImageManager.iconHeight; const sx = (iconIndex % 16) * pw; const sy = Math.floor(iconIndex / 16) * ph; const dw = w || 24; const dh = h || 24; this.bitmap.blt(bitmap, sx, sy, pw, ph, x, y, dw, dh); }; #convertEscapeCharacters = function (text) { /* eslint no-control-regex: 0 */ text = text.replace(/\\/g, "\x1b"); text = text.replace(/\x1b\x1b/g, "\\"); text = text.replace(/\x1bV\[(\d+)\]/gi, (_, p1) => $gameVariables.value(parseInt(p1)) ); text = text.replace(/\x1bN\[(\d+)\]/gi, (_, p1) => this.actorName(parseInt(p1)) ); text = text.replace(/\x1bP\[(\d+)\]/gi, (_, p1) => this.partyMemberName(parseInt(p1)) ); text = text.replace(/\x1bG/gi, TextManager.currencyUnit); return text; }; } /* ============================================================================= 以下、バトル中にヘルプ画像表示するための対応 ============================================================================= */ const PartyCommandSymbol_Help = "battle_help"; const _Scene_Battle_prototype_createAllWindows = Scene_Battle.prototype.createAllWindows; Scene_Battle.prototype.createAllWindows = function () { _Scene_Battle_prototype_createAllWindows.call(this); this.createHelpImageCommandWindow(); }; const _Scene_Battle_prototype_createPartyCommandWindow = Scene_Battle.prototype.createPartyCommandWindow; Scene_Battle.prototype.createPartyCommandWindow = function () { _Scene_Battle_prototype_createPartyCommandWindow.call(this); const commandWindow = this._partyCommandWindow; commandWindow.setHandler( PartyCommandSymbol_Help, this.commandHelp.bind(this) ); }; Scene_Battle.prototype.createHelpImageCommandWindow = function () { // const windowWidth = 300; const windowHeight = 96; const rect = new Rectangle( this._partyCommandWindow.x + this._partyCommandWindow.width, this._partyCommandWindow.y + this._partyCommandWindow.height / 2, windowWidth, windowHeight ); const helpImageWindow = new Window_HelpImageCommand(rect); helpImageWindow.deselect(); helpImageWindow.setHandler( Window_HelpImageCommand.CloseCommandSymbol, this.helpCancel.bind(this) ); this.addWindow(helpImageWindow); this._helpImageCommandWindow = helpImageWindow; }; Scene_Battle.prototype.commandHelp = function () { this._helpImageCommandWindow.openHelp(this._helpSprite); }; Scene_Battle.prototype.helpCancel = function () { this._helpImageCommandWindow.closeHelp(); this._partyCommandWindow.refresh(); this._partyCommandWindow.activate(); }; const _Window_PartyCommand_makeCommandList = Window_PartyCommand.prototype.makeCommandList; Window_PartyCommand.prototype.makeCommandList = function () { _Window_PartyCommand_makeCommandList.apply(this, arguments); if (needShowHelp) { this.addCommand( pluginParam.helpCommandName, PartyCommandSymbol_Help, true ); } }; class Window_HelpImageCommand extends Window_HorzCommand { static CloseCommandSymbol = "cancel"; static PrevCommandSymbol = "prev_page"; static NextCommandSymbol = "next_page"; _helpSprite = null; _helpIndex = 0; constructor(rect) { super(rect); this.setHandler( Window_HelpImageCommand.PrevCommandSymbol, (() => { this.#changePrevPage(); }).bind(this) ); this.setHandler( Window_HelpImageCommand.NextCommandSymbol, (() => { this.#changeNextPage(); }).bind(this) ); this.openness = 0; } openHelp(helpSprite) { this._helpIndex = 0; this._helpSprite = helpSprite; this.refresh(); this.activate(); this.open(); } closeHelp() { if (this._helpSprite) { this._helpSprite.hide(); this._helpSprite = null; } this.close(); this.deactivate(); } refresh() { super.refresh(); if (this._helpSprite) { const imagePath = pluginParam.helpImages[this._helpIndex]; const paths = imagePath.split("/"); const fileName = paths.pop(); const dirName = paths.join("/"); const helpTex = ImageManager.loadBitmap(dirName + "/", fileName); this._helpSprite.bitmap = helpTex; helpTex.addLoadListener(() => { this._helpSprite.anchor.x = 0.5; this._helpSprite.anchor.y = 0.5; this._helpSprite.width = helpTex.width; this._helpSprite.height = helpTex.height; this._helpSprite.x = Graphics.width / 2; this._helpSprite.y = Graphics.height / 2; this._helpSprite.show(); }, this); } } #changeNextPage() { this._helpIndex = Math.max( this._helpIndex + 1, pluginParam.helpImages.length - 1 ); this.refresh(); this.activate(); } #changePrevPage() { this._helpIndex = Math.max(this._helpIndex - 1, 0); this.refresh(); this.activate(); } maxCols() { return 3; } makeCommandList() { super.makeCommandList(); if (pluginParam.helpImages.length >= 2) { this.addCommand( "←", Window_HelpImageCommand.PrevCommandSymbol, this._helpIndex > 0 ); this.addCommand( "閉じる", Window_HelpImageCommand.CloseCommandSymbol, true ); this.addCommand( "→", Window_HelpImageCommand.NextCommandSymbol, this._helpIndex < pluginParam.helpImages.length - 1 ); } else { this.addCommand( "閉じる", Window_HelpImageCommand.CloseCommandSymbol, true ); } } } })();