prison-brave/js/plugins/LogWindow.js
2025-08-28 10:38:20 -05:00

855 lines
No EOL
29 KiB
JavaScript

//=============================================================================
// RPG Maker MZ - Extra Log Window
//
// Copyright 2024 Panzer-IV. All rights reserved.
// This source code or any portion thereof must not be
// reproduced or used without licensed in any manner whatsoever.
//=============================================================================
/*:
* @target MZ
* @plugindesc ログイベウィンドウプラグイン
* @author 四号戦車
* @base PluginCommonBase
* @base TextResource
* @orderAfter PluginCommonBase
* @orderAfter DebugSwitch
* @orderAfter TextResource
*
* @param logWaitVariable
* @text ログ更新速度変数
* @desc ログの更新速度を変数に紐づける場合に指定できます。
* 指定しない場合でも速度はコマンドで変更可能です。
* @type variable
* @default 0
*
* @param windows
* @text ウィンドウ
* @desc 表示するウィンドウの設定
* @type struct<WindowData>[]
* @default []
*
* @command showWindow
* @text ウィンドウの表示
* @desc ウィンドウの表示をします
*
* @arg windowName
* @text 対象ウィンドウの名前
* @desc 表示対象のウィンドウを指定します。
* 指定しない場合現在のシーンの全てのウィンドウを表示します
* @type string
* @default
*
* @command hideWindow
* @text ウィンドウの消去
* @desc ウィンドウの消去をします
*
* @arg windowName
* @text 対象ウィンドウの名前
* @desc 表示対象のウィンドウを指定します。
* 指定しない場合現在のシーンの全てのウィンドウを消去します
* @type string
* @default
*
* @command addLog
* @text ログの追加
* @desc ログを追加します。対象指定が無い場合共有ログに追加されます。
*
* @arg windowName
* @text 追加対象のウィンドウ名
* @desc 追加対象のウィンドウを指定します。
* 指定しない場合共有ログに追加されます。
* @type string
* @default
*
* @arg label
* @text ログ内容(ラベル)
* @desc 追加するログのリソースラベルを指定します。
* @type string
* @default
*
* @arg text
* @text ログ内容
* @desc 追加するログの内容を指定します。
* @type string
* @default
*
* @arg icon
* @text アイコン
* @desc 追加するログに表示するアイコンを指定します。
* @dir img/pictures
* @type file
* @default
*
* @command clearLog
* @text ログのクリア
* @desc ログをクリアします。対象指定が無い場合共有ログがクリアされます。
*
* @arg windowName
* @text 対象のウィンドウ名
* @desc 対象のウィンドウを指定します。
* 指定しない場合共有ログがクリアされます。
* @type string
* @default
*
* @command setLogWaitSpeed
* @text ログ更新速度設定
* @desc ログ表示時間を設定します。値を大きくすると早く消えます。
* 変数を紐づけている場合変数も更新されます。
*
* @arg speed
* @text ログ更新速度
* @desc ログの更新速度です。値を大きくすると早く消えます。
* @type number
* @min 1
* @max 60
* @default 10
*
*
* @help
*
* LogWindow
* Version: 0.0.3
*
* シーンにログ用のウィンドウを追加します。
*
*/
/*~struct~WindowData:
*
* @param name
* @text ウィンドウの名前
* @desc ウィンドウの名前です。個別に表示制御をしたいときの制御等に使用します。
* 指定しない場合、[シーン名_連番]になります。
*
* @param targetScene
* @text 対象シーン
* @desc 追加対象のシーンです。オリジナルのシーンを対象にする場合はシーンクラス名を直接記入します。
* @type select
* @default Scene_Title
* @option タイトル
* @value Scene_Title
* @option マップ
* @value Scene_Map
* @option ゲームオーバー
* @value Scene_Gameover
* @option バトル
* @value Scene_Battle
* @option メインメニュー
* @value Scene_Menu
* @option アイテム
* @value Scene_Item
* @option スキル
* @value Scene_Skill
* @option 装備
* @value Scene_Equip
* @option ステータス
* @value Scene_Status
* @option オプション
* @value Scene_Options
* @option セーブ
* @value Scene_Save
* @option ロード
* @value Scene_Load
* @option ゲーム終了
* @value Scene_End
* @option ショップ
* @value Scene_Shop
* @option 名前入力
* @value Scene_Name
* @option デバッグ
* @value Scene_Debug
*
* @param logLineCount
* @text 一度に表示できるログの数
* @desc 一度に表示可能なログの最大行です。
* これ以上のログは自動的に削除されます。
* ウィンドウの高さはこれにより自動決定されます。
* @type number
* @default 20
* @min 1
*
* @param windowWidth
* @text ログウィンドウの幅
* @type number
* @default 300
* @min 1
*
* @param horizontalPos
* @text 横位置
* @desc ウィンドウの横位置です。
* @type select
* @default left
* @option 左
* @value left
* @option 中央
* @value center
* @option 右
* @value right
*
* @param virticalPos
* @text 縦位置
* @desc ウィンドウの縦位置です。
* @type select
* @default top
* @option 上
* @value top
* @option 中央
* @value center
* @option 下
* @value bottom
*
* @param offsetX
* @text 横位置オフセット
* @desc ウィンドウの横位置オフセットです。
* ウィンドウ位置を微調整したいときのみ使用して下さい。
* @type number
* @default 0
*
* @param offsetY
* @text 縦位置オフセット
* @desc ウィンドウの縦位置オフセットです。
* ウィンドウ位置を微調整したいときのみ使用して下さい。
* @type number
* @default 0
*
* @param backgroundColor
* @text ログの背景色
* @desc 背景の色と透明度をRGBAのCSSカラーで指定します。
* デフォルトは透明です。
* @type string
* @default #00000000
*
* @param font
* @text フォント
* @desc 表示する文字のフォントです。
* 指定しない場合システムの標準フォントを使用します。
* @type file
* @default
* @dir fonts
*
* @param fontSize
* @text 文字サイズ
* @desc ウィンドウに表示する文字のサイズです。
* @type number
* @default 18
* @min 8
* @max 128
*
* @param fontStyle
* @text 文字修飾
* @desc 表示する文字の修飾です。
* @type select
* @option 通常
* @value normal
* @oprion 太字
* @value bold
* @option 斜体
* @value italic
*
*/
(() => {
"use strict";
const pluginName = "LogWindow";
const script = document.currentScript;
const param = PluginManagerEx.createParameter(script);
/********************************************************************************************************
* 共有基盤
********************************************************************************************************/
const log = (text) => {
if (DebugSwitch.isEnable()) {
console.log(text);
}
};
//========================================================================
// 各シーンでのローディング用
//========================================================================
const _Scene_Base_create = Scene_Base.prototype.create;
Scene_Base.prototype.create = function() {
_Scene_Base_create.apply(this, arguments);
const currentSceneName = PluginManagerEx.findClassName(this);
if (!!$gameTemp && !!$gameTemp.logWindowController) {
$gameTemp.logWindowController.buildWindows(currentSceneName);
}
};
const _Scene_Base_start = Scene_Base.prototype.start;
Scene_Base.prototype.start = function() {
_Scene_Base_start.apply(this, arguments);
if (!!$gameTemp && !!$gameTemp.logWindowController) {
const sceneName = PluginManagerEx.findClassName(this);
Logger.d(`[log] add window: scene = ${sceneName}, windows = ${$gameTemp.logWindowController.getWindows(sceneName).length}`);
$gameTemp.logWindowController.getWindows(sceneName).forEach(window => {
Logger.d(`[log] add window: name = ${window.name()}`);
this.addChild(window);
});
}
};
const _Scene_Base_prototype_stop = Scene_Base.prototype.stop;
Scene_Base.prototype.stop = function() {
if (!!$gameTemp && !!$gameTemp.logWindowController) {
const sceneName = PluginManagerEx.findClassName(this);
$gameTemp.logWindowController.getWindows(sceneName).forEach(window => {
Logger.d(`[log] remove window: name = ${window.name()}`);
this.removeChild(window);
});
}
_Scene_Base_prototype_stop.apply(this, arguments);
};
//========================================================================
// ログウィンドウ管理クラスの準備
//========================================================================
const _Game_Temp_initialize = Game_Temp.prototype.initialize;
Game_Temp.prototype.initialize = function() {
_Game_Temp_initialize.apply(this, arguments);
this.logWindowController = new LogWindowController();
};
const _Game_System_onBeforeSave = Game_System.prototype.onBeforeSave;
Game_System.prototype.onBeforeSave = function() {
_Game_System_onBeforeSave.apply(this, arguments);
this._saveLogData = $gameTemp.logWindowController.buildSaveData();
};
const _Game_System_onAfterLoad = Game_System.prototype.onAfterLoad;
Game_System.prototype.onAfterLoad = function() {
_Game_System_onAfterLoad.apply(this, arguments);
$gameTemp.logWindowController.applySaveData(this._saveLogData);
};
//========================================================================
// ログウィンドウ管理クラス
//========================================================================
class LogWindowController {
constructor() {
this._commonLogLines = [];
this._windows = {};
this._logWaitSpeed = 5;
this.visible = {};
this.windowItems = {};
this._scenes = {};
}
buildSaveData() {
return {
commonLogLines: this._commonLogLines,
logWaitSpeed: this._logWaitSpeed,
visible: this.visible,
windowItems: this.windowItems,
};
}
applySaveData(data) {
this._commonLogLines = data.commonLogLines;
this._logWaitSpeed = data.logWaitSpeed;
this.visible = data.visible;
this.windowItems = data.windowItems;
}
setLogWaitSpeed(value) {
if (param.logWaitVariable) {
$gameVariables.setValue(param.logWaitVariable, value);
}
this._logWaitSpeed = value;
}
getWaitSpeed() {
if (param.logWaitVariable) {
this._logWaitSpeed = $gameVariables.value().clamp(1, 60);
}
const multi = (ConfigManager.getItemValue?.('logSpeed') ?? 100) / 100;
return Math.trunc(this._logWaitSpeed * 60 * multi);
}
getWindows(sceneName) {
if (!!this._scenes[sceneName]) {
return this._scenes[sceneName];
} else {
return [];
}
}
hasWindow(sceneName, windowName) {
if (!this._scenes[sceneName]) {
return this._scenes[sceneName].some(window => {
return window.name() === windowName;
});
}
return false;
}
buildWindows(sceneName) {
this._scenes[sceneName] = param.windows
.map(p => p._parameter)
.filter(p => p.targetScene === sceneName)
.map(windowParam => {
return this._createWindow(windowParam);
})
.filter(window => !!window);
}
addWindow(sceneName, windowParam) {
if (!!windowParam) {
if (!this._scenes[sceneName]) {
this._scenes[sceneName] = this.buildWindows(sceneName);
}
this._scenes[sceneName].push(this._createWindow(windowParam));
}
}
_createWindow(windowParam) {
if (!!windowParam) {
const count = Object.values(this._windows).filter(window => {
return windowParam.targetScene === window.scene;
}).length;
if (!windowParam.name || windowParam.name.length === 0) {
windowParam.name = windowParam.targetScene + '_' + count;
}
const window = new Window_ExtraLog(windowParam);
this._windows[windowParam.name] = window;
if (this.visible[windowParam.name] === false) {
this.visible[windowParam.name] = false;
window.hide();
} else {
this.visible[windowParam.name] = true;
window.visible = true;
}
return window;
}
return null;
}
findWindow(windowName) {
return this._windows[windowName];
}
getAllWindow() {
return Object.values(this._windows);
}
addLog(label, icon, targetWindow) {
this.addLogByText(TextResource.format(label), icon, targetWindow);
}
addLogByText(text, icon, targetWindow) {
if (!!targetWindow && targetWindow.length > 0) {
// ターゲット指定あり
this._windows[targetWindow]?.addLog(text, icon);
} else if (!!SceneManager._scene) {
// 現シーンのすべてのウィンドウに
this.getWindows(SceneManager._scene.constructor.name)
.forEach(window => window.addLog(text, icon));
}
}
clearLog(targetWindow) {
if (targetWindow && targetWindow.length > 0) {
// ターゲット指定あり
this._windows[targetWindow]?.clearLog();
} else {
// ターゲット指定無し
this._windows.forEach(window => window.clearLog());
}
}
getWindowParam(windowName) {
return param.windows.map(p => p._parameter).find(p => p.name === windowName);
}
}
const parseRgbaHexColorCode = (colorText) => {
if (colorText.matchAll('^#[0-9A-F]{6}([0-9A-F]{2})?$')) {
if (colorText.matchAll('^#[0-9A-F]{8}$')) {
// RGBA構文
const color = colorText.substring(0, 7);
const opacity = parseInt(colorText.substring(7), 16)
return {
color: color,
opacity: opacity,
}
} else {
// RGB構文
return {
color: colorText,
opacity: 255,
};
}
}
};
//========================================================================
// ログウィンドウクラス
//========================================================================
class Window_ExtraLog extends Window_Selectable {
static LINE_HEIGHT = 44;
static DesideWindowRect = (parameter) => {
const lineHeight = Window_ExtraLog.LINE_HEIGHT;
const width = parameter.windowWidth;
const height = (lineHeight * (parameter.logLineCount + 1));
let x = 0;
switch (parameter.horizontalPos) {
case 'left':
x = -8;
break;
case 'center':
x = (Graphics.boxWidth - width) / 2;
break;
case 'right':
x = (Graphics.boxWidth - width) + 8;
break;
} // switch
x += parameter.offsetX;
let y = 0;
switch (parameter.verticalPos) {
case 'top':
y = -8;
break;
case 'center':
y = (Graphics.boxHeight - height) / 2;
break;
case 'bottom':
x = Graphics.boxHeight - height + 8;
break;
} // switch
y += parameter.offsetY;
return new Rectangle(x, y, width, height);
}
//========================================================================
// コンストラクタ
//========================================================================
constructor(parameter) {
super(Window_ExtraLog.DesideWindowRect(parameter));
const rect = Window_ExtraLog.DesideWindowRect(parameter);
if (!$gameTemp.logWindowController.windowItems[parameter.name]) {
$gameTemp.logWindowController.windowItems[parameter.name] = [];
}
this.opacity = 0;
this.param = parameter;
this._topIndex = 0;
this._padding = 0;
this._state = 'neutral';
this._offset = 0;
this._logQueue = [];
this._items = [];
this._frameCount = 0;
this.contents = new Bitmap(this.contentsWidth(), this.contentsHeight());
this.contentsBack = new Bitmap(rect.width, rect.height);
this.zIndex = 50;
}
//========================================================================
// 追加メソッド
//========================================================================
name() {
return this.param.name;
}
scene() {
return this.param.targetScene;
}
lineUpdateDuration() {
return 15;
}
needsUpdate() {
if (this._logQueue.length > 0) {
return true;
}
return false;
}
addLog(text, icon) {
log(`Log Added: text = ${text}`);
const postLog = (iconPicture, line) => {
this._logQueue.push({
text: line,
opacity: 255,
state: 'add',
frame: 0,
waitFrame: 0,
offsetX: 0,
offsetY: 0,
icon: iconPicture,
});
};
if (!!icon && icon.length > 0) {
ImageManager.loadPictureAsync(icon).then(picture => {
text.split('\n').reverse().forEach(line => {
postLog(picture, line);
})
});
} else {
text.split('\n').reverse().forEach(line => {
postLog(null, line);
})
}
}
clearLog() {
this._items.forEach(item => {
item.state = 'remove';
});
}
//========================================================================
// 親クラスの処理変更・抑止したい系
//========================================================================
setCursorRect(x, y, width, height) {
// nothing to do
}
processOk() {
// nothing to do
}
maxItems() {
return this.param.logLineCount + 1;
}
drawItemBackground(index) {
const backgroundColor = this.param.backgroundColor;
const bitmap = this.contentsBack;
const item = this.item(index);
const rect = this.itemRect(index);
const colorCode = parseRgbaHexColorCode(backgroundColor);
const itemOpacity = !!item ? item.opacity : 0;
bitmap.paintOpacity = Math.min(colorCode.opacity, itemOpacity);
bitmap.fillRect(rect.x, rect.y, rect.width, rect.height, colorCode.color);
bitmap.paintOpacity = 255;
}
itemRect(index) {
const rect = super.itemRect(index);
const item = this.item(index);
if (!!item) {
rect.x += item.offsetX;
rect.y += item.offsetY;
}
return rect;
}
drawItem(index) {
const item = this.item(index);
if (!!item) {
const rect = this.itemRect(index);
const textMergin = 22;
const bitmap = new Bitmap(rect.width - textMergin, rect.height);
bitmap.fontSize = this.param.fontSize;
this.contents.paintOpacity = item.opacity;
if (!!item.icon) {
const iw = item.icon.width;
const ih = item.icon.height;
const isize = rect.height - 4;
bitmap.blt(item.icon, 0, 0, iw, ih, 0, 2, isize, isize);
const textSx = isize + 4;
bitmap.drawText(item.text, textSx, 0, rect.width - textMergin - textSx, rect.height, 'left');
} else {
bitmap.drawText(item.text, 0, 0, rect.width - textMergin, rect.height, 'left');
}
this.contents.blt(bitmap, 0, 0, rect.width, rect.height, rect.x + textMergin, rect.y - this._offset);
bitmap.destroy();
}
}
isCursorMovable() {
return false;
}
rowSpacing() {
return 6;
}
lineHeight() {
return Window_ExtraLog.LINE_HEIGHT;
}
item(index) {
if (index < 0 || index >= this._items.length) {
return null;
}
return this._items[index];
}
itemPadding() {
return 0;
}
updatePadding() {
// nothing to do
}
//========================================================================
// フレーム毎更新処理
//========================================================================
update() {
// if (!this.needsUpdate()) {
// return;
// }
const lineHeight = this.lineHeight();
const duration = this.lineUpdateDuration();
const maxCount = this.param.logLineCount;
const itemWidth = this.itemWidth();
const eraceDuration = Math.max($gameTemp.logWindowController.getWaitSpeed(), 5);
if (this._state === 'neutral') {
if (this._logQueue.length > 0) {
const firstItem = this._logQueue.shift();
this._items.splice(0, 0, firstItem);
this._state = 'ensure';
}
}
this._items.forEach((item, index) => {
if (item.state === 'add') {
if (this._state === 'ensure') {
item.offsetX = ((itemWidth / duration) * item.frame) - itemWidth;
if (item.frame > duration) {
item.offsetX = 0;
item.state = 'active';
item.waitFrame = 0;
this._state = 'neutral';
} else {
item.frame++;
}
}
} else if (item.state === 'active' || item.state === 'remove') {
if (this._state === 'ensure') {
if (item.offsetY >= 0) {
item.offsetY -= lineHeight;
}
item.offsetY += (lineHeight / duration);
if (item.frame > duration) {
item.offsetY = 0;
} else {
item.frame++;
}
} else if (this._state === 'neutral' && item.offsetY < 0) {
item.offsetY = 0;
}
if (item.state === 'active') {
item.waitFrame++;
if (item.waitFrame >= eraceDuration) {
item.state = 'remove';
item.waitFrame = 0;
item.frame = 0;
}
if (index >= maxCount) {
item.state = 'remove';
item.frame = 0;
}
} else {
item.offsetY += (lineHeight / duration);
item.opacity -= (256 / duration);
if (item.opacity < 0) {
item.opacity = 0;
}
if (item.frame > duration) {
item.opacity = 0;
item.state = 'removed';
} else {
item.frame++ ;
}
}
}
});
this.refresh();
this._items = this._items.filter(item => (item.state !== 'removed'));
}
paint() {
if (this.contents) {
this.contents.clear();
this.contentsBack.clear();
this.drawAllItems();
}
}
}
//========================================================================
// プラグインコマンド
//========================================================================
// ウィンドウ名からウィンドウを検索
const findWindow = (target, callback) => {
if ($gameTemp.logWindowController) {
if (target && target.length > 0) {
const window = $gameTemp.logWindowController.findWindow(target);
if (window) {
if (callback && typeof callback === 'function') {
callback(window);
}
}
} else {
$gameTemp.logWindowController.getAllWindow().forEach(window => {
if (callback && typeof callback === 'function') {
callback(window);
}
});
}
}
};
PluginManagerEx.registerCommand(script, 'showWindow', args => {
findWindow(args.windowName, window => {
window.show();
$gameTemp.logWindowController.visible[args.windowName] = true;
});
});
PluginManagerEx.registerCommand(script, 'hideWindow', args => {
findWindow(args.windowName, window => {
window.hide();
$gameTemp.logWindowController.visible[args.windowName] = false;
});
});
PluginManagerEx.registerCommand(script, 'addLog', args => {
if ($gameTemp.logWindowController) {
if (!!args.label && args.label !== '') {
$gameTemp.logWindowController.addLog(args.label, args.icon, args.windowName);
} else {
$gameTemp.logWindowController.addLogByText(args.text, args.icon, args.windowName);
}
}
});
PluginManagerEx.registerCommand(script, 'clearLog', args => {
findWindow(args.windowName, window => {
$gameTemp.logWindowController.clearLog(window);
});
});
PluginManagerEx.registerCommand(script, 'setLogWaitSpeed', args => {
if ($gameTemp.logWindowController) {
$gameTemp.logWindowController.setLogWaitSpeed(args.speed);
}
});
})();