//=============================================================================
// GF Patch Plugins
// GF_Patch_ItemInfoPatch.js
//=============================================================================
var Imported = Imported || {};
Imported.GF_ItemInfoPatch = true;
var GF = GF || {};
GF.Patch = GF.Patch || {};
GF.Patch.ItemInfoWheelScroll = { version: 1.01 };
//=============================================================================
/*:
* @target MZ
* @plugindesc [v1.01] 修补 - 物品信息窗口增强
* @author 57 & deepseek
*
* @orderAfter GF_3_ItemInfoWindow
* @base GF_3_ItemInfoWindow
*
* @help
* ============================================================================
* 介绍
* ============================================================================
*
* 这是 GF_3_ItemInfoWindow 的修补插件,提供以下增强:
*
* 1. 滚轮穿透:鼠标停留在物品列表上时,滚轮即可滚动信息窗口,
* 无需移动鼠标到信息窗口内部。
*
* 2. 自动换行:当物品描述文字超出窗口宽度时,自动折行显示。
* 同时支持手动换行标记
(不区分大小写)。
*
* 两个功能均仅对"固定窗口"模式(FixWindow = true)生效。
*
* ============================================================================
* 换行说明
* ============================================================================
*
*
手动换行:不区分大小写,始终生效。
* 例如:"攻击力+100
防御力+50" 会显示为两行。
*
* 自动换行(开启后):按窗口内宽自动折行,在中文/英文/数字边界
* 均可断开。转义码(\c[n]、\i[n] 等)不会被截断。
*
* 注意:自动换行按单列宽度计算,在多列布局下文字可能比列宽略窄,
* 但不会溢出。如需多列精确控制,请使用
。
*
* ============================================================================
* 前置需求
* ============================================================================
*
* 必须放在 GF_3_ItemInfoWindow 下方。
*
* ============================================================================
* 更新日志
* ============================================================================
*
* [v1.00] 滚轮穿透功能。
* [v1.01] 新增自动换行/
手动换行功能。
*
* @param EnableWordWrap
* @text 自动换行
* @desc 是否启用自动换行。开启后超出窗口宽度的描述会自动折行。
* @type boolean
* @on 启用
* @off 关闭
* @default false
*
*/
//=============================================================================
if (!Imported.GF_3_ItemInfoWindow) {
alert("错误:未找到前置插件 GF_3_ItemInfoWindow。\n请确保 GF_ItemInfoPatch.js 放在 GF_3_ItemInfoWindow 下方。");
}
//=============================================================================
// Parameter Variables
//=============================================================================
GF.Parameters = PluginManager.parameters('GF_ItemInfoPatch');
GF.Patch.ItemInfoWheelScroll.EnableWordWrap =
eval(GF.Parameters['EnableWordWrap'] || 'false');
//=============================================================================
// Window_ObjInfoFix - 滚轮穿透
//=============================================================================
Window_ObjInfoFix.prototype.processWheelScroll = function () {
if (!this.visible) return;
// 允许两种情况下滚动:
// 1. 鼠标在信息窗口内部(原版行为)
// 2. 鼠标在目标物品列表窗口内部(新增行为)
const inInfoWindow = this.isTouchedInsideFrame();
const inTargetWindow = this._targetWindow &&
this._targetWindow.visible &&
this._targetWindow.isTouchedInsideFrame();
if (!inInfoWindow && !inTargetWindow) return;
const threshold = 20;
if (TouchInput.wheelY >= threshold) {
this.scrollOriginBy(this.scrollSpeed() * 4);
}
if (TouchInput.wheelY <= -threshold) {
this.scrollOriginBy(-this.scrollSpeed() * 4);
}
};
//=============================================================================
// Window_ObjInfoFix - 自动换行 &
支持
//=============================================================================
/**
* 刷新流程中插入换行处理。
* 在 makeObjInfo() 之后、calculateDataLength() 之前运行。
*/
Window_ObjInfoFix.prototype.processWordWrap = function () {
if (!this._data) return;
const maxWidth = this.innerWidth - this.itemPadding() * 2;
if (maxWidth <= 0) return;
// 处理 _data 中的每一组
const keys = this._dataKeys || Object.keys(this._data);
for (let k = 0; k < keys.length; k++) {
const key = keys[k];
const lines = this._data[key];
if (!lines || !lines.length) continue;
const newLines = [];
for (let i = 0; i < lines.length; i++) {
const wrapped = this.wrapLine(lines[i], maxWidth);
for (let j = 0; j < wrapped.length; j++) {
newLines.push(wrapped[j]);
}
}
this._data[key] = newLines;
}
// 处理底部信息 _lastData
if (this._lastData && this._lastData.length) {
const newLast = [];
for (let i = 0; i < this._lastData.length; i++) {
const wrapped = this.wrapLine(this._lastData[i], maxWidth);
for (let j = 0; j < wrapped.length; j++) {
newLast.push(wrapped[j]);
}
}
this._lastData = newLast;
}
};
/**
* 对单行文本进行换行处理。
* 始终处理
;仅在 EnableWordWrap 开启时做自动折行。
* @param {string} text 原始文本
* @param {number} maxWidth 最大像素宽度
* @returns {string[]} 拆分后的文本行数组
*/
Window_ObjInfoFix.prototype.wrapLine = function (text, maxWidth) {
if (!text) return [''];
if (text === '') return [''];
// Step 1:
手动换行(始终生效,不区分大小写)
const segments = text.split(/
/gi);
// Step 2: 如果自动换行关闭,按
和 \n 拆分后直接返回
if (!GF.Patch.ItemInfoWheelScroll.EnableWordWrap) {
const result = [];
for (let i = 0; i < segments.length; i++) {
const sub = segments[i].split('\n');
for (let j = 0; j < sub.length; j++) {
if (sub[j] !== '') result.push(sub[j]);
}
}
return result.length > 0 ? result : [''];
}
// Step 3: 自动换行 — 先按 \n 拆分,再对每个片段按宽度折行
const result = [];
for (let i = 0; i < segments.length; i++) {
const subs = segments[i].split('\n');
for (let j = 0; j < subs.length; j++) {
const sub = subs[j];
if (sub === '') continue;
if (this.textWidthEx(sub) <= maxWidth) {
result.push(sub);
} else {
const autoWrapped = this.autoWrapLine(sub, maxWidth);
for (let w = 0; w < autoWrapped.length; w++) {
result.push(autoWrapped[w]);
}
}
}
}
return result.length > 0 ? result : [''];
};
/**
* 将一行过长文本按像素宽度自动拆分为多行。
* 逐个 token(转义码整体 / 单字符)推进,超出宽度时在 token 前断行。
* @param {string} text 原始文本(不含 \n 和
)
* @param {number} maxWidth 最大像素宽度
* @returns {string[]} 拆分后的文本行
*/
Window_ObjInfoFix.prototype.autoWrapLine = function (text, maxWidth) {
const tokens = this.tokenizeLine(text);
const result = [];
let current = '';
for (let i = 0; i < tokens.length; i++) {
const token = tokens[i];
const candidate = current + token;
if (current !== '' && this.textWidthEx(candidate) > maxWidth) {
result.push(current);
current = token;
} else {
current = candidate;
}
}
if (current !== '') result.push(current);
return result.length > 0 ? result : [text];
};
/**
* 将文本拆分为 token 数组:
* - 转义码整体作为一个 token(不会被截断)
* - 连续的非转义字符按单字符拆分
* @param {string} text
* @returns {string[]}
*/
Window_ObjInfoFix.prototype.tokenizeLine = function (text) {
const tokens = [];
let i = 0;
while (i < text.length) {
if (text[i] === '\\' && i + 1 < text.length) {
const tokenStart = i;
i++; // 跳过反斜杠
const c = text[i];
if (c === '{' || c === '}') {
// \{ 或 \}
i++;
} else if (c === '\\') {
// \\ 转义反斜杠
i++;
} else if (/[a-zA-Z]/.test(c)) {
// \c[n], \i[n], \fs[n], \v[n], \n[n], \p[n] 等
i++;
if (i < text.length && text[i] === '[') {
let depth = 1;
i++;
while (i < text.length && depth > 0) {
if (text[i] === '[') depth++;
else if (text[i] === ']') depth--;
i++;
}
}
} else {
// \. \| \! \> \< \^ \$ \G 等单字符转义
i++;
}
tokens.push(text.substring(tokenStart, i));
} else {
// 普通字符按单字符拆分,保证可在任意字符边界断行
tokens.push(text[i]);
i++;
}
}
return tokens;
};
//=============================================================================
// 替换 refresh 流程以插入换行处理
//=============================================================================
(function () {
const _Window_ObjInfoFix_refresh = Window_ObjInfoFix.prototype.refresh;
Window_ObjInfoFix.prototype.refresh = function () {
if (!this._item || !this._targetWindow) {
this.clearAll();
return;
}
// 1. 构建原始数据
this.makeObjInfo();
// 2. 换行处理(
始终生效,自动换行按参数控制)
this.processWordWrap();
// 3. 计算布局
this.calculateDataLength();
this.calculateMaxCols();
this.calculateAllDataHeight();
this.createContents();
this.resetOrigin();
this.drawAllInfo();
};
})();