implement nonstandard drop modes

This commit is contained in:
Martin Michelsen
2023-12-16 10:09:40 -08:00
parent 2bd43391a6
commit 66d7594a36
23 changed files with 1015 additions and 866 deletions
+106 -62
View File
@@ -108,14 +108,24 @@ static void server_command_lobby_info(shared_ptr<Client> c, const std::string&)
}
lines.emplace_back(string_printf("$C7Section ID: $C6%s$C7", name_for_section_id(l->section_id)));
if (l->check_flag(Lobby::Flag::DROPS_ENABLED)) {
if (l->item_creator) {
lines.emplace_back("Server item table");
} else {
switch (l->drop_mode) {
case Lobby::DropMode::DISABLED:
lines.emplace_back("Drops disabled");
break;
case Lobby::DropMode::CLIENT:
lines.emplace_back("Client item table");
}
} else {
lines.emplace_back("No item drops");
break;
case Lobby::DropMode::SERVER_SHARED:
lines.emplace_back("Server item table");
break;
case Lobby::DropMode::SERVER_PRIVATE:
lines.emplace_back("Server indiv items");
break;
case Lobby::DropMode::SERVER_DUPLICATE:
lines.emplace_back("Server dup items");
break;
default:
lines.emplace_back("$C4Unknown drop mode$C7");
}
if (l->check_flag(Lobby::Flag::CHEATS_ENABLED)) {
lines.emplace_back("Cheats enabled");
@@ -1320,32 +1330,28 @@ static void server_command_what(shared_ptr<Client> c, const std::string&) {
if (!episode_has_arpg_semantics(l->episode)) {
return;
}
if (!l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) {
send_text_message(c, "$C4Item tracking is\nnot available");
} else {
float min_dist2 = 0.0f;
uint32_t nearest_item_id = 0xFFFFFFFF;
for (const auto& it : l->item_id_to_floor_item) {
if (it.second.floor != c->floor) {
continue;
}
float dx = it.second.x - c->x;
float dz = it.second.z - c->z;
float dist2 = (dx * dx) + (dz * dz);
if ((nearest_item_id == 0xFFFFFFFF) || (dist2 < min_dist2)) {
nearest_item_id = it.first;
min_dist2 = dist2;
}
}
if (nearest_item_id == 0xFFFFFFFF) {
send_text_message(c, "$C4No items are near you");
} else {
auto s = c->require_server_state();
const auto& item = l->item_id_to_floor_item.at(nearest_item_id);
string name = s->describe_item(c->version(), item.data, true);
send_text_message(c, name);
float min_dist2 = 0.0f;
shared_ptr<const Lobby::FloorItem> nearest_fi;
for (const auto& it : l->floor_item_managers.at(c->floor).items) {
if (!it.second->visible_to_client(c->lobby_client_id)) {
continue;
}
float dx = it.second->x - c->x;
float dz = it.second->z - c->z;
float dist2 = (dx * dx) + (dz * dz);
if (!nearest_fi || (dist2 < min_dist2)) {
nearest_fi = it.second;
min_dist2 = dist2;
}
}
if (!nearest_fi) {
send_text_message(c, "$C4No items are near you");
} else {
auto s = c->require_server_state();
string name = s->describe_item(c->version(), nearest_fi->data, true);
send_text_message(c, name);
}
}
@@ -1420,35 +1426,69 @@ static void proxy_command_switch_assist(shared_ptr<ProxyServer::LinkedSession> s
ses->config.check_flag(Client::Flag::SWITCH_ASSIST_ENABLED) ? "enabled" : "disabled");
}
static void server_command_drop(shared_ptr<Client> c, const std::string&) {
static void server_command_dropmode(shared_ptr<Client> c, const std::string& args) {
auto l = c->require_lobby();
check_is_game(l, true);
check_is_leader(l, c);
if (l->check_flag(Lobby::Flag::CANNOT_CHANGE_DROPS_ENABLED)) {
send_text_message(c, "Drop mode cannot\nbe changed on this\nserver");
} else {
l->toggle_flag(Lobby::Flag::DROPS_ENABLED);
send_text_message_printf(l, "Drops %s", l->check_flag(Lobby::Flag::DROPS_ENABLED) ? "enabled" : "disabled");
}
}
if (args.empty()) {
switch (l->drop_mode) {
case Lobby::DropMode::DISABLED:
send_text_message(c, "Drop mode: disabled");
break;
case Lobby::DropMode::CLIENT:
send_text_message(c, "Drop mode: client");
break;
case Lobby::DropMode::SERVER_SHARED:
send_text_message(c, "Drop mode: server\nshared");
break;
case Lobby::DropMode::SERVER_PRIVATE:
send_text_message(c, "Drop mode: server\nprivate");
break;
case Lobby::DropMode::SERVER_DUPLICATE:
send_text_message(c, "Drop mode: server\nduplicate");
break;
}
static void server_command_itemtable(shared_ptr<Client> c, const std::string&) {
auto s = c->require_server_state();
auto l = c->require_lobby();
check_is_game(l, true);
check_is_leader(l, c);
if (l->check_flag(Lobby::Flag::CANNOT_CHANGE_ITEM_TABLE)) {
send_text_message(c, "Cannot switch item\ntables on this\nserver");
} else if (l->base_version == Version::BB_V4) {
send_text_message(c, "Cannot use client\nitem table on BB");
} else if (!l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) {
send_text_message(c, "Cannot use server\nitem tables if item\ntracking is off");
} else if (l->item_creator) {
l->item_creator.reset();
send_text_message(l, "Game switched to\nclient item tables");
} else {
l->create_item_creator();
send_text_message(l, "Game switched to\nserver item tables");
check_is_leader(l, c);
Lobby::DropMode new_mode;
if ((args == "none") || (args == "disabled")) {
new_mode = Lobby::DropMode::DISABLED;
} else if (args == "client") {
new_mode = Lobby::DropMode::CLIENT;
} else if ((args == "shared") || (args == "server")) {
new_mode = Lobby::DropMode::SERVER_SHARED;
} else if ((args == "private") || (args == "priv")) {
new_mode = Lobby::DropMode::SERVER_PRIVATE;
} else if ((args == "duplicate") || (args == "dup")) {
new_mode = Lobby::DropMode::SERVER_DUPLICATE;
} else {
send_text_message(c, "Invalid drop mode");
return;
}
if (!(l->allowed_drop_modes & (1 << static_cast<size_t>(new_mode)))) {
send_text_message(c, "Drop mode not\nallowed");
return;
}
l->set_drop_mode(new_mode);
switch (l->drop_mode) {
case Lobby::DropMode::DISABLED:
send_text_message(l, "Item drops disabled");
break;
case Lobby::DropMode::CLIENT:
send_text_message(l, "Item drops changed\nto client mode");
break;
case Lobby::DropMode::SERVER_SHARED:
send_text_message(l, "Item drops changed\nto server shared\nmode");
break;
case Lobby::DropMode::SERVER_PRIVATE:
send_text_message(l, "Item drops changed\nto server private\nmode");
break;
case Lobby::DropMode::SERVER_DUPLICATE:
send_text_message(l, "Item drops changed\nto server duplicate\nmode");
break;
}
}
}
@@ -1461,8 +1501,13 @@ static void server_command_item(shared_ptr<Client> c, const std::string& args) {
ItemData item = s->item_name_index->parse_item_description(c->version(), args);
item.id = l->generate_item_id(c->lobby_client_id);
l->add_item(item, c->floor, c->x, c->z);
send_drop_stacked_item(l, item, c->floor, c->x, c->z);
if ((l->drop_mode == Lobby::DropMode::SERVER_PRIVATE) || (l->drop_mode == Lobby::DropMode::SERVER_DUPLICATE)) {
l->add_item(c->floor, item, c->x, c->z, (1 << c->lobby_client_id));
send_drop_stacked_item_to_channel(s, c->channel, item, c->floor, c->x, c->z);
} else {
l->add_item(c->floor, item, c->x, c->z, 0x00F);
send_drop_stacked_item_to_lobby(l, item, c->floor, c->x, c->z);
}
string name = s->describe_item(c->version(), item, true);
send_text_message(c, "$C7Item created:\n" + name);
@@ -1496,8 +1541,8 @@ static void proxy_command_item(shared_ptr<ProxyServer::LinkedSession> ses, const
send_text_message(ses->client_channel, "$C7Next drop:\n" + name);
} else {
send_drop_stacked_item(s, ses->client_channel, item, ses->floor, ses->x, ses->z);
send_drop_stacked_item(s, ses->server_channel, item, ses->floor, ses->x, ses->z);
send_drop_stacked_item_to_channel(s, ses->client_channel, item, ses->floor, ses->x, ses->z);
send_drop_stacked_item_to_channel(s, ses->server_channel, item, ses->floor, ses->x, ses->z);
string name = s->describe_item(ses->version(), item, true);
send_text_message(ses->client_channel, "$C7Item created:\n" + name);
@@ -1754,7 +1799,7 @@ static const unordered_map<string, ChatCommandDefinition> chat_commands({
{"$cheat", {server_command_cheat, nullptr}},
{"$debug", {server_command_debug, nullptr}},
{"$defrange", {server_command_ep3_set_def_dice_range, nullptr}},
{"$drop", {server_command_drop, nullptr}},
{"$dropmode", {server_command_dropmode, nullptr}},
{"$edit", {server_command_edit, nullptr}},
{"$ep3battledebug", {server_command_enable_ep3_battle_debug_menu, nullptr}},
{"$event", {server_command_lobby_event, proxy_command_lobby_event}},
@@ -1764,7 +1809,6 @@ static const unordered_map<string, ChatCommandDefinition> chat_commands({
{"$inftime", {server_command_ep3_infinite_time, nullptr}},
{"$inftp", {server_command_infinite_tp, proxy_command_infinite_tp}},
{"$item", {server_command_item, proxy_command_item}},
{"$itemtable", {server_command_itemtable, nullptr}},
{"$i", {server_command_item, proxy_command_item}},
{"$kick", {server_command_kick, nullptr}},
{"$li", {server_command_lobby_info, proxy_command_lobby_info}},
+15 -15
View File
@@ -4449,17 +4449,18 @@ struct G_BuyShopItem_6x5E {
// 6x5F: Drop item from box/enemy
struct FloorItem {
uint8_t floor = 0;
uint8_t from_enemy = 0;
le_uint16_t entity_id = 0; // < 0x0B50 if from_enemy != 0; otherwise < 0x0BA0
le_float x = 0.0f;
le_float z = 0.0f;
le_uint16_t unknown_a2 = 0;
/* 00 */ uint8_t floor = 0;
/* 01 */ uint8_t from_enemy = 0;
/* 02 */ le_uint16_t entity_id = 0; // < 0x0B50 if from_enemy != 0; otherwise < 0x0BA0
/* 04 */ le_float x = 0.0f;
/* 08 */ le_float z = 0.0f;
/* 0C */ le_uint16_t unknown_a2 = 0;
// The drop number is scoped to the floor and increments by 1 each time an
// item is dropped. The last item dropped in each floor has drop_number equal
// to total_items_dropped_per_floor[floor - 1] - 1.
le_uint16_t drop_number = 0;
ItemData item;
/* 0E */ le_uint16_t drop_number = 0;
/* 10 */ ItemData item;
/* 24 */
} __packed__;
struct G_DropItem_DC_6x5F {
@@ -4500,9 +4501,9 @@ struct G_ActivateMagEffect_6x61 {
// 6x62: Unknown
// This subcommand is completely ignored (at least, by PSO GC).
// 6x63: Destroy ground item (used when too many items have been dropped)
// 6x63: Destroy floor item (used when too many items have been dropped)
struct G_DestroyGroundItem_6x63 {
struct G_DestroyFloorItem_6x63 {
G_UnusedHeader header;
le_uint32_t item_id = 0;
le_uint32_t floor = 0;
@@ -4622,13 +4623,12 @@ struct G_SyncObjectState_6x6C_Entry_Decompressed {
// commands cannot be processed on the same frame.
struct G_SyncItemState_6x6D_Decompressed {
// TODO: Verify this format on DC and PC. It appears correct for GC and BB.
// Note: 16 vs. 15 is not a bug here - there really is an extra field in the
// total drop count vs. the floor item count. Despite this, Pioneer 2 or Lab
// (floor 0) isn't included in total_items_dropped_per_floor (so Forest 1 is
// [0] in that array) but it is included in floor_item_count_per_floor (so
// Forest 1 is [1] there).
parray<le_uint16_t, 16> total_items_dropped_per_floor;
// (floor 0) isn't included in next_drop_number_per_floor (so Forest 1 is [0]
// in that array) but it is included in floor_item_count_per_floor (so Forest
// 1 is [1] there).
parray<le_uint16_t, 16> next_drop_number_per_floor;
// Only [0]-[3] in this array are ever actually used in normal gameplay, but
// the client fills in all 12 of these with reasonable values.
parray<le_uint32_t, 12> next_item_id_per_player;
+15 -7
View File
@@ -52,6 +52,14 @@ void ItemCreator::set_random_state(uint32_t seed, uint32_t absolute_offset) {
}
}
void ItemCreator::set_box_destroyed(uint16_t entity_id) {
this->destroyed_boxes.emplace(entity_id);
}
void ItemCreator::set_monster_destroyed(uint16_t entity_id) {
this->destroyed_monsters.emplace(entity_id);
}
void ItemCreator::clear_destroyed_entities() {
this->destroyed_monsters.clear();
this->destroyed_boxes.clear();
@@ -127,15 +135,15 @@ uint8_t ItemCreator::normalize_area_number(uint8_t area) const {
}
ItemData ItemCreator::on_box_item_drop(uint16_t entity_id, uint8_t area) {
return this->destroyed_boxes.emplace(entity_id).second
? this->on_box_item_drop_with_area_norm(this->normalize_area_number(area))
: ItemData();
return this->destroyed_boxes.count(entity_id)
? ItemData()
: this->on_box_item_drop_with_area_norm(this->normalize_area_number(area));
}
ItemData ItemCreator::on_monster_item_drop(uint16_t entity_id, uint32_t enemy_type, uint8_t area) {
return this->destroyed_monsters.emplace(entity_id).second
? this->on_monster_item_drop_with_area_norm(enemy_type, this->normalize_area_number(area))
: ItemData();
return this->destroyed_monsters.count(entity_id)
? ItemData()
: this->on_monster_item_drop_with_area_norm(enemy_type, this->normalize_area_number(area));
}
ItemData ItemCreator::on_box_item_drop_with_area_norm(uint8_t area_norm) {
@@ -1656,7 +1664,7 @@ void ItemCreator::generate_weapon_shop_item_bonus2(ItemData& item, size_t player
ItemData ItemCreator::on_specialized_box_item_drop(
uint16_t entity_id, uint8_t area, float def_z, uint32_t def0, uint32_t def1, uint32_t def2) {
if (!this->destroyed_boxes.emplace(entity_id).second) {
if (this->destroyed_boxes.count(entity_id)) {
return ItemData();
}
+3
View File
@@ -35,6 +35,9 @@ public:
ItemData on_box_item_drop(uint16_t entity_id, uint8_t area);
ItemData on_specialized_box_item_drop(uint16_t entity_id, uint8_t area, float def_z, uint32_t def0, uint32_t def1, uint32_t def2);
void set_monster_destroyed(uint16_t entity_id);
void set_box_destroyed(uint16_t entity_id);
static ItemData base_item_for_specialized_box(uint32_t def0, uint32_t def1, uint32_t def2);
std::vector<ItemData> generate_armor_shop_contents(size_t player_level);
+3 -3
View File
@@ -192,9 +192,9 @@ void player_use_item(shared_ptr<Client> c, size_t item_index) {
item.data.data1.clear_after(3);
should_delete_item = false;
auto l = c->lobby.lock();
if (l) {
send_create_inventory_item(c, item.data);
auto l = c->require_lobby();
if (l->base_version == Version::BB_V4) {
send_create_inventory_item_to_lobby(c, c->lobby_client_id, item.data);
}
break;
}
+254 -47
View File
@@ -11,13 +11,137 @@
using namespace std;
bool Lobby::FloorItem::visible_to_client(uint8_t client_id) const {
return this->visibility_flags & (1 << client_id);
}
Lobby::FloorItemManager::FloorItemManager(uint32_t lobby_id, uint8_t floor)
: log(string_printf("[Lobby:%08" PRIX32 ":FloorItems:%02hhX] ", lobby_id, floor), lobby_log.min_level),
next_drop_number(0) {}
bool Lobby::FloorItemManager::exists(uint32_t item_id) const {
return this->items.count(item_id);
}
shared_ptr<Lobby::FloorItem> Lobby::FloorItemManager::find(uint32_t item_id) const {
return this->items.at(item_id);
}
void Lobby::FloorItemManager::add(const ItemData& item, float x, float z, uint16_t visibility_flags) {
auto fi = make_shared<FloorItem>();
fi->data = item;
fi->x = x;
fi->z = z;
fi->drop_number = this->next_drop_number++;
fi->visibility_flags = visibility_flags & 0x0FFF;
this->add(fi);
}
void Lobby::FloorItemManager::add(shared_ptr<Lobby::FloorItem> fi) {
if (fi->visibility_flags == 0) {
throw logic_error("floor item is not visible to any player");
}
auto emplace_ret = this->items.emplace(fi->data.id, fi);
if (!emplace_ret.second) {
throw runtime_error("floor item already exists with the same ID");
}
for (size_t z = 0; z < 12; z++) {
if (fi->visible_to_client(z)) {
this->queue_for_client[z].emplace(fi->drop_number, fi);
}
}
this->log.info("Added floor item %08" PRIX32 " at %g, %g with drop number %" PRIu64 " visible to %03hX",
fi->data.id.load(), fi->x, fi->z, fi->drop_number, fi->visibility_flags);
}
std::shared_ptr<Lobby::FloorItem> Lobby::FloorItemManager::remove(uint32_t item_id, uint8_t client_id) {
auto item_it = this->items.find(item_id);
if (item_it == this->items.end()) {
throw out_of_range("item not present");
}
auto fi = item_it->second;
if ((client_id != 0xFF) && !fi->visible_to_client(client_id)) {
throw runtime_error("client does not have access to item");
}
for (size_t z = 0; z < 12; z++) {
if (fi->visible_to_client(z) && !this->queue_for_client[z].erase(fi->drop_number)) {
throw logic_error("item queue for client is inconsistent");
}
}
this->items.erase(item_it);
this->log.info("Removed floor item %08" PRIX32 " at %g, %g with drop number %" PRIu64 " visible to %03hX",
fi->data.id.load(), fi->x, fi->z, fi->drop_number, fi->visibility_flags);
return fi;
}
std::unordered_set<std::shared_ptr<Lobby::FloorItem>> Lobby::FloorItemManager::evict() {
unordered_set<shared_ptr<FloorItem>> ret;
for (size_t z = 0; z < 12; z++) {
while (this->queue_for_client[z].size() > 48) {
ret.emplace(this->remove(this->queue_for_client[z].begin()->second->data.id, 0xFF));
}
}
this->log.info("Evicted %zu items", ret.size());
return ret;
}
void Lobby::FloorItemManager::clear_inaccessible(uint16_t remaining_clients_mask) {
unordered_set<uint32_t> item_ids_to_delete;
for (const auto& it : this->items) {
if ((it.second->visibility_flags & remaining_clients_mask) == 0) {
item_ids_to_delete.emplace(it.first);
}
}
for (uint32_t item_id : item_ids_to_delete) {
this->remove(item_id, 0xFF);
}
this->log.info("Deleted %zu inaccessible items", item_ids_to_delete.size());
}
void Lobby::FloorItemManager::clear_private() {
unordered_set<uint32_t> item_ids_to_delete;
for (const auto& it : this->items) {
if ((it.second->visibility_flags & 0x00F) != 0x00F) {
item_ids_to_delete.emplace(it.first);
}
}
for (uint32_t item_id : item_ids_to_delete) {
this->remove(item_id, 0xFF);
}
this->log.info("Deleted %zu private items", item_ids_to_delete.size());
}
void Lobby::FloorItemManager::clear() {
size_t num_items = this->items.size();
this->items.clear();
for (auto& queue : this->queue_for_client) {
queue.clear();
}
this->next_drop_number = 0;
this->log.info("Deleted %zu items", num_items);
}
uint32_t Lobby::FloorItemManager::reassign_all_item_ids(uint32_t next_item_id) {
unordered_map<uint32_t, shared_ptr<FloorItem>> old_items;
old_items.swap(this->items);
for (auto& queue : this->queue_for_client) {
queue.clear();
}
for (auto& it : old_items) {
it.second->data.id = next_item_id++;
this->add(it.second);
}
return next_item_id;
}
Lobby::Lobby(shared_ptr<ServerState> s, uint32_t id)
: server_state(s),
log(string_printf("[Lobby:%" PRIX32 "] ", id), lobby_log.min_level),
lobby_id(id),
min_level(0),
max_level(0xFFFFFFFF),
next_game_item_id(0x00810000),
next_game_item_id(0xCC000000),
base_version(Version::GC_V3),
allowed_versions(0x0000),
section_id(0),
@@ -27,6 +151,7 @@ Lobby::Lobby(shared_ptr<ServerState> s, uint32_t id)
base_exp_multiplier(1),
challenge_exp_multiplier(1.0f),
random_seed(random_object<uint32_t>()),
drop_mode(DropMode::CLIENT),
event(0),
block(0),
leader_id(0),
@@ -38,7 +163,7 @@ Lobby::Lobby(shared_ptr<ServerState> s, uint32_t id)
event_free) {
this->log.info("Created");
for (size_t x = 0; x < 12; x++) {
this->next_item_id[x] = 0x00010000 + 0x00200000 * x;
this->next_item_id_for_client[x] = 0x00010000 + 0x00200000 * x;
}
}
@@ -61,6 +186,18 @@ shared_ptr<Lobby::ChallengeParameters> Lobby::require_challenge_params() const {
return this->challenge_params;
}
void Lobby::set_drop_mode(DropMode new_mode) {
this->drop_mode = new_mode;
bool should_have_item_creator = (this->base_version == Version::BB_V4) ||
((new_mode != DropMode::DISABLED) && (new_mode != DropMode::CLIENT));
if (should_have_item_creator && !this->item_creator) {
this->create_item_creator();
} else if (!should_have_item_creator && this->item_creator) {
this->item_creator.reset();
}
}
void Lobby::create_item_creator() {
auto s = this->require_server_state();
@@ -135,9 +272,6 @@ void Lobby::load_maps() {
dat_contents.size(),
this->random_seed,
this->rare_enemy_rates ? this->rare_enemy_rates : Map::NO_RARE_ENEMIES);
if (this->item_creator) {
this->item_creator->clear_destroyed_entities();
}
} else { // No quest loaded
for (size_t floor = 0; floor < 0x10; floor++) {
@@ -330,27 +464,25 @@ void Lobby::add_client(shared_ptr<Client> c, ssize_t required_client_id) {
this->leader_id = c->lobby_client_id;
}
// If the lobby is a game and item tracking is enabled, assign the inventory's
// item IDs. If there was no one else in the lobby, reset all the next item
// IDs also
if (this->is_game() && this->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) {
// If the lobby is a game and there was no one in it, reassign all the floor
// item IDs and reset the next item IDs
if (this->is_game()) {
if (leader_index >= this->max_clients) {
for (size_t x = 0; x < 12; x++) {
this->next_item_id[x] = 0x00010000 + 0x00200000 * x;
this->next_item_id_for_client[x] = 0x00010000 + 0x00200000 * x;
}
this->next_game_item_id = 0x00810000;
this->next_game_item_id = 0xCC000000;
// Reassign all floor item IDs so they won't conflict with any players'
// item IDs
unordered_map<uint32_t, FloorItem> new_item_id_to_floor_item;
for (const auto& it : this->item_id_to_floor_item) {
uint32_t new_item_id = this->generate_item_id(0xFF);
auto& new_fi = new_item_id_to_floor_item.emplace(new_item_id, it.second).first->second;
new_fi.data.id = new_item_id;
for (auto& m : this->floor_item_managers) {
this->next_game_item_id = m.reassign_all_item_ids(this->next_game_item_id);
}
this->item_id_to_floor_item = std::move(new_item_id_to_floor_item);
}
this->assign_inventory_and_bank_item_ids(c);
// We don't consume item IDs here because the 6F handler will do it for
// real; we just want to see what they would be when the join command is
// sent
this->assign_inventory_and_bank_item_ids(c, false);
}
// If the lobby is recording a battle record, add the player join event
@@ -425,12 +557,31 @@ void Lobby::remove_client(shared_ptr<Client> c) {
}
}
// If the lobby is persistent but has an idle timeout, make it expire after
// the specified time
if ((this->count_clients() == 0) &&
// If there are still players left in the lobby, delete all items that only
// the leaving player could see. Don't do this if no one is left in the lobby,
// since that would mean items could not persist in empty lobbies.
uint16_t remaining_clients_mask = 0;
for (size_t z = 0; z < 12; z++) {
if (this->clients[z]) {
remaining_clients_mask |= (1 << z);
}
}
if (remaining_clients_mask) {
for (auto& m : this->floor_item_managers) {
m.clear_inaccessible(remaining_clients_mask);
}
} else {
for (auto& m : this->floor_item_managers) {
m.clear_private();
}
}
if (!remaining_clients_mask &&
this->check_flag(Flag::PERSISTENT) &&
!this->check_flag(Flag::DEFAULT) &&
(this->idle_timeout_usecs > 0)) {
// If the lobby is persistent but has an idle timeout, make it expire after
// the specified time
auto tv = usecs_to_timeval(this->idle_timeout_usecs);
event_add(this->idle_timeout_event.get(), &tv);
this->log.info("Idle timeout scheduled");
@@ -492,35 +643,52 @@ uint8_t Lobby::game_event_for_lobby_event(uint8_t lobby_event) {
return lobby_event;
}
bool Lobby::item_exists(uint32_t item_id) const {
return this->item_id_to_floor_item.count(item_id);
}
const Lobby::FloorItem& Lobby::find_item(uint32_t item_id) const {
return this->item_id_to_floor_item.at(item_id);
}
void Lobby::add_item(const ItemData& data, uint8_t floor, float x, float z) {
auto& fi = this->item_id_to_floor_item[data.id];
fi.data = data;
fi.floor = floor;
fi.x = x;
fi.z = z;
}
ItemData Lobby::remove_item(uint32_t item_id) {
auto item_it = this->item_id_to_floor_item.find(item_id);
if (item_it == this->item_id_to_floor_item.end()) {
throw out_of_range("item not present");
bool Lobby::item_exists(uint8_t floor, uint32_t item_id) const {
if (floor >= this->floor_item_managers.size()) {
return false;
}
ItemData ret = item_it->second.data;
this->item_id_to_floor_item.erase(item_it);
return ret;
return this->floor_item_managers.at(floor).exists(item_id);
}
shared_ptr<Lobby::FloorItem> Lobby::find_item(uint8_t floor, uint32_t item_id) const {
return this->floor_item_managers.at(floor).find(item_id);
}
void Lobby::add_item(uint8_t floor, const ItemData& data, float x, float z, uint16_t visibility_flags) {
auto& m = this->floor_item_managers.at(floor);
m.add(data, x, z, visibility_flags);
this->evict_items_from_floor(floor);
}
void Lobby::add_item(uint8_t floor, shared_ptr<FloorItem> fi) {
auto& m = this->floor_item_managers.at(floor);
m.add(fi);
this->evict_items_from_floor(floor);
}
void Lobby::evict_items_from_floor(uint8_t floor) {
auto& m = this->floor_item_managers.at(floor);
auto evicted = m.evict();
if (!evicted.empty()) {
auto l = this->shared_from_this();
for (const auto& fi : evicted) {
for (size_t z = 0; z < 12; z++) {
auto lc = this->clients[z];
if (lc && fi->visible_to_client(z)) {
send_destroy_floor_item_to_client(lc, fi->data.id, floor);
}
}
}
}
}
shared_ptr<Lobby::FloorItem> Lobby::remove_item(uint8_t floor, uint32_t item_id, uint8_t requesting_client_id) {
return this->floor_item_managers.at(floor).remove(item_id, requesting_client_id);
}
uint32_t Lobby::generate_item_id(uint8_t client_id) {
if (client_id < this->max_clients) {
return this->next_item_id[client_id]++;
return this->next_item_id_for_client[client_id]++;
}
return this->next_game_item_id++;
}
@@ -531,16 +699,20 @@ void Lobby::on_item_id_generated_externally(uint32_t item_id) {
// the range further here.
if ((item_id > 0x00010000) && (item_id < 0x00810000)) {
uint16_t item_client_id = (item_id >> 21) & 0x7FF;
uint32_t& next_item_id = this->next_item_id.at(item_client_id);
uint32_t& next_item_id = this->next_item_id_for_client.at(item_client_id);
next_item_id = std::max<uint32_t>(next_item_id, item_id + 1);
}
}
void Lobby::assign_inventory_and_bank_item_ids(shared_ptr<Client> c) {
void Lobby::assign_inventory_and_bank_item_ids(shared_ptr<Client> c, bool consume_ids) {
auto p = c->character();
uint32_t start_item_id = this->next_item_id_for_client[c->lobby_client_id];
for (size_t z = 0; z < p->inventory.num_items; z++) {
p->inventory.items[z].data.id = this->generate_item_id(c->lobby_client_id);
}
if (!consume_ids) {
this->next_item_id_for_client[c->lobby_client_id] = start_item_id;
}
if (c->log.info("Assigned inventory item IDs")) {
p->print_inventory(stderr, c->version(), c->require_server_state()->item_name_index);
if (p->bank.num_items) {
@@ -649,3 +821,38 @@ bool Lobby::compare_shared(const shared_ptr<const Lobby>& a, const shared_ptr<co
return a->name < b->name;
}
template <>
Lobby::DropMode enum_for_name<Lobby::DropMode>(const char* name) {
if (!strcmp(name, "DISABLED")) {
return Lobby::DropMode::DISABLED;
} else if (!strcmp(name, "CLIENT")) {
return Lobby::DropMode::CLIENT;
} else if (!strcmp(name, "SERVER_SHARED")) {
return Lobby::DropMode::SERVER_SHARED;
} else if (!strcmp(name, "SERVER_PRIVATE")) {
return Lobby::DropMode::SERVER_PRIVATE;
} else if (!strcmp(name, "SERVER_DUPLICATE")) {
return Lobby::DropMode::SERVER_DUPLICATE;
} else {
throw runtime_error("invalid drop mode");
}
}
template <>
const char* name_for_enum<Lobby::DropMode>(Lobby::DropMode value) {
switch (value) {
case Lobby::DropMode::DISABLED:
return "DISABLED";
case Lobby::DropMode::CLIENT:
return "CLIENT";
case Lobby::DropMode::SERVER_SHARED:
return "SERVER_SHARED";
case Lobby::DropMode::SERVER_PRIVATE:
return "SERVER_PRIVATE";
case Lobby::DropMode::SERVER_DUPLICATE:
return "SERVER_DUPLICATE";
default:
throw runtime_error("invalid drop mode");
}
}
+63 -23
View File
@@ -24,6 +24,35 @@
struct ServerState;
struct Lobby : public std::enable_shared_from_this<Lobby> {
struct FloorItem {
ItemData data;
float x;
float z;
uint64_t drop_number;
uint16_t visibility_flags;
bool visible_to_client(uint8_t client_id) const;
};
struct FloorItemManager {
PrefixedLogger log;
uint64_t next_drop_number;
std::unordered_map<uint32_t, std::shared_ptr<FloorItem>> items; // Keyed on item_id
std::array<std::map<uint64_t, std::shared_ptr<FloorItem>>, 12> queue_for_client;
FloorItemManager(uint32_t lobby_id, uint8_t floor);
~FloorItemManager() = default;
bool exists(uint32_t item_id) const;
std::shared_ptr<FloorItem> find(uint32_t item_id) const;
void add(const ItemData& item, float x, float z, uint16_t visibility_flags);
void add(std::shared_ptr<FloorItem> fi);
std::shared_ptr<FloorItem> remove(uint32_t item_id, uint8_t client_id);
std::unordered_set<std::shared_ptr<FloorItem>> evict();
void clear_inaccessible(uint16_t remaining_clients_mask);
void clear_private();
void clear();
uint32_t reassign_all_item_ids(uint32_t next_item_id);
};
enum class Flag {
GAME = 0x00000001,
PERSISTENT = 0x00000002,
@@ -33,22 +62,26 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
QUEST_IN_PROGRESS = 0x00000200,
BATTLE_IN_PROGRESS = 0x00000400,
JOINABLE_QUEST_IN_PROGRESS = 0x00000800,
ITEM_TRACKING_ENABLED = 0x00001000,
IS_SPECTATOR_TEAM = 0x00002000, // episode must be EP3 also
SPECTATORS_FORBIDDEN = 0x00004000,
START_BATTLE_PLAYER_IMMEDIATELY = 0x00008000,
DROPS_ENABLED = 0x00020000,
CANNOT_CHANGE_DROPS_ENABLED = 0x00040000,
CANNOT_CHANGE_ITEM_TABLE = 0x00080000,
CANNOT_CHANGE_CHEAT_MODE = 0x00100000,
CANNOT_CHANGE_CHEAT_MODE = 0x00010000,
// Flags used only for lobbies
PUBLIC = 0x01000000,
DEFAULT = 0x02000000,
IS_OVERFLOW = 0x08000000,
};
enum class DropMode {
DISABLED = 0,
CLIENT = 1, // Not allowed for BB games
SERVER_SHARED = 2,
SERVER_PRIVATE = 3,
SERVER_DUPLICATE = 4,
};
std::weak_ptr<ServerState> server_state;
std::weak_ptr<ServerState>
server_state;
PrefixedLogger log;
uint32_t lobby_id;
@@ -56,18 +89,14 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
uint32_t min_level;
uint32_t max_level;
// Item info
struct FloorItem {
ItemData data;
float x;
float z;
uint8_t floor;
};
// Item state
std::array<uint32_t, 12> next_item_id_for_client;
uint32_t next_game_item_id;
std::vector<FloorItemManager> floor_item_managers;
// Map state
std::shared_ptr<const Map::RareEnemyRates> rare_enemy_rates;
std::shared_ptr<Map> map;
std::array<uint32_t, 12> next_item_id;
uint32_t next_game_item_id;
std::unordered_map<uint32_t, FloorItem> item_id_to_floor_item;
parray<le_uint32_t, 0x20> variations;
// Game config
@@ -87,6 +116,8 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
// This seed is also sent to the client for rare enemy generation
uint32_t random_seed;
std::shared_ptr<PSOLFGEncryption> random_crypt;
uint8_t allowed_drop_modes;
DropMode drop_mode;
std::shared_ptr<ItemCreator> item_creator;
struct ChallengeParameters {
@@ -102,8 +133,8 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
std::shared_ptr<ChallengeParameters> challenge_params;
// Ep3 stuff
// There are three kinds of Episode 3 games. All of these types have the flag
// EPISODE_3_ONLY; types 2 and 3 additionally have the IS_SPECTATOR_TEAM flag.
// There are three kinds of Episode 3 games. All of these types have episode
// set to EP3; types 2 and 3 additionally have the IS_SPECTATOR_TEAM flag.
// 1. Primary games. These are the lobbies where battles may take place.
// 2. Watcher games. These lobbies receive all the battle and chat commands
// from a primary game. (This the implementation of spectator teams.)
@@ -158,6 +189,7 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
std::shared_ptr<ServerState> require_server_state() const;
std::shared_ptr<ChallengeParameters> require_challenge_params() const;
void set_drop_mode(DropMode new_mode);
void create_item_creator();
void load_maps();
void create_ep3_server();
@@ -192,13 +224,16 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
const std::string* identifier = nullptr,
uint64_t serial_number = 0);
bool item_exists(uint32_t item_id) const;
const FloorItem& find_item(uint32_t item_id) const;
void add_item(const ItemData& item, uint8_t floor, float x, float z);
ItemData remove_item(uint32_t item_id);
bool item_exists(uint8_t floor, uint32_t item_id) const;
std::shared_ptr<FloorItem> find_item(uint8_t floor, uint32_t item_id) const;
void add_item(uint8_t floor, const ItemData& item, float x, float z, uint16_t visibility_flags);
void add_item(uint8_t floor, std::shared_ptr<FloorItem>);
void evict_items_from_floor(uint8_t floor);
std::shared_ptr<FloorItem> remove_item(uint8_t floor, uint32_t item_id, uint8_t requesting_client_id);
uint32_t generate_item_id(uint8_t client_id);
void on_item_id_generated_externally(uint32_t item_id);
void assign_inventory_and_bank_item_ids(std::shared_ptr<Client> c);
void assign_inventory_and_bank_item_ids(std::shared_ptr<Client> c, bool consume_ids);
QuestIndex::IncludeCondition quest_include_condition() const;
@@ -210,3 +245,8 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
static bool compare_shared(const std::shared_ptr<const Lobby>& a, const std::shared_ptr<const Lobby>& b);
};
template <>
Lobby::DropMode enum_for_name<Lobby::DropMode>(const char* name);
template <>
const char* name_for_enum<Lobby::DropMode>(Lobby::DropMode value);
+4 -4
View File
@@ -997,8 +997,8 @@ static HandlerResult S_6x(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
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(s, ses->server_channel, ses->next_drop_item, true, cmd.floor, cmd.x, cmd.z, cmd.entity_id);
send_drop_item(s, ses->client_channel, ses->next_drop_item, true, cmd.floor, cmd.x, cmd.z, cmd.entity_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;
@@ -1009,8 +1009,8 @@ static HandlerResult S_6x(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
} 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(s, ses->server_channel, ses->next_drop_item, false, cmd.floor, cmd.x, cmd.z, cmd.entity_id);
send_drop_item(s, ses->client_channel, ses->next_drop_item, false, cmd.floor, cmd.x, cmd.z, cmd.entity_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;
+52 -24
View File
@@ -346,8 +346,8 @@ static void on_1D(shared_ptr<Client> c, uint16_t, uint32_t, string&) {
if (c->config.check_flag(Client::Flag::SHOULD_SEND_ARTIFICIAL_ITEM_STATE)) {
c->config.clear_flag(Client::Flag::SHOULD_SEND_ARTIFICIAL_ITEM_STATE);
auto l = c->require_lobby();
if (!is_ep3(c->version()) && l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) {
send_artificial_item_state(c);
if (!is_ep3(c->version())) {
send_game_item_state(c);
}
}
}
@@ -1861,6 +1861,9 @@ static void on_quest_loaded(shared_ptr<Lobby> l) {
if ((l->base_version == Version::BB_V4) && l->map && (l->quest->challenge_template_index < 0)) {
l->load_maps();
}
for (auto& m : l->floor_item_managers) {
m.clear();
}
for (auto& lc : l->clients) {
if (!lc) {
@@ -1885,7 +1888,7 @@ static void on_quest_loaded(shared_ptr<Lobby> l) {
lc->use_default_bank();
lc->create_challenge_overlay(lc->version(), l->quest->challenge_template_index, s->level_table);
lc->log.info("Created challenge overlay");
l->assign_inventory_and_bank_item_ids(lc);
l->assign_inventory_and_bank_item_ids(lc, true);
}
}
}
@@ -3461,7 +3464,7 @@ static void on_DF_BB(shared_ptr<Client> c, uint16_t command, uint32_t, string& d
lc->use_default_bank();
lc->create_challenge_overlay(lc->version(), l->quest->challenge_template_index, s->level_table);
lc->log.info("Created challenge overlay");
l->assign_inventory_and_bank_item_ids(lc);
l->assign_inventory_and_bank_item_ids(lc, true);
}
}
@@ -3905,23 +3908,13 @@ shared_ptr<Lobby> create_game_generic(
throw logic_error("invalid quest script version");
}
if ((game->base_version == Version::BB_V4) || s->item_tracking_enabled) {
game->set_flag(Lobby::Flag::ITEM_TRACKING_ENABLED);
}
// Only disable drops if the config flag is set and for regular multi-mode;
// drops are still enabled for battle and challenge modes
if (s->behavior_enabled(s->enable_drops_behavior) || (mode != GameMode::NORMAL)) {
game->set_flag(Lobby::Flag::DROPS_ENABLED);
while (game->floor_item_managers.size() < 0x12) {
game->floor_item_managers.emplace_back(game->lobby_id, game->floor_item_managers.size());
}
if (s->behavior_enabled(s->cheat_mode_behavior)) {
game->set_flag(Lobby::Flag::CHEATS_ENABLED);
}
if (!s->behavior_can_be_overridden(s->enable_drops_behavior)) {
game->set_flag(Lobby::Flag::CANNOT_CHANGE_DROPS_ENABLED);
}
if (!game->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED) || !s->behavior_can_be_overridden(s->use_server_item_tables_behavior)) {
game->set_flag(Lobby::Flag::CANNOT_CHANGE_ITEM_TABLE);
}
if (!s->behavior_can_be_overridden(s->cheat_mode_behavior)) {
game->set_flag(Lobby::Flag::CANNOT_CHANGE_CHEAT_MODE);
}
@@ -3951,13 +3944,44 @@ shared_ptr<Lobby> create_game_generic(
game->base_exp_multiplier = s->bb_global_exp_multiplier;
}
for (size_t x = 0; x < 4; x++) {
game->next_item_id[x] = (0x00200000 * x) + 0x00010000;
switch (game->base_version) {
case Version::DC_NTE:
case Version::DC_V1_11_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
case Version::PC_V2:
game->set_drop_mode(s->default_drop_mode_v1_v2);
game->allowed_drop_modes = s->allowed_drop_modes_v1_v2;
break;
case Version::GC_NTE:
case Version::GC_V3:
case Version::XB_V3:
game->set_drop_mode(s->default_drop_mode_v3);
game->allowed_drop_modes = s->allowed_drop_modes_v3;
break;
case Version::GC_EP3_TRIAL_EDITION:
case Version::GC_EP3:
game->set_drop_mode(Lobby::DropMode::DISABLED);
game->allowed_drop_modes = (1 << static_cast<size_t>(game->drop_mode));
break;
case Version::BB_V4:
// Disallow CLIENT mode on BB
if (s->default_drop_mode_v4 == Lobby::DropMode::CLIENT) {
// If the default is CLIENT (somehow), force it to be SERVER_SHARED
game->set_drop_mode(Lobby::DropMode::SERVER_SHARED);
game->allowed_drop_modes |= (1 << static_cast<size_t>(game->drop_mode));
} else {
game->set_drop_mode(s->default_drop_mode_v4);
game->allowed_drop_modes = s->allowed_drop_modes_v4 & ~(1 << static_cast<size_t>(Lobby::DropMode::CLIENT));
}
break;
default:
throw logic_error("invalid quest script version");
}
game->next_game_item_id = 0x00810000;
if ((game->base_version == Version::BB_V4) ||
(game->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED) && s->behavior_enabled(s->use_server_item_tables_behavior))) {
game->create_item_creator();
// Only allow DISABLED in Normal; use the default in Battle/Challenge/Solo
if ((game->drop_mode == Lobby::DropMode::DISABLED) && (mode != GameMode::NORMAL)) {
game->set_drop_mode((game->base_version == Version::BB_V4) ? Lobby::DropMode::SERVER_SHARED : Lobby::DropMode::CLIENT);
game->allowed_drop_modes |= (1 << static_cast<size_t>(game->drop_mode));
}
game->event = Lobby::game_event_for_lobby_event(current_lobby->event);
@@ -4149,6 +4173,10 @@ static void on_6F(shared_ptr<Client> c, uint16_t command, uint32_t, string& data
}
c->config.clear_flag(Client::Flag::LOADING);
if (command == 0x006F) {
l->assign_inventory_and_bank_item_ids(c, true);
}
send_server_time(c);
if (l->base_version == Version::BB_V4) {
send_set_exp_multiplier(l);
@@ -4333,7 +4361,7 @@ static void on_D2_V3_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data)
}
to_p->add_item(trade_item);
send_create_inventory_item(to_c, item);
send_create_inventory_item_to_lobby(to_c, to_c->lobby_client_id, item);
}
send_command(to_c, 0xD3, 0x00);
+340 -445
View File
File diff suppressed because it is too large Load Diff
+63 -45
View File
@@ -2304,43 +2304,39 @@ void send_set_player_visibility(shared_ptr<Lobby> l, shared_ptr<Client> c, bool
send_command_t(l, 0x60, 0x00, cmd);
}
void send_artificial_item_state(shared_ptr<Client> c) {
void send_game_item_state(shared_ptr<Client> c) {
auto l = c->require_lobby();
if (c->lobby_client_id != l->leader_id) {
throw runtime_error("artificial item state can only be sent to the leader");
}
if (l->count_clients() != 1) {
throw runtime_error("artificial item state can only be sent with no one else in the game");
}
array<StringWriter, 15> floor_ws;
auto s = c->require_server_state();
StringWriter floor_items_w;
G_SyncItemState_6x6D_Decompressed decompressed_header;
for (size_t z = 0; z < 12; z++) {
decompressed_header.next_item_id_per_player[z] = l->next_item_id[z];
decompressed_header.next_item_id_per_player[z] = l->next_item_id_for_client[z];
}
for (const auto& it : l->item_id_to_floor_item) {
const auto& item = it.second;
for (size_t floor = 0; floor < 0x10; floor++) {
const auto& m = l->floor_item_managers.at(floor);
for (const auto& it : m.queue_for_client.at(c->lobby_client_id)) {
const auto& item = it.second;
FloorItem fi;
fi.floor = item.floor;
fi.from_enemy = 0;
fi.entity_id = 0xFFFF;
fi.x = item.x;
fi.z = item.z;
fi.unknown_a2 = 0;
fi.drop_number = decompressed_header.total_items_dropped_per_floor.at(item.floor)++;
fi.item = item.data;
floor_ws.at(item.floor).put(fi);
FloorItem fi;
fi.floor = floor;
fi.from_enemy = 0;
fi.entity_id = 0xFFFF;
fi.x = item->x;
fi.z = item->z;
fi.unknown_a2 = 0;
fi.drop_number = (floor == 0) ? 0xFFFF : (decompressed_header.next_drop_number_per_floor.at(floor - 1)++);
fi.item = item->data;
fi.item.encode_for_version(c->version(), s->item_parameter_table_for_version(c->version()));
floor_items_w.put(fi);
decompressed_header.floor_item_count_per_floor.at(item.floor)++;
decompressed_header.floor_item_count_per_floor.at(floor)++;
}
}
StringWriter decompressed_w;
decompressed_w.put(decompressed_header);
for (const auto& floor_w : floor_ws) {
decompressed_w.write(floor_w.str());
}
decompressed_w.write(floor_items_w.str());
string compressed_data = bc0_compress(decompressed_w.str());
@@ -2358,10 +2354,19 @@ void send_artificial_item_state(shared_ptr<Client> c) {
while (w.size() & 3) {
w.put_u8(0x00);
}
send_command(c, 0x6D, c->lobby_client_id, w.str());
if (c->game_join_command_queue) {
c->log.info("Client not ready to receive join commands; adding to queue");
auto& cmd = c->game_join_command_queue->emplace_back();
cmd.command = 0x6D;
cmd.flag = c->lobby_client_id;
cmd.data = std::move(w.str());
} else {
send_command(c, 0x6D, c->lobby_client_id, w.str());
}
}
void send_drop_item(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) {
G_DropItem_PC_V3_BB_6x5F cmd = {
{{0x5F, 0x0B, 0x0000}, {floor, from_enemy, entity_id, x, z, 0, 0, item}}, 0};
@@ -2369,55 +2374,63 @@ void send_drop_item(shared_ptr<ServerState> s, Channel& ch, const ItemData& item
ch.send(0x60, 0x00, &cmd, sizeof(cmd));
}
void send_drop_item(shared_ptr<Lobby> l, const ItemData& item,
void send_drop_item_to_lobby(shared_ptr<Lobby> l, const ItemData& item,
bool from_enemy, uint8_t floor, float x, float z, uint16_t entity_id) {
auto s = l->require_server_state();
for (auto& c : l->clients) {
if (!c) {
continue;
}
send_drop_item(s, c->channel, item, from_enemy, floor, x, z, entity_id);
send_drop_item_to_channel(s, c->channel, item, from_enemy, floor, x, z, entity_id);
}
}
void send_drop_stacked_item(shared_ptr<ServerState> s, Channel& ch, const ItemData& item, uint8_t floor, float x, float z) {
void send_drop_stacked_item_to_channel(
shared_ptr<ServerState> s, Channel& ch, const ItemData& item, uint8_t floor, float x, float z) {
G_DropStackedItem_PC_V3_BB_6x5D cmd = {{{0x5D, 0x0A, 0x0000}, floor, 0, x, z, item}, 0};
cmd.item_data.encode_for_version(ch.version, s->item_parameter_table_for_version(ch.version));
ch.send(0x60, 0x00, &cmd, sizeof(cmd));
}
void send_drop_stacked_item(shared_ptr<Lobby> l, const ItemData& item, uint8_t floor, float x, float z) {
void send_drop_stacked_item_to_lobby(shared_ptr<Lobby> l, const ItemData& item, uint8_t floor, float x, float z) {
auto s = l->require_server_state();
for (auto& c : l->clients) {
if (!c) {
continue;
}
send_drop_stacked_item(s, c->channel, item, floor, x, z);
send_drop_stacked_item_to_channel(s, c->channel, item, floor, x, z);
}
}
void send_pick_up_item(shared_ptr<Client> c, uint32_t item_id, uint8_t floor) {
auto l = c->require_lobby();
uint16_t client_id = c->lobby_client_id;
void send_pick_up_item_to_client(shared_ptr<Client> c, uint8_t client_id, uint32_t item_id, uint8_t floor) {
G_PickUpItem_6x59 cmd = {{0x59, 0x03, client_id}, client_id, floor, item_id};
send_command_t(l, 0x60, 0x00, cmd);
send_command_t(c, 0x60, 0x00, cmd);
}
void send_create_inventory_item(shared_ptr<Client> c, const ItemData& item, bool exclude_c) {
auto l = c->require_lobby();
void send_create_inventory_item_to_client(shared_ptr<Client> c, uint8_t client_id, const ItemData& item) {
if (c->version() != Version::BB_V4) {
throw logic_error("6xBE can only be sent to BB clients");
}
uint16_t client_id = c->lobby_client_id;
G_CreateInventoryItem_BB_6xBE cmd = {{0xBE, 0x07, client_id}, item, 0};
if (exclude_c) {
send_command_excluding_client(l, c, 0x60, 0x00, &cmd, sizeof(cmd));
} else {
send_command_t(l, 0x60, 0x00, cmd);
send_command_t(c, 0x60, 0x00, cmd);
}
void send_create_inventory_item_to_lobby(shared_ptr<Client> c, uint8_t client_id, const ItemData& item, bool exclude_c) {
auto l = c->require_lobby();
for (const auto& lc : l->clients) {
if (!lc) {
continue;
}
if (lc->version() != Version::BB_V4) {
throw logic_error("6xBE can only be sent to BB clients");
}
if ((lc != c) || !exclude_c) {
send_create_inventory_item_to_client(lc, client_id, item);
}
}
}
void send_destroy_item(shared_ptr<Client> c, uint32_t item_id, uint32_t amount, bool exclude_c) {
void send_destroy_item_to_lobby(shared_ptr<Client> c, uint32_t item_id, uint32_t amount, bool exclude_c) {
auto l = c->require_lobby();
uint16_t client_id = c->lobby_client_id;
G_DeleteInventoryItem_6x29 cmd = {{0x29, 0x03, client_id}, item_id, amount};
@@ -2428,6 +2441,11 @@ void send_destroy_item(shared_ptr<Client> c, uint32_t item_id, uint32_t amount,
}
}
void send_destroy_floor_item_to_client(shared_ptr<Client> c, uint32_t item_id, uint32_t floor) {
G_DestroyFloorItem_6x63 cmd = {{0x63, 0x03, 0x0000}, item_id, floor};
send_command_t(c, 0x60, 0x00, cmd);
}
void send_item_identify_result(shared_ptr<Client> c) {
auto l = c->require_lobby();
if (c->version() != Version::BB_V4) {
+12 -8
View File
@@ -298,16 +298,20 @@ void send_ep3_change_music(Channel& ch, uint32_t song);
void send_set_player_visibility(std::shared_ptr<Client> c, bool visible);
void send_revive_player(std::shared_ptr<Client> c);
void send_artificial_item_state(std::shared_ptr<Client> c);
void send_drop_item(std::shared_ptr<ServerState> s, Channel& ch, const ItemData& item,
void send_game_item_state(std::shared_ptr<Client> c);
void send_drop_item_to_channel(std::shared_ptr<ServerState> s, Channel& ch, const ItemData& item,
bool from_enemy, uint8_t floor, float x, float z, uint16_t request_id);
void send_drop_item(std::shared_ptr<Lobby> l, const ItemData& item,
void send_drop_item_to_lobby(std::shared_ptr<Lobby> l, const ItemData& item,
bool from_enemy, uint8_t floor, float x, float z, uint16_t request_id);
void send_drop_stacked_item(std::shared_ptr<ServerState> s, Channel& ch, const ItemData& item, uint8_t floor, float x, float z);
void send_drop_stacked_item(std::shared_ptr<Lobby> l, const ItemData& item, uint8_t floor, float x, float z);
void send_pick_up_item(std::shared_ptr<Client> c, uint32_t id, uint8_t floor);
void send_create_inventory_item(std::shared_ptr<Client> c, const ItemData& item, bool exclude_c = false);
void send_destroy_item(std::shared_ptr<Client> c, uint32_t item_id, uint32_t amount, bool exclude_c = false);
void send_drop_stacked_item_to_channel(
std::shared_ptr<ServerState> s, Channel& ch, const ItemData& item, uint8_t floor, float x, float z);
void send_drop_stacked_item_to_lobby(
std::shared_ptr<Lobby> l, const ItemData& item, uint8_t floor, float x, float z);
void send_pick_up_item_to_client(std::shared_ptr<Client> c, uint8_t client_id, uint32_t id, uint8_t floor);
void send_create_inventory_item_to_client(std::shared_ptr<Client> c, uint8_t client_id, const ItemData& item);
void send_create_inventory_item_to_lobby(std::shared_ptr<Client> c, uint8_t client_id, const ItemData& item, bool exclude_c = false);
void send_destroy_item_to_lobby(std::shared_ptr<Client> c, uint32_t item_id, uint32_t amount, bool exclude_c = false);
void send_destroy_floor_item_to_client(std::shared_ptr<Client> c, uint32_t item_id, uint32_t floor);
void send_item_identify_result(std::shared_ptr<Client> c);
void send_bank(std::shared_ptr<Client> c);
void send_shop(std::shared_ptr<Client> c, uint8_t shop_type);
+2 -2
View File
@@ -816,8 +816,8 @@ Proxy session commands:\n\
send_text_message(ses->client_channel, "$C7Next drop:\n" + name);
} else {
send_drop_stacked_item(s, ses->client_channel, item, ses->floor, ses->x, ses->z);
send_drop_stacked_item(s, ses->server_channel, item, ses->floor, ses->x, ses->z);
send_drop_stacked_item_to_channel(s, ses->client_channel, item, ses->floor, ses->x, ses->z);
send_drop_stacked_item_to_channel(s, ses->server_channel, item, ses->floor, ses->x, ses->z);
string name = s->describe_item(ses->version(), ses->next_drop_item, true);
send_text_message(ses->client_channel, "$C7Item created:\n" + name);
+18 -6
View File
@@ -26,9 +26,12 @@ ServerState::ServerState(shared_ptr<struct event_base> base, const string& confi
allow_unregistered_users(false),
allow_dc_pc_games(false),
allow_gc_xb_games(true),
item_tracking_enabled(true),
enable_drops_behavior(BehaviorSwitch::ON_BY_DEFAULT),
use_server_item_tables_behavior(BehaviorSwitch::OFF_BY_DEFAULT),
allowed_drop_modes_v1_v2(0xFF),
allowed_drop_modes_v3(0xFF),
allowed_drop_modes_v4(0xFD), // CLIENT not allowed
default_drop_mode_v1_v2(Lobby::DropMode::CLIENT),
default_drop_mode_v3(Lobby::DropMode::CLIENT),
default_drop_mode_v4(Lobby::DropMode::SERVER_SHARED),
persistent_game_idle_timeout_usecs(0),
ep3_send_function_call_enabled(false),
catch_handler_exceptions(true),
@@ -628,9 +631,18 @@ void ServerState::parse_config(const JSON& json, bool is_reload) {
this->ip_stack_debug = json.get_bool("IPStackDebug", this->ip_stack_debug);
this->allow_unregistered_users = json.get_bool("AllowUnregisteredUsers", this->allow_unregistered_users);
this->item_tracking_enabled = json.get_bool("EnableItemTracking", this->item_tracking_enabled);
this->enable_drops_behavior = parse_behavior_switch("ItemDropMode", this->enable_drops_behavior);
this->use_server_item_tables_behavior = parse_behavior_switch("UseServerItemTables", this->use_server_item_tables_behavior);
this->allowed_drop_modes_v1_v2 = json.get_int("AllowedDropModesV1V2", this->allowed_drop_modes_v1_v2);
this->allowed_drop_modes_v3 = json.get_int("AllowedDropModesV3", this->allowed_drop_modes_v3);
this->allowed_drop_modes_v4 = json.get_int("AllowedDropModesV4", this->allowed_drop_modes_v4);
this->default_drop_mode_v1_v2 = json.get_enum("DefaultDropModeV1V2", this->default_drop_mode_v1_v2);
this->default_drop_mode_v3 = json.get_enum("DefaultDropModeV3", this->default_drop_mode_v3);
this->default_drop_mode_v4 = json.get_enum("DefaultDropModeV4", this->default_drop_mode_v4);
if (this->default_drop_mode_v4 == Lobby::DropMode::CLIENT) {
throw runtime_error("default V4 drop mode cannot be CLIENT");
}
if (this->allowed_drop_modes_v4 & (1 << static_cast<size_t>(Lobby::DropMode::CLIENT))) {
throw runtime_error("CLIENT drop mode cannot be allowed in V4");
}
this->persistent_game_idle_timeout_usecs = json.get_int("PersistentGameIdleTimeout", this->persistent_game_idle_timeout_usecs);
this->cheat_mode_behavior = parse_behavior_switch("CheatModeBehavior", this->cheat_mode_behavior);
this->ep3_send_function_call_enabled = json.get_bool("EnableEpisode3SendFunctionCall", this->ep3_send_function_call_enabled);
+6 -3
View File
@@ -75,9 +75,12 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
bool allow_unregistered_users;
bool allow_dc_pc_games;
bool allow_gc_xb_games;
bool item_tracking_enabled;
BehaviorSwitch enable_drops_behavior;
BehaviorSwitch use_server_item_tables_behavior;
uint8_t allowed_drop_modes_v1_v2;
uint8_t allowed_drop_modes_v3;
uint8_t allowed_drop_modes_v4;
Lobby::DropMode default_drop_mode_v1_v2;
Lobby::DropMode default_drop_mode_v3;
Lobby::DropMode default_drop_mode_v4;
uint64_t persistent_game_idle_timeout_usecs;
bool ep3_send_function_call_enabled;
bool catch_handler_exceptions;