diff --git a/README.md b/README.md index d395d94e..35ee770d 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ See TODO.md for a list of known issues and future work I've curated, or go to th * Setup * [Configuration](#configuration) * [Installing quests](#installing-quests) + * [Item tables and drop modes](#item-tables-and-drop-modes) * [Episode 3 features](#episode-3-features) * [Client patch directories for PC and BB](#client-patch-directories) * [Memory patches and DOL files for GC](#memory-patches-and-dol-files) @@ -137,6 +138,23 @@ Quest contents are cached in memory, but if you've changed the contents of the q All quests, including those originally in GCI or DLQ format, are treated as online quests unless their filenames specify the dl category. newserv allows players to download all quests, even those in non-download categories. +### Item tables and drop modes + +newserv supports server-side item generation on all game versions, except for the earliest DC prototypes (NTE and 11/2000). By default, the game behaves as it did on the original servers - on all versions except BB, item drops are controlled by the leader client in each game, and on BB, item drops are controlled by the server. + +There are five different available behaviors for item drops: +* `DISABLED` (or `NONE`): No items will drop from boxes or enemies. +* `CLIENT`: The game leader generates items, all items are visible to all players, and any player may pick up any item. This is the default mode for all game versions, except this mode cannot be used on BB. +* `SERVER_SHARED`: The server generates items, all items are visible to all players, and any player may pick up any item. This is the default mode for BB. +* `SERVER_PRIVATE`: The server generates items, but each player may get a different item from any box or enemy. If a player isn't in the same area as an enemy at the time it's defeated, they won't get any item from it. Items dropped by players are visible to everyone. +* `SERVER_DUPLICATE`: The server generates items, and each player will get the same item from any box or enemy, but there is one copy of each item for each player (and each player only sees their own copy of the item). If a player isn't in the same area as an enemy at the time it's defeated, they won't get any item from it. Items dropped by players are not duplicated and are visible to everyone. + +In the `SERVER_PRIVATE` and `SERVER_DUPLICATE` modes, there is no incentive to pick up items before another player, since other players cannot pick up the items you see dropped from boxes and enemies. However, if you pick up an item and drop it later, it can then be seen and picked up by any player. + +The drop mode can be changed at any time during a game with the `$dropmode` chat command. If the mode is changed after some items have already been dropped, the existing items retain their visibility (that is, they still can't be picked up by other players since they were dropped before the mode was changed). You can configure which drop modes are used by default, and which modes players are allowed to choose, in config.json. See the comments above the AllowedDropModes and DefaultDropMode keys. + +In the server drop modes, the item tables used to generate common items are in the `system/item-tables/ItemPT-*` files. (The V2 files are used for V1 as well.) The rare item tables are in the `rare-table-*.json` files. Unlike the original formats, it's possible to make each enemy drop multiple different rare items at different rates, though the default tables never do this. + ### Episode 3 features newserv supports many features unique to Episode 3: @@ -292,8 +310,7 @@ Some commands only work on the game server and not on the proxy server. The chat * `$maxlevel `: Sets the maximum level for players to join the current game. (This only applies when joining; if a player joins and then levels up past this level during the game, they are not kicked out, but won't be able to rejoin if they leave.) * `$minlevel `: Sets the minimum level for players to join the current game. * `$password `: Sets the game's join password. To unlock the game, run `$password` with nothing after it. - * `$itemtable`: Switches between using the client's or the server's drop table. No effect on BB (the server's drop table is always used). The server's rare tables are defined in JSON files in the system/item-tables directory. - * `$drop`: Enables or disables all item drops from boxes and enemies in the current game. + * `$dropmode [mode]`: Changes the way item drops behave in the current game. `mode` can be `none`, `client`, `shared`, `private`, or `duplicate`. If `mode` is not given, tells you the current drop mode without changing it. See the "Item tables and drop modes" section for more information. * Episode 3 commands (game server only) * `$spec`: Toggles the allow spectators flag for Episode 3 games. If any players are spectating when this flag is disabled, they will be sent back to the lobby. diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index c133a6d0..a2ed629d 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -108,14 +108,24 @@ static void server_command_lobby_info(shared_ptr 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 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 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 s ses->config.check_flag(Client::Flag::SWITCH_ASSIST_ENABLED) ? "enabled" : "disabled"); } -static void server_command_drop(shared_ptr c, const std::string&) { +static void server_command_dropmode(shared_ptr 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 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(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 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 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 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 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}}, diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index a10167fb..397be474 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -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 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 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 next_item_id_per_player; diff --git a/src/ItemCreator.cc b/src/ItemCreator.cc index 55fd2d6b..49a5e723 100644 --- a/src/ItemCreator.cc +++ b/src/ItemCreator.cc @@ -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(); } diff --git a/src/ItemCreator.hh b/src/ItemCreator.hh index 046399aa..3d672b4d 100644 --- a/src/ItemCreator.hh +++ b/src/ItemCreator.hh @@ -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 generate_armor_shop_contents(size_t player_level); diff --git a/src/Items.cc b/src/Items.cc index 7b63fa39..a4c93a7a 100644 --- a/src/Items.cc +++ b/src/Items.cc @@ -192,9 +192,9 @@ void player_use_item(shared_ptr 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; } diff --git a/src/Lobby.cc b/src/Lobby.cc index 5d66ada9..71e06137 100644 --- a/src/Lobby.cc +++ b/src/Lobby.cc @@ -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::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(); + 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 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::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> Lobby::FloorItemManager::evict() { + unordered_set> 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 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 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> 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 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 s, uint32_t id) base_exp_multiplier(1), challenge_exp_multiplier(1.0f), random_seed(random_object()), + drop_mode(DropMode::CLIENT), event(0), block(0), leader_id(0), @@ -38,7 +163,7 @@ Lobby::Lobby(shared_ptr 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::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 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 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 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::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 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::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(next_item_id, item_id + 1); } } -void Lobby::assign_inventory_and_bank_item_ids(shared_ptr c) { +void Lobby::assign_inventory_and_bank_item_ids(shared_ptr 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& a, const shared_ptrname < b->name; } + +template <> +Lobby::DropMode enum_for_name(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 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"); + } +} diff --git a/src/Lobby.hh b/src/Lobby.hh index a69664e1..3377bd97 100644 --- a/src/Lobby.hh +++ b/src/Lobby.hh @@ -24,6 +24,35 @@ struct ServerState; struct Lobby : public std::enable_shared_from_this { + 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> items; // Keyed on item_id + std::array>, 12> queue_for_client; + + FloorItemManager(uint32_t lobby_id, uint8_t floor); + ~FloorItemManager() = default; + + bool exists(uint32_t item_id) const; + std::shared_ptr find(uint32_t item_id) const; + void add(const ItemData& item, float x, float z, uint16_t visibility_flags); + void add(std::shared_ptr fi); + std::shared_ptr remove(uint32_t item_id, uint8_t client_id); + std::unordered_set> 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 { 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 server_state; + std::weak_ptr + server_state; PrefixedLogger log; uint32_t lobby_id; @@ -56,18 +89,14 @@ struct Lobby : public std::enable_shared_from_this { 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 next_item_id_for_client; + uint32_t next_game_item_id; + std::vector floor_item_managers; + + // Map state std::shared_ptr rare_enemy_rates; std::shared_ptr map; - std::array next_item_id; - uint32_t next_game_item_id; - std::unordered_map item_id_to_floor_item; parray variations; // Game config @@ -87,6 +116,8 @@ struct Lobby : public std::enable_shared_from_this { // This seed is also sent to the client for rare enemy generation uint32_t random_seed; std::shared_ptr random_crypt; + uint8_t allowed_drop_modes; + DropMode drop_mode; std::shared_ptr item_creator; struct ChallengeParameters { @@ -102,8 +133,8 @@ struct Lobby : public std::enable_shared_from_this { std::shared_ptr 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 { std::shared_ptr require_server_state() const; std::shared_ptr 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 { 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 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); + void evict_items_from_floor(uint8_t floor); + std::shared_ptr 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 c); + void assign_inventory_and_bank_item_ids(std::shared_ptr c, bool consume_ids); QuestIndex::IncludeCondition quest_include_condition() const; @@ -210,3 +245,8 @@ struct Lobby : public std::enable_shared_from_this { static bool compare_shared(const std::shared_ptr& a, const std::shared_ptr& b); }; + +template <> +Lobby::DropMode enum_for_name(const char* name); +template <> +const char* name_for_enum(Lobby::DropMode value); diff --git a/src/ProxyCommands.cc b/src/ProxyCommands.cc index 29c3c31f..ffbd23b6 100644 --- a/src/ProxyCommands.cc +++ b/src/ProxyCommands.cc @@ -997,8 +997,8 @@ static HandlerResult S_6x(shared_ptr ses, uint16_t, const auto& cmd = check_size_t( 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 ses, uint16_t, } else if ((static_cast(data[0]) == 0xA2) && ses->next_drop_item.data1d[0] && !is_v4(ses->version())) { const auto& cmd = check_size_t(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; diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index e34b361e..328811c0 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -346,8 +346,8 @@ static void on_1D(shared_ptr 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 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 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 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 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 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(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(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(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(game->drop_mode)); } game->event = Lobby::game_event_for_lobby_event(current_lobby->event); @@ -4149,6 +4173,10 @@ static void on_6F(shared_ptr 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 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); diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index c92d7d05..3e423c05 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -208,18 +208,6 @@ static void send_or_enqueue_joining_player_command(shared_ptr c, uint8_t } } -static void send_or_enqueue_joining_player_command(shared_ptr c, uint8_t command, uint8_t flag, string&& data) { - 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 = command; - cmd.flag = flag; - cmd.data = std::move(data); - } else { - send_command(c, command, flag, data.data(), data.size()); - } -} - static void send_or_enqueue_joining_player_command(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { if (c->game_join_command_queue) { c->log.info("Client not ready to receive join commands; adding to queue"); @@ -321,53 +309,22 @@ static void on_sync_joining_player_item_state(shared_ptr c, uint8_t comm "decompressed 6x6D data (0x%zX bytes) is too short for all floor items (0x%zX bytes)", decompressed.size(), required_size)); } - auto* floor_items = reinterpret_cast(decompressed.data() + sizeof(G_SyncItemState_6x6D_Decompressed)); size_t target_num_items = target->character()->inventory.num_items; for (size_t z = 0; z < 12; z++) { uint32_t client_next_id = decompressed_cmd->next_item_id_per_player[z]; - uint32_t server_next_id = l->next_item_id[z]; + uint32_t server_next_id = l->next_item_id_for_client[z]; if (client_next_id == server_next_id) { - l->log.info("Next item ID for player %zu (%08" PRIX32 ") matches expected value", z, l->next_item_id[z]); + l->log.info("Next item ID for player %zu (%08" PRIX32 ") matches expected value", z, l->next_item_id_for_client[z]); } else if ((z == target->lobby_client_id) && (client_next_id == server_next_id - target_num_items)) { - l->log.info("Next item ID for player %zu (%08" PRIX32 ") matches expected value before inventory item ID assignment (%08" PRIX32 ")", z, l->next_item_id[z], static_cast(server_next_id - target_num_items)); + l->log.info("Next item ID for player %zu (%08" PRIX32 ") matches expected value before inventory item ID assignment (%08" PRIX32 ")", z, l->next_item_id_for_client[z], static_cast(server_next_id - target_num_items)); } else { l->log.warning("Next item ID for player %zu (%08" PRIX32 ") does not match expected value (%08" PRIX32 ")", - z, decompressed_cmd->next_item_id_per_player[z].load(), l->next_item_id[z]); + z, decompressed_cmd->next_item_id_per_player[z].load(), l->next_item_id_for_client[z]); } } - // We need to byteswap mags' data2 fields if exactly one of the sender and - // recipient is PSO GC - if (is_big_endian(c->version()) == is_big_endian(target->version())) { - send_or_enqueue_joining_player_command(target, command, flag, data, size); - - } else { - auto s = target->require_server_state(); - for (size_t z = 0; z < num_floor_items; z++) { - floor_items[z].item.decode_for_version(c->version()); - floor_items[z].item.encode_for_version(target->version(), s->item_parameter_table_for_version(target->version())); - } - - string out_compressed_data = bc0_compress(decompressed); - - G_SyncGameStateHeader_6x6B_6x6C_6x6D_6x6E out_cmd; - out_cmd.header.basic_header.subcommand = 0x6D; - out_cmd.header.basic_header.size = 0x00; - out_cmd.header.basic_header.unused = 0x0000; - out_cmd.header.size = ((out_compressed_data.size() + sizeof(G_SyncGameStateHeader_6x6B_6x6C_6x6D_6x6E)) + 3) & (~3); - out_cmd.decompressed_size = decompressed.size(); - out_cmd.compressed_size = out_compressed_data.size(); - - if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) { - c->log.info("Transcoded and recompressed item sync data (%zX bytes)", out_compressed_data.size()); - } - - StringWriter w; - w.write(&out_cmd, sizeof(out_cmd)); - w.write(out_compressed_data); - send_or_enqueue_joining_player_command(target, command, flag, std::move(w.str())); - } + send_game_item_state(target); } static void on_sync_joining_player_disp_and_inventory( @@ -719,14 +676,12 @@ static void on_player_died(shared_ptr c, uint8_t command, uint8_t flag, return; } - if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { - try { - auto& inventory = c->character()->inventory; - size_t mag_index = inventory.find_equipped_item(EquipSlot::MAG); - auto& data = inventory.items[mag_index].data; - data.data2[0] = max(static_cast(data.data2[0] - 5), 0); - } catch (const out_of_range&) { - } + try { + auto& inventory = c->character()->inventory; + size_t mag_index = inventory.find_equipped_item(EquipSlot::MAG); + auto& data = inventory.items[mag_index].data; + data.data2[0] = max(static_cast(data.data2[0] - 5), 0); + } catch (const out_of_range&) { } forward_subcommand(c, command, flag, data, size); @@ -876,18 +831,16 @@ static void on_player_drop_item(shared_ptr c, uint8_t command, uint8_t f } auto l = c->require_lobby(); - if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { - auto p = c->character(); - auto item = p->remove_item(cmd.item_id, 0, c->version() != Version::BB_V4); - l->add_item(item, cmd.floor, cmd.x, cmd.z); + auto p = c->character(); + auto item = p->remove_item(cmd.item_id, 0, c->version() != Version::BB_V4); + l->add_item(cmd.floor, item, cmd.x, cmd.z, 0x00F); - if (l->log.should_log(LogLevel::INFO)) { - auto s = c->require_server_state(); - auto name = s->describe_item(c->version(), item, false); - l->log.info("Player %hu dropped item %08" PRIX32 " (%s) at %hu:(%g, %g)", - cmd.header.client_id.load(), cmd.item_id.load(), name.c_str(), cmd.floor.load(), cmd.x.load(), cmd.z.load()); - p->print_inventory(stderr, c->version(), s->item_name_index); - } + if (l->log.should_log(LogLevel::INFO)) { + auto s = c->require_server_state(); + auto name = s->describe_item(c->version(), item, false); + l->log.info("Player %hu dropped item %08" PRIX32 " (%s) at %hu:(%g, %g)", + cmd.header.client_id.load(), cmd.item_id.load(), name.c_str(), cmd.floor.load(), cmd.x.load(), cmd.z.load()); + p->print_inventory(stderr, c->version(), s->item_name_index); } forward_subcommand(c, command, flag, data, size); @@ -932,19 +885,17 @@ static void on_create_inventory_item_t(shared_ptr c, uint8_t command, ui } auto l = c->require_lobby(); - if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { - auto p = c->character(); - ItemData item = cmd.item_data; - item.decode_for_version(c->version()); - l->on_item_id_generated_externally(item.id); - p->add_item(item); + auto p = c->character(); + ItemData item = cmd.item_data; + item.decode_for_version(c->version()); + l->on_item_id_generated_externally(item.id); + p->add_item(item); - if (l->log.should_log(LogLevel::INFO)) { - auto s = c->require_server_state(); - auto name = s->describe_item(c->version(), item, false); - l->log.info("Player %hu created inventory item %08" PRIX32 " (%s)", c->lobby_client_id, item.id.load(), name.c_str()); - p->print_inventory(stderr, c->version(), s->item_name_index); - } + if (l->log.should_log(LogLevel::INFO)) { + auto s = c->require_server_state(); + auto name = s->describe_item(c->version(), item, false); + l->log.info("Player %hu created inventory item %08" PRIX32 " (%s)", c->lobby_client_id, item.id.load(), name.c_str()); + p->print_inventory(stderr, c->version(), s->item_name_index); } forward_subcommand_with_item_transcode_t(c, command, flag, cmd); @@ -973,21 +924,19 @@ static void on_drop_partial_stack_t(shared_ptr c, uint8_t command, uint8 return; } - if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { - // TODO: Should we delete anything from the inventory here? Does the client - // send an appropriate 6x29 alongside this? - ItemData item = cmd.item_data; - item.decode_for_version(c->version()); - l->on_item_id_generated_externally(item.id); - l->add_item(item, cmd.floor, cmd.x, cmd.z); + // TODO: Should we delete anything from the inventory here? Does the client + // send an appropriate 6x29 alongside this? + ItemData item = cmd.item_data; + item.decode_for_version(c->version()); + l->on_item_id_generated_externally(item.id); + l->add_item(cmd.floor, item, cmd.x, cmd.z, 0x00F); - if (l->log.should_log(LogLevel::INFO)) { - auto s = c->require_server_state(); - auto name = s->describe_item(c->version(), item, false); - l->log.info("Player %hu split stack to create floor item %08" PRIX32 " (%s) at %hu:(%g, %g)", - cmd.header.client_id.load(), item.id.load(), name.c_str(), cmd.floor.load(), cmd.x.load(), cmd.z.load()); - c->character()->print_inventory(stderr, c->version(), s->item_name_index); - } + if (l->log.should_log(LogLevel::INFO)) { + auto s = c->require_server_state(); + auto name = s->describe_item(c->version(), item, false); + l->log.info("Player %hu split stack to create floor item %08" PRIX32 " (%s) at %hu:(%g, %g)", + cmd.header.client_id.load(), item.id.load(), name.c_str(), cmd.floor.load(), cmd.x.load(), cmd.z.load()); + c->character()->print_inventory(stderr, c->version(), s->item_name_index); } forward_subcommand_with_item_transcode_t(c, command, flag, cmd); @@ -1012,10 +961,6 @@ static void on_drop_partial_stack_bb(shared_ptr c, uint8_t command, uint return; } - if (!l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { - throw logic_error("item tracking not enabled in BB game"); - } - auto p = c->character(); auto item = p->remove_item(cmd.item_id, cmd.amount, c->version() != Version::BB_V4); @@ -1031,8 +976,8 @@ static void on_drop_partial_stack_bb(shared_ptr c, uint8_t command, uint // removed again by the 6x29 handler) p->add_item(item); - l->add_item(item, cmd.floor, cmd.x, cmd.z); - send_drop_stacked_item(l, item, cmd.floor, cmd.x, cmd.z); + l->add_item(cmd.floor, item, cmd.x, cmd.z, 0x00F); + send_drop_stacked_item_to_lobby(l, item, cmd.floor, cmd.x, cmd.z); if (l->log.should_log(LogLevel::INFO)) { auto s = c->require_server_state(); @@ -1059,23 +1004,21 @@ static void on_buy_shop_item(shared_ptr c, uint8_t command, uint8_t flag return; } - if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { - auto p = c->character(); - ItemData item = cmd.item_data; - item.data2d = 0; // Clear the price field - item.decode_for_version(c->version()); - l->on_item_id_generated_externally(item.id); - p->add_item(item); + auto p = c->character(); + ItemData item = cmd.item_data; + item.data2d = 0; // Clear the price field + item.decode_for_version(c->version()); + l->on_item_id_generated_externally(item.id); + p->add_item(item); - size_t price = s->item_parameter_table_for_version(c->version())->price_for_item(item); - p->remove_meseta(price, c->version() != Version::BB_V4); + size_t price = s->item_parameter_table_for_version(c->version())->price_for_item(item); + p->remove_meseta(price, c->version() != Version::BB_V4); - if (l->log.should_log(LogLevel::INFO)) { - auto name = s->describe_item(c->version(), item, false); - l->log.info("Player %hu bought item %08" PRIX32 " (%s) from shop (%zu Meseta)", - cmd.header.client_id.load(), item.id.load(), name.c_str(), price); - p->print_inventory(stderr, c->version(), s->item_name_index); - } + if (l->log.should_log(LogLevel::INFO)) { + auto name = s->describe_item(c->version(), item, false); + l->log.info("Player %hu bought item %08" PRIX32 " (%s) from shop (%zu Meseta)", + cmd.header.client_id.load(), item.id.load(), name.c_str(), price); + p->print_inventory(stderr, c->version(), s->item_name_index); } forward_subcommand_with_item_transcode_t(c, command, flag, cmd); @@ -1100,17 +1043,14 @@ static void on_box_or_enemy_item_drop_t(shared_ptr c, uint8_t command, u return; } - if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { - ItemData item = cmd.item.item; - item.decode_for_version(c->version()); - l->on_item_id_generated_externally(item.id); - l->add_item(item, cmd.item.floor, cmd.item.x, cmd.item.z); + ItemData item = cmd.item.item; + item.decode_for_version(c->version()); + l->on_item_id_generated_externally(item.id); + l->add_item(cmd.item.floor, item, cmd.item.x, cmd.item.z, 0x00F); - auto s = c->require_server_state(); - auto name = s->describe_item(c->version(), item, false); - l->log.info("Player %hhu (leader) created floor item %08" PRIX32 " (%s) at %hhu:(%g, %g)", - l->leader_id, item.id.load(), name.c_str(), cmd.item.floor, cmd.item.x.load(), cmd.item.z.load()); - } + auto name = s->describe_item(c->version(), item, false); + l->log.info("Player %hhu (leader) created floor item %08" PRIX32 " (%s) at %hhu:(%g, %g)", + l->leader_id, item.id.load(), name.c_str(), cmd.item.floor, cmd.item.x.load(), cmd.item.z.load()); for (auto& lc : l->clients) { if (!lc || lc == c) { @@ -1135,69 +1075,11 @@ static void on_box_or_enemy_item_drop(shared_ptr c, uint8_t command, uin } } -static void on_pick_up_item(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { - auto& cmd = check_size_t(data, size); - - auto l = c->require_lobby(); - if (!l->is_game()) { - return; - } - if (l->base_version == Version::BB_V4) { - // BB clients should never send this; only the server should send this - return; - } - - auto effective_c = l->clients.at(cmd.header.client_id); - if (!effective_c.get()) { - return; - } - - if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { - auto s = c->require_server_state(); - auto effective_p = effective_c->character(); - - // It seems the client just plays it fast and loose with these commands. - // There can be multiple 6x5A (request to pick up item) commands in flight, - // and the leader will respond to *all* of them with 6x59 (pick up item), - // even if the item can't be picked up by the time the command is processed - // (e.g. if the player's inventory has become full due to a previous pick up - // request). In the case of a full inventory, the client is expected to just - // ignore the command; in the case of the floor item not existing, however, - // the client behaves strangely and eventually disconnects, so we let - // l->remove_item throw in that case instead to fail faster. - - // This might be a legitimate bug in the client: the logic for determining - // if an item can be picked up also applies to Meseta, and forbids the - // pickup if the client has 999999 on hand. However, some actions that - // affect Meseta aren't sent to other players (e.g. depositing it in the - // bank), so the game could get into a state where some players see a client - // as able to pick up a Meseta item and some see a client as unable to do - // so. The downstream result of this desynchronization is that if the - // affected player tries to pick up some Meseta, some clients will not allow - // the pickup and will not delete the floor item, so if someone else tries - // to pick it up again, they will disconnect. - - auto item = l->remove_item(cmd.item_id); - try { - effective_p->add_item(item); - } catch (const out_of_range& e) { - auto name = s->describe_item(c->version(), item, false); - l->log.warning("Player %hu attempted to pick up %08" PRIX32 " (%s) but cannot (%s); ignoring command", - cmd.header.client_id.load(), cmd.item_id.load(), name.c_str(), e.what()); - return; - } - - if (l->log.should_log(LogLevel::INFO)) { - auto name = s->describe_item(c->version(), item, false); - l->log.info("Player %hu picked up %08" PRIX32 " (%s)", cmd.header.client_id.load(), cmd.item_id.load(), name.c_str()); - effective_p->print_inventory(stderr, c->version(), s->item_name_index); - } - } - - forward_subcommand(c, command, flag, data, size); +static void on_pick_up_item(shared_ptr, uint8_t, uint8_t, const void*, size_t) { + throw runtime_error("clients should not send 6x59 commands"); } -static void on_pick_up_item_request(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { +static void on_pick_up_item_request(shared_ptr c, uint8_t, uint8_t, const void* data, size_t size) { auto& cmd = check_size_t(data, size); auto l = c->require_lobby(); @@ -1205,32 +1087,53 @@ static void on_pick_up_item_request(shared_ptr c, uint8_t command, uint8 return; } - bool item_tracking_enabled = l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED); - if (item_tracking_enabled && !l->item_exists(cmd.item_id)) { + if (!l->item_exists(cmd.floor, cmd.item_id)) { + // This can happen if the network is slow, and the client tries to pick up + // the same item multiple times. Or multiple clients could try to pick up + // the same item at approximately the same time; only one should get it. l->log.warning("Player %hu requests to pick up %08" PRIX32 ", but the item does not exist; dropping command", cmd.header.client_id.load(), cmd.item_id.load()); - } else if (l->base_version == Version::BB_V4) { + } else { // This is handled by the server on BB, and by the leader on other versions. - if (!item_tracking_enabled) { - throw logic_error("item tracking not enabled in BB game"); - } + // However, the client's logic is to simply always send a 6x59 command when + // it receives a 6x5A and the floor item exists, so we just implement that + // logic here instead of forwarding the 6x5A to the leader. auto p = c->character(); - auto item = l->remove_item(cmd.item_id); - p->add_item(item); + auto fi = l->remove_item(cmd.floor, cmd.item_id, c->lobby_client_id); + try { + p->add_item(fi->data); + } catch (const out_of_range&) { + // Inventory is full; put the item back where it was + l->log.warning("Player %hu requests to pick up %08" PRIX32 ", but their inventory is full; dropping command", + cmd.header.client_id.load(), cmd.item_id.load()); + l->add_item(cmd.floor, fi); + return; + } if (l->log.should_log(LogLevel::INFO)) { auto s = c->require_server_state(); - auto name = s->describe_item(c->version(), item, false); + auto name = s->describe_item(c->version(), fi->data, false); l->log.info("Player %hu picked up (BB) %08" PRIX32 " (%s)", cmd.header.client_id.load(), cmd.item_id.load(), name.c_str()); p->print_inventory(stderr, c->version(), s->item_name_index); } - send_pick_up_item(c, cmd.item_id, cmd.floor); - - } else { - forward_subcommand(c, command, flag, data, size); + auto s = c->require_server_state(); + for (size_t z = 0; z < 12; z++) { + auto lc = l->clients[z]; + if (!lc) { + continue; + } + if (fi->visible_to_client(z)) { + send_pick_up_item_to_client(lc, cmd.header.client_id, cmd.item_id, cmd.floor); + } else if (lc->version() == Version::BB_V4) { + send_create_inventory_item_to_client(lc, cmd.header.client_id, fi->data); + } else { + send_drop_item_to_channel(s, lc->channel, fi->data, false, lc->floor, lc->x, lc->z, 0xFFFF); + send_pick_up_item_to_client(lc, cmd.header.client_id, cmd.item_id, cmd.floor); + } + } } } @@ -1242,14 +1145,10 @@ static void on_equip_item(shared_ptr c, uint8_t command, uint8_t flag, c } auto l = c->require_lobby(); - if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { - EquipSlot slot = static_cast(cmd.equip_slot.load()); - auto p = c->character(); - p->inventory.equip_item_id(cmd.item_id, slot); - c->log.info("Equipped item %08" PRIX32, cmd.item_id.load()); - } else if (l->base_version == Version::BB_V4) { - throw logic_error("item tracking not enabled in BB game"); - } + EquipSlot slot = static_cast(cmd.equip_slot.load()); + auto p = c->character(); + p->inventory.equip_item_id(cmd.item_id, slot); + c->log.info("Equipped item %08" PRIX32, cmd.item_id.load()); forward_subcommand(c, command, flag, data, size); } @@ -1262,13 +1161,9 @@ static void on_unequip_item(shared_ptr c, uint8_t command, uint8_t flag, } auto l = c->require_lobby(); - if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { - auto p = c->character(); - p->inventory.unequip_item_id(cmd.item_id); - c->log.info("Unequipped item %08" PRIX32, cmd.item_id.load()); - } else if (l->base_version == Version::BB_V4) { - throw logic_error("item tracking not enabled in BB game"); - } + auto p = c->character(); + p->inventory.unequip_item_id(cmd.item_id); + c->log.info("Unequipped item %08" PRIX32, cmd.item_id.load()); forward_subcommand(c, command, flag, data, size); } @@ -1285,24 +1180,22 @@ static void on_use_item( } auto l = c->require_lobby(); - if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { - auto s = c->require_server_state(); - auto p = c->character(); - size_t index = p->inventory.find_item(cmd.item_id); - string name; - { - // Note: We do this weird scoping thing because player_use_item will - // likely delete the item, which will break the reference here. - const auto& item = p->inventory.items[index].data; - name = s->describe_item(c->version(), item, false); - } - player_use_item(c, index); + auto s = c->require_server_state(); + auto p = c->character(); + size_t index = p->inventory.find_item(cmd.item_id); + string name; + { + // Note: We do this weird scoping thing because player_use_item will + // likely delete the item, which will break the reference here. + const auto& item = p->inventory.items[index].data; + name = s->describe_item(c->version(), item, false); + } + player_use_item(c, index); - if (l->log.should_log(LogLevel::INFO)) { - l->log.info("Player %hhu used item %hu:%08" PRIX32 " (%s)", - c->lobby_client_id, cmd.header.client_id.load(), cmd.item_id.load(), name.c_str()); - p->print_inventory(stderr, c->version(), s->item_name_index); - } + if (l->log.should_log(LogLevel::INFO)) { + l->log.info("Player %hhu used item %hu:%08" PRIX32 " (%s)", + c->lobby_client_id, cmd.header.client_id.load(), cmd.item_id.load(), name.c_str()); + p->print_inventory(stderr, c->version(), s->item_name_index); } forward_subcommand(c, command, flag, data, size); @@ -1320,37 +1213,35 @@ static void on_feed_mag( } auto l = c->require_lobby(); - if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { - auto s = c->require_server_state(); - auto p = c->character(); + auto s = c->require_server_state(); + auto p = c->character(); - size_t mag_index = p->inventory.find_item(cmd.mag_item_id); - size_t fed_index = p->inventory.find_item(cmd.fed_item_id); - string mag_name, fed_name; - { - // Note: We do this weird scoping thing because player_feed_mag will - // likely delete the items, which will break the references here. - const auto& fed_item = p->inventory.items[fed_index].data; - fed_name = s->describe_item(c->version(), fed_item, false); - const auto& mag_item = p->inventory.items[mag_index].data; - mag_name = s->describe_item(c->version(), mag_item, false); - } - player_feed_mag(c, mag_index, fed_index); + size_t mag_index = p->inventory.find_item(cmd.mag_item_id); + size_t fed_index = p->inventory.find_item(cmd.fed_item_id); + string mag_name, fed_name; + { + // Note: We do this weird scoping thing because player_feed_mag will + // likely delete the items, which will break the references here. + const auto& fed_item = p->inventory.items[fed_index].data; + fed_name = s->describe_item(c->version(), fed_item, false); + const auto& mag_item = p->inventory.items[mag_index].data; + mag_name = s->describe_item(c->version(), mag_item, false); + } + player_feed_mag(c, mag_index, fed_index); - // On BB, the player only sends a 6x28; on other versions, the player sends - // a 6x29 immediately after to destroy the fed item. So on BB, we should - // remove the fed item here, but on other versions, we allow the following - // 6x29 command to do that. - if (l->base_version == Version::BB_V4) { - p->remove_item(cmd.fed_item_id, 1, false); - } + // On BB, the player only sends a 6x28; on other versions, the player sends + // a 6x29 immediately after to destroy the fed item. So on BB, we should + // remove the fed item here, but on other versions, we allow the following + // 6x29 command to do that. + if (l->base_version == Version::BB_V4) { + p->remove_item(cmd.fed_item_id, 1, false); + } - if (l->log.should_log(LogLevel::INFO)) { - l->log.info("Player %hhu fed item %hu:%08" PRIX32 " (%s) to mag %hu:%08" PRIX32 " (%s)", - c->lobby_client_id, cmd.header.client_id.load(), cmd.fed_item_id.load(), fed_name.c_str(), - cmd.header.client_id.load(), cmd.mag_item_id.load(), mag_name.c_str()); - p->print_inventory(stderr, c->version(), s->item_name_index); - } + if (l->log.should_log(LogLevel::INFO)) { + l->log.info("Player %hhu fed item %hu:%08" PRIX32 " (%s) to mag %hu:%08" PRIX32 " (%s)", + c->lobby_client_id, cmd.header.client_id.load(), cmd.fed_item_id.load(), fed_name.c_str(), + cmd.header.client_id.load(), cmd.mag_item_id.load(), mag_name.c_str()); + p->print_inventory(stderr, c->version(), s->item_name_index); } forward_subcommand(c, command, flag, data, size); @@ -1358,41 +1249,37 @@ static void on_feed_mag( static void on_open_shop_bb_or_ep3_battle_subs(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { auto l = c->require_lobby(); - if (l->is_ep3()) { + if (!l->is_game()) { + throw runtime_error("received 6xB5 command in lobby"); + } else if (l->is_ep3()) { on_ep3_battle_subs(c, command, flag, data, size); - - } else if (!l->item_creator.get()) { + } else if (l->base_version != Version::BB_V4) { + throw runtime_error("received BB shop subcommand in non-BB game"); + } else if (!l->item_creator) { throw runtime_error("received shop subcommand without item creator present"); - } else { const auto& cmd = check_size_t(data, size); - if ((l->base_version == Version::BB_V4) && l->is_game()) { - if (!l->item_creator) { - throw logic_error("item creator missing from BB game"); - } - - auto s = c->require_server_state(); - size_t level = c->character()->disp.stats.level + 1; - switch (cmd.shop_type) { - case 0: - c->bb_shop_contents[0] = l->item_creator->generate_tool_shop_contents(level); - break; - case 1: - c->bb_shop_contents[1] = l->item_creator->generate_weapon_shop_contents(level); - break; - case 2: - c->bb_shop_contents[2] = l->item_creator->generate_armor_shop_contents(level); - break; - default: - throw runtime_error("invalid shop type"); - } - for (auto& item : c->bb_shop_contents[cmd.shop_type]) { - item.id = 0xFFFFFFFF; - item.data2d = s->item_parameter_table_for_version(c->version())->price_for_item(item); - } - - send_shop(c, cmd.shop_type); + auto s = c->require_server_state(); + size_t level = c->character()->disp.stats.level + 1; + switch (cmd.shop_type) { + case 0: + c->bb_shop_contents[0] = l->item_creator->generate_tool_shop_contents(level); + break; + case 1: + c->bb_shop_contents[1] = l->item_creator->generate_weapon_shop_contents(level); + break; + case 2: + c->bb_shop_contents[2] = l->item_creator->generate_armor_shop_contents(level); + break; + default: + throw runtime_error("invalid shop type"); } + for (auto& item : c->bb_shop_contents[cmd.shop_type]) { + item.id = 0xFFFFFFFF; + item.data2d = s->item_parameter_table_for_version(c->version())->price_for_item(item); + } + + send_shop(c, cmd.shop_type); } } @@ -1416,10 +1303,6 @@ static void on_ep3_private_word_select_bb_bank_action(shared_ptr c, uint return; } - if (!l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { - throw logic_error("item tracking not enabled in BB game"); - } - auto p = c->character(); auto& bank = c->current_bank(); if (cmd.action == 0) { // Deposit @@ -1447,7 +1330,7 @@ static void on_ep3_private_word_select_bb_bank_action(shared_ptr c, uint item.id = cmd.item_id; } bank.add_item(item); - send_destroy_item(c, cmd.item_id, cmd.item_amount, true); + send_destroy_item_to_lobby(c, cmd.item_id, cmd.item_amount, true); if (l->log.should_log(LogLevel::INFO)) { string name = s->item_name_index->describe_item(Version::BB_V4, item); @@ -1476,7 +1359,7 @@ static void on_ep3_private_word_select_bb_bank_action(shared_ptr c, uint auto item = bank.remove_item(cmd.item_id, cmd.item_amount); item.id = l->generate_item_id(c->lobby_client_id); p->add_item(item); - send_create_inventory_item(c, item); + send_create_inventory_item_to_lobby(c, c->lobby_client_id, item); if (l->log.should_log(LogLevel::INFO)) { string name = s->item_name_index->describe_item(Version::BB_V4, item); @@ -1500,10 +1383,6 @@ static void on_sort_inventory_bb(shared_ptr c, uint8_t, uint8_t, const v if (l->base_version == Version::BB_V4) { const auto& cmd = check_size_t(data, size); - if (!l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { - throw logic_error("item tracking not enabled in BB game"); - } - auto p = c->character(); // Make sure the set of item IDs passed in by the client exactly matches the @@ -1560,12 +1439,18 @@ static void on_entity_drop_item_request(shared_ptr c, uint8_t command, u return; } - if (!l->check_flag(Lobby::Flag::DROPS_ENABLED)) { - return; - } - if (!l->item_creator) { - forward_subcommand(c, command, flag, data, size); - 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; @@ -1600,96 +1485,124 @@ static void on_entity_drop_item_request(shared_ptr c, uint8_t command, u cmd.effective_area = in_cmd.floor; } - ItemData item; - if (cmd.rt_index == 0x30) { - if (l->map) { - auto& object = l->map->objects.at(cmd.entity_id); + auto generate_item = [&]() -> ItemData { + if (cmd.rt_index == 0x30) { + if (l->map) { + auto& object = l->map->objects.at(cmd.entity_id); - const char* floor_warning_token = ""; - if (cmd.floor != object.floor) { - l->log.warning("Floor %02hhX from command does not match object\'s expected floor %02hhX", - cmd.floor, object.floor); - floor_warning_token = "$C6!F$C5 "; - } + if (cmd.floor != object.floor) { + l->log.warning("Floor %02hhX from command does not match object\'s expected floor %02hhX", cmd.floor, object.floor); + } + bool object_ignore_def = (object.param1 > 0.0); + if (cmd.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)", + cmd.ignore_def ? "true" : "false", object_ignore_def ? "true" : "false", object.param1); + } - const char* ignore_def_warning_token = ""; - bool object_ignore_def = (object.param1 > 0.0); - if (cmd.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)", - cmd.ignore_def ? "true" : "false", object_ignore_def ? "true" : "false", object.param1); - ignore_def_warning_token = "$C6!I$C5 "; - } - - if (object.item_drop_checked) { - l->log.warning("Object drop check has already occurred"); - if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) { - send_text_message_printf(c, "$C5K-%hX %04hX %s%s__CHECKED__", cmd.entity_id.load(), object.base_type, floor_warning_token, ignore_def_warning_token); + if (cmd.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.entity_id, cmd.effective_area); + } else { + l->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, object.param3, object.param4, object.param5, object.param6); + return l->item_creator->on_specialized_box_item_drop(cmd.entity_id, cmd.effective_area, object.param3, object.param4, object.param5, object.param6); } } else if (cmd.ignore_def) { l->log.info("Creating item from box %04hX (area %02hX)", cmd.entity_id.load(), cmd.effective_area); - item = l->item_creator->on_box_item_drop(cmd.entity_id, cmd.effective_area); - if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) { - send_text_message_printf(c, "$C5K-%hX %04hX %s%sGEN %s", cmd.entity_id.load(), object.base_type, floor_warning_token, ignore_def_warning_token, item.empty() ? "EMPTY" : "ITEM"); - } + return l->item_creator->on_box_item_drop(cmd.entity_id, cmd.effective_area); } else { l->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, object.param3, object.param4, object.param5, object.param6); - item = l->item_creator->on_specialized_box_item_drop(cmd.entity_id, cmd.effective_area, object.param3, object.param4, object.param5, object.param6); - if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) { - send_text_message_printf(c, "$C5K-%hX %04hX %s%sCST %s", cmd.entity_id.load(), object.base_type, floor_warning_token, ignore_def_warning_token, item.empty() ? "EMPTY" : "ITEM"); + cmd.entity_id.load(), cmd.effective_area, cmd.param3.load(), cmd.param4.load(), cmd.param5.load(), cmd.param6.load()); + return l->item_creator->on_specialized_box_item_drop( + cmd.entity_id, cmd.effective_area, cmd.param3, cmd.param4, cmd.param5, cmd.param6); + } + + } else { + l->log.info("Creating item from enemy %04hX (area %02hX)", cmd.entity_id.load(), cmd.effective_area); + + uint8_t effective_rt_index = cmd.rt_index; + if (l->map) { + const auto& enemy = l->map->enemies.at(cmd.entity_id); + effective_rt_index = rare_table_index_for_enemy_type(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 (cmd.floor != enemy.floor) { + l->log.warning("Floor %02hhX from command does not match entity\'s expected floor %02hhX", + cmd.floor, enemy.floor); } } - object.item_drop_checked = true; - - } else if (cmd.ignore_def) { - l->log.info("Creating item from box %04hX (area %02hX)", cmd.entity_id.load(), cmd.effective_area); - item = l->item_creator->on_box_item_drop(cmd.entity_id, cmd.effective_area); - } else { - l->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()); - item = l->item_creator->on_specialized_box_item_drop( - cmd.entity_id, cmd.effective_area, cmd.param3, cmd.param4, cmd.param5, cmd.param6); + return l->item_creator->on_monster_item_drop(cmd.entity_id, effective_rt_index, cmd.effective_area); } + }; - } else { - l->log.info("Creating item from enemy %04hX (area %02hX)", cmd.entity_id.load(), cmd.effective_area); + switch (l->drop_mode) { + case Lobby::DropMode::DISABLED: + case Lobby::DropMode::CLIENT: + throw logic_error("unhandled simple drop mode"); + case Lobby::DropMode::SERVER_SHARED: + case Lobby::DropMode::SERVER_DUPLICATE: { + // TODO: In SERVER_DUPLICATE mode, should we reduce the rates for rare + // items? Maybe by a factor of l->count_clients()? + auto item = generate_item(); + if (item.empty()) { + l->log.info("No item was created"); + } else { + string name = s->item_name_index->describe_item(l->base_version, item); + 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 && ((cmd.rt_index == 0x30) || (lc->floor == cmd.floor))) { + item.id = l->generate_item_id(0xFF); + l->log.info("Creating item %08" PRIX32 " at %02hhX:%g,%g for %s", + item.id.load(), cmd.floor, cmd.x.load(), cmd.z.load(), lc->channel.name.c_str()); + l->add_item(cmd.floor, item, cmd.x, cmd.z, (1 << lc->lobby_client_id)); + send_drop_item_to_channel(s, lc->channel, item, cmd.rt_index != 0x30, cmd.floor, cmd.x, cmd.z, cmd.entity_id); + } + } - uint8_t effective_rt_index = cmd.rt_index; - string rt_index_warning_token = ""; - string floor_warning_token = ""; - if (l->map) { - const auto& enemy = l->map->enemies.at(cmd.entity_id); - effective_rt_index = rare_table_index_for_enemy_type(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); - rt_index_warning_token = string_printf("$C6!RT:%02hhX/%02" PRIX32 "$C5 ", cmd.rt_index, effective_rt_index); - } - if (cmd.floor != enemy.floor) { - l->log.warning("Floor %02hhX from command does not match entity\'s expected floor %02hhX", - cmd.floor, enemy.floor); - floor_warning_token = string_printf("$C6!F:%02hhX/%02hhX$C5 ", cmd.floor, enemy.floor); + } else { + item.id = l->generate_item_id(0xFF); + l->log.info("Creating item %08" PRIX32 " at %02hhX:%g,%g for all clients", + item.id.load(), cmd.floor, cmd.x.load(), cmd.z.load()); + l->add_item(cmd.floor, item, cmd.x, cmd.z, 0x00F); + send_drop_item_to_lobby(l, item, cmd.rt_index != 0x30, cmd.floor, cmd.x, cmd.z, cmd.entity_id); + } } + break; } - - item = l->item_creator->on_monster_item_drop(cmd.entity_id, effective_rt_index, cmd.effective_area); + case Lobby::DropMode::SERVER_PRIVATE: { + for (const auto& lc : l->clients) { + if (lc && ((cmd.rt_index == 0x30) || (lc->floor == cmd.floor))) { + auto item = generate_item(); + if (item.empty()) { + l->log.info("No item was created for %s", lc->channel.name.c_str()); + } else { + string name = s->item_name_index->describe_item(l->base_version, item); + l->log.info("Entity %04hX (area %02hX) created item %s", cmd.entity_id.load(), cmd.effective_area, name.c_str()); + item.id = l->generate_item_id(0xFF); + l->log.info("Creating item %08" PRIX32 " at %02hhX:%g,%g for %s", + item.id.load(), cmd.floor, cmd.x.load(), cmd.z.load(), lc->channel.name.c_str()); + l->add_item(cmd.floor, item, cmd.x, cmd.z, (1 << lc->lobby_client_id)); + send_drop_item_to_channel(s, lc->channel, item, cmd.rt_index != 0x30, cmd.floor, cmd.x, cmd.z, cmd.entity_id); + } + } + } + break; + } + default: + throw logic_error("invalid drop mode"); } - if (item.empty()) { - l->log.info("No item was created"); + if (cmd.rt_index == 0x30) { + l->item_creator->set_box_destroyed(cmd.entity_id); } else { - item.id = l->generate_item_id(0xFF); - string name = s->item_name_index->describe_item(l->base_version, item); - l->log.info("Entity %04hX (area %02hX) created item %s", cmd.entity_id.load(), cmd.effective_area, name.c_str()); - l->log.info("Creating item %08" PRIX32 " at %02hhX:%g,%g", item.id.load(), cmd.floor, cmd.x.load(), cmd.z.load()); - if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { - l->add_item(item, cmd.floor, cmd.x, cmd.z); - } - send_drop_item(l, item, cmd.rt_index != 0x30, cmd.floor, cmd.x, cmd.z, cmd.entity_id); + l->item_creator->set_monster_destroyed(cmd.entity_id); } } @@ -2084,7 +1997,7 @@ void on_meseta_reward_request_bb(shared_ptr c, uint8_t, uint8_t, const v item.data2d = cmd.amount.load(); item.id = l->generate_item_id(c->lobby_client_id); p->add_item(item); - send_create_inventory_item(c, item); + send_create_inventory_item_to_lobby(c, c->lobby_client_id, item); } } @@ -2096,7 +2009,7 @@ void on_item_reward_request_bb(shared_ptr c, uint8_t, uint8_t, const voi item = cmd.item_data; item.id = l->generate_item_id(c->lobby_client_id); c->character()->add_item(item); - send_create_inventory_item(c, item); + send_create_inventory_item_to_lobby(c, c->lobby_client_id, item); } void on_transfer_item_via_mail_message_bb(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { @@ -2117,9 +2030,6 @@ void on_transfer_item_via_mail_message_bb(shared_ptr c, uint8_t command, if (l->base_version != Version::BB_V4) { throw runtime_error("item tracking not enabled in BB game"); } - if (!l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { - throw runtime_error("item tracking not enabled in BB game"); - } forward_subcommand(c, command, flag, data, size); @@ -2157,7 +2067,7 @@ void on_transfer_item_via_mail_message_bb(shared_ptr c, uint8_t command, // If the item failed to send, add it back to the sender's inventory item.id = l->generate_item_id(0xFF); p->add_item(item); - send_create_inventory_item(c, item); + send_create_inventory_item_to_lobby(c, c->lobby_client_id, item); } } @@ -2179,9 +2089,6 @@ void on_exchange_item_for_team_points_bb(shared_ptr c, uint8_t command, if (l->base_version != Version::BB_V4) { throw runtime_error("item tracking not enabled in BB game"); } - if (!l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { - throw runtime_error("item tracking not enabled in BB game"); - } auto s = c->require_server_state(); auto p = c->character(); @@ -2211,36 +2118,38 @@ static void on_destroy_inventory_item(shared_ptr c, uint8_t command, uin return; } - if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { - auto s = c->require_server_state(); - auto p = c->character(); - auto item = p->remove_item(cmd.item_id, cmd.amount, c->version() != Version::BB_V4); + auto s = c->require_server_state(); + auto p = c->character(); + auto item = p->remove_item(cmd.item_id, cmd.amount, c->version() != Version::BB_V4); - if (l->log.should_log(LogLevel::INFO)) { - auto name = s->describe_item(c->version(), item, false); - l->log.info("Player %hhu destroyed inventory item %hu:%08" PRIX32 " (%s)", - c->lobby_client_id, cmd.header.client_id.load(), cmd.item_id.load(), name.c_str()); - p->print_inventory(stderr, c->version(), s->item_name_index); - } - forward_subcommand(c, command, flag, data, size); + if (l->log.should_log(LogLevel::INFO)) { + auto name = s->describe_item(c->version(), item, false); + l->log.info("Player %hhu destroyed inventory item %hu:%08" PRIX32 " (%s)", + c->lobby_client_id, cmd.header.client_id.load(), cmd.item_id.load(), name.c_str()); + p->print_inventory(stderr, c->version(), s->item_name_index); } + forward_subcommand(c, command, flag, data, size); } -static void on_destroy_ground_item(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { - const auto& cmd = check_size_t(data, size); +static void on_destroy_floor_item(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { + const auto& cmd = check_size_t(data, size); auto l = c->require_lobby(); if (!l->is_game()) { return; } - if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { - auto s = c->require_server_state(); - auto item = l->remove_item(cmd.item_id); - auto name = s->describe_item(c->version(), item, false); - l->log.info("Player %hhu destroyed floor item %08" PRIX32 " (%s)", - c->lobby_client_id, cmd.item_id.load(), name.c_str()); - forward_subcommand(c, command, flag, data, size); + auto s = c->require_server_state(); + auto fi = l->remove_item(cmd.floor, cmd.item_id, 0xFF); + auto name = s->describe_item(c->version(), fi->data, false); + l->log.info("Player %hhu destroyed floor item %08" PRIX32 " (%s)", c->lobby_client_id, cmd.item_id.load(), name.c_str()); + + // Only forward to players for whom the item was visible + for (size_t z = 0; z < l->clients.size(); z++) { + auto lc = l->clients[z]; + if (lc && fi->visible_to_client(z)) { + send_command_t(lc, command, flag, cmd); + } } } @@ -2251,11 +2160,8 @@ static void on_identify_item_bb(shared_ptr c, uint8_t command, uint8_t f if (!l->is_game()) { return; } - if (!l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { - throw logic_error("item tracking not enabled in BB game"); - } - if (!l->item_creator.get()) { - throw logic_error("received item identify subcommand without item creator present"); + if (!l->item_creator) { + throw runtime_error("received item identify subcommand without item creator present"); } auto p = c->character(); @@ -2285,10 +2191,6 @@ static void on_accept_identify_item_bb(shared_ptr c, uint8_t command, ui if (l->base_version == Version::BB_V4) { const auto& cmd = check_size_t(data, size); - if (!l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { - throw logic_error("item tracking not enabled in BB game"); - } - if (!c->bb_identify_result.id || (c->bb_identify_result.id == 0xFFFFFFFF)) { throw runtime_error("no identify result present"); } @@ -2296,7 +2198,7 @@ static void on_accept_identify_item_bb(shared_ptr c, uint8_t command, ui throw runtime_error("accepted item ID does not match previous identify request"); } c->character()->add_item(c->bb_identify_result); - send_create_inventory_item(c, c->bb_identify_result); + send_create_inventory_item_to_lobby(c, c->lobby_client_id, c->bb_identify_result); c->bb_identify_result.clear(); } else { @@ -2310,10 +2212,6 @@ static void on_sell_item_at_shop_bb(shared_ptr c, uint8_t command, uint8 if (l->base_version == Version::BB_V4) { const auto& cmd = check_size_t(data, size); - if (!l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { - throw logic_error("item tracking not enabled in BB game"); - } - auto s = c->require_server_state(); auto p = c->character(); auto item = p->remove_item(cmd.item_id, cmd.amount, c->version() != Version::BB_V4); @@ -2335,9 +2233,6 @@ static void on_buy_shop_item_bb(shared_ptr c, uint8_t, uint8_t, const vo auto l = c->require_lobby(); if (l->base_version == Version::BB_V4) { const auto& cmd = check_size_t(data, size); - if (!l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { - throw logic_error("item tracking not enabled in BB game"); - } ItemData item; item = c->bb_shop_contents.at(cmd.shop_type).at(cmd.item_index); @@ -2355,7 +2250,7 @@ static void on_buy_shop_item_bb(shared_ptr c, uint8_t, uint8_t, const vo item.id = cmd.shop_item_id; l->on_item_id_generated_externally(item.id); p->add_item(item); - send_create_inventory_item(c, item, true); + send_create_inventory_item_to_lobby(c, c->lobby_client_id, item, true); if (l->log.should_log(LogLevel::INFO)) { auto s = c->require_server_state(); @@ -2441,8 +2336,8 @@ static void on_request_challenge_grave_recovery_item_bb(shared_ptr c, ui }; ItemData item = items.at(cmd.item_type); item.id = l->generate_item_id(0xFF); - l->add_item(item, cmd.floor, cmd.x, cmd.z); - send_drop_stacked_item(l, item, cmd.floor, cmd.x, cmd.z); + l->add_item(cmd.floor, item, cmd.x, cmd.z, 0x00F); + send_drop_stacked_item_to_lobby(l, item, cmd.floor, cmd.x, cmd.z); } } @@ -2458,14 +2353,14 @@ static void on_quest_exchange_item_bb(shared_ptr c, uint8_t, uint8_t, co size_t found_index = p->inventory.find_item_by_primary_identifier(cmd.find_item.primary_identifier()); auto found_item = p->remove_item(p->inventory.items[found_index].data.id, 1, false); - send_destroy_item(c, found_item.id, 1); + send_destroy_item_to_lobby(c, found_item.id, 1); // TODO: We probably should use an allow-list here to prevent the client // from creating arbitrary items if cheat mode is disabled. ItemData new_item = cmd.replace_item; new_item.id = l->generate_item_id(c->lobby_client_id); p->add_item(new_item); - send_create_inventory_item(c, new_item); + send_create_inventory_item_to_lobby(c, c->lobby_client_id, new_item); send_quest_function_call(c, cmd.success_function_id); @@ -2483,10 +2378,10 @@ static void on_wrap_item_bb(shared_ptr c, uint8_t, uint8_t, const void* auto p = c->character(); auto item = p->remove_item(cmd.item.id, 1, false); - send_destroy_item(c, item.id, 1); + send_destroy_item_to_lobby(c, item.id, 1); item.wrap(); p->add_item(item); - send_create_inventory_item(c, item); + send_create_inventory_item_to_lobby(c, c->lobby_client_id, item); } } @@ -2500,14 +2395,14 @@ static void on_photon_drop_exchange_for_item_bb(shared_ptr c, uint8_t, u size_t found_index = p->inventory.find_item_by_primary_identifier(0x031000); auto found_item = p->remove_item(p->inventory.items[found_index].data.id, 0, false); - send_destroy_item(c, found_item.id, found_item.stack_size()); + send_destroy_item_to_lobby(c, found_item.id, found_item.stack_size()); // TODO: We probably should use an allow-list here to prevent the client // from creating arbitrary items if cheat mode is disabled. ItemData new_item = cmd.new_item; new_item.id = l->generate_item_id(c->lobby_client_id); p->add_item(new_item); - send_create_inventory_item(c, new_item); + send_create_inventory_item_to_lobby(c, c->lobby_client_id, new_item); send_quest_function_call(c, cmd.success_function_id); @@ -2535,13 +2430,13 @@ static void on_photon_drop_exchange_for_s_rank_special_bb(shared_ptr c, p->inventory.find_item(cmd.item_id); auto payment_item = p->remove_item(p->inventory.items[payment_item_index].data.id, cost, false); - send_destroy_item(c, payment_item.id, cost); + send_destroy_item_to_lobby(c, payment_item.id, cost); auto item = p->remove_item(cmd.item_id, 1, false); - send_destroy_item(c, item.id, cost); + send_destroy_item_to_lobby(c, item.id, cost); item.data1[2] = cmd.special_type; p->add_item(item); - send_create_inventory_item(c, item); + send_create_inventory_item_to_lobby(c, c->lobby_client_id, item); send_quest_function_call(c, cmd.success_function_id); @@ -2581,14 +2476,14 @@ static void on_secret_lottery_ticket_exchange_bb(shared_ptr c, uint8_t, exchange_cmd.amount = 1; send_command_t(c, 0x60, 0x00, exchange_cmd); - send_destroy_item(c, slt_item_id, 1); + send_destroy_item_to_lobby(c, slt_item_id, 1); ItemData item = (s->secret_lottery_results.size() == 1) ? s->secret_lottery_results[0] : s->secret_lottery_results[random_object() % s->secret_lottery_results.size()]; item.id = l->generate_item_id(c->lobby_client_id); p->add_item(item); - send_create_inventory_item(c, item); + send_create_inventory_item_to_lobby(c, c->lobby_client_id, item); } S_ExchangeSecretLotteryTicketResult_BB_24 out_cmd; @@ -2614,7 +2509,7 @@ static void on_photon_crystal_exchange_bb(shared_ptr c, uint8_t, uint8_t auto p = c->character(); size_t index = p->inventory.find_item_by_primary_identifier(0x031002); auto item = p->remove_item(p->inventory.items[index].data.id, 1, false); - send_destroy_item(c, item.id, 1); + send_destroy_item_to_lobby(c, item.id, 1); } } @@ -2641,9 +2536,9 @@ static void on_quest_F95E_result_bb(shared_ptr c, uint8_t, uint8_t, cons } item.id = l->generate_item_id(0xFF); - l->add_item(item, cmd.floor, cmd.x, cmd.z); + l->add_item(cmd.floor, item, cmd.x, cmd.z, 0x00F); - send_drop_stacked_item(l, item, cmd.floor, cmd.x, cmd.z); + send_drop_stacked_item_to_lobby(l, item, cmd.floor, cmd.x, cmd.z); } } } @@ -2675,7 +2570,7 @@ static void on_quest_F95F_result_bb(shared_ptr c, uint8_t, uint8_t, cons ItemData new_item = result.second; new_item.id = l->generate_item_id(c->lobby_client_id); p->add_item(new_item); - send_create_inventory_item(c, new_item); + send_create_inventory_item_to_lobby(c, c->lobby_client_id, new_item); S_GallonPlanResult_BB_25 out_cmd; out_cmd.function_id = cmd.function_id1; @@ -2699,14 +2594,14 @@ static void on_momoka_item_exchange_bb(shared_ptr c, uint8_t, uint8_t, c G_ExchangeItemInQuest_BB_6xDB cmd_6xDB = {{0xDB, 0x04, c->lobby_client_id}, 1, found_item.id, 1}; send_command_t(c, 0x60, 0x00, cmd_6xDB); - send_destroy_item(c, found_item.id, 1); + send_destroy_item_to_lobby(c, found_item.id, 1); // TODO: We probably should use an allow-list here to prevent the client // from creating arbitrary items if cheat mode is disabled. ItemData new_item = cmd.replace_item; new_item.id = l->generate_item_id(c->lobby_client_id); p->add_item(new_item); - send_create_inventory_item(c, new_item); + send_create_inventory_item_to_lobby(c, c->lobby_client_id, new_item); send_command(c, 0x23, 0x00); } catch (const exception& e) { @@ -2732,7 +2627,7 @@ static void on_upgrade_weapon_attribute_bb(shared_ptr c, uint8_t, uint8_ throw runtime_error("not enough payment items present"); } p->remove_item(payment_item.id, cmd.payment_count, false); - send_destroy_item(c, payment_item.id, cmd.payment_count); + send_destroy_item_to_lobby(c, payment_item.id, cmd.payment_count); uint8_t attribute_amount = 0; if (cmd.payment_type == 1 && cmd.payment_count == 1) { @@ -2758,8 +2653,8 @@ static void on_upgrade_weapon_attribute_bb(shared_ptr c, uint8_t, uint8_ item.data1[attribute_index] = cmd.attribute; item.data1[attribute_index + 1] += attribute_amount; - send_destroy_item(c, item.id, 1); - send_create_inventory_item(c, item); + send_destroy_item_to_lobby(c, item.id, 1); + send_create_inventory_item_to_lobby(c, c->lobby_client_id, item); send_quest_function_call(c, cmd.success_function_id); } catch (const exception& e) { @@ -2883,7 +2778,7 @@ SubcommandDefinition subcommand_definitions[0x100] = { /* 6x60 */ {0x00, 0x00, 0x60, on_entity_drop_item_request}, /* 6x61 */ {0x00, 0x00, 0x61, on_forward_check_size_game}, /* 6x62 */ {0x00, 0x00, 0x62, nullptr}, - /* 6x63 */ {0x00, 0x00, 0x63, on_destroy_ground_item}, + /* 6x63 */ {0x00, 0x00, 0x63, on_destroy_floor_item}, /* 6x64 */ {0x00, 0x00, 0x64, nullptr}, /* 6x65 */ {0x00, 0x00, 0x65, nullptr}, /* 6x66 */ {0x00, 0x00, 0x66, on_forward_check_size_game}, diff --git a/src/SendCommands.cc b/src/SendCommands.cc index 3c2421b3..d873b8dc 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -2304,43 +2304,39 @@ void send_set_player_visibility(shared_ptr l, shared_ptr c, bool send_command_t(l, 0x60, 0x00, cmd); } -void send_artificial_item_state(shared_ptr c) { +void send_game_item_state(shared_ptr 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 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 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 s, Channel& ch, const ItemData& item, +void send_drop_item_to_channel(shared_ptr 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 s, Channel& ch, const ItemData& item ch.send(0x60, 0x00, &cmd, sizeof(cmd)); } -void send_drop_item(shared_ptr l, const ItemData& item, +void send_drop_item_to_lobby(shared_ptr 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 s, Channel& ch, const ItemData& item, uint8_t floor, float x, float z) { +void send_drop_stacked_item_to_channel( + shared_ptr 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 l, const ItemData& item, uint8_t floor, float x, float z) { +void send_drop_stacked_item_to_lobby(shared_ptr 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 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 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 c, const ItemData& item, bool exclude_c) { - auto l = c->require_lobby(); +void send_create_inventory_item_to_client(shared_ptr 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 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 c, uint32_t item_id, uint32_t amount, bool exclude_c) { +void send_destroy_item_to_lobby(shared_ptr 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 c, uint32_t item_id, uint32_t amount, } } +void send_destroy_floor_item_to_client(shared_ptr 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 c) { auto l = c->require_lobby(); if (c->version() != Version::BB_V4) { diff --git a/src/SendCommands.hh b/src/SendCommands.hh index 2c84c432..b936159b 100644 --- a/src/SendCommands.hh +++ b/src/SendCommands.hh @@ -298,16 +298,20 @@ void send_ep3_change_music(Channel& ch, uint32_t song); void send_set_player_visibility(std::shared_ptr c, bool visible); void send_revive_player(std::shared_ptr c); -void send_artificial_item_state(std::shared_ptr c); -void send_drop_item(std::shared_ptr s, Channel& ch, const ItemData& item, +void send_game_item_state(std::shared_ptr c); +void send_drop_item_to_channel(std::shared_ptr 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 l, const ItemData& item, +void send_drop_item_to_lobby(std::shared_ptr 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 s, Channel& ch, const ItemData& item, uint8_t floor, float x, float z); -void send_drop_stacked_item(std::shared_ptr l, const ItemData& item, uint8_t floor, float x, float z); -void send_pick_up_item(std::shared_ptr c, uint32_t id, uint8_t floor); -void send_create_inventory_item(std::shared_ptr c, const ItemData& item, bool exclude_c = false); -void send_destroy_item(std::shared_ptr c, uint32_t item_id, uint32_t amount, bool exclude_c = false); +void send_drop_stacked_item_to_channel( + std::shared_ptr s, Channel& ch, const ItemData& item, uint8_t floor, float x, float z); +void send_drop_stacked_item_to_lobby( + std::shared_ptr l, const ItemData& item, uint8_t floor, float x, float z); +void send_pick_up_item_to_client(std::shared_ptr c, uint8_t client_id, uint32_t id, uint8_t floor); +void send_create_inventory_item_to_client(std::shared_ptr c, uint8_t client_id, const ItemData& item); +void send_create_inventory_item_to_lobby(std::shared_ptr c, uint8_t client_id, const ItemData& item, bool exclude_c = false); +void send_destroy_item_to_lobby(std::shared_ptr c, uint32_t item_id, uint32_t amount, bool exclude_c = false); +void send_destroy_floor_item_to_client(std::shared_ptr c, uint32_t item_id, uint32_t floor); void send_item_identify_result(std::shared_ptr c); void send_bank(std::shared_ptr c); void send_shop(std::shared_ptr c, uint8_t shop_type); diff --git a/src/ServerShell.cc b/src/ServerShell.cc index df8cff1b..091f9c9d 100644 --- a/src/ServerShell.cc +++ b/src/ServerShell.cc @@ -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); diff --git a/src/ServerState.cc b/src/ServerState.cc index fa005ee0..b80f72a0 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -26,9 +26,12 @@ ServerState::ServerState(shared_ptr 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(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); diff --git a/src/ServerState.hh b/src/ServerState.hh index 609c7085..9dd12330 100644 --- a/src/ServerState.hh +++ b/src/ServerState.hh @@ -75,9 +75,12 @@ struct ServerState : public std::enable_shared_from_this { 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; diff --git a/system/config.example.json b/system/config.example.json index 8d98719c..65398db5 100644 --- a/system/config.example.json +++ b/system/config.example.json @@ -711,27 +711,22 @@ "AllowDCPCGames": false, "AllowGCXBGames": true, - // By default, the server keeps track of items in all games, even for versions - // other than Blue Burst. This enables use of the $what command, as well as - // protection against item duplication cheats (the cheater is disconnected - // instead of the other players). If item tracking causes any issues, it can - // be turned off here. This option has no effect on Blue Burst games - item - // tracking is always enabled for them. - "EnableItemTracking": true, - - // These options control the behavior of items dropped from boxes and enemies. - // ItemDropMode specifies whether any items drop at all; this setting applies - // to all versions. UseServerItemTables specifies whether the dropped items - // are generated by the client or by the server; this setting applies to all - // versions except BB. For BB, items are always generated by the server. - // Server item tables can only be used in non-BB games if item tracking is - // also enabled. - // Either option can be Off, On, OffByDefault, or OnByDefault. If the - // ByDefault values are used, the game leader can enable or disable drops with - // the $drop command, and can switch between server and client drop logic with - // the $itemtable command. - "ItemDropMode": "OnByDefault", - "UseServerItemTables": "OffByDefault", + // These options control which item drop modes are used by default, and which + // can be chosen by the player. The AllowedDropModes fields are a bitmask + // specifying which modes players can choose with the $dropmode command. The + // modes are (name = mask): + // DISABLED = 0x01 + // CLIENT = 0x02 + // SERVER_SHARED = 0x04 + // SERVER_PRIVATE = 0x08 + // SERVER_DUPLICATE = 0x10 + // See README.md for more information on drop modes and item tables. + "AllowedDropModesV1V2": 0x1F, // DCv1, DCv2, and PCv2 + "AllowedDropModesV3": 0x1F, // GC and Xbox + "AllowedDropModesV4": 0x1D, // BB; CLIENT not allowed + "DefaultDropModeV1V2": "CLIENT", // DCv1, DCv2, and PCv2 + "DefaultDropModeV3": "CLIENT", // GC and Xbox + "DefaultDropModeV4": "SERVER_SHARED", // BB // Rare enemy rates for BB games. The default rates specified here match the // original rates on the official servers. There is a hard limit of 16 rare diff --git a/tests/DCv1-GameSmokeTest.test.txt b/tests/DCv1-GameSmokeTest.test.txt index 679bdbca..3c082a6f 100644 --- a/tests/DCv1-GameSmokeTest.test.txt +++ b/tests/DCv1-GameSmokeTest.test.txt @@ -849,8 +849,6 @@ I 40469 2023-05-26 10:42:16 - [Commands] Received from C-2 (Tali) (version=DC co I 40469 2023-05-26 10:42:17 - [Commands] Received from C-2 (Tali) (version=DC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 7D 01 01 06 01 00 F9 8C | b Z } I 40469 2023-05-26 10:42:17 - [Commands] Sending to C-2 (Tali) (version=DC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 7D 01 01 06 01 00 F9 8C | b Z } -I 40469 2023-05-26 10:42:17 - [Commands] Received from C-2 (Tali) (version=DC command=60 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 7D 01 01 06 | ` Y } I 40469 2023-05-26 10:42:17 - [Lobby/15] Player 0 picked up 0601017D (27 Meseta) [PlayerInventory] Meseta: 327 @@ -901,12 +899,10 @@ I 40469 2023-05-26 10:42:20 - [Commands] Received from C-2 (Tali) (version=DC co 0010 | 00 00 00 00 | I 40469 2023-05-26 10:42:20 - [Commands] Received from C-2 (Tali) (version=DC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 7E 01 01 06 01 00 F9 8C | b Z ~ -I 40469 2023-05-26 10:42:20 - [Commands] Sending to C-2 (Tali) (version=DC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 7E 01 01 06 01 00 F9 8C | b Z ~ I 40469 2023-05-26 10:42:21 - [Commands] Received from C-2 (Tali) (version=DC command=60 flag=00) 0000 | 60 00 14 00 40 04 00 00 DB 4D 14 44 7E 1F 03 44 | ` @ M D~ D 0010 | 00 00 00 00 | -I 40469 2023-05-26 10:42:21 - [Commands] Received from C-2 (Tali) (version=DC command=60 flag=00) +I 40469 2023-05-26 10:42:20 - [Commands] Sending to C-2 (Tali) (version=DC command=62 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 7E 01 01 06 | ` Y ~ I 40469 2023-05-26 10:42:21 - [Lobby/15] Player 0 picked up 0601017E (Handgun 0/0/0/0/0) [PlayerInventory] Meseta: 327 diff --git a/tests/DCv2-GameSmokeTest.test.txt b/tests/DCv2-GameSmokeTest.test.txt index e9ad2ee7..214b6bb6 100644 --- a/tests/DCv2-GameSmokeTest.test.txt +++ b/tests/DCv2-GameSmokeTest.test.txt @@ -974,12 +974,10 @@ I 40992 2023-05-26 10:54:51 - [Commands] Received from C-2 (Tali) (version=DC co 0010 | 00 00 00 00 | I 40992 2023-05-26 10:54:51 - [Commands] Received from C-2 (Tali) (version=DC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 7D 01 01 06 01 00 00 00 | b Z } -I 40992 2023-05-26 10:54:51 - [Commands] Sending to C-2 (Tali) (version=DC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 7D 01 01 06 01 00 00 00 | b Z } I 40992 2023-05-26 10:54:51 - [Commands] Received from C-2 (Tali) (version=DC command=60 flag=00) 0000 | 60 00 14 00 40 04 00 00 20 F5 E6 43 F1 52 CD 43 | ` @ C R C 0010 | 00 00 00 00 | -I 40992 2023-05-26 10:54:51 - [Commands] Received from C-2 (Tali) (version=DC command=60 flag=00) +I 40992 2023-05-26 10:54:51 - [Commands] Sending to C-2 (Tali) (version=DC command=62 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 7D 01 01 06 | ` Y } I 40992 2023-05-26 10:54:51 - [Lobby/15] Player 0 picked up 0601017D (16 Meseta) [PlayerInventory] Meseta: 343 @@ -1096,12 +1094,10 @@ I 40992 2023-05-26 10:54:59 - [Commands] Received from C-2 (Tali) (version=DC co 0010 | 00 00 00 00 | I 40992 2023-05-26 10:54:59 - [Commands] Received from C-2 (Tali) (version=DC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 7B 01 01 06 01 00 00 00 | b Z { -I 40992 2023-05-26 10:54:59 - [Commands] Sending to C-2 (Tali) (version=DC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 7B 01 01 06 01 00 00 00 | b Z { I 40992 2023-05-26 10:54:59 - [Commands] Received from C-2 (Tali) (version=DC command=60 flag=00) 0000 | 60 00 14 00 40 04 00 00 D8 EC 0E 44 0D 8C 0E 44 | ` @ D D 0010 | 00 00 00 00 | -I 40992 2023-05-26 10:54:59 - [Commands] Received from C-2 (Tali) (version=DC command=60 flag=00) +I 40992 2023-05-26 10:54:59 - [Commands] Sending to C-2 (Tali) (version=DC command=62 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 7B 01 01 06 | ` Y { I 40992 2023-05-26 10:54:59 - [Lobby/15] Player 0 picked up 0601017B (Antidote x1) [PlayerInventory] Meseta: 343 diff --git a/tests/GC-ForestGame.test.txt b/tests/GC-ForestGame.test.txt index fc032dc9..b2d4b17a 100644 --- a/tests/GC-ForestGame.test.txt +++ b/tests/GC-ForestGame.test.txt @@ -908,8 +908,6 @@ I 49108 2023-05-26 16:18:52 - [Commands] Received from C-2 (Jess) (version=GC co I 49108 2023-05-26 16:18:52 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 7C 01 01 06 01 00 00 00 | b Z | I 49108 2023-05-26 16:18:52 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 7C 01 01 06 01 00 00 00 | b Z | -I 49108 2023-05-26 16:18:52 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 7C 01 01 06 | ` Y | I 49108 2023-05-26 16:18:52 - [Lobby/15] Player 0 picked up 0601017C (8 Meseta) [PlayerInventory] Meseta: 8 @@ -940,8 +938,6 @@ I 49108 2023-05-26 16:18:53 - [Commands] Received from C-2 (Jess) (version=GC co I 49108 2023-05-26 16:18:53 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 7B 01 01 06 01 00 00 00 | b Z { I 49108 2023-05-26 16:18:53 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 7B 01 01 06 01 00 00 00 | b Z { -I 49108 2023-05-26 16:18:53 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 7B 01 01 06 | ` Y { I 49108 2023-05-26 16:18:53 - [Lobby/15] Player 0 picked up 0601017B (Monofluid x1) [PlayerInventory] Meseta: 8 @@ -1062,8 +1058,6 @@ I 49108 2023-05-26 16:18:59 - [Commands] Received from C-2 (Jess) (version=GC co I 49108 2023-05-26 16:18:59 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 7E 01 01 06 01 00 00 00 | b Z ~ I 49108 2023-05-26 16:18:59 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 7E 01 01 06 01 00 00 00 | b Z ~ -I 49108 2023-05-26 16:18:59 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 7E 01 01 06 | ` Y ~ I 49108 2023-05-26 16:18:59 - [Lobby/15] Player 0 picked up 0601017E (8 Meseta) [PlayerInventory] Meseta: 16 @@ -1089,8 +1083,6 @@ I 49108 2023-05-26 16:18:59 - [Commands] Received from C-2 (Jess) (version=GC co I 49108 2023-05-26 16:18:59 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 7D 01 01 06 01 00 00 00 | b Z } I 49108 2023-05-26 16:18:59 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 7D 01 01 06 01 00 00 00 | b Z } -I 49108 2023-05-26 16:18:59 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 7D 01 01 06 | ` Y } I 49108 2023-05-26 16:18:59 - [Lobby/15] Player 0 picked up 0601017D (6 Meseta) [PlayerInventory] Meseta: 22 @@ -1512,8 +1504,6 @@ I 49108 2023-05-26 16:19:44 - [Commands] Received from C-2 (Jess) (version=GC co 0010 | 00 00 00 00 | I 49108 2023-05-26 16:19:44 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 0C 0D 01 06 01 00 00 00 | b Z -I 49108 2023-05-26 16:19:44 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 0C 0D 01 06 01 00 00 00 | b Z I 49108 2023-05-26 16:19:44 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 1C 00 3E 06 00 00 00 00 25 83 01 00 0B 00 | ` > % 0010 | 62 4B 52 43 F1 7C C0 41 65 A6 C8 43 | bKRC | Ae C @@ -1526,7 +1516,7 @@ I 49108 2023-05-26 16:19:44 - [Commands] Received from C-2 (Jess) (version=GC co I 49108 2023-05-26 16:19:44 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 14 00 40 04 00 00 E7 68 62 43 12 21 CF 43 | ` @ hbC ! C 0010 | 00 00 00 00 | -I 49108 2023-05-26 16:19:44 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) +I 49108 2023-05-26 16:19:44 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 0C 0D 01 06 | ` Y I 49108 2023-05-26 16:19:44 - [Lobby/15] Player 0 picked up 06010D0C (5 Meseta) [PlayerInventory] Meseta: 27 @@ -1732,8 +1722,6 @@ I 49108 2023-05-26 16:20:00 - [Commands] Received from C-2 (Jess) (version=GC co I 49108 2023-05-26 16:20:00 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 11 0D 01 06 01 00 00 00 | b Z I 49108 2023-05-26 16:20:00 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 11 0D 01 06 01 00 00 00 | b Z -I 49108 2023-05-26 16:20:00 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 11 0D 01 06 | ` Y I 49108 2023-05-26 16:20:00 - [Lobby/15] Player 0 picked up 06010D11 (Antiparalysis x1) [PlayerInventory] Meseta: 27 @@ -1800,8 +1788,6 @@ I 49108 2023-05-26 16:20:04 - [Commands] Received from C-2 (Jess) (version=GC co I 49108 2023-05-26 16:20:04 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 6F 01 01 06 01 00 00 00 | b Z o I 49108 2023-05-26 16:20:04 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 6F 01 01 06 01 00 00 00 | b Z o -I 49108 2023-05-26 16:20:04 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 6F 01 01 06 | ` Y o I 49108 2023-05-26 16:20:04 - [Lobby/15] Player 0 picked up 0601016F (Monofluid x1) [PlayerInventory] Meseta: 27 @@ -1846,8 +1832,6 @@ I 49108 2023-05-26 16:20:05 - [Commands] Received from C-2 (Jess) (version=GC co I 49108 2023-05-26 16:20:05 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 0F 0D 01 06 01 00 00 00 | b Z I 49108 2023-05-26 16:20:05 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 0F 0D 01 06 01 00 00 00 | b Z -I 49108 2023-05-26 16:20:06 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 0F 0D 01 06 | ` Y I 49108 2023-05-26 16:20:06 - [Lobby/15] Player 0 picked up 06010D0F (Dimate x1) [PlayerInventory] Meseta: 27 @@ -1945,12 +1929,10 @@ I 49108 2023-05-26 16:20:10 - [Commands] Received from C-2 (Jess) (version=GC co 0000 | 60 00 10 00 42 03 00 00 16 62 8F 43 28 D9 46 43 | ` B b C( FC I 49108 2023-05-26 16:20:10 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 10 0D 01 06 01 00 00 00 | b Z -I 49108 2023-05-26 16:20:10 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 10 0D 01 06 01 00 00 00 | b Z I 49108 2023-05-26 16:20:10 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 1C 00 3E 06 00 00 00 00 B9 44 01 00 0B 00 | ` > D 0010 | 7A 22 8F 43 C6 69 C1 41 47 E7 46 43 | z" C i AG FC -I 49108 2023-05-26 16:20:13 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) +I 49108 2023-05-26 16:20:10 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 10 0D 01 06 | ` Y I 49108 2023-05-26 16:20:13 - [Lobby/15] Player 0 picked up 06010D10 (18 Meseta) [PlayerInventory] Meseta: 45 @@ -1997,8 +1979,6 @@ I 49108 2023-05-26 16:20:13 - [Commands] Received from C-2 (Jess) (version=GC co I 49108 2023-05-26 16:20:14 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 72 01 01 06 01 00 00 00 | b Z r I 49108 2023-05-26 16:20:14 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 72 01 01 06 01 00 00 00 | b Z r -I 49108 2023-05-26 16:20:14 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 72 01 01 06 | ` Y r I 49108 2023-05-26 16:20:14 - [Lobby/15] Player 0 picked up 06010172 (3 Meseta) [PlayerInventory] Meseta: 48 @@ -2125,8 +2105,6 @@ I 49108 2023-05-26 16:20:21 - [Commands] Received from C-2 (Jess) (version=GC co I 49108 2023-05-26 16:20:21 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 70 01 01 06 01 00 00 00 | b Z p I 49108 2023-05-26 16:20:21 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 70 01 01 06 01 00 00 00 | b Z p -I 49108 2023-05-26 16:20:21 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 70 01 01 06 | ` Y p I 49108 2023-05-26 16:20:21 - [Lobby/15] Player 0 picked up 06010170 (8 Meseta) [PlayerInventory] Meseta: 56 @@ -2573,8 +2551,6 @@ I 49108 2023-05-26 16:20:53 - [Commands] Received from C-2 (Jess) (version=GC co I 49108 2023-05-26 16:20:53 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 06 0D 01 06 01 00 00 00 | b Z I 49108 2023-05-26 16:20:53 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 06 0D 01 06 01 00 00 00 | b Z -I 49108 2023-05-26 16:20:53 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 06 0D 01 06 | ` Y I 49108 2023-05-26 16:20:53 - [Lobby/15] Player 0 picked up 06010D06 (7 Meseta) [PlayerInventory] Meseta: 63 @@ -2700,8 +2676,6 @@ I 49108 2023-05-26 16:21:01 - [Commands] Received from C-2 (Jess) (version=GC co 0010 | 00 00 00 00 | I 49108 2023-05-26 16:21:01 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 04 0D 01 06 01 00 00 00 | b Z -I 49108 2023-05-26 16:21:01 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 04 0D 01 06 01 00 00 00 | b Z I 49108 2023-05-26 16:21:01 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 14 00 40 04 00 00 52 06 8F C2 C3 7A 21 43 | ` @ R z!C 0010 | 00 00 00 00 | @@ -2715,7 +2689,7 @@ I 49108 2023-05-26 16:21:01 - [Commands] Received from C-2 (Jess) (version=GC co 0000 | 60 00 10 00 42 03 00 00 F6 51 35 C2 8B 8B 12 43 | ` B Q5 C I 49108 2023-05-26 16:21:01 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 42 03 00 00 A8 75 20 C2 3D 2A 0D 43 | ` B u =* C -I 49108 2023-05-26 16:21:01 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) +I 49108 2023-05-26 16:21:01 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 04 0D 01 06 | ` Y I 49108 2023-05-26 16:21:01 - [Lobby/15] Player 0 picked up 06010D04 (Antidote x1) [PlayerInventory] Meseta: 63 @@ -2807,8 +2781,6 @@ I 49108 2023-05-26 16:21:07 - [Commands] Sending to C-2 (Jess) (version=GC comma I 49108 2023-05-26 16:21:10 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 68 01 01 06 01 00 00 00 | b Z h I 49108 2023-05-26 16:21:10 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 68 01 01 06 01 00 00 00 | b Z h -I 49108 2023-05-26 16:21:10 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 68 01 01 06 | ` Y h I 49108 2023-05-26 16:21:10 - [Lobby/15] Player 0 picked up 06010168 (Monofluid x1) [PlayerInventory] Meseta: 63 @@ -2852,8 +2824,6 @@ I 49108 2023-05-26 16:21:11 - [Commands] Received from C-2 (Jess) (version=GC co I 49108 2023-05-26 16:21:11 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 66 01 01 06 01 00 00 00 | b Z f I 49108 2023-05-26 16:21:11 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 66 01 01 06 01 00 00 00 | b Z f -I 49108 2023-05-26 16:21:11 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 66 01 01 06 | ` Y f I 49108 2023-05-26 16:21:11 - [Lobby/15] Player 0 picked up 06010166 (Monofluid x1) [PlayerInventory] Meseta: 63 @@ -3139,8 +3109,6 @@ I 49108 2023-05-26 16:21:30 - [Commands] Received from C-2 (Jess) (version=GC co I 49108 2023-05-26 16:21:30 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 41 01 01 06 01 00 00 00 | b Z A I 49108 2023-05-26 16:21:30 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 41 01 01 06 01 00 00 00 | b Z A -I 49108 2023-05-26 16:21:30 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 41 01 01 06 | ` Y A I 49108 2023-05-26 16:21:30 - [Lobby/15] Player 0 picked up 06010141 (9 Meseta) [PlayerInventory] Meseta: 72 @@ -3244,8 +3212,6 @@ I 49108 2023-05-26 16:21:35 - [Commands] Received from C-2 (Jess) (version=GC co I 49108 2023-05-26 16:21:36 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 34 01 01 06 01 00 00 00 | b Z 4 I 49108 2023-05-26 16:21:36 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 34 01 01 06 01 00 00 00 | b Z 4 -I 49108 2023-05-26 16:21:36 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 34 01 01 06 | ` Y 4 I 49108 2023-05-26 16:21:36 - [Lobby/15] Player 0 picked up 06010134 (9 Meseta) [PlayerInventory] Meseta: 81 @@ -3279,8 +3245,6 @@ I 49108 2023-05-26 16:21:36 - [Commands] Received from C-2 (Jess) (version=GC co I 49108 2023-05-26 16:21:36 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 35 01 01 06 01 00 00 00 | b Z 5 I 49108 2023-05-26 16:21:36 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 35 01 01 06 01 00 00 00 | b Z 5 -I 49108 2023-05-26 16:21:36 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 35 01 01 06 | ` Y 5 I 49108 2023-05-26 16:21:36 - [Lobby/15] Player 0 picked up 06010135 (Disk:Foie Lv.1) [PlayerInventory] Meseta: 81 @@ -3314,8 +3278,6 @@ I 49108 2023-05-26 16:21:37 - [Commands] Received from C-2 (Jess) (version=GC co I 49108 2023-05-26 16:21:37 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 33 01 01 06 01 00 00 00 | b Z 3 I 49108 2023-05-26 16:21:37 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 33 01 01 06 01 00 00 00 | b Z 3 -I 49108 2023-05-26 16:21:37 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 33 01 01 06 | ` Y 3 I 49108 2023-05-26 16:21:37 - [Lobby/15] Player 0 picked up 06010133 (Saber 0/0/0/0/0) [PlayerInventory] Meseta: 81 @@ -3911,8 +3873,6 @@ I 49108 2023-05-26 16:22:13 - [Commands] Received from C-2 (Jess) (version=GC co I 49108 2023-05-26 16:22:13 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 3A 01 01 06 01 00 00 00 | b Z : I 49108 2023-05-26 16:22:13 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 3A 01 01 06 01 00 00 00 | b Z : -I 49108 2023-05-26 16:22:13 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 3A 01 01 06 | ` Y : I 49108 2023-05-26 16:22:13 - [Lobby/15] Player 0 picked up 0601013A (4 Meseta) [PlayerInventory] Meseta: 85 @@ -3987,8 +3947,6 @@ I 49108 2023-05-26 16:22:15 - [Commands] Received from C-2 (Jess) (version=GC co I 49108 2023-05-26 16:22:15 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 39 01 01 06 01 00 00 00 | b Z 9 I 49108 2023-05-26 16:22:15 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 39 01 01 06 01 00 00 00 | b Z 9 -I 49108 2023-05-26 16:22:15 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 39 01 01 06 | ` Y 9 I 49108 2023-05-26 16:22:15 - [Lobby/15] Player 0 picked up 06010139 (6 Meseta) [PlayerInventory] Meseta: 91 @@ -4123,8 +4081,6 @@ I 49108 2023-05-26 16:22:24 - [Commands] Received from C-2 (Jess) (version=GC co I 49108 2023-05-26 16:22:25 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 E7 0C 01 06 01 00 00 00 | b Z I 49108 2023-05-26 16:22:25 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 E7 0C 01 06 01 00 00 00 | b Z -I 49108 2023-05-26 16:22:25 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 E7 0C 01 06 | ` Y I 49108 2023-05-26 16:22:25 - [Lobby/15] Player 0 picked up 06010CE7 (18 Meseta) [PlayerInventory] Meseta: 109 @@ -4429,8 +4385,6 @@ I 49108 2023-05-26 16:22:44 - [Commands] Received from C-2 (Jess) (version=GC co I 49108 2023-05-26 16:22:45 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 22 01 01 06 01 00 00 00 | b Z " I 49108 2023-05-26 16:22:45 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 22 01 01 06 01 00 00 00 | b Z " -I 49108 2023-05-26 16:22:45 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 22 01 01 06 | ` Y " I 49108 2023-05-26 16:22:45 - [Lobby/15] Player 0 picked up 06010122 (Monofluid x1) [PlayerInventory] Meseta: 109 @@ -4463,8 +4417,6 @@ I 49108 2023-05-26 16:22:46 - [Commands] Received from C-2 (Jess) (version=GC co I 49108 2023-05-26 16:22:46 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 21 01 01 06 01 00 00 00 | b Z ! I 49108 2023-05-26 16:22:46 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 21 01 01 06 01 00 00 00 | b Z ! -I 49108 2023-05-26 16:22:46 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 21 01 01 06 | ` Y ! I 49108 2023-05-26 16:22:46 - [Lobby/15] Player 0 picked up 06010121 (Monofluid x1) [PlayerInventory] Meseta: 109 @@ -4496,12 +4448,10 @@ I 49108 2023-05-26 16:22:46 - [Commands] Received from C-2 (Jess) (version=GC co 0010 | 00 00 00 00 | I 49108 2023-05-26 16:22:47 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 1F 01 01 06 01 00 00 00 | b Z -I 49108 2023-05-26 16:22:47 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 1F 01 01 06 01 00 00 00 | b Z I 49108 2023-05-26 16:22:47 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 14 00 40 04 00 00 D7 9D FE C3 78 DF 17 C4 | ` @ x 0010 | 00 00 00 00 | -I 49108 2023-05-26 16:22:49 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) +I 49108 2023-05-26 16:22:47 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 1F 01 01 06 | ` Y I 49108 2023-05-26 16:22:49 - [Lobby/15] Player 0 picked up 0601011F (9 Meseta) [PlayerInventory] Meseta: 118 @@ -4530,8 +4480,6 @@ I 49108 2023-05-26 16:22:49 - [Commands] Received from C-2 (Jess) (version=GC co 0010 | 00 00 00 00 | I 49108 2023-05-26 16:22:49 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 20 01 01 06 01 00 00 00 | b Z -I 49108 2023-05-26 16:22:49 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 20 01 01 06 01 00 00 00 | b Z I 49108 2023-05-26 16:22:49 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 42 03 00 00 FB A4 F8 C3 F9 8A 15 C4 | ` B I 49108 2023-05-26 16:22:49 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) @@ -4556,7 +4504,7 @@ I 49108 2023-05-26 16:22:49 - [Commands] Received from C-2 (Jess) (version=GC co 0000 | 60 00 10 00 42 03 00 00 8C CA FB C3 49 C6 05 C4 | ` B I I 49108 2023-05-26 16:22:49 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 42 03 00 00 E6 26 F8 C3 19 AD 05 C4 | ` B & -I 49108 2023-05-26 16:22:49 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) +I 49108 2023-05-26 16:22:49 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 20 01 01 06 | ` Y I 49108 2023-05-26 16:22:49 - [Lobby/15] Player 0 picked up 06010120 (9 Meseta) [PlayerInventory] Meseta: 127 @@ -4846,8 +4794,6 @@ I 49108 2023-05-26 16:23:05 - [Commands] Received from C-2 (Jess) (version=GC co I 49108 2023-05-26 16:23:06 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 26 01 01 06 01 00 00 00 | b Z & I 49108 2023-05-26 16:23:06 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 26 01 01 06 01 00 00 00 | b Z & -I 49108 2023-05-26 16:23:06 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 26 01 01 06 | ` Y & I 49108 2023-05-26 16:23:06 - [Lobby/15] Player 0 picked up 06010126 (Handgun 0/0/0/0/0) [PlayerInventory] Meseta: 127 @@ -4880,11 +4826,9 @@ I 49108 2023-05-26 16:23:06 - [Commands] Received from C-2 (Jess) (version=GC co 0000 | 60 00 10 00 42 03 00 00 4E D5 98 C3 CF 3D 13 C4 | ` B N = I 49108 2023-05-26 16:23:06 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 24 01 01 06 01 00 00 00 | b Z $ -I 49108 2023-05-26 16:23:06 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 24 01 01 06 01 00 00 00 | b Z $ I 49108 2023-05-26 16:23:06 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 42 03 00 00 4D 20 95 C3 43 86 13 C4 | ` B M C -I 49108 2023-05-26 16:23:09 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) +I 49108 2023-05-26 16:23:06 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 24 01 01 06 | ` Y $ I 49108 2023-05-26 16:23:09 - [Lobby/15] Player 0 picked up 06010124 (Saber 0/0/0/0/0) [PlayerInventory] Meseta: 127 @@ -4983,8 +4927,6 @@ I 49108 2023-05-26 16:23:12 - [Commands] Received from C-2 (Jess) (version=GC co I 49108 2023-05-26 16:23:12 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 D8 0C 01 06 01 00 00 00 | b Z I 49108 2023-05-26 16:23:12 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 D8 0C 01 06 01 00 00 00 | b Z -I 49108 2023-05-26 16:23:12 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 D8 0C 01 06 | ` Y I 49108 2023-05-26 16:23:12 - [Lobby/15] Player 0 picked up 06010CD8 (Frame +1EVP) [PlayerInventory] Meseta: 127 @@ -5116,8 +5058,6 @@ I 49108 2023-05-26 16:23:25 - [Commands] Received from C-2 (Jess) (version=GC co I 49108 2023-05-26 16:23:25 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 DA 0C 01 06 01 00 00 00 | b Z I 49108 2023-05-26 16:23:25 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 DA 0C 01 06 01 00 00 00 | b Z -I 49108 2023-05-26 16:23:25 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 DA 0C 01 06 | ` Y I 49108 2023-05-26 16:23:25 - [Lobby/15] Player 0 picked up 06010CDA (Frame +1DEF +1EVP) [PlayerInventory] Meseta: 127 @@ -5663,8 +5603,6 @@ I 49108 2023-05-26 16:24:05 - [Commands] Received from C-2 (Jess) (version=GC co I 49108 2023-05-26 16:24:05 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 42 01 01 06 01 00 00 00 | b Z B I 49108 2023-05-26 16:24:05 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 42 01 01 06 01 00 00 00 | b Z B -I 49108 2023-05-26 16:24:05 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 42 01 01 06 | ` Y B I 49108 2023-05-26 16:24:05 - [Lobby/15] Player 0 picked up 06010142 (7 Meseta) [PlayerInventory] Meseta: 134 @@ -5701,8 +5639,6 @@ I 49108 2023-05-26 16:24:05 - [Commands] Received from C-2 (Jess) (version=GC co I 49108 2023-05-26 16:24:05 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 43 01 01 06 01 00 00 00 | b Z C I 49108 2023-05-26 16:24:05 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 43 01 01 06 01 00 00 00 | b Z C -I 49108 2023-05-26 16:24:05 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 43 01 01 06 | ` Y C I 49108 2023-05-26 16:24:05 - [Lobby/15] Player 0 picked up 06010143 (Monomate x1) [PlayerInventory] Meseta: 134 @@ -5740,8 +5676,6 @@ I 49108 2023-05-26 16:24:06 - [Commands] Received from C-2 (Jess) (version=GC co I 49108 2023-05-26 16:24:06 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 49 01 01 06 01 00 00 00 | b Z I I 49108 2023-05-26 16:24:06 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 49 01 01 06 01 00 00 00 | b Z I -I 49108 2023-05-26 16:24:06 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 49 01 01 06 | ` Y I I 49108 2023-05-26 16:24:06 - [Lobby/15] Player 0 picked up 06010149 (Monofluid x1) [PlayerInventory] Meseta: 134 @@ -5828,8 +5762,6 @@ I 49108 2023-05-26 16:24:10 - [Commands] Received from C-2 (Jess) (version=GC co I 49108 2023-05-26 16:24:10 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 F8 0C 01 06 01 00 00 00 | b Z I 49108 2023-05-26 16:24:10 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 F8 0C 01 06 01 00 00 00 | b Z -I 49108 2023-05-26 16:24:10 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 F8 0C 01 06 | ` Y I 49108 2023-05-26 16:24:10 - [Lobby/15] Player 0 picked up 06010CF8 (Telepipe x1) [PlayerInventory] Meseta: 134 @@ -5904,8 +5836,6 @@ I 49108 2023-05-26 16:24:14 - [Commands] Received from C-2 (Jess) (version=GC co I 49108 2023-05-26 16:24:14 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 FA 0C 01 06 01 00 00 00 | b Z I 49108 2023-05-26 16:24:14 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 FA 0C 01 06 01 00 00 00 | b Z -I 49108 2023-05-26 16:24:14 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 FA 0C 01 06 | ` Y I 49108 2023-05-26 16:24:14 - [Lobby/15] Player 0 picked up 06010CFA (Frame +1DEF +1EVP) [PlayerInventory] Meseta: 134 @@ -6199,11 +6129,9 @@ I 49108 2023-05-26 16:24:30 - [Commands] Received from C-2 (Jess) (version=GC co 0010 | 00 00 00 00 | I 49108 2023-05-26 16:24:30 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 46 01 01 06 01 00 00 00 | b Z F -I 49108 2023-05-26 16:24:30 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 46 01 01 06 01 00 00 00 | b Z F I 49108 2023-05-26 16:24:30 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 42 03 00 00 14 5E 83 43 5E 02 9A C3 | ` B ^ C^ -I 49108 2023-05-26 16:24:32 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) +I 49108 2023-05-26 16:24:30 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 46 01 01 06 | ` Y F I 49108 2023-05-26 16:24:32 - [Lobby/15] Player 0 picked up 06010146 (7 Meseta) [PlayerInventory] Meseta: 141 @@ -6242,8 +6170,6 @@ I 49108 2023-05-26 16:24:32 - [Commands] Received from C-2 (Jess) (version=GC co 0000 | 60 00 10 00 42 03 00 00 68 69 8A 43 80 BC A2 C3 | ` B hi C I 49108 2023-05-26 16:24:32 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 48 01 01 06 01 00 00 00 | b Z H -I 49108 2023-05-26 16:24:32 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 48 01 01 06 01 00 00 00 | b Z H I 49108 2023-05-26 16:24:32 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 42 03 00 00 D3 16 87 43 25 FA A4 C3 | ` B C% I 49108 2023-05-26 16:24:32 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) @@ -6263,7 +6189,7 @@ I 49108 2023-05-26 16:24:32 - [Commands] Received from C-2 (Jess) (version=GC co I 49108 2023-05-26 16:24:32 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 1C 00 3E 06 00 00 00 00 71 00 01 00 07 00 | ` > q 0010 | 96 E3 71 43 D3 D1 9B 40 43 A3 99 C3 | qC @C -I 49108 2023-05-26 16:24:32 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) +I 49108 2023-05-26 16:24:32 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 48 01 01 06 | ` Y H I 49108 2023-05-26 16:24:32 - [Lobby/15] Player 0 picked up 06010148 (Monofluid x1) [PlayerInventory] Meseta: 141 @@ -6392,11 +6318,9 @@ I 49108 2023-05-26 16:24:39 - [Commands] Received from C-2 (Jess) (version=GC co 0000 | 60 00 10 00 42 03 00 00 F5 82 19 43 3D 03 8A C3 | ` B C= I 49108 2023-05-26 16:24:39 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 F2 0C 01 06 01 00 00 00 | b Z -I 49108 2023-05-26 16:24:39 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 F2 0C 01 06 01 00 00 00 | b Z I 49108 2023-05-26 16:24:39 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 42 03 00 00 99 54 17 43 1D 7E 86 C3 | ` B T C ~ -I 49108 2023-05-26 16:24:42 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) +I 49108 2023-05-26 16:24:39 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 F2 0C 01 06 | ` Y I 49108 2023-05-26 16:24:42 - [Lobby/15] Player 0 picked up 06010CF2 (Cane 5/0/0/0/0) [PlayerInventory] Meseta: 141 @@ -6554,8 +6478,6 @@ I 49108 2023-05-26 16:24:48 - [Commands] Received from C-2 (Jess) (version=GC co I 49108 2023-05-26 16:24:48 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 4A 01 01 06 01 00 00 00 | b Z J I 49108 2023-05-26 16:24:48 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 4A 01 01 06 01 00 00 00 | b Z J -I 49108 2023-05-26 16:24:48 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 4A 01 01 06 | ` Y J I 49108 2023-05-26 16:24:48 - [Lobby/15] Player 0 picked up 0601014A (7 Meseta) [PlayerInventory] Meseta: 148 @@ -6594,8 +6516,6 @@ I 49108 2023-05-26 16:24:48 - [Commands] Received from C-2 (Jess) (version=GC co I 49108 2023-05-26 16:24:48 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 47 01 01 06 01 00 00 00 | b Z G I 49108 2023-05-26 16:24:48 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 47 01 01 06 01 00 00 00 | b Z G -I 49108 2023-05-26 16:24:48 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 47 01 01 06 | ` Y G I 49108 2023-05-26 16:24:48 - [Lobby/15] Player 0 picked up 06010147 (3 Meseta) [PlayerInventory] Meseta: 151 @@ -7078,8 +6998,6 @@ I 49108 2023-05-26 16:25:13 - [Commands] Received from C-2 (Jess) (version=GC co I 49108 2023-05-26 16:25:13 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 2E 01 01 06 01 00 00 00 | b Z . I 49108 2023-05-26 16:25:13 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 2E 01 01 06 01 00 00 00 | b Z . -I 49108 2023-05-26 16:25:14 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 2E 01 01 06 | ` Y . I 49108 2023-05-26 16:25:14 - [Lobby/15] Player 0 picked up 0601012E (Monofluid x1) [PlayerInventory] Meseta: 151 @@ -7284,8 +7202,6 @@ I 49108 2023-05-26 16:25:24 - [Commands] Received from C-2 (Jess) (version=GC co I 49108 2023-05-26 16:25:24 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 DC 0C 01 06 01 00 00 00 | b Z I 49108 2023-05-26 16:25:24 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 DC 0C 01 06 01 00 00 00 | b Z -I 49108 2023-05-26 16:25:24 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 DC 0C 01 06 | ` Y I 49108 2023-05-26 16:25:24 - [Lobby/15] Player 0 picked up 06010CDC (Saber 0/0/0/0/0) [PlayerInventory] Meseta: 151 @@ -7325,8 +7241,6 @@ I 49108 2023-05-26 16:25:24 - [Commands] Received from C-2 (Jess) (version=GC co I 49108 2023-05-26 16:25:24 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 DE 0C 01 06 01 00 00 00 | b Z I 49108 2023-05-26 16:25:24 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 DE 0C 01 06 01 00 00 00 | b Z -I 49108 2023-05-26 16:25:24 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 DE 0C 01 06 | ` Y I 49108 2023-05-26 16:25:24 - [Lobby/15] Player 0 picked up 06010CDE (Monomate x1) [PlayerInventory] Meseta: 151 @@ -7388,8 +7302,6 @@ I 49108 2023-05-26 16:25:26 - [Commands] Received from C-2 (Jess) (version=GC co I 49108 2023-05-26 16:25:27 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 E2 0C 01 06 01 00 00 00 | b Z I 49108 2023-05-26 16:25:27 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 E2 0C 01 06 01 00 00 00 | b Z -I 49108 2023-05-26 16:25:27 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 E2 0C 01 06 | ` Y I 49108 2023-05-26 16:25:27 - [Lobby/15] Player 0 picked up 06010CE2 (Cane 0/0/0/0/0) [PlayerInventory] Meseta: 151 @@ -7436,8 +7348,6 @@ I 49108 2023-05-26 16:25:27 - [Commands] Received from C-2 (Jess) (version=GC co I 49108 2023-05-26 16:25:27 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 2C 01 01 06 01 00 00 00 | b Z , I 49108 2023-05-26 16:25:27 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 2C 01 01 06 01 00 00 00 | b Z , -I 49108 2023-05-26 16:25:27 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 2C 01 01 06 | ` Y , I 49108 2023-05-26 16:25:27 - [Lobby/15] Player 0 picked up 0601012C (3 Meseta) [PlayerInventory] Meseta: 154 @@ -7674,8 +7584,6 @@ I 49108 2023-05-26 16:25:42 - [Commands] Received from C-2 (Jess) (version=GC co I 49108 2023-05-26 16:25:42 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 5C 01 01 06 01 00 00 00 | b Z \ I 49108 2023-05-26 16:25:42 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 5C 01 01 06 01 00 00 00 | b Z \ -I 49108 2023-05-26 16:25:42 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 5C 01 01 06 | ` Y \ I 49108 2023-05-26 16:25:42 - [Lobby/15] Player 0 picked up 0601015C (7 Meseta) [PlayerInventory] Meseta: 161 @@ -7718,8 +7626,6 @@ I 49108 2023-05-26 16:25:42 - [Commands] Received from C-2 (Jess) (version=GC co I 49108 2023-05-26 16:25:42 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 60 01 01 06 01 00 00 00 | b Z ` I 49108 2023-05-26 16:25:42 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 60 01 01 06 01 00 00 00 | b Z ` -I 49108 2023-05-26 16:25:42 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 60 01 01 06 | ` Y ` I 49108 2023-05-26 16:25:42 - [Lobby/15] Player 0 picked up 06010160 (Antiparalysis x1) [PlayerInventory] Meseta: 161 @@ -7832,8 +7738,6 @@ I 49108 2023-05-26 16:25:47 - [Commands] Received from C-2 (Jess) (version=GC co I 49108 2023-05-26 16:25:47 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 02 0D 01 06 01 00 00 00 | b Z I 49108 2023-05-26 16:25:47 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 02 0D 01 06 01 00 00 00 | b Z -I 49108 2023-05-26 16:25:47 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 02 0D 01 06 | ` Y I 49108 2023-05-26 16:25:47 - [Lobby/15] Player 0 picked up 06010D02 (Handgun 0/0/0/0/0) [PlayerInventory] Meseta: 161 @@ -7948,8 +7852,6 @@ I 49108 2023-05-26 16:25:52 - [Commands] Received from C-2 (Jess) (version=GC co I 49108 2023-05-26 16:25:52 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 00 0D 01 06 01 00 00 00 | b Z I 49108 2023-05-26 16:25:52 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 00 0D 01 06 01 00 00 00 | b Z -I 49108 2023-05-26 16:25:52 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 00 0D 01 06 | ` Y I 49108 2023-05-26 16:25:52 - [Lobby/15] Player 0 picked up 06010D00 (10 Meseta) [PlayerInventory] Meseta: 171 @@ -8056,8 +7958,6 @@ I 49108 2023-05-26 16:25:56 - [Commands] Received from C-2 (Jess) (version=GC co I 49108 2023-05-26 16:25:56 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 5D 01 01 06 01 00 00 00 | b Z ] I 49108 2023-05-26 16:25:56 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 5D 01 01 06 01 00 00 00 | b Z ] -I 49108 2023-05-26 16:25:56 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 5D 01 01 06 | ` Y ] I 49108 2023-05-26 16:25:56 - [Lobby/15] Player 0 picked up 0601015D (8 Meseta) [PlayerInventory] Meseta: 179 @@ -8098,11 +7998,9 @@ I 49108 2023-05-26 16:25:56 - [Commands] Received from C-2 (Jess) (version=GC co 0000 | 60 00 10 00 42 03 00 00 F5 87 EE 43 79 1D FB 40 | ` B Cy @ I 49108 2023-05-26 16:25:57 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 5E 01 01 06 01 00 00 00 | b Z ^ -I 49108 2023-05-26 16:25:57 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 5E 01 01 06 01 00 00 00 | b Z ^ I 49108 2023-05-26 16:25:57 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 42 03 00 00 68 20 F2 43 F0 AD 03 41 | ` B h C A -I 49108 2023-05-26 16:25:59 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) +I 49108 2023-05-26 16:25:57 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 5E 01 01 06 | ` Y ^ I 49108 2023-05-26 16:25:59 - [Lobby/15] Player 0 picked up 0601015E (4 Meseta) [PlayerInventory] Meseta: 183 @@ -8641,8 +8539,6 @@ I 49108 2023-05-26 16:26:45 - [Commands] Received from C-2 (Jess) (version=GC co I 49108 2023-05-26 16:26:45 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 33 0D 01 06 02 00 00 00 | b Z 3 I 49108 2023-05-26 16:26:45 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 33 0D 01 06 02 00 00 00 | b Z 3 -I 49108 2023-05-26 16:26:45 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 02 00 33 0D 01 06 | ` Y 3 I 49108 2023-05-26 16:26:45 - [Lobby/15] Player 0 picked up 06010D33 (9 Meseta) [PlayerInventory] Meseta: 192 @@ -9340,8 +9236,6 @@ I 49108 2023-05-26 16:27:27 - [Commands] Received from C-2 (Jess) (version=GC co I 49108 2023-05-26 16:27:27 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 93 01 01 06 02 00 00 00 | b Z I 49108 2023-05-26 16:27:27 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 93 01 01 06 02 00 00 00 | b Z -I 49108 2023-05-26 16:27:27 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 02 00 93 01 01 06 | ` Y I 49108 2023-05-26 16:27:27 - [Lobby/15] Player 0 picked up 06010193 (14 Meseta) [PlayerInventory] Meseta: 206 @@ -9382,11 +9276,9 @@ I 49108 2023-05-26 16:27:27 - [Commands] Received from C-2 (Jess) (version=GC co 0000 | 60 00 10 00 42 03 00 00 A4 4A 12 C4 10 CB 0E C4 | ` B J I 49108 2023-05-26 16:27:27 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 94 01 01 06 02 00 00 00 | b Z -I 49108 2023-05-26 16:27:27 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 94 01 01 06 02 00 00 00 | b Z I 49108 2023-05-26 16:27:27 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 42 03 00 00 F1 27 14 C4 A3 D2 0E C4 | ` B ' -I 49108 2023-05-26 16:27:30 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) +I 49108 2023-05-26 16:27:27 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 02 00 94 01 01 06 | ` Y I 49108 2023-05-26 16:27:30 - [Lobby/15] Player 0 picked up 06010194 (15 Meseta) [PlayerInventory] Meseta: 221 @@ -9439,8 +9331,6 @@ I 49108 2023-05-26 16:27:30 - [Commands] Received from C-2 (Jess) (version=GC co 0000 | 60 00 10 00 42 03 00 00 39 24 0E C4 37 1D 17 C4 | ` B 9$ 7 I 49108 2023-05-26 16:27:30 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 95 01 01 06 02 00 00 00 | b Z -I 49108 2023-05-26 16:27:30 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 95 01 01 06 02 00 00 00 | b Z I 49108 2023-05-26 16:27:30 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 42 03 00 00 4C 5D 0C C4 B7 B4 16 C4 | ` B L] I 49108 2023-05-26 16:27:30 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) @@ -9453,7 +9343,7 @@ I 49108 2023-05-26 16:27:30 - [Commands] Received from C-2 (Jess) (version=GC co 0000 | 60 00 10 00 42 03 00 00 FF 32 09 C4 DC AD 12 C4 | ` B 2 I 49108 2023-05-26 16:27:30 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 42 03 00 00 97 0C 0A C4 42 FB 10 C4 | ` B B -I 49108 2023-05-26 16:27:30 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) +I 49108 2023-05-26 16:27:30 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 02 00 95 01 01 06 | ` Y I 49108 2023-05-26 16:27:30 - [Lobby/15] Player 0 picked up 06010195 (9 Meseta) [PlayerInventory] Meseta: 230 @@ -9634,8 +9524,6 @@ I 49108 2023-05-26 16:27:41 - [Commands] Received from C-2 (Jess) (version=GC co I 49108 2023-05-26 16:27:42 - [Commands] Received from C-2 (Jess) (version=GC command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 16 0D 01 06 02 00 00 00 | b Z I 49108 2023-05-26 16:27:42 - [Commands] Sending to C-2 (Jess) (version=GC command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 16 0D 01 06 02 00 00 00 | b Z -I 49108 2023-05-26 16:27:42 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 02 00 16 0D 01 06 | ` Y I 49108 2023-05-26 16:27:42 - [Lobby/15] Player 0 picked up 06010D16 (Monomate x1) [PlayerInventory] Meseta: 230 diff --git a/tests/PC-BasicGame.test.txt b/tests/PC-BasicGame.test.txt index 83e938f2..65575cbd 100644 --- a/tests/PC-BasicGame.test.txt +++ b/tests/PC-BasicGame.test.txt @@ -752,8 +752,6 @@ I 49484 2023-05-26 16:36:40 - [Commands] Sending to C-3 (Tali) (version=PC comma I 49484 2023-05-26 16:36:40 - [Commands] Received from C-3 (Tali) (version=PC command=62 flag=00) 0000 | 10 00 62 00 5A 03 00 00 05 00 01 00 01 00 00 00 | b Z I 49484 2023-05-26 16:36:40 - [Commands] Sending to C-3 (Tali) (version=PC command=62 flag=00) -0000 | 10 00 62 00 5A 03 00 00 05 00 01 00 01 00 00 00 | b Z -I 49484 2023-05-26 16:36:40 - [Commands] Received from C-3 (Tali) (version=PC command=60 flag=00) 0000 | 10 00 60 00 59 03 00 00 00 00 01 00 05 00 01 00 | ` Y I 49484 2023-05-26 16:36:40 - [Lobby/15] Player 0 picked up 00010005 (Monomate x4) [PlayerInventory] Meseta: 300 diff --git a/tests/XB-ForestGame.test.txt b/tests/XB-ForestGame.test.txt index ab4dcece..702b8b56 100644 --- a/tests/XB-ForestGame.test.txt +++ b/tests/XB-ForestGame.test.txt @@ -692,12 +692,10 @@ I 16496 2023-11-08 01:55:22 - [Commands] Received from C-2 (Tali) (version=XB co 0010 | A5 E1 11 44 61 50 08 40 F9 8A 0E 44 | DaP @ D I 16496 2023-11-08 01:55:22 - [Commands] Received from C-2 (Tali) (version=XB command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 7B 01 01 06 01 00 00 00 | b Z { -I 16496 2023-11-08 01:55:22 - [Commands] Sending to C-2 (Tali) (version=XB command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 7B 01 01 06 01 00 00 00 | b Z { I 16496 2023-11-08 01:55:22 - [Commands] Received from C-2 (Tali) (version=XB command=60 flag=00) 0000 | 60 00 14 00 40 04 00 00 D2 F9 0F 44 71 26 0F 44 | ` @ Dq& D 0010 | 00 00 00 00 | -I 16496 2023-11-08 01:55:22 - [Commands] Received from C-2 (Tali) (version=XB command=60 flag=00) +I 16496 2023-11-08 01:55:22 - [Commands] Sending to C-2 (Tali) (version=XB command=62 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 7B 01 01 06 | ` Y { I 16496 2023-11-08 01:55:22 - [Lobby:15] Player 0 picked up 0601017B (Monomate x1) [PlayerInventory] Meseta: 0 @@ -819,13 +817,11 @@ I 16496 2023-11-08 01:55:32 - [Commands] Received from C-2 (Tali) (version=XB co 0000 | 60 00 10 00 42 03 00 00 84 77 E3 43 B3 77 D2 43 | ` B w C w C I 16496 2023-11-08 01:55:32 - [Commands] Received from C-2 (Tali) (version=XB command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 7D 01 01 06 01 00 00 00 | b Z } -I 16496 2023-11-08 01:55:32 - [Commands] Sending to C-2 (Tali) (version=XB command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 7D 01 01 06 01 00 00 00 | b Z } I 16496 2023-11-08 01:55:33 - [Commands] Received from C-2 (Tali) (version=XB command=60 flag=00) 0000 | 60 00 10 00 42 03 00 00 66 69 E0 43 02 4B D0 43 | ` B fi C K C I 16496 2023-11-08 01:55:33 - [Commands] Received from C-2 (Tali) (version=XB command=60 flag=00) 0000 | 60 00 10 00 42 03 00 00 E1 48 DD 43 8E 8D D1 43 | ` B H C C -I 16496 2023-11-08 01:55:33 - [Commands] Received from C-2 (Tali) (version=XB command=60 flag=00) +I 16496 2023-11-08 01:55:32 - [Commands] Sending to C-2 (Tali) (version=XB command=62 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 7D 01 01 06 | ` Y } I 16496 2023-11-08 01:55:33 - [Lobby:15] Player 0 picked up 0601017D (7 Meseta) [PlayerInventory] Meseta: 7 @@ -1028,12 +1024,10 @@ I 16496 2023-11-08 01:55:56 - [Commands] Received from C-2 (Tali) (version=XB co 0010 | 00 00 00 00 | I 16496 2023-11-08 01:55:56 - [Commands] Received from C-2 (Tali) (version=XB command=62 flag=00) 0000 | 62 00 10 00 5A 03 00 00 11 0D 01 06 01 00 00 00 | b Z -I 16496 2023-11-08 01:55:56 - [Commands] Sending to C-2 (Tali) (version=XB command=62 flag=00) -0000 | 62 00 10 00 5A 03 00 00 11 0D 01 06 01 00 00 00 | b Z I 16496 2023-11-08 01:55:57 - [Commands] Received from C-2 (Tali) (version=XB command=60 flag=00) 0000 | 60 00 1C 00 3E 06 00 00 00 00 1E CC 01 00 0B 00 | ` > 0010 | 04 B6 5A 43 3D 18 BB 41 66 11 AC 43 | ZC= Af C -I 16496 2023-11-08 01:55:57 - [Commands] Received from C-2 (Tali) (version=XB command=60 flag=00) +I 16496 2023-11-08 01:55:56 - [Commands] Sending to C-2 (Tali) (version=XB command=62 flag=00) 0000 | 60 00 10 00 59 03 00 00 00 00 01 00 11 0D 01 06 | ` Y I 16496 2023-11-08 01:55:57 - [Lobby:15] Player 0 picked up 06010D11 (20 Meseta) [PlayerInventory] Meseta: 27 diff --git a/tests/config.json b/tests/config.json index b21bd4ec..3bfb173c 100644 --- a/tests/config.json +++ b/tests/config.json @@ -11,8 +11,12 @@ "CatchHandlerExceptions": false, "PersistentGameIdleTimeout": 1800000000, - "ItemDropMode": "OnByDefault", - "UseServerItemTables": "OffByDefault", + "AllowedDropModesV1V2": 0x1F, + "AllowedDropModesV3": 0x1F, + "AllowedDropModesV4": 0x1D, + "DefaultDropModeV1V2": "CLIENT", + "DefaultDropModeV3": "CLIENT", + "DefaultDropModeV4": "SERVER_SHARED", "LocalAddress": "en0", "ExternalAddress": "en0", @@ -20,7 +24,6 @@ "DNSServerPort": 0, "IPStackListen": [], "PPPStackListen": [], - "EnableItemTracking": true, "Episode3BehaviorFlags": 0xFA, "Episode3InfiniteMeseta": false,