feat(state): 迁密码登录与 Twitch 切片,拼出 89 槽位根 reducer
This commit is contained in:
1 parent
b4ecb80b23
commit
0d4f12effb
11 files changed
+2783
-1
No files matched your search
@@ -138,6 +138,20 @@
|
||||
- 模块:10032(Redux 核心)、18334(DI 装配)、61860(连接与对象模型)、13431(启动)、52943(工具库)、26482(SW 注册)
|
||||
- **目标**:新工程能完成"启动 → 连接 → 进入大厅"。
|
||||
- **验收**:L2 的一半(登录 + 大厅)。
|
||||
- **进度(Kotlin/JS,2026-10)**:
|
||||
- 根状态 / 委派表由 `3DTank/tools/gen_root_wiring.js` 从当前 `module_10032.js` 现场解析:
|
||||
`names/root_wiring.json` + `TankiOffline/.../App/State/RootState.kt`、`RootWiring.kt`。
|
||||
- 根状态 `z6`(`TOState`)的 **89 个槽位**全部入表;字段顺序取 `toString`,槽位取构造函数 `<名>_1`。
|
||||
- 根 reducer `k6` 的逐切片委派记全:reducer 引用、委派表达式、顶层 `instanceof` 动作名。
|
||||
- 已迁移切片 **3/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)
|
||||
- `App/State/RootReducer.kt`:`ReduceRoot(State0, Action)` 只归约上述三片,其余 **86 个字段原样透传**;
|
||||
迁移进度用 `RootReducer.Coverage`(`已迁移 3/89`)与 `RootWiring.Unmigrated` 暴露。
|
||||
- 自检:`TankiOffline/tools/RunStateTest.ps1` **61 项全绿**,含 89 槽位完整性、三片一次归约互不串片、未迁移字段透传。
|
||||
- 委派表里的已知形态:`shop` / `home` / `localBattleUserState` / `partner` 是内联 IIFE(记为 `(inline)`,动作名暂缺);
|
||||
`launchParams` 是纯透传(`(pass-through)`,无 reducer)。
|
||||
|
||||
### S4 引擎核心
|
||||
- 模块:32088(3D 数学)、92980(渲染)、61378(ECS)、55088(物理)、36659 + 71105(资源)、36734(音频)、3569(输入)
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# TOState 字段 ↔ reducer 映射表
|
||||
|
||||
> **版本说明(2026-10)**:本文依据旧版 `names/tostate-map.json`(根状态 `w8` / 根 reducer `v8`、
|
||||
> 槽位 `app_1` / `bpp_1`)写成,与当前 `src/module_10032.js`(根状态 `z6` / `TOState`、根 reducer `k6`、
|
||||
> 槽位 `<名>_1`)符号名不同,**只用于交叉验证动作归属**。当前源码口径以 `3DTank/tools/gen_root_wiring.js`
|
||||
> 现场解析出的 `names/root_wiring.json` 与 Kotlin 侧 `App/State/RootWiring.kt` 为准。
|
||||
|
||||
本文是 `names/tostate-map.json` 的人类可读版,用途:**重写客户端时对照根状态的每个分片该由哪个 reducer / 哪些 action 驱动**。
|
||||
|
||||
## 1. TOState 是什么
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,444 @@
|
||||
// gen_root_wiring.js —— 从 module_10032 抽取「根状态 → 切片 reducer」委派表
|
||||
//
|
||||
// 产出:
|
||||
// 3DTank/names/root_wiring.json 证据表(字段 / 槽位 / 切片类 / reducer / 动作名)
|
||||
// TankiOffline/app/.../App/State/RootState.kt 原 z6 的 89 个切片字段(已迁移的用强类型,其余保留 Any?)
|
||||
// TankiOffline/app/.../App/State/RootWiring.kt 委派表 + 迁移覆盖统计
|
||||
//
|
||||
// 解析口径(全部取自源码,不猜):
|
||||
// · 根 reducer:body 含 `return n.cti(` 的 function(当前是 k6)
|
||||
// · 根状态类:定义 .cti 的类(当前是 z6),字段顺序 = 构造函数参数顺序
|
||||
// · 槽位:构造函数里的 `(this.<slot>_1 = <param>)`
|
||||
// · 字段名:toString 里的 `"<field>=" + this.<slot>_1`
|
||||
// · 委派:根 reducer 顶层赋值 `x = <reducer>(t, n.<slot>…)`;hF(t, n) 这种传整个状态的,
|
||||
// 到被调函数体里找它实际读的 n.<slot>
|
||||
// · 未具名形态:内联 IIFE 记 `(inline)`,纯 `n.<slot>` 透传记 `(pass-through)`
|
||||
// · 动作:被调 reducer 顶层 `instanceof <Local>` → action_inventory 里的类名
|
||||
//
|
||||
// 用法:cd 3DTank/tools && node gen_root_wiring.js
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const P = path.resolve(__dirname, '..');
|
||||
const T = fs.readFileSync(path.join(P, 'src', 'module_10032.js'), 'utf8');
|
||||
const Inv = JSON.parse(fs.readFileSync(path.join(P, 'names', 'action_inventory_10032.json'), 'utf8'));
|
||||
const LocalToName = new Map();
|
||||
for (const R of Inv.Rows) if (!LocalToName.has(R.Local)) LocalToName.set(R.Local, R.Name);
|
||||
// module_10849 的 Redux 初始化动作(本模块只 import,不在 action_inventory_10032 里)
|
||||
LocalToName.set('iz', 'InitState');
|
||||
|
||||
// ---------------------------------------------------------------- 源码扫描基础
|
||||
|
||||
/** 字符串/注释感知的花括号配对。 */
|
||||
function BracePairs(Text) {
|
||||
const Pairs = new Map();
|
||||
const Stack = [];
|
||||
let I = 0;
|
||||
while (I < Text.length) {
|
||||
const C = Text[I];
|
||||
if (C === '"' || C === "'" || C === '`') {
|
||||
const Q = C;
|
||||
I++;
|
||||
while (I < Text.length) {
|
||||
if (Text[I] === '\\') { I += 2; continue; }
|
||||
if (Text[I] === Q) { I++; break; }
|
||||
I++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (C === '/' && Text[I + 1] === '/') { const N = Text.indexOf('\n', I); I = N < 0 ? Text.length : N; continue; }
|
||||
if (C === '/' && Text[I + 1] === '*') { const N = Text.indexOf('*/', I + 2); I = N < 0 ? Text.length : N + 2; continue; }
|
||||
if (C === '{') Stack.push(I);
|
||||
else if (C === '}') { const O = Stack.pop(); if (O !== undefined) Pairs.set(O, I); }
|
||||
I++;
|
||||
}
|
||||
return Pairs;
|
||||
}
|
||||
const Pairs = BracePairs(T);
|
||||
|
||||
/** 收集所有 function 与 `X.m = function` 的体区间。 */
|
||||
const Fns = [];
|
||||
for (const M of T.matchAll(/(?<![\w$.])function ([A-Za-z0-9_$]+)\(([^)]*)\)\s*\{/g)) {
|
||||
const Open = M.index + M[0].length - 1;
|
||||
const End = Pairs.get(Open);
|
||||
if (End !== undefined) Fns.push({ Name: M[1], Params: M[2], Start: M.index, Body: Open + 1, End });
|
||||
}
|
||||
const Methods = [];
|
||||
for (const M of T.matchAll(/\(([A-Za-z_$][\w$]*)\(([A-Za-z0-9_$]+)\)\.([A-Za-z0-9_$]+) = function \(([^)]*)\)\s*\{/g)) {
|
||||
const Open = M.index + M[0].length - 1;
|
||||
const End = Pairs.get(Open);
|
||||
if (End !== undefined) {
|
||||
Methods.push({ Obj: M[2], Method: M[3], Params: M[4], Start: M.index, Body: Open + 1, End });
|
||||
}
|
||||
}
|
||||
function FindFn(Name) { return Fns.find((F) => F.Name === Name) || null; }
|
||||
function FindMethod(Ref) {
|
||||
const Dot = Ref.lastIndexOf('.');
|
||||
if (Dot < 0) return null;
|
||||
const Obj = Ref.slice(0, Dot);
|
||||
const Method = Ref.slice(Dot + 1);
|
||||
return Methods.find((M) => M.Obj === Obj && M.Method === Method) || null;
|
||||
}
|
||||
|
||||
/** 把 [From, To) 内的嵌套 function 体挖空,只留顶层分支(避免把 IIFE 里的 instanceof 算进来)。 */
|
||||
function MaskNested(Text, From, To) {
|
||||
const Out = Text.split('');
|
||||
for (const F of Fns) {
|
||||
if (F.Start > From && F.End < To) {
|
||||
for (let I = F.Start; I <= F.End; I++) Out[I] = ' ';
|
||||
}
|
||||
}
|
||||
for (const M of Methods) {
|
||||
if (M.Start > From && M.End < To) {
|
||||
for (let I = M.Start; I <= M.End; I++) Out[I] = ' ';
|
||||
}
|
||||
}
|
||||
return Out.slice(From, To).join('');
|
||||
}
|
||||
|
||||
/** 顶层 `name = expr` 赋值;返回 [{Name, Expr}](expr 已 trim)。 */
|
||||
function TopLevelAssignments(Body) {
|
||||
const Out = [];
|
||||
let I = 0;
|
||||
let Depth = 0;
|
||||
const Strings = [];
|
||||
while (I < Body.length) {
|
||||
const C = Body[I];
|
||||
if (C === '"' || C === "'" || C === '`') {
|
||||
const Q = C;
|
||||
I++;
|
||||
while (I < Body.length) {
|
||||
if (Body[I] === '\\') { I += 2; continue; }
|
||||
if (Body[I] === Q) { I++; break; }
|
||||
I++;
|
||||
}
|
||||
Strings.push(I);
|
||||
continue;
|
||||
}
|
||||
if (C === '(' || C === '[' || C === '{') { Depth++; I++; continue; }
|
||||
if (C === ')' || C === ']' || C === '}') { Depth--; I++; continue; }
|
||||
if (Depth === 0) {
|
||||
const M = /^([A-Za-z_$][\w$]*)\s*=\s*/.exec(Body.slice(I));
|
||||
if (M && (I === 0 || /[;{}\s]/.test(Body[I - 1]))) {
|
||||
const Name = M[1];
|
||||
let J = I + M[0].length;
|
||||
let D = 0;
|
||||
while (J < Body.length) {
|
||||
const D0 = Body[J];
|
||||
if (D0 === '"' || D0 === "'" || D0 === '`') {
|
||||
const Q = D0;
|
||||
J++;
|
||||
while (J < Body.length) {
|
||||
if (Body[J] === '\\') { J += 2; continue; }
|
||||
if (Body[J] === Q) { J++; break; }
|
||||
J++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (D0 === '(' || D0 === '[' || D0 === '{') D++;
|
||||
else if (D0 === ')' || D0 === ']' || D0 === '}') D--;
|
||||
else if (D0 === ',' && D === 0) break;
|
||||
else if (D0 === ';' && D === 0) break;
|
||||
J++;
|
||||
}
|
||||
Out.push({ Name, Expr: Body.slice(I + M[0].length, J).trim() });
|
||||
I = J + 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
I++;
|
||||
}
|
||||
return Out;
|
||||
}
|
||||
|
||||
/** 定位根 reducer / 根状态类。 */
|
||||
const RootReducerFn = Fns.find((F) => T.slice(F.Body, F.End).includes('return n.cti('));
|
||||
if (!RootReducerFn) throw new Error('找不到根 reducer(body 含 return n.cti()');
|
||||
const CopyMethod = Methods.find((M) => M.Method === 'cti');
|
||||
if (!CopyMethod) throw new Error('找不到根状态类的 cti');
|
||||
const RootClass = CopyMethod.Obj;
|
||||
const RootClassFn = FindFn(RootClass);
|
||||
if (!RootClassFn) throw new Error('找不到根状态类构造函数 ' + RootClass);
|
||||
|
||||
// ---------------------------------------------------------------- 根状态:参数 / 槽位 / 字段
|
||||
|
||||
const Params = RootClassFn.Params.split(',').map((S) => S.trim()).filter(Boolean);
|
||||
const CtorBody = T.slice(RootClassFn.Body, RootClassFn.End);
|
||||
|
||||
const SlotOfParam = new Map();
|
||||
for (const M of CtorBody.matchAll(/\(this\.(\w+) = (\w+)\)/g)) SlotOfParam.set(M[2], M[1]);
|
||||
|
||||
const DefaultOfParam = new Map();
|
||||
for (const M of CtorBody.matchAll(/\((\w+) = \1 === cd \? ([\s\S]*?) : \1\)/g)) {
|
||||
DefaultOfParam.set(M[1], M[2].replace(/\s+/g, ' ').trim());
|
||||
}
|
||||
for (const M of CtorBody.matchAll(/\((\w+) = \1 !== cd && \1\)/g)) DefaultOfParam.set(M[1], 'false');
|
||||
|
||||
const ToStringStart = T.indexOf(`(ld(${RootClass}).toString = function`);
|
||||
if (ToStringStart < 0) throw new Error('找不到根状态类 toString');
|
||||
const ToStringEnd = T.indexOf(`(ld(${RootClass}).hashCode`, ToStringStart);
|
||||
const ToStringBody = T.slice(ToStringStart, ToStringEnd);
|
||||
|
||||
const Fields = [];
|
||||
for (const M of ToStringBody.matchAll(/"([^"]*?)([A-Za-z0-9_]+)="\s*\+\s*(?:[A-Za-z_$][\w$]*\()?this\.(\w+)/g)) {
|
||||
Fields.push({ Field: M[2], Slot: M[3] });
|
||||
}
|
||||
if (Fields.length !== Params.length) {
|
||||
throw new Error(`字段数 ${Fields.length} 与构造参数数 ${Params.length} 不一致`);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- 委派表达式
|
||||
|
||||
const ReduceBody = T.slice(RootReducerFn.Body, RootReducerFn.End);
|
||||
const Assignments = TopLevelAssignments(ReduceBody);
|
||||
|
||||
/** 从表达式里找出所有 n.<slot> 引用。 */
|
||||
function SlotsIn(Expr) {
|
||||
return [...new Set([...Expr.matchAll(/\bn\.([A-Za-z0-9_$]+)/g)].map((M) => M[1]))];
|
||||
}
|
||||
|
||||
/** 单例持有者 → 实际类,例如 `(Ke = new pD())`、`(nu = new MD())`。 */
|
||||
const SingletonClass = new Map();
|
||||
for (const M of T.matchAll(/\(([A-Za-z_$][\w$]*) = new ([A-Za-z_$][\w$]*)\(\)\)/g)) {
|
||||
SingletonClass.set(M[1], M[2]);
|
||||
}
|
||||
|
||||
/** `X()` 形式的工厂 → 它返回的类,例如 `function M8() { return (null == X && new N8(), X); }`。 */
|
||||
function FactoryClass(Name) {
|
||||
const F = FindFn(Name);
|
||||
if (!F) return null;
|
||||
const Body = T.slice(F.Body, F.End);
|
||||
const M = /new ([A-Za-z_$][\w$]*)\(/.exec(Body);
|
||||
return M ? M[1] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 表达式对应的 reducer 名字(`M8().qtd(t, n.ypu_1)` → `M8().qtd`)。
|
||||
* 表达式以别的调用开头、但结尾仍是 `<holder>.<method>(t, n.<slot>)` 时取末尾调用,
|
||||
* 例如 quests 的 `(null == zp && new Yrt(), zp).otg(t, n.npv_1)` → `zp.otg`;真 IIFE → `(inline)`。
|
||||
*/
|
||||
function ReducerRef(Expr) {
|
||||
// 懒汉单例:`(null == zp && new Yrt(), zp).otg(t, n.npv_1)` → `Yrt.otg`
|
||||
const Lazy = /^\(null == [A-Za-z_$][\w$]* && new ([A-Za-z_$][\w$]*)\(\), [A-Za-z_$][\w$]*\)\.([A-Za-z_$][\w$]*)\(/.exec(Expr);
|
||||
if (Lazy) return Lazy[1] + '.' + Lazy[2];
|
||||
// 纯透传:`at = n.hpx_1`(切片没有 reducer,值原样带过去)
|
||||
if (/^n\.[A-Za-z0-9_$]+$/.test(Expr)) return '(pass-through)';
|
||||
const Head = /^([A-Za-z_$][\w$]*)(\(\))?(?:\.([A-Za-z_$][\w$]*))?\s*\(/.exec(Expr);
|
||||
if (Head && (Head[3] || /^\(\s*t\s*,/.test(Expr.slice(Head[0].length - 1)))) {
|
||||
if (Head[3]) return Head[1] + (Head[2] || '') + '.' + Head[3];
|
||||
return Head[1];
|
||||
}
|
||||
const Tail = /([A-Za-z_$][\w$]*(?:\(\))?\.[A-Za-z_$][\w$]*)\(t,\s*n\.\w+\)\s*$/.exec(Expr);
|
||||
if (Tail) return Tail[1];
|
||||
return Head ? Head[1] + (Head[2] || '') : '(inline)';
|
||||
}
|
||||
|
||||
/** 取 reducer 体(function / X().m / X.m;hF 这种传整个状态的也能处理)。 */
|
||||
function BodyOfRef(Ref) {
|
||||
if (Ref.startsWith('(')) return null;
|
||||
const Dot = Ref.lastIndexOf('.');
|
||||
if (Dot < 0) {
|
||||
const F = FindFn(Ref);
|
||||
return F ? { From: F.Body, To: F.End } : null;
|
||||
}
|
||||
const ObjRaw = Ref.slice(0, Dot);
|
||||
const Method = Ref.slice(Dot + 1).replace(/\(\)$/, '');
|
||||
const IsFactory = /\(\)$/.test(ObjRaw);
|
||||
const Obj = ObjRaw.replace(/\(\)$/, '');
|
||||
let ClassName = null;
|
||||
if (IsFactory) ClassName = FactoryClass(Obj);
|
||||
if (!ClassName) ClassName = SingletonClass.get(Obj) || null;
|
||||
if (!ClassName) ClassName = Obj;
|
||||
const M = Methods.find((X) => X.Obj === ClassName && X.Method === Method) ||
|
||||
Methods.find((X) => X.Obj === Obj && X.Method === Method);
|
||||
return M ? { From: M.Body, To: M.End } : null;
|
||||
}
|
||||
|
||||
/** reducer 顶层 instanceof 命中的动作名(没有 toString 的类记作 `?<Local>`)。 */
|
||||
function ActionsOf(Ref) {
|
||||
const B = BodyOfRef(Ref);
|
||||
if (!B) return [];
|
||||
const Masked = MaskNested(T, B.From, B.To);
|
||||
const Names = new Set();
|
||||
for (const M of Masked.matchAll(/instanceof ([A-Za-z0-9_$]+)/g)) {
|
||||
const N = LocalToName.get(M[1]);
|
||||
Names.add(N || '?' + M[1]);
|
||||
}
|
||||
return [...Names].sort();
|
||||
}
|
||||
|
||||
// 槽位 → 委派 { Reducer, Expr, Slots }
|
||||
const WiringBySlot = new Map();
|
||||
for (const A of Assignments) {
|
||||
const Slots = SlotsIn(A.Expr);
|
||||
if (!Slots.length) continue;
|
||||
for (const S of Slots) {
|
||||
if (!WiringBySlot.has(S)) {
|
||||
WiringBySlot.set(S, { Reducer: ReducerRef(A.Expr), Expr: A.Expr.replace(/\s+/g, ' ').trim(), Local: A.Name });
|
||||
}
|
||||
}
|
||||
}
|
||||
// hF(t, n):表达式里没有 n.<slot>,到被调体里找它读的槽位
|
||||
for (const A of Assignments) {
|
||||
if (SlotsIn(A.Expr).length) continue;
|
||||
const Ref = ReducerRef(A.Expr);
|
||||
const B = BodyOfRef(Ref);
|
||||
if (!B) continue;
|
||||
const Slots = SlotsIn(T.slice(B.From, B.To));
|
||||
for (const S of Slots) {
|
||||
if (!WiringBySlot.has(S)) {
|
||||
WiringBySlot.set(S, { Reducer: Ref, Expr: A.Expr.replace(/\s+/g, ' ').trim(), Local: A.Name });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- 汇总
|
||||
|
||||
const Rows = Fields.map((F) => {
|
||||
const W = WiringBySlot.get(F.Slot);
|
||||
const Param = Params.find((P) => SlotOfParam.get(P) === F.Slot) || null;
|
||||
return {
|
||||
Field: F.Field,
|
||||
Slot: F.Slot,
|
||||
SliceClass: null, // 由下面补(切片类名在构造函数默认值 / toString 里没有,靠 reducer 签名查)
|
||||
Reducer: W ? W.Reducer : '(未识别)',
|
||||
Delegate: W ? W.Expr.replace(/\s+/g, ' ').trim().slice(0, 500) : '',
|
||||
Actions: W ? ActionsOf(W.Reducer) : [],
|
||||
Default: Param ? DefaultOfParam.get(Param) || '' : '',
|
||||
};
|
||||
});
|
||||
|
||||
// 切片类:从 reducer 所在类的 `X(new <Slice>)` 或根状态构造默认 `new <Slice>()` 取
|
||||
const SliceClassOfParam = new Map();
|
||||
for (const [Param, Default] of DefaultOfParam) {
|
||||
const M = /^new ([A-Za-z0-9_$]+)\(/.exec(Default);
|
||||
if (M) SliceClassOfParam.set(Param, M[1]);
|
||||
}
|
||||
for (const R of Rows) {
|
||||
const Param = Params.find((P) => SlotOfParam.get(P) === R.Slot);
|
||||
R.SliceClass = Param ? SliceClassOfParam.get(Param) || null : null;
|
||||
}
|
||||
|
||||
// 根 reducer 里没有出现过的槽位(例如被包在更大表达式里)
|
||||
for (const R of Rows) if (R.Reducer === '(未识别)') console.warn('未识别委派: ' + R.Field + ' / ' + R.Slot);
|
||||
|
||||
// ---------------------------------------------------------------- 迁移标记(与 Kotlin 侧保持一致)
|
||||
|
||||
const Migrated = {
|
||||
inviteState: { Type: 'InviteStateSlice', Reduce: 'ReduceInvite' },
|
||||
loginByPassword: { Type: 'LoginByPasswordStateSlice', Reduce: 'ReduceLoginByPassword' },
|
||||
twitchState: { Type: 'TwitchStateSlice', Reduce: 'ReduceTwitch' },
|
||||
};
|
||||
|
||||
for (const R of Rows) R.Migrated = Object.prototype.hasOwnProperty.call(Migrated, R.Field);
|
||||
|
||||
// 字段名 → Kotlin 属性名(首字母大写,保留原有大小写与数字)
|
||||
function KotlinName(Field) { return Field.charAt(0).toUpperCase() + Field.slice(1); }
|
||||
|
||||
/** JSON 字符串 → Kotlin 字符串字面量(`$` 要转义,否则会被当成模板起点)。 */
|
||||
function KStr(S) { return JSON.stringify(String(S)).replace(/\$/g, '\\$'); }
|
||||
|
||||
const SeenProps = new Set();
|
||||
for (const R of Rows) {
|
||||
const N = KotlinName(R.Field);
|
||||
if (SeenProps.has(N)) throw new Error('Kotlin 属性名重复: ' + N);
|
||||
SeenProps.add(N);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- 输出
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(P, 'names', 'root_wiring.json'),
|
||||
JSON.stringify(
|
||||
{
|
||||
generatedAt: new Date().toISOString(),
|
||||
module: 'module_10032.js',
|
||||
RootState: { Class: RootClass, Reducer: RootReducerFn.Name, FieldCount: Rows.length },
|
||||
Migrated: Object.keys(Migrated),
|
||||
Rows,
|
||||
},
|
||||
null,
|
||||
2
|
||||
) + '\n'
|
||||
);
|
||||
|
||||
const KT = path.resolve(__dirname, '..', '..', 'TankiOffline', 'app', 'src', 'jsMain', 'kotlin', 'App', 'State');
|
||||
fs.mkdirSync(KT, { recursive: true });
|
||||
|
||||
const Lines = [];
|
||||
Lines.push('// 本文件由 3DTank/tools/gen_root_wiring.js 生成,不要手改。');
|
||||
Lines.push(`// 来源:module_10032 的根状态 ${RootClass}(toString 名 TOState,${Rows.length} 个切片)`);
|
||||
Lines.push('// 与根 reducer ' + RootReducerFn.Name + '(逐切片归约后 copy 出新状态)。');
|
||||
Lines.push('// 已迁移的切片用强类型;其余保持 Any?(原样透传),迁移一个改一个再重新生成。');
|
||||
Lines.push('package App.State');
|
||||
Lines.push('');
|
||||
Lines.push('import App.State.Reducers.InviteStateSlice');
|
||||
Lines.push('import App.State.Reducers.LoginByPasswordStateSlice');
|
||||
Lines.push('import App.State.Reducers.TwitchStateSlice');
|
||||
Lines.push('');
|
||||
Lines.push('/**');
|
||||
Lines.push(' * 原 `' + RootClass + '` / `TOState`:' + Rows.length + ' 个切片字段,顺序与 toString 一致。');
|
||||
Lines.push(' * 未迁移字段默认 null —— 原包构造时会给它们 `new <Slice>()`,这里等对应切片迁移后再补默认值。');
|
||||
Lines.push(' */');
|
||||
Lines.push('data class RootState(');
|
||||
for (const R of Rows) {
|
||||
const Prop = KotlinName(R.Field);
|
||||
const M = Migrated[R.Field];
|
||||
if (M) {
|
||||
Lines.push(` /** 原 ${R.Field}(${R.SliceClass || '?'} / ${R.Reducer})。已迁移。 */`);
|
||||
Lines.push(` val ${Prop}: ${M.Type} = ${M.Type}(),`);
|
||||
} else {
|
||||
Lines.push(` /** 原 ${R.Field}(${R.SliceClass || '?'} / ${R.Reducer})。未迁移:原样透传。 */`);
|
||||
Lines.push(` val ${Prop}: Any? = null,`);
|
||||
}
|
||||
}
|
||||
Lines.push(')');
|
||||
Lines.push('');
|
||||
fs.writeFileSync(path.join(KT, 'RootState.kt'), Lines.join('\n'));
|
||||
|
||||
const WLines = [];
|
||||
WLines.push('// 本文件由 3DTank/tools/gen_root_wiring.js 生成,不要手改。');
|
||||
WLines.push('// 证据表:module_10032 根 reducer ' + RootReducerFn.Name + ' 的逐切片委派(字段 / 槽位 / 切片类 / reducer / 动作)。');
|
||||
WLines.push('package App.State');
|
||||
WLines.push('');
|
||||
WLines.push('/** 一个切片在根状态里的委派项。 */');
|
||||
WLines.push('data class SliceWiring(');
|
||||
WLines.push(' val Field: String,');
|
||||
WLines.push(' val Slot: String,');
|
||||
WLines.push(' val SliceClass: String,');
|
||||
WLines.push(' val Reducer: String,');
|
||||
WLines.push(' val Delegate: String,');
|
||||
WLines.push(' val Actions: List<String>,');
|
||||
WLines.push(')');
|
||||
WLines.push('');
|
||||
WLines.push('object RootWiring {');
|
||||
WLines.push(' val All: List<SliceWiring> = listOf(');
|
||||
for (const R of Rows) {
|
||||
const Acts = R.Actions.map((A) => KStr(A)).join(', ');
|
||||
const Parts = [
|
||||
KStr(R.Field),
|
||||
KStr(R.Slot),
|
||||
KStr(R.SliceClass || '?'),
|
||||
KStr(R.Reducer),
|
||||
KStr(R.Delegate),
|
||||
'listOf(' + Acts + ')',
|
||||
];
|
||||
WLines.push(' SliceWiring(' + Parts.join(', ') + '),');
|
||||
}
|
||||
WLines.push(' )');
|
||||
WLines.push('');
|
||||
WLines.push(' /** 已迁移(由 RootReducer 真正归约)的字段。 */');
|
||||
WLines.push(' val Migrated: Set<String> = setOf(' + Object.keys(Migrated).map((F) => KStr(F)).join(', ') + ')');
|
||||
WLines.push('');
|
||||
WLines.push(' /** 未迁移字段:归约时原样透传,作为后续迁移清单。 */');
|
||||
WLines.push(' val Unmigrated: List<SliceWiring> get() = All.filter { it.Field !in Migrated }');
|
||||
WLines.push('');
|
||||
WLines.push(' fun ByField(Field: String): SliceWiring? = All.firstOrNull { it.Field == Field }');
|
||||
WLines.push('}');
|
||||
WLines.push('');
|
||||
fs.writeFileSync(path.join(KT, 'RootWiring.kt'), WLines.join('\n'));
|
||||
|
||||
console.log(`根状态 ${RootClass}:${Rows.length} 个切片;已迁移 ${Object.keys(Migrated).length} 个,未迁移 ${Rows.length - Object.keys(Migrated).length} 个`);
|
||||
console.log('未迁移清单:' + Rows.filter((R) => !R.Migrated).map((R) => R.Field).join(', '));
|
||||
@@ -0,0 +1,87 @@
|
||||
// LoginReducer.kt —— 密码登录切片(对应 module_10032 的 gD 状态 + MD.cr6 reducer)
|
||||
//
|
||||
// 证据链:
|
||||
// 状态类 gD:`function gD(t, n, i, r, e, u, s) { t = t === cd ? "" : t; n = n === cd ? "" : n;
|
||||
// i = i === cd || i; r = r === cd ? OD() : r; e = e !== cd && e; u = u !== cd && u;
|
||||
// s = s === cd ? 0 : s; … }`
|
||||
// toString:`LoginByPasswordState(login=…, password=…, rememberMe=…, status=…,
|
||||
// onlyLogin=…, incorrectLogin2FA=…, requestsNumber2FA=…)`
|
||||
// 枚举 kD(由 mD() 初始化):NONE / LOGGING_IN / WRONG_PASSWORD / WRONG_CAPTCHA;OD() 返回 NONE
|
||||
// reducer MD.cr6(module_10032:28051 附近):
|
||||
// PD → new gD()(整个切片复位)
|
||||
// RD → copy(status = NONE) // 原包导出为 $_$ff,UI 在 82347 派发
|
||||
// SetLogin → copy(login, status = NONE) // 注意:改 login 会把 status 打回 NONE
|
||||
// SetPassword → copy(password, status = NONE)
|
||||
// SetRememberMe → copy(rememberMe)
|
||||
// SetStatus → copy(status)
|
||||
// ND → copy(login = t.dr6_1, onlyLogin = true)
|
||||
// IncorrectLogin2FA → copy(incorrectLogin2FA)
|
||||
// RequestsNumber2FA → copy(requestsNumber2FA)
|
||||
// 其它 → 原状态
|
||||
//
|
||||
// 原类的 PD / RD / ND 都没有 toString(不是动作目录里的具名动作),这里按语义命名并在注释里保留原名。
|
||||
package App.State.Reducers
|
||||
|
||||
import App.State.IncorrectLogin2FA
|
||||
import App.State.RequestsNumber2FA
|
||||
import App.State.SetLogin
|
||||
import App.State.SetPassword
|
||||
import App.State.SetRememberMe
|
||||
import App.State.SetStatus
|
||||
|
||||
/** 原 `kD`(module_10032 的密码登录状态枚举);`SetStatus` 的字段名是 `byPasswordStatus`。 */
|
||||
enum class ByPasswordStatus {
|
||||
NONE,
|
||||
LOGGING_IN,
|
||||
WRONG_PASSWORD,
|
||||
WRONG_CAPTCHA,
|
||||
;
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* 动作字段在运行期是 JS 枚举对象,toString 就是成员名(`SetStatus(byPasswordStatus=NONE)`)。
|
||||
* 与原包一致:认不出时落到默认值 NONE。
|
||||
*/
|
||||
fun FromText(Text: Any?): ByPasswordStatus =
|
||||
entries.firstOrNull { it.name == Text?.toString() } ?: NONE
|
||||
}
|
||||
}
|
||||
|
||||
/** 原 `PD`:无字段、无 toString,reducer 分支里整片复位。 */
|
||||
class ResetLoginByPassword
|
||||
|
||||
/** 原 `RD`:无字段、无 toString;原包以 `$_$ff` 导出,UI 派发它把状态打回 NONE。 */
|
||||
class ResetLoginByPasswordStatus
|
||||
|
||||
/** 原 `ND`:只有一个字段 `dr6_1`;reducer 用它做「只登录」模式(login + onlyLogin=true)。 */
|
||||
class LoginOnly(val login: String)
|
||||
|
||||
/**
|
||||
* 原 `gD`:`LoginByPasswordState(login, password, rememberMe, status, onlyLogin, incorrectLogin2FA, requestsNumber2FA)`。
|
||||
* 默认值全部取自原构造函数:`"" / "" / true / NONE / false / false / 0`。
|
||||
*/
|
||||
data class LoginByPasswordStateSlice(
|
||||
val Login: String = "",
|
||||
val Password: String = "",
|
||||
val RememberMe: Boolean = true,
|
||||
val Status: ByPasswordStatus = ByPasswordStatus.NONE,
|
||||
val OnlyLogin: Boolean = false,
|
||||
val IncorrectLogin2FA: Boolean = false,
|
||||
val RequestsNumber2FA: Int = 0,
|
||||
)
|
||||
|
||||
/** 原 `MD.cr6`。 */
|
||||
fun ReduceLoginByPassword(State0: LoginByPasswordStateSlice, A: Any): LoginByPasswordStateSlice = when (A) {
|
||||
is ResetLoginByPassword -> LoginByPasswordStateSlice()
|
||||
// RD:只把 status 复位,其余字段保留
|
||||
is ResetLoginByPasswordStatus -> State0.copy(Status = ByPasswordStatus.NONE)
|
||||
// SetLogin / SetPassword 都会顺带把 status 打回 NONE(原包 `pr5(…, OD())`)
|
||||
is SetLogin -> State0.copy(Login = A.login?.toString() ?: "", Status = ByPasswordStatus.NONE)
|
||||
is SetPassword -> State0.copy(Password = A.password?.toString() ?: "", Status = ByPasswordStatus.NONE)
|
||||
is SetRememberMe -> State0.copy(RememberMe = A.rememberMe)
|
||||
is SetStatus -> State0.copy(Status = ByPasswordStatus.FromText(A.byPasswordStatus))
|
||||
is LoginOnly -> State0.copy(Login = A.login, OnlyLogin = true)
|
||||
is IncorrectLogin2FA -> State0.copy(IncorrectLogin2FA = A.incorrectLogin2FA)
|
||||
is RequestsNumber2FA -> State0.copy(RequestsNumber2FA = A.number)
|
||||
else -> State0
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// TwitchReducer.kt —— Twitch 绑定切片(对应 module_10032 的 g9 状态 + y9.gta reducer)
|
||||
//
|
||||
// 证据链:
|
||||
// 状态类 g9:`function g9(t, n, i) { t = t !== cd && t; n = n === cd ? null : n; i = i === cd ? 0 : i; … }`
|
||||
// toString:`TwitchState(isTwitchEnabled=…, twitchAccount=…, daysForNextLinking=…)`
|
||||
// reducer y9.gta(module_10032:41442):
|
||||
// SetTwitchEnabled → copy(isTwitchEnabled = t.dta_1)
|
||||
// SetAccount → copy(twitchAccount = t.eta_1) // 这里的 SetAccount 是「Twitch 账号」,不是用户 UID
|
||||
// SetDaysForNextLinking → copy(daysForNextLinking = t.fta_1)
|
||||
// 其它 → 原状态
|
||||
//
|
||||
// 注意:动作目录里另有一个同名 `SetAccount` 的候选,靠样例 `SetAccount(account=null)` + 本 reducer 的
|
||||
// `instanceof z9` 才能锁定;`SetAccountRemovalDays` 之类属于别的切片。
|
||||
package App.State.Reducers
|
||||
|
||||
import App.State.SetAccount
|
||||
import App.State.SetDaysForNextLinking
|
||||
import App.State.SetTwitchEnabled
|
||||
|
||||
/**
|
||||
* 原 `g9`:`TwitchState(isTwitchEnabled, twitchAccount, daysForNextLinking)`。
|
||||
* 默认值取自原构造函数:`false / null / 0`。
|
||||
*/
|
||||
data class TwitchStateSlice(
|
||||
val IsTwitchEnabled: Boolean = false,
|
||||
val TwitchAccount: String? = null,
|
||||
val DaysForNextLinking: Int = 0,
|
||||
)
|
||||
|
||||
/** 原 `y9.gta`。 */
|
||||
fun ReduceTwitch(State0: TwitchStateSlice, A: Any): TwitchStateSlice = when (A) {
|
||||
is SetTwitchEnabled -> State0.copy(IsTwitchEnabled = A.twitchEnabled)
|
||||
is SetAccount -> State0.copy(TwitchAccount = A.account)
|
||||
is SetDaysForNextLinking -> State0.copy(DaysForNextLinking = A.days)
|
||||
else -> State0
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// RootReducer.kt —— 根 reducer 骨架(对应 module_10032 的根状态 z6 / TOState 与根 reducer k6)
|
||||
//
|
||||
// 原包 k6 对 89 个切片逐个调用各自的 reducer,再 `return n.cti(...)`(copy 出新状态)。
|
||||
// 这里采用同一形状,但只真正归约已迁移的切片:
|
||||
//
|
||||
// inviteState → ReduceInvite(aD / pD.er5)
|
||||
// loginByPassword → ReduceLoginByPassword(gD / MD.cr6)
|
||||
// twitchState → ReduceTwitch(g9 / y9.gta)
|
||||
//
|
||||
// 其余 86 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态,
|
||||
// 所以一次 copy 里串起全部已迁移 reducer,与按动作路由再归约的结果一致。
|
||||
// 迁移进度与每个字段的证据见生成物 RootWiring.kt(不要手改)。
|
||||
package App.State
|
||||
|
||||
import App.State.Reducers.ReduceInvite
|
||||
import App.State.Reducers.ReduceLoginByPassword
|
||||
import App.State.Reducers.ReduceTwitch
|
||||
|
||||
/** 根 reducer:`k6`。 */
|
||||
object RootReducer {
|
||||
|
||||
/** 已迁移(真正归约)的字段名,来自生成物 RootWiring.Migrated。 */
|
||||
val Migrated: Set<String> get() = RootWiring.Migrated
|
||||
|
||||
/** 形如「已迁移 3/89」,给日志与自检用。 */
|
||||
val Coverage: String get() = "已迁移 ${Migrated.size}/${RootWiring.All.size}"
|
||||
|
||||
/**
|
||||
* 归约一次。未迁移字段原样带过去(`copy` 只改已迁移的三个)。
|
||||
*/
|
||||
fun Reduce(State0: RootState, Action: Any): RootState = State0.copy(
|
||||
InviteState = ReduceInvite(State0.InviteState, Action),
|
||||
LoginByPassword = ReduceLoginByPassword(State0.LoginByPassword, Action),
|
||||
TwitchState = ReduceTwitch(State0.TwitchState, Action),
|
||||
)
|
||||
}
|
||||
|
||||
/** 顶层入口,与切片 reducer 的 `ReduceXxx(State0, Action)` 命名保持一致。 */
|
||||
fun ReduceRoot(State0: RootState, Action: Any): RootState = RootReducer.Reduce(State0, Action)
|
||||
@@ -0,0 +1,194 @@
|
||||
// 本文件由 3DTank/tools/gen_root_wiring.js 生成,不要手改。
|
||||
// 来源:module_10032 的根状态 z6(toString 名 TOState,89 个切片)
|
||||
// 与根 reducer k6(逐切片归约后 copy 出新状态)。
|
||||
// 已迁移的切片用强类型;其余保持 Any?(原样透传),迁移一个改一个再重新生成。
|
||||
package App.State
|
||||
|
||||
import App.State.Reducers.InviteStateSlice
|
||||
import App.State.Reducers.LoginByPasswordStateSlice
|
||||
import App.State.Reducers.TwitchStateSlice
|
||||
|
||||
/**
|
||||
* 原 `z6` / `TOState`:89 个切片字段,顺序与 toString 一致。
|
||||
* 未迁移字段默认 null —— 原包构造时会给它们 `new <Slice>()`,这里等对应切片迁移后再补默认值。
|
||||
*/
|
||||
data class RootState(
|
||||
/** 原 user(t8 / M8().qtd)。未迁移:原样透传。 */
|
||||
val User: Any? = null,
|
||||
/** 原 screen(WK / dY().ks5)。未迁移:原样透传。 */
|
||||
val Screen: Any? = null,
|
||||
/** 原 modalScreen(nV / Fs.js1)。未迁移:原样透传。 */
|
||||
val ModalScreen: Any? = null,
|
||||
/** 原 battleStatistics(jR / rr.fqr)。未迁移:原样透传。 */
|
||||
val BattleStatistics: Any? = null,
|
||||
/** 原 battleTutorial(rE / Q.sq8)。未迁移:原样透传。 */
|
||||
val BattleTutorial: Any? = null,
|
||||
/** 原 garage(UB / hF)。未迁移:原样透传。 */
|
||||
val Garage: Any? = null,
|
||||
/** 原 garagePreview(IG / fs.eru)。未迁移:原样透传。 */
|
||||
val GaragePreview: Any? = null,
|
||||
/** 原 challenges(CZ / nh.hsj)。未迁移:原样透传。 */
|
||||
val Challenges: Any? = null,
|
||||
/** 原 loginByPassword(gD / nu.cr6)。已迁移。 */
|
||||
val LoginByPassword: LoginByPasswordStateSlice = LoginByPasswordStateSlice(),
|
||||
/** 原 register(Cit / cp.stg)。未迁移:原样透传。 */
|
||||
val Register: Any? = null,
|
||||
/** 原 migrationState($it / Uv.rtg)。未迁移:原样透传。 */
|
||||
val MigrationState: Any? = null,
|
||||
/** 原 inviteState(aD / Ke.er5)。已迁移。 */
|
||||
val InviteState: InviteStateSlice = InviteStateSlice(),
|
||||
/** 原 captchaState(jP / ar.iqt)。未迁移:原样透传。 */
|
||||
val CaptchaState: Any? = null,
|
||||
/** 原 passwordChangeState(VD / uu.pr6)。未迁移:原样透传。 */
|
||||
val PasswordChangeState: Any? = null,
|
||||
/** 原 system(_9 / w9().xt9)。未迁移:原样透传。 */
|
||||
val System: Any? = null,
|
||||
/** 原 quests(Frt / Yrt.otg)。未迁移:原样透传。 */
|
||||
val Quests: Any? = null,
|
||||
/** 原 chatState(LP / $r.wqv)。未迁移:原样透传。 */
|
||||
val ChatState: Any? = null,
|
||||
/** 原 clanChatState(qnt / yv.qtg)。未迁移:原样透传。 */
|
||||
val ClanChatState: Any? = null,
|
||||
/** 原 clan(jN / we.hr0)。未迁移:原样透传。 */
|
||||
val Clan: Any? = null,
|
||||
/** 原 clansStorage(lO / me.vr1)。未迁移:原样透传。 */
|
||||
val ClansStorage: Any? = null,
|
||||
/** 原 tipsState(dst / Hw.vth)。未迁移:原样透传。 */
|
||||
val TipsState: Any? = null,
|
||||
/** 原 news(ZX / E_.asd)。未迁移:原样透传。 */
|
||||
val News: Any? = null,
|
||||
/** 原 settings(Uut / xw.ltg)。未迁移:原样透传。 */
|
||||
val Settings: Any? = null,
|
||||
/** 原 accountSettings(oet / Lp.mtg)。未迁移:原样透传。 */
|
||||
val AccountSettings: Any? = null,
|
||||
/** 原 lootBoxes(aH / ws.wrv)。未迁移:原样透传。 */
|
||||
val LootBoxes: Any? = null,
|
||||
/** 原 notifications(? / B_.ise)。未迁移:原样透传。 */
|
||||
val Notifications: Any? = null,
|
||||
/** 原 friends(KD / NL().cr9)。未迁移:原样透传。 */
|
||||
val Friends: Any? = null,
|
||||
/** 原 remoteUserDataStorage($Q / vh.isl)。未迁移:原样透传。 */
|
||||
val RemoteUserDataStorage: Any? = null,
|
||||
/** 原 battlesSelect(fA / Mi.vqk)。未迁移:原样透传。 */
|
||||
val BattlesSelect: Any? = null,
|
||||
/** 原 maps(OA / Oi.sql)。未迁移:原样透传。 */
|
||||
val Maps: Any? = null,
|
||||
/** 原 matchmaking(TH / JH().qrx)。未迁移:原样透传。 */
|
||||
val Matchmaking: Any? = null,
|
||||
/** 原 battleCreateState($6 / Df.ttg)。未迁移:原样透传。 */
|
||||
val BattleCreateState: Any? = null,
|
||||
/** 原 battleSearchState(Itt / Htt().vtg)。未迁移:原样透传。 */
|
||||
val BattleSearchState: Any? = null,
|
||||
/** 原 lobbyLoader(KW / Bs.es1)。未迁移:原样透传。 */
|
||||
val LobbyLoader: Any? = null,
|
||||
/** 原 battleResult(sR / Fi.aqp)。未迁移:原样透传。 */
|
||||
val BattleResult: Any? = null,
|
||||
/** 原 battleUsers(uP / sr.hqs)。未迁移:原样透传。 */
|
||||
val BattleUsers: Any? = null,
|
||||
/** 原 suppliesState(wE / $t.fq9)。未迁移:原样透传。 */
|
||||
val SuppliesState: Any? = null,
|
||||
/** 原 matchmakingGroup(rW / NW().is0)。未迁移:原样透传。 */
|
||||
val MatchmakingGroup: Any? = null,
|
||||
/** 原 shop(j4 / (inline))。未迁移:原样透传。 */
|
||||
val Shop: Any? = null,
|
||||
/** 原 battlePauseState(dT / NT().mqc)。未迁移:原样透传。 */
|
||||
val BattlePauseState: Any? = null,
|
||||
/** 原 battleViewState(DT / sI.jqd)。未迁移:原样透传。 */
|
||||
val BattleViewState: Any? = null,
|
||||
/** 原 rankUpDialogState(LO / Se.br3)。未迁移:原样透传。 */
|
||||
val RankUpDialogState: Any? = null,
|
||||
/** 原 clanUserInfo(hO / de.gr1)。未迁移:原样透传。 */
|
||||
val ClanUserInfo: Any? = null,
|
||||
/** 原 tankActionsLogState(vI / wi.gqe)。未迁移:原样透传。 */
|
||||
val TankActionsLogState: Any? = null,
|
||||
/** 原 battleMessage(FE / vT().rqb)。未迁移:原样透传。 */
|
||||
val BattleMessage: Any? = null,
|
||||
/** 原 battleChat(Q6 / Hf.wtg)。未迁移:原样透传。 */
|
||||
val BattleChat: Any? = null,
|
||||
/** 原 droppableGold(Vet / Wp.kth)。未迁移:原样透传。 */
|
||||
val DroppableGold: Any? = null,
|
||||
/** 原 home(s1 / (inline))。未迁移:原样透传。 */
|
||||
val Home: Any? = null,
|
||||
/** 原 localBattleUserState(lE / (inline))。未迁移:原样透传。 */
|
||||
val LocalBattleUserState: Any? = null,
|
||||
/** 原 soundSettings(Det / Wet().jth)。未迁移:原样透传。 */
|
||||
val SoundSettings: Any? = null,
|
||||
/** 原 gameSettings(c2 / g2().zss)。未迁移:原样透传。 */
|
||||
val GameSettings: Any? = null,
|
||||
/** 原 battleSettings(W0 / X0().esq)。未迁移:原样透传。 */
|
||||
val BattleSettings: Any? = null,
|
||||
/** 原 graphicsSettings(? / lc.isu)。未迁移:原样透传。 */
|
||||
val GraphicsSettings: Any? = null,
|
||||
/** 原 controlSettings(i2 / _2().ess)。未迁移:原样透传。 */
|
||||
val ControlSettings: Any? = null,
|
||||
/** 原 securitySettings(V2 / pc.qsv)。未迁移:原样透传。 */
|
||||
val SecuritySettings: Any? = null,
|
||||
/** 原 purchaseDialogState(CO / je.ur2)。未迁移:原样透传。 */
|
||||
val PurchaseDialogState: Any? = null,
|
||||
/** 原 battleInviteState(att / Ttt().nth)。未迁移:原样透传。 */
|
||||
val BattleInviteState: Any? = null,
|
||||
/** 原 keyboardSettings(Sut / bw.oth)。未迁移:原样透传。 */
|
||||
val KeyboardSettings: Any? = null,
|
||||
/** 原 touchscreenHudSettings(j7 / N7().xsv)。未迁移:原样透传。 */
|
||||
val TouchscreenHudSettings: Any? = null,
|
||||
/** 原 mainQuest(WZ / ih.bsk)。未迁移:原样透传。 */
|
||||
val MainQuest: Any? = null,
|
||||
/** 原 language(? / m6().jtg)。未迁移:原样透传。 */
|
||||
val Language: Any? = null,
|
||||
/** 原 launchParams(? / (pass-through))。未迁移:原样透传。 */
|
||||
val LaunchParams: Any? = null,
|
||||
/** 原 partner(UJ / (inline))。未迁移:原样透传。 */
|
||||
val Partner: Any? = null,
|
||||
/** 原 entrance(hD / Bv.pth)。未迁移:原样透传。 */
|
||||
val Entrance: Any? = null,
|
||||
/** 原 antiAddiction(UC / W.iq8)。未迁移:原样透传。 */
|
||||
val AntiAddiction: Any? = null,
|
||||
/** 原 spectator(HA / Li.jqm)。未迁移:原样透传。 */
|
||||
val Spectator: Any? = null,
|
||||
/** 原 moderation(NI / Ei.oqh)。未迁移:原样透传。 */
|
||||
val Moderation: Any? = null,
|
||||
/** 原 rulesUpdated(i1 / Ch.dsm)。未迁移:原样透传。 */
|
||||
val RulesUpdated: Any? = null,
|
||||
/** 原 announcement(lC / PC().rq5)。未迁移:原样透传。 */
|
||||
val Announcement: Any? = null,
|
||||
/** 原 notificationByCountry(gJ / R_.ssd)。未迁移:原样透传。 */
|
||||
val NotificationByCountry: Any? = null,
|
||||
/** 原 userRenameState(O8 / Rf.pte)。未迁移:原样透传。 */
|
||||
val UserRenameState: Any? = null,
|
||||
/** 原 eventGoods(W7 / Wc.esx)。未迁移:原样透传。 */
|
||||
val EventGoods: Any? = null,
|
||||
/** 原 creatorTagState(qO / ze.er2)。未迁移:原样透传。 */
|
||||
val CreatorTagState: Any? = null,
|
||||
/** 原 twoStepVerificationState(x9 / _f.atb)。未迁移:原样透传。 */
|
||||
val TwoStepVerificationState: Any? = null,
|
||||
/** 原 rewardedVideosState(JQ / xh.zsl)。未迁移:原样透传。 */
|
||||
val RewardedVideosState: Any? = null,
|
||||
/** 原 twitchState(g9 / uf.gta)。已迁移。 */
|
||||
val TwitchState: TwitchStateSlice = TwitchStateSlice(),
|
||||
/** 原 icons(oH / vs.kru)。未迁移:原样透传。 */
|
||||
val Icons: Any? = null,
|
||||
/** 原 itemNotifications(_1 / A1().dso)。未迁移:原样透传。 */
|
||||
val ItemNotifications: Any? = null,
|
||||
/** 原 contextTutorial(Mst / Jst().tth)。未迁移:原样透传。 */
|
||||
val ContextTutorial: Any? = null,
|
||||
/** 原 suppliesTutorial(? / Uw.uth)。未迁移:原样透传。 */
|
||||
val SuppliesTutorial: Any? = null,
|
||||
/** 原 questCategories(zZ / th.lsh)。未迁移:原样透传。 */
|
||||
val QuestCategories: Any? = null,
|
||||
/** 原 proBattle(Wtt / uv.wth)。未迁移:原样透传。 */
|
||||
val ProBattle: Any? = null,
|
||||
/** 原 availabilityByRank(tet / $p.xth)。未迁移:原样透传。 */
|
||||
val AvailabilityByRank: Any? = null,
|
||||
/** 原 userReportsState(Zst / qot.zth)。未迁移:原样透传。 */
|
||||
val UserReportsState: Any? = null,
|
||||
/** 原 tankBuilderState(brt / wp.ati)。未迁移:原样透传。 */
|
||||
val TankBuilderState: Any? = null,
|
||||
/** 原 contractState(Ont / xv.bti)。未迁移:原样透传。 */
|
||||
val ContractState: Any? = null,
|
||||
/** 原 promoCode(cZ / Q_.ysg)。未迁移:原样透传。 */
|
||||
val PromoCode: Any? = null,
|
||||
/** 原 referralEvent(pQ / lh.osk)。未迁移:原样透传。 */
|
||||
val ReferralEvent: Any? = null,
|
||||
/** 原 supportUrl(I4 / Yl.ct7)。未迁移:原样透传。 */
|
||||
val SupportUrl: Any? = null,
|
||||
)
|
||||
@@ -0,0 +1,115 @@
|
||||
// 本文件由 3DTank/tools/gen_root_wiring.js 生成,不要手改。
|
||||
// 证据表:module_10032 根 reducer k6 的逐切片委派(字段 / 槽位 / 切片类 / reducer / 动作)。
|
||||
package App.State
|
||||
|
||||
/** 一个切片在根状态里的委派项。 */
|
||||
data class SliceWiring(
|
||||
val Field: String,
|
||||
val Slot: String,
|
||||
val SliceClass: String,
|
||||
val Reducer: String,
|
||||
val Delegate: String,
|
||||
val Actions: List<String>,
|
||||
)
|
||||
|
||||
object RootWiring {
|
||||
val All: List<SliceWiring> = listOf(
|
||||
SliceWiring("user", "ypu_1", "t8", "M8().qtd", "M8().qtd(t, n.ypu_1)", listOf("?R8", "?p8", "?v8", "AddCoins", "AddRubies", "SetBatteryLevel", "SetClanInfo", "SetCoins", "SetDisabledWithoutPremiumLocations", "SetHasTemporaryCash", "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("?LX", "?RX", "?cY", "?eY", "?fY", "?lY", "?oC", "?oY", "?sC", "?sY", "?uY", "DisplayMainEntranceScreen", "DisplayReauthorizationMessage", "GoTo", "PressedAnyButton")),
|
||||
SliceWiring("modalScreen", "apv_1", "nV", "Fs.js1", "Fs.js1(t, n.apv_1)", listOf("?XW", "AddModal", "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("?EU", "?FB", "?FX", "?JB", "?MU", "?NU", "?OU", "?PU", "?QU", "?RU", "?SU", "?VB", "?WB", "?aF", "?bU", "?cF", "?dU", "?eF", "?fF", "?iU", "?lF", "?pF", "?rF", "?vF", "?wU", "?yU", "AddOverdriveDescription", "AddRelatedBazooka", "AddRelatedResistanceModule", "AddToFavoriteItem", "BuyProtectionSlot", "Garage", "GarageAddItemToDepot", "GarageApplyResistanceMount", "GarageBuyDevice", "GarageDeviceTrySetInfinityLifetime", "GarageDevicesLoadedAction", "GarageRemoveDevice", "GarageRemoveDiscountForDevice", "GarageRemoveItemFromDepot", "GarageRemoveItemFromStore", "GarageResistanceUnMount", "GarageSelectAlteration", "GarageSelectItem", "GarageSelectShotSkin", "GarageSelectSkin", "GarageUpdateItemCount", "GarageUpgradeItemAction", "GarageUpgradeItemConfirm", "GarageUpgradeItemCoolDownFinished", "GarageUpgradeItemCoolDownStarted", "GoTo", "InitState", "RemoveFromFavoriteItem", "RemoveFromSale", "SetGarageSkinsAction", "SetItemRarity", "SetRestrictionMount", "SwitchingRowsScroll", "UpdateDiscount")),
|
||||
SliceWiring("garagePreview", "epv_1", "IG", "fs.eru", "fs.eru(t, n.epv_1)", listOf("?BG", "?JG", "?KG", "?OG", "?QG", "?UG", "?VG", "?XG", "?YG", "?ZG", "?nH", "?tH", "AddRequestedItem", "InitGarageResources", "ResetItem3dModelActionCache", "SetCaptureActions", "SetDrone", "SetExpectedItemId", "SetMountedItemsProperties", "SetPaint", "ShowRequest", "UpdatePreviewSettings")),
|
||||
SliceWiring("challenges", "fpv_1", "CZ", "nh.hsj", "nh.hsj(t, n.fpv_1)", listOf("?LZ", "RemoveChallenge", "SetBattlePass", "SetBattlePassPackages", "SetChallenge", "SetRewardForDialog", "SetTakenBattlePassRewardIndex", "SetTakenFreeRewardIndex", "UpdateStars")),
|
||||
SliceWiring("loginByPassword", "gpv_1", "gD", "nu.cr6", "nu.cr6(t, n.gpv_1)", listOf("?ND", "?PD", "?RD", "IncorrectLogin2FA", "RequestsNumber2FA", "SetLogin", "SetPassword", "SetRememberMe", "SetStatus")),
|
||||
SliceWiring("register", "hpv_1", "Cit", "cp.stg", "cp.stg(t, n.hpv_1)", listOf("?Bit", "?Dit", "?Fit", "?Git", "?Hit", "?Lit", "?Mit", "?Uit", "?_rt", "?hrt", "EnteredUidIsBusy", "ForceSetUid", "ReceiveOAuthUri", "SetOpenedPassword", "SetPassword", "SetPasswordRestrictions", "SetUid", "SocialNetworkAvailability", "SocialNetworkClientIds")),
|
||||
SliceWiring("migrationState", "ipv_1", "\$it", "Uv.rtg", "Uv.rtg(t, n.ipv_1)", listOf("?Bit", "?Dit", "?Fit", "?Git", "?Hit", "?Lit", "?_rt", "EnteredUidIsBusy", "ForceSetUid", "SetPassword", "SetUid")),
|
||||
SliceWiring("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("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("?Hrt", "GoTo", "QuestCategory", "SetCurrentCategory")),
|
||||
SliceWiring("chatState", "opv_1", "LP", "\$r.wqv", "\$r.wqv(t, n.opv_1)", listOf("?QP", "?XP", "?_C", "?bN", "?fN", "?lN", "?nN", "?pN", "?qN", "?sN", "?vN", "?wN", "ChangeChannel", "CleanUsersMessage", "Configure", "IgnoreUserMessages", "SaveChatMessage", "SendMessageToServer", "SetMessages", "SetSelectedUid", "ShowContextMenu", "ShowUserMessages", "UpdateTypingSpeedAntifloodParams", "UpdateUsers")),
|
||||
SliceWiring("clanChatState", "ppv_1", "qnt", "yv.qtg", "yv.qtg(t, n.ppv_1)", listOf("?\$nt", "?Tnt", "?xnt", "Configure", "DoAddMessages")),
|
||||
SliceWiring("clan", "qpv_1", "jN", "we.hr0", "we.hr0(t, n.qpv_1)", listOf("?FM", "?IM", "?KM", "?PM", "?RM", "?VM", "?YM", "?iM", "AcceptedUserViewed", "AddIncomingClan", "AddNewIncoming", "AddNewIncomingClan", "AddOutgoingClan", "AddUsersToAccepted", "AddUsersToNewAccepted", "ClanBanned", "ClanInfoLoaded", "ClanLoaded", "ClanModalVisible", "IncomingAdded", "IncomingRemoved", "IncomingUserViewed", "MembersFetched", "NewAcceptedUserNotification", "NewClanInviteNotification", "NewIncomingUserNotification", "OutgoingAdded", "OutgoingRemoved", "RemoveIncomingClan", "RemoveNewIncoming", "RemoveNewIncomingClan", "RemoveOutgoingClan", "RemoveUserFromAccepted", "RemoveUsersFromNewAccepted", "SetClanEditPrice", "SetMinRankForCreate", "UpdateClanLicense", "UpdateClanNameValidation", "UpdateClanSearchValidation", "UpdateClanTagValidation", "UpdateHasClan", "UpdateTimeToJoinRestriction", "UpdateValidation")),
|
||||
SliceWiring("clansStorage", "rpv_1", "lO", "me.vr1", "me.vr1(t, n.rpv_1)", listOf("?mO", "AddUser", "AddUsersToNewAccepted", "BuyAdditionalPlace", "ClanLoaded", "ClanUnloaded", "ForeignClanLoaded", "MinimalRankChanged", "RecruitmentChanged", "RemoveUser", "RemoveUsersFromNewAccepted", "ShowForeignClan", "UpdateClan")),
|
||||
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("?Fut", "?Gut")),
|
||||
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("lootBoxes", "wpv_1", "aH", "ws.wrv", "ws.wrv(t, n.wpv_1)", listOf("?CH", "?SH", "?gH", "?kH", "?mH", "?zH", "AddKeys", "SelectCategory", "SetContainerData", "SetCurrentContainer", "SetGoToGarage", "SetIsAccelerated", "SetIsOpening", "SetIsShopVisited", "SetKeys", "SetPossibleRewards", "SetRewards", "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")),
|
||||
SliceWiring("remoteUserDataStorage", "zpv_1", "\$Q", "vh.isl", "vh.isl(t, n.zpv_1)", listOf("UpdateClanTags", "UpdateOnlineStatus", "UpdatePremiumStatus", "UpdateRanks", "UpdateUids", "UserEnterBattle", "UserLeaveBattle", "UserLeaveGroup")),
|
||||
SliceWiring("battlesSelect", "apw_1", "fA", "Mi.vqk", "Mi.vqk(t, n.apw_1)", listOf("?dA", "?gA", "AddBattle", "AddUser", "AddUsers", "ChangeName", "EnterToBattleInfo", "InitProBattleRewardLimit", "LimitReached", "RemoveBattle", "RemoveUser", "RoundFinish", "RoundStarted", "SetReEnterProtectBattleId", "SwapTeam", "UpdateFund", "UpdateSuspicion", "UpdateTeamScore", "UpdateUserScore", "UpdateUserSuspicion")),
|
||||
SliceWiring("maps", "bpw_1", "OA", "Oi.sql", "Oi.sql(t, n.bpw_1)", listOf("AddMap")),
|
||||
SliceWiring("matchmaking", "cpw_1", "TH", "JH().qrx", "JH().qrx(t, n.cpw_1)", listOf("?WH", "?nW", "?tW", "Configure", "EnterToBattleApply", "Play", "SelectMatchmakingMode", "SetRankIndexToMinGS", "WasRegistered")),
|
||||
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("?FW", "?GW")),
|
||||
SliceWiring("battleResult", "gpw_1", "sR", "Fi.aqp", "Fi.aqp(t, n.gpw_1)", listOf("?cR", "?fR", "?lR", "DoInit", "SetHasBattleResult", "SetRewards")),
|
||||
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)", "(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()),
|
||||
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("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("home", "tpw_1", "s1", "(inline)", "(function (t, n) { return t instanceof o1 ? n.f7j(!0) : n; })(t, n.tpw_1)", listOf()),
|
||||
SliceWiring("localBattleUserState", "upw_1", "lE", "(inline)", "(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()),
|
||||
SliceWiring("soundSettings", "vpw_1", "Det", "Wet().jth", "Wet().jth(t, n.vpw_1)", listOf("?Uet", "?_C", "SetEntranceSoundEnabled", "UpdateSettings")),
|
||||
SliceWiring("gameSettings", "wpw_1", "c2", "g2().zss", "g2().zss(t, n.wpw_1)", listOf("?_C", "UpdateSettings")),
|
||||
SliceWiring("battleSettings", "xpw_1", "W0", "X0().esq", "X0().esq(t, n.xpw_1)", listOf("?_C", "UpdateSettings")),
|
||||
SliceWiring("graphicsSettings", "ypw_1", "?", "lc.isu", "lc.isu(t, n.ypw_1)", listOf("UpdateSettings")),
|
||||
SliceWiring("controlSettings", "zpw_1", "i2", "_2().ess", "_2().ess(t, n.zpw_1)", listOf("?_C", "UpdateSettings")),
|
||||
SliceWiring("securitySettings", "apx_1", "V2", "pc.qsv", "pc.qsv(t, n.apx_1)", listOf("SetProfileBlockStatus")),
|
||||
SliceWiring("purchaseDialogState", "bpx_1", "CO", "je.ur2", "je.ur2(t, n.bpx_1)", listOf("?NO", "ChangeEmailStatus", "SuccessfullyPurchase")),
|
||||
SliceWiring("battleInviteState", "cpx_1", "att", "Ttt().nth", "Ttt().nth(t, n.cpx_1)", listOf("?dtt", "EnableSetBattleInviteNotifications", "InvitationResponse", "InviteToBattle", "RemoveUserFromInvited", "SelectUserToInvite")),
|
||||
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("launchParams", "hpx_1", "?", "(pass-through)", "n.hpx_1", listOf()),
|
||||
SliceWiring("partner", "ipx_1", "UJ", "(inline)", "(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()),
|
||||
SliceWiring("entrance", "jpx_1", "hD", "Bv.pth", "Bv.pth(t, n.jpx_1)", listOf("?eit", "?fit", "?lit", "?oit", "?sit", "?uit", "SetAgeWarnShow")),
|
||||
SliceWiring("antiAddiction", "kpx_1", "UC", "W.iq8", "W.iq8(t, n.kpx_1)", listOf("?FC", "?XC", "?YC", "SetRealNameAndIdNumber", "SetValidateAntiAddictionUserDataStatus")),
|
||||
SliceWiring("spectator", "lpx_1", "HA", "Li.jqm", "Li.jqm(t, n.lpx_1)", listOf("?JA", "?XA", "?YA", "?ZA", "SetSpectator", "UpdateHudLayout")),
|
||||
SliceWiring("moderation", "mpx_1", "NI", "Ei.oqh", "Ei.oqh(t, n.mpx_1)", listOf("?FI", "?GI", "?JI", "?KI", "?QI", "?UI", "?WI", "?XI", "?YI", "?ZI", "?nA", "?tA", "?uA")),
|
||||
SliceWiring("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("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")),
|
||||
SliceWiring("creatorTagState", "spx_1", "qO", "ze.er2", "ze.er2(t, n.spx_1)", listOf("?jO", "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")),
|
||||
SliceWiring("icons", "wpx_1", "oH", "vs.kru", "vs.kru(t, n.wpx_1)", listOf("SetIcons")),
|
||||
SliceWiring("itemNotifications", "xpx_1", "_1", "A1().dso", "A1().dso(t, n.xpx_1)", listOf("?S1", "?_C", "AddItemNotification", "AddItemNotifications", "RemoveItemNotification", "UpdateItemNotification")),
|
||||
SliceWiring("contextTutorial", "ypx_1", "Mst", "Jst().tth", "Jst().tth(t, n.ypx_1)", listOf("LoadCurrentElement", "MarkElementAsViewed", "SetLoadedElements", "SetMissionToElement", "SetUnViewedElements")),
|
||||
SliceWiring("suppliesTutorial", "zpx_1", "?", "Uw.uth", "Uw.uth(t, n.zpx_1)", listOf("ActivatedInCurrentBattle", "SetLoadedElement")),
|
||||
SliceWiring("questCategories", "apy_1", "zZ", "th.lsh", "th.lsh(t, n.apy_1)", listOf("RemoveQuestCategory", "RequestQuestCategoriesStatistic", "SetQuestCategories", "SetQuestCategoriesStatistic")),
|
||||
SliceWiring("proBattle", "bpy_1", "Wtt", "uv.wth", "uv.wth(t, n.bpy_1)", listOf("Fight", "SetPassword")),
|
||||
SliceWiring("availabilityByRank", "cpy_1", "tet", "\$p.xth", "\$p.xth(t, n.cpy_1)", listOf("SetLoadedData")),
|
||||
SliceWiring("userReportsState", "dpy_1", "Zst", "qot.zth", "(null == sd && new qot(), sd).zth(t, n.dpy_1)", listOf("InitUserReports", "ReportBattleChatMessage", "ReportLobbyChatMessage", "ReportProBattleName", "SetAvailableGameReports", "SetAvailableReports")),
|
||||
SliceWiring("tankBuilderState", "epy_1", "brt", "wp.ati", "wp.ati(t, n.epy_1)", listOf("?Trt", "SetKode", "SetParts", "SetPartsInfo")),
|
||||
SliceWiring("contractState", "fpy_1", "Ont", "xv.bti", "xv.bti(t, n.fpy_1)", listOf("?Hnt", "GetReward", "Init", "SetActiveContract", "SetBattleProgress", "SetContract", "UpdateProgress")),
|
||||
SliceWiring("promoCode", "gpy_1", "cZ", "Q_.ysg", "Q_.ysg(t, n.gpy_1)", listOf("ActivatePromoCode", "SetCodeValidateStatus")),
|
||||
SliceWiring("referralEvent", "hpy_1", "pQ", "lh.osk", "lh.osk(t, n.hpy_1)", listOf("SetPromoCode", "SetReferralEventData", "SetReferrals", "SetReferrer")),
|
||||
SliceWiring("supportUrl", "ipy_1", "I4", "Yl.ct7", "Yl.ct7(t, n.ipy_1)", listOf("?R4", "SetUrl")),
|
||||
)
|
||||
|
||||
/** 已迁移(由 RootReducer 真正归约)的字段。 */
|
||||
val Migrated: Set<String> = setOf("inviteState", "loginByPassword", "twitchState")
|
||||
|
||||
/** 未迁移字段:归约时原样透传,作为后续迁移清单。 */
|
||||
val Unmigrated: List<SliceWiring> get() = All.filter { it.Field !in Migrated }
|
||||
|
||||
fun ByField(Field: String): SliceWiring? = All.firstOrNull { it.Field == Field }
|
||||
}
|
||||
@@ -6,6 +6,10 @@
|
||||
// ③ 网关:按动作类的监听器在状态更新之前触发
|
||||
// ④ 网关:模型订阅可退订;重复订阅直接失败
|
||||
// ⑤ 网关:调度器负责把派发排进引擎循环(这里用同步调度器验证)
|
||||
// ⑥ 动作目录:816 个类与 138 个真实派发名对齐
|
||||
// ⑦ 回放:真实 toString 样例的类/字段/类型校验
|
||||
// ⑧ 全目录动作类都能按 KClass 路由
|
||||
// ⑨~⑫ 切片 reducer:InviteState / LoginByPasswordState / TwitchState;⑫ 根 reducer 的 89 槽位委派
|
||||
//
|
||||
// 用法:$env:TANKIOFFLINE_STATE_TEST='1'; node …(见 TankiOffline/tools/RunStateTest.ps1)
|
||||
package App
|
||||
@@ -17,11 +21,32 @@ import State.StoreListener
|
||||
import App.State.ActionCatalog
|
||||
import App.State.ActionSamples
|
||||
import App.State.ApplyInvite
|
||||
import App.State.Reducers.ByPasswordStatus
|
||||
import App.State.Reducers.InviteStateSlice
|
||||
import App.State.Reducers.InviteStatus
|
||||
import App.State.Reducers.LoginByPasswordStateSlice
|
||||
import App.State.Reducers.LoginOnly
|
||||
import App.State.Reducers.ReduceInvite
|
||||
import App.State.Reducers.ReduceLoginByPassword
|
||||
import App.State.Reducers.ReduceTwitch
|
||||
import App.State.Reducers.ResetLoginByPassword
|
||||
import App.State.Reducers.ResetLoginByPasswordStatus
|
||||
import App.State.Reducers.TwitchStateSlice
|
||||
import App.State.ReduceRoot
|
||||
import App.State.RootReducer
|
||||
import App.State.RootState
|
||||
import App.State.RootWiring
|
||||
import App.State.IncorrectLogin2FA
|
||||
import App.State.RequestsNumber2FA
|
||||
import App.State.SetAccount
|
||||
import App.State.SetDaysForNextLinking
|
||||
import App.State.SetInviteNeed
|
||||
import App.State.SetInviteStatus
|
||||
import App.State.SetLogin
|
||||
import App.State.SetPassword
|
||||
import App.State.SetRememberMe
|
||||
import App.State.SetStatus
|
||||
import App.State.SetTwitchEnabled
|
||||
|
||||
private data class AddFrags(val fragsCount: Int)
|
||||
private data class SetScreen(val screen: String)
|
||||
@@ -259,6 +284,188 @@ object StateCheck {
|
||||
)
|
||||
}
|
||||
|
||||
// ⑩ 切片 reducer:LoginByPasswordState(原 module_10032 的 gD / MD.cr6,枚举 kD)
|
||||
run {
|
||||
val S0 = LoginByPasswordStateSlice()
|
||||
Check(
|
||||
"切片 Login:默认值与原构造一致",
|
||||
S0.Login == "" && S0.Password == "" && S0.RememberMe && S0.Status == ByPasswordStatus.NONE &&
|
||||
!S0.OnlyLogin && !S0.IncorrectLogin2FA && S0.RequestsNumber2FA == 0
|
||||
)
|
||||
|
||||
// 原包 SetLogin 分支是 `pr5(t.qr5_1, cd, cd, OD())`:写 login 的同时把 status 打回 NONE
|
||||
val S1 = ReduceLoginByPassword(
|
||||
S0.copy(Status = ByPasswordStatus.WRONG_PASSWORD),
|
||||
SetLogin("pilot"),
|
||||
)
|
||||
Check("切片 Login:SetLogin 写 login 并把 status 复位为 NONE", S1.Login == "pilot" && S1.Status == ByPasswordStatus.NONE)
|
||||
|
||||
val S2 = ReduceLoginByPassword(S1.copy(Status = ByPasswordStatus.LOGGING_IN), SetPassword("secret"))
|
||||
Check(
|
||||
"切片 Login:SetPassword 写 password 并把 status 复位为 NONE",
|
||||
S2.Password == "secret" && S2.Status == ByPasswordStatus.NONE && S2.Login == "pilot"
|
||||
)
|
||||
|
||||
val S3 = ReduceLoginByPassword(S2.copy(Status = ByPasswordStatus.WRONG_CAPTCHA), SetRememberMe(false))
|
||||
Check("切片 Login:SetRememberMe 只改 rememberMe", !S3.RememberMe && S3.Status == ByPasswordStatus.WRONG_CAPTCHA)
|
||||
|
||||
val S4 = ReduceLoginByPassword(S3, SetStatus("WRONG_PASSWORD"))
|
||||
Check("切片 Login:SetStatus 按枚举名解析", S4.Status == ByPasswordStatus.WRONG_PASSWORD)
|
||||
Check(
|
||||
"切片 Login:SetStatus 认不出时落到 NONE",
|
||||
ReduceLoginByPassword(S4, SetStatus("SOMETHING_ELSE")).Status == ByPasswordStatus.NONE
|
||||
)
|
||||
|
||||
val S5 = ReduceLoginByPassword(S4, LoginOnly("uid-only"))
|
||||
Check(
|
||||
"切片 Login:LoginOnly 写 login/onlyLogin 且保留 status",
|
||||
S5.Login == "uid-only" && S5.OnlyLogin && S5.Status == ByPasswordStatus.WRONG_PASSWORD
|
||||
)
|
||||
|
||||
val S6 = ReduceLoginByPassword(S5, IncorrectLogin2FA(true))
|
||||
val S7 = ReduceLoginByPassword(S6, RequestsNumber2FA(3))
|
||||
Check(
|
||||
"切片 Login:IncorrectLogin2FA / RequestsNumber2FA 各自只改本字段",
|
||||
S7.IncorrectLogin2FA && S7.RequestsNumber2FA == 3 && S7.Login == "uid-only"
|
||||
)
|
||||
|
||||
val S8 = ReduceLoginByPassword(S7, ResetLoginByPasswordStatus())
|
||||
Check(
|
||||
"切片 Login:ResetLoginByPasswordStatus 只清 status",
|
||||
S8.Status == ByPasswordStatus.NONE && S8.Login == "uid-only" && S8.RequestsNumber2FA == 3
|
||||
)
|
||||
|
||||
val S9 = ReduceLoginByPassword(S8, ResetLoginByPassword())
|
||||
Check("切片 Login:ResetLoginByPassword 整片复位", S9 == LoginByPasswordStateSlice())
|
||||
|
||||
val SA = ReduceLoginByPassword(S4, AddFrags(1))
|
||||
Check("切片 Login:无关动作不改状态", SA == S4)
|
||||
|
||||
val Store = ReduxStore(S0, { S, A -> ReduceLoginByPassword(S, A) })
|
||||
var Notified = false
|
||||
Store.Subscribe(StoreListener { Notified = true })
|
||||
Store.Dispatch(SetPassword("p"))
|
||||
Store.Dispatch(SetStatus("LOGGING_IN"))
|
||||
Check(
|
||||
"切片 Login:接 Store 派发后状态已归约",
|
||||
Store.State.Password == "p" && Store.State.Status == ByPasswordStatus.LOGGING_IN && Notified
|
||||
)
|
||||
}
|
||||
|
||||
// ⑪ 切片 reducer:TwitchState(原 module_10032 的 g9 / y9.gta)
|
||||
run {
|
||||
val S0 = TwitchStateSlice()
|
||||
Check(
|
||||
"切片 Twitch:默认值与原构造一致",
|
||||
!S0.IsTwitchEnabled && S0.TwitchAccount == null && S0.DaysForNextLinking == 0
|
||||
)
|
||||
|
||||
val S1 = ReduceTwitch(S0, SetTwitchEnabled(true))
|
||||
Check("切片 Twitch:SetTwitchEnabled 只改开关", S1.IsTwitchEnabled && S1.TwitchAccount == null)
|
||||
|
||||
// 样例 SetAccount(account=null) 就落在这一片:SetAccount 写的是 twitchAccount
|
||||
val S2 = ReduceTwitch(S1, SetAccount("tank_tv"))
|
||||
Check("切片 Twitch:SetAccount 写 twitchAccount", S2.TwitchAccount == "tank_tv" && S2.IsTwitchEnabled)
|
||||
Check("切片 Twitch:SetAccount(null) 清空账号", ReduceTwitch(S2, SetAccount(null)).TwitchAccount == null)
|
||||
|
||||
val S3 = ReduceTwitch(S2, SetDaysForNextLinking(7))
|
||||
Check("切片 Twitch:SetDaysForNextLinking 只改天数", S3.DaysForNextLinking == 7 && S3.TwitchAccount == "tank_tv")
|
||||
Check("切片 Twitch:无关动作不改状态", ReduceTwitch(S3, AddFrags(1)) == S3)
|
||||
|
||||
val Store = ReduxStore(S0, { S, A -> ReduceTwitch(S, A) })
|
||||
Store.Dispatch(SetDaysForNextLinking(2))
|
||||
Check("切片 Twitch:接 Store 派发后状态已归约", Store.State.DaysForNextLinking == 2)
|
||||
}
|
||||
|
||||
// ⑫ 根 reducer:89 个槽位的委派表 + 三个已迁移切片一次归约
|
||||
run {
|
||||
Check("根:根状态 89 个切片槽位", RootWiring.All.size == 89)
|
||||
Check(
|
||||
"根:字段名与槽位都唯一",
|
||||
RootWiring.All.map { it.Field }.toSet().size == RootWiring.All.size &&
|
||||
RootWiring.All.map { it.Slot }.toSet().size == RootWiring.All.size
|
||||
)
|
||||
Check(
|
||||
"根:每个槽位都有 reducer 证据",
|
||||
RootWiring.All.all { it.Reducer.isNotBlank() && it.Reducer != "(未识别)" }
|
||||
)
|
||||
Check(
|
||||
"根:只有 4 个内联 IIFE + 1 个纯透传未具名",
|
||||
RootWiring.All.count { it.Reducer == "(inline)" } == 4 &&
|
||||
RootWiring.All.count { it.Reducer == "(pass-through)" } == 1
|
||||
)
|
||||
|
||||
// 已迁移三片的委派与原包一致:nu.cr6 / Ke.er5 / uf.gta
|
||||
val LoginW = RootWiring.ByField("loginByPassword")
|
||||
val InviteW = RootWiring.ByField("inviteState")
|
||||
val TwitchW = RootWiring.ByField("twitchState")
|
||||
Check(
|
||||
"根:loginByPassword 委派到 gD / nu.cr6",
|
||||
LoginW != null && LoginW.SliceClass == "gD" && LoginW.Reducer == "nu.cr6"
|
||||
)
|
||||
Check(
|
||||
"根:inviteState 委派到 aD / Ke.er5",
|
||||
InviteW != null && InviteW.SliceClass == "aD" && InviteW.Reducer == "Ke.er5"
|
||||
)
|
||||
Check(
|
||||
"根:twitchState 委派到 g9 / uf.gta",
|
||||
TwitchW != null && TwitchW.SliceClass == "g9" && TwitchW.Reducer == "uf.gta"
|
||||
)
|
||||
Check(
|
||||
"根:迁移表 = 三个已迁移字段,其余 86 个待迁",
|
||||
RootReducer.Migrated == setOf("inviteState", "loginByPassword", "twitchState") &&
|
||||
RootWiring.Unmigrated.size == 86
|
||||
)
|
||||
|
||||
// 一次 ReduceRoot 里三个切片各归约自己的动作,互不串片
|
||||
val S0 = RootState(User = "user-1", Screen = "Preloader")
|
||||
val S1 = ReduceRoot(S0, ApplyInvite("uid-1"))
|
||||
Check(
|
||||
"根:ApplyInvite 只动 inviteState",
|
||||
S1.InviteState.Invite == "uid-1" &&
|
||||
S1.LoginByPassword == S0.LoginByPassword &&
|
||||
S1.TwitchState == S0.TwitchState
|
||||
)
|
||||
|
||||
val S2 = ReduceRoot(S1, SetLogin("pilot"))
|
||||
Check(
|
||||
"根:SetLogin 只动 loginByPassword",
|
||||
S2.LoginByPassword.Login == "pilot" &&
|
||||
S2.InviteState == S1.InviteState &&
|
||||
S2.TwitchState == S1.TwitchState
|
||||
)
|
||||
|
||||
val S3 = ReduceRoot(S2, SetAccount("tank_tv"))
|
||||
Check(
|
||||
"根:SetAccount 只动 twitchState",
|
||||
S3.TwitchState.TwitchAccount == "tank_tv" &&
|
||||
S3.InviteState == S2.InviteState &&
|
||||
S3.LoginByPassword == S2.LoginByPassword
|
||||
)
|
||||
Check(
|
||||
"根:三次归约后三个切片各自保留结果",
|
||||
S3.InviteState.Invite == "uid-1" &&
|
||||
S3.LoginByPassword.Login == "pilot" &&
|
||||
S3.TwitchState.TwitchAccount == "tank_tv"
|
||||
)
|
||||
|
||||
// 未迁移字段原样透传;无关动作不改变任何字段
|
||||
Check(
|
||||
"根:未迁移字段原样透传",
|
||||
S3.User == "user-1" && S3.Screen == "Preloader"
|
||||
)
|
||||
Check("根:无关动作返回等值状态", ReduceRoot(S3, AddFrags(1)) == S3)
|
||||
Check("根:Coverage 文本", RootReducer.Coverage == "已迁移 3/89")
|
||||
|
||||
val Store = ReduxStore(RootState(), { S, A -> ReduceRoot(S, A) })
|
||||
Store.Dispatch(SetStatus("LOGGING_IN"))
|
||||
Store.Dispatch(SetTwitchEnabled(true))
|
||||
Check(
|
||||
"根:接 Store 派发后两片同时归约",
|
||||
Store.State.LoginByPassword.Status == ByPasswordStatus.LOGGING_IN && Store.State.TwitchState.IsTwitchEnabled
|
||||
)
|
||||
}
|
||||
|
||||
val Failed = Results.filter { !it.second }
|
||||
println("TankiOffline 状态层自检:${Results.size} 项")
|
||||
for ((Name, Ok) in Results) println((if (Ok) " ✓ " else " ✗ ") + Name)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# RunStateTest.ps1 —— 跑状态层内核自检(Store / 中间件 / 网关)
|
||||
# RunStateTest.ps1 —— 跑状态层内核自检(Store / 中间件 / 网关)
|
||||
#
|
||||
# 覆盖:归约、监听器通知、中间件顺序、动作监听器时序、模型订阅退订、调度器。
|
||||
# 用法:powershell -ExecutionPolicy Bypass -File .\tools\RunStateTest.ps1
|
||||
|
||||
Reference in new issue
Block a user