Compare commits

..

10 commits

Author SHA1 Message Date
YYHEggEgg
436e691245 QuickMenu bugfix 2024-08-06 23:30:16 +08:00
YYHEggEgg
bb058b451d Separate Gacha logic & DTO, Fix items issue 2024-08-06 22:58:24 +08:00
YYHEggEgg
b2401d566b Support teleport map, mod button, post girl, nickname init 2024-08-06 22:43:45 +08:00
3c2397cdbd README: update feature list 2024-08-05 15:08:00 +00:00
356fc5278e server version: milestone 2 reached (0.2.0) 2024-08-05 17:59:18 +03:00
50f694add0 H.D.D. and Combat commissions implementation
Implement Combat commissions (PureHollowBattle and LongFight) (including Rally commissions)
Refactor some battle structures
Unlock hollow quests (QuestInfo and YorozuyaInfo)
2024-08-05 17:45:13 +03:00
f99165e452 chore: move new dependencies to workspace's Cargo.toml 2024-08-04 16:20:31 +03:00
8d1211750b chore: fix warnings 2024-08-04 15:46:43 +03:00
285fc0b6fa NewsStand interaction 2024-08-04 15:40:06 +03:00
55b7ed3beb Gacha System implementation
## Abstract

This PR implements

- The Gacha System for client. Player can pull in defined pools with a similar experience to zenless & mihoyo gacha, and these status can be saved through player save and recovered.
- `player kick` command in extra. Command `player kick [player_uid] (optional: [reason_id])` can be used to kick a player to offline.

## Support list

- Similar probability to mihoyo gacha
- QingYi & Jane Doe's Agent & W-Engine banner
- Standard Banner:
  - Get a S Agent in the first 50 pulls
  - Get a 20% discount for the first 5 10-pulls
  - Choose a S Agent once you pulled for 300
- ~~Bangboo Banner~~ (not working due to other reasons)
  - Choosing your demanded S bangboo for 100% UP

## Principle

- A complex configuration file `gacha.jsonc` is introduced to define all behaviours.
- Gacha status is saved via `GachaModelBin`.

For more informations about the config and save, an article is available [here](https://yyheggegg.github.io/mihomo-gio-blogs/zzz-gacha-impl-en/).

## Known issues

- You can not see any bangboos in the collection when choosing Bangboo.
- **Specifically for 1.1 Beta**, performing gacha may lead to random client issues, including:
  - The TVs showing rarity ending up in blank after its flash.
  - Game become black screen.
  - If clicking 'Skip' but not fast enough, game'll stuck and not able to do anything. You may try to click 'Skip' scarcely when 'REC' shows, or after all animations has stopped.

Co-authored-by: YYHEggEgg <53960525+YYHEggEgg@users.noreply.github.com>
Reviewed-on: NewEriduPubSec/JaneDoe-ZS#1
Co-authored-by: YYHEggEgg <yyheggegg@xeondev.com>
Co-committed-by: YYHEggEgg <yyheggegg@xeondev.com>
2024-08-04 11:41:23 +00:00
61 changed files with 197081 additions and 1074 deletions

View file

@ -13,6 +13,8 @@ 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,9 +6,13 @@
### 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
- Unlock all characters
- Training battle
- Player management command system
- Training
### Requirements
- [Rust](https://www.rust-lang.org/tools/install)

View file

@ -200,5 +200,16 @@
"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

@ -0,0 +1,212 @@
[
{
"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

@ -0,0 +1,29 @@
[
{
"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

@ -0,0 +1,112 @@
[
{
"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

@ -0,0 +1,882 @@
[
{
"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

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

View file

@ -7,6 +7,8 @@ version.workspace = true
# Serialization
serde.workspace = true
serde_json.workspace = true
jsonc-parser.workspace = true
chrono.workspace = true
prost.workspace = true
# Util
@ -18,5 +20,3 @@ 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,7 +1,6 @@
use std::collections::{HashMap, HashSet};
use chrono::{prelude::Local, DateTime};
use proto::GachaAddedItemType;
use serde::{Deserialize, Deserializer};
use tracing;
@ -152,13 +151,64 @@ 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(default, deserialize_with = "from_str")]
#[serde(deserialize_with = "from_str")]
pub item_type: GachaAddedItemType,
}

View file

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

View file

@ -0,0 +1,16 @@
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

@ -0,0 +1,16 @@
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

@ -0,0 +1,10 @@
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

@ -0,0 +1,13 @@
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

@ -0,0 +1,10 @@
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,4 +87,15 @@ template_tables! {
MainCityBgmConfigTemplate;
ArchiveFileQuestTemplate;
ArchiveBattleQuestTemplate;
HollowQuestTemplate;
HollowConfigTemplate;
BattleEventConfigTemplate;
BattleGroupConfigTemplate;
SubAreaDataTemplate;
VariableDataTemplate;
OnceRewardTemplate;
QuickAccessTemplate;
QuickFuncTemplate;
TeleportConfigTemplate;
ItemTemplate;
}

View file

@ -0,0 +1,19 @@
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

@ -0,0 +1,11 @@
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

@ -0,0 +1,10 @@
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

@ -0,0 +1,16 @@
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

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

View file

@ -0,0 +1,25 @@
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,6 +15,7 @@ rbase64.workspace = true
toml.workspace = true
serde.workspace = true
serde_json.workspace = true
chrono.workspace = true
# Database
sqlx.workspace = true
@ -41,4 +42,3 @@ 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;
use data::tables::{self, QuickFuncID};
pub async fn on_get_tips_info(
_session: &NetSession,
@ -21,23 +21,34 @@ pub async fn on_get_client_systems_info(
player: &mut Player,
_req: GetClientSystemsInfoCsReq,
) -> NetResult<GetClientSystemsInfoScRsp> {
Ok(GetClientSystemsInfoScRsp {
retcode: Retcode::RetSucc.into(),
info: Some(ClientSystemsInfo {
post_girl_data: Some(PostGirlData {
selected_post_girl_id_list: tables::post_girl_config_template_tb::iter()
.map(|template| template.id.value())
.collect(),
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: 1000,
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()),
hbhfjgbahgf: Some(Aboegnnepmi::default()),
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())
.collect(),
..Default::default()
}),
..Default::default()
}),
})
@ -54,6 +65,16 @@ 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,
@ -173,3 +194,70 @@ 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,19 +1,18 @@
use std::{
cmp::min,
collections::{
hash_map::Entry::{Occupied, Vacant},
HashSet,
},
};
use data::{
gacha::{gacha_config::*, global_gacha_config},
tables::{AvatarBaseID, WeaponID},
gacha::{
gacha_config::{CharacterGachaPool, GachaAddedItemType},
global_gacha_config,
},
tables::{AvatarBaseID, ItemID, WeaponID},
};
use proto::{GainItemInfo, GetGachaDataScRsp};
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,
@ -32,7 +31,7 @@ pub async fn on_get_gacha_data(
Ok(GetGachaDataScRsp {
retcode: Retcode::RetSucc.into(),
gacha_type: req.gacha_type,
gacha_data: Some(generate_all_gacha_info(_player, &Local::now())),
gacha_data: Some(_player.gacha_model.to_client(&Local::now())),
})
}
}
@ -51,101 +50,58 @@ 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 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 {
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 {
tracing::info!(
"refuse gacha because: expected cost item {cost_count}, found {}",
req.cost_item_count
);
return Ok(DoGachaScRsp {
retcode: Retcode::RetFail.into(),
..Default::default()
});
return Err(NetError::from(Retcode::RetFail));
} else {
// TODO: cost resource
}
let mut gain_item_list: Vec<GainItemInfo> = vec![];
while pull_count > 0 {
while cost_count > 0 {
let pull_result = gacha_model.perform_pull_pool(&pull_time, target_pool);
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,
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(),
);
item_model.add_resource(extra_item_id, extra_item_count);
}
gain_item_list.push(GainItemInfo {
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,
item_id: pull_result.obtained_item_id.value(),
extra_item_id,
extra_item_count,
uid,
num: 1,
..GainItemInfo::default()
});
pull_count -= 1;
gacha_model.gacha_bin.gacha_records.push(pull_result);
cost_count -= 1;
gacha_model.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(generate_all_gacha_info(_player, &pull_time)),
gacha_data: Some(gacha_model.to_client(&pull_time)),
cost_item_count: req.cost_item_count,
})
}
@ -158,122 +114,29 @@ 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 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 item_id = ItemID::new(req.avatar_id);
if let None = item_id {
return Err(NetError::from(Retcode::RetFail));
}
let item_id = item_id.unwrap();
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_type = gacha_model.request_free_agent(target_pool, &item_id);
if item_type == GachaAddedItemType::None {
return Err(NetError::from(Retcode::RetFail));
}
let _ = add_item(role_model, item_model, &item_id, &item_type);
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()
});
}
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;
_session
.notify(construct_sync(role_model, item_model))
.await?;
Ok(GachaFreeAgentScRsp {
retcode: Retcode::RetSucc.into(),
})
@ -291,330 +154,82 @@ 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();
)?;
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 = ItemID::new(req.item_id);
if let None = item_id {
return Err(NetError::from(Retcode::RetFail));
}
let item_id = item_id.unwrap();
Ok(ChooseGachaUpScRsp {
retcode: Retcode::RetFail.into(),
retcode: if gacha_model.choose_gacha_up(target_pool, &item_id) {
Retcode::RetSucc.into()
} else {
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>,
) -> Option<&'conf CharacterGachaPool> {
) -> NetResult<&'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 Some(target_pool);
return Ok(target_pool);
}
}
None
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()
}
}

View file

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

View file

@ -58,6 +58,25 @@ 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,13 +16,22 @@ 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_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()
},
],
}),
})
}
@ -35,22 +44,19 @@ pub async fn on_get_yorozuya_info(
Ok(GetYorozuyaInfoScRsp {
retcode: Retcode::RetSucc.into(),
yorozuya_info: Some(YorozuyaInfo {
odohdljfdlf: vec![1001],
apmojjlcooa: vec![1001],
npgjhahijkb: vec![100001, 100101, 100401, 109901],
eoljpdnjgeg: vec![
Ofhlkjeakif {
nnkcanmllod: 199030,
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(),
kkjlnkehddj: Some(Cgpajijemlj::default()),
..Default::default()
},
Ofhlkjeakif {
nnkcanmllod: 199035,
ggnbpiofdpp: 110103,
kkjlnkehddj: Some(Cgpajijemlj::default()),
..Default::default()
},
],
})
.collect(),
..Default::default()
}),
})

View file

@ -1,12 +1,14 @@
use data::{
event,
tables::{ProcedureConfigID, SectionConfigID, TrainingQuestID},
tables::{HollowQuestID, ProcedureConfigID, SectionConfigID, TrainingQuestID},
};
use super::core::NetError;
use crate::{
logic::{game::*, procedure::ProcedureAction, ELocalPlayType, ENPCInteraction},
logic::{
game::*, procedure::ProcedureAction, EHollowQuestType, ELocalPlayType, ENPCInteraction,
},
net::NetSessionState,
};
@ -182,10 +184,42 @@ 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(),
@ -259,13 +293,79 @@ pub async fn on_enter_section(
})
}
pub async fn on_archive_quests_seen(
pub async fn on_jump_page_system(
_session: &NetSession,
_player: &mut Player,
_req: ArchiveQuestsSeenCsReq,
) -> NetResult<ArchiveQuestsSeenScRsp> {
Ok(ArchiveQuestsSeenScRsp {
_req: JumpPageSystemCsReq,
) -> NetResult<JumpPageSystemScRsp> {
Ok(JumpPageSystemScRsp {
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

@ -0,0 +1,67 @@
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

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

View file

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

View file

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

View file

@ -1,16 +1,16 @@
use std::collections::HashMap;
use crate::logic::BaseProperty;
use proto::InLevelAvatarData;
use proto::AvatarUnitInfo;
pub struct InLevelAvatarDataItem {
pub struct AvatarUnit {
pub avatar_id: u32,
pub mp_property_override: HashMap<BaseProperty, i32>,
}
impl InLevelAvatarDataItem {
pub fn to_client(&self) -> InLevelAvatarData {
InLevelAvatarData {
impl AvatarUnit {
pub fn to_client(&self) -> AvatarUnitInfo {
AvatarUnitInfo {
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::EquippedBuddyData;
use proto::BuddyUnitInfo;
use crate::logic::{BaseProperty, BuddyTeamType};
pub struct EquippedBuddyDataItem {
pub struct BuddyUnit {
pub buddy_id: u32,
pub buddy_team: BuddyTeamType,
pub override_property_map: HashMap<BaseProperty, i32>,
}
impl EquippedBuddyDataItem {
pub fn to_client(&self) -> EquippedBuddyData {
EquippedBuddyData {
impl BuddyUnit {
pub fn to_client(&self) -> BuddyUnitInfo {
BuddyUnitInfo {
buddy_id: self.buddy_id,
r#type: self.buddy_team.to_protocol().into(),
mp_property_override_map: self

View file

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

View file

@ -0,0 +1,74 @@
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,6 +12,7 @@ pub enum ESceneType {
Fight = 3,
Fresh = 4,
MultiFight = 5,
LongFight = 7,
}
#[allow(dead_code)]
@ -26,6 +27,28 @@ 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)]
@ -102,6 +125,17 @@ 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)
@ -122,6 +156,21 @@ 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)
@ -264,7 +313,7 @@ pub enum BaseProperty {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
pub enum BuddyTeamType {
Unknown = 0,
RallyGuidance = 0,
Fighting = 1,
Assisting = 2,
}
@ -272,7 +321,7 @@ pub enum BuddyTeamType {
impl BuddyTeamType {
pub fn to_protocol(&self) -> ::proto::BuddyTeamType {
match *self {
Self::Unknown => ::proto::BuddyTeamType::Unknown,
Self::RallyGuidance => ::proto::BuddyTeamType::RallyGuidance,
Self::Fighting => ::proto::BuddyTeamType::Fighting,
Self::Assisting => ::proto::BuddyTeamType::Assisting,
}

View file

@ -0,0 +1,440 @@
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,20 +1,25 @@
use super::record::*;
use super::stat::*;
use data::gacha;
use data::gacha::gacha_config::*;
use proto::*;
use data::tables::ItemID;
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_bin: GachaModelBin,
pub gacha_status_map: HashMap<String, GachaStatus>,
pub gacha_records: Vec<GachaRecord>,
}
impl Default for GachaModel {
fn default() -> GachaModel {
let result = GachaModel {
gacha_bin: GachaModelBin::default(),
gacha_status_map: HashMap::new(),
gacha_records: vec![],
};
result.post_deserialize()
}
@ -22,19 +27,37 @@ impl Default for GachaModel {
impl GachaModel {
pub fn from_bin(gacha_bin: GachaModelBin) -> Self {
let result = Self { gacha_bin };
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(),
};
result.post_deserialize()
}
pub fn to_bin(&self) -> GachaModelBin {
self.gacha_bin.clone()
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()
}
}
pub fn post_deserialize(mut self) -> GachaModel {
let gachaconf = gacha::global_gacha_config();
for gacha_pool in gachaconf.character_gacha_pool_list.iter() {
let gacha_comp_bin = &mut self.gacha_bin;
let mut gacha_status_map = &mut gacha_comp_bin.gacha_status_map;
let mut gacha_status_map = &mut self.gacha_status_map;
let status_bin = get_or_add(
&mut gacha_status_map,
&gacha_pool.sharing_guarantee_info_category,
@ -97,11 +120,11 @@ impl GachaModel {
&'bin mut self,
pull_time: &DateTime<Local>,
target_pool: &'conf CharacterGachaPool,
) -> GachaRecordBin {
let mut gacha_bin = &mut self.gacha_bin;
) -> GachaRecord {
let (rarity_items, progress_bin, status_bin, probability_model) =
determine_rarity(&gacha_bin, target_pool);
let (category_tag, category) = determine_category(rarity_items, progress_bin, target_pool);
self.determine_rarity(target_pool);
let (category_tag, category) =
self.determine_category(rarity_items, progress_bin, target_pool);
let result = determine_gacha_result(
pull_time,
category,
@ -110,31 +133,21 @@ impl GachaModel {
progress_bin,
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);
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);
result
}
}
fn get_or_add<'a, K: Eq + PartialEq + Hash + Clone, V: Default, S: BuildHasher>(
map: &'a mut HashMap<K, V, S>,
key: &K,
) -> &'a mut V {
if !map.contains_key(key) {
map.insert(key.clone(), V::default());
}
map.get_mut(key).unwrap()
}
fn rand_rarity<'bin, 'conf>(
fn rand_rarity<'bin, 'conf>(
&'bin self,
target_pool: &'conf CharacterGachaPool,
status_bin: &'bin GachaStatusBin,
) -> (
status_bin: &'bin GachaStatus,
) -> (
&'conf GachaAvailableItemsInfo,
&'bin GachaProgressBin,
&'bin GachaProgress,
&'conf ProbabilityModel,
) {
) {
let gachaconf = gacha::global_gacha_config();
let mut rng = thread_rng();
let rarity_status_map = &status_bin.rarity_status_map;
@ -152,19 +165,19 @@ fn rand_rarity<'bin, 'conf>(
}
}
panic!("The user failed to get any items.");
}
}
fn determine_rarity<'bin, 'conf>(
gacha_bin: &'bin GachaModelBin,
fn determine_rarity<'bin, 'conf>(
&'bin self,
target_pool: &'conf CharacterGachaPool,
) -> (
) -> (
&'conf GachaAvailableItemsInfo,
&'bin GachaProgressBin,
&'bin GachaStatusBin,
&'bin GachaProgress,
&'bin GachaStatus,
&'conf ProbabilityModel,
) {
) {
let gachaconf = gacha::global_gacha_config();
let status_bin = gacha_bin
let status_bin = self
.gacha_status_map
.get(&target_pool.sharing_guarantee_info_category)
.expect(&format!(
@ -172,7 +185,7 @@ fn determine_rarity<'bin, 'conf>(
target_pool.sharing_guarantee_info_category
));
let (mut rarity_items, mut progress_bin, mut probability_model) =
rand_rarity(target_pool, &status_bin);
self.rand_rarity(target_pool, &status_bin);
// We should take AdvancedGuarantee discount into consideration.
for discount_tag in target_pool.discount_policy_tags.iter() {
@ -195,8 +208,7 @@ fn determine_rarity<'bin, 'conf>(
{
continue;
}
let higher_progress_bin =
status_bin
let higher_progress_bin = status_bin
.rarity_status_map
.get(&discount.rarity)
.expect(&format!(
@ -223,13 +235,14 @@ fn determine_rarity<'bin, 'conf>(
}
(rarity_items, progress_bin, status_bin, probability_model)
}
}
fn determine_category<'bin, 'conf>(
fn determine_category<'bin, 'conf>(
&'bin self,
rarity_items: &'conf GachaAvailableItemsInfo,
progress_bin: &'bin GachaProgressBin,
progress_bin: &'bin GachaProgress,
target_pool: &'conf CharacterGachaPool,
) -> (String, &'conf GachaCategoryInfo) {
) -> (String, &'conf GachaCategoryInfo) {
let gachaconf = gacha::global_gacha_config();
let mut category_tag_inited = false;
let mut category_tag_result: HashSet<String> = HashSet::new();
@ -311,16 +324,140 @@ fn determine_category<'bin, 'conf>(
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>(
map: &'a mut HashMap<K, V, S>,
key: &K,
) -> &'a mut V {
if !map.contains_key(key) {
map.insert(key.clone(), V::default());
}
map.get_mut(key).unwrap()
}
fn determine_gacha_result<'bin, 'conf>(
pull_time: &DateTime<Local>,
category: &'conf GachaCategoryInfo,
target_pool: &'conf CharacterGachaPool,
status_bin: &'bin GachaStatusBin,
progress_bin: &'bin GachaProgressBin,
status_bin: &'bin GachaStatus,
progress_bin: &'bin GachaProgress,
rarity_items: &'conf GachaAvailableItemsInfo,
) -> GachaRecordBin {
) -> GachaRecord {
let gachaconf = gacha::global_gacha_config();
let item_pool_len = category.item_ids.len() as u32;
let mut item_id: Option<&u32> = None;
@ -361,7 +498,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: u32 = 0;
let mut extra_item_id: Option<ItemID> = None;
let mut extra_item_count: u32 = 0;
for extra_items_policy_tag in rarity_items.extra_items_policy_tags.iter() {
@ -372,134 +509,23 @@ 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 = extra_items_policy.id;
extra_item_id = ItemID::new(extra_items_policy.id);
extra_item_count = extra_items_policy.count;
}
}
let extra_item_bin = GachaExtraItemBin {
extra_item_id,
let extra_resources = match extra_item_id {
Some(item_id) => Some(GachaExtraResources {
extra_item_id: item_id,
extra_item_count,
currently_gained: 0,
}),
None => None,
};
GachaRecordBin {
GachaRecord {
pull_timestamp: pull_time.timestamp(),
obtained_item_id: item_id.clone(),
obtained_item_id: ItemID::new_unchecked(item_id.clone()),
gacha_id: target_pool.gacha_schedule_id.clone(),
progress_map: status_bin.rarity_status_map.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;
extra_resources,
item_type: category.item_type.clone(),
}
}

View file

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

View file

@ -0,0 +1,85 @@
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

@ -0,0 +1,73 @@
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,26 +1,39 @@
use common::util;
use data::tables::{ArchiveBattleQuestID, TrainingQuestID};
use data::tables::{
self, ArchiveBattleQuestID, BattleEventConfigID, HollowQuestID, TrainingQuestID,
};
use proto::{DungeonInfo, DungeonItemData, FightSceneInfo, SceneInfo, WeatherPoolInfo};
use thiserror::Error;
use crate::logic::{
battle::{EquippedBuddyDataItem, InLevelAvatarDataItem, TeamDataItem},
ELocalPlayType, ESceneType, TimePeriodType, WeatherType,
battle::{
drop::FightDropPool,
unit::{AvatarUnit, BuddyUnit},
BuddyParam, DungeonQuestManager, TeamDataItem,
},
BuddyTeamType, EHollowQuestType, ELocalPlayType, ESceneType, TimePeriodType, WeatherType,
};
use super::NapGameMode;
#[derive(Error, Debug)]
pub enum HollowGameError {}
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 struct HollowGame {
pub quest_id: u32,
pub battle_event_id: u32,
pub battle_event_id: BattleEventConfigID,
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 {
@ -38,7 +51,9 @@ impl HollowGame {
weather: WeatherType::SunShine,
start_timestamp: util::cur_timestamp() as i64,
play_type,
team_data: TeamDataItem::new(avatars, 0),
team_data: TeamDataItem::new(avatars, &[]),
fight_drop_pool: FightDropPool::new(template.battle_event_id),
quest_manager: DungeonQuestManager::default(),
})
}
@ -57,9 +72,66 @@ impl HollowGame {
weather: WeatherType::SunShine,
start_timestamp: util::cur_timestamp() as i64,
play_type,
team_data: TeamDataItem::new(avatars, buddy_id),
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(),
})
}
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 {
@ -70,14 +142,16 @@ 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,
battle_event_id: self.battle_event_id.value(),
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()
@ -93,14 +167,15 @@ impl NapGameMode for HollowGame {
.team_data
.avatar_member_list
.iter()
.map(InLevelAvatarDataItem::to_client)
.map(AvatarUnit::to_client)
.collect(),
buddy_list: self
.team_data
.equipped_buddy_list
.iter()
.map(EquippedBuddyDataItem::to_client)
.map(BuddyUnit::to_client)
.collect(),
dungeon_quest_info: Some(self.quest_manager.to_client()),
..Default::default()
})
}

View file

@ -0,0 +1,141 @@
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,20 +1,23 @@
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::{procedure::ProcedureError, ESceneType};
use super::{battle::DungeonQuestError, procedure::ProcedureError, ESceneType};
#[derive(Default)]
pub enum GameInstance {
Frontend(FrontendGame),
Fresh(FreshGame),
Hollow(HollowGame),
LongFight(LongFightGame),
#[default]
Null,
}
@ -29,6 +32,10 @@ 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 {
@ -41,6 +48,7 @@ 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, ProcedureConfigID};
use data::tables::{AvatarBaseID, PostGirlConfigID, ProcedureConfigID};
use proto::{BasicDataModelBin, PlayerBasicInfo};
pub struct BasicDataModel {
@ -8,6 +8,7 @@ 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 {
@ -19,6 +20,7 @@ impl Default for BasicDataModel {
nick_name: None,
frontend_avatar_id: None,
beginner_procedure_id: Some(ProcedureConfigID::new_unchecked(1)),
selected_post_girl_id: None,
}
}
}
@ -37,6 +39,10 @@ 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()
}
}
@ -54,6 +60,7 @@ 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),
@ -75,6 +82,10 @@ 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,11 +1,12 @@
use std::collections::BTreeSet;
use std::collections::{BTreeSet, HashMap};
use data::tables::UnlockConfigID;
use proto::{LockModelBin, UnlockData};
use data::tables::{quick_access_template_tb, QuickFuncID, UnlockConfigID};
use proto::{LockModelBin, QuickAccessData, QuickAccessType, UnlockData};
#[derive(Default)]
pub struct LockModel {
unlock_list: BTreeSet<UnlockConfigID>,
quick_access_list: HashMap<u32, QuickFuncID>,
}
impl LockModel {
@ -16,6 +17,11 @@ 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(),
}
}
@ -27,10 +33,39 @@ 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
@ -38,6 +73,7 @@ impl LockModel {
.into_iter()
.map(|i| i.value())
.collect(),
quick_access_data_list: self.quick_access_to_client(),
..Default::default()
}
}
@ -49,4 +85,28 @@ 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,12 +14,16 @@ 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)]