feat(state): 批量迁移 accountSettings/proBattle/language/announcement 切片
This commit is contained in:
1 parent
6947fcb567
commit
006e6a7ebd
17 files changed
+749
-53
No files matched your search
@@ -143,7 +143,7 @@
|
||||
`names/root_wiring.json` + `TankiOffline/.../App/State/RootState.kt`、`RootWiring.kt`。
|
||||
- 根状态 `z6`(`TOState`)的 **89 个槽位**全部入表;字段顺序取 `toString`,槽位取构造函数 `<名>_1`。
|
||||
- 根 reducer `k6` 的逐切片委派记全:reducer 引用、委派表达式、顶层 `instanceof` 动作名。
|
||||
- 已迁移切片 **14/89**:
|
||||
- 已迁移切片 **18/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)
|
||||
@@ -197,12 +197,25 @@
|
||||
- `settings` → `Uut` / `xw.ltg`(`SettingsReducer.kt`):2 个字段,2 个 `instanceof` 臂。
|
||||
`currentCategory` 是设置页分类枚举(`ACCOUNT`…`SUPPORT`,默认 `GAME`),
|
||||
`disableAccountSettings` 默认 false;`Fut`→`SetSettingsCategory`、`Gut`→`DisableAccountSettings`。
|
||||
- `accountSettings` → `oet` / `Lp.mtg`(`AccountSettingsReducer.kt`):13 字段,13 个 `instanceof` 臂。
|
||||
`newPasswordStatus` 用 `PasswordStatus`(`NONE/OK/SIMPLE/NOT_EQUALS`);
|
||||
`ResetPasswordFields` 只清三个密码输入、状态和解锁按钮;`removalTimer` 存时间戳 `Long?`;
|
||||
`locationCheckEnabled` 没有写入臂;11 个匿名动作按效果定名并登记。
|
||||
- `proBattle` → `Wtt` / `uv.wth`(`ProBattleReducer.kt`):3 字段,2 个 `instanceof` 臂。
|
||||
默认 `lastFightBattleId=-1`、`lastFightBattleTeam=NONE`、`battlePasswords=emptyMap()`;
|
||||
`Fight` 写 id/team,`SetPassword` 空串删键、非空覆盖。登录切片的 `SetPassword` 是不同类,
|
||||
这里用 `App.State.Reducers.SetPassword(battleId, password)`,避免丢 `battleId`。
|
||||
- `language` → 枚举值 / `m6().jtg`(`LanguageReducer.kt`):`language` 在根状态里是枚举对象本身,
|
||||
默认 `EN`;`d6`→`SetLanguage` 只写回语言,不包 StateSlice。
|
||||
- `announcement` → `lC` / `PC().rq5`(`AnnouncementReducer.kt`):5 字段,4 个 `instanceof` 臂。
|
||||
`isEnabled` 缺省 true,其余 false/空列表;`bC`→`DisableAnnouncement`、`qC`→`SetAnnouncementModelLoaded`、
|
||||
`CC`→`SetAnnouncementItems`(生成物 `Init` 属于另一个切片,已改名)、`$C`→`SetSkipNextPromotedAnnounce`。
|
||||
- `launchParams` → `(pass-through)`:原根 reducer 对 `hpx_1` 没有调用,缺省是 `ag()` 空 Map;
|
||||
`RootState.LaunchParams` 收口为 `LaunchParamsStateSlice = Map<String, String>`,不新增 reducer/动作。
|
||||
- `App/State/RootReducer.kt`:`ReduceRoot(State0, Action)` 归约上述 13 个有 reducer 的切片,
|
||||
`launchParams` 与其余 **75 个字段原样透传**;
|
||||
迁移进度用 `RootReducer.Coverage`(`已迁移 14/89`)与 `RootWiring.Unmigrated` 暴露。
|
||||
- 自检:`TankiOffline/tools/RunStateTest.ps1` **325 项全绿**,含 89 槽位完整性、13 个归约切片一次归约互不串片、未迁移字段透传,
|
||||
- `App/State/RootReducer.kt`:`ReduceRoot(State0, Action)` 归约上述 17 个有 reducer 的切片,
|
||||
`launchParams` 与其余 **71 个字段原样透传**;
|
||||
迁移进度用 `RootReducer.Coverage`(`已迁移 18/89`)与 `RootWiring.Unmigrated` 暴露。
|
||||
- 自检:`TankiOffline/tools/RunStateTest.ps1` **350 项全绿**,含 89 槽位完整性、17 个归约切片一次归约互不串片、未迁移字段透传,
|
||||
以及用 `action_trace_merged.json` 真实派发的两条商店动作(`SetCoinPaymentProviderUrl` / `ToggleShopActivity`)回放。
|
||||
- 委派表里的已知形态:`shop` / `home` / `localBattleUserState` / `partner` 是内联 IIFE,
|
||||
已按字段具名为 `(inline@<field>)` 并从函数体抓到顶层动作名(69 / 1 / 2 / 11 个);
|
||||
@@ -212,7 +225,8 @@
|
||||
效果是把 `HomeScreen.shopDiscountNotification` 置 true,对应主菜单商店项的折扣提示);
|
||||
shop 的 17 个匿名类按 reducer 效果定名并登记(`Yut`→`ShopLoaded`、`F5`→`ResetShop` 等);
|
||||
screen 的 9 个、lootBoxes 的 6 个、garage 的 25 个、garagePreview 的 15 个、
|
||||
entrance 的 6 个、user 的 3 个、settings 的 2 个匿名类同样按效果定名并登记。
|
||||
entrance 的 6 个、user 的 3 个、settings 的 2 个、accountSettings 的 11 个、
|
||||
language 的 1 个、announcement 的 3 个匿名类同样按效果定名并登记。
|
||||
|
||||
### S4 引擎核心
|
||||
- 模块:32088(3D 数学)、92980(渲染)、61378(ECS)、55088(物理)、36659 + 71105(资源)、36734(音频)、3569(输入)
|
||||
|
||||
+115
-21
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"generatedAt": "2026-10-23T09:11:41.133Z",
|
||||
"generatedAt": "2026-10-23T09:52:15.365Z",
|
||||
"module": "module_10032.js",
|
||||
"RootState": {
|
||||
"Class": "z6",
|
||||
@@ -20,6 +20,10 @@
|
||||
"entrance",
|
||||
"user",
|
||||
"settings",
|
||||
"accountSettings",
|
||||
"proBattle",
|
||||
"language",
|
||||
"announcement",
|
||||
"launchParams"
|
||||
],
|
||||
"AnonymousActions": [
|
||||
@@ -527,6 +531,96 @@
|
||||
"Name": "DisableAccountSettings",
|
||||
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
|
||||
"Effect": "disableAccountSettings = true(`n.bu8(cd, !0)`)"
|
||||
},
|
||||
{
|
||||
"Local": "aet",
|
||||
"Name": "ResetPasswordFields",
|
||||
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
|
||||
"Effect": "currentPassword/newPassword/repeatNewPassword = \"\",newPasswordStatus = NONE,isUnlockButtonForRenameUid = false"
|
||||
},
|
||||
{
|
||||
"Local": "fet",
|
||||
"Name": "SetCurrentPassword",
|
||||
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
|
||||
"Effect": "currentPassword = action.currentPassword"
|
||||
},
|
||||
{
|
||||
"Local": "vet",
|
||||
"Name": "SetNewPassword",
|
||||
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
|
||||
"Effect": "newPassword = action.newPassword"
|
||||
},
|
||||
{
|
||||
"Local": "pet",
|
||||
"Name": "SetRepeatNewPassword",
|
||||
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
|
||||
"Effect": "repeatNewPassword = action.repeatNewPassword"
|
||||
},
|
||||
{
|
||||
"Local": "wet",
|
||||
"Name": "SetPasswordStatus",
|
||||
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
|
||||
"Effect": "newPasswordStatus = action.status"
|
||||
},
|
||||
{
|
||||
"Local": "det",
|
||||
"Name": "SetCurrentPasswordMandatory",
|
||||
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
|
||||
"Effect": "isCurrentPasswordMandatory = action.isMandatory"
|
||||
},
|
||||
{
|
||||
"Local": "get",
|
||||
"Name": "SetEmail",
|
||||
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
|
||||
"Effect": "email = action.email"
|
||||
},
|
||||
{
|
||||
"Local": "met",
|
||||
"Name": "SetEmailConfirmed",
|
||||
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
|
||||
"Effect": "emailConfirmed = action.emailConfirmed"
|
||||
},
|
||||
{
|
||||
"Local": "cet",
|
||||
"Name": "SetCountry",
|
||||
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
|
||||
"Effect": "countryCode / countryName = action 的两个字段"
|
||||
},
|
||||
{
|
||||
"Local": "ket",
|
||||
"Name": "SetUnlockButtonForRenameUid",
|
||||
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
|
||||
"Effect": "isUnlockButtonForRenameUid = action.isUnlockButtonForRenameUid"
|
||||
},
|
||||
{
|
||||
"Local": "xet",
|
||||
"Name": "ClearAccountRemovalTimer",
|
||||
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
|
||||
"Effect": "removalTimer = null"
|
||||
},
|
||||
{
|
||||
"Local": "d6",
|
||||
"Name": "SetLanguage",
|
||||
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
|
||||
"Effect": "language = action.language(`t instanceof d6 ? t.itg_1 : n`)"
|
||||
},
|
||||
{
|
||||
"Local": "bC",
|
||||
"Name": "DisableAnnouncement",
|
||||
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
|
||||
"Effect": "isEnabled = false"
|
||||
},
|
||||
{
|
||||
"Local": "qC",
|
||||
"Name": "SetAnnouncementModelLoaded",
|
||||
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
|
||||
"Effect": "modelLoaded = true"
|
||||
},
|
||||
{
|
||||
"Local": "CC",
|
||||
"Name": "SetAnnouncementItems",
|
||||
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
|
||||
"Effect": "items = action.items"
|
||||
}
|
||||
],
|
||||
"Markers": [
|
||||
@@ -1131,22 +1225,22 @@
|
||||
"Reducer": "Lp.mtg",
|
||||
"Delegate": "Lp.mtg(t, n.vpv_1)",
|
||||
"Actions": [
|
||||
"?aet",
|
||||
"?cet",
|
||||
"?det",
|
||||
"?fet",
|
||||
"?get",
|
||||
"?ket",
|
||||
"?met",
|
||||
"?pet",
|
||||
"?vet",
|
||||
"?wet",
|
||||
"?xet",
|
||||
"ClearAccountRemovalTimer",
|
||||
"ClearInputUid",
|
||||
"SetAccountRemovalDays"
|
||||
"ResetPasswordFields",
|
||||
"SetAccountRemovalDays",
|
||||
"SetCountry",
|
||||
"SetCurrentPassword",
|
||||
"SetCurrentPasswordMandatory",
|
||||
"SetEmail",
|
||||
"SetEmailConfirmed",
|
||||
"SetNewPassword",
|
||||
"SetPasswordStatus",
|
||||
"SetRepeatNewPassword",
|
||||
"SetUnlockButtonForRenameUid"
|
||||
],
|
||||
"Default": "new oet()",
|
||||
"Migrated": false
|
||||
"Migrated": true
|
||||
},
|
||||
{
|
||||
"Field": "lootBoxes",
|
||||
@@ -1838,10 +1932,10 @@
|
||||
"Reducer": "m6().jtg",
|
||||
"Delegate": "m6().jtg(t, n.gpx_1)",
|
||||
"Actions": [
|
||||
"?d6"
|
||||
"SetLanguage"
|
||||
],
|
||||
"Default": "tj()",
|
||||
"Migrated": false
|
||||
"Migrated": true
|
||||
},
|
||||
{
|
||||
"Field": "launchParams",
|
||||
@@ -1969,13 +2063,13 @@
|
||||
"Reducer": "PC().rq5",
|
||||
"Delegate": "PC().rq5(t, n.opx_1)",
|
||||
"Actions": [
|
||||
"?bC",
|
||||
"?qC",
|
||||
"Init",
|
||||
"DisableAnnouncement",
|
||||
"SetAnnouncementItems",
|
||||
"SetAnnouncementModelLoaded",
|
||||
"SetSkipNextPromotedAnnounce"
|
||||
],
|
||||
"Default": "new lC()",
|
||||
"Migrated": false
|
||||
"Migrated": true
|
||||
},
|
||||
{
|
||||
"Field": "notificationByCountry",
|
||||
@@ -2165,7 +2259,7 @@
|
||||
"SetPassword"
|
||||
],
|
||||
"Default": "new Wtt()",
|
||||
"Migrated": false
|
||||
"Migrated": true
|
||||
},
|
||||
{
|
||||
"Field": "availabilityByRank",
|
||||
|
||||
@@ -569,6 +569,10 @@ function InferDeep(Expr, Mod, Off, Depth) {
|
||||
// 类型定义见 TankiOffline/.../App/State/ScreenId.kt。
|
||||
const FieldTypeOverrides = new Map([
|
||||
['GoTo.screen', 'ScreenId'],
|
||||
// proBattle 切片:Fight 的字段在别处调用点只能推成 Any?,reducer 需要真实类型。
|
||||
// battleId → Long,battleTeam → BattleTeam(module_31908 的 TEAM_A/TEAM_B/NONE)。
|
||||
['Fight.battleId', 'Long'],
|
||||
['Fight.battleTeam', 'BattleTeam'],
|
||||
// lootBoxes 切片:lH/qH/yH 的字段在别处的调用点只能给出 Boolean / Any?,但 reducer 里分别是
|
||||
// ContainerType(默认 Ly()=STANDARD)、可空 ContainerType、及补给箱奖励列表。
|
||||
['SetCurrentContainer.type', 'ContainerType'],
|
||||
|
||||
@@ -136,6 +136,28 @@ const AnonymousActionNames = new Map([
|
||||
// 括号里是原 reducer 的效果;实现见 SettingsReducer.kt。
|
||||
['Fut', 'SetSettingsCategory'],
|
||||
['Gut', 'DisableAccountSettings'],
|
||||
// accountSettings 切片(oet / Lp.mtg):11 个没有 toString / 不在 action_inventory 的类。
|
||||
// 括号里是原 reducer 的效果;实现见 AccountSettingsReducer.kt。
|
||||
['aet', 'ResetPasswordFields'],
|
||||
['fet', 'SetCurrentPassword'],
|
||||
['vet', 'SetNewPassword'],
|
||||
['pet', 'SetRepeatNewPassword'],
|
||||
['wet', 'SetPasswordStatus'],
|
||||
['det', 'SetCurrentPasswordMandatory'],
|
||||
['get', 'SetEmail'],
|
||||
['met', 'SetEmailConfirmed'],
|
||||
['cet', 'SetCountry'],
|
||||
['ket', 'SetUnlockButtonForRenameUid'],
|
||||
['xet', 'ClearAccountRemovalTimer'],
|
||||
// language 切片(m6().jtg):1 个没有 toString / 不在 action_inventory 的类。
|
||||
// 实现见 LanguageReducer.kt。
|
||||
['d6', 'SetLanguage'],
|
||||
// announcement 切片(lC / PC().rq5):3 个没有 toString / 不在 action_inventory 的类。
|
||||
// CC 在 action_inventory 里叫 Init,但生成物 App.State.Init 是另一个切片的同名类,这里改名。
|
||||
// 实现见 AnnouncementReducer.kt。
|
||||
['bC', 'DisableAnnouncement'],
|
||||
['qC', 'SetAnnouncementModelLoaded'],
|
||||
['CC', 'SetAnnouncementItems'],
|
||||
]);
|
||||
|
||||
// 非动作的 `instanceof`:屏幕标识类,用于 GoTo 分支判断(不是 dispatch 的动作)。
|
||||
@@ -149,7 +171,9 @@ const NonActionInstanceof = new Map([
|
||||
|
||||
/** 动作局部类名 → 动作名;未收录的返回 null。 */
|
||||
function ActionNameOf(Local) {
|
||||
return LocalToName.get(Local) || AnonymousActionNames.get(Local) || null;
|
||||
// 匿名表优先:个别同名类(如 announcement 的 CC=Init)会用生成物里的名字,
|
||||
// 但该生成物属于另一个切片,必须在匿名表里显式改名。
|
||||
return AnonymousActionNames.get(Local) || LocalToName.get(Local) || null;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- 源码扫描基础
|
||||
@@ -509,6 +533,16 @@ const Migrated = {
|
||||
entrance: { Type: 'EntranceStateSlice', Reduce: 'ReduceEntrance' },
|
||||
user: { Type: 'UserStateSlice', Reduce: 'ReduceUser' },
|
||||
settings: { Type: 'SettingsStateSlice', Reduce: 'ReduceSettings' },
|
||||
accountSettings: { Type: 'AccountSettingsStateSlice', Reduce: 'ReduceAccountSettings' },
|
||||
proBattle: { Type: 'ProBattleStateSlice', Reduce: 'ReduceProBattle' },
|
||||
// language 在原根状态里是枚举对象本身,不是对象切片;Default/Import 显式指定。
|
||||
language: {
|
||||
Type: 'Language',
|
||||
Default: 'App.State.Language.EN',
|
||||
Reduce: 'ReduceLanguage',
|
||||
Import: false,
|
||||
},
|
||||
announcement: { Type: 'AnnouncementStateSlice', Reduce: 'ReduceAnnouncement' },
|
||||
// 原根 reducer 对这一片没有调用,只有默认值 `ag()`(空 Map);这里仅做类型收口。
|
||||
launchParams: {
|
||||
Type: 'LaunchParamsStateSlice',
|
||||
@@ -651,6 +685,33 @@ const SettingsAnonymousEffects = {
|
||||
Gut: 'disableAccountSettings = true(`n.bu8(cd, !0)`)',
|
||||
};
|
||||
|
||||
// accountSettings 切片(oet / Lp.mtg):没有 toString / 不在 action_inventory 的类,按 reducer 分支效果定名。
|
||||
const AccountSettingsAnonymousEffects = {
|
||||
aet: 'currentPassword/newPassword/repeatNewPassword = "",newPasswordStatus = NONE,isUnlockButtonForRenameUid = false',
|
||||
fet: 'currentPassword = action.currentPassword',
|
||||
vet: 'newPassword = action.newPassword',
|
||||
pet: 'repeatNewPassword = action.repeatNewPassword',
|
||||
wet: 'newPasswordStatus = action.status',
|
||||
det: 'isCurrentPasswordMandatory = action.isMandatory',
|
||||
get: 'email = action.email',
|
||||
met: 'emailConfirmed = action.emailConfirmed',
|
||||
cet: 'countryCode / countryName = action 的两个字段',
|
||||
ket: 'isUnlockButtonForRenameUid = action.isUnlockButtonForRenameUid',
|
||||
xet: 'removalTimer = null',
|
||||
};
|
||||
|
||||
// language 切片(m6().jtg):只把动作里的语言枚举写回。
|
||||
const LanguageAnonymousEffects = {
|
||||
d6: 'language = action.language(`t instanceof d6 ? t.itg_1 : n`)',
|
||||
};
|
||||
|
||||
// announcement 切片(lC / PC().rq5):没有 toString / 不在 action_inventory 的类,按 reducer 分支效果定名。
|
||||
const AnnouncementAnonymousEffects = {
|
||||
bC: 'isEnabled = false',
|
||||
qC: 'modelLoaded = true',
|
||||
CC: 'items = action.items',
|
||||
};
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(P, 'names', 'root_wiring.json'),
|
||||
JSON.stringify(
|
||||
@@ -715,6 +776,24 @@ fs.writeFileSync(
|
||||
DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名',
|
||||
Effect,
|
||||
})),
|
||||
...Object.entries(AccountSettingsAnonymousEffects).map(([Local, Effect]) => ({
|
||||
Local,
|
||||
Name: AnonymousActionNames.get(Local),
|
||||
DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名',
|
||||
Effect,
|
||||
})),
|
||||
...Object.entries(LanguageAnonymousEffects).map(([Local, Effect]) => ({
|
||||
Local,
|
||||
Name: AnonymousActionNames.get(Local),
|
||||
DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名',
|
||||
Effect,
|
||||
})),
|
||||
...Object.entries(AnnouncementAnonymousEffects).map(([Local, Effect]) => ({
|
||||
Local,
|
||||
Name: AnonymousActionNames.get(Local),
|
||||
DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名',
|
||||
Effect,
|
||||
})),
|
||||
],
|
||||
Markers: [
|
||||
{
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// AccountSettingsModels.kt —— accountSettings 切片用到的枚举(App.State 包内)
|
||||
//
|
||||
// 证据链:module_10032 的 `_et()` 初始化
|
||||
// `NONE=0, OK=1, SIMPLE=2, NOT_EQUALS=3`;`Ret()` 返回 NONE,
|
||||
// 即 `oet.newPasswordStatus` 的默认值。
|
||||
package App.State
|
||||
|
||||
/** 原 `het`:新密码校验状态,顺序与枚举 ordinal 一致。 */
|
||||
enum class PasswordStatus {
|
||||
NONE,
|
||||
OK,
|
||||
SIMPLE,
|
||||
NOT_EQUALS,
|
||||
}
|
||||
@@ -721,7 +721,7 @@ object ActionCatalog {
|
||||
ActionMeta("SelectBattle", SelectBattle::class, listOf(ActionFieldMeta("battleId", "Long"))),
|
||||
ActionMeta("SelectBattleFormat", SelectBattleFormat::class, listOf(ActionFieldMeta("format", "BattleFormat"))),
|
||||
ActionMeta("ProBattleState", ProBattleState::class, listOf(ActionFieldMeta("lastFightBattleId", "Any?"), ActionFieldMeta("lastFightBattleTeam", "Any?"), ActionFieldMeta("battlePasswords", "Any?"))),
|
||||
ActionMeta("Fight", Fight::class, listOf(ActionFieldMeta("battleId", "Any?"), ActionFieldMeta("battleTeam", "Any?"))),
|
||||
ActionMeta("Fight", Fight::class, listOf(ActionFieldMeta("battleId", "Long"), ActionFieldMeta("battleTeam", "BattleTeam"))),
|
||||
ActionMeta("Spectate", Spectate::class, listOf(ActionFieldMeta("battleId", "Any?"))),
|
||||
ActionMeta("CheckPassword", CheckPassword::class, listOf(ActionFieldMeta("battleId", "Any?"), ActionFieldMeta("password", "Any?"))),
|
||||
ActionMeta("GoToBattleInfo", GoToBattleInfo::class, listOf(ActionFieldMeta("battleId", "Any?"))),
|
||||
|
||||
@@ -5,7 +5,7 @@ package App.State
|
||||
|
||||
import Core.Vector3
|
||||
|
||||
// 动作/状态类共 816 个,字段 2150 个(已定类型 584 个)
|
||||
// 动作/状态类共 816 个,字段 2150 个(已定类型 586 个)
|
||||
data class AnnouncementState(
|
||||
val isEnabled: Any?, // 仅 copy 转发(无类型信息)
|
||||
val modelLoaded: Any?, // 仅 copy 转发(无类型信息)
|
||||
@@ -4077,8 +4077,8 @@ data class ProBattleState(
|
||||
)
|
||||
|
||||
data class Fight(
|
||||
val battleId: Any?, // 调用点: P2t().kar().paq_1.bpy_1.fto_1 | t.props.battleId
|
||||
val battleTeam: Any?, // 调用点: P2t().kar().paq_1.bpy_1.gto_1 | t.props.battleTeam
|
||||
val battleId: Long, // override
|
||||
val battleTeam: BattleTeam, // override
|
||||
)
|
||||
|
||||
data class Spectate(
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// LanguageModels.kt —— language 切片的值类型(App.State 包内)
|
||||
//
|
||||
// 证据链:module_52943 的 `lo()` 初始化
|
||||
// `EN=0, RU=1, DE=2, ES=3, PL=4, PT=5, ZH=6, FR=7, JA=8, TR=9, CS=10, HI=11`;
|
||||
// `tj()` 返回 `lt`,即根状态 language 的默认值 EN。
|
||||
// language 在根状态里是枚举对象本身,不是对象切片,因此不额外包一层 StateSlice。
|
||||
package App.State
|
||||
|
||||
/** 原 `fo`:客户端语言,顺序与枚举 ordinal 一致。 */
|
||||
enum class Language {
|
||||
EN,
|
||||
RU,
|
||||
DE,
|
||||
ES,
|
||||
PL,
|
||||
PT,
|
||||
ZH,
|
||||
FR,
|
||||
JA,
|
||||
TR,
|
||||
CS,
|
||||
HI,
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// ProBattleModels.kt —— proBattle 切片用到的枚举(App.State 包内)
|
||||
//
|
||||
// 证据链:module_31908 的 `Ir()` 初始化
|
||||
// `TEAM_A=0, TEAM_B=1, NONE=2`;module_10032 的 `em()` 返回 NONE,
|
||||
// 即 `Wtt.lastFightBattleTeam` 的默认值。
|
||||
package App.State
|
||||
|
||||
/** 原 `Ar`:上一场战斗的队伍,顺序与枚举 ordinal 一致。 */
|
||||
enum class BattleTeam {
|
||||
TEAM_A,
|
||||
TEAM_B,
|
||||
NONE,
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
// AccountSettingsReducer.kt —— 账户设置切片(对应 module_10032 的 oet 状态 + Lp.mtg reducer)
|
||||
//
|
||||
// 证据链:
|
||||
// 状态类 oet:13 个字段,toString 名 "AccountSettings(...)";copy 是 nu6(13 字段 + 回调)。
|
||||
// 默认:isCurrentPasswordMandatory=false、三个密码字段空串、newPasswordStatus=NONE、
|
||||
// countryCode/countryName/email=null、emailConfirmed/locationCheckEnabled=false、
|
||||
// isUnlockButtonForRenameUid/isClearInputUid=false、removalTimer=null。
|
||||
// 根委派:`Lp.mtg(t, n.vpv_1)`。
|
||||
// 13 个 instanceof 臂见下方 reducer;`locationCheckEnabled` 没有写入臂。
|
||||
// `removalTimer` 原值由 `cm(t.au7_1)` 生成 Kotlin Date,这里存时间戳 Long?,语义仍是同一时刻。
|
||||
package App.State.Reducers
|
||||
|
||||
import App.State.ClearInputUid
|
||||
import App.State.PasswordStatus
|
||||
import App.State.SetAccountRemovalDays
|
||||
|
||||
/* ------------------------------------------------------------------ 匿名动作 */
|
||||
|
||||
/** 原 `aet`:清空三个密码输入,把校验状态打回 NONE,并复位改名解锁按钮。 */
|
||||
object ResetPasswordFields
|
||||
|
||||
class SetCurrentPassword(val currentPassword: String)
|
||||
|
||||
class SetNewPassword(val newPassword: String)
|
||||
|
||||
class SetRepeatNewPassword(val repeatNewPassword: String)
|
||||
|
||||
class SetPasswordStatus(val status: PasswordStatus)
|
||||
|
||||
class SetCurrentPasswordMandatory(val isMandatory: Boolean)
|
||||
|
||||
class SetEmail(val email: String?)
|
||||
|
||||
class SetEmailConfirmed(val emailConfirmed: Boolean)
|
||||
|
||||
class SetCountry(val countryCode: String?, val countryName: String?)
|
||||
|
||||
class SetUnlockButtonForRenameUid(val isUnlockButtonForRenameUid: Boolean)
|
||||
|
||||
/** 原 `xet`:只把 removalTimer 置 null。 */
|
||||
object ClearAccountRemovalTimer
|
||||
|
||||
/* ------------------------------------------------------------------ 状态 */
|
||||
|
||||
data class AccountSettingsStateSlice(
|
||||
val IsCurrentPasswordMandatory: Boolean = false,
|
||||
val CurrentPassword: String = "",
|
||||
val NewPassword: String = "",
|
||||
val RepeatNewPassword: String = "",
|
||||
val NewPasswordStatus: PasswordStatus = PasswordStatus.NONE,
|
||||
val CountryCode: String? = null,
|
||||
val CountryName: String? = null,
|
||||
val Email: String? = null,
|
||||
val EmailConfirmed: Boolean = false,
|
||||
val LocationCheckEnabled: Boolean = false,
|
||||
val IsUnlockButtonForRenameUid: Boolean = false,
|
||||
val IsClearInputUid: Boolean = false,
|
||||
val RemovalTimer: Long? = null,
|
||||
)
|
||||
|
||||
/* ------------------------------------------------------------------ reducer */
|
||||
|
||||
fun ReduceAccountSettings(
|
||||
State0: AccountSettingsStateSlice,
|
||||
Action: Any,
|
||||
): AccountSettingsStateSlice = when (Action) {
|
||||
is ResetPasswordFields -> State0.copy(
|
||||
CurrentPassword = "",
|
||||
NewPassword = "",
|
||||
RepeatNewPassword = "",
|
||||
NewPasswordStatus = PasswordStatus.NONE,
|
||||
IsUnlockButtonForRenameUid = false,
|
||||
)
|
||||
is SetCurrentPassword -> State0.copy(CurrentPassword = Action.currentPassword)
|
||||
is SetNewPassword -> State0.copy(NewPassword = Action.newPassword)
|
||||
is SetRepeatNewPassword -> State0.copy(RepeatNewPassword = Action.repeatNewPassword)
|
||||
is SetPasswordStatus -> State0.copy(NewPasswordStatus = Action.status)
|
||||
is SetCurrentPasswordMandatory -> State0.copy(IsCurrentPasswordMandatory = Action.isMandatory)
|
||||
is SetEmail -> State0.copy(Email = Action.email)
|
||||
is SetEmailConfirmed -> State0.copy(EmailConfirmed = Action.emailConfirmed)
|
||||
is SetCountry -> State0.copy(CountryCode = Action.countryCode, CountryName = Action.countryName)
|
||||
is SetUnlockButtonForRenameUid ->
|
||||
State0.copy(IsUnlockButtonForRenameUid = Action.isUnlockButtonForRenameUid)
|
||||
is ClearInputUid -> State0.copy(IsClearInputUid = Action.isClearInputUid)
|
||||
is SetAccountRemovalDays -> State0.copy(RemovalTimer = Action.removalTimer)
|
||||
is ClearAccountRemovalTimer -> State0.copy(RemovalTimer = null)
|
||||
else -> State0
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// AnnouncementReducer.kt —— 公告切片(对应 module_10032 的 lC 状态 + RC.rq5)
|
||||
//
|
||||
// 证据链:
|
||||
// 状态类 lC:5 个字段,toString 名 "AnnouncementState(...)";copy 是 cq0(5 字段 + 回调)。
|
||||
// 默认:isEnabled=true、modelLoaded=false、items=emptyList()、wasInBattle=false、
|
||||
// skipNextPromotedAnnounce=false。
|
||||
// 根委派:`PC().rq5(t, n.opx_1)`。
|
||||
// reducer 四个 instanceof 臂:
|
||||
// bC → isEnabled=false
|
||||
// qC → modelLoaded=true
|
||||
// CC → items=action.announcementItems(生成物里的 Init 是另一个切片的同名类,故单独建类)
|
||||
// $C → skipNextPromotedAnnounce=action.skipShowNextAnnounce
|
||||
// `wasInBattle` 没有写入臂。
|
||||
package App.State.Reducers
|
||||
|
||||
import App.State.SetSkipNextPromotedAnnounce
|
||||
|
||||
/* ------------------------------------------------------------------ 匿名动作 */
|
||||
|
||||
/** 原 `bC`:关闭公告。 */
|
||||
object DisableAnnouncement
|
||||
|
||||
/** 原 `qC`:公告模型已加载。 */
|
||||
object SetAnnouncementModelLoaded
|
||||
|
||||
/** 原 `CC`:写入公告列表。 */
|
||||
class SetAnnouncementItems(val items: List<Any?>)
|
||||
|
||||
/* ------------------------------------------------------------------ 状态 */
|
||||
|
||||
data class AnnouncementStateSlice(
|
||||
val IsEnabled: Boolean = true,
|
||||
val ModelLoaded: Boolean = false,
|
||||
val Items: List<Any?> = emptyList(),
|
||||
val WasInBattle: Boolean = false,
|
||||
val SkipNextPromotedAnnounce: Boolean = false,
|
||||
)
|
||||
|
||||
/* ------------------------------------------------------------------ reducer */
|
||||
|
||||
fun ReduceAnnouncement(
|
||||
State0: AnnouncementStateSlice,
|
||||
Action: Any,
|
||||
): AnnouncementStateSlice = when (Action) {
|
||||
is DisableAnnouncement -> State0.copy(IsEnabled = false)
|
||||
is SetAnnouncementModelLoaded -> State0.copy(ModelLoaded = true)
|
||||
is SetAnnouncementItems -> State0.copy(Items = Action.items)
|
||||
is SetSkipNextPromotedAnnounce ->
|
||||
State0.copy(SkipNextPromotedAnnounce = Action.skipShowNextAnnounce)
|
||||
else -> State0
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// LanguageReducer.kt —— 语言切片(对应 module_10032 的 language 值 + g6.jtg)
|
||||
//
|
||||
// 证据链:
|
||||
// 根状态 language 的默认值是 `tj()` → module_52943 的 `vo()` → EN。
|
||||
// reducer:`t instanceof d6 ? t.itg_1 : n`,即只把动作里的语言枚举写回。
|
||||
// language 在原根状态里就是枚举对象本身,因此 reducer 直接 (Language, Any) → Language。
|
||||
package App.State.Reducers
|
||||
|
||||
import App.State.Language
|
||||
|
||||
/* ------------------------------------------------------------------ 动作 */
|
||||
|
||||
class SetLanguage(val language: Language)
|
||||
|
||||
/* ------------------------------------------------------------------ reducer */
|
||||
|
||||
fun ReduceLanguage(State0: Language, Action: Any): Language = when (Action) {
|
||||
is SetLanguage -> Action.language
|
||||
else -> State0
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// ProBattleReducer.kt —— 上一场战斗 / 战斗密码切片(对应 module_10032 的 Wtt 状态 + Jtt.wth)
|
||||
//
|
||||
// 证据链:
|
||||
// 状态类 Wtt:3 个字段,toString 名 "ProBattleState(...)";copy 是 jto(3 字段 + 回调)。
|
||||
// 默认:lastFightBattleId=-1、lastFightBattleTeam=NONE、battlePasswords=emptyMap()。
|
||||
// 根委派:`uv.wth(t, n.bpy_1)`。
|
||||
// reducer:
|
||||
// `t instanceof Vtt ? n.jto(t.lto_1, t.mto_1)`(Fight 只写 id + team)
|
||||
// `t instanceof Xtt ? password 为空 ? n.jto(cd, cd, am(map, id)) : n.jto(cd, cd, cg(map, Vd(id, password)))`
|
||||
// `SetPassword` 在登录切片也有同名类(qD,只有 password),这里按 proBattle 的 Xtt 建独立类,
|
||||
// 避免复用生成物 App.State.SetPassword 导致丢 battleId。
|
||||
package App.State.Reducers
|
||||
|
||||
import App.State.BattleTeam
|
||||
import App.State.Fight
|
||||
|
||||
/* ------------------------------------------------------------------ 动作 */
|
||||
|
||||
/**
|
||||
* 原 `Xtt`:i = battleId,password = 新密码。
|
||||
* 密码为空时从 map 删除,否则覆盖写入。
|
||||
*/
|
||||
class SetPassword(val battleId: Long, val password: String)
|
||||
|
||||
/* ------------------------------------------------------------------ 状态 */
|
||||
|
||||
data class ProBattleStateSlice(
|
||||
val LastFightBattleId: Long = -1L,
|
||||
val LastFightBattleTeam: BattleTeam = BattleTeam.NONE,
|
||||
val BattlePasswords: Map<Long, String> = emptyMap(),
|
||||
)
|
||||
|
||||
/* ------------------------------------------------------------------ reducer */
|
||||
|
||||
fun ReduceProBattle(State0: ProBattleStateSlice, Action: Any): ProBattleStateSlice = when (Action) {
|
||||
is Fight -> State0.copy(
|
||||
LastFightBattleId = Action.battleId,
|
||||
LastFightBattleTeam = Action.battleTeam,
|
||||
)
|
||||
is SetPassword -> State0.copy(
|
||||
BattlePasswords = if (Action.password.isEmpty()) {
|
||||
State0.BattlePasswords - Action.battleId
|
||||
} else {
|
||||
State0.BattlePasswords + (Action.battleId to Action.password)
|
||||
},
|
||||
)
|
||||
else -> State0
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// RootReducer.kt —— 根 reducer 骨架(对应 module_10032 的根状态 z6 / TOState 与根 reducer k6)
|
||||
//
|
||||
// 原包 k6 对 89 个切片逐个调用各自的 reducer,再 `return n.cti(...)`(copy 出新状态)。
|
||||
// 这里采用同一形状。已迁移的 14 个切片里,13 个真正调用 reducer,launchParams 只做类型收口:
|
||||
// 这里采用同一形状。已迁移的 18 个切片里,17 个真正调用 reducer,launchParams 只做类型收口:
|
||||
//
|
||||
// inviteState → ReduceInvite(aD / pD.er5)
|
||||
// loginByPassword → ReduceLoginByPassword(gD / MD.cr6)
|
||||
@@ -16,21 +16,29 @@
|
||||
// entrance → ReduceEntrance(hD / Bv.pth,根委派 `Bv.pth(t, n.jpx_1)`)
|
||||
// user → ReduceUser(t8 / M8().qtd,根委派 `M8().qtd(t, n.ypu_1)`)
|
||||
// settings → ReduceSettings(Uut / xw.ltg,根委派 `xw.ltg(t, n.upv_1)`)
|
||||
// accountSettings → ReduceAccountSettings(oet / Lp.mtg,根委派 `Lp.mtg(t, n.vpv_1)`)
|
||||
// proBattle → ReduceProBattle(Wtt / uv.wth,根委派 `uv.wth(t, n.bpy_1)`)
|
||||
// language → ReduceLanguage(枚举值 / m6().jtg,根委派 `m6().jtg(t, n.gpx_1)`)
|
||||
// announcement → ReduceAnnouncement(lC / PC().rq5,根委派 `PC().rq5(t, n.opx_1)`)
|
||||
// launchParams → 无 reducer(原根 reducer 原样透传;默认 `ag()` = emptyMap())
|
||||
//
|
||||
// 其余 75 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态,
|
||||
// 其余 71 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态,
|
||||
// 所以一次 copy 里串起全部已迁移 reducer,与按动作路由再归约的结果一致。
|
||||
// 迁移进度与每个字段的证据见生成物 RootWiring.kt(不要手改)。
|
||||
package App.State
|
||||
|
||||
import App.State.Reducers.ReduceAccountSettings
|
||||
import App.State.Reducers.ReduceAnnouncement
|
||||
import App.State.Reducers.ReduceEntrance
|
||||
import App.State.Reducers.ReduceGarage
|
||||
import App.State.Reducers.ReduceGaragePreview
|
||||
import App.State.Reducers.ReduceHome
|
||||
import App.State.Reducers.ReduceInvite
|
||||
import App.State.Reducers.ReduceLanguage
|
||||
import App.State.Reducers.ReduceLocalBattleUser
|
||||
import App.State.Reducers.ReduceLoginByPassword
|
||||
import App.State.Reducers.ReduceLootBoxes
|
||||
import App.State.Reducers.ReduceProBattle
|
||||
import App.State.Reducers.ReduceScreen
|
||||
import App.State.Reducers.ReduceSettings
|
||||
import App.State.Reducers.ReduceShop
|
||||
@@ -63,6 +71,10 @@ object RootReducer {
|
||||
Entrance = ReduceEntrance(State0.Entrance, Action),
|
||||
User = ReduceUser(State0.User, Action),
|
||||
Settings = ReduceSettings(State0.Settings, Action),
|
||||
AccountSettings = ReduceAccountSettings(State0.AccountSettings, Action),
|
||||
ProBattle = ReduceProBattle(State0.ProBattle, Action),
|
||||
Language = ReduceLanguage(State0.Language, Action),
|
||||
Announcement = ReduceAnnouncement(State0.Announcement, Action),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
// 已迁移的切片用强类型;其余保持 Any?(原样透传),迁移一个改一个再重新生成。
|
||||
package App.State
|
||||
|
||||
import App.State.Reducers.AccountSettingsStateSlice
|
||||
import App.State.Reducers.AnnouncementStateSlice
|
||||
import App.State.Reducers.EntranceStateSlice
|
||||
import App.State.Reducers.GarageStateSlice
|
||||
import App.State.Reducers.GaragePreviewStateSlice
|
||||
@@ -12,6 +14,7 @@ import App.State.Reducers.InviteStateSlice
|
||||
import App.State.Reducers.LocalBattleUserStateSlice
|
||||
import App.State.Reducers.LoginByPasswordStateSlice
|
||||
import App.State.Reducers.LootBoxesStateSlice
|
||||
import App.State.Reducers.ProBattleStateSlice
|
||||
import App.State.Reducers.ScreenStateSlice
|
||||
import App.State.Reducers.SettingsStateSlice
|
||||
import App.State.Reducers.ShopStateSlice
|
||||
@@ -69,8 +72,8 @@ data class RootState(
|
||||
val News: Any? = null,
|
||||
/** 原 settings(Uut / xw.ltg)。已迁移。 */
|
||||
val Settings: SettingsStateSlice = SettingsStateSlice(),
|
||||
/** 原 accountSettings(oet / Lp.mtg)。未迁移:原样透传。 */
|
||||
val AccountSettings: Any? = null,
|
||||
/** 原 accountSettings(oet / Lp.mtg)。已迁移。 */
|
||||
val AccountSettings: AccountSettingsStateSlice = AccountSettingsStateSlice(),
|
||||
/** 原 lootBoxes(aH / ws.wrv)。已迁移。 */
|
||||
val LootBoxes: LootBoxesStateSlice = LootBoxesStateSlice(),
|
||||
/** 原 notifications(? / B_.ise)。未迁移:原样透传。 */
|
||||
@@ -143,8 +146,8 @@ data class RootState(
|
||||
val TouchscreenHudSettings: Any? = null,
|
||||
/** 原 mainQuest(WZ / ih.bsk)。未迁移:原样透传。 */
|
||||
val MainQuest: Any? = null,
|
||||
/** 原 language(? / m6().jtg)。未迁移:原样透传。 */
|
||||
val Language: Any? = null,
|
||||
/** 原 language(? / m6().jtg)。已迁移。 */
|
||||
val Language: Language = App.State.Language.EN,
|
||||
/** 原 launchParams(? / (pass-through))。已迁移(透传,强类型)。 */
|
||||
val LaunchParams: LaunchParamsStateSlice = emptyMap(),
|
||||
/** 原 partner(UJ / (inline@partner))。未迁移:原样透传。 */
|
||||
@@ -159,8 +162,8 @@ data class RootState(
|
||||
val Moderation: Any? = null,
|
||||
/** 原 rulesUpdated(i1 / Ch.dsm)。未迁移:原样透传。 */
|
||||
val RulesUpdated: Any? = null,
|
||||
/** 原 announcement(lC / PC().rq5)。未迁移:原样透传。 */
|
||||
val Announcement: Any? = null,
|
||||
/** 原 announcement(lC / PC().rq5)。已迁移。 */
|
||||
val Announcement: AnnouncementStateSlice = AnnouncementStateSlice(),
|
||||
/** 原 notificationByCountry(gJ / R_.ssd)。未迁移:原样透传。 */
|
||||
val NotificationByCountry: Any? = null,
|
||||
/** 原 userRenameState(O8 / Rf.pte)。未迁移:原样透传。 */
|
||||
@@ -185,8 +188,8 @@ data class RootState(
|
||||
val SuppliesTutorial: Any? = null,
|
||||
/** 原 questCategories(zZ / th.lsh)。未迁移:原样透传。 */
|
||||
val QuestCategories: Any? = null,
|
||||
/** 原 proBattle(Wtt / uv.wth)。未迁移:原样透传。 */
|
||||
val ProBattle: Any? = null,
|
||||
/** 原 proBattle(Wtt / uv.wth)。已迁移。 */
|
||||
val ProBattle: ProBattleStateSlice = ProBattleStateSlice(),
|
||||
/** 原 availabilityByRank(tet / $p.xth)。未迁移:原样透传。 */
|
||||
val AvailabilityByRank: Any? = null,
|
||||
/** 原 userReportsState(Zst / qot.zth)。未迁移:原样透传。 */
|
||||
|
||||
@@ -37,7 +37,7 @@ object RootWiring {
|
||||
SliceWiring("tipsState", "spv_1", "dst", "Hw.vth", "Hw.vth(t, n.spv_1)", listOf("UpdateTips")),
|
||||
SliceWiring("news", "tpv_1", "ZX", "E_.asd", "E_.asd(t, n.tpv_1)", listOf("?hJ", "AddNewsItemAction", "RemoveNewsItemAction", "SetNewsItemAction", "SetUnreadNewsCounter")),
|
||||
SliceWiring("settings", "upv_1", "Uut", "xw.ltg", "xw.ltg(t, n.upv_1)", listOf("DisableAccountSettings", "SetSettingsCategory")),
|
||||
SliceWiring("accountSettings", "vpv_1", "oet", "Lp.mtg", "Lp.mtg(t, n.vpv_1)", listOf("?aet", "?cet", "?det", "?fet", "?get", "?ket", "?met", "?pet", "?vet", "?wet", "?xet", "ClearInputUid", "SetAccountRemovalDays")),
|
||||
SliceWiring("accountSettings", "vpv_1", "oet", "Lp.mtg", "Lp.mtg(t, n.vpv_1)", listOf("ClearAccountRemovalTimer", "ClearInputUid", "ResetPasswordFields", "SetAccountRemovalDays", "SetCountry", "SetCurrentPassword", "SetCurrentPasswordMandatory", "SetEmail", "SetEmailConfirmed", "SetNewPassword", "SetPasswordStatus", "SetRepeatNewPassword", "SetUnlockButtonForRenameUid")),
|
||||
SliceWiring("lootBoxes", "wpv_1", "aH", "ws.wrv", "ws.wrv(t, n.wpv_1)", listOf("AddKeys", "ClearRewards", "LootBoxOpeningFailed", "SelectCategory", "SetContainerData", "SetCurrentContainer", "SetGoToGarage", "SetIsAccelerated", "SetIsBlockOpenButtons", "SetIsOpening", "SetIsReopening", "SetIsShopVisited", "SetKeys", "SetPeriodOfGiveLootBox", "SetPossibleRewards", "SetRewards", "SetTimeToGiveLootBox", "ShownRewards")),
|
||||
SliceWiring("notifications", "xpv_1", "?", "B_.ise", "B_.ise(t, n.xpv_1)", listOf("?PJ", "?RJ", "AddNotification", "InitNotificationSound")),
|
||||
SliceWiring("friends", "ypv_1", "KD", "NL().cr9", "NL().cr9(t, n.ypv_1)", listOf("?_L", "?hL", "AddUsersToAccepted", "AddUsersToIncoming", "AddUsersToNewAccepted", "AddUsersToNewIncoming", "ChangeFriendAddFindState", "RemoveUserFromAccepted", "RemoveUserFromIncoming", "RemoveUserFromNewAccepted", "RemoveUsersFromNewIncoming", "SetLimits", "SetValidationStatus")),
|
||||
@@ -74,7 +74,7 @@ object RootWiring {
|
||||
SliceWiring("keyboardSettings", "dpx_1", "Sut", "bw.oth", "bw.oth(t, n.dpx_1)", listOf("?Iut", "?_C", "ChangeAction", "RemoveAction", "ResetAction")),
|
||||
SliceWiring("touchscreenHudSettings", "epx_1", "j7", "N7().xsv", "N7().xsv(t, n.epx_1)", listOf("?E7", "?_C", "UpdateSettings")),
|
||||
SliceWiring("mainQuest", "fpx_1", "WZ", "ih.bsk", "ih.bsk(t, n.fpx_1)", listOf("?VZ", "AddRewardedQuest", "QuestsLoaded", "SetBattleUpdatedQuests", "SetEventPassShopItemId", "SetQuests", "UpdateQuests")),
|
||||
SliceWiring("language", "gpx_1", "?", "m6().jtg", "m6().jtg(t, n.gpx_1)", listOf("?d6")),
|
||||
SliceWiring("language", "gpx_1", "?", "m6().jtg", "m6().jtg(t, n.gpx_1)", listOf("SetLanguage")),
|
||||
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")),
|
||||
@@ -82,7 +82,7 @@ object RootWiring {
|
||||
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("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("?bC", "?qC", "Init", "SetSkipNextPromotedAnnounce")),
|
||||
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("?qJ", "Init")),
|
||||
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")),
|
||||
@@ -106,7 +106,7 @@ object RootWiring {
|
||||
)
|
||||
|
||||
/** 已迁移(强类型;launchParams 为透传,不调用 reducer)的字段。 */
|
||||
val Migrated: Set<String> = setOf("inviteState", "loginByPassword", "twitchState", "home", "localBattleUserState", "shop", "screen", "lootBoxes", "garage", "garagePreview", "entrance", "user", "settings", "launchParams")
|
||||
val Migrated: Set<String> = setOf("inviteState", "loginByPassword", "twitchState", "home", "localBattleUserState", "shop", "screen", "lootBoxes", "garage", "garagePreview", "entrance", "user", "settings", "accountSettings", "proBattle", "language", "announcement", "launchParams")
|
||||
|
||||
/** 未迁移字段:归约时原样透传,作为后续迁移清单。 */
|
||||
val Unmigrated: List<SliceWiring> get() = All.filter { it.Field !in Migrated }
|
||||
|
||||
@@ -9,8 +9,9 @@
|
||||
// ⑥ 动作目录:816 个类与 138 个真实派发名对齐
|
||||
// ⑦ 回放:真实 toString 样例的类/字段/类型校验
|
||||
// ⑧ 全目录动作类都能按 KClass 路由
|
||||
// ⑨~㉔ 切片 reducer:InviteState / LoginByPasswordState / TwitchState / HomeScreen / LocalBattleUserState /
|
||||
// Shop(含 U7 类别表与 w3 物品表两个子 reducer)/ Screen / LootBoxes / Garage / GaragePreview / Entrance / User / Settings;
|
||||
// ⑨~㉘ 切片 reducer:InviteState / LoginByPasswordState / TwitchState / HomeScreen / LocalBattleUserState /
|
||||
// Shop(含 U7 类别表与 w3 物品表两个子 reducer)/ Screen / LootBoxes / Garage / GaragePreview / Entrance / User / Settings /
|
||||
// AccountSettings / ProBattle / Language / Announcement;
|
||||
// ㉑ launchParams 是纯透传切片(无 reducer/动作),只做 Map 类型收口;⑭ 根 reducer 的 89 槽位委派
|
||||
//
|
||||
// 用法:$env:TANKIOFFLINE_STATE_TEST='1'; node …(见 TankiOffline/tools/RunStateTest.ps1)
|
||||
@@ -100,6 +101,13 @@ import App.State.SetRankNames
|
||||
import App.State.SetRubies
|
||||
import App.State.SetUserPropertiesAction
|
||||
import App.State.SetUserId
|
||||
import App.State.BattleTeam
|
||||
import App.State.ClearInputUid
|
||||
import App.State.Fight
|
||||
import App.State.Language
|
||||
import App.State.PasswordStatus
|
||||
import App.State.SetAccountRemovalDays
|
||||
import App.State.SetSkipNextPromotedAnnounce
|
||||
import App.State.SettingsCategory
|
||||
import App.State.SpendCoins
|
||||
import App.State.SpendCrystals
|
||||
@@ -268,8 +276,31 @@ import App.State.Reducers.DisableAccountSettings
|
||||
import App.State.Reducers.DisableLobbyTutorial
|
||||
import App.State.Reducers.ReduceSettings
|
||||
import App.State.Reducers.ReduceUser
|
||||
import App.State.Reducers.AccountSettingsStateSlice
|
||||
import App.State.Reducers.AnnouncementStateSlice
|
||||
import App.State.Reducers.ClearAccountRemovalTimer
|
||||
import App.State.Reducers.DisableAnnouncement
|
||||
import App.State.Reducers.ProBattleStateSlice
|
||||
import App.State.Reducers.ReduceAccountSettings
|
||||
import App.State.Reducers.ReduceAnnouncement
|
||||
import App.State.Reducers.ReduceLanguage
|
||||
import App.State.Reducers.ReduceProBattle
|
||||
import App.State.Reducers.ResetPasswordFields
|
||||
import App.State.Reducers.SetAnnouncementItems
|
||||
import App.State.Reducers.SetAnnouncementModelLoaded
|
||||
import App.State.Reducers.SetCountry
|
||||
import App.State.Reducers.SetCurrentPassword
|
||||
import App.State.Reducers.SetCurrentPasswordMandatory
|
||||
import App.State.Reducers.SetEmail
|
||||
import App.State.Reducers.SetEmailConfirmed
|
||||
import App.State.Reducers.SetInDebt
|
||||
import App.State.Reducers.SetLanguage
|
||||
import App.State.Reducers.SetNewPassword
|
||||
import App.State.Reducers.SetPassword as SetBattlePassword
|
||||
import App.State.Reducers.SetPasswordStatus
|
||||
import App.State.Reducers.SetRepeatNewPassword
|
||||
import App.State.Reducers.SetSettingsCategory
|
||||
import App.State.Reducers.SetUnlockButtonForRenameUid
|
||||
import App.State.Reducers.SettingsStateSlice
|
||||
import App.State.Reducers.UserRankBounds
|
||||
import App.State.Reducers.UserStateSlice
|
||||
@@ -698,7 +729,7 @@ object StateCheck {
|
||||
Check("切片 LocalBattleUser:接 Store 派发后状态已归约", Store.State.IsReArmorTemporaryDisabled)
|
||||
}
|
||||
|
||||
// ⑭ 根 reducer:89 个槽位的委派表 + 14 个已迁移切片(13 归约 + launchParams 透传)
|
||||
// ⑭ 根 reducer:89 个槽位的委派表 + 18 个已迁移切片(17 归约 + launchParams 透传)
|
||||
run {
|
||||
Check("根:根状态 89 个切片槽位", RootWiring.All.size == 89)
|
||||
Check(
|
||||
@@ -762,14 +793,15 @@ object StateCheck {
|
||||
LocalBattleW.Reducer == "(inline@localBattleUserState)"
|
||||
)
|
||||
Check(
|
||||
"根:迁移表 = 14 个已迁移字段,其余 75 个待迁",
|
||||
"根:迁移表 = 18 个已迁移字段,其余 71 个待迁",
|
||||
RootReducer.Migrated ==
|
||||
setOf(
|
||||
"inviteState", "loginByPassword", "twitchState", "home",
|
||||
"localBattleUserState", "shop", "screen", "lootBoxes", "garage",
|
||||
"garagePreview", "entrance", "user", "settings", "launchParams",
|
||||
"garagePreview", "entrance", "user", "settings", "accountSettings",
|
||||
"proBattle", "language", "announcement", "launchParams",
|
||||
) &&
|
||||
RootWiring.Unmigrated.size == 75
|
||||
RootWiring.Unmigrated.size == 71
|
||||
)
|
||||
|
||||
// 一次 ReduceRoot 里多个切片各归约自己的动作,互不串片
|
||||
@@ -825,7 +857,7 @@ object StateCheck {
|
||||
S5.User.Uid == "user-1" && S5.Screen.Current == NamedScreen("Preloader")
|
||||
)
|
||||
Check("根:无关动作返回等值状态", ReduceRoot(S5, AddFrags(1)) == S5)
|
||||
Check("根:Coverage 文本", RootReducer.Coverage == "已迁移 14/89")
|
||||
Check("根:Coverage 文本", RootReducer.Coverage == "已迁移 18/89")
|
||||
|
||||
val Store = ReduxStore(RootState(), { S, A -> ReduceRoot(S, A) })
|
||||
Store.Dispatch(SetStatus("LOGGING_IN"))
|
||||
@@ -2692,6 +2724,207 @@ object StateCheck {
|
||||
)
|
||||
}
|
||||
|
||||
// ㉕ 切片 reducer:AccountSettings(oet / Lp.mtg,13 字段;13 个 instanceof 臂)
|
||||
run {
|
||||
val S0 = AccountSettingsStateSlice()
|
||||
Check(
|
||||
"切片 AccountSettings:默认值完整",
|
||||
!S0.IsCurrentPasswordMandatory && S0.CurrentPassword == "" &&
|
||||
S0.NewPassword == "" && S0.RepeatNewPassword == "" &&
|
||||
S0.NewPasswordStatus == PasswordStatus.NONE && S0.CountryCode == null &&
|
||||
S0.CountryName == null && S0.Email == null && !S0.EmailConfirmed &&
|
||||
!S0.LocationCheckEnabled && !S0.IsUnlockButtonForRenameUid &&
|
||||
!S0.IsClearInputUid && S0.RemovalTimer == null
|
||||
)
|
||||
val Reset = ReduceAccountSettings(
|
||||
S0.copy(
|
||||
CurrentPassword = "old",
|
||||
NewPassword = "new",
|
||||
RepeatNewPassword = "new",
|
||||
NewPasswordStatus = PasswordStatus.OK,
|
||||
IsUnlockButtonForRenameUid = true,
|
||||
Email = "keep@example.com",
|
||||
RemovalTimer = 42L,
|
||||
),
|
||||
ResetPasswordFields,
|
||||
)
|
||||
Check(
|
||||
"切片 AccountSettings:ResetPasswordFields 只清密码三件套/状态/解锁按钮",
|
||||
Reset.CurrentPassword == "" && Reset.NewPassword == "" &&
|
||||
Reset.RepeatNewPassword == "" && Reset.NewPasswordStatus == PasswordStatus.NONE &&
|
||||
!Reset.IsUnlockButtonForRenameUid && Reset.Email == "keep@example.com" &&
|
||||
Reset.RemovalTimer == 42L
|
||||
)
|
||||
val S1 = listOf<Any>(
|
||||
SetCurrentPassword("cur"),
|
||||
SetNewPassword("next"),
|
||||
SetRepeatNewPassword("next"),
|
||||
SetPasswordStatus(PasswordStatus.SIMPLE),
|
||||
SetCurrentPasswordMandatory(true),
|
||||
SetEmail("user@example.com"),
|
||||
SetEmailConfirmed(true),
|
||||
SetCountry("RU", "Russia"),
|
||||
SetUnlockButtonForRenameUid(true),
|
||||
ClearInputUid(true),
|
||||
SetAccountRemovalDays(1_700_000_000_000L),
|
||||
).fold(S0) { S, A -> ReduceAccountSettings(S, A) }
|
||||
Check(
|
||||
"切片 AccountSettings:11 个单字段动作按顺序写入",
|
||||
S1.CurrentPassword == "cur" && S1.NewPassword == "next" &&
|
||||
S1.RepeatNewPassword == "next" && S1.NewPasswordStatus == PasswordStatus.SIMPLE &&
|
||||
S1.IsCurrentPasswordMandatory && S1.Email == "user@example.com" &&
|
||||
S1.EmailConfirmed && S1.CountryCode == "RU" && S1.CountryName == "Russia" &&
|
||||
S1.IsUnlockButtonForRenameUid && S1.IsClearInputUid &&
|
||||
S1.RemovalTimer == 1_700_000_000_000L
|
||||
)
|
||||
Check(
|
||||
"切片 AccountSettings:ClearAccountRemovalTimer 只清 removalTimer",
|
||||
ReduceAccountSettings(S1, ClearAccountRemovalTimer).let {
|
||||
it.RemovalTimer == null && it.Email == "user@example.com"
|
||||
}
|
||||
)
|
||||
Check(
|
||||
"切片 AccountSettings:无关动作不改状态",
|
||||
ReduceAccountSettings(S0, AddFrags(1)) == S0
|
||||
)
|
||||
|
||||
val Root0 = RootState()
|
||||
val Root1 = ReduceRoot(Root0, SetCurrentPassword("root-pass"))
|
||||
Check(
|
||||
"根:SetCurrentPassword 只动 accountSettings",
|
||||
Root1.AccountSettings.CurrentPassword == "root-pass" &&
|
||||
Root1.Language == Root0.Language && Root1.ProBattle == Root0.ProBattle
|
||||
)
|
||||
Check(
|
||||
"根:accountSettings 无关动作保留前一次归约",
|
||||
ReduceRoot(Root1, AddFrags(1)).AccountSettings == Root1.AccountSettings
|
||||
)
|
||||
val W = RootWiring.ByField("accountSettings")!!
|
||||
Check(
|
||||
"根:accountSettings 委派到 oet / Lp.mtg 且 13 个动作全部具名",
|
||||
W.SliceClass == "oet" && W.Reducer == "Lp.mtg" && W.Actions.size == 13 &&
|
||||
W.Actions.containsAll(
|
||||
setOf(
|
||||
"ClearAccountRemovalTimer", "ClearInputUid", "ResetPasswordFields",
|
||||
"SetAccountRemovalDays", "SetCountry", "SetCurrentPassword",
|
||||
"SetCurrentPasswordMandatory", "SetEmail", "SetEmailConfirmed",
|
||||
"SetNewPassword", "SetPasswordStatus", "SetRepeatNewPassword",
|
||||
"SetUnlockButtonForRenameUid",
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// ㉖ 切片 reducer:ProBattle(Wtt / uv.wth,3 字段;2 个 instanceof 臂)
|
||||
run {
|
||||
val S0 = ProBattleStateSlice()
|
||||
Check(
|
||||
"切片 ProBattle:默认 -1 / NONE / 空密码表",
|
||||
S0.LastFightBattleId == -1L && S0.LastFightBattleTeam == BattleTeam.NONE &&
|
||||
S0.BattlePasswords.isEmpty()
|
||||
)
|
||||
val Fight1 = ReduceProBattle(S0, Fight(10L, BattleTeam.TEAM_A))
|
||||
Check(
|
||||
"切片 ProBattle:Fight 写上一场 id + team",
|
||||
Fight1.LastFightBattleId == 10L && Fight1.LastFightBattleTeam == BattleTeam.TEAM_A
|
||||
)
|
||||
val WithPass = ReduceProBattle(Fight1, SetBattlePassword(10L, "secret"))
|
||||
Check(
|
||||
"切片 ProBattle:SetPassword 非空时写入密码表",
|
||||
WithPass.BattlePasswords == mapOf(10L to "secret")
|
||||
)
|
||||
Check(
|
||||
"切片 ProBattle:SetPassword 空串时删除密码表项",
|
||||
ReduceProBattle(WithPass, SetBattlePassword(10L, "")).BattlePasswords.isEmpty()
|
||||
)
|
||||
Check("切片 ProBattle:无关动作不改状态", ReduceProBattle(S0, AddFrags(1)) == S0)
|
||||
|
||||
val Root0 = RootState()
|
||||
val Root1 = ReduceRoot(Root0, Fight(77L, BattleTeam.TEAM_B))
|
||||
Check(
|
||||
"根:Fight 只动 proBattle",
|
||||
Root1.ProBattle.LastFightBattleId == 77L &&
|
||||
Root1.ProBattle.LastFightBattleTeam == BattleTeam.TEAM_B &&
|
||||
Root1.AccountSettings == Root0.AccountSettings
|
||||
)
|
||||
val W = RootWiring.ByField("proBattle")!!
|
||||
Check(
|
||||
"根:proBattle 委派到 Wtt / uv.wth 且 2 个动作",
|
||||
W.SliceClass == "Wtt" && W.Reducer == "uv.wth" &&
|
||||
W.Actions.toSet() == setOf("Fight", "SetPassword")
|
||||
)
|
||||
}
|
||||
|
||||
// ㉗ 切片 reducer:Language(枚举值 / m6().jtg,1 个 instanceof 臂)
|
||||
run {
|
||||
Check("切片 Language:根状态默认 EN", RootState().Language == Language.EN)
|
||||
Check(
|
||||
"切片 Language:SetLanguage 写回枚举",
|
||||
ReduceLanguage(Language.EN, SetLanguage(Language.ZH)) == Language.ZH
|
||||
)
|
||||
Check(
|
||||
"切片 Language:无关动作保留当前语言",
|
||||
ReduceLanguage(Language.ZH, AddFrags(1)) == Language.ZH
|
||||
)
|
||||
val Root0 = RootState()
|
||||
val Root1 = ReduceRoot(Root0, SetLanguage(Language.DE))
|
||||
Check(
|
||||
"根:SetLanguage 只动 language",
|
||||
Root1.Language == Language.DE && Root1.Announcement == Root0.Announcement
|
||||
)
|
||||
val W = RootWiring.ByField("language")!!
|
||||
Check(
|
||||
"根:language 委派到 m6().jtg 且动作具名",
|
||||
W.SliceClass == "?" && W.Reducer == "m6().jtg" &&
|
||||
W.Actions == listOf("SetLanguage")
|
||||
)
|
||||
}
|
||||
|
||||
// ㉘ 切片 reducer:Announcement(lC / PC().rq5,5 字段;4 个 instanceof 臂)
|
||||
run {
|
||||
val S0 = AnnouncementStateSlice()
|
||||
Check(
|
||||
"切片 Announcement:默认值完整",
|
||||
S0.IsEnabled && !S0.ModelLoaded && S0.Items.isEmpty() &&
|
||||
!S0.WasInBattle && !S0.SkipNextPromotedAnnounce
|
||||
)
|
||||
val S1 = ReduceAnnouncement(
|
||||
ReduceAnnouncement(
|
||||
ReduceAnnouncement(
|
||||
ReduceAnnouncement(S0, DisableAnnouncement),
|
||||
SetAnnouncementModelLoaded,
|
||||
),
|
||||
SetAnnouncementItems(listOf("a", "b")),
|
||||
),
|
||||
SetSkipNextPromotedAnnounce(true),
|
||||
)
|
||||
Check(
|
||||
"切片 Announcement:4 个臂分别写 enabled/modelLoaded/items/skip",
|
||||
!S1.IsEnabled && S1.ModelLoaded && S1.Items == listOf<Any?>("a", "b") &&
|
||||
S1.SkipNextPromotedAnnounce
|
||||
)
|
||||
Check(
|
||||
"切片 Announcement:无关动作不改状态",
|
||||
ReduceAnnouncement(S0, AddFrags(1)) == S0
|
||||
)
|
||||
|
||||
val Root0 = RootState()
|
||||
val Root1 = ReduceRoot(Root0, DisableAnnouncement)
|
||||
Check(
|
||||
"根:DisableAnnouncement 只动 announcement",
|
||||
!Root1.Announcement.IsEnabled && Root1.AccountSettings == Root0.AccountSettings
|
||||
)
|
||||
val W = RootWiring.ByField("announcement")!!
|
||||
Check(
|
||||
"根:announcement 委派到 lC / PC().rq5 且 4 个动作全部具名",
|
||||
W.SliceClass == "lC" && W.Reducer == "PC().rq5" &&
|
||||
W.Actions.toSet() == setOf(
|
||||
"DisableAnnouncement", "SetAnnouncementItems",
|
||||
"SetAnnouncementModelLoaded", "SetSkipNextPromotedAnnounce",
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val Failed = Results.filter { !it.second }
|
||||
println("TankiOffline 状态层自检:${Results.size} 项")
|
||||
for ((Name, Ok) in Results) println((if (Ok) " ✓ " else " ✗ ") + Name)
|
||||
|
||||
Reference in new issue
Block a user