diff --git a/3DTank/docs/KtRebuild_Plan.md b/3DTank/docs/KtRebuild_Plan.md index fd49348..07d38ec 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` 动作名。 - - 已迁移切片 **43/89**: + - 已迁移切片 **53/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) @@ -279,12 +279,42 @@ 新增枚举 `RewardVideoDisplayState { NONE, LOADING, DISPLAY }`,缺省 `NONE`;`SetDisplayState` 整体替换。 - `passwordChangeState` → `VD` / `uu.pr6`(`PasswordChangeReducer.kt`):3 个字符串字段(hash / uid / email), 缺省全为空串;`SetPasswordChangeHash` 只写 hash,`ShowEmailAndPasswordChangeDialog` 写 uid/email 并保留 hash。 + - `system` → `_9` / `w9().xt9`(`SystemReducer.kt`):3 字段,2 个分支。 + `DoShowMessage(message, restart)` 写前两字段,`SetFatalError` 只写 `fatalError`。 + - `contextTutorial` → `Mst` / `Jst().tth`(`ContextTutorialReducer.kt`):4 字段,5 个分支。 + `MarkElementAsViewed` 从 `unViewedElements` 删一个元素(`Ym`),其余为整表 / 整 Map 替换。 + - `battleTutorial` → `rE` / `Q.sq8`(`BattleTutorialReducer.kt`):3 字段,4 个分支。 + `oE`→`MarkMovementStarted` 置 `movementHasStarted`,`sE`→`ResetBattleTutorial` 整片复位。 + - `spectator` → `HA` / `Li.jqm`(`SpectatorReducer.kt`):4 字段,6 个分支。 + `JA`→`AddSpectator` 追加 `SpectatorInfo`(复用生成物里的同名模型),`ZA`→`RemoveSpectator` 按 userId 过滤, + `XA`→`SetSpectators` 整表替换,`YA`→`SetSpectatorsListVisible`。 + `spectatorHudLayout` 的原模型是 15 字段的 HUD 布局对象(战斗层),按 `Any?` 收口,缺省 null。 + - `twoStepVerificationState` → `x9` / `_f.atb`(`TwoStepVerificationReducer.kt`):6 字段,5 个分支。 + `VerificationStatus` 三态:null 清空、false 写失败、true 直接把 `isActivated2FA` 置 true(原 `rta(cd, !0)`)。 + - `antiAddiction` → `UC` / `W.iq8`(`AntiAddictionReducer.kt`):8 字段,5 个分支。 + `FC`→`EnableRealNameCheck`、`XC`→`RequireUserIdVerification`(同时置 `unknownUserId`)、 + `YC`→`UpdatePlayingTime`;`SetRealNameAndIdNumber` 的参数为 null 时保留原值。 + - `moderation` → `NI` / `Ei.oqh`(`ModerationReducer.kt`):11 字段,13 个分支。 + 选中用户列表用 `ModerationUserRecord` 收口:`GI`/`WI` 先按 userId 去重再追加,`KI` 删除,`YI`/`uA` 清空; + `XI`/`JI` 与 `ZI`/`QI` 分别是战斗信息面板、系统消息的显示/收起,`tA`/`nA` 是封禁菜单。 + `battleInfo` 的原模型 `MI` 有 13 字段(战斗层),按 `Any?` 收口。 + - `migrationState` → `$it` / `Uv.rtg`(`MigrationStateReducer.kt`):7 字段,11 个分支。 + 新增 `MigrationLoginStatus { UNKNOWN, FREE, INCORRECT, BUSY, SHORT_NAME }` 与 + `MigrationRegisterStatus { UNKNOWN, PASSWORD_CORRECT, PASSWORD_INCORRECT, FAILED }`(对应源码 `Eit()` / `Iit()`)。 + `SetUid` / `ForceSetUid` / `SetPassword` 与注册、登录切片共用同一批动作类,因此几片 reducer 同时响应、各自动自己的字段。 + - `register` → `Cit` / `cp.stg`(`RegisterReducer.kt`):16 字段,19 个分支。 + 新增 `UidStatus`、`RegistrationStatus` 两个枚举;`SetUid` / `ForceSetUid` 会复位 uid 校验状态, + 且只有原状态不是 `INCORRECT` 时才把 `registerStatus` 一起打回 `UNKNOWN`(原 `2 === n.wtr_1.k3_1` 分支)。 + - `userReportsState` → `Zst` / `qot.zth`(`UserReportsReducer.kt`):5 字段,6 个分支。 + 新增 `ReportKind { BATTLE_NAME, LOBBY_CHAT_MESSAGE, BATTLE_CHAT_MESSAGE }` 与 `ReportEntry(kind, targetId, expireTime)`; + 三类举报都追加一条记录、有效期 +1 天。原 reducer 还把列表写回 localStorage 的 `SENT_REPORTS`, + 属 storage port 职责,纯 reducer 不做 IO(Init 保留现有列表)。 - `launchParams` → `(pass-through)`:原根 reducer 对 `hpx_1` 没有调用,缺省是 `ag()` 空 Map; `RootState.LaunchParams` 收口为 `LaunchParamsStateSlice = Map`,不新增 reducer/动作。 - - `App/State/RootReducer.kt`:`ReduceRoot(State0, Action)` 归约上述 42 个有 reducer 的切片, - `launchParams` 与其余 **46 个字段原样透传**; - 迁移进度用 `RootReducer.Coverage`(`已迁移 43/89`)与 `RootWiring.Unmigrated` 暴露。 - - 自检:`TankiOffline/tools/RunStateTest.ps1` **529 项全绿**,含 89 槽位完整性、42 个归约切片一次归约互不串片、未迁移字段透传, + - `App/State/RootReducer.kt`:`ReduceRoot(State0, Action)` 归约上述 52 个有 reducer 的切片, + `launchParams` 与其余 **36 个字段原样透传**; + 迁移进度用 `RootReducer.Coverage`(`已迁移 53/89`)与 `RootWiring.Unmigrated` 暴露。 + - 自检:`TankiOffline/tools/RunStateTest.ps1` **627 项全绿**,含 89 槽位完整性、52 个归约切片一次归约互不串片、未迁移字段透传, 以及用 `action_trace_merged.json` 真实派发的两条商店动作(`SetCoinPaymentProviderUrl` / `ToggleShopActivity`)回放。 - 委派表里的已知形态:`shop` / `home` / `localBattleUserState` / `partner` 是内联 IIFE, 已按字段具名为 `(inline@)` 并从函数体抓到顶层动作名(69 / 1 / 2 / 11 个); @@ -298,7 +328,9 @@ language 的 1 个、announcement 的 3 个、news 的 3 个(含两个单例)、notifications 的 2 个、 quests 的 1 个、lobbyLoader 的 2 个、purchaseDialogState 的 1 个、supportUrl 的 1 个、 notificationByCountry 的 1 个、mainQuest 的 1 个、rankUpDialogState 的 1 个、battleResult 的 3 个、 - captchaState 的 5 个、modalScreen 的 1 个、droppableGold 的 2 个、creatorTagState 的 1 个匿名类 / 单例同样按效果定名并登记。 + captchaState 的 5 个、modalScreen 的 1 个、droppableGold 的 2 个、creatorTagState 的 1 个、 + battleTutorial 的 2 个、spectator 的 4 个、antiAddiction 的 3 个、moderation 的 13 个、 + 迁移 / 注册共用的 10 个(`Mit`…`_rt`)匿名类 / 单例同样按效果定名并登记。 ### 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 ecfcf1b..3f1ce13 100644 --- a/3DTank/names/root_wiring.json +++ b/3DTank/names/root_wiring.json @@ -1,5 +1,5 @@ { - "generatedAt": "2026-10-24T06:30:01.615Z", + "generatedAt": "2026-10-24T07:23:39.141Z", "module": "module_10032.js", "RootState": { "Class": "z6", @@ -49,7 +49,17 @@ "eventGoods", "creatorTagState", "rewardedVideosState", - "passwordChangeState" + "passwordChangeState", + "system", + "contextTutorial", + "battleTutorial", + "spectator", + "twoStepVerificationState", + "antiAddiction", + "moderation", + "migrationState", + "register", + "userReportsState" ], "AnonymousActions": [ { @@ -796,6 +806,198 @@ "Name": "ClearCreatorTag", "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", "Effect": "creatorTag=null(`n.ar2(null)`)" + }, + { + "Local": "oE", + "Name": "MarkMovementStarted", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "movementHasStarted=true(`n.pq8(cd, cd, !0)`)" + }, + { + "Local": "sE", + "Name": "ResetBattleTutorial", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "new rE() 整片复位(`new rE()`)" + }, + { + "Local": "XA", + "Name": "SetSpectators", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "spectators=整表(`n.yql(cd, cd, t.dqm_1)`)" + }, + { + "Local": "JA", + "Name": "AddSpectator", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "spectators += `new iR(userId, uid, inSupportGroup, inPunishersGroup)`(`Hg`)" + }, + { + "Local": "ZA", + "Name": "RemoveSpectator", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "spectators 按 userId 过滤掉一条(`Qd(s.kqm_1, t.iqm_1) || e.y(s)`)" + }, + { + "Local": "YA", + "Name": "SetSpectatorsListVisible", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "spectatorsListVisible=布尔(`n.yql(cd, cd, cd, t.cqm_1)`)" + }, + { + "Local": "FC", + "Name": "EnableRealNameCheck", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "isEnableRealNameCheck=true(`n.lq7(!0)`)" + }, + { + "Local": "XC", + "Name": "RequireUserIdVerification", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "unknownUserId=true、urlForEnterUserId=参数(`n.lq7(cd, !0, t.hq8_1)`)" + }, + { + "Local": "YC", + "Name": "UpdatePlayingTime", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "playingTimeInMinutes、alertMessage(`n.lq7(cd, cd, cd, t.fq8_1, t.gq8_1)`)" + }, + { + "Local": "UI", + "Name": "SetModeratorModes", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "moderatorMode / battleModeratorMode / eventOrganizerMode(`n.aqf(a, b, c)`)" + }, + { + "Local": "FI", + "Name": "SetModerationUrlTemplates", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "两个 URL 模板(`n.aqf(cd, cd, cd, t.vqg_1, t.wqg_1)`)" + }, + { + "Local": "XI", + "Name": "ShowBattleInfo", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "battleInfo + forceShowBattleInfo=true(`n.aqf(…, t.fqh_1, !0)`)" + }, + { + "Local": "JI", + "Name": "HideBattleInfo", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "forceShowBattleInfo=false(`n.aqf(…, !1)`)" + }, + { + "Local": "GI", + "Name": "AddSelectedUser", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "selectedUsers 去重后追加 `new OI(...)`(`Hg(e, new OI(...))`)" + }, + { + "Local": "WI", + "Name": "AddSelectedUserFromChat", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "同 GI,另一个派发点(`Hg(_, new OI(...))`)" + }, + { + "Local": "KI", + "Name": "RemoveSelectedUser", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "selectedUsers 按 userId 过滤(`Qd(v.oqf_1, t.eqh_1) || l.y(v)`)" + }, + { + "Local": "YI", + "Name": "ClearSelectedUsers", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "selectedUsers 清空(`var p = md()`)" + }, + { + "Local": "uA", + "Name": "ResetSelectedUsers", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "selectedUsers 清空(`var w = md()`,另一个派发点)" + }, + { + "Local": "ZI", + "Name": "ShowModerationSystemMessage", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "systemMessage + forceShowSystemMessage=true(`n.aqf(…, t.gqh_1, !0)`)" + }, + { + "Local": "QI", + "Name": "HideModerationSystemMessage", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "forceShowSystemMessage=false(`n.aqf(…, !1)`)" + }, + { + "Local": "tA", + "Name": "ShowBlockUserMenu", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "forceShowBlockUserMenu=true(`n.aqf(…, !0)`)" + }, + { + "Local": "nA", + "Name": "HideBlockUserMenu", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "forceShowBlockUserMenu=false(`n.aqf(…, !1)`)" + }, + { + "Local": "Mit", + "Name": "DisableRegister", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "isDisabled=true(`n.its(!0)`,只有注册切片读)" + }, + { + "Local": "Dit", + "Name": "UidIsFree", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "loginStatus / uidValidationStatus = FREE(`lrt()`)" + }, + { + "Local": "Lit", + "Name": "UidIsIncorrect", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "loginStatus / uidValidationStatus = INCORRECT(`frt()`)" + }, + { + "Local": "Bit", + "Name": "ResetUidStatus", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "loginStatus / uidValidationStatus = UNKNOWN(`crt()`)" + }, + { + "Local": "Hit", + "Name": "UidTooShort", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "loginStatus / uidValidationStatus = SHORT_NAME(`prt()`)" + }, + { + "Local": "Uit", + "Name": "RegisterPasswordCorrect", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "registerStatus=PASSWORD_CORRECT(`(Iit(), Xv)`,只有注册切片读)" + }, + { + "Local": "Fit", + "Name": "RegisterPasswordIncorrect", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "registerStatus=PASSWORD_INCORRECT(`drt()`)" + }, + { + "Local": "Git", + "Name": "RegisterFailed", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "registerStatus=FAILED(`grt()`)" + }, + { + "Local": "hrt", + "Name": "ResetRegisterStatus", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "registerStatus=UNKNOWN(`n.its(cd, cd, cd, cd, cd, wrt())`,只有注册切片读)" + }, + { + "Local": "_rt", + "Name": "ResetAuthenticationForm", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "login=\"\"、两个状态复位(两个切片都读)" } ], "Markers": [ @@ -958,13 +1160,13 @@ "Reducer": "Q.sq8", "Delegate": "Q.sq8(t, n.cpv_1)", "Actions": [ - "?oE", - "?sE", + "MarkMovementStarted", + "ResetBattleTutorial", "SetFragsCount", "ShowMessage" ], "Default": "new rE()", - "Migrated": false + "Migrated": true }, { "Field": "garage", @@ -1115,28 +1317,28 @@ "Reducer": "cp.stg", "Delegate": "cp.stg(t, n.hpv_1)", "Actions": [ - "?Bit", - "?Dit", - "?Fit", - "?Git", - "?Hit", - "?Lit", - "?Mit", - "?Uit", - "?_rt", - "?hrt", + "DisableRegister", "EnteredUidIsBusy", "ForceSetUid", "ReceiveOAuthUri", + "RegisterFailed", + "RegisterPasswordCorrect", + "RegisterPasswordIncorrect", + "ResetAuthenticationForm", + "ResetRegisterStatus", + "ResetUidStatus", "SetOpenedPassword", "SetPassword", "SetPasswordRestrictions", "SetUid", "SocialNetworkAvailability", - "SocialNetworkClientIds" + "SocialNetworkClientIds", + "UidIsFree", + "UidIsIncorrect", + "UidTooShort" ], "Default": "new Cit()", - "Migrated": false + "Migrated": true }, { "Field": "migrationState", @@ -1145,20 +1347,20 @@ "Reducer": "Uv.rtg", "Delegate": "Uv.rtg(t, n.ipv_1)", "Actions": [ - "?Bit", - "?Dit", - "?Fit", - "?Git", - "?Hit", - "?Lit", - "?_rt", "EnteredUidIsBusy", "ForceSetUid", + "RegisterFailed", + "RegisterPasswordIncorrect", + "ResetAuthenticationForm", + "ResetUidStatus", "SetPassword", - "SetUid" + "SetUid", + "UidIsFree", + "UidIsIncorrect", + "UidTooShort" ], "Default": "new $it()", - "Migrated": false + "Migrated": true }, { "Field": "inviteState", @@ -1216,7 +1418,7 @@ "SetFatalError" ], "Default": "new _9()", - "Migrated": false + "Migrated": true }, { "Field": "quests", @@ -2178,14 +2380,14 @@ "Reducer": "W.iq8", "Delegate": "W.iq8(t, n.kpx_1)", "Actions": [ - "?FC", - "?XC", - "?YC", + "EnableRealNameCheck", + "RequireUserIdVerification", "SetRealNameAndIdNumber", - "SetValidateAntiAddictionUserDataStatus" + "SetValidateAntiAddictionUserDataStatus", + "UpdatePlayingTime" ], "Default": "new UC()", - "Migrated": false + "Migrated": true }, { "Field": "spectator", @@ -2194,15 +2396,15 @@ "Reducer": "Li.jqm", "Delegate": "Li.jqm(t, n.lpx_1)", "Actions": [ - "?JA", - "?XA", - "?YA", - "?ZA", + "AddSpectator", + "RemoveSpectator", "SetSpectator", + "SetSpectators", + "SetSpectatorsListVisible", "UpdateHudLayout" ], "Default": "new HA()", - "Migrated": false + "Migrated": true }, { "Field": "moderation", @@ -2211,22 +2413,22 @@ "Reducer": "Ei.oqh", "Delegate": "Ei.oqh(t, n.mpx_1)", "Actions": [ - "?FI", - "?GI", - "?JI", - "?KI", - "?QI", - "?UI", - "?WI", - "?XI", - "?YI", - "?ZI", - "?nA", - "?tA", - "?uA" + "AddSelectedUser", + "AddSelectedUserFromChat", + "ClearSelectedUsers", + "HideBattleInfo", + "HideBlockUserMenu", + "HideModerationSystemMessage", + "RemoveSelectedUser", + "ResetSelectedUsers", + "SetModerationUrlTemplates", + "SetModeratorModes", + "ShowBattleInfo", + "ShowBlockUserMenu", + "ShowModerationSystemMessage" ], "Default": "new NI()", - "Migrated": false + "Migrated": true }, { "Field": "rulesUpdated", @@ -2331,7 +2533,7 @@ "VerificationStatus" ], "Default": "new x9()", - "Migrated": false + "Migrated": true }, { "Field": "rewardedVideosState", @@ -2402,7 +2604,7 @@ "SetUnViewedElements" ], "Default": "new Mst()", - "Migrated": false + "Migrated": true }, { "Field": "suppliesTutorial", @@ -2472,7 +2674,7 @@ "SetAvailableReports" ], "Default": "new Zst()", - "Migrated": false + "Migrated": true }, { "Field": "tankBuilderState", diff --git a/3DTank/tools/gen_root_wiring.js b/3DTank/tools/gen_root_wiring.js index a103d32..957d184 100644 --- a/3DTank/tools/gen_root_wiring.js +++ b/3DTank/tools/gen_root_wiring.js @@ -200,6 +200,43 @@ const AnonymousActionNames = new Map([ ['Yet', 'HideDroppableGoldAuthor'], // creatorTagState 切片(qO / SO.er2):jO 没有 toString。 ['jO', 'ClearCreatorTag'], + // battleTutorial 切片(rE / hE.sq8):oE/sE 没有 toString。 + ['oE', 'MarkMovementStarted'], + ['sE', 'ResetBattleTutorial'], + // spectator 切片(HA / tR.jqm):XA/JA/ZA/YA 没有 toString。 + ['XA', 'SetSpectators'], + ['JA', 'AddSpectator'], + ['ZA', 'RemoveSpectator'], + ['YA', 'SetSpectatorsListVisible'], + // antiAddiction 切片(UC / JC.iq8):FC/XC/YC 没有 toString。 + ['FC', 'EnableRealNameCheck'], + ['XC', 'RequireUserIdVerification'], + ['YC', 'UpdatePlayingTime'], + // moderation 切片(NI / _A.oqh):13 个类全都没有 toString。 + ['UI', 'SetModeratorModes'], + ['FI', 'SetModerationUrlTemplates'], + ['XI', 'ShowBattleInfo'], + ['JI', 'HideBattleInfo'], + ['GI', 'AddSelectedUser'], + ['WI', 'AddSelectedUserFromChat'], + ['KI', 'RemoveSelectedUser'], + ['YI', 'ClearSelectedUsers'], + ['uA', 'ResetSelectedUsers'], + ['ZI', 'ShowModerationSystemMessage'], + ['QI', 'HideModerationSystemMessage'], + ['tA', 'ShowBlockUserMenu'], + ['nA', 'HideBlockUserMenu'], + // 迁移 / 注册共用(module_10032 里各只定义一次):Mit/Dit/Lit/Bit/Uit/Fit/Git/Hit/hrt/_rt。 + ['Mit', 'DisableRegister'], + ['Dit', 'UidIsFree'], + ['Lit', 'UidIsIncorrect'], + ['Bit', 'ResetUidStatus'], + ['Hit', 'UidTooShort'], + ['Uit', 'RegisterPasswordCorrect'], + ['Fit', 'RegisterPasswordIncorrect'], + ['Git', 'RegisterFailed'], + ['hrt', 'ResetRegisterStatus'], + ['_rt', 'ResetAuthenticationForm'], ]); // 非动作的 `instanceof`:屏幕标识类,用于 GoTo 分支判断(不是 dispatch 的动作)。 @@ -628,6 +665,19 @@ const Migrated = { creatorTagState: { Type: 'CreatorTagStateSlice', Reduce: 'ReduceCreatorTag' }, rewardedVideosState: { Type: 'RewardedVideosStateSlice', Reduce: 'ReduceRewardedVideos' }, passwordChangeState: { Type: 'PasswordChangeStateSlice', Reduce: 'ReducePasswordChange' }, + system: { Type: 'SystemStateSlice', Reduce: 'ReduceSystem' }, + contextTutorial: { Type: 'ContextTutorialStateSlice', Reduce: 'ReduceContextTutorial' }, + battleTutorial: { Type: 'BattleTutorialStateSlice', Reduce: 'ReduceBattleTutorial' }, + spectator: { Type: 'SpectatorStateSlice', Reduce: 'ReduceSpectator' }, + twoStepVerificationState: { + Type: 'TwoStepVerificationStateSlice', + Reduce: 'ReduceTwoStepVerification', + }, + antiAddiction: { Type: 'AntiAddictionStateSlice', Reduce: 'ReduceAntiAddiction' }, + moderation: { Type: 'ModerationStateSlice', Reduce: 'ReduceModeration' }, + migrationState: { Type: 'MigrationStateSlice', Reduce: 'ReduceMigrationState' }, + register: { Type: 'RegisterStateSlice', Reduce: 'ReduceRegister' }, + userReportsState: { Type: 'UserReportsStateSlice', Reduce: 'ReduceUserReports' }, }; for (const R of Rows) R.Migrated = Object.prototype.hasOwnProperty.call(Migrated, R.Field); @@ -877,6 +927,58 @@ const CreatorTagAnonymousEffects = { jO: 'creatorTag=null(`n.ar2(null)`)', }; +// battleTutorial 切片(rE / hE.sq8):oE/sE 没有 toString。 +const BattleTutorialAnonymousEffects = { + oE: 'movementHasStarted=true(`n.pq8(cd, cd, !0)`)', + sE: 'new rE() 整片复位(`new rE()`)', +}; + +// spectator 切片(HA / tR.jqm):XA/JA/ZA/YA 没有 toString。 +const SpectatorAnonymousEffects = { + XA: 'spectators=整表(`n.yql(cd, cd, t.dqm_1)`)', + JA: 'spectators += `new iR(userId, uid, inSupportGroup, inPunishersGroup)`(`Hg`)', + ZA: 'spectators 按 userId 过滤掉一条(`Qd(s.kqm_1, t.iqm_1) || e.y(s)`)', + YA: 'spectatorsListVisible=布尔(`n.yql(cd, cd, cd, t.cqm_1)`)', +}; + +// antiAddiction 切片(UC / JC.iq8):FC/XC/YC 没有 toString。 +const AntiAddictionAnonymousEffects = { + FC: 'isEnableRealNameCheck=true(`n.lq7(!0)`)', + XC: 'unknownUserId=true、urlForEnterUserId=参数(`n.lq7(cd, !0, t.hq8_1)`)', + YC: 'playingTimeInMinutes、alertMessage(`n.lq7(cd, cd, cd, t.fq8_1, t.gq8_1)`)', +}; + +// moderation 切片(NI / _A.oqh):13 个类全都没有 toString。 +const ModerationAnonymousEffects = { + UI: 'moderatorMode / battleModeratorMode / eventOrganizerMode(`n.aqf(a, b, c)`)', + FI: '两个 URL 模板(`n.aqf(cd, cd, cd, t.vqg_1, t.wqg_1)`)', + XI: 'battleInfo + forceShowBattleInfo=true(`n.aqf(…, t.fqh_1, !0)`)', + JI: 'forceShowBattleInfo=false(`n.aqf(…, !1)`)', + GI: 'selectedUsers 去重后追加 `new OI(...)`(`Hg(e, new OI(...))`)', + WI: '同 GI,另一个派发点(`Hg(_, new OI(...))`)', + KI: 'selectedUsers 按 userId 过滤(`Qd(v.oqf_1, t.eqh_1) || l.y(v)`)', + YI: 'selectedUsers 清空(`var p = md()`)', + uA: 'selectedUsers 清空(`var w = md()`,另一个派发点)', + ZI: 'systemMessage + forceShowSystemMessage=true(`n.aqf(…, t.gqh_1, !0)`)', + QI: 'forceShowSystemMessage=false(`n.aqf(…, !1)`)', + tA: 'forceShowBlockUserMenu=true(`n.aqf(…, !0)`)', + nA: 'forceShowBlockUserMenu=false(`n.aqf(…, !1)`)', +}; + +// 迁移 / 注册共用(Sit.rtg 与 art.stg 都 instanceof 同一批类)。 +const AuthAnonymousEffects = { + Mit: 'isDisabled=true(`n.its(!0)`,只有注册切片读)', + Dit: 'loginStatus / uidValidationStatus = FREE(`lrt()`)', + Lit: 'loginStatus / uidValidationStatus = INCORRECT(`frt()`)', + Bit: 'loginStatus / uidValidationStatus = UNKNOWN(`crt()`)', + Hit: 'loginStatus / uidValidationStatus = SHORT_NAME(`prt()`)', + Uit: 'registerStatus=PASSWORD_CORRECT(`(Iit(), Xv)`,只有注册切片读)', + Fit: 'registerStatus=PASSWORD_INCORRECT(`drt()`)', + Git: 'registerStatus=FAILED(`grt()`)', + hrt: 'registerStatus=UNKNOWN(`n.its(cd, cd, cd, cd, cd, wrt())`,只有注册切片读)', + _rt: 'login=""、两个状态复位(两个切片都读)', +}; + fs.writeFileSync( path.join(P, 'names', 'root_wiring.json'), JSON.stringify( @@ -1043,6 +1145,36 @@ fs.writeFileSync( DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', Effect, })), + ...Object.entries(BattleTutorialAnonymousEffects).map(([Local, Effect]) => ({ + Local, + Name: AnonymousActionNames.get(Local), + DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', + Effect, + })), + ...Object.entries(SpectatorAnonymousEffects).map(([Local, Effect]) => ({ + Local, + Name: AnonymousActionNames.get(Local), + DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', + Effect, + })), + ...Object.entries(AntiAddictionAnonymousEffects).map(([Local, Effect]) => ({ + Local, + Name: AnonymousActionNames.get(Local), + DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', + Effect, + })), + ...Object.entries(ModerationAnonymousEffects).map(([Local, Effect]) => ({ + Local, + Name: AnonymousActionNames.get(Local), + DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', + Effect, + })), + ...Object.entries(AuthAnonymousEffects).map(([Local, Effect]) => ({ + Local, + Name: AnonymousActionNames.get(Local), + DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', + Effect, + })), ], Markers: [ { diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/AntiAddictionReducer.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/AntiAddictionReducer.kt new file mode 100644 index 0000000..b065cf0 --- /dev/null +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/AntiAddictionReducer.kt @@ -0,0 +1,68 @@ +// AntiAddictionReducer.kt —— 防沉迷切片(对应 module_10032 的 UC 状态 + JC.iq8) +// +// 证据链: +// 状态类 UC:8 个字段,toString 名 "AntiAddictionState(…)"。 +// 默认:isEnableRealNameCheck=false、unknownUserId=false、urlForEnterUserId=null、 +// playingTimeInMinutes=0、alertMessage=""、idNumber=""、realName=""、validateStatus=null。 +// 根委派:`W.iq8(t, n.kpx_1)`。 +// reducer: +// FC(无 toString) → isEnableRealNameCheck=true +// XC(无 toString) → unknownUserId=true、urlForEnterUserId=参数 +// YC(无 toString) → playingTimeInMinutes、alertMessage +// HC(SetRealNameAndIdNumber) → idNumber/realName;参数为 null 时保留原值 +// VC(SetValidateAntiAddictionUserDataStatus)→ validateStatus +package App.State.Reducers + +import App.State.SetRealNameAndIdNumber +import App.State.SetValidateAntiAddictionUserDataStatus + +/* ------------------------------------------------------------------ 匿名动作 */ + +/** 原 `FC`:开启实名认证检查(`n.lq7(!0)`)。 */ +object EnableRealNameCheck + +/** 原 `XC`:要求补录用户 id,并给出入口地址(`n.lq7(cd, !0, url)`)。 */ +data class RequireUserIdVerification(val urlForEnterUserId: Any?) + +/** 原 `YC`:更新累计在线时长与提示语。 */ +data class UpdatePlayingTime( + val playingTimeInMinutes: Any?, + val alertMessage: String, +) + +/* ------------------------------------------------------------------ 状态 */ + +data class AntiAddictionStateSlice( + val IsEnableRealNameCheck: Boolean = false, + val UnknownUserId: Boolean = false, + val UrlForEnterUserId: Any? = null, + val PlayingTimeInMinutes: Any? = 0, + val AlertMessage: String = "", + val IdNumber: Any? = "", + val RealName: Any? = "", + val ValidateStatus: Any? = null, +) + +/* ------------------------------------------------------------------ reducer */ + +fun ReduceAntiAddiction( + State0: AntiAddictionStateSlice, + Action: Any, +): AntiAddictionStateSlice = when (Action) { + is EnableRealNameCheck -> State0.copy(IsEnableRealNameCheck = true) + is RequireUserIdVerification -> State0.copy( + UnknownUserId = true, + UrlForEnterUserId = Action.urlForEnterUserId, + ) + is UpdatePlayingTime -> State0.copy( + PlayingTimeInMinutes = Action.playingTimeInMinutes, + AlertMessage = Action.alertMessage, + ) + is SetRealNameAndIdNumber -> State0.copy( + IdNumber = Action.idNumber ?: State0.IdNumber, + RealName = Action.realName ?: State0.RealName, + ) + is SetValidateAntiAddictionUserDataStatus -> + State0.copy(ValidateStatus = Action.validateStatus) + else -> State0 +} diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/AuthActions.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/AuthActions.kt new file mode 100644 index 0000000..d67fd19 --- /dev/null +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/AuthActions.kt @@ -0,0 +1,36 @@ +// AuthActions.kt —— 登录 / 迁移 / 注册共用的匿名动作 +// +// 证据链:module_10032 里这些动作类只定义一次(16907–16965), +// 迁移切片(Sit.rtg)和注册切片(art.stg)都直接 `instanceof` 它们, +// 因此 Kotlin 侧也只能有一份,两个 reducer 共同响应、各自动自己读的字段。 +package App.State.Reducers + +/** 原 `Mit`:注册按钮置灰(只有注册切片读)。 */ +object DisableRegister + +/** 原 `Dit`:uid 可用(迁移的 loginStatus / 注册的 uidValidationStatus)。 */ +object UidIsFree + +/** 原 `Lit`:uid 不合法。 */ +object UidIsIncorrect + +/** 原 `Bit`:uid 校验状态复位为 UNKNOWN。 */ +object ResetUidStatus + +/** 原 `Hit`:uid 过短。 */ +object UidTooShort + +/** 原 `Uit`:注册密码校验通过(只有注册切片读)。 */ +object RegisterPasswordCorrect + +/** 原 `Fit`:注册密码错误。 */ +object RegisterPasswordIncorrect + +/** 原 `Git`:注册失败。 */ +object RegisterFailed + +/** 原 `hrt`:注册状态复位为 UNKNOWN(只有注册切片读)。 */ +object ResetRegisterStatus + +/** 原 `_rt`:清空 login 并把两个状态复位(迁移与注册都读)。 */ +object ResetAuthenticationForm diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/BattleTutorialReducer.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/BattleTutorialReducer.kt new file mode 100644 index 0000000..54ffb53 --- /dev/null +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/BattleTutorialReducer.kt @@ -0,0 +1,44 @@ +// BattleTutorialReducer.kt —— 战斗内上手指引切片(对应 module_10032 的 rE 状态 + hE.sq8) +// +// 证据链: +// 状态类 rE:3 个字段,toString 名 "BattleTutorialState(…)"。 +// 默认:message=null、fragsCount=0、movementHasStarted=false。 +// 根委派:`Q.sq8(t, n.cpv_1)`。 +// reducer: +// eE(ShowMessage) → message +// _E(SetFragsCount) → fragsCount +// oE(无 toString) → movementHasStarted=true +// sE(无 toString) → `new rE()` 整片复位 +package App.State.Reducers + +import App.State.SetFragsCount +import App.State.ShowMessage + +/* ------------------------------------------------------------------ 匿名动作 */ + +/** 原 `oE`:玩家开始移动,指引进入下一步。 */ +object MarkMovementStarted + +/** 原 `sE`:复位整片指引状态(`new rE()`)。 */ +object ResetBattleTutorial + +/* ------------------------------------------------------------------ 状态 */ + +data class BattleTutorialStateSlice( + val Message: Any? = null, + val FragsCount: Any? = 0, + val MovementHasStarted: Boolean = false, +) + +/* ------------------------------------------------------------------ reducer */ + +fun ReduceBattleTutorial( + State0: BattleTutorialStateSlice, + Action: Any, +): BattleTutorialStateSlice = when (Action) { + is ShowMessage -> State0.copy(Message = Action.message) + is SetFragsCount -> State0.copy(FragsCount = Action.fragsCount) + is MarkMovementStarted -> State0.copy(MovementHasStarted = true) + is ResetBattleTutorial -> BattleTutorialStateSlice() + else -> State0 +} diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/ContextTutorialReducer.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/ContextTutorialReducer.kt new file mode 100644 index 0000000..e759d3b --- /dev/null +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/ContextTutorialReducer.kt @@ -0,0 +1,47 @@ +// ContextTutorialReducer.kt —— 上手指引切片(对应 module_10032 的 Mst 状态 + Xst.tth) +// +// 证据链: +// 状态类 Mst:4 个字段,toString 名 "ContextTutorialState(…)"。 +// 默认:currentElement=null、loadedElements=emptyMap()、unViewedElements=emptyList()、missionToElement=emptyMap()。 +// 根委派:`Jst().tth(t, n.ypx_1)`。 +// reducer: +// Lst(LoadCurrentElement) → currentElement +// Bst(SetUnViewedElements) → unViewedElements +// Fst(MarkElementAsViewed) → unViewedElements 去掉 element(`Ym` = List.minus) +// Ust(SetLoadedElements) → loadedElements +// Gst(SetMissionToElement) → missionToElement +package App.State.Reducers + +import App.State.LoadCurrentElement +import App.State.MarkElementAsViewed +import App.State.SetLoadedElements +import App.State.SetMissionToElement +import App.State.SetUnViewedElements + +/* ------------------------------------------------------------------ 状态 */ + +data class ContextTutorialStateSlice( + val CurrentElement: Any? = null, + val LoadedElements: Map = emptyMap(), + val UnViewedElements: List = emptyList(), + val MissionToElement: Map = emptyMap(), +) + +/* ------------------------------------------------------------------ reducer */ + +@Suppress("UNCHECKED_CAST") +private fun AsMap(Value: Any?): Map = + Value as? Map ?: emptyMap() + +fun ReduceContextTutorial( + State0: ContextTutorialStateSlice, + Action: Any, +): ContextTutorialStateSlice = when (Action) { + is LoadCurrentElement -> State0.copy(CurrentElement = Action.element) + is SetUnViewedElements -> State0.copy(UnViewedElements = Action.unViewedElements) + is MarkElementAsViewed -> + State0.copy(UnViewedElements = State0.UnViewedElements - Action.element) + is SetLoadedElements -> State0.copy(LoadedElements = AsMap(Action.loadedElements)) + is SetMissionToElement -> State0.copy(MissionToElement = Action.missionToElement) + else -> State0 +} diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/MigrationStateReducer.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/MigrationStateReducer.kt new file mode 100644 index 0000000..eae5fed --- /dev/null +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/MigrationStateReducer.kt @@ -0,0 +1,102 @@ +// MigrationStateReducer.kt —— 老账号迁移切片(对应 module_10032 的 $it 状态 + Sit.rtg) +// +// 证据链: +// 状态类 $it:7 个字段,toString 名 "MigrationState(…)";copy 是 jtr(7 字段 + 回调)。 +// 默认:requestLogin=false、login=""、forceLogin=null、password=""、 +// loginStatus=UNKNOWN、registerStatus=UNKNOWN、advisedUids=emptyList()。 +// 根委派:`Uv.rtg(t, n.ipv_1)`。 +// reducer(11 个实例臂): +// Kit(SetUid) → login + 两个状态复位 +// Jit(ForceSetUid) → forceLogin + 两个状态复位 +// Yit(SetPassword) → password + registerStatus 复位 +// Dit(UidIsFree) → loginStatus=FREE +// Oit(EnteredUidIsBusy)→ loginStatus=BUSY + advisedUids +// Lit(UidIsIncorrect) → loginStatus=INCORRECT +// Bit(ResetUidStatus) → loginStatus=UNKNOWN +// Fit(RegisterPasswordIncorrect)→ registerStatus=PASSWORD_INCORRECT +// Git(RegisterFailed) → registerStatus=FAILED +// Hit(UidTooShort) → loginStatus=SHORT_NAME +// _rt(ResetAuthenticationForm)→ login="" + 两个状态复位 +// +// 状态枚举直接对应源码 `Eit()` / `Iit()`: +// LoginStatus = UNKNOWN / FREE / INCORRECT / BUSY / SHORT_NAME +// RegisterStatus = UNKNOWN / PASSWORD_CORRECT / PASSWORD_INCORRECT / FAILED +// +// 注意:`SetUid` / `ForceSetUid` / `SetPassword` 与注册、登录切片共用同一批动作类 +// (原包也是同一批类),因此几片 reducer 会同时响应它们,各自动自己读的字段。 +package App.State.Reducers + +import App.State.EnteredUidIsBusy +import App.State.ForceSetUid +import App.State.SetPassword +import App.State.SetUid + +/* ------------------------------------------------------------------ 枚举 */ + +enum class MigrationLoginStatus { + UNKNOWN, + FREE, + INCORRECT, + BUSY, + SHORT_NAME, +} + +enum class MigrationRegisterStatus { + UNKNOWN, + PASSWORD_CORRECT, + PASSWORD_INCORRECT, + FAILED, +} + +/* ------------------------------------------------------------------ 状态 */ + +data class MigrationStateSlice( + val RequestLogin: Boolean = false, + val Login: String = "", + val ForceLogin: Any? = null, + val Password: String = "", + val LoginStatus: MigrationLoginStatus = MigrationLoginStatus.UNKNOWN, + val RegisterStatus: MigrationRegisterStatus = MigrationRegisterStatus.UNKNOWN, + val AdvisedUids: List = emptyList(), +) + +/* ------------------------------------------------------------------ reducer */ + +fun ReduceMigrationState( + State0: MigrationStateSlice, + Action: Any, +): MigrationStateSlice = when (Action) { + // Kit / Jit / Yit 都会把两个状态打回 UNKNOWN(原 `crt()` / `wrt()`)。 + is SetUid -> State0.copy( + Login = Action.uid?.toString() ?: "", + LoginStatus = MigrationLoginStatus.UNKNOWN, + RegisterStatus = MigrationRegisterStatus.UNKNOWN, + ) + is ForceSetUid -> State0.copy( + ForceLogin = Action.uid, + LoginStatus = MigrationLoginStatus.UNKNOWN, + RegisterStatus = MigrationRegisterStatus.UNKNOWN, + ) + is SetPassword -> State0.copy( + Password = Action.password?.toString() ?: "", + RegisterStatus = MigrationRegisterStatus.UNKNOWN, + ) + is UidIsFree -> State0.copy(LoginStatus = MigrationLoginStatus.FREE) + is EnteredUidIsBusy -> State0.copy( + LoginStatus = MigrationLoginStatus.BUSY, + AdvisedUids = Action.advisedUids as? List ?: emptyList(), + ) + is UidIsIncorrect -> State0.copy(LoginStatus = MigrationLoginStatus.INCORRECT) + is ResetUidStatus -> State0.copy(LoginStatus = MigrationLoginStatus.UNKNOWN) + is UidTooShort -> State0.copy(LoginStatus = MigrationLoginStatus.SHORT_NAME) + is RegisterPasswordIncorrect -> + State0.copy(RegisterStatus = MigrationRegisterStatus.PASSWORD_INCORRECT) + is RegisterFailed -> + State0.copy(RegisterStatus = MigrationRegisterStatus.FAILED) + is ResetAuthenticationForm -> State0.copy( + Login = "", + LoginStatus = MigrationLoginStatus.UNKNOWN, + RegisterStatus = MigrationRegisterStatus.UNKNOWN, + ) + else -> State0 +} diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/ModerationReducer.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/ModerationReducer.kt new file mode 100644 index 0000000..8909bbb --- /dev/null +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/ModerationReducer.kt @@ -0,0 +1,158 @@ +// ModerationReducer.kt —— 管理员工具切片(对应 module_10032 的 NI 状态 + _A.oqh) +// +// 证据链: +// 状态类 NI:11 个字段,toString 名 "Moderation(…)"。 +// 默认:三个模式 false、两个 URL 模板 ""、battleInfo=new MI()、三个 forceShow false、 +// systemMessage=""、selectedUsers=emptyList()。 +// 根委派:`Ei.oqh(t, n.mpx_1)`。 +// reducer(13 个实例臂): +// UI(无 toString) → 三个模式开关 +// FI(无 toString) → 两个 URL 模板 +// XI(无 toString) → battleInfo + forceShowBattleInfo=true +// JI(无 toString) → forceShowBattleInfo=false +// GI(无 toString) → selectedUsers 先按 userId 去重、再追加 `new OI(...)` +// WI(无 toString) → 同 GI(另一个派发点,效果一致) +// KI(无 toString) → selectedUsers 按 userId 移除 +// YI(无 toString) → selectedUsers 清空 +// uA(无 toString) → selectedUsers 清空(同 YI,另一个派发点) +// ZI(无 toString) → systemMessage + forceShowSystemMessage=true +// QI(无 toString) → forceShowSystemMessage=false +// tA(无 toString) → forceShowBlockUserMenu=true +// nA(无 toString) → forceShowBlockUserMenu=false +// +// 说明:`battleInfo` 的原模型 `MI` 有 13 个字段(战斗信息面板),属战斗层,这里按 +// `Any?` 收口(缺省 null),只在 ShowBattleInfo 时整体替换。 +package App.State.Reducers + +/* ------------------------------------------------------------------ 匿名动作 */ + +/** 原 `UI`:一次写三个管理员模式开关。 */ +data class SetModeratorModes( + val moderatorMode: Boolean, + val battleModeratorMode: Boolean, + val eventOrganizerMode: Boolean, +) + +/** 原 `FI`:写管理员工具的战斗 / 用户链接模板。 */ +data class SetModerationUrlTemplates( + val battleUrlTemplate: String, + val userUrlTemplate: String, +) + +/** 原 `XI`:显示战斗信息面板。 */ +data class ShowBattleInfo(val battleInfo: Any?) + +/** 原 `JI`:收起战斗信息面板。 */ +object HideBattleInfo + +/** 原 `GI`:把用户加入选中列表(先按 userId 去重)。 */ +data class AddSelectedUser( + val userId: Any?, + val userRank: Any?, + val userName: Any?, +) + +/** 原 `WI`:另一个派发点上把用户加入选中列表,效果与 `GI` 相同。 */ +data class AddSelectedUserFromChat( + val userId: Any?, + val userRank: Any?, + val userName: Any?, +) + +/** 原 `KI`:从选中列表移除用户。 */ +data class RemoveSelectedUser(val userId: Any?) + +/** 原 `YI`:清空选中列表。 */ +object ClearSelectedUsers + +/** 原 `uA`:另一个派发点上清空选中列表,效果与 `YI` 相同。 */ +object ResetSelectedUsers + +/** 原 `ZI`:显示管理员系统消息。 */ +data class ShowModerationSystemMessage(val message: String) + +/** 原 `QI`:收起管理员系统消息。 */ +object HideModerationSystemMessage + +/** 原 `tA`:强制弹出封禁用户菜单。 */ +object ShowBlockUserMenu + +/** 原 `nA`:收起封禁用户菜单。 */ +object HideBlockUserMenu + +/* ------------------------------------------------------------------ 模型 */ + +data class ModerationUserRecord( + val userId: Any?, + val userRank: Any?, + val userName: Any?, +) + +/* ------------------------------------------------------------------ 状态 */ + +data class ModerationStateSlice( + val ModeratorMode: Boolean = false, + val BattleModeratorMode: Boolean = false, + val EventOrganizerMode: Boolean = false, + val ModerationToolBattleUrlTemplate: String = "", + val ModerationToolUserUrlTemplate: String = "", + val BattleInfo: Any? = null, + val ForceShowBattleInfo: Boolean = false, + val SystemMessage: String = "", + val ForceShowSystemMessage: Boolean = false, + val ForceShowBlockUserMenu: Boolean = false, + val SelectedUsers: List = emptyList(), +) + +/* ------------------------------------------------------------------ reducer */ + +/** `Hg(n.yqe_1, new OI(...))` 的等价:先去掉同 userId 的旧记录,再追加。 */ +private fun UpsertUser(Users: List, Record: ModerationUserRecord): List = + Users.filterNot { (it as? ModerationUserRecord)?.userId == Record.userId } + Record + +fun ReduceModeration( + State0: ModerationStateSlice, + Action: Any, +): ModerationStateSlice = when (Action) { + is SetModeratorModes -> State0.copy( + ModeratorMode = Action.moderatorMode, + BattleModeratorMode = Action.battleModeratorMode, + EventOrganizerMode = Action.eventOrganizerMode, + ) + is SetModerationUrlTemplates -> State0.copy( + ModerationToolBattleUrlTemplate = Action.battleUrlTemplate, + ModerationToolUserUrlTemplate = Action.userUrlTemplate, + ) + is ShowBattleInfo -> State0.copy( + BattleInfo = Action.battleInfo, + ForceShowBattleInfo = true, + ) + is HideBattleInfo -> State0.copy(ForceShowBattleInfo = false) + is AddSelectedUser -> State0.copy( + SelectedUsers = UpsertUser( + State0.SelectedUsers, + ModerationUserRecord(Action.userId, Action.userRank, Action.userName), + ), + ) + is AddSelectedUserFromChat -> State0.copy( + SelectedUsers = UpsertUser( + State0.SelectedUsers, + ModerationUserRecord(Action.userId, Action.userRank, Action.userName), + ), + ) + is RemoveSelectedUser -> State0.copy( + SelectedUsers = State0.SelectedUsers.filterNot { + (it as? ModerationUserRecord)?.userId == Action.userId + }, + ) + is ClearSelectedUsers -> State0.copy(SelectedUsers = emptyList()) + is ResetSelectedUsers -> State0.copy(SelectedUsers = emptyList()) + is ShowModerationSystemMessage -> State0.copy( + SystemMessage = Action.message, + ForceShowSystemMessage = true, + ) + is HideModerationSystemMessage -> State0.copy(ForceShowSystemMessage = false) + is ShowBlockUserMenu -> State0.copy(ForceShowBlockUserMenu = true) + is HideBlockUserMenu -> State0.copy(ForceShowBlockUserMenu = false) + else -> State0 +} diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/RegisterReducer.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/RegisterReducer.kt new file mode 100644 index 0000000..ccd2471 --- /dev/null +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/RegisterReducer.kt @@ -0,0 +1,128 @@ +// RegisterReducer.kt —— 注册切片(对应 module_10032 的 Cit 状态 + art.stg) +// +// 证据链: +// 状态类 Cit:16 个字段,toString 名 "RegisterState(…)";copy 是 its(16 字段 + 回调)。 +// 默认:isDisabled=false、login=""、forceLogin=null、password=""、 +// uidValidationStatus=UNKNOWN、registerStatus=UNKNOWN、advisedUids=emptyList()、 +// socialNetworkAvailability=emptyMap()、socialNetworkClientIds=emptyMap()、 +// socialNetworkOAuthData=null、minPasswordLength=6、maxPasswordLength=20、 +// 三个 passwordNeed* 为 false、openedPassword=true。 +// 根委派:`cp.stg(t, n.hpv_1)`。 +// reducer(19 个实例臂): +// Mit(DisableRegister)→ isDisabled=true +// Kit(SetUid) → login;uidValidationStatus=UNKNOWN;若原状态不是 INCORRECT, +// registerStatus 也复位(原 `2 === n.wtr_1.k3_1` 分支) +// Jit(ForceSetUid)→ forceLogin;状态复位规则同上 +// Yit(SetPassword)→ password + registerStatus=UNKNOWN +// Dit / Oit / Lit / Bit / Hit → uidValidationStatus = FREE / BUSY(+advisedUids) / INCORRECT / UNKNOWN / SHORT_NAME +// Uit / Fit / Git → registerStatus = PASSWORD_CORRECT / PASSWORD_INCORRECT / FAILED +// trt(SocialNetworkAvailability)/ nrt(SocialNetworkClientIds)/ ert(ReceiveOAuthUri) +// _rt(ResetAuthenticationForm)→ login="" + 两个状态复位 +// hrt(ResetRegisterStatus)→ registerStatus=UNKNOWN +// Nit(SetPasswordRestrictions)→ 5 个密码规则字段 +// ort(SetOpenedPassword)→ openedPassword +package App.State.Reducers + +import App.State.EnteredUidIsBusy +import App.State.ForceSetUid +import App.State.ReceiveOAuthUri +import App.State.SetOpenedPassword +import App.State.SetPassword +import App.State.SetPasswordRestrictions +import App.State.SetUid +import App.State.SocialNetworkAvailability +import App.State.SocialNetworkClientIds + +/* ------------------------------------------------------------------ 枚举 */ + +enum class UidStatus { + UNKNOWN, + FREE, + INCORRECT, + BUSY, + SHORT_NAME, +} + +enum class RegistrationStatus { + UNKNOWN, + PASSWORD_CORRECT, + PASSWORD_INCORRECT, + FAILED, +} + +/* ------------------------------------------------------------------ 状态 */ + +data class RegisterStateSlice( + val IsDisabled: Boolean = false, + val Login: String = "", + val ForceLogin: Any? = null, + val Password: String = "", + val UidValidationStatus: UidStatus = UidStatus.UNKNOWN, + val RegisterStatus: RegistrationStatus = RegistrationStatus.UNKNOWN, + val AdvisedUids: List = emptyList(), + val SocialNetworkAvailability: Map = emptyMap(), + val SocialNetworkClientIds: Any? = emptyMap(), + val SocialNetworkOAuthData: Any? = null, + val MinPasswordLength: Int = 6, + val MaxPasswordLength: Int = 20, + val PasswordNeedLower: Boolean = false, + val PasswordNeedUpper: Boolean = false, + val PasswordNeedDigit: Boolean = false, + val OpenedPassword: Boolean = true, +) + +/* ------------------------------------------------------------------ reducer */ + +/** `Kit` / `Jit` 共用:uid 变化时复位校验状态;原本不是 INCORRECT 才连注册状态一起复位。 */ +private fun ResetUidRelated(State0: RegisterStateSlice): RegisterStateSlice = State0.copy( + UidValidationStatus = UidStatus.UNKNOWN, + RegisterStatus = + if (State0.UidValidationStatus == UidStatus.INCORRECT) State0.RegisterStatus + else RegistrationStatus.UNKNOWN, +) + +fun ReduceRegister( + State0: RegisterStateSlice, + Action: Any, +): RegisterStateSlice = when (Action) { + is DisableRegister -> State0.copy(IsDisabled = true) + is SetUid -> ResetUidRelated(State0.copy(Login = Action.uid?.toString() ?: "")) + is ForceSetUid -> ResetUidRelated(State0.copy(ForceLogin = Action.uid)) + is SetPassword -> State0.copy( + Password = Action.password?.toString() ?: "", + RegisterStatus = RegistrationStatus.UNKNOWN, + ) + is UidIsFree -> State0.copy(UidValidationStatus = UidStatus.FREE) + is EnteredUidIsBusy -> State0.copy( + UidValidationStatus = UidStatus.BUSY, + AdvisedUids = Action.advisedUids as? List ?: emptyList(), + ) + is UidIsIncorrect -> State0.copy(UidValidationStatus = UidStatus.INCORRECT) + is ResetUidStatus -> State0.copy(UidValidationStatus = UidStatus.UNKNOWN) + is UidTooShort -> State0.copy(UidValidationStatus = UidStatus.SHORT_NAME) + is RegisterPasswordCorrect -> + State0.copy(RegisterStatus = RegistrationStatus.PASSWORD_CORRECT) + is RegisterPasswordIncorrect -> + State0.copy(RegisterStatus = RegistrationStatus.PASSWORD_INCORRECT) + is RegisterFailed -> State0.copy(RegisterStatus = RegistrationStatus.FAILED) + is ResetRegisterStatus -> State0.copy(RegisterStatus = RegistrationStatus.UNKNOWN) + is SocialNetworkAvailability -> + State0.copy(SocialNetworkAvailability = Action.socialNetworkAvailability) + is SocialNetworkClientIds -> + State0.copy(SocialNetworkClientIds = Action.socialNetworkClientIds) + is ReceiveOAuthUri -> State0.copy(SocialNetworkOAuthData = Action.startOAuthData) + is ResetAuthenticationForm -> State0.copy( + Login = "", + UidValidationStatus = UidStatus.UNKNOWN, + RegisterStatus = RegistrationStatus.UNKNOWN, + ) + is SetPasswordRestrictions -> State0.copy( + MinPasswordLength = Action.minPasswordLength, + MaxPasswordLength = Action.maxPasswordLength, + PasswordNeedLower = Action.passwordNeedLower, + PasswordNeedUpper = Action.passwordNeedUpper, + PasswordNeedDigit = Action.passwordNeedDigit, + ) + is SetOpenedPassword -> State0.copy(OpenedPassword = Action.openedPassword) + else -> State0 +} diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/SpectatorReducer.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/SpectatorReducer.kt new file mode 100644 index 0000000..dcccba1 --- /dev/null +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/SpectatorReducer.kt @@ -0,0 +1,75 @@ +// SpectatorReducer.kt —— 观战切片(对应 module_10032 的 HA 状态 + tR.jqm) +// +// 证据链: +// 状态类 HA:4 个字段,toString 名 "Spectator(…)"。 +// 默认:isSpectator=false、spectatorHudLayout=new nR()、spectators=emptyList()、spectatorsListVisible=true。 +// 根委派:`Li.jqm(t, n.lpx_1)`。 +// reducer: +// WA(SetSpectator) → isSpectator +// VA(UpdateHudLayout) → spectatorHudLayout +// XA(无 toString) → spectators 整表替换 +// JA(无 toString) → spectators 追加 `new iR(...)`(SpectatorInfo 四字段) +// ZA(无 toString) → spectators 按 userId 过滤掉一个 +// YA(无 toString) → spectatorsListVisible +// +// 说明:`spectatorHudLayout` 的默认值是 `new nR()`(15 字段的 HUD 布局对象), +// 该模型属于战斗层,这里按 `Any?` 收口(缺省 null),只在收到 UpdateHudLayout 时替换。 +package App.State.Reducers + +import App.State.SetSpectator +import App.State.SpectatorInfo +import App.State.UpdateHudLayout + +/* ------------------------------------------------------------------ 匿名动作 */ + +/** 原 `XA`:整体替换观战者列表。 */ +data class SetSpectators(val spectators: List) + +/** 原 `JA`:追加一个观战者(`new iR(userId, uid, inSupportGroup, inPunishersGroup)`)。 */ +data class AddSpectator( + val userId: Long, + val uid: Any?, + val inSupportGroup: Boolean, + val inPunishersGroup: Boolean, +) + +/** 原 `ZA`:按 userId 移除观战者。 */ +data class RemoveSpectator(val userId: Long) + +/** 原 `YA`:切换观战者列表可见性。 */ +data class SetSpectatorsListVisible(val visible: Boolean) + +/* ------------------------------------------------------------------ 状态 */ + +data class SpectatorStateSlice( + val IsSpectator: Boolean = false, + val SpectatorHudLayout: Any? = null, + val Spectators: List = emptyList(), + val SpectatorsListVisible: Boolean = true, +) + +/* ------------------------------------------------------------------ reducer */ + +fun ReduceSpectator( + State0: SpectatorStateSlice, + Action: Any, +): SpectatorStateSlice = when (Action) { + is SetSpectator -> State0.copy(IsSpectator = Action.spectatorMode) + is UpdateHudLayout -> State0.copy(SpectatorHudLayout = Action.hudLayout) + is SetSpectators -> State0.copy(Spectators = Action.spectators) + is AddSpectator -> State0.copy( + Spectators = State0.Spectators + SpectatorInfo( + userId = Action.userId, + uid = Action.uid, + inSupportGroup = Action.inSupportGroup, + inPunishersGroup = Action.inPunishersGroup, + ), + ) + is RemoveSpectator -> State0.copy( + Spectators = State0.Spectators.filterNot { + (it as? SpectatorInfo)?.userId == Action.userId + }, + ) + is SetSpectatorsListVisible -> State0.copy(SpectatorsListVisible = Action.visible) + else -> State0 +} diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/SystemReducer.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/SystemReducer.kt new file mode 100644 index 0000000..5762b7d --- /dev/null +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/SystemReducer.kt @@ -0,0 +1,32 @@ +// SystemReducer.kt —— 全屏系统消息切片(对应 module_10032 的 _9 状态 + p9.xt9) +// +// 证据链: +// 状态类 _9:3 个字段,toString 名 "SystemScreenState(message=…, restart=…, fatalError=…)"。 +// 默认:message=null、restart=false、fatalError=null。 +// 根委派:`w9().xt9(t, n.mpv_1)`。 +// reducer: +// v9(DoShowMessage) → message、restart +// SetFatalError → fatalError +package App.State.Reducers + +import App.State.DoShowMessage +import App.State.SetFatalError + +/* ------------------------------------------------------------------ 状态 */ + +data class SystemStateSlice( + val Message: Any? = null, + val Restart: Boolean = false, + val FatalError: Any? = null, +) + +/* ------------------------------------------------------------------ reducer */ + +fun ReduceSystem( + State0: SystemStateSlice, + Action: Any, +): SystemStateSlice = when (Action) { + is DoShowMessage -> State0.copy(Message = Action.message, Restart = Action.restart) + is SetFatalError -> State0.copy(FatalError = Action.fatalError) + else -> State0 +} diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/TwoStepVerificationReducer.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/TwoStepVerificationReducer.kt new file mode 100644 index 0000000..e9df1b8 --- /dev/null +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/TwoStepVerificationReducer.kt @@ -0,0 +1,55 @@ +// TwoStepVerificationReducer.kt —— 两步验证切片(对应 module_10032 的 x9 状态 + P9.atb) +// +// 证据链: +// 状态类 x9:6 个字段,toString 名 "TwoStepVerificationState(…)"。 +// 默认:isUse2FA=false、isActivated2FA=false、qrCodeDataURI=null、sharedSecret=null、 +// verificationStatus=null、enable2FaRequest=false。 +// 根委派:`_f.atb(t, n.tpx_1)`。 +// reducer: +// j9(SetUse2FA) → isUse2FA +// S9(ActivationStatus2FA) → isActivated2FA +// C9(SetSecretAndQRCode) → qrCodeDataURI、sharedSecret +// I9(VerificationStatus) → null → verificationStatus=null +// false → verificationStatus=false +// true → isActivated2FA=true(成功即视为已启用) +// A9(Enable2FaRequest) → enable2FaRequest +package App.State.Reducers + +import App.State.ActivationStatus2FA +import App.State.Enable2FaRequest +import App.State.SetSecretAndQRCode +import App.State.SetUse2FA +import App.State.VerificationStatus + +/* ------------------------------------------------------------------ 状态 */ + +data class TwoStepVerificationStateSlice( + val IsUse2FA: Boolean = false, + val IsActivated2FA: Boolean = false, + val QrCodeDataURI: Any? = null, + val SharedSecret: Any? = null, + val VerificationStatus: Any? = null, + val Enable2FaRequest: Boolean = false, +) + +/* ------------------------------------------------------------------ reducer */ + +fun ReduceTwoStepVerification( + State0: TwoStepVerificationStateSlice, + Action: Any, +): TwoStepVerificationStateSlice = when (Action) { + is SetUse2FA -> State0.copy(IsUse2FA = Action.isUse2FA) + is ActivationStatus2FA -> State0.copy(IsActivated2FA = Action.isActivated) + is SetSecretAndQRCode -> State0.copy( + QrCodeDataURI = Action.qrCodeDataURI, + SharedSecret = Action.sharedSecret, + ) + is VerificationStatus -> when (val Success = Action.isValidationSuccess) { + null -> State0.copy(VerificationStatus = null) + false -> State0.copy(VerificationStatus = false) + true -> State0.copy(IsActivated2FA = true) + else -> State0 + } + is Enable2FaRequest -> State0.copy(Enable2FaRequest = Action.enable) + else -> State0 +} diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/UserReportsReducer.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/UserReportsReducer.kt new file mode 100644 index 0000000..1371796 --- /dev/null +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/UserReportsReducer.kt @@ -0,0 +1,89 @@ +// UserReportsReducer.kt —— 举报切片(对应 module_10032 的 Zst 状态 + qot.zth) +// +// 证据链: +// 状态类 Zst:5 个字段,toString 名 "UserReportsState(…)"。 +// 默认:isEnabled=false、helpdeskTicketsEnabled=false、availableReports=0、 +// availableGameReports=0、reports=(原 `dsv()` 从 localStorage 的 SENT_REPORTS 读回)。 +// 根委派:`(null == sd && new qot(), sd).zth(t, n.dpy_1)`。 +// reducer(6 个实例臂): +// cot(InitUserReports) → 4 个开关/额度字段 +// lot(SetAvailableReports) → availableReports +// fot(SetAvailableGameReports)→ availableGameReports +// vot(ReportProBattleName) → reports 追加 BATTLE_NAME 记录(有效期 +1 天) +// pot(ReportLobbyChatMessage) → reports 追加 LOBBY_CHAT_MESSAGE 记录 +// wot(ReportBattleChatMessage)→ reports 追加 BATTLE_CHAT_MESSAGE 记录 +// +// 原 reducer 在 reports 变化后会把列表序列化写回 localStorage 的 SENT_REPORTS +// (以及 Init 时从里面过滤过期项读回)。这属于 storage port 的职责,纯 reducer 不做 IO: +// Init 保留当前列表,写回留给中间件层。 +package App.State.Reducers + +import App.State.InitUserReports +import App.State.ReportBattleChatMessage +import App.State.ReportLobbyChatMessage +import App.State.ReportProBattleName +import App.State.SetAvailableGameReports +import App.State.SetAvailableReports + +/* ------------------------------------------------------------------ 模型 */ + +/** 原 `dot` 枚举:BATTLE_NAME / LOBBY_CHAT_MESSAGE / BATTLE_CHAT_MESSAGE。 */ +enum class ReportKind { + BATTLE_NAME, + LOBBY_CHAT_MESSAGE, + BATTLE_CHAT_MESSAGE, +} + +/** 原 `mot(type, targetId, expireTime)`:一条已发送的举报。 */ +data class ReportEntry( + val kind: ReportKind, + val targetId: Any?, + val expireTime: Double, +) + +/* ------------------------------------------------------------------ 时间 */ + +/** 原 `Fg(Md(), new Dd(864e5, 0))`:当前时间 + 1 天。 */ +private fun ExpireAtNextDay(): Double = (js("Date.now() + 86400000") as Number).toDouble() + +/* ------------------------------------------------------------------ 状态 */ + +data class UserReportsStateSlice( + val IsEnabled: Boolean = false, + val HelpdeskTicketsEnabled: Boolean = false, + val AvailableReports: Any? = 0, + val AvailableGameReports: Any? = 0, + val Reports: List = emptyList(), +) + +/* ------------------------------------------------------------------ reducer */ + +private fun AppendReport(State0: UserReportsStateSlice, Entry: ReportEntry): UserReportsStateSlice = + State0.copy(Reports = State0.Reports + Entry) + +fun ReduceUserReports( + State0: UserReportsStateSlice, + Action: Any, +): UserReportsStateSlice = when (Action) { + is InitUserReports -> State0.copy( + IsEnabled = Action.isEnabled, + HelpdeskTicketsEnabled = Action.helpdeskTicketsEnabled, + AvailableReports = Action.availableReports, + AvailableGameReports = Action.availableGameReports, + ) + is SetAvailableReports -> State0.copy(AvailableReports = Action.availableReports) + is SetAvailableGameReports -> State0.copy(AvailableGameReports = Action.availableGameReports) + is ReportProBattleName -> AppendReport( + State0, + ReportEntry(ReportKind.BATTLE_NAME, Action.battleId, ExpireAtNextDay()), + ) + is ReportLobbyChatMessage -> AppendReport( + State0, + ReportEntry(ReportKind.LOBBY_CHAT_MESSAGE, Action.messageId, ExpireAtNextDay()), + ) + is ReportBattleChatMessage -> AppendReport( + State0, + ReportEntry(ReportKind.BATTLE_CHAT_MESSAGE, Action.messageId, ExpireAtNextDay()), + ) + 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 aad9b5d..b4923a9 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 出新状态)。 -// 这里采用同一形状。已迁移的 43 个切片里,42 个真正调用 reducer,launchParams 只做类型收口: +// 这里采用同一形状。已迁移的 53 个切片里,52 个真正调用 reducer,launchParams 只做类型收口: // // inviteState → ReduceInvite(aD / pD.er5) // loginByPassword → ReduceLoginByPassword(gD / MD.cr6) @@ -45,17 +45,30 @@ // creatorTagState → ReduceCreatorTag(qO / ze.er2,根委派 `ze.er2(t, n.spx_1)`) // rewardedVideosState → ReduceRewardedVideos(JQ / xh.zsl,根委派 `xh.zsl(t, n.upx_1)`) // passwordChangeState → ReducePasswordChange(VD / uu.pr6,根委派 `uu.pr6(t, n.lpv_1)`) +// system → ReduceSystem(_9 / w9().xt9,根委派 `w9().xt9(t, n.mpv_1)`) +// contextTutorial → ReduceContextTutorial(Mst / Jst().tth,根委派 `Jst().tth(t, n.ypx_1)`) +// battleTutorial → ReduceBattleTutorial(rE / Q.sq8,根委派 `Q.sq8(t, n.cpv_1)`) +// spectator → ReduceSpectator(HA / Li.jqm,根委派 `Li.jqm(t, n.lpx_1)`) +// twoStepVerificationState → ReduceTwoStepVerification(x9 / _f.atb,根委派 `_f.atb(t, n.tpx_1)`) +// antiAddiction → ReduceAntiAddiction(UC / W.iq8,根委派 `W.iq8(t, n.kpx_1)`) +// moderation → ReduceModeration(NI / Ei.oqh,根委派 `Ei.oqh(t, n.mpx_1)`) +// migrationState → ReduceMigrationState($it / Uv.rtg,根委派 `Uv.rtg(t, n.ipv_1)`) +// register → ReduceRegister(Cit / cp.stg,根委派 `cp.stg(t, n.hpv_1)`) +// userReportsState → ReduceUserReports(Zst / qot.zth,根委派 `(null == sd && new qot(), sd).zth(t, n.dpy_1)`) // launchParams → 无 reducer(原根 reducer 原样透传;默认 `ag()` = emptyMap()) // -// 其余 46 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态, +// 其余 36 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态, // 所以一次 copy 里串起全部已迁移 reducer,与按动作路由再归约的结果一致。 // 迁移进度与每个字段的证据见生成物 RootWiring.kt(不要手改)。 package App.State import App.State.Reducers.ReduceAccountSettings import App.State.Reducers.ReduceAnnouncement +import App.State.Reducers.ReduceAntiAddiction import App.State.Reducers.ReduceBattleResult +import App.State.Reducers.ReduceBattleTutorial import App.State.Reducers.ReduceCaptcha +import App.State.Reducers.ReduceContextTutorial import App.State.Reducers.ReduceCreatorTag import App.State.Reducers.ReduceDroppableGold import App.State.Reducers.ReduceEntrance @@ -72,7 +85,9 @@ import App.State.Reducers.ReduceLootBoxes import App.State.Reducers.ReduceMainQuest import App.State.Reducers.ReduceAvailabilityByRank import App.State.Reducers.ReduceLobbyLoader +import App.State.Reducers.ReduceMigrationState import App.State.Reducers.ReduceModalScreen +import App.State.Reducers.ReduceModeration import App.State.Reducers.ReduceNews import App.State.Reducers.ReduceNotificationByCountry import App.State.Reducers.ReduceNotifications @@ -84,16 +99,21 @@ import App.State.Reducers.ReduceQuestCategories import App.State.Reducers.ReduceQuests import App.State.Reducers.ReduceRankUpDialog import App.State.Reducers.ReduceReferralEvent +import App.State.Reducers.ReduceRegister import App.State.Reducers.ReduceRewardedVideos import App.State.Reducers.ReduceRulesUpdated import App.State.Reducers.ReduceScreen import App.State.Reducers.ReduceSecuritySettings +import App.State.Reducers.ReduceSpectator import App.State.Reducers.ReduceSupportUrl import App.State.Reducers.ReduceSettings import App.State.Reducers.ReduceShop +import App.State.Reducers.ReduceSystem import App.State.Reducers.ReduceTips import App.State.Reducers.ReduceTwitch +import App.State.Reducers.ReduceTwoStepVerification import App.State.Reducers.ReduceUser +import App.State.Reducers.ReduceUserReports /** 根 reducer:`k6`。 */ object RootReducer { @@ -150,6 +170,16 @@ object RootReducer { CreatorTagState = ReduceCreatorTag(State0.CreatorTagState, Action), RewardedVideosState = ReduceRewardedVideos(State0.RewardedVideosState, Action), PasswordChangeState = ReducePasswordChange(State0.PasswordChangeState, Action), + System = ReduceSystem(State0.System, Action), + ContextTutorial = ReduceContextTutorial(State0.ContextTutorial, Action), + BattleTutorial = ReduceBattleTutorial(State0.BattleTutorial, Action), + Spectator = ReduceSpectator(State0.Spectator, Action), + TwoStepVerificationState = ReduceTwoStepVerification(State0.TwoStepVerificationState, Action), + AntiAddiction = ReduceAntiAddiction(State0.AntiAddiction, Action), + Moderation = ReduceModeration(State0.Moderation, Action), + MigrationState = ReduceMigrationState(State0.MigrationState, Action), + Register = ReduceRegister(State0.Register, Action), + UserReportsState = ReduceUserReports(State0.UserReportsState, Action), ) } diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/RootState.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/RootState.kt index 0c0335c..807bfe6 100644 --- a/TankiOffline/app/src/jsMain/kotlin/App/State/RootState.kt +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/RootState.kt @@ -6,9 +6,12 @@ package App.State import App.State.Reducers.AccountSettingsStateSlice import App.State.Reducers.AnnouncementStateSlice +import App.State.Reducers.AntiAddictionStateSlice import App.State.Reducers.AvailabilityByRankStateSlice import App.State.Reducers.BattleResultStateSlice +import App.State.Reducers.BattleTutorialStateSlice import App.State.Reducers.CaptchaStateSlice +import App.State.Reducers.ContextTutorialStateSlice import App.State.Reducers.CreatorTagStateSlice import App.State.Reducers.DroppableGoldStateSlice import App.State.Reducers.EntranceStateSlice @@ -23,7 +26,9 @@ import App.State.Reducers.LocalBattleUserStateSlice import App.State.Reducers.LoginByPasswordStateSlice import App.State.Reducers.LootBoxesStateSlice import App.State.Reducers.MainQuestStateSlice +import App.State.Reducers.MigrationStateSlice import App.State.Reducers.ModalScreenStateSlice +import App.State.Reducers.ModerationStateSlice import App.State.Reducers.NewsStateSlice import App.State.Reducers.NotificationByCountryStateSlice import App.State.Reducers.NotificationsStateSlice @@ -35,16 +40,21 @@ import App.State.Reducers.QuestCategoriesStateSlice import App.State.Reducers.QuestsStateSlice import App.State.Reducers.RankUpDialogStateSlice import App.State.Reducers.ReferralEventStateSlice +import App.State.Reducers.RegisterStateSlice import App.State.Reducers.RewardedVideosStateSlice import App.State.Reducers.RulesUpdatedStateSlice import App.State.Reducers.ScreenStateSlice import App.State.Reducers.SecuritySettingsStateSlice import App.State.Reducers.SettingsStateSlice import App.State.Reducers.ShopStateSlice +import App.State.Reducers.SpectatorStateSlice import App.State.Reducers.SupportUrlStateSlice +import App.State.Reducers.SystemStateSlice import App.State.Reducers.TipsStateSlice import App.State.Reducers.TwitchStateSlice +import App.State.Reducers.TwoStepVerificationStateSlice import App.State.Reducers.UserStateSlice +import App.State.Reducers.UserReportsStateSlice /** * 原 `z6` / `TOState`:89 个切片字段,顺序与 toString 一致。 @@ -59,8 +69,8 @@ data class RootState( val ModalScreen: ModalScreenStateSlice = ModalScreenStateSlice(), /** 原 battleStatistics(jR / rr.fqr)。未迁移:原样透传。 */ val BattleStatistics: Any? = null, - /** 原 battleTutorial(rE / Q.sq8)。未迁移:原样透传。 */ - val BattleTutorial: Any? = null, + /** 原 battleTutorial(rE / Q.sq8)。已迁移。 */ + val BattleTutorial: BattleTutorialStateSlice = BattleTutorialStateSlice(), /** 原 garage(UB / hF)。已迁移。 */ val Garage: GarageStateSlice = GarageStateSlice(), /** 原 garagePreview(IG / fs.eru)。已迁移。 */ @@ -69,18 +79,18 @@ data class RootState( val Challenges: Any? = null, /** 原 loginByPassword(gD / nu.cr6)。已迁移。 */ val LoginByPassword: LoginByPasswordStateSlice = LoginByPasswordStateSlice(), - /** 原 register(Cit / cp.stg)。未迁移:原样透传。 */ - val Register: Any? = null, - /** 原 migrationState($it / Uv.rtg)。未迁移:原样透传。 */ - val MigrationState: Any? = null, + /** 原 register(Cit / cp.stg)。已迁移。 */ + val Register: RegisterStateSlice = RegisterStateSlice(), + /** 原 migrationState($it / Uv.rtg)。已迁移。 */ + val MigrationState: MigrationStateSlice = MigrationStateSlice(), /** 原 inviteState(aD / Ke.er5)。已迁移。 */ val InviteState: InviteStateSlice = InviteStateSlice(), /** 原 captchaState(jP / ar.iqt)。已迁移。 */ val CaptchaState: CaptchaStateSlice = CaptchaStateSlice(), /** 原 passwordChangeState(VD / uu.pr6)。已迁移。 */ val PasswordChangeState: PasswordChangeStateSlice = PasswordChangeStateSlice(), - /** 原 system(_9 / w9().xt9)。未迁移:原样透传。 */ - val System: Any? = null, + /** 原 system(_9 / w9().xt9)。已迁移。 */ + val System: SystemStateSlice = SystemStateSlice(), /** 原 quests(Frt / Yrt.otg)。已迁移。 */ val Quests: QuestsStateSlice = QuestsStateSlice(), /** 原 chatState(LP / $r.wqv)。未迁移:原样透传。 */ @@ -179,12 +189,12 @@ data class RootState( val Partner: Any? = null, /** 原 entrance(hD / Bv.pth)。已迁移。 */ val Entrance: EntranceStateSlice = EntranceStateSlice(), - /** 原 antiAddiction(UC / W.iq8)。未迁移:原样透传。 */ - val AntiAddiction: Any? = null, - /** 原 spectator(HA / Li.jqm)。未迁移:原样透传。 */ - val Spectator: Any? = null, - /** 原 moderation(NI / Ei.oqh)。未迁移:原样透传。 */ - val Moderation: Any? = null, + /** 原 antiAddiction(UC / W.iq8)。已迁移。 */ + val AntiAddiction: AntiAddictionStateSlice = AntiAddictionStateSlice(), + /** 原 spectator(HA / Li.jqm)。已迁移。 */ + val Spectator: SpectatorStateSlice = SpectatorStateSlice(), + /** 原 moderation(NI / Ei.oqh)。已迁移。 */ + val Moderation: ModerationStateSlice = ModerationStateSlice(), /** 原 rulesUpdated(i1 / Ch.dsm)。已迁移。 */ val RulesUpdated: RulesUpdatedStateSlice = RulesUpdatedStateSlice(), /** 原 announcement(lC / PC().rq5)。已迁移。 */ @@ -197,8 +207,8 @@ data class RootState( val EventGoods: EventGoodsStateSlice = EventGoodsStateSlice(), /** 原 creatorTagState(qO / ze.er2)。已迁移。 */ val CreatorTagState: CreatorTagStateSlice = CreatorTagStateSlice(), - /** 原 twoStepVerificationState(x9 / _f.atb)。未迁移:原样透传。 */ - val TwoStepVerificationState: Any? = null, + /** 原 twoStepVerificationState(x9 / _f.atb)。已迁移。 */ + val TwoStepVerificationState: TwoStepVerificationStateSlice = TwoStepVerificationStateSlice(), /** 原 rewardedVideosState(JQ / xh.zsl)。已迁移。 */ val RewardedVideosState: RewardedVideosStateSlice = RewardedVideosStateSlice(), /** 原 twitchState(g9 / uf.gta)。已迁移。 */ @@ -207,8 +217,8 @@ data class RootState( val Icons: IconsStateSlice = IconsStateSlice(), /** 原 itemNotifications(_1 / A1().dso)。未迁移:原样透传。 */ val ItemNotifications: Any? = null, - /** 原 contextTutorial(Mst / Jst().tth)。未迁移:原样透传。 */ - val ContextTutorial: Any? = null, + /** 原 contextTutorial(Mst / Jst().tth)。已迁移。 */ + val ContextTutorial: ContextTutorialStateSlice = ContextTutorialStateSlice(), /** 原 suppliesTutorial(? / Uw.uth)。未迁移:原样透传。 */ val SuppliesTutorial: Any? = null, /** 原 questCategories(zZ / th.lsh)。已迁移。 */ @@ -217,8 +227,8 @@ data class RootState( val ProBattle: ProBattleStateSlice = ProBattleStateSlice(), /** 原 availabilityByRank(tet / $p.xth)。已迁移。 */ val AvailabilityByRank: AvailabilityByRankStateSlice = AvailabilityByRankStateSlice(), - /** 原 userReportsState(Zst / qot.zth)。未迁移:原样透传。 */ - val UserReportsState: Any? = null, + /** 原 userReportsState(Zst / qot.zth)。已迁移。 */ + val UserReportsState: UserReportsStateSlice = UserReportsStateSlice(), /** 原 tankBuilderState(brt / wp.ati)。未迁移:原样透传。 */ val TankBuilderState: Any? = null, /** 原 contractState(Ont / xv.bti)。未迁移:原样透传。 */ diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/RootWiring.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/RootWiring.kt index ffd8623..7e19c25 100644 --- a/TankiOffline/app/src/jsMain/kotlin/App/State/RootWiring.kt +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/RootWiring.kt @@ -18,13 +18,13 @@ object RootWiring { SliceWiring("screen", "zpu_1", "WK", "dY().ks5", "dY().ks5(t, n.zpu_1)", listOf("ClearScreenStack", "DelayNextGoTo", "DisableBack", "DisableForeground", "DisplayMainEntranceScreen", "DisplayReauthorizationMessage", "EnableBack", "EnableForeground", "FilterScreenStack", "GoTo", "PopScreen", "PressedAnyButton", "ResetScreenStackOnNextGoTo")), SliceWiring("modalScreen", "apv_1", "nV", "Fs.js1", "Fs.js1(t, n.apv_1)", listOf("AddModal", "CloseCurrentModal", "RemoveCurrentByName", "RemoveModal")), SliceWiring("battleStatistics", "bpv_1", "jR", "rr.fqr", "rr.fqr(t, n.bpv_1)", listOf("?DR", "?HR", "?KR", "?PR", "?QR", "?UR", "?XR", "?YR", "?ZR", "?nP", "?tP", "Init", "InitBattleModeWithLevels", "SetBattleMode", "SetDisplayClientInfo", "SetFlagOrBallOwner", "SetRankRange", "SetReArmor", "SetRemainingTime", "SetRoundRestartTime", "UpdateFund", "UpdateScore")), - SliceWiring("battleTutorial", "cpv_1", "rE", "Q.sq8", "Q.sq8(t, n.cpv_1)", listOf("?oE", "?sE", "SetFragsCount", "ShowMessage")), + SliceWiring("battleTutorial", "cpv_1", "rE", "Q.sq8", "Q.sq8(t, n.cpv_1)", listOf("MarkMovementStarted", "ResetBattleTutorial", "SetFragsCount", "ShowMessage")), SliceWiring("garage", "dpv_1", "UB", "hF", "hF(t, n)", listOf("AddGarageItemCount", "AddNewItem", "AddOverdriveDescription", "AddRelatedBazooka", "AddRelatedResistanceModule", "AddToFavoriteItem", "BuyProtectionSlot", "ClearWaitServerItemMount", "GarageAddItemToDepot", "GarageApplyResistanceMount", "GarageBuyDevice", "GarageDeviceTrySetInfinityLifetime", "GarageDevicesLoadedAction", "GarageLoaded", "GarageMountItem", "GarageMountItems", "GarageRemoveDevice", "GarageRemoveDiscountForDevice", "GarageRemoveItemFromDepot", "GarageRemoveItemFromStore", "GarageResistanceUnMount", "GarageSelectAlteration", "GarageSelectItem", "GarageSelectShotSkin", "GarageSelectSkin", "GarageUnmountItem", "GarageUpdateItemCount", "GarageUpgradeItemAction", "GarageUpgradeItemConfirm", "GarageUpgradeItemCoolDownFinished", "GarageUpgradeItemCoolDownStarted", "GoTo", "HideVideoInDescription", "InitState", "RemoveFromFavoriteItem", "RemoveFromSale", "RemoveNewItem", "RemoveWaitServerItemMount", "ResetGarage", "ResetMountDelay", "SelectGarageCategory", "SetCooldownReduceEndTime", "SetGarageNotLoaded", "SetGarageObject", "SetGarageSkinsAction", "SetItemRarity", "SetItemsOnDepot", "SetItemsOnMarket", "SetMountedItems", "SetMountedShotSkinForItems", "SetMountedSkinForItems", "SetNewItems", "SetResistanceReadyToMount", "SetRestrictionMount", "ShowGarageDiscountNotification", "StartCooldownReduce", "SwitchingRowsScroll", "UpdateDiscount")), SliceWiring("garagePreview", "epv_1", "IG", "fs.eru", "fs.eru(t, n.epv_1)", listOf("AddRequestedItem", "CaptureScreenshot", "CaptureVideo", "ClearDroneData", "ClearGrenadeData", "ClearHasGrenades", "ClearHullData", "ClearWeaponData", "InitGarageResources", "ResetItem3dModelActionCache", "SetCaptureActions", "SetDrone", "SetExpectedItemId", "SetGrenadeData", "SetHasGrenades", "SetHullData", "SetMountedItemsProperties", "SetPaint", "SetWeaponData", "ShowRequest", "UpdatePreviewSettings", "UpdateViewStyle")), SliceWiring("challenges", "fpv_1", "CZ", "nh.hsj", "nh.hsj(t, n.fpv_1)", listOf("?LZ", "RemoveChallenge", "SetBattlePass", "SetBattlePassPackages", "SetChallenge", "SetRewardForDialog", "SetTakenBattlePassRewardIndex", "SetTakenFreeRewardIndex", "UpdateStars")), SliceWiring("loginByPassword", "gpv_1", "gD", "nu.cr6", "nu.cr6(t, n.gpv_1)", listOf("?ND", "?PD", "?RD", "IncorrectLogin2FA", "RequestsNumber2FA", "SetLogin", "SetPassword", "SetRememberMe", "SetStatus")), - SliceWiring("register", "hpv_1", "Cit", "cp.stg", "cp.stg(t, n.hpv_1)", listOf("?Bit", "?Dit", "?Fit", "?Git", "?Hit", "?Lit", "?Mit", "?Uit", "?_rt", "?hrt", "EnteredUidIsBusy", "ForceSetUid", "ReceiveOAuthUri", "SetOpenedPassword", "SetPassword", "SetPasswordRestrictions", "SetUid", "SocialNetworkAvailability", "SocialNetworkClientIds")), - SliceWiring("migrationState", "ipv_1", "\$it", "Uv.rtg", "Uv.rtg(t, n.ipv_1)", listOf("?Bit", "?Dit", "?Fit", "?Git", "?Hit", "?Lit", "?_rt", "EnteredUidIsBusy", "ForceSetUid", "SetPassword", "SetUid")), + SliceWiring("register", "hpv_1", "Cit", "cp.stg", "cp.stg(t, n.hpv_1)", listOf("DisableRegister", "EnteredUidIsBusy", "ForceSetUid", "ReceiveOAuthUri", "RegisterFailed", "RegisterPasswordCorrect", "RegisterPasswordIncorrect", "ResetAuthenticationForm", "ResetRegisterStatus", "ResetUidStatus", "SetOpenedPassword", "SetPassword", "SetPasswordRestrictions", "SetUid", "SocialNetworkAvailability", "SocialNetworkClientIds", "UidIsFree", "UidIsIncorrect", "UidTooShort")), + SliceWiring("migrationState", "ipv_1", "\$it", "Uv.rtg", "Uv.rtg(t, n.ipv_1)", listOf("EnteredUidIsBusy", "ForceSetUid", "RegisterFailed", "RegisterPasswordIncorrect", "ResetAuthenticationForm", "ResetUidStatus", "SetPassword", "SetUid", "UidIsFree", "UidIsIncorrect", "UidTooShort")), SliceWiring("inviteState", "jpv_1", "aD", "Ke.er5", "Ke.er5(t, n.jpv_1)", listOf("ApplyInvite", "SetInviteNeed", "SetInviteStatus")), SliceWiring("captchaState", "kpv_1", "jP", "ar.iqt", "ar.iqt(t, n.kpv_1)", listOf("AddCaptchaLocationToReset", "AddVerifiedCaptchaLocation", "LocationsUpdated", "RemoveCaptchaLocationToReset", "RemoveVerifiedCaptchaLocation", "SetLocations", "ShowFakeCaptcha")), SliceWiring("passwordChangeState", "lpv_1", "VD", "uu.pr6", "uu.pr6(t, n.lpv_1)", listOf("SetPasswordChangeHash", "ShowEmailAndPasswordChangeDialog")), @@ -78,9 +78,9 @@ object RootWiring { SliceWiring("launchParams", "hpx_1", "?", "(pass-through)", "n.hpx_1", listOf()), SliceWiring("partner", "ipx_1", "UJ", "(inline@partner)", "(function (t, n) { var i; if (t instanceof VJ) i = n.osf(t.rsf_1, cd, cd, cd, cd, cd, cd, cd, cd, cd, t.ssf_1); else if (t instanceof nZ) { var r = t.bsg_1, e = t.csg_1, u = t.dsg_1, s = t.esg_1, o = t.fsg_1, _ = t.gsg_1; i = n.osf(cd, cd, cd, r, e, u, cd, cd, o, _, s); } else i = t instanceof iZ ? n.osf(cd, t.hsg_1) : t instanceof uZ ? n.osf(cd, cd, t.ksg_1) : t instanceof eZ ? n.osf(cd, cd, cd, cd, cd, cd, cd, t.jsg_1) : t instanceof _Z ? n.osf(cd, cd, cd, cd, cd, cd, t.osg_1) : t instanceof X …", listOf("?JJ", "?_Z", "?hZ", "?tZ", "InitPartnerInviteAction", "InitSocialNetworkParameters", "SetLoginParams", "SetLoginStatusForBinding", "SetPartnerId", "SetPartnerParameters", "SetPartnerReady")), SliceWiring("entrance", "jpx_1", "hD", "Bv.pth", "Bv.pth(t, n.jpx_1)", listOf("HideSocialNetworks", "SetAgeWarnShow", "SetAuthorizedByChinaSdkToken", "SetClassic", "SetControlsForChina", "SetRunningInChinaDataCenter", "SetRunningInChinaPartners")), - SliceWiring("antiAddiction", "kpx_1", "UC", "W.iq8", "W.iq8(t, n.kpx_1)", listOf("?FC", "?XC", "?YC", "SetRealNameAndIdNumber", "SetValidateAntiAddictionUserDataStatus")), - SliceWiring("spectator", "lpx_1", "HA", "Li.jqm", "Li.jqm(t, n.lpx_1)", listOf("?JA", "?XA", "?YA", "?ZA", "SetSpectator", "UpdateHudLayout")), - SliceWiring("moderation", "mpx_1", "NI", "Ei.oqh", "Ei.oqh(t, n.mpx_1)", listOf("?FI", "?GI", "?JI", "?KI", "?QI", "?UI", "?WI", "?XI", "?YI", "?ZI", "?nA", "?tA", "?uA")), + SliceWiring("antiAddiction", "kpx_1", "UC", "W.iq8", "W.iq8(t, n.kpx_1)", listOf("EnableRealNameCheck", "RequireUserIdVerification", "SetRealNameAndIdNumber", "SetValidateAntiAddictionUserDataStatus", "UpdatePlayingTime")), + SliceWiring("spectator", "lpx_1", "HA", "Li.jqm", "Li.jqm(t, n.lpx_1)", listOf("AddSpectator", "RemoveSpectator", "SetSpectator", "SetSpectators", "SetSpectatorsListVisible", "UpdateHudLayout")), + SliceWiring("moderation", "mpx_1", "NI", "Ei.oqh", "Ei.oqh(t, n.mpx_1)", listOf("AddSelectedUser", "AddSelectedUserFromChat", "ClearSelectedUsers", "HideBattleInfo", "HideBlockUserMenu", "HideModerationSystemMessage", "RemoveSelectedUser", "ResetSelectedUsers", "SetModerationUrlTemplates", "SetModeratorModes", "ShowBattleInfo", "ShowBlockUserMenu", "ShowModerationSystemMessage")), SliceWiring("rulesUpdated", "npx_1", "i1", "Ch.dsm", "Ch.dsm(t, n.npx_1)", listOf("UpdateRules")), SliceWiring("announcement", "opx_1", "lC", "PC().rq5", "PC().rq5(t, n.opx_1)", listOf("DisableAnnouncement", "SetAnnouncementItems", "SetAnnouncementModelLoaded", "SetSkipNextPromotedAnnounce")), SliceWiring("notificationByCountry", "ppx_1", "gJ", "R_.ssd", "R_.ssd(t, n.ppx_1)", listOf("Init", "MarkHomeScreenDialogShown")), @@ -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") + 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") /** 未迁移字段:归约时原样透传,作为后续迁移清单。 */ 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 8260bd7..95b1b51 100644 --- a/TankiOffline/app/src/jsMain/kotlin/App/StateCheck.kt +++ b/TankiOffline/app/src/jsMain/kotlin/App/StateCheck.kt @@ -9,13 +9,15 @@ // ⑥ 动作目录:816 个类与 138 个真实派发名对齐 // ⑦ 回放:真实 toString 样例的类/字段/类型校验 // ⑧ 全目录动作类都能按 KClass 路由 -// ⑨~㊿/51-53 切片 reducer:InviteState / LoginByPasswordState / TwitchState / HomeScreen / LocalBattleUserState / +// ⑨~㊿/51-63 切片 reducer:InviteState / LoginByPasswordState / TwitchState / HomeScreen / LocalBattleUserState / // Shop(含 U7 类别表与 w3 物品表两个子 reducer)/ Screen / LootBoxes / Garage / GaragePreview / Entrance / User / Settings / // AccountSettings / ProBattle / Language / Announcement / 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; +// CreatorTagState / RewardedVideosState / PasswordChangeState / +// System / ContextTutorial / BattleTutorial / Spectator / TwoStepVerification / AntiAddiction / +// Moderation / MigrationState / Register / UserReports; // ㉑ launchParams 是纯透传切片(无 reducer/动作),只做 Map 类型收口;⑭ 根 reducer 的 89 槽位委派 // // 用法:$env:TANKIOFFLINE_STATE_TEST='1'; node …(见 TankiOffline/tools/RunStateTest.ps1) @@ -440,6 +442,98 @@ import App.State.SetPasswordChangeHash import App.State.ShowEmailAndPasswordChangeDialog import App.State.Reducers.PasswordChangeStateSlice import App.State.Reducers.ReducePasswordChange +import App.State.DoShowMessage +import App.State.SetFatalError +import App.State.Reducers.SystemStateSlice +import App.State.Reducers.ReduceSystem +import App.State.LoadCurrentElement +import App.State.MarkElementAsViewed +import App.State.SetLoadedElements +import App.State.SetMissionToElement +import App.State.SetUnViewedElements +import App.State.Reducers.ContextTutorialStateSlice +import App.State.Reducers.ReduceContextTutorial +import App.State.SetFragsCount +import App.State.ShowMessage +import App.State.Reducers.BattleTutorialStateSlice +import App.State.Reducers.MarkMovementStarted +import App.State.Reducers.ReduceBattleTutorial +import App.State.Reducers.ResetBattleTutorial +import App.State.SetSpectator +import App.State.UpdateHudLayout +import App.State.SpectatorInfo +import App.State.Reducers.AddSpectator +import App.State.Reducers.ReduceSpectator +import App.State.Reducers.RemoveSpectator +import App.State.Reducers.SetSpectators +import App.State.Reducers.SetSpectatorsListVisible +import App.State.Reducers.SpectatorStateSlice +import App.State.ActivationStatus2FA +import App.State.Enable2FaRequest +import App.State.SetSecretAndQRCode +import App.State.SetUse2FA +import App.State.VerificationStatus +import App.State.Reducers.ReduceTwoStepVerification +import App.State.Reducers.TwoStepVerificationStateSlice +import App.State.SetRealNameAndIdNumber +import App.State.SetValidateAntiAddictionUserDataStatus +import App.State.Reducers.AntiAddictionStateSlice +import App.State.Reducers.EnableRealNameCheck +import App.State.Reducers.ReduceAntiAddiction +import App.State.Reducers.RequireUserIdVerification +import App.State.Reducers.UpdatePlayingTime +import App.State.Reducers.AddSelectedUser +import App.State.Reducers.AddSelectedUserFromChat +import App.State.Reducers.ClearSelectedUsers +import App.State.Reducers.HideBattleInfo +import App.State.Reducers.HideBlockUserMenu +import App.State.Reducers.HideModerationSystemMessage +import App.State.Reducers.ModerationStateSlice +import App.State.Reducers.ModerationUserRecord +import App.State.Reducers.ReduceModeration +import App.State.Reducers.RemoveSelectedUser +import App.State.Reducers.ResetSelectedUsers +import App.State.Reducers.SetModerationUrlTemplates +import App.State.Reducers.SetModeratorModes +import App.State.Reducers.ShowBattleInfo +import App.State.Reducers.ShowBlockUserMenu +import App.State.Reducers.ShowModerationSystemMessage +import App.State.EnteredUidIsBusy +import App.State.ForceSetUid +import App.State.SetUid +import App.State.ReceiveOAuthUri +import App.State.SetOpenedPassword +import App.State.SetPasswordRestrictions +import App.State.SocialNetworkAvailability +import App.State.SocialNetworkClientIds +import App.State.Reducers.DisableRegister +import App.State.Reducers.MigrationLoginStatus +import App.State.Reducers.MigrationRegisterStatus +import App.State.Reducers.MigrationStateSlice +import App.State.Reducers.ReduceMigrationState +import App.State.Reducers.ReduceRegister +import App.State.Reducers.RegisterFailed +import App.State.Reducers.RegisterPasswordCorrect +import App.State.Reducers.RegisterPasswordIncorrect +import App.State.Reducers.RegisterStateSlice +import App.State.Reducers.RegistrationStatus +import App.State.Reducers.ResetAuthenticationForm +import App.State.Reducers.ResetRegisterStatus +import App.State.Reducers.ResetUidStatus +import App.State.Reducers.UidIsFree +import App.State.Reducers.UidIsIncorrect +import App.State.Reducers.UidStatus +import App.State.Reducers.UidTooShort +import App.State.InitUserReports +import App.State.ReportBattleChatMessage +import App.State.ReportLobbyChatMessage +import App.State.ReportProBattleName +import App.State.SetAvailableGameReports +import App.State.SetAvailableReports +import App.State.Reducers.ReduceUserReports +import App.State.Reducers.ReportEntry +import App.State.Reducers.ReportKind +import App.State.Reducers.UserReportsStateSlice import App.State.Reducers.UserRankBounds import App.State.Reducers.UserStateSlice import App.State.Reducers.ClearHullData @@ -867,7 +961,7 @@ object StateCheck { Check("切片 LocalBattleUser:接 Store 派发后状态已归约", Store.State.IsReArmorTemporaryDisabled) } - // ⑭ 根 reducer:89 个槽位的委派表 + 43 个已迁移切片(42 归约 + launchParams 透传) + // ⑭ 根 reducer:89 个槽位的委派表 + 53 个已迁移切片(52 归约 + launchParams 透传) run { Check("根:根状态 89 个切片槽位", RootWiring.All.size == 89) Check( @@ -931,7 +1025,7 @@ object StateCheck { LocalBattleW.Reducer == "(inline@localBattleUserState)" ) Check( - "根:迁移表 = 43 个已迁移字段,其余 46 个待迁", + "根:迁移表 = 53 个已迁移字段,其余 36 个待迁", RootReducer.Migrated == setOf( "inviteState", "loginByPassword", "twitchState", "home", @@ -945,8 +1039,11 @@ object StateCheck { "tipsState", "rankUpDialogState", "battleResult", "captchaState", "modalScreen", "droppableGold", "eventGoods", "creatorTagState", "rewardedVideosState", "passwordChangeState", + "system", "contextTutorial", "battleTutorial", "spectator", + "twoStepVerificationState", "antiAddiction", "moderation", + "migrationState", "register", "userReportsState", ) && - RootWiring.Unmigrated.size == 46 + RootWiring.Unmigrated.size == 36 ) // 一次 ReduceRoot 里多个切片各归约自己的动作,互不串片 @@ -1002,7 +1099,7 @@ object StateCheck { S5.User.Uid == "user-1" && S5.Screen.Current == NamedScreen("Preloader") ) Check("根:无关动作返回等值状态", ReduceRoot(S5, AddFrags(1)) == S5) - Check("根:Coverage 文本", RootReducer.Coverage == "已迁移 43/89") + Check("根:Coverage 文本", RootReducer.Coverage == "已迁移 53/89") val Store = ReduxStore(RootState(), { S, A -> ReduceRoot(S, A) }) Store.Dispatch(SetStatus("LOGGING_IN")) @@ -4120,6 +4217,604 @@ object StateCheck { ) } + // 54 切片 reducer:System(_9 / w9().xt9,3 字段;2 个动作) + run { + val S0 = SystemStateSlice() + Check( + "切片 System:默认值完整", + S0.Message == null && !S0.Restart && S0.FatalError == null + ) + val S1 = ReduceSystem(S0, DoShowMessage("m", true)) + Check( + "切片 System:DoShowMessage 写 message/restart", + S1.Message == "m" && S1.Restart && S1.FatalError == null + ) + val S2 = ReduceSystem(S1, SetFatalError("boom")) + Check( + "切片 System:SetFatalError 写 fatalError", + S2.FatalError == "boom" && S2.Message == "m" + ) + Check("切片 System:无关动作不改状态", ReduceSystem(S2, AddFrags(1)) == S2) + + val Root0 = RootState() + val Root1 = ReduceRoot(Root0, SetFatalError("x")) + Check( + "根:SetFatalError 只动 system", + Root1.System.FatalError == "x" && Root1.ContextTutorial == Root0.ContextTutorial + ) + val W = RootWiring.ByField("system")!! + Check( + "根:system 委派到 _9 / w9().xt9", + W.SliceClass == "_9" && W.Reducer == "w9().xt9" && + W.Actions.toSet() == setOf("DoShowMessage", "SetFatalError") + ) + } + + // 55 切片 reducer:ContextTutorial(Mst / Jst().tth,4 字段;5 个动作) + run { + val S0 = ContextTutorialStateSlice() + Check( + "切片 ContextTutorial:默认空表空 Map", + S0.CurrentElement == null && S0.LoadedElements.isEmpty() && + S0.UnViewedElements.isEmpty() && S0.MissionToElement.isEmpty() + ) + val S1 = ReduceContextTutorial(S0, LoadCurrentElement("e1")) + Check("切片 ContextTutorial:LoadCurrentElement 写当前元素", S1.CurrentElement == "e1") + val S2 = ReduceContextTutorial( + ReduceContextTutorial(S1, SetUnViewedElements(listOf("a", "b"))), + MarkElementAsViewed("a"), + ) + Check( + "切片 ContextTutorial:MarkElementAsViewed 从待看列表删一个", + S2.UnViewedElements == listOf("b") + ) + val S3 = ReduceContextTutorial( + ReduceContextTutorial(S2, SetLoadedElements(mapOf("k" to 1))), + SetMissionToElement(mapOf("m" to "e")), + ) + Check( + "切片 ContextTutorial:loadedElements / missionToElement 整体替换", + S3.LoadedElements == mapOf("k" to 1) && + S3.MissionToElement == mapOf("m" to "e") + ) + Check( + "切片 ContextTutorial:无关动作不改状态", + ReduceContextTutorial(S3, AddFrags(1)) == S3 + ) + + val Root0 = RootState() + val Root1 = ReduceRoot(Root0, LoadCurrentElement("z")) + Check( + "根:LoadCurrentElement 只动 contextTutorial", + Root1.ContextTutorial.CurrentElement == "z" && Root1.System == Root0.System + ) + val W = RootWiring.ByField("contextTutorial")!! + Check( + "根:contextTutorial 委派到 Mst / Jst().tth 且 5 个动作", + W.SliceClass == "Mst" && W.Reducer == "Jst().tth" && + W.Actions.toSet() == setOf( + "LoadCurrentElement", "SetUnViewedElements", "MarkElementAsViewed", + "SetLoadedElements", "SetMissionToElement", + ) + ) + } + + // 56 切片 reducer:BattleTutorial(rE / Q.sq8,3 字段;4 个动作) + run { + val S0 = BattleTutorialStateSlice() + Check( + "切片 BattleTutorial:默认值完整", + S0.Message == null && S0.FragsCount == 0 && !S0.MovementHasStarted + ) + val S1 = ReduceBattleTutorial(S0, ShowMessage("m")) + Check("切片 BattleTutorial:ShowMessage 写 message", S1.Message == "m") + Check( + "切片 BattleTutorial:SetFragsCount 写击杀数", + ReduceBattleTutorial(S1, SetFragsCount(3)).FragsCount == 3 + ) + val S2 = ReduceBattleTutorial(S1, MarkMovementStarted) + Check("切片 BattleTutorial:MarkMovementStarted 置位", S2.MovementHasStarted) + Check( + "切片 BattleTutorial:ResetBattleTutorial 整片复位", + ReduceBattleTutorial(S2, ResetBattleTutorial) == BattleTutorialStateSlice() + ) + Check( + "切片 BattleTutorial:无关动作不改状态", + ReduceBattleTutorial(S2, AddFrags(1)) == S2 + ) + + val Root0 = RootState() + val Root1 = ReduceRoot(Root0, MarkMovementStarted) + Check( + "根:MarkMovementStarted 只动 battleTutorial", + Root1.BattleTutorial.MovementHasStarted && Root1.Spectator == Root0.Spectator + ) + val W = RootWiring.ByField("battleTutorial")!! + Check( + "根:battleTutorial 委派到 rE / Q.sq8 且 4 个动作", + W.SliceClass == "rE" && W.Reducer == "Q.sq8" && + W.Actions.toSet() == setOf( + "ShowMessage", "SetFragsCount", "MarkMovementStarted", "ResetBattleTutorial", + ) + ) + } + + // 57 切片 reducer:Spectator(HA / Li.jqm,4 字段;6 个动作) + run { + val S0 = SpectatorStateSlice() + Check( + "切片 Spectator:默认非观战、列表可见", + !S0.IsSpectator && S0.Spectators.isEmpty() && S0.SpectatorsListVisible + ) + val S1 = ReduceSpectator(S0, SetSpectator(true)) + Check("切片 Spectator:SetSpectator 写布尔", S1.IsSpectator) + val S2 = ReduceSpectator(S1, AddSpectator(1L, "uid1", true, false)) + Check( + "切片 Spectator:AddSpectator 追加 SpectatorInfo", + S2.Spectators.size == 1 && + (S2.Spectators[0] as SpectatorInfo).userId == 1L + ) + val S3 = ReduceSpectator(S2, AddSpectator(2L, "uid2", false, false)) + Check("切片 Spectator:第二个观战者入列", S3.Spectators.size == 2) + Check( + "切片 Spectator:RemoveSpectator 按 userId 删", + ReduceSpectator(S3, RemoveSpectator(1L)).Spectators.size == 1 + ) + Check( + "切片 Spectator:SetSpectators 整表替换", + ReduceSpectator(S3, SetSpectators(listOf("x"))).Spectators == listOf("x") + ) + Check( + "切片 Spectator:SetSpectatorsListVisible 写可见性", + !ReduceSpectator(S3, SetSpectatorsListVisible(false)).SpectatorsListVisible + ) + Check( + "切片 Spectator:UpdateHudLayout 写布局", + ReduceSpectator(S3, UpdateHudLayout("hud")).SpectatorHudLayout == "hud" + ) + Check("切片 Spectator:无关动作不改状态", ReduceSpectator(S3, AddFrags(1)) == S3) + + val Root0 = RootState() + val Root1 = ReduceRoot(Root0, SetSpectator(true)) + Check( + "根:SetSpectator 只动 spectator", + Root1.Spectator.IsSpectator && Root1.BattleTutorial == Root0.BattleTutorial + ) + val W = RootWiring.ByField("spectator")!! + Check( + "根:spectator 委派到 HA / Li.jqm 且 6 个动作", + W.SliceClass == "HA" && W.Reducer == "Li.jqm" && + W.Actions.toSet() == setOf( + "SetSpectator", "UpdateHudLayout", "SetSpectators", "AddSpectator", + "RemoveSpectator", "SetSpectatorsListVisible", + ) + ) + } + + // 58 切片 reducer:TwoStepVerification(x9 / _f.atb,6 字段;5 个动作) + run { + val S0 = TwoStepVerificationStateSlice() + Check( + "切片 TwoStepVerification:默认值完整", + !S0.IsUse2FA && !S0.IsActivated2FA && S0.QrCodeDataURI == null && + S0.SharedSecret == null && S0.VerificationStatus == null && + !S0.Enable2FaRequest + ) + val S1 = ReduceTwoStepVerification(S0, SetUse2FA(true)) + Check("切片 TwoStepVerification:SetUse2FA 写开关", S1.IsUse2FA) + val S2 = ReduceTwoStepVerification(S1, SetSecretAndQRCode("secret", "data")) + Check( + "切片 TwoStepVerification:SetSecretAndQRCode 同时写两字段", + S2.SharedSecret == "secret" && S2.QrCodeDataURI == "data" + ) + Check( + "切片 TwoStepVerification:VerificationStatus(false) 写失败", + ReduceTwoStepVerification(S2, VerificationStatus(false)).VerificationStatus == false + ) + Check( + "切片 TwoStepVerification:VerificationStatus(true) 视为已启用", + ReduceTwoStepVerification(S2, VerificationStatus(true)).IsActivated2FA + ) + Check( + "切片 TwoStepVerification:VerificationStatus(null) 清空状态", + ReduceTwoStepVerification( + ReduceTwoStepVerification(S2, VerificationStatus(false)), + VerificationStatus(null), + ).VerificationStatus == null + ) + Check( + "切片 TwoStepVerification:ActivationStatus2FA / Enable2FaRequest", + !ReduceTwoStepVerification(S2, ActivationStatus2FA(false)).IsActivated2FA && + ReduceTwoStepVerification(S2, Enable2FaRequest(true)).Enable2FaRequest + ) + Check( + "切片 TwoStepVerification:无关动作不改状态", + ReduceTwoStepVerification(S2, AddFrags(1)) == S2 + ) + + val Root0 = RootState() + val Root1 = ReduceRoot(Root0, SetUse2FA(true)) + Check( + "根:SetUse2FA 只动 twoStepVerificationState", + Root1.TwoStepVerificationState.IsUse2FA && Root1.Spectator == Root0.Spectator + ) + val W = RootWiring.ByField("twoStepVerificationState")!! + Check( + "根:twoStepVerificationState 委派到 x9 / _f.atb 且 5 个动作", + W.SliceClass == "x9" && W.Reducer == "_f.atb" && + W.Actions.toSet() == setOf( + "SetUse2FA", "ActivationStatus2FA", "SetSecretAndQRCode", + "VerificationStatus", "Enable2FaRequest", + ) + ) + } + + // 59 切片 reducer:AntiAddiction(UC / W.iq8,8 字段;5 个动作) + run { + val S0 = AntiAddictionStateSlice() + Check( + "切片 AntiAddiction:默认值完整", + !S0.IsEnableRealNameCheck && !S0.UnknownUserId && S0.UrlForEnterUserId == null && + S0.PlayingTimeInMinutes == 0 && S0.AlertMessage == "" && + S0.IdNumber == "" && S0.RealName == "" && S0.ValidateStatus == null + ) + val S1 = ReduceAntiAddiction(S0, EnableRealNameCheck) + Check("切片 AntiAddiction:EnableRealNameCheck 置位", S1.IsEnableRealNameCheck) + val S2 = ReduceAntiAddiction(S1, RequireUserIdVerification("https://u")) + Check( + "切片 AntiAddiction:RequireUserIdVerification 写 unknown+url", + S2.UnknownUserId && S2.UrlForEnterUserId == "https://u" + ) + val S3 = ReduceAntiAddiction(S2, UpdatePlayingTime(180, "休息")) + Check( + "切片 AntiAddiction:UpdatePlayingTime 写时长与提示", + S3.PlayingTimeInMinutes == 180 && S3.AlertMessage == "休息" + ) + val S4 = ReduceAntiAddiction(S3, SetRealNameAndIdNumber("张三", "110")) + Check( + "切片 AntiAddiction:SetRealNameAndIdNumber 写实名与证件号", + S4.RealName == "张三" && S4.IdNumber == "110" + ) + Check( + "切片 AntiAddiction:实名参数为 null 时保留旧值", + ReduceAntiAddiction(S4, SetRealNameAndIdNumber(null, null)) == S4 + ) + Check( + "切片 AntiAddiction:SetValidateAntiAddictionUserDataStatus 写状态", + ReduceAntiAddiction(S4, SetValidateAntiAddictionUserDataStatus("ok")) + .ValidateStatus == "ok" + ) + Check("切片 AntiAddiction:无关动作不改状态", ReduceAntiAddiction(S4, AddFrags(1)) == S4) + + val Root0 = RootState() + val Root1 = ReduceRoot(Root0, EnableRealNameCheck) + Check( + "根:EnableRealNameCheck 只动 antiAddiction", + Root1.AntiAddiction.IsEnableRealNameCheck && Root1.Moderation == Root0.Moderation + ) + val W = RootWiring.ByField("antiAddiction")!! + Check( + "根:antiAddiction 委派到 UC / W.iq8 且 5 个动作", + W.SliceClass == "UC" && W.Reducer == "W.iq8" && + W.Actions.toSet() == setOf( + "EnableRealNameCheck", "RequireUserIdVerification", "UpdatePlayingTime", + "SetRealNameAndIdNumber", "SetValidateAntiAddictionUserDataStatus", + ) + ) + } + + // 60 切片 reducer:Moderation(NI / Ei.oqh,11 字段;13 个动作) + run { + val S0 = ModerationStateSlice() + Check( + "切片 Moderation:默认值完整", + !S0.ModeratorMode && !S0.BattleModeratorMode && !S0.EventOrganizerMode && + S0.ModerationToolBattleUrlTemplate == "" && + S0.ModerationToolUserUrlTemplate == "" && S0.BattleInfo == null && + !S0.ForceShowBattleInfo && S0.SystemMessage == "" && + !S0.ForceShowSystemMessage && !S0.ForceShowBlockUserMenu && + S0.SelectedUsers.isEmpty() + ) + val S1 = ReduceModeration(S0, SetModeratorModes(true, false, true)) + Check( + "切片 Moderation:SetModeratorModes 写三个开关", + S1.ModeratorMode && !S1.BattleModeratorMode && S1.EventOrganizerMode + ) + Check( + "切片 Moderation:SetModerationUrlTemplates 写两个模板", + ReduceModeration(S1, SetModerationUrlTemplates("b", "u")) + .ModerationToolBattleUrlTemplate == "b" + ) + val S2 = ReduceModeration(S1, ShowBattleInfo("info")) + Check( + "切片 Moderation:ShowBattleInfo 写面板并强制显示", + S2.BattleInfo == "info" && S2.ForceShowBattleInfo + ) + Check( + "切片 Moderation:HideBattleInfo 取消强制显示", + !ReduceModeration(S2, HideBattleInfo).ForceShowBattleInfo + ) + val S3 = ReduceModeration( + ReduceModeration(S2, AddSelectedUser(1L, 5, "a")), + AddSelectedUserFromChat(1L, 9, "a2"), + ) + Check( + "切片 Moderation:选中用户按 userId 去重后追加", + S3.SelectedUsers.size == 1 && + (S3.SelectedUsers[0] as ModerationUserRecord).userRank == 9 + ) + Check( + "切片 Moderation:RemoveSelectedUser 按 userId 删", + ReduceModeration( + ReduceModeration(S3, AddSelectedUser(2L, 1, "b")), + RemoveSelectedUser(1L), + ).SelectedUsers.size == 1 + ) + Check( + "切片 Moderation:ClearSelectedUsers / ResetSelectedUsers 清空", + ReduceModeration(S3, ClearSelectedUsers).SelectedUsers.isEmpty() && + ReduceModeration(S3, ResetSelectedUsers).SelectedUsers.isEmpty() + ) + val S4 = ReduceModeration(S3, ShowModerationSystemMessage("hello")) + Check( + "切片 Moderation:ShowModerationSystemMessage 写消息并强制显示", + S4.SystemMessage == "hello" && S4.ForceShowSystemMessage + ) + Check( + "切片 Moderation:HideModerationSystemMessage 取消强制显示", + !ReduceModeration(S4, HideModerationSystemMessage).ForceShowSystemMessage + ) + Check( + "切片 Moderation:ShowBlockUserMenu / HideBlockUserMenu 切换", + ReduceModeration(S4, ShowBlockUserMenu).ForceShowBlockUserMenu && + !ReduceModeration(S4, HideBlockUserMenu).ForceShowBlockUserMenu + ) + Check("切片 Moderation:无关动作不改状态", ReduceModeration(S4, AddFrags(1)) == S4) + + val Root0 = RootState() + val Root1 = ReduceRoot(Root0, ShowBlockUserMenu) + Check( + "根:ShowBlockUserMenu 只动 moderation", + Root1.Moderation.ForceShowBlockUserMenu && Root1.AntiAddiction == Root0.AntiAddiction + ) + val W = RootWiring.ByField("moderation")!! + Check( + "根:moderation 委派到 NI / Ei.oqh 且 13 个动作", + W.SliceClass == "NI" && W.Reducer == "Ei.oqh" && W.Actions.size == 13 + ) + } + + // 61 切片 reducer:MigrationState($it / Uv.rtg,7 字段;11 个动作) + run { + val S0 = MigrationStateSlice() + Check( + "切片 MigrationState:默认值完整", + !S0.RequestLogin && S0.Login == "" && S0.ForceLogin == null && + S0.Password == "" && + S0.LoginStatus == MigrationLoginStatus.UNKNOWN && + S0.RegisterStatus == MigrationRegisterStatus.UNKNOWN && + S0.AdvisedUids.isEmpty() + ) + val S1 = ReduceMigrationState(S0, SetUid("alice")) + Check( + "切片 MigrationState:SetUid 写 login 并复位状态", + S1.Login == "alice" && + S1.LoginStatus == MigrationLoginStatus.UNKNOWN && + S1.RegisterStatus == MigrationRegisterStatus.UNKNOWN + ) + Check( + "切片 MigrationState:ForceSetUid 写 forceLogin", + ReduceMigrationState(S1, ForceSetUid("forced")).ForceLogin == "forced" + ) + Check( + "切片 MigrationState:SetPassword 写 password", + ReduceMigrationState(S1, SetPassword("pwd")).Password == "pwd" + ) + Check( + "切片 MigrationState:UidIsFree/UidIsIncorrect/UidTooShort 写 loginStatus", + ReduceMigrationState(S1, UidIsFree).LoginStatus == MigrationLoginStatus.FREE && + ReduceMigrationState(S1, UidIsIncorrect).LoginStatus == + MigrationLoginStatus.INCORRECT && + ReduceMigrationState(S1, UidTooShort).LoginStatus == + MigrationLoginStatus.SHORT_NAME + ) + val S2 = ReduceMigrationState(S1, EnteredUidIsBusy(listOf("u1"))) + Check( + "切片 MigrationState:EnteredUidIsBusy 写 BUSY 与 advisedUids", + S2.LoginStatus == MigrationLoginStatus.BUSY && S2.AdvisedUids == listOf("u1") + ) + Check( + "切片 MigrationState:注册状态两支", + ReduceMigrationState(S1, RegisterPasswordIncorrect).RegisterStatus == + MigrationRegisterStatus.PASSWORD_INCORRECT && + ReduceMigrationState(S1, RegisterFailed).RegisterStatus == + MigrationRegisterStatus.FAILED + ) + Check( + "切片 MigrationState:ResetUidStatus / ResetAuthenticationForm", + ReduceMigrationState(S2, ResetUidStatus).LoginStatus == + MigrationLoginStatus.UNKNOWN && + ReduceMigrationState(S2, ResetAuthenticationForm).Login == "" + ) + Check( + "切片 MigrationState:无关动作不改状态", + ReduceMigrationState(S2, AddFrags(1)) == S2 + ) + + val Root0 = RootState() + val Root1 = ReduceRoot(Root0, SetUid("bob")) + Check( + "根:SetUid 同时动 migrationState 与 register", + Root1.MigrationState.Login == "bob" && Root1.Register.Login == "bob" && + Root1.System == Root0.System + ) + val W = RootWiring.ByField("migrationState")!! + Check( + "根:migrationState 委派到 \$it / Uv.rtg 且 11 个动作", + W.SliceClass == "\$it" && W.Reducer == "Uv.rtg" && W.Actions.size == 11 + ) + } + + // 62 切片 reducer:Register(Cit / cp.stg,16 字段;19 个动作) + run { + val S0 = RegisterStateSlice() + Check( + "切片 Register:默认值完整", + !S0.IsDisabled && S0.Login == "" && S0.ForceLogin == null && S0.Password == "" && + S0.UidValidationStatus == UidStatus.UNKNOWN && + S0.RegisterStatus == RegistrationStatus.UNKNOWN && + S0.AdvisedUids.isEmpty() && S0.SocialNetworkAvailability.isEmpty() && + S0.SocialNetworkClientIds == emptyMap() && + S0.SocialNetworkOAuthData == null && S0.MinPasswordLength == 6 && + S0.MaxPasswordLength == 20 && !S0.PasswordNeedLower && + !S0.PasswordNeedUpper && !S0.PasswordNeedDigit && S0.OpenedPassword + ) + val S1 = ReduceRegister(S0, DisableRegister) + Check("切片 Register:DisableRegister 置灰", S1.IsDisabled) + val S2 = ReduceRegister(S1, SetUid("alice")) + Check( + "切片 Register:SetUid 写 login 并复位 uid 状态", + S2.Login == "alice" && S2.UidValidationStatus == UidStatus.UNKNOWN + ) + Check( + "切片 Register:原状态为 INCORRECT 时保留 registerStatus,否则复位", + ReduceRegister( + ReduceRegister( + ReduceRegister(S2, RegisterFailed), + UidIsIncorrect, + ), + SetUid("alice2"), + ).RegisterStatus == RegistrationStatus.FAILED && + ReduceRegister( + ReduceRegister( + ReduceRegister(S2, RegisterFailed), + ResetUidStatus, + ), + SetUid("alice2"), + ).RegisterStatus == RegistrationStatus.UNKNOWN + ) + val S3 = ReduceRegister(S2, EnteredUidIsBusy(listOf("u1"))) + Check( + "切片 Register:EnteredUidIsBusy 写 BUSY + advisedUids", + S3.UidValidationStatus == UidStatus.BUSY && S3.AdvisedUids == listOf("u1") + ) + Check( + "切片 Register:uid 三个状态臂", + ReduceRegister(S2, UidIsFree).UidValidationStatus == UidStatus.FREE && + ReduceRegister(S2, UidIsIncorrect).UidValidationStatus == UidStatus.INCORRECT && + ReduceRegister(S2, UidTooShort).UidValidationStatus == UidStatus.SHORT_NAME + ) + Check( + "切片 Register:注册状态四臂", + ReduceRegister(S2, RegisterPasswordCorrect).RegisterStatus == + RegistrationStatus.PASSWORD_CORRECT && + ReduceRegister(S2, RegisterPasswordIncorrect).RegisterStatus == + RegistrationStatus.PASSWORD_INCORRECT && + ReduceRegister(S2, RegisterFailed).RegisterStatus == RegistrationStatus.FAILED && + ReduceRegister(S2, ResetRegisterStatus).RegisterStatus == + RegistrationStatus.UNKNOWN + ) + val S4 = ReduceRegister( + ReduceRegister(S2, SetPasswordRestrictions(8, 32, true, false, true)), + SetOpenedPassword(false), + ) + Check( + "切片 Register:密码规则与展开开关", + S4.MinPasswordLength == 8 && S4.MaxPasswordLength == 32 && + S4.PasswordNeedLower && !S4.PasswordNeedUpper && S4.PasswordNeedDigit && + !S4.OpenedPassword + ) + Check( + "切片 Register:社媒三支", + ReduceRegister(S2, SocialNetworkAvailability(mapOf("vk" to true))) + .SocialNetworkAvailability == mapOf("vk" to true) && + ReduceRegister(S2, SocialNetworkClientIds("cid")) + .SocialNetworkClientIds == "cid" && + ReduceRegister(S2, ReceiveOAuthUri("oauth")) + .SocialNetworkOAuthData == "oauth" + ) + run { + val R = ReduceRegister(S3, ResetAuthenticationForm) + Check( + "切片 Register:ResetAuthenticationForm 清 login 与两个状态", + R.Login == "" && R.UidValidationStatus == UidStatus.UNKNOWN && + R.RegisterStatus == RegistrationStatus.UNKNOWN && + R.AdvisedUids == S3.AdvisedUids + ) + } + Check("切片 Register:无关动作不改状态", ReduceRegister(S4, AddFrags(1)) == S4) + + val Root0 = RootState() + val Root1 = ReduceRoot(Root0, RegisterFailed) + Check( + "根:RegisterFailed 同时动 migrationState 与 register", + Root1.Register.RegisterStatus == RegistrationStatus.FAILED && + Root1.MigrationState.RegisterStatus == + MigrationRegisterStatus.FAILED + ) + val W = RootWiring.ByField("register")!! + Check( + "根:register 委派到 Cit / cp.stg 且 19 个动作", + W.SliceClass == "Cit" && W.Reducer == "cp.stg" && W.Actions.size == 19 + ) + } + + // 63 切片 reducer:UserReports(Zst / qot.zth,5 字段;6 个动作) + run { + val S0 = UserReportsStateSlice() + Check( + "切片 UserReports:默认值完整", + !S0.IsEnabled && !S0.HelpdeskTicketsEnabled && S0.AvailableReports == 0 && + S0.AvailableGameReports == 0 && S0.Reports.isEmpty() + ) + val S1 = ReduceUserReports(S0, InitUserReports(true, true, 5, 3)) + Check( + "切片 UserReports:InitUserReports 写四个开关/额度", + S1.IsEnabled && S1.HelpdeskTicketsEnabled && + S1.AvailableReports == 5 && S1.AvailableGameReports == 3 + ) + Check( + "切片 UserReports:SetAvailableReports / SetAvailableGameReports", + ReduceUserReports(S1, SetAvailableReports(7)).AvailableReports == 7 && + ReduceUserReports(S1, SetAvailableGameReports(9)).AvailableGameReports == 9 + ) + val S2 = ReduceUserReports(S1, ReportProBattleName("b1")) + Check( + "切片 UserReports:ReportProBattleName 追加 BATTLE_NAME 记录", + S2.Reports.size == 1 && + (S2.Reports[0] as ReportEntry).kind == ReportKind.BATTLE_NAME && + (S2.Reports[0] as ReportEntry).targetId == "b1" && + (S2.Reports[0] as ReportEntry).expireTime > 0.0 + ) + val S3 = ReduceUserReports( + ReduceUserReports(S2, ReportLobbyChatMessage("m1")), + ReportBattleChatMessage("m2"), + ) + Check( + "切片 UserReports:两种聊天举报各追加一条", + S3.Reports.size == 3 && + (S3.Reports[1] as ReportEntry).kind == ReportKind.LOBBY_CHAT_MESSAGE && + (S3.Reports[2] as ReportEntry).kind == ReportKind.BATTLE_CHAT_MESSAGE + ) + Check( + "切片 UserReports:无关动作不改状态", + ReduceUserReports(S3, AddFrags(1)) == S3 + ) + + val Root0 = RootState() + val Root1 = ReduceRoot(Root0, ReportProBattleName("b")) + Check( + "根:ReportProBattleName 只动 userReportsState", + Root1.UserReportsState.Reports.size == 1 && + Root1.Register == Root0.Register + ) + val W = RootWiring.ByField("userReportsState")!! + Check( + "根:userReportsState 委派到 Zst / qot.zth 且 6 个动作", + W.SliceClass == "Zst" && W.Reducer == "qot.zth" && W.Actions.size == 6 + ) + } + val Failed = Results.filter { !it.second } println("TankiOffline 状态层自检:${Results.size} 项") for ((Name, Ok) in Results) println((if (Ok) " ✓ " else " ✗ ") + Name)