fix tool item stackability on 11/2000

This commit is contained in:
Martin Michelsen
2024-01-01 11:22:03 -08:00
parent 2fda85c750
commit 962ee6874e
16 changed files with 142 additions and 125 deletions
+3 -3
View File
@@ -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;
+1 -1
View File
@@ -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<ItemData> generate_armor_shop_contents(size_t player_level);
std::vector<ItemData> generate_tool_shop_contents(size_t player_level);
+18 -18
View File
@@ -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;
+9 -9
View File
@@ -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;
+8 -6
View File
@@ -21,9 +21,11 @@ using namespace std;
// };
ItemNameIndex::ItemNameIndex(
Version version,
std::shared_ptr<const ItemParameterTable> item_parameter_table,
const std::vector<std::string>& 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;
+5 -1
View File
@@ -19,7 +19,10 @@ public:
std::string name;
};
ItemNameIndex(std::shared_ptr<const ItemParameterTable> pmt, const std::vector<std::string>& name_coll);
ItemNameIndex(
Version version,
std::shared_ptr<const ItemParameterTable> pmt,
const std::vector<std::string>& 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<const ItemParameterTable> item_parameter_table;
std::unordered_map<uint32_t, std::shared_ptr<const ItemMetadata>> primary_identifier_index;
+3 -3
View File
@@ -111,9 +111,9 @@ void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<PSOLFGE
}
armor.data.data1[5]++;
} else if (item.data.is_wrapped()) {
} else if (item.data.is_wrapped(c->version())) {
// 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<Client> c, size_t item_index, shared_ptr<PSOLFGE
if (should_delete_item) {
// Allow overdrafting meseta if the client is not BB, since the server isn't
// informed when meseta is added or removed from the bank.
player->remove_item(item.data.id, 1, !is_v4(c->version()));
player->remove_item(item.data.id, 1, c->version());
}
}
+5 -5
View File
@@ -450,7 +450,7 @@ PlayerRecordsBB_Challenge::operator PlayerRecordsV3_Challenge<false>() 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;
+2 -2
View File
@@ -100,8 +100,8 @@ struct PlayerBank {
/* 0008 */ parray<PlayerBankItem, 200> 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();
+5 -5
View File
@@ -3671,7 +3671,7 @@ static void on_DF_BB(shared_ptr<Client> 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<Client> 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<Client> 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<Client> 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;
+51 -51
View File
@@ -1376,7 +1376,7 @@ static void on_player_drop_item(shared_ptr<Client> 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<Client> 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<Client> 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<Client> 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<Client> 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<Client> 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<Client> 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<Client> 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<Client> 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<Client> 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<Client> 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<Client> 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<Client> 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<Client> 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<Client> 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<Client> 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<Client> 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<Client> 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<Client> 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<Client> 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<Client> c, uint8_t, uint8_t, void* data,
const auto& cmd = check_size_t<G_WrapItem_BB_6xD6>(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<Client> 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<Client> 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<Client> 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<Client> c, uint8_t, uint8_t
check_size_t<G_ExchangePhotonCrystals_BB_6xDF>(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<Client> 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<Client> 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<Client> 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<Client> 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<Client> 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<Client> 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<Client> 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;
+5 -5
View File
@@ -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");
+2 -2
View File
@@ -231,8 +231,8 @@ struct PSOBBCharacterFile {
const PlayerDispDataBBPreview& preview,
std::shared_ptr<const LevelTable> 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);
+18 -11
View File
@@ -1146,25 +1146,25 @@ shared_ptr<ItemNameIndex> ServerState::create_item_name_index_for_version(
Version version, shared_ptr<const ItemParameterTable> pmt, shared_ptr<const TextIndex> text_index) {
switch (version) {
case Version::DC_NTE:
return make_shared<ItemNameIndex>(pmt, text_index->get(Version::DC_NTE, 0, 2));
return make_shared<ItemNameIndex>(version, pmt, text_index->get(Version::DC_NTE, 0, 2));
case Version::DC_V1_11_2000_PROTOTYPE:
return make_shared<ItemNameIndex>(pmt, text_index->get(Version::DC_V1_11_2000_PROTOTYPE, 1, 2));
return make_shared<ItemNameIndex>(version, pmt, text_index->get(Version::DC_V1_11_2000_PROTOTYPE, 1, 2));
case Version::DC_V1:
return make_shared<ItemNameIndex>(pmt, text_index->get(Version::DC_V1, 1, 2));
return make_shared<ItemNameIndex>(version, pmt, text_index->get(Version::DC_V1, 1, 2));
case Version::DC_V2:
return make_shared<ItemNameIndex>(pmt, text_index->get(Version::DC_V2, 1, 3));
return make_shared<ItemNameIndex>(version, pmt, text_index->get(Version::DC_V2, 1, 3));
case Version::PC_NTE:
return make_shared<ItemNameIndex>(pmt, text_index->get(Version::PC_NTE, 1, 3));
return make_shared<ItemNameIndex>(version, pmt, text_index->get(Version::PC_NTE, 1, 3));
case Version::PC_V2:
return make_shared<ItemNameIndex>(pmt, text_index->get(Version::PC_V2, 1, 3));
return make_shared<ItemNameIndex>(version, pmt, text_index->get(Version::PC_V2, 1, 3));
case Version::GC_NTE:
return make_shared<ItemNameIndex>(pmt, text_index->get(Version::GC_NTE, 1, 0));
return make_shared<ItemNameIndex>(version, pmt, text_index->get(Version::GC_NTE, 1, 0));
case Version::GC_V3:
return make_shared<ItemNameIndex>(pmt, text_index->get(Version::GC_V3, 1, 0));
return make_shared<ItemNameIndex>(version, pmt, text_index->get(Version::GC_V3, 1, 0));
case Version::XB_V3:
return make_shared<ItemNameIndex>(pmt, text_index->get(Version::XB_V3, 1, 0));
return make_shared<ItemNameIndex>(version, pmt, text_index->get(Version::XB_V3, 1, 0));
case Version::BB_V4:
return make_shared<ItemNameIndex>(pmt, text_index->get(Version::BB_V4, 1, 1));
return make_shared<ItemNameIndex>(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<ItemNameIndex>(
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);
+5 -2
View File
@@ -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;
+2 -1
View File
@@ -7,6 +7,7 @@
#include <vector>
#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<std::string> tech_id_to_name;
extern const std::unordered_map<std::string, uint8_t> name_to_tech_id;