Fetch forge from len
This commit is contained in:
parent
79e099ad08
commit
aa6f147db6
12 changed files with 2446 additions and 185 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -23,6 +23,9 @@ __pycache__
|
||||||
!util/tl_inspector/TLInspector.js
|
!util/tl_inspector/TLInspector.js
|
||||||
!util/forge/Forge_MZ.js
|
!util/forge/Forge_MZ.js
|
||||||
!util/forge/Forge_MV.js
|
!util/forge/Forge_MV.js
|
||||||
|
!util/forge/upstream/Forge_MZ.js
|
||||||
|
!util/forge/upstream/Forge_MV.js
|
||||||
|
util/forge/.forge_version.json
|
||||||
util/ace/*.exe
|
util/ace/*.exe
|
||||||
util/ace/.tools_version.json
|
util/ace/.tools_version.json
|
||||||
!util/ace/offline/*.exe
|
!util/ace/offline/*.exe
|
||||||
|
|
|
||||||
|
|
@ -186,6 +186,13 @@ if errorlevel 1 (
|
||||||
)
|
)
|
||||||
echo.
|
echo.
|
||||||
|
|
||||||
|
echo Checking Forge plugins...
|
||||||
|
python -m util.forge.update_tools
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo WARNING: Forge update failed. Playtest Forge features use the bundled upstream copy.
|
||||||
|
)
|
||||||
|
echo.
|
||||||
|
|
||||||
:: Launch the GUI
|
:: Launch the GUI
|
||||||
echo ==========================================
|
echo ==========================================
|
||||||
echo Launching DazedMTLTool GUI...
|
echo Launching DazedMTLTool GUI...
|
||||||
|
|
|
||||||
6
START.sh
6
START.sh
|
|
@ -226,6 +226,12 @@ if ! python -m util.ace.update_tools; then
|
||||||
fi
|
fi
|
||||||
echo
|
echo
|
||||||
|
|
||||||
|
echo "Checking Forge plugins..."
|
||||||
|
if ! python -m util.forge.update_tools; then
|
||||||
|
echo "WARNING: Forge update failed. Playtest Forge features use the bundled upstream copy."
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo " Launching DazedMTLTool GUI..."
|
echo " Launching DazedMTLTool GUI..."
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,12 @@ class UpdateThread(QThread):
|
||||||
ensure_ace_tools(log_fn=lambda m: self.progress.emit(m))
|
ensure_ace_tools(log_fn=lambda m: self.progress.emit(m))
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
self.progress.emit(f"Ace tool update skipped: {exc}")
|
self.progress.emit(f"Ace tool update skipped: {exc}")
|
||||||
|
try:
|
||||||
|
from util.forge.update_tools import ensure_forge_plugins
|
||||||
|
self.progress.emit("Updating Forge plugins…")
|
||||||
|
ensure_forge_plugins(log_fn=lambda m: self.progress.emit(m))
|
||||||
|
except Exception as exc:
|
||||||
|
self.progress.emit(f"Forge update skipped: {exc}")
|
||||||
self.finished.emit(True, f"updated:{latest_sha[:8]}")
|
self.finished.emit(True, f"updated:{latest_sha[:8]}")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,12 @@ def main():
|
||||||
ensure_ace_tools()
|
ensure_ace_tools()
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
print(f"Warning: Ace tool setup failed ({exc}). Ace features may be unavailable.")
|
print(f"Warning: Ace tool setup failed ({exc}). Ace features may be unavailable.")
|
||||||
|
|
||||||
|
try:
|
||||||
|
from util.forge.update_tools import ensure_forge_plugins
|
||||||
|
ensure_forge_plugins()
|
||||||
|
except Exception as exc:
|
||||||
|
print(f"Warning: Forge plugin setup failed ({exc}). Playtest Forge may be unavailable.")
|
||||||
|
|
||||||
# Import and run GUI
|
# Import and run GUI
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,7 @@
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
/*:
|
/*:
|
||||||
* @plugindesc Forge — modern in-game editor: variables, switches, items, actors, gold, teleport, common events, freeze/lock & battle cheats. (MV)
|
* @plugindesc Forge — modern in-game editor: variables, switches, items, actors, gold, teleport, common events, freeze/lock & battle cheats. (MV)
|
||||||
* @author len
|
* @author len (Forge - clean-room, MTool-inspired)
|
||||||
* @url https://gitgud.io/zero64801/forge-mvmz
|
|
||||||
*
|
*
|
||||||
* @param Hotkey
|
* @param Hotkey
|
||||||
* @desc Key to toggle the panel.
|
* @desc Key to toggle the panel.
|
||||||
|
|
@ -22,10 +21,6 @@
|
||||||
* @desc Value used by the "Max" button for item stacks (0 = use engine maxItems).
|
* @desc Value used by the "Max" button for item stacks (0 = use engine maxItems).
|
||||||
* @default 0
|
* @default 0
|
||||||
*
|
*
|
||||||
* @param UiScale
|
|
||||||
* @desc Overlay size. Use auto to match the window, or a number like 1.5 or 2.
|
|
||||||
* @default auto
|
|
||||||
*
|
|
||||||
* @help
|
* @help
|
||||||
* Forge — drop-in cheat & editor for RPG Maker MV.
|
* Forge — drop-in cheat & editor for RPG Maker MV.
|
||||||
* Place in js/plugins/ and enable. Press F10 in-game to open the overlay.
|
* Place in js/plugins/ and enable. Press F10 in-game to open the overlay.
|
||||||
|
|
@ -35,8 +30,6 @@
|
||||||
*
|
*
|
||||||
* All edits are RAM-only and never written to save files. No external requests.
|
* All edits are RAM-only and never written to save files. No external requests.
|
||||||
* Plugin command: MTC open (toggles the panel)
|
* Plugin command: MTC open (toggles the panel)
|
||||||
*
|
|
||||||
* Credits: len — https://gitgud.io/zero64801/forge-mvmz
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* ===================================================================================
|
/* ===================================================================================
|
||||||
|
|
@ -384,7 +377,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
// expose the assembled API (UI is attached below if a real DOM is present)
|
// 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, _uiScale: 'auto' };
|
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 };
|
||||||
if (root) { root.Forge = MTC; root.__FORGE_CORE__ = true; }
|
if (root) { root.Forge = MTC; root.__FORGE_CORE__ = true; }
|
||||||
if (typeof module !== 'undefined' && module.exports) module.exports = MTC;
|
if (typeof module !== 'undefined' && module.exports) module.exports = MTC;
|
||||||
|
|
||||||
|
|
@ -408,37 +401,6 @@
|
||||||
var LS = 'forge:';
|
var LS = 'forge:';
|
||||||
function store(k, v) { try { localStorage.setItem(LS + k, JSON.stringify(v)); } catch (e) {} }
|
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 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)
|
// persisted speed settings (UI-controlled; override core/param defaults if the user changed them)
|
||||||
API._speedKey = load('speedKey', API._speedKey || 'Control');
|
API._speedKey = load('speedKey', API._speedKey || 'Control');
|
||||||
API._speedUseKey = load('speedUseKey', true);
|
API._speedUseKey = load('speedUseKey', true);
|
||||||
|
|
@ -451,7 +413,7 @@
|
||||||
if (k === 'class') n.className = props[k];
|
if (k === 'class') n.className = props[k];
|
||||||
else if (k === 'text') n.textContent = props[k];
|
else if (k === 'text') n.textContent = props[k];
|
||||||
else if (k === 'html') n.innerHTML = props[k];
|
else if (k === 'html') n.innerHTML = props[k];
|
||||||
else if (k === 'style') n.setAttribute('style', scaleStyle(props[k], uiFx));
|
else if (k === 'style') n.setAttribute('style', props[k]);
|
||||||
else if (k.slice(0, 2) === 'on' && typeof props[k] === 'function') n.addEventListener(k.slice(2).toLowerCase(), props[k]);
|
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]);
|
else if (props[k] != null) n.setAttribute(k, props[k]);
|
||||||
}
|
}
|
||||||
|
|
@ -537,17 +499,19 @@
|
||||||
.fg-confirm__box{background:var(--fg-bg-elev);border:1px solid var(--fg-border);border-radius:var(--rad-lg);padding:var(--sp-4);max-width:340px;box-shadow:var(--sh-panel)}\
|
.fg-confirm__box{background:var(--fg-bg-elev);border:1px solid var(--fg-border);border-radius:var(--rad-lg);padding:var(--sp-4);max-width:340px;box-shadow:var(--sh-panel)}\
|
||||||
.fg-confirm__box p{margin:0 0 var(--sp-4)}.fg-confirm__row{display:flex;justify-content:flex-end;gap:var(--sp-2)}\
|
.fg-confirm__box p{margin:0 0 var(--sp-4)}.fg-confirm__row{display:flex;justify-content:flex-end;gap:var(--sp-2)}\
|
||||||
input::placeholder{color:var(--fg-text-faint)}\
|
input::placeholder{color:var(--fg-text-faint)}\
|
||||||
.fg-list::-webkit-scrollbar,.fg-rail::-webkit-scrollbar,.fg-detail::-webkit-scrollbar{width:9px}\
|
::-webkit-scrollbar{width:9px;height:9px}\
|
||||||
.fg-list::-webkit-scrollbar-thumb,.fg-detail::-webkit-scrollbar-thumb{background:var(--fg-border-strong);border-radius:5px}";
|
::-webkit-scrollbar-track{background:transparent}\
|
||||||
|
::-webkit-scrollbar-thumb{background:var(--fg-border-strong);border-radius:5px}\
|
||||||
|
::-webkit-scrollbar-thumb:hover{background:var(--fg-text-faint)}";
|
||||||
|
|
||||||
// ---------- shadow host ----------
|
// ---------- shadow host ----------
|
||||||
var host = el('div', { id: 'forge-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;overflow:visible!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';
|
||||||
document.body.appendChild(host);
|
document.body.appendChild(host);
|
||||||
var shadow = host.attachShadow({ mode: 'open' });
|
var shadow = host.attachShadow({ mode: 'open' });
|
||||||
var rootEl = el('div', { id: 'forge-root' });
|
var rootEl = el('div', { id: 'forge-root' });
|
||||||
rootEl.setAttribute('data-theme', load('theme', 'dark'));
|
rootEl.setAttribute('data-theme', load('theme', 'dark'));
|
||||||
shadow.appendChild(el('style', { text: scalePxCss(CSS, uiFx) }));
|
shadow.appendChild(el('style', { text: CSS }));
|
||||||
shadow.appendChild(rootEl);
|
shadow.appendChild(rootEl);
|
||||||
|
|
||||||
// Swallow events so panel interaction never leaks to the game. BUBBLE phase: the panel's own
|
// Swallow events so panel interaction never leaks to the game. BUBBLE phase: the panel's own
|
||||||
|
|
@ -582,8 +546,8 @@ input::placeholder{color:var(--fg-text-faint)}\
|
||||||
// ---------- panel skeleton ----------
|
// ---------- panel skeleton ----------
|
||||||
var vw = window.innerWidth || 816, vh = window.innerHeight || 624;
|
var vw = window.innerWidth || 816, vh = window.innerHeight || 624;
|
||||||
// Default size fits the window (many MV games are ~816x624); leave a margin.
|
// Default size fits the window (many MV games are ~816x624); leave a margin.
|
||||||
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)) });
|
var st = load('panel', { x: 16, y: 16, w: Math.min(880, vw - 32), h: Math.min(560, vh - 32) });
|
||||||
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.w = Math.max(420, Math.min(st.w, vw - 8)); st.h = Math.max(320, Math.min(st.h, vh - 8));
|
||||||
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
|
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' });
|
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';
|
panel.style.left = st.x + 'px'; panel.style.top = st.y + 'px'; panel.style.width = st.w + 'px'; panel.style.height = st.h + 'px';
|
||||||
|
|
@ -606,8 +570,8 @@ input::placeholder{color:var(--fg-text-faint)}\
|
||||||
|
|
||||||
// ---------- launcher (docks to bottom-right corner by default; 'launcherPos' is a fresh key so
|
// ---------- launcher (docks to bottom-right corner by default; 'launcherPos' is a fresh key so
|
||||||
// any stale mid-screen position from older builds is ignored) ----------
|
// any stale mid-screen position from older builds is ignored) ----------
|
||||||
var lp = load('launcherPos', { x: vw - scalePx(56, uiFx), y: vh - scalePx(56, uiFx) });
|
var lp = load('launcherPos', { x: vw - 56, y: vh - 56 });
|
||||||
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)));
|
lp.x = Math.max(4, Math.min(lp.x, vw - 46)); lp.y = Math.max(4, Math.min(lp.y, vh - 46));
|
||||||
var launcher = el('button', { class: 'fg-launcher', text: '⚒', title: 'Forge (F10)' });
|
var launcher = el('button', { class: 'fg-launcher', text: '⚒', title: 'Forge (F10)' });
|
||||||
launcher.style.left = lp.x + 'px'; launcher.style.top = lp.y + 'px';
|
launcher.style.left = lp.x + 'px'; launcher.style.top = lp.y + 'px';
|
||||||
launcher.addEventListener('click', function (e) { if (!launcher._dragged) toggle(); });
|
launcher.addEventListener('click', function (e) { if (!launcher._dragged) toggle(); });
|
||||||
|
|
@ -620,7 +584,7 @@ input::placeholder{color:var(--fg-text-faint)}\
|
||||||
if (e.target.closest && e.target.closest('input,button.fg-iconbtn')) return;
|
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;
|
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;
|
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 - scalePx(30, uiFx), 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 - 30, 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); }
|
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);
|
document.addEventListener('mousemove', mv, true); document.addEventListener('mouseup', up, true);
|
||||||
});
|
});
|
||||||
|
|
@ -628,7 +592,7 @@ input::placeholder{color:var(--fg-text-faint)}\
|
||||||
makeDraggable(titlebar, panel, function (x, y) { st.x = x; st.y = y; store('panel', st); });
|
makeDraggable(titlebar, panel, function (x, y) { st.x = x; st.y = y; store('panel', st); });
|
||||||
resize.addEventListener('mousedown', function (e) {
|
resize.addEventListener('mousedown', function (e) {
|
||||||
e.preventDefault(); e.stopPropagation(); var sx = e.clientX, sy = e.clientY, ow = panel.offsetWidth, oh = panel.offsetHeight;
|
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(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 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 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); }
|
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);
|
document.addEventListener('mousemove', mv, true); document.addEventListener('mouseup', up, true);
|
||||||
});
|
});
|
||||||
|
|
@ -640,7 +604,7 @@ input::placeholder{color:var(--fg-text-faint)}\
|
||||||
var sizer = el('div', { class: 'fg-list__sizer' });
|
var sizer = el('div', { class: 'fg-list__sizer' });
|
||||||
var win = el('div', { class: 'fg-list__window' });
|
var win = el('div', { class: 'fg-list__window' });
|
||||||
sizer.appendChild(win); listEl.appendChild(sizer);
|
sizer.appendChild(win); listEl.appendChild(sizer);
|
||||||
var rowH = opts.rowH || scalePx(30, uiFx), pool = [], model = [], view = [], query = '';
|
var rowH = opts.rowH || 30, pool = [], model = [], view = [], query = '';
|
||||||
// ONE delegated row click (recycled rows must not accumulate per-draw listeners).
|
// 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); });
|
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(); }
|
function rebuild() { model = opts.getModel() || []; applyFilter(); }
|
||||||
|
|
@ -1104,7 +1068,6 @@ input::placeholder{color:var(--fg-text-faint)}\
|
||||||
if (window.Forge) {
|
if (window.Forge) {
|
||||||
window.Forge._hotkey = (P.Hotkey || 'F10').trim();
|
window.Forge._hotkey = (P.Hotkey || 'F10').trim();
|
||||||
window.Forge._itemMaxOverride = Number(P.ItemMaxOverride) || 0;
|
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
|
// 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(); }
|
try { if (!localStorage.getItem('forge:speedKey')) window.Forge._speedKey = (P.SpeedKey || 'Control').trim(); } catch (e) { window.Forge._speedKey = (P.SpeedKey || 'Control').trim(); }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@
|
||||||
/*:
|
/*:
|
||||||
* @target MZ
|
* @target MZ
|
||||||
* @plugindesc Forge — modern in-game editor: variables, switches, items, actors, gold, teleport, common events, freeze/lock & battle cheats. (MZ)
|
* @plugindesc Forge — modern in-game editor: variables, switches, items, actors, gold, teleport, common events, freeze/lock & battle cheats. (MZ)
|
||||||
* @author len
|
* @author len (Forge - clean-room, MTool-inspired)
|
||||||
* @url https://gitgud.io/zero64801/forge-mvmz
|
* @url
|
||||||
*
|
*
|
||||||
* @param hotkey
|
* @param hotkey
|
||||||
* @text Toggle Hotkey
|
* @text Toggle Hotkey
|
||||||
|
|
@ -32,12 +32,6 @@
|
||||||
* @min 0
|
* @min 0
|
||||||
* @default 0
|
* @default 0
|
||||||
*
|
*
|
||||||
* @param uiScale
|
|
||||||
* @text UI Scale
|
|
||||||
* @desc Overlay size. Use auto to match game width, or a number like 1.5 or 2.
|
|
||||||
* @type string
|
|
||||||
* @default auto
|
|
||||||
*
|
|
||||||
* @command open
|
* @command open
|
||||||
* @text Open / Toggle Panel
|
* @text Open / Toggle Panel
|
||||||
* @desc Toggle the Forge overlay.
|
* @desc Toggle the Forge overlay.
|
||||||
|
|
@ -51,8 +45,6 @@
|
||||||
*
|
*
|
||||||
* All edits are RAM-only and never written to save files. No external requests.
|
* All edits are RAM-only and never written to save files. No external requests.
|
||||||
* Plugin command "Open / Toggle Panel" toggles the overlay from events.
|
* Plugin command "Open / Toggle Panel" toggles the overlay from events.
|
||||||
*
|
|
||||||
* Credits: len — https://gitgud.io/zero64801/forge-mvmz
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* ===================================================================================
|
/* ===================================================================================
|
||||||
|
|
@ -400,7 +392,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
// expose the assembled API (UI is attached below if a real DOM is present)
|
// 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, _uiScale: 'auto' };
|
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 };
|
||||||
if (root) { root.Forge = MTC; root.__FORGE_CORE__ = true; }
|
if (root) { root.Forge = MTC; root.__FORGE_CORE__ = true; }
|
||||||
if (typeof module !== 'undefined' && module.exports) module.exports = MTC;
|
if (typeof module !== 'undefined' && module.exports) module.exports = MTC;
|
||||||
|
|
||||||
|
|
@ -424,37 +416,6 @@
|
||||||
var LS = 'forge:';
|
var LS = 'forge:';
|
||||||
function store(k, v) { try { localStorage.setItem(LS + k, JSON.stringify(v)); } catch (e) {} }
|
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 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)
|
// persisted speed settings (UI-controlled; override core/param defaults if the user changed them)
|
||||||
API._speedKey = load('speedKey', API._speedKey || 'Control');
|
API._speedKey = load('speedKey', API._speedKey || 'Control');
|
||||||
API._speedUseKey = load('speedUseKey', true);
|
API._speedUseKey = load('speedUseKey', true);
|
||||||
|
|
@ -467,7 +428,7 @@
|
||||||
if (k === 'class') n.className = props[k];
|
if (k === 'class') n.className = props[k];
|
||||||
else if (k === 'text') n.textContent = props[k];
|
else if (k === 'text') n.textContent = props[k];
|
||||||
else if (k === 'html') n.innerHTML = props[k];
|
else if (k === 'html') n.innerHTML = props[k];
|
||||||
else if (k === 'style') n.setAttribute('style', scaleStyle(props[k], uiFx));
|
else if (k === 'style') n.setAttribute('style', props[k]);
|
||||||
else if (k.slice(0, 2) === 'on' && typeof props[k] === 'function') n.addEventListener(k.slice(2).toLowerCase(), props[k]);
|
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]);
|
else if (props[k] != null) n.setAttribute(k, props[k]);
|
||||||
}
|
}
|
||||||
|
|
@ -553,17 +514,19 @@
|
||||||
.fg-confirm__box{background:var(--fg-bg-elev);border:1px solid var(--fg-border);border-radius:var(--rad-lg);padding:var(--sp-4);max-width:340px;box-shadow:var(--sh-panel)}\
|
.fg-confirm__box{background:var(--fg-bg-elev);border:1px solid var(--fg-border);border-radius:var(--rad-lg);padding:var(--sp-4);max-width:340px;box-shadow:var(--sh-panel)}\
|
||||||
.fg-confirm__box p{margin:0 0 var(--sp-4)}.fg-confirm__row{display:flex;justify-content:flex-end;gap:var(--sp-2)}\
|
.fg-confirm__box p{margin:0 0 var(--sp-4)}.fg-confirm__row{display:flex;justify-content:flex-end;gap:var(--sp-2)}\
|
||||||
input::placeholder{color:var(--fg-text-faint)}\
|
input::placeholder{color:var(--fg-text-faint)}\
|
||||||
.fg-list::-webkit-scrollbar,.fg-rail::-webkit-scrollbar,.fg-detail::-webkit-scrollbar{width:9px}\
|
::-webkit-scrollbar{width:9px;height:9px}\
|
||||||
.fg-list::-webkit-scrollbar-thumb,.fg-detail::-webkit-scrollbar-thumb{background:var(--fg-border-strong);border-radius:5px}";
|
::-webkit-scrollbar-track{background:transparent}\
|
||||||
|
::-webkit-scrollbar-thumb{background:var(--fg-border-strong);border-radius:5px}\
|
||||||
|
::-webkit-scrollbar-thumb:hover{background:var(--fg-text-faint)}";
|
||||||
|
|
||||||
// ---------- shadow host ----------
|
// ---------- shadow host ----------
|
||||||
var host = el('div', { id: 'forge-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;overflow:visible!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';
|
||||||
document.body.appendChild(host);
|
document.body.appendChild(host);
|
||||||
var shadow = host.attachShadow({ mode: 'open' });
|
var shadow = host.attachShadow({ mode: 'open' });
|
||||||
var rootEl = el('div', { id: 'forge-root' });
|
var rootEl = el('div', { id: 'forge-root' });
|
||||||
rootEl.setAttribute('data-theme', load('theme', 'dark'));
|
rootEl.setAttribute('data-theme', load('theme', 'dark'));
|
||||||
shadow.appendChild(el('style', { text: scalePxCss(CSS, uiFx) }));
|
shadow.appendChild(el('style', { text: CSS }));
|
||||||
shadow.appendChild(rootEl);
|
shadow.appendChild(rootEl);
|
||||||
|
|
||||||
// Swallow events so panel interaction never leaks to the game. BUBBLE phase: the panel's own
|
// Swallow events so panel interaction never leaks to the game. BUBBLE phase: the panel's own
|
||||||
|
|
@ -598,8 +561,8 @@ input::placeholder{color:var(--fg-text-faint)}\
|
||||||
// ---------- panel skeleton ----------
|
// ---------- panel skeleton ----------
|
||||||
var vw = window.innerWidth || 816, vh = window.innerHeight || 624;
|
var vw = window.innerWidth || 816, vh = window.innerHeight || 624;
|
||||||
// Default size fits the window (many MV games are ~816x624); leave a margin.
|
// Default size fits the window (many MV games are ~816x624); leave a margin.
|
||||||
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)) });
|
var st = load('panel', { x: 16, y: 16, w: Math.min(880, vw - 32), h: Math.min(560, vh - 32) });
|
||||||
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.w = Math.max(420, Math.min(st.w, vw - 8)); st.h = Math.max(320, Math.min(st.h, vh - 8));
|
||||||
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
|
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' });
|
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';
|
panel.style.left = st.x + 'px'; panel.style.top = st.y + 'px'; panel.style.width = st.w + 'px'; panel.style.height = st.h + 'px';
|
||||||
|
|
@ -622,8 +585,8 @@ input::placeholder{color:var(--fg-text-faint)}\
|
||||||
|
|
||||||
// ---------- launcher (docks to bottom-right corner by default; 'launcherPos' is a fresh key so
|
// ---------- launcher (docks to bottom-right corner by default; 'launcherPos' is a fresh key so
|
||||||
// any stale mid-screen position from older builds is ignored) ----------
|
// any stale mid-screen position from older builds is ignored) ----------
|
||||||
var lp = load('launcherPos', { x: vw - scalePx(56, uiFx), y: vh - scalePx(56, uiFx) });
|
var lp = load('launcherPos', { x: vw - 56, y: vh - 56 });
|
||||||
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)));
|
lp.x = Math.max(4, Math.min(lp.x, vw - 46)); lp.y = Math.max(4, Math.min(lp.y, vh - 46));
|
||||||
var launcher = el('button', { class: 'fg-launcher', text: '⚒', title: 'Forge (F10)' });
|
var launcher = el('button', { class: 'fg-launcher', text: '⚒', title: 'Forge (F10)' });
|
||||||
launcher.style.left = lp.x + 'px'; launcher.style.top = lp.y + 'px';
|
launcher.style.left = lp.x + 'px'; launcher.style.top = lp.y + 'px';
|
||||||
launcher.addEventListener('click', function (e) { if (!launcher._dragged) toggle(); });
|
launcher.addEventListener('click', function (e) { if (!launcher._dragged) toggle(); });
|
||||||
|
|
@ -636,7 +599,7 @@ input::placeholder{color:var(--fg-text-faint)}\
|
||||||
if (e.target.closest && e.target.closest('input,button.fg-iconbtn')) return;
|
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;
|
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;
|
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 - scalePx(30, uiFx), 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 - 30, 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); }
|
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);
|
document.addEventListener('mousemove', mv, true); document.addEventListener('mouseup', up, true);
|
||||||
});
|
});
|
||||||
|
|
@ -644,7 +607,7 @@ input::placeholder{color:var(--fg-text-faint)}\
|
||||||
makeDraggable(titlebar, panel, function (x, y) { st.x = x; st.y = y; store('panel', st); });
|
makeDraggable(titlebar, panel, function (x, y) { st.x = x; st.y = y; store('panel', st); });
|
||||||
resize.addEventListener('mousedown', function (e) {
|
resize.addEventListener('mousedown', function (e) {
|
||||||
e.preventDefault(); e.stopPropagation(); var sx = e.clientX, sy = e.clientY, ow = panel.offsetWidth, oh = panel.offsetHeight;
|
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(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 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 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); }
|
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);
|
document.addEventListener('mousemove', mv, true); document.addEventListener('mouseup', up, true);
|
||||||
});
|
});
|
||||||
|
|
@ -656,7 +619,7 @@ input::placeholder{color:var(--fg-text-faint)}\
|
||||||
var sizer = el('div', { class: 'fg-list__sizer' });
|
var sizer = el('div', { class: 'fg-list__sizer' });
|
||||||
var win = el('div', { class: 'fg-list__window' });
|
var win = el('div', { class: 'fg-list__window' });
|
||||||
sizer.appendChild(win); listEl.appendChild(sizer);
|
sizer.appendChild(win); listEl.appendChild(sizer);
|
||||||
var rowH = opts.rowH || scalePx(30, uiFx), pool = [], model = [], view = [], query = '';
|
var rowH = opts.rowH || 30, pool = [], model = [], view = [], query = '';
|
||||||
// ONE delegated row click (recycled rows must not accumulate per-draw listeners).
|
// 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); });
|
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(); }
|
function rebuild() { model = opts.getModel() || []; applyFilter(); }
|
||||||
|
|
@ -1119,7 +1082,6 @@ input::placeholder{color:var(--fg-text-faint)}\
|
||||||
if (window.Forge) {
|
if (window.Forge) {
|
||||||
window.Forge._hotkey = (P.hotkey || 'F10').trim();
|
window.Forge._hotkey = (P.hotkey || 'F10').trim();
|
||||||
window.Forge._itemMaxOverride = Number(P.itemMaxOverride) || 0;
|
window.Forge._itemMaxOverride = Number(P.itemMaxOverride) || 0;
|
||||||
window.Forge._uiScale = (P.uiScale || 'auto').trim();
|
|
||||||
try { if (!localStorage.getItem('forge:speedKey')) window.Forge._speedKey = (P.speedKey || 'Control').trim(); } catch (e) { window.Forge._speedKey = (P.speedKey || 'Control').trim(); }
|
try { if (!localStorage.getItem('forge:speedKey')) window.Forge._speedKey = (P.speedKey || 'Control').trim(); } catch (e) { window.Forge._speedKey = (P.speedKey || 'Control').trim(); }
|
||||||
}
|
}
|
||||||
if (typeof PluginManager !== 'undefined' && PluginManager.registerCommand) {
|
if (typeof PluginManager !== 'undefined' && PluginManager.registerCommand) {
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1 @@
|
||||||
"""Forge — in-game cheat & editor overlay for RPG Maker MV/MZ (plugin by len)."""
|
"""Forge — in-game cheat & editor overlay for RPG Maker MV/MZ (plugin by len)."""
|
||||||
|
|
||||||
from util.forge.installer import (
|
|
||||||
apply_config,
|
|
||||||
bundled_plugin_path,
|
|
||||||
detect_engine,
|
|
||||||
detect_mz,
|
|
||||||
install,
|
|
||||||
status,
|
|
||||||
uninstall,
|
|
||||||
)
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"apply_config",
|
|
||||||
"bundled_plugin_path",
|
|
||||||
"detect_engine",
|
|
||||||
"detect_mz",
|
|
||||||
"install",
|
|
||||||
"status",
|
|
||||||
"uninstall",
|
|
||||||
]
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
"""Forge plugin build — hotkey and UI scale injection at install/apply time."""
|
"""Forge plugin build — hotkey injection at install/apply time."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
@ -34,21 +34,21 @@ def _js_literal(value) -> str:
|
||||||
|
|
||||||
|
|
||||||
def plugin_entry(engine: str, hotkey: str, ui_scale: str = "auto") -> str:
|
def plugin_entry(engine: str, hotkey: str, ui_scale: str = "auto") -> str:
|
||||||
|
_ = ui_scale # reserved; vanilla Forge has no UI scale param yet
|
||||||
hk = _js_literal(hotkey.strip() or "F10")
|
hk = _js_literal(hotkey.strip() or "F10")
|
||||||
scale = _js_literal(ui_scale.strip() or "auto")
|
|
||||||
name = PLUGIN_BY_ENGINE[engine]
|
name = PLUGIN_BY_ENGINE[engine]
|
||||||
if engine == "MZ":
|
if engine == "MZ":
|
||||||
return (
|
return (
|
||||||
f' {{ "name": "{name}", "status": true, '
|
f' {{ "name": "{name}", "status": true, '
|
||||||
f'"description": "Forge — in-game cheat & editor overlay", '
|
f'"description": "Forge — in-game cheat & editor overlay", '
|
||||||
f'"parameters": {{ "hotkey": {hk}, "speedKey": "Control", '
|
f'"parameters": {{ "hotkey": {hk}, "speedKey": "Control", '
|
||||||
f'"startOpen": "false", "itemMaxOverride": "0", "uiScale": {scale} }} }}'
|
f'"startOpen": "false", "itemMaxOverride": "0" }} }}'
|
||||||
)
|
)
|
||||||
return (
|
return (
|
||||||
f' {{ "name": "{name}", "status": true, '
|
f' {{ "name": "{name}", "status": true, '
|
||||||
f'"description": "Forge — in-game cheat & editor overlay", '
|
f'"description": "Forge — in-game cheat & editor overlay", '
|
||||||
f'"parameters": {{ "Hotkey": {hk}, "SpeedKey": "Control", '
|
f'"parameters": {{ "Hotkey": {hk}, "SpeedKey": "Control", '
|
||||||
f'"StartOpen": "false", "ItemMaxOverride": "0", "UiScale": {scale} }} }}'
|
f'"StartOpen": "false", "ItemMaxOverride": "0" }} }}'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -76,80 +76,38 @@ def _patch_forge_hotkey(forge_text: str, hotkey: str, engine: str) -> str:
|
||||||
return forge_text
|
return forge_text
|
||||||
|
|
||||||
|
|
||||||
def _patch_forge_ui_scale_default(forge_text: str, ui_scale: str, engine: str) -> str:
|
def _patch_forge_runtime_hotkey(forge_text: str, hotkey: 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")
|
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":
|
if engine == "MZ":
|
||||||
forge_text = re.sub(
|
forge_text, n = re.subn(
|
||||||
r"window\.Forge\._hotkey = \(P\.hotkey \|\| '[^']*'\)\.trim\(\);",
|
r"window\.Forge\._hotkey = \(P\.hotkey \|\| '[^']*'\)\.trim\(\);",
|
||||||
f"window.Forge._hotkey = {hk};",
|
f"window.Forge._hotkey = {hk};",
|
||||||
forge_text,
|
forge_text,
|
||||||
count=1,
|
count=1,
|
||||||
)
|
)
|
||||||
forge_text = re.sub(
|
if n == 0:
|
||||||
r"window\.Forge\._uiScale = \(P\.uiScale \|\| '[^']*'\)\.trim\(\);",
|
raise ValueError("Could not patch runtime hotkey in Forge_MZ.js")
|
||||||
f"window.Forge._uiScale = {scale};",
|
|
||||||
forge_text,
|
|
||||||
count=1,
|
|
||||||
)
|
|
||||||
return forge_text
|
return forge_text
|
||||||
|
|
||||||
forge_text = re.sub(
|
forge_text, n = re.subn(
|
||||||
r"window\.Forge\._hotkey = \(P\.Hotkey \|\| '[^']*'\)\.trim\(\);",
|
r"window\.Forge\._hotkey = \(P\.Hotkey \|\| '[^']*'\)\.trim\(\);",
|
||||||
f"window.Forge._hotkey = {hk};",
|
f"window.Forge._hotkey = {hk};",
|
||||||
forge_text,
|
forge_text,
|
||||||
count=1,
|
count=1,
|
||||||
)
|
)
|
||||||
forge_text = re.sub(
|
if n == 0:
|
||||||
r"window\.Forge\._uiScale = \(P\.UiScale \|\| '[^']*'\)\.trim\(\);",
|
raise ValueError("Could not patch runtime hotkey in Forge_MV.js")
|
||||||
f"window.Forge._uiScale = {scale};",
|
|
||||||
forge_text,
|
|
||||||
count=1,
|
|
||||||
)
|
|
||||||
return forge_text
|
return forge_text
|
||||||
|
|
||||||
|
|
||||||
def prepare_forge_js(engine: str, source: Path | None = None, cfg: dict | None = None) -> str:
|
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 UI scale."""
|
"""Build Forge_MV.js or Forge_MZ.js with configured hotkey."""
|
||||||
src = source or bundled_plugin_path(engine)
|
src = source or bundled_plugin_path(engine)
|
||||||
effective = {**load_config(), **(cfg or {})}
|
effective = {**load_config(), **(cfg or {})}
|
||||||
hotkey = effective.get("forgeHotkey", "F10")
|
hotkey = effective.get("forgeHotkey", "F10")
|
||||||
ui_scale = effective.get("uiScale", "auto")
|
text = src.read_text(encoding="utf-8")
|
||||||
text = _patch_forge_hotkey(src.read_text(encoding="utf-8"), hotkey, engine)
|
text = _patch_forge_hotkey(text, hotkey, engine)
|
||||||
text = _patch_forge_ui_scale_default(text, str(ui_scale), engine)
|
text = _patch_forge_runtime_hotkey(text, hotkey, engine)
|
||||||
text = _patch_forge_mtc_defaults(text, hotkey, str(ui_scale))
|
|
||||||
text = _patch_forge_runtime(text, hotkey, str(ui_scale), engine)
|
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
191
util/forge/update_tools.py
Normal file
191
util/forge/update_tools.py
Normal file
|
|
@ -0,0 +1,191 @@
|
||||||
|
"""Download / update Forge plugins from len's upstream repo (gitgud.io).
|
||||||
|
|
||||||
|
Upstream: https://gitgud.io/zero64801/forge-mvmz
|
||||||
|
Offline copies: util/forge/upstream/
|
||||||
|
Active plugins: util/forge/Forge_MZ.js, util/forge/Forge_MV.js
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
import urllib.parse
|
||||||
|
import urllib.request
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
_PKG_ROOT = Path(__file__).resolve().parent
|
||||||
|
UPSTREAM_DIR = _PKG_ROOT / "upstream"
|
||||||
|
|
||||||
|
PLUGIN_BY_ENGINE = {
|
||||||
|
"MV": "Forge_MV",
|
||||||
|
"MZ": "Forge_MZ",
|
||||||
|
}
|
||||||
|
|
||||||
|
FORGE_PROJECT = "zero64801/forge-mvmz"
|
||||||
|
FORGE_BRANCH = "master"
|
||||||
|
GITGUD_API = "https://gitgud.io/api/v4"
|
||||||
|
VERSION_FILE = _PKG_ROOT / ".forge_version.json"
|
||||||
|
USER_AGENT = "DazedMTLTool"
|
||||||
|
|
||||||
|
|
||||||
|
def bundled_plugin_path(engine: str) -> Path:
|
||||||
|
name = PLUGIN_BY_ENGINE.get(engine)
|
||||||
|
if not name:
|
||||||
|
raise ValueError(f"Unsupported engine: {engine}")
|
||||||
|
return _PKG_ROOT / f"{name}.js"
|
||||||
|
|
||||||
|
|
||||||
|
def upstream_plugin_path(engine: str) -> Path:
|
||||||
|
return UPSTREAM_DIR / f"{PLUGIN_BY_ENGINE[engine]}.js"
|
||||||
|
|
||||||
|
|
||||||
|
def _load_versions() -> dict:
|
||||||
|
if not VERSION_FILE.is_file():
|
||||||
|
return {}
|
||||||
|
try:
|
||||||
|
return json.loads(VERSION_FILE.read_text(encoding="utf-8"))
|
||||||
|
except Exception:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def _save_versions(data: dict) -> None:
|
||||||
|
VERSION_FILE.write_text(json.dumps(data, indent=2), encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def _api_json(url: str) -> dict | list:
|
||||||
|
req = urllib.request.Request(url, headers={"User-Agent": USER_AGENT})
|
||||||
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
|
return json.loads(resp.read().decode("utf-8"))
|
||||||
|
|
||||||
|
|
||||||
|
def _download(url: str, dest: Path) -> None:
|
||||||
|
dest.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
tmp = dest.with_suffix(dest.suffix + ".part")
|
||||||
|
req = urllib.request.Request(url, headers={"User-Agent": USER_AGENT})
|
||||||
|
with urllib.request.urlopen(req, timeout=600) as resp, open(tmp, "wb") as fh:
|
||||||
|
shutil.copyfileobj(resp, fh)
|
||||||
|
tmp.replace(dest)
|
||||||
|
|
||||||
|
|
||||||
|
def _project_id() -> str:
|
||||||
|
return urllib.parse.quote(FORGE_PROJECT, safe="")
|
||||||
|
|
||||||
|
|
||||||
|
def _upstream_commit() -> str:
|
||||||
|
url = f"{GITGUD_API}/projects/{_project_id()}/repository/commits/{FORGE_BRANCH}"
|
||||||
|
data = _api_json(url)
|
||||||
|
commit = data.get("id") or data.get("sha") or ""
|
||||||
|
if not commit:
|
||||||
|
raise RuntimeError(f"Could not resolve upstream commit for {FORGE_PROJECT}")
|
||||||
|
return commit
|
||||||
|
|
||||||
|
|
||||||
|
def _raw_file_url(filename: str) -> str:
|
||||||
|
enc = urllib.parse.quote(filename, safe="")
|
||||||
|
return (
|
||||||
|
f"{GITGUD_API}/projects/{_project_id()}/repository/files/{enc}/raw"
|
||||||
|
f"?ref={FORGE_BRANCH}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _log(msg: str, log_fn) -> None:
|
||||||
|
if log_fn:
|
||||||
|
log_fn(msg)
|
||||||
|
else:
|
||||||
|
print(msg, flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
def _seed_from_offline(engine: str, log_fn) -> bool:
|
||||||
|
"""Copy bundled offline copy into the active plugin path if missing."""
|
||||||
|
dest = bundled_plugin_path(engine)
|
||||||
|
if dest.is_file():
|
||||||
|
return True
|
||||||
|
offline = upstream_plugin_path(engine)
|
||||||
|
if not offline.is_file():
|
||||||
|
return False
|
||||||
|
shutil.copy2(offline, dest)
|
||||||
|
_log(f"Using offline bundled {dest.name}", log_fn)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def _fetch_plugin(engine: str, log_fn) -> None:
|
||||||
|
name = PLUGIN_BY_ENGINE[engine]
|
||||||
|
_log(f"Downloading {name}.js from {FORGE_PROJECT}...", log_fn)
|
||||||
|
data = _download_bytes(_raw_file_url(f"{name}.js"))
|
||||||
|
bundled_plugin_path(engine).write_bytes(data)
|
||||||
|
UPSTREAM_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
upstream_plugin_path(engine).write_bytes(data)
|
||||||
|
|
||||||
|
|
||||||
|
def _download_bytes(url: str) -> bytes:
|
||||||
|
req = urllib.request.Request(url, headers={"User-Agent": USER_AGENT})
|
||||||
|
with urllib.request.urlopen(req, timeout=600) as resp:
|
||||||
|
return resp.read()
|
||||||
|
|
||||||
|
|
||||||
|
def refresh_forge_plugins(log_fn=print) -> bool:
|
||||||
|
"""Download len's latest Forge_MV.js and Forge_MZ.js."""
|
||||||
|
try:
|
||||||
|
commit = _upstream_commit()
|
||||||
|
except Exception as exc:
|
||||||
|
_log(f"ERROR: could not contact upstream ({exc})", log_fn)
|
||||||
|
return False
|
||||||
|
|
||||||
|
ok = True
|
||||||
|
for engine in PLUGIN_BY_ENGINE:
|
||||||
|
try:
|
||||||
|
_fetch_plugin(engine, log_fn)
|
||||||
|
except Exception as exc:
|
||||||
|
_log(f"ERROR: download failed for {PLUGIN_BY_ENGINE[engine]} ({exc})", log_fn)
|
||||||
|
ok = False
|
||||||
|
|
||||||
|
if not ok:
|
||||||
|
return False
|
||||||
|
|
||||||
|
versions = _load_versions()
|
||||||
|
versions["commit"] = commit
|
||||||
|
versions["branch"] = FORGE_BRANCH
|
||||||
|
_save_versions(versions)
|
||||||
|
_log(f"Forge plugins updated ({commit[:12]})", log_fn)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_forge_plugins(force: bool = False, log_fn=print) -> bool:
|
||||||
|
"""Ensure Forge plugins are present; fetch upstream when missing or stale."""
|
||||||
|
for engine in PLUGIN_BY_ENGINE:
|
||||||
|
_seed_from_offline(engine, log_fn)
|
||||||
|
|
||||||
|
missing = [e for e in PLUGIN_BY_ENGINE if not bundled_plugin_path(e).is_file()]
|
||||||
|
versions = _load_versions()
|
||||||
|
local_commit = versions.get("commit", "")
|
||||||
|
|
||||||
|
need_fetch = bool(missing) or force
|
||||||
|
if not need_fetch:
|
||||||
|
try:
|
||||||
|
need_fetch = _upstream_commit() != local_commit
|
||||||
|
except Exception as exc:
|
||||||
|
if missing:
|
||||||
|
_log(f"ERROR: Forge upstream unavailable ({exc})", log_fn)
|
||||||
|
return False
|
||||||
|
_log(f"Warning: could not check Forge update ({exc}); using local copy.", log_fn)
|
||||||
|
|
||||||
|
if need_fetch:
|
||||||
|
if refresh_forge_plugins(log_fn=log_fn):
|
||||||
|
return True
|
||||||
|
return all(bundled_plugin_path(e).is_file() for e in PLUGIN_BY_ENGINE)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
if "--refresh-offline" in sys.argv:
|
||||||
|
UPSTREAM_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
ok = refresh_forge_plugins()
|
||||||
|
return 0 if ok else 1
|
||||||
|
force = "--force" in sys.argv or "-f" in sys.argv
|
||||||
|
return 0 if ensure_forge_plugins(force=force) else 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
1084
util/forge/upstream/Forge_MV.js
Normal file
1084
util/forge/upstream/Forge_MV.js
Normal file
File diff suppressed because it is too large
Load diff
1095
util/forge/upstream/Forge_MZ.js
Normal file
1095
util/forge/upstream/Forge_MZ.js
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue