feat(state): 迁移 garage 切片(31 字段 / 60 分支)

This commit is contained in:
TermiNexus committed 2026-10-23 13:58:47 +08:00
1 parent f66f431670
commit 3c0ed0a998
14 files changed
+3366 -153

No files matched your search

+13 -5
View File
@@ -143,7 +143,7 @@
`names/root_wiring.json` + `TankiOffline/.../App/State/RootState.kt`、`RootWiring.kt`。
- 根状态 `z6`(`TOState`)的 **89 个槽位**全部入表;字段顺序取 `toString`,槽位取构造函数 `<名>_1`。
- 根 reducer `k6` 的逐切片委派记全:reducer 引用、委派表达式、顶层 `instanceof` 动作名。
- 已迁移切片 **8/89**:
- 已迁移切片 **9/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)
@@ -164,9 +164,17 @@
`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 槽位完整性、八片一次归约互不串片、未迁移字段透传,
- `garage` → `UB` / `hF`(`GarageReducer.kt`):31 字段;58 个 `instanceof` 臂,
另加 `Xd(t, LF)`(`GarageInsertDeviceClientAndServer`,`LF` 是标记接口)与 `yd(t, Nu)`(`xU` 单例)两个非 `instanceof` 臂,**共 60 分支**。
`PB`(`itemsByBase`)用 `GarageBaseItems`,`UF`(`baseItemIdToDevices`)与 `GarageDevice` 强类型;
`aB` 的 `urb_1` = `modification.vcf_1 ?: id`、`trb_1` = `hasDeviceSale && owned`。
`GarageLoaded` 按 depot/mounted/market/skins 重建物品表;`MU` 的 `krg` 是批量 `owned + mounted = true`(不是卸下);
`dU`/`wU` 写整组 `mountedSkin`/`mountedShotSkin`;`GoTo` 仅在目标屏幕为 Garage 时复位 `currentCategory`。
无 `toString` 的 25 个匿名类按效果定名(`rF`→`ResetGarage` … `FB`→`ClearWaitServerItemMount`);
`FX`(Garage 屏幕)与 `UB`(状态类)已从动作表剔除并记入 `Markers`(`screen-id-class` / `state-class`)。
- `App/State/RootReducer.kt`:`ReduceRoot(State0, Action)` 只归约上述九片,其余 **80 个字段原样透传**;
迁移进度用 `RootReducer.Coverage`(`已迁移 9/89`)与 `RootWiring.Unmigrated` 暴露。
- 自检:`TankiOffline/tools/RunStateTest.ps1` **234 项全绿**,含 89 槽位完整性、九片一次归约互不串片、未迁移字段透传,
以及用 `action_trace_merged.json` 真实派发的两条商店动作(`SetCoinPaymentProviderUrl` / `ToggleShopActivity`)回放。
- 委派表里的已知形态:`shop` / `home` / `localBattleUserState` / `partner` 是内联 IIFE,
已按字段具名为 `(inline@<field>)` 并从函数体抓到顶层动作名(69 / 1 / 2 / 11 个);
@@ -175,7 +183,7 @@
`o1` → `ShowShopDiscountNotification`(`$_$.gl` 单例,由 module_18334 的 `Qx.qge` 派发;
效果是把 `HomeScreen.shopDiscountNotification` 置 true,对应主菜单商店项的折扣提示);
shop 的 17 个匿名类按 reducer 效果定名并登记(`Yut`→`ShopLoaded`、`F5`→`ResetShop` 等);
screen 的 9 个、lootBoxes 的 6 个匿名类同样按效果定名并登记。
screen 的 9 个、lootBoxes 的 6 个、garage 的 25 个匿名类同样按效果定名并登记。
### S4 引擎核心
- 模块:32088(3D 数学)、92980(渲染)、61378(ECS)、55088(物理)、36659 + 71105(资源)、36734(音频)、3569(输入)
+189 -30
View File
@@ -1,5 +1,5 @@
{
"generatedAt": "2026-10-23T04:19:26.216Z",
"generatedAt": "2026-10-23T05:35:58.037Z",
"module": "module_10032.js",
"RootState": {
"Class": "z6",
@@ -14,7 +14,8 @@
"localBattleUserState",
"shop",
"screen",
"lootBoxes"
"lootBoxes",
"garage"
],
"AnonymousActions": [
{
@@ -215,6 +216,156 @@
"Name": "SetIsBlockOpenButtons",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "isBlockOpenButtons = action.xrv_1(第 18 个构造参数)"
},
{
"Local": "rF",
"Name": "ResetGarage",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "devices = new UF()、upgradeCoolDownEndTimes = emptyMap()、upgradeItemInProcessingId = null"
},
{
"Local": "EU",
"Name": "SetGarageObject",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "garageObject = action.nrk_1"
},
{
"Local": "RU",
"Name": "SetItemsOnDepot",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "itemsOnDepot = action.rrk_1"
},
{
"Local": "PU",
"Name": "SetItemsOnMarket",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "itemsOnMarket = action.srk_1"
},
{
"Local": "NU",
"Name": "SetMountedItems",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "mountedItems = action.trk_1"
},
{
"Local": "eF",
"Name": "GarageLoaded",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "按 itemsOnDepot / mountedItems / itemsOnMarket / skinsItems 重建 items(PB),isLoaded = true"
},
{
"Local": "VB",
"Name": "SetGarageNotLoaded",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "isLoaded = false"
},
{
"Local": "JB",
"Name": "SelectGarageCategory",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "currentCategory = action.qri_1、currentItemId = null"
},
{
"Local": "QU",
"Name": "AddGarageItemCount",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "items.grg(item, copy(owned=true, count += action.trl_1, devicePreview = 改装等级对应项))"
},
{
"Local": "SU",
"Name": "GarageMountItem",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "items.jrg(action.lrk_1)(同 viewCategory 先卸下已装配项,再装配本项)"
},
{
"Local": "iU",
"Name": "GarageUnmountItem",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "items.mrg(action.crj_1)(mounted = false)"
},
{
"Local": "MU",
"Name": "GarageMountItems",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "items.krg(action.urk_1)(批量 mounted = true)"
},
{
"Local": "dU",
"Name": "SetMountedSkinForItems",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "items.qrg(action.vrj_1, action.wrj_1)(整组 mountedSkin = skinId)"
},
{
"Local": "wU",
"Name": "SetMountedShotSkinForItems",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "items.rrg(action.srj_1, action.trj_1)(整组 mountedShotSkin = skinId)"
},
{
"Local": "aF",
"Name": "SetNewItems",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "newItems = action.srm_1"
},
{
"Local": "cF",
"Name": "AddNewItem",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "newItems[itemId] += value(Set 并集)"
},
{
"Local": "lF",
"Name": "RemoveNewItem",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "newItems[itemId] -= value,空集则删键"
},
{
"Local": "fF",
"Name": "SetResistanceReadyToMount",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "resistanceReadyToMount = action.nrm_1"
},
{
"Local": "bU",
"Name": "ResetMountDelay",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "delayMountTimeMs = 0"
},
{
"Local": "vF",
"Name": "StartCooldownReduce",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "cooldownReduceEndTime = now + seconds*1000、timeToReduceUpdateInMin = action.mrm_1"
},
{
"Local": "pF",
"Name": "SetCooldownReduceEndTime",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "cooldownReduceEndTime = now + seconds*1000"
},
{
"Local": "yU",
"Name": "ShowGarageDiscountNotification",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "garageDiscountNotification = true"
},
{
"Local": "WB",
"Name": "HideVideoInDescription",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "showVideoInDescription = false"
},
{
"Local": "OU",
"Name": "RemoveWaitServerItemMount",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "waitServerItemMount 的给定键全部置 false"
},
{
"Local": "FB",
"Name": "ClearWaitServerItemMount",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "waitServerItemMount = emptyMap()"
}
],
"Markers": [
@@ -237,6 +388,16 @@
"Local": "RX",
"Kind": "screen-id-class",
"Evidence": "屏幕标识类 System.Loader:`function RX(){ this.ysb_1=\"System.Loader\" }`,`PX()` 返回该单例,也是 WK.current 的默认值;GoTo 到它时不发 screen_show。"
},
{
"Local": "FX",
"Kind": "screen-id-class",
"Evidence": "屏幕标识类 Garage:`function FX(t){ … this.irm_1 = t; this.jrm_1 = \"Garage\" }`,默认分类 `BB().sq5_1`;garage 的 GoTo 分支用它把 currentCategory 复位。"
},
{
"Local": "UB",
"Kind": "state-class",
"Evidence": "garage 切片状态类:`function UB(…31 字段)`,toString 名 \"Garage(...)\";InitState 分支用 `h instanceof UB` 校验灌入状态,不是动作。"
}
],
"Rows": [
@@ -372,43 +533,22 @@
"Reducer": "hF",
"Delegate": "hF(t, n)",
"Actions": [
"?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",
"AddGarageItemCount",
"AddNewItem",
"AddOverdriveDescription",
"AddRelatedBazooka",
"AddRelatedResistanceModule",
"AddToFavoriteItem",
"BuyProtectionSlot",
"Garage",
"ClearWaitServerItemMount",
"GarageAddItemToDepot",
"GarageApplyResistanceMount",
"GarageBuyDevice",
"GarageDeviceTrySetInfinityLifetime",
"GarageDevicesLoadedAction",
"GarageLoaded",
"GarageMountItem",
"GarageMountItems",
"GarageRemoveDevice",
"GarageRemoveDiscountForDevice",
"GarageRemoveItemFromDepot",
@@ -418,23 +558,42 @@
"GarageSelectItem",
"GarageSelectShotSkin",
"GarageSelectSkin",
"GarageUnmountItem",
"GarageUpdateItemCount",
"GarageUpgradeItemAction",
"GarageUpgradeItemConfirm",
"GarageUpgradeItemCoolDownFinished",
"GarageUpgradeItemCoolDownStarted",
"GoTo",
"HideVideoInDescription",
"InitState",
"RemoveFromFavoriteItem",
"RemoveFromSale",
"RemoveNewItem",
"RemoveWaitServerItemMount",
"ResetGarage",
"ResetMountDelay",
"SelectGarageCategory",
"SetCooldownReduceEndTime",
"SetGarageNotLoaded",
"SetGarageObject",
"SetGarageSkinsAction",
"SetItemRarity",
"SetItemsOnDepot",
"SetItemsOnMarket",
"SetMountedItems",
"SetMountedShotSkinForItems",
"SetMountedSkinForItems",
"SetNewItems",
"SetResistanceReadyToMount",
"SetRestrictionMount",
"ShowGarageDiscountNotification",
"StartCooldownReduce",
"SwitchingRowsScroll",
"UpdateDiscount"
],
"Default": "new UB()",
"Migrated": false
"Migrated": true
},
{
"Field": "garagePreview",
File diff suppressed because it is too large. Load diff
+61
View File
@@ -574,6 +574,67 @@ const FieldTypeOverrides = new Map([
['SetCurrentContainer.type', 'ContainerType'],
['SelectCategory.containerCategory', 'ContainerType?'],
['ShownRewards.rewards', 'List<ContainerGivenItem>'],
// garage 切片:生成器从调用点只能给 Any?,但 reducer 里这些字段参与 Map 键/算术/子状态类型。
// · 装备/物品 id 在运行时派发里都是 Long;count 是 Int。
// · itemsByBase 的值是 iB(无 toString):Kotlin 侧自建 GarageBaseItems(Items + 派生字段)。
// · baseItemIdToDevices 的值是 List<VF(GarageDevice)>。
['GarageItem.id', 'Long'],
['GarageItem.count', 'Int'],
['GarageDevice.id', 'Long'],
['GarageDevice.baseItemId', 'Long'],
['GarageItem.modification', 'GarageItemModification?'],
['GarageItem.upgradeableParams', 'UpgradableItemParams?'],
['GarageItemsState.itemsByBase', 'Map<Long, GarageBaseItems>'],
['Devices.baseItemIdToDevices', 'Map<Long, List<GarageDevice>>'],
['GarageItemObject.id', 'Long'],
['GarageItemObject.count', 'Int'],
['GarageItemObject.itemCategory', 'Int'],
['GarageItemObject.itemViewCategory', 'Int'],
['GarageItemObject.isPremium', 'Boolean'],
['GarageItemObject.buyable', 'Boolean'],
['GarageItemObject.availableForAll', 'Boolean'],
['GarageItemObject.modification', 'GarageItemModification?'],
['GarageItemObject.upgradeableParams', 'UpgradableItemParams?'],
['GarageItemObject.visible', 'Boolean'],
['GarageDeviceObject.id', 'Long'],
['GarageDeviceObject.baseItemId', 'Long'],
['GarageDeviceObject.itemCategory', 'Int'],
['GarageDeviceObject.itemViewCategory', 'Int'],
['GarageDeviceObject.isPremium', 'Boolean'],
['GarageDeviceObject.buyable', 'Boolean'],
['GarageDeviceObject.visible', 'Boolean'],
['GarageDevicesLoadedAction.mountedDevice', 'GarageDevice?'],
['GarageSelectItem.itemId', 'Long'],
['GarageAddItemToDepot.itemId', 'Long'],
['GarageAddItemToDepot.count', 'Int'],
['GarageRemoveItemFromDepot.item', 'GarageItemObject'],
['GarageRemoveItemFromStore.itemId', 'Long'],
['GarageUpdateItemCount.item', 'GarageItem'],
['GarageUpdateItemCount.count', 'Int'],
['GarageUpgradeItemAction.itemId', 'Long'],
['GarageUpgradeItemCoolDownStarted.itemId', 'Long'],
['GarageUpgradeItemCoolDownStarted.endTime', 'Long'],
['GarageUpgradeItemCoolDownFinished.itemId', 'Long'],
['GarageUpgradeItemCoolDownFinished.endTime', 'Long'],
['GarageRemoveDevice.device', 'GarageDevice'],
['GarageRemoveDevice.item', 'GarageItem'],
['GarageInsertDeviceClientAndServer.device', 'GarageDevice'],
['GarageInsertDeviceClientAndServer.item', 'GarageItem'],
['GarageBuyDevice.device', 'GarageDevice'],
['GarageBuyDevice.item', 'GarageItem'],
['GarageDeviceTrySetInfinityLifetime.deviceId', 'Long'],
['GarageApplyResistanceMount.resistance', 'GarageItem'],
['GarageApplyResistanceMount.index', 'Int'],
['GarageResistanceUnMount.resistance', 'Long'],
['GarageSelectAlteration.alterationId', 'Long'],
['GarageSelectSkin.skinId', 'Long'],
['GarageSelectShotSkin.shotSkinId', 'Long'],
['GarageRemoveDiscountForDevice.item', 'GarageItem'],
['UpdateDiscount.item', 'GarageItem'],
['RemoveFromSale.removeItems', 'Collection<Long>'],
['AddToFavoriteItem.itemId', 'Long'],
['RemoveFromFavoriteItem.itemId', 'Long'],
['BuyProtectionSlot.slotToUnlock', 'Int'],
]);
const Emitted = [];
+70 -5
View File
@@ -75,13 +75,42 @@ const AnonymousActionNames = new Map([
['gH', 'LootBoxOpeningFailed'],
['CH', 'SetIsReopening'],
['SH', 'SetIsBlockOpenButtons'],
// garage 切片(UB / hF):没有 toString / 不在 action_inventory 的 25 个类。
// 括号里是原 reducer 的效果;实现见 GarageReducer.kt。
['rF', 'ResetGarage'],
['EU', 'SetGarageObject'],
['RU', 'SetItemsOnDepot'],
['PU', 'SetItemsOnMarket'],
['NU', 'SetMountedItems'],
['eF', 'GarageLoaded'],
['VB', 'SetGarageNotLoaded'],
['JB', 'SelectGarageCategory'],
['QU', 'AddGarageItemCount'],
['SU', 'GarageMountItem'],
['iU', 'GarageUnmountItem'],
['MU', 'GarageMountItems'],
['dU', 'SetMountedSkinForItems'],
['wU', 'SetMountedShotSkinForItems'],
['aF', 'SetNewItems'],
['cF', 'AddNewItem'],
['lF', 'RemoveNewItem'],
['fF', 'SetResistanceReadyToMount'],
['bU', 'ResetMountDelay'],
['vF', 'StartCooldownReduce'],
['pF', 'SetCooldownReduceEndTime'],
['yU', 'ShowGarageDiscountNotification'],
['WB', 'HideVideoInDescription'],
['OU', 'RemoveWaitServerItemMount'],
['FB', 'ClearWaitServerItemMount'],
]);
// 非动作的 `instanceof`:屏幕标识类,用于 GoTo 分支判断(不是 dispatch 的动作)。
// 它们从 Actions 列表里剔除,改记到 root_wiring.json 的 Markers。
const NonActionInstanceof = new Map([
['LX', '屏幕标识类 Entrance:`function LX(){ this.gs5_1="Entrance" }`,`ld(LX).m()` 返回 "Entrance";GoTo 分支用它判断 analytics 事件是否 isModal。'],
['RX', '屏幕标识类 System.Loader:`function RX(){ this.ysb_1="System.Loader" }`,`PX()` 返回该单例,也是 WK.current 的默认值;GoTo 到它时不发 screen_show。'],
['LX', { Kind: 'screen-id-class', Evidence: '屏幕标识类 Entrance:`function LX(){ this.gs5_1="Entrance" }`,`ld(LX).m()` 返回 "Entrance";GoTo 分支用它判断 analytics 事件是否 isModal。' }],
['RX', { Kind: 'screen-id-class', Evidence: '屏幕标识类 System.Loader:`function RX(){ this.ysb_1="System.Loader" }`,`PX()` 返回该单例,也是 WK.current 的默认值;GoTo 到它时不发 screen_show。' }],
['FX', { Kind: 'screen-id-class', Evidence: '屏幕标识类 Garage:`function FX(t){ … this.irm_1 = t; this.jrm_1 = "Garage" }`,默认分类 `BB().sq5_1`;garage 的 GoTo 分支用它把 currentCategory 复位。' }],
['UB', { Kind: 'state-class', Evidence: 'garage 切片状态类:`function UB(…31 字段)`,toString 名 "Garage(...)";InitState 分支用 `h instanceof UB` 校验灌入状态,不是动作。' }],
]);
/** 动作局部类名 → 动作名;未收录的返回 null。 */
@@ -441,6 +470,7 @@ const Migrated = {
shop: { Type: 'ShopStateSlice', Reduce: 'ReduceShop' },
screen: { Type: 'ScreenStateSlice', Reduce: 'ReduceScreen' },
lootBoxes: { Type: 'LootBoxesStateSlice', Reduce: 'ReduceLootBoxes' },
garage: { Type: 'GarageStateSlice', Reduce: 'ReduceGarage' },
};
for (const R of Rows) R.Migrated = Object.prototype.hasOwnProperty.call(Migrated, R.Field);
@@ -505,6 +535,35 @@ const LootBoxesAnonymousEffects = {
SH: 'isBlockOpenButtons = action.xrv_1(第 18 个构造参数)',
};
// garage 切片(UB / hF):没有 toString / 不在 action_inventory 的类,按 reducer 分支效果定名。
const GarageAnonymousEffects = {
rF: 'devices = new UF()、upgradeCoolDownEndTimes = emptyMap()、upgradeItemInProcessingId = null',
EU: 'garageObject = action.nrk_1',
RU: 'itemsOnDepot = action.rrk_1',
PU: 'itemsOnMarket = action.srk_1',
NU: 'mountedItems = action.trk_1',
eF: '按 itemsOnDepot / mountedItems / itemsOnMarket / skinsItems 重建 items(PB),isLoaded = true',
VB: 'isLoaded = false',
JB: 'currentCategory = action.qri_1、currentItemId = null',
QU: 'items.grg(item, copy(owned=true, count += action.trl_1, devicePreview = 改装等级对应项))',
SU: 'items.jrg(action.lrk_1)(同 viewCategory 先卸下已装配项,再装配本项)',
iU: 'items.mrg(action.crj_1)(mounted = false)',
MU: 'items.krg(action.urk_1)(批量 mounted = true)',
dU: 'items.qrg(action.vrj_1, action.wrj_1)(整组 mountedSkin = skinId)',
wU: 'items.rrg(action.srj_1, action.trj_1)(整组 mountedShotSkin = skinId)',
aF: 'newItems = action.srm_1',
cF: 'newItems[itemId] += value(Set 并集)',
lF: 'newItems[itemId] -= value,空集则删键',
fF: 'resistanceReadyToMount = action.nrm_1',
bU: 'delayMountTimeMs = 0',
vF: 'cooldownReduceEndTime = now + seconds*1000、timeToReduceUpdateInMin = action.mrm_1',
pF: 'cooldownReduceEndTime = now + seconds*1000',
yU: 'garageDiscountNotification = true',
WB: 'showVideoInDescription = false',
OU: 'waitServerItemMount 的给定键全部置 false',
FB: 'waitServerItemMount = emptyMap()',
};
fs.writeFileSync(
path.join(P, 'names', 'root_wiring.json'),
JSON.stringify(
@@ -539,6 +598,12 @@ fs.writeFileSync(
DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名',
Effect,
})),
...Object.entries(GarageAnonymousEffects).map(([Local, Effect]) => ({
Local,
Name: AnonymousActionNames.get(Local),
DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名',
Effect,
})),
],
Markers: [
{
@@ -551,10 +616,10 @@ fs.writeFileSync(
Kind: 'marker-action',
Evidence: '`fd(x4, "", cd, Wm)`;根 reducer 的 `Xd(t, $4) || t instanceof x4` 同样走 U7+w3 快路径,本身无字段、无独立效果。',
},
...[...NonActionInstanceof].map(([Local, Evidence]) => ({
...[...NonActionInstanceof].map(([Local, Info]) => ({
Local,
Kind: 'screen-id-class',
Evidence,
Kind: Info.Kind,
Evidence: Info.Evidence,
})),
],
Rows,
+56 -13
View File
@@ -268,7 +268,24 @@ function KotlinName(Field) { return Field.charAt(0).toUpperCase() + Field.slice(
function PlanSlice(Field) {
const F = RootFields.find((X) => X.Field === Field);
if (!F) throw new Error('根状态里没有字段 ' + Field);
const A = Assignments.find((X) => new RegExp('\\bn\\.' + F.Slot + '\\b').test(X.Expr));
// 委派有两种形状:
// 1) 根 reducer 内联写 `n.<slot>`(多数切片,或 `n.<slot>.方法(t)`)
// 2) 根 reducer 直接调用具名 reducer `hF(t, n)`,切片槽位只在 hF 体内出现(garage)
// 第 2 种需要回查被调函数的函数体,确认它确实读写 `n.<slot>`。
let A = Assignments.find((X) => new RegExp('\\bn\\.' + F.Slot + '\\b').test(X.Expr));
let NamedReducer = null;
if (!A) {
for (const X of Assignments) {
const Call = /^([A-Za-z0-9_$]+)\(t,\s*n\)$/.exec(X.Expr.replace(/\s+/g, ' ').trim());
if (!Call || !Fns.has(Call[1])) continue;
const Fn = Fns.get(Call[1]);
if (new RegExp('\\bn\\.' + F.Slot + '\\b').test(T.slice(Fn.Body, Fn.End))) {
A = X;
NamedReducer = Call[1];
break;
}
}
}
if (!A) throw new Error(Field + ' 在根 reducer 里找不到委派');
const Param = [...SlotOfParam.keys()].find((P2) => SlotOfParam.get(P2) === F.Slot);
@@ -277,7 +294,7 @@ function PlanSlice(Field) {
const Info = {
Field, Slot: F.Slot, Class: ClassName, ClassSource: DefaultOfParam.get(Param) || '',
Params: [], Fields: [], CopyMethod: null, Branches: [], SubReducers: [], Counts: {}, Locals: {},
NamedReducer, Params: [], Fields: [], CopyMethod: null, Branches: [], SubReducers: [], Counts: {}, Locals: {},
Delegate: A.Expr.replace(/\s+/g, ' ').trim().slice(0, 400),
};
@@ -302,31 +319,57 @@ function PlanSlice(Field) {
}
const CopyName = (A.Expr.match(/\bn\.([A-Za-z0-9_$]+)\(/) || [])[1] || null;
Info.CopyMethod = CopyName;
if (CopyName && ClassName) {
const M = Methods.find((X) => X.Obj === ClassName && X.Method === CopyName);
// 具名 reducer 的副本方法只在函数体里(`o.hri(...)`),按「参数个数 == 字段数」锁定。
let NamedBody = null;
if (NamedReducer) {
const Fn = Fns.get(NamedReducer);
NamedBody = T.slice(Fn.Body, Fn.End);
const ByArity = [];
for (const M of NamedBody.matchAll(/\bo\.([A-Za-z0-9_$]+)\(/g)) {
let O = M.index + M[0].length, D = 0, E = -1;
for (let J = O; J < NamedBody.length; J++) {
const C2 = NamedBody[J];
if (C2 === '(') D++;
else if (C2 === ')') { D--; if (D === 0) { E = J; break; } }
}
if (E < 0) continue;
const N = SplitTop(NamedBody.slice(O, E)).length;
ByArity.push({ Name: M[1], Count: N });
}
const Hit = ByArity.find((X) => X.Count === Info.Fields.length);
Info.CopyMethod = Hit ? Hit.Name : (ByArity[0] || {}).Name || null;
} else {
Info.CopyMethod = CopyName;
}
const CopyMethodName = Info.CopyMethod;
if (CopyMethodName && ClassName) {
const M = Methods.find((X) => X.Obj === ClassName && X.Method === CopyMethodName);
Info.CopyParamCount = M ? M.Params.split(',').map((S) => S.trim()).filter(Boolean).length : null;
Info.CopyMatchesFields = Info.CopyParamCount === Info.Fields.length;
}
// ---- 分支抽取:只处理内联 IIFE;具名 reducer 请用 gen_root_wiring 的证据表
// ---- 分支抽取:内联 IIFE 或具名 reducer(`hF(t, n)`)
const Expr = A.Expr;
if (!/^\(function \(t, n\)/.test(Expr)) {
let Body;
if (NamedReducer) {
Body = NamedBody;
} else if (/^\(function \(t, n\)/.test(Expr)) {
// IIFE 的 { 位置要对到全局源码:用 Pos 定位
const Pos = T.indexOf(A.Expr, RootReducerFn.Body);
const GlobalOpen = T.indexOf('{', T.indexOf('function', Pos));
const GlobalClose = Pairs.get(GlobalOpen);
Body = T.slice(GlobalOpen + 1, GlobalClose);
} else {
Info.Branches = [{ Guard: '(具名 reducer)', Locals: [], Kind: 'named', Expr: Expr.replace(/\s+/g, ' ').slice(0, 200) }];
Info.Counts = { TotalInstanceof: null, TopLevelBranches: 1, SubReducerArms: 0 };
return Info;
}
// IIFE 的 { 位置要对到全局源码:用 Pos 定位
const Pos = T.indexOf(A.Expr, RootReducerFn.Body);
const GlobalOpen = T.indexOf('{', T.indexOf('function', Pos));
const GlobalClose = Pairs.get(GlobalOpen);
const Body = T.slice(GlobalOpen + 1, GlobalClose);
function Decode(BranchExpr) {
const E = BranchExpr.replace(/\s+/g, ' ').trim();
const Fields = [];
if (/^n$/.test(E)) return { Kind: 'noop', Changed: [] };
const CopyCall = new RegExp('\\bn\\.' + (CopyName || 'qt6') + '\\(');
const CopyCall = new RegExp('\\b(?:n|o)\\.' + (CopyMethodName || 'qt6') + '\\(');
const M = CopyCall.exec(E);
if (/new [A-Za-z0-9_$]+\(/.test(E) && !M) return { Kind: 'reset', Changed: [] };
if (!M) return { Kind: 'conditional', Changed: [] };
@@ -268,66 +268,66 @@ object ActionCatalog {
ActionMeta("ShowNotificationAlreadyInOutgoingFriends", ShowNotificationAlreadyInOutgoingFriends::class, listOf(ActionFieldMeta("uid", "Any?"), ActionFieldMeta("userId", "Any?"))),
ActionMeta("ShowNotificationAcceptedLimitExceeded", ShowNotificationAcceptedLimitExceeded::class, listOf(ActionFieldMeta("uid", "Any?"), ActionFieldMeta("userId", "Any?"))),
ActionMeta("Discount", Discount::class, listOf(ActionFieldMeta("itemId", "Any?"), ActionFieldMeta("discount", "Float"), ActionFieldMeta("timeLeft", "Any?"), ActionFieldMeta("timeStart", "Any?"), ActionFieldMeta("isBuyable", "Boolean"))),
ActionMeta("UpdateDiscount", UpdateDiscount::class, listOf(ActionFieldMeta("item", "Any?"))),
ActionMeta("GarageItem", GarageItem::class, listOf(ActionFieldMeta("id", "Any?"), ActionFieldMeta("name", "Any?"), ActionFieldMeta("category", "Int"), ActionFieldMeta("viewCategory", "Int"), ActionFieldMeta("priceWithoutDiscount", "Int"), ActionFieldMeta("description", "Any?"), ActionFieldMeta("descriptionMedia", "Any?"), ActionFieldMeta("preview", "Any?"), ActionFieldMeta("isPremium", "Any?"), ActionFieldMeta("owned", "Boolean"), ActionFieldMeta("mountable", "Any?"), ActionFieldMeta("mounted", "Boolean"), ActionFieldMeta("mountIndex", "Any?"), ActionFieldMeta("position", "Int"), ActionFieldMeta("minRank", "Int"), ActionFieldMeta("maxRank", "Int"), ActionFieldMeta("discount", "Any?"), ActionFieldMeta("modification", "Any?"), ActionFieldMeta("properties", "Any?"), ActionFieldMeta("upgradeableParams", "Any?"), ActionFieldMeta("count", "Any?"), ActionFieldMeta("kit", "Any?"), ActionFieldMeta("countable", "Any?"), ActionFieldMeta("hasDeviceSale", "Boolean"), ActionFieldMeta("devicesAvailable", "Boolean"), ActionFieldMeta("devicePreview", "Any?"), ActionFieldMeta("buyable", "Boolean"), ActionFieldMeta("availableForAll", "Boolean"), ActionFieldMeta("availableSkins", "Any?"), ActionFieldMeta("availableShotSkins", "Any?"), ActionFieldMeta("mountedSkin", "Any?"), ActionFieldMeta("mountedShotSkin", "Any?"), ActionFieldMeta("skinPreview", "Any?"), ActionFieldMeta("group", "Any?"), ActionFieldMeta("rarity", "Any?"), ActionFieldMeta("favorite", "Boolean"), ActionFieldMeta("resistanceBackgroundImg", "Any?"), ActionFieldMeta("visible", "Boolean"), ActionFieldMeta("secret", "Boolean"), ActionFieldMeta("saleByKitItems", "Any?"), ActionFieldMeta("isSellingForCoin", "Boolean"), ActionFieldMeta("isSellingForCrystal", "Boolean"), ActionFieldMeta("isSellingForRuby", "Boolean"), ActionFieldMeta("isSellingForMoney", "Boolean"), ActionFieldMeta("isSellingForADS", "Boolean"))),
ActionMeta("UpdateDiscount", UpdateDiscount::class, listOf(ActionFieldMeta("item", "GarageItem"))),
ActionMeta("GarageItem", GarageItem::class, listOf(ActionFieldMeta("id", "Long"), ActionFieldMeta("name", "Any?"), ActionFieldMeta("category", "Int"), ActionFieldMeta("viewCategory", "Int"), ActionFieldMeta("priceWithoutDiscount", "Int"), ActionFieldMeta("description", "Any?"), ActionFieldMeta("descriptionMedia", "Any?"), ActionFieldMeta("preview", "Any?"), ActionFieldMeta("isPremium", "Any?"), ActionFieldMeta("owned", "Boolean"), ActionFieldMeta("mountable", "Any?"), ActionFieldMeta("mounted", "Boolean"), ActionFieldMeta("mountIndex", "Any?"), ActionFieldMeta("position", "Int"), ActionFieldMeta("minRank", "Int"), ActionFieldMeta("maxRank", "Int"), ActionFieldMeta("discount", "Any?"), ActionFieldMeta("modification", "GarageItemModification?"), ActionFieldMeta("properties", "Any?"), ActionFieldMeta("upgradeableParams", "UpgradableItemParams?"), ActionFieldMeta("count", "Int"), ActionFieldMeta("kit", "Any?"), ActionFieldMeta("countable", "Any?"), ActionFieldMeta("hasDeviceSale", "Boolean"), ActionFieldMeta("devicesAvailable", "Boolean"), ActionFieldMeta("devicePreview", "Any?"), ActionFieldMeta("buyable", "Boolean"), ActionFieldMeta("availableForAll", "Boolean"), ActionFieldMeta("availableSkins", "Any?"), ActionFieldMeta("availableShotSkins", "Any?"), ActionFieldMeta("mountedSkin", "Any?"), ActionFieldMeta("mountedShotSkin", "Any?"), ActionFieldMeta("skinPreview", "Any?"), ActionFieldMeta("group", "Any?"), ActionFieldMeta("rarity", "Any?"), ActionFieldMeta("favorite", "Boolean"), ActionFieldMeta("resistanceBackgroundImg", "Any?"), ActionFieldMeta("visible", "Boolean"), ActionFieldMeta("secret", "Boolean"), ActionFieldMeta("saleByKitItems", "Any?"), ActionFieldMeta("isSellingForCoin", "Boolean"), ActionFieldMeta("isSellingForCrystal", "Boolean"), ActionFieldMeta("isSellingForRuby", "Boolean"), ActionFieldMeta("isSellingForMoney", "Boolean"), ActionFieldMeta("isSellingForADS", "Boolean"))),
ActionMeta("ItemProperty", ItemProperty::class, listOf(ActionFieldMeta("params", "Any?"), ActionFieldMeta("value", "Any?"))),
ActionMeta("Kit", Kit::class, listOf(ActionFieldMeta("items", "Any?"))),
ActionMeta("GarageItemObject", GarageItemObject::class, listOf(ActionFieldMeta("id", "Any?"), ActionFieldMeta("description", "Any?"), ActionFieldMeta("item", "Any?"), ActionFieldMeta("isPremium", "Any?"), ActionFieldMeta("itemCategory", "Any?"), ActionFieldMeta("itemViewCategory", "Any?"), ActionFieldMeta("buyable", "Any?"), ActionFieldMeta("availableForAll", "Any?"), ActionFieldMeta("discount", "Any?"), ActionFieldMeta("timePeriod", "Any?"), ActionFieldMeta("modification", "Any?"), ActionFieldMeta("properties", "Any?"), ActionFieldMeta("upgradeableParams", "Any?"), ActionFieldMeta("devices", "Any?"), ActionFieldMeta("count", "Any?"), ActionFieldMeta("kit", "Any?"), ActionFieldMeta("countable", "Any?"), ActionFieldMeta("mountIndex", "Any?"), ActionFieldMeta("availableSkins", "Any?"), ActionFieldMeta("availableShotSkins", "Any?"), ActionFieldMeta("mountedSkin", "Any?"), ActionFieldMeta("mountedShotSkin", "Any?"), ActionFieldMeta("skinPreview", "Any?"), ActionFieldMeta("grouped", "Any?"), ActionFieldMeta("rarity", "Any?"), ActionFieldMeta("favorite", "Boolean"), ActionFieldMeta("isSellingForCoin", "Any?"), ActionFieldMeta("isSellingForCrystal", "Any?"), ActionFieldMeta("isSellingForRuby", "Any?"), ActionFieldMeta("resistanceBackgroundImg", "Any?"), ActionFieldMeta("visible", "Any?"), ActionFieldMeta("descriptionMedia", "Any?"), ActionFieldMeta("saleByKitItems", "Any?"))),
ActionMeta("GarageItemObject", GarageItemObject::class, listOf(ActionFieldMeta("id", "Long"), ActionFieldMeta("description", "Any?"), ActionFieldMeta("item", "Any?"), ActionFieldMeta("isPremium", "Boolean"), ActionFieldMeta("itemCategory", "Int"), ActionFieldMeta("itemViewCategory", "Int"), ActionFieldMeta("buyable", "Boolean"), ActionFieldMeta("availableForAll", "Boolean"), ActionFieldMeta("discount", "Any?"), ActionFieldMeta("timePeriod", "Any?"), ActionFieldMeta("modification", "GarageItemModification?"), ActionFieldMeta("properties", "Any?"), ActionFieldMeta("upgradeableParams", "UpgradableItemParams?"), ActionFieldMeta("devices", "Any?"), ActionFieldMeta("count", "Int"), ActionFieldMeta("kit", "Any?"), ActionFieldMeta("countable", "Any?"), ActionFieldMeta("mountIndex", "Any?"), ActionFieldMeta("availableSkins", "Any?"), ActionFieldMeta("availableShotSkins", "Any?"), ActionFieldMeta("mountedSkin", "Any?"), ActionFieldMeta("mountedShotSkin", "Any?"), ActionFieldMeta("skinPreview", "Any?"), ActionFieldMeta("grouped", "Any?"), ActionFieldMeta("rarity", "Any?"), ActionFieldMeta("favorite", "Boolean"), ActionFieldMeta("isSellingForCoin", "Any?"), ActionFieldMeta("isSellingForCrystal", "Any?"), ActionFieldMeta("isSellingForRuby", "Any?"), ActionFieldMeta("resistanceBackgroundImg", "Any?"), ActionFieldMeta("visible", "Boolean"), ActionFieldMeta("descriptionMedia", "Any?"), ActionFieldMeta("saleByKitItems", "Any?"))),
ActionMeta("KitItem", KitItem::class, listOf(ActionFieldMeta("id", "Any?"), ActionFieldMeta("count", "Int"), ActionFieldMeta("mount", "Boolean"))),
ActionMeta("GarageItemsState", GarageItemsState::class, listOf(ActionFieldMeta("itemsByBase", "Any?"))),
ActionMeta("GarageItemsState", GarageItemsState::class, listOf(ActionFieldMeta("itemsByBase", "Map<Long, GarageBaseItems>"))),
ActionMeta("GarageObject", GarageObject::class, listOf(ActionFieldMeta("temperatureCC", "Any?"))),
ActionMeta("Garage", Garage::class, listOf(ActionFieldMeta("itemsOnDepot", "Any?"), ActionFieldMeta("itemsOnMarket", "Any?"), ActionFieldMeta("mountedItems", "Any?"), ActionFieldMeta("skinsItems", "Any?"), ActionFieldMeta("items", "Any?"), ActionFieldMeta("devices", "Any?"), ActionFieldMeta("garageObject", "Any?"), ActionFieldMeta("isLoaded", "Boolean"), ActionFieldMeta("currentCategory", "Any?"), ActionFieldMeta("currentItemId", "Any?"), ActionFieldMeta("selectAlterationId", "Any?"), ActionFieldMeta("selectedSkinId", "Any?"), ActionFieldMeta("selectedShotSkinId", "Any?"), ActionFieldMeta("newItems", "Any?"), ActionFieldMeta("resistanceReadyToMount", "Any?"), ActionFieldMeta("delayMountTimeMs", "Any?"), ActionFieldMeta("cooldownReduceEndTime", "Any?"), ActionFieldMeta("timeToReduceUpdateInMin", "Any?"), ActionFieldMeta("currentRarity", "Any?"), ActionFieldMeta("purchaseOfUpgradesShopItemId", "Any?"), ActionFieldMeta("garageDiscountNotification", "Any?"), ActionFieldMeta("showVideoInDescription", "Any?"), ActionFieldMeta("unlockedProtectionSlots", "Any?"), ActionFieldMeta("pricesToUnlockSlot", "Any?"), ActionFieldMeta("numberRowsInPaintsUI", "Any?"), ActionFieldMeta("overdriveDescriptions", "Any?"), ActionFieldMeta("weaponToResistanceModule", "Any?"), ActionFieldMeta("waitServerItemMount", "Any?"), ActionFieldMeta("bazookaToSupply", "Any?"), ActionFieldMeta("upgradeCoolDownEndTimes", "Any?"), ActionFieldMeta("upgradeItemInProcessingId", "Any?"))),
ActionMeta("AddItemToOrderProcessing", AddItemToOrderProcessing::class, listOf(ActionFieldMeta("itemId", "Any?"), ActionFieldMeta("onSuccess", "Any?"))),
ActionMeta("RequestItem3DPart", RequestItem3DPart::class, listOf(ActionFieldMeta("itemId", "Any?"))),
ActionMeta("GarageUpgradeItemAction", GarageUpgradeItemAction::class, listOf(ActionFieldMeta("itemId", "Any?"), ActionFieldMeta("expectedPrice", "Any?"), ActionFieldMeta("currency", "Any?"))),
ActionMeta("GarageSelectItem", GarageSelectItem::class, listOf(ActionFieldMeta("itemId", "Any?"))),
ActionMeta("GarageUpgradeItemAction", GarageUpgradeItemAction::class, listOf(ActionFieldMeta("itemId", "Long"), ActionFieldMeta("expectedPrice", "Any?"), ActionFieldMeta("currency", "Any?"))),
ActionMeta("GarageSelectItem", GarageSelectItem::class, listOf(ActionFieldMeta("itemId", "Long"))),
ActionMeta("GarageBuyResistanceModule", GarageBuyResistanceModule::class, listOf(ActionFieldMeta("resistance", "Any?"))),
ActionMeta("GarageItemBought", GarageItemBought::class, listOf(ActionFieldMeta("item", "Any?"), ActionFieldMeta("count", "Int"), ActionFieldMeta("select", "Any?"))),
ActionMeta("GarageItemMounted", GarageItemMounted::class, listOf(ActionFieldMeta("item", "Any?"), ActionFieldMeta("needServerMount", "Boolean"))),
ActionMeta("GarageResistanceUnMount", GarageResistanceUnMount::class, listOf(ActionFieldMeta("resistance", "Any?"), ActionFieldMeta("needServerUnmount", "Boolean"))),
ActionMeta("GarageResistanceUnMount", GarageResistanceUnMount::class, listOf(ActionFieldMeta("resistance", "Long"), ActionFieldMeta("needServerUnmount", "Boolean"))),
ActionMeta("GarageResistanceMount", GarageResistanceMount::class, listOf(ActionFieldMeta("resistance", "Any?"), ActionFieldMeta("index", "Any?"))),
ActionMeta("BuyProtectionSlot", BuyProtectionSlot::class, listOf(ActionFieldMeta("slotToUnlock", "Any?"))),
ActionMeta("BuyProtectionSlot", BuyProtectionSlot::class, listOf(ActionFieldMeta("slotToUnlock", "Int"))),
ActionMeta("SetItemRarity", SetItemRarity::class, listOf(ActionFieldMeta("rarity", "Any?"))),
ActionMeta("SwitchingRowsScroll", SwitchingRowsScroll::class, listOf(ActionFieldMeta("numberRowsInPaintsUI", "Int"))),
ActionMeta("BuyGarageKit", BuyGarageKit::class, listOf(ActionFieldMeta("item", "Any?"))),
ActionMeta("GarageSelectAlteration", GarageSelectAlteration::class, listOf(ActionFieldMeta("alterationId", "Any?"))),
ActionMeta("GarageSelectSkin", GarageSelectSkin::class, listOf(ActionFieldMeta("skinId", "Any?"))),
ActionMeta("GarageSelectShotSkin", GarageSelectShotSkin::class, listOf(ActionFieldMeta("shotSkinId", "Any?"))),
ActionMeta("GarageSelectAlteration", GarageSelectAlteration::class, listOf(ActionFieldMeta("alterationId", "Long"))),
ActionMeta("GarageSelectSkin", GarageSelectSkin::class, listOf(ActionFieldMeta("skinId", "Long"))),
ActionMeta("GarageSelectShotSkin", GarageSelectShotSkin::class, listOf(ActionFieldMeta("shotSkinId", "Long"))),
ActionMeta("GarageLoadAvailableDevices", GarageLoadAvailableDevices::class, listOf(ActionFieldMeta("itemId", "Any?"))),
ActionMeta("RemoveFromFavoriteItem", RemoveFromFavoriteItem::class, listOf(ActionFieldMeta("itemId", "Any?"))),
ActionMeta("AddToFavoriteItem", AddToFavoriteItem::class, listOf(ActionFieldMeta("itemId", "Any?"))),
ActionMeta("RemoveFromFavoriteItem", RemoveFromFavoriteItem::class, listOf(ActionFieldMeta("itemId", "Long"))),
ActionMeta("AddToFavoriteItem", AddToFavoriteItem::class, listOf(ActionFieldMeta("itemId", "Long"))),
ActionMeta("GoToGarageAndSelectItem", GoToGarageAndSelectItem::class, listOf(ActionFieldMeta("category", "Int"), ActionFieldMeta("itemId", "Long"))),
ActionMeta("AddRelatedBazooka", AddRelatedBazooka::class, listOf(ActionFieldMeta("bazookaId", "Long"), ActionFieldMeta("supplyId", "Long"))),
ActionMeta("GarageUpgradeItemConfirm", GarageUpgradeItemConfirm::class, listOf(ActionFieldMeta("itemId", "Long"), ActionFieldMeta("level", "Int"))),
ActionMeta("GarageDevicesLoadedAction", GarageDevicesLoadedAction::class, listOf(ActionFieldMeta("baseItemId", "Long"), ActionFieldMeta("devices", "List<GarageDeviceObject>"), ActionFieldMeta("mountedDevice", "List<String>?"))),
ActionMeta("GarageDevicesLoadedAction", GarageDevicesLoadedAction::class, listOf(ActionFieldMeta("baseItemId", "Long"), ActionFieldMeta("devices", "List<GarageDeviceObject>"), ActionFieldMeta("mountedDevice", "GarageDevice?"))),
ActionMeta("SetRestrictionMount", SetRestrictionMount::class, listOf(ActionFieldMeta("delayMs", "Int"))),
ActionMeta("SetGarageSkinsAction", SetGarageSkinsAction::class, listOf(ActionFieldMeta("skins", "List<GarageItemObject>"))),
ActionMeta("GarageRemoveItemFromStore", GarageRemoveItemFromStore::class, listOf(ActionFieldMeta("itemId", "Any?"))),
ActionMeta("GarageRemoveItemFromStore", GarageRemoveItemFromStore::class, listOf(ActionFieldMeta("itemId", "Long"))),
ActionMeta("GarageUpdateDepotItem", GarageUpdateDepotItem::class, listOf(ActionFieldMeta("itemId", "Int"), ActionFieldMeta("count", "Int"))),
ActionMeta("GarageApplyResistanceMount", GarageApplyResistanceMount::class, listOf(ActionFieldMeta("resistance", "Any?"), ActionFieldMeta("index", "Any?"), ActionFieldMeta("needServerMount", "Boolean"))),
ActionMeta("GarageApplyResistanceMount", GarageApplyResistanceMount::class, listOf(ActionFieldMeta("resistance", "GarageItem"), ActionFieldMeta("index", "Int"), ActionFieldMeta("needServerMount", "Boolean"))),
ActionMeta("GarageApplyResistanceMountOnBuy", GarageApplyResistanceMountOnBuy::class, listOf(ActionFieldMeta("resistance", "Any?"), ActionFieldMeta("index", "Any?"), ActionFieldMeta("currency", "Any?"))),
ActionMeta("RemoveItemFromOrderProcessing", RemoveItemFromOrderProcessing::class, listOf(ActionFieldMeta("itemId", "Any?"), ActionFieldMeta("success", "Boolean"))),
ActionMeta("AddOverdriveDescription", AddOverdriveDescription::class, listOf(ActionFieldMeta("itemId", "Long"), ActionFieldMeta("descriptionData", "OverdriveDescriptionData"))),
ActionMeta("AddRelatedResistanceModule", AddRelatedResistanceModule::class, listOf(ActionFieldMeta("itemId", "Long"), ActionFieldMeta("resistanceModuleId", "Long"))),
ActionMeta("GarageUpgradeItemCoolDownStarted", GarageUpgradeItemCoolDownStarted::class, listOf(ActionFieldMeta("itemId", "Any?"), ActionFieldMeta("endTime", "Any?"))),
ActionMeta("GarageUpgradeItemCoolDownFinished", GarageUpgradeItemCoolDownFinished::class, listOf(ActionFieldMeta("itemId", "Any?"), ActionFieldMeta("endTime", "Any?"))),
ActionMeta("RemoveFromSale", RemoveFromSale::class, listOf(ActionFieldMeta("removeItems", "Any?"))),
ActionMeta("GarageUpdateItemCount", GarageUpdateItemCount::class, listOf(ActionFieldMeta("item", "Any?"), ActionFieldMeta("count", "Any?"))),
ActionMeta("GarageRemoveItemFromDepot", GarageRemoveItemFromDepot::class, listOf(ActionFieldMeta("item", "Any?"))),
ActionMeta("GarageAddItemToDepot", GarageAddItemToDepot::class, listOf(ActionFieldMeta("itemId", "Any?"), ActionFieldMeta("count", "Any?"))),
ActionMeta("GarageUpgradeItemCoolDownStarted", GarageUpgradeItemCoolDownStarted::class, listOf(ActionFieldMeta("itemId", "Long"), ActionFieldMeta("endTime", "Long"))),
ActionMeta("GarageUpgradeItemCoolDownFinished", GarageUpgradeItemCoolDownFinished::class, listOf(ActionFieldMeta("itemId", "Long"), ActionFieldMeta("endTime", "Long"))),
ActionMeta("RemoveFromSale", RemoveFromSale::class, listOf(ActionFieldMeta("removeItems", "Collection<Long>"))),
ActionMeta("GarageUpdateItemCount", GarageUpdateItemCount::class, listOf(ActionFieldMeta("item", "GarageItem"), ActionFieldMeta("count", "Int"))),
ActionMeta("GarageRemoveItemFromDepot", GarageRemoveItemFromDepot::class, listOf(ActionFieldMeta("item", "GarageItemObject"))),
ActionMeta("GarageAddItemToDepot", GarageAddItemToDepot::class, listOf(ActionFieldMeta("itemId", "Long"), ActionFieldMeta("count", "Int"))),
ActionMeta("ItemPurchaseData", ItemPurchaseData::class, listOf(ActionFieldMeta("isAvailable", "Boolean"), ActionFieldMeta("item", "Any?"), ActionFieldMeta("amount", "Any?"), ActionFieldMeta("priceWithoutDiscount", "Any?"), ActionFieldMeta("priceWithDiscount", "Any?"), ActionFieldMeta("discountInPercent", "Any?"), ActionFieldMeta("discountEndTime", "Any?"), ActionFieldMeta("isSellingForCrystal", "Boolean"), ActionFieldMeta("isSellingForCoin", "Boolean"), ActionFieldMeta("isSellingForRuby", "Boolean"), ActionFieldMeta("isAvailableByCrystals", "Boolean"), ActionFieldMeta("isAvailableByCoins", "Boolean"), ActionFieldMeta("isAvailableByRubies", "Boolean"), ActionFieldMeta("isAvailableByRank", "Boolean"), ActionFieldMeta("minRank", "Any?"), ActionFieldMeta("isAvailableByPremium", "Boolean"))),
ActionMeta("OverdriveDescriptionData", OverdriveDescriptionData::class, listOf(ActionFieldMeta("name", "Any?"), ActionFieldMeta("icon", "Any?"), ActionFieldMeta("description", "Any?"))),
ActionMeta("GarageLoadDevicesIfNotLoaded", GarageLoadDevicesIfNotLoaded::class, listOf(ActionFieldMeta("item", "Any?"))),
ActionMeta("GarageRemoveDevice", GarageRemoveDevice::class, listOf(ActionFieldMeta("device", "Any?"), ActionFieldMeta("item", "Any?"))),
ActionMeta("GarageInsertDeviceClientAndServer", GarageInsertDeviceClientAndServer::class, listOf(ActionFieldMeta("device", "Any?"), ActionFieldMeta("item", "Any?"))),
ActionMeta("GarageRemoveDevice", GarageRemoveDevice::class, listOf(ActionFieldMeta("device", "GarageDevice"), ActionFieldMeta("item", "GarageItem"))),
ActionMeta("GarageInsertDeviceClientAndServer", GarageInsertDeviceClientAndServer::class, listOf(ActionFieldMeta("device", "GarageDevice"), ActionFieldMeta("item", "GarageItem"))),
ActionMeta("GarageBuyAndInsertDevice", GarageBuyAndInsertDevice::class, listOf(ActionFieldMeta("device", "Any?"), ActionFieldMeta("price", "Any?"), ActionFieldMeta("itemId", "Any?"))),
ActionMeta("GarageBuyDevice", GarageBuyDevice::class, listOf(ActionFieldMeta("device", "Any?"), ActionFieldMeta("item", "Any?"), ActionFieldMeta("currency", "Any?"), ActionFieldMeta("mount", "Boolean"))),
ActionMeta("GarageDeviceTrySetInfinityLifetime", GarageDeviceTrySetInfinityLifetime::class, listOf(ActionFieldMeta("deviceId", "Any?"))),
ActionMeta("GarageRemoveDiscountForDevice", GarageRemoveDiscountForDevice::class, listOf(ActionFieldMeta("item", "Any?"))),
ActionMeta("Devices", Devices::class, listOf(ActionFieldMeta("baseItemIdToDevices", "Any?"))),
ActionMeta("GarageDevice", GarageDevice::class, listOf(ActionFieldMeta("id", "Any?"), ActionFieldMeta("baseItemId", "Any?"), ActionFieldMeta("installed", "Boolean"), ActionFieldMeta("name", "Any?"), ActionFieldMeta("category", "Int"), ActionFieldMeta("viewCategory", "Int"), ActionFieldMeta("priceWithoutDiscount", "Int"), ActionFieldMeta("description", "Any?"), ActionFieldMeta("descriptionMedia", "Any?"), ActionFieldMeta("previewImage", "Any?"), ActionFieldMeta("isPremium", "Boolean"), ActionFieldMeta("position", "Int"), ActionFieldMeta("minRank", "Int"), ActionFieldMeta("maxRank", "Int"), ActionFieldMeta("discount", "Int"), ActionFieldMeta("timeDiscountEnd", "Any?"), ActionFieldMeta("timeDiscountToStart", "Any?"), ActionFieldMeta("infinityLifetimeItem", "Boolean"), ActionFieldMeta("lifeTimeInSec", "Int"), ActionFieldMeta("property", "String"), ActionFieldMeta("properties", "Any?"), ActionFieldMeta("isSellingForCoin", "Boolean"), ActionFieldMeta("isSellingForCrystal", "Boolean"), ActionFieldMeta("isSellingForRuby", "Boolean"), ActionFieldMeta("isSellingForADS", "Boolean"), ActionFieldMeta("isSellingForMoney", "Boolean"), ActionFieldMeta("rarity", "Any?"), ActionFieldMeta("visible", "Boolean"), ActionFieldMeta("secret", "Boolean"), ActionFieldMeta("buyable", "Boolean"))),
ActionMeta("GarageBuyDevice", GarageBuyDevice::class, listOf(ActionFieldMeta("device", "GarageDevice"), ActionFieldMeta("item", "GarageItem"), ActionFieldMeta("currency", "Any?"), ActionFieldMeta("mount", "Boolean"))),
ActionMeta("GarageDeviceTrySetInfinityLifetime", GarageDeviceTrySetInfinityLifetime::class, listOf(ActionFieldMeta("deviceId", "Long"))),
ActionMeta("GarageRemoveDiscountForDevice", GarageRemoveDiscountForDevice::class, listOf(ActionFieldMeta("item", "GarageItem"))),
ActionMeta("Devices", Devices::class, listOf(ActionFieldMeta("baseItemIdToDevices", "Map<Long, List<GarageDevice>>"))),
ActionMeta("GarageDevice", GarageDevice::class, listOf(ActionFieldMeta("id", "Long"), ActionFieldMeta("baseItemId", "Long"), ActionFieldMeta("installed", "Boolean"), ActionFieldMeta("name", "Any?"), ActionFieldMeta("category", "Int"), ActionFieldMeta("viewCategory", "Int"), ActionFieldMeta("priceWithoutDiscount", "Int"), ActionFieldMeta("description", "Any?"), ActionFieldMeta("descriptionMedia", "Any?"), ActionFieldMeta("previewImage", "Any?"), ActionFieldMeta("isPremium", "Boolean"), ActionFieldMeta("position", "Int"), ActionFieldMeta("minRank", "Int"), ActionFieldMeta("maxRank", "Int"), ActionFieldMeta("discount", "Int"), ActionFieldMeta("timeDiscountEnd", "Any?"), ActionFieldMeta("timeDiscountToStart", "Any?"), ActionFieldMeta("infinityLifetimeItem", "Boolean"), ActionFieldMeta("lifeTimeInSec", "Int"), ActionFieldMeta("property", "String"), ActionFieldMeta("properties", "Any?"), ActionFieldMeta("isSellingForCoin", "Boolean"), ActionFieldMeta("isSellingForCrystal", "Boolean"), ActionFieldMeta("isSellingForRuby", "Boolean"), ActionFieldMeta("isSellingForADS", "Boolean"), ActionFieldMeta("isSellingForMoney", "Boolean"), ActionFieldMeta("rarity", "Any?"), ActionFieldMeta("visible", "Boolean"), ActionFieldMeta("secret", "Boolean"), ActionFieldMeta("buyable", "Boolean"))),
ActionMeta("DescriptionAlteration", DescriptionAlteration::class, listOf(ActionFieldMeta("description", "Any?"), ActionFieldMeta("property", "String"))),
ActionMeta("GarageDeviceObject", GarageDeviceObject::class, listOf(ActionFieldMeta("id", "Any?"), ActionFieldMeta("baseItemId", "Any?"), ActionFieldMeta("temporaryItemCC", "Any?"), ActionFieldMeta("description", "Any?"), ActionFieldMeta("item", "Any?"), ActionFieldMeta("isPremium", "Any?"), ActionFieldMeta("itemCategory", "Any?"), ActionFieldMeta("itemViewCategory", "Any?"), ActionFieldMeta("buyable", "Any?"), ActionFieldMeta("discount", "Any?"), ActionFieldMeta("timePeriod", "Any?"), ActionFieldMeta("deviceProperty", "Any?"), ActionFieldMeta("rarity", "Any?"), ActionFieldMeta("visible", "Any?"), ActionFieldMeta("descriptionMedia", "Any?"))),
ActionMeta("GarageDeviceObject", GarageDeviceObject::class, listOf(ActionFieldMeta("id", "Long"), ActionFieldMeta("baseItemId", "Long"), ActionFieldMeta("temporaryItemCC", "Any?"), ActionFieldMeta("description", "Any?"), ActionFieldMeta("item", "Any?"), ActionFieldMeta("isPremium", "Boolean"), ActionFieldMeta("itemCategory", "Int"), ActionFieldMeta("itemViewCategory", "Int"), ActionFieldMeta("buyable", "Boolean"), ActionFieldMeta("discount", "Any?"), ActionFieldMeta("timePeriod", "Any?"), ActionFieldMeta("deviceProperty", "Any?"), ActionFieldMeta("rarity", "Any?"), ActionFieldMeta("visible", "Boolean"), ActionFieldMeta("descriptionMedia", "Any?"))),
ActionMeta("UpgradableItemParams", UpgradableItemParams::class, listOf(ActionFieldMeta("itemPropertyParams", "Any?"), ActionFieldMeta("properties", "Any?"), ActionFieldMeta("currentLevel", "Int"), ActionFieldMeta("upgradeParams", "Any?"), ActionFieldMeta("upgradeDiscount", "Int"), ActionFieldMeta("upgradeConfirmed", "Any?"))),
ActionMeta("ItemPropertyParams", ItemPropertyParams::class, listOf(ActionFieldMeta("sortIndex", "Int"), ActionFieldMeta("name", "Any?"), ActionFieldMeta("properties", "Any?"), ActionFieldMeta("aggregationMode", "Any?"), ActionFieldMeta("multiplier", "Any?"), ActionFieldMeta("additiveShift", "Any?"), ActionFieldMeta("isKernelProperty", "Boolean"), ActionFieldMeta("unitName", "Any?"))),
ActionMeta("GaragePreview", GaragePreview::class, listOf(ActionFieldMeta("isVisible", "Any?"), ActionFieldMeta("photoMode", "Any?"), ActionFieldMeta("needVisible", "Any?"), ActionFieldMeta("viewStyle", "Any?"), ActionFieldMeta("dynViewStyle", "Any?"), ActionFieldMeta("tankPreviewSettings", "Any?"), ActionFieldMeta("needShowDrone", "Any?"), ActionFieldMeta("params", "Any?"), ActionFieldMeta("hullData", "Any?"), ActionFieldMeta("weaponData", "Any?"), ActionFieldMeta("droneData", "Any?"), ActionFieldMeta("grenadeData", "Any?"), ActionFieldMeta("hasGrenades", "Any?"), ActionFieldMeta("hullEngineType", "Any?"), ActionFieldMeta("turretMechanism", "Any?"), ActionFieldMeta("paint", "Any?"), ActionFieldMeta("properties", "Any?"), ActionFieldMeta("captureScreenshotAction", "Any?"), ActionFieldMeta("captureVideoAction", "Any?"), ActionFieldMeta("item3dModelActionCache", "Any?"), ActionFieldMeta("requestedItems", "Any?"), ActionFieldMeta("expectedItemId", "Any?"))),
@@ -0,0 +1,44 @@
// GarageModels.kt —— garage 切片里没有 toString 的嵌套模型(原 module_10032)
//
// 证据链:
// · PB = GarageItemsState(`function PB(t){ this.or9_1 = t === cd ? ag() : t }`),
// `or9_1` 是 Map<baseItemId, iB>。iB 没有 toString(`function iB(t){ this.xrb_1 = wm(t, QL(tB)) … }`),
// 保存一组 aB(GarageItem) 并按首项派生出 owned/mounted/selected/position/… 等只读字段。
// 这里保留 reducer 真正用到的部分:Items 列表 + Selected/HasOwned/HasMounted。
// · aB 的派生字段:
// urb_1 = modification?.vcf_1 ?: id (baseItemId,见 aB 构造 5342–5344)
// trb_1 = hasDeviceSale && owned (见 aB 构造 5341)
// GarageItem 由 gen_action_kt.js 生成,派生值用扩展函数计算。
// · qra_1(modification)来自另一模块,只有 vcf_1(baseItemId) 与 xcf_1(level) 被本切片使用。
package App.State
/** 原 `aB.qra_1`:改装/等级信息。字段名与运行时对象一致(vcf_1 / xcf_1)。 */
data class GarageItemModification(
val vcf_1: Long? = null,
val xcf_1: Int = 0,
)
/** 原 `iB`:同一个 baseItemId 下的一组 GarageItem。 */
data class GarageBaseItems(
val Items: List<GarageItem> = emptyList(),
) {
/** 原 `arc_1`:第一个已拥有项;没有则第一项。 */
val Selected: GarageItem? get() = Items.firstOrNull { it.owned } ?: Items.firstOrNull()
/** 原 `yrb_1`:组内是否有已拥有项。 */
val HasOwned: Boolean get() = Items.any { it.owned }
/** 原 `zrb_1`:组内是否有已装配项。 */
val HasMounted: Boolean get() = Items.any { it.mounted }
/** 原 `iB.orc(i)`:按下标取项(原包直接 `g1(i)`,越界会抛)。 */
fun At(Index: Int): GarageItem = Items[Index]
/** 原 `iB.krc(id, fn)`:按 id 替换一项。 */
fun Replace(ItemId: Long, F: (GarageItem) -> GarageItem): GarageBaseItems =
copy(Items = Items.map { if (it.id == ItemId) F(it) else it })
/** 原 `iB.lrc(fn)`:整组映射。 */
fun MapAll(F: (GarageItem) -> GarageItem): GarageBaseItems =
copy(Items = Items.map(F))
}
@@ -5,7 +5,7 @@ package App.State
import Core.Vector3
// 动作/状态类共 816 个,字段 2150 个(已定类型 511 个)
// 动作/状态类共 816 个,字段 2150 个(已定类型 567 个)
data class AnnouncementState(
val isEnabled: Any?, // 仅 copy 转发(无类型信息)
val modelLoaded: Any?, // 仅 copy 转发(无类型信息)
@@ -1445,11 +1445,11 @@ data class Discount(
)
data class UpdateDiscount(
val item: Any?, // 调用点: HL(t).paq_1.dpv_1.yq5_1.rr9(t.gr9_1)
val item: GarageItem, // override
)
data class GarageItem(
val id: Any?, // 调用点: h | t
val id: Long, // override
val name: Any?, // 调用点: c | n
val category: Int, // dataflow
val viewCategory: Int, // dataflow
@@ -1466,10 +1466,10 @@ data class GarageItem(
val minRank: Int, // dataflow
val maxRank: Int, // dataflow
val discount: Any?, // 调用点: y | w
val modification: Any?, // 调用点: $ | d
val modification: GarageItemModification?, // override
val properties: Any?, // 调用点: R | g
val upgradeableParams: Any?, // 调用点: M | m
val count: Any?, // 调用点: O | k
val upgradeableParams: UpgradableItemParams?, // override
val count: Int, // override
val kit: Any?, // 调用点: D | z
val countable: Any?, // 调用点: L | b
val hasDeviceSale: Boolean, // dataflow
@@ -1506,21 +1506,21 @@ data class Kit(
)
data class GarageItemObject(
val id: Any?, // 调用点: t | o
val id: Long, // override
val description: Any?, // 调用点: n | _
val item: Any?, // 调用点: i | h
val isPremium: Any?, // 调用点: r | a
val itemCategory: Any?, // 调用点: e | c
val itemViewCategory: Any?, // 调用点: u | l
val buyable: Any?, // 调用点: s | f
val availableForAll: Any?, // 调用点: o | v
val isPremium: Boolean, // override
val itemCategory: Int, // override
val itemViewCategory: Int, // override
val buyable: Boolean, // override
val availableForAll: Boolean, // override
val discount: Any?, // 调用点: _ | w
val timePeriod: Any?, // 调用点: h | k
val modification: Any?, // 调用点: a | d
val modification: GarageItemModification?, // override
val properties: Any?, // 调用点: c | g
val upgradeableParams: Any?, // 调用点: l | m
val upgradeableParams: UpgradableItemParams?, // override
val devices: Any?, // 调用点: f | z
val count: Any?, // 调用点: v | y
val count: Int, // override
val kit: Any?, // 调用点: p | $
val countable: Any?, // 调用点: w | x
val mountIndex: Any?, // 调用点: d | i
@@ -1536,7 +1536,7 @@ data class GarageItemObject(
val isSellingForCrystal: Any?, // 调用点: j | Ct
val isSellingForRuby: Any?, // 调用点: S | Ct
val resistanceBackgroundImg: Any?, // 调用点: C | H
val visible: Any?, // 调用点: E | W
val visible: Boolean, // override
val descriptionMedia: Any?, // 调用点: T | V
val saleByKitItems: Any?, // 调用点: I | K
)
@@ -1548,7 +1548,7 @@ data class KitItem(
)
data class GarageItemsState(
val itemsByBase: Any?, // 调用点: cg(this.or9_1, Vd(i, new iB(e))) | cg(c, Vd(t.urb_1, l))
val itemsByBase: Map<Long, GarageBaseItems>, // override
)
data class GarageObject(
@@ -1599,13 +1599,13 @@ data class RequestItem3DPart(
)
data class GarageUpgradeItemAction(
val itemId: Any?, // 调用点: n.us1_1.zr9_1 | n.us1_1.zr9_1
val itemId: Long, // override
val expectedPrice: Any?, // 调用点: n.ws1_1 | n.xs1_1
val currency: Any?, // 调用点: rB() | uB()
)
data class GarageSelectItem(
val itemId: Any?, // 调用点: n.zr9_1 | n.zr9_1
val itemId: Long, // override
)
data class GarageBuyResistanceModule(
@@ -1624,7 +1624,7 @@ data class GarageItemMounted(
)
data class GarageResistanceUnMount(
val resistance: Any?, // 调用点: k | f
val resistance: Long, // override
val needServerUnmount: Boolean, // callsite
)
@@ -1634,7 +1634,7 @@ data class GarageResistanceMount(
)
data class BuyProtectionSlot(
val slotToUnlock: Any?, // 调用点: s
val slotToUnlock: Int, // override
)
data class SetItemRarity(
@@ -1650,15 +1650,15 @@ data class BuyGarageKit(
)
data class GarageSelectAlteration(
val alterationId: Any?, // 调用点: n.arh_1 | n.arh_1
val alterationId: Long, // override
)
data class GarageSelectSkin(
val skinId: Any?, // 调用点: n.zr9_1 | null
val skinId: Long, // override
)
data class GarageSelectShotSkin(
val shotSkinId: Any?, // 调用点: n.zr9_1 | null
val shotSkinId: Long, // override
)
data class GarageLoadAvailableDevices(
@@ -1666,11 +1666,11 @@ data class GarageLoadAvailableDevices(
)
data class RemoveFromFavoriteItem(
val itemId: Any?, // 调用点: _
val itemId: Long, // override
)
data class AddToFavoriteItem(
val itemId: Any?, // 调用点: _
val itemId: Long, // override
)
data class GoToGarageAndSelectItem(
@@ -1691,7 +1691,7 @@ data class GarageUpgradeItemConfirm(
data class GarageDevicesLoadedAction(
val baseItemId: Long, // runtime
val devices: List<GarageDeviceObject>, // runtime
val mountedDevice: List<String>?, // runtime
val mountedDevice: GarageDevice?, // override
)
data class SetRestrictionMount(
@@ -1703,7 +1703,7 @@ data class SetGarageSkinsAction(
)
data class GarageRemoveItemFromStore(
val itemId: Any?, // 调用点: t.x57()
val itemId: Long, // override
)
data class GarageUpdateDepotItem(
@@ -1712,8 +1712,8 @@ data class GarageUpdateDepotItem(
)
data class GarageApplyResistanceMount(
val resistance: Any?, // 调用点: n | n
val index: Any?, // 调用点: r._v | r._v
val resistance: GarageItem, // override
val index: Int, // override
val needServerMount: Boolean, // callsite
)
@@ -1739,31 +1739,31 @@ data class AddRelatedResistanceModule(
)
data class GarageUpgradeItemCoolDownStarted(
val itemId: Any?, // 调用点: e
val endTime: Any?, // 调用点: i
val itemId: Long, // override
val endTime: Long, // override
)
data class GarageUpgradeItemCoolDownFinished(
val itemId: Any?, // 调用点: n
val endTime: Any?, // 调用点: i
val itemId: Long, // override
val endTime: Long, // override
)
data class RemoveFromSale(
val removeItems: Any?, // 调用点: h | h
val removeItems: Collection<Long>, // override
)
data class GarageUpdateItemCount(
val item: Any?, // 调用点: _
val count: Any?, // 调用点: r
val item: GarageItem, // override
val count: Int, // override
)
data class GarageRemoveItemFromDepot(
val item: Any?, // 调用点: o.rrf(cd, cd, cd, cd, cd, cd, cd, cd, cd
val item: GarageItemObject, // override
)
data class GarageAddItemToDepot(
val itemId: Any?, // 调用点: l.frd_1
val count: Any?, // 调用点: r
val itemId: Long, // override
val count: Int, // override
)
data class ItemPurchaseData(
@@ -1796,13 +1796,13 @@ data class GarageLoadDevicesIfNotLoaded(
)
data class GarageRemoveDevice(
val device: Any?, // 调用点: f | e
val item: Any?, // 调用点: u | t.zas_1.o1p8_1
val device: GarageDevice, // override
val item: GarageItem, // override
)
data class GarageInsertDeviceClientAndServer(
val device: Any?, // 调用点: n | n
val item: Any?, // 调用点: t.zas_1.o1p8_1 | t.zas_1.z1pm_1
val device: GarageDevice, // override
val item: GarageItem, // override
)
data class GarageBuyAndInsertDevice(
@@ -1812,27 +1812,27 @@ data class GarageBuyAndInsertDevice(
)
data class GarageBuyDevice(
val device: Any?, // 调用点: e
val item: Any?, // 调用点: u
val device: GarageDevice, // override
val item: GarageItem, // override
val currency: Any?, // 调用点: o
val mount: Boolean, // dataflow
)
data class GarageDeviceTrySetInfinityLifetime(
val deviceId: Any?, // 调用点: i
val deviceId: Long, // override
)
data class GarageRemoveDiscountForDevice(
val item: Any?, // 调用点: u
val item: GarageItem, // override
)
data class Devices(
val baseItemIdToDevices: Any?, // 调用点: i | cg(this.xrg_1, Vd(t, l))
val baseItemIdToDevices: Map<Long, List<GarageDevice>>, // override
)
data class GarageDevice(
val id: Any?, // 调用点: e | t
val baseItemId: Any?, // 调用点: u | n
val id: Long, // override
val baseItemId: Long, // override
val installed: Boolean, // callsite
val name: Any?, // 调用点: s | r
val category: Int, // dataflow
@@ -1869,20 +1869,20 @@ data class DescriptionAlteration(
)
data class GarageDeviceObject(
val id: Any?, // 调用点: h
val baseItemId: Any?, // 调用点: i
val id: Long, // override
val baseItemId: Long, // override
val temporaryItemCC: Any?, // 调用点: a
val description: Any?, // 调用点: c
val item: Any?, // 调用点: l
val isPremium: Any?, // 调用点: f
val itemCategory: Any?, // 调用点: v
val itemViewCategory: Any?, // 调用点: p
val buyable: Any?, // 调用点: w
val isPremium: Boolean, // override
val itemCategory: Int, // override
val itemViewCategory: Int, // override
val buyable: Boolean, // override
val discount: Any?, // 调用点: d
val timePeriod: Any?, // 调用点: g
val deviceProperty: Any?, // 调用点: m
val rarity: Any?, // 调用点: null == z ? Yr() : z
val visible: Any?, // 调用点: Gr(_, Z(rx))
val visible: Boolean, // override
val descriptionMedia: Any?, // 调用点: Gr(_, Z(ox))
)
@@ -0,0 +1,653 @@
// GarageReducer.kt —— 车库切片(对应 module_10032 的 UB 状态 + hF reducer)
//
// 证据链:
// 状态类 UB:`function UB(t, n, i, r, e, u, s, o, _, h, a, c, l, f, v, p, w, d, g, m, k, z, b, q, y, $, x, j, S, C, E)`,
// 31 个字段,toString 名为 `Garage(itemsOnDepot=…, …, upgradeItemInProcessingId=…)`。默认值取自构造:
// itemsOnDepot/itemsOnMarket/mountedItems/skinsItems = Yg()(HashSet)、items = new PB()、
// devices = new UF()、garageObject = null、isLoaded = false、currentCategory = BB().sq5_1、
// currentItemId/selectAlterationId/selectedSkinId/selectedShotSkinId = null、newItems = ag()(emptyMap)、
// resistanceReadyToMount = false、delayMountTimeMs/cooldownReduceEndTime = new Dd(0,0)(Long 0)、
// timeToReduceUpdateInMin = 0、currentRarity = null、purchaseOfUpgradesShopItemId = new Dd(886599726,214)、
// garageDiscountNotification = false、showVideoInDescription = true、unlockedProtectionSlots = 1、
// pricesToUnlockSlot = md()(emptyList)、numberRowsInPaintsUI = 2、overdriveDescriptions = ag()、
// weaponToResistanceModule = ag()、waitServerItemMount = ag()、bazookaToSupply = ag()、
// upgradeCoolDownEndTimes = ag()、upgradeItemInProcessingId = null。
//
// 根委派:`i = hF(t, n)`(根 reducer 把整个状态传入具名 reducer,切片槽位 n.dpv_1 只在 hF 体内出现)。
//
// hF 顶层分支:58 个 `t instanceof <动作>` + 2 个“接口/单例”分支:
// · `Xd(t, LF)`:NF = GarageInsertDeviceClientAndServer(LF 是标记接口,NF 实现它)
// · `yd(t, Nu)`:xU 单例动作(无字段,效果是把 upgradeItemInProcessingId 置 null)
// 其余 58 个分支与 slice_garage.json 一一对应;`FX` / `UB` 是屏幕/状态标识,不是动作。
//
// 嵌套模型:
// PB = GarageItemsState(Map<baseItemId, iB>),iB 无 toString → 见 GarageModels.kt 的 GarageBaseItems。
// UF = Devices(Map<baseItemId, List<VF>>),VF = GarageDevice。
// 物品对象 fB = GarageItemObject;aB = GarageItem(生成物,45 字段)。
// 关键派生:urb_1 = modification.vcf_1 ?: id;trb_1 = hasDeviceSale && owned。
package App.State.Reducers
import App.State.AddOverdriveDescription
import App.State.AddRelatedBazooka
import App.State.AddRelatedResistanceModule
import App.State.AddToFavoriteItem
import App.State.BuyProtectionSlot
import App.State.Devices
import App.State.GarageApplyResistanceMount
import App.State.GarageAddItemToDepot
import App.State.GarageBaseItems
import App.State.GarageBuyDevice
import App.State.GarageDevice
import App.State.GarageDeviceObject
import App.State.GarageDeviceTrySetInfinityLifetime
import App.State.GarageDevicesLoadedAction
import App.State.GarageInsertDeviceClientAndServer
import App.State.GarageItem
import App.State.GarageItemModification
import App.State.GarageItemObject
import App.State.GarageItemsState
import App.State.GarageObject
import App.State.GarageRemoveDevice
import App.State.GarageRemoveDiscountForDevice
import App.State.GarageRemoveItemFromDepot
import App.State.GarageRemoveItemFromStore
import App.State.GarageResistanceUnMount
import App.State.GarageSelectAlteration
import App.State.GarageSelectItem
import App.State.GarageSelectShotSkin
import App.State.GarageSelectSkin
import App.State.GarageUpgradeItemAction
import App.State.GarageUpgradeItemConfirm
import App.State.GarageUpgradeItemCoolDownFinished
import App.State.GarageUpgradeItemCoolDownStarted
import App.State.GarageUpdateItemCount
import App.State.GoTo
import App.State.OverdriveDescriptionData
import App.State.RemoveFromFavoriteItem
import App.State.RemoveFromSale
import App.State.SetGarageSkinsAction
import App.State.SetItemRarity
import App.State.SetRestrictionMount
import App.State.SwitchingRowsScroll
import App.State.UpdateDiscount
import State.InitState
import kotlin.js.Date
/** 原 `BB().sq5_1`:Garage 屏幕的默认分类。真实值由屏幕对象携带;S3 用 null 作为默认分类。 */
val GarageDefaultCategory: Any? = null
/** 原 `nz()`:抵抗模块的等待装配键。原包是分类常量,这里用一个不会与普通 category 冲突的哨兵。 */
const val GarageResistanceCategoryKey: Int = Int.MIN_VALUE
/* ------------------------------------------------------------------ 匿名动作 */
// 原 rF:没有 toString / 不在 action_inventory。效果见 gen_root_wiring.js 的 GarageAnonymousEffects。
class ResetGarage
data class SetGarageObject(val garageObject: GarageObject?)
data class SetItemsOnDepot(val items: Collection<GarageItemObject>)
data class SetItemsOnMarket(val items: Collection<GarageItemObject>)
data class SetMountedItems(val items: Collection<GarageItemObject>)
class GarageLoaded
class SetGarageNotLoaded
data class SelectGarageCategory(val category: Any?)
data class AddGarageItemCount(val item: GarageItem, val delta: Int)
data class GarageMountItem(val item: GarageItem)
data class GarageUnmountItem(val itemId: Long)
data class GarageMountItems(val itemIds: Collection<Long>)
data class SetMountedSkinForItems(val skinId: Long, val item: GarageItem)
data class SetMountedShotSkinForItems(val shotSkinId: Long, val item: GarageItem)
data class SetNewItems(val items: Map<Long, Set<Long>>)
data class AddNewItem(val itemId: Long, val value: Long)
data class RemoveNewItem(val itemId: Long, val value: Long)
data class SetResistanceReadyToMount(val ready: Boolean)
class ResetMountDelay
data class StartCooldownReduce(val seconds: Int, val minutes: Int)
data class SetCooldownReduceEndTime(val seconds: Int)
class ShowGarageDiscountNotification
class HideVideoInDescription
data class RemoveWaitServerItemMount(val categories: Collection<Int>)
class ClearWaitServerItemMount
/** 原 xU:`Nu = new xU()` 单例动作,效果只有 upgradeItemInProcessingId = null。 */
class ClearUpgradeItemInProcessing
/* ------------------------------------------------------------------ 状态 */
data class GarageStateSlice(
val ItemsOnDepot: Collection<GarageItemObject> = emptySet(),
val ItemsOnMarket: Collection<GarageItemObject> = emptySet(),
val MountedItems: Collection<GarageItemObject> = emptySet(),
val SkinsItems: Collection<GarageItemObject> = emptySet(),
val Items: GarageItemsState = GarageItemsState(emptyMap()),
val Devices: Devices = Devices(emptyMap()),
val GarageObject: GarageObject? = null,
val IsLoaded: Boolean = false,
val CurrentCategory: Any? = GarageDefaultCategory,
val CurrentItemId: Long? = null,
val SelectAlterationId: Long? = null,
val SelectedSkinId: Long? = null,
val SelectedShotSkinId: Long? = null,
val NewItems: Map<Long, Set<Long>> = emptyMap(),
val ResistanceReadyToMount: Boolean = false,
val DelayMountTimeMs: Long = 0L,
val CooldownReduceEndTime: Long = 0L,
val TimeToReduceUpdateInMin: Int = 0,
val CurrentRarity: Any? = null,
val PurchaseOfUpgradesShopItemId: Long = 920109776942L,
val GarageDiscountNotification: Boolean = false,
val ShowVideoInDescription: Boolean = true,
val UnlockedProtectionSlots: Int = 1,
val PricesToUnlockSlot: List<Any?> = emptyList(),
val NumberRowsInPaintsUI: Int = 2,
val OverdriveDescriptions: Map<Long, Any?> = emptyMap(),
val WeaponToResistanceModule: Map<Long, Long> = emptyMap(),
val WaitServerItemMount: Map<Int, Boolean> = emptyMap(),
val BazookaToSupply: Map<Long, Long> = emptyMap(),
val UpgradeCoolDownEndTimes: Map<Long, Long> = emptyMap(),
val UpgradeItemInProcessingId: Long? = null,
)
/* ------------------------------------------------------------------ 小工具 */
private fun ToLong(Value: Any?): Long? = when (Value) {
is Long -> Value
is Int -> Value.toLong()
is Number -> Value.toLong()
else -> null
}
private fun ToInt(Value: Any?, Default: Int = 0): Int = when (Value) {
is Int -> Value
is Long -> Value.toInt()
is Number -> Value.toInt()
else -> Default
}
private fun ToBool(Value: Any?): Boolean = Value == true
private fun NowMs(): Long = Date.now().toLong()
/** 原 `aB.urb_1`:modification.vcf_1 ?: id。 */
internal fun GarageItem.BaseItemId(): Long = modification?.vcf_1 ?: id
/** 原 `aB.trb_1`:hasDeviceSale && owned。 */
private fun GarageItem.HasDeviceSaleAndOwned(): Boolean = hasDeviceSale && owned
/* ------------------------------------------------------------------ GarageItemsState(PB) */
private val GarageItemsState.ItemById: Map<Long, GarageItem>
get() = itemsByBase.values.flatMap { it.Items }.associateBy { it.id }
private fun GarageItemsState.Item(ItemId: Long): GarageItem =
ItemById[ItemId] ?: error("GarageItemsState 里没有 item $ItemId")
private fun GarageItemsState.HasItem(ItemId: Long): Boolean = ItemById.containsKey(ItemId)
private fun GarageItemsState.GroupOf(Item: GarageItem): GarageBaseItems =
itemsByBase[Item.BaseItemId()] ?: error("GarageItemsState 里没有 baseItemId ${Item.BaseItemId()}")
private fun GarageItemsState.UpdateGroup(BaseId: Long, Group: GarageBaseItems): GarageItemsState =
copy(itemsByBase = itemsByBase + (BaseId to Group))
/** 原 `PB.rr9`:按 itemId 取项(原包用 Map.getValue,缺键会抛)。 */
private fun GarageItemsState.ItemOrThrow(ItemId: Long): GarageItem = Item(ItemId)
/** 原 `PB.grg`:在 item 所属 base 组里按 id 替换一项。 */
private fun GarageItemsState.ReplaceItem(Item: GarageItem, F: (GarageItem) -> GarageItem): GarageItemsState {
val BaseId = Item.BaseItemId()
val Group = itemsByBase[BaseId] ?: error("GarageItemsState 里没有 baseItemId $BaseId")
return copy(itemsByBase = itemsByBase + (BaseId to Group.Replace(Item.id, F)))
}
/** 原 `PB.jrg`:同一 viewCategory 内先卸下已装配项,再装配本项。 */
private fun GarageItemsState.MountItem(Item: GarageItem): GarageItemsState {
val SameCategory = itemsByBase.values.filter { Group ->
Group.Items.any { it.viewCategory == Item.viewCategory }
}
val Mounted = SameCategory.flatMap { it.Items }.firstOrNull { it.mounted }
var Result = this
if (Mounted != null) {
Result = Result.ReplaceItem(Mounted) { it.copy(mounted = false) }
}
return Result.ReplaceItem(Item) { it.copy(mounted = true) }
}
/** 原 `PB.krg`:批量装配;先卸下同 viewCategory 的已装配项,再置 owned + mounted。 */
private fun GarageItemsState.MountItems(ItemIds: Collection<Long>): GarageItemsState {
val Items = ItemIds.map { ItemOrThrow(it) }
val Categories = Items.map { it.viewCategory }.toSet()
val Groups = itemsByBase.values.filter { Group ->
Group.Items.any { it.viewCategory in Categories }
}
val Mounted = Groups.flatMap { it.Items }.filter { it.mounted }
var Result = this
for (M in Mounted) Result = Result.ReplaceItem(M) { it.copy(mounted = false) }
for (I in Items) Result = Result.ReplaceItem(I) { it.copy(owned = true, mounted = true) }
return Result
}
/** 原 `PB.mrg`:卸下单项。 */
private fun GarageItemsState.UnmountItem(ItemId: Long): GarageItemsState =
ReplaceItem(ItemOrThrow(ItemId)) { it.copy(mounted = false) }
/** 原 `PB.lrg`:装配抵抗模块;先检查 unlockedProtectionSlots。 */
private fun GarageItemsState.MountResistance(
State: GarageStateSlice,
Item: GarageItem,
Index: Int,
): GarageItemsState {
val MountedCount = itemsByBase.values
.flatMap { it.Items }
.count { it.mounted && it.viewCategory == Item.viewCategory }
if (MountedCount >= State.UnlockedProtectionSlots) {
error("已装配抵抗模块数 $MountedCount 超过 unlockedProtectionSlots ${State.UnlockedProtectionSlots}")
}
return ReplaceItem(Item) { it.copy(mounted = true, mountIndex = Index) }
}
/** 原 `PB.qrg`:整组 mountedSkin = skinId。 */
private fun GarageItemsState.SetGroupMountedSkin(SkinId: Long, Item: GarageItem): GarageItemsState {
val Group = GroupOf(Item)
return UpdateGroup(Item.BaseItemId(), Group.MapAll { it.copy(mountedSkin = SkinId) })
}
/** 原 `PB.rrg`:整组 mountedShotSkin = skinId。 */
private fun GarageItemsState.SetGroupMountedShotSkin(SkinId: Long, Item: GarageItem): GarageItemsState {
val Group = GroupOf(Item)
return UpdateGroup(Item.BaseItemId(), Group.MapAll { it.copy(mountedShotSkin = SkinId) })
}
/** 原 `PB.srg`:把给定 baseItemId 集合里的所有项 buyable 置 false。 */
private fun GarageItemsState.RemoveFromSale(BaseIds: Collection<Long>): GarageItemsState {
val Set = BaseIds.toSet()
var Result = this
for ((BaseId, Group) in itemsByBase) {
if (BaseId in Set) {
Result = Result.UpdateGroup(BaseId, Group.MapAll { it.copy(buyable = false) })
}
}
return Result
}
/** 原 `PB.trg`:置 owned=true、count=n。 */
private fun GarageItemsState.SetOwnedCount(Item: GarageItem, Count: Int): GarageItemsState =
ReplaceItem(Item) { it.copy(owned = true, count = Count) }
/* ------------------------------------------------------------------ Devices(UF) */
private fun Devices.UpdateByIds(Ids: Set<Long>, F: (GarageDevice) -> GarageDevice): Devices =
copy(baseItemIdToDevices = baseItemIdToDevices.mapValues { (_, List) ->
List.map { if (it.id in Ids) F(it) else it }
})
/** 原 `UF.frn`:去掉同组里旧 id 的项,再追加从 GarageDeviceObject 转换出的新项。 */
private fun Devices.LoadDevices(
BaseItemId: Long,
Objects: List<GarageDeviceObject>,
MountedDevice: GarageDevice?,
): Devices {
val NewIds = Objects.map { it.id }.toSet()
val Old = baseItemIdToDevices[BaseItemId].orEmpty().filter { it.id !in NewIds }
val Converted = Objects.map { DeviceFromObject(it, MountedDevice?.id == it.id) }
return copy(baseItemIdToDevices = baseItemIdToDevices + (BaseItemId to (Old + Converted)))
}
private fun DeviceFromObject(Object: GarageDeviceObject, Installed: Boolean): GarageDevice =
GarageDevice(
id = Object.id,
baseItemId = Object.baseItemId,
installed = Installed,
name = Object.description?.toString() ?: "",
category = Object.itemCategory,
viewCategory = Object.itemViewCategory,
priceWithoutDiscount = 0,
description = Object.description,
descriptionMedia = Object.descriptionMedia,
previewImage = Object.item,
isPremium = Object.isPremium,
position = 0,
minRank = 0,
maxRank = 0,
discount = ToInt(Object.discount),
timeDiscountEnd = null,
timeDiscountToStart = null,
infinityLifetimeItem = false,
lifeTimeInSec = 0,
property = Object.deviceProperty?.toString() ?: "",
properties = emptyList<Any?>(),
isSellingForCoin = false,
isSellingForCrystal = false,
isSellingForRuby = false,
isSellingForADS = false,
isSellingForMoney = false,
rarity = Object.rarity,
visible = Object.visible,
secret = false,
buyable = Object.buyable,
)
/* ------------------------------------------------------------------ 物品对象 → GarageItem */
private fun GarageItemObject.ToGarageItem(Owned: Boolean, Mounted: Boolean): GarageItem =
GarageItem(
id = id,
name = description?.toString() ?: "",
category = itemCategory,
viewCategory = itemViewCategory,
priceWithoutDiscount = 0,
description = description,
descriptionMedia = descriptionMedia,
preview = item,
isPremium = isPremium,
owned = Owned,
mountable = false,
mounted = Mounted,
mountIndex = mountIndex,
position = 0,
minRank = 0,
maxRank = 0,
discount = discount,
modification = modification,
properties = properties,
upgradeableParams = upgradeableParams,
count = count,
kit = kit,
countable = ToBool(countable),
hasDeviceSale = false,
devicesAvailable = devices != null,
devicePreview = null,
buyable = buyable,
availableForAll = availableForAll,
availableSkins = availableSkins,
availableShotSkins = availableShotSkins,
mountedSkin = mountedSkin,
mountedShotSkin = mountedShotSkin,
skinPreview = skinPreview,
group = grouped,
rarity = rarity,
favorite = favorite,
resistanceBackgroundImg = resistanceBackgroundImg,
visible = visible,
secret = false,
saleByKitItems = saleByKitItems,
isSellingForCoin = ToBool(isSellingForCoin),
isSellingForCrystal = ToBool(isSellingForCrystal),
isSellingForRuby = ToBool(isSellingForRuby),
isSellingForMoney = false,
isSellingForADS = false,
)
/** 原 `eF`:按 depot / mounted / market / skins 四个物品对象集合重建 PB。 */
private fun RebuildItems(State: GarageStateSlice): GarageItemsState {
val All = LinkedHashMap<Long, GarageItem>()
for (Object in State.ItemsOnDepot) All[Object.id] = Object.ToGarageItem(Owned = true, Mounted = false)
for (Object in State.MountedItems) All[Object.id] = Object.ToGarageItem(Owned = true, Mounted = true)
for (Object in State.ItemsOnMarket) All[Object.id] = Object.ToGarageItem(Owned = false, Mounted = false)
for (Object in State.SkinsItems) All[Object.id] = Object.ToGarageItem(Owned = false, Mounted = false)
val Grouped = All.values.groupBy { it.BaseItemId() }.mapValues { GarageBaseItems(it.value) }
return GarageItemsState(Grouped)
}
/* ------------------------------------------------------------------ reducer */
fun ReduceGarage(State0: GarageStateSlice, Action: Any): GarageStateSlice = when (Action) {
is InitState -> {
// 原:if (t.zap_1.equals(og(UB))) { var h = t.aaq_1(o); _ = h instanceof UB ? h : wd() }
// InitState(type, action) 里的 action 是「当前状态 → 新状态」的函数。
if (Action.type == GarageStateSlice::class) {
val Result = when (val F = Action.action) {
is Function1<*, *> -> @Suppress("UNCHECKED_CAST") (F as (GarageStateSlice) -> Any?)(State0)
is Function0<*> -> (F as () -> Any?)()
else -> null
}
Result as? GarageStateSlice ?: State0
} else State0
}
is ResetGarage -> State0.copy(
Devices = Devices(emptyMap()),
UpgradeCoolDownEndTimes = emptyMap(),
UpgradeItemInProcessingId = null,
)
is SetGarageObject -> State0.copy(GarageObject = Action.garageObject)
is SetItemsOnDepot -> State0.copy(ItemsOnDepot = Action.items)
is SetItemsOnMarket -> State0.copy(ItemsOnMarket = Action.items)
is SetMountedItems -> State0.copy(MountedItems = Action.items)
is SetGarageSkinsAction -> State0.copy(SkinsItems = Action.skins)
is GarageLoaded -> State0.copy(Items = RebuildItems(State0), IsLoaded = true)
is SetGarageNotLoaded -> State0.copy(IsLoaded = false)
is SelectGarageCategory -> State0.copy(CurrentCategory = Action.category, CurrentItemId = null)
is GarageSelectItem -> State0.copy(CurrentItemId = Action.itemId)
is AddGarageItemCount -> {
val Item = Action.item
val Level = Item.modification?.xcf_1 ?: 0
val Index = if (Level > 0) Level - 1 else 0
val Preview = State0.Items.GroupOf(Item).Items.getOrNull(Index)?.devicePreview
State0.copy(
Items = State0.Items.ReplaceItem(Item) {
it.copy(
owned = true,
count = it.count + Action.delta,
devicePreview = Preview,
)
},
)
}
is GarageUpdateItemCount ->
State0.copy(Items = State0.Items.ReplaceItem(Action.item) { it.copy(count = Action.count) })
is GarageMountItem -> State0.copy(
Items = State0.Items.MountItem(Action.item),
WaitServerItemMount = State0.WaitServerItemMount + (Action.item.category to true),
)
is GarageUnmountItem ->
State0.copy(Items = State0.Items.UnmountItem(Action.itemId))
is GarageMountItems ->
State0.copy(Items = State0.Items.MountItems(Action.itemIds))
is SetMountedSkinForItems ->
State0.copy(Items = State0.Items.SetGroupMountedSkin(Action.skinId, Action.item))
is SetMountedShotSkinForItems ->
State0.copy(Items = State0.Items.SetGroupMountedShotSkin(Action.shotSkinId, Action.item))
is GarageApplyResistanceMount -> State0.copy(
Items = State0.Items.MountResistance(State0, Action.resistance, Action.index),
// 原 WU 分支:`cg(o.vq6_1, new Km(nz(), !0))`,键是抵抗模块分类哨兵,不是物品 category。
WaitServerItemMount = State0.WaitServerItemMount + (GarageResistanceCategoryKey to true),
)
is GarageResistanceUnMount -> State0.copy(
Items = State0.Items.UnmountItem(Action.resistance),
WaitServerItemMount = State0.WaitServerItemMount + (GarageResistanceCategoryKey to true),
)
is GarageUpgradeItemAction -> {
val Item = State0.Items.ItemOrThrow(Action.itemId)
State0.copy(
Items = State0.Items.ReplaceItem(Item) {
val Params = it.upgradeableParams
it.copy(
upgradeableParams = Params?.copy(
currentLevel = Params.currentLevel + 1,
upgradeConfirmed = false,
) ?: Params,
)
},
UpgradeItemInProcessingId = Action.itemId,
)
}
is GarageUpgradeItemCoolDownStarted -> State0.copy(
UpgradeCoolDownEndTimes = State0.UpgradeCoolDownEndTimes + (Action.itemId to Action.endTime),
)
is GarageUpgradeItemCoolDownFinished ->
if (State0.UpgradeCoolDownEndTimes[Action.itemId] == Action.endTime) {
State0.copy(UpgradeCoolDownEndTimes = State0.UpgradeCoolDownEndTimes - Action.itemId)
} else State0
is GarageUpgradeItemConfirm -> {
val Item = State0.Items.ItemOrThrow(Action.itemId)
val Confirmed = if (State0.UpgradeItemInProcessingId == Action.itemId) null else State0.UpgradeItemInProcessingId
State0.copy(
Items = State0.Items.ReplaceItem(Item) {
val Params = it.upgradeableParams
it.copy(
upgradeableParams = Params?.copy(
upgradeConfirmed = Params.upgradeConfirmed == true || Params.currentLevel == Action.level,
) ?: Params,
)
},
UpgradeItemInProcessingId = Confirmed,
)
}
is ClearUpgradeItemInProcessing -> State0.copy(UpgradeItemInProcessingId = null)
is GarageDevicesLoadedAction -> State0.copy(
Devices = State0.Devices.LoadDevices(Action.baseItemId, Action.devices, Action.mountedDevice),
)
is GarageRemoveDevice -> State0.copy(
Items = State0.Items.ReplaceItem(Action.item) { it.copy(devicePreview = null) },
Devices = State0.Devices.UpdateByIds(setOf(Action.device.id)) { it.copy(installed = false) },
)
is GarageInsertDeviceClientAndServer -> State0.copy(
Items = State0.Items.ReplaceItem(Action.item) {
it.copy(devicePreview = Action.device.previewImage)
},
Devices = State0.Devices.UpdateByIds(setOf(Action.device.id)) { it.copy(installed = true) },
)
is GarageBuyDevice -> State0.copy(
Items = State0.Items.ReplaceItem(Action.item) {
it.copy(devicePreview = Action.device.previewImage)
},
Devices = State0.Devices.UpdateByIds(setOf(Action.device.id)) {
it.copy(installed = Action.mount, infinityLifetimeItem = true)
},
)
is GarageDeviceTrySetInfinityLifetime -> State0.copy(
Devices = State0.Devices.UpdateByIds(setOf(Action.deviceId)) {
it.copy(infinityLifetimeItem = true)
},
)
is GarageSelectAlteration -> State0.copy(SelectAlterationId = Action.alterationId)
is GarageSelectSkin -> State0.copy(SelectedSkinId = Action.skinId)
is GarageSelectShotSkin -> State0.copy(SelectedShotSkinId = Action.shotSkinId)
is GarageRemoveDiscountForDevice -> State0.copy(
Items = State0.Items.ReplaceItem(Action.item) {
it.copy(hasDeviceSale = it.HasDeviceSaleAndOwned())
},
)
is UpdateDiscount -> State0.copy(
// 原 zF 只把 discount 换成同一对象的副本(重新计算时间窗),字段值不变。
Items = State0.Items.ReplaceItem(Action.item) { it.copy(discount = it.discount) },
)
is GarageAddItemToDepot -> State0.copy(
Items = State0.Items.SetOwnedCount(State0.Items.ItemOrThrow(Action.itemId), Action.count),
)
is GarageRemoveItemFromDepot -> State0.copy(
Items = State0.Items.ReplaceItem(State0.Items.ItemOrThrow(Action.item.id)) {
it.copy(owned = false, count = Action.item.count)
},
)
is GarageRemoveItemFromStore -> State0.copy(
Items = State0.Items.ReplaceItem(State0.Items.ItemOrThrow(Action.itemId)) {
it.copy(buyable = false)
},
)
is SetNewItems -> State0.copy(NewItems = Action.items)
is AddNewItem -> {
val Existing = State0.NewItems[Action.itemId].orEmpty()
State0.copy(NewItems = State0.NewItems + (Action.itemId to (Existing + Action.value)))
}
is RemoveNewItem -> {
val Existing = State0.NewItems[Action.itemId].orEmpty() - Action.value
State0.copy(
NewItems = if (Existing.isEmpty()) State0.NewItems - Action.itemId
else State0.NewItems + (Action.itemId to Existing),
)
}
is SetResistanceReadyToMount -> State0.copy(ResistanceReadyToMount = Action.ready)
is RemoveFromSale -> State0.copy(Items = State0.Items.RemoveFromSale(Action.removeItems))
is SetRestrictionMount -> State0.copy(
DelayMountTimeMs = if (Action.delayMs > 0) NowMs() + Action.delayMs else 0L,
)
is ResetMountDelay -> State0.copy(DelayMountTimeMs = 0L)
is StartCooldownReduce -> State0.copy(
CooldownReduceEndTime = NowMs() + Action.seconds * 1000L,
TimeToReduceUpdateInMin = Action.minutes,
)
is SetCooldownReduceEndTime -> State0.copy(
CooldownReduceEndTime = NowMs() + Action.seconds * 1000L,
)
is SetItemRarity -> State0.copy(CurrentRarity = Action.rarity)
is AddToFavoriteItem -> State0.copy(
Items = State0.Items.ReplaceItem(State0.Items.ItemOrThrow(Action.itemId)) {
it.copy(favorite = true)
},
)
is RemoveFromFavoriteItem -> State0.copy(
Items = State0.Items.ReplaceItem(State0.Items.ItemOrThrow(Action.itemId)) {
it.copy(favorite = false)
},
)
is GoTo -> {
// 原:if (Tk(t.grm_1).equals(og(FX))) return o.hri(…, Y.irm_1); 否则 i = o
if (Action.screen.name == "Garage") {
State0.copy(CurrentCategory = GarageDefaultCategory)
} else State0
}
is ShowGarageDiscountNotification -> State0.copy(GarageDiscountNotification = true)
is HideVideoInDescription -> State0.copy(ShowVideoInDescription = false)
is BuyProtectionSlot -> State0.copy(UnlockedProtectionSlots = State0.UnlockedProtectionSlots + 1)
is SwitchingRowsScroll -> State0.copy(NumberRowsInPaintsUI = Action.numberRowsInPaintsUI)
is AddOverdriveDescription -> State0.copy(
OverdriveDescriptions = State0.OverdriveDescriptions + (Action.itemId to Action.descriptionData),
)
is AddRelatedResistanceModule -> State0.copy(
WeaponToResistanceModule = State0.WeaponToResistanceModule + (Action.itemId to Action.resistanceModuleId),
)
is AddRelatedBazooka -> State0.copy(
BazookaToSupply = State0.BazookaToSupply + (Action.bazookaId to Action.supplyId),
)
is RemoveWaitServerItemMount -> State0.copy(
WaitServerItemMount = State0.WaitServerItemMount + Action.categories.map { it to false },
)
is ClearWaitServerItemMount -> State0.copy(WaitServerItemMount = emptyMap())
else -> State0
}
@@ -11,12 +11,14 @@
// shop → ReduceShop(j4 / `(inline@shop)` 分支)
// screen → ReduceScreen(WK / dY().ks5)
// lootBoxes → ReduceLootBoxes(aH / EH.wrv)
// garage → ReduceGarage(UB / hF,根委派 `i = hF(t, n)`)
//
// 其余 81 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态,
// 其余 80 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态,
// 所以一次 copy 里串起全部已迁移 reducer,与按动作路由再归约的结果一致。
// 迁移进度与每个字段的证据见生成物 RootWiring.kt(不要手改)。
package App.State
import App.State.Reducers.ReduceGarage
import App.State.Reducers.ReduceHome
import App.State.Reducers.ReduceInvite
import App.State.Reducers.ReduceLocalBattleUser
@@ -47,6 +49,7 @@ object RootReducer {
Shop = ReduceShop(State0.Shop, Action),
Screen = ReduceScreen(State0.Screen, Action),
LootBoxes = ReduceLootBoxes(State0.LootBoxes, Action),
Garage = ReduceGarage(State0.Garage, Action),
)
}
@@ -4,6 +4,7 @@
// 已迁移的切片用强类型;其余保持 Any?(原样透传),迁移一个改一个再重新生成。
package App.State
import App.State.Reducers.GarageStateSlice
import App.State.Reducers.HomeStateSlice
import App.State.Reducers.InviteStateSlice
import App.State.Reducers.LocalBattleUserStateSlice
@@ -28,8 +29,8 @@ data class RootState(
val BattleStatistics: Any? = null,
/** 原 battleTutorial(rE / Q.sq8)。未迁移:原样透传。 */
val BattleTutorial: Any? = null,
/** 原 garage(UB / hF)。未迁移:原样透传。 */
val Garage: Any? = null,
/** 原 garage(UB / hF)。已迁移。 */
val Garage: GarageStateSlice = GarageStateSlice(),
/** 原 garagePreview(IG / fs.eru)。未迁移:原样透传。 */
val GaragePreview: Any? = null,
/** 原 challenges(CZ / nh.hsj)。未迁移:原样透传。 */
@@ -19,7 +19,7 @@ object RootWiring {
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("garage", "dpv_1", "UB", "hF", "hF(t, n)", listOf("AddGarageItemCount", "AddNewItem", "AddOverdriveDescription", "AddRelatedBazooka", "AddRelatedResistanceModule", "AddToFavoriteItem", "BuyProtectionSlot", "ClearWaitServerItemMount", "GarageAddItemToDepot", "GarageApplyResistanceMount", "GarageBuyDevice", "GarageDeviceTrySetInfinityLifetime", "GarageDevicesLoadedAction", "GarageLoaded", "GarageMountItem", "GarageMountItems", "GarageRemoveDevice", "GarageRemoveDiscountForDevice", "GarageRemoveItemFromDepot", "GarageRemoveItemFromStore", "GarageResistanceUnMount", "GarageSelectAlteration", "GarageSelectItem", "GarageSelectShotSkin", "GarageSelectSkin", "GarageUnmountItem", "GarageUpdateItemCount", "GarageUpgradeItemAction", "GarageUpgradeItemConfirm", "GarageUpgradeItemCoolDownFinished", "GarageUpgradeItemCoolDownStarted", "GoTo", "HideVideoInDescription", "InitState", "RemoveFromFavoriteItem", "RemoveFromSale", "RemoveNewItem", "RemoveWaitServerItemMount", "ResetGarage", "ResetMountDelay", "SelectGarageCategory", "SetCooldownReduceEndTime", "SetGarageNotLoaded", "SetGarageObject", "SetGarageSkinsAction", "SetItemRarity", "SetItemsOnDepot", "SetItemsOnMarket", "SetMountedItems", "SetMountedShotSkinForItems", "SetMountedSkinForItems", "SetNewItems", "SetResistanceReadyToMount", "SetRestrictionMount", "ShowGarageDiscountNotification", "StartCooldownReduce", "SwitchingRowsScroll", "UpdateDiscount")),
SliceWiring("garagePreview", "epv_1", "IG", "fs.eru", "fs.eru(t, n.epv_1)", listOf("?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")),
@@ -106,7 +106,7 @@ object RootWiring {
)
/** 已迁移(由 RootReducer 真正归约)的字段。 */
val Migrated: Set<String> = setOf("inviteState", "loginByPassword", "twitchState", "home", "localBattleUserState", "shop", "screen", "lootBoxes")
val Migrated: Set<String> = setOf("inviteState", "loginByPassword", "twitchState", "home", "localBattleUserState", "shop", "screen", "lootBoxes", "garage")
/** 未迁移字段:归约时原样透传,作为后续迁移清单。 */
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 / LootBoxes;⑭ 根 reducer 的 89 槽位委派
// ⑨~⑲ 切片 reducer:InviteState / LoginByPasswordState / TwitchState / HomeScreen / LocalBattleUserState /
// Shop(含 U7 类别表与 w3 物品表两个子 reducer)/ Screen / LootBoxes / Garage;⑭ 根 reducer 的 89 槽位委派
//
// 用法:$env:TANKIOFFLINE_STATE_TEST='1'; node …(见 TankiOffline/tools/RunStateTest.ps1)
package App
@@ -166,6 +166,80 @@ import App.State.Reducers.SetIsBlockOpenButtons
import App.State.Reducers.SetIsReopening
import App.State.Reducers.SetPeriodOfGiveLootBox
import App.State.Reducers.SetTimeToGiveLootBox
import App.State.Devices
import App.State.AddRelatedBazooka
import App.State.AddRelatedResistanceModule
import App.State.AddToFavoriteItem
import App.State.AddOverdriveDescription
import App.State.BuyProtectionSlot
import App.State.GarageAddItemToDepot
import App.State.GarageApplyResistanceMount
import App.State.GarageBaseItems
import App.State.GarageBuyDevice
import App.State.GarageDevice
import App.State.GarageDeviceObject
import App.State.GarageDeviceTrySetInfinityLifetime
import App.State.GarageDevicesLoadedAction
import App.State.GarageInsertDeviceClientAndServer
import App.State.GarageItem
import App.State.GarageItemModification
import App.State.GarageItemObject
import App.State.GarageItemsState
import App.State.GarageObject
import App.State.GarageRemoveDevice
import App.State.GarageRemoveDiscountForDevice
import App.State.GarageRemoveItemFromDepot
import App.State.GarageRemoveItemFromStore
import App.State.GarageResistanceUnMount
import App.State.GarageSelectAlteration
import App.State.GarageSelectItem
import App.State.GarageSelectShotSkin
import App.State.GarageSelectSkin
import App.State.GarageUpdateItemCount
import App.State.GarageUpgradeItemAction
import App.State.GarageUpgradeItemConfirm
import App.State.GarageUpgradeItemCoolDownFinished
import App.State.GarageUpgradeItemCoolDownStarted
import App.State.OverdriveDescriptionData
import App.State.RemoveFromFavoriteItem
import App.State.RemoveFromSale
import App.State.SetGarageSkinsAction
import App.State.SetItemRarity
import App.State.SetRestrictionMount
import App.State.SwitchingRowsScroll
import App.State.UpdateDiscount
import App.State.UpgradableItemParams
import App.State.Reducers.AddGarageItemCount
import App.State.Reducers.AddNewItem
import App.State.Reducers.ClearUpgradeItemInProcessing
import App.State.Reducers.ClearWaitServerItemMount
import App.State.Reducers.GarageDefaultCategory
import App.State.Reducers.GarageLoaded
import App.State.Reducers.GarageMountItem
import App.State.Reducers.GarageMountItems
import App.State.Reducers.GarageResistanceCategoryKey
import App.State.Reducers.GarageStateSlice
import App.State.Reducers.GarageUnmountItem
import App.State.Reducers.HideVideoInDescription
import App.State.Reducers.ReduceGarage
import App.State.Reducers.RemoveNewItem
import App.State.Reducers.RemoveWaitServerItemMount
import App.State.Reducers.ResetGarage
import App.State.Reducers.ResetMountDelay
import App.State.Reducers.SelectGarageCategory
import App.State.Reducers.SetCooldownReduceEndTime
import App.State.Reducers.SetGarageNotLoaded
import App.State.Reducers.SetGarageObject
import App.State.Reducers.SetItemsOnDepot
import App.State.Reducers.SetItemsOnMarket
import App.State.Reducers.SetMountedItems
import App.State.Reducers.SetMountedShotSkinForItems
import App.State.Reducers.SetMountedSkinForItems
import App.State.Reducers.SetNewItems
import App.State.Reducers.SetResistanceReadyToMount
import App.State.Reducers.ShowGarageDiscountNotification
import App.State.Reducers.StartCooldownReduce
import State.InitState
private data class AddFrags(val fragsCount: Int)
private data class SetScreen(val screen: String)
@@ -539,7 +613,7 @@ object StateCheck {
Check("切片 LocalBattleUser:接 Store 派发后状态已归约", Store.State.IsReArmorTemporaryDisabled)
}
// ⑭ 根 reducer:89 个槽位的委派表 + 八个已迁移切片一次归约
// ⑭ 根 reducer:89 个槽位的委派表 + 九个已迁移切片一次归约
run {
Check("根:根状态 89 个切片槽位", RootWiring.All.size == 89)
Check(
@@ -603,13 +677,13 @@ object StateCheck {
LocalBattleW.Reducer == "(inline@localBattleUserState)"
)
Check(
"根:迁移表 = 八个已迁移字段,其余 81 个待迁",
"根:迁移表 = 九个已迁移字段,其余 80 个待迁",
RootReducer.Migrated ==
setOf(
"inviteState", "loginByPassword", "twitchState", "home",
"localBattleUserState", "shop", "screen", "lootBoxes",
"localBattleUserState", "shop", "screen", "lootBoxes", "garage",
) &&
RootWiring.Unmigrated.size == 81
RootWiring.Unmigrated.size == 80
)
// 一次 ReduceRoot 里八个切片各归约自己的动作,互不串片
@@ -665,7 +739,7 @@ object StateCheck {
S5.User == "user-1" && S5.Screen.Current == NamedScreen("Preloader")
)
Check("根:无关动作返回等值状态", ReduceRoot(S5, AddFrags(1)) == S5)
Check("根:Coverage 文本", RootReducer.Coverage == "已迁移 8/89")
Check("根:Coverage 文本", RootReducer.Coverage == "已迁移 9/89")
val Store = ReduxStore(RootState(), { S, A -> ReduceRoot(S, A) })
Store.Dispatch(SetStatus("LOGGING_IN"))
@@ -1252,6 +1326,634 @@ object StateCheck {
}
}
// ⑲ 切片 reducer:Garage(原 module_10032 的 UB / hF,31 字段;58 个 instanceof + 2 个接口/单例臂)
run {
fun ToObj(
Id: Long,
Base: Long? = null,
Category: Int = 1,
View: Int = 1,
Count: Int = 3,
Buyable: Boolean = true,
): GarageItemObject = GarageItemObject(
id = Id,
description = "obj-$Id",
item = "preview-$Id",
isPremium = false,
itemCategory = Category,
itemViewCategory = View,
buyable = Buyable,
availableForAll = false,
discount = null,
timePeriod = null,
modification = if (Base != null) GarageItemModification(Base, 0) else null,
properties = null,
upgradeableParams = null,
devices = null,
count = Count,
kit = null,
countable = false,
mountIndex = null,
availableSkins = null,
availableShotSkins = null,
mountedSkin = null,
mountedShotSkin = null,
skinPreview = null,
grouped = null,
rarity = null,
favorite = false,
isSellingForCoin = false,
isSellingForCrystal = false,
isSellingForRuby = false,
resistanceBackgroundImg = null,
visible = true,
descriptionMedia = null,
saleByKitItems = null,
)
fun ToItem(
Id: Long,
Base: Long? = null,
Category: Int = 1,
View: Int = 1,
Owned: Boolean = true,
Mounted: Boolean = false,
Count: Int = 1,
Buyable: Boolean = true,
Favorite: Boolean = false,
HasDeviceSale: Boolean = false,
DevicePreview: Any? = null,
Level: Int = 0,
Confirmed: Boolean = false,
Discount: Any? = null,
): GarageItem = GarageItem(
id = Id,
name = "item-$Id",
category = Category,
viewCategory = View,
priceWithoutDiscount = 100,
description = "item-$Id",
descriptionMedia = null,
preview = "preview-$Id",
isPremium = false,
owned = Owned,
mountable = true,
mounted = Mounted,
mountIndex = null,
position = 0,
minRank = 0,
maxRank = 0,
discount = Discount,
modification = if (Base != null) GarageItemModification(Base, Level) else null,
properties = null,
upgradeableParams = UpgradableItemParams(
itemPropertyParams = null,
properties = null,
currentLevel = Level,
upgradeParams = null,
upgradeDiscount = 0,
upgradeConfirmed = Confirmed,
),
count = Count,
kit = null,
countable = false,
hasDeviceSale = HasDeviceSale,
devicesAvailable = false,
devicePreview = DevicePreview,
buyable = Buyable,
availableForAll = false,
availableSkins = null,
availableShotSkins = null,
mountedSkin = null,
mountedShotSkin = null,
skinPreview = null,
group = null,
rarity = null,
favorite = Favorite,
resistanceBackgroundImg = null,
visible = true,
secret = false,
saleByKitItems = null,
isSellingForCoin = false,
isSellingForCrystal = false,
isSellingForRuby = false,
isSellingForMoney = false,
isSellingForADS = false,
)
fun ToDevice(
Id: Long,
Base: Long,
Installed: Boolean = false,
Preview: Any? = "device-$Id",
Infinity: Boolean = false,
): GarageDevice = GarageDevice(
id = Id,
baseItemId = Base,
installed = Installed,
name = "device-$Id",
category = 1,
viewCategory = 1,
priceWithoutDiscount = 50,
description = "device-$Id",
descriptionMedia = null,
previewImage = Preview,
isPremium = false,
position = 0,
minRank = 0,
maxRank = 0,
discount = 0,
timeDiscountEnd = null,
timeDiscountToStart = null,
infinityLifetimeItem = Infinity,
lifeTimeInSec = 0,
property = "",
properties = null,
isSellingForCoin = false,
isSellingForCrystal = false,
isSellingForRuby = false,
isSellingForADS = false,
isSellingForMoney = false,
rarity = null,
visible = true,
secret = false,
buyable = true,
)
fun ToDeviceObj(Id: Long, Base: Long, Preview: Any? = "device-$Id"): GarageDeviceObject =
GarageDeviceObject(
id = Id,
baseItemId = Base,
temporaryItemCC = null,
description = "device-$Id",
item = Preview,
isPremium = false,
itemCategory = 1,
itemViewCategory = 1,
buyable = true,
discount = null,
timePeriod = null,
deviceProperty = "",
rarity = null,
visible = true,
descriptionMedia = null,
)
fun BaseState(
ItemList: List<GarageItem>,
DeviceMap: Map<Long, List<GarageDevice>> = emptyMap(),
): GarageStateSlice {
val Grouped = ItemList
.groupBy { it.modification?.vcf_1 ?: it.id }
.mapValues { GarageBaseItems(it.value) }
return GarageStateSlice(
Items = GarageItemsState(Grouped),
Devices = Devices(DeviceMap),
)
}
fun ItemIn(State: GarageStateSlice, Id: Long): GarageItem =
State.Items.itemsByBase.values.flatMap { it.Items }.first { it.id == Id }
val S0 = GarageStateSlice()
Check(
"切片 Garage:默认值与原构造一致(31 字段)",
S0.Items.itemsByBase.isEmpty() && S0.Devices.baseItemIdToDevices.isEmpty() &&
S0.GarageObject == null && !S0.IsLoaded &&
S0.CurrentCategory == GarageDefaultCategory && S0.CurrentItemId == null &&
S0.SelectAlterationId == null && S0.SelectedSkinId == null &&
S0.SelectedShotSkinId == null && S0.NewItems.isEmpty() &&
!S0.ResistanceReadyToMount && S0.DelayMountTimeMs == 0L &&
S0.CooldownReduceEndTime == 0L && S0.TimeToReduceUpdateInMin == 0 &&
S0.CurrentRarity == null && S0.PurchaseOfUpgradesShopItemId == 920109776942L &&
!S0.GarageDiscountNotification && S0.ShowVideoInDescription &&
S0.UnlockedProtectionSlots == 1 && S0.PricesToUnlockSlot.isEmpty() &&
S0.NumberRowsInPaintsUI == 2 && S0.OverdriveDescriptions.isEmpty() &&
S0.WeaponToResistanceModule.isEmpty() && S0.WaitServerItemMount.isEmpty() &&
S0.BazookaToSupply.isEmpty() && S0.UpgradeCoolDownEndTimes.isEmpty() &&
S0.UpgradeItemInProcessingId == null
)
val O1 = ToObj(101, Count = 3)
val O2 = ToObj(202, Count = 1, Buyable = false)
val O3 = ToObj(303, Count = 2)
val O4 = ToObj(404, Count = 1)
val GarageObj = GarageObject("temp")
val S1 = ReduceGarage(S0, SetGarageObject(GarageObj))
Check("切片 Garage:SetGarageObject", S1.GarageObject == GarageObj)
val S1a = ReduceGarage(S1, SetItemsOnDepot(listOf(O1, O3)))
Check(
"切片 Garage:SetItemsOnDepot",
S1a.ItemsOnDepot.toList() == listOf(O1, O3) && S1a.GarageObject == GarageObj
)
val S1b = ReduceGarage(S1a, SetItemsOnMarket(listOf(O2)))
Check("切片 Garage:SetItemsOnMarket", S1b.ItemsOnMarket.toList() == listOf(O2))
val S1c = ReduceGarage(S1b, SetMountedItems(listOf(O3)))
Check("切片 Garage:SetMountedItems", S1c.MountedItems.toList() == listOf(O3))
val S1d = ReduceGarage(S1c, SetGarageSkinsAction(listOf(O4)))
Check("切片 Garage:SetGarageSkinsAction", S1d.SkinsItems.toList() == listOf(O4))
val Loaded0 = GarageStateSlice(
ItemsOnDepot = listOf(O1, O3, ToObj(505, Count = 1)),
MountedItems = listOf(O3),
ItemsOnMarket = listOf(O2),
SkinsItems = listOf(O4),
)
val Loaded1 = ReduceGarage(Loaded0, GarageLoaded())
val LoadedItems = Loaded1.Items.itemsByBase.values.flatMap { it.Items }
Check(
"切片 Garage:GarageLoaded 重建 PB(depot/mounted/market/skins)",
Loaded1.IsLoaded && LoadedItems.map { it.id }.sorted() == listOf(101L, 202L, 303L, 404L, 505L) &&
LoadedItems.first { it.id == 101L }.owned &&
LoadedItems.first { it.id == 303L }.owned && LoadedItems.first { it.id == 303L }.mounted &&
!LoadedItems.first { it.id == 202L }.owned && !LoadedItems.first { it.id == 202L }.buyable &&
!LoadedItems.first { it.id == 404L }.owned
)
Check(
"切片 Garage:SetGarageNotLoaded",
!ReduceGarage(Loaded1, SetGarageNotLoaded()).IsLoaded
)
val SC = ReduceGarage(S0, SelectGarageCategory("paints"))
Check(
"切片 Garage:SelectGarageCategory 同时清空 currentItemId",
SC.CurrentCategory == "paints" && SC.CurrentItemId == null
)
val SI = ReduceGarage(SC, GarageSelectItem(101))
Check(
"切片 Garage:GarageSelectItem 只改 currentItemId",
SI.CurrentItemId == 101L && SI.CurrentCategory == "paints"
)
val I101 = ToItem(101, Count = 3)
val SU = ReduceGarage(BaseState(listOf(I101)), GarageUpdateItemCount(I101, 9))
Check("切片 Garage:GarageUpdateItemCount", ItemIn(SU, 101).count == 9)
val L1 = ToItem(110, Base = 110, Level = 1, DevicePreview = "lvl-1")
val L2 = ToItem(111, Base = 110, Level = 2, DevicePreview = "lvl-2")
val L3 = ToItem(112, Base = 110, Level = 3, DevicePreview = "lvl-3")
val AddCount = ReduceGarage(BaseState(listOf(L1, L2, L3)), AddGarageItemCount(L2, 4))
Check(
"切片 Garage:AddGarageItemCount(owned=true、count+=、devicePreview 取等级项)",
ItemIn(AddCount, 111).owned && ItemIn(AddCount, 111).count == 5 &&
ItemIn(AddCount, 111).devicePreview == "lvl-2"
)
val M1 = ToItem(201, Mounted = true)
val M2 = ToItem(202)
val Mounted = ReduceGarage(BaseState(listOf(M1, M2)), GarageMountItem(M2))
Check(
"切片 Garage:GarageMountItem 先卸同 viewCategory 再装,并写 waitServerItemMount",
!ItemIn(Mounted, 201).mounted && ItemIn(Mounted, 202).mounted &&
Mounted.WaitServerItemMount[1] == true
)
val Unmounted = ReduceGarage(Mounted, GarageUnmountItem(202))
Check("切片 Garage:GarageUnmountItem", !ItemIn(Unmounted, 202).mounted)
val MountAll = ReduceGarage(
BaseState(listOf(M1, M2)),
GarageMountItems(listOf(201, 202)),
)
Check(
"切片 Garage:GarageMountItems 批量 owned+mounted",
ItemIn(MountAll, 201).mounted && ItemIn(MountAll, 201).owned &&
ItemIn(MountAll, 202).mounted && ItemIn(MountAll, 202).owned
)
val SkinBase = BaseState(listOf(ToItem(301, Base = 301), ToItem(302, Base = 301)))
val Skinned = ReduceGarage(SkinBase, SetMountedSkinForItems(9001, ItemIn(SkinBase, 302)))
Check(
"切片 Garage:SetMountedSkinForItems 整组写入 mountedSkin",
ItemIn(Skinned, 301).mountedSkin == 9001L && ItemIn(Skinned, 302).mountedSkin == 9001L
)
val Shot = ReduceGarage(SkinBase, SetMountedShotSkinForItems(9002, ItemIn(SkinBase, 302)))
Check(
"切片 Garage:SetMountedShotSkinForItems 整组写入 mountedShotSkin",
ItemIn(Shot, 301).mountedShotSkin == 9002L && ItemIn(Shot, 302).mountedShotSkin == 9002L
)
val R1 = ToItem(401, View = 5)
val R2 = ToItem(402, View = 5)
val Resist = ReduceGarage(BaseState(listOf(R1, R2)), GarageApplyResistanceMount(R1, 3, false))
Check(
"切片 Garage:GarageApplyResistanceMount(mounted + mountIndex + waitServerItemMount)",
ItemIn(Resist, 401).mounted && ItemIn(Resist, 401).mountIndex == 3 &&
Resist.WaitServerItemMount[GarageResistanceCategoryKey] == true
)
var ResistanceThrew = false
try {
ReduceGarage(Resist, GarageApplyResistanceMount(R2, 4, false))
} catch (_: Throwable) {
ResistanceThrew = true
}
Check("切片 Garage:抵抗模块装配数受 unlockedProtectionSlots 限制", ResistanceThrew)
val ResistOff = ReduceGarage(Resist, GarageResistanceUnMount(401, true))
Check(
"切片 Garage:GarageResistanceUnMount",
!ItemIn(ResistOff, 401).mounted &&
ResistOff.WaitServerItemMount[GarageResistanceCategoryKey] == true
)
val Up0 = ReduceGarage(
BaseState(listOf(ToItem(501, Level = 2, Confirmed = true))),
GarageUpgradeItemAction(501, 100, "coin"),
)
Check(
"切片 Garage:GarageUpgradeItemAction(level+1、confirmed=false、processing=itemId)",
ItemIn(Up0, 501).upgradeableParams!!.currentLevel == 3 &&
ItemIn(Up0, 501).upgradeableParams!!.upgradeConfirmed == false &&
Up0.UpgradeItemInProcessingId == 501L
)
val Up1 = ReduceGarage(Up0, GarageUpgradeItemCoolDownStarted(501, 12345))
Check("切片 Garage:GarageUpgradeItemCoolDownStarted", Up1.UpgradeCoolDownEndTimes[501L] == 12345L)
val Up2 = ReduceGarage(Up1, GarageUpgradeItemCoolDownFinished(501, 12345))
Check("切片 Garage:GarageUpgradeItemCoolDownFinished 命中时删键", Up2.UpgradeCoolDownEndTimes.isEmpty())
Check(
"切片 Garage:GarageUpgradeItemCoolDownFinished 不命中时原样返回",
ReduceGarage(Up1, GarageUpgradeItemCoolDownFinished(501, 999)) == Up1
)
val Confirmed = ReduceGarage(Up0, GarageUpgradeItemConfirm(501, 3))
Check(
"切片 Garage:GarageUpgradeItemConfirm 确认并清 processing",
ItemIn(Confirmed, 501).upgradeableParams!!.upgradeConfirmed == true &&
Confirmed.UpgradeItemInProcessingId == null
)
val ConfirmedOther = ReduceGarage(
Up0.copy(UpgradeItemInProcessingId = 999L),
GarageUpgradeItemConfirm(501, 3),
)
Check(
"切片 Garage:Confirm 其他 processing id 不清",
ConfirmedOther.UpgradeItemInProcessingId == 999L
)
Check(
"切片 Garage:ClearUpgradeItemInProcessing(原 xU 单例)",
ReduceGarage(Up0, ClearUpgradeItemInProcessing()).UpgradeItemInProcessingId == null
)
val DItem = ToItem(601)
val DBase = BaseState(listOf(DItem), mapOf(601L to listOf(ToDevice(9001, 601))))
val DLoaded = ReduceGarage(
DBase,
GarageDevicesLoadedAction(
601,
listOf(ToDeviceObj(9001, 601), ToDeviceObj(9002, 601)),
ToDevice(9002, 601),
),
)
val DList = DLoaded.Devices.baseItemIdToDevices[601L].orEmpty()
Check(
"切片 Garage:GarageDevicesLoadedAction 合并旧列表并按 mountedDevice 置 installed",
DList.map { it.id }.sorted() == listOf(9001L, 9002L) &&
!DList.first { it.id == 9001L }.installed &&
DList.first { it.id == 9002L }.installed
)
val DRemoved = ReduceGarage(DLoaded, GarageRemoveDevice(ToDevice(9001, 601), DItem))
Check(
"切片 Garage:GarageRemoveDevice(devicePreview=null、installed=false)",
ItemIn(DRemoved, 601).devicePreview == null &&
!DRemoved.Devices.baseItemIdToDevices[601L]!!.first { it.id == 9001L }.installed
)
val DInserted = ReduceGarage(
DLoaded,
GarageInsertDeviceClientAndServer(ToDevice(9002, 601, Preview = "dev-preview"), DItem),
)
Check(
"切片 Garage:GarageInsertDeviceClientAndServer(devicePreview=设备预览图、installed=true)",
ItemIn(DInserted, 601).devicePreview == "dev-preview" &&
DInserted.Devices.baseItemIdToDevices[601L]!!.first { it.id == 9002L }.installed
)
val DBought = ReduceGarage(
DLoaded,
GarageBuyDevice(ToDevice(9002, 601, Preview = "bought"), DItem, "coin", true),
)
Check(
"切片 Garage:GarageBuyDevice(installed=mount、infinityLifetimeItem=true、devicePreview)",
ItemIn(DBought, 601).devicePreview == "bought" &&
DBought.Devices.baseItemIdToDevices[601L]!!.first { it.id == 9002L }.installed &&
DBought.Devices.baseItemIdToDevices[601L]!!.first { it.id == 9002L }.infinityLifetimeItem
)
val DInf = ReduceGarage(DLoaded, GarageDeviceTrySetInfinityLifetime(9001))
Check(
"切片 Garage:GarageDeviceTrySetInfinityLifetime",
DInf.Devices.baseItemIdToDevices[601L]!!.first { it.id == 9001L }.infinityLifetimeItem
)
val Sel = ReduceGarage(S0, GarageSelectAlteration(7001))
val Sel2 = ReduceGarage(Sel, GarageSelectSkin(7002))
val Sel3 = ReduceGarage(Sel2, GarageSelectShotSkin(7003))
Check(
"切片 Garage:三个选择动作",
Sel3.SelectAlterationId == 7001L && Sel3.SelectedSkinId == 7002L &&
Sel3.SelectedShotSkinId == 7003L
)
val DiscItem = ToItem(801, Owned = true, HasDeviceSale = true)
val NoDisc = ReduceGarage(BaseState(listOf(DiscItem)), GarageRemoveDiscountForDevice(DiscItem))
Check(
"切片 Garage:GarageRemoveDiscountForDevice(hasDeviceSale && owned)",
ItemIn(NoDisc, 801).hasDeviceSale
)
val UnownedDisc = ToItem(802, Owned = false, HasDeviceSale = true)
val NoDisc2 = ReduceGarage(BaseState(listOf(UnownedDisc)), GarageRemoveDiscountForDevice(UnownedDisc))
Check("切片 Garage:未拥有项去折扣后 hasDeviceSale=false", !ItemIn(NoDisc2, 802).hasDeviceSale)
Check(
"切片 Garage:UpdateDiscount 保留原 discount 值",
ReduceGarage(BaseState(listOf(ToItem(803, Discount = "d"))), UpdateDiscount(ToItem(803, Discount = "d")))
.let { ItemIn(it, 803).discount == "d" }
)
val DepotItem = ToItem(901, Owned = false, Count = 1)
val DepotState = BaseState(listOf(DepotItem))
val Added = ReduceGarage(DepotState, GarageAddItemToDepot(901, 5))
Check("切片 Garage:GarageAddItemToDepot(owned=true、count 覆盖)",
ItemIn(Added, 901).owned && ItemIn(Added, 901).count == 5)
val Removed = ReduceGarage(Added, GarageRemoveItemFromDepot(ToObj(901, Count = 2)))
Check("切片 Garage:GarageRemoveItemFromDepot(owned=false、count 取动作 item)",
!ItemIn(Removed, 901).owned && ItemIn(Removed, 901).count == 2)
val NotSold = ReduceGarage(Added, GarageRemoveItemFromStore(901))
Check("切片 Garage:GarageRemoveItemFromStore(buyable=false)", !ItemIn(NotSold, 901).buyable)
val New0 = ReduceGarage(S0, SetNewItems(mapOf(1L to setOf(10L, 20L))))
val New1 = ReduceGarage(New0, AddNewItem(1, 30))
val New2 = ReduceGarage(New1, AddNewItem(2, 5))
Check(
"切片 Garage:SetNewItems / AddNewItem",
New2.NewItems[1L] == setOf(10L, 20L, 30L) && New2.NewItems[2L] == setOf(5L)
)
val New3 = ReduceGarage(New2, RemoveNewItem(1, 10))
val New4 = ReduceGarage(New3, RemoveNewItem(1, 20))
val New5 = ReduceGarage(New4, RemoveNewItem(1, 30))
Check(
"切片 Garage:RemoveNewItem 逐个移除并在空集时删键",
New3.NewItems[1L] == setOf(20L, 30L) && !New5.NewItems.containsKey(1L) &&
New5.NewItems[2L] == setOf(5L)
)
Check(
"切片 Garage:SetResistanceReadyToMount",
ReduceGarage(S0, SetResistanceReadyToMount(true)).ResistanceReadyToMount
)
val SaleBase = BaseState(
listOf(ToItem(1001, Base = 1001), ToItem(1002, Base = 1001), ToItem(1003, Base = 1003))
)
val Sale = ReduceGarage(SaleBase, RemoveFromSale(listOf(1001L)))
Check(
"切片 Garage:RemoveFromSale 只影响给定 baseItemId 组",
!ItemIn(Sale, 1001).buyable && !ItemIn(Sale, 1002).buyable && ItemIn(Sale, 1003).buyable
)
val Delay = ReduceGarage(S0, SetRestrictionMount(5000))
Check(
"切片 Garage:SetRestrictionMount 正延迟写成 now+delay",
Delay.DelayMountTimeMs > 0L && ReduceGarage(S0, SetRestrictionMount(0)).DelayMountTimeMs == 0L
)
Check("切片 Garage:ResetMountDelay", ReduceGarage(Delay, ResetMountDelay()).DelayMountTimeMs == 0L)
val Cool = ReduceGarage(S0, StartCooldownReduce(60, 5))
Check(
"切片 Garage:StartCooldownReduce(now+seconds*1000、minutes)",
Cool.CooldownReduceEndTime > 0L && Cool.TimeToReduceUpdateInMin == 5
)
Check(
"切片 Garage:SetCooldownReduceEndTime(now+seconds*1000)",
ReduceGarage(S0, SetCooldownReduceEndTime(10)).CooldownReduceEndTime > 0L
)
Check("切片 Garage:SetItemRarity", ReduceGarage(S0, SetItemRarity("rare")).CurrentRarity == "rare")
val Fav = BaseState(listOf(ToItem(1101)))
val Favorited = ReduceGarage(Fav, AddToFavoriteItem(1101))
Check("切片 Garage:AddToFavoriteItem", ItemIn(Favorited, 1101).favorite)
Check(
"切片 Garage:RemoveFromFavoriteItem",
!ItemIn(ReduceGarage(Favorited, RemoveFromFavoriteItem(1101)), 1101).favorite
)
val GoGarage = ReduceGarage(SC.copy(CurrentItemId = 101L), GoTo(NamedScreen("Garage"), false))
Check(
"切片 Garage:GoTo Garage 复位 currentCategory 并原样返回 currentItemId",
GoGarage.CurrentCategory == GarageDefaultCategory && GoGarage.CurrentItemId == 101L
)
val GoShop = ReduceGarage(SC, GoTo(NamedScreen("Shop"), false))
Check("切片 Garage:GoTo 其他屏幕不改分类", GoShop.CurrentCategory == "paints")
Check(
"切片 Garage:ShowGarageDiscountNotification",
ReduceGarage(S0, ShowGarageDiscountNotification()).GarageDiscountNotification
)
Check(
"切片 Garage:HideVideoInDescription",
!ReduceGarage(S0, HideVideoInDescription()).ShowVideoInDescription
)
Check(
"切片 Garage:BuyProtectionSlot",
ReduceGarage(S0, BuyProtectionSlot(1)).UnlockedProtectionSlots == 2
)
Check(
"切片 Garage:SwitchingRowsScroll",
ReduceGarage(S0, SwitchingRowsScroll(4)).NumberRowsInPaintsUI == 4
)
val Desc = OverdriveDescriptionData("name", "icon", "text")
Check(
"切片 Garage:AddOverdriveDescription",
ReduceGarage(S0, AddOverdriveDescription(12, Desc)).OverdriveDescriptions[12L] == Desc
)
Check(
"切片 Garage:AddRelatedResistanceModule",
ReduceGarage(S0, AddRelatedResistanceModule(12, 34)).WeaponToResistanceModule[12L] == 34L
)
Check(
"切片 Garage:AddRelatedBazooka",
ReduceGarage(S0, AddRelatedBazooka(12, 34)).BazookaToSupply[12L] == 34L
)
val Wait0 = S0.copy(WaitServerItemMount = mapOf(1 to true, 2 to true))
val Wait1 = ReduceGarage(Wait0, RemoveWaitServerItemMount(listOf(1)))
Check(
"切片 Garage:RemoveWaitServerItemMount 给定键置 false",
Wait1.WaitServerItemMount[1] == false && Wait1.WaitServerItemMount[2] == true
)
Check(
"切片 Garage:ClearWaitServerItemMount",
ReduceGarage(Wait1, ClearWaitServerItemMount()).WaitServerItemMount.isEmpty()
)
val Reset0 = S0.copy(
Devices = Devices(mapOf(1L to listOf(ToDevice(1, 1)))),
UpgradeCoolDownEndTimes = mapOf(1L to 2L),
UpgradeItemInProcessingId = 3L,
)
val Reset1 = ReduceGarage(Reset0, ResetGarage())
Check(
"切片 Garage:ResetGarage 清 devices / coolDown / processing",
Reset1.Devices.baseItemIdToDevices.isEmpty() &&
Reset1.UpgradeCoolDownEndTimes.isEmpty() && Reset1.UpgradeItemInProcessingId == null
)
val Init = ReduceGarage(
S0,
InitState(GarageStateSlice::class, { S: GarageStateSlice -> S.copy(IsLoaded = true) }),
)
Check("切片 Garage:InitState 按 GarageStateSlice::class 灌入状态", Init.IsLoaded)
Check(
"切片 Garage:InitState 其他类型不改状态",
ReduceGarage(S0, InitState(String::class, { S: GarageStateSlice -> S.copy(IsLoaded = true) })) == S0
)
Check("切片 Garage:无关动作返回等值状态", ReduceGarage(BaseState(listOf(I101)), AddFrags(1)) == BaseState(listOf(I101)))
val Root0 = RootState()
val Root1 = ReduceRoot(Root0, GarageSelectItem(101))
Check(
"根:GarageSelectItem 只动 garage",
Root1.Garage.CurrentItemId == 101L && Root1.Shop == Root0.Shop && Root1.Screen == Root0.Screen &&
Root1.LootBoxes == Root0.LootBoxes && Root1.Home == Root0.Home
)
val Root2 = ReduceRoot(Root1, SetGarageObject(GarageObj))
Check(
"根:SetGarageObject 保留 garage 前一次归约",
Root2.Garage.GarageObject == GarageObj && Root2.Garage.CurrentItemId == 101L &&
Root2.LootBoxes == Root1.LootBoxes
)
Check("根:garage 迁移后无关动作仍返回等值状态", ReduceRoot(Root2, AddFrags(1)) == Root2)
val W = RootWiring.ByField("garage")!!
Check(
"根:garage 委派到 UB / hF 且 58 个动作全部具名",
W.SliceClass == "UB" && W.Reducer == "hF" && W.Actions.size == 58 &&
W.Actions.none { it.startsWith("?") }
)
Check(
"根:garage 的动作名覆盖 58 个 instanceof 臂",
listOf(
"ResetGarage", "SetGarageObject", "SetItemsOnDepot", "SetItemsOnMarket",
"SetMountedItems", "GarageLoaded", "SetGarageNotLoaded", "SelectGarageCategory",
"GarageSelectItem", "GarageUpdateItemCount", "AddGarageItemCount", "GarageMountItem", "GarageUnmountItem",
"GarageMountItems", "SetMountedSkinForItems", "SetMountedShotSkinForItems",
"SetGarageSkinsAction",
"GarageApplyResistanceMount", "GarageResistanceUnMount", "GarageUpgradeItemAction",
"GarageUpgradeItemCoolDownStarted", "GarageUpgradeItemCoolDownFinished",
"GarageUpgradeItemConfirm", "GarageDevicesLoadedAction", "GarageRemoveDevice",
"GarageBuyDevice",
"GarageDeviceTrySetInfinityLifetime", "GarageSelectAlteration", "GarageSelectSkin",
"GarageSelectShotSkin", "GarageRemoveDiscountForDevice", "UpdateDiscount",
"GarageAddItemToDepot", "GarageRemoveItemFromDepot", "GarageRemoveItemFromStore",
"SetNewItems", "AddNewItem", "RemoveNewItem", "SetResistanceReadyToMount",
"RemoveFromSale", "SetRestrictionMount", "ResetMountDelay", "StartCooldownReduce",
"SetCooldownReduceEndTime", "SetItemRarity", "AddToFavoriteItem",
"RemoveFromFavoriteItem", "GoTo", "ShowGarageDiscountNotification",
"HideVideoInDescription", "BuyProtectionSlot", "SwitchingRowsScroll",
"AddOverdriveDescription", "AddRelatedResistanceModule", "AddRelatedBazooka",
"RemoveWaitServerItemMount", "ClearWaitServerItemMount", "InitState",
).all { it in W.Actions }
)
Check(
"根:garage 的动作表不含 FX / UB 标识类",
W.Actions.none { it == "FX" || it == "UB" || it == "Garage" }
)
}
val Failed = Results.filter { !it.second }
println("TankiOffline 状态层自检:${Results.size} 项")
for ((Name, Ok) in Results) println((if (Ok) " ✓ " else " ✗ ") + Name)