feat(state): 迁移补给箱切片(18 个动作 + ContainerType)
This commit is contained in:
1 parent
974a9f49ad
commit
f66f431670
13 files changed
+606
-35
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` 动作名。
|
||||
- 已迁移切片 **7/89**:
|
||||
- 已迁移切片 **8/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)
|
||||
@@ -158,9 +158,15 @@
|
||||
`current` 换成目标屏并清 `isResetBackStackOnNextGoTo` / `isDelayedGoto`;`screen_show` analytics 是副作用,纯 reducer 只保留状态。
|
||||
屏幕身份用 `App.State.ScreenId`(`EntranceScreen` / `LoaderScreen` / `NamedScreen(name)`);
|
||||
`LX` / `RX` 是屏幕标识类,已从动作表剔除并记入 `root_wiring.json` 的 `Markers`(`screen-id-class`)。
|
||||
- `App/State/RootReducer.kt`:`ReduceRoot(State0, Action)` 只归约上述七片,其余 **82 个字段原样透传**;
|
||||
迁移进度用 `RootReducer.Coverage`(`已迁移 7/89`)与 `RootWiring.Unmigrated` 暴露。
|
||||
- 自检:`TankiOffline/tools/RunStateTest.ps1` **141 项全绿**,含 89 槽位完整性、七片一次归约互不串片、未迁移字段透传,
|
||||
- `lootBoxes` → `aH` / `EH.wrv`(`LootBoxesReducer.kt`):18 字段,18 个 `instanceof` 臂全量翻译。
|
||||
`timeToGiveLootBox` 是 Long 毫秒,`SetTimeToGiveLootBox` 按 `Date.now() + seconds * 1000` 写入;
|
||||
`AddKeys` 沿用原 `Map.getValue` 语义(缺键抛错),`SetRewards` 同时把 `isReopening` 置 false;
|
||||
`ClearRewards` 清空 `rewards` / `shownRewards`,`ShownRewards` 是追加(原 `List.plus`)。
|
||||
补给箱类型用 `App.State.ContainerType`(`STANDARD/RARE/EPIC/LEGENDARY`,默认 `STANDARD`);
|
||||
无 `toString` 的 `mH/kH/zH/gH/CH/SH` 按效果定名(`SetTimeToGiveLootBox`/`SetPeriodOfGiveLootBox`/…)。
|
||||
- `App/State/RootReducer.kt`:`ReduceRoot(State0, Action)` 只归约上述八片,其余 **81 个字段原样透传**;
|
||||
迁移进度用 `RootReducer.Coverage`(`已迁移 8/89`)与 `RootWiring.Unmigrated` 暴露。
|
||||
- 自检:`TankiOffline/tools/RunStateTest.ps1` **163 项全绿**,含 89 槽位完整性、八片一次归约互不串片、未迁移字段透传,
|
||||
以及用 `action_trace_merged.json` 真实派发的两条商店动作(`SetCoinPaymentProviderUrl` / `ToggleShopActivity`)回放。
|
||||
- 委派表里的已知形态:`shop` / `home` / `localBattleUserState` / `partner` 是内联 IIFE,
|
||||
已按字段具名为 `(inline@<field>)` 并从函数体抓到顶层动作名(69 / 1 / 2 / 11 个);
|
||||
@@ -168,7 +174,8 @@
|
||||
- 无 `toString` 的默认动作按证据定名(`root_wiring.json` 的 `AnonymousActions`):
|
||||
`o1` → `ShowShopDiscountNotification`(`$_$.gl` 单例,由 module_18334 的 `Qx.qge` 派发;
|
||||
效果是把 `HomeScreen.shopDiscountNotification` 置 true,对应主菜单商店项的折扣提示);
|
||||
shop 的 17 个匿名类按 reducer 效果定名并登记(`Yut`→`ShopLoaded`、`F5`→`ResetShop` 等)。
|
||||
shop 的 17 个匿名类按 reducer 效果定名并登记(`Yut`→`ShopLoaded`、`F5`→`ResetShop` 等);
|
||||
screen 的 9 个、lootBoxes 的 6 个匿名类同样按效果定名并登记。
|
||||
|
||||
### S4 引擎核心
|
||||
- 模块:32088(3D 数学)、92980(渲染)、61378(ECS)、55088(物理)、36659 + 71105(资源)、36734(音频)、3569(输入)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"generatedAt": "2026-10-22T19:22:01.390Z",
|
||||
"generatedAt": "2026-10-23T04:19:26.216Z",
|
||||
"module": "module_10032.js",
|
||||
"RootState": {
|
||||
"Class": "z6",
|
||||
@@ -13,7 +13,8 @@
|
||||
"home",
|
||||
"localBattleUserState",
|
||||
"shop",
|
||||
"screen"
|
||||
"screen",
|
||||
"lootBoxes"
|
||||
],
|
||||
"AnonymousActions": [
|
||||
{
|
||||
@@ -178,6 +179,42 @@
|
||||
"Name": "PopScreen",
|
||||
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
|
||||
"Effect": "screenStack = dropLast(1)(`f$(stack, 1)` 是 Kotlin dropLast)"
|
||||
},
|
||||
{
|
||||
"Local": "mH",
|
||||
"Name": "SetTimeToGiveLootBox",
|
||||
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
|
||||
"Effect": "timeToGiveLootBox = Date.now() + seconds * 1000(第 9 个构造参数 `Fg(Md(), Qk.y2o(Wg(t.zrv_1)))`)"
|
||||
},
|
||||
{
|
||||
"Local": "kH",
|
||||
"Name": "SetPeriodOfGiveLootBox",
|
||||
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
|
||||
"Effect": "periodOfGiveLootBoxInSec = action.yrv_1(第 10 个构造参数)"
|
||||
},
|
||||
{
|
||||
"Local": "zH",
|
||||
"Name": "ClearRewards",
|
||||
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
|
||||
"Effect": "rewards = emptyList()、shownRewards = emptyList()(第 6、7 个构造参数 `md(), md()`)"
|
||||
},
|
||||
{
|
||||
"Local": "gH",
|
||||
"Name": "LootBoxOpeningFailed",
|
||||
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
|
||||
"Effect": "openingFailed = true(第 11 个构造参数 !0)"
|
||||
},
|
||||
{
|
||||
"Local": "CH",
|
||||
"Name": "SetIsReopening",
|
||||
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
|
||||
"Effect": "isReopening = true(第 17 个构造参数 !0)"
|
||||
},
|
||||
{
|
||||
"Local": "SH",
|
||||
"Name": "SetIsBlockOpenButtons",
|
||||
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
|
||||
"Effect": "isBlockOpenButtons = action.xrv_1(第 18 个构造参数)"
|
||||
}
|
||||
],
|
||||
"Markers": [
|
||||
@@ -798,27 +835,27 @@
|
||||
"Reducer": "ws.wrv",
|
||||
"Delegate": "ws.wrv(t, n.wpv_1)",
|
||||
"Actions": [
|
||||
"?CH",
|
||||
"?SH",
|
||||
"?gH",
|
||||
"?kH",
|
||||
"?mH",
|
||||
"?zH",
|
||||
"AddKeys",
|
||||
"ClearRewards",
|
||||
"LootBoxOpeningFailed",
|
||||
"SelectCategory",
|
||||
"SetContainerData",
|
||||
"SetCurrentContainer",
|
||||
"SetGoToGarage",
|
||||
"SetIsAccelerated",
|
||||
"SetIsBlockOpenButtons",
|
||||
"SetIsOpening",
|
||||
"SetIsReopening",
|
||||
"SetIsShopVisited",
|
||||
"SetKeys",
|
||||
"SetPeriodOfGiveLootBox",
|
||||
"SetPossibleRewards",
|
||||
"SetRewards",
|
||||
"SetTimeToGiveLootBox",
|
||||
"ShownRewards"
|
||||
],
|
||||
"Default": "new aH()",
|
||||
"Migrated": false
|
||||
"Migrated": true
|
||||
},
|
||||
{
|
||||
"Field": "notifications",
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
{
|
||||
"generatedAt": "2026-10-23T03:54:25.170Z",
|
||||
"module": "module_10032.js",
|
||||
"Field": "lootBoxes",
|
||||
"Slot": "wpv_1",
|
||||
"Class": "aH",
|
||||
"ClassSource": "new aH()",
|
||||
"Params": [
|
||||
"t",
|
||||
"n",
|
||||
"i",
|
||||
"r",
|
||||
"e",
|
||||
"u",
|
||||
"s",
|
||||
"o",
|
||||
"_",
|
||||
"h",
|
||||
"a",
|
||||
"c",
|
||||
"l",
|
||||
"f",
|
||||
"v",
|
||||
"p",
|
||||
"w",
|
||||
"d"
|
||||
],
|
||||
"Fields": [
|
||||
{
|
||||
"Field": "rewardsForKeys",
|
||||
"Slot": "lru_1",
|
||||
"Kotlin": "RewardsForKeys",
|
||||
"Default": "md()"
|
||||
},
|
||||
{
|
||||
"Field": "currentContainerType",
|
||||
"Slot": "mru_1",
|
||||
"Kotlin": "CurrentContainerType",
|
||||
"Default": "Ly()"
|
||||
},
|
||||
{
|
||||
"Field": "keys",
|
||||
"Slot": "nru_1",
|
||||
"Kotlin": "Keys",
|
||||
"Default": "ag()"
|
||||
},
|
||||
{
|
||||
"Field": "containerNames",
|
||||
"Slot": "oru_1",
|
||||
"Kotlin": "ContainerNames",
|
||||
"Default": "ag()"
|
||||
},
|
||||
{
|
||||
"Field": "containerDescriptions",
|
||||
"Slot": "pru_1",
|
||||
"Kotlin": "ContainerDescriptions",
|
||||
"Default": "ag()"
|
||||
},
|
||||
{
|
||||
"Field": "rewards",
|
||||
"Slot": "qru_1",
|
||||
"Kotlin": "Rewards",
|
||||
"Default": "md()"
|
||||
},
|
||||
{
|
||||
"Field": "shownRewards",
|
||||
"Slot": "rru_1",
|
||||
"Kotlin": "ShownRewards",
|
||||
"Default": "md()"
|
||||
},
|
||||
{
|
||||
"Field": "gotoGarage",
|
||||
"Slot": "sru_1",
|
||||
"Kotlin": "GotoGarage",
|
||||
"Default": "false"
|
||||
},
|
||||
{
|
||||
"Field": "timeToGiveLootBox",
|
||||
"Slot": "tru_1",
|
||||
"Kotlin": "TimeToGiveLootBox",
|
||||
"Default": "new Dd(0, 0)"
|
||||
},
|
||||
{
|
||||
"Field": "periodOfGiveLootBoxInSec",
|
||||
"Slot": "uru_1",
|
||||
"Kotlin": "PeriodOfGiveLootBoxInSec",
|
||||
"Default": "0"
|
||||
},
|
||||
{
|
||||
"Field": "openingFailed",
|
||||
"Slot": "vru_1",
|
||||
"Kotlin": "OpeningFailed",
|
||||
"Default": "false"
|
||||
},
|
||||
{
|
||||
"Field": "adsAvailable",
|
||||
"Slot": "wru_1",
|
||||
"Kotlin": "AdsAvailable",
|
||||
"Default": "false"
|
||||
},
|
||||
{
|
||||
"Field": "currentCategory",
|
||||
"Slot": "xru_1",
|
||||
"Kotlin": "CurrentCategory",
|
||||
"Default": "null"
|
||||
},
|
||||
{
|
||||
"Field": "isOpening",
|
||||
"Slot": "yru_1",
|
||||
"Kotlin": "IsOpening",
|
||||
"Default": "false"
|
||||
},
|
||||
{
|
||||
"Field": "isAccelerated",
|
||||
"Slot": "zru_1",
|
||||
"Kotlin": "IsAccelerated",
|
||||
"Default": "false"
|
||||
},
|
||||
{
|
||||
"Field": "isShopVisited",
|
||||
"Slot": "arv_1",
|
||||
"Kotlin": "IsShopVisited",
|
||||
"Default": "false"
|
||||
},
|
||||
{
|
||||
"Field": "isReopening",
|
||||
"Slot": "brv_1",
|
||||
"Kotlin": "IsReopening",
|
||||
"Default": "false"
|
||||
},
|
||||
{
|
||||
"Field": "isBlockOpenButtons",
|
||||
"Slot": "crv_1",
|
||||
"Kotlin": "IsBlockOpenButtons",
|
||||
"Default": "false"
|
||||
}
|
||||
],
|
||||
"CopyMethod": null,
|
||||
"Branches": [
|
||||
{
|
||||
"Guard": "(具名 reducer)",
|
||||
"Locals": [],
|
||||
"Kind": "named",
|
||||
"Expr": "ws.wrv(t, n.wpv_1)"
|
||||
}
|
||||
],
|
||||
"SubReducers": [],
|
||||
"Counts": {
|
||||
"TotalInstanceof": null,
|
||||
"TopLevelBranches": 1,
|
||||
"SubReducerArms": 0
|
||||
},
|
||||
"Locals": {},
|
||||
"Delegate": "ws.wrv(t, n.wpv_1)"
|
||||
}
|
||||
@@ -569,6 +569,11 @@ function InferDeep(Expr, Mod, Off, Depth) {
|
||||
// 类型定义见 TankiOffline/.../App/State/ScreenId.kt。
|
||||
const FieldTypeOverrides = new Map([
|
||||
['GoTo.screen', 'ScreenId'],
|
||||
// lootBoxes 切片:lH/qH/yH 的字段在别处的调用点只能给出 Boolean / Any?,但 reducer 里分别是
|
||||
// ContainerType(默认 Ly()=STANDARD)、可空 ContainerType、及补给箱奖励列表。
|
||||
['SetCurrentContainer.type', 'ContainerType'],
|
||||
['SelectCategory.containerCategory', 'ContainerType?'],
|
||||
['ShownRewards.rewards', 'List<ContainerGivenItem>'],
|
||||
]);
|
||||
|
||||
const Emitted = [];
|
||||
|
||||
@@ -68,6 +68,13 @@ const AnonymousActionNames = new Map([
|
||||
['lY', 'EnableBack'],
|
||||
['cY', 'DisableBack'],
|
||||
['fY', 'PopScreen'],
|
||||
// 补给箱切片(aH / EH.wrv):没有 toString / 不在 action_inventory 的 6 个类。
|
||||
['mH', 'SetTimeToGiveLootBox'],
|
||||
['kH', 'SetPeriodOfGiveLootBox'],
|
||||
['zH', 'ClearRewards'],
|
||||
['gH', 'LootBoxOpeningFailed'],
|
||||
['CH', 'SetIsReopening'],
|
||||
['SH', 'SetIsBlockOpenButtons'],
|
||||
]);
|
||||
|
||||
// 非动作的 `instanceof`:屏幕标识类,用于 GoTo 分支判断(不是 dispatch 的动作)。
|
||||
@@ -433,6 +440,7 @@ const Migrated = {
|
||||
localBattleUserState: { Type: 'LocalBattleUserStateSlice', Reduce: 'ReduceLocalBattleUser' },
|
||||
shop: { Type: 'ShopStateSlice', Reduce: 'ReduceShop' },
|
||||
screen: { Type: 'ScreenStateSlice', Reduce: 'ReduceScreen' },
|
||||
lootBoxes: { Type: 'LootBoxesStateSlice', Reduce: 'ReduceLootBoxes' },
|
||||
};
|
||||
|
||||
for (const R of Rows) R.Migrated = Object.prototype.hasOwnProperty.call(Migrated, R.Field);
|
||||
@@ -487,6 +495,16 @@ const ScreenAnonymousEffects = {
|
||||
fY: 'screenStack = dropLast(1)(`f$(stack, 1)` 是 Kotlin dropLast)',
|
||||
};
|
||||
|
||||
// 补给箱切片(aH / EH.wrv)的匿名动作:按 reducer 分支效果定名。
|
||||
const LootBoxesAnonymousEffects = {
|
||||
mH: 'timeToGiveLootBox = Date.now() + seconds * 1000(第 9 个构造参数 `Fg(Md(), Qk.y2o(Wg(t.zrv_1)))`)',
|
||||
kH: 'periodOfGiveLootBoxInSec = action.yrv_1(第 10 个构造参数)',
|
||||
zH: 'rewards = emptyList()、shownRewards = emptyList()(第 6、7 个构造参数 `md(), md()`)',
|
||||
gH: 'openingFailed = true(第 11 个构造参数 !0)',
|
||||
CH: 'isReopening = true(第 17 个构造参数 !0)',
|
||||
SH: 'isBlockOpenButtons = action.xrv_1(第 18 个构造参数)',
|
||||
};
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(P, 'names', 'root_wiring.json'),
|
||||
JSON.stringify(
|
||||
@@ -515,6 +533,12 @@ fs.writeFileSync(
|
||||
DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名',
|
||||
Effect,
|
||||
})),
|
||||
...Object.entries(LootBoxesAnonymousEffects).map(([Local, Effect]) => ({
|
||||
Local,
|
||||
Name: AnonymousActionNames.get(Local),
|
||||
DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名',
|
||||
Effect,
|
||||
})),
|
||||
],
|
||||
Markers: [
|
||||
{
|
||||
|
||||
@@ -346,14 +346,14 @@ object ActionCatalog {
|
||||
ActionMeta("SetIcons", SetIcons::class, listOf(ActionFieldMeta("icons", "List<IconData>"))),
|
||||
ActionMeta("LootBoxes", LootBoxes::class, listOf(ActionFieldMeta("rewardsForKeys", "Any?"), ActionFieldMeta("currentContainerType", "Any?"), ActionFieldMeta("keys", "Any?"), ActionFieldMeta("containerNames", "Any?"), ActionFieldMeta("containerDescriptions", "Any?"), ActionFieldMeta("rewards", "Any?"), ActionFieldMeta("shownRewards", "Any?"), ActionFieldMeta("gotoGarage", "Any?"), ActionFieldMeta("timeToGiveLootBox", "Any?"), ActionFieldMeta("periodOfGiveLootBoxInSec", "Any?"), ActionFieldMeta("openingFailed", "Any?"), ActionFieldMeta("adsAvailable", "Any?"), ActionFieldMeta("currentCategory", "Any?"), ActionFieldMeta("isOpening", "Any?"), ActionFieldMeta("isAccelerated", "Any?"), ActionFieldMeta("isShopVisited", "Any?"), ActionFieldMeta("isReopening", "Any?"), ActionFieldMeta("isBlockOpenButtons", "Any?"))),
|
||||
ActionMeta("SetPossibleRewards", SetPossibleRewards::class, listOf(ActionFieldMeta("rewards", "Any?"))),
|
||||
ActionMeta("SetCurrentContainer", SetCurrentContainer::class, listOf(ActionFieldMeta("type", "Boolean"))),
|
||||
ActionMeta("SetCurrentContainer", SetCurrentContainer::class, listOf(ActionFieldMeta("type", "ContainerType"))),
|
||||
ActionMeta("SetKeys", SetKeys::class, listOf(ActionFieldMeta("keys", "Map<String, Int>"))),
|
||||
ActionMeta("AddKeys", AddKeys::class, listOf(ActionFieldMeta("keyType", "String"), ActionFieldMeta("delta", "Int"))),
|
||||
ActionMeta("SetContainerData", SetContainerData::class, listOf(ActionFieldMeta("type", "String"), ActionFieldMeta("name", "String"), ActionFieldMeta("description", "String"))),
|
||||
ActionMeta("SetGoToGarage", SetGoToGarage::class, listOf(ActionFieldMeta("gotoGarage", "Boolean"))),
|
||||
ActionMeta("OpenContainer", OpenContainer::class, listOf(ActionFieldMeta("amount", "Any?"), ActionFieldMeta("type", "Any?"))),
|
||||
ActionMeta("SelectCategory", SelectCategory::class, listOf(ActionFieldMeta("containerCategory", "Any?"))),
|
||||
ActionMeta("ShownRewards", ShownRewards::class, listOf(ActionFieldMeta("rewards", "Any?"))),
|
||||
ActionMeta("SelectCategory", SelectCategory::class, listOf(ActionFieldMeta("containerCategory", "ContainerType?"))),
|
||||
ActionMeta("ShownRewards", ShownRewards::class, listOf(ActionFieldMeta("rewards", "List<ContainerGivenItem>"))),
|
||||
ActionMeta("SetIsOpening", SetIsOpening::class, listOf(ActionFieldMeta("isOpening", "Boolean"))),
|
||||
ActionMeta("SetIsAccelerated", SetIsAccelerated::class, listOf(ActionFieldMeta("isAccelerated", "Boolean"))),
|
||||
ActionMeta("SetIsShopVisited", SetIsShopVisited::class, listOf(ActionFieldMeta("isShopVisited", "Boolean"))),
|
||||
|
||||
@@ -5,7 +5,7 @@ package App.State
|
||||
|
||||
import Core.Vector3
|
||||
|
||||
// 动作/状态类共 816 个,字段 2150 个(已定类型 509 个)
|
||||
// 动作/状态类共 816 个,字段 2150 个(已定类型 511 个)
|
||||
data class AnnouncementState(
|
||||
val isEnabled: Any?, // 仅 copy 转发(无类型信息)
|
||||
val modelLoaded: Any?, // 仅 copy 转发(无类型信息)
|
||||
@@ -2019,7 +2019,7 @@ data class SetPossibleRewards(
|
||||
)
|
||||
|
||||
data class SetCurrentContainer(
|
||||
val type: Boolean, // callsite
|
||||
val type: ContainerType, // override
|
||||
)
|
||||
|
||||
data class SetKeys(
|
||||
@@ -2047,11 +2047,11 @@ data class OpenContainer(
|
||||
)
|
||||
|
||||
data class SelectCategory(
|
||||
val containerCategory: Any?, // 调用点: i | null
|
||||
val containerCategory: ContainerType?, // override
|
||||
)
|
||||
|
||||
data class ShownRewards(
|
||||
val rewards: Any?, // 调用点: t.state.rewards | HT(n)
|
||||
val rewards: List<ContainerGivenItem>, // override
|
||||
)
|
||||
|
||||
data class SetIsOpening(
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
// LootBoxesModels.kt —— 补给箱切片用到的枚举(App.State 包内,供 Inventory.kt 的字段类型引用)
|
||||
//
|
||||
// 证据链:module_88693 的 `Vt()` 初始化
|
||||
// `(u = new Kt("STANDARD", 0)), (s = new Kt("RARE", 1)), (o = new Kt("EPIC", 2)), (_ = new Kt("LEGENDARY", 3))`
|
||||
// `Yt()` 返回 `u`(STANDARD)——module_10032 的 `Ly()` 正是 `Yt`,即 aH.currentContainerType 的默认值。
|
||||
package App.State
|
||||
|
||||
/** 原 `Kt`:补给箱类型,顺序与枚举 ordinal 一致(STANDARD=0 … LEGENDARY=3)。 */
|
||||
enum class ContainerType {
|
||||
STANDARD,
|
||||
RARE,
|
||||
EPIC,
|
||||
LEGENDARY,
|
||||
;
|
||||
|
||||
companion object {
|
||||
/** 运行期是 JS 枚举对象,toString 就是成员名;认不出时与原包默认值一致落到 STANDARD。 */
|
||||
fun FromText(Text: Any?): ContainerType =
|
||||
entries.firstOrNull { it.name == Text?.toString() } ?: STANDARD
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
// LootBoxesReducer.kt —— 补给箱切片(对应 module_10032 的 aH 状态 + EH.wrv reducer)
|
||||
//
|
||||
// 证据链:
|
||||
// 状态类 aH:`function aH(t, n, i, r, e, u, s, o, _, h, a, c, l, f, v, p, w, d)`,18 个字段,
|
||||
// toString 名为 `LootBoxes(rewardsForKeys=…, currentContainerType=…, …)`。默认值取自构造:
|
||||
// rewardsForKeys = md()(emptyList)、currentContainerType = Ly()(ContainerType.STANDARD,
|
||||
// 见 module_88693 的 `Vt()` / `Yt()`)、keys / containerNames / containerDescriptions = ag()(emptyMap)、
|
||||
// rewards / shownRewards = md()、gotoGarage = false、timeToGiveLootBox = new Dd(0, 0)(Long 0)、
|
||||
// periodOfGiveLootBoxInSec = 0、openingFailed / adsAvailable = false、currentCategory = null、
|
||||
// 其余布尔 = false。
|
||||
// reducer:`ld(EH).wrv`(module_10032 第 33150 行)18 个 instanceof 臂:
|
||||
//
|
||||
// cH SetPossibleRewards → rewardsForKeys = action.rewards
|
||||
// lH SetCurrentContainer → currentContainerType = action.type
|
||||
// fH SetKeys → keys = action.keys
|
||||
// vH AddKeys → keys[action.keyType] += action.delta(原 `nm` 是 Map.getValue,
|
||||
// 缺键抛 "Key … is missing in the map.";加法按 32 位 Int 截断)
|
||||
// pH SetContainerData → containerNames[type] = name、containerDescriptions[type] = description
|
||||
// (原 `cg(map, Vd(k, v))` 是 Map.plus(Pair))
|
||||
// wH SetGoToGarage → gotoGarage = action.gotoGarage
|
||||
// bH SetRewards → rewards = action.rewards,同时 isReopening = false(第 17 个构造参数 !1)
|
||||
// mH SetTimeToGiveLootBox → timeToGiveLootBox = Date.now() + action.seconds * 1000
|
||||
// (原 `Fg(Md(), Qk.y2o(Wg(sec)))`:Md()=Date.now()、Wg=Int→Long、
|
||||
// Qk.y2o=秒→毫秒、Fg=Long 加法)
|
||||
// kH SetPeriodOfGiveLootBox → periodOfGiveLootBoxInSec = action.periodOfGiveLootBoxInSec
|
||||
// zH ClearRewards → rewards = emptyList()、shownRewards = emptyList()
|
||||
// gH LootBoxOpeningFailed → openingFailed = true
|
||||
// qH SelectCategory → currentCategory = action.containerCategory
|
||||
// yH ShownRewards → shownRewards += action.rewards(原 `Im(list, list)` 是 List.plus)
|
||||
// $H SetIsOpening → isOpening = action.isOpening
|
||||
// xH SetIsAccelerated → isAccelerated = action.isAccelerated
|
||||
// jH SetIsShopVisited → isShopVisited = action.isShopVisited
|
||||
// CH SetIsReopening → isReopening = true
|
||||
// SH SetIsBlockOpenButtons → isBlockOpenButtons = action.isBlockOpenButtons
|
||||
//
|
||||
// 无 toString / 不在 action_inventory 的 6 个类按 reducer 效果定名,原名写在注释里;名字同时登记在
|
||||
// 3DTank/tools/gen_root_wiring.js 的 AnonymousActionNames,重新生成 RootWiring 后显示。
|
||||
package App.State.Reducers
|
||||
|
||||
import App.State.AddKeys
|
||||
import App.State.ContainerGivenItem
|
||||
import App.State.ContainerType
|
||||
import App.State.SelectCategory
|
||||
import App.State.SetContainerData
|
||||
import App.State.SetCurrentContainer
|
||||
import App.State.SetGoToGarage
|
||||
import App.State.SetIsAccelerated
|
||||
import App.State.SetIsOpening
|
||||
import App.State.SetIsShopVisited
|
||||
import App.State.SetKeys
|
||||
import App.State.SetPossibleRewards
|
||||
import App.State.SetRewards
|
||||
import App.State.ShownRewards
|
||||
|
||||
/** 原 `mH`:补给箱倒计时(秒);归约后写到 timeToGiveLootBox = now + seconds * 1000。 */
|
||||
data class SetTimeToGiveLootBox(val seconds: Int)
|
||||
|
||||
/** 原 `kH`:补给箱发放周期(秒)。 */
|
||||
data class SetPeriodOfGiveLootBox(val periodOfGiveLootBoxInSec: Int)
|
||||
|
||||
/** 原 `zH`:无字段;清空 rewards 与 shownRewards。 */
|
||||
object ClearRewards
|
||||
|
||||
/** 原 `gH`:无字段;openingFailed = true。 */
|
||||
object LootBoxOpeningFailed
|
||||
|
||||
/** 原 `CH`:无字段;isReopening = true。 */
|
||||
object SetIsReopening
|
||||
|
||||
/** 原 `SH`:isBlockOpenButtons = action.xrv_1。 */
|
||||
data class SetIsBlockOpenButtons(val isBlockOpenButtons: Boolean)
|
||||
|
||||
/**
|
||||
* 原 `aH` / `LootBoxes`:`LootBoxes(rewardsForKeys, currentContainerType, keys, containerNames,
|
||||
* containerDescriptions, rewards, shownRewards, gotoGarage, timeToGiveLootBox, periodOfGiveLootBoxInSec,
|
||||
* openingFailed, adsAvailable, currentCategory, isOpening, isAccelerated, isShopVisited, isReopening,
|
||||
* isBlockOpenButtons)`。
|
||||
*/
|
||||
data class LootBoxesStateSlice(
|
||||
/** 本 reducer 只 copy 转发、从不读;原构造默认 md()(emptyList),元素类型没有证据,保留 Any?。 */
|
||||
val RewardsForKeys: Any? = emptyList<ContainerGivenItem>(),
|
||||
val CurrentContainerType: ContainerType = ContainerType.STANDARD,
|
||||
val Keys: Map<String, Int> = emptyMap(),
|
||||
val ContainerNames: Map<String, String> = emptyMap(),
|
||||
val ContainerDescriptions: Map<String, String> = emptyMap(),
|
||||
val Rewards: List<ContainerGivenItem> = emptyList(),
|
||||
val ShownRewards: List<ContainerGivenItem> = emptyList(),
|
||||
val GotoGarage: Boolean = false,
|
||||
val TimeToGiveLootBox: Long = 0L,
|
||||
val PeriodOfGiveLootBoxInSec: Int = 0,
|
||||
val OpeningFailed: Boolean = false,
|
||||
val AdsAvailable: Boolean = false,
|
||||
val CurrentCategory: ContainerType? = null,
|
||||
val IsOpening: Boolean = false,
|
||||
val IsAccelerated: Boolean = false,
|
||||
val IsShopVisited: Boolean = false,
|
||||
val IsReopening: Boolean = false,
|
||||
val IsBlockOpenButtons: Boolean = false,
|
||||
)
|
||||
|
||||
/** 原 `Md()`:`ve(Date.now())`,即当前毫秒(Long)。 */
|
||||
private fun CurrentTimeMillis(): Long = kotlin.js.Date.now().toLong()
|
||||
|
||||
/** 根 reducer 的 lootBoxes 分支(原 `ws.wrv(t, n.wpv_1)`)。 */
|
||||
fun ReduceLootBoxes(State0: LootBoxesStateSlice, A: Any): LootBoxesStateSlice = when (A) {
|
||||
is SetPossibleRewards -> State0.copy(RewardsForKeys = A.rewards)
|
||||
is SetCurrentContainer -> State0.copy(CurrentContainerType = A.type)
|
||||
is SetKeys -> State0.copy(Keys = A.keys)
|
||||
|
||||
is AddKeys -> State0.copy(
|
||||
Keys = State0.Keys + (A.keyType to (State0.Keys.getValue(A.keyType) + A.delta))
|
||||
)
|
||||
|
||||
is SetContainerData -> State0.copy(
|
||||
ContainerNames = State0.ContainerNames + (A.type to A.name),
|
||||
ContainerDescriptions = State0.ContainerDescriptions + (A.type to A.description),
|
||||
)
|
||||
|
||||
is SetGoToGarage -> State0.copy(GotoGarage = A.gotoGarage)
|
||||
is SetRewards -> State0.copy(Rewards = A.rewards, IsReopening = false)
|
||||
is SetTimeToGiveLootBox -> State0.copy(
|
||||
TimeToGiveLootBox = CurrentTimeMillis() + A.seconds.toLong() * 1000L
|
||||
)
|
||||
is SetPeriodOfGiveLootBox -> State0.copy(PeriodOfGiveLootBoxInSec = A.periodOfGiveLootBoxInSec)
|
||||
is ClearRewards -> State0.copy(Rewards = emptyList(), ShownRewards = emptyList())
|
||||
is LootBoxOpeningFailed -> State0.copy(OpeningFailed = true)
|
||||
is SelectCategory -> State0.copy(CurrentCategory = A.containerCategory)
|
||||
is ShownRewards -> State0.copy(ShownRewards = State0.ShownRewards + A.rewards)
|
||||
is SetIsOpening -> State0.copy(IsOpening = A.isOpening)
|
||||
is SetIsAccelerated -> State0.copy(IsAccelerated = A.isAccelerated)
|
||||
is SetIsShopVisited -> State0.copy(IsShopVisited = A.isShopVisited)
|
||||
is SetIsReopening -> State0.copy(IsReopening = true)
|
||||
is SetIsBlockOpenButtons -> State0.copy(IsBlockOpenButtons = A.isBlockOpenButtons)
|
||||
else -> State0
|
||||
}
|
||||
@@ -10,8 +10,9 @@
|
||||
// localBattleUserState → ReduceLocalBattleUser(lE / `(inline@localBattleUserState)` 分支)
|
||||
// shop → ReduceShop(j4 / `(inline@shop)` 分支)
|
||||
// screen → ReduceScreen(WK / dY().ks5)
|
||||
// lootBoxes → ReduceLootBoxes(aH / EH.wrv)
|
||||
//
|
||||
// 其余 82 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态,
|
||||
// 其余 81 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态,
|
||||
// 所以一次 copy 里串起全部已迁移 reducer,与按动作路由再归约的结果一致。
|
||||
// 迁移进度与每个字段的证据见生成物 RootWiring.kt(不要手改)。
|
||||
package App.State
|
||||
@@ -20,6 +21,7 @@ import App.State.Reducers.ReduceHome
|
||||
import App.State.Reducers.ReduceInvite
|
||||
import App.State.Reducers.ReduceLocalBattleUser
|
||||
import App.State.Reducers.ReduceLoginByPassword
|
||||
import App.State.Reducers.ReduceLootBoxes
|
||||
import App.State.Reducers.ReduceScreen
|
||||
import App.State.Reducers.ReduceShop
|
||||
import App.State.Reducers.ReduceTwitch
|
||||
@@ -34,7 +36,7 @@ object RootReducer {
|
||||
val Coverage: String get() = "已迁移 ${Migrated.size}/${RootWiring.All.size}"
|
||||
|
||||
/**
|
||||
* 归约一次。未迁移字段原样带过去(`copy` 只改已迁移的三个)。
|
||||
* 归约一次。未迁移字段原样带过去(`copy` 只改已迁移的字段)。
|
||||
*/
|
||||
fun Reduce(State0: RootState, Action: Any): RootState = State0.copy(
|
||||
InviteState = ReduceInvite(State0.InviteState, Action),
|
||||
@@ -44,6 +46,7 @@ object RootReducer {
|
||||
LocalBattleUserState = ReduceLocalBattleUser(State0.LocalBattleUserState, Action),
|
||||
Shop = ReduceShop(State0.Shop, Action),
|
||||
Screen = ReduceScreen(State0.Screen, Action),
|
||||
LootBoxes = ReduceLootBoxes(State0.LootBoxes, Action),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import App.State.Reducers.HomeStateSlice
|
||||
import App.State.Reducers.InviteStateSlice
|
||||
import App.State.Reducers.LocalBattleUserStateSlice
|
||||
import App.State.Reducers.LoginByPasswordStateSlice
|
||||
import App.State.Reducers.LootBoxesStateSlice
|
||||
import App.State.Reducers.ScreenStateSlice
|
||||
import App.State.Reducers.ShopStateSlice
|
||||
import App.State.Reducers.TwitchStateSlice
|
||||
@@ -65,8 +66,8 @@ data class RootState(
|
||||
val Settings: Any? = null,
|
||||
/** 原 accountSettings(oet / Lp.mtg)。未迁移:原样透传。 */
|
||||
val AccountSettings: Any? = null,
|
||||
/** 原 lootBoxes(aH / ws.wrv)。未迁移:原样透传。 */
|
||||
val LootBoxes: Any? = null,
|
||||
/** 原 lootBoxes(aH / ws.wrv)。已迁移。 */
|
||||
val LootBoxes: LootBoxesStateSlice = LootBoxesStateSlice(),
|
||||
/** 原 notifications(? / B_.ise)。未迁移:原样透传。 */
|
||||
val Notifications: Any? = null,
|
||||
/** 原 friends(KD / NL().cr9)。未迁移:原样透传。 */
|
||||
|
||||
@@ -38,7 +38,7 @@ object RootWiring {
|
||||
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("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")),
|
||||
SliceWiring("remoteUserDataStorage", "zpv_1", "\$Q", "vh.isl", "vh.isl(t, n.zpv_1)", listOf("UpdateClanTags", "UpdateOnlineStatus", "UpdatePremiumStatus", "UpdateRanks", "UpdateUids", "UserEnterBattle", "UserLeaveBattle", "UserLeaveGroup")),
|
||||
@@ -106,7 +106,7 @@ object RootWiring {
|
||||
)
|
||||
|
||||
/** 已迁移(由 RootReducer 真正归约)的字段。 */
|
||||
val Migrated: Set<String> = setOf("inviteState", "loginByPassword", "twitchState", "home", "localBattleUserState", "shop", "screen")
|
||||
val Migrated: Set<String> = setOf("inviteState", "loginByPassword", "twitchState", "home", "localBattleUserState", "shop", "screen", "lootBoxes")
|
||||
|
||||
/** 未迁移字段:归约时原样透传,作为后续迁移清单。 */
|
||||
val Unmigrated: List<SliceWiring> get() = All.filter { it.Field !in Migrated }
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
// ⑥ 动作目录:816 个类与 138 个真实派发名对齐
|
||||
// ⑦ 回放:真实 toString 样例的类/字段/类型校验
|
||||
// ⑧ 全目录动作类都能按 KClass 路由
|
||||
// ⑨~⑰ 切片 reducer:InviteState / LoginByPasswordState / TwitchState / HomeScreen / LocalBattleUserState /
|
||||
// Shop(含 U7 类别表与 w3 物品表两个子 reducer)/ Screen;⑭ 根 reducer 的 89 槽位委派
|
||||
// ⑨~⑱ 切片 reducer:InviteState / LoginByPasswordState / TwitchState / HomeScreen / LocalBattleUserState /
|
||||
// Shop(含 U7 类别表与 w3 物品表两个子 reducer)/ Screen / LootBoxes;⑭ 根 reducer 的 89 槽位委派
|
||||
//
|
||||
// 用法:$env:TANKIOFFLINE_STATE_TEST='1'; node …(见 TankiOffline/tools/RunStateTest.ps1)
|
||||
package App
|
||||
@@ -144,6 +144,28 @@ import App.State.Reducers.PopScreen
|
||||
import App.State.Reducers.ReduceScreen
|
||||
import App.State.Reducers.ResetScreenStackOnNextGoTo
|
||||
import App.State.Reducers.ScreenStateSlice
|
||||
import App.State.AddKeys
|
||||
import App.State.ContainerGivenItem
|
||||
import App.State.ContainerType
|
||||
import App.State.SelectCategory
|
||||
import App.State.SetContainerData
|
||||
import App.State.SetCurrentContainer
|
||||
import App.State.SetGoToGarage
|
||||
import App.State.SetIsAccelerated
|
||||
import App.State.SetIsOpening
|
||||
import App.State.SetIsShopVisited
|
||||
import App.State.SetKeys
|
||||
import App.State.SetPossibleRewards
|
||||
import App.State.SetRewards
|
||||
import App.State.ShownRewards
|
||||
import App.State.Reducers.ClearRewards
|
||||
import App.State.Reducers.LootBoxOpeningFailed
|
||||
import App.State.Reducers.LootBoxesStateSlice
|
||||
import App.State.Reducers.ReduceLootBoxes
|
||||
import App.State.Reducers.SetIsBlockOpenButtons
|
||||
import App.State.Reducers.SetIsReopening
|
||||
import App.State.Reducers.SetPeriodOfGiveLootBox
|
||||
import App.State.Reducers.SetTimeToGiveLootBox
|
||||
|
||||
private data class AddFrags(val fragsCount: Int)
|
||||
private data class SetScreen(val screen: String)
|
||||
@@ -517,7 +539,7 @@ object StateCheck {
|
||||
Check("切片 LocalBattleUser:接 Store 派发后状态已归约", Store.State.IsReArmorTemporaryDisabled)
|
||||
}
|
||||
|
||||
// ⑭ 根 reducer:89 个槽位的委派表 + 六个已迁移切片一次归约
|
||||
// ⑭ 根 reducer:89 个槽位的委派表 + 八个已迁移切片一次归约
|
||||
run {
|
||||
Check("根:根状态 89 个切片槽位", RootWiring.All.size == 89)
|
||||
Check(
|
||||
@@ -581,13 +603,16 @@ object StateCheck {
|
||||
LocalBattleW.Reducer == "(inline@localBattleUserState)"
|
||||
)
|
||||
Check(
|
||||
"根:迁移表 = 七个已迁移字段,其余 82 个待迁",
|
||||
"根:迁移表 = 八个已迁移字段,其余 81 个待迁",
|
||||
RootReducer.Migrated ==
|
||||
setOf("inviteState", "loginByPassword", "twitchState", "home", "localBattleUserState", "shop", "screen") &&
|
||||
RootWiring.Unmigrated.size == 82
|
||||
setOf(
|
||||
"inviteState", "loginByPassword", "twitchState", "home",
|
||||
"localBattleUserState", "shop", "screen", "lootBoxes",
|
||||
) &&
|
||||
RootWiring.Unmigrated.size == 81
|
||||
)
|
||||
|
||||
// 一次 ReduceRoot 里六个切片各归约自己的动作,互不串片
|
||||
// 一次 ReduceRoot 里八个切片各归约自己的动作,互不串片
|
||||
val S0 = RootState(User = "user-1", Screen = ScreenStateSlice(Current = NamedScreen("Preloader")))
|
||||
val S1 = ReduceRoot(S0, ApplyInvite("uid-1"))
|
||||
Check(
|
||||
@@ -640,7 +665,7 @@ object StateCheck {
|
||||
S5.User == "user-1" && S5.Screen.Current == NamedScreen("Preloader")
|
||||
)
|
||||
Check("根:无关动作返回等值状态", ReduceRoot(S5, AddFrags(1)) == S5)
|
||||
Check("根:Coverage 文本", RootReducer.Coverage == "已迁移 7/89")
|
||||
Check("根:Coverage 文本", RootReducer.Coverage == "已迁移 8/89")
|
||||
|
||||
val Store = ReduxStore(RootState(), { S, A -> ReduceRoot(S, A) })
|
||||
Store.Dispatch(SetStatus("LOGGING_IN"))
|
||||
@@ -1067,6 +1092,164 @@ object StateCheck {
|
||||
).all { it in W.Actions }
|
||||
)
|
||||
}
|
||||
|
||||
// ⑱ 切片 reducer:LootBoxes(原 module_10032 的 aH / EH.wrv,18 个 instanceof 臂)
|
||||
run {
|
||||
val Item = { Name: String ->
|
||||
ContainerGivenItem("INVENTORY", 1, "INVENTORY", 10007277, null, Name, "mine")
|
||||
}
|
||||
val S0 = LootBoxesStateSlice()
|
||||
Check(
|
||||
"切片 LootBoxes:默认值与原构造一致",
|
||||
S0.RewardsForKeys == emptyList<ContainerGivenItem>() &&
|
||||
S0.CurrentContainerType == ContainerType.STANDARD &&
|
||||
S0.Keys.isEmpty() && S0.ContainerNames.isEmpty() &&
|
||||
S0.ContainerDescriptions.isEmpty() && S0.Rewards.isEmpty() &&
|
||||
S0.ShownRewards.isEmpty() && !S0.GotoGarage && S0.TimeToGiveLootBox == 0L &&
|
||||
S0.PeriodOfGiveLootBoxInSec == 0 && !S0.OpeningFailed && !S0.AdsAvailable &&
|
||||
S0.CurrentCategory == null && !S0.IsOpening && !S0.IsAccelerated &&
|
||||
!S0.IsShopVisited && !S0.IsReopening && !S0.IsBlockOpenButtons
|
||||
)
|
||||
|
||||
val Rewards = listOf(Item("mine"))
|
||||
Check(
|
||||
"切片 LootBoxes:SetPossibleRewards / SetCurrentContainer 各写本字段",
|
||||
ReduceLootBoxes(S0, SetPossibleRewards(Rewards)).RewardsForKeys == Rewards &&
|
||||
ReduceLootBoxes(S0, SetCurrentContainer(ContainerType.EPIC))
|
||||
.CurrentContainerType == ContainerType.EPIC &&
|
||||
ReduceLootBoxes(S0, SetCurrentContainer(ContainerType.EPIC))
|
||||
.CurrentContainerType != ContainerType.RARE
|
||||
)
|
||||
|
||||
val Keyed = ReduceLootBoxes(S0, SetKeys(mapOf("STANDARD" to 3)))
|
||||
Check(
|
||||
"切片 LootBoxes:SetKeys 覆盖整张表",
|
||||
Keyed.Keys == mapOf("STANDARD" to 3) &&
|
||||
ReduceLootBoxes(S0, SetKeys(emptyMap())).Keys.isEmpty()
|
||||
)
|
||||
Check(
|
||||
"切片 LootBoxes:AddKeys 在原值上加 delta",
|
||||
ReduceLootBoxes(Keyed, AddKeys("STANDARD", 2)).Keys == mapOf("STANDARD" to 5) &&
|
||||
ReduceLootBoxes(Keyed, AddKeys("STANDARD", -3)).Keys == mapOf("STANDARD" to 0)
|
||||
)
|
||||
Check(
|
||||
"切片 LootBoxes:AddKeys 缺键抛错(原 nm 是 Map.getValue)",
|
||||
runCatching { ReduceLootBoxes(Keyed, AddKeys("MISSING", 1)) }.isFailure
|
||||
)
|
||||
|
||||
val Described = ReduceLootBoxes(S0, SetContainerData("STANDARD", "标准箱", "普通补给箱"))
|
||||
Check(
|
||||
"切片 LootBoxes:SetContainerData 同时写名字与描述两张表",
|
||||
Described.ContainerNames == mapOf("STANDARD" to "标准箱") &&
|
||||
Described.ContainerDescriptions == mapOf("STANDARD" to "普通补给箱") &&
|
||||
Described.Keys.isEmpty() && Described.Rewards.isEmpty()
|
||||
)
|
||||
|
||||
Check(
|
||||
"切片 LootBoxes:SetGoToGarage 写 gotoGarage",
|
||||
ReduceLootBoxes(S0, SetGoToGarage(true)).GotoGarage &&
|
||||
!ReduceLootBoxes(S0.copy(GotoGarage = true), SetGoToGarage(false)).GotoGarage
|
||||
)
|
||||
|
||||
val Rewarded = ReduceLootBoxes(
|
||||
S0.copy(IsReopening = true, ShownRewards = listOf(Item("old"))),
|
||||
SetRewards(Rewards)
|
||||
)
|
||||
Check(
|
||||
"切片 LootBoxes:SetRewards 换奖励并把 isReopening 归 false",
|
||||
Rewarded.Rewards == Rewards && !Rewarded.IsReopening &&
|
||||
Rewarded.ShownRewards == listOf(Item("old"))
|
||||
)
|
||||
|
||||
val Before = kotlin.js.Date.now().toLong()
|
||||
val Timed = ReduceLootBoxes(S0, SetTimeToGiveLootBox(3))
|
||||
val After = kotlin.js.Date.now().toLong()
|
||||
Check(
|
||||
"切片 LootBoxes:SetTimeToGiveLootBox = 当前毫秒 + 秒 * 1000",
|
||||
Timed.TimeToGiveLootBox >= Before + 3000L && Timed.TimeToGiveLootBox <= After + 3000L
|
||||
)
|
||||
Check(
|
||||
"切片 LootBoxes:SetPeriodOfGiveLootBox 写周期(秒)",
|
||||
ReduceLootBoxes(S0, SetPeriodOfGiveLootBox(3600)).PeriodOfGiveLootBoxInSec == 3600
|
||||
)
|
||||
|
||||
val Cleared = ReduceLootBoxes(
|
||||
S0.copy(Rewards = Rewards, ShownRewards = Rewards, OpeningFailed = true),
|
||||
ClearRewards
|
||||
)
|
||||
Check(
|
||||
"切片 LootBoxes:ClearRewards 只清两个奖励表",
|
||||
Cleared.Rewards.isEmpty() && Cleared.ShownRewards.isEmpty() && Cleared.OpeningFailed
|
||||
)
|
||||
Check(
|
||||
"切片 LootBoxes:LootBoxOpeningFailed 置 openingFailed",
|
||||
ReduceLootBoxes(S0, LootBoxOpeningFailed).OpeningFailed
|
||||
)
|
||||
|
||||
Check(
|
||||
"切片 LootBoxes:SelectCategory 写可空 currentCategory",
|
||||
ReduceLootBoxes(S0, SelectCategory(ContainerType.RARE)).CurrentCategory == ContainerType.RARE &&
|
||||
ReduceLootBoxes(S0.copy(CurrentCategory = ContainerType.RARE), SelectCategory(null))
|
||||
.CurrentCategory == null
|
||||
)
|
||||
|
||||
val Shown = ReduceLootBoxes(S0.copy(ShownRewards = listOf(Item("old"))), ShownRewards(Rewards))
|
||||
Check(
|
||||
"切片 LootBoxes:ShownRewards 追加到 shownRewards(原 Im 是 List.plus)",
|
||||
Shown.ShownRewards == listOf(Item("old"), Item("mine")) && Shown.Rewards.isEmpty()
|
||||
)
|
||||
|
||||
Check(
|
||||
"切片 LootBoxes:四个布尔动作各写本字段",
|
||||
!ReduceLootBoxes(S0.copy(IsOpening = true), SetIsOpening(false)).IsOpening &&
|
||||
ReduceLootBoxes(S0, SetIsOpening(true)).IsOpening &&
|
||||
ReduceLootBoxes(S0, SetIsAccelerated(true)).IsAccelerated &&
|
||||
ReduceLootBoxes(S0, SetIsShopVisited(true)).IsShopVisited
|
||||
)
|
||||
Check(
|
||||
"切片 LootBoxes:SetIsReopening 置 true;SetIsBlockOpenButtons 写值",
|
||||
ReduceLootBoxes(S0, SetIsReopening).IsReopening &&
|
||||
ReduceLootBoxes(S0, SetIsBlockOpenButtons(true)).IsBlockOpenButtons &&
|
||||
!ReduceLootBoxes(S0.copy(IsBlockOpenButtons = true), SetIsBlockOpenButtons(false))
|
||||
.IsBlockOpenButtons
|
||||
)
|
||||
Check("切片 LootBoxes:无关动作不改状态", ReduceLootBoxes(S0, AddFrags(1)) == S0)
|
||||
|
||||
// 根:lootBoxes 动作只动 lootBoxes
|
||||
val R0 = RootState()
|
||||
val R1 = ReduceRoot(R0, SetCurrentContainer(ContainerType.LEGENDARY))
|
||||
Check(
|
||||
"根:SetCurrentContainer 只动 lootBoxes",
|
||||
R1.LootBoxes.CurrentContainerType == ContainerType.LEGENDARY &&
|
||||
R1.Shop == R0.Shop && R1.Screen == R0.Screen &&
|
||||
R1.InviteState == R0.InviteState && R1.Home == R0.Home
|
||||
)
|
||||
val R2 = ReduceRoot(R1, SetRewards(listOf(Item("root"))))
|
||||
Check(
|
||||
"根:SetRewards 只动 lootBoxes,且保留前一次归约",
|
||||
R2.LootBoxes.Rewards == listOf(Item("root")) &&
|
||||
R2.LootBoxes.CurrentContainerType == ContainerType.LEGENDARY &&
|
||||
R2.Shop == R1.Shop
|
||||
)
|
||||
Check("根:lootBoxes 迁移后无关动作仍返回等值状态", ReduceRoot(R2, AddFrags(1)) == R2)
|
||||
|
||||
val W = RootWiring.ByField("lootBoxes")!!
|
||||
Check(
|
||||
"根:lootBoxes 委派到 aH / ws.wrv 且 18 个动作全部具名",
|
||||
W.SliceClass == "aH" && W.Reducer == "ws.wrv" && W.Actions.size == 18 &&
|
||||
W.Actions.none { it.startsWith("?") }
|
||||
)
|
||||
Check(
|
||||
"根:lootBoxes 的动作名覆盖 18 个 instanceof 臂",
|
||||
listOf(
|
||||
"SetPossibleRewards", "SetCurrentContainer", "SetKeys", "AddKeys",
|
||||
"SetContainerData", "SetGoToGarage", "SetRewards", "SetTimeToGiveLootBox",
|
||||
"SetPeriodOfGiveLootBox", "ClearRewards", "LootBoxOpeningFailed",
|
||||
"SelectCategory", "ShownRewards", "SetIsOpening", "SetIsAccelerated",
|
||||
"SetIsShopVisited", "SetIsReopening", "SetIsBlockOpenButtons",
|
||||
).all { it in W.Actions }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val Failed = Results.filter { !it.second }
|
||||
|
||||
Reference in new issue
Block a user