459 lines
14 KiB
JavaScript
459 lines
14 KiB
JavaScript
//=============================================================================
|
||
// 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
|
||
* ■ マウスホバーでのバルーン表示
|
||
* ステートのメモ欄に
|
||
* <ShowHoverState:バルーン上で表示する文字列>
|
||
* と記載したステートのアイコンにマウスカーソルを合わせると説明文が表示されます。
|
||
*
|
||
* ■ ヘルプ画像表示コマンドの追加(パーティコマンド)
|
||
* プラグインパラメータの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
|
||
);
|
||
}
|
||
}
|
||
}
|
||
})();
|