小井盖改为自绘 GM 窗口(WebView2)+ 背包可见 + 启动器互斥

- 90CN.dll:新增 90CN_gm_window.cpp,自绘标题栏/边框的置顶窗口,内嵌 WebView2 承载 gmweb 页面
- 点小井盖时隐藏游戏内嵌 IE 面板、弹出控制台;关掉后还原,独占全屏压不住时可选 minimize_game
- 窗口每轮确认置顶,避免游戏抢窗口栈后消失
- gmweb:新增背包页,发放后自动跳转并高亮新入包的行(GET /local/dnf-gm/inventory/list)
- 启动器:新增 /console-status,控制台运行时置灰 GM 工具入口并阻止进入
- 服务端指纹回绑新 DLL(1258496 / 7A179B1B…)
This commit is contained in:
hanfuit committed 2026-11-05 13:16:52 +08:00
1 parent e9a3952a9d
commit b22579658e
10 files changed
+1246 -97

No files matched your search

+32
View File
@@ -41,6 +41,28 @@ DETACHED_PROCESS = 0x00000008
DEFAULT_PORT = 19527 # 启动器本地端口
# 游戏内「简易 GM 控制台」的窗口类名(90CN.dll 自绘窗口,见 client-patch/90CN_gm_window.cpp)。
# 它和这里的完整 GM 工具是两套东西:控制台是精简版、只能发物品/调角色,
# 同时开着会互相覆盖同一个角色的数据,所以控制台在运行时把 GM 工具入口锁掉。
CONSOLE_WINDOW_CLASS = "90CN_GMHostWindow"
def console_running() -> bool:
"""游戏内简易 GM 控制台是否正在显示(跨进程按窗口类名查,不需要共享状态文件)。"""
try:
u32 = ctypes.WinDLL("user32", use_last_error=True)
u32.FindWindowW.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p]
u32.FindWindowW.restype = ctypes.c_void_p
u32.IsWindowVisible.argtypes = [ctypes.c_void_p]
u32.IsWindowVisible.restype = ctypes.c_bool
hwnd = u32.FindWindowW(CONSOLE_WINDOW_CLASS, None)
if not hwnd:
return False
return bool(u32.IsWindowVisible(hwnd))
except Exception:
return False
# 更新检查配置:Gitea 自托管仓库的 Releases 地址
UPDATE_REPO_API = "https://git.hanfuit.com/api/v1/repos/Lingbao/90CNServer/releases"
UPDATE_REPO_WEB = "https://git.hanfuit.com/Lingbao/90CNServer/releases"
@@ -549,6 +571,8 @@ class Handler(BaseHTTPRequestHandler):
# 静态资源
if path in ("/", "/index.html"):
self._send_file(os.path.join(RES_DIR, "index.html"), "text/html; charset=utf-8")
elif path == "/console-status":
self._api_console_status()
elif path == "/style.css":
self._send_file(os.path.join(RES_DIR, "style.css"), "text/css; charset=utf-8")
elif path == "/shell.css":
@@ -712,6 +736,14 @@ class Handler(BaseHTTPRequestHandler):
self._json({"error": "not found"}, 404)
# ==================== 启动器接口 ====================
def _api_console_status(self):
running = console_running()
self._json({
"ok": True,
"running": running,
"reason": "游戏内简易 GM 控制台正在运行,先关掉它再用完整 GM 工具" if running else "",
})
def _api_status(self):
server_dir = os.path.join(SERVER_ROOT, "Server")
client_dir = os.path.join(SERVER_ROOT, "Client")
+4
View File
@@ -389,3 +389,7 @@ body.gm-fullscreen .win-resize{display:none}
.gs-switch.on{background:var(--primary)}
.gs-switch.on .gs-knob{left:22px}
/* 简易 GM 控制台运行时,完整 GM 工具入口置灰不可点 */
.shell-nav-item.nav-disabled{opacity:.38;cursor:not-allowed;filter:grayscale(1)}
.shell-nav-item.nav-disabled:hover{background:transparent;color:inherit}
+47 -1
View File
@@ -11,6 +11,11 @@
function shellGo(page) {
if (!pages.includes(page)) page = 'start';
// 控制台在跑时不允许进 GM(除了点按钮,开始页等地方也可能跳过去)
if (page === 'gm' && window.shellConsoleBusy && window.shellConsoleBusy()) {
shellTip('游戏内简易 GM 控制台正在运行,先关掉它再用完整 GM 工具', 'err');
return;
}
document.querySelectorAll('.shell-page').forEach(el => el.classList.toggle('active', el.id === 'page-' + page));
document.querySelectorAll('.shell-nav-item').forEach(el => el.classList.toggle('active', el.dataset.shellPage === page));
$('shellCrumb').textContent = crumb[page] || page;
@@ -51,9 +56,50 @@
window.shellExitGm = shellExitGm;
document.querySelectorAll('.shell-nav-item').forEach(el => {
el.addEventListener('click', () => shellGo(el.dataset.shellPage));
el.addEventListener('click', () => {
if (el.classList.contains('nav-disabled')) {
shellTip(el.getAttribute('title') || '暂时不可用', 'err');
return;
}
shellGo(el.dataset.shellPage);
});
});
// 简易 GM 控制台(游戏内那个自绘窗口)在运行时,锁掉完整 GM 工具入口:
// 两边改的是同一份角色数据,同时开会互相覆盖。控制台是精简版,优先保它。
let consoleBusy = false;
function shellTip(msg, type) {
if (typeof gmToast === 'function') { gmToast(msg, type || 'info'); return; }
alert(msg);
}
function applyConsoleBusy(busy) {
const btn = document.querySelector('.shell-nav-item[data-shell-page="gm"]');
if (!btn) return;
btn.classList.toggle('nav-disabled', busy);
btn.setAttribute('title', busy
? '游戏内简易 GM 控制台正在运行,先关掉它再用完整 GM 工具'
: '');
if (busy && document.body.classList.contains('gm-fullscreen')) {
shellExitGm();
shellTip('简易 GM 控制台已启动,完整 GM 工具已自动退出', 'info');
}
}
function pollConsoleStatus() {
fetch('/console-status', { cache: 'no-store' })
.then(r => r.json())
.then(d => {
const busy = !!(d && d.running);
if (busy !== consoleBusy) {
consoleBusy = busy;
applyConsoleBusy(busy);
}
})
.catch(() => {});
}
window.shellConsoleBusy = () => consoleBusy;
setInterval(pollConsoleStatus, 1500);
setTimeout(pollConsoleStatus, 300);
// 顶部窗口按钮
const win = () => (window.pywebview && window.pywebview.api) || null;
const bMin = $('shellWinMin'); if (bMin) bMin.addEventListener('click', () => { const w = win(); if (w && w.minimize) w.minimize(); });