From 0d1e2d00c5dcf2f94a1da931b6d6274b423fda83 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Sat, 24 Oct 2026 17:24:01 +0800 Subject: [PATCH] =?UTF-8?q?feat(state):=20=E8=BF=81=E7=A7=BB=20graphicsSet?= =?UTF-8?q?tings/controlSettings/battleSettings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 3DTank/docs/KtRebuild_Plan.md | 22 ++- 3DTank/names/root_wiring.json | 43 +++-- 3DTank/tools/gen_root_wiring.js | 15 ++ .../State/Reducers/BattleSettingsReducer.kt | 46 +++++ .../State/Reducers/ControlSettingsReducer.kt | 70 ++++++++ .../State/Reducers/GraphicsSettingsReducer.kt | 51 ++++++ .../jsMain/kotlin/App/State/RootReducer.kt | 13 +- .../src/jsMain/kotlin/App/State/RootState.kt | 15 +- .../src/jsMain/kotlin/App/State/RootWiring.kt | 8 +- .../app/src/jsMain/kotlin/App/StateCheck.kt | 159 +++++++++++++++++- 10 files changed, 408 insertions(+), 34 deletions(-) create mode 100644 TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/BattleSettingsReducer.kt create mode 100644 TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/ControlSettingsReducer.kt create mode 100644 TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/GraphicsSettingsReducer.kt diff --git a/3DTank/docs/KtRebuild_Plan.md b/3DTank/docs/KtRebuild_Plan.md index 3822546..4af4e7f 100644 --- a/3DTank/docs/KtRebuild_Plan.md +++ b/3DTank/docs/KtRebuild_Plan.md @@ -143,7 +143,7 @@ `names/root_wiring.json` + `TankiOffline/.../App/State/RootState.kt`、`RootWiring.kt`。 - 根状态 `z6`(`TOState`)的 **89 个槽位**全部入表;字段顺序取 `toString`,槽位取构造函数 `<名>_1`。 - 根 reducer `k6` 的逐切片委派记全:reducer 引用、委派表达式、顶层 `instanceof` 动作名。 - - 已迁移切片 **70/89**: + - 已迁移切片 **73/89**: - `inviteState` → `aD` / `pD.er5`(`InviteReducer.kt`) - `loginByPassword` → `gD` / `MD.cr6`(`LoginReducer.kt`,含 `SetLogin`/`SetPassword` 把 status 打回 `NONE` 的原语义) - `twitchState` → `g9` / `y9.gta`(`TwitchReducer.kt`;`SetAccount` 是 Twitch 账号,不是 UID) @@ -361,12 +361,21 @@ `ResetAction` 依赖外部 helper,纯 reducer 保留原状态;两个默认复位清空绑定表。 - `touchscreenHudSettings` → `j7` / `N7().xsv`(`TouchscreenHudSettingsReducer.kt`):1 字段,3 个分支。 `C7`(原名 `UpdateSettings`)改名为 `ApplyTouchscreenHudSettings`;`E7`→`ResetTouchscreenHudMapping` 恢复默认布局。 + - `graphicsSettings` → `L2` / `lc.isu`(`GraphicsSettingsReducer.kt`):17 字段,1 个分支。 + `B2`(原名 `UpdateSettings`)改名为 `ApplyGraphicsSettings` 整体替换;画质枚举按 `Any?` 收口, + vsync 变化触发的重启提示是副作用,留给中间件层。 + - `controlSettings` → `i2` / `_2().ess`(`ControlSettingsReducer.kt`):7 字段,2 个分支。 + 嵌套模型收口为 `ControlMouseSettings` / `ControlGamepadSettings` / `ControlTouchscreenSettings` + (缺省 50/true、true/13/13、50/true/false/true/true/true/true); + `u2`(原名 `UpdateSettings`)改名为 `ApplyControlSettings`。 + - `battleSettings` → `W0` / `X0().esq`(`BattleSettingsReducer.kt`):11 字段,2 个分支。 + `K0`(原名 `UpdateSettings`)改名为 `ApplyBattleSettings`;`_C` 的旧键迁移属 storage port,保留现状态。 - `launchParams` → `(pass-through)`:原根 reducer 对 `hpx_1` 没有调用,缺省是 `ag()` 空 Map; `RootState.LaunchParams` 收口为 `LaunchParamsStateSlice = Map`,不新增 reducer/动作。 - - `App/State/RootReducer.kt`:`ReduceRoot(State0, Action)` 归约上述 69 个有 reducer 的切片, - `launchParams` 与其余 **19 个字段原样透传**; - 迁移进度用 `RootReducer.Coverage`(`已迁移 70/89`)与 `RootWiring.Unmigrated` 暴露。 - - 自检:`TankiOffline/tools/RunStateTest.ps1` **787 项全绿**,含 89 槽位完整性、69 个归约切片一次归约互不串片、未迁移字段透传, + - `App/State/RootReducer.kt`:`ReduceRoot(State0, Action)` 归约上述 72 个有 reducer 的切片, + `launchParams` 与其余 **16 个字段原样透传**; + 迁移进度用 `RootReducer.Coverage`(`已迁移 73/89`)与 `RootWiring.Unmigrated` 暴露。 + - 自检:`TankiOffline/tools/RunStateTest.ps1` **804 项全绿**,含 89 槽位完整性、72 个归约切片一次归约互不串片、未迁移字段透传, 以及用 `action_trace_merged.json` 真实派发的两条商店动作(`SetCoinPaymentProviderUrl` / `ToggleShopActivity`)回放。 - 委派表里的已知形态:`shop` / `home` / `localBattleUserState` / `partner` 是内联 IIFE, 已按字段具名为 `(inline@)` 并从函数体抓到顶层动作名(69 / 1 / 2 / 11 个); @@ -389,7 +398,8 @@ clanChatState 的 3 个(`$nt`/`xnt`/`Tnt`)、battleSearchState 的 1 个也已登记。 battleStatistics 的 12 个(含改名的 `FR`→`InitBattle`)同样按效果定名。 设置类切片的 `_C`(共用重载)+ `Bet`/`Fet`/`Uet`/`v2`/`C7`/`E7`/`Iut` 也已登记, - 其中四个原名都是 `UpdateSettings` 的类按切片改名为 `Apply*`,避免生成物同名冲突。 + 其中原名都是 `UpdateSettings` 的类按切片改名为 `Apply*`,避免生成物同名冲突 + (`Bet`/`v2`/`B2`/`u2`/`K0`/`C7` 共 6 个)。 ### S4 引擎核心 - 模块:32088(3D 数学)、92980(渲染)、61378(ECS)、55088(物理)、36659 + 71105(资源)、36734(音频)、3569(输入) diff --git a/3DTank/names/root_wiring.json b/3DTank/names/root_wiring.json index 762d5d5..d0c399e 100644 --- a/3DTank/names/root_wiring.json +++ b/3DTank/names/root_wiring.json @@ -1,5 +1,5 @@ { - "generatedAt": "2026-10-24T09:04:37.952Z", + "generatedAt": "2026-10-24T09:19:04.205Z", "module": "module_10032.js", "RootState": { "Class": "z6", @@ -76,7 +76,10 @@ "soundSettings", "gameSettings", "keyboardSettings", - "touchscreenHudSettings" + "touchscreenHudSettings", + "graphicsSettings", + "controlSettings", + "battleSettings" ], "AnonymousActions": [ { @@ -1333,6 +1336,24 @@ "Name": "ResetKeyMappingFromDefaults", "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", "Effect": "恢复键位默认绑定(`rut().mu7_1`)" + }, + { + "Local": "B2", + "Name": "ApplyGraphicsSettings", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "画质设置整体替换(原名 UpdateSettings)" + }, + { + "Local": "u2", + "Name": "ApplyControlSettings", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "操作设置整体替换(原名 UpdateSettings)" + }, + { + "Local": "K0", + "Name": "ApplyBattleSettings", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "战斗显示设置整体替换(原名 UpdateSettings)" } ], "Markers": [ @@ -2524,23 +2545,23 @@ "Reducer": "X0().esq", "Delegate": "X0().esq(t, n.xpw_1)", "Actions": [ - "ResetSettingsFromStorage", - "UpdateSettings" + "ApplyBattleSettings", + "ResetSettingsFromStorage" ], "Default": "new W0()", - "Migrated": false + "Migrated": true }, { "Field": "graphicsSettings", "Slot": "ypw_1", - "SliceClass": null, + "SliceClass": "L2", "Reducer": "lc.isu", "Delegate": "lc.isu(t, n.ypw_1)", "Actions": [ - "UpdateSettings" + "ApplyGraphicsSettings" ], "Default": "vc.dsv()", - "Migrated": false + "Migrated": true }, { "Field": "controlSettings", @@ -2549,11 +2570,11 @@ "Reducer": "_2().ess", "Delegate": "_2().ess(t, n.zpw_1)", "Actions": [ - "ResetSettingsFromStorage", - "UpdateSettings" + "ApplyControlSettings", + "ResetSettingsFromStorage" ], "Default": "new i2()", - "Migrated": false + "Migrated": true }, { "Field": "securitySettings", diff --git a/3DTank/tools/gen_root_wiring.js b/3DTank/tools/gen_root_wiring.js index 28c4649..137b6d1 100644 --- a/3DTank/tools/gen_root_wiring.js +++ b/3DTank/tools/gen_root_wiring.js @@ -307,6 +307,10 @@ const AnonymousActionNames = new Map([ ['E7', 'ResetTouchscreenHudMapping'], // keyboardSettings 切片(Sut / Nut.oth):Iut 没有 toString。 ['Iut', 'ResetKeyMappingFromDefaults'], + // graphicsSettings / controlSettings / battleSettings 的 UpdateSettings 同名类。 + ['B2', 'ApplyGraphicsSettings'], + ['u2', 'ApplyControlSettings'], + ['K0', 'ApplyBattleSettings'], ]); // 非动作的 `instanceof`:屏幕标识类,用于 GoTo 分支判断(不是 dispatch 的动作)。 @@ -773,6 +777,14 @@ const Migrated = { Type: 'TouchscreenHudSettingsStateSlice', Reduce: 'ReduceTouchscreenHudSettings', }, + // 根构造里 graphicsSettings 的默认值不是 `new ()`,生成器认不出切片类,这里显式补。 + graphicsSettings: { + Type: 'GraphicsSettingsStateSlice', + SliceClass: 'L2', + Reduce: 'ReduceGraphicsSettings', + }, + controlSettings: { Type: 'ControlSettingsStateSlice', Reduce: 'ReduceControlSettings' }, + battleSettings: { Type: 'BattleSettingsStateSlice', Reduce: 'ReduceBattleSettings' }, }; for (const R of Rows) R.Migrated = Object.prototype.hasOwnProperty.call(Migrated, R.Field); @@ -1173,6 +1185,9 @@ const AppSettingsAnonymousEffects = { C7: '触屏布局整体替换(原名 UpdateSettings)', E7: '恢复默认触屏布局(`new j7()`)', Iut: '恢复键位默认绑定(`rut().mu7_1`)', + B2: '画质设置整体替换(原名 UpdateSettings)', + u2: '操作设置整体替换(原名 UpdateSettings)', + K0: '战斗显示设置整体替换(原名 UpdateSettings)', }; fs.writeFileSync( diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/BattleSettingsReducer.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/BattleSettingsReducer.kt new file mode 100644 index 0000000..e62aed4 --- /dev/null +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/BattleSettingsReducer.kt @@ -0,0 +1,46 @@ +// BattleSettingsReducer.kt —— 战斗显示设置切片(对应 module_10032 的 W0 状态 + Y0.esq) +// +// 证据链: +// 状态类 W0:11 个字段,toString 名 "BattleSettings(…)"。 +// 默认:6 个显示开关 true(dot/damage/tankPointers/buttonLabels/autoSuicide/statusName), +// isCenteredLayout=false、battleUISize=50、aimhelperDotOpacity=50、 +// aimhelperDotScale=1、autoSupply=true。 +// 根委派:`X0().esq(t, n.xpw_1)`。 +// reducer: +// _C(ResetSettingsFromStorage)→ 从 localStorage 读旧键迁移并重建整套设置 +// K0(UpdateSettings) → 整体替换设置对象(并写回存储) +// +// storage 迁移 / 写回属 storage port 职责,纯 reducer 不做 IO(`_C` 保留现有状态)。 +package App.State.Reducers + +/* ------------------------------------------------------------------ 动作 */ + +/** 原 `K0`(原名 `UpdateSettings`,与其它设置切片同名):整体替换战斗显示设置。 */ +data class ApplyBattleSettings(val settings: BattleSettingsStateSlice) + +/* ------------------------------------------------------------------ 状态 */ + +data class BattleSettingsStateSlice( + val ShowAimHelperDot: Boolean = true, + val ShowDamage: Boolean = true, + val ShowTankPointers: Boolean = true, + val ShowButtonLabels: Boolean = true, + val AutoSuicide: Boolean = true, + val ShowStatusName: Boolean = true, + val IsCenteredLayout: Boolean = false, + val BattleUISize: Double = 50.0, + val AimhelperDotOpacity: Double = 50.0, + val AimhelperDotScale: Double = 1.0, + val AutoSupply: Boolean = true, +) + +/* ------------------------------------------------------------------ reducer */ + +fun ReduceBattleSettings( + State0: BattleSettingsStateSlice, + Action: Any, +): BattleSettingsStateSlice = when (Action) { + is ApplyBattleSettings -> Action.settings + is ResetSettingsFromStorage -> State0 + else -> State0 +} diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/ControlSettingsReducer.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/ControlSettingsReducer.kt new file mode 100644 index 0000000..26ee79c --- /dev/null +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/ControlSettingsReducer.kt @@ -0,0 +1,70 @@ +// ControlSettingsReducer.kt —— 操作设置切片(对应 module_10032 的 i2 状态 + o2.ess) +// +// 证据链: +// 状态类 i2:7 个字段,toString 名 "ControlSettings(…)"。 +// 默认:availableInputModes=空集合(服务缺省)、inputMode=null、 +// inversedBackwardMoving=false、invertShaftVerticalAxis=false、 +// mouse=Q0(50, true)、gamepad=t2(true, 13, 13)、touchscreen=n2(50, true, false, true, true, true, true)。 +// 根委派:`_2().ess(t, n.zpw_1)`。 +// reducer: +// _C(ResetSettingsFromStorage)→ 从存储 + 输入服务重建整套设置 +// u2(UpdateSettings) → 整体替换设置对象(并写回存储 / 输入服务) +// +// 嵌套模型按源码构造参数收口: +// Mouse(cameraSensitivity, enableAcceleration) +// Gamepad(enabled, leftStickDeadZone, rightStickDeadZone) +// Touchscreen(cameraSensitivity, rotateJoystick, movableJoystick, doubleTapFire, +// autoFire, showMovementIndicator, autoCenterTurret) +// storage / 输入服务写回属 side effect,纯 reducer 不做 IO(`_C` 保留现有状态)。 +package App.State.Reducers + +/* ------------------------------------------------------------------ 动作 */ + +/** 原 `u2`(原名 `UpdateSettings`,与其它设置切片同名):整体替换操作设置。 */ +data class ApplyControlSettings(val settings: ControlSettingsStateSlice) + +/* ------------------------------------------------------------------ 嵌套模型 */ + +data class ControlMouseSettings( + val CameraSensitivity: Double = 50.0, + val EnableAcceleration: Boolean = true, +) + +data class ControlGamepadSettings( + val Enabled: Boolean = true, + val LeftStickDeadZone: Int = 13, + val RightStickDeadZone: Int = 13, +) + +data class ControlTouchscreenSettings( + val CameraSensitivity: Double = 50.0, + val RotateJoystick: Boolean = true, + val MovableJoystick: Boolean = false, + val DoubleTapFire: Boolean = true, + val AutoFire: Boolean = true, + val ShowMovementIndicator: Boolean = true, + val AutoCenterTurret: Boolean = true, +) + +/* ------------------------------------------------------------------ 状态 */ + +data class ControlSettingsStateSlice( + val AvailableInputModes: Set = emptySet(), + val InputMode: Any? = null, + val InversedBackwardMoving: Boolean = false, + val InvertShaftVerticalAxis: Boolean = false, + val Mouse: ControlMouseSettings = ControlMouseSettings(), + val Gamepad: ControlGamepadSettings = ControlGamepadSettings(), + val Touchscreen: ControlTouchscreenSettings = ControlTouchscreenSettings(), +) + +/* ------------------------------------------------------------------ reducer */ + +fun ReduceControlSettings( + State0: ControlSettingsStateSlice, + Action: Any, +): ControlSettingsStateSlice = when (Action) { + is ApplyControlSettings -> Action.settings + is ResetSettingsFromStorage -> State0 + else -> State0 +} diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/GraphicsSettingsReducer.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/GraphicsSettingsReducer.kt new file mode 100644 index 0000000..35a0e78 --- /dev/null +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/GraphicsSettingsReducer.kt @@ -0,0 +1,51 @@ +// GraphicsSettingsReducer.kt —— 画质设置切片(对应 module_10032 的 L2 状态 + U2.isu) +// +// 证据链: +// 状态类 L2:17 个字段,toString 名 "GraphicsSettings(…)"。 +// 默认:qualityDetectByCardCompleted=null、extended=false、fpsLimit=0(服务缺省)、 +// showFPSAndPing/showSkybox=true、dynamicShadows/dynamicLighting/softParticles=false、 +// fog=true、antialiasing=false、vsync=true、decalsProfile=null、dustEnabled=false、 +// anisotropicFilteringLevel=0、treesQuality/effectsQuality/resolutionScale=null。 +// 根委派:`lc.isu(t, n.ypw_1)`。 +// reducer:`t instanceof B2 ? t.hsu_1 : n` —— 整体替换设置对象。 +// +// 各类画质枚举(decals / trees / effects / resolution)来自画质服务,按 `Any?` 收口; +// 原 reducer 在 vsync 变化时还会派发重启提示(副作用),留给中间件层。 +package App.State.Reducers + +/* ------------------------------------------------------------------ 动作 */ + +/** 原 `B2`(原名 `UpdateSettings`,与其它设置切片同名):整体替换画质设置。 */ +data class ApplyGraphicsSettings(val settings: GraphicsSettingsStateSlice) + +/* ------------------------------------------------------------------ 状态 */ + +data class GraphicsSettingsStateSlice( + val QualityDetectByCardCompleted: Boolean? = null, + val Extended: Boolean = false, + val FpsLimit: Int = 0, + val ShowFPSAndPing: Boolean = true, + val ShowSkybox: Boolean = true, + val DynamicShadows: Boolean = false, + val DynamicLighting: Boolean = false, + val SoftParticles: Boolean = false, + val Fog: Boolean = true, + val Antialiasing: Boolean = false, + val Vsync: Boolean = true, + val DecalsProfile: Any? = null, + val DustEnabled: Boolean = false, + val AnisotropicFilteringLevel: Int = 0, + val TreesQuality: Any? = null, + val EffectsQuality: Any? = null, + val ResolutionScale: Any? = null, +) + +/* ------------------------------------------------------------------ reducer */ + +fun ReduceGraphicsSettings( + State0: GraphicsSettingsStateSlice, + Action: Any, +): GraphicsSettingsStateSlice = when (Action) { + is ApplyGraphicsSettings -> Action.settings + else -> State0 +} diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/RootReducer.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/RootReducer.kt index 5d468ea..4184935 100644 --- a/TankiOffline/app/src/jsMain/kotlin/App/State/RootReducer.kt +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/RootReducer.kt @@ -1,7 +1,7 @@ // RootReducer.kt —— 根 reducer 骨架(对应 module_10032 的根状态 z6 / TOState 与根 reducer k6) // // 原包 k6 对 89 个切片逐个调用各自的 reducer,再 `return n.cti(...)`(copy 出新状态)。 -// 这里采用同一形状。已迁移的 70 个切片里,69 个真正调用 reducer,launchParams 只做类型收口: +// 这里采用同一形状。已迁移的 73 个切片里,72 个真正调用 reducer,launchParams 只做类型收口: // // inviteState → ReduceInvite(aD / pD.er5) // loginByPassword → ReduceLoginByPassword(gD / MD.cr6) @@ -72,9 +72,12 @@ // gameSettings → ReduceGameSettings(c2 / g2().zss,根委派 `g2().zss(t, n.wpw_1)`) // keyboardSettings → ReduceKeyboardSettings(Sut / bw.oth,根委派 `bw.oth(t, n.dpx_1)`) // touchscreenHudSettings → ReduceTouchscreenHudSettings(j7 / N7().xsv,根委派 `N7().xsv(t, n.epx_1)`) +// graphicsSettings → ReduceGraphicsSettings(L2 / lc.isu,根委派 `lc.isu(t, n.ypw_1)`) +// controlSettings → ReduceControlSettings(i2 / _2().ess,根委派 `_2().ess(t, n.zpw_1)`) +// battleSettings → ReduceBattleSettings(W0 / X0().esq,根委派 `X0().esq(t, n.xpw_1)`) // launchParams → 无 reducer(原根 reducer 原样透传;默认 `ag()` = emptyMap()) // -// 其余 19 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态, +// 其余 16 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态, // 所以一次 copy 里串起全部已迁移 reducer,与按动作路由再归约的结果一致。 // 迁移进度与每个字段的证据见生成物 RootWiring.kt(不要手改)。 package App.State @@ -85,6 +88,7 @@ import App.State.Reducers.ReduceAntiAddiction import App.State.Reducers.ReduceBattleResult import App.State.Reducers.ReduceBattlePause import App.State.Reducers.ReduceBattleSearch +import App.State.Reducers.ReduceBattleSettings import App.State.Reducers.ReduceBattleStatistics import App.State.Reducers.ReduceBattleTutorial import App.State.Reducers.ReduceBattleView @@ -93,6 +97,7 @@ import App.State.Reducers.ReduceCaptcha import App.State.Reducers.ReduceClanUserInfo import App.State.Reducers.ReduceClanChat import App.State.Reducers.ReduceContextTutorial +import App.State.Reducers.ReduceControlSettings import App.State.Reducers.ReduceCreatorTag import App.State.Reducers.ReduceDroppableGold import App.State.Reducers.ReduceEntrance @@ -101,6 +106,7 @@ import App.State.Reducers.ReduceGarage import App.State.Reducers.ReduceGaragePreview import App.State.Reducers.ReduceFriends import App.State.Reducers.ReduceGameSettings +import App.State.Reducers.ReduceGraphicsSettings import App.State.Reducers.ReduceHome import App.State.Reducers.ReduceIcons import App.State.Reducers.ReduceInvite @@ -231,6 +237,9 @@ object RootReducer { GameSettings = ReduceGameSettings(State0.GameSettings, Action), KeyboardSettings = ReduceKeyboardSettings(State0.KeyboardSettings, Action), TouchscreenHudSettings = ReduceTouchscreenHudSettings(State0.TouchscreenHudSettings, Action), + GraphicsSettings = ReduceGraphicsSettings(State0.GraphicsSettings, Action), + ControlSettings = ReduceControlSettings(State0.ControlSettings, Action), + BattleSettings = ReduceBattleSettings(State0.BattleSettings, Action), ) } diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/RootState.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/RootState.kt index 2860b2e..5ea565a 100644 --- a/TankiOffline/app/src/jsMain/kotlin/App/State/RootState.kt +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/RootState.kt @@ -12,6 +12,7 @@ import App.State.Reducers.BattleInviteStateSlice import App.State.Reducers.BattlePauseStateSlice import App.State.Reducers.BattleResultStateSlice import App.State.Reducers.BattleSearchStateSlice +import App.State.Reducers.BattleSettingsStateSlice import App.State.Reducers.BattleStatisticsStateSlice import App.State.Reducers.BattleTutorialStateSlice import App.State.Reducers.BattleViewStateSlice @@ -19,6 +20,7 @@ import App.State.Reducers.CaptchaStateSlice import App.State.Reducers.ClanChatStateSlice import App.State.Reducers.ClanUserInfoStateSlice import App.State.Reducers.ContextTutorialStateSlice +import App.State.Reducers.ControlSettingsStateSlice import App.State.Reducers.CreatorTagStateSlice import App.State.Reducers.DroppableGoldStateSlice import App.State.Reducers.EntranceStateSlice @@ -27,6 +29,7 @@ import App.State.Reducers.FriendsStateSlice import App.State.Reducers.GameSettingsStateSlice import App.State.Reducers.GarageStateSlice import App.State.Reducers.GaragePreviewStateSlice +import App.State.Reducers.GraphicsSettingsStateSlice import App.State.Reducers.HomeStateSlice import App.State.Reducers.IconsStateSlice import App.State.Reducers.InviteStateSlice @@ -180,12 +183,12 @@ data class RootState( val SoundSettings: SoundSettingsStateSlice = SoundSettingsStateSlice(), /** 原 gameSettings(c2 / g2().zss)。已迁移。 */ val GameSettings: GameSettingsStateSlice = GameSettingsStateSlice(), - /** 原 battleSettings(W0 / X0().esq)。未迁移:原样透传。 */ - val BattleSettings: Any? = null, - /** 原 graphicsSettings(? / lc.isu)。未迁移:原样透传。 */ - val GraphicsSettings: Any? = null, - /** 原 controlSettings(i2 / _2().ess)。未迁移:原样透传。 */ - val ControlSettings: Any? = null, + /** 原 battleSettings(W0 / X0().esq)。已迁移。 */ + val BattleSettings: BattleSettingsStateSlice = BattleSettingsStateSlice(), + /** 原 graphicsSettings(L2 / lc.isu)。已迁移。 */ + val GraphicsSettings: GraphicsSettingsStateSlice = GraphicsSettingsStateSlice(), + /** 原 controlSettings(i2 / _2().ess)。已迁移。 */ + val ControlSettings: ControlSettingsStateSlice = ControlSettingsStateSlice(), /** 原 securitySettings(V2 / pc.qsv)。已迁移。 */ val SecuritySettings: SecuritySettingsStateSlice = SecuritySettingsStateSlice(), /** 原 purchaseDialogState(CO / je.ur2)。已迁移。 */ diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/RootWiring.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/RootWiring.kt index e3a7fb8..c409287 100644 --- a/TankiOffline/app/src/jsMain/kotlin/App/State/RootWiring.kt +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/RootWiring.kt @@ -65,9 +65,9 @@ object RootWiring { SliceWiring("localBattleUserState", "upw_1", "lE", "(inline@localBattleUserState)", "(function (t, n) { return t instanceof fE ? n.xq8(t.yq8_1) : t instanceof vE ? n.xq8(cd, t.zq8_1) : n; })(t, n.upw_1)", listOf("SetLocalWeaponResistanceProperty", "SetReArmorTemporaryDisabled")), SliceWiring("soundSettings", "vpw_1", "Det", "Wet().jth", "Wet().jth(t, n.vpw_1)", listOf("ApplySoundSettings", "EnableEntranceSound", "ResetSettingsFromStorage", "SetEntranceSoundSettings")), SliceWiring("gameSettings", "wpw_1", "c2", "g2().zss", "g2().zss(t, n.wpw_1)", listOf("ApplyGameSettings", "ResetSettingsFromStorage")), - SliceWiring("battleSettings", "xpw_1", "W0", "X0().esq", "X0().esq(t, n.xpw_1)", listOf("ResetSettingsFromStorage", "UpdateSettings")), - SliceWiring("graphicsSettings", "ypw_1", "?", "lc.isu", "lc.isu(t, n.ypw_1)", listOf("UpdateSettings")), - SliceWiring("controlSettings", "zpw_1", "i2", "_2().ess", "_2().ess(t, n.zpw_1)", listOf("ResetSettingsFromStorage", "UpdateSettings")), + SliceWiring("battleSettings", "xpw_1", "W0", "X0().esq", "X0().esq(t, n.xpw_1)", listOf("ApplyBattleSettings", "ResetSettingsFromStorage")), + SliceWiring("graphicsSettings", "ypw_1", "L2", "lc.isu", "lc.isu(t, n.ypw_1)", listOf("ApplyGraphicsSettings")), + SliceWiring("controlSettings", "zpw_1", "i2", "_2().ess", "_2().ess(t, n.zpw_1)", listOf("ApplyControlSettings", "ResetSettingsFromStorage")), SliceWiring("securitySettings", "apx_1", "V2", "pc.qsv", "pc.qsv(t, n.apx_1)", listOf("SetProfileBlockStatus")), SliceWiring("purchaseDialogState", "bpx_1", "CO", "je.ur2", "je.ur2(t, n.bpx_1)", listOf("ChangeEmailStatus", "ResetPurchaseDialog", "SuccessfullyPurchase")), SliceWiring("battleInviteState", "cpx_1", "att", "Ttt().nth", "Ttt().nth(t, n.cpx_1)", listOf("AddInvitedUser", "ClearSelectedForInvite", "EnableSetBattleInviteNotifications", "InvitationResponse", "RemoveUserFromInvited", "SelectUserToInvite")), @@ -106,7 +106,7 @@ object RootWiring { ) /** 已迁移(强类型;launchParams 为透传,不调用 reducer)的字段。 */ - val Migrated: Set = setOf("inviteState", "loginByPassword", "twitchState", "home", "localBattleUserState", "shop", "screen", "lootBoxes", "garage", "garagePreview", "entrance", "user", "settings", "accountSettings", "proBattle", "language", "announcement", "launchParams", "news", "notifications", "quests", "lobbyLoader", "purchaseDialogState", "promoCode", "supportUrl", "rulesUpdated", "notificationByCountry", "availabilityByRank", "icons", "questCategories", "mainQuest", "securitySettings", "referralEvent", "tipsState", "rankUpDialogState", "battleResult", "captchaState", "modalScreen", "droppableGold", "eventGoods", "creatorTagState", "rewardedVideosState", "passwordChangeState", "system", "contextTutorial", "battleTutorial", "spectator", "twoStepVerificationState", "antiAddiction", "moderation", "migrationState", "register", "userReportsState", "friends", "suppliesState", "clanUserInfo", "battleInviteState", "partner", "userRenameState", "battlePauseState", "battleViewState", "tankActionsLogState", "suppliesTutorial", "clanChatState", "battleSearchState", "battleStatistics", "soundSettings", "gameSettings", "keyboardSettings", "touchscreenHudSettings") + val Migrated: Set = setOf("inviteState", "loginByPassword", "twitchState", "home", "localBattleUserState", "shop", "screen", "lootBoxes", "garage", "garagePreview", "entrance", "user", "settings", "accountSettings", "proBattle", "language", "announcement", "launchParams", "news", "notifications", "quests", "lobbyLoader", "purchaseDialogState", "promoCode", "supportUrl", "rulesUpdated", "notificationByCountry", "availabilityByRank", "icons", "questCategories", "mainQuest", "securitySettings", "referralEvent", "tipsState", "rankUpDialogState", "battleResult", "captchaState", "modalScreen", "droppableGold", "eventGoods", "creatorTagState", "rewardedVideosState", "passwordChangeState", "system", "contextTutorial", "battleTutorial", "spectator", "twoStepVerificationState", "antiAddiction", "moderation", "migrationState", "register", "userReportsState", "friends", "suppliesState", "clanUserInfo", "battleInviteState", "partner", "userRenameState", "battlePauseState", "battleViewState", "tankActionsLogState", "suppliesTutorial", "clanChatState", "battleSearchState", "battleStatistics", "soundSettings", "gameSettings", "keyboardSettings", "touchscreenHudSettings", "graphicsSettings", "controlSettings", "battleSettings") /** 未迁移字段:归约时原样透传,作为后续迁移清单。 */ val Unmigrated: List get() = All.filter { it.Field !in Migrated } diff --git a/TankiOffline/app/src/jsMain/kotlin/App/StateCheck.kt b/TankiOffline/app/src/jsMain/kotlin/App/StateCheck.kt index 9231e73..ec1ab9c 100644 --- a/TankiOffline/app/src/jsMain/kotlin/App/StateCheck.kt +++ b/TankiOffline/app/src/jsMain/kotlin/App/StateCheck.kt @@ -19,7 +19,7 @@ // System / ContextTutorial / BattleTutorial / Spectator / TwoStepVerification / AntiAddiction / // Moderation / MigrationState / Register / UserReports / // Friends / Supplies / ClanUserInfo / BattleInvite / Partner / UserRename / -// BattlePause / BattleView / TankActionsLog / SuppliesTutorial / ClanChat / BattleSearch / BattleStatistics / SoundSettings / GameSettings / KeyboardSettings / TouchscreenHudSettings; +// BattlePause / BattleView / TankActionsLog / SuppliesTutorial / ClanChat / BattleSearch / BattleStatistics / SoundSettings / GameSettings / KeyboardSettings / TouchscreenHudSettings / GraphicsSettings / ControlSettings / BattleSettings; // ㉑ launchParams 是纯透传切片(无 reducer/动作),只做 Map 类型收口;⑭ 根 reducer 的 89 槽位委派 // // 用法:$env:TANKIOFFLINE_STATE_TEST='1'; node …(见 TankiOffline/tools/RunStateTest.ps1) @@ -686,6 +686,18 @@ import App.State.Reducers.ResetTouchscreenHudMapping import App.State.Reducers.SetEntranceSoundSettings import App.State.Reducers.SoundSettingsStateSlice import App.State.Reducers.TouchscreenHudSettingsStateSlice +import App.State.Reducers.ApplyBattleSettings +import App.State.Reducers.ApplyControlSettings +import App.State.Reducers.ApplyGraphicsSettings +import App.State.Reducers.BattleSettingsStateSlice +import App.State.Reducers.ControlGamepadSettings +import App.State.Reducers.ControlMouseSettings +import App.State.Reducers.ControlSettingsStateSlice +import App.State.Reducers.ControlTouchscreenSettings +import App.State.Reducers.GraphicsSettingsStateSlice +import App.State.Reducers.ReduceBattleSettings +import App.State.Reducers.ReduceControlSettings +import App.State.Reducers.ReduceGraphicsSettings import App.State.Reducers.UserRankBounds import App.State.Reducers.UserStateSlice import App.State.Reducers.ClearHullData @@ -1113,7 +1125,7 @@ object StateCheck { Check("切片 LocalBattleUser:接 Store 派发后状态已归约", Store.State.IsReArmorTemporaryDisabled) } - // ⑭ 根 reducer:89 个槽位的委派表 + 70 个已迁移切片(69 归约 + launchParams 透传) + // ⑭ 根 reducer:89 个槽位的委派表 + 73 个已迁移切片(72 归约 + launchParams 透传) run { Check("根:根状态 89 个切片槽位", RootWiring.All.size == 89) Check( @@ -1177,7 +1189,7 @@ object StateCheck { LocalBattleW.Reducer == "(inline@localBattleUserState)" ) Check( - "根:迁移表 = 70 个已迁移字段,其余 19 个待迁", + "根:迁移表 = 73 个已迁移字段,其余 16 个待迁", RootReducer.Migrated == setOf( "inviteState", "loginByPassword", "twitchState", "home", @@ -1199,8 +1211,9 @@ object StateCheck { "battlePauseState", "battleViewState", "tankActionsLogState", "suppliesTutorial", "clanChatState", "battleSearchState", "battleStatistics", "soundSettings", "gameSettings", "keyboardSettings", "touchscreenHudSettings", + "graphicsSettings", "controlSettings", "battleSettings", ) && - RootWiring.Unmigrated.size == 19 + RootWiring.Unmigrated.size == 16 ) // 一次 ReduceRoot 里多个切片各归约自己的动作,互不串片 @@ -1256,7 +1269,7 @@ object StateCheck { S5.User.Uid == "user-1" && S5.Screen.Current == NamedScreen("Preloader") ) Check("根:无关动作返回等值状态", ReduceRoot(S5, AddFrags(1)) == S5) - Check("根:Coverage 文本", RootReducer.Coverage == "已迁移 70/89") + Check("根:Coverage 文本", RootReducer.Coverage == "已迁移 73/89") val Store = ReduxStore(RootState(), { S, A -> ReduceRoot(S, A) }) Store.Dispatch(SetStatus("LOGGING_IN")) @@ -5967,6 +5980,142 @@ object StateCheck { ) } + // 81 切片 reducer:GraphicsSettings(L2 / lc.isu,17 字段;1 个动作) + run { + val S0 = GraphicsSettingsStateSlice() + Check( + "切片 GraphicsSettings:默认值完整", + S0.QualityDetectByCardCompleted == null && !S0.Extended && S0.FpsLimit == 0 && + S0.ShowFPSAndPing && S0.ShowSkybox && !S0.DynamicShadows && + !S0.DynamicLighting && !S0.SoftParticles && S0.Fog && !S0.Antialiasing && + S0.Vsync && S0.DecalsProfile == null && !S0.DustEnabled && + S0.AnisotropicFilteringLevel == 0 && S0.TreesQuality == null && + S0.EffectsQuality == null && S0.ResolutionScale == null + ) + val Custom = GraphicsSettingsStateSlice( + QualityDetectByCardCompleted = true, Extended = true, FpsLimit = 144, + ShowFPSAndPing = false, ShowSkybox = false, DynamicShadows = true, + DynamicLighting = true, SoftParticles = true, Fog = false, + Antialiasing = true, Vsync = false, DecalsProfile = "high", DustEnabled = true, + AnisotropicFilteringLevel = 8, TreesQuality = "high", + EffectsQuality = "high", ResolutionScale = "1.0", + ) + Check( + "切片 GraphicsSettings:ApplyGraphicsSettings 整体替换", + ReduceGraphicsSettings(S0, ApplyGraphicsSettings(Custom)) == Custom + ) + Check( + "切片 GraphicsSettings:无关动作不改状态", + ReduceGraphicsSettings(Custom, AddFrags(1)) == Custom + ) + + val Root0 = RootState() + val Root1 = ReduceRoot(Root0, ApplyGraphicsSettings(Custom)) + Check( + "根:ApplyGraphicsSettings 只动 graphicsSettings", + Root1.GraphicsSettings == Custom && + Root1.ControlSettings == Root0.ControlSettings + ) + val W = RootWiring.ByField("graphicsSettings")!! + Check( + "根:graphicsSettings 委派到 L2 / lc.isu", + W.SliceClass == "L2" && W.Reducer == "lc.isu" && + W.Actions == listOf("ApplyGraphicsSettings") + ) + } + + // 82 切片 reducer:ControlSettings(i2 / _2().ess,7 字段;2 个动作) + run { + val S0 = ControlSettingsStateSlice() + Check( + "切片 ControlSettings:默认值完整", + S0.AvailableInputModes.isEmpty() && S0.InputMode == null && + !S0.InversedBackwardMoving && !S0.InvertShaftVerticalAxis && + S0.Mouse == ControlMouseSettings(50.0, true) && + S0.Gamepad == ControlGamepadSettings(true, 13, 13) && + S0.Touchscreen == + ControlTouchscreenSettings(50.0, true, false, true, true, true, true) + ) + val Custom = ControlSettingsStateSlice( + AvailableInputModes = setOf("mouse", "touch"), + InputMode = "touch", + InversedBackwardMoving = true, + InvertShaftVerticalAxis = true, + Mouse = ControlMouseSettings(30.0, false), + Gamepad = ControlGamepadSettings(false, 20, 25), + Touchscreen = ControlTouchscreenSettings(70.0, false, true, false, false, false, false), + ) + Check( + "切片 ControlSettings:ApplyControlSettings 整体替换", + ReduceControlSettings(S0, ApplyControlSettings(Custom)) == Custom + ) + Check( + "切片 ControlSettings:ResetSettingsFromStorage 保留状态", + ReduceControlSettings(Custom, ResetSettingsFromStorage) == Custom + ) + Check( + "切片 ControlSettings:无关动作不改状态", + ReduceControlSettings(Custom, AddFrags(1)) == Custom + ) + + val Root0 = RootState() + val Root1 = ReduceRoot(Root0, ApplyControlSettings(Custom)) + Check( + "根:ApplyControlSettings 只动 controlSettings", + Root1.ControlSettings == Custom && Root1.BattleSettings == Root0.BattleSettings + ) + val W = RootWiring.ByField("controlSettings")!! + Check( + "根:controlSettings 委派到 i2 / _2().ess 且 2 个动作", + W.SliceClass == "i2" && W.Reducer == "_2().ess" && + W.Actions.toSet() == setOf("ApplyControlSettings", "ResetSettingsFromStorage") + ) + } + + // 83 切片 reducer:BattleSettings(W0 / X0().esq,11 字段;2 个动作) + run { + val S0 = BattleSettingsStateSlice() + Check( + "切片 BattleSettings:默认值完整", + S0.ShowAimHelperDot && S0.ShowDamage && S0.ShowTankPointers && + S0.ShowButtonLabels && S0.AutoSuicide && S0.ShowStatusName && + !S0.IsCenteredLayout && S0.BattleUISize == 50.0 && + S0.AimhelperDotOpacity == 50.0 && S0.AimhelperDotScale == 1.0 && + S0.AutoSupply + ) + val Custom = BattleSettingsStateSlice( + ShowAimHelperDot = false, ShowDamage = false, ShowTankPointers = false, + ShowButtonLabels = false, AutoSuicide = false, ShowStatusName = false, + IsCenteredLayout = true, BattleUISize = 80.0, AimhelperDotOpacity = 20.0, + AimhelperDotScale = 2.0, AutoSupply = false, + ) + Check( + "切片 BattleSettings:ApplyBattleSettings 整体替换", + ReduceBattleSettings(S0, ApplyBattleSettings(Custom)) == Custom + ) + Check( + "切片 BattleSettings:ResetSettingsFromStorage 保留状态", + ReduceBattleSettings(Custom, ResetSettingsFromStorage) == Custom + ) + Check( + "切片 BattleSettings:无关动作不改状态", + ReduceBattleSettings(Custom, AddFrags(1)) == Custom + ) + + val Root0 = RootState() + val Root1 = ReduceRoot(Root0, ApplyBattleSettings(Custom)) + Check( + "根:ApplyBattleSettings 只动 battleSettings", + Root1.BattleSettings == Custom && Root1.GraphicsSettings == Root0.GraphicsSettings + ) + val W = RootWiring.ByField("battleSettings")!! + Check( + "根:battleSettings 委派到 W0 / X0().esq 且 2 个动作", + W.SliceClass == "W0" && W.Reducer == "X0().esq" && + W.Actions.toSet() == setOf("ApplyBattleSettings", "ResetSettingsFromStorage") + ) + } + val Failed = Results.filter { !it.second } println("TankiOffline 状态层自检:${Results.size} 项") for ((Name, Ok) in Results) println((if (Ok) " ✓ " else " ✗ ") + Name)