Compare commits

..

3 commits

Author SHA1 Message Date
99ab5b239d Implement 'avatar add_all' command 2024-08-07 13:35:34 +03:00
84397a847e Implement proper Avatar and Buddy UnitID (Base/Robot), refactor template id
TemplateID::new now returns Result<T, TemplateNotFoundError>
Cleanup code in some of new handlers
2024-08-06 21:42:03 +03:00
28864ff1e4 Teleport Map & Switching Dynamic Wallpaper Support, Gacha optimization (#2)
## Abstract

This PR implements

- Showing Teleports on map
- Quick Menu usage
- Dynamic Wallpaper switching
- Settings UP for Bangboo pool (via CLI)

## Support list

- The support of Teleport with map.
- Unlocking the 3 buttons (Interknow, DMs, Map) as a prerequisite of the previous feature. By pressing 'F' you can also modify the buttons in Quick Menu.
- Changing Dynamic Wallpaper to a specific one instead of being randomly chosen. **Notice that you can't use one's Dynamic Wallpaper if you don't own this agent.**
- Fixed some Gacha bugs, including:
  - Obtained items won't be sent to your bag unless you're pulling the standard banner;
  - Obtained items won't show in bag until relogin;
- Alternative command `gacha up` to choose the UP Bangboo. Start with `gacha up [player_uid]` and follow the guide to fill params.

## Principle

- Player's `UnlockModelBin` stores Quick Menu data, with the player's chosen buttons and their positions.
- Separate Gacha's DTO & Saving model to make it more standardized, and ensure ID validation is always performed.
- Player's bag information is now sync to client appropriately after finishing any gacha operations.
- `gacha up` search for pools with a `chooseable` Category Guarantee Policy, then list included items if user has provided the target pool's `gacha_schedule_id`.

## Known issues

- **Specifically for 1.1 Beta**, teleport may meet these issues leading to a 'black screen', forcing you to restart the client (or use `player kick` command as an alternative).
  - Don't try to teleport to `治安局光映分署`, it won't succeed. This is the 32nd teleport out of the 31 ones defined visible in assets.
  - Don't close the page after opening map. Teleport to somewhere.
- `gacha up` command is an alternative to in-game operation. You still can not see any bangboos in the collection when choosing Bangboo.

Co-authored-by: YYHEggEgg <53960525+YYHEggEgg@users.noreply.github.com>
Reviewed-on: NewEriduPubSec/JaneDoe-ZS#2
Co-authored-by: YYHEggEgg <yyheggegg@xeondev.com>
Co-committed-by: YYHEggEgg <yyheggegg@xeondev.com>
2024-08-06 17:15:04 +00:00
53 changed files with 11210 additions and 171 deletions

View file

@ -0,0 +1,266 @@
[
{
"ID": 50001,
"BuddyType": 1,
"ECHNDHDIIAC": 0,
"OHLKAFPBJHD": 0,
"GDDJBFHBJNK": 1,
"LAFKHMCKNIO": "Bangboo_Name_en_50001",
"DIIDBBGLDOL": "Bangboo_Name_50001",
"PCNEIBEDMCO": "BK_Eous",
"HIMPMHKGGIC": "",
"ANDDIMCDBME": 9831098947874880184
},
{
"ID": 53001,
"BuddyType": 2,
"ECHNDHDIIAC": 0,
"OHLKAFPBJHD": 0,
"GDDJBFHBJNK": 1,
"LAFKHMCKNIO": "Bangboo_Name_en_53001",
"DIIDBBGLDOL": "Bangboo_Name_53001",
"PCNEIBEDMCO": "BK_Eous",
"HIMPMHKGGIC": "",
"ANDDIMCDBME": 8017298617212498953
},
{
"ID": 53002,
"BuddyType": 2,
"ECHNDHDIIAC": 0,
"OHLKAFPBJHD": 0,
"GDDJBFHBJNK": 1,
"LAFKHMCKNIO": "Bangboo_Name_en_53002",
"DIIDBBGLDOL": "Bangboo_Name_53002",
"PCNEIBEDMCO": "BK_Eous",
"HIMPMHKGGIC": "",
"ANDDIMCDBME": 15759244567804428825
},
{
"ID": 53003,
"BuddyType": 2,
"ECHNDHDIIAC": 0,
"OHLKAFPBJHD": 0,
"GDDJBFHBJNK": 1,
"LAFKHMCKNIO": "Bangboo_Name_en_53003",
"DIIDBBGLDOL": "Bangboo_Name_53003",
"PCNEIBEDMCO": "BK_Eous",
"HIMPMHKGGIC": "",
"ANDDIMCDBME": 793541769103470830
},
{
"ID": 53004,
"BuddyType": 2,
"ECHNDHDIIAC": 0,
"OHLKAFPBJHD": 0,
"GDDJBFHBJNK": 1,
"LAFKHMCKNIO": "Bangboo_Name_en_53004",
"DIIDBBGLDOL": "Bangboo_Name_53004",
"PCNEIBEDMCO": "BK_Eous",
"HIMPMHKGGIC": "",
"ANDDIMCDBME": 15333283710424173893
},
{
"ID": 53005,
"BuddyType": 2,
"ECHNDHDIIAC": 0,
"OHLKAFPBJHD": 0,
"GDDJBFHBJNK": 1,
"LAFKHMCKNIO": "Bangboo_Name_en_53005",
"DIIDBBGLDOL": "Bangboo_Name_53005",
"PCNEIBEDMCO": "BK_Eous",
"HIMPMHKGGIC": "",
"ANDDIMCDBME": 6625485447473768309
},
{
"ID": 53006,
"BuddyType": 2,
"ECHNDHDIIAC": 0,
"OHLKAFPBJHD": 0,
"GDDJBFHBJNK": 1,
"LAFKHMCKNIO": "Bangboo_Name_en_53006",
"DIIDBBGLDOL": "Bangboo_Name_53006",
"PCNEIBEDMCO": "BK_Eous",
"HIMPMHKGGIC": "",
"ANDDIMCDBME": 12516213533679239810
},
{
"ID": 53007,
"BuddyType": 2,
"ECHNDHDIIAC": 0,
"OHLKAFPBJHD": 0,
"GDDJBFHBJNK": 1,
"LAFKHMCKNIO": "Bangboo_Name_en_53007",
"DIIDBBGLDOL": "Bangboo_Name_53007",
"PCNEIBEDMCO": "BK_Eous",
"HIMPMHKGGIC": "",
"ANDDIMCDBME": 1205030983673817181
},
{
"ID": 53008,
"BuddyType": 2,
"ECHNDHDIIAC": 0,
"OHLKAFPBJHD": 0,
"GDDJBFHBJNK": 1,
"LAFKHMCKNIO": "Bangboo_Name_en_53008",
"DIIDBBGLDOL": "Bangboo_Name_53008",
"PCNEIBEDMCO": "BK_Eous",
"HIMPMHKGGIC": "",
"ANDDIMCDBME": 825481331324073038
},
{
"ID": 53009,
"BuddyType": 2,
"ECHNDHDIIAC": 0,
"OHLKAFPBJHD": 0,
"GDDJBFHBJNK": 1,
"LAFKHMCKNIO": "Bangboo_Name_en_53009",
"DIIDBBGLDOL": "Bangboo_Name_53009",
"PCNEIBEDMCO": "BK_Eous",
"HIMPMHKGGIC": "",
"ANDDIMCDBME": 1109142289374885163
},
{
"ID": 53010,
"BuddyType": 2,
"ECHNDHDIIAC": 0,
"OHLKAFPBJHD": 0,
"GDDJBFHBJNK": 1,
"LAFKHMCKNIO": "Bangboo_Name_en_53010",
"DIIDBBGLDOL": "Bangboo_Name_53010",
"PCNEIBEDMCO": "BK_Eous",
"HIMPMHKGGIC": "",
"ANDDIMCDBME": 7654740197435466664
},
{
"ID": 53011,
"BuddyType": 2,
"ECHNDHDIIAC": 0,
"OHLKAFPBJHD": 0,
"GDDJBFHBJNK": 1,
"LAFKHMCKNIO": "Bangboo_Name_en_53011",
"DIIDBBGLDOL": "Bangboo_Name_53011",
"PCNEIBEDMCO": "BK_Eous",
"HIMPMHKGGIC": "",
"ANDDIMCDBME": 13043263780191768021
},
{
"ID": 53012,
"BuddyType": 2,
"ECHNDHDIIAC": 0,
"OHLKAFPBJHD": 0,
"GDDJBFHBJNK": 1,
"LAFKHMCKNIO": "Bangboo_Name_en_53012",
"DIIDBBGLDOL": "Bangboo_Name_53012",
"PCNEIBEDMCO": "BK_Eous",
"HIMPMHKGGIC": "",
"ANDDIMCDBME": 2166541009547495725
},
{
"ID": 54001,
"BuddyType": 2,
"ECHNDHDIIAC": 0,
"OHLKAFPBJHD": 0,
"GDDJBFHBJNK": 1,
"LAFKHMCKNIO": "Bangboo_Name_en_54001",
"DIIDBBGLDOL": "Bangboo_Name_54001",
"PCNEIBEDMCO": "BK_Eous",
"HIMPMHKGGIC": "",
"ANDDIMCDBME": 12126171553945369848
},
{
"ID": 54002,
"BuddyType": 2,
"ECHNDHDIIAC": 3,
"OHLKAFPBJHD": 0,
"GDDJBFHBJNK": 1,
"LAFKHMCKNIO": "Bangboo_Name_en_54002",
"DIIDBBGLDOL": "Bangboo_Name_54002",
"PCNEIBEDMCO": "BK_Eous",
"HIMPMHKGGIC": "",
"ANDDIMCDBME": 13558320022686804846
},
{
"ID": 54003,
"BuddyType": 2,
"ECHNDHDIIAC": 0,
"OHLKAFPBJHD": 0,
"GDDJBFHBJNK": 1,
"LAFKHMCKNIO": "Bangboo_Name_en_54003",
"DIIDBBGLDOL": "Bangboo_Name_54003",
"PCNEIBEDMCO": "BK_Eous",
"HIMPMHKGGIC": "",
"ANDDIMCDBME": 9057893760084610210
},
{
"ID": 54004,
"BuddyType": 2,
"ECHNDHDIIAC": 2,
"OHLKAFPBJHD": 0,
"GDDJBFHBJNK": 1,
"LAFKHMCKNIO": "Bangboo_Name_en_54004",
"DIIDBBGLDOL": "Bangboo_Name_54004",
"PCNEIBEDMCO": "BK_Eous",
"HIMPMHKGGIC": "",
"ANDDIMCDBME": 7098007717545413607
},
{
"ID": 54005,
"BuddyType": 2,
"ECHNDHDIIAC": 1,
"OHLKAFPBJHD": 0,
"GDDJBFHBJNK": 1,
"LAFKHMCKNIO": "Bangboo_Name_en_54005",
"DIIDBBGLDOL": "Bangboo_Name_54005",
"PCNEIBEDMCO": "BK_Eous",
"HIMPMHKGGIC": "",
"ANDDIMCDBME": 7896901026978333696
},
{
"ID": 54006,
"BuddyType": 2,
"ECHNDHDIIAC": 0,
"OHLKAFPBJHD": 0,
"GDDJBFHBJNK": 1,
"LAFKHMCKNIO": "Bangboo_Name_en_54006",
"DIIDBBGLDOL": "Bangboo_Name_54006",
"PCNEIBEDMCO": "BK_Eous",
"HIMPMHKGGIC": "",
"ANDDIMCDBME": 15413390933002967244
},
{
"ID": 54008,
"BuddyType": 2,
"ECHNDHDIIAC": 0,
"OHLKAFPBJHD": 0,
"GDDJBFHBJNK": 1,
"LAFKHMCKNIO": "Bangboo_Name_en_54008",
"DIIDBBGLDOL": "Bangboo_Name_54008",
"PCNEIBEDMCO": "BK_Eous",
"HIMPMHKGGIC": "",
"ANDDIMCDBME": 13745481736816990381
},
{
"ID": 54009,
"BuddyType": 2,
"ECHNDHDIIAC": 0,
"OHLKAFPBJHD": 0,
"GDDJBFHBJNK": 1,
"LAFKHMCKNIO": "Bangboo_Name_en_54009",
"DIIDBBGLDOL": "Bangboo_Name_54009",
"PCNEIBEDMCO": "BK_Eous",
"HIMPMHKGGIC": "",
"ANDDIMCDBME": 4565374391606395253
},
{
"ID": 54013,
"BuddyType": 2,
"ECHNDHDIIAC": 0,
"OHLKAFPBJHD": 0,
"GDDJBFHBJNK": 1,
"LAFKHMCKNIO": "Bangboo_Name_en_54013",
"DIIDBBGLDOL": "Bangboo_Name_54013",
"PCNEIBEDMCO": "BK_Eous",
"HIMPMHKGGIC": "",
"ANDDIMCDBME": 4076165347655962714
}
]

View file

@ -0,0 +1,519 @@
[
{
"RobotBuddyID": 19053001,
"BuddyID": 53001,
"CharLevel": 20,
"CharUpgradeLevel": 2,
"CharStar": 1,
"GHGKBFEKDND": 0,
"ACGEEJMKBBO": 1053001,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
},
{
"RobotBuddyID": 19053002,
"BuddyID": 53002,
"CharLevel": 20,
"CharUpgradeLevel": 2,
"CharStar": 1,
"GHGKBFEKDND": 0,
"ACGEEJMKBBO": 1053002,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
},
{
"RobotBuddyID": 19053003,
"BuddyID": 53003,
"CharLevel": 20,
"CharUpgradeLevel": 2,
"CharStar": 1,
"GHGKBFEKDND": 0,
"ACGEEJMKBBO": 1053003,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
},
{
"RobotBuddyID": 19053004,
"BuddyID": 53004,
"CharLevel": 20,
"CharUpgradeLevel": 2,
"CharStar": 1,
"GHGKBFEKDND": 0,
"ACGEEJMKBBO": 1053004,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
},
{
"RobotBuddyID": 19053005,
"BuddyID": 53005,
"CharLevel": 20,
"CharUpgradeLevel": 2,
"CharStar": 1,
"GHGKBFEKDND": 0,
"ACGEEJMKBBO": 1053005,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
},
{
"RobotBuddyID": 19053006,
"BuddyID": 53006,
"CharLevel": 20,
"CharUpgradeLevel": 2,
"CharStar": 1,
"GHGKBFEKDND": 0,
"ACGEEJMKBBO": 1053006,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
},
{
"RobotBuddyID": 19053007,
"BuddyID": 53007,
"CharLevel": 20,
"CharUpgradeLevel": 2,
"CharStar": 1,
"GHGKBFEKDND": 0,
"ACGEEJMKBBO": 1053007,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
},
{
"RobotBuddyID": 19053008,
"BuddyID": 53008,
"CharLevel": 20,
"CharUpgradeLevel": 2,
"CharStar": 1,
"GHGKBFEKDND": 0,
"ACGEEJMKBBO": 1053008,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
},
{
"RobotBuddyID": 19053009,
"BuddyID": 53009,
"CharLevel": 20,
"CharUpgradeLevel": 2,
"CharStar": 1,
"GHGKBFEKDND": 0,
"ACGEEJMKBBO": 1053009,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
},
{
"RobotBuddyID": 19053010,
"BuddyID": 53010,
"CharLevel": 20,
"CharUpgradeLevel": 2,
"CharStar": 1,
"GHGKBFEKDND": 0,
"ACGEEJMKBBO": 1053010,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
},
{
"RobotBuddyID": 19053011,
"BuddyID": 53011,
"CharLevel": 20,
"CharUpgradeLevel": 2,
"CharStar": 1,
"GHGKBFEKDND": 0,
"ACGEEJMKBBO": 1053011,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
},
{
"RobotBuddyID": 19053012,
"BuddyID": 53012,
"CharLevel": 20,
"CharUpgradeLevel": 2,
"CharStar": 1,
"GHGKBFEKDND": 0,
"ACGEEJMKBBO": 1053012,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
},
{
"RobotBuddyID": 19054001,
"BuddyID": 54001,
"CharLevel": 20,
"CharUpgradeLevel": 2,
"CharStar": 1,
"GHGKBFEKDND": 0,
"ACGEEJMKBBO": 1054001,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
},
{
"RobotBuddyID": 19054002,
"BuddyID": 54002,
"CharLevel": 20,
"CharUpgradeLevel": 2,
"CharStar": 1,
"GHGKBFEKDND": 0,
"ACGEEJMKBBO": 1054002,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
},
{
"RobotBuddyID": 19054003,
"BuddyID": 54003,
"CharLevel": 20,
"CharUpgradeLevel": 2,
"CharStar": 1,
"GHGKBFEKDND": 0,
"ACGEEJMKBBO": 1054003,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
},
{
"RobotBuddyID": 19054004,
"BuddyID": 54004,
"CharLevel": 20,
"CharUpgradeLevel": 2,
"CharStar": 1,
"GHGKBFEKDND": 0,
"ACGEEJMKBBO": 1054004,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
},
{
"RobotBuddyID": 19054005,
"BuddyID": 54005,
"CharLevel": 20,
"CharUpgradeLevel": 2,
"CharStar": 1,
"GHGKBFEKDND": 0,
"ACGEEJMKBBO": 1054005,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
},
{
"RobotBuddyID": 19054006,
"BuddyID": 54006,
"CharLevel": 20,
"CharUpgradeLevel": 2,
"CharStar": 1,
"GHGKBFEKDND": 0,
"ACGEEJMKBBO": 1054006,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
},
{
"RobotBuddyID": 19054008,
"BuddyID": 54008,
"CharLevel": 20,
"CharUpgradeLevel": 2,
"CharStar": 1,
"GHGKBFEKDND": 0,
"ACGEEJMKBBO": 1054008,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
},
{
"RobotBuddyID": 19054009,
"BuddyID": 54009,
"CharLevel": 20,
"CharUpgradeLevel": 2,
"CharStar": 1,
"GHGKBFEKDND": 0,
"ACGEEJMKBBO": 1054009,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
},
{
"RobotBuddyID": 19054013,
"BuddyID": 54013,
"CharLevel": 20,
"CharUpgradeLevel": 2,
"CharStar": 1,
"GHGKBFEKDND": 0,
"ACGEEJMKBBO": 1054013,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
},
{
"RobotBuddyID": 101000404,
"BuddyID": 53006,
"CharLevel": 1,
"CharUpgradeLevel": 1,
"CharStar": 1,
"GHGKBFEKDND": 53006,
"ACGEEJMKBBO": 0,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
},
{
"RobotBuddyID": 101000504,
"BuddyID": 54005,
"CharLevel": 6,
"CharUpgradeLevel": 1,
"CharStar": 1,
"GHGKBFEKDND": 0,
"ACGEEJMKBBO": 0,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
},
{
"RobotBuddyID": 1000010104,
"BuddyID": 54005,
"CharLevel": 9,
"CharUpgradeLevel": 1,
"CharStar": 1,
"GHGKBFEKDND": 0,
"ACGEEJMKBBO": 0,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
},
{
"RobotBuddyID": 1000010204,
"BuddyID": 54005,
"CharLevel": 9,
"CharUpgradeLevel": 1,
"CharStar": 1,
"GHGKBFEKDND": 0,
"ACGEEJMKBBO": 0,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
},
{
"RobotBuddyID": 1000010304,
"BuddyID": 54005,
"CharLevel": 9,
"CharUpgradeLevel": 1,
"CharStar": 1,
"GHGKBFEKDND": 0,
"ACGEEJMKBBO": 0,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
},
{
"RobotBuddyID": 1000010604,
"BuddyID": 54005,
"CharLevel": 11,
"CharUpgradeLevel": 2,
"CharStar": 1,
"GHGKBFEKDND": 0,
"ACGEEJMKBBO": 0,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
},
{
"RobotBuddyID": 1000010704,
"BuddyID": 54005,
"CharLevel": 12,
"CharUpgradeLevel": 2,
"CharStar": 1,
"GHGKBFEKDND": 0,
"ACGEEJMKBBO": 0,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
},
{
"RobotBuddyID": 1000010804,
"BuddyID": 54005,
"CharLevel": 13,
"CharUpgradeLevel": 2,
"CharStar": 1,
"GHGKBFEKDND": 0,
"ACGEEJMKBBO": 0,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
},
{
"RobotBuddyID": 1001000404,
"BuddyID": 54005,
"CharLevel": 8,
"CharUpgradeLevel": 1,
"CharStar": 1,
"GHGKBFEKDND": 0,
"ACGEEJMKBBO": 0,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
},
{
"RobotBuddyID": 1001010104,
"BuddyID": 54005,
"CharLevel": 10,
"CharUpgradeLevel": 2,
"CharStar": 1,
"GHGKBFEKDND": 0,
"ACGEEJMKBBO": 0,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
},
{
"RobotBuddyID": 1001010204,
"BuddyID": 54005,
"CharLevel": 15,
"CharUpgradeLevel": 2,
"CharStar": 1,
"GHGKBFEKDND": 0,
"ACGEEJMKBBO": 0,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
},
{
"RobotBuddyID": 1001020204,
"BuddyID": 54002,
"CharLevel": 24,
"CharUpgradeLevel": 3,
"CharStar": 1,
"GHGKBFEKDND": 0,
"ACGEEJMKBBO": 0,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
},
{
"RobotBuddyID": 1001020304,
"BuddyID": 54002,
"CharLevel": 24,
"CharUpgradeLevel": 3,
"CharStar": 1,
"GHGKBFEKDND": 0,
"ACGEEJMKBBO": 0,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
},
{
"RobotBuddyID": 1001020404,
"BuddyID": 54002,
"CharLevel": 26,
"CharUpgradeLevel": 3,
"CharStar": 1,
"GHGKBFEKDND": 0,
"ACGEEJMKBBO": 0,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
},
{
"RobotBuddyID": 1001020904,
"BuddyID": 54002,
"CharLevel": 28,
"CharUpgradeLevel": 3,
"CharStar": 1,
"GHGKBFEKDND": 0,
"ACGEEJMKBBO": 0,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
},
{
"RobotBuddyID": 1001021104,
"BuddyID": 54009,
"CharLevel": 32,
"CharUpgradeLevel": 4,
"CharStar": 1,
"GHGKBFEKDND": 0,
"ACGEEJMKBBO": 0,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
},
{
"RobotBuddyID": 1001021204,
"BuddyID": 54009,
"CharLevel": 33,
"CharUpgradeLevel": 4,
"CharStar": 1,
"GHGKBFEKDND": 0,
"ACGEEJMKBBO": 0,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
},
{
"RobotBuddyID": 1001030204,
"BuddyID": 54005,
"CharLevel": 34,
"CharUpgradeLevel": 4,
"CharStar": 1,
"GHGKBFEKDND": 0,
"ACGEEJMKBBO": 0,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
},
{
"RobotBuddyID": 1001030304,
"BuddyID": 54004,
"CharLevel": 35,
"CharUpgradeLevel": 4,
"CharStar": 1,
"GHGKBFEKDND": 0,
"ACGEEJMKBBO": 0,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
},
{
"RobotBuddyID": 1001030404,
"BuddyID": 54004,
"CharLevel": 36,
"CharUpgradeLevel": 4,
"CharStar": 1,
"GHGKBFEKDND": 0,
"ACGEEJMKBBO": 0,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
},
{
"RobotBuddyID": 1001030504,
"BuddyID": 54004,
"CharLevel": 38,
"CharUpgradeLevel": 4,
"CharStar": 1,
"GHGKBFEKDND": 0,
"ACGEEJMKBBO": 0,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
},
{
"RobotBuddyID": 1009010104,
"BuddyID": 53006,
"CharLevel": 25,
"CharUpgradeLevel": 3,
"CharStar": 1,
"GHGKBFEKDND": 0,
"ACGEEJMKBBO": 0,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
},
{
"RobotBuddyID": 1009010204,
"BuddyID": 53006,
"CharLevel": 35,
"CharUpgradeLevel": 4,
"CharStar": 1,
"GHGKBFEKDND": 0,
"ACGEEJMKBBO": 0,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
},
{
"RobotBuddyID": 1009010304,
"BuddyID": 53006,
"CharLevel": 45,
"CharUpgradeLevel": 5,
"CharStar": 1,
"GHGKBFEKDND": 0,
"ACGEEJMKBBO": 0,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
},
{
"RobotBuddyID": 1009010404,
"BuddyID": 53006,
"CharLevel": 55,
"CharUpgradeLevel": 6,
"CharStar": 1,
"GHGKBFEKDND": 0,
"ACGEEJMKBBO": 0,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
},
{
"RobotBuddyID": 1009010504,
"BuddyID": 53006,
"CharLevel": 60,
"CharUpgradeLevel": 6,
"CharStar": 1,
"GHGKBFEKDND": 0,
"ACGEEJMKBBO": 0,
"INPPGCADDPO": 0,
"FMPOMKKHELJ": 0
}
]

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,7 @@ use serde::Deserialize;
use super::BattleEventConfigID;
template_id!(ArchiveBattleQuest u32 id);
template_id!(ArchiveBattleQuest id);
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]

View file

@ -1,6 +1,6 @@
use serde::Deserialize;
template_id!(ArchiveFileQuest u32 id);
template_id!(ArchiveFileQuest id);
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]

View file

@ -1,6 +1,6 @@
use serde::Deserialize;
template_id!(AvatarBase u32 id);
template_id!(AvatarBase id);
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]

View file

@ -2,7 +2,7 @@ use serde::Deserialize;
use super::OnceRewardID;
template_id!(BattleEventConfig u32 id);
template_id!(BattleEventConfig id);
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]

View file

@ -2,7 +2,7 @@ use serde::Deserialize;
use super::BattleEventConfigID;
template_id!(BattleGroupConfig u32 id);
template_id!(BattleGroupConfig id);
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]

View file

@ -0,0 +1,11 @@
use serde::Deserialize;
template_id!(BuddyBase id);
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct BuddyBaseTemplate {
#[serde(rename = "ID")]
pub id: BuddyBaseID,
pub buddy_type: u32,
}

View file

@ -1,6 +1,6 @@
use serde::Deserialize;
template_id!(HollowConfig u32 id);
template_id!(HollowConfig id);
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]

View file

@ -1,6 +1,6 @@
use serde::Deserialize;
template_id!(HollowQuest u32 id);
template_id!(HollowQuest id);
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]

View file

@ -1,6 +1,6 @@
use serde::Deserialize;
template_id!(Item u32 id);
template_id!(Item id);
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]

View file

@ -1,6 +1,6 @@
use serde::Deserialize;
template_id!(MainCityBgmConfig u32 id);
template_id!(MainCityBgmConfig id);
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]

View file

@ -1,6 +1,6 @@
use serde::Deserialize;
template_id!(MainCityDefaultObject u32 tag_id);
template_id!(MainCityDefaultObject tag_id);
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]

View file

@ -1,6 +1,6 @@
use serde::Deserialize;
template_id!(MainCityObject u32 tag_id);
template_id!(MainCityObject tag_id);
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]

View file

@ -1,29 +1,31 @@
use paste::paste;
use std::sync::OnceLock;
use thiserror::Error;
use super::DataLoadError;
#[derive(Debug, Error)]
#[error("template with id {0} not found")]
pub struct TemplateNotFoundError(pub u32);
macro_rules! template_id {
($type_name:ident $underlying_type:ident $id_field:ident) => {
($type_name:ident $id_field:ident) => {
::paste::paste! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, ::serde::Deserialize, ::serde::Serialize)]
pub struct [<$type_name ID>]($underlying_type);
pub struct [<$type_name ID>](u32);
impl [<$type_name ID>] {
pub fn new(id: $underlying_type) -> Option<Self> {
if crate::tables::[<$type_name:snake _template_tb>]::iter().any(|tmpl| tmpl.$id_field.value() == id) {
Some(Self(id))
}
else {
None
}
pub fn new(id: u32) -> Result<Self, super::TemplateNotFoundError> {
crate::tables::[<$type_name:snake _template_tb>]::iter()
.any(|tmpl| tmpl.$id_field.value() == id)
.then_some(Self(id)).ok_or(super::TemplateNotFoundError(id))
}
pub fn new_unchecked(id: $underlying_type) -> Self {
pub const fn new_unchecked(id: u32) -> Self {
Self(id)
}
pub fn value(&self) -> $underlying_type {
pub fn value(&self) -> u32 {
self.0
}
@ -76,6 +78,7 @@ macro_rules! template_tables {
template_tables! {
AvatarBaseTemplate;
BuddyBaseTemplate;
UnlockConfigTemplate;
SectionConfigTemplate;
ProcedureConfigTemplate;
@ -98,4 +101,6 @@ template_tables! {
QuickFuncTemplate;
TeleportConfigTemplate;
ItemTemplate;
RobotConfigTemplate;
RobotBuddyConfigTemplate;
}

View file

@ -1,6 +1,6 @@
use serde::Deserialize;
template_id!(OnceReward u32 reward_id);
template_id!(OnceReward reward_id);
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]

View file

@ -1,6 +1,6 @@
use serde::Deserialize;
template_id!(PostGirlConfig u32 id);
template_id!(PostGirlConfig id);
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]

View file

@ -1,6 +1,6 @@
use serde::Deserialize;
template_id!(ProcedureConfig u32 procedure_id);
template_id!(ProcedureConfig procedure_id);
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]

View file

@ -1,6 +1,6 @@
use serde::Deserialize;
template_id!(QuickAccess u32 quick_func_id);
template_id!(QuickAccess quick_func_id);
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]

View file

@ -1,6 +1,6 @@
use serde::Deserialize;
template_id!(QuickFunc u32 btn_id);
template_id!(QuickFunc btn_id);
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]

View file

@ -0,0 +1,17 @@
use serde::Deserialize;
use super::BuddyBaseID;
template_id!(RobotBuddyConfig robot_id);
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct RobotBuddyConfigTemplate {
#[serde(rename = "RobotBuddyID")]
pub robot_id: RobotBuddyConfigID,
#[serde(rename = "BuddyID")]
pub buddy_id: BuddyBaseID,
pub char_level: u32,
pub char_upgrade_level: u32,
pub char_star: u32,
}

View file

@ -0,0 +1,24 @@
use serde::Deserialize;
use super::{AvatarBaseID, WeaponID};
template_id!(RobotConfig robot_id);
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct RobotConfigTemplate {
#[serde(rename = "RobotID")]
pub robot_id: RobotConfigID,
#[serde(rename = "CharacterID")]
pub character_id: AvatarBaseID,
pub char_level: u32,
pub char_upgrade_level: u32,
pub char_star: u32,
pub skill_levels: Vec<u32>,
pub talent_level: u32,
#[serde(rename = "WeaponID")]
pub weapon_id: WeaponID,
pub weapon_level: u32,
pub weapon_upgrade_level: u32,
pub weapon_refine_level: u32,
}

View file

@ -1,6 +1,6 @@
use serde::Deserialize;
template_id!(SectionConfig u32 section_id);
template_id!(SectionConfig section_id);
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]

View file

@ -2,7 +2,7 @@ use std::u32;
use serde::Deserialize;
template_id!(SubAreaData u32 area_id);
template_id!(SubAreaData area_id);
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]

View file

@ -1,6 +1,6 @@
use serde::Deserialize;
template_id!(TeleportConfig i32 teleport_id);
template_id!(TeleportConfig teleport_id);
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]

View file

@ -2,7 +2,7 @@ use serde::Deserialize;
use super::BattleEventConfigID;
template_id!(TrainingQuest u32 id);
template_id!(TrainingQuest id);
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]

View file

@ -1,6 +1,6 @@
use serde::Deserialize;
template_id!(UnlockConfig i32 id);
template_id!(UnlockConfig id);
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]

View file

@ -2,7 +2,7 @@ use std::u32;
use serde::Deserialize;
template_id!(VariableData u32 id);
template_id!(VariableData id);
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]

View file

@ -1,6 +1,6 @@
use serde::Deserialize;
template_id!(Weapon u32 item_id);
template_id!(Weapon item_id);
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]

View file

@ -1,4 +1,4 @@
use data::tables::AvatarBaseID;
use data::tables::{self, AvatarBaseID};
use proto::{AddAvatarPerformType, AddAvatarScNotify, PlayerSyncScNotify};
use crate::ServerState;
@ -17,7 +17,7 @@ pub async fn add(
let uid = args[0].parse::<u32>()?;
let avatar_id = args[1].parse::<u32>()?;
let Some(avatar_id) = AvatarBaseID::new(avatar_id) else {
let Ok(avatar_id) = AvatarBaseID::new(avatar_id) else {
return Ok(format!("avatar with id {avatar_id} doesn't exist"));
};
@ -55,3 +55,62 @@ pub async fn add(
"successfully added avatar {avatar_id} to player {uid}"
))
}
pub async fn add_all(
args: ArgSlice<'_>,
state: &ServerState,
) -> Result<String, Box<dyn std::error::Error>> {
const USAGE: &str = "Usage: avatar add_all [player_uid]";
if args.len() != 1 {
return Ok(USAGE.to_string());
}
let uid = args[0].parse::<u32>()?;
let Some(player_lock) = state.player_mgr.get_player(uid).await else {
return Ok(String::from("player not found"));
};
let (session_id, avatar_sync, avatar_id_list) = {
let mut player = player_lock.lock().await;
let avatar_id_list = tables::avatar_base_template_tb::iter()
.filter(|tmpl| tmpl.id.value() < 2000 && !player.role_model.has_avatar(tmpl.id))
.map(|tmpl| tmpl.id)
.collect::<Vec<_>>();
avatar_id_list
.iter()
.for_each(|id| player.role_model.add_avatar(*id));
(
player.current_session_id(),
player.role_model.avatar_sync(),
avatar_id_list,
)
};
if let Some(session) = session_id.map(|id| state.session_mgr.get(id)).flatten() {
for id in avatar_id_list {
session
.notify(AddAvatarScNotify {
avatar_id: id.value(),
perform_type: AddAvatarPerformType::ShowPopup.into(),
..Default::default()
})
.await?;
}
session
.notify(PlayerSyncScNotify {
avatar: Some(avatar_sync),
..Default::default()
})
.await?;
} else {
state.player_mgr.save_and_remove(uid).await;
}
Ok(format!("successfully added all avatars to player {uid}"))
}

View file

@ -17,7 +17,7 @@ pub async fn add_weapon(
let uid = args[0].parse::<u32>()?;
let weapon_id = args[1].parse::<u32>()?;
let Some(weapon_id) = WeaponID::new(weapon_id) else {
let Ok(weapon_id) = WeaponID::new(weapon_id) else {
return Ok(format!("weapon with id {weapon_id} doesn't exist"));
};

View file

@ -86,9 +86,10 @@ impl CommandManager {
player::avatar "[player_uid] [avatar_id]" "changes player avatar for main city";
player::nickname "[player_uid] [nickname]" "changes player nickname";
player::procedure "[player_uid] [procedure_id]" "changes current beginner procedure id, parameter -1 can be used for skipping it";
avatar::add "[player_uid] [avatar_id]" "gives avatar with specified id to player";
item::add_weapon "[player_uid] [weapon_id]" "gives weapon with specified id to player";
player::kick "[player_uid] [reason]" "kick the specified player (reason is optional)";
avatar::add "[player_uid] [avatar_id]" "gives avatar with specified id to player";
avatar::add_all "[player_uid]" "gives all avatars to player";
item::add_weapon "[player_uid] [weapon_id]" "gives weapon with specified id to player";
gacha::up "[player_uid]" "start a gacha UP setting guide (available for Bangboo pool)";
}
}

View file

@ -17,7 +17,7 @@ pub async fn avatar(
let uid = args[0].parse::<u32>()?;
let avatar_id = args[1].parse::<u32>()?;
let Some(avatar_id) = AvatarBaseID::new(avatar_id) else {
let Ok(avatar_id) = AvatarBaseID::new(avatar_id) else {
return Ok(format!("avatar with id {avatar_id} doesn't exist"));
};
@ -103,7 +103,7 @@ pub async fn procedure(
let procedure_id = args[1].parse::<i32>()?;
let procedure_id = match procedure_id {
1.. => ProcedureConfigID::new(procedure_id as u32),
1.. => ProcedureConfigID::new(procedure_id as u32).ok(),
_ => None,
};

View file

@ -1,8 +1,8 @@
use crate::logic::{EOperator, ESystem};
use crate::logic::{game::LogicError, EOperator, ESystem};
use super::*;
use data::tables::{self, QuickFuncID};
use data::tables::{self, PostGirlConfigID, QuickFuncID};
pub async fn on_get_tips_info(
_session: &NetSession,
@ -45,7 +45,7 @@ pub async fn on_get_client_systems_info(
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())
.map(|template| template.teleport_id.value() as i32)
.collect(),
..Default::default()
}),
@ -196,22 +196,20 @@ pub async fn on_interact_with_scene_object(
}
pub async fn on_mod_quick_menu(
_session: &NetSession,
_player: &mut Player,
_req: ModQuickMenuCsReq,
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
req.quick_access_data_list.iter().for_each(|data| {
player
.lock_model
.mod_quick_access(data.quick_access_index, QuickFuncID::new(data.btn_id)),
);
}
_session
.mod_quick_access(data.quick_access_index, QuickFuncID::new(data.btn_id).ok())
});
session
.notify(PlayerSyncScNotify {
client_systems_sync: Some(ClientSystemsSync {
quick_access_data_list: _player.lock_model.quick_access_to_client(),
quick_access_data_list: player.lock_model.quick_access_to_client(),
..Default::default()
}),
..Default::default()
@ -225,20 +223,19 @@ pub async fn on_mod_quick_menu(
}
pub async fn on_change_post_girl(
_session: &NetSession,
_player: &mut Player,
_req: ChangePostGirlCsReq,
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
let post_girl_id = *req
.new_selected_post_girl_id_list
.get(0)
.ok_or(Retcode::RetFail)?;
let post_girl_id = PostGirlConfigID::new(post_girl_id).map_err(LogicError::from)?;
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 {
@ -250,14 +247,9 @@ pub async fn on_change_post_girl(
..Default::default()
})
.await?;
Ok(ChangePostGirlScRsp {
retcode: Retcode::RetSucc.into(),
..Default::default()
})
}
None => Ok(ChangePostGirlScRsp {
retcode: Retcode::RetFail.into(),
..Default::default()
}),
}
}

View file

@ -123,7 +123,7 @@ pub async fn on_gacha_free_agent(
)?;
let item_id = ItemID::new(req.avatar_id);
if let None = item_id {
if item_id.is_err() {
return Err(NetError::from(Retcode::RetFail));
}
let item_id = item_id.unwrap();
@ -157,7 +157,7 @@ pub async fn on_choose_gacha_up(
)?;
let item_id = ItemID::new(req.item_id);
if let None = item_id {
if item_id.is_err() {
return Err(NetError::from(Retcode::RetFail));
}
let item_id = item_id.unwrap();
@ -200,18 +200,18 @@ fn add_item(
) -> NetResult<u32> {
match item_type {
GachaAddedItemType::Character => match AvatarBaseID::new(item_id.value()) {
Some(avatar_id) => {
Ok(avatar_id) => {
role_model.add_avatar(avatar_id);
Ok(0)
}
None => {
Err(_) => {
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 => {
Ok(weapon_id) => Ok(item_model.add_weapon(weapon_id).value()),
Err(_) => {
tracing::info!("add item failed for weapon id {item_id}");
Err(NetError::from(Retcode::RetFail))
}

View file

@ -1,5 +1,7 @@
use data::tables::AvatarBaseID;
use crate::logic::game::LogicError;
use super::*;
pub async fn on_get_item_data(
@ -23,7 +25,7 @@ pub async fn on_weapon_dress(
req: WeaponDressCsReq,
) -> NetResult<WeaponDressScRsp> {
player.dress_weapon(
AvatarBaseID::new(req.avatar_id).ok_or(Retcode::RetFail)?,
AvatarBaseID::new(req.avatar_id).map_err(LogicError::from)?,
req.weapon_uid.into(),
)?;
@ -45,7 +47,7 @@ pub async fn on_weapon_un_dress(
player: &mut Player,
req: WeaponUnDressCsReq,
) -> NetResult<WeaponUnDressScRsp> {
let avatar_id = AvatarBaseID::new(req.avatar_id).ok_or(Retcode::RetFail)?;
let avatar_id = AvatarBaseID::new(req.avatar_id).map_err(LogicError::from)?;
let avatar = player
.role_model
.avatar_list

View file

@ -22,7 +22,7 @@ pub async fn on_create_role(
player: &mut Player,
req: CreateRoleCsReq,
) -> NetResult<CreateRoleScRsp> {
let avatar_id = AvatarBaseID::new(req.avatar_id).ok_or(Retcode::RetFail)?;
let avatar_id = AvatarBaseID::new(req.avatar_id).map_err(LogicError::from)?;
let GameInstance::Fresh(fresh_game) = &mut player.game_instance else {
return Err(NetError::from(Retcode::RetFail));

View file

@ -88,7 +88,7 @@ pub async fn on_begin_archive_battle_quest(
player: &mut Player,
req: BeginArchiveBattleQuestCsReq,
) -> NetResult<BeginArchiveBattleQuestScRsp> {
let quest_id = ArchiveBattleQuestID::new(req.quest_id).ok_or(Retcode::RetFail)?;
let quest_id = ArchiveBattleQuestID::new(req.quest_id).map_err(LogicError::from)?;
player.game_instance = GameInstance::Hollow(
HollowGame::create_archive_battle(

View file

@ -47,7 +47,7 @@ pub async fn on_advance_beginner_procedure(
};
let procedure_id =
ProcedureConfigID::new(req.procedure_id as u32).ok_or(Retcode::RetFail)?;
ProcedureConfigID::new(req.procedure_id as u32).map_err(LogicError::from)?;
fresh_game
.procedure_mgr
@ -168,7 +168,7 @@ pub async fn on_start_trial_fighting_mission(
player: &mut Player,
req: StartTrialFightingMissionCsReq,
) -> NetResult<StartTrialFightingMissionScRsp> {
let quest_id = TrainingQuestID::new(req.quest_id).ok_or(Retcode::RetFail)?;
let quest_id = TrainingQuestID::new(req.quest_id).map_err(LogicError::from)?;
player.game_instance = GameInstance::Hollow(
HollowGame::create_training_game(quest_id, ELocalPlayType::TrainingRoomFight, &req.avatars)
@ -273,7 +273,7 @@ pub async fn on_enter_section(
player: &mut Player,
req: EnterSectionCsReq,
) -> NetResult<EnterSectionScRsp> {
let section_id = SectionConfigID::new(req.section_id).ok_or(Retcode::RetFail)?;
let section_id = SectionConfigID::new(req.section_id).map_err(LogicError::from)?;
player.main_city_model.switch_section(section_id);
player.init_frontend_game()?;
@ -311,7 +311,7 @@ pub async fn on_start_hollow_quest(
) -> NetResult<StartHollowQuestScRsp> {
use crate::logic::{TimePeriodType, WeatherType};
let quest_id = HollowQuestID::new(req.quest_id).ok_or(Retcode::RetFail)?;
let quest_id = HollowQuestID::new(req.quest_id).map_err(LogicError::from)?;
let quest_type = EHollowQuestType::from(quest_id.template().hollow_quest_type);
match quest_type {

View file

@ -2,17 +2,17 @@ use std::collections::HashMap;
use crate::logic::BuddyTeamType;
use super::unit::{AvatarUnit, BuddyUnit};
use super::unit::{AvatarUnit, AvatarUnitID, BuddyUnit, BuddyUnitID};
pub struct TeamDataItem {
pub avatar_member_list: Vec<AvatarUnit>,
pub equipped_buddy_list: Vec<BuddyUnit>,
}
pub struct BuddyParam(pub u32, pub BuddyTeamType);
pub struct BuddyParam(pub BuddyUnitID, pub BuddyTeamType);
impl TeamDataItem {
pub fn new(avatars: &[u32], buddy_params: &[BuddyParam]) -> Self {
pub fn new(avatars: &[AvatarUnitID], buddy_params: &[BuddyParam]) -> Self {
Self {
avatar_member_list: avatars
.iter()

View file

@ -1,17 +1,36 @@
use std::collections::HashMap;
use crate::logic::BaseProperty;
use data::tables::{AvatarBaseID, RobotConfigID};
use proto::AvatarUnitInfo;
pub struct AvatarUnit {
pub avatar_id: u32,
pub avatar_id: AvatarUnitID,
pub mp_property_override: HashMap<BaseProperty, i32>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum AvatarUnitID {
Base(AvatarBaseID),
Robot(RobotConfigID),
}
impl AvatarUnitID {
pub fn base_id(&self) -> AvatarBaseID {
match *self {
Self::Base(id) => id,
Self::Robot(id) => id.template().character_id,
}
}
}
impl AvatarUnit {
pub fn to_client(&self) -> AvatarUnitInfo {
AvatarUnitInfo {
avatar_id: self.avatar_id,
avatar_id: match self.avatar_id {
AvatarUnitID::Base(id) => id.value(),
AvatarUnitID::Robot(id) => id.value(),
},
mp_property_override_map: self
.mp_property_override
.iter()

View file

@ -1,19 +1,38 @@
use std::collections::HashMap;
use data::tables::{BuddyBaseID, RobotBuddyConfigID};
use proto::BuddyUnitInfo;
use crate::logic::{BaseProperty, BuddyTeamType};
pub struct BuddyUnit {
pub buddy_id: u32,
pub buddy_id: BuddyUnitID,
pub buddy_team: BuddyTeamType,
pub override_property_map: HashMap<BaseProperty, i32>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum BuddyUnitID {
Base(BuddyBaseID),
Robot(RobotBuddyConfigID),
}
impl BuddyUnitID {
pub fn base_id(&self) -> BuddyBaseID {
match *self {
Self::Base(id) => id,
Self::Robot(id) => id.template().buddy_id,
}
}
}
impl BuddyUnit {
pub fn to_client(&self) -> BuddyUnitInfo {
BuddyUnitInfo {
buddy_id: self.buddy_id,
buddy_id: match self.buddy_id {
BuddyUnitID::Base(id) => id.value(),
BuddyUnitID::Robot(id) => id.value(),
},
r#type: self.buddy_team.to_protocol().into(),
mp_property_override_map: self
.override_property_map

View file

@ -1,5 +1,5 @@
mod avatar;
mod buddy;
pub use avatar::AvatarUnit;
pub use buddy::BuddyUnit;
pub use avatar::{AvatarUnit, AvatarUnitID};
pub use buddy::{BuddyUnit, BuddyUnitID};

View file

@ -509,7 +509,7 @@ fn determine_gacha_result<'bin, 'conf>(
// TODO: apply_on_owned_count in a context with bag
// TODO: That's what RoleModel should do, not me.
if extra_items_policy.apply_on_owned_count == 0 {
extra_item_id = ItemID::new(extra_items_policy.id);
extra_item_id = ItemID::new(extra_items_policy.id).ok();
extra_item_count = extra_items_policy.count;
}
}

View file

@ -19,8 +19,8 @@ impl GachaExtraResources {
Some(bin) => {
let item_id_opt = ItemID::new(bin.extra_item_id);
match item_id_opt {
None => None,
Some(extra_item_id) => Some(Self {
Err(_) => None,
Ok(extra_item_id) => Some(Self {
extra_item_id,
extra_item_count: bin.extra_item_count,
}),

View file

@ -1,6 +1,7 @@
use common::util;
use data::tables::{
self, ArchiveBattleQuestID, BattleEventConfigID, HollowQuestID, TrainingQuestID,
self, ArchiveBattleQuestID, AvatarBaseID, BattleEventConfigID, BuddyBaseID, HollowQuestID,
RobotBuddyConfigID, RobotConfigID, TrainingQuestID,
};
use proto::{DungeonInfo, DungeonItemData, FightSceneInfo, SceneInfo, WeatherPoolInfo};
use thiserror::Error;
@ -8,7 +9,7 @@ use thiserror::Error;
use crate::logic::{
battle::{
drop::FightDropPool,
unit::{AvatarUnit, BuddyUnit},
unit::{AvatarUnit, AvatarUnitID, BuddyUnit, BuddyUnitID},
BuddyParam, DungeonQuestManager, TeamDataItem,
},
BuddyTeamType, EHollowQuestType, ELocalPlayType, ESceneType, TimePeriodType, WeatherType,
@ -22,6 +23,10 @@ pub enum HollowGameError {
QuestTypeNotSupported(u32, EHollowQuestType),
#[error("Battle group not found, quest id: {0}")]
BattleGroupNotFound(u32),
#[error("Invalid avatar id: {0}")]
InvalidAvatarID(u32),
#[error("Invalid robot id: {0}")]
InvalidRobotID(u32),
}
pub struct HollowGame {
@ -44,6 +49,15 @@ impl HollowGame {
) -> Result<Self, HollowGameError> {
let template = training_quest_id.template();
let avatars = match avatars
.iter()
.map(|id| AvatarBaseID::new(*id).map(|id| AvatarUnitID::Base(id)))
.collect::<Result<Vec<_>, _>>()
{
Ok(avatars) => avatars,
Err(err) => return Err(HollowGameError::InvalidAvatarID(err.0)),
};
Ok(Self {
quest_id: template.id.value(),
battle_event_id: template.battle_event_id,
@ -51,7 +65,7 @@ impl HollowGame {
weather: WeatherType::SunShine,
start_timestamp: util::cur_timestamp() as i64,
play_type,
team_data: TeamDataItem::new(avatars, &[]),
team_data: TeamDataItem::new(&avatars, &[]),
fight_drop_pool: FightDropPool::new(template.battle_event_id),
quest_manager: DungeonQuestManager::default(),
})
@ -65,6 +79,20 @@ impl HollowGame {
) -> Result<Self, HollowGameError> {
let template = archive_battle_quest_id.template();
let avatars = match avatars
.iter()
.map(|id| RobotConfigID::new(*id).map(|id| AvatarUnitID::Robot(id)))
.collect::<Result<Vec<_>, _>>()
{
Ok(avatars) => avatars,
Err(err) => return Err(HollowGameError::InvalidAvatarID(err.0)),
};
let buddy_params = match RobotBuddyConfigID::new(buddy_id) {
Ok(id) => vec![BuddyParam(BuddyUnitID::Robot(id), BuddyTeamType::Fighting)],
Err(_) => Vec::with_capacity(0),
};
Ok(Self {
quest_id: template.id.value(),
battle_event_id: template.battle_event_id,
@ -72,12 +100,7 @@ impl HollowGame {
weather: WeatherType::SunShine,
start_timestamp: util::cur_timestamp() as i64,
play_type,
team_data: TeamDataItem::new(
avatars,
&(buddy_id != 0)
.then_some(vec![BuddyParam(buddy_id, BuddyTeamType::Fighting)])
.unwrap_or_default(),
),
team_data: TeamDataItem::new(&avatars, &buddy_params),
fight_drop_pool: FightDropPool::new(template.battle_event_id),
quest_manager: DungeonQuestManager::default(),
})
@ -104,6 +127,20 @@ impl HollowGame {
return Err(HollowGameError::BattleGroupNotFound(template.id.value()));
};
let avatars = match avatars
.iter()
.map(|id| AvatarBaseID::new(*id).map(|id| AvatarUnitID::Base(id)))
.collect::<Result<Vec<_>, _>>()
{
Ok(avatars) => avatars,
Err(err) => return Err(HollowGameError::InvalidAvatarID(err.0)),
};
let buddy_params = match BuddyBaseID::new(buddy_id) {
Ok(id) => vec![BuddyParam(BuddyUnitID::Base(id), BuddyTeamType::Fighting)],
Err(_) => Vec::with_capacity(0),
};
Ok(Self {
quest_id: template.id.value(),
battle_event_id: battle_group.battle_event_id,
@ -113,12 +150,7 @@ impl HollowGame {
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(),
),
team_data: TeamDataItem::new(&avatars, &buddy_params),
fight_drop_pool: FightDropPool::new(battle_group.battle_event_id),
quest_manager: DungeonQuestManager::new_for_battle_group(battle_group.id),
})

View file

@ -1,5 +1,5 @@
use common::util;
use data::tables::{self, BattleEventConfigID, HollowQuestID};
use data::tables::{self, AvatarBaseID, BattleEventConfigID, BuddyBaseID, HollowQuestID};
use proto::{
DungeonInfo, DungeonItemData, FightQuestInfo, LongFightInfo, LongFightSceneInfo, SceneInfo,
WeatherPoolInfo,
@ -9,7 +9,7 @@ use thiserror::Error;
use crate::logic::{
battle::{
drop::FightDropPool,
unit::{AvatarUnit, BuddyUnit},
unit::{AvatarUnit, AvatarUnitID, BuddyUnit, BuddyUnitID},
BuddyParam, DungeonQuestManager, LogicVariableTable, TeamDataItem,
},
BuddyTeamType, EHollowQuestType, ELocalPlayType, ESceneType, TimePeriodType, WeatherType,
@ -23,6 +23,8 @@ pub enum LongFightGameError {
InvalidQuestType(EHollowQuestType),
#[error("Battle group not found, quest id: {0}")]
BattleGroupNotFound(u32),
#[error("Invalid avatar id: {0}")]
InvalidAvatarID(u32),
}
pub struct LongFightGame {
@ -39,7 +41,7 @@ pub struct LongFightGame {
}
impl LongFightGame {
const RALLY_GUIDANCE_BUDDY_ID: u32 = 50001;
const RALLY_GUIDANCE_BUDDY: BuddyBaseID = BuddyBaseID::new_unchecked(50001);
pub fn create_rally_game(
quest_id: HollowQuestID,
@ -61,13 +63,25 @@ impl LongFightGame {
return Err(LongFightGameError::BattleGroupNotFound(template.id.value()));
};
let avatars = match avatars
.iter()
.map(|id| AvatarBaseID::new(*id).map(|id| AvatarUnitID::Base(id)))
.collect::<Result<Vec<_>, _>>()
{
Ok(avatars) => avatars,
Err(err) => return Err(LongFightGameError::InvalidAvatarID(err.0)),
};
let mut buddy_params = vec![BuddyParam(
Self::RALLY_GUIDANCE_BUDDY_ID,
BuddyUnitID::Base(Self::RALLY_GUIDANCE_BUDDY),
BuddyTeamType::RallyGuidance,
)];
if buddy_id != 0 {
buddy_params.push(BuddyParam(buddy_id, BuddyTeamType::Fighting));
if let Ok(buddy_id) = BuddyBaseID::new(buddy_id) {
buddy_params.push(BuddyParam(
BuddyUnitID::Base(buddy_id),
BuddyTeamType::Fighting,
));
}
Ok(Self {
@ -77,7 +91,7 @@ impl LongFightGame {
time_period,
weather,
start_timestamp: util::cur_timestamp() as i64,
team_data: TeamDataItem::new(avatars, &buddy_params),
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),

View file

@ -2,6 +2,7 @@ mod fresh;
mod frontend;
mod hollow;
mod long_fight;
use data::tables::TemplateNotFoundError;
pub use fresh::*;
pub use frontend::*;
pub use hollow::*;
@ -36,6 +37,8 @@ pub enum LogicError {
LongFight(#[from] LongFightGameError),
#[error("dungeon quest error: {0}")]
DungeonQuest(#[from] DungeonQuestError),
#[error("{0}")]
TemplateNotFound(#[from] TemplateNotFoundError),
}
impl GameInstance {

View file

@ -53,14 +53,14 @@ impl BasicDataModel {
exp: bin.exp,
profile_icon: bin.profile_icon,
frontend_avatar_id: match bin.frontend_avatar_id {
1.. => AvatarBaseID::new(bin.frontend_avatar_id as u32),
1.. => AvatarBaseID::new(bin.frontend_avatar_id as u32).ok(),
_ => None,
},
beginner_procedure_id: match bin.beginner_procedure_id {
1.. => ProcedureConfigID::new(bin.beginner_procedure_id as u32),
1.. => ProcedureConfigID::new(bin.beginner_procedure_id as u32).ok(),
_ => None,
},
selected_post_girl_id: PostGirlConfigID::new(bin.selected_post_girl_id),
selected_post_girl_id: PostGirlConfigID::new(bin.selected_post_girl_id).ok(),
nick_name: match bin.nick_name.is_empty() {
true => None,
false => Some(bin.nick_name),
@ -84,7 +84,7 @@ impl BasicDataModel {
.unwrap_or(-1),
selected_post_girl_id: match self.selected_post_girl_id {
Some(post_girl_id) => post_girl_id.value(),
None => 0
None => 0,
},
}
}

View file

@ -15,7 +15,7 @@ impl LockModel {
unlock_list: bin
.unlock_list
.into_iter()
.map(UnlockConfigID::new_unchecked)
.map(|id| UnlockConfigID::new_unchecked(id as u32))
.collect(),
quick_access_list: bin
.quick_access_list
@ -31,7 +31,7 @@ impl LockModel {
.unlock_list
.clone()
.into_iter()
.map(|i| i.value())
.map(|i| i.value() as i32)
.collect(),
quick_access_list: self
.quick_access_list
@ -65,13 +65,12 @@ impl LockModel {
}
pub fn to_client(&self) -> UnlockData {
UnlockData {
unlock_id_list: self
.unlock_list
.clone()
.into_iter()
.map(|i| i.value())
.map(|i| i.value() as i32)
.collect(),
quick_access_data_list: self.quick_access_to_client(),
..Default::default()
@ -86,27 +85,10 @@ impl LockModel {
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
}
pub fn mod_quick_access(&mut self, index: u32, id: Option<QuickFuncID>) {
match id {
Some(quick_access_id) => self.quick_access_list.insert(index, quick_access_id),
None => self.quick_access_list.remove(&index),
};
QuickAccessData {
r#type: QuickAccessType::QuickMenu.into(),
quick_access_index: index,
btn_id,
}
}
}

View file

@ -22,15 +22,17 @@ impl RoleModel {
const DEFAULT_AVATARS: [u32; 2] = [1011, 1081];
pub fn add_avatar(&mut self, template_id: AvatarBaseID) {
if !self
.avatar_list
.iter()
.any(|a| a.template_id == template_id)
{
if !self.has_avatar(template_id) {
self.avatar_list.push(Avatar::new(template_id));
}
}
pub fn has_avatar(&self, template_id: AvatarBaseID) -> bool {
self.avatar_list
.iter()
.any(|a| a.template_id == template_id)
}
pub fn avatar_sync(&self) -> AvatarSync {
AvatarSync {
avatar_list: self.avatar_list.iter().map(Avatar::to_client).collect(),

View file

@ -12,7 +12,7 @@ impl SceneUnitManager {
let unit_vec = tables::main_city_object_template_tb::iter()
.filter(|tmpl| tmpl.get_section_name() == section_template.section_name)
.filter(|tmpl| MainCityDefaultObjectID::new(tmpl.tag_id.value()).is_some()) // check if npc tag present in default object table
.filter(|tmpl| MainCityDefaultObjectID::new(tmpl.tag_id.value()).is_ok()) // check if npc tag present in default object table
.map(|tmpl| SceneUnit::new(tmpl.tag_id))
.collect();