Compare commits

..

7 commits

Author SHA1 Message Date
YYHEggEgg
3043d45ac9 implement free agent & choosing UP 2024-08-04 19:00:34 +08:00
YYHEggEgg
05e8ef740d Make Gacha interact with Item & Role Model 2024-08-01 14:15:28 +08:00
YYHEggEgg
950a3725ea command: player kick [uid] [reason] 2024-08-01 14:15:28 +08:00
YYHEggEgg
157832198c Gacha Model bugfix: post_configure on creating 2024-08-01 14:15:27 +08:00
YYHEggEgg
55780314d1 Gacha impl 2024-08-01 14:15:26 +08:00
YYHEggEgg
db2677be50 Proto (Gacha related) 2024-08-01 14:15:26 +08:00
YYHEggEgg
a7a1f4da37 Implement static config gacha needed 2024-08-01 14:15:25 +08:00
61 changed files with 1074 additions and 197081 deletions

View file

@ -13,8 +13,6 @@ tokio-util = { version = "0.7.10", features = ["io"] }
# Serialization
serde = { version = "1.0.197", features = ["derive"] }
serde_json = "1.0.114"
jsonc-parser = { version = "0.23.0", features = ["serde"] }
chrono = { version = "0.4.38", features = ["serde"] }
toml = "0.8.4"
rbase64 = "2.0.3"
prost = "0.12.3"

View file

@ -6,13 +6,9 @@
### Current features
- Logging in
- Fully playable prologue
- HollowDeepDive with Combat and Rally commissions
- Archive (playable cutscenes and battles)
- Open world
- World exploration with any character (can be changed with a command)
- Player progress saving with PostgreSQL
- Player management command system
- Training
- Unlock all characters
- Training battle
### Requirements
- [Rust](https://www.rust-lang.org/tools/install)

View file

@ -200,16 +200,5 @@
"store_template_id": 1191
}
]
},
{
"event_id": 15000301,
"actions": [
{
"$type": "OpenUi",
"ui": "UINewsStandPageController",
"args": 0,
"store_template_id": 1061
}
]
}
]

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,212 +0,0 @@
[
{
"ID": 100001,
"FOPDNLNNHDJ": 1000,
"DIIDBBGLDOL": "HollowGroupNameText_Arcade",
"EFPBDDJIJBO": "1040140114",
"OCBLGHECPLH": "Hollow_LockedHint_01",
"BEIEFNLAJCH": "UI/Sprite/A1DynamicLoad/YorozuyaPage/UnPacker/Icon_RallyQuest.png",
"CCFBKFKFJGM": "UI/3D/Yorozuya/Yorozuya_Hollow_Sphere_L.prefab",
"LDKNDDHOHND": "FF0019FF",
"EEBAIANJMEC": "006EFFFF|FF001900",
"EKMLECIKMID": false,
"FNIGJDFMHBA": "",
"BECFHCOGPKG": ""
},
{
"ID": 100101,
"FOPDNLNNHDJ": 1001,
"DIIDBBGLDOL": "HollowGroupNameText_Story",
"EFPBDDJIJBO": "",
"OCBLGHECPLH": "Hollow_LockedHint_01",
"BEIEFNLAJCH": "UI/Sprite/A1DynamicLoad/YorozuyaPage/UnPacker/Icon_Story.png",
"CCFBKFKFJGM": "UI/3D/Yorozuya/Yorozuya_Hollow_Sphere_M.prefab",
"LDKNDDHOHND": "FF9500FF",
"EEBAIANJMEC": "FF9500FF|FFDE0000",
"EKMLECIKMID": false,
"FNIGJDFMHBA": "UI/Menus/Widget/Yorozuya/Icon_MapTips.prefab",
"BECFHCOGPKG": "YorozuyaMapTipsUnLockChange_01"
},
{
"ID": 100102,
"FOPDNLNNHDJ": 1001,
"DIIDBBGLDOL": "HollowGroupNameText_exploration",
"EFPBDDJIJBO": "",
"OCBLGHECPLH": "Hollow_LockedHint_01",
"BEIEFNLAJCH": "UI/Sprite/A1DynamicLoad/YorozuyaPage/UnPacker/Icon_Explore.png",
"CCFBKFKFJGM": "UI/3D/Yorozuya/Yorozuya_Hollow_Sphere_M02.prefab",
"LDKNDDHOHND": "00A1FFFF",
"EEBAIANJMEC": "00A1FFFF|00FFD300",
"EKMLECIKMID": true,
"FNIGJDFMHBA": "UI/Menus/Widget/Yorozuya/Icon_MapTips03.prefab",
"BECFHCOGPKG": "YorozuyaMapTipsUnLockChange_03"
},
{
"ID": 100103,
"FOPDNLNNHDJ": 1001,
"DIIDBBGLDOL": "HollowGroupNameText_combat",
"EFPBDDJIJBO": "",
"OCBLGHECPLH": "Hollow_LockedHint_01",
"BEIEFNLAJCH": "UI/Sprite/A1DynamicLoad/YorozuyaPage/UnPacker/Icon_Combat.png",
"CCFBKFKFJGM": "UI/3D/Yorozuya/Yorozuya_Hollow_Sphere_S.prefab",
"LDKNDDHOHND": "00A1FFFF",
"EEBAIANJMEC": "00A1FFFF|00FFD300",
"EKMLECIKMID": true,
"FNIGJDFMHBA": "UI/Menus/Widget/Yorozuya/Icon_MapTips02.prefab",
"BECFHCOGPKG": "YorozuyaMapTipsUnLockChange_02"
},
{
"ID": 100201,
"FOPDNLNNHDJ": 1002,
"DIIDBBGLDOL": "HollowGroupNameText_Story",
"EFPBDDJIJBO": "",
"OCBLGHECPLH": "Hollow_LockedHint_01",
"BEIEFNLAJCH": "UI/Sprite/A1DynamicLoad/YorozuyaPage/UnPacker/Icon_Story.png",
"CCFBKFKFJGM": "UI/3D/Yorozuya/Yorozuya_Hollow_Sphere_M.prefab",
"LDKNDDHOHND": "FF9500FF",
"EEBAIANJMEC": "FF9500FF|FFDE0000",
"EKMLECIKMID": true,
"FNIGJDFMHBA": "UI/Menus/Widget/Yorozuya/Icon_MapTips.prefab",
"BECFHCOGPKG": "YorozuyaMapTipsUnLockChange_01"
},
{
"ID": 100202,
"FOPDNLNNHDJ": 1002,
"DIIDBBGLDOL": "HollowGroupNameText_exploration",
"EFPBDDJIJBO": "",
"OCBLGHECPLH": "Hollow_LockedHint_01",
"BEIEFNLAJCH": "UI/Sprite/A1DynamicLoad/YorozuyaPage/UnPacker/Icon_Explore.png",
"CCFBKFKFJGM": "UI/3D/Yorozuya/Yorozuya_Hollow_Sphere_M02.prefab",
"LDKNDDHOHND": "00A1FFFF",
"EEBAIANJMEC": "00A1FFFF|00FFD300",
"EKMLECIKMID": true,
"FNIGJDFMHBA": "UI/Menus/Widget/Yorozuya/Icon_MapTips03.prefab",
"BECFHCOGPKG": "YorozuyaMapTipsUnLockChange_03"
},
{
"ID": 100203,
"FOPDNLNNHDJ": 1002,
"DIIDBBGLDOL": "HollowGroupNameText_combat",
"EFPBDDJIJBO": "",
"OCBLGHECPLH": "Hollow_LockedHint_01",
"BEIEFNLAJCH": "UI/Sprite/A1DynamicLoad/YorozuyaPage/UnPacker/Icon_Combat.png",
"CCFBKFKFJGM": "UI/3D/Yorozuya/Yorozuya_Hollow_Sphere_S.prefab",
"LDKNDDHOHND": "00A1FFFF",
"EEBAIANJMEC": "00A1FFFF|00FFD300",
"EKMLECIKMID": true,
"FNIGJDFMHBA": "UI/Menus/Widget/Yorozuya/Icon_MapTips02.prefab",
"BECFHCOGPKG": "YorozuyaMapTipsUnLockChange_02"
},
{
"ID": 100301,
"FOPDNLNNHDJ": 1003,
"DIIDBBGLDOL": "HollowGroupNameText_Story",
"EFPBDDJIJBO": "",
"OCBLGHECPLH": "Hollow_LockedHint_01",
"BEIEFNLAJCH": "UI/Sprite/A1DynamicLoad/YorozuyaPage/UnPacker/Icon_Story.png",
"CCFBKFKFJGM": "UI/3D/Yorozuya/Yorozuya_Hollow_Sphere_M.prefab",
"LDKNDDHOHND": "FF9500FF",
"EEBAIANJMEC": "FF9500FF|FFDE0000",
"EKMLECIKMID": true,
"FNIGJDFMHBA": "UI/Menus/Widget/Yorozuya/Icon_MapTips.prefab",
"BECFHCOGPKG": "YorozuyaMapTipsUnLockChange_01"
},
{
"ID": 100302,
"FOPDNLNNHDJ": 1003,
"DIIDBBGLDOL": "HollowGroupNameText_exploration",
"EFPBDDJIJBO": "",
"OCBLGHECPLH": "Hollow_LockedHint_01",
"BEIEFNLAJCH": "UI/Sprite/A1DynamicLoad/YorozuyaPage/UnPacker/Icon_Explore.png",
"CCFBKFKFJGM": "UI/3D/Yorozuya/Yorozuya_Hollow_Sphere_M02.prefab",
"LDKNDDHOHND": "00A1FFFF",
"EEBAIANJMEC": "00A1FFFF|00FFD300",
"EKMLECIKMID": true,
"FNIGJDFMHBA": "UI/Menus/Widget/Yorozuya/Icon_MapTips03.prefab",
"BECFHCOGPKG": "YorozuyaMapTipsUnLockChange_03"
},
{
"ID": 100303,
"FOPDNLNNHDJ": 1003,
"DIIDBBGLDOL": "HollowGroupNameText_combat",
"EFPBDDJIJBO": "",
"OCBLGHECPLH": "Hollow_LockedHint_01",
"BEIEFNLAJCH": "UI/Sprite/A1DynamicLoad/YorozuyaPage/UnPacker/Icon_Combat.png",
"CCFBKFKFJGM": "UI/3D/Yorozuya/Yorozuya_Hollow_Sphere_S.prefab",
"LDKNDDHOHND": "00A1FFFF",
"EEBAIANJMEC": "00A1FFFF|00FFD300",
"EKMLECIKMID": true,
"FNIGJDFMHBA": "UI/Menus/Widget/Yorozuya/Icon_MapTips02.prefab",
"BECFHCOGPKG": "YorozuyaMapTipsUnLockChange_02"
},
{
"ID": 100401,
"FOPDNLNNHDJ": 1004,
"DIIDBBGLDOL": "HollowGroupNameText_BigWorld",
"EFPBDDJIJBO": "1100140008",
"OCBLGHECPLH": "Hollow_LockedHint_01",
"BEIEFNLAJCH": "UI/Sprite/A1DynamicLoad/YorozuyaPage/UnPacker/Icon_HighRisk.png",
"CCFBKFKFJGM": "UI/3D/Yorozuya/Yorozuya_Hollow_Sphere_L.prefab",
"LDKNDDHOHND": "00A1FFFF",
"EEBAIANJMEC": "00A1FFFF|00FFD300",
"EKMLECIKMID": false,
"FNIGJDFMHBA": "",
"BECFHCOGPKG": ""
},
{
"ID": 100501,
"FOPDNLNNHDJ": 1005,
"DIIDBBGLDOL": "HollowGroupNameText_Story",
"EFPBDDJIJBO": "",
"OCBLGHECPLH": "Hollow_LockedHint_01",
"BEIEFNLAJCH": "UI/Sprite/A1DynamicLoad/YorozuyaPage/UnPacker/Icon_Story.png",
"CCFBKFKFJGM": "UI/3D/Yorozuya/Yorozuya_Hollow_Sphere_M.prefab",
"LDKNDDHOHND": "FF9500FF",
"EEBAIANJMEC": "FF9500FF|FFDE0000",
"EKMLECIKMID": true,
"FNIGJDFMHBA": "UI/Menus/Widget/Yorozuya/Icon_MapTips.prefab",
"BECFHCOGPKG": "YorozuyaMapTipsUnLockChange_01"
},
{
"ID": 100502,
"FOPDNLNNHDJ": 1005,
"DIIDBBGLDOL": "HollowGroupNameText_exploration",
"EFPBDDJIJBO": "",
"OCBLGHECPLH": "Hollow_LockedHint_01",
"BEIEFNLAJCH": "UI/Sprite/A1DynamicLoad/YorozuyaPage/UnPacker/Icon_Explore.png",
"CCFBKFKFJGM": "UI/3D/Yorozuya/Yorozuya_Hollow_Sphere_M02.prefab",
"LDKNDDHOHND": "00A1FFFF",
"EEBAIANJMEC": "00A1FFFF|00FFD300",
"EKMLECIKMID": true,
"FNIGJDFMHBA": "UI/Menus/Widget/Yorozuya/Icon_MapTips03.prefab",
"BECFHCOGPKG": "YorozuyaMapTipsUnLockChange_03"
},
{
"ID": 100503,
"FOPDNLNNHDJ": 1005,
"DIIDBBGLDOL": "HollowGroupNameText_combat",
"EFPBDDJIJBO": "",
"OCBLGHECPLH": "Hollow_LockedHint_01",
"BEIEFNLAJCH": "UI/Sprite/A1DynamicLoad/YorozuyaPage/UnPacker/Icon_Combat.png",
"CCFBKFKFJGM": "UI/3D/Yorozuya/Yorozuya_Hollow_Sphere_S.prefab",
"LDKNDDHOHND": "00A1FFFF",
"EEBAIANJMEC": "00A1FFFF|00FFD300",
"EKMLECIKMID": true,
"FNIGJDFMHBA": "UI/Menus/Widget/Yorozuya/Icon_MapTips02.prefab",
"BECFHCOGPKG": "YorozuyaMapTipsUnLockChange_02"
},
{
"ID": 109901,
"FOPDNLNNHDJ": 1099,
"DIIDBBGLDOL": "HollowGroupNameText_Story",
"EFPBDDJIJBO": "",
"OCBLGHECPLH": "Hollow_LockedHint_01",
"BEIEFNLAJCH": "UI/Sprite/A1DynamicLoad/YorozuyaPage/UnPacker/Icon_Story.png",
"CCFBKFKFJGM": "UI/3D/Yorozuya/Yorozuya_Hollow_Sphere_M.prefab",
"LDKNDDHOHND": "FF9500FF",
"EEBAIANJMEC": "FF9500FF|FFDE0000",
"EKMLECIKMID": false,
"FNIGJDFMHBA": "UI/Menus/Widget/Yorozuya/Icon_MapTips.prefab",
"BECFHCOGPKG": "YorozuyaMapTipsUnLockChange_01"
}
]

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,29 +0,0 @@
[
{
"QuickAccessIndex": 1,
"QuickFuncID": 1005,
"RequiredUnlockIDs": [
1005
],
"EFPBDDJIJBO": 1040140154,
"BOKMLGNMJPB": null
},
{
"QuickAccessIndex": 2,
"QuickFuncID": 1022,
"RequiredUnlockIDs": [
1022
],
"EFPBDDJIJBO": 1040140049,
"BOKMLGNMJPB": null
},
{
"QuickAccessIndex": 3,
"QuickFuncID": 1009,
"RequiredUnlockIDs": [
1009
],
"EFPBDDJIJBO": 1050060115,
"BOKMLGNMJPB": null
}
]

View file

@ -1,112 +0,0 @@
[
{
"BtnID": 1001,
"BOMNEKHOGIH": "",
"PECPFBBOGKO": ""
},
{
"BtnID": 1002,
"BOMNEKHOGIH": "",
"PECPFBBOGKO": ""
},
{
"BtnID": 1003,
"BOMNEKHOGIH": "",
"PECPFBBOGKO": ""
},
{
"BtnID": 1004,
"BOMNEKHOGIH": "",
"PECPFBBOGKO": ""
},
{
"BtnID": 1005,
"BOMNEKHOGIH": "UI/Sprite/A1DynamicLoad/MainCityMenus/Packer/BtnInterKnot.png",
"PECPFBBOGKO": "UI/Sprite/A1DynamicLoad/MainCityMenus/Packer/BtnInterKnot.png"
},
{
"BtnID": 1006,
"BOMNEKHOGIH": "",
"PECPFBBOGKO": ""
},
{
"BtnID": 1007,
"BOMNEKHOGIH": "",
"PECPFBBOGKO": ""
},
{
"BtnID": 1008,
"BOMNEKHOGIH": "",
"PECPFBBOGKO": ""
},
{
"BtnID": 1009,
"BOMNEKHOGIH": "UI/Sprite/A1DynamicLoad/MainCityMenus/Packer/BtnGuide.png",
"PECPFBBOGKO": "UI/Sprite/A1DynamicLoad/MainCityMenus/Packer/BtnGuide.png"
},
{
"BtnID": 1010,
"BOMNEKHOGIH": "",
"PECPFBBOGKO": ""
},
{
"BtnID": 1011,
"BOMNEKHOGIH": "",
"PECPFBBOGKO": ""
},
{
"BtnID": 1013,
"BOMNEKHOGIH": "",
"PECPFBBOGKO": ""
},
{
"BtnID": 1014,
"BOMNEKHOGIH": "",
"PECPFBBOGKO": ""
},
{
"BtnID": 1015,
"BOMNEKHOGIH": "",
"PECPFBBOGKO": ""
},
{
"BtnID": 1016,
"BOMNEKHOGIH": "",
"PECPFBBOGKO": ""
},
{
"BtnID": 1017,
"BOMNEKHOGIH": "",
"PECPFBBOGKO": ""
},
{
"BtnID": 1018,
"BOMNEKHOGIH": "",
"PECPFBBOGKO": ""
},
{
"BtnID": 1019,
"BOMNEKHOGIH": "",
"PECPFBBOGKO": ""
},
{
"BtnID": 1020,
"BOMNEKHOGIH": "",
"PECPFBBOGKO": ""
},
{
"BtnID": 1021,
"BOMNEKHOGIH": "UI/Sprite/A1DynamicLoad/MainCityMenus/Packer/BtnCamera.png",
"PECPFBBOGKO": "UI/Sprite/A1DynamicLoad/MainCityMenus/Packer/BtnCamera.png"
},
{
"BtnID": 1022,
"BOMNEKHOGIH": "UI/Sprite/A1DynamicLoad/MainCityMenus/Packer/BtnMessage.png",
"PECPFBBOGKO": "UI/Sprite/A1DynamicLoad/MainCityMenus/Packer/BtnMessage.png"
},
{
"BtnID": 1023,
"BOMNEKHOGIH": "",
"PECPFBBOGKO": ""
}
]

View file

@ -1,882 +0,0 @@
[
{
"AreaID": 500000101,
"BattleEventID": 5000001,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5000001_1.asset",
"MDMOGIDKCLG": "metro4_2",
"LLPNKBBJHKL": "PlayerPos_E2",
"TimePeriod": "Morning",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 500000102,
"BattleEventID": 5000001,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5000001_2.asset",
"MDMOGIDKCLG": "metro4_2",
"LLPNKBBJHKL": "PlayerPos_E1",
"TimePeriod": "Morning",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 500000103,
"BattleEventID": 5000001,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5000001_3.asset",
"MDMOGIDKCLG": "metro4_2",
"LLPNKBBJHKL": "PlayerPos_E1",
"TimePeriod": "Morning",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 500000201,
"BattleEventID": 5000002,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5000002_1.asset",
"MDMOGIDKCLG": "Construction2_3_Boss",
"LLPNKBBJHKL": "PlayerPos_B5",
"TimePeriod": "Night",
"Weather": "Thunder",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 500000202,
"BattleEventID": 5000002,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5000002_2.asset",
"MDMOGIDKCLG": "Construction2_3_Boss",
"LLPNKBBJHKL": "PlayerPos_A1",
"TimePeriod": "Night",
"Weather": "Thunder",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 500000203,
"BattleEventID": 5000002,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5000002_3.asset",
"MDMOGIDKCLG": "Construction2_3_Boss",
"LLPNKBBJHKL": "PlayerPos_A1",
"TimePeriod": "Night",
"Weather": "Thunder",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 500000301,
"BattleEventID": 5000003,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5000003_1.asset",
"MDMOGIDKCLG": "Stage_SkyScraper_H_3_1",
"LLPNKBBJHKL": "PlayerPos_A1",
"TimePeriod": "Morning",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 500000302,
"BattleEventID": 5000003,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5000003_2.asset",
"MDMOGIDKCLG": "Stage_SkyScraper_H_3_1",
"LLPNKBBJHKL": "PlayerPos_A1",
"TimePeriod": "Morning",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 500000303,
"BattleEventID": 5000003,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5000003_3.asset",
"MDMOGIDKCLG": "Stage_SkyScraper_H_3_1",
"LLPNKBBJHKL": "PlayerPos_A1",
"TimePeriod": "Morning",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 500000401,
"BattleEventID": 5000004,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5000004_1.asset",
"MDMOGIDKCLG": "Stage_Zero_SpecialRoom_5",
"LLPNKBBJHKL": "PlayerPos_E1",
"TimePeriod": "Morning",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 500000402,
"BattleEventID": 5000004,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5000004_2.asset",
"MDMOGIDKCLG": "Stage_Zero_SpecialRoom_5",
"LLPNKBBJHKL": "PlayerPos_SP",
"TimePeriod": "Morning",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 500000403,
"BattleEventID": 5000004,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5000004_3.asset",
"MDMOGIDKCLG": "Stage_Zero_SpecialRoom_5",
"LLPNKBBJHKL": "PlayerPos_SP",
"TimePeriod": "Morning",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 501010101,
"BattleEventID": 5010101,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010101_1.asset",
"MDMOGIDKCLG": "metro4_1_Rally",
"LLPNKBBJHKL": "PlayerPos_CheckPoint1",
"TimePeriod": "Night",
"Weather": "SunShine",
"HAIGBCBDJPL": true,
"MBIMPLIHABC": ""
},
{
"AreaID": 501010102,
"BattleEventID": 5010101,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010101_2.asset",
"MDMOGIDKCLG": "metro4_1_Rally",
"LLPNKBBJHKL": "PlayerPos_CheckPoint2",
"TimePeriod": "Night",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 501010103,
"BattleEventID": 5010101,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010101_3.asset",
"MDMOGIDKCLG": "metro4_1_Rally",
"LLPNKBBJHKL": "PlayerPos_CheckPoint3",
"TimePeriod": "Night",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 501010104,
"BattleEventID": 5010101,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010101_4.asset",
"MDMOGIDKCLG": "Stage_Zero_ComplexRoom_22",
"LLPNKBBJHKL": "SpecialPlayerPos_1",
"TimePeriod": "Morning",
"Weather": "SunShine",
"HAIGBCBDJPL": true,
"MBIMPLIHABC": ""
},
{
"AreaID": 501010105,
"BattleEventID": 5010101,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010101_5.asset",
"MDMOGIDKCLG": "metro4_1_Rally",
"LLPNKBBJHKL": "PlayerPos_CheckPoint5",
"TimePeriod": "Night",
"Weather": "SunShine",
"HAIGBCBDJPL": true,
"MBIMPLIHABC": ""
},
{
"AreaID": 501011101,
"BattleEventID": 5010111,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010111_1.asset",
"MDMOGIDKCLG": "metro4_2_Rally",
"LLPNKBBJHKL": "PlayerPos_A1",
"TimePeriod": "Night",
"Weather": "Rain",
"HAIGBCBDJPL": true,
"MBIMPLIHABC": ""
},
{
"AreaID": 501011102,
"BattleEventID": 5010111,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010111_2.asset",
"MDMOGIDKCLG": "metro4_2_Rally",
"LLPNKBBJHKL": "RallyPlayerPos_C1",
"TimePeriod": "Night",
"Weather": "Rain",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 501011103,
"BattleEventID": 5010111,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010111_3.asset",
"MDMOGIDKCLG": "metro4_2_Rally",
"LLPNKBBJHKL": "RallyPlayerPos_A1",
"TimePeriod": "Night",
"Weather": "Rain",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 501011104,
"BattleEventID": 5010111,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010111_4.asset",
"MDMOGIDKCLG": "Stage_Zero_ComplexRoom_22",
"LLPNKBBJHKL": "SpecialPlayerPos_1",
"TimePeriod": "Morning",
"Weather": "Rain",
"HAIGBCBDJPL": true,
"MBIMPLIHABC": ""
},
{
"AreaID": 501011105,
"BattleEventID": 5010111,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010111_5.asset",
"MDMOGIDKCLG": "metro4_2_Rally",
"LLPNKBBJHKL": "PlayerPos_F2",
"TimePeriod": "Night",
"Weather": "Rain",
"HAIGBCBDJPL": true,
"MBIMPLIHABC": ""
},
{
"AreaID": 501012101,
"BattleEventID": 5010121,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010121_1.asset",
"MDMOGIDKCLG": "metro4_1_Rally",
"LLPNKBBJHKL": "PlayerPos_CheckPoint6",
"TimePeriod": "Night",
"Weather": "SunShine",
"HAIGBCBDJPL": true,
"MBIMPLIHABC": ""
},
{
"AreaID": 501012102,
"BattleEventID": 5010121,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010121_2.asset",
"MDMOGIDKCLG": "metro4_1_Rally",
"LLPNKBBJHKL": "PlayerPos_CheckPoint7",
"TimePeriod": "Night",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 501012103,
"BattleEventID": 5010121,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010121_3.asset",
"MDMOGIDKCLG": "metro4_2_Rally",
"LLPNKBBJHKL": "PlayerPos_A1",
"TimePeriod": "Night",
"Weather": "SunShine",
"HAIGBCBDJPL": true,
"MBIMPLIHABC": ""
},
{
"AreaID": 501012104,
"BattleEventID": 5010121,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010121_4.asset",
"MDMOGIDKCLG": "metro4_2_Rally",
"LLPNKBBJHKL": "PlayerPos_E2",
"TimePeriod": "Night",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 501020200,
"BattleEventID": 5010201,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010201_0.asset",
"MDMOGIDKCLG": "Stage_VR_TrainingRoom_30R",
"LLPNKBBJHKL": "PlayerPos_A1",
"TimePeriod": "Evening",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 501020201,
"BattleEventID": 5010201,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010201_1.asset",
"MDMOGIDKCLG": "Construction3_4_SuccessiveLevel",
"LLPNKBBJHKL": "PlayerPos_CheckPoint1",
"TimePeriod": "Evening",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 501020202,
"BattleEventID": 5010201,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010201_2.asset",
"MDMOGIDKCLG": "Construction3_4_SuccessiveLevel",
"LLPNKBBJHKL": "PlayerPos_CheckPoint2",
"TimePeriod": "Evening",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 501020203,
"BattleEventID": 5010201,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010201_3.asset",
"MDMOGIDKCLG": "Construction3_4_SuccessiveLevel",
"LLPNKBBJHKL": "PlayerPos_CheckPoint3",
"TimePeriod": "Evening",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 501020204,
"BattleEventID": 5010201,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010201_4.asset",
"MDMOGIDKCLG": "Construction3_4_SuccessiveLevel",
"LLPNKBBJHKL": "PlayerPos_CheckPoint4",
"TimePeriod": "Evening",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 501020205,
"BattleEventID": 5010201,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010201_5.asset",
"MDMOGIDKCLG": "Construction3_4_SuccessiveLevel",
"LLPNKBBJHKL": "PlayerPos_CheckPoint5",
"TimePeriod": "Evening",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 501020206,
"BattleEventID": 5010201,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010201_6.asset",
"MDMOGIDKCLG": "Stage_Zero_ComplexRoom_25_SuccessiveLevel",
"LLPNKBBJHKL": "PlayerPos_CheckPoint6",
"TimePeriod": "Morning",
"Weather": "SunShine",
"HAIGBCBDJPL": true,
"MBIMPLIHABC": ""
},
{
"AreaID": 501020207,
"BattleEventID": 5010201,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010201_7.asset",
"MDMOGIDKCLG": "Construction3_4_SuccessiveLevel",
"LLPNKBBJHKL": "PlayerPos_CheckPoint7",
"TimePeriod": "Evening",
"Weather": "SunShine",
"HAIGBCBDJPL": true,
"MBIMPLIHABC": ""
},
{
"AreaID": 501020208,
"BattleEventID": 5010201,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010201_6.asset",
"MDMOGIDKCLG": "Stage_Zero_ComplexRoom_25_SuccessiveLevel",
"LLPNKBBJHKL": "PlayerPos_CheckPoint6",
"TimePeriod": "Morning",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 501020209,
"BattleEventID": 5010201,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010201_9.asset",
"MDMOGIDKCLG": "Construction2_3_Boss",
"LLPNKBBJHKL": "PlayerPos_A1",
"TimePeriod": "Night",
"Weather": "Thunder",
"HAIGBCBDJPL": true,
"MBIMPLIHABC": ""
},
{
"AreaID": 501021101,
"BattleEventID": 5010211,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010211_1.asset",
"MDMOGIDKCLG": "Construction2_5_Rally",
"LLPNKBBJHKL": "PlayerPos_CheckPoint1",
"TimePeriod": "Evening",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 501021102,
"BattleEventID": 5010211,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010211_2.asset",
"MDMOGIDKCLG": "Construction2_5_Rally",
"LLPNKBBJHKL": "PlayerPos_CheckPoint2",
"TimePeriod": "Evening",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 501021103,
"BattleEventID": 5010211,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010211_3.asset",
"MDMOGIDKCLG": "Construction2_5_Rally",
"LLPNKBBJHKL": "PlayerPos_CheckPoint3",
"TimePeriod": "Evening",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 501021104,
"BattleEventID": 5010211,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010211_4.asset",
"MDMOGIDKCLG": "Stage_Zero_ComplexRoom_12_Rally",
"LLPNKBBJHKL": "PlayerPos_CheckPoint4",
"TimePeriod": "Morning",
"Weather": "SunShine",
"HAIGBCBDJPL": true,
"MBIMPLIHABC": ""
},
{
"AreaID": 501021105,
"BattleEventID": 5010211,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010211_5.asset",
"MDMOGIDKCLG": "Construction2_5_Rally",
"LLPNKBBJHKL": "PlayerPos_CheckPoint5",
"TimePeriod": "Evening",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 501021106,
"BattleEventID": 5010211,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010211_6.asset",
"MDMOGIDKCLG": "Construction2_5_Rally",
"LLPNKBBJHKL": "PlayerPos_CheckPoint6",
"TimePeriod": "Evening",
"Weather": "SunShine",
"HAIGBCBDJPL": true,
"MBIMPLIHABC": ""
},
{
"AreaID": 501021107,
"BattleEventID": 5010211,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010211_7.asset",
"MDMOGIDKCLG": "Construction2_5_Rally",
"LLPNKBBJHKL": "PlayerPos_CheckPoint3",
"TimePeriod": "Evening",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 501021301,
"BattleEventID": 5010213,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010213_1.asset",
"MDMOGIDKCLG": "Construction2_3_Boss",
"LLPNKBBJHKL": "PlayerPos_B5",
"TimePeriod": "Night",
"Weather": "Thunder",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 501021302,
"BattleEventID": 5010213,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010213_2.asset",
"MDMOGIDKCLG": "Construction2_3_Boss",
"LLPNKBBJHKL": "PlayerPos_A1",
"TimePeriod": "Night",
"Weather": "Thunder",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 501021303,
"BattleEventID": 5010213,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010213_3.asset",
"MDMOGIDKCLG": "Construction2_3_Boss",
"LLPNKBBJHKL": "PlayerPos_A1",
"TimePeriod": "Night",
"Weather": "Thunder",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 501021304,
"BattleEventID": 5010213,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010213_4.asset",
"MDMOGIDKCLG": "Construction2_3_Boss",
"LLPNKBBJHKL": "PlayerPos_A1",
"TimePeriod": "Night",
"Weather": "Thunder",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 501022101,
"BattleEventID": 5010221,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010221_1.asset",
"MDMOGIDKCLG": "Construction3_4_SuccessiveLevel",
"LLPNKBBJHKL": "PlayerPos_CheckPoint9",
"TimePeriod": "Evening",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 501022102,
"BattleEventID": 5010221,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010221_2.asset",
"MDMOGIDKCLG": "Construction3_4_SuccessiveLevel",
"LLPNKBBJHKL": "PlayerPos_C2",
"TimePeriod": "Evening",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 501022103,
"BattleEventID": 5010221,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010221_3.asset",
"MDMOGIDKCLG": "Construction2_3_Zero",
"LLPNKBBJHKL": "PlayerPos_E4_Rally",
"TimePeriod": "Evening",
"Weather": "SunShine",
"HAIGBCBDJPL": true,
"MBIMPLIHABC": ""
},
{
"AreaID": 501022104,
"BattleEventID": 5010221,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010221_4.asset",
"MDMOGIDKCLG": "Construction2_3_Zero",
"LLPNKBBJHKL": "PlayerPos_D4_Rally",
"TimePeriod": "Evening",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 501030101,
"BattleEventID": 5010301,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010301_1.asset",
"MDMOGIDKCLG": "Stage_SkyScraper_H_2_3_Successive",
"LLPNKBBJHKL": "SpecialPlayerPos_B1",
"TimePeriod": "Night",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 501030102,
"BattleEventID": 5010301,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010301_2.asset",
"MDMOGIDKCLG": "Stage_SkyScraper_L_2_1_Successive",
"LLPNKBBJHKL": "PlayerPos_CheckPoint1",
"TimePeriod": "Night",
"Weather": "SunShine",
"HAIGBCBDJPL": true,
"MBIMPLIHABC": ""
},
{
"AreaID": 501030103,
"BattleEventID": 5010301,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010301_3.asset",
"MDMOGIDKCLG": "Stage_SkyScraper_L_2_1_Successive",
"LLPNKBBJHKL": "PlayerPos_CheckPoint2",
"TimePeriod": "Night",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 501030104,
"BattleEventID": 5010301,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010301_4.asset",
"MDMOGIDKCLG": "Stage_SkyScraper_L_2_1_Successive",
"LLPNKBBJHKL": "PlayerPos_CheckPoint3",
"TimePeriod": "Night",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 501030105,
"BattleEventID": 5010301,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010301_5.asset",
"MDMOGIDKCLG": "Stage_SkyScraper_L_2_1_Successive",
"LLPNKBBJHKL": "PlayerPos_CheckPoint4",
"TimePeriod": "Night",
"Weather": "SunShine",
"HAIGBCBDJPL": true,
"MBIMPLIHABC": ""
},
{
"AreaID": 501030106,
"BattleEventID": 5010301,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010301_6.asset",
"MDMOGIDKCLG": "Stage_Zero_ComplexRoom_15_Rally",
"LLPNKBBJHKL": "PlayerPos_CheckPointSideQuest1",
"TimePeriod": "Morning",
"Weather": "SunShine",
"HAIGBCBDJPL": true,
"MBIMPLIHABC": ""
},
{
"AreaID": 501030107,
"BattleEventID": 5010301,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010301_7.asset",
"MDMOGIDKCLG": "Stage_SkyScraper_L_2_1_Successive",
"LLPNKBBJHKL": "SpecialRallyPos_7",
"TimePeriod": "Night",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 501031101,
"BattleEventID": 5010311,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010311_1.asset",
"MDMOGIDKCLG": "Stage_SkyScraper_H_2_1_Rally",
"LLPNKBBJHKL": "PlayerPos_CheckPoint1",
"TimePeriod": "Night",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 501031102,
"BattleEventID": 5010311,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010311_2.asset",
"MDMOGIDKCLG": "Stage_SkyScraper_H_2_1_Rally",
"LLPNKBBJHKL": "PlayerPos_CheckPoint2",
"TimePeriod": "Night",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 501031103,
"BattleEventID": 5010311,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010311_3.asset",
"MDMOGIDKCLG": "Stage_SkyScraper_H_2_1_Rally",
"LLPNKBBJHKL": "PlayerPos_CheckPoint3",
"TimePeriod": "Night",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 501031104,
"BattleEventID": 5010311,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010311_4.asset",
"MDMOGIDKCLG": "Stage_SkyScraper_H_2_1_Rally",
"LLPNKBBJHKL": "PlayerPos_CheckPoint4",
"TimePeriod": "Night",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 501031105,
"BattleEventID": 5010311,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010311_5.asset",
"MDMOGIDKCLG": "Stage_SkyScraper_H_2_1_Rally",
"LLPNKBBJHKL": "PlayerPos_CheckPoint5",
"TimePeriod": "Night",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 501031106,
"BattleEventID": 5010311,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010311_6.asset",
"MDMOGIDKCLG": "Stage_SkyScraper_H_2_1_Rally",
"LLPNKBBJHKL": "PlayerPos_CheckPoint6",
"TimePeriod": "Night",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 501031107,
"BattleEventID": 5010311,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010311_7.asset",
"MDMOGIDKCLG": "Stage_SkyScraper_L_1_1_Rally",
"LLPNKBBJHKL": "PlayerPos_CheckPoint7",
"TimePeriod": "Night",
"Weather": "SunShine",
"HAIGBCBDJPL": true,
"MBIMPLIHABC": ""
},
{
"AreaID": 501031108,
"BattleEventID": 5010311,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010311_8.asset",
"MDMOGIDKCLG": "Stage_Zero_ComplexRoom_15_Rally",
"LLPNKBBJHKL": "PlayerPos_CheckPoint8",
"TimePeriod": "Morning",
"Weather": "SunShine",
"HAIGBCBDJPL": true,
"MBIMPLIHABC": ""
},
{
"AreaID": 501031109,
"BattleEventID": 5010311,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010311_9.asset",
"MDMOGIDKCLG": "Stage_SkyScraper_H_2_1_Rally",
"LLPNKBBJHKL": "PlayerPos_CheckPoint9",
"TimePeriod": "Night",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 501031110,
"BattleEventID": 5010311,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010311_10.asset",
"MDMOGIDKCLG": "Stage_SkyScraper_H_1_1_Rally",
"LLPNKBBJHKL": "PlayerPos_CheckPoint10",
"TimePeriod": "Night",
"Weather": "SunShine",
"HAIGBCBDJPL": true,
"MBIMPLIHABC": ""
},
{
"AreaID": 501031111,
"BattleEventID": 5010311,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010311_11.asset",
"MDMOGIDKCLG": "Stage_SkyScraper_H_2_1_Rally",
"LLPNKBBJHKL": "PlayerPos_CheckPoint11",
"TimePeriod": "Night",
"Weather": "SunShine",
"HAIGBCBDJPL": true,
"MBIMPLIHABC": ""
},
{
"AreaID": 501031112,
"BattleEventID": 5010311,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010311_12.asset",
"MDMOGIDKCLG": "Stage_SkyScraper_L_1_1_Rally",
"LLPNKBBJHKL": "PlayerPos_CheckPoint12",
"TimePeriod": "Night",
"Weather": "SunShine",
"HAIGBCBDJPL": true,
"MBIMPLIHABC": ""
},
{
"AreaID": 501032101,
"BattleEventID": 5010321,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010321_1.asset",
"MDMOGIDKCLG": "Stage_SkyScraper_L_1_2_Zero",
"LLPNKBBJHKL": "PlayerPos_A5_Rally",
"TimePeriod": "Night",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 501032102,
"BattleEventID": 5010321,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010321_2.asset",
"MDMOGIDKCLG": "Stage_SkyScraper_L_1_2_Zero",
"LLPNKBBJHKL": "PlayerPos_A6_Rally",
"TimePeriod": "Night",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 501032103,
"BattleEventID": 5010321,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010321_3.asset",
"MDMOGIDKCLG": "Stage_SkyScraper_L_1_2_Zero",
"LLPNKBBJHKL": "PlayerPos_C5_Rally",
"TimePeriod": "Night",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 501032104,
"BattleEventID": 5010321,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5010321_4.asset",
"MDMOGIDKCLG": "Stage_SkyScraper_H_1_1_Rally",
"LLPNKBBJHKL": "PlayerPos_A1",
"TimePeriod": "Night",
"Weather": "SunShine",
"HAIGBCBDJPL": true,
"MBIMPLIHABC": ""
},
{
"AreaID": 503020101,
"BattleEventID": 5030201,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5030201_1.asset",
"MDMOGIDKCLG": "Stage_MetroArea_4_1_Lycaon",
"LLPNKBBJHKL": "PlayerPos_Ly1",
"TimePeriod": "Night",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 503020102,
"BattleEventID": 5030201,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5030201_2.asset",
"MDMOGIDKCLG": "Stage_MetroArea_4_1_Lycaon",
"LLPNKBBJHKL": "PlayerPos_Ly2",
"TimePeriod": "Night",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 503020103,
"BattleEventID": 5030201,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5030201_3.asset",
"MDMOGIDKCLG": "Stage_MetroArea_4_1_Lycaon",
"LLPNKBBJHKL": "PlayerPos_Ly3",
"TimePeriod": "Night",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 503021101,
"BattleEventID": 5030211,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5030211_1.asset",
"MDMOGIDKCLG": "Stage_SkyScraper_L_1_2_Lycaon",
"LLPNKBBJHKL": "PlayerPos_Ly1",
"TimePeriod": "Night",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 503021102,
"BattleEventID": 5030211,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5030211_2.asset",
"MDMOGIDKCLG": "Stage_SkyScraper_L_1_2_Lycaon",
"LLPNKBBJHKL": "PlayerPos_Ly2",
"TimePeriod": "Night",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
},
{
"AreaID": 503021103,
"BattleEventID": 5030211,
"NELIKCGAKEJ": "Level/FC_Main/FC_Successive/FC_5030211_3.asset",
"MDMOGIDKCLG": "Stage_SkyScraper_L_1_2_Lycaon",
"LLPNKBBJHKL": "PlayerPos_Ly3",
"TimePeriod": "Night",
"Weather": "SunShine",
"HAIGBCBDJPL": false,
"MBIMPLIHABC": ""
}
]

File diff suppressed because it is too large Load diff

View file

@ -1,604 +0,0 @@
[
{
"ID": 500000101,
"BattleEventID": 5000001,
"VariableName": "IsArea1Finish",
"ValueType": "bool",
"InitialValue": "false"
},
{
"ID": 500000102,
"BattleEventID": 5000001,
"VariableName": "IsArea2Finish",
"ValueType": "bool",
"InitialValue": "false"
},
{
"ID": 500000103,
"BattleEventID": 5000001,
"VariableName": "IsArea3Finish",
"ValueType": "bool",
"InitialValue": "false"
},
{
"ID": 500000104,
"BattleEventID": 5000001,
"VariableName": "IsArea4Finish",
"ValueType": "bool",
"InitialValue": "false"
},
{
"ID": 500000105,
"BattleEventID": 5000001,
"VariableName": "IsArea5Finish",
"ValueType": "bool",
"InitialValue": "false"
},
{
"ID": 500000106,
"BattleEventID": 5000001,
"VariableName": "IsArea6Finish",
"ValueType": "bool",
"InitialValue": "false"
},
{
"ID": 500000107,
"BattleEventID": 5000001,
"VariableName": "SideQuest",
"ValueType": "bool",
"InitialValue": "false"
},
{
"ID": 500000108,
"BattleEventID": 5000001,
"VariableName": "SecondSideQuest",
"ValueType": "bool",
"InitialValue": "false"
},
{
"ID": 500000109,
"BattleEventID": 5000001,
"VariableName": "AreaSelect",
"ValueType": "int",
"InitialValue": "1"
},
{
"ID": 500000201,
"BattleEventID": 5000002,
"VariableName": "whichAreaIn",
"ValueType": "int",
"InitialValue": "1"
},
{
"ID": 500000202,
"BattleEventID": 5000002,
"VariableName": "IsArea2Finish",
"ValueType": "bool",
"InitialValue": "false"
},
{
"ID": 500000203,
"BattleEventID": 5000002,
"VariableName": "IsArea3Finish",
"ValueType": "bool",
"InitialValue": "false"
},
{
"ID": 500000204,
"BattleEventID": 5000002,
"VariableName": "IsArea4Finish",
"ValueType": "bool",
"InitialValue": "false"
},
{
"ID": 500000205,
"BattleEventID": 5000002,
"VariableName": "IsArea5Finish",
"ValueType": "bool",
"InitialValue": "false"
},
{
"ID": 500000206,
"BattleEventID": 5000002,
"VariableName": "SecretMission",
"ValueType": "int",
"InitialValue": "0"
},
{
"ID": 500000207,
"BattleEventID": 5000002,
"VariableName": "AreaSelect",
"ValueType": "int",
"InitialValue": "1"
},
{
"ID": 500000301,
"BattleEventID": 5000003,
"VariableName": "AreaSelect",
"ValueType": "int",
"InitialValue": "1"
},
{
"ID": 500000401,
"BattleEventID": 5000004,
"VariableName": "AreaSelect",
"ValueType": "int",
"InitialValue": "1"
},
{
"ID": 501010100,
"BattleEventID": 5010101,
"VariableName": "AreaSelect",
"ValueType": "int",
"InitialValue": "1"
},
{
"ID": 501010101,
"BattleEventID": 5010101,
"VariableName": "SideQuest",
"ValueType": "bool",
"InitialValue": "false"
},
{
"ID": 501010102,
"BattleEventID": 5010101,
"VariableName": "IsSideQuestFinsih",
"ValueType": "bool",
"InitialValue": "false"
},
{
"ID": 501010103,
"BattleEventID": 5010101,
"VariableName": "RevivedTime",
"ValueType": "int",
"InitialValue": "0"
},
{
"ID": 501010104,
"BattleEventID": 5010101,
"VariableName": "IsSideQuestRestart",
"ValueType": "bool",
"InitialValue": "false"
},
{
"ID": 501011101,
"BattleEventID": 5010111,
"VariableName": "AreaSelect",
"ValueType": "int",
"InitialValue": "1"
},
{
"ID": 501011102,
"BattleEventID": 5010111,
"VariableName": "SideQuest",
"ValueType": "bool",
"InitialValue": "false"
},
{
"ID": 501011103,
"BattleEventID": 5010111,
"VariableName": "IsSideQuestFinsih",
"ValueType": "bool",
"InitialValue": "false"
},
{
"ID": 501011104,
"BattleEventID": 5010111,
"VariableName": "RevivedTime",
"ValueType": "int",
"InitialValue": "0"
},
{
"ID": 501011105,
"BattleEventID": 5010111,
"VariableName": "GroundSideQuest",
"ValueType": "bool",
"InitialValue": "false"
},
{
"ID": 501011106,
"BattleEventID": 5010111,
"VariableName": "IsSideQuestRestart",
"ValueType": "bool",
"InitialValue": "FALSE"
},
{
"ID": 501012101,
"BattleEventID": 5010121,
"VariableName": "AreaSelect",
"ValueType": "int",
"InitialValue": "4"
},
{
"ID": 501012102,
"BattleEventID": 5010121,
"VariableName": "RevivedTime",
"ValueType": "int",
"InitialValue": "0"
},
{
"ID": 501020200,
"BattleEventID": 5010201,
"VariableName": "IsEnterTeachLevel",
"ValueType": "bool",
"InitialValue": "false"
},
{
"ID": 501020201,
"BattleEventID": 5010201,
"VariableName": "IsArea1Finish",
"ValueType": "bool",
"InitialValue": "false"
},
{
"ID": 501020202,
"BattleEventID": 5010201,
"VariableName": "IsArea2Finish",
"ValueType": "bool",
"InitialValue": "false"
},
{
"ID": 501020203,
"BattleEventID": 5010201,
"VariableName": "IsArea3Finish",
"ValueType": "bool",
"InitialValue": "false"
},
{
"ID": 501020204,
"BattleEventID": 5010201,
"VariableName": "IsArea4Finish",
"ValueType": "bool",
"InitialValue": "false"
},
{
"ID": 501020205,
"BattleEventID": 5010201,
"VariableName": "IsArea5Finish",
"ValueType": "bool",
"InitialValue": "false"
},
{
"ID": 501020206,
"BattleEventID": 5010201,
"VariableName": "IsArea6Finish",
"ValueType": "bool",
"InitialValue": "false"
},
{
"ID": 501020207,
"BattleEventID": 5010201,
"VariableName": "IsArea7Finish",
"ValueType": "bool",
"InitialValue": "false"
},
{
"ID": 501020208,
"BattleEventID": 5010201,
"VariableName": "SideQuest",
"ValueType": "bool",
"InitialValue": "false"
},
{
"ID": 501020209,
"BattleEventID": 5010201,
"VariableName": "IsArea8Finish",
"ValueType": "bool",
"InitialValue": "false"
},
{
"ID": 501020210,
"BattleEventID": 5010201,
"VariableName": "SecondSideQuest",
"ValueType": "bool",
"InitialValue": "false"
},
{
"ID": 501020211,
"BattleEventID": 5010201,
"VariableName": "StartSecondPhase",
"ValueType": "bool",
"InitialValue": "false"
},
{
"ID": 501020212,
"BattleEventID": 5010201,
"VariableName": "AreaSelect",
"ValueType": "int",
"InitialValue": "1"
},
{
"ID": 501020213,
"BattleEventID": 5010201,
"VariableName": "IsSideQuestRestart",
"ValueType": "bool",
"InitialValue": "false"
},
{
"ID": 501020214,
"BattleEventID": 5010201,
"VariableName": "IsSideQuestFinsih",
"ValueType": "bool",
"InitialValue": "false"
},
{
"ID": 501020215,
"BattleEventID": 5010201,
"VariableName": "RevivedTime",
"ValueType": "int",
"InitialValue": "0"
},
{
"ID": 501020216,
"BattleEventID": 5010201,
"VariableName": "LifeCoinGet",
"ValueType": "bool",
"InitialValue": "FALSE"
},
{
"ID": 501021101,
"BattleEventID": 5010211,
"VariableName": "AreaSelect",
"ValueType": "int",
"InitialValue": "1"
},
{
"ID": 501021102,
"BattleEventID": 5010211,
"VariableName": "IsSideQuestRestart",
"ValueType": "bool",
"InitialValue": "false"
},
{
"ID": 501021103,
"BattleEventID": 5010211,
"VariableName": "IsSideQuestFinsih",
"ValueType": "bool",
"InitialValue": "false"
},
{
"ID": 501021104,
"BattleEventID": 5010211,
"VariableName": "RevivedTime",
"ValueType": "int",
"InitialValue": "0"
},
{
"ID": 501021105,
"BattleEventID": 5010211,
"VariableName": "BossHealth",
"ValueType": "float",
"InitialValue": "1"
},
{
"ID": 501021106,
"BattleEventID": 5010211,
"VariableName": "CargoCartStopped",
"ValueType": "bool",
"InitialValue": "FALSE"
},
{
"ID": 501021107,
"BattleEventID": 5010211,
"VariableName": "IsLastAreaFinish",
"ValueType": "bool",
"InitialValue": "false"
},
{
"ID": 501021301,
"BattleEventID": 5010213,
"VariableName": "whichAreaIn",
"ValueType": "int",
"InitialValue": "1"
},
{
"ID": 501022101,
"BattleEventID": 5010221,
"VariableName": "AreaSelect",
"ValueType": "int",
"InitialValue": "4"
},
{
"ID": 501022102,
"BattleEventID": 5010221,
"VariableName": "RevivedTime",
"ValueType": "int",
"InitialValue": "0"
},
{
"ID": 501030101,
"BattleEventID": 5010301,
"VariableName": "AreaSelect",
"ValueType": "int",
"InitialValue": "1"
},
{
"ID": 501030102,
"BattleEventID": 5010301,
"VariableName": "IsSideQuestFinsih",
"ValueType": "bool",
"InitialValue": "false"
},
{
"ID": 501030103,
"BattleEventID": 5010301,
"VariableName": "RevivedTime",
"ValueType": "int",
"InitialValue": "0"
},
{
"ID": 501030104,
"BattleEventID": 5010301,
"VariableName": "DoorStatus1",
"ValueType": "int",
"InitialValue": "0"
},
{
"ID": 501030105,
"BattleEventID": 5010301,
"VariableName": "DoorStatus2",
"ValueType": "int",
"InitialValue": "0"
},
{
"ID": 501030106,
"BattleEventID": 5010301,
"VariableName": "DoorStatus3",
"ValueType": "int",
"InitialValue": "0"
},
{
"ID": 501030107,
"BattleEventID": 5010301,
"VariableName": "DoorStatus4",
"ValueType": "int",
"InitialValue": "0"
},
{
"ID": 501030108,
"BattleEventID": 5010301,
"VariableName": "DoorStatus5",
"ValueType": "int",
"InitialValue": "0"
},
{
"ID": 501030109,
"BattleEventID": 5010301,
"VariableName": "IsSideQuestRestart",
"ValueType": "bool",
"InitialValue": "FALSE"
},
{
"ID": 501030110,
"BattleEventID": 5010301,
"VariableName": "IsTrickFinish",
"ValueType": "int",
"InitialValue": "0"
},
{
"ID": 501031101,
"BattleEventID": 5010311,
"VariableName": "AreaSelect",
"ValueType": "int",
"InitialValue": "1"
},
{
"ID": 501031102,
"BattleEventID": 5010311,
"VariableName": "IsSideQuestFinsih",
"ValueType": "bool",
"InitialValue": "false"
},
{
"ID": 501031103,
"BattleEventID": 5010311,
"VariableName": "RevivedTime",
"ValueType": "int",
"InitialValue": "0"
},
{
"ID": 501031104,
"BattleEventID": 5010311,
"VariableName": "IsAllyRescued",
"ValueType": "bool",
"InitialValue": "FALSE"
},
{
"ID": 501031105,
"BattleEventID": 5010311,
"VariableName": "IsShortcutOpened",
"ValueType": "bool",
"InitialValue": "FALSE"
},
{
"ID": 501031106,
"BattleEventID": 5010311,
"VariableName": "IsGuardDefeated",
"ValueType": "bool",
"InitialValue": "FALSE"
},
{
"ID": 501031107,
"BattleEventID": 5010311,
"VariableName": "DoorStatus1",
"ValueType": "int",
"InitialValue": "0"
},
{
"ID": 501031108,
"BattleEventID": 5010311,
"VariableName": "DoorStatus2",
"ValueType": "int",
"InitialValue": "0"
},
{
"ID": 501031109,
"BattleEventID": 5010311,
"VariableName": "DoorStatus3",
"ValueType": "int",
"InitialValue": "0"
},
{
"ID": 501031110,
"BattleEventID": 5010311,
"VariableName": "DoorStatus4",
"ValueType": "int",
"InitialValue": "0"
},
{
"ID": 501031111,
"BattleEventID": 5010311,
"VariableName": "DoorStatus5",
"ValueType": "int",
"InitialValue": "0"
},
{
"ID": 501031112,
"BattleEventID": 5010311,
"VariableName": "IsAllyImposterCatched",
"ValueType": "bool",
"InitialValue": "FALSE"
},
{
"ID": 501031113,
"BattleEventID": 5010311,
"VariableName": "IsSideQuestRestart",
"ValueType": "bool",
"InitialValue": "FALSE"
},
{
"ID": 501031114,
"BattleEventID": 5010311,
"VariableName": "LifeCoinGet",
"ValueType": "bool",
"InitialValue": "FALSE"
},
{
"ID": 501032101,
"BattleEventID": 5010321,
"VariableName": "AreaSelect",
"ValueType": "int",
"InitialValue": "4"
},
{
"ID": 501032102,
"BattleEventID": 5010321,
"VariableName": "RevivedTime",
"ValueType": "int",
"InitialValue": "0"
},
{
"ID": 503020101,
"BattleEventID": 5030201,
"VariableName": "AreaSelect",
"ValueType": "int",
"InitialValue": "1"
},
{
"ID": 503021101,
"BattleEventID": 5030211,
"VariableName": "AreaSelect",
"ValueType": "int",
"InitialValue": "1"
}
]

View file

@ -168,7 +168,6 @@
],
"categories": {
"Standard:Bangboo": {
"item_type": "GACHA_ADDED_ITEM_TYPE_BANGBOO",
"item_ids": [
54002, // 阿全
54005, // 艾米莉安
@ -181,7 +180,6 @@
"category_weight": 1
},
"Event-Exclusive:Bangboo": {
"item_type": "GACHA_ADDED_ITEM_TYPE_BANGBOO",
"item_ids": [
54004, // 巴特勒
],
@ -201,7 +199,6 @@
],
"categories": {
"Standard:Bangboo": {
"item_type": "GACHA_ADDED_ITEM_TYPE_BANGBOO",
"item_ids": [
53001, // 企鹅布
53003, // 寻宝布
@ -227,7 +224,6 @@
"probability_model_tag": "get-B",
"categories": {
"Standard:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [
12001, // 「月相」-望
12002, // 「月相」-晦
@ -271,7 +267,6 @@
],
"categories": {
"Standard:Agent": {
"item_type": "GACHA_ADDED_ITEM_TYPE_CHARACTER",
"item_ids": [
1021, // 猫又
1101, // 珂蕾妲
@ -283,7 +278,6 @@
"category_weight": 50
},
"Event-Exclusive:Agent": {
"item_type": "GACHA_ADDED_ITEM_TYPE_CHARACTER",
"is_promotional_items": true,
"item_ids": [
1191, // 艾莲
@ -306,7 +300,6 @@
],
"categories": {
"Standard:Agent": {
"item_type": "GACHA_ADDED_ITEM_TYPE_CHARACTER",
"item_ids": [
1011, // 安比
1031, // 妮可
@ -319,7 +312,6 @@
"category_weight": 3525
},
"Event-Exclusive:Agent": {
"item_type": "GACHA_ADDED_ITEM_TYPE_CHARACTER",
"is_promotional_items": true,
"item_ids": [
1111, // 安东
@ -328,7 +320,6 @@
"category_weight": 3525
},
"Standard:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [
13001, // 街头巨星
13002, // 时光切片
@ -361,7 +352,6 @@
"probability_model_tag": "get-B",
"categories": {
"Standard:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [
12001, // 「月相」-望
12002, // 「月相」-晦
@ -408,7 +398,6 @@
],
"categories": {
"Standard:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [
14102, // 钢铁肉垫
14110, // 燃狱齿轮
@ -420,7 +409,6 @@
"category_weight": 25
},
"Event-Exclusive:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [
14119, // 深海访客
],
@ -445,7 +433,6 @@
],
"categories": {
"Standard:Agent": {
"item_type": "GACHA_ADDED_ITEM_TYPE_CHARACTER",
"item_ids": [
1011, // 安比
1031, // 妮可
@ -460,7 +447,6 @@
"category_weight": 3750
},
"Standard:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [
13001, // 街头巨星
13002, // 时光切片
@ -482,7 +468,6 @@
"category_weight": 13125
},
"Event-Exclusive:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [
13113, // 含羞恶面
13111, // 旋钻机-赤轴
@ -500,7 +485,6 @@
"probability_model_tag": "get-B",
"categories": {
"Standard:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [
12001, // 「月相」-望
12002, // 「月相」-晦
@ -544,7 +528,6 @@
],
"categories": {
"Standard:Agent": {
"item_type": "GACHA_ADDED_ITEM_TYPE_CHARACTER",
"item_ids": [
1021, // 猫又
1101, // 珂蕾妲
@ -556,7 +539,6 @@
"category_weight": 50
},
"Event-Exclusive:Agent": {
"item_type": "GACHA_ADDED_ITEM_TYPE_CHARACTER",
"is_promotional_items": true,
"item_ids": [
1241 // 朱鸢
@ -576,7 +558,6 @@
],
"categories": {
"Standard:Agent": {
"item_type": "GACHA_ADDED_ITEM_TYPE_CHARACTER",
"item_ids": [
1011, // 安比
1061, // 可琳
@ -589,7 +570,6 @@
"category_weight": 3525
},
"Event-Exclusive:Agent": {
"item_type": "GACHA_ADDED_ITEM_TYPE_CHARACTER",
"is_promotional_items": true,
"item_ids": [
1031, // 妮可
@ -598,7 +578,6 @@
"category_weight": 3525
},
"Standard:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [
13001, // 街头巨星
13002, // 时光切片
@ -631,7 +610,6 @@
"probability_model_tag": "get-B",
"categories": {
"Standard:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [
12001, // 「月相」-望
12002, // 「月相」-晦
@ -675,7 +653,6 @@
],
"categories": {
"Standard:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [
14102, // 钢铁肉垫
14110, // 燃狱齿轮
@ -687,7 +664,6 @@
"category_weight": 25
},
"Event-Exclusive:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [
14124 // 防暴者VI型
],
@ -707,7 +683,6 @@
],
"categories": {
"Standard:Agent": {
"item_type": "GACHA_ADDED_ITEM_TYPE_CHARACTER",
"item_ids": [
1011, // 安比
1031, // 妮可
@ -722,7 +697,6 @@
"category_weight": 3750
},
"Standard:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [
13007, // 正版变身器
13002, // 时光切片
@ -744,7 +718,6 @@
"category_weight": 13125
},
"Event-Exclusive:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [
13103, // 聚宝箱
13112, // 比格气缸
@ -762,7 +735,6 @@
"probability_model_tag": "get-B",
"categories": {
"Standard:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [
12001, // 「月相」-望
12002, // 「月相」-晦
@ -808,7 +780,6 @@
],
"categories": {
"Standard:Agent": {
"item_type": "GACHA_ADDED_ITEM_TYPE_CHARACTER",
"item_ids": [
1021, // 猫又
1101, // 珂蕾妲
@ -820,7 +791,6 @@
"category_weight": 50
},
"Event-Exclusive:Agent": {
"item_type": "GACHA_ADDED_ITEM_TYPE_CHARACTER",
"is_promotional_items": true,
"item_ids": [
1251, // 青衣
@ -840,7 +810,6 @@
],
"categories": {
"Standard:Agent": {
"item_type": "GACHA_ADDED_ITEM_TYPE_CHARACTER",
"item_ids": [
1011, // 安比
1031, // 妮可
@ -854,7 +823,6 @@
"category_weight": 3525
},
"Event-Exclusive:Agent": {
"item_type": "GACHA_ADDED_ITEM_TYPE_CHARACTER",
"is_promotional_items": true,
"item_ids": [
1131, // 苍角
@ -863,7 +831,6 @@
"category_weight": 3525
},
"Standard:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [
13001, // 街头巨星
13002, // 时光切片
@ -897,7 +864,6 @@
"probability_model_tag": "get-B",
"categories": {
"Standard:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [
12001, // 「月相」-望
12002, // 「月相」-晦
@ -941,7 +907,6 @@
],
"categories": {
"Standard:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [
14102, // 钢铁肉垫
14110, // 燃狱齿轮
@ -953,7 +918,6 @@
"category_weight": 25
},
"Event-Exclusive:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [
14125, // 玉壶青冰
],
@ -973,7 +937,6 @@
],
"categories": {
"Standard:Agent": {
"item_type": "GACHA_ADDED_ITEM_TYPE_CHARACTER",
"item_ids": [
1011, // 安比
1031, // 妮可
@ -986,7 +949,6 @@
"category_weight": 3750
},
"Standard:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [
13001, // 街头巨星
13002, // 时光切片
@ -1009,7 +971,6 @@
"category_weight": 13125
},
"Event-Exclusive:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [
13113, // 含羞恶面
13127, // 维序者-特化型
@ -1027,7 +988,6 @@
"probability_model_tag": "get-B",
"categories": {
"Standard:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [
12001, // 「月相」-望
12002, // 「月相」-晦
@ -1071,7 +1031,6 @@
],
"categories": {
"Standard:Agent": {
"item_type": "GACHA_ADDED_ITEM_TYPE_CHARACTER",
"item_ids": [
1021, // 猫又
1101, // 珂蕾妲
@ -1083,7 +1042,6 @@
"category_weight": 50
},
"Event-Exclusive:Agent": {
"item_type": "GACHA_ADDED_ITEM_TYPE_CHARACTER",
"is_promotional_items": true,
"item_ids": [
1261, // 简
@ -1103,7 +1061,6 @@
],
"categories": {
"Standard:Agent": {
"item_type": "GACHA_ADDED_ITEM_TYPE_CHARACTER",
"item_ids": [
1011, // 安比
1031, // 妮可
@ -1117,7 +1074,6 @@
"category_weight": 3525
},
"Event-Exclusive:Agent": {
"item_type": "GACHA_ADDED_ITEM_TYPE_CHARACTER",
"is_promotional_items": true,
"item_ids": [
1271, // 赛斯
@ -1126,7 +1082,6 @@
"category_weight": 3525
},
"Standard:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [
13001, // 街头巨星
13002, // 时光切片
@ -1160,7 +1115,6 @@
"probability_model_tag": "get-B",
"categories": {
"Standard:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [
12001, // 「月相」-望
12002, // 「月相」-晦
@ -1204,7 +1158,6 @@
],
"categories": {
"Standard:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [
14102, // 钢铁肉垫
14110, // 燃狱齿轮
@ -1216,7 +1169,6 @@
"category_weight": 25
},
"Event-Exclusive:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [
14126, // 淬锋钳刺
],
@ -1236,7 +1188,6 @@
],
"categories": {
"Standard:Agent": {
"item_type": "GACHA_ADDED_ITEM_TYPE_CHARACTER",
"item_ids": [
1011, // 安比
1031, // 妮可
@ -1249,7 +1200,6 @@
"category_weight": 3750
},
"Standard:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [
13001, // 街头巨星
13002, // 时光切片
@ -1272,7 +1222,6 @@
"category_weight": 13125
},
"Event-Exclusive:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [
13127, // 维序者-特化型
13113, // 含羞恶面
@ -1290,7 +1239,6 @@
"probability_model_tag": "get-B",
"categories": {
"Standard:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [
12001, // 「月相」-望
12002, // 「月相」-晦

View file

@ -1,2 +1,2 @@
pub const SERVER_VERSION: &str = "0.2.0";
pub const SERVER_VERSION: &str = "0.1.0";
pub const CLIENT_VERSION: &str = "CNBeta1.1.1";

View file

@ -7,8 +7,6 @@ version.workspace = true
# Serialization
serde.workspace = true
serde_json.workspace = true
jsonc-parser.workspace = true
chrono.workspace = true
prost.workspace = true
# Util
@ -20,3 +18,5 @@ tracing.workspace = true
# Internal
proto.workspace = true
jsonc-parser = { version = "0.23.0", features = ["serde"] }
chrono = { version = "0.4.38", features = ["serde"] }

View file

@ -1,6 +1,7 @@
use std::collections::{HashMap, HashSet};
use chrono::{prelude::Local, DateTime};
use proto::GachaAddedItemType;
use serde::{Deserialize, Deserializer};
use tracing;
@ -151,64 +152,13 @@ impl DiscountPolicyCollection {
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum GachaAddedItemType {
#[default]
None = 0,
Weapon = 1,
Character = 2,
Bangboo = 3,
}
impl GachaAddedItemType {
pub fn as_str_name(&self) -> &'static str {
match self {
GachaAddedItemType::None => "GACHA_ADDED_ITEM_TYPE_NONE",
GachaAddedItemType::Weapon => "GACHA_ADDED_ITEM_TYPE_WEAPON",
GachaAddedItemType::Character => "GACHA_ADDED_ITEM_TYPE_CHARACTER",
GachaAddedItemType::Bangboo => "GACHA_ADDED_ITEM_TYPE_BANGBOO",
}
}
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
match value {
"GACHA_ADDED_ITEM_TYPE_NONE" => Some(Self::None),
"GACHA_ADDED_ITEM_TYPE_WEAPON" => Some(Self::Weapon),
"GACHA_ADDED_ITEM_TYPE_CHARACTER" => Some(Self::Character),
"GACHA_ADDED_ITEM_TYPE_BANGBOO" => Some(Self::Bangboo),
_ => None,
}
}
}
impl From<i32> for GachaAddedItemType {
fn from(value: i32) -> Self {
match value {
1 => Self::Weapon,
2 => Self::Character,
3 => Self::Bangboo,
_ => Self::None
}
}
}
impl Into<i32> for GachaAddedItemType {
fn into(self) -> i32 {
match self {
Self::Weapon => 1,
Self::Character => 2,
Self::Bangboo => 3,
Self::None => 0,
}
}
}
#[derive(Debug, Default, Deserialize)]
pub struct GachaCategoryInfo {
#[serde(default)]
pub is_promotional_items: bool,
pub item_ids: Vec<u32>,
pub category_weight: u32,
#[serde(deserialize_with = "from_str")]
#[serde(default, deserialize_with = "from_str")]
pub item_type: GachaAddedItemType,
}

View file

@ -1,7 +1,5 @@
use serde::Deserialize;
use super::BattleEventConfigID;
template_id!(ArchiveBattleQuest u32 id);
#[derive(Deserialize, Debug)]
@ -21,7 +19,7 @@ pub struct ArchiveBattleQuestTemplate {
#[serde(rename = "FirstBattleEventID")]
pub first_battle_event_id: u32,
#[serde(rename = "BattleEventID")]
pub battle_event_id: BattleEventConfigID,
pub battle_event_id: u32,
pub battle_rank: String,
pub slot1_avatar: i32,
pub slot2_avatar: i32,

View file

@ -1,16 +0,0 @@
use serde::Deserialize;
use super::OnceRewardID;
template_id!(BattleEventConfig u32 id);
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct BattleEventConfigTemplate {
#[serde(rename = "ID")]
pub id: BattleEventConfigID,
#[serde(rename = "LevelDesignID")]
pub level_design_id: u32,
pub normal_drop: String,
pub special_drop: Vec<OnceRewardID>,
}

View file

@ -1,16 +0,0 @@
use serde::Deserialize;
use super::BattleEventConfigID;
template_id!(BattleGroupConfig u32 id);
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct BattleGroupConfigTemplate {
#[serde(rename = "ID")]
pub id: BattleGroupConfigID,
#[serde(rename = "QuestID")]
pub quest_id: u32,
#[serde(rename = "BattleEventID")]
pub battle_event_id: BattleEventConfigID,
}

View file

@ -1,10 +0,0 @@
use serde::Deserialize;
template_id!(HollowConfig u32 id);
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct HollowConfigTemplate {
#[serde(rename = "ID")]
pub id: HollowConfigID,
}

View file

@ -1,13 +0,0 @@
use serde::Deserialize;
template_id!(HollowQuest u32 id);
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct HollowQuestTemplate {
#[serde(rename = "ID")]
pub id: HollowQuestID,
#[serde(rename = "ChessBoardID")]
pub chess_board_id: u32,
pub hollow_quest_type: u32,
}

View file

@ -1,10 +0,0 @@
use serde::Deserialize;
template_id!(Item u32 id);
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct ItemTemplate {
#[serde(rename = "ID")]
pub id: ItemID,
}

View file

@ -87,15 +87,4 @@ template_tables! {
MainCityBgmConfigTemplate;
ArchiveFileQuestTemplate;
ArchiveBattleQuestTemplate;
HollowQuestTemplate;
HollowConfigTemplate;
BattleEventConfigTemplate;
BattleGroupConfigTemplate;
SubAreaDataTemplate;
VariableDataTemplate;
OnceRewardTemplate;
QuickAccessTemplate;
QuickFuncTemplate;
TeleportConfigTemplate;
ItemTemplate;
}

View file

@ -1,19 +0,0 @@
use serde::Deserialize;
template_id!(OnceReward u32 reward_id);
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct OnceRewardTemplate {
#[serde(rename = "RewardID")]
pub reward_id: OnceRewardID,
pub reward_list: Vec<RewardItem>,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct RewardItem {
#[serde(rename = "RewardItemID")]
pub reward_item_id: u32,
pub reward_amount: u32,
}

View file

@ -1,11 +0,0 @@
use serde::Deserialize;
template_id!(QuickAccess u32 quick_func_id);
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct QuickAccessTemplate {
pub quick_access_index: u32,
#[serde(rename = "QuickFuncID")]
pub quick_func_id: QuickAccessID,
}

View file

@ -1,10 +0,0 @@
use serde::Deserialize;
template_id!(QuickFunc u32 btn_id);
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct QuickFuncTemplate {
#[serde(rename = "BtnID")]
pub btn_id: QuickFuncID,
}

View file

@ -1,16 +0,0 @@
use std::u32;
use serde::Deserialize;
template_id!(SubAreaData u32 area_id);
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct SubAreaDataTemplate {
#[serde(rename = "AreaID")]
pub area_id: SubAreaDataID,
#[serde(rename = "BattleEventID")]
pub battle_event_id: u32,
pub time_period: String,
pub weather: String,
}

View file

@ -1,11 +0,0 @@
use serde::Deserialize;
template_id!(TeleportConfig i32 teleport_id);
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct TeleportConfigTemplate {
#[serde(rename = "TeleportID")]
pub teleport_id: TeleportConfigID,
pub client_visible: u32,
}

View file

@ -1,7 +1,5 @@
use serde::Deserialize;
use super::BattleEventConfigID;
template_id!(TrainingQuest u32 id);
#[derive(Deserialize, Debug)]
@ -9,5 +7,5 @@ template_id!(TrainingQuest u32 id);
pub struct TrainingQuestTemplate {
pub id: TrainingQuestID,
pub training_type: u32,
pub battle_event_id: BattleEventConfigID,
pub battle_event_id: u32,
}

View file

@ -1,25 +0,0 @@
use std::u32;
use serde::Deserialize;
template_id!(VariableData u32 id);
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct VariableDataTemplate {
#[serde(rename = "ID")]
pub id: VariableDataID,
#[serde(rename = "BattleEventID")]
pub battle_event_id: u32,
pub variable_name: String,
pub value_type: VariableValueType,
pub initial_value: String,
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum VariableValueType {
Int,
Bool,
Float,
}

View file

@ -15,7 +15,6 @@ rbase64.workspace = true
toml.workspace = true
serde.workspace = true
serde_json.workspace = true
chrono.workspace = true
# Database
sqlx.workspace = true
@ -42,3 +41,4 @@ tracing-bunyan-formatter.workspace = true
common.workspace = true
data.workspace = true
proto.workspace = true
chrono = { version = "0.4.38", features = ["serde"] }

View file

@ -2,7 +2,7 @@ use crate::logic::{EOperator, ESystem};
use super::*;
use data::tables::{self, QuickFuncID};
use data::tables;
pub async fn on_get_tips_info(
_session: &NetSession,
@ -21,34 +21,23 @@ pub async fn on_get_client_systems_info(
player: &mut Player,
_req: GetClientSystemsInfoCsReq,
) -> NetResult<GetClientSystemsInfoScRsp> {
let mut post_girl_data = PostGirlData {
post_girl_list: tables::post_girl_config_template_tb::iter()
.map(|template| PostGirlItem {
template_id: template.id.value(),
unlock_time: 1720052644,
})
.collect(),
..Default::default()
};
if let Some(post_girl_id) = player.basic_data_model.selected_post_girl_id {
post_girl_data
.selected_post_girl_id_list
.push(post_girl_id.value());
}
Ok(GetClientSystemsInfoScRsp {
retcode: Retcode::RetSucc.into(),
info: Some(ClientSystemsInfo {
post_girl_data: Some(post_girl_data),
unlock_data: Some(player.lock_model.to_client()),
calling_card_data: Some(CallingCardData::default()),
teleport_data: Some(TeleportData {
unlock_id_list: tables::teleport_config_template_tb::iter()
.filter(|template| template.client_visible > 0)
.map(|template| template.teleport_id.value())
post_girl_data: Some(PostGirlData {
selected_post_girl_id_list: tables::post_girl_config_template_tb::iter()
.map(|template| template.id.value())
.collect(),
post_girl_list: tables::post_girl_config_template_tb::iter()
.map(|template| PostGirlItem {
template_id: template.id.value(),
unlock_time: 1000,
})
.collect(),
..Default::default()
}),
unlock_data: Some(player.lock_model.to_client()),
hbhfjgbahgf: Some(Aboegnnepmi::default()),
..Default::default()
}),
})
@ -65,16 +54,6 @@ pub async fn on_get_news_stand_data(
})
}
pub async fn on_news_stand_seen(
_session: &NetSession,
_player: &mut Player,
_req: NewsStandSeenCsReq,
) -> NetResult<NewsStandSeenScRsp> {
Ok(NewsStandSeenScRsp {
retcode: Retcode::RetSucc.into(),
})
}
pub async fn on_get_trashbin_hermit_data(
_session: &NetSession,
_player: &mut Player,
@ -194,70 +173,3 @@ pub async fn on_interact_with_scene_object(
retcode: Retcode::RetSucc.into(),
})
}
pub async fn on_mod_quick_menu(
_session: &NetSession,
_player: &mut Player,
_req: ModQuickMenuCsReq,
) -> NetResult<ModQuickMenuScRsp> {
let mut quick_access_data_list: Vec<QuickAccessData> = vec![];
for data in _req.quick_access_data_list.iter() {
quick_access_data_list.push(
_player
.lock_model
.mod_quick_access(data.quick_access_index, QuickFuncID::new(data.btn_id)),
);
}
_session
.notify(PlayerSyncScNotify {
client_systems_sync: Some(ClientSystemsSync {
quick_access_data_list: _player.lock_model.quick_access_to_client(),
..Default::default()
}),
..Default::default()
})
.await?;
Ok(ModQuickMenuScRsp {
retcode: Retcode::RetSucc.into(),
..Default::default()
})
}
pub async fn on_change_post_girl(
_session: &NetSession,
_player: &mut Player,
_req: ChangePostGirlCsReq,
) -> NetResult<ChangePostGirlScRsp> {
if _req.new_selected_post_girl_id_list.len() != 1 {
return Ok(ChangePostGirlScRsp {
retcode: Retcode::RetFail.into(),
..Default::default()
});
};
match tables::PostGirlConfigID::new(*_req.new_selected_post_girl_id_list.get(0).unwrap()) {
Some(post_girl_id) => {
_player.basic_data_model.selected_post_girl_id = Some(post_girl_id);
_session
.notify(PlayerSyncScNotify {
client_systems_sync: Some(ClientSystemsSync {
post_girl_data: Some(PostGirlSync {
selected_post_girl_id_list: vec![post_girl_id.value()],
..Default::default()
}),
..Default::default()
}),
..Default::default()
})
.await?;
Ok(ChangePostGirlScRsp {
retcode: Retcode::RetSucc.into(),
..Default::default()
})
}
None => Ok(ChangePostGirlScRsp {
retcode: Retcode::RetFail.into(),
..Default::default()
}),
}
}

View file

@ -1,18 +1,19 @@
use data::{
gacha::{
gacha_config::{CharacterGachaPool, GachaAddedItemType},
global_gacha_config,
use std::{
cmp::min,
collections::{
hash_map::Entry::{Occupied, Vacant},
HashSet,
},
tables::{AvatarBaseID, ItemID, WeaponID},
};
use proto::{GainItemInfo, GetGachaDataScRsp};
use data::{
gacha::{gacha_config::*, global_gacha_config},
tables::{AvatarBaseID, WeaponID},
};
use super::*;
use crate::{
handlers::core::NetError,
logic::{item::ItemModel, role::RoleModel},
};
use chrono::{DateTime, Local};
use proto::*;
pub async fn on_get_gacha_data(
_session: &NetSession,
@ -31,7 +32,7 @@ pub async fn on_get_gacha_data(
Ok(GetGachaDataScRsp {
retcode: Retcode::RetSucc.into(),
gacha_type: req.gacha_type,
gacha_data: Some(_player.gacha_model.to_client(&Local::now())),
gacha_data: Some(generate_all_gacha_info(_player, &Local::now())),
})
}
}
@ -50,58 +51,101 @@ pub async fn on_do_gacha(
&gachaconf.character_gacha_pool_list,
&req.gacha_parent_schedule_id,
&pull_time,
)?;
);
if let None = target_pool {
tracing::info!(
"refuse gacha because: pool of parent_schedule_id {} not found",
req.gacha_parent_schedule_id
);
return Ok(DoGachaScRsp {
retcode: Retcode::RetFail.into(),
..Default::default()
});
};
let target_pool = target_pool.unwrap();
// tracing::info!("cost_item_count: {}", req.cost_item_count);
let pull_count = if req.cost_item_count > 1 { 10 } else { 1 };
let mut cost_count = gacha_model.get_actual_cost_count(target_pool, &pull_count);
if pull_count != req.cost_item_count {
let mut pull_count = if req.cost_item_count > 1 { 10 } else { 1 };
let mut cost_count = pull_count;
if pull_count == 10 {
let discount_tag = &gachaconf.common_properties.ten_pull_discount_tag;
if target_pool.discount_policy_tags.contains(&discount_tag) {
let gacha_bin = &mut gacha_model.gacha_bin;
let status_bin = gacha_bin
.gacha_status_map
.get_mut(&target_pool.sharing_guarantee_info_category)
.unwrap();
let discount_policy = gachaconf
.discount_policies
.ten_pull_discount_map
.get(discount_tag)
.unwrap();
let usage = status_bin.discount_usage_map.get_mut(discount_tag).unwrap();
if *usage < discount_policy.use_limit {
*usage += 1;
// cost_count = discount_policy.discounted_prize;
}
}
}
if cost_count != req.cost_item_count {
tracing::info!(
"refuse gacha because: expected cost item {cost_count}, found {}",
req.cost_item_count
);
return Err(NetError::from(Retcode::RetFail));
return Ok(DoGachaScRsp {
retcode: Retcode::RetFail.into(),
..Default::default()
});
} else {
// TODO: cost resource
}
let mut gain_item_list: Vec<GainItemInfo> = vec![];
while cost_count > 0 {
while pull_count > 0 {
let pull_result = gacha_model.perform_pull_pool(&pull_time, target_pool);
let uid = add_item(
role_model,
item_model,
&pull_result.obtained_item_id,
&pull_result.item_type,
)?;
let (mut extra_item_id, mut extra_item_count) = (0, 0);
if let Some(extra_resources) = &pull_result.extra_resources {
(extra_item_id, extra_item_count) = (
extra_resources.extra_item_id.value(),
extra_resources.extra_item_count.clone(),
let extra_item_bin = pull_result.extra_item_bin.clone().unwrap();
let uid = match GachaAddedItemType::try_from(pull_result.item_type) {
Ok(enum_val) => match enum_val {
GachaAddedItemType::Weapon => match WeaponID::new(pull_result.obtained_item_id) {
Some(id) => item_model.add_weapon(id).value(),
None => 0,
},
GachaAddedItemType::Character => {
match AvatarBaseID::new(pull_result.obtained_item_id) {
Some(id) => {
role_model.add_avatar(id);
0
}
None => 0,
}
}
_ => 0,
},
Err(_err) => 0,
};
if extra_item_bin.extra_item_id != 0 {
item_model.add_resource(
extra_item_bin.extra_item_id,
extra_item_bin.extra_item_count,
);
item_model.add_resource(extra_item_id, extra_item_count);
}
gain_item_list.push(GainItemInfo {
item_id: pull_result.obtained_item_id.value(),
extra_item_id,
extra_item_count,
item_id: pull_result.obtained_item_id,
extra_item_id: extra_item_bin.extra_item_id,
extra_item_count: extra_item_bin.extra_item_count,
uid,
num: 1,
..GainItemInfo::default()
});
cost_count -= 1;
gacha_model.gacha_records.push(pull_result);
pull_count -= 1;
gacha_model.gacha_bin.gacha_records.push(pull_result);
}
_session
.notify(construct_sync(role_model, item_model))
.await?;
Ok(DoGachaScRsp {
retcode: Retcode::RetSucc.into(),
gain_item_list,
gacha_data: Some(gacha_model.to_client(&pull_time)),
gacha_data: Some(generate_all_gacha_info(_player, &pull_time)),
cost_item_count: req.cost_item_count,
})
}
@ -114,29 +158,122 @@ pub async fn on_gacha_free_agent(
let gachaconf = global_gacha_config();
let gacha_model = &mut _player.gacha_model;
let role_model = &mut _player.role_model;
let item_model = &mut _player.item_model;
let pull_time = Local::now();
let target_pool = get_gacha_pool(
&gachaconf.character_gacha_pool_list,
&req.gacha_parent_schedule_id,
&pull_time,
)?;
);
if let None = target_pool {
tracing::info!(
"refuse free agent because: pool of parent_schedule_id {} not found",
req.gacha_parent_schedule_id
);
return Ok(GachaFreeAgentScRsp {
retcode: Retcode::RetFail.into(),
..Default::default()
});
};
let target_pool = target_pool.unwrap();
let gacha_bin = &mut _player.gacha_model.gacha_bin;
let sharing_guarantee_category_tag = &target_pool.sharing_guarantee_info_category;
let status_bin = gacha_bin
.gacha_status_map
.get_mut(sharing_guarantee_category_tag)
.unwrap();
let item_id = ItemID::new(req.avatar_id);
if let None = item_id {
return Err(NetError::from(Retcode::RetFail));
let mut free_select_policy: Option<&FreeSelectItem> = None;
let mut free_select_progress: u32 = 0;
let mut free_select_required_pull: u32 = 0;
for discount_policy_tag in target_pool.discount_policy_tags.iter() {
if gachaconf
.discount_policies
.free_select_map
.contains_key(discount_policy_tag)
{
let policy = gachaconf
.discount_policies
.free_select_map
.get(discount_policy_tag)
.unwrap();
let free_select_demand_idx = usize::try_from(
*(status_bin
.discount_usage_map
.get(&policy.free_select_usage_record_tag)
.unwrap()),
)
.unwrap();
if policy.milestones.len() <= free_select_demand_idx {
continue;
}
let free_select_actual_progress = status_bin
.discount_usage_map
.get(&policy.free_select_progress_record_tag)
.unwrap();
free_select_policy = Some(policy);
free_select_required_pull = policy
.milestones
.get(free_select_demand_idx)
.unwrap()
.to_owned();
free_select_progress = min(free_select_required_pull, *free_select_actual_progress);
}
}
let item_id = item_id.unwrap();
let item_type = gacha_model.request_free_agent(target_pool, &item_id);
if item_type == GachaAddedItemType::None {
return Err(NetError::from(Retcode::RetFail));
if let None = free_select_policy {
tracing::info!(
"refuse free agent because: pool of parent_schedule_id {} hasn't defined free agent discount yet (or used up chance)",
req.gacha_parent_schedule_id
);
return Ok(GachaFreeAgentScRsp {
retcode: Retcode::RetFail.into(),
..Default::default()
});
} else if free_select_progress < free_select_required_pull {
tracing::info!(
"refuse free agent because: use pulled {free_select_progress} (after last free agent) in parent_schedule_id {}, required {free_select_required_pull}",
req.gacha_parent_schedule_id
);
return Ok(GachaFreeAgentScRsp {
retcode: Retcode::RetFail.into(),
..Default::default()
});
}
let free_select_policy = free_select_policy.unwrap();
let mut has_demanded_item = false;
for rarity_items in target_pool.gacha_items.iter() {
if rarity_items.rarity != free_select_policy.rarity {
continue;
}
for (category_tag, category) in rarity_items.categories.iter() {
if !free_select_policy.category_tags.contains(category_tag) {
continue;
}
has_demanded_item |= category.item_ids.contains(&req.avatar_id);
}
}
if !has_demanded_item {
tracing::info!(
"refuse free agent because: pool of parent_schedule_id {} doesn't have demanded item {}",
req.gacha_parent_schedule_id, req.avatar_id
);
return Ok(GachaFreeAgentScRsp {
retcode: Retcode::RetFail.into(),
..Default::default()
});
}
let _ = add_item(role_model, item_model, &item_id, &item_type);
_session
.notify(construct_sync(role_model, item_model))
.await?;
role_model.add_avatar(AvatarBaseID::new(req.avatar_id).unwrap());
(*status_bin
.discount_usage_map
.get_mut(&free_select_policy.free_select_usage_record_tag)
.unwrap()) += 1;
(*status_bin
.discount_usage_map
.get_mut(&free_select_policy.free_select_progress_record_tag)
.unwrap()) -= free_select_required_pull;
Ok(GachaFreeAgentScRsp {
retcode: Retcode::RetSucc.into(),
})
@ -154,82 +291,330 @@ pub async fn on_choose_gacha_up(
&gachaconf.character_gacha_pool_list,
&req.gacha_parent_schedule_id,
&pull_time,
)?;
);
if let None = target_pool {
return Ok(ChooseGachaUpScRsp {
retcode: Retcode::RetFail.into(),
..Default::default()
});
};
let target_pool = target_pool.unwrap();
let item_id = ItemID::new(req.item_id);
if let None = item_id {
return Err(NetError::from(Retcode::RetFail));
for rarity_items in target_pool.gacha_items.iter() {
if rarity_items.rarity != gachaconf.common_properties.s_item_rarity {
continue;
}
for guarantee_policy_tag in rarity_items.category_guarantee_policy_tags.iter() {
let category_guarantee_policy = gachaconf
.category_guarantee_policy_map
.get(guarantee_policy_tag)
.unwrap();
if !category_guarantee_policy.chooseable {
continue;
}
let mut up_category: Option<&String> = None;
for (category_tag, category) in rarity_items.categories.iter() {
if category.item_ids.contains(&req.item_id) {
up_category = Some(category_tag);
break;
}
}
if let None = up_category {
return Ok(ChooseGachaUpScRsp {
retcode: Retcode::RetFail.into(),
..Default::default()
});
};
let up_category = up_category.unwrap();
let progress_bin = gacha_model
.gacha_bin
.gacha_status_map
.get_mut(&target_pool.sharing_guarantee_info_category)
.unwrap()
.rarity_status_map
.get_mut(&rarity_items.rarity)
.unwrap();
match progress_bin
.categories_chosen_guarantee_item_map
.entry(guarantee_policy_tag.clone())
{
Occupied(mut occupied_entry) => {
occupied_entry.insert(req.item_id);
}
Vacant(vacant_entry) => {
vacant_entry.insert(req.item_id);
}
};
match progress_bin
.categories_chosen_guarantee_category_map
.entry(up_category.clone())
{
Occupied(mut occupied_entry) => {
occupied_entry.insert(up_category.clone());
}
Vacant(vacant_entry) => {
vacant_entry.insert(up_category.clone());
}
};
return Ok(ChooseGachaUpScRsp {
retcode: Retcode::RetSucc.into(),
..Default::default()
});
}
}
let item_id = item_id.unwrap();
Ok(ChooseGachaUpScRsp {
retcode: if gacha_model.choose_gacha_up(target_pool, &item_id) {
Retcode::RetSucc.into()
} else {
Retcode::RetFail.into()
},
retcode: Retcode::RetFail.into(),
..Default::default()
})
}
fn generate_gacha_info_from_pool(
gacha_bin: &GachaModelBin,
target_pool: &CharacterGachaPool,
common_properties: &GachaCommonProperties,
) -> Gacha {
let gachaconf = data::gacha::global_gacha_config();
let sharing_guarantee_category_tag = &target_pool.sharing_guarantee_info_category;
let status_bin = gacha_bin
.gacha_status_map
.get(sharing_guarantee_category_tag)
.unwrap();
let pity_s = status_bin
.rarity_status_map
.get(&common_properties.s_item_rarity)
.unwrap()
.pity;
let pity_a = status_bin
.rarity_status_map
.get(&common_properties.a_item_rarity)
.unwrap()
.pity;
let mut discount_ten_roll_prize: u32 = 0;
let mut discount_avaliable_num: u32 = 0;
let mut advanced_s_guarantee: u32 = 0;
let mut free_select_progress: u32 = 0;
let mut free_select_required_pull: u32 = 0;
let mut free_select_policy: Option<&FreeSelectItem> = None;
for discount_policy_tag in target_pool.discount_policy_tags.iter() {
if common_properties.newcomer_advanced_s_tag == *discount_policy_tag {
let policy = gachaconf
.discount_policies
.advanced_guarantee_map
.get(discount_policy_tag)
.unwrap();
if status_bin
.discount_usage_map
.get(discount_policy_tag)
.unwrap()
< &policy.use_limit
{
advanced_s_guarantee = policy.guarantee_pity - pity_s + 1;
}
} else if common_properties.ten_pull_discount_tag == *discount_policy_tag {
let policy = gachaconf
.discount_policies
.ten_pull_discount_map
.get(discount_policy_tag)
.unwrap();
let discount_usage = status_bin
.discount_usage_map
.get(discount_policy_tag)
.unwrap();
if discount_usage < &policy.use_limit {
discount_ten_roll_prize = policy.discounted_prize;
discount_avaliable_num = policy.use_limit - discount_usage;
}
} else if gachaconf
.discount_policies
.free_select_map
.contains_key(discount_policy_tag)
{
let policy = gachaconf
.discount_policies
.free_select_map
.get(discount_policy_tag)
.unwrap();
let free_select_demand_idx = usize::try_from(
*(status_bin
.discount_usage_map
.get(&policy.free_select_usage_record_tag)
.unwrap()),
)
.unwrap();
if policy.milestones.len() <= free_select_demand_idx {
continue;
}
let free_select_actual_progress = status_bin
.discount_usage_map
.get(&policy.free_select_progress_record_tag)
.unwrap();
free_select_policy = Some(policy);
free_select_required_pull = policy
.milestones
.get(free_select_demand_idx)
.unwrap()
.to_owned();
free_select_progress = min(free_select_required_pull, *free_select_actual_progress);
}
}
let mut up_s_item_list: Vec<u32> = vec![];
let mut up_a_item_list: Vec<u32> = vec![];
let mut free_select_item_list: Vec<u32> = vec![];
let mut chooseable_up_list: Vec<u32> = vec![];
let mut chosen_up_item: u32 = 0;
let mut s_guarantee: u32 = 0;
let mut a_guarantee: u32 = 0;
for rarity_items in target_pool.gacha_items.iter() {
let mut chooseable_up_included_category_tags: Option<&HashSet<String>> = None;
let mut chooseable_policy_tag: Option<&String> = None;
for guarantee_policy_tag in rarity_items.category_guarantee_policy_tags.iter() {
let category_guarantee_policy = gachaconf
.category_guarantee_policy_map
.get(guarantee_policy_tag)
.unwrap();
if !category_guarantee_policy.chooseable {
continue;
}
chooseable_policy_tag = Some(guarantee_policy_tag);
chooseable_up_included_category_tags =
Some(&category_guarantee_policy.included_category_tags);
if let Some(item) = status_bin
.rarity_status_map
.get(&rarity_items.rarity)
.unwrap()
.categories_chosen_guarantee_item_map
.get(guarantee_policy_tag)
{
chosen_up_item = item.clone();
}
}
for (category_tag, category) in rarity_items.categories.iter() {
let probability_model = gachaconf
.probability_model_map
.get(&rarity_items.probability_model_tag)
.unwrap();
let maximum_pity = &probability_model.maximum_guarantee_pity;
if rarity_items.rarity == common_properties.s_item_rarity {
if category.is_promotional_items {
up_s_item_list = category.item_ids.clone();
}
// tracing::info!("pity_s: {pity_s}");
// thread 'tokio-runtime-worker' panicked at nap_gameserver\src\handlers\gacha.rs:369:31:
// attempt to subtract with overflow
s_guarantee = maximum_pity - min(pity_s, maximum_pity.clone()) + 1;
}
if rarity_items.rarity == common_properties.a_item_rarity {
if category.is_promotional_items {
up_a_item_list = category.item_ids.clone();
}
// tracing::info!("pity_a: {pity_a}");
a_guarantee = maximum_pity - min(pity_a, maximum_pity.clone()) + 1;
}
if let Some(val) = free_select_policy {
if val.rarity == rarity_items.rarity && val.category_tags.contains(category_tag) {
free_select_item_list.append(&mut category.item_ids.clone());
}
}
if let Some(tags) = chooseable_up_included_category_tags {
if tags.contains(category_tag) {
chooseable_up_list.append(&mut category.item_ids.clone());
}
}
}
if let Some(priority_policy_tag) = chooseable_policy_tag {
// if let Some(item) = status_bin
// .rarity_status_map
// .get(&rarity_items.rarity)
// .unwrap()
// .categories_chosen_guarantee_item_map
// .get(priority_policy_tag)
// {
if rarity_items.rarity == gachaconf.common_properties.s_item_rarity {
up_s_item_list = vec![];
} else if rarity_items.rarity == gachaconf.common_properties.a_item_rarity {
up_a_item_list = vec![];
}
// }
}
}
let need_item_info_list: Vec<NeedItemInfo> = vec![NeedItemInfo {
need_item_id: target_pool.cost_item_id,
need_item_count: 1,
}];
let mut result = Gacha {
gacha_schedule_id: target_pool.gacha_schedule_id,
gacha_parent_schedule_id: target_pool.gacha_parent_schedule_id,
gacha_type: target_pool.gacha_type,
start_timestamp: target_pool.start_time.timestamp(),
end_timestamp: target_pool.end_time.timestamp(),
discount_avaliable_num,
discount_ten_roll_prize,
advanced_s_guarantee,
s_guarantee,
a_guarantee,
need_item_info_list,
free_select_progress,
free_select_required_pull,
free_select_item_list,
chosen_up_item,
// nammdglepbk: 563,
// hgmcofcjmbg: 101,
// akggbhgkifd: chooseable_up_list.clone(),
chooseable_up_list,
..Gacha::default()
};
if up_s_item_list.len() > 0 {
result.up_s_item_list = up_s_item_list;
}
if up_a_item_list.len() > 0 {
result.up_a_item_list = up_a_item_list;
}
result
}
fn generate_all_gacha_info(_player: &Player, now: &DateTime<Local>) -> GachaData {
let gachaconf = global_gacha_config();
let gacha_bin = &_player.gacha_model.gacha_bin;
let mut gacha_list: Vec<Gacha> = vec![];
for target_pool in gachaconf.character_gacha_pool_list.iter() {
if target_pool.is_still_open(now) {
gacha_list.push(generate_gacha_info_from_pool(
&gacha_bin,
target_pool,
&gachaconf.common_properties,
));
}
}
// tracing::info!("gacha_list: {:?}", gacha_list);
GachaData {
random_number: 6167,
gacha_pool: Some(GachaPool { gacha_list }),
..GachaData::default()
}
}
fn get_gacha_pool<'conf>(
character_gacha_pool_list: &'conf Vec<CharacterGachaPool>,
gacha_parent_schedule_id: &u32,
pull_time: &DateTime<Local>,
) -> NetResult<&'conf CharacterGachaPool> {
) -> Option<&'conf CharacterGachaPool> {
for target_pool in character_gacha_pool_list.iter() {
if &target_pool.gacha_parent_schedule_id == gacha_parent_schedule_id
&& target_pool.is_still_open(pull_time)
{
return Ok(target_pool);
return Some(target_pool);
}
}
tracing::info!(
"refuse gacha op because: pool of parent_schedule_id {} not found or isn't in open time",
gacha_parent_schedule_id
);
Err(NetError::from(Retcode::RetFail))
}
/// Return is item UID (weapon specific)
fn add_item(
role_model: &mut RoleModel,
item_model: &mut ItemModel,
item_id: &ItemID,
item_type: &GachaAddedItemType,
) -> NetResult<u32> {
match item_type {
GachaAddedItemType::Character => match AvatarBaseID::new(item_id.value()) {
Some(avatar_id) => {
role_model.add_avatar(avatar_id);
Ok(0)
}
None => {
tracing::info!("add item failed for avatar id {item_id}");
Err(NetError::from(Retcode::RetFail))
}
},
GachaAddedItemType::Weapon => match WeaponID::new(item_id.value()) {
Some(weapon_id) => Ok(item_model.add_weapon(weapon_id).value()),
None => {
tracing::info!("add item failed for weapon id {item_id}");
Err(NetError::from(Retcode::RetFail))
}
},
GachaAddedItemType::Bangboo => Ok(0),
_ => {
tracing::info!(
"add item failed due to undefined item type (from {item_id}) in configuration"
);
Err(NetError::from(Retcode::RetFail))
}
}
}
fn construct_sync(role_model: &RoleModel, item_model: &ItemModel) -> PlayerSyncScNotify {
PlayerSyncScNotify {
avatar: Some(role_model.avatar_sync()),
item_sync: Some(item_model.item_sync()),
..Default::default()
}
None
}

View file

@ -98,10 +98,7 @@ req_handlers! {
world::LeaveCurDungeon;
world::InteractWithUnit;
world::EnterSection;
world::JumpPageSystem;
world::StartHollowQuest;
world::FinishHollowBattleEvent;
world::LongFightProgressUpdate;
world::ArchiveQuestsSeen;
client_systems::ReportUiLayoutPlatform;
client_systems::PlayerOperation;
client_systems::UnlockNewbieGroup;
@ -110,7 +107,6 @@ req_handlers! {
client_systems::ReportSystemSettingsChange;
client_systems::InteractWithSceneObject;
client_systems::PlayerSystemParameterChange;
client_systems::NewsStandSeen;
perform::PerformTrigger;
perform::PerformEnd;
perform::PerformJump;
@ -126,9 +122,6 @@ req_handlers! {
gacha::DoGacha;
gacha::ChooseGachaUp;
gacha::GachaFreeAgent;
player::ModNickname;
client_systems::ModQuickMenu;
client_systems::ChangePostGirl;
}
notify_handlers! {

View file

@ -58,25 +58,6 @@ pub async fn on_get_player_transaction(
})
}
pub async fn on_mod_nickname(
session: &NetSession,
_player: &mut Player,
_req: ModNicknameCsReq,
) -> NetResult<ModNicknameScRsp> {
_player.basic_data_model.nick_name = Some(_req.nick_name.to_string());
session
.notify(PlayerSyncScNotify {
basic_info: Some(_player.basic_data_model.player_basic_info()),
..Default::default()
})
.await?;
Ok(ModNicknameScRsp {
retcode: Retcode::RetSucc.into(),
..Default::default()
})
}
pub async fn on_keep_alive(
_session: &NetSession,
_player: &mut Player,

View file

@ -16,22 +16,13 @@ pub async fn on_get_quest_data(
retcode: Retcode::RetSucc.into(),
quest_type: req.quest_type,
quest_data: Some(QuestData {
quest_collection_list: vec![
QuestCollection {
quest_type: EQuestType::ArchiveFile as u32,
quest_id_list: tables::archive_file_quest_template_tb::iter()
.map(|tmpl| tmpl.id.value())
.collect(),
..Default::default()
},
QuestCollection {
quest_type: EQuestType::Hollow as u32,
quest_id_list: tables::hollow_quest_template_tb::iter()
.map(|tmpl| tmpl.id.value())
.collect(),
..Default::default()
},
],
quest_collection_list: vec![QuestCollection {
quest_type: EQuestType::ArchiveFile as u32,
quest_id_list: tables::archive_file_quest_template_tb::iter()
.map(|tmpl| tmpl.id.value())
.collect(),
..Default::default()
}],
}),
})
}
@ -44,19 +35,22 @@ pub async fn on_get_yorozuya_info(
Ok(GetYorozuyaInfoScRsp {
retcode: Retcode::RetSucc.into(),
yorozuya_info: Some(YorozuyaInfo {
odohdljfdlf: vec![1000, 1001, 1002, 1003, 1004],
apmojjlcooa: vec![1000, 1001, 1002, 1003, 1004],
akiddbalfoa: vec![10010001, 10010002, 10010004],
npgjhahijkb: tables::hollow_config_template_tb::iter()
.map(|tmpl| tmpl.id.value())
.collect(),
eoljpdnjgeg: tables::hollow_quest_template_tb::iter()
.map(|tmpl| Ofhlkjeakif {
nnkcanmllod: tmpl.id.value(),
odohdljfdlf: vec![1001],
apmojjlcooa: vec![1001],
npgjhahijkb: vec![100001, 100101, 100401, 109901],
eoljpdnjgeg: vec![
Ofhlkjeakif {
nnkcanmllod: 199030,
kkjlnkehddj: Some(Cgpajijemlj::default()),
..Default::default()
})
.collect(),
},
Ofhlkjeakif {
nnkcanmllod: 199035,
ggnbpiofdpp: 110103,
kkjlnkehddj: Some(Cgpajijemlj::default()),
..Default::default()
},
],
..Default::default()
}),
})

View file

@ -1,14 +1,12 @@
use data::{
event,
tables::{HollowQuestID, ProcedureConfigID, SectionConfigID, TrainingQuestID},
tables::{ProcedureConfigID, SectionConfigID, TrainingQuestID},
};
use super::core::NetError;
use crate::{
logic::{
game::*, procedure::ProcedureAction, EHollowQuestType, ELocalPlayType, ENPCInteraction,
},
logic::{game::*, procedure::ProcedureAction, ELocalPlayType, ENPCInteraction},
net::NetSessionState,
};
@ -184,42 +182,10 @@ pub async fn on_start_trial_fighting_mission(
}
pub async fn on_end_battle(
session: &NetSession,
player: &mut Player,
req: EndBattleCsReq,
_session: &NetSession,
_player: &mut Player,
_req: EndBattleCsReq,
) -> NetResult<EndBattleScRsp> {
match &mut player.game_instance {
GameInstance::Hollow(hollow) if hollow.quest_manager.has_active_quests() => {
hollow
.quest_manager
.finish_quest(hollow.battle_event_id.value())
.map_err(LogicError::from)?;
session
.notify(DungeonQuestFinishedScNotify {
result: req.battle_result.unwrap().result as u32,
quest_id: hollow.quest_id,
..Default::default()
})
.await?;
}
GameInstance::LongFight(fight) => {
fight
.quest_manager
.finish_quest(fight.battle_event_id.value())
.map_err(LogicError::from)?;
session
.notify(DungeonQuestFinishedScNotify {
result: req.battle_result.unwrap().result as u32,
quest_id: fight.quest_id,
..Default::default()
})
.await?;
}
_ => (),
};
Ok(EndBattleScRsp {
battle_reward: Some(BattleRewardInfo::default()),
retcode: Retcode::RetSucc.into(),
@ -293,79 +259,13 @@ pub async fn on_enter_section(
})
}
pub async fn on_jump_page_system(
pub async fn on_archive_quests_seen(
_session: &NetSession,
_player: &mut Player,
_req: JumpPageSystemCsReq,
) -> NetResult<JumpPageSystemScRsp> {
Ok(JumpPageSystemScRsp {
_req: ArchiveQuestsSeenCsReq,
) -> NetResult<ArchiveQuestsSeenScRsp> {
Ok(ArchiveQuestsSeenScRsp {
retcode: Retcode::RetSucc.into(),
..Default::default()
})
}
pub async fn on_start_hollow_quest(
session: &NetSession,
player: &mut Player,
req: StartHollowQuestCsReq,
) -> NetResult<StartHollowQuestScRsp> {
use crate::logic::{TimePeriodType, WeatherType};
let quest_id = HollowQuestID::new(req.quest_id).ok_or(Retcode::RetFail)?;
let quest_type = EHollowQuestType::from(quest_id.template().hollow_quest_type);
match quest_type {
EHollowQuestType::RallyBattle => {
player.game_instance = GameInstance::LongFight(
LongFightGame::create_rally_game(
quest_id,
&req.avatars,
req.buddy_id,
TimePeriodType::from_str(&req.quest_time_period),
WeatherType::from_str(&req.quest_weather),
)
.map_err(LogicError::from)?,
)
}
_ => {
player.game_instance = GameInstance::Hollow(
HollowGame::create_pure_hollow_battle(
quest_id,
&req.avatars,
req.buddy_id,
TimePeriodType::from_str(&req.quest_time_period),
WeatherType::from_str(&req.quest_weather),
)
.map_err(LogicError::from)?,
)
}
}
let world_init_notify = player.game_instance.create_world_init_notify()?;
session.notify(world_init_notify).await?;
Ok(StartHollowQuestScRsp {
retcode: Retcode::RetSucc.into(),
quest_id: 0,
})
}
pub async fn on_finish_hollow_battle_event(
_session: &NetSession,
_player: &mut Player,
_req: FinishHollowBattleEventCsReq,
) -> NetResult<FinishHollowBattleEventScRsp> {
Ok(FinishHollowBattleEventScRsp {
retcode: Retcode::RetSucc.into(),
})
}
pub async fn on_long_fight_progress_update(
_session: &NetSession,
_player: &mut Player,
_req: LongFightProgressUpdateCsReq,
) -> NetResult<LongFightProgressUpdateScRsp> {
Ok(LongFightProgressUpdateScRsp {
retcode: Retcode::RetSucc.into(),
})
}

View file

@ -1,16 +1,16 @@
use std::collections::HashMap;
use crate::logic::BaseProperty;
use proto::AvatarUnitInfo;
use proto::InLevelAvatarData;
pub struct AvatarUnit {
pub struct InLevelAvatarDataItem {
pub avatar_id: u32,
pub mp_property_override: HashMap<BaseProperty, i32>,
}
impl AvatarUnit {
pub fn to_client(&self) -> AvatarUnitInfo {
AvatarUnitInfo {
impl InLevelAvatarDataItem {
pub fn to_client(&self) -> InLevelAvatarData {
InLevelAvatarData {
avatar_id: self.avatar_id,
mp_property_override_map: self
.mp_property_override

View file

@ -1,18 +1,18 @@
use std::collections::HashMap;
use proto::BuddyUnitInfo;
use proto::EquippedBuddyData;
use crate::logic::{BaseProperty, BuddyTeamType};
pub struct BuddyUnit {
pub struct EquippedBuddyDataItem {
pub buddy_id: u32,
pub buddy_team: BuddyTeamType,
pub override_property_map: HashMap<BaseProperty, i32>,
}
impl BuddyUnit {
pub fn to_client(&self) -> BuddyUnitInfo {
BuddyUnitInfo {
impl EquippedBuddyDataItem {
pub fn to_client(&self) -> EquippedBuddyData {
EquippedBuddyData {
buddy_id: self.buddy_id,
r#type: self.buddy_team.to_protocol().into(),
mp_property_override_map: self

View file

@ -1,67 +0,0 @@
use std::collections::HashMap;
use data::tables::{BattleEventConfigID, OnceRewardID};
use proto::{FightDropInfo, FightReward, RewardItems};
pub struct FightDropPool {
special_drop: Vec<SpecialReward>,
}
pub struct SpecialReward {
pub reward_id: OnceRewardID,
pub item_count_map: HashMap<u32, u32>,
}
impl FightDropPool {
pub fn new(battle_event_id: BattleEventConfigID) -> Self {
let template = battle_event_id.template();
Self {
special_drop: template
.special_drop
.iter()
.map(|id| SpecialReward::new(*id))
.collect(),
}
}
pub fn to_client(&self) -> FightDropInfo {
FightDropInfo {
normal_drop_list: Vec::new(),
special_drop_list: self
.special_drop
.iter()
.map(SpecialReward::to_client)
.collect(),
..Default::default()
}
}
}
impl SpecialReward {
pub fn new(template_id: OnceRewardID) -> Self {
let template = template_id.template();
Self {
reward_id: template_id,
item_count_map: template
.reward_list
.iter()
.map(|r| (r.reward_item_id, r.reward_amount))
.collect(),
}
}
pub fn to_client(&self) -> FightReward {
FightReward {
reward_id: self.reward_id.value(),
fight_reward_map: HashMap::from([(
0,
RewardItems {
reward_item_map: self.item_count_map.clone(),
},
)]),
..Default::default()
}
}
}

View file

@ -1,2 +0,0 @@
mod fight_drop;
pub use fight_drop::FightDropPool;

View file

@ -1,9 +1,7 @@
pub mod drop;
mod quest;
mod avatar;
mod buddy;
mod team;
pub mod unit;
mod variable;
pub use quest::{DungeonQuestError, DungeonQuestManager};
pub use team::{BuddyParam, TeamDataItem};
pub use variable::LogicVariableTable;
pub use avatar::InLevelAvatarDataItem;
pub use buddy::EquippedBuddyDataItem;
pub use team::TeamDataItem;

View file

@ -1,44 +0,0 @@
use data::tables::BattleGroupConfigID;
use proto::DungeonQuestInfo;
use thiserror::Error;
#[derive(Default)]
pub struct DungeonQuestManager {
inner_quests: Vec<u32>,
}
#[derive(Error, Debug)]
pub enum DungeonQuestError {
#[error("dungeon inner quest with id {0} is not active")]
QuestNotActive(u32),
}
impl DungeonQuestManager {
pub fn new_for_battle_group(battle_group_id: BattleGroupConfigID) -> Self {
Self {
inner_quests: vec![battle_group_id.template().battle_event_id.value()],
}
}
pub fn finish_quest(&mut self, quest_id: u32) -> Result<(), DungeonQuestError> {
let idx = self
.inner_quests
.iter()
.position(|id| *id == quest_id)
.ok_or(DungeonQuestError::QuestNotActive(quest_id))?;
self.inner_quests.remove(idx);
Ok(())
}
pub fn has_active_quests(&self) -> bool {
!self.inner_quests.is_empty()
}
pub fn to_client(&self) -> DungeonQuestInfo {
DungeonQuestInfo {
inner_quest_id_list: self.inner_quests.clone(),
..Default::default()
}
}
}

View file

@ -2,33 +2,30 @@ use std::collections::HashMap;
use crate::logic::BuddyTeamType;
use super::unit::{AvatarUnit, BuddyUnit};
use super::{EquippedBuddyDataItem, InLevelAvatarDataItem};
pub struct TeamDataItem {
pub avatar_member_list: Vec<AvatarUnit>,
pub equipped_buddy_list: Vec<BuddyUnit>,
pub avatar_member_list: Vec<InLevelAvatarDataItem>,
pub equipped_buddy_list: Vec<EquippedBuddyDataItem>,
}
pub struct BuddyParam(pub u32, pub BuddyTeamType);
impl TeamDataItem {
pub fn new(avatars: &[u32], buddy_params: &[BuddyParam]) -> Self {
pub fn new(avatars: &[u32], buddy_id: u32) -> Self {
Self {
avatar_member_list: avatars
.iter()
.map(|id| AvatarUnit {
.map(|id| InLevelAvatarDataItem {
avatar_id: *id,
mp_property_override: HashMap::new(),
})
.collect(),
equipped_buddy_list: buddy_params
.iter()
.map(|param| BuddyUnit {
buddy_id: param.0,
buddy_team: param.1,
equipped_buddy_list: (buddy_id != 0)
.then_some(vec![EquippedBuddyDataItem {
buddy_id,
buddy_team: BuddyTeamType::Assisting,
override_property_map: HashMap::new(),
})
.collect(),
}])
.unwrap_or_default(),
}
}
}

View file

@ -1,5 +0,0 @@
mod avatar;
mod buddy;
pub use avatar::AvatarUnit;
pub use buddy::BuddyUnit;

View file

@ -1,74 +0,0 @@
use std::collections::HashMap;
use data::tables::{self, BattleEventConfigID, VariableDataID, VariableValueType};
use proto::FightVariable;
pub struct LogicVariableTable {
variable_map: HashMap<String, LogicVariable>,
}
impl LogicVariableTable {
pub fn new(battle_event_id: BattleEventConfigID) -> Self {
let event_id = battle_event_id.value();
Self {
variable_map: tables::variable_data_template_tb::iter()
.filter(|tmpl| tmpl.battle_event_id == event_id)
.map(|tmpl| (tmpl.variable_name.clone(), LogicVariable::new(tmpl.id)))
.collect(),
}
}
pub fn to_client(&self) -> HashMap<String, FightVariable> {
self.variable_map
.iter()
.map(|(name, var)| (name.clone(), var.to_client()))
.collect()
}
}
pub enum LogicVariable {
Int(i64),
Float(f64),
Bool(bool),
}
impl LogicVariable {
pub fn new(id: VariableDataID) -> Self {
let template = id.template();
match template.value_type {
VariableValueType::Int => Self::Int(template.initial_value.parse().unwrap_or_default()),
VariableValueType::Bool => Self::Bool(
template
.initial_value
.to_lowercase()
.parse()
.unwrap_or_default(),
),
VariableValueType::Float => {
Self::Float(template.initial_value.parse().unwrap_or_default())
}
}
}
pub fn to_client(&self) -> FightVariable {
match self {
Self::Int(val) => FightVariable {
r#type: 1,
int_value: *val,
..Default::default()
},
Self::Bool(val) => FightVariable {
r#type: 4,
int_value: *val as i64,
..Default::default()
},
Self::Float(val) => FightVariable {
r#type: 2,
float_value: *val,
..Default::default()
},
}
}
}

View file

@ -12,7 +12,6 @@ pub enum ESceneType {
Fight = 3,
Fresh = 4,
MultiFight = 5,
LongFight = 7,
}
#[allow(dead_code)]
@ -27,28 +26,6 @@ pub enum EQuestType {
HollowChallenge = 6,
ArchiveBattle = 7,
Knowledge = 8,
Daily = 9,
}
#[allow(dead_code)]
#[derive(Debug, PartialEq, Eq, Clone, Copy, FromPrimitive)]
#[repr(u32)]
pub enum EHollowQuestType {
ChallengeChaos = 6,
BossRushBattle = 14,
World = 8,
MainQuest = 1,
SideQuest = 2,
#[default]
Common = 0,
Challenge = 5,
PromoteBattle = 11,
DifficutyBattle = 12,
AvatarSide = 7,
RallyBattle = 13,
Urgent = 3,
UrgentSupplement = 4,
NormalBattle = 10,
}
#[derive(Debug, Default, PartialEq, Eq, Clone, Copy, FromPrimitive)]
@ -125,17 +102,6 @@ pub enum TimePeriodType {
Night = 2,
}
impl TimePeriodType {
pub fn from_str(s: &str) -> Self {
match s {
"Morning" => Self::Morning,
"Evening" => Self::Evening,
"Night" => Self::Night,
_ => Self::Morning,
}
}
}
impl Display for TimePeriodType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Debug::fmt(self, f)
@ -156,21 +122,6 @@ pub enum WeatherType {
None = 0xFFFFFFFF,
}
impl WeatherType {
pub fn from_str(s: &str) -> Self {
match s {
"SunShine" => Self::SunShine,
"Fog" => Self::Fog,
"Cloudy" => Self::Cloudy,
"Rain" => Self::Rain,
"Thunder" => Self::Thunder,
"ThickFog" => Self::ThickFog,
"ThickCloudy" => Self::ThickCloudy,
_ => Self::SunShine,
}
}
}
impl Display for WeatherType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Debug::fmt(self, f)
@ -313,7 +264,7 @@ pub enum BaseProperty {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
pub enum BuddyTeamType {
RallyGuidance = 0,
Unknown = 0,
Fighting = 1,
Assisting = 2,
}
@ -321,7 +272,7 @@ pub enum BuddyTeamType {
impl BuddyTeamType {
pub fn to_protocol(&self) -> ::proto::BuddyTeamType {
match *self {
Self::RallyGuidance => ::proto::BuddyTeamType::RallyGuidance,
Self::Unknown => ::proto::BuddyTeamType::Unknown,
Self::Fighting => ::proto::BuddyTeamType::Fighting,
Self::Assisting => ::proto::BuddyTeamType::Assisting,
}

View file

@ -1,440 +0,0 @@
use data::gacha::gacha_config::*;
use data::gacha::global_gacha_config;
use data::tables::ItemID;
use chrono::{DateTime, Local};
use proto::{Gacha, GachaData, GachaPool, NeedItemInfo};
use std::{
cmp::min,
collections::{
hash_map::Entry::{Occupied, Vacant},
HashSet,
},
};
use super::GachaModel;
impl GachaModel {
pub fn to_client(&self, now: &DateTime<Local>) -> GachaData {
let gachaconf = global_gacha_config();
let mut gacha_list: Vec<Gacha> = vec![];
for target_pool in gachaconf.character_gacha_pool_list.iter() {
if target_pool.is_still_open(now) {
gacha_list.push(
self.generate_gacha_info_from_pool(target_pool, &gachaconf.common_properties),
);
}
}
// tracing::info!("gacha_list: {:?}", gacha_list);
GachaData {
random_number: 6167,
gacha_pool: Some(GachaPool { gacha_list }),
..GachaData::default()
}
}
fn generate_gacha_info_from_pool(
&self,
target_pool: &CharacterGachaPool,
common_properties: &GachaCommonProperties,
) -> Gacha {
let gachaconf = data::gacha::global_gacha_config();
let sharing_guarantee_category_tag = &target_pool.sharing_guarantee_info_category;
let status_bin = self
.gacha_status_map
.get(sharing_guarantee_category_tag)
.unwrap();
let pity_s = status_bin
.rarity_status_map
.get(&common_properties.s_item_rarity)
.unwrap()
.pity;
let pity_a = status_bin
.rarity_status_map
.get(&common_properties.a_item_rarity)
.unwrap()
.pity;
let mut discount_ten_roll_prize: u32 = 0;
let mut discount_avaliable_num: u32 = 0;
let mut advanced_s_guarantee: u32 = 0;
let mut free_select_progress: u32 = 0;
let mut free_select_required_pull: u32 = 0;
let mut free_select_policy: Option<&FreeSelectItem> = None;
for discount_policy_tag in target_pool.discount_policy_tags.iter() {
if common_properties.newcomer_advanced_s_tag == *discount_policy_tag {
let policy = gachaconf
.discount_policies
.advanced_guarantee_map
.get(discount_policy_tag)
.unwrap();
if status_bin
.discount_usage_map
.get(discount_policy_tag)
.unwrap()
< &policy.use_limit
{
advanced_s_guarantee = policy.guarantee_pity - pity_s + 1;
}
} else if common_properties.ten_pull_discount_tag == *discount_policy_tag {
let policy = gachaconf
.discount_policies
.ten_pull_discount_map
.get(discount_policy_tag)
.unwrap();
let discount_usage = status_bin
.discount_usage_map
.get(discount_policy_tag)
.unwrap();
if discount_usage < &policy.use_limit {
discount_ten_roll_prize = policy.discounted_prize;
discount_avaliable_num = policy.use_limit - discount_usage;
}
} else if gachaconf
.discount_policies
.free_select_map
.contains_key(discount_policy_tag)
{
let policy = gachaconf
.discount_policies
.free_select_map
.get(discount_policy_tag)
.unwrap();
let free_select_demand_idx = usize::try_from(
*(status_bin
.discount_usage_map
.get(&policy.free_select_usage_record_tag)
.unwrap()),
)
.unwrap();
if policy.milestones.len() <= free_select_demand_idx {
continue;
}
let free_select_actual_progress = status_bin
.discount_usage_map
.get(&policy.free_select_progress_record_tag)
.unwrap();
free_select_policy = Some(policy);
free_select_required_pull = policy
.milestones
.get(free_select_demand_idx)
.unwrap()
.to_owned();
free_select_progress = min(free_select_required_pull, *free_select_actual_progress);
}
}
let mut up_s_item_list: Vec<u32> = vec![];
let mut up_a_item_list: Vec<u32> = vec![];
let mut free_select_item_list: Vec<u32> = vec![];
let mut chooseable_up_list: Vec<u32> = vec![];
let mut chosen_up_item: u32 = 0;
let mut s_guarantee: u32 = 0;
let mut a_guarantee: u32 = 0;
for rarity_items in target_pool.gacha_items.iter() {
let mut chooseable_up_included_category_tags: Option<&HashSet<String>> = None;
let mut chooseable_policy_tag: Option<&String> = None;
for guarantee_policy_tag in rarity_items.category_guarantee_policy_tags.iter() {
let category_guarantee_policy = gachaconf
.category_guarantee_policy_map
.get(guarantee_policy_tag)
.unwrap();
if !category_guarantee_policy.chooseable {
continue;
}
chooseable_policy_tag = Some(guarantee_policy_tag);
chooseable_up_included_category_tags =
Some(&category_guarantee_policy.included_category_tags);
if let Some(item) = status_bin
.rarity_status_map
.get(&rarity_items.rarity)
.unwrap()
.categories_chosen_guarantee_item_map
.get(guarantee_policy_tag)
{
chosen_up_item = item.clone();
}
}
for (category_tag, category) in rarity_items.categories.iter() {
let probability_model = gachaconf
.probability_model_map
.get(&rarity_items.probability_model_tag)
.unwrap();
let maximum_pity = &probability_model.maximum_guarantee_pity;
if rarity_items.rarity == common_properties.s_item_rarity {
if category.is_promotional_items {
up_s_item_list = category.item_ids.clone();
}
// tracing::info!("pity_s: {pity_s}");
// thread 'tokio-runtime-worker' panicked at nap_gameserver\src\handlers\gacha.rs:369:31:
// attempt to subtract with overflow
s_guarantee = maximum_pity - min(pity_s, maximum_pity.clone()) + 1;
}
if rarity_items.rarity == common_properties.a_item_rarity {
if category.is_promotional_items {
up_a_item_list = category.item_ids.clone();
}
// tracing::info!("pity_a: {pity_a}");
a_guarantee = maximum_pity - min(pity_a, maximum_pity.clone()) + 1;
}
if let Some(val) = free_select_policy {
if val.rarity == rarity_items.rarity && val.category_tags.contains(category_tag)
{
free_select_item_list.append(&mut category.item_ids.clone());
}
}
if let Some(tags) = chooseable_up_included_category_tags {
if tags.contains(category_tag) {
chooseable_up_list.append(&mut category.item_ids.clone());
}
}
}
if let Some(_priority_policy_tag) = chooseable_policy_tag {
// if let Some(item) = status_bin
// .rarity_status_map
// .get(&rarity_items.rarity)
// .unwrap()
// .categories_chosen_guarantee_item_map
// .get(priority_policy_tag)
// {
if rarity_items.rarity == gachaconf.common_properties.s_item_rarity {
up_s_item_list = chooseable_up_list.clone();
} else if rarity_items.rarity == gachaconf.common_properties.a_item_rarity {
up_a_item_list = vec![];
}
// }
}
}
let need_item_info_list: Vec<NeedItemInfo> = vec![NeedItemInfo {
need_item_id: target_pool.cost_item_id,
need_item_count: 1,
}];
let mut result = Gacha {
gacha_schedule_id: target_pool.gacha_schedule_id,
gacha_parent_schedule_id: target_pool.gacha_parent_schedule_id,
gacha_type: target_pool.gacha_type,
start_timestamp: target_pool.start_time.timestamp(),
end_timestamp: target_pool.end_time.timestamp(),
discount_avaliable_num,
discount_ten_roll_prize,
advanced_s_guarantee,
s_guarantee,
a_guarantee,
need_item_info_list,
free_select_progress,
free_select_required_pull,
free_select_item_list,
chosen_up_item,
// nammdglepbk: 563,
// hgmcofcjmbg: 101,
// akggbhgkifd: chooseable_up_list.clone(),
chooseable_up_list,
..Gacha::default()
};
if up_s_item_list.len() > 0 {
result.up_s_item_list = up_s_item_list;
}
if up_a_item_list.len() > 0 {
result.up_a_item_list = up_a_item_list;
}
result
}
/// Get the actual item cost count (counting discount).
pub fn get_actual_cost_count<'bin, 'conf>(
&'bin mut self,
target_pool: &'conf CharacterGachaPool,
pull_count: &u32,
) -> u32 {
let gachaconf = global_gacha_config();
if *pull_count == 10 {
let discount_tag = &gachaconf.common_properties.ten_pull_discount_tag;
if target_pool.discount_policy_tags.contains(&discount_tag) {
let status_bin = self
.gacha_status_map
.get_mut(&target_pool.sharing_guarantee_info_category)
.unwrap();
let discount_policy = gachaconf
.discount_policies
.ten_pull_discount_map
.get(discount_tag)
.unwrap();
let usage = status_bin.discount_usage_map.get_mut(discount_tag).unwrap();
if *usage < discount_policy.use_limit {
*usage += 1;
return discount_policy.discounted_prize;
}
}
}
pull_count.clone()
}
pub fn request_free_agent<'bin, 'conf>(
&'bin mut self,
target_pool: &'conf CharacterGachaPool,
item_id: &ItemID,
) -> GachaAddedItemType {
let gachaconf = global_gacha_config();
let sharing_guarantee_category_tag = &target_pool.sharing_guarantee_info_category;
let status_bin = self
.gacha_status_map
.get_mut(sharing_guarantee_category_tag)
.unwrap();
let item_id = item_id.value();
let mut free_select_policy: Option<&FreeSelectItem> = None;
let mut free_select_progress: u32 = 0;
let mut free_select_required_pull: u32 = 0;
for discount_policy_tag in target_pool.discount_policy_tags.iter() {
if gachaconf
.discount_policies
.free_select_map
.contains_key(discount_policy_tag)
{
let policy = gachaconf
.discount_policies
.free_select_map
.get(discount_policy_tag)
.unwrap();
let free_select_demand_idx = usize::try_from(
*(status_bin
.discount_usage_map
.get(&policy.free_select_usage_record_tag)
.unwrap()),
)
.unwrap();
if policy.milestones.len() <= free_select_demand_idx {
continue;
}
let free_select_actual_progress = status_bin
.discount_usage_map
.get(&policy.free_select_progress_record_tag)
.unwrap();
free_select_policy = Some(policy);
free_select_required_pull = policy
.milestones
.get(free_select_demand_idx)
.unwrap()
.to_owned();
free_select_progress = min(free_select_required_pull, *free_select_actual_progress);
}
}
if let None = free_select_policy {
tracing::info!(
"refuse free agent because: pool of parent_schedule_id {} hasn't defined free agent discount yet (or used up chance)",
target_pool.gacha_parent_schedule_id
);
return GachaAddedItemType::None;
} else if free_select_progress < free_select_required_pull {
tracing::info!(
"refuse free agent because: use pulled {free_select_progress} (after last free agent) in parent_schedule_id {}, required {free_select_required_pull}",
target_pool.gacha_parent_schedule_id
);
return GachaAddedItemType::None;
}
let free_select_policy = free_select_policy.unwrap();
let mut item_type: GachaAddedItemType = GachaAddedItemType::None;
for rarity_items in target_pool.gacha_items.iter() {
if rarity_items.rarity != free_select_policy.rarity {
continue;
}
for (category_tag, category) in rarity_items.categories.iter() {
if !free_select_policy.category_tags.contains(category_tag) {
continue;
}
if category.item_ids.contains(&item_id) {
item_type = category.item_type.clone();
}
}
}
if item_type != GachaAddedItemType::None {
(*status_bin
.discount_usage_map
.get_mut(&free_select_policy.free_select_usage_record_tag)
.unwrap()) += 1;
(*status_bin
.discount_usage_map
.get_mut(&free_select_policy.free_select_progress_record_tag)
.unwrap()) -= free_select_required_pull;
}
item_type
}
pub fn choose_gacha_up<'bin, 'conf>(
&'bin mut self,
target_pool: &'conf CharacterGachaPool,
item_id: &ItemID,
) -> bool {
let gachaconf = global_gacha_config();
let item_id = item_id.value();
for rarity_items in target_pool.gacha_items.iter() {
for guarantee_policy_tag in rarity_items.category_guarantee_policy_tags.iter() {
let category_guarantee_policy = gachaconf
.category_guarantee_policy_map
.get(guarantee_policy_tag)
.unwrap();
if !category_guarantee_policy.chooseable {
continue;
}
let mut up_category: Option<&String> = None;
for (category_tag, category) in rarity_items.categories.iter() {
if category.item_ids.contains(&item_id) {
up_category = Some(category_tag);
break;
}
}
if let None = up_category {
continue;
};
let up_category = up_category.unwrap();
let progress_bin = self
.gacha_status_map
.get_mut(&target_pool.sharing_guarantee_info_category)
.unwrap()
.rarity_status_map
.get_mut(&rarity_items.rarity)
.unwrap();
match progress_bin
.categories_chosen_guarantee_item_map
.entry(guarantee_policy_tag.clone())
{
Occupied(mut occupied_entry) => {
occupied_entry.insert(item_id);
}
Vacant(vacant_entry) => {
vacant_entry.insert(item_id);
}
};
match progress_bin
.categories_chosen_guarantee_category_map
.entry(up_category.clone())
{
Occupied(mut occupied_entry) => {
occupied_entry.insert(up_category.clone());
}
Vacant(vacant_entry) => {
vacant_entry.insert(up_category.clone());
}
};
return true;
}
}
false
}
}

View file

@ -1,25 +1,20 @@
use super::record::*;
use super::stat::*;
use data::gacha;
use data::gacha::gacha_config::*;
use data::tables::ItemID;
use proto::*;
use chrono::{DateTime, Local};
use proto::GachaModelBin;
use rand::{thread_rng, Rng};
use std::collections::{HashMap, HashSet};
use std::hash::{BuildHasher, Hash};
pub struct GachaModel {
pub gacha_status_map: HashMap<String, GachaStatus>,
pub gacha_records: Vec<GachaRecord>,
pub gacha_bin: GachaModelBin,
}
impl Default for GachaModel {
fn default() -> GachaModel {
let result = GachaModel {
gacha_status_map: HashMap::new(),
gacha_records: vec![],
gacha_bin: GachaModelBin::default(),
};
result.post_deserialize()
}
@ -27,37 +22,19 @@ impl Default for GachaModel {
impl GachaModel {
pub fn from_bin(gacha_bin: GachaModelBin) -> Self {
let result = Self {
gacha_status_map: gacha_bin
.gacha_status_map
.into_iter()
.map(|(k, v)| (k, GachaStatus::from_bin(v)))
.collect(),
gacha_records: gacha_bin
.gacha_records
.into_iter()
.map(|x| GachaRecord::from_bin(x))
.collect(),
};
let result = Self { gacha_bin };
result.post_deserialize()
}
pub fn to_bin(&self) -> GachaModelBin {
GachaModelBin {
gacha_status_map: self
.gacha_status_map
.iter()
.map(|(k, v)| (k.clone(), v.to_bin()))
.collect(),
gacha_records: self.gacha_records.iter().map(|x| x.to_bin()).collect(),
..Default::default()
}
self.gacha_bin.clone()
}
pub fn post_deserialize(mut self) -> GachaModel {
let gachaconf = gacha::global_gacha_config();
for gacha_pool in gachaconf.character_gacha_pool_list.iter() {
let mut gacha_status_map = &mut self.gacha_status_map;
let gacha_comp_bin = &mut self.gacha_bin;
let mut gacha_status_map = &mut gacha_comp_bin.gacha_status_map;
let status_bin = get_or_add(
&mut gacha_status_map,
&gacha_pool.sharing_guarantee_info_category,
@ -120,11 +97,11 @@ impl GachaModel {
&'bin mut self,
pull_time: &DateTime<Local>,
target_pool: &'conf CharacterGachaPool,
) -> GachaRecord {
) -> GachaRecordBin {
let mut gacha_bin = &mut self.gacha_bin;
let (rarity_items, progress_bin, status_bin, probability_model) =
self.determine_rarity(target_pool);
let (category_tag, category) =
self.determine_category(rarity_items, progress_bin, target_pool);
determine_rarity(&gacha_bin, target_pool);
let (category_tag, category) = determine_category(rarity_items, progress_bin, target_pool);
let result = determine_gacha_result(
pull_time,
category,
@ -133,311 +110,11 @@ impl GachaModel {
progress_bin,
rarity_items,
);
self.update_pity(rarity_items, probability_model, target_pool);
self.update_category_guarantee_info(rarity_items, &category_tag, target_pool);
self.update_discount(target_pool, &category_tag, rarity_items);
update_pity(&mut gacha_bin, rarity_items, probability_model, target_pool);
update_category_guarantee_info(&mut gacha_bin, rarity_items, &category_tag, target_pool);
update_discount(&mut gacha_bin, target_pool, &category_tag, rarity_items);
result
}
fn rand_rarity<'bin, 'conf>(
&'bin self,
target_pool: &'conf CharacterGachaPool,
status_bin: &'bin GachaStatus,
) -> (
&'conf GachaAvailableItemsInfo,
&'bin GachaProgress,
&'conf ProbabilityModel,
) {
let gachaconf = gacha::global_gacha_config();
let mut rng = thread_rng();
let rarity_status_map = &status_bin.rarity_status_map;
// gacha_items is already sorted by rarity descendingly in its post_configure.
for rarity_items in target_pool.gacha_items.iter() {
// Surely any judgement should be made on the current pity.
let progress_bin = rarity_status_map.get(&rarity_items.rarity).unwrap();
let pity = progress_bin.pity;
let probability_model = gachaconf
.probability_model_map
.get(&rarity_items.probability_model_tag)
.unwrap();
if rng.gen_range(0.0..100.0) <= probability_model.get_chance_percent(&pity) {
return (rarity_items, progress_bin, probability_model);
}
}
panic!("The user failed to get any items.");
}
fn determine_rarity<'bin, 'conf>(
&'bin self,
target_pool: &'conf CharacterGachaPool,
) -> (
&'conf GachaAvailableItemsInfo,
&'bin GachaProgress,
&'bin GachaStatus,
&'conf ProbabilityModel,
) {
let gachaconf = gacha::global_gacha_config();
let status_bin = self
.gacha_status_map
.get(&target_pool.sharing_guarantee_info_category)
.expect(&format!(
"post_deserialize forgot StatusBin/sharing_guarantee_info_category: {}",
target_pool.sharing_guarantee_info_category
));
let (mut rarity_items, mut progress_bin, mut probability_model) =
self.rand_rarity(target_pool, &status_bin);
// We should take AdvancedGuarantee discount into consideration.
for discount_tag in target_pool.discount_policy_tags.iter() {
if let Some(discount) = gachaconf
.discount_policies
.advanced_guarantee_map
.get(discount_tag)
{
if discount.rarity <= rarity_items.rarity {
continue;
}
if status_bin
.discount_usage_map
.get(discount_tag)
.expect(&format!(
"post_deserialize forgot StatusBin/discount_usage_map: {}",
discount_tag
))
>= &discount.use_limit
{
continue;
}
let higher_progress_bin = status_bin
.rarity_status_map
.get(&discount.rarity)
.expect(&format!(
"post_deserialize forgot StatusBin/rarity_status_map: {}",
&discount.rarity
));
if higher_progress_bin.pity >= discount.guarantee_pity {
let mut found_rarity_items = false;
for gacha_items in target_pool.gacha_items.iter() {
if gacha_items.rarity == discount.rarity {
rarity_items = gacha_items;
probability_model = gachaconf
.probability_model_map
.get(&gacha_items.probability_model_tag)
.unwrap();
found_rarity_items = true;
break;
}
}
assert!(found_rarity_items, "Handle AdvancedGuarantee Discount ({discount_tag}) error: The target rarity does not exist in this pool.");
progress_bin = higher_progress_bin;
}
}
}
(rarity_items, progress_bin, status_bin, probability_model)
}
fn determine_category<'bin, 'conf>(
&'bin self,
rarity_items: &'conf GachaAvailableItemsInfo,
progress_bin: &'bin GachaProgress,
target_pool: &'conf CharacterGachaPool,
) -> (String, &'conf GachaCategoryInfo) {
let gachaconf = gacha::global_gacha_config();
let mut category_tag_inited = false;
let mut category_tag_result: HashSet<String> = HashSet::new();
// First of all, if there's a chooseable category and
// it is SELECTED then we MUST give that category's item.
for guarantee_policy_tag in rarity_items.category_guarantee_policy_tags.iter() {
let category_guarantee_policy = gachaconf
.category_guarantee_policy_map
.get(guarantee_policy_tag)
.unwrap();
if !category_guarantee_policy.chooseable {
continue;
}
// As we found a policy defined chooseable, we
// should head to look whether the user chose
// the category he want.
if let Some(category_tag) = progress_bin
.categories_chosen_guarantee_category_map
.get(guarantee_policy_tag)
{
// User chose a category; our work are done here.
category_tag_result.insert(category_tag.clone());
category_tag_inited = true;
}
}
// Then we should take a look at MustGainItem.
if !category_tag_inited {
for discount_policy_tag in target_pool.discount_policy_tags.iter() {
if let Some(discount) = gachaconf
.discount_policies
.must_gain_item_map
.get(discount_policy_tag)
{
if discount.rarity != rarity_items.rarity {
continue;
}
category_tag_result.insert(discount.category_tag.clone());
category_tag_inited = true;
}
}
}
// Otherwise, just select as normal.
if !category_tag_inited {
for tag in rarity_items.categories.keys() {
category_tag_result.insert(tag.clone());
}
for guarantee_policy_tag in rarity_items.category_guarantee_policy_tags.iter() {
let category_guarantee_policy = gachaconf
.category_guarantee_policy_map
.get(guarantee_policy_tag)
.unwrap();
let failure_times = progress_bin.categories_progress_map
.get(guarantee_policy_tag)
.expect(&format!("post_deserialize forgot StatusBin/rarity_status_map[{}]/categories_progress_map: {}", &rarity_items.rarity, guarantee_policy_tag));
if failure_times >= &category_guarantee_policy.trigger_on_failure_times {
category_tag_result = category_tag_result
.intersection(&category_guarantee_policy.included_category_tags)
.cloned()
.collect();
}
}
// category_tag_inited = true;
}
let mut categories: Vec<(String, &GachaCategoryInfo)> = vec![];
let mut weight_sum = 0;
for result_tag in category_tag_result {
let category = rarity_items.categories.get(&result_tag).unwrap();
categories.push((result_tag, category));
weight_sum += category.category_weight;
}
let randomnum = rand::thread_rng().gen_range(0..weight_sum);
let mut enumerated_ranges_end = 0;
for category in categories.into_iter() {
if randomnum <= enumerated_ranges_end + category.1.category_weight {
return (category.0, category.1);
}
enumerated_ranges_end += category.1.category_weight;
}
panic!("No category is chosen.");
}
fn update_pity<'bin, 'conf>(
&'bin mut self,
rarity_items: &'conf GachaAvailableItemsInfo,
probability_model: &'conf ProbabilityModel,
target_pool: &'conf CharacterGachaPool,
) {
let status_bin = self
.gacha_status_map
.get_mut(&target_pool.sharing_guarantee_info_category)
.unwrap();
for (rarity, rarity_status) in status_bin.rarity_status_map.iter_mut() {
if (rarity == &rarity_items.rarity)
|| (probability_model.clear_status_on_higher_rarity_pulled
&& rarity < &rarity_items.rarity)
{
rarity_status.pity = 1;
} else {
rarity_status.pity += 1;
}
}
}
fn update_category_guarantee_info<'bin, 'conf>(
&'bin mut self,
rarity_items: &'conf GachaAvailableItemsInfo,
category_tag: &String,
target_pool: &'conf CharacterGachaPool,
) {
let gachaconf = gacha::global_gacha_config();
let status_bin = self
.gacha_status_map
.get_mut(&target_pool.sharing_guarantee_info_category)
.unwrap();
let progress_bin = status_bin
.rarity_status_map
.get_mut(&rarity_items.rarity)
.unwrap();
for policy_tag in rarity_items.category_guarantee_policy_tags.iter() {
let policy = gachaconf
.category_guarantee_policy_map
.get(policy_tag)
.unwrap();
// TODO: Chooseable guarantee not implemented
let prev_failure = progress_bin
.categories_progress_map
.get_mut(policy_tag)
.expect(&format!(
"post_deserialize forgot StatusBin/rarity_status_map[{}]/categories_progress_map: {}",
rarity_items.rarity, policy_tag
));
if policy.included_category_tags.contains(category_tag) {
*prev_failure = 0;
} else {
*prev_failure += 1;
}
}
}
fn update_discount<'bin, 'conf>(
&'bin mut self,
target_pool: &'conf CharacterGachaPool,
category_tag: &String,
rarity_items: &GachaAvailableItemsInfo,
) {
let gachaconf = gacha::global_gacha_config();
for (policy_tag, policy) in gachaconf.discount_policies.must_gain_item_map.iter() {
if *category_tag != policy.category_tag {
continue;
}
if !target_pool.discount_policy_tags.contains(policy_tag) {
continue;
}
let status_bin = self
.gacha_status_map
.get_mut(&target_pool.sharing_guarantee_info_category)
.unwrap();
let usage = status_bin.discount_usage_map.get_mut(policy_tag).unwrap();
if *usage < policy.use_limit {
*usage += 1;
}
}
for (policy_tag, policy) in gachaconf.discount_policies.advanced_guarantee_map.iter() {
if rarity_items.rarity != policy.rarity {
continue;
}
if !target_pool.discount_policy_tags.contains(policy_tag) {
continue;
}
let status_bin = self
.gacha_status_map
.get_mut(&target_pool.sharing_guarantee_info_category)
.unwrap();
let usage = status_bin.discount_usage_map.get_mut(policy_tag).unwrap();
if *usage < policy.use_limit {
*usage += 1;
}
}
for (policy_tag, policy) in gachaconf.discount_policies.free_select_map.iter() {
if !target_pool.discount_policy_tags.contains(policy_tag) {
continue;
}
let status_bin = self
.gacha_status_map
.get_mut(&target_pool.sharing_guarantee_info_category)
.unwrap();
let progress = status_bin
.discount_usage_map
.get_mut(&policy.free_select_progress_record_tag)
.unwrap();
*progress += 1;
}
}
}
fn get_or_add<'a, K: Eq + PartialEq + Hash + Clone, V: Default, S: BuildHasher>(
@ -450,14 +127,200 @@ fn get_or_add<'a, K: Eq + PartialEq + Hash + Clone, V: Default, S: BuildHasher>(
map.get_mut(key).unwrap()
}
fn rand_rarity<'bin, 'conf>(
target_pool: &'conf CharacterGachaPool,
status_bin: &'bin GachaStatusBin,
) -> (
&'conf GachaAvailableItemsInfo,
&'bin GachaProgressBin,
&'conf ProbabilityModel,
) {
let gachaconf = gacha::global_gacha_config();
let mut rng = thread_rng();
let rarity_status_map = &status_bin.rarity_status_map;
// gacha_items is already sorted by rarity descendingly in its post_configure.
for rarity_items in target_pool.gacha_items.iter() {
// Surely any judgement should be made on the current pity.
let progress_bin = rarity_status_map.get(&rarity_items.rarity).unwrap();
let pity = progress_bin.pity;
let probability_model = gachaconf
.probability_model_map
.get(&rarity_items.probability_model_tag)
.unwrap();
if rng.gen_range(0.0..100.0) <= probability_model.get_chance_percent(&pity) {
return (rarity_items, progress_bin, probability_model);
}
}
panic!("The user failed to get any items.");
}
fn determine_rarity<'bin, 'conf>(
gacha_bin: &'bin GachaModelBin,
target_pool: &'conf CharacterGachaPool,
) -> (
&'conf GachaAvailableItemsInfo,
&'bin GachaProgressBin,
&'bin GachaStatusBin,
&'conf ProbabilityModel,
) {
let gachaconf = gacha::global_gacha_config();
let status_bin = gacha_bin
.gacha_status_map
.get(&target_pool.sharing_guarantee_info_category)
.expect(&format!(
"post_deserialize forgot StatusBin/sharing_guarantee_info_category: {}",
target_pool.sharing_guarantee_info_category
));
let (mut rarity_items, mut progress_bin, mut probability_model) =
rand_rarity(target_pool, &status_bin);
// We should take AdvancedGuarantee discount into consideration.
for discount_tag in target_pool.discount_policy_tags.iter() {
if let Some(discount) = gachaconf
.discount_policies
.advanced_guarantee_map
.get(discount_tag)
{
if discount.rarity <= rarity_items.rarity {
continue;
}
if status_bin
.discount_usage_map
.get(discount_tag)
.expect(&format!(
"post_deserialize forgot StatusBin/discount_usage_map: {}",
discount_tag
))
>= &discount.use_limit
{
continue;
}
let higher_progress_bin =
status_bin
.rarity_status_map
.get(&discount.rarity)
.expect(&format!(
"post_deserialize forgot StatusBin/rarity_status_map: {}",
&discount.rarity
));
if higher_progress_bin.pity >= discount.guarantee_pity {
let mut found_rarity_items = false;
for gacha_items in target_pool.gacha_items.iter() {
if gacha_items.rarity == discount.rarity {
rarity_items = gacha_items;
probability_model = gachaconf
.probability_model_map
.get(&gacha_items.probability_model_tag)
.unwrap();
found_rarity_items = true;
break;
}
}
assert!(found_rarity_items, "Handle AdvancedGuarantee Discount ({discount_tag}) error: The target rarity does not exist in this pool.");
progress_bin = higher_progress_bin;
}
}
}
(rarity_items, progress_bin, status_bin, probability_model)
}
fn determine_category<'bin, 'conf>(
rarity_items: &'conf GachaAvailableItemsInfo,
progress_bin: &'bin GachaProgressBin,
target_pool: &'conf CharacterGachaPool,
) -> (String, &'conf GachaCategoryInfo) {
let gachaconf = gacha::global_gacha_config();
let mut category_tag_inited = false;
let mut category_tag_result: HashSet<String> = HashSet::new();
// First of all, if there's a chooseable category and
// it is SELECTED then we MUST give that category's item.
for guarantee_policy_tag in rarity_items.category_guarantee_policy_tags.iter() {
let category_guarantee_policy = gachaconf
.category_guarantee_policy_map
.get(guarantee_policy_tag)
.unwrap();
if !category_guarantee_policy.chooseable {
continue;
}
// As we found a policy defined chooseable, we
// should head to look whether the user chose
// the category he want.
if let Some(category_tag) = progress_bin
.categories_chosen_guarantee_category_map
.get(guarantee_policy_tag)
{
// User chose a category; our work are done here.
category_tag_result.insert(category_tag.clone());
category_tag_inited = true;
}
}
// Then we should take a look at MustGainItem.
if !category_tag_inited {
for discount_policy_tag in target_pool.discount_policy_tags.iter() {
if let Some(discount) = gachaconf
.discount_policies
.must_gain_item_map
.get(discount_policy_tag)
{
if discount.rarity != rarity_items.rarity {
continue;
}
category_tag_result.insert(discount.category_tag.clone());
category_tag_inited = true;
}
}
}
// Otherwise, just select as normal.
if !category_tag_inited {
for tag in rarity_items.categories.keys() {
category_tag_result.insert(tag.clone());
}
for guarantee_policy_tag in rarity_items.category_guarantee_policy_tags.iter() {
let category_guarantee_policy = gachaconf
.category_guarantee_policy_map
.get(guarantee_policy_tag)
.unwrap();
let failure_times = progress_bin.categories_progress_map
.get(guarantee_policy_tag)
.expect(&format!("post_deserialize forgot StatusBin/rarity_status_map[{}]/categories_progress_map: {}", &rarity_items.rarity, guarantee_policy_tag));
if failure_times >= &category_guarantee_policy.trigger_on_failure_times {
category_tag_result = category_tag_result
.intersection(&category_guarantee_policy.included_category_tags)
.cloned()
.collect();
}
}
// category_tag_inited = true;
}
let mut categories: Vec<(String, &GachaCategoryInfo)> = vec![];
let mut weight_sum = 0;
for result_tag in category_tag_result {
let category = rarity_items.categories.get(&result_tag).unwrap();
categories.push((result_tag, category));
weight_sum += category.category_weight;
}
let randomnum = rand::thread_rng().gen_range(0..weight_sum);
let mut enumerated_ranges_end = 0;
for category in categories.into_iter() {
if randomnum <= enumerated_ranges_end + category.1.category_weight {
return (category.0, category.1);
}
enumerated_ranges_end += category.1.category_weight;
}
panic!("No category is chosen.");
}
fn determine_gacha_result<'bin, 'conf>(
pull_time: &DateTime<Local>,
category: &'conf GachaCategoryInfo,
target_pool: &'conf CharacterGachaPool,
status_bin: &'bin GachaStatus,
progress_bin: &'bin GachaProgress,
status_bin: &'bin GachaStatusBin,
progress_bin: &'bin GachaProgressBin,
rarity_items: &'conf GachaAvailableItemsInfo,
) -> GachaRecord {
) -> GachaRecordBin {
let gachaconf = gacha::global_gacha_config();
let item_pool_len = category.item_ids.len() as u32;
let mut item_id: Option<&u32> = None;
@ -498,7 +361,7 @@ fn determine_gacha_result<'bin, 'conf>(
.get(rand::thread_rng().gen_range(0..item_pool_len) as usize)
.unwrap(),
};
let mut extra_item_id: Option<ItemID> = None;
let mut extra_item_id: u32 = 0;
let mut extra_item_count: u32 = 0;
for extra_items_policy_tag in rarity_items.extra_items_policy_tags.iter() {
@ -509,23 +372,134 @@ fn determine_gacha_result<'bin, 'conf>(
// TODO: apply_on_owned_count in a context with bag
// TODO: That's what RoleModel should do, not me.
if extra_items_policy.apply_on_owned_count == 0 {
extra_item_id = ItemID::new(extra_items_policy.id);
extra_item_id = extra_items_policy.id;
extra_item_count = extra_items_policy.count;
}
}
let extra_resources = match extra_item_id {
Some(item_id) => Some(GachaExtraResources {
extra_item_id: item_id,
extra_item_count,
}),
None => None,
let extra_item_bin = GachaExtraItemBin {
extra_item_id,
extra_item_count,
currently_gained: 0,
};
GachaRecord {
GachaRecordBin {
pull_timestamp: pull_time.timestamp(),
obtained_item_id: ItemID::new_unchecked(item_id.clone()),
obtained_item_id: item_id.clone(),
gacha_id: target_pool.gacha_schedule_id.clone(),
progress_map: status_bin.rarity_status_map.clone(),
extra_resources,
item_type: category.item_type.clone(),
extra_item_bin: Some(extra_item_bin),
item_type: category.item_type.into(),
}
}
fn update_pity<'bin, 'conf>(
gacha_bin: &'bin mut GachaModelBin,
rarity_items: &'conf GachaAvailableItemsInfo,
probability_model: &'conf ProbabilityModel,
target_pool: &'conf CharacterGachaPool,
) {
let status_bin = gacha_bin
.gacha_status_map
.get_mut(&target_pool.sharing_guarantee_info_category)
.unwrap();
for (rarity, rarity_status) in status_bin.rarity_status_map.iter_mut() {
if (rarity == &rarity_items.rarity)
|| (probability_model.clear_status_on_higher_rarity_pulled
&& rarity < &rarity_items.rarity)
{
rarity_status.pity = 1;
} else {
rarity_status.pity += 1;
}
}
}
fn update_category_guarantee_info<'bin, 'conf>(
gacha_bin: &'bin mut GachaModelBin,
rarity_items: &'conf GachaAvailableItemsInfo,
category_tag: &String,
target_pool: &'conf CharacterGachaPool,
) {
let gachaconf = gacha::global_gacha_config();
let status_bin = gacha_bin
.gacha_status_map
.get_mut(&target_pool.sharing_guarantee_info_category)
.unwrap();
let progress_bin = status_bin
.rarity_status_map
.get_mut(&rarity_items.rarity)
.unwrap();
for policy_tag in rarity_items.category_guarantee_policy_tags.iter() {
let policy = gachaconf
.category_guarantee_policy_map
.get(policy_tag)
.unwrap();
// TODO: Chooseable guarantee not implemented
let prev_failure = progress_bin
.categories_progress_map
.get_mut(policy_tag)
.expect(&format!(
"post_deserialize forgot StatusBin/rarity_status_map[{}]/categories_progress_map: {}",
rarity_items.rarity, policy_tag
));
if policy.included_category_tags.contains(category_tag) {
*prev_failure = 0;
} else {
*prev_failure += 1;
}
}
}
fn update_discount<'bin, 'conf>(
gacha_bin: &'bin mut GachaModelBin,
target_pool: &'conf CharacterGachaPool,
category_tag: &String,
rarity_items: &GachaAvailableItemsInfo,
) {
let gachaconf = gacha::global_gacha_config();
for (policy_tag, policy) in gachaconf.discount_policies.must_gain_item_map.iter() {
if *category_tag != policy.category_tag {
continue;
}
if !target_pool.discount_policy_tags.contains(policy_tag) {
continue;
}
let status_bin = gacha_bin
.gacha_status_map
.get_mut(&target_pool.sharing_guarantee_info_category)
.unwrap();
let usage = status_bin.discount_usage_map.get_mut(policy_tag).unwrap();
if *usage < policy.use_limit {
*usage += 1;
}
}
for (policy_tag, policy) in gachaconf.discount_policies.advanced_guarantee_map.iter() {
if rarity_items.rarity != policy.rarity {
continue;
}
if !target_pool.discount_policy_tags.contains(policy_tag) {
continue;
}
let status_bin = gacha_bin
.gacha_status_map
.get_mut(&target_pool.sharing_guarantee_info_category)
.unwrap();
let usage = status_bin.discount_usage_map.get_mut(policy_tag).unwrap();
if *usage < policy.use_limit {
*usage += 1;
}
}
for (policy_tag, policy) in gachaconf.discount_policies.free_select_map.iter() {
if !target_pool.discount_policy_tags.contains(policy_tag) {
continue;
}
let status_bin = gacha_bin
.gacha_status_map
.get_mut(&target_pool.sharing_guarantee_info_category)
.unwrap();
let progress = status_bin
.discount_usage_map
.get_mut(&policy.free_select_progress_record_tag)
.unwrap();
*progress += 1;
}
}

View file

@ -1,6 +1,4 @@
mod client_op;
mod gacha_model;
mod record;
mod stat;
pub use gacha_model::GachaModel;

View file

@ -1,85 +0,0 @@
use data::gacha::gacha_config::*;
use data::tables::ItemID;
use proto::{GachaExtraItemBin, GachaRecordBin};
use std::collections::HashMap;
use super::stat::*;
#[derive(Debug, Clone)]
pub struct GachaExtraResources {
pub extra_item_id: ItemID,
pub extra_item_count: u32,
}
impl GachaExtraResources {
pub fn from_bin_opt(bin_opt: Option<GachaExtraItemBin>) -> Option<Self> {
match bin_opt {
None => None,
Some(bin) => {
let item_id_opt = ItemID::new(bin.extra_item_id);
match item_id_opt {
None => None,
Some(extra_item_id) => Some(Self {
extra_item_id,
extra_item_count: bin.extra_item_count,
}),
}
}
}
}
pub fn to_bin_opt(opt: &Option<Self>) -> Option<GachaExtraItemBin> {
match opt {
None => None,
Some(this) => Some(GachaExtraItemBin {
extra_item_id: this.extra_item_id.value(),
extra_item_count: this.extra_item_count.clone(),
..Default::default()
}),
}
}
}
#[derive(Debug, Clone)]
pub struct GachaRecord {
pub pull_timestamp: i64,
pub obtained_item_id: ItemID,
pub gacha_id: u32,
/// The progress BEFORE this gacha is performed. key is rarity.
pub progress_map: HashMap<u32, GachaProgress>,
pub extra_resources: Option<GachaExtraResources>,
pub item_type: GachaAddedItemType,
}
impl GachaRecord {
pub fn from_bin(bin: GachaRecordBin) -> Self {
Self {
pull_timestamp: bin.pull_timestamp,
obtained_item_id: ItemID::new_unchecked(bin.obtained_item_id),
gacha_id: bin.gacha_id,
progress_map: bin
.progress_map
.into_iter()
.map(|(k, v)| (k, GachaProgress::from_bin(v)))
.collect(),
extra_resources: GachaExtraResources::from_bin_opt(bin.extra_item_bin),
item_type: GachaAddedItemType::from(bin.item_type),
}
}
pub fn to_bin(&self) -> GachaRecordBin {
GachaRecordBin {
pull_timestamp: self.pull_timestamp.clone(),
obtained_item_id: self.obtained_item_id.value(),
gacha_id: self.gacha_id.clone(),
progress_map: self
.progress_map
.iter()
.map(|(k, v)| (k.clone(), v.to_bin()))
.collect(),
extra_item_bin: GachaExtraResources::to_bin_opt(&self.extra_resources),
item_type: self.item_type.clone().into(),
}
}
}

View file

@ -1,73 +0,0 @@
use proto::*;
use std::collections::HashMap;
#[derive(Debug, Default, Clone)]
pub struct GachaProgress {
/// The pity (counting how many pulls) of this pull (in previous record) or the next pull (in status).
pub pity: u32,
/// The failure times of this category.
pub categories_progress_map: HashMap<String, u32>,
/// The selected priority (category) for a Chooseable category.
pub categories_chosen_guarantee_category_map: HashMap<String, String>,
/// The selectedpriority (a specified item) for a Chooseable category.
pub categories_chosen_guarantee_item_map: HashMap<String, u32>,
/// The failure times for selected priority (a specified item).
pub categories_chosen_guarantee_progress_map: HashMap<String, u32>,
}
impl GachaProgress {
pub fn from_bin(bin: GachaProgressBin) -> Self {
Self {
pity: bin.pity,
categories_progress_map: bin.categories_progress_map,
categories_chosen_guarantee_category_map: bin.categories_chosen_guarantee_category_map,
categories_chosen_guarantee_item_map: bin.categories_chosen_guarantee_item_map,
categories_chosen_guarantee_progress_map: bin.categories_chosen_guarantee_progress_map,
}
}
pub fn to_bin(&self) -> GachaProgressBin {
GachaProgressBin {
pity: self.pity,
categories_progress_map: self.categories_progress_map.clone(),
categories_chosen_guarantee_category_map: self
.categories_chosen_guarantee_category_map
.clone(),
categories_chosen_guarantee_item_map: self.categories_chosen_guarantee_item_map.clone(),
categories_chosen_guarantee_progress_map: self
.categories_chosen_guarantee_progress_map
.clone(),
}
}
}
#[derive(Debug, Default, Clone)]
pub struct GachaStatus {
pub rarity_status_map: HashMap<u32, GachaProgress>,
pub discount_usage_map: HashMap<String, u32>,
}
impl GachaStatus {
pub fn from_bin(bin: GachaStatusBin) -> Self {
Self {
rarity_status_map: bin
.rarity_status_map
.into_iter()
.map(|(k, v)| (k, GachaProgress::from_bin(v)))
.collect(),
discount_usage_map: bin.discount_usage_map,
}
}
pub fn to_bin(&self) -> GachaStatusBin {
GachaStatusBin {
rarity_status_map: self
.rarity_status_map
.iter()
.map(|(k, v)| (k.clone(), v.to_bin()))
.collect(),
discount_usage_map: self.discount_usage_map.clone(),
}
}
}

View file

@ -1,39 +1,26 @@
use common::util;
use data::tables::{
self, ArchiveBattleQuestID, BattleEventConfigID, HollowQuestID, TrainingQuestID,
};
use data::tables::{ArchiveBattleQuestID, TrainingQuestID};
use proto::{DungeonInfo, DungeonItemData, FightSceneInfo, SceneInfo, WeatherPoolInfo};
use thiserror::Error;
use crate::logic::{
battle::{
drop::FightDropPool,
unit::{AvatarUnit, BuddyUnit},
BuddyParam, DungeonQuestManager, TeamDataItem,
},
BuddyTeamType, EHollowQuestType, ELocalPlayType, ESceneType, TimePeriodType, WeatherType,
battle::{EquippedBuddyDataItem, InLevelAvatarDataItem, TeamDataItem},
ELocalPlayType, ESceneType, TimePeriodType, WeatherType,
};
use super::NapGameMode;
#[derive(Error, Debug)]
pub enum HollowGameError {
#[error("Quest ({0}) type is not supported: {1:?}")]
QuestTypeNotSupported(u32, EHollowQuestType),
#[error("Battle group not found, quest id: {0}")]
BattleGroupNotFound(u32),
}
pub enum HollowGameError {}
pub struct HollowGame {
pub quest_id: u32,
pub battle_event_id: BattleEventConfigID,
pub battle_event_id: u32,
pub time_period: TimePeriodType,
pub weather: WeatherType,
pub play_type: ELocalPlayType,
pub start_timestamp: i64,
pub team_data: TeamDataItem,
pub fight_drop_pool: FightDropPool,
pub quest_manager: DungeonQuestManager,
}
impl HollowGame {
@ -51,9 +38,7 @@ impl HollowGame {
weather: WeatherType::SunShine,
start_timestamp: util::cur_timestamp() as i64,
play_type,
team_data: TeamDataItem::new(avatars, &[]),
fight_drop_pool: FightDropPool::new(template.battle_event_id),
quest_manager: DungeonQuestManager::default(),
team_data: TeamDataItem::new(avatars, 0),
})
}
@ -72,66 +57,9 @@ impl HollowGame {
weather: WeatherType::SunShine,
start_timestamp: util::cur_timestamp() as i64,
play_type,
team_data: TeamDataItem::new(
avatars,
&(buddy_id != 0)
.then_some(vec![BuddyParam(buddy_id, BuddyTeamType::Fighting)])
.unwrap_or_default(),
),
fight_drop_pool: FightDropPool::new(template.battle_event_id),
quest_manager: DungeonQuestManager::default(),
team_data: TeamDataItem::new(avatars, buddy_id),
})
}
pub fn create_pure_hollow_battle(
quest_id: HollowQuestID,
avatars: &[u32],
buddy_id: u32,
time_period: TimePeriodType,
weather: WeatherType,
) -> Result<Self, HollowGameError> {
let template = quest_id.template();
if template.chess_board_id != 0 {
return Err(HollowGameError::QuestTypeNotSupported(
template.id.value(),
EHollowQuestType::from(template.hollow_quest_type),
));
}
let Some(battle_group) = tables::battle_group_config_template_tb::iter()
.find(|tmpl| tmpl.quest_id == template.id.value())
else {
return Err(HollowGameError::BattleGroupNotFound(template.id.value()));
};
Ok(Self {
quest_id: template.id.value(),
battle_event_id: battle_group.battle_event_id,
time_period,
weather,
start_timestamp: util::cur_timestamp() as i64,
play_type: Self::get_play_type_by_quest_type(EHollowQuestType::from(
template.hollow_quest_type,
)),
team_data: TeamDataItem::new(
avatars,
&(buddy_id != 0)
.then_some(vec![BuddyParam(buddy_id, BuddyTeamType::Fighting)])
.unwrap_or_default(),
),
fight_drop_pool: FightDropPool::new(battle_group.battle_event_id),
quest_manager: DungeonQuestManager::new_for_battle_group(battle_group.id),
})
}
pub fn get_play_type_by_quest_type(quest_type: EHollowQuestType) -> ELocalPlayType {
match quest_type {
EHollowQuestType::NormalBattle => ELocalPlayType::PureHollowBattle,
EHollowQuestType::BossRushBattle => ELocalPlayType::BossRushBattle,
EHollowQuestType::DifficutyBattle => ELocalPlayType::PureHollowBattleHardmode,
_ => ELocalPlayType::PureHollowBattle,
}
}
}
impl NapGameMode for HollowGame {
@ -142,16 +70,14 @@ impl NapGameMode for HollowGame {
fn scene_info(&self) -> Option<SceneInfo> {
Some(SceneInfo {
scene_type: self.scene_type() as u32,
battle_event_id: self.battle_event_id.value(),
battle_event_id: self.battle_event_id,
play_type: self.play_type as u32,
fight_scene_info: Some(FightSceneInfo {
nmhdkmcabjg: true,
weather_pool: Some(WeatherPoolInfo {
time_period: self.time_period.to_string(),
weather: self.weather.to_string(),
..Default::default()
}),
fight_drop_info: Some(self.fight_drop_pool.to_client()),
..Default::default()
}),
..Default::default()
@ -167,15 +93,14 @@ impl NapGameMode for HollowGame {
.team_data
.avatar_member_list
.iter()
.map(AvatarUnit::to_client)
.map(InLevelAvatarDataItem::to_client)
.collect(),
buddy_list: self
.team_data
.equipped_buddy_list
.iter()
.map(BuddyUnit::to_client)
.map(EquippedBuddyDataItem::to_client)
.collect(),
dungeon_quest_info: Some(self.quest_manager.to_client()),
..Default::default()
})
}

View file

@ -1,141 +0,0 @@
use common::util;
use data::tables::{self, BattleEventConfigID, HollowQuestID};
use proto::{
DungeonInfo, DungeonItemData, FightQuestInfo, LongFightInfo, LongFightSceneInfo, SceneInfo,
WeatherPoolInfo,
};
use thiserror::Error;
use crate::logic::{
battle::{
drop::FightDropPool,
unit::{AvatarUnit, BuddyUnit},
BuddyParam, DungeonQuestManager, LogicVariableTable, TeamDataItem,
},
BuddyTeamType, EHollowQuestType, ELocalPlayType, ESceneType, TimePeriodType, WeatherType,
};
use super::NapGameMode;
#[derive(Error, Debug)]
pub enum LongFightGameError {
#[error("Tried to run quest of type {0:?} using LongFight logic")]
InvalidQuestType(EHollowQuestType),
#[error("Battle group not found, quest id: {0}")]
BattleGroupNotFound(u32),
}
pub struct LongFightGame {
pub quest_id: u32,
pub battle_event_id: BattleEventConfigID,
pub play_type: ELocalPlayType,
pub time_period: TimePeriodType,
pub weather: WeatherType,
pub start_timestamp: i64,
pub team_data: TeamDataItem,
pub variable_table: LogicVariableTable,
pub fight_drop_pool: FightDropPool,
pub quest_manager: DungeonQuestManager,
}
impl LongFightGame {
const RALLY_GUIDANCE_BUDDY_ID: u32 = 50001;
pub fn create_rally_game(
quest_id: HollowQuestID,
avatars: &[u32],
buddy_id: u32,
time_period: TimePeriodType,
weather: WeatherType,
) -> Result<Self, LongFightGameError> {
let template = quest_id.template();
let quest_type = EHollowQuestType::from(template.hollow_quest_type);
if quest_type != EHollowQuestType::RallyBattle {
return Err(LongFightGameError::InvalidQuestType(quest_type));
}
let Some(battle_group) = tables::battle_group_config_template_tb::iter()
.find(|tmpl| tmpl.quest_id == template.id.value())
else {
return Err(LongFightGameError::BattleGroupNotFound(template.id.value()));
};
let mut buddy_params = vec![BuddyParam(
Self::RALLY_GUIDANCE_BUDDY_ID,
BuddyTeamType::RallyGuidance,
)];
if buddy_id != 0 {
buddy_params.push(BuddyParam(buddy_id, BuddyTeamType::Fighting));
}
Ok(Self {
quest_id: template.id.value(),
battle_event_id: battle_group.battle_event_id,
play_type: ELocalPlayType::RallyLongFight,
time_period,
weather,
start_timestamp: util::cur_timestamp() as i64,
team_data: TeamDataItem::new(avatars, &buddy_params),
variable_table: LogicVariableTable::new(battle_group.battle_event_id),
fight_drop_pool: FightDropPool::new(battle_group.battle_event_id),
quest_manager: DungeonQuestManager::new_for_battle_group(battle_group.id),
})
}
}
impl NapGameMode for LongFightGame {
fn scene_type(&self) -> ESceneType {
ESceneType::LongFight
}
fn scene_info(&self) -> Option<SceneInfo> {
Some(SceneInfo {
scene_type: self.scene_type() as u32,
battle_event_id: self.battle_event_id.value(),
play_type: self.play_type as u32,
long_fight_scene_info: Some(LongFightSceneInfo {
fight_data: Some(LongFightInfo {
fight_quest_info: Some(FightQuestInfo {
fight_variable_map: self.variable_table.to_client(),
..Default::default()
}),
..Default::default()
}),
fight_drop_info: Some(self.fight_drop_pool.to_client()),
weather_pool: Some(WeatherPoolInfo {
time_period: self.time_period.to_string(),
weather: self.weather.to_string(),
febgjinpcbp: true,
bejeblcfcha: true,
..Default::default()
}),
..Default::default()
}),
..Default::default()
})
}
fn dungeon_info(&self) -> Option<DungeonInfo> {
Some(DungeonInfo {
quest_id: self.quest_id,
start_timestamp: self.start_timestamp,
dungeon_item_data: Some(DungeonItemData::default()),
avatar_list: self
.team_data
.avatar_member_list
.iter()
.map(AvatarUnit::to_client)
.collect(),
buddy_list: self
.team_data
.equipped_buddy_list
.iter()
.map(BuddyUnit::to_client)
.collect(),
dungeon_quest_info: Some(self.quest_manager.to_client()),
..Default::default()
})
}
}

View file

@ -1,23 +1,20 @@
mod fresh;
mod frontend;
mod hollow;
mod long_fight;
pub use fresh::*;
pub use frontend::*;
pub use hollow::*;
pub use long_fight::*;
use proto::{DungeonInfo, SceneInfo, WorldInitScNotify};
use thiserror::Error;
use super::{battle::DungeonQuestError, procedure::ProcedureError, ESceneType};
use super::{procedure::ProcedureError, ESceneType};
#[derive(Default)]
pub enum GameInstance {
Frontend(FrontendGame),
Fresh(FreshGame),
Hollow(HollowGame),
LongFight(LongFightGame),
#[default]
Null,
}
@ -32,10 +29,6 @@ pub enum LogicError {
Procedure(#[from] ProcedureError),
#[error("hollow error: {0}")]
Hollow(#[from] HollowGameError),
#[error("longfight error: {0}")]
LongFight(#[from] LongFightGameError),
#[error("dungeon quest error: {0}")]
DungeonQuest(#[from] DungeonQuestError),
}
impl GameInstance {
@ -48,7 +41,6 @@ impl GameInstance {
Self::Frontend(game) => game,
Self::Fresh(game) => game,
Self::Hollow(game) => game,
Self::LongFight(game) => game,
Self::Null => return Err(LogicError::GameIsNull),
}))
}

View file

@ -1,4 +1,4 @@
use data::tables::{AvatarBaseID, PostGirlConfigID, ProcedureConfigID};
use data::tables::{AvatarBaseID, ProcedureConfigID};
use proto::{BasicDataModelBin, PlayerBasicInfo};
pub struct BasicDataModel {
@ -8,7 +8,6 @@ pub struct BasicDataModel {
pub nick_name: Option<String>,
pub frontend_avatar_id: Option<AvatarBaseID>,
pub beginner_procedure_id: Option<ProcedureConfigID>,
pub selected_post_girl_id: Option<PostGirlConfigID>,
}
impl Default for BasicDataModel {
@ -20,7 +19,6 @@ impl Default for BasicDataModel {
nick_name: None,
frontend_avatar_id: None,
beginner_procedure_id: Some(ProcedureConfigID::new_unchecked(1)),
selected_post_girl_id: None,
}
}
}
@ -39,10 +37,6 @@ impl BasicDataModel {
avatar_id,
frontend_avatar_id: avatar_id,
kbjleelonfe: self.profile_icon,
has_nickname: match &self.nick_name {
Some(_name) => 1,
None => 0,
},
..Default::default()
}
}
@ -60,7 +54,6 @@ impl BasicDataModel {
1.. => ProcedureConfigID::new(bin.beginner_procedure_id as u32),
_ => None,
},
selected_post_girl_id: PostGirlConfigID::new(bin.selected_post_girl_id),
nick_name: match bin.nick_name.is_empty() {
true => None,
false => Some(bin.nick_name),
@ -82,10 +75,6 @@ impl BasicDataModel {
.beginner_procedure_id
.map(|i| i.value() as i32)
.unwrap_or(-1),
selected_post_girl_id: match self.selected_post_girl_id {
Some(post_girl_id) => post_girl_id.value(),
None => 0
},
}
}
}

View file

@ -1,12 +1,11 @@
use std::collections::{BTreeSet, HashMap};
use std::collections::BTreeSet;
use data::tables::{quick_access_template_tb, QuickFuncID, UnlockConfigID};
use proto::{LockModelBin, QuickAccessData, QuickAccessType, UnlockData};
use data::tables::UnlockConfigID;
use proto::{LockModelBin, UnlockData};
#[derive(Default)]
pub struct LockModel {
unlock_list: BTreeSet<UnlockConfigID>,
quick_access_list: HashMap<u32, QuickFuncID>,
}
impl LockModel {
@ -17,11 +16,6 @@ impl LockModel {
.into_iter()
.map(UnlockConfigID::new_unchecked)
.collect(),
quick_access_list: bin
.quick_access_list
.into_iter()
.map(|(k, v)| (k, QuickFuncID::new_unchecked(v)))
.collect(),
}
}
@ -33,39 +27,10 @@ impl LockModel {
.into_iter()
.map(|i| i.value())
.collect(),
quick_access_list: self
.quick_access_list
.iter()
.map(|(k, v)| (k.clone(), v.value()))
.collect(),
}
}
pub fn quick_access_to_client(&self) -> Vec<QuickAccessData> {
let mut quick_access_data_list: Vec<QuickAccessData> = vec![];
for quick_access in quick_access_template_tb::iter() {
quick_access_data_list.push(QuickAccessData {
r#type: QuickAccessType::Direct.into(),
quick_access_index: quick_access.quick_access_index,
btn_id: quick_access.quick_func_id.value(),
});
}
for quick_access_index in 1..8 {
let mut btn_id = 0;
if let Some(id) = self.quick_access_list.get(&quick_access_index) {
btn_id = id.value();
}
quick_access_data_list.push(QuickAccessData {
r#type: QuickAccessType::QuickMenu.into(),
quick_access_index,
btn_id,
});
}
quick_access_data_list
}
pub fn to_client(&self) -> UnlockData {
UnlockData {
unlock_id_list: self
.unlock_list
@ -73,7 +38,6 @@ impl LockModel {
.into_iter()
.map(|i| i.value())
.collect(),
quick_access_data_list: self.quick_access_to_client(),
..Default::default()
}
}
@ -85,28 +49,4 @@ impl LockModel {
pub fn is_unlock(&self, id: UnlockConfigID) -> bool {
self.unlock_list.contains(&id)
}
pub fn mod_quick_access(&mut self, index: u32, id: Option<QuickFuncID>) -> QuickAccessData {
let btn_id = match id {
Some(quick_access_id) => {
if self.quick_access_list.contains_key(&index) {
*self.quick_access_list.get_mut(&index).unwrap() = quick_access_id;
} else {
self.quick_access_list.insert(index, quick_access_id);
}
quick_access_id.value()
}
None => {
if self.quick_access_list.contains_key(&index) {
self.quick_access_list.remove(&index);
}
0
}
};
QuickAccessData {
r#type: QuickAccessType::QuickMenu.into(),
quick_access_index: index,
btn_id,
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -14,16 +14,12 @@ pub struct BasicDataModelBin {
pub frontend_avatar_id: i32,
#[prost(int32, tag = "6")]
pub beginner_procedure_id: i32,
#[prost(uint32, tag = "7")]
pub selected_post_girl_id: u32,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct LockModelBin {
#[prost(int32, repeated, tag = "1")]
pub unlock_list: ::prost::alloc::vec::Vec<i32>,
#[prost(map = "uint32, uint32", tag = "2")]
pub quick_access_list: ::std::collections::HashMap<u32, u32>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]