crypts-tear/www/js/plugins/KMS_QuickNotification.js
2023-07-07 20:03:33 -05:00

945 lines
28 KiB
JavaScript

//=============================================================================
// KMS_QuickNotification.js
// Last update: 2020/02/01
//=============================================================================
/*:
* @plugindesc
* [v0.1.1] Show popup notification.
*
* @author Kameo (Kamesoft)
*
* @param Display duration
* @default 300
* @desc Display frame count.
*
* @param Display position
* @default 0
* @desc
* Notification position.
* 0: TopLeft 1: TopRight 2: BottomLeft 3: BottomRight
*
* @param Face image size
* @default 64
* @desc Face image size in notification by pixel.
*
* @param Font size
* @default 18
* @desc Font size for notification.
*
* @param Max notification count
* @default 4
* @desc Max number of notifications which can be displayed together.
*
* @param Notification SE
* @default Saint5
* @require 1
* @dir audio/se/
* @type file
* @desc SE for showing notification.
*
* @param Notification SE param
* @default 100, 50
* @desc
* Format: Volume, Pitch, Pan
* Parameters for notification SE.
*
* @param Notification skin
* @default QuickNotificationSkin
* @require 1
* @dir img/system/
* @type file
* @desc Skin for notifications. Load from img/system.
*
*
* @help
*
* ## Plugin command
*
* QuickNotification enable # Enable quick notification
* QuickNotification disable # Display quick notification
* QuickNotification register # Register quick notification by the next message event
*
*/
/*:ja
* @plugindesc
* [v0.1.1] ポップアップ通知を表示する機能を追加します。
*
* @author かめお (Kamesoft)
*
* @param Display duration
* @default 180
* @desc 通知表示時間をフレーム単位で指定します。
*
* @param Display position
* @default 0
* @desc
* 通知を表示する位置です。
* 0: 左上 1: 右上 2: 左下 3: 右下
*
* @param Face image size
* @default 64
* @desc 通知用顔グラフィックのサイズをピクセル単位で指定します。
*
* @param Font size
* @default 18
* @desc 通知のフォントサイズを指定します。
*
* @param Max notification count
* @default 4
* @desc 同時に表示できる通知の最大数です。
*
* @param Notification SE
* @default Saint5
* @require 1
* @dir audio/se/
* @type file
* @desc 通知表示時に演奏する SE のファイル名です。 audio/se から読み込みます。
*
* @param Notification SE param
* @default 100, 50
* @desc
* 書式: ボリューム, ピッチ, パン
* 通知表示時に演奏する SE のパラメータです。ピッチ以降省略可。
*
* @param Notification skin
* @default QuickNotificationSkin
* @require 1
* @dir img/system/
* @type file
* @desc 通知の表示に使用するスキン画像です。 img/system から読み込みます。
*
*
* @help
*
* ■ プラグインコマンド
*
* QuickNotification enable # 通知を有効にします。
* QuickNotification disable # 通知を無効にします。
* QuickNotification register # 次の「文章の表示」コマンドで通知を登録します。
*
*/
var KMS = KMS || {};
(function () {
'use strict';
// 定数
var Const =
{
debug: false, // デバッグモード
pluginCode: 'QuickNotification', // プラグインコード
// 表示位置
displayPosition:
{
topLeft: 0, // 左上
topRight: 1, // 右上
bottomLeft: 2, // 左下
bottomRight: 3 // 右下
}
};
var PluginName = 'KMS_' + Const.pluginCode;
KMS.imported = KMS.imported || {};
KMS.imported[Const.pluginCode] = true;
// デフォルト値つきで文字列から int を解析
function parseIntWithDefault(param, defaultValue) {
var value = parseInt(param);
return isNaN(value) ? defaultValue : value;
}
var pluginParams = PluginManager.parameters(PluginName);
var Params = {};
Params.displayDuration = Math.max(parseIntWithDefault(pluginParams['Display duration'], 300), 1);
Params.displayGap = parseIntWithDefault(pluginParams['Display gap'], 16);
Params.displayOffsetX = parseIntWithDefault(pluginParams['Display offset X'], -2);
Params.displayOffsetY = parseIntWithDefault(pluginParams['Display offset Y'], 32);
Params.faceImageSize = Math.max(parseIntWithDefault(pluginParams['Face image size'], 64), 1);
Params.fadeFrameCount = Math.max(parseIntWithDefault(pluginParams['Fade frame count'], 8), 1);
Params.fontSize = Math.max(parseIntWithDefault(pluginParams['Font size'], 18), 1);
Params.notificationCountMax = Math.max(parseIntWithDefault(pluginParams['Max notification count'], 4), 1);
Params.notificationSkin = pluginParams['Notification skin'] || 'QuickNotificationSkin';
Params.displayPosition =
Math.min(
Math.max(
parseIntWithDefault(pluginParams['Display position'], Const.displayPosition.topLeft),
Const.displayPosition.topLeft),
Const.displayPosition.bottomRight
);
// 通知 SE の解析
(function () {
Params.notificationSe = null;
var paramFile = pluginParams['Notification SE'];
if (!paramFile) {
return;
}
var paramFileArgs = paramFile.replace(/\s+/g, '').split(/,/);
if (paramFileArgs.length <= 0) {
return;
}
Params.notificationSe = { name: paramFileArgs[0] };
var paramArgs = { volume: 90, pitch: 100, pan: 0 };
var paramDetail = pluginParams['Notification SE param'];
if (paramDetail != null) {
var paramDetailArgs = paramDetail.replace(/\s+/g, '').split(/,/);
paramArgs.volume = Number(paramDetailArgs[0]) || paramArgs.volume;
paramArgs.pitch = Number(paramDetailArgs[1]) || paramArgs.pitch;
paramArgs.pan = Number(paramDetailArgs[2]) || paramArgs.pan;
}
Params.notificationSe.volume = Number(paramArgs.volume) || 90;
Params.notificationSe.pitch = Number(paramArgs.pitch) || 100;
Params.notificationSe.pan = Number(paramArgs.pan) || 0;
})();
// デバッグログ
var debuglog;
if (Const.debug) {
debuglog = function () { console.log(arguments); }
}
else {
debuglog = function () { }
}
var _Game_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand;
Game_Interpreter.prototype.pluginCommand = function (command, args) {
_Game_Interpreter_pluginCommand.call(this, command, args);
if (command !== Const.pluginCode) {
return;
}
switch (args[0]) {
case 'enable': // 有効化
$gameSystem.setQuickNotificationEnabled(true);
break;
case 'disable': // 無効化
$gameSystem.setQuickNotificationEnabled(false);
break;
case 'register': // 通知の登録開始
$gameTemp.setQuickNotificationRegistrationMode(true);
break;
default:
// 不明なコマンド
console.error('[%s %s] Unknown command.', Const.pluginCode, args[0]);
break;
}
};
//-----------------------------------------------------------------------------
// Game_Temp
var _Game_Temp_initialize = Game_Temp.prototype.initialize;
Game_Temp.prototype.initialize = function () {
_Game_Temp_initialize.call(this);
this.setQuickNotificationRegistrationMode(false);
this.clearQuickNotification();
};
/**
* 通知登録モードの切り替え
*/
Game_Temp.prototype.setQuickNotificationRegistrationMode = function (flag) {
this._isQuickNotificationRegistering = !!flag;
};
/**
* 通知登録モードに設定されているか
*/
Game_Temp.prototype.isQuickNotificationRegistrationMode = function () {
return this._isQuickNotificationRegistering;
};
/**
* 通知の登録
*/
Game_Temp.prototype.registerQuickNotification = function (notification) {
this._quickNotifications.push(notification);
};
/**
* 通知のクリア
*/
Game_Temp.prototype.clearQuickNotification = function () {
this._quickNotifications = [];
};
/**
* 次の通知を取得
*/
Game_Temp.prototype.retrieveNextQuickNotification = function () {
return this._quickNotifications.shift();
};
/**
* 通知が登録されているか
*/
Game_Temp.prototype.isQuickNotificationReady = function () {
return this._quickNotifications.length > 0;
};
//-----------------------------------------------------------------------------
// Game_System
/**
* 通知の有効状態を取得
*/
Game_System.prototype.isQuickNotificationEnabled = function () {
return this._quickNotificationEnabled != null ? this._quickNotificationEnabled : true;
};
/**
* 通知の有効状態を設定
*/
Game_System.prototype.setQuickNotificationEnabled = function (enabled) {
this._quickNotificationEnabled = !!enabled;
};
//-----------------------------------------------------------------------------
// Game_Interpreter
var _Game_Interpreter_command101 = Game_Interpreter.prototype.command101;
Game_Interpreter.prototype.command101 = function () {
if (!$gameMessage.isBusy() &&
$gameTemp.isQuickNotificationRegistrationMode()) {
$gameTemp.setQuickNotificationRegistrationMode(false);
var notification =
{
text: '',
faceName: this._params[0],
faceIndex: this._params[1]
};
//$gameMessage.setBackground(this._params[2]);
//$gameMessage.setPositionType(this._params[3]);
// テキストを改行区切りで連結
while (this.nextEventCode() === 401) {
if (notification.text.length > 0) {
notification.text += '\n';
}
this._index++;
notification.text += this.currentCommand().parameters[0];
}
// 通知の登録
$gameTemp.registerQuickNotification(notification);
// 次のコマンドへ
this._index++;
return false;
}
return _Game_Interpreter_command101.call(this);
};
//-----------------------------------------------------------------------------
// Window_QuickNotification
//
// 通知を表示するウィンドウ
function Window_QuickNotification() {
this.initialize.apply(this, arguments);
}
Window_QuickNotification.prototype = Object.create(Window_Base.prototype);
Window_QuickNotification.prototype.constructor = Window_QuickNotification;
// メッセージの一時描画バッファ
Window_QuickNotification.tempMessageBuffer = null;
/**
* 通知用スキンの事前読み込み
*/
Window_QuickNotification.preloadWindowskin = function () {
ImageManager.loadSystem(Params.notificationSkin);
};
Window_QuickNotification.prototype.initialize = function () {
// コンテンツサイズは内容に応じて可変なので、ウィンドウサイズは仮
Window_Base.prototype.initialize.call(this, 0, 0, 64, 64);
if (Window_QuickNotification.tempMessageBuffer == null) {
Window_QuickNotification.tempMessageBuffer =
new Bitmap(Graphics.boxWidth, Graphics.boxHeight / 2);
}
this.opacity = 0;
this.contentsOpacity = 0;
this.initMembers();
};
Window_QuickNotification.prototype.standardFontSize = function () {
return Params.fontSize;
};
Window_QuickNotification.prototype.loadWindowskin = function () {
// デフォルトのスキンの代わりに、通知用のスキンを読み込む
this._windowskin = ImageManager.loadSystem(Params.notificationSkin);
};
Window_QuickNotification.prototype.initMembers = function () {
this._textState = null;
this._allTextWidth = 0;
this._allTextHeight = 0;
this._duration = 0;
this._destinationY = -1;
this._faceName = '';
this._faceIndex = 0;
this._faceBitmap = null;
};
/**
* 目標 Y 座標を設定
*/
Window_QuickNotification.prototype.setDestinationY = function (y) {
// 最初は目標位置にそのまま表示する
if (this._destinationY < 0) {
this.y = y;
}
this._destinationY = y;
};
Window_QuickNotification.prototype.hide = function () {
this._duration = 0;
Window_Base.prototype.hide.call(this);
};
/**
* フェードアウト
*/
Window_QuickNotification.prototype.fadeOut = function () {
if (this.isFadingOut()) {
return;
}
if (this.isFadingIn()) {
// フェードイン中は同じ不透明度からフェードアウトする
this._duration = Params.displayDuration - this._duration;
}
else {
this._duration = Params.fadeFrameCount;
}
};
Window_QuickNotification.prototype.numVisibleRows = function () {
return 4;
};
/**
* 通知を表示中か
*/
Window_QuickNotification.prototype.isDisplaying = function () {
return this._duration > 0;
};
/**
* フェードイン中か
*/
Window_QuickNotification.prototype.isFadingIn = function () {
return this._duration >= Params.displayDuration - Params.fadeFrameCount;
};
/**
* フェードアウト中か
*/
Window_QuickNotification.prototype.isFadingOut = function () {
return this._duration < Params.fadeFrameCount;
};
/**
* 顔グラフィックのロード中処理
*/
Window_QuickNotification.prototype.updateLoading = function () {
if (this._faceBitmap == null) {
return false;
}
if (ImageManager.isReady()) {
this.drawMessageFace();
this._faceBitmap = null;
return false;
}
else {
return true;
}
};
/**
* 目標不透明度の取得
*/
Window_QuickNotification.prototype.getTargetOpacity = function () {
if (this.isFadingIn()) {
// フェードイン
return 255 * (Params.displayDuration - this._duration) / Params.fadeFrameCount;
}
else if (this.isFadingOut()) {
// フェードアウト
return 255 * this._duration / Params.fadeFrameCount;
}
else {
return 255;
}
};
/**
* 通知の表示開始
*/
Window_QuickNotification.prototype.display = function (notification) {
this.initMembers();
if (notification == null ||
notification.text == null) {
this.hide();
return;
}
// 表示パラメータの設定
this._textState =
{
index: 0,
text: this.convertEscapeCharacters(notification.text)
};
this._allTextWidth = 0;
this._duration = Params.displayDuration;
this._faceName = notification.faceName || '';
this._faceIndex = notification.faceIndex;
this._faceBitmap = null;
this.newPage(this._textState);
this.updatePosition();
this.updateOpacity();
this.show();
};
/**
* 新しいページの表示
*/
Window_QuickNotification.prototype.newPage = function (textState) {
// コンテンツを一時領域に書く
this.contents = Window_QuickNotification.tempMessageBuffer;
this.contents.clear();
this.resetFontSettings();
this.loadMessageFace();
textState.x = this.newLineX();
textState.y = 0;
textState.left = this.newLineX();
textState.height = this.calcTextHeight(textState, false);
this._allTextHeight = this.calcTextHeight(textState, true);
if (this._faceName) {
this._allTextHeight = Math.max(this._allTextHeight, Params.faceImageSize);
}
this.updateMessage();
// コンテンツを一時領域から正式に反映
this.createContents();
this.contents.blt(
Window_QuickNotification.tempMessageBuffer,
0, 0, this._allTextWidth, this._allTextHeight,
0, 0, this._allTextWidth, this._allTextHeight
);
};
/**
* 顔グラフィックを読み込む
*/
Window_QuickNotification.prototype.loadMessageFace = function () {
this._faceBitmap = ImageManager.loadFace(this._faceName);
};
/**
* 表示位置を設定
*/
Window_QuickNotification.prototype.updatePosition = function () {
// 不透明度に応じて位置をずらす
var xOffset = Params.displayOffsetX - this.width / 2 * (255 - this.getTargetOpacity()) / 255;
switch (Params.displayPosition) {
case Const.displayPosition.topRight:
case Const.displayPosition.bottomRight:
// 右側
this.x = Graphics.boxWidth - this.width - xOffset;
break;
default:
// 左側
this.x = xOffset;
break;
}
var epsilon = 8;
var diffY = this.y - this._destinationY;
if (Math.abs(diffY) > epsilon) {
// 目標位置とズレている場合はぬるっと移動
if (diffY > 0) {
this.y -= Math.max(diffY / 8, epsilon);
}
else {
this.y -= Math.min(diffY / -8, -epsilon);
}
}
else {
this.y = this._destinationY;
}
};
/**
* ウィンドウのサイズを更新
*/
Window_QuickNotification.prototype.updateWindowSize = function () {
var offset = this.standardPadding() * 2;
this.width = this._allTextWidth + offset;
this.height = this._allTextHeight + offset;
};
/**
* 不透明度の更新
*/
Window_QuickNotification.prototype.updateOpacity = function () {
this.opacity = this.contentsOpacity = this.getTargetOpacity();
};
Window_QuickNotification.prototype.update = function () {
Window_Base.prototype.update.call(this);
if (!this.isDisplaying()) {
return;
}
if (this.updateLoading()) {
// スキンのロード中
return;
}
this._duration--;
this.updateOpacity();
this.updatePosition();
// 表示が終了したら初期化
if (!this.isDisplaying()) {
this.initMembers();
}
};
/**
* メッセージの更新
*/
Window_QuickNotification.prototype.updateMessage = function () {
if (this._textState == null) {
return false;
}
while (!this.isEndOfText(this._textState)) {
this.processCharacter(this._textState);
this._allTextWidth = Math.max(this._allTextWidth, this._textState.x);
}
this.updateWindowSize();
return true;
};
/**
* 顔グラフィックを描画
*/
Window_QuickNotification.prototype.drawMessageFace = function () {
// 指定サイズに拡縮して描画
var size = Params.faceImageSize;
var bitmap = ImageManager.loadFace(this._faceName);
var sw = Window_Base._faceWidth;
var sh = Window_Base._faceHeight;
var sx = this._faceIndex % 4 * sw;
var sy = Math.floor(this._faceIndex / 4) * sh;
this.contents.blt(bitmap, sx, sy, sw, sh, 0, 0, size, size);
};
/**
* 改行時の描画先 X 座標
*/
Window_QuickNotification.prototype.newLineX = function () {
return this._faceName === '' ? 0 : (Params.faceImageSize + 24);
};
/**
* 改行の処理
*/
Window_QuickNotification.prototype.processNewLine = function (textState) {
this._allTextWidth = Math.max(this._allTextWidth, textState.x);
Window_Base.prototype.processNewLine.call(this, textState);
};
/**
* 改ページの処理
*/
Window_QuickNotification.prototype.processNewPage = function (textState) {
Window_Base.prototype.processNewPage.call(this, textState);
if (textState.text[textState.index] === '\n') {
textState.index++;
}
textState.y = this.contents.height;
};
/**
* テキスト終端か
*/
Window_QuickNotification.prototype.isEndOfText = function (textState) {
return textState.index >= textState.text.length;
};
/**
* 改ページが必要か
*/
Window_QuickNotification.prototype.needsNewPage = function (textState) {
return (!this.isEndOfText(textState) &&
textState.y + textState.height > this.contents.height);
};
//-----------------------------------------------------------------------------
// Scene_Base
// シーンをまたいで保持するための通知ウィンドウの一覧
Scene_Base._gQuickNotificationWindows = [];
/**
* 通知ウィンドウの作成
*/
Scene_Base.prototype.createQuickNotificationWindow = function () {
if (this._quickNotificationWindowLayer != null) {
return;
}
var width = Graphics.boxWidth;
var height = Graphics.boxHeight;
var x = (Graphics.width - width) / 2;
var y = (Graphics.height - height) / 2;
this._quickNotificationWindowLayer = new WindowLayer();
this._quickNotificationWindowLayer.move(x, y, width, height);
this.addChild(this._quickNotificationWindowLayer);
Window_QuickNotification.preloadWindowskin();
// 表示終了前の通知があれば表示
Scene_Base._gQuickNotificationWindows.forEach(function (windowObj) {
this._quickNotificationWindowLayer.addChild(windowObj);
}, this);
};
/**
* 通知ウィンドウを表示可能か
*/
Scene_Base.prototype.isQuickNotificationAvailable = function () {
// ゲーム起動時は $gameTemp と $gameSystem が生成されていない可能性があるのでチェック
return this._quickNotificationWindowLayer != null &&
$gameTemp != null &&
$gameSystem != null;
};
var _Scene_Base_update = Scene_Base.prototype.update;
Scene_Base.prototype.update = function () {
_Scene_Base_update.call(this);
this.updateQuickNotificationDisplay();
};
var _Scene_Base_terminate = Scene_Base.prototype.terminate;
Scene_Base.prototype.terminate = function () {
_Scene_Base_terminate.call(this);
this.destroyQuickNotificationWindows();
};
/**
* 通知ウィンドウの破棄
*/
Scene_Base.prototype.destroyQuickNotificationWindows = function () {
if (!this.isQuickNotificationAvailable()) {
return;
}
var layer = this._quickNotificationWindowLayer;
layer.children.forEach(function (child) {
this._quickNotificationWindowLayer.removeChild(child);
}, this);
this.removeChild(layer);
this._quickNotificationWindowLayer = null;
};
/**
* 通知の表示を更新
*/
Scene_Base.prototype.updateQuickNotificationDisplay = function () {
if (!this.isQuickNotificationAvailable()) {
return;
}
if (!$gameSystem.isQuickNotificationEnabled()) {
// 通知を表示できないときは非表示にしつつ各種フラグを解除
$gameTemp.clearQuickNotification();
this._quickNotificationWindowLayer.children.forEach(function (windowObj) {
windowObj.fadeOut();
});
return;
}
this.removeFinishedQuickNotification();
// 最大数まで表示されている場合は追加表示しない
if (this._quickNotificationWindowLayer.children.length >= Params.notificationCountMax) {
return;
}
// 要求されているメッセージを表示できるだけ表示
while ($gameTemp.isQuickNotificationReady()) {
if (!this.displayQuickNotification()) {
break;
}
}
this.updateQuickNotificationPosition();
};
/**
* 表示が終了した通知を削除
*/
Scene_Base.prototype.removeFinishedQuickNotification = function () {
var finishedWindows = this._quickNotificationWindowLayer.children.filter(function (child) {
return !child.isDisplaying();
});
if (finishedWindows.length <= 0) {
// 終了した通知がない
return;
}
finishedWindows.forEach(function (windowObj) {
this._quickNotificationWindowLayer.removeChild(windowObj);
}, this);
Scene_Base._gQuickNotificationWindows =
Scene_Base._gQuickNotificationWindows.filter(function (windowObj) {
return finishedWindows.indexOf(windowObj) < 0;
});
};
/**
* 通知の表示
*/
Scene_Base.prototype.displayQuickNotification = function () {
var notification = $gameTemp.retrieveNextQuickNotification();
if (notification == null) {
return false;
}
// 予約されている通知を表示
var windowObj = new Window_QuickNotification();
windowObj.display(notification);
this._quickNotificationWindowLayer.addChild(windowObj);
Scene_Base._gQuickNotificationWindows.push(windowObj);
// 通知音を再生
if (Params.notificationSe) {
AudioManager.playSe(Params.notificationSe);
}
return true;
};
/**
* 通知の表示位置を更新
*/
Scene_Base.prototype.updateQuickNotificationPosition = function () {
// 表示位置に応じた調整
var isBottom;
var y;
var sign;
switch (Params.displayPosition) {
case Const.displayPosition.bottomLeft:
case Const.displayPosition.bottomRight:
// 下側
isBottom = true;
y = Graphics.boxHeight - Params.displayOffsetY;
sign = -1;
break;
default:
// 上側
isBottom = false;
y = Params.displayOffsetY;
sign = 1;
break;
}
var children = this._quickNotificationWindowLayer.children;
for (var i = 0; i < children.length; i++) {
var windowObj = children[i];
if (isBottom) {
y -= windowObj.height;
}
windowObj.setDestinationY(y);
if (!isBottom) {
y += windowObj.height;
}
y += sign * Params.displayGap;
}
};
//-----------------------------------------------------------------------------
// Scene_Base
var _Scene_Base_start = Scene_Base.prototype.start;
Scene_Base.prototype.start = function () {
_Scene_Base_start.call(this);
// 他のオブジェクトより前面に表示するため、シーン開始時まで生成を遅延
this.createQuickNotificationWindow();
};
//-----------------------------------------------------------------------------
// Scene_Map
var _Scene_Map_snapForBattleBackground = Scene_Map.prototype.snapForBattleBackground;
Scene_Map.prototype.snapForBattleBackground = function () {
// 通知はキャプチャしない
var layer = this._quickNotificationWindowLayer;
if (layer != null) {
layer.visible = false;
}
_Scene_Map_snapForBattleBackground.call(this);
if (layer != null) {
layer.visible = true;
}
};
})();