support joinable quests on all versions
This commit is contained in:
@@ -1051,6 +1051,7 @@ static void server_command_playrec(shared_ptr<Client> c, const std::string& args
|
||||
}
|
||||
s->change_client_lobby(c, game);
|
||||
c->config.set_flag(Client::Flag::LOADING);
|
||||
c->log.info("LOADING flag set");
|
||||
}
|
||||
} else {
|
||||
send_text_message(c, "$C4This command cannot\nbe used in a game");
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
#include "IPStackSimulator.hh"
|
||||
#include "Loggers.hh"
|
||||
#include "SendCommands.hh"
|
||||
#include "Server.hh"
|
||||
#include "Version.hh"
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ extern const uint64_t CLIENT_CONFIG_MAGIC;
|
||||
|
||||
class Server;
|
||||
struct Lobby;
|
||||
class Parsed6x70Data;
|
||||
|
||||
class Client : public std::enable_shared_from_this<Client> {
|
||||
public:
|
||||
@@ -67,6 +68,7 @@ public:
|
||||
SHOULD_SEND_ARTIFICIAL_ENEMY_AND_SET_STATE = 0x0040000000000000, // Server-side only
|
||||
SHOULD_SEND_ARTIFICIAL_OBJECT_STATE = 0x0080000000000000, // Server-side only
|
||||
SHOULD_SEND_ARTIFICIAL_FLAG_STATE = 0x0002000000000000, // Server-side only
|
||||
SHOULD_SEND_ARTIFICIAL_PLAYER_STATES = 0x0200000000000000, // Server-side only
|
||||
SHOULD_SEND_ENABLE_SAVE = 0x0004000000000000,
|
||||
SWITCH_ASSIST_ENABLED = 0x0000000100000000,
|
||||
IS_CLIENT_CUSTOMIZATION = 0x0100000000000000,
|
||||
@@ -241,6 +243,7 @@ public:
|
||||
bool should_update_play_time;
|
||||
std::unordered_set<uint32_t> blocked_senders;
|
||||
std::unique_ptr<PlayerDispDataDCPCV3> v1_v2_last_reported_disp;
|
||||
std::shared_ptr<Parsed6x70Data> last_reported_6x70;
|
||||
// These are null unless the client is within the trade sequence (D0-D4 or EE commands)
|
||||
std::unique_ptr<PendingItemTrade> pending_item_trade;
|
||||
std::unique_ptr<PendingCardTrade> pending_card_trade;
|
||||
|
||||
@@ -4974,11 +4974,11 @@ struct G_SyncPlayerDispAndInventory_BB_6x70 {
|
||||
/* 04C8 */
|
||||
} __packed_ws__(G_SyncPlayerDispAndInventory_BB_6x70, 0x4C0);
|
||||
|
||||
// 6x71: Unknown (used while loading into game)
|
||||
// 6x71: Unblock game join (used while loading into game)
|
||||
|
||||
struct G_Unknown_6x71 {
|
||||
struct G_UnblockGameJoin_6x71 {
|
||||
G_UnusedHeader header;
|
||||
} __packed_ws__(G_Unknown_6x71, 4);
|
||||
} __packed_ws__(G_UnblockGameJoin_6x71, 4);
|
||||
|
||||
// 6x72: Player done loading into game
|
||||
|
||||
|
||||
+15
-8
@@ -205,7 +205,8 @@ VersionedQuest::VersionedQuest(
|
||||
std::shared_ptr<const BattleRules> battle_rules,
|
||||
ssize_t challenge_template_index,
|
||||
std::shared_ptr<const IntegralExpression> available_expression,
|
||||
std::shared_ptr<const IntegralExpression> enabled_expression)
|
||||
std::shared_ptr<const IntegralExpression> enabled_expression,
|
||||
bool force_joinable)
|
||||
: quest_number(quest_number),
|
||||
category_id(category_id),
|
||||
episode(Episode::NONE),
|
||||
@@ -233,7 +234,7 @@ VersionedQuest::VersionedQuest(
|
||||
throw invalid_argument("file is too small for header");
|
||||
}
|
||||
auto* header = reinterpret_cast<const PSOQuestHeaderDCNTE*>(bin_decompressed.data());
|
||||
this->joinable = false;
|
||||
this->joinable = force_joinable;
|
||||
this->episode = Episode::EP1;
|
||||
if (this->quest_number == 0xFFFFFFFF) {
|
||||
this->quest_number = fnv1a32(header, sizeof(header)) & 0xFFFF;
|
||||
@@ -249,7 +250,7 @@ VersionedQuest::VersionedQuest(
|
||||
throw invalid_argument("file is too small for header");
|
||||
}
|
||||
auto* header = reinterpret_cast<const PSOQuestHeaderDC*>(bin_decompressed.data());
|
||||
this->joinable = false;
|
||||
this->joinable = force_joinable;
|
||||
this->episode = Episode::EP1;
|
||||
if (this->quest_number == 0xFFFFFFFF) {
|
||||
this->quest_number = header->quest_number;
|
||||
@@ -266,7 +267,7 @@ VersionedQuest::VersionedQuest(
|
||||
throw invalid_argument("file is too small for header");
|
||||
}
|
||||
auto* header = reinterpret_cast<const PSOQuestHeaderPC*>(bin_decompressed.data());
|
||||
this->joinable = false;
|
||||
this->joinable = force_joinable;
|
||||
this->episode = Episode::EP1;
|
||||
if (this->quest_number == 0xFFFFFFFF) {
|
||||
this->quest_number = header->quest_number;
|
||||
@@ -289,7 +290,7 @@ VersionedQuest::VersionedQuest(
|
||||
throw invalid_argument("file is incorrect size");
|
||||
}
|
||||
auto* map = reinterpret_cast<const Episode3::MapDefinition*>(bin_decompressed.data());
|
||||
this->joinable = false;
|
||||
this->joinable = force_joinable;
|
||||
this->episode = Episode::EP3;
|
||||
if (this->quest_number == 0xFFFFFFFF) {
|
||||
this->quest_number = map->map_number;
|
||||
@@ -307,7 +308,7 @@ VersionedQuest::VersionedQuest(
|
||||
throw invalid_argument("file is too small for header");
|
||||
}
|
||||
auto* header = reinterpret_cast<const PSOQuestHeaderGC*>(bin_decompressed.data());
|
||||
this->joinable = false;
|
||||
this->joinable = force_joinable;
|
||||
this->episode = find_quest_episode_from_script(bin_decompressed.data(), bin_decompressed.size(), this->version);
|
||||
if (this->quest_number == 0xFFFFFFFF) {
|
||||
this->quest_number = header->quest_number;
|
||||
@@ -323,7 +324,7 @@ VersionedQuest::VersionedQuest(
|
||||
throw invalid_argument("file is too small for header");
|
||||
}
|
||||
auto* header = reinterpret_cast<const PSOQuestHeaderBB*>(bin_decompressed.data());
|
||||
this->joinable = header->joinable;
|
||||
this->joinable = header->joinable || force_joinable;
|
||||
this->episode = find_quest_episode_from_script(bin_decompressed.data(), bin_decompressed.size(), this->version);
|
||||
if (this->quest_number == 0xFFFFFFFF) {
|
||||
this->quest_number = header->quest_number;
|
||||
@@ -674,6 +675,7 @@ QuestIndex::QuestIndex(
|
||||
ssize_t challenge_template_index = -1;
|
||||
shared_ptr<const IntegralExpression> available_expression;
|
||||
shared_ptr<const IntegralExpression> enabled_expression;
|
||||
bool force_joinable = false;
|
||||
try {
|
||||
json_filedata = &json_files.at(basename);
|
||||
} catch (const out_of_range&) {
|
||||
@@ -704,6 +706,10 @@ QuestIndex::QuestIndex(
|
||||
enabled_expression = make_shared<IntegralExpression>(metadata_json.get_string("EnabledIf"));
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
try {
|
||||
force_joinable = metadata_json.get_bool("Joinable");
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
}
|
||||
|
||||
auto vq = make_shared<VersionedQuest>(
|
||||
@@ -717,7 +723,8 @@ QuestIndex::QuestIndex(
|
||||
battle_rules,
|
||||
challenge_template_index,
|
||||
available_expression,
|
||||
enabled_expression);
|
||||
enabled_expression,
|
||||
force_joinable);
|
||||
|
||||
auto category_name = this->category_index->at(vq->category_id)->name;
|
||||
string filenames_str = bin_filedata->filename;
|
||||
|
||||
+2
-1
@@ -90,7 +90,8 @@ struct VersionedQuest {
|
||||
std::shared_ptr<const BattleRules> battle_rules = nullptr,
|
||||
ssize_t challenge_template_index = -1,
|
||||
std::shared_ptr<const IntegralExpression> available_expression = nullptr,
|
||||
std::shared_ptr<const IntegralExpression> enabled_expression = nullptr);
|
||||
std::shared_ptr<const IntegralExpression> enabled_expression = nullptr,
|
||||
bool force_joinable = false);
|
||||
|
||||
std::string bin_filename() const;
|
||||
std::string dat_filename() const;
|
||||
|
||||
+91
-17
@@ -465,6 +465,20 @@ static void on_1D(shared_ptr<Client> c, uint16_t, uint32_t, string&) {
|
||||
c->config.clear_flag(Client::Flag::SHOULD_SEND_ARTIFICIAL_FLAG_STATE);
|
||||
send_game_flag_state(c); // 6x6F
|
||||
}
|
||||
if (c->config.check_flag(Client::Flag::SHOULD_SEND_ARTIFICIAL_PLAYER_STATES)) {
|
||||
c->config.clear_flag(Client::Flag::SHOULD_SEND_ARTIFICIAL_PLAYER_STATES);
|
||||
auto l = c->require_lobby();
|
||||
if (l->is_game()) {
|
||||
for (auto lc : l->clients) {
|
||||
// If we haven't received a 6x70 from this client, maybe they're VERY
|
||||
// far behind and wil lstll send it, or maybe they'll time out and be
|
||||
// disconnected soon - either way, we shouldn't fail if it's missing.
|
||||
if (lc && (lc != c) && lc->last_reported_6x70) {
|
||||
send_game_player_state(c, lc, true); // 6x70
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void on_05_XB(shared_ptr<Client> c, uint16_t, uint32_t, string&) {
|
||||
@@ -1229,6 +1243,7 @@ static bool add_next_game_client(shared_ptr<Lobby> l) {
|
||||
|
||||
s->change_client_lobby(c, l, true, target_client_id);
|
||||
c->config.set_flag(Client::Flag::LOADING);
|
||||
c->log.info("LOADING flag set");
|
||||
if (tourn) {
|
||||
c->config.set_flag(Client::Flag::LOADING_TOURNAMENT);
|
||||
}
|
||||
@@ -2045,6 +2060,7 @@ void set_lobby_quest(shared_ptr<Lobby> l, shared_ptr<const Quest> q, bool substi
|
||||
|
||||
if (use_loading_flag) {
|
||||
lc->config.set_flag(Client::Flag::LOADING_QUEST);
|
||||
lc->log.info("LOADING_QUEST flag set");
|
||||
lc->disconnect_hooks.emplace(QUEST_BARRIER_DISCONNECT_HOOK_NAME, [l]() -> void {
|
||||
send_quest_barrier_if_all_clients_ready(l);
|
||||
});
|
||||
@@ -2385,6 +2401,8 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
}
|
||||
if (game->is_game()) {
|
||||
c->config.set_flag(Client::Flag::LOADING);
|
||||
c->log.info("LOADING flag set");
|
||||
|
||||
// If no one was in the game before, then there's no leader to send
|
||||
// the game state - send it to the joining player (who is now the
|
||||
// leader)
|
||||
@@ -2834,30 +2852,49 @@ static void on_A2(shared_ptr<Client> c, uint16_t, uint32_t flag, string& data) {
|
||||
}
|
||||
}
|
||||
|
||||
static void on_joinable_quest_loaded(shared_ptr<Client> c) {
|
||||
auto l = c->require_lobby();
|
||||
if (!l->is_game() || !l->quest) {
|
||||
throw runtime_error("joinable quest load completed in non-game");
|
||||
}
|
||||
auto leader_c = l->clients.at(l->leader_id);
|
||||
if (!leader_c) {
|
||||
throw logic_error("lobby leader is missing");
|
||||
}
|
||||
|
||||
// On BB, ask the leader to send the quest state to the joining player (and
|
||||
// we'll need to use the game join command queue to avoid any item ID races).
|
||||
// On other versions, the server will have to generate the state commands;
|
||||
// this happens when the response to the ping (1D) is received, so we don't
|
||||
// need the game join command queue in that case.
|
||||
if (leader_c->version() == Version::BB_V4) {
|
||||
send_command(leader_c, 0xDD, c->lobby_client_id);
|
||||
c->log.info("Creating game join command queue");
|
||||
c->game_join_command_queue = make_unique<deque<Client::JoinCommand>>();
|
||||
} else {
|
||||
c->config.set_flag(Client::Flag::SHOULD_SEND_ARTIFICIAL_ITEM_STATE);
|
||||
c->config.set_flag(Client::Flag::SHOULD_SEND_ARTIFICIAL_ENEMY_AND_SET_STATE);
|
||||
c->config.set_flag(Client::Flag::SHOULD_SEND_ARTIFICIAL_OBJECT_STATE);
|
||||
c->config.set_flag(Client::Flag::SHOULD_SEND_ARTIFICIAL_FLAG_STATE);
|
||||
c->config.set_flag(Client::Flag::SHOULD_SEND_ARTIFICIAL_PLAYER_STATES);
|
||||
}
|
||||
send_command(c, 0x1D, 0x00);
|
||||
|
||||
if (!is_v1_or_v2(c->version())) {
|
||||
send_command(c, 0xAC, 0x00);
|
||||
}
|
||||
}
|
||||
|
||||
static void on_AC_V3_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
check_size_v(data.size(), 0);
|
||||
|
||||
auto l = c->require_lobby();
|
||||
|
||||
if (c->config.check_flag(Client::Flag::LOADING_RUNNING_JOINABLE_QUEST)) {
|
||||
if (l->base_version != Version::BB_V4) {
|
||||
throw logic_error("joinable quest started on non-BB version");
|
||||
}
|
||||
|
||||
auto leader_c = l->clients.at(l->leader_id);
|
||||
if (!leader_c) {
|
||||
throw logic_error("lobby leader is missing");
|
||||
}
|
||||
|
||||
send_command(leader_c, 0xDD, c->lobby_client_id);
|
||||
send_command(c, 0xAC, 0x00);
|
||||
|
||||
c->log.info("Creating game join command queue");
|
||||
c->game_join_command_queue = make_unique<deque<Client::JoinCommand>>();
|
||||
send_command(c, 0x1D, 0x00);
|
||||
on_joinable_quest_loaded(c);
|
||||
|
||||
} else if (c->config.check_flag(Client::Flag::LOADING_QUEST)) {
|
||||
c->config.clear_flag(Client::Flag::LOADING_QUEST);
|
||||
c->log.info("LOADING_QUEST flag cleared");
|
||||
auto l = c->require_lobby();
|
||||
if (l->quest && send_quest_barrier_if_all_clients_ready(l)) {
|
||||
on_quest_loaded(l);
|
||||
}
|
||||
@@ -4343,6 +4380,7 @@ static void on_C1_PC(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
if (game) {
|
||||
s->change_client_lobby(c, game);
|
||||
c->config.set_flag(Client::Flag::LOADING);
|
||||
c->log.info("LOADING flag set");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4415,6 +4453,7 @@ static void on_0C_C1_E7_EC(shared_ptr<Client> c, uint16_t command, uint32_t, str
|
||||
if (game) {
|
||||
s->change_client_lobby(c, game);
|
||||
c->config.set_flag(Client::Flag::LOADING);
|
||||
c->log.info("LOADING flag set");
|
||||
|
||||
// There is a bug in DC NTE and 11/2000 that causes them to assign item IDs
|
||||
// twice when joining a game. If there are other players in the game, this
|
||||
@@ -4472,6 +4511,7 @@ static void on_C1_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
if (game) {
|
||||
s->change_client_lobby(c, game);
|
||||
c->config.set_flag(Client::Flag::LOADING);
|
||||
c->log.info("LOADING flag set");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4501,6 +4541,7 @@ static void on_6F(shared_ptr<Client> c, uint16_t command, uint32_t, string& data
|
||||
// don't matter for Ep3)
|
||||
if (c->config.check_flag(Client::Flag::LOADING)) {
|
||||
c->config.clear_flag(Client::Flag::LOADING);
|
||||
c->log.info("LOADING flag cleared");
|
||||
|
||||
// The client sends 6F when it has created its TObjPlayer and assigned its
|
||||
// item IDs. For the leader, however, this happens before any inbound commands
|
||||
@@ -4533,6 +4574,9 @@ static void on_6F(shared_ptr<Client> c, uint16_t command, uint32_t, string& data
|
||||
send_update_team_reward_flags(c);
|
||||
send_all_nearby_team_metadatas_to_client(c, false);
|
||||
|
||||
// On BB, send the joinable quest file as soon as the client is ready (6F).
|
||||
// On other versions, we send joinable quests in the 99 handler instead,
|
||||
// since we need to wait for the client's save to complete.
|
||||
// BB sends 016F when the client is done loading a quest. In that case, we
|
||||
// shouldn't send the quest to them again!
|
||||
if ((command == 0x006F) && l->check_flag(Lobby::Flag::JOINABLE_QUEST_IN_PROGRESS)) {
|
||||
@@ -4549,10 +4593,12 @@ static void on_6F(shared_ptr<Client> c, uint16_t command, uint32_t, string& data
|
||||
send_open_quest_file(c, bin_filename, bin_filename, "", vq->quest_number, QuestFileType::ONLINE, vq->bin_contents);
|
||||
send_open_quest_file(c, dat_filename, dat_filename, "", vq->quest_number, QuestFileType::ONLINE, vq->dat_contents);
|
||||
c->config.set_flag(Client::Flag::LOADING_RUNNING_JOINABLE_QUEST);
|
||||
c->log.info("LOADING_RUNNING_JOINABLE_QUEST flag set");
|
||||
should_resume_game = false;
|
||||
|
||||
} else if ((command == 0x016F) && c->config.check_flag(Client::Flag::LOADING_RUNNING_JOINABLE_QUEST)) {
|
||||
c->config.clear_flag(Client::Flag::LOADING_RUNNING_JOINABLE_QUEST);
|
||||
c->log.info("LOADING_RUNNING_JOINABLE_QUEST flag cleared");
|
||||
}
|
||||
if (l->map) {
|
||||
send_rare_enemy_index_list(c, l->map->rare_enemy_indexes);
|
||||
@@ -4616,6 +4662,34 @@ static void on_99(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
} else if (c->should_send_to_proxy_server) {
|
||||
send_client_to_proxy_server(c);
|
||||
}
|
||||
|
||||
// See the comment in on_6F about why we do this here, but only for non-BB
|
||||
// versions.
|
||||
if (l && l->check_flag(Lobby::Flag::JOINABLE_QUEST_IN_PROGRESS) && (c->version() != Version::BB_V4)) {
|
||||
if (!l->quest) {
|
||||
throw runtime_error("JOINABLE_QUEST_IN_PROGRESS is set, but lobby has no quest");
|
||||
}
|
||||
auto vq = l->quest->version(c->version(), c->language());
|
||||
if (!vq) {
|
||||
throw runtime_error("JOINABLE_QUEST_IN_PROGRESS is set, but lobby has no quest for client version");
|
||||
}
|
||||
string bin_filename = vq->bin_filename();
|
||||
string dat_filename = vq->dat_filename();
|
||||
|
||||
send_open_quest_file(c, bin_filename, bin_filename, "", vq->quest_number, QuestFileType::ONLINE, vq->bin_contents);
|
||||
send_open_quest_file(c, dat_filename, dat_filename, "", vq->quest_number, QuestFileType::ONLINE, vq->dat_contents);
|
||||
c->config.set_flag(Client::Flag::LOADING_RUNNING_JOINABLE_QUEST);
|
||||
c->log.info("LOADING_RUNNING_JOINABLE_QUEST flag set");
|
||||
|
||||
// On v1 and v2, there is no confirmation when the client is done
|
||||
// downloading the quest file, so just set the in-quest state immediately.
|
||||
// On v3 and later, we do this when we receive the AC command.
|
||||
// TODO: This might not work for GC NTE, since we wait for file chunk
|
||||
// confirmations (13 commands) but there is no AC command.
|
||||
if (is_v1_or_v2(c->version())) {
|
||||
on_joinable_quest_loaded(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void on_D0_V3_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
|
||||
+361
-363
@@ -27,7 +27,6 @@ struct SubcommandDefinition {
|
||||
enum Flag {
|
||||
ALWAYS_FORWARD_TO_WATCHERS = 0x01,
|
||||
ALLOW_FORWARD_TO_WATCHED_LOBBY = 0x02,
|
||||
USE_JOIN_COMMAND_QUEUE = 0x04,
|
||||
};
|
||||
uint8_t nte_subcommand;
|
||||
uint8_t proto_subcommand;
|
||||
@@ -189,7 +188,7 @@ static void forward_subcommand(shared_ptr<Client> c, uint8_t command, uint8_t fl
|
||||
if ((command == 0xCB) && (lc->version() == Version::GC_EP3_NTE)) {
|
||||
command = 0xC9;
|
||||
}
|
||||
if ((def_flags & SDF::USE_JOIN_COMMAND_QUEUE) && lc->game_join_command_queue) {
|
||||
if (lc->game_join_command_queue) {
|
||||
lc->log.info("Client not ready to receive join commands; adding to queue");
|
||||
auto& cmd = lc->game_join_command_queue->emplace_back();
|
||||
cmd.command = command;
|
||||
@@ -652,316 +651,350 @@ static void on_sync_joining_player_quest_flags(shared_ptr<Client> c, uint8_t com
|
||||
}
|
||||
}
|
||||
|
||||
class Parsed6x70Data {
|
||||
public:
|
||||
G_SyncPlayerDispAndInventory_BaseDCNTE base;
|
||||
uint32_t unknown_a5_nte = 0;
|
||||
uint32_t unknown_a6_nte = 0;
|
||||
uint16_t bonus_hp_from_materials = 0;
|
||||
uint16_t bonus_tp_from_materials = 0;
|
||||
parray<uint8_t, 0x10> unknown_a5_112000;
|
||||
parray<G_Unknown_6x70_SubA2, 5> unknown_a4_final;
|
||||
uint32_t language = 0;
|
||||
uint32_t player_tag = 0;
|
||||
uint32_t guild_card_number = 0;
|
||||
uint32_t unknown_a6 = 0;
|
||||
uint32_t battle_team_number = 0;
|
||||
Telepipe telepipe;
|
||||
uint32_t unknown_a8 = 0;
|
||||
parray<uint8_t, 0x10> unknown_a9_nte_112000;
|
||||
G_Unknown_6x70_SubA1 unknown_a9_final;
|
||||
uint32_t area = 0;
|
||||
uint32_t flags2 = 0;
|
||||
parray<uint8_t, 0x14> technique_levels_v1 = 0xFF;
|
||||
PlayerVisualConfig visual;
|
||||
std::string name;
|
||||
PlayerStats stats;
|
||||
uint32_t num_items = 0;
|
||||
parray<PlayerInventoryItem, 0x1E> items;
|
||||
uint32_t floor = 0;
|
||||
uint64_t xb_user_id = 0;
|
||||
uint32_t xb_unknown_a16 = 0;
|
||||
|
||||
Parsed6x70Data(const G_SyncPlayerDispAndInventory_DCNTE_6x70& cmd, uint32_t guild_card_number)
|
||||
: base(cmd.base),
|
||||
unknown_a5_nte(cmd.unknown_a5),
|
||||
unknown_a6_nte(cmd.unknown_a6),
|
||||
bonus_hp_from_materials(0),
|
||||
bonus_tp_from_materials(0),
|
||||
language(0),
|
||||
player_tag(0x00010000),
|
||||
guild_card_number(guild_card_number),
|
||||
unknown_a6(0),
|
||||
battle_team_number(0),
|
||||
telepipe(cmd.telepipe),
|
||||
unknown_a8(cmd.unknown_a8),
|
||||
unknown_a9_nte_112000(cmd.unknown_a9),
|
||||
area(cmd.area),
|
||||
flags2(cmd.flags2),
|
||||
visual(cmd.visual),
|
||||
stats(cmd.stats),
|
||||
num_items(cmd.num_items),
|
||||
items(cmd.items),
|
||||
floor(cmd.area),
|
||||
xb_user_id(this->default_xb_user_id()),
|
||||
xb_unknown_a16(0) {
|
||||
this->name = this->visual.name.decode(this->language);
|
||||
static void transcode_inventory_items(
|
||||
parray<PlayerInventoryItem, 30>& items,
|
||||
size_t num_items,
|
||||
Version from_version,
|
||||
Version to_version,
|
||||
shared_ptr<const ItemParameterTable> to_item_parameter_table) {
|
||||
if (num_items > 30) {
|
||||
throw runtime_error("invalid inventory item count");
|
||||
}
|
||||
|
||||
Parsed6x70Data(const G_SyncPlayerDispAndInventory_DC112000_6x70& cmd, uint32_t guild_card_number, uint8_t language)
|
||||
: base(cmd.base),
|
||||
unknown_a5_nte(0),
|
||||
unknown_a6_nte(0),
|
||||
bonus_hp_from_materials(cmd.bonus_hp_from_materials),
|
||||
bonus_tp_from_materials(cmd.bonus_tp_from_materials),
|
||||
unknown_a5_112000(cmd.unknown_a5),
|
||||
language(language),
|
||||
player_tag(0x00010000),
|
||||
guild_card_number(guild_card_number),
|
||||
unknown_a6(0),
|
||||
battle_team_number(0),
|
||||
telepipe(cmd.telepipe),
|
||||
unknown_a8(cmd.unknown_a8),
|
||||
unknown_a9_nte_112000(cmd.unknown_a9),
|
||||
area(cmd.area),
|
||||
flags2(cmd.flags2),
|
||||
visual(cmd.visual),
|
||||
stats(cmd.stats),
|
||||
num_items(cmd.num_items),
|
||||
items(cmd.items),
|
||||
floor(cmd.area),
|
||||
xb_user_id(this->default_xb_user_id()),
|
||||
xb_unknown_a16(0) {
|
||||
this->name = this->visual.name.decode(this->language);
|
||||
}
|
||||
|
||||
Parsed6x70Data(const G_SyncPlayerDispAndInventory_DC_PC_6x70& cmd, uint32_t guild_card_number)
|
||||
: Parsed6x70Data(cmd.base, guild_card_number) {
|
||||
this->stats = cmd.stats;
|
||||
this->num_items = cmd.num_items;
|
||||
this->items = cmd.items;
|
||||
this->floor = cmd.base.area;
|
||||
this->xb_user_id = this->default_xb_user_id();
|
||||
this->xb_unknown_a16 = 0;
|
||||
this->name = this->visual.name.decode(this->language);
|
||||
}
|
||||
|
||||
Parsed6x70Data(const G_SyncPlayerDispAndInventory_GC_6x70& cmd, uint32_t guild_card_number)
|
||||
: Parsed6x70Data(cmd.base, guild_card_number) {
|
||||
this->stats = cmd.stats;
|
||||
this->num_items = cmd.num_items;
|
||||
this->items = cmd.items;
|
||||
this->floor = cmd.floor;
|
||||
this->xb_user_id = this->default_xb_user_id();
|
||||
this->xb_unknown_a16 = 0;
|
||||
this->name = this->visual.name.decode(this->language);
|
||||
}
|
||||
|
||||
Parsed6x70Data(const G_SyncPlayerDispAndInventory_XB_6x70& cmd, uint32_t guild_card_number)
|
||||
: Parsed6x70Data(cmd.base, guild_card_number) {
|
||||
this->stats = cmd.stats;
|
||||
this->num_items = cmd.num_items;
|
||||
this->items = cmd.items;
|
||||
this->floor = cmd.floor;
|
||||
this->xb_user_id = (static_cast<uint64_t>(cmd.xb_user_id_high) << 32) | cmd.xb_user_id_low;
|
||||
this->xb_unknown_a16 = cmd.unknown_a16;
|
||||
this->name = this->visual.name.decode(this->language);
|
||||
}
|
||||
|
||||
Parsed6x70Data(const G_SyncPlayerDispAndInventory_BB_6x70& cmd, uint32_t guild_card_number)
|
||||
: Parsed6x70Data(cmd.base, guild_card_number) {
|
||||
this->stats = cmd.stats;
|
||||
this->num_items = cmd.num_items;
|
||||
this->items = cmd.items;
|
||||
this->floor = cmd.floor;
|
||||
this->xb_user_id = this->default_xb_user_id();
|
||||
this->xb_unknown_a16 = cmd.unknown_a16;
|
||||
this->name = cmd.name.decode(cmd.base.language);
|
||||
this->visual.name.encode(this->name, cmd.base.language);
|
||||
}
|
||||
|
||||
G_SyncPlayerDispAndInventory_DCNTE_6x70 as_dc_nte() const {
|
||||
G_SyncPlayerDispAndInventory_DCNTE_6x70 ret;
|
||||
ret.base = this->base;
|
||||
ret.unknown_a5 = this->unknown_a5_nte;
|
||||
ret.unknown_a6 = this->unknown_a6;
|
||||
ret.telepipe = this->telepipe;
|
||||
ret.unknown_a8 = this->unknown_a8;
|
||||
ret.unknown_a9 = this->unknown_a9_nte_112000;
|
||||
ret.area = this->area;
|
||||
ret.flags2 = this->flags2;
|
||||
ret.visual = this->visual;
|
||||
ret.stats = this->stats;
|
||||
ret.num_items = this->num_items;
|
||||
ret.items = this->items;
|
||||
return ret;
|
||||
}
|
||||
|
||||
G_SyncPlayerDispAndInventory_DC112000_6x70 as_dc_112000() const {
|
||||
G_SyncPlayerDispAndInventory_DC112000_6x70 ret;
|
||||
ret.base = this->base;
|
||||
ret.bonus_hp_from_materials = this->bonus_hp_from_materials;
|
||||
ret.bonus_tp_from_materials = this->bonus_tp_from_materials;
|
||||
ret.unknown_a5 = this->unknown_a5_112000;
|
||||
ret.telepipe = this->telepipe;
|
||||
ret.unknown_a8 = this->unknown_a8;
|
||||
ret.unknown_a9 = this->unknown_a9_nte_112000;
|
||||
ret.area = this->area;
|
||||
ret.flags2 = this->flags2;
|
||||
ret.visual = this->visual;
|
||||
ret.stats = this->stats;
|
||||
ret.num_items = this->num_items;
|
||||
ret.items = this->items;
|
||||
return ret;
|
||||
}
|
||||
|
||||
G_SyncPlayerDispAndInventory_DC_PC_6x70 as_dc_pc() const {
|
||||
G_SyncPlayerDispAndInventory_DC_PC_6x70 ret;
|
||||
ret.base = this->base_v1();
|
||||
ret.stats = this->stats;
|
||||
ret.num_items = this->num_items;
|
||||
ret.items = this->items;
|
||||
return ret;
|
||||
}
|
||||
|
||||
G_SyncPlayerDispAndInventory_GC_6x70 as_gc() const {
|
||||
G_SyncPlayerDispAndInventory_GC_6x70 ret;
|
||||
ret.base = this->base_v1();
|
||||
ret.stats = this->stats;
|
||||
ret.num_items = this->num_items;
|
||||
ret.items = this->items;
|
||||
ret.floor = this->floor;
|
||||
return ret;
|
||||
}
|
||||
|
||||
G_SyncPlayerDispAndInventory_XB_6x70 as_xb() const {
|
||||
G_SyncPlayerDispAndInventory_XB_6x70 ret;
|
||||
ret.base = this->base_v1();
|
||||
ret.stats = this->stats;
|
||||
ret.num_items = this->num_items;
|
||||
ret.items = this->items;
|
||||
ret.floor = this->floor;
|
||||
ret.xb_user_id_high = this->xb_user_id >> 32;
|
||||
ret.xb_user_id_low = this->xb_user_id;
|
||||
ret.unknown_a16 = this->xb_unknown_a16;
|
||||
return ret;
|
||||
}
|
||||
|
||||
G_SyncPlayerDispAndInventory_BB_6x70 as_bb(uint8_t language) const {
|
||||
G_SyncPlayerDispAndInventory_BB_6x70 ret;
|
||||
ret.base = this->base_v1();
|
||||
ret.name.encode(this->name, language);
|
||||
ret.base.visual.name.encode(string_printf("%10" PRId32, this->guild_card_number), language);
|
||||
ret.stats = this->stats;
|
||||
ret.num_items = this->num_items;
|
||||
ret.items = this->items;
|
||||
ret.floor = this->floor;
|
||||
ret.xb_user_id_high = this->xb_user_id >> 32;
|
||||
ret.xb_user_id_low = this->xb_user_id;
|
||||
ret.unknown_a16 = this->xb_unknown_a16;
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint64_t default_xb_user_id() const {
|
||||
return (0xAE00000000000000 | this->guild_card_number);
|
||||
}
|
||||
|
||||
void clear_v1_unused_item_fields() {
|
||||
for (size_t z = 0; z < min<uint32_t>(this->num_items, 30); z++) {
|
||||
auto& item = this->items[z];
|
||||
item.unknown_a1 = 0;
|
||||
item.extension_data1 = 0;
|
||||
item.extension_data2 = 0;
|
||||
if (from_version != to_version) {
|
||||
for (size_t z = 0; z < num_items; z++) {
|
||||
items[z].data.decode_for_version(from_version);
|
||||
items[z].data.encode_for_version(to_version, to_item_parameter_table);
|
||||
}
|
||||
}
|
||||
|
||||
void clear_dc_protos_unused_item_fields() {
|
||||
for (size_t z = 0; z < min<uint32_t>(this->num_items, 30); z++) {
|
||||
auto& item = this->items[z];
|
||||
item.unknown_a1 = 0;
|
||||
item.extension_data1 = 0;
|
||||
item.extension_data2 = 0;
|
||||
item.data.data2d = 0;
|
||||
for (size_t z = num_items; z < 30; z++) {
|
||||
auto& item = items[z];
|
||||
item.present = 0;
|
||||
item.unknown_a1 = 0;
|
||||
item.flags = 0;
|
||||
item.data.clear();
|
||||
}
|
||||
if (is_v1(to_version)) {
|
||||
for (size_t z = 0; z < 30; z++) {
|
||||
auto& item = items[z];
|
||||
item.extension_data1 = 0x00;
|
||||
item.extension_data2 = 0x00;
|
||||
}
|
||||
} else {
|
||||
for (size_t z = 20; z < 30; z++) {
|
||||
items[z].extension_data1 = 0x00;
|
||||
}
|
||||
for (size_t z = 16; z < 30; z++) {
|
||||
items[z].extension_data2 = 0x00;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void transcode_inventory_items(
|
||||
Version from_version,
|
||||
Version to_version,
|
||||
shared_ptr<const ItemParameterTable> to_item_parameter_table) {
|
||||
if (this->num_items > 30) {
|
||||
throw runtime_error("invalid inventory item count");
|
||||
}
|
||||
if (from_version != to_version) {
|
||||
for (size_t z = 0; z < this->num_items; z++) {
|
||||
this->items[z].data.decode_for_version(from_version);
|
||||
this->items[z].data.encode_for_version(to_version, to_item_parameter_table);
|
||||
}
|
||||
}
|
||||
for (size_t z = this->num_items; z < 30; z++) {
|
||||
auto& item = this->items[z];
|
||||
item.present = 0;
|
||||
item.unknown_a1 = 0;
|
||||
item.flags = 0;
|
||||
item.data.clear();
|
||||
}
|
||||
if (is_v1(to_version)) {
|
||||
for (size_t z = 0; z < 30; z++) {
|
||||
auto& item = this->items[z];
|
||||
item.extension_data1 = 0x00;
|
||||
item.extension_data2 = 0x00;
|
||||
}
|
||||
Parsed6x70Data::Parsed6x70Data(
|
||||
const G_SyncPlayerDispAndInventory_DCNTE_6x70& cmd, uint32_t guild_card_number, Version from_version)
|
||||
: from_version(from_version),
|
||||
item_version(from_version),
|
||||
base(cmd.base),
|
||||
unknown_a5_nte(cmd.unknown_a5),
|
||||
unknown_a6_nte(cmd.unknown_a6),
|
||||
bonus_hp_from_materials(0),
|
||||
bonus_tp_from_materials(0),
|
||||
language(0),
|
||||
player_tag(0x00010000),
|
||||
guild_card_number(guild_card_number),
|
||||
unknown_a6(0),
|
||||
battle_team_number(0),
|
||||
telepipe(cmd.telepipe),
|
||||
unknown_a8(cmd.unknown_a8),
|
||||
unknown_a9_nte_112000(cmd.unknown_a9),
|
||||
area(cmd.area),
|
||||
flags2(cmd.flags2),
|
||||
visual(cmd.visual),
|
||||
stats(cmd.stats),
|
||||
num_items(cmd.num_items),
|
||||
items(cmd.items),
|
||||
floor(cmd.area),
|
||||
xb_user_id(this->default_xb_user_id()),
|
||||
xb_unknown_a16(0) {
|
||||
this->name = this->visual.name.decode(this->language);
|
||||
}
|
||||
|
||||
Parsed6x70Data::Parsed6x70Data(
|
||||
const G_SyncPlayerDispAndInventory_DC112000_6x70& cmd, uint32_t guild_card_number, uint8_t language, Version from_version)
|
||||
: from_version(from_version),
|
||||
item_version(from_version),
|
||||
base(cmd.base),
|
||||
unknown_a5_nte(0),
|
||||
unknown_a6_nte(0),
|
||||
bonus_hp_from_materials(cmd.bonus_hp_from_materials),
|
||||
bonus_tp_from_materials(cmd.bonus_tp_from_materials),
|
||||
unknown_a5_112000(cmd.unknown_a5),
|
||||
language(language),
|
||||
player_tag(0x00010000),
|
||||
guild_card_number(guild_card_number),
|
||||
unknown_a6(0),
|
||||
battle_team_number(0),
|
||||
telepipe(cmd.telepipe),
|
||||
unknown_a8(cmd.unknown_a8),
|
||||
unknown_a9_nte_112000(cmd.unknown_a9),
|
||||
area(cmd.area),
|
||||
flags2(cmd.flags2),
|
||||
visual(cmd.visual),
|
||||
stats(cmd.stats),
|
||||
num_items(cmd.num_items),
|
||||
items(cmd.items),
|
||||
floor(cmd.area),
|
||||
xb_user_id(this->default_xb_user_id()),
|
||||
xb_unknown_a16(0) {
|
||||
this->name = this->visual.name.decode(this->language);
|
||||
}
|
||||
|
||||
Parsed6x70Data::Parsed6x70Data(
|
||||
const G_SyncPlayerDispAndInventory_DC_PC_6x70& cmd, uint32_t guild_card_number, Version from_version)
|
||||
: Parsed6x70Data(cmd.base, guild_card_number, from_version) {
|
||||
this->stats = cmd.stats;
|
||||
this->num_items = cmd.num_items;
|
||||
this->items = cmd.items;
|
||||
this->floor = cmd.base.area;
|
||||
this->xb_user_id = this->default_xb_user_id();
|
||||
this->xb_unknown_a16 = 0;
|
||||
this->name = this->visual.name.decode(this->language);
|
||||
}
|
||||
|
||||
Parsed6x70Data::Parsed6x70Data(
|
||||
const G_SyncPlayerDispAndInventory_GC_6x70& cmd, uint32_t guild_card_number, Version from_version)
|
||||
: Parsed6x70Data(cmd.base, guild_card_number, from_version) {
|
||||
this->stats = cmd.stats;
|
||||
this->num_items = cmd.num_items;
|
||||
this->items = cmd.items;
|
||||
this->floor = cmd.floor;
|
||||
this->xb_user_id = this->default_xb_user_id();
|
||||
this->xb_unknown_a16 = 0;
|
||||
this->name = this->visual.name.decode(this->language);
|
||||
}
|
||||
|
||||
Parsed6x70Data::Parsed6x70Data(
|
||||
const G_SyncPlayerDispAndInventory_XB_6x70& cmd, uint32_t guild_card_number, Version from_version)
|
||||
: Parsed6x70Data(cmd.base, guild_card_number, from_version) {
|
||||
this->stats = cmd.stats;
|
||||
this->num_items = cmd.num_items;
|
||||
this->items = cmd.items;
|
||||
this->floor = cmd.floor;
|
||||
this->xb_user_id = (static_cast<uint64_t>(cmd.xb_user_id_high) << 32) | cmd.xb_user_id_low;
|
||||
this->xb_unknown_a16 = cmd.unknown_a16;
|
||||
this->name = this->visual.name.decode(this->language);
|
||||
}
|
||||
|
||||
Parsed6x70Data::Parsed6x70Data(
|
||||
const G_SyncPlayerDispAndInventory_BB_6x70& cmd, uint32_t guild_card_number, Version from_version)
|
||||
: Parsed6x70Data(cmd.base, guild_card_number, from_version) {
|
||||
this->stats = cmd.stats;
|
||||
this->num_items = cmd.num_items;
|
||||
this->items = cmd.items;
|
||||
this->floor = cmd.floor;
|
||||
this->xb_user_id = this->default_xb_user_id();
|
||||
this->xb_unknown_a16 = cmd.unknown_a16;
|
||||
this->name = cmd.name.decode(cmd.base.language);
|
||||
this->visual.name.encode(this->name, cmd.base.language);
|
||||
}
|
||||
|
||||
G_SyncPlayerDispAndInventory_DCNTE_6x70 Parsed6x70Data::as_dc_nte(shared_ptr<ServerState> s) const {
|
||||
G_SyncPlayerDispAndInventory_DCNTE_6x70 ret;
|
||||
ret.base = this->base;
|
||||
ret.unknown_a5 = this->unknown_a5_nte;
|
||||
ret.unknown_a6 = this->unknown_a6;
|
||||
ret.telepipe = this->telepipe;
|
||||
ret.unknown_a8 = this->unknown_a8;
|
||||
ret.unknown_a9 = this->unknown_a9_nte_112000;
|
||||
ret.area = this->area;
|
||||
ret.flags2 = this->flags2;
|
||||
ret.visual = this->visual;
|
||||
ret.stats = this->stats;
|
||||
ret.num_items = this->num_items;
|
||||
ret.items = this->items;
|
||||
|
||||
transcode_inventory_items(
|
||||
ret.items, ret.num_items, this->item_version, Version::DC_NTE, s->item_parameter_table_for_encode(Version::DC_NTE));
|
||||
ret.visual.enforce_lobby_join_limits_for_version(Version::DC_NTE);
|
||||
if (s->version_name_colors) {
|
||||
ret.visual.name_color = s->name_color_for_version(this->from_version);
|
||||
ret.visual.compute_name_color_checksum();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
G_SyncPlayerDispAndInventory_DC112000_6x70 Parsed6x70Data::as_dc_112000(shared_ptr<ServerState> s) const {
|
||||
G_SyncPlayerDispAndInventory_DC112000_6x70 ret;
|
||||
ret.base = this->base;
|
||||
ret.bonus_hp_from_materials = this->bonus_hp_from_materials;
|
||||
ret.bonus_tp_from_materials = this->bonus_tp_from_materials;
|
||||
ret.unknown_a5 = this->unknown_a5_112000;
|
||||
ret.telepipe = this->telepipe;
|
||||
ret.unknown_a8 = this->unknown_a8;
|
||||
ret.unknown_a9 = this->unknown_a9_nte_112000;
|
||||
ret.area = this->area;
|
||||
ret.flags2 = this->flags2;
|
||||
ret.visual = this->visual;
|
||||
ret.stats = this->stats;
|
||||
ret.num_items = this->num_items;
|
||||
ret.items = this->items;
|
||||
|
||||
transcode_inventory_items(
|
||||
ret.items, ret.num_items, this->item_version, Version::DC_V1_11_2000_PROTOTYPE, s->item_parameter_table_for_encode(Version::DC_V1_11_2000_PROTOTYPE));
|
||||
ret.visual.enforce_lobby_join_limits_for_version(Version::DC_V1_11_2000_PROTOTYPE);
|
||||
if (s->version_name_colors) {
|
||||
ret.visual.name_color = s->name_color_for_version(this->from_version);
|
||||
ret.visual.compute_name_color_checksum();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
G_SyncPlayerDispAndInventory_DC_PC_6x70 Parsed6x70Data::as_dc_pc(shared_ptr<ServerState> s, Version to_version) const {
|
||||
G_SyncPlayerDispAndInventory_DC_PC_6x70 ret;
|
||||
ret.base = this->base_v1();
|
||||
ret.stats = this->stats;
|
||||
ret.num_items = this->num_items;
|
||||
ret.items = this->items;
|
||||
|
||||
transcode_inventory_items(
|
||||
ret.items, ret.num_items, this->item_version, to_version, s->item_parameter_table_for_encode(to_version));
|
||||
ret.base.visual.enforce_lobby_join_limits_for_version(to_version);
|
||||
if (s->version_name_colors) {
|
||||
ret.base.visual.name_color = s->name_color_for_version(this->from_version);
|
||||
ret.base.visual.compute_name_color_checksum();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
G_SyncPlayerDispAndInventory_GC_6x70 Parsed6x70Data::as_gc_gcnte(shared_ptr<ServerState> s, Version to_version) const {
|
||||
G_SyncPlayerDispAndInventory_GC_6x70 ret;
|
||||
ret.base = this->base_v1();
|
||||
ret.stats = this->stats;
|
||||
ret.num_items = this->num_items;
|
||||
ret.items = this->items;
|
||||
ret.floor = this->floor;
|
||||
|
||||
transcode_inventory_items(
|
||||
ret.items, ret.num_items, this->item_version, to_version, s->item_parameter_table_for_encode(to_version));
|
||||
ret.base.visual.enforce_lobby_join_limits_for_version(to_version);
|
||||
if (s->version_name_colors) {
|
||||
ret.base.visual.name_color = s->name_color_for_version(this->from_version);
|
||||
if (is_v1_or_v2(to_version)) {
|
||||
ret.base.visual.compute_name_color_checksum();
|
||||
} else {
|
||||
for (size_t z = 20; z < 30; z++) {
|
||||
this->items[z].extension_data1 = 0x00;
|
||||
}
|
||||
for (size_t z = 16; z < 30; z++) {
|
||||
this->items[z].extension_data2 = 0x00;
|
||||
}
|
||||
ret.base.visual.name_color_checksum = 0;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
protected:
|
||||
Parsed6x70Data(const G_SyncPlayerDispAndInventory_BaseV1& base, uint32_t guild_card_number) {
|
||||
this->base = base.base;
|
||||
this->bonus_hp_from_materials = base.bonus_hp_from_materials;
|
||||
this->bonus_tp_from_materials = base.bonus_tp_from_materials;
|
||||
this->unknown_a4_final = base.unknown_a4;
|
||||
this->language = base.language;
|
||||
this->player_tag = base.player_tag;
|
||||
this->guild_card_number = guild_card_number; // Ignore the client's GC#
|
||||
this->unknown_a6 = base.unknown_a6;
|
||||
this->battle_team_number = base.battle_team_number;
|
||||
this->telepipe = base.telepipe;
|
||||
this->unknown_a8 = base.unknown_a8;
|
||||
this->unknown_a9_final = base.unknown_a9;
|
||||
this->area = base.area;
|
||||
this->flags2 = base.flags2;
|
||||
this->technique_levels_v1 = base.technique_levels_v1;
|
||||
this->visual = base.visual;
|
||||
}
|
||||
G_SyncPlayerDispAndInventory_XB_6x70 Parsed6x70Data::as_xb(shared_ptr<ServerState> s) const {
|
||||
G_SyncPlayerDispAndInventory_XB_6x70 ret;
|
||||
ret.base = this->base_v1();
|
||||
ret.stats = this->stats;
|
||||
ret.num_items = this->num_items;
|
||||
ret.items = this->items;
|
||||
ret.floor = this->floor;
|
||||
ret.xb_user_id_high = this->xb_user_id >> 32;
|
||||
ret.xb_user_id_low = this->xb_user_id;
|
||||
ret.unknown_a16 = this->xb_unknown_a16;
|
||||
|
||||
G_SyncPlayerDispAndInventory_BaseV1 base_v1() const {
|
||||
G_SyncPlayerDispAndInventory_BaseV1 ret;
|
||||
ret.base = this->base;
|
||||
ret.bonus_hp_from_materials = this->bonus_hp_from_materials;
|
||||
ret.bonus_tp_from_materials = this->bonus_tp_from_materials;
|
||||
ret.unknown_a4 = this->unknown_a4_final;
|
||||
ret.language = this->language;
|
||||
ret.player_tag = this->player_tag;
|
||||
ret.guild_card_number = this->guild_card_number;
|
||||
ret.unknown_a6 = this->unknown_a6;
|
||||
ret.battle_team_number = this->battle_team_number;
|
||||
ret.telepipe = this->telepipe;
|
||||
ret.unknown_a8 = this->unknown_a8;
|
||||
ret.unknown_a9 = this->unknown_a9_final;
|
||||
ret.area = this->area;
|
||||
ret.flags2 = this->flags2;
|
||||
ret.technique_levels_v1 = this->technique_levels_v1;
|
||||
ret.visual = this->visual;
|
||||
return ret;
|
||||
transcode_inventory_items(
|
||||
ret.items, ret.num_items, this->item_version, Version::XB_V3, s->item_parameter_table_for_encode(Version::XB_V3));
|
||||
ret.base.visual.enforce_lobby_join_limits_for_version(Version::XB_V3);
|
||||
if (s->version_name_colors) {
|
||||
ret.base.visual.name_color = s->name_color_for_version(this->from_version);
|
||||
ret.base.visual.name_color_checksum = 0;
|
||||
}
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
G_SyncPlayerDispAndInventory_BB_6x70 Parsed6x70Data::as_bb(shared_ptr<ServerState> s, uint8_t language) const {
|
||||
G_SyncPlayerDispAndInventory_BB_6x70 ret;
|
||||
ret.base = this->base_v1();
|
||||
ret.name.encode(this->name, language);
|
||||
ret.base.visual.name.encode(string_printf("%10" PRId32, this->guild_card_number), language);
|
||||
ret.stats = this->stats;
|
||||
ret.num_items = this->num_items;
|
||||
ret.items = this->items;
|
||||
ret.floor = this->floor;
|
||||
ret.xb_user_id_high = this->xb_user_id >> 32;
|
||||
ret.xb_user_id_low = this->xb_user_id;
|
||||
ret.unknown_a16 = this->xb_unknown_a16;
|
||||
|
||||
transcode_inventory_items(
|
||||
ret.items, ret.num_items, this->item_version, Version::BB_V4, s->item_parameter_table_for_encode(Version::BB_V4));
|
||||
ret.base.visual.enforce_lobby_join_limits_for_version(Version::BB_V4);
|
||||
if (s->version_name_colors) {
|
||||
ret.base.visual.name_color = s->name_color_for_version(this->from_version);
|
||||
ret.base.visual.name_color_checksum = 0;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint64_t Parsed6x70Data::default_xb_user_id() const {
|
||||
return (0xAE00000000000000 | this->guild_card_number);
|
||||
}
|
||||
|
||||
void Parsed6x70Data::clear_v1_unused_item_fields() {
|
||||
for (size_t z = 0; z < min<uint32_t>(this->num_items, 30); z++) {
|
||||
auto& item = this->items[z];
|
||||
item.unknown_a1 = 0;
|
||||
item.extension_data1 = 0;
|
||||
item.extension_data2 = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void Parsed6x70Data::clear_dc_protos_unused_item_fields() {
|
||||
for (size_t z = 0; z < min<uint32_t>(this->num_items, 30); z++) {
|
||||
auto& item = this->items[z];
|
||||
item.unknown_a1 = 0;
|
||||
item.extension_data1 = 0;
|
||||
item.extension_data2 = 0;
|
||||
item.data.data2d = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Parsed6x70Data::Parsed6x70Data(
|
||||
const G_SyncPlayerDispAndInventory_BaseV1& base, uint32_t guild_card_number, Version from_version)
|
||||
: from_version(from_version),
|
||||
item_version(this->from_version),
|
||||
base(base.base),
|
||||
bonus_hp_from_materials(base.bonus_hp_from_materials),
|
||||
bonus_tp_from_materials(base.bonus_tp_from_materials),
|
||||
unknown_a4_final(base.unknown_a4),
|
||||
language(base.language),
|
||||
player_tag(base.player_tag),
|
||||
guild_card_number(guild_card_number), // Ignore the client's GC#
|
||||
unknown_a6(base.unknown_a6),
|
||||
battle_team_number(base.battle_team_number),
|
||||
telepipe(base.telepipe),
|
||||
unknown_a8(base.unknown_a8),
|
||||
unknown_a9_final(base.unknown_a9),
|
||||
area(base.area),
|
||||
flags2(base.flags2),
|
||||
technique_levels_v1(base.technique_levels_v1),
|
||||
visual(base.visual) {}
|
||||
|
||||
G_SyncPlayerDispAndInventory_BaseV1 Parsed6x70Data::base_v1() const {
|
||||
G_SyncPlayerDispAndInventory_BaseV1 ret;
|
||||
ret.base = this->base;
|
||||
ret.bonus_hp_from_materials = this->bonus_hp_from_materials;
|
||||
ret.bonus_tp_from_materials = this->bonus_tp_from_materials;
|
||||
ret.unknown_a4 = this->unknown_a4_final;
|
||||
ret.language = this->language;
|
||||
ret.player_tag = this->player_tag;
|
||||
ret.guild_card_number = this->guild_card_number;
|
||||
ret.unknown_a6 = this->unknown_a6;
|
||||
ret.battle_team_number = this->battle_team_number;
|
||||
ret.telepipe = this->telepipe;
|
||||
ret.unknown_a8 = this->unknown_a8;
|
||||
ret.unknown_a9 = this->unknown_a9_final;
|
||||
ret.area = this->area;
|
||||
ret.flags2 = this->flags2;
|
||||
ret.technique_levels_v1 = this->technique_levels_v1;
|
||||
ret.visual = this->visual;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void on_sync_joining_player_disp_and_inventory(
|
||||
shared_ptr<Client> c, uint8_t command, uint8_t flag, void* data, size_t size) {
|
||||
@@ -985,92 +1018,53 @@ static void on_sync_joining_player_disp_and_inventory(
|
||||
send_command(target, 0x62, target->lobby_client_id, &data, sizeof(data));
|
||||
}
|
||||
|
||||
unique_ptr<Parsed6x70Data> parsed;
|
||||
|
||||
switch (c_v) {
|
||||
case Version::DC_NTE:
|
||||
parsed = make_unique<Parsed6x70Data>(
|
||||
c->last_reported_6x70.reset(new Parsed6x70Data(
|
||||
check_size_t<G_SyncPlayerDispAndInventory_DCNTE_6x70>(data, size),
|
||||
c->login->account->account_id);
|
||||
parsed->clear_dc_protos_unused_item_fields();
|
||||
c->login->account->account_id, c_v));
|
||||
c->last_reported_6x70->clear_dc_protos_unused_item_fields();
|
||||
break;
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
parsed = make_unique<Parsed6x70Data>(
|
||||
c->last_reported_6x70.reset(new Parsed6x70Data(
|
||||
check_size_t<G_SyncPlayerDispAndInventory_DC112000_6x70>(data, size),
|
||||
c->login->account->account_id,
|
||||
c->language());
|
||||
parsed->clear_dc_protos_unused_item_fields();
|
||||
c->login->account->account_id, c->language(), c_v));
|
||||
c->last_reported_6x70->clear_dc_protos_unused_item_fields();
|
||||
break;
|
||||
case Version::DC_V1:
|
||||
case Version::DC_V2:
|
||||
case Version::PC_NTE:
|
||||
case Version::PC_V2:
|
||||
parsed = make_unique<Parsed6x70Data>(
|
||||
c->last_reported_6x70.reset(new Parsed6x70Data(
|
||||
check_size_t<G_SyncPlayerDispAndInventory_DC_PC_6x70>(data, size),
|
||||
c->login->account->account_id);
|
||||
c->login->account->account_id, c_v));
|
||||
if (c_v == Version::DC_V1) {
|
||||
parsed->clear_v1_unused_item_fields();
|
||||
c->last_reported_6x70->clear_v1_unused_item_fields();
|
||||
}
|
||||
break;
|
||||
case Version::GC_NTE:
|
||||
case Version::GC_V3:
|
||||
case Version::GC_EP3_NTE:
|
||||
case Version::GC_EP3:
|
||||
parsed = make_unique<Parsed6x70Data>(
|
||||
c->last_reported_6x70.reset(new Parsed6x70Data(
|
||||
check_size_t<G_SyncPlayerDispAndInventory_GC_6x70>(data, size),
|
||||
c->login->account->account_id);
|
||||
c->login->account->account_id, c_v));
|
||||
break;
|
||||
case Version::XB_V3:
|
||||
parsed = make_unique<Parsed6x70Data>(
|
||||
c->last_reported_6x70.reset(new Parsed6x70Data(
|
||||
check_size_t<G_SyncPlayerDispAndInventory_XB_6x70>(data, size),
|
||||
c->login->account->account_id);
|
||||
c->login->account->account_id, c_v));
|
||||
break;
|
||||
case Version::BB_V4:
|
||||
parsed = make_unique<Parsed6x70Data>(
|
||||
c->last_reported_6x70.reset(new Parsed6x70Data(
|
||||
check_size_t<G_SyncPlayerDispAndInventory_BB_6x70>(data, size),
|
||||
c->login->account->account_id);
|
||||
c->login->account->account_id, c_v));
|
||||
break;
|
||||
default:
|
||||
throw logic_error("6x70 command from unknown game version");
|
||||
}
|
||||
|
||||
parsed->transcode_inventory_items(c_v, target_v, s->item_parameter_table_for_encode(target_v));
|
||||
parsed->visual.enforce_lobby_join_limits_for_version(target_v);
|
||||
if (s->version_name_colors) {
|
||||
parsed->visual.name_color = s->name_color_for_version(c_v);
|
||||
if (is_v1_or_v2(target_v)) {
|
||||
parsed->visual.compute_name_color_checksum();
|
||||
}
|
||||
}
|
||||
|
||||
switch (target_v) {
|
||||
case Version::DC_NTE:
|
||||
forward_subcommand_t(target, command, flag, parsed->as_dc_nte());
|
||||
break;
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
forward_subcommand_t(target, command, flag, parsed->as_dc_112000());
|
||||
break;
|
||||
case Version::DC_V1:
|
||||
case Version::DC_V2:
|
||||
case Version::PC_NTE:
|
||||
case Version::PC_V2:
|
||||
forward_subcommand_t(target, command, flag, parsed->as_dc_pc());
|
||||
break;
|
||||
case Version::GC_NTE:
|
||||
case Version::GC_V3:
|
||||
case Version::GC_EP3_NTE:
|
||||
case Version::GC_EP3:
|
||||
forward_subcommand_t(target, command, flag, parsed->as_gc());
|
||||
break;
|
||||
case Version::XB_V3:
|
||||
forward_subcommand_t(target, command, flag, parsed->as_xb());
|
||||
break;
|
||||
case Version::BB_V4:
|
||||
forward_subcommand_t(target, command, flag, parsed->as_bb(target->language()));
|
||||
break;
|
||||
default:
|
||||
throw logic_error("6x70 command from unknown game version");
|
||||
}
|
||||
send_game_player_state(target, c, false);
|
||||
}
|
||||
|
||||
static void on_forward_check_client(shared_ptr<Client> c, uint8_t command, uint8_t flag, void* data, size_t size) {
|
||||
@@ -1346,6 +1340,9 @@ static void on_set_player_visible(shared_ptr<Client> c, uint8_t command, uint8_t
|
||||
send_update_team_reward_flags(c);
|
||||
send_all_nearby_team_metadatas_to_client(c, false);
|
||||
}
|
||||
} else if (c->config.check_flag(Client::Flag::LOADING_RUNNING_JOINABLE_QUEST) && (c->version() != Version::BB_V4)) {
|
||||
c->config.clear_flag(Client::Flag::LOADING_RUNNING_JOINABLE_QUEST);
|
||||
c->log.info("LOADING_RUNNING_JOINABLE_QUEST flag cleared");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1360,6 +1357,7 @@ static void on_change_floor_6x1F(shared_ptr<Client> c, uint8_t command, uint8_t
|
||||
// the loading flag here instead.
|
||||
if (c->config.check_flag(Client::Flag::LOADING)) {
|
||||
c->config.clear_flag(Client::Flag::LOADING);
|
||||
c->log.info("LOADING flag cleared");
|
||||
send_resume_game(c->require_lobby(), c);
|
||||
c->require_lobby()->assign_inventory_and_bank_item_ids(c, true);
|
||||
}
|
||||
@@ -4470,14 +4468,14 @@ const SubcommandDefinition subcommand_definitions[0x100] = {
|
||||
/* 6x68 */ {0x59, 0x60, 0x68, on_forward_check_game},
|
||||
/* 6x69 */ {0x5A, 0x61, 0x69, on_npc_control},
|
||||
/* 6x6A */ {0x5B, 0x62, 0x6A, on_forward_check_game},
|
||||
/* 6x6B */ {0x5C, 0x63, 0x6B, on_sync_joining_player_compressed_state, SDF::USE_JOIN_COMMAND_QUEUE},
|
||||
/* 6x6C */ {0x5D, 0x64, 0x6C, on_sync_joining_player_compressed_state, SDF::USE_JOIN_COMMAND_QUEUE},
|
||||
/* 6x6D */ {0x5E, 0x65, 0x6D, on_sync_joining_player_compressed_state, SDF::USE_JOIN_COMMAND_QUEUE},
|
||||
/* 6x6E */ {0x5F, 0x66, 0x6E, on_sync_joining_player_compressed_state, SDF::USE_JOIN_COMMAND_QUEUE},
|
||||
/* 6x6F */ {0x00, 0x00, 0x6F, on_sync_joining_player_quest_flags, SDF::USE_JOIN_COMMAND_QUEUE},
|
||||
/* 6x70 */ {0x60, 0x67, 0x70, on_sync_joining_player_disp_and_inventory, SDF::USE_JOIN_COMMAND_QUEUE},
|
||||
/* 6x71 */ {0x00, 0x00, 0x71, on_forward_check_game_loading, SDF::USE_JOIN_COMMAND_QUEUE},
|
||||
/* 6x72 */ {0x61, 0x68, 0x72, on_forward_check_game_loading, SDF::USE_JOIN_COMMAND_QUEUE},
|
||||
/* 6x6B */ {0x5C, 0x63, 0x6B, on_sync_joining_player_compressed_state},
|
||||
/* 6x6C */ {0x5D, 0x64, 0x6C, on_sync_joining_player_compressed_state},
|
||||
/* 6x6D */ {0x5E, 0x65, 0x6D, on_sync_joining_player_compressed_state},
|
||||
/* 6x6E */ {0x5F, 0x66, 0x6E, on_sync_joining_player_compressed_state},
|
||||
/* 6x6F */ {0x00, 0x00, 0x6F, on_sync_joining_player_quest_flags},
|
||||
/* 6x70 */ {0x60, 0x67, 0x70, on_sync_joining_player_disp_and_inventory},
|
||||
/* 6x71 */ {0x00, 0x00, 0x71, on_forward_check_game_loading},
|
||||
/* 6x72 */ {0x61, 0x68, 0x72, on_forward_check_game_loading},
|
||||
/* 6x73 */ {0x00, 0x00, 0x73, on_forward_check_game_quest},
|
||||
/* 6x74 */ {0x62, 0x69, 0x74, on_word_select, SDF::ALWAYS_FORWARD_TO_WATCHERS},
|
||||
/* 6x75 */ {0x00, 0x00, 0x75, on_set_quest_flag},
|
||||
|
||||
@@ -36,3 +36,78 @@ DropReconcileResult reconcile_drop_request_with_map(
|
||||
const Client::Config& config,
|
||||
std::shared_ptr<Map> map,
|
||||
bool mark_drop);
|
||||
|
||||
class Parsed6x70Data {
|
||||
public:
|
||||
Version from_version;
|
||||
Version item_version;
|
||||
|
||||
G_SyncPlayerDispAndInventory_BaseDCNTE base;
|
||||
uint32_t unknown_a5_nte = 0;
|
||||
uint32_t unknown_a6_nte = 0;
|
||||
uint16_t bonus_hp_from_materials = 0;
|
||||
uint16_t bonus_tp_from_materials = 0;
|
||||
parray<uint8_t, 0x10> unknown_a5_112000;
|
||||
parray<G_Unknown_6x70_SubA2, 5> unknown_a4_final;
|
||||
uint32_t language = 0;
|
||||
uint32_t player_tag = 0;
|
||||
uint32_t guild_card_number = 0;
|
||||
uint32_t unknown_a6 = 0;
|
||||
uint32_t battle_team_number = 0;
|
||||
Telepipe telepipe;
|
||||
uint32_t unknown_a8 = 0;
|
||||
parray<uint8_t, 0x10> unknown_a9_nte_112000;
|
||||
G_Unknown_6x70_SubA1 unknown_a9_final;
|
||||
uint32_t area = 0;
|
||||
uint32_t flags2 = 0;
|
||||
parray<uint8_t, 0x14> technique_levels_v1 = 0xFF;
|
||||
PlayerVisualConfig visual;
|
||||
std::string name;
|
||||
PlayerStats stats;
|
||||
uint32_t num_items = 0;
|
||||
parray<PlayerInventoryItem, 0x1E> items;
|
||||
uint32_t floor = 0;
|
||||
uint64_t xb_user_id = 0;
|
||||
uint32_t xb_unknown_a16 = 0;
|
||||
|
||||
Parsed6x70Data(
|
||||
const G_SyncPlayerDispAndInventory_DCNTE_6x70& cmd,
|
||||
uint32_t guild_card_number,
|
||||
Version from_version);
|
||||
Parsed6x70Data(
|
||||
const G_SyncPlayerDispAndInventory_DC112000_6x70& cmd,
|
||||
uint32_t guild_card_number,
|
||||
uint8_t language,
|
||||
Version from_version);
|
||||
Parsed6x70Data(
|
||||
const G_SyncPlayerDispAndInventory_DC_PC_6x70& cmd,
|
||||
uint32_t guild_card_number,
|
||||
Version from_version);
|
||||
Parsed6x70Data(
|
||||
const G_SyncPlayerDispAndInventory_GC_6x70& cmd,
|
||||
uint32_t guild_card_number,
|
||||
Version from_version);
|
||||
Parsed6x70Data(
|
||||
const G_SyncPlayerDispAndInventory_XB_6x70& cmd,
|
||||
uint32_t guild_card_number,
|
||||
Version from_version);
|
||||
Parsed6x70Data(
|
||||
const G_SyncPlayerDispAndInventory_BB_6x70& cmd,
|
||||
uint32_t guild_card_number,
|
||||
Version from_version);
|
||||
|
||||
G_SyncPlayerDispAndInventory_DCNTE_6x70 as_dc_nte(std::shared_ptr<ServerState> s) const;
|
||||
G_SyncPlayerDispAndInventory_DC112000_6x70 as_dc_112000(std::shared_ptr<ServerState> s) const;
|
||||
G_SyncPlayerDispAndInventory_DC_PC_6x70 as_dc_pc(std::shared_ptr<ServerState> s, Version to_version) const;
|
||||
G_SyncPlayerDispAndInventory_GC_6x70 as_gc_gcnte(std::shared_ptr<ServerState> s, Version to_version) const;
|
||||
G_SyncPlayerDispAndInventory_XB_6x70 as_xb(std::shared_ptr<ServerState> s) const;
|
||||
G_SyncPlayerDispAndInventory_BB_6x70 as_bb(std::shared_ptr<ServerState> s, uint8_t language) const;
|
||||
|
||||
uint64_t default_xb_user_id() const;
|
||||
void clear_v1_unused_item_fields();
|
||||
void clear_dc_protos_unused_item_fields();
|
||||
|
||||
protected:
|
||||
Parsed6x70Data(const G_SyncPlayerDispAndInventory_BaseV1& base, uint32_t guild_card_number, Version from_version);
|
||||
G_SyncPlayerDispAndInventory_BaseV1 base_v1() const;
|
||||
};
|
||||
|
||||
+77
-2
@@ -19,6 +19,7 @@
|
||||
#include "FileContentsCache.hh"
|
||||
#include "PSOProtocol.hh"
|
||||
#include "ProxyServer.hh"
|
||||
#include "ReceiveSubcommands.hh"
|
||||
#include "StaticGameData.hh"
|
||||
#include "Text.hh"
|
||||
|
||||
@@ -2436,7 +2437,13 @@ void send_arrow_update(shared_ptr<Lobby> l) {
|
||||
}
|
||||
}
|
||||
|
||||
// tells the player that the joining player is done joining, and the game can resume
|
||||
void send_unblock_join(shared_ptr<Client> c) {
|
||||
if (!is_pre_v1(c->version())) {
|
||||
static const be_uint32_t data = 0x71010000;
|
||||
send_command(c, 0x60, 0x00, &data, sizeof(be_uint32_t));
|
||||
}
|
||||
}
|
||||
|
||||
void send_resume_game(shared_ptr<Lobby> l, shared_ptr<Client> ready_client) {
|
||||
for (auto lc : l->clients) {
|
||||
if (lc && (lc != ready_client) && !is_pre_v1(lc->version())) {
|
||||
@@ -2567,7 +2574,7 @@ void send_game_join_sync_command_compressed(
|
||||
}
|
||||
|
||||
if (c->game_join_command_queue) {
|
||||
c->log.info("Client not ready to receive join commands; adding to queue");
|
||||
c->log.info("Client not ready to receive game commands; adding to queue");
|
||||
auto& cmd = c->game_join_command_queue->emplace_back();
|
||||
cmd.command = 0x6D;
|
||||
cmd.flag = c->lobby_client_id;
|
||||
@@ -2789,6 +2796,74 @@ void send_game_flag_state(shared_ptr<Client> c) {
|
||||
}
|
||||
}
|
||||
|
||||
void send_game_player_state(shared_ptr<Client> to_c, shared_ptr<Client> from_c, bool apply_overrides) {
|
||||
if (!from_c->last_reported_6x70) {
|
||||
throw runtime_error("source client did not send a 6x70 command");
|
||||
}
|
||||
if (!from_c->login) {
|
||||
throw logic_error("source client is not logged in");
|
||||
}
|
||||
|
||||
auto s = to_c->require_server_state();
|
||||
Parsed6x70Data to_send = *from_c->last_reported_6x70;
|
||||
|
||||
to_send.base.client_id = from_c->lobby_client_id;
|
||||
to_send.player_tag = 0x00010000;
|
||||
to_send.guild_card_number = from_c->login->account->account_id;
|
||||
|
||||
if (apply_overrides) {
|
||||
auto from_p = from_c->character();
|
||||
to_send.base.x = from_c->x;
|
||||
to_send.base.z = from_c->z;
|
||||
to_send.bonus_hp_from_materials = from_p->inventory.hp_from_materials;
|
||||
to_send.bonus_tp_from_materials = from_p->inventory.tp_from_materials;
|
||||
to_send.language = from_c->language();
|
||||
// TODO: Deal with telepipes. Probably we should track their state via the
|
||||
// subcommands sent when they're created/destroyed, but currently we don't.
|
||||
to_send.area = from_c->floor;
|
||||
to_send.technique_levels_v1 = from_p->disp.technique_levels_v1;
|
||||
to_send.visual = from_p->disp.visual;
|
||||
to_send.name = from_p->disp.name.decode(from_c->language());
|
||||
if (to_c->version() != Version::BB_V4) {
|
||||
to_send.visual.name.encode(to_send.name, to_c->language());
|
||||
}
|
||||
to_send.stats = from_p->disp.stats;
|
||||
to_send.num_items = from_p->inventory.num_items;
|
||||
to_send.items = from_p->inventory.items;
|
||||
to_send.item_version = Version::BB_V4; // Server-side items are stored in BB encoding
|
||||
to_send.floor = from_c->floor;
|
||||
}
|
||||
|
||||
switch (to_c->version()) {
|
||||
case Version::DC_NTE:
|
||||
send_or_enqueue_command(to_c, 0x6D, to_c->lobby_client_id, to_send.as_dc_nte(s));
|
||||
break;
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
send_or_enqueue_command(to_c, 0x6D, to_c->lobby_client_id, to_send.as_dc_112000(s));
|
||||
break;
|
||||
case Version::DC_V1:
|
||||
case Version::DC_V2:
|
||||
case Version::PC_NTE:
|
||||
case Version::PC_V2:
|
||||
send_or_enqueue_command(to_c, 0x6D, to_c->lobby_client_id, to_send.as_dc_pc(s, to_c->version()));
|
||||
break;
|
||||
case Version::GC_NTE:
|
||||
case Version::GC_V3:
|
||||
case Version::GC_EP3_NTE:
|
||||
case Version::GC_EP3:
|
||||
send_or_enqueue_command(to_c, 0x6D, to_c->lobby_client_id, to_send.as_gc_gcnte(s, to_c->version()));
|
||||
break;
|
||||
case Version::XB_V3:
|
||||
send_or_enqueue_command(to_c, 0x6D, to_c->lobby_client_id, to_send.as_xb(s));
|
||||
break;
|
||||
case Version::BB_V4:
|
||||
send_or_enqueue_command(to_c, 0x6D, to_c->lobby_client_id, to_send.as_bb(s, to_c->language()));
|
||||
break;
|
||||
default:
|
||||
throw logic_error("attempting to send 6x70 command to unknown game version");
|
||||
}
|
||||
}
|
||||
|
||||
void send_drop_item_to_channel(shared_ptr<ServerState> s, Channel& ch, const ItemData& item,
|
||||
bool from_enemy, uint8_t floor, float x, float z, uint16_t entity_id) {
|
||||
uint8_t subcommand = get_pre_v1_subcommand(ch.version, 0x51, 0x58, 0x5F);
|
||||
|
||||
@@ -32,6 +32,21 @@ extern const std::unordered_set<std::string> bb_crypt_initial_client_commands;
|
||||
// pointer is given but size is accidentally not given (e.g. if the type of
|
||||
// data in the calling function is changed from string to void*).
|
||||
|
||||
template <typename CmdT>
|
||||
void send_or_enqueue_command(std::shared_ptr<Client> c, uint16_t command, uint32_t flag, const CmdT& cmd) {
|
||||
if (c->game_join_command_queue) {
|
||||
c->log.info("Client not ready to receive game commands; adding to queue");
|
||||
auto& q_cmd = c->game_join_command_queue->emplace_back();
|
||||
q_cmd.command = command;
|
||||
q_cmd.flag = flag;
|
||||
// TODO: It'd be nice to avoid this copy. Maybe take in a pointer to cmd
|
||||
// and move it into q_cmd somehow, so q_cmd can free it when needed?
|
||||
q_cmd.data.assign(reinterpret_cast<const char*>(&cmd), sizeof(cmd));
|
||||
} else {
|
||||
send_command(c, command, flag, &cmd, sizeof(cmd));
|
||||
}
|
||||
}
|
||||
|
||||
void send_command(std::shared_ptr<Client> c, uint16_t command,
|
||||
uint32_t flag, const std::vector<std::pair<const void*, size_t>>& blocks);
|
||||
|
||||
@@ -293,6 +308,7 @@ void send_execute_item_trade(std::shared_ptr<Client> c, const std::vector<ItemDa
|
||||
void send_execute_card_trade(std::shared_ptr<Client> c, const std::vector<std::pair<uint32_t, uint32_t>>& card_to_count);
|
||||
|
||||
void send_arrow_update(std::shared_ptr<Lobby> l);
|
||||
void send_unblock_join(std::shared_ptr<Client> c);
|
||||
void send_resume_game(std::shared_ptr<Lobby> l, std::shared_ptr<Client> ready_client);
|
||||
|
||||
enum PlayerStatsChange {
|
||||
@@ -331,6 +347,7 @@ void send_game_enemy_state(std::shared_ptr<Client> c);
|
||||
void send_game_object_state(std::shared_ptr<Client> c);
|
||||
void send_game_set_state(std::shared_ptr<Client> c);
|
||||
void send_game_flag_state(std::shared_ptr<Client> c);
|
||||
void send_game_player_state(std::shared_ptr<Client> to_c, std::shared_ptr<Client> from_c, bool apply_overrides);
|
||||
void send_drop_item_to_channel(std::shared_ptr<ServerState> s, Channel& ch, const ItemData& item,
|
||||
bool from_enemy, uint8_t floor, float x, float z, uint16_t request_id);
|
||||
void send_drop_item_to_lobby(std::shared_ptr<Lobby> l, const ItemData& item,
|
||||
|
||||
@@ -35,8 +35,8 @@
|
||||
// "ChallengeTemplateIndex": 0,
|
||||
|
||||
// Quests may be set to be unavailable until a preceding quest has been
|
||||
// cleared or a team reward has been purchased. To enable this feature, set a
|
||||
// value for AvailableIf in the quest's JSON file. (This is ignored if the
|
||||
// cleared or a team reward has been purchased. To enable this behavior, set
|
||||
// a value for AvailableIf in the quest's JSON file. (This is ignored if the
|
||||
// player has the DISABLE_QUEST_REQUIREMENTS flag in their account.) This
|
||||
// field's value should be an expression that tests any of the following:
|
||||
// F_XXXX: Quest flag specified in hex (e.g. F_014D)
|
||||
@@ -54,4 +54,11 @@
|
||||
// to false, this is ignored. This field is also ignored if the player has
|
||||
// the DISABLE_QUEST_REQUIREMENTS flag in their account.
|
||||
// "EnabledIf": "!F_0169",
|
||||
|
||||
// On BB, a quest's joinability flag is part of the quest file header, but
|
||||
// other versions don't natively support joining quests in progress. This
|
||||
// flag, if present, enables non-BB quests to be joined when already in
|
||||
// progress. Note that this will likely not work properly unless the quest
|
||||
// script is designed to support joining players.
|
||||
// "Joinable": true,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user