implement $dropmode on proxy server
This commit is contained in:
+47
-2
@@ -359,7 +359,7 @@ static void proxy_command_qset_qclear(shared_ptr<ProxyServer::LinkedSession> ses
|
||||
ses->client_channel.send(0x60, 0x00, &cmd, sizeof(cmd));
|
||||
ses->server_channel.send(0x60, 0x00, &cmd, sizeof(cmd));
|
||||
} else {
|
||||
G_UpdateQuestFlag_V3_BB_6x75 cmd = {{{0x75, 0x03, 0x0000}, flag_num, should_set ? 0 : 1}, ses->difficulty, 0x0000};
|
||||
G_UpdateQuestFlag_V3_BB_6x75 cmd = {{{0x75, 0x03, 0x0000}, flag_num, should_set ? 0 : 1}, ses->lobby_difficulty, 0x0000};
|
||||
ses->client_channel.send(0x60, 0x00, &cmd, sizeof(cmd));
|
||||
ses->server_channel.send(0x60, 0x00, &cmd, sizeof(cmd));
|
||||
}
|
||||
@@ -1705,6 +1705,51 @@ static void server_command_dropmode(shared_ptr<Client> c, const std::string& arg
|
||||
}
|
||||
}
|
||||
|
||||
static void proxy_command_dropmode(shared_ptr<ProxyServer::LinkedSession> ses, const std::string& args) {
|
||||
check_cheats_allowed(ses->require_server_state(), ses);
|
||||
|
||||
using DropMode = ProxyServer::LinkedSession::DropMode;
|
||||
if (args.empty()) {
|
||||
switch (ses->drop_mode) {
|
||||
case DropMode::DISABLED:
|
||||
send_text_message(ses->client_channel, "Drop mode: disabled");
|
||||
break;
|
||||
case DropMode::PASSTHROUGH:
|
||||
send_text_message(ses->client_channel, "Drop mode: default");
|
||||
break;
|
||||
case DropMode::INTERCEPT:
|
||||
send_text_message(ses->client_channel, "Drop mode: proxy");
|
||||
break;
|
||||
}
|
||||
|
||||
} else {
|
||||
DropMode new_mode;
|
||||
if ((args == "none") || (args == "disabled")) {
|
||||
new_mode = DropMode::DISABLED;
|
||||
} else if ((args == "default") || (args == "passthrough")) {
|
||||
new_mode = DropMode::PASSTHROUGH;
|
||||
} else if ((args == "proxy") || (args == "intercept")) {
|
||||
new_mode = DropMode::INTERCEPT;
|
||||
} else {
|
||||
send_text_message(ses->client_channel, "Invalid drop mode");
|
||||
return;
|
||||
}
|
||||
|
||||
ses->set_drop_mode(new_mode);
|
||||
switch (ses->drop_mode) {
|
||||
case DropMode::DISABLED:
|
||||
send_text_message(ses->client_channel, "Item drops disabled");
|
||||
break;
|
||||
case DropMode::PASSTHROUGH:
|
||||
send_text_message(ses->client_channel, "Item drops changed\nto default mode");
|
||||
break;
|
||||
case DropMode::INTERCEPT:
|
||||
send_text_message(ses->client_channel, "Item drops changed\nto proxy mode");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void server_command_item(shared_ptr<Client> c, const std::string& args) {
|
||||
auto s = c->require_server_state();
|
||||
auto l = c->require_lobby();
|
||||
@@ -2092,7 +2137,7 @@ static const unordered_map<string, ChatCommandDefinition> chat_commands({
|
||||
{"$cheat", {server_command_cheat, nullptr}},
|
||||
{"$debug", {server_command_debug, nullptr}},
|
||||
{"$dicerange", {server_command_ep3_set_dice_range, nullptr}},
|
||||
{"$dropmode", {server_command_dropmode, nullptr}},
|
||||
{"$dropmode", {server_command_dropmode, proxy_command_dropmode}},
|
||||
{"$edit", {server_command_edit, nullptr}},
|
||||
{"$ep3battledebug", {server_command_enable_ep3_battle_debug_menu, nullptr}},
|
||||
{"$event", {server_command_lobby_event, proxy_command_lobby_event}},
|
||||
|
||||
+7
-7
@@ -269,17 +269,14 @@ shared_ptr<Map> Lobby::load_maps(
|
||||
uint32_t lobby_id,
|
||||
shared_ptr<const Map::RareEnemyRates> rare_rates,
|
||||
shared_ptr<PSOLFGEncryption> random_crypt,
|
||||
shared_ptr<const VersionedQuest> vq) {
|
||||
if (!vq->dat_contents_decompressed) {
|
||||
throw runtime_error("quest does not have DAT data");
|
||||
}
|
||||
shared_ptr<const string> quest_dat_contents_decompressed) {
|
||||
auto map = make_shared<Map>(version, lobby_id, random_crypt);
|
||||
map->add_enemies_and_objects_from_quest_data(
|
||||
episode,
|
||||
difficulty,
|
||||
event,
|
||||
vq->dat_contents_decompressed->data(),
|
||||
vq->dat_contents_decompressed->size(),
|
||||
quest_dat_contents_decompressed->data(),
|
||||
quest_dat_contents_decompressed->size(),
|
||||
rare_rates);
|
||||
return map;
|
||||
}
|
||||
@@ -377,6 +374,9 @@ void Lobby::load_maps() {
|
||||
}
|
||||
|
||||
auto vq = this->quest->version(this->base_version, leader_c->language());
|
||||
if (!vq->dat_contents_decompressed) {
|
||||
throw runtime_error("quest does not have DAT data");
|
||||
}
|
||||
this->map = this->load_maps(
|
||||
this->base_version,
|
||||
this->episode,
|
||||
@@ -385,7 +385,7 @@ void Lobby::load_maps() {
|
||||
this->lobby_id,
|
||||
rare_rates,
|
||||
this->random_crypt,
|
||||
vq);
|
||||
vq->dat_contents_decompressed);
|
||||
|
||||
} else if (this->mode != GameMode::CHALLENGE) {
|
||||
auto s = this->require_server_state();
|
||||
|
||||
+1
-1
@@ -203,7 +203,7 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
|
||||
uint32_t lobby_id,
|
||||
std::shared_ptr<const Map::RareEnemyRates> rare_rates,
|
||||
std::shared_ptr<PSOLFGEncryption> random_crypt,
|
||||
std::shared_ptr<const VersionedQuest> vq);
|
||||
std::shared_ptr<const std::string> quest_dat_contents_decompressed);
|
||||
static std::shared_ptr<Map> load_maps(
|
||||
Version version,
|
||||
Episode episode,
|
||||
|
||||
+4
-1
@@ -2128,7 +2128,10 @@ Action a_find_rare_enemy_seeds(
|
||||
|
||||
shared_ptr<Map> map;
|
||||
if (vq) {
|
||||
map = Lobby::load_maps(version, episode, difficulty, 0, 0, rare_rates, random_crypt, vq);
|
||||
if (!vq->dat_contents_decompressed) {
|
||||
throw runtime_error("quest does not have DAT data");
|
||||
}
|
||||
map = Lobby::load_maps(version, episode, difficulty, 0, 0, rare_rates, random_crypt, vq->dat_contents_decompressed);
|
||||
|
||||
} else {
|
||||
generate_variations_deprecated(variations, random_crypt, version, episode, (mode == GameMode::SOLO));
|
||||
|
||||
+246
-87
@@ -76,18 +76,6 @@ static void forward_command(shared_ptr<ProxyServer::LinkedSession> ses, bool to_
|
||||
}
|
||||
}
|
||||
|
||||
static void check_implemented_subcommand(
|
||||
shared_ptr<ProxyServer::LinkedSession> ses, const string& data) {
|
||||
if (data.size() < 4) {
|
||||
ses->log.warning("Received broadcast/target command with no contents");
|
||||
} else {
|
||||
if (!subcommand_is_implemented(data[0])) {
|
||||
ses->log.warning("Received subcommand %02hhX which is not implemented on the server",
|
||||
data[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Command handlers. These are called to preprocess or react to specific
|
||||
// commands in either direction. The functions have abbreviated names in order
|
||||
// to make the massive table more readable. The functions' names are, in
|
||||
@@ -954,6 +942,73 @@ static HandlerResult S_V3_BB_DA(shared_ptr<ProxyServer::LinkedSession> ses, uint
|
||||
}
|
||||
}
|
||||
|
||||
static HandlerResult SC_6x60_6xA2(shared_ptr<ProxyServer::LinkedSession> ses, const string& data) {
|
||||
if (!ses->is_in_game) {
|
||||
return HandlerResult::Type::FORWARD;
|
||||
}
|
||||
|
||||
if (ses->next_drop_item.data1d[0]) {
|
||||
G_SpecializableItemDropRequest_6xA2 cmd = normalize_drop_request(data.data(), data.size());
|
||||
auto s = ses->require_server_state();
|
||||
ses->next_drop_item.id = ses->next_item_id++;
|
||||
bool is_box = (cmd.rt_index == 0x30);
|
||||
send_drop_item_to_channel(s, ses->server_channel, ses->next_drop_item, !is_box, cmd.floor, cmd.x, cmd.z, cmd.entity_id);
|
||||
send_drop_item_to_channel(s, ses->client_channel, ses->next_drop_item, !is_box, cmd.floor, cmd.x, cmd.z, cmd.entity_id);
|
||||
ses->next_drop_item.clear();
|
||||
return HandlerResult::Type::SUPPRESS;
|
||||
}
|
||||
|
||||
using DropMode = ProxyServer::LinkedSession::DropMode;
|
||||
switch (ses->drop_mode) {
|
||||
case DropMode::DISABLED:
|
||||
return HandlerResult::Type::SUPPRESS;
|
||||
case DropMode::PASSTHROUGH:
|
||||
return HandlerResult::Type::FORWARD;
|
||||
case DropMode::INTERCEPT:
|
||||
if (!ses->item_creator) {
|
||||
ses->log.warning("Received item drop request in intercept mode, but item creator is missing");
|
||||
return HandlerResult::Type::FORWARD;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw logic_error("invalid drop mode");
|
||||
}
|
||||
|
||||
G_SpecializableItemDropRequest_6xA2 cmd = normalize_drop_request(data.data(), data.size());
|
||||
auto rec = reconcile_drop_request_with_map(
|
||||
ses->log, ses->client_channel, cmd, ses->version(), ses->lobby_episode, ses->config, ses->map, false);
|
||||
|
||||
ItemCreator::DropResult res;
|
||||
if (rec.is_box) {
|
||||
if (rec.ignore_def) {
|
||||
ses->log.info("Creating item from box %04hX (area %02hX)", cmd.entity_id.load(), cmd.effective_area);
|
||||
res = ses->item_creator->on_box_item_drop(cmd.effective_area);
|
||||
} else {
|
||||
ses->log.info("Creating item from box %04hX (area %02hX; specialized with %g %08" PRIX32 " %08" PRIX32 " %08" PRIX32 ")",
|
||||
cmd.entity_id.load(), cmd.effective_area, cmd.param3.load(), cmd.param4.load(), cmd.param5.load(), cmd.param6.load());
|
||||
res = ses->item_creator->on_specialized_box_item_drop(cmd.effective_area, cmd.param3, cmd.param4, cmd.param5, cmd.param6);
|
||||
}
|
||||
} else {
|
||||
ses->log.info("Creating item from enemy %04hX (area %02hX)", cmd.entity_id.load(), cmd.effective_area);
|
||||
res = ses->item_creator->on_monster_item_drop(rec.effective_rt_index, cmd.effective_area);
|
||||
}
|
||||
|
||||
if (res.item.empty()) {
|
||||
ses->log.info("No item was created");
|
||||
} else {
|
||||
auto s = ses->require_server_state();
|
||||
string name = s->describe_item(ses->version(), res.item, false);
|
||||
ses->log.info("Entity %04hX (area %02hX) created item %s", cmd.entity_id.load(), cmd.effective_area, name.c_str());
|
||||
res.item.id = ses->next_item_id++;
|
||||
ses->log.info("Creating item %08" PRIX32 " at %02hhX:%g,%g for all clients",
|
||||
res.item.id.load(), cmd.floor, cmd.x.load(), cmd.z.load());
|
||||
send_drop_item_to_channel(s, ses->client_channel, res.item, !rec.is_box, cmd.floor, cmd.x, cmd.z, cmd.entity_id);
|
||||
send_drop_item_to_channel(s, ses->server_channel, res.item, !rec.is_box, cmd.floor, cmd.x, cmd.z, cmd.entity_id);
|
||||
send_item_notification_if_needed(s, ses->client_channel, ses->config, res.item, res.is_from_rare_table);
|
||||
}
|
||||
return HandlerResult::Type::SUPPRESS;
|
||||
}
|
||||
|
||||
static HandlerResult S_6x(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t, uint32_t, string& data) {
|
||||
auto s = ses->require_server_state();
|
||||
|
||||
@@ -1061,32 +1116,15 @@ static HandlerResult S_6x(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
|
||||
const auto& cmd = check_size_t<G_DropItem_DC_6x5F>(data, sizeof(G_DropItem_PC_V3_BB_6x5F));
|
||||
send_item_notification_if_needed(ses->require_server_state(), ses->client_channel, ses->config, cmd.item.item, true);
|
||||
|
||||
} else if ((data[0] == 0x60) && ses->next_drop_item.data1d[0] && !is_v4(ses->version())) {
|
||||
const auto& cmd = check_size_t<G_StandardDropItemRequest_DC_6x60>(
|
||||
data, sizeof(G_StandardDropItemRequest_PC_V3_BB_6x60));
|
||||
ses->next_drop_item.id = ses->next_item_id++;
|
||||
send_drop_item_to_channel(s, ses->server_channel, ses->next_drop_item, true, cmd.floor, cmd.x, cmd.z, cmd.entity_id);
|
||||
send_drop_item_to_channel(s, ses->client_channel, ses->next_drop_item, true, cmd.floor, cmd.x, cmd.z, cmd.entity_id);
|
||||
ses->next_drop_item.clear();
|
||||
return HandlerResult::Type::SUPPRESS;
|
||||
|
||||
// Note: This static_cast is required to make compilers not complain that
|
||||
// the comparison is always false (which even happens in some environments
|
||||
// if we use -0x5E... apparently char is unsigned on some systems, or
|
||||
// std::string's char_type isn't char??)
|
||||
} else if ((static_cast<uint8_t>(data[0]) == 0xA2) && ses->next_drop_item.data1d[0] && !is_v4(ses->version())) {
|
||||
const auto& cmd = check_size_t<G_SpecializableItemDropRequest_6xA2>(data);
|
||||
ses->next_drop_item.id = ses->next_item_id++;
|
||||
send_drop_item_to_channel(s, ses->server_channel, ses->next_drop_item, false, cmd.floor, cmd.x, cmd.z, cmd.entity_id);
|
||||
send_drop_item_to_channel(s, ses->client_channel, ses->next_drop_item, false, cmd.floor, cmd.x, cmd.z, cmd.entity_id);
|
||||
ses->next_drop_item.clear();
|
||||
return HandlerResult::Type::SUPPRESS;
|
||||
} else if ((data[0] == 0x60) || (static_cast<uint8_t>(data[0]) == 0xA2)) {
|
||||
return SC_6x60_6xA2(ses, data);
|
||||
|
||||
} else if ((static_cast<uint8_t>(data[0]) == 0xB5) && is_ep3(ses->version()) && (data.size() > 4)) {
|
||||
if (data[4] == 0x1A) {
|
||||
return HandlerResult::Type::SUPPRESS;
|
||||
} else if (data[4] == 0x36) {
|
||||
const auto& cmd = check_size_t<G_RecreatePlayer_Ep3_6xB5x36>(data);
|
||||
auto& cmd = check_size_t<G_RecreatePlayer_Ep3_6xB5x36>(data);
|
||||
set_mask_for_ep3_game_command(&cmd, sizeof(cmd), 0);
|
||||
if (ses->is_in_game && (cmd.client_id >= 4)) {
|
||||
return HandlerResult::Type::SUPPRESS;
|
||||
}
|
||||
@@ -1299,6 +1337,20 @@ static HandlerResult S_13_A7(shared_ptr<ProxyServer::LinkedSession> ses, uint16_
|
||||
} else {
|
||||
ses->log.info("Download complete for file %s", sf->basename.c_str());
|
||||
}
|
||||
|
||||
if (!sf->is_download && ends_with(sf->basename, ".dat")) {
|
||||
auto quest_dat_data = make_shared<std::string>(join(sf->blocks));
|
||||
ses->map = Lobby::load_maps(
|
||||
ses->version(),
|
||||
ses->lobby_episode,
|
||||
ses->lobby_difficulty,
|
||||
ses->lobby_event,
|
||||
ses->id,
|
||||
Map::DEFAULT_RARE_ENEMIES,
|
||||
make_shared<PSOV2Encryption>(ses->lobby_random_seed),
|
||||
quest_dat_data);
|
||||
}
|
||||
|
||||
ses->saving_files.erase(cmd.filename.decode());
|
||||
}
|
||||
|
||||
@@ -1430,7 +1482,13 @@ static HandlerResult S_65_67_68_EB(shared_ptr<ProxyServer::LinkedSession> ses, u
|
||||
ses->is_in_game = false;
|
||||
ses->is_in_quest = false;
|
||||
ses->floor = 0x0F;
|
||||
ses->difficulty = 0;
|
||||
ses->lobby_difficulty = 0;
|
||||
ses->lobby_section_id = 0;
|
||||
ses->lobby_mode = GameMode::NORMAL;
|
||||
ses->lobby_episode = Episode::EP1;
|
||||
ses->lobby_random_seed = 0;
|
||||
ses->item_creator.reset();
|
||||
ses->map.reset();
|
||||
|
||||
// This command can cause the client to no longer send D6 responses when
|
||||
// 1A/D5 large message boxes are closed. newserv keeps track of this
|
||||
@@ -1448,6 +1506,7 @@ static HandlerResult S_65_67_68_EB(shared_ptr<ProxyServer::LinkedSession> ses, u
|
||||
|
||||
size_t num_replacements = 0;
|
||||
ses->lobby_client_id = cmd.lobby_flags.client_id;
|
||||
ses->lobby_event = cmd.lobby_flags.event;
|
||||
update_leader_id(ses, cmd.lobby_flags.leader_id);
|
||||
for (size_t x = 0; x < flag; x++) {
|
||||
auto& entry = cmd.entries[x];
|
||||
@@ -1496,6 +1555,50 @@ constexpr on_command_t S_P_65_67_68 = &S_65_67_68_EB<S_JoinLobby_PC_65_67_68>;
|
||||
constexpr on_command_t S_X_65_67_68 = &S_65_67_68_EB<S_JoinLobby_XB_65_67_68>;
|
||||
constexpr on_command_t S_B_65_67_68 = &S_65_67_68_EB<S_JoinLobby_BB_65_67_68>;
|
||||
|
||||
template <typename CmdT>
|
||||
Episode get_episode(const CmdT&) {
|
||||
return Episode::EP1;
|
||||
}
|
||||
template <>
|
||||
Episode get_episode<S_JoinGame_GC_64>(const S_JoinGame_GC_64& cmd) {
|
||||
switch (cmd.episode) {
|
||||
case 1:
|
||||
return Episode::EP1;
|
||||
case 2:
|
||||
return Episode::EP2;
|
||||
default:
|
||||
return Episode::NONE;
|
||||
}
|
||||
}
|
||||
template <>
|
||||
Episode get_episode<S_JoinGame_XB_64>(const S_JoinGame_XB_64& cmd) {
|
||||
switch (cmd.episode) {
|
||||
case 1:
|
||||
return Episode::EP1;
|
||||
case 2:
|
||||
return Episode::EP2;
|
||||
default:
|
||||
return Episode::NONE;
|
||||
}
|
||||
}
|
||||
template <>
|
||||
Episode get_episode<S_JoinGame_BB_64>(const S_JoinGame_BB_64& cmd) {
|
||||
switch (cmd.episode) {
|
||||
case 1:
|
||||
return Episode::EP1;
|
||||
case 2:
|
||||
return Episode::EP2;
|
||||
case 3:
|
||||
return Episode::EP4;
|
||||
default:
|
||||
return Episode::NONE;
|
||||
}
|
||||
}
|
||||
template <>
|
||||
Episode get_episode<S_JoinGame_Ep3_64>(const S_JoinGame_Ep3_64&) {
|
||||
return Episode::EP3;
|
||||
}
|
||||
|
||||
template <typename CmdT>
|
||||
static HandlerResult S_64(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t, uint32_t flag, string& data) {
|
||||
CmdT* cmd;
|
||||
@@ -1515,7 +1618,41 @@ static HandlerResult S_64(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
|
||||
ses->floor = 0;
|
||||
ses->is_in_game = true;
|
||||
ses->is_in_quest = false;
|
||||
ses->difficulty = cmd->difficulty;
|
||||
ses->lobby_event = cmd->event;
|
||||
ses->lobby_difficulty = cmd->difficulty;
|
||||
ses->lobby_section_id = cmd->section_id;
|
||||
// We only need the game mode for overriding drops, and SOLO behaves the same
|
||||
// as NORMAL in that regard, so we can conveniently ignore SOLO here
|
||||
if (cmd->battle_mode) {
|
||||
ses->lobby_mode = GameMode::BATTLE;
|
||||
} else if (cmd->challenge_mode) {
|
||||
ses->lobby_mode = GameMode::CHALLENGE;
|
||||
} else {
|
||||
ses->lobby_mode = GameMode::NORMAL;
|
||||
}
|
||||
ses->lobby_random_seed = cmd->rare_seed;
|
||||
if (cmd_ep3) {
|
||||
ses->lobby_episode = Episode::EP3;
|
||||
} else {
|
||||
ses->lobby_episode = get_episode(*cmd);
|
||||
}
|
||||
|
||||
// Recreate the item creator if needed, and load maps
|
||||
auto s = ses->require_server_state();
|
||||
ses->set_drop_mode(ses->drop_mode);
|
||||
ses->map = Lobby::load_maps(
|
||||
ses->version(),
|
||||
ses->lobby_episode,
|
||||
ses->lobby_mode,
|
||||
ses->lobby_difficulty,
|
||||
ses->lobby_event,
|
||||
ses->id,
|
||||
s->set_data_table(ses->version(), ses->lobby_episode, ses->lobby_mode, ses->lobby_difficulty),
|
||||
bind(&ServerState::load_map_file, s.get(), placeholders::_1, placeholders::_2),
|
||||
Map::DEFAULT_RARE_ENEMIES,
|
||||
make_shared<PSOV2Encryption>(ses->lobby_random_seed),
|
||||
cmd->variations,
|
||||
&ses->log);
|
||||
|
||||
bool modified = false;
|
||||
|
||||
@@ -1569,7 +1706,14 @@ static HandlerResult S_E8(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
|
||||
ses->floor = 0;
|
||||
ses->is_in_game = true;
|
||||
ses->is_in_quest = false;
|
||||
ses->difficulty = 0;
|
||||
ses->lobby_event = cmd.event;
|
||||
ses->lobby_difficulty = 0;
|
||||
ses->lobby_section_id = cmd.section_id;
|
||||
ses->lobby_mode = GameMode::NORMAL;
|
||||
ses->lobby_random_seed = 0;
|
||||
ses->lobby_episode = Episode::EP3;
|
||||
ses->item_creator.reset();
|
||||
ses->map.reset();
|
||||
|
||||
bool modified = false;
|
||||
|
||||
@@ -1649,7 +1793,15 @@ static HandlerResult C_98(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t c
|
||||
ses->floor = 0x0F;
|
||||
ses->is_in_game = false;
|
||||
ses->is_in_quest = false;
|
||||
ses->difficulty = 0;
|
||||
ses->lobby_event = 0;
|
||||
ses->lobby_difficulty = 0;
|
||||
ses->lobby_section_id = 0;
|
||||
ses->lobby_episode = Episode::EP1;
|
||||
ses->lobby_mode = GameMode::NORMAL;
|
||||
ses->lobby_random_seed = 0;
|
||||
ses->item_creator.reset();
|
||||
ses->map.reset();
|
||||
|
||||
if (is_v3(ses->version()) || is_v4(ses->version())) {
|
||||
return C_GXB_61(ses, command, flag, data);
|
||||
} else {
|
||||
@@ -1765,44 +1917,6 @@ static HandlerResult C_6x(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t c
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!data.empty()) {
|
||||
if (data[0] == 0x21) {
|
||||
const auto& cmd = check_size_t<G_InterLevelWarp_6x21>(data);
|
||||
ses->floor = cmd.floor;
|
||||
|
||||
} else if (data[0] == 0x0C) {
|
||||
if (is_v1_or_v2(ses->version()) && ses->config.check_flag(Client::Flag::INFINITE_HP_ENABLED)) {
|
||||
send_remove_conditions(ses->client_channel, ses->lobby_client_id);
|
||||
send_remove_conditions(ses->server_channel, ses->lobby_client_id);
|
||||
}
|
||||
} else if (data[0] == 0x2F || data[0] == 0x4B || data[0] == 0x4C) {
|
||||
if (ses->config.check_flag(Client::Flag::INFINITE_HP_ENABLED)) {
|
||||
send_player_stats_change(ses->client_channel,
|
||||
ses->lobby_client_id, PlayerStatsChange::ADD_HP, 2550);
|
||||
send_player_stats_change(ses->server_channel,
|
||||
ses->lobby_client_id, PlayerStatsChange::ADD_HP, 2550);
|
||||
}
|
||||
} else if (data[0] == 0x3E) {
|
||||
C_6x_movement<G_StopAtPosition_6x3E>(ses, data);
|
||||
} else if (data[0] == 0x3F) {
|
||||
C_6x_movement<G_SetPosition_6x3F>(ses, data);
|
||||
} else if (data[0] == 0x40) {
|
||||
C_6x_movement<G_WalkToPosition_6x40>(ses, data);
|
||||
} else if (data[0] == 0x42) {
|
||||
C_6x_movement<G_RunToPosition_6x42>(ses, data);
|
||||
} else if (data[0] == 0x48) {
|
||||
if (ses->config.check_flag(Client::Flag::INFINITE_TP_ENABLED)) {
|
||||
send_player_stats_change(ses->client_channel,
|
||||
ses->lobby_client_id, PlayerStatsChange::ADD_TP, 255);
|
||||
send_player_stats_change(ses->server_channel,
|
||||
ses->lobby_client_id, PlayerStatsChange::ADD_TP, 255);
|
||||
}
|
||||
} else if (data[0] == 0x5F) {
|
||||
const auto& cmd = check_size_t<G_DropItem_DC_6x5F>(data, sizeof(G_DropItem_PC_V3_BB_6x5F));
|
||||
send_item_notification_if_needed(ses->require_server_state(), ses->client_channel, ses->config, cmd.item.item, true);
|
||||
}
|
||||
}
|
||||
return C_6x<void>(ses, command, flag, data);
|
||||
}
|
||||
|
||||
@@ -1814,19 +1928,64 @@ constexpr on_command_t C_B_6x = &C_6x<G_SendGuildCard_BB_6x06>;
|
||||
|
||||
template <>
|
||||
HandlerResult C_6x<void>(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t, uint32_t, string& data) {
|
||||
check_implemented_subcommand(ses, data);
|
||||
|
||||
if (!data.empty() && (data[0] == 0x05) && ses->config.check_flag(Client::Flag::SWITCH_ASSIST_ENABLED)) {
|
||||
auto& cmd = check_size_t<G_SwitchStateChanged_6x05>(data);
|
||||
if (cmd.flags && cmd.header.object_id != 0xFFFF) {
|
||||
if (ses->last_switch_enabled_command.header.subcommand == 0x05) {
|
||||
ses->log.info("Switch assist: replaying previous enable command");
|
||||
ses->server_channel.send(0x60, 0x00, &ses->last_switch_enabled_command,
|
||||
sizeof(ses->last_switch_enabled_command));
|
||||
ses->client_channel.send(0x60, 0x00, &ses->last_switch_enabled_command,
|
||||
sizeof(ses->last_switch_enabled_command));
|
||||
if (!data.empty()) {
|
||||
if ((data[0] == 0x05) && ses->config.check_flag(Client::Flag::SWITCH_ASSIST_ENABLED)) {
|
||||
auto& cmd = check_size_t<G_SwitchStateChanged_6x05>(data);
|
||||
if (cmd.flags && cmd.header.object_id != 0xFFFF) {
|
||||
if (ses->last_switch_enabled_command.header.subcommand == 0x05) {
|
||||
ses->log.info("Switch assist: replaying previous enable command");
|
||||
ses->server_channel.send(0x60, 0x00, &ses->last_switch_enabled_command,
|
||||
sizeof(ses->last_switch_enabled_command));
|
||||
ses->client_channel.send(0x60, 0x00, &ses->last_switch_enabled_command,
|
||||
sizeof(ses->last_switch_enabled_command));
|
||||
}
|
||||
ses->last_switch_enabled_command = cmd;
|
||||
}
|
||||
ses->last_switch_enabled_command = cmd;
|
||||
|
||||
} else if (data[0] == 0x21) {
|
||||
const auto& cmd = check_size_t<G_InterLevelWarp_6x21>(data);
|
||||
ses->floor = cmd.floor;
|
||||
|
||||
} else if (data[0] == 0x0C) {
|
||||
if (is_v1_or_v2(ses->version()) && ses->config.check_flag(Client::Flag::INFINITE_HP_ENABLED)) {
|
||||
send_remove_conditions(ses->client_channel, ses->lobby_client_id);
|
||||
send_remove_conditions(ses->server_channel, ses->lobby_client_id);
|
||||
}
|
||||
|
||||
} else if (data[0] == 0x2F || data[0] == 0x4B || data[0] == 0x4C) {
|
||||
if (ses->config.check_flag(Client::Flag::INFINITE_HP_ENABLED)) {
|
||||
send_player_stats_change(ses->client_channel,
|
||||
ses->lobby_client_id, PlayerStatsChange::ADD_HP, 2550);
|
||||
send_player_stats_change(ses->server_channel,
|
||||
ses->lobby_client_id, PlayerStatsChange::ADD_HP, 2550);
|
||||
}
|
||||
|
||||
} else if (data[0] == 0x3E) {
|
||||
C_6x_movement<G_StopAtPosition_6x3E>(ses, data);
|
||||
|
||||
} else if (data[0] == 0x3F) {
|
||||
C_6x_movement<G_SetPosition_6x3F>(ses, data);
|
||||
|
||||
} else if (data[0] == 0x40) {
|
||||
C_6x_movement<G_WalkToPosition_6x40>(ses, data);
|
||||
|
||||
} else if (data[0] == 0x42) {
|
||||
C_6x_movement<G_RunToPosition_6x42>(ses, data);
|
||||
|
||||
} else if (data[0] == 0x48) {
|
||||
if (ses->config.check_flag(Client::Flag::INFINITE_TP_ENABLED)) {
|
||||
send_player_stats_change(ses->client_channel,
|
||||
ses->lobby_client_id, PlayerStatsChange::ADD_TP, 255);
|
||||
send_player_stats_change(ses->server_channel,
|
||||
ses->lobby_client_id, PlayerStatsChange::ADD_TP, 255);
|
||||
}
|
||||
|
||||
} else if (data[0] == 0x5F) {
|
||||
const auto& cmd = check_size_t<G_DropItem_DC_6x5F>(data, sizeof(G_DropItem_PC_V3_BB_6x5F));
|
||||
send_item_notification_if_needed(ses->require_server_state(), ses->client_channel, ses->config, cmd.item.item, true);
|
||||
|
||||
} else if (data[0] == 0x60 || static_cast<uint8_t>(data[0]) == 0xA2) {
|
||||
return SC_6x60_6xA2(ses, data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+72
-1
@@ -522,6 +522,7 @@ ProxyServer::LinkedSession::LinkedSession(
|
||||
sub_version(0), // This is set during resume()
|
||||
remote_guild_card_number(-1),
|
||||
next_item_id(0x0F000000),
|
||||
drop_mode(DropMode::PASSTHROUGH),
|
||||
lobby_players(12),
|
||||
lobby_client_id(0),
|
||||
leader_client_id(0),
|
||||
@@ -529,7 +530,13 @@ ProxyServer::LinkedSession::LinkedSession(
|
||||
x(0.0),
|
||||
z(0.0),
|
||||
is_in_game(false),
|
||||
is_in_quest(false) {
|
||||
is_in_quest(false),
|
||||
lobby_event(0),
|
||||
lobby_difficulty(0),
|
||||
lobby_section_id(0),
|
||||
lobby_mode(GameMode::NORMAL),
|
||||
lobby_episode(Episode::EP1),
|
||||
lobby_random_seed(0) {
|
||||
this->last_switch_enabled_command.header.subcommand = 0;
|
||||
memset(this->prev_server_command_bytes, 0, sizeof(this->prev_server_command_bytes));
|
||||
}
|
||||
@@ -724,6 +731,70 @@ void ProxyServer::LinkedSession::clear_lobby_players(size_t num_slots) {
|
||||
this->log.info("Cleared lobby players");
|
||||
}
|
||||
|
||||
void ProxyServer::LinkedSession::set_drop_mode(DropMode new_mode) {
|
||||
this->drop_mode = new_mode;
|
||||
if (this->drop_mode == DropMode::INTERCEPT) {
|
||||
auto s = this->require_server_state();
|
||||
auto version = this->version();
|
||||
|
||||
shared_ptr<const RareItemSet> rare_item_set;
|
||||
shared_ptr<const CommonItemSet> common_item_set;
|
||||
switch (version) {
|
||||
case Version::PC_PATCH:
|
||||
case Version::BB_PATCH:
|
||||
case Version::GC_EP3_NTE:
|
||||
case Version::GC_EP3:
|
||||
throw runtime_error("cannot create item creator for this base version");
|
||||
case Version::DC_NTE:
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
case Version::DC_V1:
|
||||
// TODO: We should probably have a v1 common item set at some point too
|
||||
common_item_set = s->common_item_set_v2;
|
||||
rare_item_set = s->rare_item_sets.at("rare-table-v1");
|
||||
break;
|
||||
case Version::DC_V2:
|
||||
case Version::PC_NTE:
|
||||
case Version::PC_V2:
|
||||
common_item_set = s->common_item_set_v2;
|
||||
rare_item_set = s->rare_item_sets.at("rare-table-v2");
|
||||
break;
|
||||
case Version::GC_NTE:
|
||||
case Version::GC_V3:
|
||||
case Version::XB_V3:
|
||||
common_item_set = s->common_item_set_v3_v4;
|
||||
rare_item_set = s->rare_item_sets.at("rare-table-v3");
|
||||
break;
|
||||
case Version::BB_V4:
|
||||
common_item_set = s->common_item_set_v3_v4;
|
||||
rare_item_set = s->rare_item_sets.at("rare-table-v4");
|
||||
break;
|
||||
default:
|
||||
throw logic_error("invalid lobby base version");
|
||||
}
|
||||
uint32_t random_seed = this->config.check_flag(Client::Flag::USE_OVERRIDE_RANDOM_SEED)
|
||||
? this->config.override_random_seed
|
||||
: this->lobby_random_seed;
|
||||
this->item_creator = make_shared<ItemCreator>(
|
||||
common_item_set,
|
||||
rare_item_set,
|
||||
s->armor_random_set,
|
||||
s->tool_random_set,
|
||||
s->weapon_random_sets.at(this->lobby_difficulty),
|
||||
s->tekker_adjustment_set,
|
||||
s->item_parameter_table(version),
|
||||
version,
|
||||
this->lobby_episode,
|
||||
(this->lobby_mode == GameMode::SOLO) ? GameMode::NORMAL : this->lobby_mode,
|
||||
this->lobby_difficulty,
|
||||
this->lobby_section_id,
|
||||
random_seed,
|
||||
// TODO: Can we get battle rules here somehow?
|
||||
nullptr);
|
||||
} else {
|
||||
this->item_creator.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void ProxyServer::LinkedSession::send_to_game_server(const char* error_message) {
|
||||
// If there is no license, do nothing - we can't return to the game server
|
||||
// from unlicensed sessions
|
||||
|
||||
+16
-1
@@ -74,6 +74,16 @@ public:
|
||||
ItemData next_drop_item;
|
||||
uint32_t next_item_id;
|
||||
|
||||
enum class DropMode {
|
||||
DISABLED = 0,
|
||||
PASSTHROUGH,
|
||||
INTERCEPT,
|
||||
};
|
||||
DropMode drop_mode;
|
||||
std::shared_ptr<std::string> quest_dat_data;
|
||||
std::shared_ptr<ItemCreator> item_creator;
|
||||
std::shared_ptr<Map> map;
|
||||
|
||||
struct LobbyPlayer {
|
||||
uint32_t guild_card_number = 0;
|
||||
uint64_t xb_user_id = 0;
|
||||
@@ -90,7 +100,12 @@ public:
|
||||
float z;
|
||||
bool is_in_game;
|
||||
bool is_in_quest;
|
||||
uint8_t difficulty;
|
||||
uint8_t lobby_event;
|
||||
uint8_t lobby_difficulty;
|
||||
uint8_t lobby_section_id;
|
||||
GameMode lobby_mode;
|
||||
Episode lobby_episode;
|
||||
uint32_t lobby_random_seed;
|
||||
uint64_t client_ping_start_time = 0;
|
||||
uint64_t server_ping_start_time = 0;
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ static shared_ptr<const Menu> proxy_options_menu_for_client(shared_ptr<const Cli
|
||||
"Chat commands", "Enable chat\ncommands");
|
||||
add_flag_option(ProxyOptionsMenuItemID::PLAYER_NOTIFICATIONS, Client::Flag::PROXY_PLAYER_NOTIFICATIONS_ENABLED,
|
||||
"Player notifs", "Show a message\nwhen other players\njoin or leave");
|
||||
static const char* item_drop_notifs_description = "Enable item drop\nnotifications:\n- No = no notifs\n- Rare = rares only\n- Item = all items\nbut not Meseta\n- Every = everything";
|
||||
static const char* item_drop_notifs_description = "Enable item drop\nnotifications:\n- No: no notifs\n- Rare: rares only\n- Item: all items\nbut not Meseta\n- Every: everything";
|
||||
if (!is_ep3(c->version())) {
|
||||
switch (c->config.get_drop_notification_mode()) {
|
||||
case Client::ItemDropNotificationMode::NOTHING:
|
||||
|
||||
+94
-72
@@ -2148,27 +2148,7 @@ static void on_sort_inventory_bb(shared_ptr<Client> c, uint8_t, uint8_t, void* d
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// EXP/Drop Item commands
|
||||
|
||||
static void on_entity_drop_item_request(shared_ptr<Client> c, uint8_t command, uint8_t flag, void* data, size_t size) {
|
||||
auto s = c->require_server_state();
|
||||
auto l = c->require_lobby();
|
||||
if (!l->is_game()) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (l->drop_mode) {
|
||||
case Lobby::DropMode::CLIENT:
|
||||
forward_subcommand(c, command, flag, data, size);
|
||||
return;
|
||||
case Lobby::DropMode::DISABLED:
|
||||
return;
|
||||
case Lobby::DropMode::SERVER_SHARED:
|
||||
case Lobby::DropMode::SERVER_DUPLICATE:
|
||||
case Lobby::DropMode::SERVER_PRIVATE:
|
||||
break;
|
||||
default:
|
||||
throw logic_error("invalid drop mode");
|
||||
}
|
||||
|
||||
G_SpecializableItemDropRequest_6xA2 normalize_drop_request(const void* data, size_t size) {
|
||||
G_SpecializableItemDropRequest_6xA2 cmd;
|
||||
if (size == sizeof(G_SpecializableItemDropRequest_6xA2)) {
|
||||
cmd = check_size_t<G_SpecializableItemDropRequest_6xA2>(data, size);
|
||||
@@ -2200,21 +2180,35 @@ static void on_entity_drop_item_request(shared_ptr<Client> c, uint8_t command, u
|
||||
cmd.ignore_def = in_cmd.ignore_def;
|
||||
cmd.effective_area = in_cmd.floor;
|
||||
}
|
||||
return cmd;
|
||||
}
|
||||
|
||||
DropReconcileResult reconcile_drop_request_with_map(
|
||||
PrefixedLogger& log,
|
||||
Channel& client_channel,
|
||||
G_SpecializableItemDropRequest_6xA2& cmd,
|
||||
Version version,
|
||||
Episode episode,
|
||||
const Client::Config& config,
|
||||
shared_ptr<Map> map,
|
||||
bool mark_drop) {
|
||||
DropReconcileResult res;
|
||||
res.effective_rt_index = 0xFF;
|
||||
res.is_box = (cmd.rt_index == 0x30);
|
||||
res.should_drop = true;
|
||||
res.ignore_def = (cmd.ignore_def != 0);
|
||||
|
||||
Map::Object* map_object = nullptr;
|
||||
Map::Enemy* map_enemy = nullptr;
|
||||
bool ignore_def = (cmd.ignore_def != 0);
|
||||
uint8_t effective_rt_index = 0xFF;
|
||||
bool is_box = (cmd.rt_index == 0x30);
|
||||
if (is_box) {
|
||||
if (l->map) {
|
||||
map_object = &l->map->objects.at(cmd.entity_id);
|
||||
l->log.info("Drop check for K-%hX %c %s",
|
||||
map_object->object_id, ignore_def ? 'G' : 'S', Map::name_for_object_type(map_object->base_type));
|
||||
if (res.is_box) {
|
||||
if (map) {
|
||||
map_object = &map->objects.at(cmd.entity_id);
|
||||
log.info("Drop check for K-%hX %c %s",
|
||||
map_object->object_id, res.ignore_def ? 'G' : 'S', Map::name_for_object_type(map_object->base_type));
|
||||
if (cmd.floor != map_object->floor) {
|
||||
l->log.warning("Floor %02hhX from command does not match object\'s expected floor %02hhX", cmd.floor, map_object->floor);
|
||||
log.warning("Floor %02hhX from command does not match object\'s expected floor %02hhX", cmd.floor, map_object->floor);
|
||||
}
|
||||
if (is_v1_or_v2(l->base_version) && (l->base_version != Version::GC_NTE)) {
|
||||
if (is_v1_or_v2(version) && (version != Version::GC_NTE)) {
|
||||
// V1 and V2 don't have 6xA2, so we can't get ignore_def or the object
|
||||
// parameters from the client on those versions
|
||||
cmd.param3 = map_object->param3;
|
||||
@@ -2223,62 +2217,90 @@ static void on_entity_drop_item_request(shared_ptr<Client> c, uint8_t command, u
|
||||
cmd.param6 = map_object->param6;
|
||||
}
|
||||
bool object_ignore_def = (map_object->param1 > 0.0);
|
||||
if (ignore_def != object_ignore_def) {
|
||||
l->log.warning("ignore_def value %s from command does not match object\'s expected ignore_def %s (from p1=%g)",
|
||||
ignore_def ? "true" : "false", object_ignore_def ? "true" : "false", map_object->param1);
|
||||
if (res.ignore_def != object_ignore_def) {
|
||||
log.warning("ignore_def value %s from command does not match object\'s expected ignore_def %s (from p1=%g)",
|
||||
res.ignore_def ? "true" : "false", object_ignore_def ? "true" : "false", map_object->param1);
|
||||
}
|
||||
if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) {
|
||||
send_text_message_printf(c, "$C5K-%hX %c %s",
|
||||
map_object->object_id, ignore_def ? 'G' : 'S', Map::name_for_object_type(map_object->base_type));
|
||||
if (config.check_flag(Client::Flag::DEBUG_ENABLED)) {
|
||||
send_text_message_printf(client_channel, "$C5K-%hX %c %s",
|
||||
map_object->object_id, res.ignore_def ? 'G' : 'S', Map::name_for_object_type(map_object->base_type));
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
if (l->map) {
|
||||
map_enemy = &l->map->enemies.at(cmd.entity_id);
|
||||
l->log.info("Drop check for E-%hX %s", map_enemy->enemy_id, name_for_enum(map_enemy->type));
|
||||
effective_rt_index = rare_table_index_for_enemy_type(map_enemy->type);
|
||||
if (map) {
|
||||
map_enemy = &map->enemies.at(cmd.entity_id);
|
||||
log.info("Drop check for E-%hX %s", map_enemy->enemy_id, name_for_enum(map_enemy->type));
|
||||
res.effective_rt_index = rare_table_index_for_enemy_type(map_enemy->type);
|
||||
// rt_indexes in Episode 4 don't match those sent in the command; we just
|
||||
// ignore what the client sends.
|
||||
if ((l->episode != Episode::EP4) && (cmd.rt_index != effective_rt_index)) {
|
||||
l->log.warning("rt_index %02hhX from command does not match entity\'s expected index %02" PRIX32,
|
||||
cmd.rt_index, effective_rt_index);
|
||||
if (!is_v4(l->base_version)) {
|
||||
effective_rt_index = cmd.rt_index;
|
||||
if ((episode != Episode::EP4) && (cmd.rt_index != res.effective_rt_index)) {
|
||||
log.warning("rt_index %02hhX from command does not match entity\'s expected index %02" PRIX32,
|
||||
cmd.rt_index, res.effective_rt_index);
|
||||
if (!is_v4(version)) {
|
||||
res.effective_rt_index = cmd.rt_index;
|
||||
}
|
||||
}
|
||||
if (cmd.floor != map_enemy->floor) {
|
||||
l->log.warning("Floor %02hhX from command does not match entity\'s expected floor %02hhX",
|
||||
log.warning("Floor %02hhX from command does not match entity\'s expected floor %02hhX",
|
||||
cmd.floor, map_enemy->floor);
|
||||
}
|
||||
if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) {
|
||||
send_text_message_printf(c, "$C5E-%hX %s", map_enemy->enemy_id, name_for_enum(map_enemy->type));
|
||||
if (config.check_flag(Client::Flag::DEBUG_ENABLED)) {
|
||||
send_text_message_printf(client_channel, "$C5E-%hX %s", map_enemy->enemy_id, name_for_enum(map_enemy->type));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool should_drop = true;
|
||||
if (map_object) {
|
||||
if (map_object->item_drop_checked) {
|
||||
l->log.info("Drop check has already occurred for K-%04hX; skipping it", map_object->object_id);
|
||||
should_drop = false;
|
||||
} else {
|
||||
map_object->item_drop_checked = true;
|
||||
if (mark_drop) {
|
||||
if (map_object) {
|
||||
if (map_object->item_drop_checked) {
|
||||
log.info("Drop check has already occurred for K-%04hX; skipping it", map_object->object_id);
|
||||
res.should_drop = false;
|
||||
} else {
|
||||
map_object->item_drop_checked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (map_enemy) {
|
||||
if (map_enemy->state_flags & Map::Enemy::Flag::ITEM_DROPPED) {
|
||||
l->log.info("Drop check has already occurred for E-%04hX; skipping it", map_enemy->enemy_id);
|
||||
should_drop = false;
|
||||
} else {
|
||||
map_enemy->state_flags |= Map::Enemy::Flag::ITEM_DROPPED;
|
||||
if (map_enemy) {
|
||||
if (map_enemy->state_flags & Map::Enemy::Flag::ITEM_DROPPED) {
|
||||
log.info("Drop check has already occurred for E-%04hX; skipping it", map_enemy->enemy_id);
|
||||
res.should_drop = false;
|
||||
} else {
|
||||
map_enemy->state_flags |= Map::Enemy::Flag::ITEM_DROPPED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (should_drop) {
|
||||
return res;
|
||||
}
|
||||
|
||||
static void on_entity_drop_item_request(shared_ptr<Client> c, uint8_t command, uint8_t flag, void* data, size_t size) {
|
||||
auto s = c->require_server_state();
|
||||
auto l = c->require_lobby();
|
||||
if (!l->is_game()) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (l->drop_mode) {
|
||||
case Lobby::DropMode::CLIENT:
|
||||
forward_subcommand(c, command, flag, data, size);
|
||||
return;
|
||||
case Lobby::DropMode::DISABLED:
|
||||
return;
|
||||
case Lobby::DropMode::SERVER_SHARED:
|
||||
case Lobby::DropMode::SERVER_DUPLICATE:
|
||||
case Lobby::DropMode::SERVER_PRIVATE:
|
||||
break;
|
||||
default:
|
||||
throw logic_error("invalid drop mode");
|
||||
}
|
||||
|
||||
G_SpecializableItemDropRequest_6xA2 cmd = normalize_drop_request(data, size);
|
||||
auto rec = reconcile_drop_request_with_map(c->log, c->channel, cmd, c->version(), l->episode, c->config, l->map, true);
|
||||
|
||||
if (rec.should_drop) {
|
||||
auto generate_item = [&]() -> ItemCreator::DropResult {
|
||||
if (is_box) {
|
||||
if (ignore_def) {
|
||||
if (rec.is_box) {
|
||||
if (rec.ignore_def) {
|
||||
l->log.info("Creating item from box %04hX (area %02hX)", cmd.entity_id.load(), cmd.effective_area);
|
||||
return l->item_creator->on_box_item_drop(cmd.effective_area);
|
||||
} else {
|
||||
@@ -2288,7 +2310,7 @@ static void on_entity_drop_item_request(shared_ptr<Client> c, uint8_t command, u
|
||||
}
|
||||
} else {
|
||||
l->log.info("Creating item from enemy %04hX (area %02hX)", cmd.entity_id.load(), cmd.effective_area);
|
||||
return l->item_creator->on_monster_item_drop(effective_rt_index, cmd.effective_area);
|
||||
return l->item_creator->on_monster_item_drop(rec.effective_rt_index, cmd.effective_area);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2308,12 +2330,12 @@ static void on_entity_drop_item_request(shared_ptr<Client> c, uint8_t command, u
|
||||
l->log.info("Entity %04hX (area %02hX) created item %s", cmd.entity_id.load(), cmd.effective_area, name.c_str());
|
||||
if (l->drop_mode == Lobby::DropMode::SERVER_DUPLICATE) {
|
||||
for (const auto& lc : l->clients) {
|
||||
if (lc && (is_box || (lc->floor == cmd.floor))) {
|
||||
if (lc && (rec.is_box || (lc->floor == cmd.floor))) {
|
||||
res.item.id = l->generate_item_id(0xFF);
|
||||
l->log.info("Creating item %08" PRIX32 " at %02hhX:%g,%g for %s",
|
||||
res.item.id.load(), cmd.floor, cmd.x.load(), cmd.z.load(), lc->channel.name.c_str());
|
||||
l->add_item(cmd.floor, res.item, cmd.x, cmd.z, (1 << lc->lobby_client_id));
|
||||
send_drop_item_to_channel(s, lc->channel, res.item, !is_box, cmd.floor, cmd.x, cmd.z, cmd.entity_id);
|
||||
send_drop_item_to_channel(s, lc->channel, res.item, !rec.is_box, cmd.floor, cmd.x, cmd.z, cmd.entity_id);
|
||||
send_item_notification_if_needed(s, lc->channel, lc->config, res.item, res.is_from_rare_table);
|
||||
}
|
||||
}
|
||||
@@ -2323,7 +2345,7 @@ static void on_entity_drop_item_request(shared_ptr<Client> c, uint8_t command, u
|
||||
l->log.info("Creating item %08" PRIX32 " at %02hhX:%g,%g for all clients",
|
||||
res.item.id.load(), cmd.floor, cmd.x.load(), cmd.z.load());
|
||||
l->add_item(cmd.floor, res.item, cmd.x, cmd.z, 0x00F);
|
||||
send_drop_item_to_lobby(l, res.item, !is_box, cmd.floor, cmd.x, cmd.z, cmd.entity_id);
|
||||
send_drop_item_to_lobby(l, res.item, !rec.is_box, cmd.floor, cmd.x, cmd.z, cmd.entity_id);
|
||||
for (auto lc : l->clients) {
|
||||
if (lc) {
|
||||
send_item_notification_if_needed(s, lc->channel, lc->config, res.item, res.is_from_rare_table);
|
||||
@@ -2335,7 +2357,7 @@ static void on_entity_drop_item_request(shared_ptr<Client> c, uint8_t command, u
|
||||
}
|
||||
case Lobby::DropMode::SERVER_PRIVATE: {
|
||||
for (const auto& lc : l->clients) {
|
||||
if (lc && (is_box || (lc->floor == cmd.floor))) {
|
||||
if (lc && (rec.is_box || (lc->floor == cmd.floor))) {
|
||||
auto res = generate_item();
|
||||
if (res.item.empty()) {
|
||||
l->log.info("No item was created for %s", lc->channel.name.c_str());
|
||||
@@ -2346,7 +2368,7 @@ static void on_entity_drop_item_request(shared_ptr<Client> c, uint8_t command, u
|
||||
l->log.info("Creating item %08" PRIX32 " at %02hhX:%g,%g for %s",
|
||||
res.item.id.load(), cmd.floor, cmd.x.load(), cmd.z.load(), lc->channel.name.c_str());
|
||||
l->add_item(cmd.floor, res.item, cmd.x, cmd.z, (1 << lc->lobby_client_id));
|
||||
send_drop_item_to_channel(s, lc->channel, res.item, !is_box, cmd.floor, cmd.x, cmd.z, cmd.entity_id);
|
||||
send_drop_item_to_channel(s, lc->channel, res.item, !rec.is_box, cmd.floor, cmd.x, cmd.z, cmd.entity_id);
|
||||
send_item_notification_if_needed(s, lc->channel, lc->config, res.item, res.is_from_rare_table);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#include "Client.hh"
|
||||
#include "CommandFormats.hh"
|
||||
#include "Lobby.hh"
|
||||
#include "PSOProtocol.hh"
|
||||
#include "ServerState.hh"
|
||||
@@ -16,3 +17,22 @@ void send_item_notification_if_needed(
|
||||
const Client::Config& config,
|
||||
const ItemData& item,
|
||||
bool is_from_rare_table);
|
||||
|
||||
G_SpecializableItemDropRequest_6xA2 normalize_drop_request(const void* data, size_t size);
|
||||
|
||||
struct DropReconcileResult {
|
||||
uint8_t effective_rt_index;
|
||||
bool is_box;
|
||||
bool should_drop;
|
||||
bool ignore_def;
|
||||
};
|
||||
|
||||
DropReconcileResult reconcile_drop_request_with_map(
|
||||
PrefixedLogger& log,
|
||||
Channel& client_channel,
|
||||
G_SpecializableItemDropRequest_6xA2& cmd,
|
||||
Version version,
|
||||
Episode episode,
|
||||
const Client::Config& config,
|
||||
std::shared_ptr<Map> map,
|
||||
bool mark_drop);
|
||||
|
||||
Reference in New Issue
Block a user