fix BB shops + sale prices
This commit is contained in:
+4
-4
@@ -797,18 +797,18 @@ void ItemCreator::generate_common_unit_variances(uint8_t det, ItemData& item) {
|
||||
const auto& def = this->item_parameter_table->get_unit(item.data1[2]);
|
||||
switch (z % 5) {
|
||||
case 0:
|
||||
item.set_item_unit_bonus(-(def.modifier_amount * 2));
|
||||
item.set_unit_bonus(-(def.modifier_amount * 2));
|
||||
break;
|
||||
case 1:
|
||||
item.set_item_unit_bonus(-def.modifier_amount);
|
||||
item.set_unit_bonus(-def.modifier_amount);
|
||||
break;
|
||||
case 2:
|
||||
break;
|
||||
case 3:
|
||||
item.set_item_unit_bonus(def.modifier_amount);
|
||||
item.set_unit_bonus(def.modifier_amount);
|
||||
break;
|
||||
case 4:
|
||||
item.set_item_unit_bonus(def.modifier_amount * 2);
|
||||
item.set_unit_bonus(def.modifier_amount * 2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
+55
-3
@@ -109,6 +109,10 @@ void ItemData::set_unidentified_or_present_flag(uint16_t v) {
|
||||
this->data1[11] = v;
|
||||
}
|
||||
|
||||
uint8_t ItemData::get_tool_item_amount() const {
|
||||
return this->is_stackable() ? this->data1[5] : 1;
|
||||
}
|
||||
|
||||
void ItemData::set_tool_item_amount(uint8_t amount) {
|
||||
if (this->is_stackable()) {
|
||||
this->data1[5] = amount;
|
||||
@@ -117,24 +121,72 @@ void ItemData::set_tool_item_amount(uint8_t amount) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int16_t ItemData::get_armor_or_shield_defense_bonus() const {
|
||||
return this->data1w[3];
|
||||
}
|
||||
|
||||
void ItemData::set_armor_or_shield_defense_bonus(int16_t bonus) {
|
||||
this->data1w[3] = bonus;
|
||||
}
|
||||
|
||||
int16_t ItemData::get_common_armor_evasion_bonus() const {
|
||||
return this->data1w[4];
|
||||
}
|
||||
|
||||
void ItemData::set_common_armor_evasion_bonus(int16_t bonus) {
|
||||
this->data1w[4] = bonus;
|
||||
}
|
||||
|
||||
int16_t ItemData::get_unit_bonus() const {
|
||||
return this->data1w[3];
|
||||
}
|
||||
|
||||
|
||||
void ItemData::set_item_unit_bonus(int16_t bonus) {
|
||||
void ItemData::set_unit_bonus(int16_t bonus) {
|
||||
this->data1w[3] = bonus;
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool ItemData::has_bonuses() const {
|
||||
switch (this->data1[0]) {
|
||||
case 0:
|
||||
for (size_t z = 6; z <= 10; z += 2) {
|
||||
if (this->data1[z] != 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
case 1:
|
||||
switch (this->data1[1]) {
|
||||
case 1:
|
||||
if (this->data1[5] != 0) {
|
||||
return true;
|
||||
}
|
||||
[[fallthrough]];
|
||||
case 2:
|
||||
return ((this->get_armor_or_shield_defense_bonus() > 0) ||
|
||||
(this->get_common_armor_evasion_bonus() > 0));
|
||||
case 3:
|
||||
return (this->get_unit_bonus() > 0);
|
||||
default:
|
||||
throw runtime_error("invalid item");
|
||||
}
|
||||
case 2:
|
||||
if (this->data1[1] < 0x23) {
|
||||
return ((this->data1[1] == 0x1D) || (this->data1[1] == 0x22));
|
||||
} else {
|
||||
return (this->data1[1] == 0x27);
|
||||
}
|
||||
case 3:
|
||||
case 4:
|
||||
return false;
|
||||
default:
|
||||
throw runtime_error("invalid item");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool ItemData::compare_for_sort(const ItemData& a, const ItemData& b) {
|
||||
for (size_t z = 0; z < 12; z++) {
|
||||
if (a.data1[z] < b.data1[z]) {
|
||||
|
||||
+7
-1
@@ -81,10 +81,16 @@ struct ItemData { // 0x14 bytes
|
||||
void clear_mag_stats();
|
||||
|
||||
void set_unidentified_or_present_flag(uint16_t v);
|
||||
uint8_t get_tool_item_amount() const;
|
||||
void set_tool_item_amount(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;
|
||||
void set_common_armor_evasion_bonus(int16_t bonus);
|
||||
void set_item_unit_bonus(int16_t bonus);
|
||||
int16_t get_unit_bonus() const;
|
||||
void set_unit_bonus(int16_t bonus);
|
||||
|
||||
bool has_bonuses() const;
|
||||
|
||||
bool empty() const;
|
||||
|
||||
|
||||
+109
-8
@@ -71,7 +71,7 @@ pair<uint8_t, uint8_t> ItemParameterTable::find_tool_by_class(
|
||||
}
|
||||
}
|
||||
}
|
||||
throw out_of_range("invalid tool class");
|
||||
throw runtime_error("invalid tool class");
|
||||
}
|
||||
|
||||
const ItemParameterTable::Mag& ItemParameterTable::get_mag(
|
||||
@@ -131,10 +131,10 @@ uint8_t ItemParameterTable::get_special_stars(uint8_t det) const {
|
||||
|
||||
uint8_t ItemParameterTable::get_max_tech_level(uint8_t char_class, uint8_t tech_num) const {
|
||||
if (char_class >= 12) {
|
||||
throw logic_error("invalid character class");
|
||||
throw runtime_error("invalid character class");
|
||||
}
|
||||
if (tech_num >= 19) {
|
||||
throw logic_error("invalid technique number");
|
||||
throw runtime_error("invalid technique number");
|
||||
}
|
||||
return r.pget_u8(this->offsets->max_tech_level_table + tech_num * 12 + char_class);
|
||||
}
|
||||
@@ -152,7 +152,7 @@ const ItemParameterTable::ItemBase& ItemParameterTable::get_item_definition(
|
||||
} else if ((item.data1[1] == 1) || (item.data1[1] == 2)) {
|
||||
return this->get_armor_or_shield(item.data1[1], item.data1[2]).base;
|
||||
}
|
||||
throw logic_error("invalid item");
|
||||
throw runtime_error("invalid item");
|
||||
case 2:
|
||||
return this->get_mag(item.data1[1]).base;
|
||||
case 3:
|
||||
@@ -163,13 +163,13 @@ const ItemParameterTable::ItemBase& ItemParameterTable::get_item_definition(
|
||||
}
|
||||
throw logic_error("this should be impossible");
|
||||
case 4:
|
||||
throw logic_error("item is meseta and therefore has no definition");
|
||||
throw runtime_error("item is meseta and therefore has no definition");
|
||||
default:
|
||||
throw logic_error("invalid item");
|
||||
throw runtime_error("invalid item");
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t ItemParameterTable::get_item_stars(const ItemData& item) const {
|
||||
uint8_t ItemParameterTable::get_item_base_stars(const ItemData& item) const {
|
||||
if (item.data1[0] == 2) {
|
||||
return (item.data1[1] > 0x27) ? 12 : 0;
|
||||
} else if (item.data1[0] < 2) {
|
||||
@@ -184,8 +184,31 @@ uint8_t ItemParameterTable::get_item_stars(const ItemData& item) const {
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t ItemParameterTable::get_item_adjusted_stars(const ItemData& item) const {
|
||||
uint8_t ret = this->get_item_base_stars(item);
|
||||
if (item.data1[0] == 0) {
|
||||
if (ret < 9) {
|
||||
if (!(item.data1[4] & 0x80)) {
|
||||
ret += this->get_special_stars(item.data1[4]);
|
||||
}
|
||||
} else if (item.data1[4] & 0x80) {
|
||||
ret = 0;
|
||||
}
|
||||
} else if (item.data1[0] == 1) {
|
||||
if (item.data1[1] == 3) {
|
||||
int16_t unit_bonus = item.get_unit_bonus();
|
||||
if (unit_bonus < 0) {
|
||||
ret--;
|
||||
} else if (unit_bonus > 0) {
|
||||
ret++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return min<uint8_t>(ret, 12);
|
||||
}
|
||||
|
||||
bool ItemParameterTable::is_item_rare(const ItemData& item) const {
|
||||
return (this->get_item_stars(item) >= 9);
|
||||
return (this->get_item_base_stars(item) >= 9);
|
||||
}
|
||||
|
||||
bool ItemParameterTable::is_unsealable_item(const ItemData& item) const {
|
||||
@@ -202,3 +225,81 @@ bool ItemParameterTable::is_unsealable_item(const ItemData& item) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
size_t ItemParameterTable::price_for_item(const ItemData& item) const {
|
||||
switch (item.data1[0]) {
|
||||
case 0: {
|
||||
if (item.data1[4] & 0x80) {
|
||||
return 8;
|
||||
}
|
||||
if (this->is_item_rare(item)) {
|
||||
return 80;
|
||||
}
|
||||
|
||||
float sale_divisor = this->get_sale_divisor(item.data1[0], item.data1[1]);
|
||||
if (sale_divisor == 0.0) {
|
||||
throw runtime_error("item sale divisor is zero");
|
||||
}
|
||||
|
||||
const auto& def = this->get_weapon(item.data1[1], item.data1[2]);
|
||||
double atp_max = def.atp_max + item.data1[3];
|
||||
double atp_factor = ((atp_max * atp_max) / sale_divisor);
|
||||
|
||||
double bonus_factor = 0.0;
|
||||
for (size_t bonus_index = 0; bonus_index < 3; bonus_index++) {
|
||||
uint8_t bonus_type = item.data1[(2 * bonus_index) + 6];
|
||||
if ((bonus_type > 0) && (bonus_type < 6)) {
|
||||
bonus_factor += item.data1[(2 * bonus_index) + 7];
|
||||
}
|
||||
bonus_factor += 100.0;
|
||||
}
|
||||
|
||||
size_t special_stars = this->get_special_stars(item.data1[4]);
|
||||
double special_stars_factor = 1000.0 * special_stars * special_stars;
|
||||
|
||||
return special_stars_factor + (atp_factor * (bonus_factor / 100.0));
|
||||
}
|
||||
|
||||
case 1: {
|
||||
if (this->is_item_rare(item)) {
|
||||
return 80;
|
||||
}
|
||||
|
||||
if (item.data1[1] == 3) { // Unit
|
||||
return this->get_item_adjusted_stars(item) * this->get_sale_divisor(item.data1[0], 3);
|
||||
}
|
||||
|
||||
double sale_divisor = (double)this->get_sale_divisor(item.data1[0], item.data1[1]);
|
||||
if (sale_divisor == 0.0) {
|
||||
throw runtime_error("item sale divisor is zero");
|
||||
}
|
||||
|
||||
int16_t def_bonus = item.get_armor_or_shield_defense_bonus();
|
||||
int16_t evp_bonus = item.get_common_armor_evasion_bonus();
|
||||
|
||||
const auto& def = this->get_armor_or_shield(item.data1[1], item.data1[2]);
|
||||
double power_factor = def.dfp + def.evp + def_bonus + evp_bonus;
|
||||
double power_factor_floor = static_cast<int32_t>((power_factor * power_factor) / sale_divisor);
|
||||
return power_factor_floor + (
|
||||
70.0 *
|
||||
static_cast<double>(item.data1[5] + 1) *
|
||||
static_cast<double>(def.required_level + 1));
|
||||
}
|
||||
|
||||
case 2:
|
||||
return (item.data1[2] + 1) * this->get_sale_divisor(2, item.data1[1]);
|
||||
|
||||
case 3: {
|
||||
const auto& def = this->get_tool(item.data1[1], item.data1[2]);
|
||||
return def.cost * ((item.data1[1] == 2) ? (item.data1[2] + 1) : 1);
|
||||
}
|
||||
|
||||
case 4:
|
||||
return item.data2d;
|
||||
|
||||
default:
|
||||
throw runtime_error("invalid item");
|
||||
}
|
||||
throw logic_error("this should be impossible");
|
||||
}
|
||||
|
||||
@@ -229,10 +229,13 @@ public:
|
||||
uint8_t get_max_tech_level(uint8_t char_class, uint8_t tech_num) const;
|
||||
|
||||
const ItemBase& get_item_definition(const ItemData& item) const;
|
||||
uint8_t get_item_stars(const ItemData& item) const;
|
||||
uint8_t get_item_base_stars(const ItemData& item) const;
|
||||
uint8_t get_item_adjusted_stars(const ItemData& item) const;
|
||||
bool is_item_rare(const ItemData& item) const;
|
||||
bool is_unsealable_item(const ItemData& param_1) const;
|
||||
|
||||
size_t price_for_item(const ItemData& item) const;
|
||||
|
||||
private:
|
||||
std::shared_ptr<const std::string> data;
|
||||
StringReader r;
|
||||
|
||||
+2
-1
@@ -3,6 +3,7 @@
|
||||
#include <inttypes.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
@@ -512,7 +513,7 @@ public:
|
||||
std::string bb_username;
|
||||
size_t bb_player_index;
|
||||
PlayerInventoryItem identify_result;
|
||||
std::vector<ItemData> shop_contents;
|
||||
std::array<std::vector<ItemData>, 3> shop_contents;
|
||||
bool should_save;
|
||||
|
||||
ClientGameData();
|
||||
|
||||
+65
-19
@@ -838,19 +838,20 @@ static void on_open_shop_bb_or_ep3_battle_subs(shared_ptr<ServerState> s,
|
||||
size_t level = c->game_data.player()->disp.level + 1;
|
||||
switch (cmd.shop_type) {
|
||||
case 0:
|
||||
c->game_data.shop_contents = l->item_creator->generate_tool_shop_contents(level);
|
||||
c->game_data.shop_contents[0] = l->item_creator->generate_tool_shop_contents(level);
|
||||
break;
|
||||
case 1:
|
||||
c->game_data.shop_contents = l->item_creator->generate_weapon_shop_contents(level);
|
||||
c->game_data.shop_contents[1] = l->item_creator->generate_weapon_shop_contents(level);
|
||||
break;
|
||||
case 2:
|
||||
c->game_data.shop_contents = l->item_creator->generate_armor_shop_contents(level);
|
||||
c->game_data.shop_contents[2] = l->item_creator->generate_armor_shop_contents(level);
|
||||
break;
|
||||
default:
|
||||
throw runtime_error("invalid shop type");
|
||||
}
|
||||
for (auto& item : c->game_data.shop_contents) {
|
||||
for (auto& item : c->game_data.shop_contents[cmd.shop_type]) {
|
||||
item.id = l->generate_item_id(c->lobby_client_id);
|
||||
item.data2d = s->item_parameter_table->price_for_item(item);
|
||||
}
|
||||
|
||||
send_shop(c, cmd.shop_type);
|
||||
@@ -1188,6 +1189,11 @@ static void on_destroy_inventory_item(shared_ptr<ServerState>,
|
||||
auto name = item.data.name(false);
|
||||
l->log.info("Inventory item %hu:%08" PRIX32 " destroyed (%s)",
|
||||
cmd.header.client_id.load(), cmd.item_id.load(), name.c_str());
|
||||
if (c->options.debug) {
|
||||
string name = item.data.name(true);
|
||||
send_text_message_printf(c, "$C5Items: destroy %08" PRIX32 "\n%s",
|
||||
cmd.item_id.load(), name.c_str());
|
||||
}
|
||||
c->game_data.player()->print_inventory(stderr);
|
||||
forward_subcommand(l, c, command, flag, data);
|
||||
}
|
||||
@@ -1205,6 +1211,11 @@ static void on_destroy_ground_item(shared_ptr<ServerState>,
|
||||
auto name = item.data.name(false);
|
||||
l->log.info("Ground item %08" PRIX32 " destroyed (%s)", cmd.item_id.load(),
|
||||
name.c_str());
|
||||
if (c->options.debug) {
|
||||
string name = item.data.name(true);
|
||||
send_text_message_printf(c, "$C5Items: destroy/ground %08" PRIX32 "\n%s",
|
||||
cmd.item_id.load(), name.c_str());
|
||||
}
|
||||
forward_subcommand(l, c, command, flag, data);
|
||||
}
|
||||
}
|
||||
@@ -1269,37 +1280,72 @@ static void on_accept_identify_item_bb(shared_ptr<ServerState>,
|
||||
}
|
||||
}
|
||||
|
||||
static void on_sell_item_at_shop_bb(shared_ptr<ServerState>,
|
||||
shared_ptr<Lobby> l, shared_ptr<Client>, uint8_t, uint8_t, const string&) {
|
||||
static void on_sell_item_at_shop_bb(shared_ptr<ServerState> s,
|
||||
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag, const string& data) {
|
||||
|
||||
if (l->version == GameVersion::BB) {
|
||||
// const auto& cmd = check_size_sc<G_ItemSubcommand>(data);
|
||||
const auto& cmd = check_size_sc<G_SellItemAtShop_BB_6xC0>(data);
|
||||
|
||||
if (!(l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED)) {
|
||||
throw logic_error("item tracking not enabled in BB game");
|
||||
}
|
||||
|
||||
// TODO: We should subtract the appropriate amount of meseta and do an
|
||||
// appropriate send_create_inventory_item call here. Shop prices are not
|
||||
// implemented yet, though, which is why this is difficult.
|
||||
throw logic_error("shop actions are not yet implemented");
|
||||
auto item = c->game_data.player()->remove_item(
|
||||
cmd.item_id, cmd.amount, c->version() != GameVersion::BB);
|
||||
size_t price = (s->item_parameter_table->price_for_item(item.data) >> 3) * cmd.amount;
|
||||
c->game_data.player()->disp.meseta = min<uint32_t>(
|
||||
c->game_data.player()->disp.meseta + price, 999999);
|
||||
|
||||
auto name = item.data.name(false);
|
||||
l->log.info("Inventory item %hu:%08" PRIX32 " destroyed via sale (%s)",
|
||||
c->lobby_client_id, cmd.item_id.load(), name.c_str());
|
||||
c->game_data.player()->print_inventory(stderr);
|
||||
if (c->options.debug) {
|
||||
string name = item.data.name(true);
|
||||
send_text_message_printf(c, "$C5Items: destroy/sale %08" PRIX32 "\n+%zu Meseta\n%s",
|
||||
cmd.item_id.load(), price, name.c_str());
|
||||
}
|
||||
|
||||
forward_subcommand(l, c, command, flag, data);
|
||||
}
|
||||
}
|
||||
|
||||
static void on_buy_shop_item_bb(shared_ptr<ServerState>,
|
||||
shared_ptr<Lobby> l, shared_ptr<Client>, uint8_t, uint8_t, const string&) {
|
||||
|
||||
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t, uint8_t, const string& data) {
|
||||
if (l->version == GameVersion::BB) {
|
||||
// const auto& cmd = check_size_sc<G_BuyShopItem_BB_6xB7>(data);
|
||||
|
||||
const auto& cmd = check_size_sc<G_BuyShopItem_BB_6xB7>(data);
|
||||
if (!(l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED)) {
|
||||
throw logic_error("item tracking not enabled in BB game");
|
||||
}
|
||||
|
||||
// TODO: We should subtract the appropriate amount of meseta and do an
|
||||
// appropriate send_create_inventory_item call here. Shop prices are not
|
||||
// implemented yet, though, which is why this is difficult.
|
||||
throw logic_error("shop actions are not yet implemented");
|
||||
PlayerInventoryItem item;
|
||||
item.data = c->game_data.shop_contents.at(cmd.shop_type).at(cmd.item_index);
|
||||
if (item.data.is_stackable()) {
|
||||
item.data.data1[5] = cmd.amount;
|
||||
} else if (cmd.amount != 1) {
|
||||
throw runtime_error("item is not stackable");
|
||||
}
|
||||
|
||||
size_t price = item.data.data2d * cmd.amount;
|
||||
item.data.data2d = 0;
|
||||
if (c->game_data.player()->disp.meseta < price) {
|
||||
throw runtime_error("player does not have enough money");
|
||||
}
|
||||
c->game_data.player()->disp.meseta -= price;
|
||||
|
||||
item.data.id = cmd.inventory_item_id;
|
||||
c->game_data.player()->add_item(item);
|
||||
send_create_inventory_item(l, c, item.data);
|
||||
|
||||
auto name = item.data.name(false);
|
||||
l->log.info("Inventory item %hu:%08" PRIX32 " created via purchase (%s) for %zu meseta",
|
||||
c->lobby_client_id, cmd.inventory_item_id.load(), name.c_str(), price);
|
||||
c->game_data.player()->print_inventory(stderr);
|
||||
if (c->options.debug) {
|
||||
string name = item.data.name(true);
|
||||
send_text_message_printf(c, "$C5Items: create/purchase %08" PRIX32 "\n-%zu Meseta\n%s",
|
||||
cmd.inventory_item_id.load(), price, name.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+7
-11
@@ -1870,24 +1870,20 @@ void send_bank(shared_ptr<Client> c) {
|
||||
|
||||
// sends the player a shop's contents
|
||||
void send_shop(shared_ptr<Client> c, uint8_t shop_type) {
|
||||
const auto& contents = c->game_data.shop_contents.at(shop_type);
|
||||
|
||||
G_ShopContents_BB_6xB6 cmd = {
|
||||
{0xB6, 0x2C, 0x037F},
|
||||
{0xB6, static_cast<uint8_t>(2 + (sizeof(ItemData) >> 2) * contents.size()), 0x0000},
|
||||
shop_type,
|
||||
static_cast<uint8_t>(c->game_data.shop_contents.size()),
|
||||
static_cast<uint8_t>(contents.size()),
|
||||
0,
|
||||
{},
|
||||
};
|
||||
|
||||
size_t count = c->game_data.shop_contents.size();
|
||||
if (count > sizeof(cmd.entries) / sizeof(cmd.entries[0])) {
|
||||
throw logic_error("too many items in shop");
|
||||
for (size_t x = 0; x < contents.size(); x++) {
|
||||
cmd.entries[x] = contents[x];
|
||||
}
|
||||
|
||||
for (size_t x = 0; x < count; x++) {
|
||||
cmd.entries[x] = c->game_data.shop_contents[x];
|
||||
}
|
||||
|
||||
send_command(c, 0x6C, 0x00, &cmd, sizeof(cmd) - sizeof(cmd.entries[0]) * (20 - count));
|
||||
send_command(c, 0x60, 0x00, &cmd, sizeof(cmd) - sizeof(cmd.entries[0]) * (20 - contents.size()));
|
||||
}
|
||||
|
||||
// notifies players about a level up
|
||||
|
||||
Reference in New Issue
Block a user