feat(game): hand over pom-pom skins, the jukebox, the data bank and messages
This commit is contained in:
1 parent
a6d15b6262
commit
4dda820c44
9 files changed
+227
-6
No files matched your search
@@ -255,6 +255,16 @@ GetShopListCsReq
|
||||
GetShopListScRsp
|
||||
BuyGoodsCsReq
|
||||
BuyGoodsScRsp
|
||||
GetPamSkinDataCsReq
|
||||
GetPamSkinDataScRsp
|
||||
SelectPamSkinCsReq
|
||||
SelectPamSkinScRsp
|
||||
GetJukeboxDataCsReq
|
||||
GetJukeboxDataScRsp
|
||||
PlayBackGroundMusicCsReq
|
||||
PlayBackGroundMusicScRsp
|
||||
GetNpcMessageGroupCsReq
|
||||
GetNpcMessageGroupScRsp
|
||||
UseItemCsReq
|
||||
UseItemScRsp
|
||||
SellItemCsReq
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
// Pom-Pom's outfits, the Jukebox, the Data Bank and phone messages. A private server
|
||||
// has nothing to earn them with, so each collection is handed over whole.
|
||||
#include <algorithm>
|
||||
|
||||
#include "core/logger.h"
|
||||
#include "core/util.h"
|
||||
#include "data/excel.h"
|
||||
#include "game/handlers.h"
|
||||
#include "game/player.h"
|
||||
#include "net/cmd_ids.h"
|
||||
#include "net/handler.h"
|
||||
#include "net/session.h"
|
||||
#include "proto/gen/protos.h"
|
||||
|
||||
namespace game {
|
||||
namespace {
|
||||
|
||||
Player* playerOf(net::Session& session, const char* what) {
|
||||
Player* player = session.player();
|
||||
if (player == nullptr) logging::warn("game", "{} before login", what);
|
||||
return player;
|
||||
}
|
||||
|
||||
void onGetPamSkinData(net::Session& session, const proto::GetPamSkinDataCsReq&) {
|
||||
Player* player = playerOf(session, "GetPamSkinData");
|
||||
if (player == nullptr) return;
|
||||
|
||||
proto::GetPamSkinDataScRsp rsp;
|
||||
rsp.retcode = 0;
|
||||
rsp.cur_skin = player->pamSkin();
|
||||
rsp.unlock_skin_list = data::Tables::get().pamSkins();
|
||||
session.send(cmd::GetPamSkinDataScRsp, rsp);
|
||||
}
|
||||
|
||||
void onSelectPamSkin(net::Session& session, const proto::SelectPamSkinCsReq& req) {
|
||||
Player* player = playerOf(session, "SelectPamSkin");
|
||||
if (player == nullptr) return;
|
||||
|
||||
const std::vector<uint32_t>& skins = data::Tables::get().pamSkins();
|
||||
if (std::find(skins.begin(), skins.end(), req.pam_skin) != skins.end()) {
|
||||
player->setPamSkin(req.pam_skin);
|
||||
player->save();
|
||||
}
|
||||
// Both fields carry the outfit now worn.
|
||||
proto::SelectPamSkinScRsp rsp;
|
||||
rsp.retcode = 0;
|
||||
rsp.cur_skin = player->pamSkin();
|
||||
rsp.set_skin = player->pamSkin();
|
||||
session.send(cmd::SelectPamSkinScRsp, rsp);
|
||||
}
|
||||
|
||||
void onGetJukeboxData(net::Session& session, const proto::GetJukeboxDataCsReq&) {
|
||||
Player* player = playerOf(session, "GetJukeboxData");
|
||||
if (player == nullptr) return;
|
||||
|
||||
proto::GetJukeboxDataScRsp rsp;
|
||||
rsp.retcode = 0;
|
||||
for (const data::MusicInfo& track : data::Tables::get().music()) {
|
||||
proto::LHHGCDLCJDA entry;
|
||||
entry.id = track.id;
|
||||
entry.group_id = track.groupId;
|
||||
entry.BIMDKNLMICG = true; // unlocked
|
||||
rsp.IOKAJIBHLMP.push_back(entry);
|
||||
}
|
||||
// What is playing.
|
||||
rsp.GFFOBALDBPM.emplace().JKNLCEEDAHJ.emplace().id = player->music();
|
||||
session.send(cmd::GetJukeboxDataScRsp, rsp);
|
||||
}
|
||||
|
||||
void onPlayBackGroundMusic(net::Session& session, const proto::PlayBackGroundMusicCsReq& req) {
|
||||
Player* player = playerOf(session, "PlayBackGroundMusic");
|
||||
if (player == nullptr) return;
|
||||
|
||||
uint32_t id = req.IKJNIKIGFLF && req.IKJNIKIGFLF->JKNLCEEDAHJ ? req.IKJNIKIGFLF->JKNLCEEDAHJ->id : 0;
|
||||
proto::PlayBackGroundMusicScRsp rsp;
|
||||
if (data::Tables::get().musicTrack(id) == nullptr) {
|
||||
rsp.retcode = fail(proto::Retcode::RET_MUSIC_NOT_EXIST);
|
||||
} else {
|
||||
player->setMusic(id);
|
||||
player->save();
|
||||
rsp.retcode = 0;
|
||||
// Echoed whole, pause state included.
|
||||
rsp.GFFOBALDBPM = *req.IKJNIKIGFLF;
|
||||
}
|
||||
session.send(cmd::PlayBackGroundMusicScRsp, rsp);
|
||||
}
|
||||
|
||||
void onGetArchiveData(net::Session& session, const proto::GetArchiveDataCsReq&) {
|
||||
const data::Tables& tables = data::Tables::get();
|
||||
proto::GetArchiveDataScRsp rsp;
|
||||
rsp.retcode = 0;
|
||||
auto& archive = rsp.archive_data.emplace();
|
||||
for (const auto& [id, lightcone] : tables.lightcones()) archive.archive_equipment_id_list.push_back(id);
|
||||
std::sort(archive.archive_equipment_id_list.begin(), archive.archive_equipment_id_list.end());
|
||||
for (const data::RelicSetPiece& piece : tables.relicSetPieces()) {
|
||||
proto::RelicList relic;
|
||||
relic.set_id = piece.setId;
|
||||
relic.type = piece.type;
|
||||
archive.relic_list.push_back(relic);
|
||||
}
|
||||
std::vector<uint32_t> monsters;
|
||||
for (const auto& [id, monster] : tables.monsters()) monsters.push_back(id);
|
||||
std::sort(monsters.begin(), monsters.end());
|
||||
for (uint32_t id : monsters) {
|
||||
proto::MonsterList entry;
|
||||
entry.monster_id = id;
|
||||
entry.num = 1;
|
||||
archive.kill_monster_list.push_back(entry);
|
||||
}
|
||||
session.send(cmd::GetArchiveDataScRsp, rsp);
|
||||
}
|
||||
|
||||
void onGetNpcMessageGroup(net::Session& session, const proto::GetNpcMessageGroupCsReq& req) {
|
||||
// Nothing tracks how far a conversation got, so each one reads as finished: where the
|
||||
// story leaves them, and what unlocks gated on a finished chat look for.
|
||||
const data::Tables& tables = data::Tables::get();
|
||||
proto::GetNpcMessageGroupScRsp rsp;
|
||||
rsp.retcode = 0;
|
||||
auto refreshed = static_cast<int64_t>(util::nowSec());
|
||||
for (uint32_t contact : req.BJIPBBFIBPD) { // contact ids
|
||||
const std::vector<data::MessageGroupInfo>* groups = tables.messageGroups(contact);
|
||||
if (groups == nullptr) continue;
|
||||
for (const data::MessageGroupInfo& info : *groups) {
|
||||
proto::MessageGroup group;
|
||||
group.id = info.id;
|
||||
group.status = proto::MessageGroupStatus::MessageGroupStatus_MessageGroupFinish;
|
||||
group.refresh_time = refreshed;
|
||||
for (uint32_t sectionId : info.sections) {
|
||||
proto::MessageSection section;
|
||||
section.id = sectionId;
|
||||
section.status = proto::MessageSectionStatus::MessageSectionStatus_MessageSectionFinish;
|
||||
group.message_section_list.push_back(section);
|
||||
}
|
||||
if (!info.sections.empty()) group.message_section_id = info.sections.back();
|
||||
rsp.message_group_list.push_back(std::move(group));
|
||||
}
|
||||
}
|
||||
session.send(cmd::GetNpcMessageGroupScRsp, rsp);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void registerCollectionHandlers() {
|
||||
net::on<proto::GetPamSkinDataCsReq>(cmd::GetPamSkinDataCsReq, onGetPamSkinData);
|
||||
net::on<proto::SelectPamSkinCsReq>(cmd::SelectPamSkinCsReq, onSelectPamSkin);
|
||||
net::on<proto::GetJukeboxDataCsReq>(cmd::GetJukeboxDataCsReq, onGetJukeboxData);
|
||||
net::on<proto::PlayBackGroundMusicCsReq>(cmd::PlayBackGroundMusicCsReq, onPlayBackGroundMusic);
|
||||
net::on<proto::GetArchiveDataCsReq>(cmd::GetArchiveDataCsReq, onGetArchiveData);
|
||||
net::on<proto::GetNpcMessageGroupCsReq>(cmd::GetNpcMessageGroupCsReq, onGetNpcMessageGroup);
|
||||
}
|
||||
|
||||
} // namespace game
|
||||
@@ -25,6 +25,7 @@ void registerTierceHandlers();
|
||||
void registerPeakHandlers();
|
||||
void registerGachaHandlers();
|
||||
void registerShopHandlers();
|
||||
void registerCollectionHandlers();
|
||||
void registerModuleHandlers();
|
||||
void registerMiscHandlers();
|
||||
|
||||
|
||||
@@ -208,6 +208,7 @@ void registerAllHandlers() {
|
||||
registerPeakHandlers();
|
||||
registerGachaHandlers();
|
||||
registerShopHandlers();
|
||||
registerCollectionHandlers();
|
||||
registerModuleHandlers();
|
||||
registerMiscHandlers();
|
||||
logging::info("game", "{} packet handlers registered, {} answered empty",
|
||||
|
||||
@@ -43,14 +43,11 @@ constexpr uint16_t kSilent[] = {
|
||||
cmd::ChimeraDuelGetDataCsReq,
|
||||
cmd::ChenLingGetDataCsReq,
|
||||
cmd::GetEvolveBuildQueryInfoCsReq,
|
||||
cmd::GetJukeboxDataCsReq,
|
||||
// Second round, from the OS 4.5.53 client's own log: CycleScore.Sync,
|
||||
// PetMarbleModule._OnPetMarbleGetDataScRsp, ArchiveModule._OnGetArchiveDataScRsp
|
||||
// (through AvatarArchiveData.Sync) and DrinkMakerBar._UpdateGameplayData all
|
||||
// PetMarbleModule._OnPetMarbleGetDataScRsp and DrinkMakerBar._UpdateGameplayData
|
||||
// dereference their way through an empty body.
|
||||
cmd::CycleScoreRewardGetDataCsReq,
|
||||
cmd::PetMarbleGetDataCsReq,
|
||||
cmd::GetArchiveDataCsReq,
|
||||
cmd::GetDrinkMakerDataCsReq,
|
||||
};
|
||||
|
||||
|
||||
@@ -216,6 +216,11 @@ public:
|
||||
std::map<uint32_t, GoodsPurchase>& goodsPurchases() { return goodsPurchases_; }
|
||||
const std::map<uint32_t, GoodsPurchase>& goodsPurchases() const { return goodsPurchases_; }
|
||||
|
||||
uint32_t pamSkin() const { return pamSkin_; }
|
||||
void setPamSkin(uint32_t value) { pamSkin_ = value; }
|
||||
uint32_t music() const { return music_; }
|
||||
void setMusic(uint32_t value) { music_ = value; }
|
||||
|
||||
uint32_t mainCharacter() const { return mainCharacter_; }
|
||||
void setMainCharacter(uint32_t value) { mainCharacter_ = value; }
|
||||
uint32_t marchType() const { return marchType_; }
|
||||
@@ -257,6 +262,8 @@ private:
|
||||
std::map<uint32_t, ChallengeRecord> challengeRecords_;
|
||||
std::map<uint32_t, uint64_t> challengeRewardsTaken_;
|
||||
std::map<uint32_t, GoodsPurchase> goodsPurchases_;
|
||||
uint32_t pamSkin_ = 252000;
|
||||
uint32_t music_ = 210000;
|
||||
|
||||
uint32_t mainCharacter_ = 8008;
|
||||
uint32_t marchType_ = 1224;
|
||||
|
||||
@@ -244,6 +244,8 @@ bool loadPlayerState(Player& player) {
|
||||
}
|
||||
}
|
||||
|
||||
player.setPamSkin(u32(j, "pam_skin", player.pamSkin()));
|
||||
player.setMusic(u32(j, "music", player.music()));
|
||||
if (auto bought = j.find("goods_purchases"); bought != j.end() && bought->is_array()) {
|
||||
for (const json& entry : *bought) {
|
||||
uint32_t goods = entry.is_object() ? u32(entry, "goods", 0) : 0;
|
||||
@@ -369,6 +371,8 @@ std::string playerStateJson(const Player& player) {
|
||||
}
|
||||
j["challenge_rewards_taken"] = taken;
|
||||
|
||||
j["pam_skin"] = player.pamSkin();
|
||||
j["music"] = player.music();
|
||||
json bought = json::array();
|
||||
for (const auto& [goods, purchase] : player.goodsPurchases()) {
|
||||
if (purchase.times == 0) continue;
|
||||
|
||||
+47
-1
@@ -1122,7 +1122,7 @@ void runFlowTests() {
|
||||
claimed.retcode == 0 && !claimed.taken_reward_list.empty(),
|
||||
"season rewards can be claimed");
|
||||
|
||||
// Shops.
|
||||
// Shops, Pom-Pom's outfits, the Jukebox, the Data Bank and phone messages.
|
||||
proto::GetShopListCsReq shops;
|
||||
shops.shop_type = 1;
|
||||
client.send(cmd::GetShopListCsReq, shops);
|
||||
@@ -1131,6 +1131,52 @@ void runFlowTests() {
|
||||
!shopList.shop_list.empty() && !shopList.shop_list[0].goods_list.empty(),
|
||||
"a shop lists its goods");
|
||||
|
||||
client.sendEmpty(cmd::GetPamSkinDataCsReq);
|
||||
proto::GetPamSkinDataScRsp skins;
|
||||
check(client.await(cmd::GetPamSkinDataScRsp, packet) && parseBody(packet, skins) &&
|
||||
skins.unlock_skin_list.size() >= 5 && skins.cur_skin == 252000,
|
||||
"every Pom-Pom outfit is unlocked");
|
||||
proto::SelectPamSkinCsReq outfit;
|
||||
outfit.pam_skin = 252001;
|
||||
client.send(cmd::SelectPamSkinCsReq, outfit);
|
||||
proto::SelectPamSkinScRsp worn;
|
||||
check(client.await(cmd::SelectPamSkinScRsp, packet) && parseBody(packet, worn) && worn.retcode == 0 &&
|
||||
worn.cur_skin == 252001,
|
||||
"and one can be worn");
|
||||
|
||||
client.sendEmpty(cmd::GetJukeboxDataCsReq);
|
||||
proto::GetJukeboxDataScRsp jukebox;
|
||||
check(client.await(cmd::GetJukeboxDataScRsp, packet) && parseBody(packet, jukebox) &&
|
||||
jukebox.IOKAJIBHLMP.size() > 200 && jukebox.GFFOBALDBPM.has(),
|
||||
"the Jukebox lists its tracks and what is playing");
|
||||
if (jukebox.IOKAJIBHLMP.size() > 1) {
|
||||
proto::PlayBackGroundMusicCsReq play;
|
||||
play.IKJNIKIGFLF.emplace().JKNLCEEDAHJ.emplace().id = jukebox.IOKAJIBHLMP[1].id;
|
||||
client.send(cmd::PlayBackGroundMusicCsReq, play);
|
||||
proto::PlayBackGroundMusicScRsp playing;
|
||||
check(client.await(cmd::PlayBackGroundMusicScRsp, packet) && parseBody(packet, playing) &&
|
||||
playing.retcode == 0 && playing.GFFOBALDBPM && playing.GFFOBALDBPM->JKNLCEEDAHJ &&
|
||||
playing.GFFOBALDBPM->JKNLCEEDAHJ->id == jukebox.IOKAJIBHLMP[1].id,
|
||||
"and plays a pick");
|
||||
}
|
||||
|
||||
client.sendEmpty(cmd::GetArchiveDataCsReq);
|
||||
proto::GetArchiveDataScRsp archive;
|
||||
check(client.await(cmd::GetArchiveDataScRsp, packet) && parseBody(packet, archive) && archive.archive_data &&
|
||||
!archive.archive_data->kill_monster_list.empty() && !archive.archive_data->relic_list.empty() &&
|
||||
!archive.archive_data->archive_equipment_id_list.empty(),
|
||||
"the Data Bank is filled");
|
||||
|
||||
proto::GetNpcMessageGroupCsReq contacts;
|
||||
contacts.BJIPBBFIBPD = {1002};
|
||||
client.send(cmd::GetNpcMessageGroupCsReq, contacts);
|
||||
proto::GetNpcMessageGroupScRsp conversations;
|
||||
check(client.await(cmd::GetNpcMessageGroupScRsp, packet) && parseBody(packet, conversations) &&
|
||||
!conversations.message_group_list.empty() &&
|
||||
conversations.message_group_list[0].status ==
|
||||
proto::MessageGroupStatus::MessageGroupStatus_MessageGroupFinish,
|
||||
"phone conversations read as finished");
|
||||
|
||||
// An unimplemented request still has to complete, or the client hangs on it. This
|
||||
// one has no handler at all, so it exercises the name-derived fallback.
|
||||
client.sendEmpty(cmd::GetStarFightDataCsReq);
|
||||
|
||||
+4
-1
@@ -672,10 +672,13 @@ void testShop() {
|
||||
|
||||
std::string realPath = core::Config::get().paths.playerFile;
|
||||
core::Config::get().paths.playerFile = "build/test-shop-player.json";
|
||||
player.setPamSkin(252003);
|
||||
player.setMusic(210001);
|
||||
game::savePlayerState(player);
|
||||
game::Player loaded(1);
|
||||
game::loadPlayerState(loaded);
|
||||
check(loaded.goodsPurchases().count(pick->id) == 1, "purchases survive a restart");
|
||||
check(loaded.goodsPurchases().count(pick->id) == 1 && loaded.pamSkin() == 252003 && loaded.music() == 210001,
|
||||
"purchases, the outfit and the music survive a restart");
|
||||
core::Config::get().paths.playerFile = realPath;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user