diff --git a/src/ItemCreator.cc b/src/ItemCreator.cc index fe8faf9f..63c5000a 100644 --- a/src/ItemCreator.cc +++ b/src/ItemCreator.cc @@ -480,7 +480,7 @@ void ItemCreator::set_item_unidentified_flag_if_not_challenge(ItemData& item) co void ItemCreator::set_tool_item_amount_to_1(ItemData& item) const { if (item.data1[0] == 0x03) { - item.set_tool_item_amount(1); + item.set_tool_item_amount(this->version, 1); } } @@ -1686,7 +1686,7 @@ ItemCreator::DropResult ItemCreator::on_specialized_box_item_drop( return res; } -ItemData ItemCreator::base_item_for_specialized_box(uint32_t def0, uint32_t def1, uint32_t def2) { +ItemData ItemCreator::base_item_for_specialized_box(uint32_t def0, uint32_t def1, uint32_t def2) const { ItemData item; item.data1[0] = (def0 >> 0x18) & 0x0F; item.data1[1] = (def0 >> 0x10) + ((item.data1[0] == 0x00) || (item.data1[0] == 0x01)); @@ -1715,7 +1715,7 @@ ItemData ItemCreator::base_item_for_specialized_box(uint32_t def0, uint32_t def1 if (item.data1[1] == 0x02) { item.data1[4] = def0 & 0xFF; } - item.set_tool_item_amount(1); + item.set_tool_item_amount(this->version, 1); break; case 0x04: item.data2d = ((def1 >> 0x10) & 0xFFFF) * 10; diff --git a/src/ItemCreator.hh b/src/ItemCreator.hh index c8bcf6bb..e1d6c1cf 100644 --- a/src/ItemCreator.hh +++ b/src/ItemCreator.hh @@ -43,7 +43,7 @@ public: 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); + ItemData base_item_for_specialized_box(uint32_t def0, uint32_t def1, uint32_t def2) const; std::vector generate_armor_shop_contents(size_t player_level); std::vector generate_tool_shop_contents(size_t player_level); diff --git a/src/ItemData.cc b/src/ItemData.cc index 02caeea3..8edf4d74 100644 --- a/src/ItemData.cc +++ b/src/ItemData.cc @@ -84,7 +84,7 @@ uint32_t ItemData::primary_identifier() const { } } -bool ItemData::is_wrapped() const { +bool ItemData::is_wrapped(Version version) const { switch (this->data1[0]) { case 0: case 1: @@ -92,7 +92,7 @@ bool ItemData::is_wrapped() const { case 2: return this->data2[2] & 0x40; case 3: - return !this->is_stackable() && (this->data1[3] & 0x40); + return !this->is_stackable(version) && (this->data1[3] & 0x40); case 4: return false; default: @@ -100,7 +100,7 @@ bool ItemData::is_wrapped() const { } } -void ItemData::wrap() { +void ItemData::wrap(Version version) { switch (this->data1[0]) { case 0: case 1: @@ -110,7 +110,7 @@ void ItemData::wrap() { this->data2[2] |= 0x40; break; case 3: - if (!this->is_stackable()) { + if (!this->is_stackable(version)) { this->data1[3] |= 0x40; } break; @@ -121,7 +121,7 @@ void ItemData::wrap() { } } -void ItemData::unwrap() { +void ItemData::unwrap(Version version) { switch (this->data1[0]) { case 0: case 1: @@ -131,7 +131,7 @@ void ItemData::unwrap() { this->data2[2] &= 0xBF; break; case 3: - if (!this->is_stackable()) { + if (!this->is_stackable(version)) { this->data1[3] &= 0xBF; } break; @@ -142,23 +142,23 @@ void ItemData::unwrap() { } } -bool ItemData::is_stackable() const { - return this->max_stack_size() > 1; +bool ItemData::is_stackable(Version version) const { + return this->max_stack_size(version) > 1; } -size_t ItemData::stack_size() const { - if (max_stack_size_for_item(this->data1[0], this->data1[1]) > 1) { +size_t ItemData::stack_size(Version version) const { + if (max_stack_size_for_item(version, this->data1[0], this->data1[1]) > 1) { return this->data1[5]; } return 1; } -size_t ItemData::max_stack_size() const { - return max_stack_size_for_item(this->data1[0], this->data1[1]); +size_t ItemData::max_stack_size(Version version) const { + return max_stack_size_for_item(version, this->data1[0], this->data1[1]); } -void ItemData::enforce_min_stack_size() { - if (this->stack_size() == 0) { +void ItemData::enforce_min_stack_size(Version version) { + if (this->stack_size(version) == 0) { this->data1[5] = 1; } } @@ -502,12 +502,12 @@ void ItemData::set_sealed_item_kill_count(uint16_t v) { } } -uint8_t ItemData::get_tool_item_amount() const { - return this->is_stackable() ? this->data1[5] : 1; +uint8_t ItemData::get_tool_item_amount(Version version) const { + return this->is_stackable(version) ? this->data1[5] : 1; } -void ItemData::set_tool_item_amount(uint8_t amount) { - if (this->is_stackable()) { +void ItemData::set_tool_item_amount(Version version, uint8_t amount) { + if (this->is_stackable(version)) { this->data1[5] = amount; } else if (this->data1[0] == 0x03) { this->data1[5] = 0x00; diff --git a/src/ItemData.hh b/src/ItemData.hh index 900caaf8..002466b5 100644 --- a/src/ItemData.hh +++ b/src/ItemData.hh @@ -140,14 +140,14 @@ struct ItemData { // 0x14 bytes std::string hex() const; uint32_t primary_identifier() const; - bool is_wrapped() const; - void wrap(); - void unwrap(); + bool is_wrapped(Version version) const; + void wrap(Version version); + void unwrap(Version version); - bool is_stackable() const; - size_t stack_size() const; - size_t max_stack_size() const; - void enforce_min_stack_size(); + bool is_stackable(Version version) const; + size_t stack_size(Version version) const; + size_t max_stack_size(Version version) const; + void enforce_min_stack_size(Version version); static bool is_common_consumable(uint32_t primary_identifier); bool is_common_consumable() const; @@ -166,8 +166,8 @@ struct ItemData { // 0x14 bytes uint16_t get_sealed_item_kill_count() const; void set_sealed_item_kill_count(uint16_t v); - uint8_t get_tool_item_amount() const; - void set_tool_item_amount(uint8_t amount); + uint8_t get_tool_item_amount(Version version) const; + void set_tool_item_amount(Version version, uint8_t amount); int16_t get_armor_or_shield_defense_bonus() const; void set_armor_or_shield_defense_bonus(int16_t bonus); int16_t get_common_armor_evasion_bonus() const; diff --git a/src/ItemNameIndex.cc b/src/ItemNameIndex.cc index 54a7748e..22555cc0 100644 --- a/src/ItemNameIndex.cc +++ b/src/ItemNameIndex.cc @@ -21,9 +21,11 @@ using namespace std; // }; ItemNameIndex::ItemNameIndex( + Version version, std::shared_ptr item_parameter_table, const std::vector& name_coll) - : item_parameter_table(item_parameter_table) { + : version(version), + item_parameter_table(item_parameter_table) { auto find_items_1d = [&](uint64_t data1, size_t position) -> size_t { ItemData item(data1, 0); @@ -176,7 +178,7 @@ std::string ItemNameIndex::describe_item( // flags in a different location. if (((item.data1[1] == 0x01) && (item.data1[4] & 0x40)) || ((item.data1[0] == 0x02) && (item.data2[2] & 0x40)) || - ((item.data1[0] == 0x03) && !item.is_stackable() && (item.data1[3] & 0x40))) { + ((item.data1[0] == 0x03) && !item.is_stackable(this->version) && (item.data1[3] & 0x40))) { ret_tokens.emplace_back("Wrapped"); } @@ -359,7 +361,7 @@ std::string ItemNameIndex::describe_item( // For tools, add the amount (if applicable) } else if (item.data1[0] == 0x03) { - if (item.max_stack_size() > 1) { + if (item.max_stack_size(this->version) > 1) { ret_tokens.emplace_back(string_printf("x%hhu", item.data1[5])); } } @@ -401,7 +403,7 @@ ItemData ItemNameIndex::parse_item_description(const std::string& desc) const { } } } - ret.enforce_min_stack_size(); + ret.enforce_min_stack_size(this->version); return ret; } @@ -635,7 +637,7 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript ret.data2[2] |= 0x40; } } else if (ret.data1[0] == 0x03) { - if (ret.max_stack_size() > 1) { + if (ret.max_stack_size(this->version) > 1) { if (starts_with(desc, "x")) { ret.data1[5] = stoul(desc.substr(1), nullptr, 10); } else { @@ -646,7 +648,7 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript } if (is_wrapped) { - if (ret.is_stackable()) { + if (ret.is_stackable(this->version)) { throw runtime_error("stackable items cannot be wrapped"); } else { ret.data1[3] |= 0x40; diff --git a/src/ItemNameIndex.hh b/src/ItemNameIndex.hh index 15cf76a9..451249d4 100644 --- a/src/ItemNameIndex.hh +++ b/src/ItemNameIndex.hh @@ -19,7 +19,10 @@ public: std::string name; }; - ItemNameIndex(std::shared_ptr pmt, const std::vector& name_coll); + ItemNameIndex( + Version version, + std::shared_ptr pmt, + const std::vector& name_coll); inline size_t entry_count() const { return this->primary_identifier_index.size(); @@ -38,6 +41,7 @@ public: private: ItemData parse_item_description_phase(const std::string& description, bool skip_special) const; + Version version; std::shared_ptr item_parameter_table; std::unordered_map> primary_identifier_index; diff --git a/src/Items.cc b/src/Items.cc index f3b09bb5..572bcbc5 100644 --- a/src/Items.cc +++ b/src/Items.cc @@ -111,9 +111,9 @@ void player_use_item(shared_ptr c, size_t item_index, shared_ptrversion())) { // Unwrap present - item.data.unwrap(); + item.data.unwrap(c->version()); should_delete_item = false; } else if (item_identifier == 0x003300) { @@ -250,7 +250,7 @@ void player_use_item(shared_ptr c, size_t item_index, shared_ptrremove_item(item.data.id, 1, !is_v4(c->version())); + player->remove_item(item.data.id, 1, c->version()); } } diff --git a/src/PlayerSubordinates.cc b/src/PlayerSubordinates.cc index d57cd4a7..9f76eaed 100644 --- a/src/PlayerSubordinates.cc +++ b/src/PlayerSubordinates.cc @@ -450,7 +450,7 @@ PlayerRecordsBB_Challenge::operator PlayerRecordsV3_Challenge() const { return ret; } -void PlayerBank::add_item(const ItemData& item) { +void PlayerBank::add_item(const ItemData& item, Version version) { uint32_t pid = item.primary_identifier(); if (pid == MESETA_IDENTIFIER) { @@ -461,7 +461,7 @@ void PlayerBank::add_item(const ItemData& item) { return; } - size_t combine_max = item.max_stack_size(); + size_t combine_max = item.max_stack_size(version); if (combine_max > 1) { size_t y; for (y = 0; y < this->num_items; y++) { @@ -486,17 +486,17 @@ void PlayerBank::add_item(const ItemData& item) { } auto& last_item = this->items[this->num_items]; last_item.data = item; - last_item.amount = (item.max_stack_size() > 1) ? item.data1[5] : 1; + last_item.amount = (item.max_stack_size(version) > 1) ? item.data1[5] : 1; last_item.present = 1; this->num_items++; } -ItemData PlayerBank::remove_item(uint32_t item_id, uint32_t amount) { +ItemData PlayerBank::remove_item(uint32_t item_id, uint32_t amount, Version version) { size_t index = this->find_item(item_id); auto& bank_item = this->items[index]; ItemData ret; - if (amount && (bank_item.data.stack_size() > 1) && (amount < bank_item.data.data1[5])) { + if (amount && (bank_item.data.stack_size(version) > 1) && (amount < bank_item.data.data1[5])) { ret = bank_item.data; ret.data1[5] = amount; bank_item.data.data1[5] -= amount; diff --git a/src/PlayerSubordinates.hh b/src/PlayerSubordinates.hh index fb8d4fd4..88224cec 100644 --- a/src/PlayerSubordinates.hh +++ b/src/PlayerSubordinates.hh @@ -100,8 +100,8 @@ struct PlayerBank { /* 0008 */ parray items; /* 12C8 */ - void add_item(const ItemData& item); - ItemData remove_item(uint32_t item_id, uint32_t amount); + void add_item(const ItemData& item, Version version); + ItemData remove_item(uint32_t item_id, uint32_t amount, Version version); size_t find_item(uint32_t item_id); void sort(); diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index cc04b22f..98d0c212 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -3671,7 +3671,7 @@ static void on_DF_BB(shared_ptr c, uint16_t command, uint32_t, string& d ? p->challenge_records.ep2_online_award_state : p->challenge_records.ep1_online_award_state; award_state.rank_award_flags |= cmd.rank_bitmask; - p->add_item(cmd.item); + p->add_item(cmd.item, c->version()); l->on_item_id_generated_externally(cmd.item.id); string desc = s->describe_item(Version::BB_V4, cmd.item, false); l->log.info("(Challenge mode) Item awarded to player %hhu: %s", c->lobby_client_id, desc.c_str()); @@ -4543,9 +4543,9 @@ static void on_D2_V3_BB(shared_ptr c, uint16_t, uint32_t, string& data) auto to_p = to_c->character(); auto from_p = from_c->character(); for (const auto& trade_item : from_c->pending_item_trade->items) { - size_t amount = trade_item.stack_size(); + size_t amount = trade_item.stack_size(from_c->version()); - auto item = from_p->remove_item(trade_item.id, amount, false); + auto item = from_p->remove_item(trade_item.id, amount, from_c->version()); // This is a special case: when the trade is executed, the client // deletes the traded items from its own inventory automatically, so we // should NOT send the 6x29 to that client; we should only send it to @@ -4557,7 +4557,7 @@ static void on_D2_V3_BB(shared_ptr c, uint16_t, uint32_t, string& data) } } - to_p->add_item(trade_item); + to_p->add_item(trade_item, to_c->version()); send_create_inventory_item_to_lobby(to_c, to_c->lobby_client_id, item); } send_command(to_c, 0xD3, 0x00); @@ -4991,7 +4991,7 @@ static void on_EA_BB(shared_ptr c, uint16_t command, uint32_t flag, stri } } if (!reward.reward_item.empty()) { - c->current_bank().add_item(reward.reward_item); + c->current_bank().add_item(reward.reward_item, c->version()); } } break; diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index 401b88a2..cea872f7 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -1376,7 +1376,7 @@ static void on_player_drop_item(shared_ptr c, uint8_t command, uint8_t f auto l = c->require_lobby(); auto p = c->character(); - auto item = p->remove_item(cmd.item_id, 0, c->version() != Version::BB_V4); + auto item = p->remove_item(cmd.item_id, 0, c->version()); l->add_item(cmd.floor, item, cmd.x, cmd.z, 0x00F); if (l->log.should_log(LogLevel::INFO)) { @@ -1438,7 +1438,7 @@ static void on_create_inventory_item_t(shared_ptr c, uint8_t command, ui ItemData item = cmd.item_data; item.decode_for_version(c->version()); l->on_item_id_generated_externally(item.id); - p->add_item(item); + p->add_item(item, c->version()); if (l->log.should_log(LogLevel::INFO)) { auto s = c->require_server_state(); @@ -1511,7 +1511,7 @@ static void on_drop_partial_stack_bb(shared_ptr c, uint8_t command, uint } auto p = c->character(); - auto item = p->remove_item(cmd.item_id, cmd.amount, c->version() != Version::BB_V4); + auto item = p->remove_item(cmd.item_id, cmd.amount, c->version()); // If a stack was split, the original item still exists, so the dropped item // needs a new ID. remove_item signals this by returning an item with an ID @@ -1523,7 +1523,7 @@ static void on_drop_partial_stack_bb(shared_ptr c, uint8_t command, uint // PSOBB sends a 6x29 command after it receives the 6x5D, so we need to add // the item back to the player's inventory to correct for this (it will get // removed again by the 6x29 handler) - p->add_item(item); + p->add_item(item, c->version()); 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); @@ -1558,7 +1558,7 @@ static void on_buy_shop_item(shared_ptr c, uint8_t command, uint8_t flag 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); + p->add_item(item, c->version()); size_t price = s->item_parameter_table(c->version())->price_for_item(item); p->remove_meseta(price, c->version() != Version::BB_V4); @@ -1674,7 +1674,7 @@ static void on_pick_up_item_generic( } try { - p->add_item(fi->data); + p->add_item(fi->data, c->version()); } 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", @@ -1811,8 +1811,8 @@ static void on_feed_mag( // 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 (c->version() == Version::BB_V4) { + p->remove_item(cmd.fed_item_id, 1, c->version()); } if (l->log.should_log(LogLevel::INFO)) { @@ -1899,7 +1899,7 @@ static void on_ep3_private_word_select_bb_bank_action(shared_ptr c, uint } } else { // Deposit item - auto item = p->remove_item(cmd.item_id, cmd.item_amount, c->version() != Version::BB_V4); + auto item = p->remove_item(cmd.item_id, cmd.item_amount, c->version()); // If a stack was split, the bank item retains the same item ID as the // inventory item. This is annoying but doesn't cause any problems // because we always generate a new item ID when withdrawing from the @@ -1907,7 +1907,7 @@ static void on_ep3_private_word_select_bb_bank_action(shared_ptr c, uint if (item.id == 0xFFFFFFFF) { item.id = cmd.item_id; } - bank.add_item(item); + bank.add_item(item, c->version()); send_destroy_item_to_lobby(c, cmd.item_id, cmd.item_amount, true); if (l->log.should_log(LogLevel::INFO)) { @@ -1934,9 +1934,9 @@ static void on_ep3_private_word_select_bb_bank_action(shared_ptr c, uint } } else { // Take item - auto item = bank.remove_item(cmd.item_id, cmd.item_amount); + auto item = bank.remove_item(cmd.item_id, cmd.item_amount, c->version()); item.id = l->generate_item_id(c->lobby_client_id); - p->add_item(item); + p->add_item(item, c->version()); send_create_inventory_item_to_lobby(c, c->lobby_client_id, item); if (l->log.should_log(LogLevel::INFO)) { @@ -2659,7 +2659,7 @@ void on_adjust_player_meseta_bb(shared_ptr c, uint8_t, uint8_t, void* da item.data1[0] = 0x04; item.data2d = cmd.amount.load(); item.id = l->generate_item_id(c->lobby_client_id); - p->add_item(item); + p->add_item(item, c->version()); send_create_inventory_item_to_lobby(c, c->lobby_client_id, item); } } @@ -2670,9 +2670,9 @@ void on_item_reward_request_bb(shared_ptr c, uint8_t, uint8_t, void* dat ItemData item; item = cmd.item_data; - item.enforce_min_stack_size(); + item.enforce_min_stack_size(c->version()); item.id = l->generate_item_id(c->lobby_client_id); - c->character()->add_item(item); + c->character()->add_item(item, c->version()); send_create_inventory_item_to_lobby(c, c->lobby_client_id, item); } @@ -2699,7 +2699,7 @@ void on_transfer_item_via_mail_message_bb(shared_ptr c, uint8_t command, 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 item = p->remove_item(cmd.item_id, cmd.amount, c->version()); if (l->log.should_log(LogLevel::INFO)) { auto name = s->describe_item(c->version(), item, false); @@ -2718,7 +2718,7 @@ void on_transfer_item_via_mail_message_bb(shared_ptr c, uint8_t command, (target_c->character(false) != nullptr) && !target_c->config.check_flag(Client::Flag::AT_BANK_COUNTER)) { try { - target_c->current_bank().add_item(item); + target_c->current_bank().add_item(item, target_c->version()); item_sent = true; } catch (const runtime_error&) { } @@ -2730,7 +2730,7 @@ void on_transfer_item_via_mail_message_bb(shared_ptr c, uint8_t command, send_command(c, 0x16EA, 0x00000000); // 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); + p->add_item(item, c->version()); send_create_inventory_item_to_lobby(c, c->lobby_client_id, item); } } @@ -2756,7 +2756,7 @@ void on_exchange_item_for_team_points_bb(shared_ptr c, uint8_t command, 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 item = p->remove_item(cmd.item_id, cmd.amount, c->version()); size_t points = s->item_parameter_table_v4->get_item_team_points(item); s->team_index->add_member_points(c->license->serial_number, points); @@ -2784,7 +2784,7 @@ static void on_destroy_inventory_item(shared_ptr c, uint8_t command, uin 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 item = p->remove_item(cmd.item_id, cmd.amount, c->version()); if (l->log.should_log(LogLevel::INFO)) { auto name = s->describe_item(c->version(), item, false); @@ -2876,7 +2876,7 @@ static void on_accept_identify_item_bb(shared_ptr c, uint8_t command, ui if (c->bb_identify_result.id != cmd.item_id) { throw runtime_error("accepted item ID does not match previous identify request"); } - c->character()->add_item(c->bb_identify_result); + c->character()->add_item(c->bb_identify_result, c->version()); send_create_inventory_item_to_lobby(c, c->lobby_client_id, c->bb_identify_result); c->bb_identify_result.clear(); @@ -2893,7 +2893,7 @@ static void on_sell_item_at_shop_bb(shared_ptr c, uint8_t command, uint8 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 item = p->remove_item(cmd.item_id, cmd.amount, c->version()); size_t price = (s->item_parameter_table(c->version())->price_for_item(item) >> 3) * cmd.amount; p->add_meseta(price); @@ -2915,7 +2915,7 @@ static void on_buy_shop_item_bb(shared_ptr c, uint8_t, uint8_t, void* da ItemData item; item = c->bb_shop_contents.at(cmd.shop_type).at(cmd.item_index); - if (item.is_stackable()) { + if (item.is_stackable(c->version())) { item.data1[5] = cmd.amount; } else if (cmd.amount != 1) { throw runtime_error("item is not stackable"); @@ -2928,7 +2928,7 @@ static void on_buy_shop_item_bb(shared_ptr c, uint8_t, uint8_t, void* da item.id = cmd.shop_item_id; l->on_item_id_generated_externally(item.id); - p->add_item(item); + p->add_item(item, c->version()); send_create_inventory_item_to_lobby(c, c->lobby_client_id, item, true); if (l->log.should_log(LogLevel::INFO)) { @@ -3031,15 +3031,15 @@ static void on_quest_exchange_item_bb(shared_ptr c, uint8_t, uint8_t, vo auto p = c->character(); 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); + auto found_item = p->remove_item(p->inventory.items[found_index].data.id, 1, c->version()); 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.enforce_min_stack_size(); + new_item.enforce_min_stack_size(c->version()); new_item.id = l->generate_item_id(c->lobby_client_id); - p->add_item(new_item); + p->add_item(new_item, c->version()); send_create_inventory_item_to_lobby(c, c->lobby_client_id, new_item); send_quest_function_call(c, cmd.success_function_id); @@ -3057,10 +3057,10 @@ static void on_wrap_item_bb(shared_ptr c, uint8_t, uint8_t, void* data, const auto& cmd = check_size_t(data, size); auto p = c->character(); - auto item = p->remove_item(cmd.item.id, 1, false); + auto item = p->remove_item(cmd.item.id, 1, c->version()); send_destroy_item_to_lobby(c, item.id, 1); - item.wrap(); - p->add_item(item); + item.wrap(c->version()); + p->add_item(item, c->version()); send_create_inventory_item_to_lobby(c, c->lobby_client_id, item); } } @@ -3074,15 +3074,15 @@ static void on_photon_drop_exchange_for_item_bb(shared_ptr c, uint8_t, u auto p = c->character(); 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_to_lobby(c, found_item.id, found_item.stack_size()); + auto found_item = p->remove_item(p->inventory.items[found_index].data.id, 0, c->version()); + send_destroy_item_to_lobby(c, found_item.id, found_item.stack_size(c->version())); // 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.enforce_min_stack_size(); + new_item.enforce_min_stack_size(c->version()); new_item.id = l->generate_item_id(c->lobby_client_id); - p->add_item(new_item); + p->add_item(new_item, c->version()); send_create_inventory_item_to_lobby(c, c->lobby_client_id, new_item); send_quest_function_call(c, cmd.success_function_id); @@ -3110,13 +3110,13 @@ static void on_photon_drop_exchange_for_s_rank_special_bb(shared_ptr c, // consistent in case of error p->inventory.find_item(cmd.item_id); - auto payment_item = p->remove_item(p->inventory.items[payment_item_index].data.id, cost, false); + auto payment_item = p->remove_item(p->inventory.items[payment_item_index].data.id, cost, c->version()); send_destroy_item_to_lobby(c, payment_item.id, cost); - auto item = p->remove_item(cmd.item_id, 1, false); + auto item = p->remove_item(cmd.item_id, 1, c->version()); send_destroy_item_to_lobby(c, item.id, cost); item.data1[2] = cmd.special_type; - p->add_item(item); + p->add_item(item, c->version()); send_create_inventory_item_to_lobby(c, c->lobby_client_id, item); send_quest_function_call(c, cmd.success_function_id); @@ -3157,14 +3157,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_to_lobby(c, slt_item_id, 1); + p->remove_item(slt_item_id, 1, c->version()); ItemData item = (s->secret_lottery_results.size() == 1) ? s->secret_lottery_results[0] : s->secret_lottery_results[l->random_crypt->next() % s->secret_lottery_results.size()]; - item.enforce_min_stack_size(); + item.enforce_min_stack_size(c->version()); item.id = l->generate_item_id(c->lobby_client_id); - p->add_item(item); + p->add_item(item, c->version()); send_create_inventory_item_to_lobby(c, c->lobby_client_id, item); } @@ -3190,7 +3190,7 @@ static void on_photon_crystal_exchange_bb(shared_ptr c, uint8_t, uint8_t check_size_t(data, size); 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); + auto item = p->remove_item(p->inventory.items[index].data.id, 1, c->version()); send_destroy_item_to_lobby(c, item.id, 1); } } @@ -3216,7 +3216,7 @@ static void on_quest_F95E_result_bb(shared_ptr c, uint8_t, uint8_t, void } else if (item.data1[0] == 0x00) { item.data1[4] |= 0x80; // Unidentified } else { - item.enforce_min_stack_size(); + item.enforce_min_stack_size(c->version()); } item.id = l->generate_item_id(0xFF); @@ -3240,7 +3240,7 @@ static void on_quest_F95F_result_bb(shared_ptr c, uint8_t, uint8_t, void } size_t index = p->inventory.find_item_by_primary_identifier(0x031004); // Photon Ticket - auto ticket_item = p->remove_item(p->inventory.items[index].data.id, result.first, false); + auto ticket_item = p->remove_item(p->inventory.items[index].data.id, result.first, c->version()); // TODO: Shouldn't we send a 6x29 here? Check if this causes desync in an // actual game @@ -3252,9 +3252,9 @@ static void on_quest_F95F_result_bb(shared_ptr c, uint8_t, uint8_t, void send_command_t(c, 0x60, 0x00, cmd_6xDB); ItemData new_item = result.second; - new_item.enforce_min_stack_size(); + new_item.enforce_min_stack_size(c->version()); new_item.id = l->generate_item_id(c->lobby_client_id); - p->add_item(new_item); + p->add_item(new_item, c->version()); send_create_inventory_item_to_lobby(c, c->lobby_client_id, new_item); S_GallonPlanResult_BB_25 out_cmd; @@ -3318,7 +3318,7 @@ static void on_quest_F960_result_bb(shared_ptr c, uint8_t, uint8_t, void send_command_t(c, 0x60, 0x00, cmd_6xE3); try { - p->add_item(item); + p->add_item(item, c->version()); send_create_inventory_item_to_lobby(c, c->lobby_client_id, item); if (c->log.should_log(LogLevel::INFO)) { string name = s->describe_item(c->version(), item, false); @@ -3340,7 +3340,7 @@ static void on_momoka_item_exchange_bb(shared_ptr c, uint8_t, uint8_t, v auto p = c->character(); try { 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); + auto found_item = p->remove_item(p->inventory.items[found_index].data.id, 1, c->version()); 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); @@ -3350,9 +3350,9 @@ static void on_momoka_item_exchange_bb(shared_ptr c, uint8_t, uint8_t, v // 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.enforce_min_stack_size(); + new_item.enforce_min_stack_size(c->version()); new_item.id = l->generate_item_id(c->lobby_client_id); - p->add_item(new_item); + p->add_item(new_item, c->version()); send_create_inventory_item_to_lobby(c, c->lobby_client_id, new_item); send_command(c, 0x23, 0x00); @@ -3375,10 +3375,10 @@ static void on_upgrade_weapon_attribute_bb(shared_ptr c, uint8_t, uint8_ uint32_t payment_primary_identifier = cmd.payment_type ? 0x031001 : 0x031000; size_t payment_index = p->inventory.find_item_by_primary_identifier(payment_primary_identifier); auto& payment_item = p->inventory.items[payment_index].data; - if (payment_item.stack_size() < cmd.payment_count) { + if (payment_item.stack_size(c->version()) < cmd.payment_count) { throw runtime_error("not enough payment items present"); } - p->remove_item(payment_item.id, cmd.payment_count, false); + p->remove_item(payment_item.id, cmd.payment_count, c->version()); send_destroy_item_to_lobby(c, payment_item.id, cmd.payment_count); uint8_t attribute_amount = 0; diff --git a/src/SaveFileFormats.cc b/src/SaveFileFormats.cc index f48abc40..13f66485 100644 --- a/src/SaveFileFormats.cc +++ b/src/SaveFileFormats.cc @@ -396,7 +396,7 @@ PSOBBCharacterFile::SymbolChatEntry PSOBBCharacterFile::DefaultSymbolChatEntry:: // TODO: Eliminate duplication between this function and the parallel function // in PlayerBank -void PSOBBCharacterFile::add_item(const ItemData& item) { +void PSOBBCharacterFile::add_item(const ItemData& item, Version version) { uint32_t pid = item.primary_identifier(); // Annoyingly, meseta is in the disp data, not in the inventory struct. If the @@ -407,7 +407,7 @@ void PSOBBCharacterFile::add_item(const ItemData& item) { } // Handle combinable items - size_t combine_max = item.max_stack_size(); + size_t combine_max = item.max_stack_size(version); if (combine_max > 1) { // Get the item index if there's already a stack of the same item in the // player's inventory @@ -444,13 +444,13 @@ void PSOBBCharacterFile::add_item(const ItemData& item) { // TODO: Eliminate code duplication between this function and the parallel // function in PlayerBank -ItemData PSOBBCharacterFile::remove_item(uint32_t item_id, uint32_t amount, bool allow_meseta_overdraft) { +ItemData PSOBBCharacterFile::remove_item(uint32_t item_id, uint32_t amount, Version version) { ItemData ret; // If we're removing meseta (signaled by an invalid item ID), then create a // meseta item. if (item_id == 0xFFFFFFFF) { - this->remove_meseta(amount, allow_meseta_overdraft); + this->remove_meseta(amount, !is_v4(version)); ret.data1[0] = 0x04; ret.data2d = amount; return ret; @@ -464,7 +464,7 @@ ItemData PSOBBCharacterFile::remove_item(uint32_t item_id, uint32_t amount, bool // then create a new item and reduce the amount of the existing stack. Note // that passing amount == 0 means to remove the entire stack, so this only // applies if amount is nonzero. - if (amount && (inventory_item.data.stack_size() > 1) && + if (amount && (inventory_item.data.stack_size(version) > 1) && (amount < inventory_item.data.data1[5])) { if (is_equipped) { throw runtime_error("character has a combine item equipped"); diff --git a/src/SaveFileFormats.hh b/src/SaveFileFormats.hh index 17eaea2a..b36bcee5 100644 --- a/src/SaveFileFormats.hh +++ b/src/SaveFileFormats.hh @@ -231,8 +231,8 @@ struct PSOBBCharacterFile { const PlayerDispDataBBPreview& preview, std::shared_ptr level_table); - void add_item(const ItemData& item); - ItemData remove_item(uint32_t item_id, uint32_t amount, bool allow_meseta_overdraft); + void add_item(const ItemData& item, Version version); + ItemData remove_item(uint32_t item_id, uint32_t amount, Version version); void add_meseta(uint32_t amount); void remove_meseta(uint32_t amount, bool allow_overdraft); diff --git a/src/ServerState.cc b/src/ServerState.cc index f04bdfaf..c3df9d93 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -1146,25 +1146,25 @@ shared_ptr ServerState::create_item_name_index_for_version( Version version, shared_ptr pmt, shared_ptr text_index) { switch (version) { case Version::DC_NTE: - return make_shared(pmt, text_index->get(Version::DC_NTE, 0, 2)); + return make_shared(version, pmt, text_index->get(Version::DC_NTE, 0, 2)); case Version::DC_V1_11_2000_PROTOTYPE: - return make_shared(pmt, text_index->get(Version::DC_V1_11_2000_PROTOTYPE, 1, 2)); + return make_shared(version, pmt, text_index->get(Version::DC_V1_11_2000_PROTOTYPE, 1, 2)); case Version::DC_V1: - return make_shared(pmt, text_index->get(Version::DC_V1, 1, 2)); + return make_shared(version, pmt, text_index->get(Version::DC_V1, 1, 2)); case Version::DC_V2: - return make_shared(pmt, text_index->get(Version::DC_V2, 1, 3)); + return make_shared(version, pmt, text_index->get(Version::DC_V2, 1, 3)); case Version::PC_NTE: - return make_shared(pmt, text_index->get(Version::PC_NTE, 1, 3)); + return make_shared(version, pmt, text_index->get(Version::PC_NTE, 1, 3)); case Version::PC_V2: - return make_shared(pmt, text_index->get(Version::PC_V2, 1, 3)); + return make_shared(version, pmt, text_index->get(Version::PC_V2, 1, 3)); case Version::GC_NTE: - return make_shared(pmt, text_index->get(Version::GC_NTE, 1, 0)); + return make_shared(version, pmt, text_index->get(Version::GC_NTE, 1, 0)); case Version::GC_V3: - return make_shared(pmt, text_index->get(Version::GC_V3, 1, 0)); + return make_shared(version, pmt, text_index->get(Version::GC_V3, 1, 0)); case Version::XB_V3: - return make_shared(pmt, text_index->get(Version::XB_V3, 1, 0)); + return make_shared(version, pmt, text_index->get(Version::XB_V3, 1, 0)); case Version::BB_V4: - return make_shared(pmt, text_index->get(Version::BB_V4, 1, 1)); + return make_shared(version, pmt, text_index->get(Version::BB_V4, 1, 1)); default: return nullptr; } @@ -1180,12 +1180,19 @@ void ServerState::load_item_name_indexes() { auto pc_v2_index = create_item_name_index_for_version( Version::PC_V2, this->item_parameter_table(Version::PC_V2), this->text_index); this->set_item_name_index(Version::DC_NTE, pc_v2_index); - this->set_item_name_index(Version::DC_V1_11_2000_PROTOTYPE, pc_v2_index); this->set_item_name_index(Version::DC_V1, pc_v2_index); this->set_item_name_index(Version::DC_V2, pc_v2_index); this->set_item_name_index(Version::PC_NTE, pc_v2_index); this->set_item_name_index(Version::PC_V2, pc_v2_index); + // All tools are stackable on 11/2000, so make a separate index (still using + // V2 data) with the correct version + auto dc_112000_index = make_shared( + Version::DC_V1_11_2000_PROTOTYPE, + this->item_parameter_table(Version::PC_V2), + this->text_index->get(Version::PC_V2, 1, 3)); + this->set_item_name_index(Version::DC_V1_11_2000_PROTOTYPE, dc_112000_index); + auto gc_v3_index = create_item_name_index_for_version( Version::GC_V3, this->item_parameter_table(Version::GC_V3), this->text_index); this->set_item_name_index(Version::GC_NTE, gc_v3_index); diff --git a/src/StaticGameData.cc b/src/StaticGameData.cc index e0895e2e..c7047d48 100644 --- a/src/StaticGameData.cc +++ b/src/StaticGameData.cc @@ -493,12 +493,15 @@ uint8_t language_code_for_char(char language_char) { } } -size_t max_stack_size_for_item(uint8_t data0, uint8_t data1) { +size_t max_stack_size_for_item(Version version, uint8_t data0, uint8_t data1) { if (data0 == 4) { return 999999; } if (data0 == 3) { - if ((data1 < 9) && (data1 != 2)) { + if (version == Version::DC_V1_11_2000_PROTOTYPE) { + // All tool items are stackable up to x10 on this version + return 10; + } else if ((data1 < 9) && (data1 != 2)) { return 10; } else if (data1 == 0x10) { return 99; diff --git a/src/StaticGameData.hh b/src/StaticGameData.hh index 87861eaf..dfcfbdad 100644 --- a/src/StaticGameData.hh +++ b/src/StaticGameData.hh @@ -7,6 +7,7 @@ #include #include "FileContentsCache.hh" +#include "Version.hh" enum class Episode { NONE = 0, @@ -32,7 +33,7 @@ enum class GameMode { const char* name_for_mode(GameMode mode); const char* abbreviation_for_mode(GameMode mode); -size_t max_stack_size_for_item(uint8_t data0, uint8_t data1); +size_t max_stack_size_for_item(Version version, uint8_t data0, uint8_t data1); extern const std::vector tech_id_to_name; extern const std::unordered_map name_to_tech_id;