allow overriding stack sizes

This commit is contained in:
Martin Michelsen
2024-02-22 00:05:40 -08:00
parent 4e4ba5650d
commit 0383dc90b8
24 changed files with 504 additions and 354 deletions
+78 -52
View File
@@ -1385,9 +1385,10 @@ static void on_player_drop_item(shared_ptr<Client> c, uint8_t command, uint8_t f
return;
}
auto s = c->require_server_state();
auto l = c->require_lobby();
auto p = c->character();
auto item = p->remove_item(cmd.item_id, 0, c->version());
auto item = p->remove_item(cmd.item_id, 0, *s->item_stack_limits(c->version()));
l->add_item(cmd.floor, item, cmd.x, cmd.z, 0x00F);
if (l->log.should_log(LogLevel::INFO)) {
@@ -1444,12 +1445,13 @@ static void on_create_inventory_item_t(shared_ptr<Client> c, uint8_t command, ui
return;
}
auto s = c->require_server_state();
auto l = c->require_lobby();
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, c->version());
p->add_item(item, *s->item_stack_limits(c->version()));
if (l->log.should_log(LogLevel::INFO)) {
auto s = c->require_server_state();
@@ -1516,13 +1518,15 @@ static void on_drop_partial_stack_bb(shared_ptr<Client> c, uint8_t command, uint
auto l = c->require_lobby();
if (l->base_version == Version::BB_V4) {
const auto& cmd = check_size_t<G_SplitStackedItem_BB_6xC3>(data, size);
auto s = c->require_server_state();
if (!l->is_game() || (cmd.header.client_id != c->lobby_client_id)) {
return;
}
auto p = c->character();
auto item = p->remove_item(cmd.item_id, cmd.amount, c->version());
const auto& limits = *s->item_stack_limits(c->version());
auto item = p->remove_item(cmd.item_id, cmd.amount, limits);
// 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
@@ -1534,7 +1538,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, c->version());
p->add_item(item, limits);
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);
@@ -1569,7 +1573,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, c->version());
p->add_item(item, *s->item_stack_limits(c->version()));
size_t price = s->item_parameter_table(c->version())->price_for_item(item);
p->remove_meseta(price, c->version() != Version::BB_V4);
@@ -1696,6 +1700,7 @@ static void on_pick_up_item_generic(
// logic here instead of forwarding the 6x5A to the leader.
auto p = c->character();
auto s = c->require_server_state();
auto fi = l->remove_item(floor, item_id, c->lobby_client_id);
if (!fi->visible_to_client(c->lobby_client_id)) {
l->log.warning("Player %hu requests to pick up %08" PRIX32 ", but is it not visible to them; dropping command",
@@ -1705,7 +1710,7 @@ static void on_pick_up_item_generic(
}
try {
p->add_item(fi->data, c->version());
p->add_item(fi->data, *s->item_stack_limits(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",
@@ -1721,7 +1726,6 @@ static void on_pick_up_item_generic(
c->print_inventory(stderr);
}
auto s = c->require_server_state();
for (size_t z = 0; z < 12; z++) {
auto lc = l->clients[z];
if ((!lc) || (!is_request && (lc == c))) {
@@ -1821,8 +1825,8 @@ static void on_feed_mag(
return;
}
auto l = c->require_lobby();
auto s = c->require_server_state();
auto l = c->require_lobby();
auto p = c->character();
size_t mag_index = p->inventory.find_item(cmd.mag_item_id);
@@ -1843,7 +1847,7 @@ static void on_feed_mag(
// remove the fed item here, but on other versions, we allow the following
// 6x29 command to do that.
if (c->version() == Version::BB_V4) {
p->remove_item(cmd.fed_item_id, 1, c->version());
p->remove_item(cmd.fed_item_id, 1, *s->item_stack_limits(c->version()));
}
if (l->log.should_log(LogLevel::INFO)) {
@@ -1984,7 +1988,8 @@ 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());
const auto& limits = *s->item_stack_limits(c->version());
auto item = p->remove_item(cmd.item_id, cmd.item_amount, limits);
// 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
@@ -1992,7 +1997,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, c->version());
bank.add_item(item, limits);
send_destroy_item_to_lobby(c, cmd.item_id, cmd.item_amount, true);
if (l->log.should_log(LogLevel::INFO)) {
@@ -2019,9 +2024,10 @@ 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, c->version());
const auto& limits = *s->item_stack_limits(c->version());
auto item = bank.remove_item(cmd.item_id, cmd.item_amount, limits);
item.id = l->generate_item_id(c->lobby_client_id);
p->add_item(item, c->version());
p->add_item(item, limits);
send_create_inventory_item_to_lobby(c, c->lobby_client_id, item);
if (l->log.should_log(LogLevel::INFO)) {
@@ -2841,26 +2847,29 @@ void on_adjust_player_meseta_bb(shared_ptr<Client> c, uint8_t, uint8_t, void* da
p->disp.stats.meseta += cmd.amount;
}
} else if (cmd.amount > 0) {
auto s = c->require_server_state();
auto l = c->require_lobby();
ItemData item;
item.data1[0] = 0x04;
item.data2d = cmd.amount.load();
item.id = l->generate_item_id(c->lobby_client_id);
p->add_item(item, c->version());
p->add_item(item, *s->item_stack_limits(c->version()));
send_create_inventory_item_to_lobby(c, c->lobby_client_id, item);
}
}
void on_item_reward_request_bb(shared_ptr<Client> c, uint8_t, uint8_t, void* data, size_t size) {
const auto& cmd = check_size_t<G_ItemRewardRequest_BB_6xCA>(data, size);
auto s = c->require_server_state();
auto l = c->require_lobby();
const auto& limits = *s->item_stack_limits(c->version());
ItemData item;
item = cmd.item_data;
item.enforce_min_stack_size(c->version());
item.enforce_min_stack_size(limits);
item.id = l->generate_item_id(c->lobby_client_id);
c->character()->add_item(item, c->version());
c->character()->add_item(item, limits);
send_create_inventory_item_to_lobby(c, c->lobby_client_id, item);
}
@@ -2885,7 +2894,8 @@ 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());
const auto& limits = *s->item_stack_limits(c->version());
auto item = p->remove_item(cmd.item_id, cmd.amount, limits);
if (l->log.should_log(LogLevel::INFO)) {
auto name = s->describe_item(c->version(), item, false);
@@ -2904,7 +2914,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->version());
target_c->current_bank().add_item(item, limits);
item_sent = true;
} catch (const runtime_error&) {
}
@@ -2917,7 +2927,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, c->version());
p->add_item(item, limits);
send_create_inventory_item_to_lobby(c, c->lobby_client_id, item);
}
}
@@ -2943,7 +2953,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());
auto item = p->remove_item(cmd.item_id, cmd.amount, *s->item_stack_limits(c->version()));
size_t points = s->item_parameter_table(Version::BB_V4)->get_item_team_points(item);
s->team_index->add_member_points(c->license->serial_number, points);
@@ -2971,7 +2981,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());
auto item = p->remove_item(cmd.item_id, cmd.amount, *s->item_stack_limits(c->version()));
if (l->log.should_log(LogLevel::INFO)) {
auto name = s->describe_item(c->version(), item, false);
@@ -3075,7 +3085,8 @@ 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->version());
auto s = c->require_server_state();
c->character()->add_item(c->bb_identify_result, *s->item_stack_limits(c->version()));
send_create_inventory_item_to_lobby(c, c->lobby_client_id, c->bb_identify_result);
c->bb_identify_result.clear();
@@ -3092,7 +3103,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());
auto item = p->remove_item(cmd.item_id, cmd.amount, *s->item_stack_limits(c->version()));
size_t price = (s->item_parameter_table(c->version())->price_for_item(item) >> 3) * cmd.amount;
p->add_meseta(price);
@@ -3111,10 +3122,12 @@ static void on_buy_shop_item_bb(shared_ptr<Client> c, uint8_t, uint8_t, void* da
auto l = c->require_lobby();
if (l->base_version == Version::BB_V4) {
const auto& cmd = check_size_t<G_BuyShopItem_BB_6xB7>(data, size);
auto s = c->require_server_state();
const auto& limits = *s->item_stack_limits(c->version());
ItemData item;
item = c->bb_shop_contents.at(cmd.shop_type).at(cmd.item_index);
if (item.is_stackable(c->version())) {
if (item.is_stackable(limits)) {
item.data1[5] = cmd.amount;
} else if (cmd.amount != 1) {
throw runtime_error("item is not stackable");
@@ -3127,7 +3140,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, c->version());
p->add_item(item, limits);
send_create_inventory_item_to_lobby(c, c->lobby_client_id, item, true);
if (l->log.should_log(LogLevel::INFO)) {
@@ -3375,20 +3388,22 @@ static void on_quest_exchange_item_bb(shared_ptr<Client> c, uint8_t, uint8_t, vo
(l->base_version == Version::BB_V4) &&
l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) {
const auto& cmd = check_size_t<G_ExchangeItemInQuest_BB_6xD5>(data, size);
auto s = c->require_server_state();
try {
auto p = c->character();
const auto& limits = *s->item_stack_limits(c->version());
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, c->version());
auto found_item = p->remove_item(p->inventory.items[found_index].data.id, 1, limits);
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(c->version());
new_item.enforce_min_stack_size(limits);
new_item.id = l->generate_item_id(c->lobby_client_id);
p->add_item(new_item, c->version());
p->add_item(new_item, limits);
send_create_inventory_item_to_lobby(c, c->lobby_client_id, new_item);
send_quest_function_call(c, cmd.success_function_id);
@@ -3404,12 +3419,13 @@ static void on_wrap_item_bb(shared_ptr<Client> c, uint8_t, uint8_t, void* data,
auto l = c->require_lobby();
if (l->is_game() && (l->base_version == Version::BB_V4)) {
const auto& cmd = check_size_t<G_WrapItem_BB_6xD6>(data, size);
auto s = c->require_server_state();
auto p = c->character();
auto item = p->remove_item(cmd.item.id, 1, c->version());
auto item = p->remove_item(cmd.item.id, 1, *s->item_stack_limits(c->version()));
send_destroy_item_to_lobby(c, item.id, 1);
item.wrap(c->version());
p->add_item(item, c->version());
item.wrap(*s->item_stack_limits(c->version()));
p->add_item(item, *s->item_stack_limits(c->version()));
send_create_inventory_item_to_lobby(c, c->lobby_client_id, item);
}
}
@@ -3418,20 +3434,22 @@ static void on_photon_drop_exchange_for_item_bb(shared_ptr<Client> c, uint8_t, u
auto l = c->require_lobby();
if (l->is_game() && (l->base_version == Version::BB_V4)) {
const auto& cmd = check_size_t<G_PaganiniPhotonDropExchange_BB_6xD7>(data, size);
auto s = c->require_server_state();
try {
auto p = c->character();
const auto& limits = *s->item_stack_limits(c->version());
size_t found_index = p->inventory.find_item_by_primary_identifier(0x03100000);
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()));
auto found_item = p->remove_item(p->inventory.items[found_index].data.id, 0, limits);
send_destroy_item_to_lobby(c, found_item.id, found_item.stack_size(limits));
// 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(c->version());
new_item.enforce_min_stack_size(limits);
new_item.id = l->generate_item_id(c->lobby_client_id);
p->add_item(new_item, c->version());
p->add_item(new_item, limits);
send_create_inventory_item_to_lobby(c, c->lobby_client_id, new_item);
send_quest_function_call(c, cmd.success_function_id);
@@ -3447,6 +3465,8 @@ static void on_photon_drop_exchange_for_s_rank_special_bb(shared_ptr<Client> c,
auto l = c->require_lobby();
if (l->is_game() && (l->base_version == Version::BB_V4)) {
const auto& cmd = check_size_t<G_AddSRankWeaponSpecial_BB_6xD8>(data, size);
auto s = c->require_server_state();
const auto& limits = *s->item_stack_limits(c->version());
try {
auto p = c->character();
@@ -3459,13 +3479,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, c->version());
auto payment_item = p->remove_item(p->inventory.items[payment_item_index].data.id, cost, limits);
send_destroy_item_to_lobby(c, payment_item.id, cost);
auto item = p->remove_item(cmd.item_id, 1, c->version());
auto item = p->remove_item(cmd.item_id, 1, limits);
send_destroy_item_to_lobby(c, item.id, cost);
item.data1[2] = cmd.special_type;
p->add_item(item, c->version());
p->add_item(item, limits);
send_create_inventory_item_to_lobby(c, c->lobby_client_id, item);
send_quest_function_call(c, cmd.success_function_id);
@@ -3495,6 +3515,7 @@ static void on_secret_lottery_ticket_exchange_bb(shared_ptr<Client> c, uint8_t,
}
if (slt_index >= 0) {
const auto& limits = *s->item_stack_limits(c->version());
uint32_t slt_item_id = p->inventory.items[slt_index].data.id;
G_ExchangeItemInQuest_BB_6xDB exchange_cmd;
@@ -3506,14 +3527,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);
p->remove_item(slt_item_id, 1, c->version());
p->remove_item(slt_item_id, 1, limits);
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(c->version());
item.enforce_min_stack_size(limits);
item.id = l->generate_item_id(c->lobby_client_id);
p->add_item(item, c->version());
p->add_item(item, limits);
send_create_inventory_item_to_lobby(c, c->lobby_client_id, item);
}
@@ -3537,9 +3558,10 @@ static void on_photon_crystal_exchange_bb(shared_ptr<Client> c, uint8_t, uint8_t
auto l = c->require_lobby();
if (l->is_game() && (l->base_version == Version::BB_V4) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) {
check_size_t<G_ExchangePhotonCrystals_BB_6xDF>(data, size);
auto s = c->require_server_state();
auto p = c->character();
size_t index = p->inventory.find_item_by_primary_identifier(0x03100200);
auto item = p->remove_item(p->inventory.items[index].data.id, 1, c->version());
auto item = p->remove_item(p->inventory.items[index].data.id, 1, *s->item_stack_limits(c->version()));
send_destroy_item_to_lobby(c, item.id, 1);
}
}
@@ -3565,7 +3587,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(c->version());
item.enforce_min_stack_size(*s->item_stack_limits(c->version()));
}
item.id = l->generate_item_id(0xFF);
@@ -3588,8 +3610,9 @@ static void on_quest_F95F_result_bb(shared_ptr<Client> c, uint8_t, uint8_t, void
throw runtime_error("invalid result index");
}
const auto& limits = *s->item_stack_limits(c->version());
size_t index = p->inventory.find_item_by_primary_identifier(0x03100400); // Photon Ticket
auto ticket_item = p->remove_item(p->inventory.items[index].data.id, result.first, c->version());
auto ticket_item = p->remove_item(p->inventory.items[index].data.id, result.first, limits);
// TODO: Shouldn't we send a 6x29 here? Check if this causes desync in an
// actual game
@@ -3601,9 +3624,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(c->version());
new_item.enforce_min_stack_size(limits);
new_item.id = l->generate_item_id(c->lobby_client_id);
p->add_item(new_item, c->version());
p->add_item(new_item, limits);
send_create_inventory_item_to_lobby(c, c->lobby_client_id, new_item);
S_GallonPlanResult_BB_25 out_cmd;
@@ -3667,7 +3690,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, c->version());
p->add_item(item, *s->item_stack_limits(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);
@@ -3686,10 +3709,12 @@ static void on_momoka_item_exchange_bb(shared_ptr<Client> c, uint8_t, uint8_t, v
auto l = c->require_lobby();
if (l->is_game() && (l->base_version == Version::BB_V4) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) {
const auto& cmd = check_size_t<G_MomokaItemExchange_BB_6xD9>(data, size);
auto s = c->require_server_state();
auto p = c->character();
try {
const auto& limits = *s->item_stack_limits(c->version());
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, c->version());
auto found_item = p->remove_item(p->inventory.items[found_index].data.id, 1, limits);
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);
@@ -3699,9 +3724,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(c->version());
new_item.enforce_min_stack_size(limits);
new_item.id = l->generate_item_id(c->lobby_client_id);
p->add_item(new_item, c->version());
p->add_item(new_item, limits);
send_create_inventory_item_to_lobby(c, c->lobby_client_id, new_item);
send_command(c, 0x23, 0x00);
@@ -3716,6 +3741,7 @@ static void on_upgrade_weapon_attribute_bb(shared_ptr<Client> c, uint8_t, uint8_
auto l = c->require_lobby();
if (l->is_game() && (l->base_version == Version::BB_V4) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) {
const auto& cmd = check_size_t<G_UpgradeWeaponAttribute_BB_6xDA>(data, size);
auto s = c->require_server_state();
auto p = c->character();
try {
size_t item_index = p->inventory.find_item(cmd.item_id);
@@ -3724,10 +3750,10 @@ static void on_upgrade_weapon_attribute_bb(shared_ptr<Client> c, uint8_t, uint8_
uint32_t payment_primary_identifier = cmd.payment_type ? 0x03100100 : 0x03100000;
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(c->version()) < cmd.payment_count) {
if (payment_item.stack_size(*s->item_stack_limits(c->version())) < cmd.payment_count) {
throw runtime_error("not enough payment items present");
}
p->remove_item(payment_item.id, cmd.payment_count, c->version());
p->remove_item(payment_item.id, cmd.payment_count, *s->item_stack_limits(c->version()));
send_destroy_item_to_lobby(c, payment_item.id, cmd.payment_count);
uint8_t attribute_amount = 0;