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);
|
s->change_client_lobby(c, game);
|
||||||
c->config.set_flag(Client::Flag::LOADING);
|
c->config.set_flag(Client::Flag::LOADING);
|
||||||
|
c->log.info("LOADING flag set");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
send_text_message(c, "$C4This command cannot\nbe used in a game");
|
send_text_message(c, "$C4This command cannot\nbe used in a game");
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
|
|
||||||
#include "IPStackSimulator.hh"
|
#include "IPStackSimulator.hh"
|
||||||
#include "Loggers.hh"
|
#include "Loggers.hh"
|
||||||
|
#include "SendCommands.hh"
|
||||||
#include "Server.hh"
|
#include "Server.hh"
|
||||||
#include "Version.hh"
|
#include "Version.hh"
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ extern const uint64_t CLIENT_CONFIG_MAGIC;
|
|||||||
|
|
||||||
class Server;
|
class Server;
|
||||||
struct Lobby;
|
struct Lobby;
|
||||||
|
class Parsed6x70Data;
|
||||||
|
|
||||||
class Client : public std::enable_shared_from_this<Client> {
|
class Client : public std::enable_shared_from_this<Client> {
|
||||||
public:
|
public:
|
||||||
@@ -67,6 +68,7 @@ public:
|
|||||||
SHOULD_SEND_ARTIFICIAL_ENEMY_AND_SET_STATE = 0x0040000000000000, // Server-side only
|
SHOULD_SEND_ARTIFICIAL_ENEMY_AND_SET_STATE = 0x0040000000000000, // Server-side only
|
||||||
SHOULD_SEND_ARTIFICIAL_OBJECT_STATE = 0x0080000000000000, // Server-side only
|
SHOULD_SEND_ARTIFICIAL_OBJECT_STATE = 0x0080000000000000, // Server-side only
|
||||||
SHOULD_SEND_ARTIFICIAL_FLAG_STATE = 0x0002000000000000, // 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,
|
SHOULD_SEND_ENABLE_SAVE = 0x0004000000000000,
|
||||||
SWITCH_ASSIST_ENABLED = 0x0000000100000000,
|
SWITCH_ASSIST_ENABLED = 0x0000000100000000,
|
||||||
IS_CLIENT_CUSTOMIZATION = 0x0100000000000000,
|
IS_CLIENT_CUSTOMIZATION = 0x0100000000000000,
|
||||||
@@ -241,6 +243,7 @@ public:
|
|||||||
bool should_update_play_time;
|
bool should_update_play_time;
|
||||||
std::unordered_set<uint32_t> blocked_senders;
|
std::unordered_set<uint32_t> blocked_senders;
|
||||||
std::unique_ptr<PlayerDispDataDCPCV3> v1_v2_last_reported_disp;
|
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)
|
// 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<PendingItemTrade> pending_item_trade;
|
||||||
std::unique_ptr<PendingCardTrade> pending_card_trade;
|
std::unique_ptr<PendingCardTrade> pending_card_trade;
|
||||||
|
|||||||
@@ -4974,11 +4974,11 @@ struct G_SyncPlayerDispAndInventory_BB_6x70 {
|
|||||||
/* 04C8 */
|
/* 04C8 */
|
||||||
} __packed_ws__(G_SyncPlayerDispAndInventory_BB_6x70, 0x4C0);
|
} __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;
|
G_UnusedHeader header;
|
||||||
} __packed_ws__(G_Unknown_6x71, 4);
|
} __packed_ws__(G_UnblockGameJoin_6x71, 4);
|
||||||
|
|
||||||
// 6x72: Player done loading into game
|
// 6x72: Player done loading into game
|
||||||
|
|
||||||
|
|||||||
+15
-8
@@ -205,7 +205,8 @@ VersionedQuest::VersionedQuest(
|
|||||||
std::shared_ptr<const BattleRules> battle_rules,
|
std::shared_ptr<const BattleRules> battle_rules,
|
||||||
ssize_t challenge_template_index,
|
ssize_t challenge_template_index,
|
||||||
std::shared_ptr<const IntegralExpression> available_expression,
|
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),
|
: quest_number(quest_number),
|
||||||
category_id(category_id),
|
category_id(category_id),
|
||||||
episode(Episode::NONE),
|
episode(Episode::NONE),
|
||||||
@@ -233,7 +234,7 @@ VersionedQuest::VersionedQuest(
|
|||||||
throw invalid_argument("file is too small for header");
|
throw invalid_argument("file is too small for header");
|
||||||
}
|
}
|
||||||
auto* header = reinterpret_cast<const PSOQuestHeaderDCNTE*>(bin_decompressed.data());
|
auto* header = reinterpret_cast<const PSOQuestHeaderDCNTE*>(bin_decompressed.data());
|
||||||
this->joinable = false;
|
this->joinable = force_joinable;
|
||||||
this->episode = Episode::EP1;
|
this->episode = Episode::EP1;
|
||||||
if (this->quest_number == 0xFFFFFFFF) {
|
if (this->quest_number == 0xFFFFFFFF) {
|
||||||
this->quest_number = fnv1a32(header, sizeof(header)) & 0xFFFF;
|
this->quest_number = fnv1a32(header, sizeof(header)) & 0xFFFF;
|
||||||
@@ -249,7 +250,7 @@ VersionedQuest::VersionedQuest(
|
|||||||
throw invalid_argument("file is too small for header");
|
throw invalid_argument("file is too small for header");
|
||||||
}
|
}
|
||||||
auto* header = reinterpret_cast<const PSOQuestHeaderDC*>(bin_decompressed.data());
|
auto* header = reinterpret_cast<const PSOQuestHeaderDC*>(bin_decompressed.data());
|
||||||
this->joinable = false;
|
this->joinable = force_joinable;
|
||||||
this->episode = Episode::EP1;
|
this->episode = Episode::EP1;
|
||||||
if (this->quest_number == 0xFFFFFFFF) {
|
if (this->quest_number == 0xFFFFFFFF) {
|
||||||
this->quest_number = header->quest_number;
|
this->quest_number = header->quest_number;
|
||||||
@@ -266,7 +267,7 @@ VersionedQuest::VersionedQuest(
|
|||||||
throw invalid_argument("file is too small for header");
|
throw invalid_argument("file is too small for header");
|
||||||
}
|
}
|
||||||
auto* header = reinterpret_cast<const PSOQuestHeaderPC*>(bin_decompressed.data());
|
auto* header = reinterpret_cast<const PSOQuestHeaderPC*>(bin_decompressed.data());
|
||||||
this->joinable = false;
|
this->joinable = force_joinable;
|
||||||
this->episode = Episode::EP1;
|
this->episode = Episode::EP1;
|
||||||
if (this->quest_number == 0xFFFFFFFF) {
|
if (this->quest_number == 0xFFFFFFFF) {
|
||||||
this->quest_number = header->quest_number;
|
this->quest_number = header->quest_number;
|
||||||
@@ -289,7 +290,7 @@ VersionedQuest::VersionedQuest(
|
|||||||
throw invalid_argument("file is incorrect size");
|
throw invalid_argument("file is incorrect size");
|
||||||
}
|
}
|
||||||
auto* map = reinterpret_cast<const Episode3::MapDefinition*>(bin_decompressed.data());
|
auto* map = reinterpret_cast<const Episode3::MapDefinition*>(bin_decompressed.data());
|
||||||
this->joinable = false;
|
this->joinable = force_joinable;
|
||||||
this->episode = Episode::EP3;
|
this->episode = Episode::EP3;
|
||||||
if (this->quest_number == 0xFFFFFFFF) {
|
if (this->quest_number == 0xFFFFFFFF) {
|
||||||
this->quest_number = map->map_number;
|
this->quest_number = map->map_number;
|
||||||
@@ -307,7 +308,7 @@ VersionedQuest::VersionedQuest(
|
|||||||
throw invalid_argument("file is too small for header");
|
throw invalid_argument("file is too small for header");
|
||||||
}
|
}
|
||||||
auto* header = reinterpret_cast<const PSOQuestHeaderGC*>(bin_decompressed.data());
|
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);
|
this->episode = find_quest_episode_from_script(bin_decompressed.data(), bin_decompressed.size(), this->version);
|
||||||
if (this->quest_number == 0xFFFFFFFF) {
|
if (this->quest_number == 0xFFFFFFFF) {
|
||||||
this->quest_number = header->quest_number;
|
this->quest_number = header->quest_number;
|
||||||
@@ -323,7 +324,7 @@ VersionedQuest::VersionedQuest(
|
|||||||
throw invalid_argument("file is too small for header");
|
throw invalid_argument("file is too small for header");
|
||||||
}
|
}
|
||||||
auto* header = reinterpret_cast<const PSOQuestHeaderBB*>(bin_decompressed.data());
|
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);
|
this->episode = find_quest_episode_from_script(bin_decompressed.data(), bin_decompressed.size(), this->version);
|
||||||
if (this->quest_number == 0xFFFFFFFF) {
|
if (this->quest_number == 0xFFFFFFFF) {
|
||||||
this->quest_number = header->quest_number;
|
this->quest_number = header->quest_number;
|
||||||
@@ -674,6 +675,7 @@ QuestIndex::QuestIndex(
|
|||||||
ssize_t challenge_template_index = -1;
|
ssize_t challenge_template_index = -1;
|
||||||
shared_ptr<const IntegralExpression> available_expression;
|
shared_ptr<const IntegralExpression> available_expression;
|
||||||
shared_ptr<const IntegralExpression> enabled_expression;
|
shared_ptr<const IntegralExpression> enabled_expression;
|
||||||
|
bool force_joinable = false;
|
||||||
try {
|
try {
|
||||||
json_filedata = &json_files.at(basename);
|
json_filedata = &json_files.at(basename);
|
||||||
} catch (const out_of_range&) {
|
} catch (const out_of_range&) {
|
||||||
@@ -704,6 +706,10 @@ QuestIndex::QuestIndex(
|
|||||||
enabled_expression = make_shared<IntegralExpression>(metadata_json.get_string("EnabledIf"));
|
enabled_expression = make_shared<IntegralExpression>(metadata_json.get_string("EnabledIf"));
|
||||||
} catch (const out_of_range&) {
|
} catch (const out_of_range&) {
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
force_joinable = metadata_json.get_bool("Joinable");
|
||||||
|
} catch (const out_of_range&) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto vq = make_shared<VersionedQuest>(
|
auto vq = make_shared<VersionedQuest>(
|
||||||
@@ -717,7 +723,8 @@ QuestIndex::QuestIndex(
|
|||||||
battle_rules,
|
battle_rules,
|
||||||
challenge_template_index,
|
challenge_template_index,
|
||||||
available_expression,
|
available_expression,
|
||||||
enabled_expression);
|
enabled_expression,
|
||||||
|
force_joinable);
|
||||||
|
|
||||||
auto category_name = this->category_index->at(vq->category_id)->name;
|
auto category_name = this->category_index->at(vq->category_id)->name;
|
||||||
string filenames_str = bin_filedata->filename;
|
string filenames_str = bin_filedata->filename;
|
||||||
|
|||||||
+2
-1
@@ -90,7 +90,8 @@ struct VersionedQuest {
|
|||||||
std::shared_ptr<const BattleRules> battle_rules = nullptr,
|
std::shared_ptr<const BattleRules> battle_rules = nullptr,
|
||||||
ssize_t challenge_template_index = -1,
|
ssize_t challenge_template_index = -1,
|
||||||
std::shared_ptr<const IntegralExpression> available_expression = nullptr,
|
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 bin_filename() const;
|
||||||
std::string dat_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);
|
c->config.clear_flag(Client::Flag::SHOULD_SEND_ARTIFICIAL_FLAG_STATE);
|
||||||
send_game_flag_state(c); // 6x6F
|
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&) {
|
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);
|
s->change_client_lobby(c, l, true, target_client_id);
|
||||||
c->config.set_flag(Client::Flag::LOADING);
|
c->config.set_flag(Client::Flag::LOADING);
|
||||||
|
c->log.info("LOADING flag set");
|
||||||
if (tourn) {
|
if (tourn) {
|
||||||
c->config.set_flag(Client::Flag::LOADING_TOURNAMENT);
|
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) {
|
if (use_loading_flag) {
|
||||||
lc->config.set_flag(Client::Flag::LOADING_QUEST);
|
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 {
|
lc->disconnect_hooks.emplace(QUEST_BARRIER_DISCONNECT_HOOK_NAME, [l]() -> void {
|
||||||
send_quest_barrier_if_all_clients_ready(l);
|
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()) {
|
if (game->is_game()) {
|
||||||
c->config.set_flag(Client::Flag::LOADING);
|
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
|
// 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
|
// the game state - send it to the joining player (who is now the
|
||||||
// leader)
|
// 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) {
|
static void on_AC_V3_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||||
check_size_v(data.size(), 0);
|
check_size_v(data.size(), 0);
|
||||||
|
|
||||||
auto l = c->require_lobby();
|
|
||||||
|
|
||||||
if (c->config.check_flag(Client::Flag::LOADING_RUNNING_JOINABLE_QUEST)) {
|
if (c->config.check_flag(Client::Flag::LOADING_RUNNING_JOINABLE_QUEST)) {
|
||||||
if (l->base_version != Version::BB_V4) {
|
on_joinable_quest_loaded(c);
|
||||||
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);
|
|
||||||
|
|
||||||
} else if (c->config.check_flag(Client::Flag::LOADING_QUEST)) {
|
} else if (c->config.check_flag(Client::Flag::LOADING_QUEST)) {
|
||||||
c->config.clear_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)) {
|
if (l->quest && send_quest_barrier_if_all_clients_ready(l)) {
|
||||||
on_quest_loaded(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) {
|
if (game) {
|
||||||
s->change_client_lobby(c, game);
|
s->change_client_lobby(c, game);
|
||||||
c->config.set_flag(Client::Flag::LOADING);
|
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) {
|
if (game) {
|
||||||
s->change_client_lobby(c, game);
|
s->change_client_lobby(c, game);
|
||||||
c->config.set_flag(Client::Flag::LOADING);
|
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
|
// 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
|
// 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) {
|
if (game) {
|
||||||
s->change_client_lobby(c, game);
|
s->change_client_lobby(c, game);
|
||||||
c->config.set_flag(Client::Flag::LOADING);
|
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)
|
// don't matter for Ep3)
|
||||||
if (c->config.check_flag(Client::Flag::LOADING)) {
|
if (c->config.check_flag(Client::Flag::LOADING)) {
|
||||||
c->config.clear_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
|
// 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
|
// 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_update_team_reward_flags(c);
|
||||||
send_all_nearby_team_metadatas_to_client(c, false);
|
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
|
// BB sends 016F when the client is done loading a quest. In that case, we
|
||||||
// shouldn't send the quest to them again!
|
// shouldn't send the quest to them again!
|
||||||
if ((command == 0x006F) && l->check_flag(Lobby::Flag::JOINABLE_QUEST_IN_PROGRESS)) {
|
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, 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);
|
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->config.set_flag(Client::Flag::LOADING_RUNNING_JOINABLE_QUEST);
|
||||||
|
c->log.info("LOADING_RUNNING_JOINABLE_QUEST flag set");
|
||||||
should_resume_game = false;
|
should_resume_game = false;
|
||||||
|
|
||||||
} else if ((command == 0x016F) && c->config.check_flag(Client::Flag::LOADING_RUNNING_JOINABLE_QUEST)) {
|
} 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->config.clear_flag(Client::Flag::LOADING_RUNNING_JOINABLE_QUEST);
|
||||||
|
c->log.info("LOADING_RUNNING_JOINABLE_QUEST flag cleared");
|
||||||
}
|
}
|
||||||
if (l->map) {
|
if (l->map) {
|
||||||
send_rare_enemy_index_list(c, l->map->rare_enemy_indexes);
|
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) {
|
} else if (c->should_send_to_proxy_server) {
|
||||||
send_client_to_proxy_server(c);
|
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) {
|
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 {
|
enum Flag {
|
||||||
ALWAYS_FORWARD_TO_WATCHERS = 0x01,
|
ALWAYS_FORWARD_TO_WATCHERS = 0x01,
|
||||||
ALLOW_FORWARD_TO_WATCHED_LOBBY = 0x02,
|
ALLOW_FORWARD_TO_WATCHED_LOBBY = 0x02,
|
||||||
USE_JOIN_COMMAND_QUEUE = 0x04,
|
|
||||||
};
|
};
|
||||||
uint8_t nte_subcommand;
|
uint8_t nte_subcommand;
|
||||||
uint8_t proto_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)) {
|
if ((command == 0xCB) && (lc->version() == Version::GC_EP3_NTE)) {
|
||||||
command = 0xC9;
|
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");
|
lc->log.info("Client not ready to receive join commands; adding to queue");
|
||||||
auto& cmd = lc->game_join_command_queue->emplace_back();
|
auto& cmd = lc->game_join_command_queue->emplace_back();
|
||||||
cmd.command = command;
|
cmd.command = command;
|
||||||
@@ -652,316 +651,350 @@ static void on_sync_joining_player_quest_flags(shared_ptr<Client> c, uint8_t com
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Parsed6x70Data {
|
static void transcode_inventory_items(
|
||||||
public:
|
parray<PlayerInventoryItem, 30>& items,
|
||||||
G_SyncPlayerDispAndInventory_BaseDCNTE base;
|
size_t num_items,
|
||||||
uint32_t unknown_a5_nte = 0;
|
Version from_version,
|
||||||
uint32_t unknown_a6_nte = 0;
|
Version to_version,
|
||||||
uint16_t bonus_hp_from_materials = 0;
|
shared_ptr<const ItemParameterTable> to_item_parameter_table) {
|
||||||
uint16_t bonus_tp_from_materials = 0;
|
if (num_items > 30) {
|
||||||
parray<uint8_t, 0x10> unknown_a5_112000;
|
throw runtime_error("invalid inventory item count");
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
if (from_version != to_version) {
|
||||||
Parsed6x70Data(const G_SyncPlayerDispAndInventory_DC112000_6x70& cmd, uint32_t guild_card_number, uint8_t language)
|
for (size_t z = 0; z < num_items; z++) {
|
||||||
: base(cmd.base),
|
items[z].data.decode_for_version(from_version);
|
||||||
unknown_a5_nte(0),
|
items[z].data.encode_for_version(to_version, to_item_parameter_table);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (size_t z = num_items; z < 30; z++) {
|
||||||
void clear_dc_protos_unused_item_fields() {
|
auto& item = items[z];
|
||||||
for (size_t z = 0; z < min<uint32_t>(this->num_items, 30); z++) {
|
item.present = 0;
|
||||||
auto& item = this->items[z];
|
item.unknown_a1 = 0;
|
||||||
item.unknown_a1 = 0;
|
item.flags = 0;
|
||||||
item.extension_data1 = 0;
|
item.data.clear();
|
||||||
item.extension_data2 = 0;
|
}
|
||||||
item.data.data2d = 0;
|
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(
|
Parsed6x70Data::Parsed6x70Data(
|
||||||
Version from_version,
|
const G_SyncPlayerDispAndInventory_DCNTE_6x70& cmd, uint32_t guild_card_number, Version from_version)
|
||||||
Version to_version,
|
: from_version(from_version),
|
||||||
shared_ptr<const ItemParameterTable> to_item_parameter_table) {
|
item_version(from_version),
|
||||||
if (this->num_items > 30) {
|
base(cmd.base),
|
||||||
throw runtime_error("invalid inventory item count");
|
unknown_a5_nte(cmd.unknown_a5),
|
||||||
}
|
unknown_a6_nte(cmd.unknown_a6),
|
||||||
if (from_version != to_version) {
|
bonus_hp_from_materials(0),
|
||||||
for (size_t z = 0; z < this->num_items; z++) {
|
bonus_tp_from_materials(0),
|
||||||
this->items[z].data.decode_for_version(from_version);
|
language(0),
|
||||||
this->items[z].data.encode_for_version(to_version, to_item_parameter_table);
|
player_tag(0x00010000),
|
||||||
}
|
guild_card_number(guild_card_number),
|
||||||
}
|
unknown_a6(0),
|
||||||
for (size_t z = this->num_items; z < 30; z++) {
|
battle_team_number(0),
|
||||||
auto& item = this->items[z];
|
telepipe(cmd.telepipe),
|
||||||
item.present = 0;
|
unknown_a8(cmd.unknown_a8),
|
||||||
item.unknown_a1 = 0;
|
unknown_a9_nte_112000(cmd.unknown_a9),
|
||||||
item.flags = 0;
|
area(cmd.area),
|
||||||
item.data.clear();
|
flags2(cmd.flags2),
|
||||||
}
|
visual(cmd.visual),
|
||||||
if (is_v1(to_version)) {
|
stats(cmd.stats),
|
||||||
for (size_t z = 0; z < 30; z++) {
|
num_items(cmd.num_items),
|
||||||
auto& item = this->items[z];
|
items(cmd.items),
|
||||||
item.extension_data1 = 0x00;
|
floor(cmd.area),
|
||||||
item.extension_data2 = 0x00;
|
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 {
|
} else {
|
||||||
for (size_t z = 20; z < 30; z++) {
|
ret.base.visual.name_color_checksum = 0;
|
||||||
this->items[z].extension_data1 = 0x00;
|
|
||||||
}
|
|
||||||
for (size_t z = 16; z < 30; z++) {
|
|
||||||
this->items[z].extension_data2 = 0x00;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
G_SyncPlayerDispAndInventory_XB_6x70 Parsed6x70Data::as_xb(shared_ptr<ServerState> s) const {
|
||||||
Parsed6x70Data(const G_SyncPlayerDispAndInventory_BaseV1& base, uint32_t guild_card_number) {
|
G_SyncPlayerDispAndInventory_XB_6x70 ret;
|
||||||
this->base = base.base;
|
ret.base = this->base_v1();
|
||||||
this->bonus_hp_from_materials = base.bonus_hp_from_materials;
|
ret.stats = this->stats;
|
||||||
this->bonus_tp_from_materials = base.bonus_tp_from_materials;
|
ret.num_items = this->num_items;
|
||||||
this->unknown_a4_final = base.unknown_a4;
|
ret.items = this->items;
|
||||||
this->language = base.language;
|
ret.floor = this->floor;
|
||||||
this->player_tag = base.player_tag;
|
ret.xb_user_id_high = this->xb_user_id >> 32;
|
||||||
this->guild_card_number = guild_card_number; // Ignore the client's GC#
|
ret.xb_user_id_low = this->xb_user_id;
|
||||||
this->unknown_a6 = base.unknown_a6;
|
ret.unknown_a16 = this->xb_unknown_a16;
|
||||||
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_BaseV1 base_v1() const {
|
transcode_inventory_items(
|
||||||
G_SyncPlayerDispAndInventory_BaseV1 ret;
|
ret.items, ret.num_items, this->item_version, Version::XB_V3, s->item_parameter_table_for_encode(Version::XB_V3));
|
||||||
ret.base = this->base;
|
ret.base.visual.enforce_lobby_join_limits_for_version(Version::XB_V3);
|
||||||
ret.bonus_hp_from_materials = this->bonus_hp_from_materials;
|
if (s->version_name_colors) {
|
||||||
ret.bonus_tp_from_materials = this->bonus_tp_from_materials;
|
ret.base.visual.name_color = s->name_color_for_version(this->from_version);
|
||||||
ret.unknown_a4 = this->unknown_a4_final;
|
ret.base.visual.name_color_checksum = 0;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
};
|
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(
|
static void on_sync_joining_player_disp_and_inventory(
|
||||||
shared_ptr<Client> c, uint8_t command, uint8_t flag, void* data, size_t size) {
|
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));
|
send_command(target, 0x62, target->lobby_client_id, &data, sizeof(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
unique_ptr<Parsed6x70Data> parsed;
|
|
||||||
|
|
||||||
switch (c_v) {
|
switch (c_v) {
|
||||||
case Version::DC_NTE:
|
case Version::DC_NTE:
|
||||||
parsed = make_unique<Parsed6x70Data>(
|
c->last_reported_6x70.reset(new Parsed6x70Data(
|
||||||
check_size_t<G_SyncPlayerDispAndInventory_DCNTE_6x70>(data, size),
|
check_size_t<G_SyncPlayerDispAndInventory_DCNTE_6x70>(data, size),
|
||||||
c->login->account->account_id);
|
c->login->account->account_id, c_v));
|
||||||
parsed->clear_dc_protos_unused_item_fields();
|
c->last_reported_6x70->clear_dc_protos_unused_item_fields();
|
||||||
break;
|
break;
|
||||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
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),
|
check_size_t<G_SyncPlayerDispAndInventory_DC112000_6x70>(data, size),
|
||||||
c->login->account->account_id,
|
c->login->account->account_id, c->language(), c_v));
|
||||||
c->language());
|
c->last_reported_6x70->clear_dc_protos_unused_item_fields();
|
||||||
parsed->clear_dc_protos_unused_item_fields();
|
|
||||||
break;
|
break;
|
||||||
case Version::DC_V1:
|
case Version::DC_V1:
|
||||||
case Version::DC_V2:
|
case Version::DC_V2:
|
||||||
case Version::PC_NTE:
|
case Version::PC_NTE:
|
||||||
case Version::PC_V2:
|
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),
|
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) {
|
if (c_v == Version::DC_V1) {
|
||||||
parsed->clear_v1_unused_item_fields();
|
c->last_reported_6x70->clear_v1_unused_item_fields();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Version::GC_NTE:
|
case Version::GC_NTE:
|
||||||
case Version::GC_V3:
|
case Version::GC_V3:
|
||||||
case Version::GC_EP3_NTE:
|
case Version::GC_EP3_NTE:
|
||||||
case Version::GC_EP3:
|
case Version::GC_EP3:
|
||||||
parsed = make_unique<Parsed6x70Data>(
|
c->last_reported_6x70.reset(new Parsed6x70Data(
|
||||||
check_size_t<G_SyncPlayerDispAndInventory_GC_6x70>(data, size),
|
check_size_t<G_SyncPlayerDispAndInventory_GC_6x70>(data, size),
|
||||||
c->login->account->account_id);
|
c->login->account->account_id, c_v));
|
||||||
break;
|
break;
|
||||||
case Version::XB_V3:
|
case Version::XB_V3:
|
||||||
parsed = make_unique<Parsed6x70Data>(
|
c->last_reported_6x70.reset(new Parsed6x70Data(
|
||||||
check_size_t<G_SyncPlayerDispAndInventory_XB_6x70>(data, size),
|
check_size_t<G_SyncPlayerDispAndInventory_XB_6x70>(data, size),
|
||||||
c->login->account->account_id);
|
c->login->account->account_id, c_v));
|
||||||
break;
|
break;
|
||||||
case Version::BB_V4:
|
case Version::BB_V4:
|
||||||
parsed = make_unique<Parsed6x70Data>(
|
c->last_reported_6x70.reset(new Parsed6x70Data(
|
||||||
check_size_t<G_SyncPlayerDispAndInventory_BB_6x70>(data, size),
|
check_size_t<G_SyncPlayerDispAndInventory_BB_6x70>(data, size),
|
||||||
c->login->account->account_id);
|
c->login->account->account_id, c_v));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw logic_error("6x70 command from unknown game version");
|
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));
|
send_game_player_state(target, c, false);
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void on_forward_check_client(shared_ptr<Client> c, uint8_t command, uint8_t flag, void* data, size_t size) {
|
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_update_team_reward_flags(c);
|
||||||
send_all_nearby_team_metadatas_to_client(c, false);
|
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.
|
// the loading flag here instead.
|
||||||
if (c->config.check_flag(Client::Flag::LOADING)) {
|
if (c->config.check_flag(Client::Flag::LOADING)) {
|
||||||
c->config.clear_flag(Client::Flag::LOADING);
|
c->config.clear_flag(Client::Flag::LOADING);
|
||||||
|
c->log.info("LOADING flag cleared");
|
||||||
send_resume_game(c->require_lobby(), c);
|
send_resume_game(c->require_lobby(), c);
|
||||||
c->require_lobby()->assign_inventory_and_bank_item_ids(c, true);
|
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},
|
/* 6x68 */ {0x59, 0x60, 0x68, on_forward_check_game},
|
||||||
/* 6x69 */ {0x5A, 0x61, 0x69, on_npc_control},
|
/* 6x69 */ {0x5A, 0x61, 0x69, on_npc_control},
|
||||||
/* 6x6A */ {0x5B, 0x62, 0x6A, on_forward_check_game},
|
/* 6x6A */ {0x5B, 0x62, 0x6A, on_forward_check_game},
|
||||||
/* 6x6B */ {0x5C, 0x63, 0x6B, on_sync_joining_player_compressed_state, 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, SDF::USE_JOIN_COMMAND_QUEUE},
|
/* 6x6C */ {0x5D, 0x64, 0x6C, on_sync_joining_player_compressed_state},
|
||||||
/* 6x6D */ {0x5E, 0x65, 0x6D, on_sync_joining_player_compressed_state, SDF::USE_JOIN_COMMAND_QUEUE},
|
/* 6x6D */ {0x5E, 0x65, 0x6D, on_sync_joining_player_compressed_state},
|
||||||
/* 6x6E */ {0x5F, 0x66, 0x6E, on_sync_joining_player_compressed_state, SDF::USE_JOIN_COMMAND_QUEUE},
|
/* 6x6E */ {0x5F, 0x66, 0x6E, on_sync_joining_player_compressed_state},
|
||||||
/* 6x6F */ {0x00, 0x00, 0x6F, on_sync_joining_player_quest_flags, SDF::USE_JOIN_COMMAND_QUEUE},
|
/* 6x6F */ {0x00, 0x00, 0x6F, on_sync_joining_player_quest_flags},
|
||||||
/* 6x70 */ {0x60, 0x67, 0x70, on_sync_joining_player_disp_and_inventory, SDF::USE_JOIN_COMMAND_QUEUE},
|
/* 6x70 */ {0x60, 0x67, 0x70, on_sync_joining_player_disp_and_inventory},
|
||||||
/* 6x71 */ {0x00, 0x00, 0x71, on_forward_check_game_loading, SDF::USE_JOIN_COMMAND_QUEUE},
|
/* 6x71 */ {0x00, 0x00, 0x71, on_forward_check_game_loading},
|
||||||
/* 6x72 */ {0x61, 0x68, 0x72, on_forward_check_game_loading, SDF::USE_JOIN_COMMAND_QUEUE},
|
/* 6x72 */ {0x61, 0x68, 0x72, on_forward_check_game_loading},
|
||||||
/* 6x73 */ {0x00, 0x00, 0x73, on_forward_check_game_quest},
|
/* 6x73 */ {0x00, 0x00, 0x73, on_forward_check_game_quest},
|
||||||
/* 6x74 */ {0x62, 0x69, 0x74, on_word_select, SDF::ALWAYS_FORWARD_TO_WATCHERS},
|
/* 6x74 */ {0x62, 0x69, 0x74, on_word_select, SDF::ALWAYS_FORWARD_TO_WATCHERS},
|
||||||
/* 6x75 */ {0x00, 0x00, 0x75, on_set_quest_flag},
|
/* 6x75 */ {0x00, 0x00, 0x75, on_set_quest_flag},
|
||||||
|
|||||||
@@ -36,3 +36,78 @@ DropReconcileResult reconcile_drop_request_with_map(
|
|||||||
const Client::Config& config,
|
const Client::Config& config,
|
||||||
std::shared_ptr<Map> map,
|
std::shared_ptr<Map> map,
|
||||||
bool mark_drop);
|
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 "FileContentsCache.hh"
|
||||||
#include "PSOProtocol.hh"
|
#include "PSOProtocol.hh"
|
||||||
#include "ProxyServer.hh"
|
#include "ProxyServer.hh"
|
||||||
|
#include "ReceiveSubcommands.hh"
|
||||||
#include "StaticGameData.hh"
|
#include "StaticGameData.hh"
|
||||||
#include "Text.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) {
|
void send_resume_game(shared_ptr<Lobby> l, shared_ptr<Client> ready_client) {
|
||||||
for (auto lc : l->clients) {
|
for (auto lc : l->clients) {
|
||||||
if (lc && (lc != ready_client) && !is_pre_v1(lc->version())) {
|
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) {
|
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();
|
auto& cmd = c->game_join_command_queue->emplace_back();
|
||||||
cmd.command = 0x6D;
|
cmd.command = 0x6D;
|
||||||
cmd.flag = c->lobby_client_id;
|
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,
|
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) {
|
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);
|
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
|
// 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*).
|
// 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,
|
void send_command(std::shared_ptr<Client> c, uint16_t command,
|
||||||
uint32_t flag, const std::vector<std::pair<const void*, size_t>>& blocks);
|
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_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_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);
|
void send_resume_game(std::shared_ptr<Lobby> l, std::shared_ptr<Client> ready_client);
|
||||||
|
|
||||||
enum PlayerStatsChange {
|
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_object_state(std::shared_ptr<Client> c);
|
||||||
void send_game_set_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_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,
|
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);
|
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,
|
void send_drop_item_to_lobby(std::shared_ptr<Lobby> l, const ItemData& item,
|
||||||
|
|||||||
@@ -35,8 +35,8 @@
|
|||||||
// "ChallengeTemplateIndex": 0,
|
// "ChallengeTemplateIndex": 0,
|
||||||
|
|
||||||
// Quests may be set to be unavailable until a preceding quest has been
|
// 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
|
// cleared or a team reward has been purchased. To enable this behavior, set
|
||||||
// value for AvailableIf in the quest's JSON file. (This is ignored if the
|
// 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
|
// player has the DISABLE_QUEST_REQUIREMENTS flag in their account.) This
|
||||||
// field's value should be an expression that tests any of the following:
|
// field's value should be an expression that tests any of the following:
|
||||||
// F_XXXX: Quest flag specified in hex (e.g. F_014D)
|
// 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
|
// to false, this is ignored. This field is also ignored if the player has
|
||||||
// the DISABLE_QUEST_REQUIREMENTS flag in their account.
|
// the DISABLE_QUEST_REQUIREMENTS flag in their account.
|
||||||
// "EnabledIf": "!F_0169",
|
// "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