feat: integrate login flow, lobby UI and bitmap font resources

This commit is contained in:
2026-09-06 13:46:57 +08:00
parent e1bcfd91ec
commit 842c68ef55
141 changed files with 11279 additions and 1717 deletions
@@ -1,5 +1,6 @@
import { resolveRuntimeMode, type RuntimeMode } from './runtime-mode.ts';
/** 调试用登录玩家档案;值只能由测试或本地启动配置显式注入。 */
export interface DebugAccount {
openid: string;
@@ -12,6 +13,8 @@ export interface DebugAccount {
}
export interface StartupConfig {
/** 输出全部收发包、配置文件内容及连接状态/重连原因。 */
readonly debugIO: boolean;
readonly mode: RuntimeMode;
/** 配置内容来源;local 仅允许 debug,独立于 WebSocket 服务器选择。 */
readonly configSource: 'remote' | 'local';
@@ -24,6 +27,7 @@ export interface StartupConfig {
/** 唯一发布配置入口;mode 与服务器选择互相独立。 */
export const STARTUP_CONFIG: Readonly<StartupConfig> = Object.freeze({
debugIO: true,
mode: 'debug',
// 开发时改为 local,编辑 assets/resources/dev-config/update_jsonv2.txt 后重新预览。
configSource: 'local',
@@ -53,6 +57,7 @@ export function validateLocalServer(value: string, field = 'localServers'): void
export function validateStartupConfig(source: Readonly<StartupConfig>): Readonly<StartupConfig> {
if (!source || typeof source !== 'object') invalid('source', 'must be an object');
const mode = resolveRuntimeMode(source.mode);
if (typeof source.debugIO !== 'boolean') invalid('debugIO', 'must be boolean');
if (source.configSource !== 'remote' && source.configSource !== 'local') invalid('configSource', 'must be remote or local');
if (source.configSource === 'local' && mode !== 'debug') invalid('configSource', 'local is only allowed in debug mode');
if (typeof source.localConfigResource !== 'string' || !/^[a-zA-Z0-9_-]+(?:\/[a-zA-Z0-9_-]+)*$/.test(source.localConfigResource)) {
@@ -65,7 +70,7 @@ export function validateStartupConfig(source: Readonly<StartupConfig>): Readonly
if (url.protocol !== 'http:' && url.protocol !== 'https:') invalid('gameserver', 'must be http(s) URL');
if (!Array.isArray(source.localServers) || source.localServers.length === 0) invalid('localServers', 'must be a nonempty array');
for (let index = 0; index < source.localServers.length; index++) validateLocalServer(source.localServers[index], 'localServers[' + index + ']');
return Object.freeze({mode, configSource:source.configSource, localConfigResource:source.localConfigResource, gameserver:source.gameserver, useLocalServer:source.useLocalServer, localServers:Object.freeze([...source.localServers])});
return Object.freeze({mode, debugIO:source.debugIO, configSource:source.configSource, localConfigResource:source.localConfigResource, gameserver:source.gameserver, useLocalServer:source.useLocalServer, localServers:Object.freeze([...source.localServers])});
}
/** 忠实旧 min_replaceAll 链:- → /,# → :,再包成 http://...txt。 */
@@ -1,4 +1,5 @@
import type { ConfigFetcher } from './remote-config.ts';
import { STARTUP_CONFIG } from './profiles.ts';
import { ConfigFetchError, ConfigParseError } from './remote-config.ts';
type FetchImplementation = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
@@ -15,8 +16,11 @@ export class HttpConfigFetcher implements ConfigFetcher {
}
const text = await response.text();
try {
return JSON.parse(text) as unknown;
const data: unknown = JSON.parse(text);
if (STARTUP_CONFIG.debugIO) console.log('[Config REMOTE]', url, data);
return data;
} catch {
if (STARTUP_CONFIG.debugIO) console.log('[Config REMOTE]', url, { parseError: 'Invalid JSON', raw: text });
throw new ConfigParseError(`远程配置 JSON 解析失败: ${url}`);
}
}