diff --git a/3DTank/docs/KtRebuild_Plan.md b/3DTank/docs/KtRebuild_Plan.md index 4d72aa1..fd49348 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` 动作名。 - - 已迁移切片 **33/89**: + - 已迁移切片 **43/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) @@ -256,12 +256,35 @@ - `referralEvent` → `pQ` / `lh.osk`(`ReferralEventReducer.kt`):14 字段,4 个实例臂。 `SetReferrer` 写前 2 字段、`SetReferrals` 写人数与明细、`SetPromoCode` 写邀请码; `SetReferralEventData` 一次写后 9 个字段(面板标题 / 弹窗图与背景 / 两个分类 id / 任务数 / 两个隐藏标志 / 公告时间)。 + - `tipsState` → `dst` / `Hw.vth`(`TipsStateReducer.kt`):1 字段,`UpdateTips` 整体替换提示列表,缺省空表。 + - `rankUpDialogState` → `LO` / `Se.br3`(`RankUpDialogReducer.kt`):1 字段,2 个分支。 + `ShowAvailableItems` 是**追加**(`Im`:元素或整表),`FO`→`ResetRankUpDialog` 直接 `new LO()` 清空。 + - `battleResult` → `sR` / `Fi.aqp`(`BattleResultReducer.kt`):5 字段,6 个实例臂。 + `SetRewards` / `SetHasBattleResult` 写前两字段;`cR`/`lR`→`SetIncreasedReward`/`SetSimpleReward` 写两个奖励数值; + `fR`→`ResetBattleRewards` 只把两数值归零;`DoInit` 重建状态只带 `initialCurrentUserXP`。 + - `captchaState` → `jP` / `ar.iqt`(`CaptchaStateReducer.kt`):4 字段,7 个实例臂。 + 三个位置集合缺省 `emptySet()`;`SetLocations` 用 `toSet()` 收口; + `SP`→`ShowFakeCaptcha` 只置 `useFake`,其余是启用 / 已验证 / 待重置集合的增删元素。 + - `modalScreen` → `nV` / `Fs.js1`(`ModalScreenReducer.kt`):2 字段,4 个分支。 + `AddModal` 按 `toString()` 判重后 `current=screenId` 并入队;`RemoveModal` 出队后 `current = queue.firstOrNull()`; + `XW`→`CloseCurrentModal` 关当前窗口;`RemoveCurrentByName` 名称不匹配时 no-op。 + - `droppableGold` → `Vet` / `Wp.kth`(`DroppableGoldReducer.kt`):1 字段,缺省 `showDroppableGoldAuthor=true`; + `Ket`/`Yet` 两个单例 → `ShowDroppableGoldAuthor` / `HideDroppableGoldAuthor`。 + - `eventGoods` → `W7` / `Wc.esx`(`EventGoodsReducer.kt`):1 字段 `Map`,缺省空 Map; + `InitEventGoods(itemId, goods)` 在 `goods == null` 时删键,否则写键值。 + - `creatorTagState` → `qO` / `ze.er2`(`CreatorTagReducer.kt`):2 字段,4 个分支。 + `creatorTags` 缺省 null;`SetCreatorTags` / `SetCreatorTag` / `SetAttachedCreatorTag` 写对应字段, + `jO`→`ClearCreatorTag` 只清 `creatorTag`。 + - `rewardedVideosState` → `JQ` / `xh.zsl`(`RewardedVideosReducer.kt`):1 字段, + 新增枚举 `RewardVideoDisplayState { NONE, LOADING, DISPLAY }`,缺省 `NONE`;`SetDisplayState` 整体替换。 + - `passwordChangeState` → `VD` / `uu.pr6`(`PasswordChangeReducer.kt`):3 个字符串字段(hash / uid / email), + 缺省全为空串;`SetPasswordChangeHash` 只写 hash,`ShowEmailAndPasswordChangeDialog` 写 uid/email 并保留 hash。 - `launchParams` → `(pass-through)`:原根 reducer 对 `hpx_1` 没有调用,缺省是 `ag()` 空 Map; `RootState.LaunchParams` 收口为 `LaunchParamsStateSlice = Map`,不新增 reducer/动作。 - - `App/State/RootReducer.kt`:`ReduceRoot(State0, Action)` 归约上述 32 个有 reducer 的切片, - `launchParams` 与其余 **56 个字段原样透传**; - 迁移进度用 `RootReducer.Coverage`(`已迁移 33/89`)与 `RootWiring.Unmigrated` 暴露。 - - 自检:`TankiOffline/tools/RunStateTest.ps1` **457 项全绿**,含 89 槽位完整性、32 个归约切片一次归约互不串片、未迁移字段透传, + - `App/State/RootReducer.kt`:`ReduceRoot(State0, Action)` 归约上述 42 个有 reducer 的切片, + `launchParams` 与其余 **46 个字段原样透传**; + 迁移进度用 `RootReducer.Coverage`(`已迁移 43/89`)与 `RootWiring.Unmigrated` 暴露。 + - 自检:`TankiOffline/tools/RunStateTest.ps1` **529 项全绿**,含 89 槽位完整性、42 个归约切片一次归约互不串片、未迁移字段透传, 以及用 `action_trace_merged.json` 真实派发的两条商店动作(`SetCoinPaymentProviderUrl` / `ToggleShopActivity`)回放。 - 委派表里的已知形态:`shop` / `home` / `localBattleUserState` / `partner` 是内联 IIFE, 已按字段具名为 `(inline@)` 并从函数体抓到顶层动作名(69 / 1 / 2 / 11 个); @@ -274,7 +297,8 @@ entrance 的 6 个、user 的 3 个、settings 的 2 个、accountSettings 的 11 个、 language 的 1 个、announcement 的 3 个、news 的 3 个(含两个单例)、notifications 的 2 个、 quests 的 1 个、lobbyLoader 的 2 个、purchaseDialogState 的 1 个、supportUrl 的 1 个、 - notificationByCountry 的 1 个、mainQuest 的 1 个匿名类 / 单例同样按效果定名并登记。 + notificationByCountry 的 1 个、mainQuest 的 1 个、rankUpDialogState 的 1 个、battleResult 的 3 个、 + captchaState 的 5 个、modalScreen 的 1 个、droppableGold 的 2 个、creatorTagState 的 1 个匿名类 / 单例同样按效果定名并登记。 ### 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 6319c22..ecfcf1b 100644 --- a/3DTank/names/root_wiring.json +++ b/3DTank/names/root_wiring.json @@ -1,5 +1,5 @@ { - "generatedAt": "2026-10-24T06:01:37.203Z", + "generatedAt": "2026-10-24T06:30:01.615Z", "module": "module_10032.js", "RootState": { "Class": "z6", @@ -39,7 +39,17 @@ "questCategories", "mainQuest", "securitySettings", - "referralEvent" + "referralEvent", + "tipsState", + "rankUpDialogState", + "battleResult", + "captchaState", + "modalScreen", + "droppableGold", + "eventGoods", + "creatorTagState", + "rewardedVideosState", + "passwordChangeState" ], "AnonymousActions": [ { @@ -708,6 +718,84 @@ "Name": "ResetQuestsLoaded", "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", "Effect": "isLoaded=false(`n.qsj(!1)`;动作类 rsj_1 在 reducer 里未被读取)" + }, + { + "Local": "FO", + "Name": "ResetRankUpDialog", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "availableItems=emptyList()(`new LO()`)" + }, + { + "Local": "cR", + "Name": "SetIncreasedReward", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "increasedReward=动作数值(`n.qqn(cd, cd, cd, t.cqp_1)`)" + }, + { + "Local": "lR", + "Name": "SetSimpleReward", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "simpleReward=动作数值(`n.qqn(cd, cd, cd, cd, t.bqp_1)`)" + }, + { + "Local": "fR", + "Name": "ResetBattleRewards", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "increasedReward=0、simpleReward=0(`n.qqn(cd, cd, cd, 0, 0)`)" + }, + { + "Local": "SP", + "Name": "ShowFakeCaptcha", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "useFake=true(`n.wqs(!0)`)" + }, + { + "Local": "IP", + "Name": "AddVerifiedCaptchaLocation", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "verifiedLocations + location(`Xg`)" + }, + { + "Local": "AP", + "Name": "RemoveVerifiedCaptchaLocation", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "verifiedLocations - location(`Jg`)" + }, + { + "Local": "RP", + "Name": "AddCaptchaLocationToReset", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "locationsToReset + location(`Xg`)" + }, + { + "Local": "PP", + "Name": "RemoveCaptchaLocationToReset", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "locationsToReset - location(`Jg`)" + }, + { + "Local": "XW", + "Name": "CloseCurrentModal", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "current 非空时 queue -= current、current=queue.firstOrNull()" + }, + { + "Local": "Ket", + "Name": "ShowDroppableGoldAuthor", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "showDroppableGoldAuthor=true(`n.f7j(!0)`)" + }, + { + "Local": "Yet", + "Name": "HideDroppableGoldAuthor", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "showDroppableGoldAuthor=false(`n.f7j(!1)`)" + }, + { + "Local": "jO", + "Name": "ClearCreatorTag", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "creatorTag=null(`n.ar2(null)`)" } ], "Markers": [ @@ -822,13 +910,13 @@ "Reducer": "Fs.js1", "Delegate": "Fs.js1(t, n.apv_1)", "Actions": [ - "?XW", "AddModal", + "CloseCurrentModal", "RemoveCurrentByName", "RemoveModal" ], "Default": "new nV()", - "Migrated": false + "Migrated": true }, { "Field": "battleStatistics", @@ -1093,16 +1181,16 @@ "Reducer": "ar.iqt", "Delegate": "ar.iqt(t, n.kpv_1)", "Actions": [ - "?AP", - "?IP", - "?PP", - "?RP", - "?SP", + "AddCaptchaLocationToReset", + "AddVerifiedCaptchaLocation", "LocationsUpdated", - "SetLocations" + "RemoveCaptchaLocationToReset", + "RemoveVerifiedCaptchaLocation", + "SetLocations", + "ShowFakeCaptcha" ], "Default": "new jP()", - "Migrated": false + "Migrated": true }, { "Field": "passwordChangeState", @@ -1115,7 +1203,7 @@ "ShowEmailAndPasswordChangeDialog" ], "Default": "new VD()", - "Migrated": false + "Migrated": true }, { "Field": "system", @@ -1283,7 +1371,7 @@ "UpdateTips" ], "Default": "new dst()", - "Migrated": false + "Migrated": true }, { "Field": "news", @@ -1556,15 +1644,15 @@ "Reducer": "Fi.aqp", "Delegate": "Fi.aqp(t, n.gpw_1)", "Actions": [ - "?cR", - "?fR", - "?lR", "DoInit", + "ResetBattleRewards", "SetHasBattleResult", - "SetRewards" + "SetIncreasedReward", + "SetRewards", + "SetSimpleReward" ], "Default": "new sR()", - "Migrated": false + "Migrated": true }, { "Field": "battleUsers", @@ -1758,11 +1846,11 @@ "Reducer": "Se.br3", "Delegate": "Se.br3(t, n.npw_1)", "Actions": [ - "?FO", + "ResetRankUpDialog", "ShowAvailableItems" ], "Default": "new LO()", - "Migrated": false + "Migrated": true }, { "Field": "clanUserInfo", @@ -1833,11 +1921,11 @@ "Reducer": "Wp.kth", "Delegate": "Wp.kth(t, n.spw_1)", "Actions": [ - "?Ket", - "?Yet" + "HideDroppableGoldAuthor", + "ShowDroppableGoldAuthor" ], "Default": "new Vet()", - "Migrated": false + "Migrated": true }, { "Field": "home", @@ -2212,7 +2300,7 @@ "InitEventGoods" ], "Default": "new W7()", - "Migrated": false + "Migrated": true }, { "Field": "creatorTagState", @@ -2221,13 +2309,13 @@ "Reducer": "ze.er2", "Delegate": "ze.er2(t, n.spx_1)", "Actions": [ - "?jO", + "ClearCreatorTag", "SetAttachedCreatorTag", "SetCreatorTag", "SetCreatorTags" ], "Default": "new qO()", - "Migrated": false + "Migrated": true }, { "Field": "twoStepVerificationState", @@ -2255,7 +2343,7 @@ "SetDisplayState" ], "Default": "new JQ()", - "Migrated": false + "Migrated": true }, { "Field": "twitchState", diff --git a/3DTank/tools/gen_root_wiring.js b/3DTank/tools/gen_root_wiring.js index e032bbd..a103d32 100644 --- a/3DTank/tools/gen_root_wiring.js +++ b/3DTank/tools/gen_root_wiring.js @@ -181,6 +181,25 @@ const AnonymousActionNames = new Map([ ['qJ', 'MarkHomeScreenDialogShown'], // mainQuest 切片(WZ / ih.bsk):VZ 没有 toString,按 reducer 效果定名。 ['VZ', 'ResetQuestsLoaded'], + // rankUpDialogState 切片(LO / GO.br3):FO 没有 toString。 + ['FO', 'ResetRankUpDialog'], + // battleResult 切片(sR / pR.aqp):cR/lR/fR 没有 toString。 + ['cR', 'SetIncreasedReward'], + ['lR', 'SetSimpleReward'], + ['fR', 'ResetBattleRewards'], + // captchaState 切片(jP / MP.iqt):SP/IP/AP/RP/PP 没有 toString。 + ['SP', 'ShowFakeCaptcha'], + ['IP', 'AddVerifiedCaptchaLocation'], + ['AP', 'RemoveVerifiedCaptchaLocation'], + ['RP', 'AddCaptchaLocationToReset'], + ['PP', 'RemoveCaptchaLocationToReset'], + // modalScreen 切片(nV / QW.js1):XW 没有 toString。 + ['XW', 'CloseCurrentModal'], + // droppableGold 切片(Vet / Xet.kth):Ket/Yet 两个单例。 + ['Ket', 'ShowDroppableGoldAuthor'], + ['Yet', 'HideDroppableGoldAuthor'], + // creatorTagState 切片(qO / SO.er2):jO 没有 toString。 + ['jO', 'ClearCreatorTag'], ]); // 非动作的 `instanceof`:屏幕标识类,用于 GoTo 分支判断(不是 dispatch 的动作)。 @@ -599,6 +618,16 @@ const Migrated = { mainQuest: { Type: 'MainQuestStateSlice', Reduce: 'ReduceMainQuest' }, securitySettings: { Type: 'SecuritySettingsStateSlice', Reduce: 'ReduceSecuritySettings' }, referralEvent: { Type: 'ReferralEventStateSlice', Reduce: 'ReduceReferralEvent' }, + tipsState: { Type: 'TipsStateSlice', Reduce: 'ReduceTips' }, + rankUpDialogState: { Type: 'RankUpDialogStateSlice', Reduce: 'ReduceRankUpDialog' }, + battleResult: { Type: 'BattleResultStateSlice', Reduce: 'ReduceBattleResult' }, + captchaState: { Type: 'CaptchaStateSlice', Reduce: 'ReduceCaptcha' }, + modalScreen: { Type: 'ModalScreenStateSlice', Reduce: 'ReduceModalScreen' }, + droppableGold: { Type: 'DroppableGoldStateSlice', Reduce: 'ReduceDroppableGold' }, + eventGoods: { Type: 'EventGoodsStateSlice', Reduce: 'ReduceEventGoods' }, + creatorTagState: { Type: 'CreatorTagStateSlice', Reduce: 'ReduceCreatorTag' }, + rewardedVideosState: { Type: 'RewardedVideosStateSlice', Reduce: 'ReduceRewardedVideos' }, + passwordChangeState: { Type: 'PasswordChangeStateSlice', Reduce: 'ReducePasswordChange' }, }; for (const R of Rows) R.Migrated = Object.prototype.hasOwnProperty.call(Migrated, R.Field); @@ -811,6 +840,43 @@ const MainQuestAnonymousEffects = { VZ: 'isLoaded=false(`n.qsj(!1)`;动作类 rsj_1 在 reducer 里未被读取)', }; +// rankUpDialogState 切片(LO / GO.br3):FO 没有 toString。 +const RankUpDialogAnonymousEffects = { + FO: 'availableItems=emptyList()(`new LO()`)', +}; + +// battleResult 切片(sR / pR.aqp):cR/lR/fR 没有 toString。 +const BattleResultAnonymousEffects = { + cR: 'increasedReward=动作数值(`n.qqn(cd, cd, cd, t.cqp_1)`)', + lR: 'simpleReward=动作数值(`n.qqn(cd, cd, cd, cd, t.bqp_1)`)', + fR: 'increasedReward=0、simpleReward=0(`n.qqn(cd, cd, cd, 0, 0)`)', +}; + +// captchaState 切片(jP / MP.iqt):SP/IP/AP/RP/PP 没有 toString。 +const CaptchaAnonymousEffects = { + SP: 'useFake=true(`n.wqs(!0)`)', + IP: 'verifiedLocations + location(`Xg`)', + AP: 'verifiedLocations - location(`Jg`)', + RP: 'locationsToReset + location(`Xg`)', + PP: 'locationsToReset - location(`Jg`)', +}; + +// modalScreen 切片(nV / QW.js1):XW 没有 toString。 +const ModalScreenAnonymousEffects = { + XW: 'current 非空时 queue -= current、current=queue.firstOrNull()', +}; + +// droppableGold 切片(Vet / Xet.kth):Ket/Yet 是单例。 +const DroppableGoldAnonymousEffects = { + Ket: 'showDroppableGoldAuthor=true(`n.f7j(!0)`)', + Yet: 'showDroppableGoldAuthor=false(`n.f7j(!1)`)', +}; + +// creatorTagState 切片(qO / SO.er2):jO 没有 toString。 +const CreatorTagAnonymousEffects = { + jO: 'creatorTag=null(`n.ar2(null)`)', +}; + fs.writeFileSync( path.join(P, 'names', 'root_wiring.json'), JSON.stringify( @@ -941,6 +1007,42 @@ fs.writeFileSync( DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', Effect, })), + ...Object.entries(RankUpDialogAnonymousEffects).map(([Local, Effect]) => ({ + Local, + Name: AnonymousActionNames.get(Local), + DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', + Effect, + })), + ...Object.entries(BattleResultAnonymousEffects).map(([Local, Effect]) => ({ + Local, + Name: AnonymousActionNames.get(Local), + DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', + Effect, + })), + ...Object.entries(CaptchaAnonymousEffects).map(([Local, Effect]) => ({ + Local, + Name: AnonymousActionNames.get(Local), + DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', + Effect, + })), + ...Object.entries(ModalScreenAnonymousEffects).map(([Local, Effect]) => ({ + Local, + Name: AnonymousActionNames.get(Local), + DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', + Effect, + })), + ...Object.entries(DroppableGoldAnonymousEffects).map(([Local, Effect]) => ({ + Local, + Name: AnonymousActionNames.get(Local), + DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', + Effect, + })), + ...Object.entries(CreatorTagAnonymousEffects).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/BattleResultReducer.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/BattleResultReducer.kt new file mode 100644 index 0000000..9ebd176 --- /dev/null +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/BattleResultReducer.kt @@ -0,0 +1,55 @@ +// BattleResultReducer.kt —— 战斗结算切片(对应 module_10032 的 sR 状态 + pR.aqp) +// +// 证据链: +// 状态类 sR:5 个字段,字段名取自 toString;copy 是 qqn(5 字段 + 回调)。 +// 默认:hasBattleResult=false、rewards=emptyList()、initialCurrentUserXP=0、increasedReward=0、simpleReward=0。 +// 根委派:`Fi.aqp(t, n.gpw_1)`。 +// reducer: +// aR(SetRewards) → rewards +// hR(SetHasBattleResult)→ hasBattleResult +// cR(无 toString) → increasedReward +// lR(无 toString) → simpleReward +// fR(无 toString) → increasedReward=0、simpleReward=0 +// vR(DoInit) → 全新状态,只带 initialCurrentUserXP(`new sR(cd, cd, t.zqo_1)`) +package App.State.Reducers + +import App.State.ContainerGivenItem +import App.State.DoInit +import App.State.SetHasBattleResult +import App.State.SetRewards + +/* ------------------------------------------------------------------ 匿名动作 */ + +/** 原 `cR`:写「加成后奖励」数值。 */ +data class SetIncreasedReward(val value: Int) + +/** 原 `lR`:写「原始奖励」数值。 */ +data class SetSimpleReward(val value: Int) + +/** 原 `fR`:把两个奖励数值归零。 */ +object ResetBattleRewards + +/* ------------------------------------------------------------------ 状态 */ + +data class BattleResultStateSlice( + val HasBattleResult: Boolean = false, + val Rewards: List = emptyList(), + val InitialCurrentUserXP: Any? = null, + val IncreasedReward: Int = 0, + val SimpleReward: Int = 0, +) + +/* ------------------------------------------------------------------ reducer */ + +fun ReduceBattleResult( + State0: BattleResultStateSlice, + Action: Any, +): BattleResultStateSlice = when (Action) { + is SetRewards -> State0.copy(Rewards = Action.rewards) + is SetHasBattleResult -> State0.copy(HasBattleResult = Action.hasBattleResult) + is SetIncreasedReward -> State0.copy(IncreasedReward = Action.value) + is SetSimpleReward -> State0.copy(SimpleReward = Action.value) + is ResetBattleRewards -> State0.copy(IncreasedReward = 0, SimpleReward = 0) + is DoInit -> BattleResultStateSlice(InitialCurrentUserXP = Action.initialCurrentUserXP) + else -> State0 +} diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/CaptchaStateReducer.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/CaptchaStateReducer.kt new file mode 100644 index 0000000..3e2d05d --- /dev/null +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/CaptchaStateReducer.kt @@ -0,0 +1,67 @@ +// CaptchaStateReducer.kt —— 验证码切片(对应 module_10032 的 jP 状态 + MP.iqt) +// +// 证据链: +// 状态类 jP:4 个字段,copy 是 wqs(4 字段 + 回调)。 +// 默认:useFake=false,三个位置集合都是 `Yg()` = emptySet()。 +// 根委派:`ar.iqt(t, n.kpv_1)`。 +// reducer: +// SP(无 toString) → useFake=true +// CP(SetLocations) → enabledLocations = Sm(captchaLocations)(`Sm` = toSet) +// IP(无 toString) → verifiedLocations + location(`Xg` = 集合加元素) +// AP(无 toString) → verifiedLocations - location(`Jg` = 集合删元素) +// RP(无 toString) → locationsToReset + location +// PP(无 toString) → locationsToReset - location +// NP(LocationsUpdated)→ enabledLocations + location +package App.State.Reducers + +import App.State.LocationsUpdated +import App.State.SetLocations + +/* ------------------------------------------------------------------ 匿名动作 */ + +/** 原 `SP`:开启假验证码(`n.wqs(!0)`)。 */ +object ShowFakeCaptcha + +/** 原 `IP`:把地点加入已验证集合。 */ +data class AddVerifiedCaptchaLocation(val location: Any?) + +/** 原 `AP`:把地点移出已验证集合。 */ +data class RemoveVerifiedCaptchaLocation(val location: Any?) + +/** 原 `RP`:把地点加入待重置集合。 */ +data class AddCaptchaLocationToReset(val location: Any?) + +/** 原 `PP`:把地点移出待重置集合。 */ +data class RemoveCaptchaLocationToReset(val location: Any?) + +/* ------------------------------------------------------------------ 状态 */ + +data class CaptchaStateSlice( + val UseFake: Boolean = false, + val EnabledLocations: Set = emptySet(), + val VerifiedLocations: Set = emptySet(), + val LocationsToReset: Set = emptySet(), +) + +/* ------------------------------------------------------------------ reducer */ + +fun ReduceCaptcha( + State0: CaptchaStateSlice, + Action: Any, +): CaptchaStateSlice = when (Action) { + is ShowFakeCaptcha -> State0.copy(UseFake = true) + is SetLocations -> State0.copy( + EnabledLocations = (Action.captchaLocations as List).toSet(), + ) + is AddVerifiedCaptchaLocation -> + State0.copy(VerifiedLocations = State0.VerifiedLocations + Action.location) + is RemoveVerifiedCaptchaLocation -> + State0.copy(VerifiedLocations = State0.VerifiedLocations - Action.location) + is AddCaptchaLocationToReset -> + State0.copy(LocationsToReset = State0.LocationsToReset + Action.location) + is RemoveCaptchaLocationToReset -> + State0.copy(LocationsToReset = State0.LocationsToReset - Action.location) + is LocationsUpdated -> + State0.copy(EnabledLocations = State0.EnabledLocations + Action.location) + else -> State0 +} diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/CreatorTagReducer.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/CreatorTagReducer.kt new file mode 100644 index 0000000..a1bb7f6 --- /dev/null +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/CreatorTagReducer.kt @@ -0,0 +1,42 @@ +// CreatorTagReducer.kt —— 创作者标签切片(对应 module_10032 的 qO 状态 + SO.er2) +// +// 证据链: +// 状态类 qO:2 个字段,toString 名 "CreatorTagState(creatorTag=…, creatorTags=…)";copy 是 ar2(2 字段)。 +// 默认:creatorTag=null、creatorTags=null。 +// 根委派:`ze.er2(t, n.spx_1)`。 +// reducer: +// yO(SetCreatorTags) → creatorTags +// $O(SetCreatorTag) → creatorTag +// jO(无 toString) → creatorTag=null(`n.ar2(null)`) +// xO(SetAttachedCreatorTag) → creatorTag +package App.State.Reducers + +import App.State.SetAttachedCreatorTag +import App.State.SetCreatorTag +import App.State.SetCreatorTags + +/* ------------------------------------------------------------------ 匿名动作 */ + +/** 原 `jO`:清空已挂载的创作者标签。 */ +object ClearCreatorTag + +/* ------------------------------------------------------------------ 状态 */ + +data class CreatorTagStateSlice( + val CreatorTag: Any? = null, + val CreatorTags: List? = null, +) + +/* ------------------------------------------------------------------ reducer */ + +fun ReduceCreatorTag( + State0: CreatorTagStateSlice, + Action: Any, +): CreatorTagStateSlice = when (Action) { + is SetCreatorTags -> + State0.copy(CreatorTags = Action.creatorTags as List?) + is SetCreatorTag -> State0.copy(CreatorTag = Action.creatorTag) + is SetAttachedCreatorTag -> State0.copy(CreatorTag = Action.creatorTag) + is ClearCreatorTag -> State0.copy(CreatorTag = null) + else -> State0 +} diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/DroppableGoldReducer.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/DroppableGoldReducer.kt new file mode 100644 index 0000000..abf9f85 --- /dev/null +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/DroppableGoldReducer.kt @@ -0,0 +1,33 @@ +// DroppableGoldReducer.kt —— 掉落金币署名开关切片(对应 module_10032 的 Vet 状态 + Xet.kth) +// +// 证据链: +// 状态类 Vet:1 个字段,copy 是 f7j(1 字段)。 +// 默认:`showDroppableGoldAuthor=true`。 +// 根委派:`Wp.kth(t, n.spw_1)`。 +// reducer:`Ket ? n.f7j(!0) : Yet ? n.f7j(!1) : n`。 +package App.State.Reducers + +/* ------------------------------------------------------------------ 匿名动作 */ + +/** 原 `Ket` 单例:显示掉落金币的作者署名。 */ +object ShowDroppableGoldAuthor + +/** 原 `Yet` 单例:隐藏掉落金币的作者署名。 */ +object HideDroppableGoldAuthor + +/* ------------------------------------------------------------------ 状态 */ + +data class DroppableGoldStateSlice( + val ShowDroppableGoldAuthor: Boolean = true, +) + +/* ------------------------------------------------------------------ reducer */ + +fun ReduceDroppableGold( + State0: DroppableGoldStateSlice, + Action: Any, +): DroppableGoldStateSlice = when (Action) { + is ShowDroppableGoldAuthor -> State0.copy(ShowDroppableGoldAuthor = true) + is HideDroppableGoldAuthor -> State0.copy(ShowDroppableGoldAuthor = false) + else -> State0 +} diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/EventGoodsReducer.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/EventGoodsReducer.kt new file mode 100644 index 0000000..ddeee07 --- /dev/null +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/EventGoodsReducer.kt @@ -0,0 +1,31 @@ +// EventGoodsReducer.kt —— 活动商品表切片(对应 module_10032 的 W7 状态 + K7.esx) +// +// 证据链: +// 状态类 W7:1 个字段,toString 名 "EventGoodsState(eventGoods=…)";copy 是 bsx(1 字段)。 +// 默认:eventGoods = ag() = emptyMap()。 +// 根委派:`Wc.esx(t, n.rpx_1)`。 +// reducer:`t instanceof V7 ? (goods 为空 ? map 删键 : map 写键值) : n` +// V7 = InitEventGoods(itemId, goods);`am` = map 删键、`cg` = map 写键值。 +package App.State.Reducers + +import App.State.InitEventGoods + +/* ------------------------------------------------------------------ 状态 */ + +data class EventGoodsStateSlice( + val EventGoods: Map = emptyMap(), +) + +/* ------------------------------------------------------------------ reducer */ + +fun ReduceEventGoods( + State0: EventGoodsStateSlice, + Action: Any, +): EventGoodsStateSlice = when (Action) { + is InitEventGoods -> State0.copy( + EventGoods = + if (Action.goods == null) State0.EventGoods - Action.itemId + else State0.EventGoods + (Action.itemId to Action.goods), + ) + else -> State0 +} diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/ModalScreenReducer.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/ModalScreenReducer.kt new file mode 100644 index 0000000..612dbc8 --- /dev/null +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/ModalScreenReducer.kt @@ -0,0 +1,67 @@ +// ModalScreenReducer.kt —— 模态窗口队列切片(对应 module_10032 的 nV 状态 + QW.js1) +// +// 证据链: +// 状态类 nV:2 个字段,toString 名 "ModalScreen(current=…, modalQueue=…)";copy 是 ks1(2 字段)。 +// 默认:current=null、modalQueue=emptyList()。 +// 根委派:`Fs.js1(t, n.apv_1)`。 +// reducer: +// YW(AddModal) → 队列里已有同名窗口时 no-op;否则 current=screenId、queue += screenId。 +// 原判重是 `Xd(e, iV) && n.ls1(e)`:`ls1` 用 `toString()` 比较队列元素。 +// `iV` 是模态屏幕标识的标记接口,Kotlin 侧按「所有 AddModal 都参与判重」处理。 +// JW(RemoveModal)→ queue -= screenId、current = queue.firstOrNull()(`mk` = firstOrNull) +// XW(无 toString) → current 为空时 no-op;否则 queue -= current、current = queue.firstOrNull() +// ZW(RemoveCurrentByName)→ current 为空或 toString 不等于 name 时 no-op,否则同上 +package App.State.Reducers + +import App.State.AddModal +import App.State.RemoveCurrentByName +import App.State.RemoveModal + +/* ------------------------------------------------------------------ 匿名动作 */ + +/** 原 `XW` 单例:关掉当前窗口,并把 current 交给队首。 */ +object CloseCurrentModal + +/* ------------------------------------------------------------------ 状态 */ + +data class ModalScreenStateSlice( + val Current: Any? = null, + val ModalQueue: List = emptyList(), +) + +/* ------------------------------------------------------------------ reducer */ + +private fun FirstOrNull(Items: List): Any? = Items.firstOrNull() + +fun ReduceModalScreen( + State0: ModalScreenStateSlice, + Action: Any, +): ModalScreenStateSlice = when (Action) { + is AddModal -> { + val ScreenId: Any? = Action.screenId + val Name = ScreenId.toString() + if (State0.ModalQueue.any { it.toString() == Name }) State0 + else State0.copy(Current = ScreenId, ModalQueue = State0.ModalQueue + ScreenId) + } + is RemoveModal -> { + val Queue = State0.ModalQueue - Action.screenId + State0.copy(Current = FirstOrNull(Queue), ModalQueue = Queue) + } + is CloseCurrentModal -> { + val CurrentModal = State0.Current + if (CurrentModal == null) State0 + else { + val Queue = State0.ModalQueue - CurrentModal + State0.copy(Current = FirstOrNull(Queue), ModalQueue = Queue) + } + } + is RemoveCurrentByName -> { + val CurrentModal = State0.Current + if (CurrentModal == null || CurrentModal.toString() != Action.name.toString()) State0 + else { + val Queue = State0.ModalQueue - CurrentModal + State0.copy(Current = FirstOrNull(Queue), ModalQueue = Queue) + } + } + else -> State0 +} diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/PasswordChangeReducer.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/PasswordChangeReducer.kt new file mode 100644 index 0000000..55ed872 --- /dev/null +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/PasswordChangeReducer.kt @@ -0,0 +1,35 @@ +// PasswordChangeReducer.kt —— 改密流程切片(对应 module_10032 的 VD 状态 + WD.pr6) +// +// 证据链: +// 状态类 VD:3 个字段,toString 名 "PasswordChangeState(hash=…, uid=…, email=…)";copy 是 tr6(3 字段)。 +// 默认:hash=""、uid=""、email=""。 +// 根委派:`uu.pr6(t, n.lpv_1)`。 +// reducer: +// UD(SetPasswordChangeHash) → hash(`n.tr6(t.fr6_1)`) +// HD(ShowEmailAndPasswordChangeDialog)→ `n.tr6(cd, e, r)`,其中 r=email、e=uid,即保持 hash +package App.State.Reducers + +import App.State.SetPasswordChangeHash +import App.State.ShowEmailAndPasswordChangeDialog + +/* ------------------------------------------------------------------ 状态 */ + +data class PasswordChangeStateSlice( + val Hash: String = "", + val Uid: String = "", + val Email: String = "", +) + +/* ------------------------------------------------------------------ reducer */ + +fun ReducePasswordChange( + State0: PasswordChangeStateSlice, + Action: Any, +): PasswordChangeStateSlice = when (Action) { + is SetPasswordChangeHash -> State0.copy(Hash = Action.hash.toString()) + is ShowEmailAndPasswordChangeDialog -> State0.copy( + Uid = Action.uid.toString(), + Email = Action.email.toString(), + ) + else -> State0 +} diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/RankUpDialogReducer.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/RankUpDialogReducer.kt new file mode 100644 index 0000000..7ea6c4c --- /dev/null +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/RankUpDialogReducer.kt @@ -0,0 +1,39 @@ +// RankUpDialogReducer.kt —— 升级奖励弹窗切片(对应 module_10032 的 LO 状态 + GO.br3) +// +// 证据链: +// 状态类 LO:1 个字段,toString 名 "RankUpDialogState(availableItems=…)";copy 是 wr2(1 字段)。 +// 默认:availableItems = md() = emptyList()。 +// 根委派:`Se.br3(t, n.npw_1)`。 +// reducer: +// UO(ShowAvailableItems)→ `n.wr2(Im(n.vr2_1, t.ar3_1))`,即在现有列表后追加(元素或整表) +// FO(无 toString) → `new LO()`,复位为空表 +package App.State.Reducers + +import App.State.ShowAvailableItems + +/* ------------------------------------------------------------------ 匿名动作 */ + +/** 原 `FO`:清空升级奖励弹窗列表(`new LO()`)。 */ +object ResetRankUpDialog + +/* ------------------------------------------------------------------ 状态 */ + +data class RankUpDialogStateSlice( + val AvailableItems: List = emptyList(), +) + +/* ------------------------------------------------------------------ reducer */ + +/** 原 `Im`:列表追加;`Items` 本身是集合时做拼接,否则当作单个元素。 */ +private fun PlusItems(Base: List, Items: Any?): List = + if (Items is List<*>) Base + Items else Base + listOf(Items) + +fun ReduceRankUpDialog( + State0: RankUpDialogStateSlice, + Action: Any, +): RankUpDialogStateSlice = when (Action) { + is ShowAvailableItems -> + State0.copy(AvailableItems = PlusItems(State0.AvailableItems, Action.items)) + is ResetRankUpDialog -> RankUpDialogStateSlice() + else -> State0 +} diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/RewardedVideosReducer.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/RewardedVideosReducer.kt new file mode 100644 index 0000000..85fe04e --- /dev/null +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/RewardedVideosReducer.kt @@ -0,0 +1,33 @@ +// RewardedVideosReducer.kt —— 激励视频切片(对应 module_10032 的 JQ 状态 + n1.zsl) +// +// 证据链: +// 状态类 JQ:1 个字段,toString 名 "RewardedVideosState(displayState=…)";copy 是 vsl(1 字段)。 +// 默认:`XQ()` → `FQ()` 里的 `dh`,即枚举 NONE。 +// reducer:`t instanceof t1 ? n.vsl(t.ysl_1) : n`,`t1` 是 SetDisplayState(displayState)。 +// 枚举 `VQ`:NONE / LOADING / DISPLAY。 +package App.State.Reducers + +import App.State.SetDisplayState + +/* ------------------------------------------------------------------ 状态 */ + +enum class RewardVideoDisplayState { + NONE, + LOADING, + DISPLAY, +} + +data class RewardedVideosStateSlice( + val DisplayState: RewardVideoDisplayState = RewardVideoDisplayState.NONE, +) + +/* ------------------------------------------------------------------ reducer */ + +fun ReduceRewardedVideos( + State0: RewardedVideosStateSlice, + Action: Any, +): RewardedVideosStateSlice = when (Action) { + is SetDisplayState -> + State0.copy(DisplayState = Action.displayState as RewardVideoDisplayState) + else -> State0 +} diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/TipsStateReducer.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/TipsStateReducer.kt new file mode 100644 index 0000000..fb10790 --- /dev/null +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/TipsStateReducer.kt @@ -0,0 +1,26 @@ +// TipsStateReducer.kt —— 大厅提示条切片(对应 module_10032 的 dst 状态 + bst.vth) +// +// 证据链: +// 状态类 dst:1 个字段,toString 名 "TipsState(tips=…)";copy 是 su8(1 字段)。 +// 默认:tips = md() = emptyList()。 +// 根委派:`Hw.vth(t, n.spv_1)`。 +// reducer:`t instanceof mst ? n.su8(t.yu8_1) : n`,`mst` 是 UpdateTips(tips),整体替换。 +package App.State.Reducers + +import App.State.UpdateTips + +/* ------------------------------------------------------------------ 状态 */ + +data class TipsStateSlice( + val Tips: List = emptyList(), +) + +/* ------------------------------------------------------------------ reducer */ + +fun ReduceTips( + State0: TipsStateSlice, + Action: Any, +): TipsStateSlice = when (Action) { + is UpdateTips -> State0.copy(Tips = Action.tips as List) + 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 9aac6f3..aad9b5d 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 出新状态)。 -// 这里采用同一形状。已迁移的 33 个切片里,32 个真正调用 reducer,launchParams 只做类型收口: +// 这里采用同一形状。已迁移的 43 个切片里,42 个真正调用 reducer,launchParams 只做类型收口: // // inviteState → ReduceInvite(aD / pD.er5) // loginByPassword → ReduceLoginByPassword(gD / MD.cr6) @@ -35,16 +35,31 @@ // mainQuest → ReduceMainQuest(WZ / ih.bsk,根委派 `(null == ih && new nQ(), ih).bsk(t, n.fpx_1)`) // securitySettings → ReduceSecuritySettings(V2 / pc.qsv,根委派 `pc.qsv(t, n.apx_1)`) // referralEvent → ReduceReferralEvent(pQ / lh.osk,根委派 `lh.osk(t, n.hpy_1)`) +// tipsState → ReduceTips(dst / Hw.vth,根委派 `Hw.vth(t, n.spv_1)`) +// rankUpDialogState → ReduceRankUpDialog(LO / Se.br3,根委派 `Se.br3(t, n.npw_1)`) +// battleResult → ReduceBattleResult(sR / Fi.aqp,根委派 `Fi.aqp(t, n.gpw_1)`) +// captchaState → ReduceCaptcha(jP / ar.iqt,根委派 `ar.iqt(t, n.kpv_1)`) +// modalScreen → ReduceModalScreen(nV / Fs.js1,根委派 `Fs.js1(t, n.apv_1)`) +// droppableGold → ReduceDroppableGold(Vet / Wp.kth,根委派 `Wp.kth(t, n.spw_1)`) +// eventGoods → ReduceEventGoods(W7 / Wc.esx,根委派 `Wc.esx(t, n.rpx_1)`) +// 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)`) // launchParams → 无 reducer(原根 reducer 原样透传;默认 `ag()` = emptyMap()) // -// 其余 56 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态, +// 其余 46 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态, // 所以一次 copy 里串起全部已迁移 reducer,与按动作路由再归约的结果一致。 // 迁移进度与每个字段的证据见生成物 RootWiring.kt(不要手改)。 package App.State import App.State.Reducers.ReduceAccountSettings import App.State.Reducers.ReduceAnnouncement +import App.State.Reducers.ReduceBattleResult +import App.State.Reducers.ReduceCaptcha +import App.State.Reducers.ReduceCreatorTag +import App.State.Reducers.ReduceDroppableGold import App.State.Reducers.ReduceEntrance +import App.State.Reducers.ReduceEventGoods import App.State.Reducers.ReduceGarage import App.State.Reducers.ReduceGaragePreview import App.State.Reducers.ReduceHome @@ -57,21 +72,26 @@ import App.State.Reducers.ReduceLootBoxes import App.State.Reducers.ReduceMainQuest import App.State.Reducers.ReduceAvailabilityByRank import App.State.Reducers.ReduceLobbyLoader +import App.State.Reducers.ReduceModalScreen import App.State.Reducers.ReduceNews import App.State.Reducers.ReduceNotificationByCountry import App.State.Reducers.ReduceNotifications +import App.State.Reducers.ReducePasswordChange import App.State.Reducers.ReduceProBattle import App.State.Reducers.ReducePromoCode import App.State.Reducers.ReducePurchaseDialog import App.State.Reducers.ReduceQuestCategories import App.State.Reducers.ReduceQuests +import App.State.Reducers.ReduceRankUpDialog import App.State.Reducers.ReduceReferralEvent +import App.State.Reducers.ReduceRewardedVideos import App.State.Reducers.ReduceRulesUpdated import App.State.Reducers.ReduceScreen import App.State.Reducers.ReduceSecuritySettings import App.State.Reducers.ReduceSupportUrl import App.State.Reducers.ReduceSettings import App.State.Reducers.ReduceShop +import App.State.Reducers.ReduceTips import App.State.Reducers.ReduceTwitch import App.State.Reducers.ReduceUser @@ -120,6 +140,16 @@ object RootReducer { MainQuest = ReduceMainQuest(State0.MainQuest, Action), SecuritySettings = ReduceSecuritySettings(State0.SecuritySettings, Action), ReferralEvent = ReduceReferralEvent(State0.ReferralEvent, Action), + TipsState = ReduceTips(State0.TipsState, Action), + RankUpDialogState = ReduceRankUpDialog(State0.RankUpDialogState, Action), + BattleResult = ReduceBattleResult(State0.BattleResult, Action), + CaptchaState = ReduceCaptcha(State0.CaptchaState, Action), + ModalScreen = ReduceModalScreen(State0.ModalScreen, Action), + DroppableGold = ReduceDroppableGold(State0.DroppableGold, Action), + EventGoods = ReduceEventGoods(State0.EventGoods, Action), + CreatorTagState = ReduceCreatorTag(State0.CreatorTagState, Action), + RewardedVideosState = ReduceRewardedVideos(State0.RewardedVideosState, Action), + PasswordChangeState = ReducePasswordChange(State0.PasswordChangeState, Action), ) } diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/RootState.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/RootState.kt index a7f58a2..0c0335c 100644 --- a/TankiOffline/app/src/jsMain/kotlin/App/State/RootState.kt +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/RootState.kt @@ -7,7 +7,12 @@ package App.State import App.State.Reducers.AccountSettingsStateSlice import App.State.Reducers.AnnouncementStateSlice import App.State.Reducers.AvailabilityByRankStateSlice +import App.State.Reducers.BattleResultStateSlice +import App.State.Reducers.CaptchaStateSlice +import App.State.Reducers.CreatorTagStateSlice +import App.State.Reducers.DroppableGoldStateSlice import App.State.Reducers.EntranceStateSlice +import App.State.Reducers.EventGoodsStateSlice import App.State.Reducers.GarageStateSlice import App.State.Reducers.GaragePreviewStateSlice import App.State.Reducers.HomeStateSlice @@ -18,21 +23,26 @@ import App.State.Reducers.LocalBattleUserStateSlice import App.State.Reducers.LoginByPasswordStateSlice import App.State.Reducers.LootBoxesStateSlice import App.State.Reducers.MainQuestStateSlice +import App.State.Reducers.ModalScreenStateSlice import App.State.Reducers.NewsStateSlice import App.State.Reducers.NotificationByCountryStateSlice import App.State.Reducers.NotificationsStateSlice +import App.State.Reducers.PasswordChangeStateSlice import App.State.Reducers.ProBattleStateSlice import App.State.Reducers.PromoCodeStateSlice import App.State.Reducers.PurchaseDialogStateSlice import App.State.Reducers.QuestCategoriesStateSlice import App.State.Reducers.QuestsStateSlice +import App.State.Reducers.RankUpDialogStateSlice import App.State.Reducers.ReferralEventStateSlice +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.SupportUrlStateSlice +import App.State.Reducers.TipsStateSlice import App.State.Reducers.TwitchStateSlice import App.State.Reducers.UserStateSlice @@ -45,8 +55,8 @@ data class RootState( val User: UserStateSlice = UserStateSlice(), /** 原 screen(WK / dY().ks5)。已迁移。 */ val Screen: ScreenStateSlice = ScreenStateSlice(), - /** 原 modalScreen(nV / Fs.js1)。未迁移:原样透传。 */ - val ModalScreen: Any? = null, + /** 原 modalScreen(nV / Fs.js1)。已迁移。 */ + val ModalScreen: ModalScreenStateSlice = ModalScreenStateSlice(), /** 原 battleStatistics(jR / rr.fqr)。未迁移:原样透传。 */ val BattleStatistics: Any? = null, /** 原 battleTutorial(rE / Q.sq8)。未迁移:原样透传。 */ @@ -65,10 +75,10 @@ data class RootState( val MigrationState: Any? = null, /** 原 inviteState(aD / Ke.er5)。已迁移。 */ val InviteState: InviteStateSlice = InviteStateSlice(), - /** 原 captchaState(jP / ar.iqt)。未迁移:原样透传。 */ - val CaptchaState: Any? = null, - /** 原 passwordChangeState(VD / uu.pr6)。未迁移:原样透传。 */ - val PasswordChangeState: Any? = null, + /** 原 captchaState(jP / ar.iqt)。已迁移。 */ + val CaptchaState: CaptchaStateSlice = CaptchaStateSlice(), + /** 原 passwordChangeState(VD / uu.pr6)。已迁移。 */ + val PasswordChangeState: PasswordChangeStateSlice = PasswordChangeStateSlice(), /** 原 system(_9 / w9().xt9)。未迁移:原样透传。 */ val System: Any? = null, /** 原 quests(Frt / Yrt.otg)。已迁移。 */ @@ -81,8 +91,8 @@ data class RootState( val Clan: Any? = null, /** 原 clansStorage(lO / me.vr1)。未迁移:原样透传。 */ val ClansStorage: Any? = null, - /** 原 tipsState(dst / Hw.vth)。未迁移:原样透传。 */ - val TipsState: Any? = null, + /** 原 tipsState(dst / Hw.vth)。已迁移。 */ + val TipsState: TipsStateSlice = TipsStateSlice(), /** 原 news(ZX / E_.asd)。已迁移。 */ val News: NewsStateSlice = NewsStateSlice(), /** 原 settings(Uut / xw.ltg)。已迁移。 */ @@ -109,8 +119,8 @@ data class RootState( val BattleSearchState: Any? = null, /** 原 lobbyLoader(KW / Bs.es1)。已迁移。 */ val LobbyLoader: LobbyLoaderStateSlice = LobbyLoaderStateSlice(), - /** 原 battleResult(sR / Fi.aqp)。未迁移:原样透传。 */ - val BattleResult: Any? = null, + /** 原 battleResult(sR / Fi.aqp)。已迁移。 */ + val BattleResult: BattleResultStateSlice = BattleResultStateSlice(), /** 原 battleUsers(uP / sr.hqs)。未迁移:原样透传。 */ val BattleUsers: Any? = null, /** 原 suppliesState(wE / $t.fq9)。未迁移:原样透传。 */ @@ -123,8 +133,8 @@ data class RootState( val BattlePauseState: Any? = null, /** 原 battleViewState(DT / sI.jqd)。未迁移:原样透传。 */ val BattleViewState: Any? = null, - /** 原 rankUpDialogState(LO / Se.br3)。未迁移:原样透传。 */ - val RankUpDialogState: Any? = null, + /** 原 rankUpDialogState(LO / Se.br3)。已迁移。 */ + val RankUpDialogState: RankUpDialogStateSlice = RankUpDialogStateSlice(), /** 原 clanUserInfo(hO / de.gr1)。未迁移:原样透传。 */ val ClanUserInfo: Any? = null, /** 原 tankActionsLogState(vI / wi.gqe)。未迁移:原样透传。 */ @@ -133,8 +143,8 @@ data class RootState( val BattleMessage: Any? = null, /** 原 battleChat(Q6 / Hf.wtg)。未迁移:原样透传。 */ val BattleChat: Any? = null, - /** 原 droppableGold(Vet / Wp.kth)。未迁移:原样透传。 */ - val DroppableGold: Any? = null, + /** 原 droppableGold(Vet / Wp.kth)。已迁移。 */ + val DroppableGold: DroppableGoldStateSlice = DroppableGoldStateSlice(), /** 原 home(s1 / (inline@home))。已迁移。 */ val Home: HomeStateSlice = HomeStateSlice(), /** 原 localBattleUserState(lE / (inline@localBattleUserState))。已迁移。 */ @@ -183,14 +193,14 @@ data class RootState( val NotificationByCountry: NotificationByCountryStateSlice = NotificationByCountryStateSlice(), /** 原 userRenameState(O8 / Rf.pte)。未迁移:原样透传。 */ val UserRenameState: Any? = null, - /** 原 eventGoods(W7 / Wc.esx)。未迁移:原样透传。 */ - val EventGoods: Any? = null, - /** 原 creatorTagState(qO / ze.er2)。未迁移:原样透传。 */ - val CreatorTagState: Any? = null, + /** 原 eventGoods(W7 / Wc.esx)。已迁移。 */ + val EventGoods: EventGoodsStateSlice = EventGoodsStateSlice(), + /** 原 creatorTagState(qO / ze.er2)。已迁移。 */ + val CreatorTagState: CreatorTagStateSlice = CreatorTagStateSlice(), /** 原 twoStepVerificationState(x9 / _f.atb)。未迁移:原样透传。 */ val TwoStepVerificationState: Any? = null, - /** 原 rewardedVideosState(JQ / xh.zsl)。未迁移:原样透传。 */ - val RewardedVideosState: Any? = null, + /** 原 rewardedVideosState(JQ / xh.zsl)。已迁移。 */ + val RewardedVideosState: RewardedVideosStateSlice = RewardedVideosStateSlice(), /** 原 twitchState(g9 / uf.gta)。已迁移。 */ val TwitchState: TwitchStateSlice = TwitchStateSlice(), /** 原 icons(oH / vs.kru)。已迁移。 */ diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/RootWiring.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/RootWiring.kt index 2f060c1..ffd8623 100644 --- a/TankiOffline/app/src/jsMain/kotlin/App/State/RootWiring.kt +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/RootWiring.kt @@ -16,7 +16,7 @@ object RootWiring { val All: List = listOf( SliceWiring("user", "ypu_1", "t8", "M8().qtd", "M8().qtd(t, n.ypu_1)", listOf("AddCoins", "AddRubies", "ClearInDebt", "DisableLobbyTutorial", "SetBatteryLevel", "SetClanInfo", "SetCoins", "SetDisabledWithoutPremiumLocations", "SetHasTemporaryCash", "SetInDebt", "SetPremiumExpirationTime", "SetRankNames", "SetRubies", "SetUserId", "SetUserPropertiesAction", "SpendCoins", "SpendCrystals", "SpendRubies", "UpdateClanTag", "UpdateCrystals", "UpdateEventPass", "UpdateGearScore", "UpdatePremium", "UpdateRank", "UpdateScore", "UpdateUid", "UpdateUserRating")), 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("?XW", "AddModal", "RemoveCurrentByName", "RemoveModal")), + 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("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")), @@ -26,7 +26,7 @@ object RootWiring { 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("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("?AP", "?IP", "?PP", "?RP", "?SP", "LocationsUpdated", "SetLocations")), + 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")), SliceWiring("system", "mpv_1", "_9", "w9().xt9", "w9().xt9(t, n.mpv_1)", listOf("DoShowMessage", "SetFatalError")), SliceWiring("quests", "npv_1", "Frt", "Yrt.otg", "(null == zp && new Yrt(), zp).otg(t, n.npv_1)", listOf("GoTo", "SetCurrentCategory", "SetNotCompletedQuests")), @@ -48,19 +48,19 @@ object RootWiring { SliceWiring("battleCreateState", "dpw_1", "\$6", "Df.ttg", "Df.ttg(t, n.dpw_1, n.bpw_1, n.ypu_1)", listOf("?A6", "?B6", "?C6", "?D6", "?E6", "?F6", "?I6", "?L6", "?M6", "?N6", "?P6", "?R6", "?T6", "?U6", "Configure", "CreateBattle", "SetRestrictions")), SliceWiring("battleSearchState", "epw_1", "Itt", "Htt().vtg", "Htt().vtg(t, n.epw_1)", listOf("?Dtt", "EnterToBattle", "Fight", "RemoveBattle", "SelectBattle", "SelectBattleFormat", "SetFilter", "SetSearch", "SetSuspiciousFilter")), SliceWiring("lobbyLoader", "fpw_1", "KW", "Bs.es1", "Bs.es1(t, n.fpw_1)", listOf("HideLoader", "ShowLoader")), - SliceWiring("battleResult", "gpw_1", "sR", "Fi.aqp", "Fi.aqp(t, n.gpw_1)", listOf("?cR", "?fR", "?lR", "DoInit", "SetHasBattleResult", "SetRewards")), + SliceWiring("battleResult", "gpw_1", "sR", "Fi.aqp", "Fi.aqp(t, n.gpw_1)", listOf("DoInit", "ResetBattleRewards", "SetHasBattleResult", "SetIncreasedReward", "SetRewards", "SetSimpleReward")), SliceWiring("battleUsers", "hpw_1", "uP", "sr.hqs", "sr.hqs(t, n.hpw_1)", listOf("?_P", "?hP", "?lP", "?oP", "?qP", "?sP", "InitSuspiciousState", "InitUserLevels", "RemoveTankInfo", "SetGearScore", "SetTankInfo", "SetTankResistance", "UpdateUserLevel", "UpdateUserRank", "UpdateUserScoreKillsAndDeaths", "UpdateUserSuspiciousState", "UpdateUsersClanTag", "UpdateUsersScoreKillsAndDeaths")), SliceWiring("suppliesState", "ipw_1", "wE", "\$t.fq9", "\$t.fq9(t, n.ipw_1)", listOf("?qE")), SliceWiring("matchmakingGroup", "jpw_1", "rW", "NW().is0", "NW().is0(t, n.jpw_1)", listOf("?zW", "AcceptedInviteToGroup", "AddUser", "AddUserToSelected", "InitMatchmakingGroup", "Invite", "RejectedInviteToGroup", "RemoveUserToSelected", "SetAllowedFromFriends", "SetSelectedUsers", "UpdateMountItems", "UpdateUserReadiness", "UserRemoved")), SliceWiring("shop", "kpw_1", "j4", "(inline@shop)", "(function (t, n) { var i; if (t instanceof w4) i = n.qt6(cd, cd, cd, cd, cd, cd, cd, t.lt6_1); else if (t instanceof Yut) i = n.qt6(cd, cd, cd, cd, !0); else if (t instanceof F5) i = new j4(cd, cd, cd, cd, cd, cd, cd, n.qq3_1); else if (t instanceof h4) i = n.qt6(cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, t.st5_1); else if (t instanceof c4) i = n.qt6(cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, t.ut5_1); else if (t instanceof u4) i = n.qt6( cd, cd, cd, cd, cd, cd, …", listOf("?x4", "AddCategoriesWithBonus", "AddItemToOrderProcessing", "BlockBackButton", "BraintreeLoadingInterrupt", "ClearPreviousScreen", "ClearTargetCategory", "ClearTargetProductId", "CurrentPayMode", "DeleteOneTimePurchaseItem", "InitAdditionalDescription", "InitAmountString", "InitCategory", "InitCoinPackage", "InitCompensation", "InitCrystalKitPackage", "InitFeatureItem", "InitGroup", "InitItemViewType", "InitKeysPackage", "InitKitBundle", "InitKitPackage", "InitLootBoxAndPaint", "InitOneTimePurchaseItem", "InitPayModeAction", "InitPaymentModeAsShopItem", "InitPaymentProviderItems", "InitPaymentViewType", "InitRestrictionByPayMode", "InitRewardedVideosBlocks", "InitShopCard", "InitShopCardDiscount", "InitShopCardView", "InitShopItem", "InitShopItemName", "InitSingleItemKit", "InitSpecialKitPackage", "MobilePaymentStateChanged", "PaymentProviderFinishFail", "PaymentProviderFinishSuccess", "PaymentProviderInProgress", "PaymentUrlReceiveFailed", "PaymentUrlReceived", "RemoveDiscountItemForItem", "RemoveItemFromOrderProcessing", "ResetPaymentState", "ResetShop", "SavePreviousScreen", "SaveTargetProductId", "SaveTargetVisitedCategory", "SelectCategory", "SelectCategoryId", "SelectItem", "SetBraintreeToken", "SetCoinPaymentProviderUrl", "SetGoToHomeAfterBuy", "SetInitShopItemId", "SetPurchaseCompleted", "SetStripeIntentParams", "SetupDiscountCategoryByItem", "SetupDiscountItem", "SetupDiscountItemForItem", "ShopCardExpired", "ShopCardRemoved", "ShopCardSold", "ShopLoaded", "ToggleShopActivity", "TryToBuyOneTimePurchase", "UnblockBackButton")), SliceWiring("battlePauseState", "lpw_1", "dT", "NT().mqc", "NT().mqc(t, n.lpw_1)", listOf("?TT", "?xT", "OnPauseMode", "SetIdleKickPeriod")), SliceWiring("battleViewState", "mpw_1", "DT", "sI.jqd", "(null == \$n && new sI(), \$n).jqd(t, n.mpw_1)", listOf("?JT", "?QT", "?WT", "?YT", "?ZT", "?eI", "?iI", "?nE", "?nI", "?rI", "?tE", "?tI", "SetElementsVisibility", "SetVisibility")), - SliceWiring("rankUpDialogState", "npw_1", "LO", "Se.br3", "Se.br3(t, n.npw_1)", listOf("?FO", "ShowAvailableItems")), + SliceWiring("rankUpDialogState", "npw_1", "LO", "Se.br3", "Se.br3(t, n.npw_1)", listOf("ResetRankUpDialog", "ShowAvailableItems")), SliceWiring("clanUserInfo", "opw_1", "hO", "de.gr1", "de.gr1(t, n.opw_1)", listOf("Loaded")), SliceWiring("tankActionsLogState", "ppw_1", "vI", "wi.gqe", "wi.gqe(t, n.ppw_1)", listOf("?dI", "OnUserJoinTheBattle", "OnUserLeaveTheBattle", "UpdateLog")), SliceWiring("battleMessage", "qpw_1", "FE", "vT().rqb", "vT().rqb(t, n.qpw_1)", listOf("?sT", "DoAddLocalizedMessage", "DoAddLocalizedTeamMessage", "DoAddLocalizedUserMessage", "DoAddTextMessage", "HideMessage", "RemoveMessage")), SliceWiring("battleChat", "rpw_1", "Q6", "Hf.wtg", "Hf.wtg(t, n.rpw_1)", listOf("?ett", "?rtt", "?stt", "?utt", "AddMessage", "ChatLoaded")), - SliceWiring("droppableGold", "spw_1", "Vet", "Wp.kth", "Wp.kth(t, n.spw_1)", listOf("?Ket", "?Yet")), + SliceWiring("droppableGold", "spw_1", "Vet", "Wp.kth", "Wp.kth(t, n.spw_1)", listOf("HideDroppableGoldAuthor", "ShowDroppableGoldAuthor")), SliceWiring("home", "tpw_1", "s1", "(inline@home)", "(function (t, n) { return t instanceof o1 ? n.f7j(!0) : n; })(t, n.tpw_1)", listOf("ShowShopDiscountNotification")), SliceWiring("localBattleUserState", "upw_1", "lE", "(inline@localBattleUserState)", "(function (t, n) { return t instanceof fE ? n.xq8(t.yq8_1) : t instanceof vE ? n.xq8(cd, t.zq8_1) : n; })(t, n.upw_1)", listOf("SetLocalWeaponResistanceProperty", "SetReArmorTemporaryDisabled")), SliceWiring("soundSettings", "vpw_1", "Det", "Wet().jth", "Wet().jth(t, n.vpw_1)", listOf("?Uet", "?_C", "SetEntranceSoundEnabled", "UpdateSettings")), @@ -86,7 +86,7 @@ object RootWiring { SliceWiring("notificationByCountry", "ppx_1", "gJ", "R_.ssd", "R_.ssd(t, n.ppx_1)", listOf("Init", "MarkHomeScreenDialogShown")), SliceWiring("userRenameState", "qpx_1", "O8", "Rf.pte", "Rf.pte(t, n.qpx_1)", listOf("?B8", "?F8", "?G8", "?Q8", "?U8", "ChangeUidInputState", "ResponseActual", "ResponseDelivered", "SetChangeUidParams", "SetRubiesForRename", "SetValidationStatus")), SliceWiring("eventGoods", "rpx_1", "W7", "Wc.esx", "Wc.esx(t, n.rpx_1)", listOf("InitEventGoods")), - SliceWiring("creatorTagState", "spx_1", "qO", "ze.er2", "ze.er2(t, n.spx_1)", listOf("?jO", "SetAttachedCreatorTag", "SetCreatorTag", "SetCreatorTags")), + SliceWiring("creatorTagState", "spx_1", "qO", "ze.er2", "ze.er2(t, n.spx_1)", listOf("ClearCreatorTag", "SetAttachedCreatorTag", "SetCreatorTag", "SetCreatorTags")), SliceWiring("twoStepVerificationState", "tpx_1", "x9", "_f.atb", "_f.atb(t, n.tpx_1)", listOf("ActivationStatus2FA", "Enable2FaRequest", "SetSecretAndQRCode", "SetUse2FA", "VerificationStatus")), SliceWiring("rewardedVideosState", "upx_1", "JQ", "xh.zsl", "xh.zsl(t, n.upx_1)", listOf("SetDisplayState")), SliceWiring("twitchState", "vpx_1", "g9", "uf.gta", "uf.gta(t, n.vpx_1)", listOf("SetAccount", "SetDaysForNextLinking", "SetTwitchEnabled")), @@ -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") + 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 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 a8f45a6..8260bd7 100644 --- a/TankiOffline/app/src/jsMain/kotlin/App/StateCheck.kt +++ b/TankiOffline/app/src/jsMain/kotlin/App/StateCheck.kt @@ -9,11 +9,13 @@ // ⑥ 动作目录:816 个类与 138 个真实派发名对齐 // ⑦ 回放:真实 toString 样例的类/字段/类型校验 // ⑧ 全目录动作类都能按 KClass 路由 -// ⑨~㊸ 切片 reducer:InviteState / LoginByPasswordState / TwitchState / HomeScreen / LocalBattleUserState / +// ⑨~㊿/51-53 切片 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; +// Icons / QuestCategories / MainQuest / SecuritySettings / ReferralEvent / +// TipsState / RankUpDialogState / BattleResult / CaptchaState / ModalScreen / DroppableGold / EventGoods / +// CreatorTagState / RewardedVideosState / PasswordChangeState; // ㉑ launchParams 是纯透传切片(无 reducer/动作),只做 Map 类型收口;⑭ 根 reducer 的 89 槽位委派 // // 用法:$env:TANKIOFFLINE_STATE_TEST='1'; node …(见 TankiOffline/tools/RunStateTest.ps1) @@ -388,6 +390,56 @@ import App.State.SetReferrals import App.State.SetReferrer import App.State.Reducers.ReduceReferralEvent import App.State.Reducers.ReferralEventStateSlice +import App.State.UpdateTips +import App.State.Reducers.ReduceTips +import App.State.Reducers.TipsStateSlice +import App.State.ShowAvailableItems +import App.State.Reducers.RankUpDialogStateSlice +import App.State.Reducers.ReduceRankUpDialog +import App.State.Reducers.ResetRankUpDialog +import App.State.DoInit +import App.State.SetHasBattleResult +import App.State.Reducers.BattleResultStateSlice +import App.State.Reducers.ReduceBattleResult +import App.State.Reducers.ResetBattleRewards +import App.State.Reducers.SetIncreasedReward +import App.State.Reducers.SetSimpleReward +import App.State.LocationsUpdated +import App.State.SetLocations +import App.State.Reducers.AddCaptchaLocationToReset +import App.State.Reducers.AddVerifiedCaptchaLocation +import App.State.Reducers.CaptchaStateSlice +import App.State.Reducers.ReduceCaptcha +import App.State.Reducers.RemoveCaptchaLocationToReset +import App.State.Reducers.RemoveVerifiedCaptchaLocation +import App.State.Reducers.ShowFakeCaptcha +import App.State.AddModal +import App.State.RemoveCurrentByName +import App.State.RemoveModal +import App.State.Reducers.CloseCurrentModal +import App.State.Reducers.ModalScreenStateSlice +import App.State.Reducers.ReduceModalScreen +import App.State.Reducers.DroppableGoldStateSlice +import App.State.Reducers.HideDroppableGoldAuthor +import App.State.Reducers.ReduceDroppableGold +import App.State.Reducers.ShowDroppableGoldAuthor +import App.State.InitEventGoods +import App.State.Reducers.EventGoodsStateSlice +import App.State.Reducers.ReduceEventGoods +import App.State.SetAttachedCreatorTag +import App.State.SetCreatorTag +import App.State.SetCreatorTags +import App.State.Reducers.ClearCreatorTag +import App.State.Reducers.CreatorTagStateSlice +import App.State.Reducers.ReduceCreatorTag +import App.State.SetDisplayState +import App.State.Reducers.ReduceRewardedVideos +import App.State.Reducers.RewardVideoDisplayState +import App.State.Reducers.RewardedVideosStateSlice +import App.State.SetPasswordChangeHash +import App.State.ShowEmailAndPasswordChangeDialog +import App.State.Reducers.PasswordChangeStateSlice +import App.State.Reducers.ReducePasswordChange import App.State.Reducers.UserRankBounds import App.State.Reducers.UserStateSlice import App.State.Reducers.ClearHullData @@ -815,7 +867,7 @@ object StateCheck { Check("切片 LocalBattleUser:接 Store 派发后状态已归约", Store.State.IsReArmorTemporaryDisabled) } - // ⑭ 根 reducer:89 个槽位的委派表 + 33 个已迁移切片(32 归约 + launchParams 透传) + // ⑭ 根 reducer:89 个槽位的委派表 + 43 个已迁移切片(42 归约 + launchParams 透传) run { Check("根:根状态 89 个切片槽位", RootWiring.All.size == 89) Check( @@ -879,7 +931,7 @@ object StateCheck { LocalBattleW.Reducer == "(inline@localBattleUserState)" ) Check( - "根:迁移表 = 33 个已迁移字段,其余 56 个待迁", + "根:迁移表 = 43 个已迁移字段,其余 46 个待迁", RootReducer.Migrated == setOf( "inviteState", "loginByPassword", "twitchState", "home", @@ -890,8 +942,11 @@ object StateCheck { "lobbyLoader", "purchaseDialogState", "promoCode", "supportUrl", "rulesUpdated", "notificationByCountry", "availabilityByRank", "icons", "questCategories", "mainQuest", "securitySettings", "referralEvent", + "tipsState", "rankUpDialogState", "battleResult", "captchaState", + "modalScreen", "droppableGold", "eventGoods", "creatorTagState", + "rewardedVideosState", "passwordChangeState", ) && - RootWiring.Unmigrated.size == 56 + RootWiring.Unmigrated.size == 46 ) // 一次 ReduceRoot 里多个切片各归约自己的动作,互不串片 @@ -947,7 +1002,7 @@ object StateCheck { S5.User.Uid == "user-1" && S5.Screen.Current == NamedScreen("Preloader") ) Check("根:无关动作返回等值状态", ReduceRoot(S5, AddFrags(1)) == S5) - Check("根:Coverage 文本", RootReducer.Coverage == "已迁移 33/89") + Check("根:Coverage 文本", RootReducer.Coverage == "已迁移 43/89") val Store = ReduxStore(RootState(), { S, A -> ReduceRoot(S, A) }) Store.Dispatch(SetStatus("LOGGING_IN")) @@ -3664,6 +3719,407 @@ object StateCheck { ) } + // ㊹ 切片 reducer:TipsState(dst / Hw.vth,1 字段;mst → UpdateTips) + run { + val S0 = TipsStateSlice() + Check("切片 TipsState:默认空列表", S0.Tips.isEmpty()) + val S1 = ReduceTips(S0, UpdateTips(listOf("a", "b"))) + Check("切片 TipsState:UpdateTips 整体替换", S1.Tips == listOf("a", "b")) + Check("切片 TipsState:无关动作不改状态", ReduceTips(S1, AddFrags(1)) == S1) + + val Root0 = RootState() + val Root1 = ReduceRoot(Root0, UpdateTips(listOf("x"))) + Check( + "根:UpdateTips 只动 tipsState", + Root1.TipsState.Tips == listOf("x") && Root1.BattleResult == Root0.BattleResult + ) + val W = RootWiring.ByField("tipsState")!! + Check( + "根:tipsState 委派到 dst / Hw.vth", + W.SliceClass == "dst" && W.Reducer == "Hw.vth" && W.Actions == listOf("UpdateTips") + ) + } + + // ㊺ 切片 reducer:RankUpDialog(LO / Se.br3,1 字段;UO + FO) + run { + val S0 = RankUpDialogStateSlice() + Check("切片 RankUpDialog:默认空表", S0.AvailableItems.isEmpty()) + val S1 = ReduceRankUpDialog(S0, ShowAvailableItems(listOf("a"))) + Check( + "切片 RankUpDialog:ShowAvailableItems 追加整表", + S1.AvailableItems == listOf("a") + ) + val S2 = ReduceRankUpDialog(S1, ShowAvailableItems(listOf("b", "c"))) + Check( + "切片 RankUpDialog:再次追加是拼接", + S2.AvailableItems == listOf("a", "b", "c") + ) + Check( + "切片 RankUpDialog:ResetRankUpDialog 清空列表", + ReduceRankUpDialog(S2, ResetRankUpDialog).AvailableItems.isEmpty() + ) + Check( + "切片 RankUpDialog:无关动作不改状态", + ReduceRankUpDialog(S2, AddFrags(1)) == S2 + ) + + val Root0 = RootState() + val Root1 = ReduceRoot(Root0, ShowAvailableItems(listOf("x"))) + Check( + "根:ShowAvailableItems 只动 rankUpDialogState", + Root1.RankUpDialogState.AvailableItems == listOf("x") && + Root1.TipsState == Root0.TipsState + ) + val W = RootWiring.ByField("rankUpDialogState")!! + Check( + "根:rankUpDialogState 委派到 LO / Se.br3 且 2 个动作", + W.SliceClass == "LO" && W.Reducer == "Se.br3" && + W.Actions.toSet() == setOf("ShowAvailableItems", "ResetRankUpDialog") + ) + } + + // ㊻ 切片 reducer:BattleResult(sR / Fi.aqp,5 字段;6 个动作) + run { + val Item = ContainerGivenItem( + category = "cat", count = 1, garageItemCategory = "hull", garageItemId = 2, + image = null, name = "n", rewardType = "t", + ) + val S0 = BattleResultStateSlice() + Check( + "切片 BattleResult:默认值完整", + !S0.HasBattleResult && S0.Rewards.isEmpty() && + S0.InitialCurrentUserXP == null && S0.IncreasedReward == 0 && + S0.SimpleReward == 0 + ) + val S1 = ReduceBattleResult(S0, SetRewards(listOf(Item))) + Check("切片 BattleResult:SetRewards 写奖励表", S1.Rewards == listOf(Item)) + val S2 = ReduceBattleResult(S1, SetHasBattleResult(true)) + Check("切片 BattleResult:SetHasBattleResult 写布尔", S2.HasBattleResult) + val S3 = ReduceBattleResult( + ReduceBattleResult(S2, SetIncreasedReward(15)), + SetSimpleReward(10), + ) + Check( + "切片 BattleResult:cR / lR 分别写两个奖励数值", + S3.IncreasedReward == 15 && S3.SimpleReward == 10 + ) + Check( + "切片 BattleResult:ResetBattleRewards 只把两个数值归零", + ReduceBattleResult(S3, ResetBattleRewards) == + S3.copy(IncreasedReward = 0, SimpleReward = 0) + ) + val S4 = ReduceBattleResult(S3, DoInit(500)) + Check( + "切片 BattleResult:DoInit 重建状态只带初始 XP", + !S4.HasBattleResult && S4.Rewards.isEmpty() && + S4.InitialCurrentUserXP == 500 && S4.IncreasedReward == 0 && + S4.SimpleReward == 0 + ) + Check( + "切片 BattleResult:无关动作不改状态", + ReduceBattleResult(S4, AddFrags(1)) == S4 + ) + + val Root0 = RootState() + val Root1 = ReduceRoot(Root0, SetHasBattleResult(true)) + Check( + "根:SetHasBattleResult 只动 battleResult", + Root1.BattleResult.HasBattleResult && Root1.CaptchaState == Root0.CaptchaState + ) + val W = RootWiring.ByField("battleResult")!! + Check( + "根:battleResult 委派到 sR / Fi.aqp 且 6 个动作", + W.SliceClass == "sR" && W.Reducer == "Fi.aqp" && + W.Actions.toSet() == setOf( + "SetRewards", "SetHasBattleResult", "SetIncreasedReward", + "SetSimpleReward", "ResetBattleRewards", "DoInit", + ) + ) + } + + // ㊼ 切片 reducer:Captcha(jP / ar.iqt,4 字段;7 个动作) + run { + val S0 = CaptchaStateSlice() + Check( + "切片 Captcha:默认不造假、三个集合为空", + !S0.UseFake && S0.EnabledLocations.isEmpty() && + S0.VerifiedLocations.isEmpty() && S0.LocationsToReset.isEmpty() + ) + val S1 = ReduceCaptcha(S0, ShowFakeCaptcha) + Check("切片 Captcha:ShowFakeCaptcha 置 useFake", S1.UseFake) + val S2 = ReduceCaptcha(S1, SetLocations(listOf("a", "b"))) + Check( + "切片 Captcha:SetLocations 收成集合", + S2.EnabledLocations == setOf("a", "b") + ) + val S3 = ReduceCaptcha( + ReduceCaptcha(S2, AddVerifiedCaptchaLocation("a")), + AddVerifiedCaptchaLocation("b"), + ) + Check( + "切片 Captcha:已验证集合加元素", + S3.VerifiedLocations == setOf("a", "b") + ) + Check( + "切片 Captcha:已验证集合删元素", + ReduceCaptcha(S3, RemoveVerifiedCaptchaLocation("a")) + .VerifiedLocations == setOf("b") + ) + val S4 = ReduceCaptcha( + ReduceCaptcha(S3, AddCaptchaLocationToReset("c")), + RemoveCaptchaLocationToReset("c"), + ) + Check("切片 Captcha:待重置集合加减后为空", S4.LocationsToReset.isEmpty()) + Check( + "切片 Captcha:LocationsUpdated 追加到启用集合", + ReduceCaptcha(S4, LocationsUpdated("d")) + .EnabledLocations == setOf("a", "b", "d") + ) + Check("切片 Captcha:无关动作不改状态", ReduceCaptcha(S4, AddFrags(1)) == S4) + + val Root0 = RootState() + val Root1 = ReduceRoot(Root0, ShowFakeCaptcha) + Check( + "根:ShowFakeCaptcha 只动 captchaState", + Root1.CaptchaState.UseFake && Root1.ModalScreen == Root0.ModalScreen + ) + val W = RootWiring.ByField("captchaState")!! + Check( + "根:captchaState 委派到 jP / ar.iqt 且 7 个动作", + W.SliceClass == "jP" && W.Reducer == "ar.iqt" && + W.Actions.toSet() == setOf( + "ShowFakeCaptcha", "SetLocations", "AddVerifiedCaptchaLocation", + "RemoveVerifiedCaptchaLocation", "AddCaptchaLocationToReset", + "RemoveCaptchaLocationToReset", "LocationsUpdated", + ) + ) + } + + // ㊽ 切片 reducer:ModalScreen(nV / Fs.js1,2 字段;4 个动作) + run { + val S0 = ModalScreenStateSlice() + Check( + "切片 ModalScreen:默认无当前窗口、队列为空", + S0.Current == null && S0.ModalQueue.isEmpty() + ) + val S1 = ReduceModalScreen(S0, AddModal(true)) + Check( + "切片 ModalScreen:AddModal 写 current 并入队", + S1.Current == true && S1.ModalQueue == listOf(true) + ) + Check( + "切片 ModalScreen:同名窗口不重复入队", + ReduceModalScreen(S1, AddModal(true)) == S1 + ) + val S2 = ReduceModalScreen(S1, AddModal(false)) + Check( + "切片 ModalScreen:第二个窗口排到队尾", + S2.Current == false && S2.ModalQueue == listOf(true, false) + ) + val S3 = ReduceModalScreen(S2, RemoveModal(true)) + Check( + "切片 ModalScreen:RemoveModal 出队并把 current 交给队首", + S3.Current == false && S3.ModalQueue == listOf(false) + ) + Check( + "切片 ModalScreen:CloseCurrentModal 清掉当前窗口", + ReduceModalScreen(S3, CloseCurrentModal) == ModalScreenStateSlice() + ) + Check( + "切片 ModalScreen:RemoveCurrentByName 名称不符时不动", + ReduceModalScreen(S3, RemoveCurrentByName("other")) == S3 + ) + Check( + "切片 ModalScreen:RemoveCurrentByName 名称匹配时关闭", + ReduceModalScreen(S3, RemoveCurrentByName("false")) == ModalScreenStateSlice() + ) + Check( + "切片 ModalScreen:无关动作不改状态", + ReduceModalScreen(S3, AddFrags(1)) == S3 + ) + + val Root0 = RootState() + val Root1 = ReduceRoot(Root0, AddModal(true)) + Check( + "根:AddModal 只动 modalScreen", + Root1.ModalScreen.Current == true && + Root1.EventGoods == Root0.EventGoods + ) + val W = RootWiring.ByField("modalScreen")!! + Check( + "根:modalScreen 委派到 nV / Fs.js1 且 4 个动作", + W.SliceClass == "nV" && W.Reducer == "Fs.js1" && + W.Actions.toSet() == setOf( + "AddModal", "RemoveModal", "CloseCurrentModal", "RemoveCurrentByName", + ) + ) + } + + // ㊾ 切片 reducer:DroppableGold(Vet / Wp.kth,1 字段;Ket/Yet 两个单例) + run { + val S0 = DroppableGoldStateSlice() + Check("切片 DroppableGold:默认显示作者署名", S0.ShowDroppableGoldAuthor) + Check( + "切片 DroppableGold:Hide 置 false、Show 置 true", + !ReduceDroppableGold(S0, HideDroppableGoldAuthor).ShowDroppableGoldAuthor && + ReduceDroppableGold(S0, ShowDroppableGoldAuthor).ShowDroppableGoldAuthor + ) + Check( + "切片 DroppableGold:无关动作不改状态", + ReduceDroppableGold(S0, AddFrags(1)) == S0 + ) + + val Root0 = RootState() + val Root1 = ReduceRoot(Root0, HideDroppableGoldAuthor) + Check( + "根:HideDroppableGoldAuthor 只动 droppableGold", + !Root1.DroppableGold.ShowDroppableGoldAuthor && + Root1.CreatorTagState == Root0.CreatorTagState + ) + val W = RootWiring.ByField("droppableGold")!! + Check( + "根:droppableGold 委派到 Vet / Wp.kth 且 2 个动作", + W.SliceClass == "Vet" && W.Reducer == "Wp.kth" && + W.Actions.toSet() == setOf("ShowDroppableGoldAuthor", "HideDroppableGoldAuthor") + ) + } + + // ㊿ 切片 reducer:EventGoods(W7 / Wc.esx,1 字段;V7 → InitEventGoods) + run { + val S0 = EventGoodsStateSlice() + Check("切片 EventGoods:默认空表", S0.EventGoods.isEmpty()) + val S1 = ReduceEventGoods(S0, InitEventGoods("spectatorMode", true)) + Check( + "切片 EventGoods:InitEventGoods 写键值", + S1.EventGoods == mapOf("spectatorMode" to true) + ) + val S2 = ReduceEventGoods(S1, InitEventGoods("spectatorMode", null)) + Check("切片 EventGoods:goods 为空时删键", S2.EventGoods.isEmpty()) + Check( + "切片 EventGoods:无关动作不改状态", + ReduceEventGoods(S1, AddFrags(1)) == S1 + ) + + val Root0 = RootState() + val Root1 = ReduceRoot(Root0, InitEventGoods("k", 1)) + Check( + "根:InitEventGoods 只动 eventGoods", + Root1.EventGoods.EventGoods == mapOf("k" to 1) && + Root1.RewardedVideosState == Root0.RewardedVideosState + ) + val W = RootWiring.ByField("eventGoods")!! + Check( + "根:eventGoods 委派到 W7 / Wc.esx", + W.SliceClass == "W7" && W.Reducer == "Wc.esx" && + W.Actions == listOf("InitEventGoods") + ) + } + + // 51 切片 reducer:CreatorTag(qO / ze.er2,2 字段;4 个动作) + run { + val S0 = CreatorTagStateSlice() + Check("切片 CreatorTag:默认两个字段都为 null", S0.CreatorTag == null && S0.CreatorTags == null) + val S1 = ReduceCreatorTag(S0, SetCreatorTags(listOf("a"))) + Check("切片 CreatorTag:SetCreatorTags 写列表", S1.CreatorTags == listOf("a")) + Check( + "切片 CreatorTag:SetCreatorTag 写当前标签", + ReduceCreatorTag(S1, SetCreatorTag("t1")).CreatorTag == "t1" + ) + val S2 = ReduceCreatorTag(S1, SetAttachedCreatorTag("t2")) + Check( + "切片 CreatorTag:SetAttachedCreatorTag 覆盖当前标签", + S2.CreatorTag == "t2" && S2.CreatorTags == listOf("a") + ) + val S3 = ReduceCreatorTag(S2, ClearCreatorTag) + Check( + "切片 CreatorTag:ClearCreatorTag 只清当前标签", + S3.CreatorTag == null && S3.CreatorTags == listOf("a") + ) + Check("切片 CreatorTag:无关动作不改状态", ReduceCreatorTag(S3, AddFrags(1)) == S3) + + val Root0 = RootState() + val Root1 = ReduceRoot(Root0, SetCreatorTag("x")) + Check( + "根:SetCreatorTag 只动 creatorTagState", + Root1.CreatorTagState.CreatorTag == "x" && Root1.EventGoods == Root0.EventGoods + ) + val W = RootWiring.ByField("creatorTagState")!! + Check( + "根:creatorTagState 委派到 qO / ze.er2 且 4 个动作", + W.SliceClass == "qO" && W.Reducer == "ze.er2" && + W.Actions.toSet() == setOf( + "SetCreatorTags", "SetCreatorTag", "SetAttachedCreatorTag", "ClearCreatorTag", + ) + ) + } + + // 52 切片 reducer:RewardedVideos(JQ / xh.zsl,1 字段;t1 → SetDisplayState) + run { + val S0 = RewardedVideosStateSlice() + Check("切片 RewardedVideos:默认 NONE", S0.DisplayState == RewardVideoDisplayState.NONE) + Check( + "切片 RewardedVideos:SetDisplayState 写枚举", + ReduceRewardedVideos(S0, SetDisplayState(RewardVideoDisplayState.DISPLAY)) + .DisplayState == RewardVideoDisplayState.DISPLAY + ) + Check( + "切片 RewardedVideos:无关动作不改状态", + ReduceRewardedVideos(S0, AddFrags(1)) == S0 + ) + + val Root0 = RootState() + val Root1 = ReduceRoot(Root0, SetDisplayState(RewardVideoDisplayState.LOADING)) + Check( + "根:SetDisplayState 只动 rewardedVideosState", + Root1.RewardedVideosState.DisplayState == RewardVideoDisplayState.LOADING && + Root1.PasswordChangeState == Root0.PasswordChangeState + ) + val W = RootWiring.ByField("rewardedVideosState")!! + Check( + "根:rewardedVideosState 委派到 JQ / xh.zsl", + W.SliceClass == "JQ" && W.Reducer == "xh.zsl" && + W.Actions == listOf("SetDisplayState") + ) + } + + // 53 切片 reducer:PasswordChange(VD / uu.pr6,3 字段;UD + HD) + run { + val S0 = PasswordChangeStateSlice() + Check( + "切片 PasswordChange:默认三个空串", + S0.Hash == "" && S0.Uid == "" && S0.Email == "" + ) + val S1 = ReducePasswordChange(S0, SetPasswordChangeHash("h")) + Check( + "切片 PasswordChange:SetPasswordChangeHash 只写 hash", + S1.Hash == "h" && S1.Uid == "" && S1.Email == "" + ) + val S2 = ReducePasswordChange(S1, ShowEmailAndPasswordChangeDialog("e", "u", "r")) + Check( + "切片 PasswordChange:ShowEmailAndPasswordChangeDialog 写 uid/email 但保留 hash", + S2.Hash == "h" && S2.Uid == "u" && S2.Email == "e" + ) + Check( + "切片 PasswordChange:无关动作不改状态", + ReducePasswordChange(S2, AddFrags(1)) == S2 + ) + + val Root0 = RootState() + val Root1 = ReduceRoot(Root0, SetPasswordChangeHash("x")) + Check( + "根:SetPasswordChangeHash 只动 passwordChangeState", + Root1.PasswordChangeState.Hash == "x" && Root1.TipsState == Root0.TipsState + ) + val W = RootWiring.ByField("passwordChangeState")!! + Check( + "根:passwordChangeState 委派到 VD / uu.pr6 且 2 个动作", + W.SliceClass == "VD" && W.Reducer == "uu.pr6" && + W.Actions.toSet() == setOf( + "SetPasswordChangeHash", "ShowEmailAndPasswordChangeDialog", + ) + ) + } + val Failed = Results.filter { !it.second } println("TankiOffline 状态层自检:${Results.size} 项") for ((Name, Ok) in Results) println((if (Ok) " ✓ " else " ✗ ") + Name)