This commit is contained in:
DazedAnon 2026-06-13 15:50:25 -05:00
parent 9f206eb39d
commit 6aa3b74348
8 changed files with 120 additions and 8 deletions

View file

@ -86,3 +86,5 @@ tlEditorCmd='auto'
# Hotkeys (event.key names, e.g. F9, F10, Control)
tlHotkey='F9'
forgeHotkey='F10'
# UI scale for in-game overlays: auto (match game width) or a number like 1.5 / 2
playtestUiScale='auto'

View file

@ -2551,6 +2551,32 @@ class WorkflowTab(QWidget):
hotkey_row.addStretch(1)
settings_inner.addLayout(hotkey_row)
scale_row = QHBoxLayout()
scale_row.setSpacing(8)
scale_lbl = QLabel("UI scale:")
scale_lbl.setFixedWidth(_PT_LABEL_W)
scale_lbl.setStyleSheet(_PT_LBL_STYLE)
scale_lbl.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
scale_row.addWidget(scale_lbl)
self._pt_ui_scale_combo = QComboBox()
self._pt_ui_scale_combo.setToolTip(
"In-game overlay size. Auto scales from game resolution and display DPI."
)
for label, value in (
("Auto (match game width)", "auto"),
("100%", "1"),
("125%", "1.25"),
("150%", "1.5"),
("175%", "1.75"),
("200%", "2"),
("225%", "2.25"),
("250%", "2.5"),
):
self._pt_ui_scale_combo.addItem(label, value)
scale_row.addWidget(self._pt_ui_scale_combo, 1)
settings_inner.addLayout(scale_row)
editor_title = QLabel("Editor settings")
editor_title.setStyleSheet(_PT_SECTION_STYLE + "padding-top:2px;")
settings_inner.addWidget(editor_title)
@ -2753,11 +2779,19 @@ class WorkflowTab(QWidget):
cfg = {
"hotkey": "F9",
"forgeHotkey": "F10",
"uiScale": "auto",
"editorCmd": "auto",
}
self._pt_hotkey_edit.setText(cfg.get("hotkey", "F9"))
self._pt_forge_hotkey_edit.setText(cfg.get("forgeHotkey", "F10"))
want_scale = str(cfg.get("uiScale", "auto"))
scale_idx = self._pt_ui_scale_combo.findData(want_scale)
if scale_idx >= 0:
self._pt_ui_scale_combo.setCurrentIndex(scale_idx)
else:
custom_idx = self._pt_ui_scale_combo.findData("auto")
self._pt_ui_scale_combo.setCurrentIndex(custom_idx if custom_idx >= 0 else 0)
self._populate_tli_editor_combo(select=cfg.get("editorCmd", "auto"))
def _resolve_playtest_config(self) -> dict:
@ -2773,6 +2807,7 @@ class WorkflowTab(QWidget):
return {
"hotkey": self._pt_hotkey_edit.text().strip() or "F9",
"forgeHotkey": self._pt_forge_hotkey_edit.text().strip() or "F10",
"uiScale": str(self._pt_ui_scale_combo.currentData() or "auto"),
"editorCmd": editor,
"workspaceFolder": "auto",
}
@ -2785,7 +2820,7 @@ class WorkflowTab(QWidget):
self._log(
"✅ Playtest settings saved — "
f"inspector={cfg['hotkey']}, forge={cfg['forgeHotkey']}, "
f"editor={cfg['editorCmd']}"
f"scale={cfg['uiScale']}, editor={cfg['editorCmd']}"
)
except Exception as exc:
self._log(f"❌ Could not save playtest settings: {exc}")

View file

@ -32,6 +32,12 @@
* @min 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
* @text Open / Toggle Panel
* @desc Toggle the Forge overlay.
@ -416,6 +422,18 @@
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 w = (typeof Graphics !== 'undefined' && Graphics.width) ? Graphics.width : (window.innerWidth || base);
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));
}
// 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);
@ -524,6 +542,8 @@ input::placeholder{color:var(--fg-text-faint)}\
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(rootEl);
@ -1080,6 +1100,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();
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) {

View file

@ -22,13 +22,14 @@ def _js_literal(value) -> str:
return json.dumps(str(value))
def plugin_entry(hotkey: str) -> str:
def plugin_entry(hotkey: str, ui_scale: str = "auto") -> str:
hk = _js_literal(hotkey.strip() or "F10")
scale = _js_literal(ui_scale.strip() or "auto")
return (
f' {{ "name": "Forge_MZ", "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} }} }}'
)
@ -48,7 +49,18 @@ def _patch_forge_hotkey(forge_text: str, hotkey: str) -> str:
def prepare_forge_mz_js(source: Path | None = None, cfg: dict | None = None) -> str:
"""Build Forge_MZ.js with the configured toggle hotkey."""
"""Build Forge_MZ.js with the configured toggle hotkey and UI scale."""
src = source or BUNDLED_PLUGIN
effective = {**load_config(), **(cfg or {})}
return _patch_forge_hotkey(src.read_text(encoding="utf-8"), effective["forgeHotkey"])
text = _patch_forge_hotkey(src.read_text(encoding="utf-8"), effective["forgeHotkey"])
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)
return text

View file

@ -90,7 +90,8 @@ def install(game_root: Path, source_js: Path | None = None, cfg: dict | None = N
target.write_text(prepare_forge_mz_js(source_js, cfg), encoding="utf-8")
hotkey = (cfg or {}).get("forgeHotkey", "F10")
entry = plugin_entry(hotkey)
ui_scale = (cfg or {}).get("uiScale", "auto")
entry = plugin_entry(hotkey, ui_scale)
if not _is_declared(content):
idx = content.rfind("];")
if idx < 0:

View file

@ -9,11 +9,13 @@ from dotenv import dotenv_values
ENV_TL_HOTKEY = "tlHotkey"
ENV_FORGE_HOTKEY = "forgeHotkey"
ENV_UI_SCALE = "playtestUiScale"
ENV_EDITOR = "tlEditorCmd"
DEFAULTS = {
"hotkey": "F9",
"forgeHotkey": "F10",
"uiScale": "auto",
"editorCmd": "auto",
"workspaceFolder": "auto",
}
@ -28,6 +30,7 @@ def load_config(env_path: Path | None = None) -> dict:
for key, env_key in (
("hotkey", ENV_TL_HOTKEY),
("forgeHotkey", ENV_FORGE_HOTKEY),
("uiScale", ENV_UI_SCALE),
("editorCmd", ENV_EDITOR),
):
val = (env.get(env_key) or "").strip()
@ -42,6 +45,7 @@ def save_config(cfg: dict, env_path: Path | None = None) -> None:
updates = {
ENV_TL_HOTKEY: cfg.get("hotkey", DEFAULTS["hotkey"]),
ENV_FORGE_HOTKEY: cfg.get("forgeHotkey", DEFAULTS["forgeHotkey"]),
ENV_UI_SCALE: cfg.get("uiScale", DEFAULTS["uiScale"]),
ENV_EDITOR: cfg.get("editorCmd", DEFAULTS["editorCmd"]),
}
for key, val in updates.items():

View file

@ -72,11 +72,44 @@
historySize: 80, // how many past text lines to keep
captureUiText: true, // capture plugin / menu / UI text drawn via Bitmap.drawText
// (so e.g. status-window strings from plugins.js are locatable)
dataDirOverride: null // set an absolute path to force the data dir
dataDirOverride: null, // set an absolute path to force the data dir
uiScale: 'auto' // overlay scale: 'auto' from game width, or a number (1.5, 2, …)
};
if (!CFG.enabled) { return; }
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 w = (typeof Graphics !== 'undefined' && Graphics.width) ? Graphics.width :
(window.innerWidth || base);
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 currentUiScale() {
return resolveUiScale(CFG.uiScale);
}
function applyOverlayScale() {
var s = currentUiScale();
var z = (s === 1) ? '' : String(s);
if (ui.root) {
ui.root.style.zoom = z;
ui.root.style.transformOrigin = (ui.side === 'left') ? 'top left' : 'top right';
}
if (editor.root) {
editor.root.style.zoom = z;
editor.root.style.transformOrigin = 'top center';
}
if (ui.pickLabel) { ui.pickLabel.style.zoom = z; }
}
//=========================================================================
// Node / environment bootstrap (NW.js exposes require)
//=========================================================================
@ -1339,6 +1372,7 @@
editor.tabsEl = tabs;
editor.findEl = find;
editor.statusEl = foot.querySelector('.tl-ed-status');
applyOverlayScale();
}
//--- documents / tabs ----------------------------------------------------
@ -1781,6 +1815,7 @@
ui.root.style.left = left ? '0' : 'auto';
ui.root.style.right = left ? 'auto' : '0';
ui.root.style.boxShadow = (left ? '2px' : '-2px') + ' 0 12px rgba(0,0,0,0.6)';
applyOverlayScale();
}
function flipSide() {
@ -1941,6 +1976,7 @@
hlz.style.cssText = 'position:fixed;left:0;top:0;z-index:2147483645;pointer-events:none;display:none';
document.body.appendChild(hlz);
ui.hlLayer = hlz;
applyOverlayScale();
}
function setTabStyles() {

View file

@ -12,12 +12,13 @@ from util.playtest.config import load_config as _load_playtest_config, save_conf
ENV_EDITOR = "tlEditorCmd"
CFG_KEYS = ("editorCmd", "workspaceFolder", "hotkey")
CFG_KEYS = ("editorCmd", "workspaceFolder", "hotkey", "uiScale")
DEFAULTS = {
"editorCmd": "auto",
"workspaceFolder": "auto",
"hotkey": "F9",
"uiScale": "auto",
}
_PKG_ROOT = Path(__file__).resolve().parent