Working scaling
This commit is contained in:
parent
33b5a8749a
commit
79e099ad08
6 changed files with 238 additions and 64 deletions
|
|
@ -2561,7 +2561,7 @@ class WorkflowTab(QWidget):
|
|||
|
||||
self._pt_ui_scale_combo = QComboBox()
|
||||
self._pt_ui_scale_combo.setToolTip(
|
||||
"In-game overlay size. Auto scales from game resolution and display DPI."
|
||||
"In-game overlay size. Auto scales from window size and display DPI."
|
||||
)
|
||||
for label, value in (
|
||||
("Auto (match game width)", "auto"),
|
||||
|
|
|
|||
|
|
@ -22,6 +22,10 @@
|
|||
* @desc Value used by the "Max" button for item stacks (0 = use engine maxItems).
|
||||
* @default 0
|
||||
*
|
||||
* @param UiScale
|
||||
* @desc Overlay size. Use auto to match the window, or a number like 1.5 or 2.
|
||||
* @default auto
|
||||
*
|
||||
* @help
|
||||
* Forge — drop-in cheat & editor for RPG Maker MV.
|
||||
* Place in js/plugins/ and enable. Press F10 in-game to open the overlay.
|
||||
|
|
@ -380,7 +384,7 @@
|
|||
};
|
||||
|
||||
// expose the assembled API (UI is attached below if a real DOM is present)
|
||||
var MTC = { env: { IS_MZ: IS_MZ }, ops: ops, locks: locks, cheats: cheats, LOCK: LOCK, CHEAT: CHEAT, PARAM_NAMES: PARAM_NAMES, _itemMaxOverride: 0, _hotkey: 'F10', _speedKey: 'Control', _speedBoost: 2 };
|
||||
var MTC = { env: { IS_MZ: IS_MZ }, ops: ops, locks: locks, cheats: cheats, LOCK: LOCK, CHEAT: CHEAT, PARAM_NAMES: PARAM_NAMES, _itemMaxOverride: 0, _hotkey: 'F10', _speedKey: 'Control', _speedBoost: 2, _uiScale: 'auto' };
|
||||
if (root) { root.Forge = MTC; root.__FORGE_CORE__ = true; }
|
||||
if (typeof module !== 'undefined' && module.exports) module.exports = MTC;
|
||||
|
||||
|
|
@ -404,6 +408,37 @@
|
|||
var LS = 'forge:';
|
||||
function store(k, v) { try { localStorage.setItem(LS + k, JSON.stringify(v)); } catch (e) {} }
|
||||
function load(k, d) { try { var s = localStorage.getItem(LS + k); return s == null ? d : JSON.parse(s); } catch (e) { return d; } }
|
||||
function resolveUiScale(v) {
|
||||
if (v !== 'auto' && v != null && String(v).trim() !== '') {
|
||||
var n = parseFloat(v);
|
||||
if (!isNaN(n) && n > 0) return Math.max(0.75, Math.min(3, n));
|
||||
}
|
||||
var base = 816;
|
||||
var gameW = (typeof Graphics !== 'undefined' && Graphics.width) ? Graphics.width : 0;
|
||||
var viewW = window.innerWidth || document.documentElement.clientWidth || base;
|
||||
var w = Math.max(gameW || base, viewW);
|
||||
var scale = w / base;
|
||||
var dpr = window.devicePixelRatio || 1;
|
||||
if (dpr > 1.15) scale *= Math.min(2, 0.75 + dpr * 0.35);
|
||||
return Math.max(1, Math.min(2.75, scale));
|
||||
}
|
||||
function scalePx(n, fx) {
|
||||
if (!fx || fx === 1) return n;
|
||||
return Math.round(n * fx);
|
||||
}
|
||||
function scalePxCss(css, fx) {
|
||||
if (!fx || fx === 1) return css;
|
||||
return css.replace(/(\d+(?:\.\d+)?)px/g, function (_, n) {
|
||||
return scalePx(parseFloat(n), fx) + 'px';
|
||||
});
|
||||
}
|
||||
function scaleStyle(style, fx) {
|
||||
if (!style || !fx || fx === 1) return style;
|
||||
return style.replace(/(\d+(?:\.\d+)?)px/g, function (_, n) {
|
||||
return scalePx(parseFloat(n), fx) + 'px';
|
||||
});
|
||||
}
|
||||
var uiFx = resolveUiScale(API._uiScale || 'auto');
|
||||
// persisted speed settings (UI-controlled; override core/param defaults if the user changed them)
|
||||
API._speedKey = load('speedKey', API._speedKey || 'Control');
|
||||
API._speedUseKey = load('speedUseKey', true);
|
||||
|
|
@ -416,7 +451,7 @@
|
|||
if (k === 'class') n.className = props[k];
|
||||
else if (k === 'text') n.textContent = props[k];
|
||||
else if (k === 'html') n.innerHTML = props[k];
|
||||
else if (k === 'style') n.setAttribute('style', props[k]);
|
||||
else if (k === 'style') n.setAttribute('style', scaleStyle(props[k], uiFx));
|
||||
else if (k.slice(0, 2) === 'on' && typeof props[k] === 'function') n.addEventListener(k.slice(2).toLowerCase(), props[k]);
|
||||
else if (props[k] != null) n.setAttribute(k, props[k]);
|
||||
}
|
||||
|
|
@ -507,12 +542,12 @@ input::placeholder{color:var(--fg-text-faint)}\
|
|||
|
||||
// ---------- shadow host ----------
|
||||
var host = el('div', { id: 'forge-host' });
|
||||
host.style.cssText = 'position:fixed!important;top:0!important;left:0!important;width:0!important;height:0!important;z-index:2147483647!important;margin:0!important;padding:0!important;border:0!important;pointer-events:none!important';
|
||||
host.style.cssText = 'position:fixed!important;top:0!important;left:0!important;width:0!important;height:0!important;z-index:2147483647!important;margin:0!important;padding:0!important;border:0!important;pointer-events:none!important;overflow:visible!important';
|
||||
document.body.appendChild(host);
|
||||
var shadow = host.attachShadow({ mode: 'open' });
|
||||
var rootEl = el('div', { id: 'forge-root' });
|
||||
rootEl.setAttribute('data-theme', load('theme', 'dark'));
|
||||
shadow.appendChild(el('style', { text: CSS }));
|
||||
shadow.appendChild(el('style', { text: scalePxCss(CSS, uiFx) }));
|
||||
shadow.appendChild(rootEl);
|
||||
|
||||
// Swallow events so panel interaction never leaks to the game. BUBBLE phase: the panel's own
|
||||
|
|
@ -547,8 +582,8 @@ input::placeholder{color:var(--fg-text-faint)}\
|
|||
// ---------- panel skeleton ----------
|
||||
var vw = window.innerWidth || 816, vh = window.innerHeight || 624;
|
||||
// Default size fits the window (many MV games are ~816x624); leave a margin.
|
||||
var st = load('panel', { x: 16, y: 16, w: Math.min(880, vw - 32), h: Math.min(560, vh - 32) });
|
||||
st.w = Math.max(420, Math.min(st.w, vw - 8)); st.h = Math.max(320, Math.min(st.h, vh - 8));
|
||||
var st = load('panel', { x: scalePx(16, uiFx), y: scalePx(16, uiFx), w: Math.min(scalePx(880, uiFx), vw - scalePx(32, uiFx)), h: Math.min(scalePx(560, uiFx), vh - scalePx(32, uiFx)) });
|
||||
st.w = Math.max(scalePx(420, uiFx), Math.min(st.w, vw - scalePx(8, uiFx))); st.h = Math.max(scalePx(320, uiFx), Math.min(st.h, vh - scalePx(8, uiFx)));
|
||||
st.x = Math.max(0, Math.min(st.x, vw - st.w)); st.y = Math.max(0, Math.min(st.y, vh - st.h)); // keep fully on-screen
|
||||
var panel = el('div', { class: 'fg-panel' });
|
||||
panel.style.left = st.x + 'px'; panel.style.top = st.y + 'px'; panel.style.width = st.w + 'px'; panel.style.height = st.h + 'px';
|
||||
|
|
@ -571,8 +606,8 @@ input::placeholder{color:var(--fg-text-faint)}\
|
|||
|
||||
// ---------- launcher (docks to bottom-right corner by default; 'launcherPos' is a fresh key so
|
||||
// any stale mid-screen position from older builds is ignored) ----------
|
||||
var lp = load('launcherPos', { x: vw - 56, y: vh - 56 });
|
||||
lp.x = Math.max(4, Math.min(lp.x, vw - 46)); lp.y = Math.max(4, Math.min(lp.y, vh - 46));
|
||||
var lp = load('launcherPos', { x: vw - scalePx(56, uiFx), y: vh - scalePx(56, uiFx) });
|
||||
lp.x = Math.max(scalePx(4, uiFx), Math.min(lp.x, vw - scalePx(46, uiFx))); lp.y = Math.max(scalePx(4, uiFx), Math.min(lp.y, vh - scalePx(46, uiFx)));
|
||||
var launcher = el('button', { class: 'fg-launcher', text: '⚒', title: 'Forge (F10)' });
|
||||
launcher.style.left = lp.x + 'px'; launcher.style.top = lp.y + 'px';
|
||||
launcher.addEventListener('click', function (e) { if (!launcher._dragged) toggle(); });
|
||||
|
|
@ -585,7 +620,7 @@ input::placeholder{color:var(--fg-text-faint)}\
|
|||
if (e.target.closest && e.target.closest('input,button.fg-iconbtn')) return;
|
||||
e.preventDefault(); e.stopPropagation(); var sx = e.clientX, sy = e.clientY, ox = parseInt(target.style.left) || 0, oy = parseInt(target.style.top) || 0, moved = false;
|
||||
if (isLauncher) target._dragged = false;
|
||||
function mv(ev) { ev.stopPropagation(); var dx = ev.clientX - sx, dy = ev.clientY - sy; if (Math.abs(dx) + Math.abs(dy) > 3) moved = true; var nx = Math.max(0, Math.min(window.innerWidth - target.offsetWidth, ox + dx)), ny = Math.max(0, Math.min(window.innerHeight - 30, oy + dy)); target.style.left = nx + 'px'; target.style.top = ny + 'px'; if (isLauncher) target._dragged = moved; }
|
||||
function mv(ev) { ev.stopPropagation(); var dx = ev.clientX - sx, dy = ev.clientY - sy; if (Math.abs(dx) + Math.abs(dy) > 3) moved = true; var nx = Math.max(0, Math.min(window.innerWidth - target.offsetWidth, ox + dx)), ny = Math.max(0, Math.min(window.innerHeight - scalePx(30, uiFx), oy + dy)); target.style.left = nx + 'px'; target.style.top = ny + 'px'; if (isLauncher) target._dragged = moved; }
|
||||
function up(ev) { if (ev) { ev.stopPropagation(); ev.preventDefault(); } document.removeEventListener('mousemove', mv, true); document.removeEventListener('mouseup', up, true); if (onEnd) onEnd(parseInt(target.style.left), parseInt(target.style.top)); setTimeout(function () { if (isLauncher) target._dragged = false; }, 0); }
|
||||
document.addEventListener('mousemove', mv, true); document.addEventListener('mouseup', up, true);
|
||||
});
|
||||
|
|
@ -593,7 +628,7 @@ input::placeholder{color:var(--fg-text-faint)}\
|
|||
makeDraggable(titlebar, panel, function (x, y) { st.x = x; st.y = y; store('panel', st); });
|
||||
resize.addEventListener('mousedown', function (e) {
|
||||
e.preventDefault(); e.stopPropagation(); var sx = e.clientX, sy = e.clientY, ow = panel.offsetWidth, oh = panel.offsetHeight;
|
||||
function mv(ev) { ev.stopPropagation(); var w = Math.max(480, ow + ev.clientX - sx), h = Math.max(360, oh + ev.clientY - sy); panel.style.width = w + 'px'; panel.style.height = h + 'px'; if (activeRenderer && activeRenderer.onResize) activeRenderer.onResize(); }
|
||||
function mv(ev) { ev.stopPropagation(); var w = Math.max(scalePx(480, uiFx), ow + ev.clientX - sx), h = Math.max(scalePx(360, uiFx), oh + ev.clientY - sy); panel.style.width = w + 'px'; panel.style.height = h + 'px'; if (activeRenderer && activeRenderer.onResize) activeRenderer.onResize(); }
|
||||
function up(ev) { if (ev) { ev.stopPropagation(); ev.preventDefault(); } document.removeEventListener('mousemove', mv, true); document.removeEventListener('mouseup', up, true); st.w = panel.offsetWidth; st.h = panel.offsetHeight; store('panel', st); }
|
||||
document.addEventListener('mousemove', mv, true); document.addEventListener('mouseup', up, true);
|
||||
});
|
||||
|
|
@ -605,7 +640,7 @@ input::placeholder{color:var(--fg-text-faint)}\
|
|||
var sizer = el('div', { class: 'fg-list__sizer' });
|
||||
var win = el('div', { class: 'fg-list__window' });
|
||||
sizer.appendChild(win); listEl.appendChild(sizer);
|
||||
var rowH = opts.rowH || 30, pool = [], model = [], view = [], query = '';
|
||||
var rowH = opts.rowH || scalePx(30, uiFx), pool = [], model = [], view = [], query = '';
|
||||
// ONE delegated row click (recycled rows must not accumulate per-draw listeners).
|
||||
if (opts.onRowClick) listEl.addEventListener('click', function (e) { var r = e.target && e.target.closest ? e.target.closest('.fg-row') : null; if (r && r._item) opts.onRowClick(r._item); });
|
||||
function rebuild() { model = opts.getModel() || []; applyFilter(); }
|
||||
|
|
@ -1069,6 +1104,7 @@ input::placeholder{color:var(--fg-text-faint)}\
|
|||
if (window.Forge) {
|
||||
window.Forge._hotkey = (P.Hotkey || 'F10').trim();
|
||||
window.Forge._itemMaxOverride = Number(P.ItemMaxOverride) || 0;
|
||||
window.Forge._uiScale = (P.UiScale || 'auto').trim();
|
||||
// speed key: plugin-param is the first-run default; a key rebound in the panel (persisted) wins
|
||||
try { if (!localStorage.getItem('forge:speedKey')) window.Forge._speedKey = (P.SpeedKey || 'Control').trim(); } catch (e) { window.Forge._speedKey = (P.SpeedKey || 'Control').trim(); }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -400,7 +400,7 @@
|
|||
};
|
||||
|
||||
// expose the assembled API (UI is attached below if a real DOM is present)
|
||||
var MTC = { env: { IS_MZ: IS_MZ }, ops: ops, locks: locks, cheats: cheats, LOCK: LOCK, CHEAT: CHEAT, PARAM_NAMES: PARAM_NAMES, _itemMaxOverride: 0, _hotkey: 'F10', _speedKey: 'Control', _speedBoost: 2 };
|
||||
var MTC = { env: { IS_MZ: IS_MZ }, ops: ops, locks: locks, cheats: cheats, LOCK: LOCK, CHEAT: CHEAT, PARAM_NAMES: PARAM_NAMES, _itemMaxOverride: 0, _hotkey: 'F10', _speedKey: 'Control', _speedBoost: 2, _uiScale: 'auto' };
|
||||
if (root) { root.Forge = MTC; root.__FORGE_CORE__ = true; }
|
||||
if (typeof module !== 'undefined' && module.exports) module.exports = MTC;
|
||||
|
||||
|
|
@ -430,12 +430,31 @@
|
|||
if (!isNaN(n) && n > 0) return Math.max(0.75, Math.min(3, n));
|
||||
}
|
||||
var base = 816;
|
||||
var w = (typeof Graphics !== 'undefined' && Graphics.width) ? Graphics.width : (window.innerWidth || base);
|
||||
var gameW = (typeof Graphics !== 'undefined' && Graphics.width) ? Graphics.width : 0;
|
||||
var viewW = window.innerWidth || document.documentElement.clientWidth || base;
|
||||
var w = Math.max(gameW || base, viewW);
|
||||
var scale = w / base;
|
||||
var dpr = window.devicePixelRatio || 1;
|
||||
if (dpr > 1.15) scale *= Math.min(2, 0.75 + dpr * 0.35);
|
||||
return Math.max(1, Math.min(2.75, scale));
|
||||
}
|
||||
function scalePx(n, fx) {
|
||||
if (!fx || fx === 1) return n;
|
||||
return Math.round(n * fx);
|
||||
}
|
||||
function scalePxCss(css, fx) {
|
||||
if (!fx || fx === 1) return css;
|
||||
return css.replace(/(\d+(?:\.\d+)?)px/g, function (_, n) {
|
||||
return scalePx(parseFloat(n), fx) + 'px';
|
||||
});
|
||||
}
|
||||
function scaleStyle(style, fx) {
|
||||
if (!style || !fx || fx === 1) return style;
|
||||
return style.replace(/(\d+(?:\.\d+)?)px/g, function (_, n) {
|
||||
return scalePx(parseFloat(n), fx) + 'px';
|
||||
});
|
||||
}
|
||||
var uiFx = resolveUiScale(API._uiScale || 'auto');
|
||||
// persisted speed settings (UI-controlled; override core/param defaults if the user changed them)
|
||||
API._speedKey = load('speedKey', API._speedKey || 'Control');
|
||||
API._speedUseKey = load('speedUseKey', true);
|
||||
|
|
@ -448,7 +467,7 @@
|
|||
if (k === 'class') n.className = props[k];
|
||||
else if (k === 'text') n.textContent = props[k];
|
||||
else if (k === 'html') n.innerHTML = props[k];
|
||||
else if (k === 'style') n.setAttribute('style', props[k]);
|
||||
else if (k === 'style') n.setAttribute('style', scaleStyle(props[k], uiFx));
|
||||
else if (k.slice(0, 2) === 'on' && typeof props[k] === 'function') n.addEventListener(k.slice(2).toLowerCase(), props[k]);
|
||||
else if (props[k] != null) n.setAttribute(k, props[k]);
|
||||
}
|
||||
|
|
@ -539,14 +558,12 @@ input::placeholder{color:var(--fg-text-faint)}\
|
|||
|
||||
// ---------- shadow host ----------
|
||||
var host = el('div', { id: 'forge-host' });
|
||||
host.style.cssText = 'position:fixed!important;top:0!important;left:0!important;width:0!important;height:0!important;z-index:2147483647!important;margin:0!important;padding:0!important;border:0!important;pointer-events:none!important';
|
||||
host.style.cssText = 'position:fixed!important;top:0!important;left:0!important;width:0!important;height:0!important;z-index:2147483647!important;margin:0!important;padding:0!important;border:0!important;pointer-events:none!important;overflow:visible!important';
|
||||
document.body.appendChild(host);
|
||||
var shadow = host.attachShadow({ mode: 'open' });
|
||||
var rootEl = el('div', { id: 'forge-root' });
|
||||
rootEl.setAttribute('data-theme', load('theme', 'dark'));
|
||||
var uiScale = resolveUiScale(API._uiScale || 'auto');
|
||||
if (uiScale !== 1) rootEl.style.zoom = String(uiScale);
|
||||
shadow.appendChild(el('style', { text: CSS }));
|
||||
shadow.appendChild(el('style', { text: scalePxCss(CSS, uiFx) }));
|
||||
shadow.appendChild(rootEl);
|
||||
|
||||
// Swallow events so panel interaction never leaks to the game. BUBBLE phase: the panel's own
|
||||
|
|
@ -581,8 +598,8 @@ input::placeholder{color:var(--fg-text-faint)}\
|
|||
// ---------- panel skeleton ----------
|
||||
var vw = window.innerWidth || 816, vh = window.innerHeight || 624;
|
||||
// Default size fits the window (many MV games are ~816x624); leave a margin.
|
||||
var st = load('panel', { x: 16, y: 16, w: Math.min(880, vw - 32), h: Math.min(560, vh - 32) });
|
||||
st.w = Math.max(420, Math.min(st.w, vw - 8)); st.h = Math.max(320, Math.min(st.h, vh - 8));
|
||||
var st = load('panel', { x: scalePx(16, uiFx), y: scalePx(16, uiFx), w: Math.min(scalePx(880, uiFx), vw - scalePx(32, uiFx)), h: Math.min(scalePx(560, uiFx), vh - scalePx(32, uiFx)) });
|
||||
st.w = Math.max(scalePx(420, uiFx), Math.min(st.w, vw - scalePx(8, uiFx))); st.h = Math.max(scalePx(320, uiFx), Math.min(st.h, vh - scalePx(8, uiFx)));
|
||||
st.x = Math.max(0, Math.min(st.x, vw - st.w)); st.y = Math.max(0, Math.min(st.y, vh - st.h)); // keep fully on-screen
|
||||
var panel = el('div', { class: 'fg-panel' });
|
||||
panel.style.left = st.x + 'px'; panel.style.top = st.y + 'px'; panel.style.width = st.w + 'px'; panel.style.height = st.h + 'px';
|
||||
|
|
@ -605,8 +622,8 @@ input::placeholder{color:var(--fg-text-faint)}\
|
|||
|
||||
// ---------- launcher (docks to bottom-right corner by default; 'launcherPos' is a fresh key so
|
||||
// any stale mid-screen position from older builds is ignored) ----------
|
||||
var lp = load('launcherPos', { x: vw - 56, y: vh - 56 });
|
||||
lp.x = Math.max(4, Math.min(lp.x, vw - 46)); lp.y = Math.max(4, Math.min(lp.y, vh - 46));
|
||||
var lp = load('launcherPos', { x: vw - scalePx(56, uiFx), y: vh - scalePx(56, uiFx) });
|
||||
lp.x = Math.max(scalePx(4, uiFx), Math.min(lp.x, vw - scalePx(46, uiFx))); lp.y = Math.max(scalePx(4, uiFx), Math.min(lp.y, vh - scalePx(46, uiFx)));
|
||||
var launcher = el('button', { class: 'fg-launcher', text: '⚒', title: 'Forge (F10)' });
|
||||
launcher.style.left = lp.x + 'px'; launcher.style.top = lp.y + 'px';
|
||||
launcher.addEventListener('click', function (e) { if (!launcher._dragged) toggle(); });
|
||||
|
|
@ -619,7 +636,7 @@ input::placeholder{color:var(--fg-text-faint)}\
|
|||
if (e.target.closest && e.target.closest('input,button.fg-iconbtn')) return;
|
||||
e.preventDefault(); e.stopPropagation(); var sx = e.clientX, sy = e.clientY, ox = parseInt(target.style.left) || 0, oy = parseInt(target.style.top) || 0, moved = false;
|
||||
if (isLauncher) target._dragged = false;
|
||||
function mv(ev) { ev.stopPropagation(); var dx = ev.clientX - sx, dy = ev.clientY - sy; if (Math.abs(dx) + Math.abs(dy) > 3) moved = true; var nx = Math.max(0, Math.min(window.innerWidth - target.offsetWidth, ox + dx)), ny = Math.max(0, Math.min(window.innerHeight - 30, oy + dy)); target.style.left = nx + 'px'; target.style.top = ny + 'px'; if (isLauncher) target._dragged = moved; }
|
||||
function mv(ev) { ev.stopPropagation(); var dx = ev.clientX - sx, dy = ev.clientY - sy; if (Math.abs(dx) + Math.abs(dy) > 3) moved = true; var nx = Math.max(0, Math.min(window.innerWidth - target.offsetWidth, ox + dx)), ny = Math.max(0, Math.min(window.innerHeight - scalePx(30, uiFx), oy + dy)); target.style.left = nx + 'px'; target.style.top = ny + 'px'; if (isLauncher) target._dragged = moved; }
|
||||
function up(ev) { if (ev) { ev.stopPropagation(); ev.preventDefault(); } document.removeEventListener('mousemove', mv, true); document.removeEventListener('mouseup', up, true); if (onEnd) onEnd(parseInt(target.style.left), parseInt(target.style.top)); setTimeout(function () { if (isLauncher) target._dragged = false; }, 0); }
|
||||
document.addEventListener('mousemove', mv, true); document.addEventListener('mouseup', up, true);
|
||||
});
|
||||
|
|
@ -627,7 +644,7 @@ input::placeholder{color:var(--fg-text-faint)}\
|
|||
makeDraggable(titlebar, panel, function (x, y) { st.x = x; st.y = y; store('panel', st); });
|
||||
resize.addEventListener('mousedown', function (e) {
|
||||
e.preventDefault(); e.stopPropagation(); var sx = e.clientX, sy = e.clientY, ow = panel.offsetWidth, oh = panel.offsetHeight;
|
||||
function mv(ev) { ev.stopPropagation(); var w = Math.max(480, ow + ev.clientX - sx), h = Math.max(360, oh + ev.clientY - sy); panel.style.width = w + 'px'; panel.style.height = h + 'px'; if (activeRenderer && activeRenderer.onResize) activeRenderer.onResize(); }
|
||||
function mv(ev) { ev.stopPropagation(); var w = Math.max(scalePx(480, uiFx), ow + ev.clientX - sx), h = Math.max(scalePx(360, uiFx), oh + ev.clientY - sy); panel.style.width = w + 'px'; panel.style.height = h + 'px'; if (activeRenderer && activeRenderer.onResize) activeRenderer.onResize(); }
|
||||
function up(ev) { if (ev) { ev.stopPropagation(); ev.preventDefault(); } document.removeEventListener('mousemove', mv, true); document.removeEventListener('mouseup', up, true); st.w = panel.offsetWidth; st.h = panel.offsetHeight; store('panel', st); }
|
||||
document.addEventListener('mousemove', mv, true); document.addEventListener('mouseup', up, true);
|
||||
});
|
||||
|
|
@ -639,7 +656,7 @@ input::placeholder{color:var(--fg-text-faint)}\
|
|||
var sizer = el('div', { class: 'fg-list__sizer' });
|
||||
var win = el('div', { class: 'fg-list__window' });
|
||||
sizer.appendChild(win); listEl.appendChild(sizer);
|
||||
var rowH = opts.rowH || 30, pool = [], model = [], view = [], query = '';
|
||||
var rowH = opts.rowH || scalePx(30, uiFx), pool = [], model = [], view = [], query = '';
|
||||
// ONE delegated row click (recycled rows must not accumulate per-draw listeners).
|
||||
if (opts.onRowClick) listEl.addEventListener('click', function (e) { var r = e.target && e.target.closest ? e.target.closest('.fg-row') : null; if (r && r._item) opts.onRowClick(r._item); });
|
||||
function rebuild() { model = opts.getModel() || []; applyFilter(); }
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
"""Forge plugin build — hotkey (and MZ UI scale) injection at install time."""
|
||||
"""Forge plugin build — hotkey and UI scale injection at install/apply time."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
|
@ -35,9 +35,9 @@ def _js_literal(value) -> str:
|
|||
|
||||
def plugin_entry(engine: str, hotkey: str, ui_scale: str = "auto") -> str:
|
||||
hk = _js_literal(hotkey.strip() or "F10")
|
||||
scale = _js_literal(ui_scale.strip() or "auto")
|
||||
name = PLUGIN_BY_ENGINE[engine]
|
||||
if engine == "MZ":
|
||||
scale = _js_literal(ui_scale.strip() or "auto")
|
||||
return (
|
||||
f' {{ "name": "{name}", "status": true, '
|
||||
f'"description": "Forge — in-game cheat & editor overlay", '
|
||||
|
|
@ -48,7 +48,7 @@ def plugin_entry(engine: str, hotkey: str, ui_scale: str = "auto") -> str:
|
|||
f' {{ "name": "{name}", "status": true, '
|
||||
f'"description": "Forge — in-game cheat & editor overlay", '
|
||||
f'"parameters": {{ "Hotkey": {hk}, "SpeedKey": "Control", '
|
||||
f'"StartOpen": "false", "ItemMaxOverride": "0" }} }}'
|
||||
f'"StartOpen": "false", "ItemMaxOverride": "0", "UiScale": {scale} }} }}'
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -63,8 +63,6 @@ def _patch_forge_hotkey(forge_text: str, hotkey: str, engine: str) -> str:
|
|||
forge_text, n = re.subn(pattern, rf"\g<1>{hk}", forge_text, count=1)
|
||||
if n == 0:
|
||||
raise ValueError("Could not patch @default hotkey in Forge_MZ.js")
|
||||
forge_text = re.sub(r"\(P\.hotkey \|\| 'F10'\)", f"(P.hotkey || '{hk}')", forge_text)
|
||||
forge_text = re.sub(r"API\._hotkey \|\| 'F10'", f"API._hotkey || '{hk}'", forge_text)
|
||||
return forge_text
|
||||
|
||||
pattern = (
|
||||
|
|
@ -75,28 +73,83 @@ def _patch_forge_hotkey(forge_text: str, hotkey: str, engine: str) -> str:
|
|||
forge_text, n = re.subn(pattern, rf"\g<1>{hk}", forge_text, count=1)
|
||||
if n == 0:
|
||||
raise ValueError("Could not patch @default Hotkey in Forge_MV.js")
|
||||
forge_text = re.sub(r"\(P\.Hotkey \|\| 'F10'\)", f"(P.Hotkey || '{hk}')", forge_text)
|
||||
return forge_text
|
||||
|
||||
|
||||
def _patch_forge_ui_scale_default(forge_text: str, ui_scale: str, engine: str) -> str:
|
||||
scale = ui_scale.strip() or "auto"
|
||||
if engine == "MZ":
|
||||
pattern = (
|
||||
r"(\* @param uiScale\s*\n(?:\s*\*[^\n]*\n)*?\s*\* @default )auto"
|
||||
)
|
||||
if scale != "auto":
|
||||
forge_text, n = re.subn(pattern, rf"\g<1>{scale}", forge_text, count=1)
|
||||
if n == 0:
|
||||
raise ValueError("Could not patch @default uiScale in Forge_MZ.js")
|
||||
return forge_text
|
||||
|
||||
pattern = (
|
||||
r"(\* @param UiScale\s*\n(?:\s*\*[^\n]*\n)*?\s*\* @default )auto"
|
||||
)
|
||||
if scale != "auto":
|
||||
forge_text, n = re.subn(pattern, rf"\g<1>{scale}", forge_text, count=1)
|
||||
if n == 0:
|
||||
raise ValueError("Could not patch @default UiScale in Forge_MV.js")
|
||||
return forge_text
|
||||
|
||||
|
||||
def _patch_forge_mtc_defaults(forge_text: str, hotkey: str, ui_scale: str) -> str:
|
||||
hk = json.dumps(hotkey.strip() or "F10")
|
||||
scale = json.dumps(str(ui_scale or "auto").strip() or "auto")
|
||||
forge_text = re.sub(r"_hotkey: 'F10'", f"_hotkey: {hk}", forge_text, count=1)
|
||||
forge_text = re.sub(r"_uiScale: 'auto'", f"_uiScale: {scale}", forge_text, count=1)
|
||||
return forge_text
|
||||
|
||||
|
||||
def _patch_forge_runtime(forge_text: str, hotkey: str, ui_scale: str, engine: str) -> str:
|
||||
"""Bake hotkey/scale into the plugin file (plugins.js params alone are not enough)."""
|
||||
hk = json.dumps(hotkey.strip() or "F10")
|
||||
scale = json.dumps(str(ui_scale or "auto").strip() or "auto")
|
||||
if engine == "MZ":
|
||||
forge_text = re.sub(
|
||||
r"window\.Forge\._hotkey = \(P\.hotkey \|\| '[^']*'\)\.trim\(\);",
|
||||
f"window.Forge._hotkey = {hk};",
|
||||
forge_text,
|
||||
count=1,
|
||||
)
|
||||
forge_text = re.sub(
|
||||
r"window\.Forge\._uiScale = \(P\.uiScale \|\| '[^']*'\)\.trim\(\);",
|
||||
f"window.Forge._uiScale = {scale};",
|
||||
forge_text,
|
||||
count=1,
|
||||
)
|
||||
return forge_text
|
||||
|
||||
forge_text = re.sub(
|
||||
r"window\.Forge\._hotkey = \(P\.Hotkey \|\| '[^']*'\)\.trim\(\);",
|
||||
f"window.Forge._hotkey = {hk};",
|
||||
forge_text,
|
||||
count=1,
|
||||
)
|
||||
forge_text = re.sub(
|
||||
r"window\.Forge\._uiScale = \(P\.UiScale \|\| '[^']*'\)\.trim\(\);",
|
||||
f"window.Forge._uiScale = {scale};",
|
||||
forge_text,
|
||||
count=1,
|
||||
)
|
||||
return forge_text
|
||||
|
||||
|
||||
def prepare_forge_js(engine: str, source: Path | None = None, cfg: dict | None = None) -> str:
|
||||
"""Build Forge_MV.js or Forge_MZ.js with configured hotkey (and MZ UI scale)."""
|
||||
"""Build Forge_MV.js or Forge_MZ.js with configured hotkey and UI scale."""
|
||||
src = source or bundled_plugin_path(engine)
|
||||
effective = {**load_config(), **(cfg or {})}
|
||||
text = _patch_forge_hotkey(src.read_text(encoding="utf-8"), effective["forgeHotkey"], engine)
|
||||
if engine != "MZ":
|
||||
return text
|
||||
|
||||
scale = effective.get("uiScale", "auto")
|
||||
text, n = re.subn(
|
||||
r"(\* @param uiScale\s*\n(?:\s*\*[^\n]*\n)*?\s*\* @default )auto",
|
||||
rf"\g<1>{scale}",
|
||||
text,
|
||||
count=1,
|
||||
)
|
||||
if n == 0:
|
||||
raise ValueError("Could not patch @default uiScale in Forge_MZ.js")
|
||||
text = re.sub(r"\(P\.uiScale \|\| 'auto'\)", f"(P.uiScale || '{scale}')", text)
|
||||
hotkey = effective.get("forgeHotkey", "F10")
|
||||
ui_scale = effective.get("uiScale", "auto")
|
||||
text = _patch_forge_hotkey(src.read_text(encoding="utf-8"), hotkey, engine)
|
||||
text = _patch_forge_ui_scale_default(text, str(ui_scale), engine)
|
||||
text = _patch_forge_mtc_defaults(text, hotkey, str(ui_scale))
|
||||
text = _patch_forge_runtime(text, hotkey, str(ui_scale), engine)
|
||||
return text
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -89,6 +89,81 @@ def status(game_root: Path) -> dict:
|
|||
}
|
||||
|
||||
|
||||
def _find_plugin_block_span(content: str, plugin_name: str) -> tuple[int, int] | None:
|
||||
"""Return [start, end) span of the plugin object in plugins.js, or None."""
|
||||
m = re.search(rf'"name"\s*:\s*"{re.escape(plugin_name)}"', content)
|
||||
if not m:
|
||||
return None
|
||||
start = content.rfind("{", 0, m.start())
|
||||
if start < 0:
|
||||
return None
|
||||
depth = 0
|
||||
for i in range(start, len(content)):
|
||||
ch = content[i]
|
||||
if ch == "{":
|
||||
depth += 1
|
||||
elif ch == "}":
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
end = i + 1
|
||||
while end < len(content) and content[end] in " \t\r\n":
|
||||
end += 1
|
||||
if end < len(content) and content[end] == ",":
|
||||
end += 1
|
||||
return start, end
|
||||
return None
|
||||
|
||||
|
||||
def _remove_plugin_block(content: str, plugin_name: str) -> str:
|
||||
compact = rf"\{{[^{{}}]*\"name\"\s*:\s*\"{re.escape(plugin_name)}\"[^{{}}]*\}}\s*,?"
|
||||
content = re.sub(compact, "", content)
|
||||
while True:
|
||||
span = _find_plugin_block_span(content, plugin_name)
|
||||
if span is None:
|
||||
break
|
||||
start, end = span
|
||||
before = content[:start].rstrip()
|
||||
after = content[end:].lstrip()
|
||||
if before.endswith(","):
|
||||
before = before[:-1].rstrip()
|
||||
elif after.startswith(","):
|
||||
after = after[1:].lstrip()
|
||||
gap = "\n" if before and after and not before.endswith("\n") else ""
|
||||
content = before + gap + after
|
||||
lines = [
|
||||
line
|
||||
for line in re.split(r"\r?\n", content)
|
||||
if not re.search(rf"\"name\"\s*:\s*\"{re.escape(plugin_name)}\"", line)
|
||||
]
|
||||
content = "\n".join(lines)
|
||||
# Leftover fragments from a previously corrupted multi-line entry.
|
||||
content = re.sub(
|
||||
r"\{\s*\"status\"\s*:\s*true\s*,\s*\"description\"\s*:\s*\"Forge[^\"]*\""
|
||||
r"\s*,\s*\"parameters\"\s*:\s*\{[^{}]*\}\s*\}\s*,?",
|
||||
"",
|
||||
content,
|
||||
)
|
||||
return content
|
||||
|
||||
|
||||
def _upsert_plugin_entry(content: str, plugin_name: str, entry: str, nl: str) -> str:
|
||||
"""Insert or replace a Forge plugin entry in plugins.js."""
|
||||
span = _find_plugin_block_span(content, plugin_name)
|
||||
if span is not None:
|
||||
start, end = span
|
||||
before = content[:start].rstrip()
|
||||
after = content[end:].lstrip()
|
||||
sep = nl if before.endswith(",") or not before else "," + nl
|
||||
return before + sep + entry + nl + after
|
||||
idx = content.rfind("];")
|
||||
if idx < 0:
|
||||
raise ValueError("Could not find plugin list end ( ]; ) in plugins.js.")
|
||||
before = content[:idx].rstrip()
|
||||
after = content[idx:]
|
||||
sep = nl if before.endswith(",") else "," + nl
|
||||
return before + sep + entry + nl + " " + after
|
||||
|
||||
|
||||
def install(game_root: Path, source_js: Path | None = None, cfg: dict | None = None) -> tuple[bool, str]:
|
||||
"""Copy Forge_MV.js or Forge_MZ.js into the game and declare it in plugins.js."""
|
||||
info = detect_engine(game_root)
|
||||
|
|
@ -113,15 +188,11 @@ def install(game_root: Path, source_js: Path | None = None, cfg: dict | None = N
|
|||
hotkey = (cfg or {}).get("forgeHotkey", "F10")
|
||||
ui_scale = (cfg or {}).get("uiScale", "auto")
|
||||
entry = plugin_entry(engine, hotkey, ui_scale)
|
||||
if not _is_declared(content, plugin_name):
|
||||
idx = content.rfind("];")
|
||||
if idx < 0:
|
||||
return False, "Could not find plugin list end ( ]; ) in plugins.js."
|
||||
before = content[:idx].rstrip()
|
||||
after = content[idx:]
|
||||
sep = nl if before.endswith(",") else "," + nl
|
||||
content = before + sep + entry + nl + " " + after
|
||||
try:
|
||||
content = _upsert_plugin_entry(content, plugin_name, entry, nl)
|
||||
plugins_js.write_text(content, encoding="utf-8", newline="")
|
||||
except ValueError as exc:
|
||||
return False, str(exc)
|
||||
|
||||
return True, f"Forge installed for RPG Maker {engine}. Press {hotkey} in-game to open."
|
||||
|
||||
|
|
@ -138,13 +209,9 @@ def uninstall(game_root: Path) -> tuple[bool, str]:
|
|||
content, nl = _read_plugins_js(plugins_js)
|
||||
|
||||
if _is_declared(content, plugin_name):
|
||||
kept = [
|
||||
line for line in re.split(r"\r?\n", content)
|
||||
if not re.search(rf'"name"\s*:\s*"{re.escape(plugin_name)}"', line)
|
||||
]
|
||||
new_text = nl.join(kept)
|
||||
new_text = re.sub(r",(\s*)\];", r"\1];", new_text)
|
||||
plugins_js.write_text(new_text, encoding="utf-8", newline="")
|
||||
content = _remove_plugin_block(content, plugin_name)
|
||||
content = re.sub(r",(\s*)\];", r"\1];", content)
|
||||
plugins_js.write_text(content, encoding="utf-8", newline="")
|
||||
|
||||
if target.is_file():
|
||||
target.unlink()
|
||||
|
|
|
|||
|
|
@ -84,8 +84,9 @@
|
|||
if (!isNaN(n) && n > 0) { return Math.max(0.75, Math.min(3, n)); }
|
||||
}
|
||||
var base = 816;
|
||||
var w = (typeof Graphics !== 'undefined' && Graphics.width) ? Graphics.width :
|
||||
(window.innerWidth || base);
|
||||
var gameW = (typeof Graphics !== 'undefined' && Graphics.width) ? Graphics.width : 0;
|
||||
var viewW = window.innerWidth || document.documentElement.clientWidth || base;
|
||||
var w = Math.max(gameW || base, viewW);
|
||||
var scale = w / base;
|
||||
var dpr = window.devicePixelRatio || 1;
|
||||
if (dpr > 1.15) { scale *= Math.min(2, 0.75 + dpr * 0.35); }
|
||||
|
|
|
|||
Loading…
Reference in a new issue