Forgot MV
This commit is contained in:
parent
25bf5e9457
commit
9fffd9c814
8 changed files with 1337 additions and 113 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -22,6 +22,7 @@ __pycache__
|
|||
!requirements.txt
|
||||
!util/tl_inspector/TLInspector.js
|
||||
!util/forge/Forge_MZ.js
|
||||
!util/forge/Forge_MV.js
|
||||
util/ace/*.exe
|
||||
util/ace/.tools_version.json
|
||||
!util/ace/offline/*.exe
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ An AI-powered game translation tool with a GUI. Translate RPG Maker, Ren'Py, Tyr
|
|||
|
||||
- **[Sinflower](https://github.com/Sinflower)** — [RV2JSON](https://github.com/Sinflower/RV2JSON) — enables RPGMaker Ace games to be translated the same way as MV/MZ by converting rvdata2 files to JSON and back. A copy is bundled offline in `util/ace/offline/`; newer builds are downloaded when online.
|
||||
- **Sakura & Kao_SSS** — TL Inspector (`util/tl_inspector/`) — in-game translation source inspector and live-edit plugin for RPG Maker MV/MZ playtesting.
|
||||
- **Len** — [Forge](https://gitgud.io/zero64801/forge-mvmz) MZ playtest plugin (`util/forge/`), Mistral API support (provider integration and adaptive rate limiting), and batch translation mode (Anthropic Message Batches API).
|
||||
- **Len** — [Forge](https://gitgud.io/zero64801/forge-mvmz) MV/MZ playtest plugin (`util/forge/`), Mistral API support (provider integration and adaptive rate limiting), and batch translation mode (Anthropic Message Batches API).
|
||||
|
||||
## Table of Contents
|
||||
|
||||
|
|
|
|||
|
|
@ -2496,8 +2496,8 @@ class WorkflowTab(QWidget):
|
|||
|
||||
self._step8_main_hint = QLabel(
|
||||
"Install playtest plugins into your RPG Maker game. "
|
||||
"<b>TL Inspector</b> works on MV and MZ. "
|
||||
"<b>Forge</b> is MZ-only. Use <b>Install Both</b> on MZ to add both plugins at once."
|
||||
"<b>TL Inspector</b> and <b>Forge</b> work on MV and MZ. "
|
||||
"Use <b>Install Both</b> to add both plugins at once."
|
||||
)
|
||||
self._step8_main_hint.setWordWrap(True)
|
||||
self._step8_main_hint.setTextFormat(Qt.RichText)
|
||||
|
|
@ -2640,10 +2640,10 @@ class WorkflowTab(QWidget):
|
|||
|
||||
action_row.addStretch(1)
|
||||
|
||||
self._install_both_btn = _make_btn("⬇ Install Both (MZ)", "#3a5a7a")
|
||||
self._install_both_btn = _make_btn("⬇ Install Both", "#3a5a7a")
|
||||
self._install_both_btn.setFixedHeight(30)
|
||||
self._install_both_btn.setToolTip(
|
||||
"Install TL Inspector and Forge as separate plugins (MZ only)"
|
||||
"Install TL Inspector and Forge as separate plugins (MV/MZ)"
|
||||
)
|
||||
self._install_both_btn.clicked.connect(self._install_both_playtest)
|
||||
action_row.addWidget(self._install_both_btn)
|
||||
|
|
@ -2689,11 +2689,11 @@ class WorkflowTab(QWidget):
|
|||
forge_section_layout.setContentsMargins(0, 4, 0, 0)
|
||||
forge_section_layout.setSpacing(8)
|
||||
|
||||
forge_title = QLabel("Forge (MZ only)")
|
||||
forge_title = QLabel("Forge (MV / MZ)")
|
||||
forge_title.setStyleSheet(_PT_SECTION_STYLE)
|
||||
forge_section_layout.addWidget(forge_title)
|
||||
|
||||
self._forge_status_label = QLabel("Status: (MZ project required)")
|
||||
self._forge_status_label = QLabel("Status: (MV or MZ project required)")
|
||||
self._forge_status_label.setWordWrap(True)
|
||||
self._forge_status_label.setStyleSheet("color:#7a7a7a;font-size:13px;")
|
||||
forge_section_layout.addWidget(self._forge_status_label)
|
||||
|
|
@ -2840,13 +2840,13 @@ class WorkflowTab(QWidget):
|
|||
return
|
||||
cfg = self._resolve_playtest_config()
|
||||
try:
|
||||
from util.forge.installer import detect_mz
|
||||
from util.forge.installer import detect_engine
|
||||
from util.playtest.config import save_config
|
||||
|
||||
save_config(cfg)
|
||||
is_mz = detect_mz(Path(game_root)) is not None
|
||||
info = detect_engine(Path(game_root))
|
||||
msgs: list[str] = []
|
||||
if is_mz:
|
||||
if info is not None:
|
||||
from util.forge.installer import apply_config as apply_forge
|
||||
from util.forge.installer import status as forge_status
|
||||
if forge_status(Path(game_root)).get("plugin_file"):
|
||||
|
|
@ -2871,16 +2871,7 @@ class WorkflowTab(QWidget):
|
|||
"""Update Step 8 status labels for the current engine."""
|
||||
if getattr(self, "_step8_playtest_box", None) is not None:
|
||||
self._refresh_tl_inspector_status()
|
||||
game_root = self.folder_edit.text().strip()
|
||||
is_mz = False
|
||||
if game_root:
|
||||
try:
|
||||
from util.forge.installer import detect_mz
|
||||
is_mz = detect_mz(Path(game_root)) is not None
|
||||
except Exception:
|
||||
is_mz = False
|
||||
if is_mz:
|
||||
self._refresh_forge_status()
|
||||
self._refresh_forge_status()
|
||||
|
||||
def _on_tli_editor_combo_changed(self, _index: int | None = None):
|
||||
custom = self._tli_editor_combo.currentData() == "__custom__"
|
||||
|
|
@ -3006,9 +2997,9 @@ class WorkflowTab(QWidget):
|
|||
label.setStyleSheet("color:#7a7a7a;font-size:13px;")
|
||||
return
|
||||
try:
|
||||
from util.forge.installer import detect_mz, status
|
||||
if detect_mz(Path(game_root)) is None:
|
||||
label.setText("Status: not an MZ project (Forge is MZ-only).")
|
||||
from util.forge.installer import detect_engine, status
|
||||
if detect_engine(Path(game_root)) is None:
|
||||
label.setText("Status: not an MV/MZ project.")
|
||||
label.setStyleSheet("color:#e9a12a;font-size:13px;")
|
||||
return
|
||||
st = status(Path(game_root))
|
||||
|
|
@ -3017,6 +3008,7 @@ class WorkflowTab(QWidget):
|
|||
label.setStyleSheet("color:#f48771;font-size:13px;")
|
||||
return
|
||||
|
||||
engine = st.get("engine", "?")
|
||||
msg = st.get("message", "")
|
||||
if st.get("declared") and st.get("plugin_file"):
|
||||
detail = "plugin declared in plugins.js and file present"
|
||||
|
|
@ -3026,7 +3018,7 @@ class WorkflowTab(QWidget):
|
|||
detail = "plugin file present (not declared in plugins.js)"
|
||||
else:
|
||||
detail = "not installed"
|
||||
label.setText(f"Status: RPG Maker MZ · {msg} — {detail}")
|
||||
label.setText(f"Status: RPG Maker {engine} · {msg} — {detail}")
|
||||
color = "#6a9a6a" if st.get("declared") and st.get("plugin_file") else "#9d9d9d"
|
||||
label.setStyleSheet(f"color:{color};font-size:13px;")
|
||||
|
||||
|
|
@ -3053,9 +3045,9 @@ class WorkflowTab(QWidget):
|
|||
self._log("⚠ No game folder set. Complete Step 0 first.")
|
||||
return
|
||||
try:
|
||||
from util.forge.installer import detect_mz
|
||||
if detect_mz(Path(game_root)) is None:
|
||||
self._log("⚠ Install Both is for MZ projects only.")
|
||||
from util.forge.installer import detect_engine
|
||||
if detect_engine(Path(game_root)) is None:
|
||||
self._log("⚠ Install Both requires an MV or MZ project.")
|
||||
return
|
||||
except Exception as exc:
|
||||
self._log(f"❌ Could not detect engine: {exc}")
|
||||
|
|
@ -3087,7 +3079,7 @@ class WorkflowTab(QWidget):
|
|||
reply = QMessageBox.question(
|
||||
self,
|
||||
"Uninstall Forge",
|
||||
"Remove Forge_MZ from plugins.js and delete the plugin file?",
|
||||
"Remove Forge from plugins.js and delete the plugin file?",
|
||||
QMessageBox.Yes | QMessageBox.No,
|
||||
QMessageBox.No,
|
||||
)
|
||||
|
|
@ -3215,30 +3207,12 @@ class WorkflowTab(QWidget):
|
|||
if is_ace and self._step_tabs.currentIndex() == 8:
|
||||
self._step_tabs.setCurrentIndex(7)
|
||||
box = getattr(self, "_step8_playtest_box", None)
|
||||
forge_section = getattr(self, "_step8_forge_section", None)
|
||||
is_mz = False
|
||||
if not is_ace:
|
||||
game_root = self.folder_edit.text().strip()
|
||||
if game_root:
|
||||
try:
|
||||
from util.forge.installer import detect_mz
|
||||
is_mz = detect_mz(Path(game_root)) is not None
|
||||
except Exception:
|
||||
is_mz = False
|
||||
install_both_btn = getattr(self, "_install_both_btn", None)
|
||||
if box is not None:
|
||||
box.setVisible(not is_ace)
|
||||
box.setEnabled(not is_ace)
|
||||
if forge_section is not None:
|
||||
forge_section.setVisible(is_mz)
|
||||
install_both_btn = getattr(self, "_install_both_btn", None)
|
||||
if install_both_btn is not None:
|
||||
install_both_btn.setVisible(is_mz)
|
||||
forge_hk_lbl = getattr(self, "_pt_forge_hotkey_lbl", None)
|
||||
forge_hk_edit = getattr(self, "_pt_forge_hotkey_edit", None)
|
||||
if forge_hk_lbl is not None:
|
||||
forge_hk_lbl.setVisible(is_mz)
|
||||
if forge_hk_edit is not None:
|
||||
forge_hk_edit.setVisible(is_mz)
|
||||
install_both_btn.setVisible(not is_ace)
|
||||
if not is_ace:
|
||||
self._refresh_playtest_status()
|
||||
|
||||
|
|
|
|||
96
util/forge/Forge_MV.bat
Normal file
96
util/forge/Forge_MV.bat
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
@echo off
|
||||
rem ============================================================================
|
||||
rem Forge_MV installer / uninstaller (RPG Maker MV only)
|
||||
rem Credits: len — https://gitgud.io/zero64801/forge-mvmz (Forge plugin)
|
||||
rem Put this file and Forge_MV.js in the GAME ROOT (next to the .exe /
|
||||
rem index.html), then double-click this file.
|
||||
rem ============================================================================
|
||||
chcp 65001 >nul
|
||||
setlocal
|
||||
set "FORGE_ROOT=%~dp0"
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -Command "$l=Get-Content -LiteralPath '%~f0'; $i=[Array]::IndexOf($l,'#:PSSTART'); Invoke-Expression (($l[($i+1)..($l.Count-1)]) -join [char]10)"
|
||||
endlocal
|
||||
exit /b
|
||||
#:PSSTART
|
||||
$ErrorActionPreference = 'Stop'
|
||||
try { [Console]::OutputEncoding = [Text.Encoding]::UTF8 } catch {}
|
||||
function PauseExit { [void](Read-Host "`nPress Enter to exit"); exit }
|
||||
|
||||
$root = $env:FORGE_ROOT
|
||||
Write-Host "============================================"
|
||||
Write-Host " Forge_MV installer (RPG Maker MV only)"
|
||||
Write-Host "============================================"
|
||||
|
||||
$mvJs = Join-Path $root 'www\js\plugins.js'
|
||||
if (Test-Path -LiteralPath $mvJs) {
|
||||
$pluginsJs = $mvJs; $pluginsDir = Join-Path $root 'www\js\plugins'
|
||||
} else {
|
||||
Write-Host ""
|
||||
Write-Host "ERROR: No RPG Maker MV game found here." -ForegroundColor Red
|
||||
Write-Host "Could not find 'www\js\plugins.js'."
|
||||
Write-Host "Place this installer and Forge_MV.js in the game ROOT folder."
|
||||
PauseExit
|
||||
}
|
||||
Write-Host "Detected RPG Maker MV"
|
||||
Write-Host ("plugins list: {0}" -f $pluginsJs)
|
||||
|
||||
$target = Join-Path $pluginsDir 'Forge_MV.js'
|
||||
$srcRoot = Join-Path $root 'Forge_MV.js'
|
||||
$utf8 = New-Object System.Text.UTF8Encoding($false)
|
||||
$content = [IO.File]::ReadAllText($pluginsJs)
|
||||
$nl = if ($content -match "`r`n") { "`r`n" } else { "`n" }
|
||||
$declared = [regex]::IsMatch($content, '"name"\s*:\s*"Forge_MV"')
|
||||
$fileThere = Test-Path -LiteralPath $target
|
||||
|
||||
if ($declared -or $fileThere) {
|
||||
Write-Host ""
|
||||
Write-Host "Forge_MV is currently INSTALLED." -ForegroundColor Yellow
|
||||
$ans = Read-Host "Remove it? (Y/N)"
|
||||
if ($ans -match '^(y|yes)$') {
|
||||
if ($declared) {
|
||||
$kept = ($content -split "`r?`n") | Where-Object { $_ -notmatch '"name"\s*:\s*"Forge_MV"' }
|
||||
$newText = ($kept -join $nl)
|
||||
$newText = [regex]::Replace($newText, ',(\s*)\];', '$1];')
|
||||
[IO.File]::WriteAllText($pluginsJs, $newText, $utf8)
|
||||
Write-Host "Removed the Forge_MV entry from plugins.js"
|
||||
}
|
||||
if ($fileThere) {
|
||||
Remove-Item -LiteralPath $target -Force
|
||||
Write-Host ("Deleted {0}" -f $target)
|
||||
}
|
||||
Write-Host ""
|
||||
Write-Host "Uninstalled." -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "Cancelled - nothing changed."
|
||||
}
|
||||
PauseExit
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $srcRoot)) {
|
||||
Write-Host ""
|
||||
Write-Host "ERROR: Forge_MV.js was not found next to this installer." -ForegroundColor Red
|
||||
Write-Host ("Expected: {0}" -f $srcRoot)
|
||||
PauseExit
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath $pluginsDir)) {
|
||||
New-Item -ItemType Directory -Path $pluginsDir -Force | Out-Null
|
||||
}
|
||||
Copy-Item -LiteralPath $srcRoot -Destination $target -Force
|
||||
Write-Host ("Copied plugin -> {0}" -f $target)
|
||||
|
||||
$entry = ' { "name": "Forge_MV", "status": true, "description": "Forge — in-game cheat & editor overlay", "parameters": {} }'
|
||||
$idx = $content.LastIndexOf('];')
|
||||
if ($idx -lt 0) {
|
||||
Write-Host ""
|
||||
Write-Host "ERROR: could not find the end of the plugin list ( ]; ) in plugins.js." -ForegroundColor Red
|
||||
PauseExit
|
||||
}
|
||||
$before = $content.Substring(0, $idx).TrimEnd()
|
||||
$after = $content.Substring($idx)
|
||||
$sep = if ($before.EndsWith(',')) { $nl } else { ',' + $nl }
|
||||
$newText = $before + $sep + $entry + $nl + ' ' + $after
|
||||
[IO.File]::WriteAllText($pluginsJs, $newText, $utf8)
|
||||
Write-Host "Declared Forge_MV at the end of plugins.js"
|
||||
Write-Host ""
|
||||
Write-Host "Installed! Start the game and press F10 to open Forge." -ForegroundColor Green
|
||||
PauseExit
|
||||
1085
util/forge/Forge_MV.js
Normal file
1085
util/forge/Forge_MV.js
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,10 +1,19 @@
|
|||
"""Forge — in-game cheat & editor overlay for RPG Maker 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_mz, install, status, uninstall
|
||||
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",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
"""Forge_MZ build — hotkey injection at install time."""
|
||||
"""Forge plugin build — hotkey (and MZ UI scale) injection at install time."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
|
@ -9,7 +9,18 @@ from pathlib import Path
|
|||
from util.playtest.config import load_config
|
||||
|
||||
_PKG_ROOT = Path(__file__).resolve().parent
|
||||
BUNDLED_PLUGIN = _PKG_ROOT / "Forge_MZ.js"
|
||||
|
||||
PLUGIN_BY_ENGINE = {
|
||||
"MV": "Forge_MV",
|
||||
"MZ": "Forge_MZ",
|
||||
}
|
||||
|
||||
|
||||
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 _js_literal(value) -> str:
|
||||
|
|
@ -22,37 +33,60 @@ def _js_literal(value) -> str:
|
|||
return json.dumps(str(value))
|
||||
|
||||
|
||||
def plugin_entry(hotkey: str, ui_scale: str = "auto") -> 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", '
|
||||
f'"parameters": {{ "hotkey": {hk}, "speedKey": "Control", '
|
||||
f'"startOpen": "false", "itemMaxOverride": "0", "uiScale": {scale} }} }}'
|
||||
)
|
||||
return (
|
||||
f' {{ "name": "Forge_MZ", "status": true, '
|
||||
f' {{ "name": "{name}", "status": true, '
|
||||
f'"description": "Forge — in-game cheat & editor overlay", '
|
||||
f'"parameters": {{ "hotkey": {hk}, "speedKey": "Control", '
|
||||
f'"startOpen": "false", "itemMaxOverride": "0", "uiScale": {scale} }} }}'
|
||||
f'"parameters": {{ "Hotkey": {hk}, "SpeedKey": "Control", '
|
||||
f'"StartOpen": "false", "ItemMaxOverride": "0" }} }}'
|
||||
)
|
||||
|
||||
|
||||
def _patch_forge_hotkey(forge_text: str, hotkey: str) -> str:
|
||||
def _patch_forge_hotkey(forge_text: str, hotkey: str, engine: str) -> str:
|
||||
hk = hotkey.strip() or "F10"
|
||||
if engine == "MZ":
|
||||
pattern = (
|
||||
r"(\* @param hotkey\s*\n"
|
||||
r"(?:\s*\*[^\n]*\n)*?"
|
||||
r"\s*\* @default )F10"
|
||||
)
|
||||
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 = (
|
||||
r"(\* @param hotkey\s*\n"
|
||||
r"(\* @param Hotkey\s*\n"
|
||||
r"(?:\s*\*[^\n]*\n)*?"
|
||||
r"\s*\* @default )F10"
|
||||
)
|
||||
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)
|
||||
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 prepare_forge_mz_js(source: Path | None = None, cfg: dict | None = None) -> str:
|
||||
"""Build Forge_MZ.js with the configured toggle hotkey and UI scale."""
|
||||
src = source or BUNDLED_PLUGIN
|
||||
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)."""
|
||||
src = source or bundled_plugin_path(engine)
|
||||
effective = {**load_config(), **(cfg or {})}
|
||||
text = _patch_forge_hotkey(src.read_text(encoding="utf-8"), effective["forgeHotkey"])
|
||||
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",
|
||||
|
|
@ -64,3 +98,8 @@ def prepare_forge_mz_js(source: Path | None = None, cfg: dict | None = None) ->
|
|||
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
|
||||
|
||||
|
||||
# Back-compat alias
|
||||
def prepare_forge_mz_js(source: Path | None = None, cfg: dict | None = None) -> str:
|
||||
return prepare_forge_js("MZ", source, cfg)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
"""Install / uninstall Forge_MZ into an RPG Maker MZ game folder.
|
||||
"""Install / uninstall Forge into an RPG Maker MV or MZ game folder.
|
||||
|
||||
Credits: len — https://gitgud.io/zero64801/forge-mvmz (Forge plugin)
|
||||
"""
|
||||
|
|
@ -8,25 +8,39 @@ from __future__ import annotations
|
|||
import re
|
||||
from pathlib import Path
|
||||
|
||||
from util.forge.config import plugin_entry, prepare_forge_mz_js
|
||||
|
||||
PLUGIN_NAME = "Forge_MZ"
|
||||
from util.forge.config import PLUGIN_BY_ENGINE, plugin_entry, prepare_forge_js
|
||||
|
||||
_PKG_ROOT = Path(__file__).resolve().parent
|
||||
DEFAULT_PLUGIN_SRC = _PKG_ROOT / "Forge_MZ.js"
|
||||
|
||||
|
||||
def bundled_plugin_path() -> Path:
|
||||
return DEFAULT_PLUGIN_SRC
|
||||
def bundled_plugin_path(engine: str) -> Path:
|
||||
from util.forge.config import bundled_plugin_path as _path
|
||||
|
||||
return _path(engine)
|
||||
|
||||
|
||||
def detect_engine(game_root: Path) -> tuple[str, Path, Path] | None:
|
||||
"""Return (engine, plugins_js, plugins_dir) or None if not MV/MZ."""
|
||||
root = Path(game_root)
|
||||
mv_js = root / "www" / "js" / "plugins.js"
|
||||
mz_js = root / "js" / "plugins.js"
|
||||
if mv_js.is_file():
|
||||
return "MV", mv_js, root / "www" / "js" / "plugins"
|
||||
if mz_js.is_file():
|
||||
return "MZ", mz_js, root / "js" / "plugins"
|
||||
return None
|
||||
|
||||
|
||||
def detect_mz(game_root: Path) -> tuple[Path, Path] | None:
|
||||
"""Return (plugins_js, plugins_dir) or None if not MZ."""
|
||||
root = Path(game_root)
|
||||
mz_js = root / "js" / "plugins.js"
|
||||
if mz_js.is_file():
|
||||
return mz_js, root / "js" / "plugins"
|
||||
return None
|
||||
"""Return (plugins_js, plugins_dir) for MZ only, or None."""
|
||||
info = detect_engine(game_root)
|
||||
if info is None or info[0] != "MZ":
|
||||
return None
|
||||
return info[1], info[2]
|
||||
|
||||
|
||||
def _plugin_name(engine: str) -> str:
|
||||
return PLUGIN_BY_ENGINE[engine]
|
||||
|
||||
|
||||
def _read_plugins_js(plugins_js: Path) -> tuple[str, str]:
|
||||
|
|
@ -35,13 +49,13 @@ def _read_plugins_js(plugins_js: Path) -> tuple[str, str]:
|
|||
return content, nl
|
||||
|
||||
|
||||
def _is_declared(content: str) -> bool:
|
||||
return bool(re.search(rf'"name"\s*:\s*"{re.escape(PLUGIN_NAME)}"', content))
|
||||
def _is_declared(content: str, plugin_name: str) -> bool:
|
||||
return bool(re.search(rf'"name"\s*:\s*"{re.escape(plugin_name)}"', content))
|
||||
|
||||
|
||||
def status(game_root: Path) -> dict:
|
||||
"""Return install state for the game folder."""
|
||||
info = detect_mz(game_root)
|
||||
info = detect_engine(game_root)
|
||||
if info is None:
|
||||
return {
|
||||
"ok": False,
|
||||
|
|
@ -49,16 +63,17 @@ def status(game_root: Path) -> dict:
|
|||
"installed": False,
|
||||
"declared": False,
|
||||
"plugin_file": None,
|
||||
"message": "No RPG Maker MZ game found (missing js/plugins.js).",
|
||||
"message": "No RPG Maker MV/MZ game found (missing plugins.js).",
|
||||
}
|
||||
plugins_js, plugins_dir = info
|
||||
target = plugins_dir / f"{PLUGIN_NAME}.js"
|
||||
engine, plugins_js, plugins_dir = info
|
||||
plugin_name = _plugin_name(engine)
|
||||
target = plugins_dir / f"{plugin_name}.js"
|
||||
content, _ = _read_plugins_js(plugins_js)
|
||||
declared = _is_declared(content)
|
||||
declared = _is_declared(content, plugin_name)
|
||||
file_there = target.is_file()
|
||||
return {
|
||||
"ok": True,
|
||||
"engine": "MZ",
|
||||
"engine": engine,
|
||||
"installed": declared or file_there,
|
||||
"declared": declared,
|
||||
"plugin_file": file_there,
|
||||
|
|
@ -75,27 +90,30 @@ def status(game_root: Path) -> dict:
|
|||
|
||||
|
||||
def install(game_root: Path, source_js: Path | None = None, cfg: dict | None = None) -> tuple[bool, str]:
|
||||
"""Copy Forge_MZ.js into the game and declare it in plugins.js."""
|
||||
info = detect_mz(game_root)
|
||||
"""Copy Forge_MV.js or Forge_MZ.js into the game and declare it in plugins.js."""
|
||||
info = detect_engine(game_root)
|
||||
if info is None:
|
||||
return False, "No RPG Maker MZ game found at that path (Forge is MZ-only)."
|
||||
return False, "No RPG Maker MV/MZ game found at that path."
|
||||
|
||||
engine, plugins_js, plugins_dir = info
|
||||
plugin_name = _plugin_name(engine)
|
||||
default_src = bundled_plugin_path(engine)
|
||||
|
||||
plugins_js, plugins_dir = info
|
||||
if source_js and not Path(source_js).is_file():
|
||||
return False, f"Forge_MZ.js not found: {source_js}"
|
||||
if not source_js and not DEFAULT_PLUGIN_SRC.is_file():
|
||||
return False, f"Forge_MZ.js not found: {DEFAULT_PLUGIN_SRC}"
|
||||
return False, f"{plugin_name}.js not found: {source_js}"
|
||||
if not source_js and not default_src.is_file():
|
||||
return False, f"{plugin_name}.js not found: {default_src}"
|
||||
|
||||
target = plugins_dir / f"{PLUGIN_NAME}.js"
|
||||
target = plugins_dir / f"{plugin_name}.js"
|
||||
content, nl = _read_plugins_js(plugins_js)
|
||||
|
||||
plugins_dir.mkdir(parents=True, exist_ok=True)
|
||||
target.write_text(prepare_forge_mz_js(source_js, cfg), encoding="utf-8")
|
||||
target.write_text(prepare_forge_js(engine, source_js, cfg), encoding="utf-8")
|
||||
|
||||
hotkey = (cfg or {}).get("forgeHotkey", "F10")
|
||||
ui_scale = (cfg or {}).get("uiScale", "auto")
|
||||
entry = plugin_entry(hotkey, ui_scale)
|
||||
if not _is_declared(content):
|
||||
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."
|
||||
|
|
@ -105,23 +123,24 @@ def install(game_root: Path, source_js: Path | None = None, cfg: dict | None = N
|
|||
content = before + sep + entry + nl + " " + after
|
||||
plugins_js.write_text(content, encoding="utf-8", newline="")
|
||||
|
||||
return True, f"Forge installed for RPG Maker MZ. Press {hotkey} in-game to open."
|
||||
return True, f"Forge installed for RPG Maker {engine}. Press {hotkey} in-game to open."
|
||||
|
||||
|
||||
def uninstall(game_root: Path) -> tuple[bool, str]:
|
||||
"""Remove Forge_MZ from plugins.js and delete the plugin file."""
|
||||
info = detect_mz(game_root)
|
||||
"""Remove Forge from plugins.js and delete the plugin file."""
|
||||
info = detect_engine(game_root)
|
||||
if info is None:
|
||||
return False, "No RPG Maker MZ game found at that path."
|
||||
return False, "No RPG Maker MV/MZ game found at that path."
|
||||
|
||||
plugins_js, plugins_dir = info
|
||||
target = plugins_dir / f"{PLUGIN_NAME}.js"
|
||||
engine, plugins_js, plugins_dir = info
|
||||
plugin_name = _plugin_name(engine)
|
||||
target = plugins_dir / f"{plugin_name}.js"
|
||||
content, nl = _read_plugins_js(plugins_js)
|
||||
|
||||
if _is_declared(content):
|
||||
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)
|
||||
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)
|
||||
|
|
@ -134,15 +153,16 @@ def uninstall(game_root: Path) -> tuple[bool, str]:
|
|||
|
||||
|
||||
def apply_config(game_root: Path, cfg: dict | None = None) -> tuple[bool, str]:
|
||||
"""Rewrite an installed Forge_MZ.js with the current Forge hotkey."""
|
||||
info = detect_mz(game_root)
|
||||
"""Rewrite an installed Forge plugin with current playtest settings."""
|
||||
info = detect_engine(game_root)
|
||||
if info is None:
|
||||
return False, "No RPG Maker MZ game found at that path."
|
||||
return False, "No RPG Maker MV/MZ game found at that path."
|
||||
|
||||
_, plugins_dir = info
|
||||
target = plugins_dir / f"{PLUGIN_NAME}.js"
|
||||
engine, _, plugins_dir = info
|
||||
plugin_name = _plugin_name(engine)
|
||||
target = plugins_dir / f"{plugin_name}.js"
|
||||
if not target.is_file():
|
||||
return False, "Forge is not installed in this game folder."
|
||||
|
||||
target.write_text(prepare_forge_mz_js(cfg=cfg), encoding="utf-8")
|
||||
return True, "Forge hotkey applied to the installed plugin."
|
||||
target.write_text(prepare_forge_js(engine, cfg=cfg), encoding="utf-8")
|
||||
return True, f"Forge settings applied to the installed {plugin_name} plugin."
|
||||
|
|
|
|||
Loading…
Reference in a new issue