diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index 94449249..46bca979 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -305,57 +305,57 @@ static void command_edit(shared_ptr s, shared_ptr l, } if (tokens[0] == "atp") { - c->player.disp.stats.atp = stoul(tokens[1]); + c->game_data.player()->disp.stats.atp = stoul(tokens[1]); } else if (tokens[0] == "mst") { - c->player.disp.stats.mst = stoul(tokens[1]); + c->game_data.player()->disp.stats.mst = stoul(tokens[1]); } else if (tokens[0] == "evp") { - c->player.disp.stats.evp = stoul(tokens[1]); + c->game_data.player()->disp.stats.evp = stoul(tokens[1]); } else if (tokens[0] == "hp") { - c->player.disp.stats.hp = stoul(tokens[1]); + c->game_data.player()->disp.stats.hp = stoul(tokens[1]); } else if (tokens[0] == "dfp") { - c->player.disp.stats.dfp = stoul(tokens[1]); + c->game_data.player()->disp.stats.dfp = stoul(tokens[1]); } else if (tokens[0] == "ata") { - c->player.disp.stats.ata = stoul(tokens[1]); + c->game_data.player()->disp.stats.ata = stoul(tokens[1]); } else if (tokens[0] == "lck") { - c->player.disp.stats.lck = stoul(tokens[1]); + c->game_data.player()->disp.stats.lck = stoul(tokens[1]); } else if (tokens[0] == "meseta") { - c->player.disp.meseta = stoul(tokens[1]); + c->game_data.player()->disp.meseta = stoul(tokens[1]); } else if (tokens[0] == "exp") { - c->player.disp.experience = stoul(tokens[1]); + c->game_data.player()->disp.experience = stoul(tokens[1]); } else if (tokens[0] == "level") { - c->player.disp.level = stoul(tokens[1]) - 1; + c->game_data.player()->disp.level = stoul(tokens[1]) - 1; } else if (tokens[0] == "namecolor") { uint32_t new_color; sscanf(tokens[1].c_str(), "%8X", &new_color); - c->player.disp.name_color = new_color; + c->game_data.player()->disp.name_color = new_color; } else if (tokens[0] == "secid") { uint8_t secid = section_id_for_name(decode_sjis(tokens[1])); if (secid == 0xFF) { send_text_message(c, u"$C6No such section ID."); return; } else { - c->player.disp.section_id = secid; + c->game_data.player()->disp.section_id = secid; } } else if (tokens[0] == "name") { - c->player.disp.name = add_language_marker(tokens[1], 'J'); + c->game_data.player()->disp.name = add_language_marker(tokens[1], 'J'); } else if (tokens[0] == "npc") { if (tokens[1] == "none") { - c->player.disp.extra_model = 0; - c->player.disp.v2_flags &= 0xFD; + c->game_data.player()->disp.extra_model = 0; + c->game_data.player()->disp.v2_flags &= 0xFD; } else { uint8_t npc = npc_for_name(decode_sjis(tokens[1])); if (npc == 0xFF) { send_text_message(c, u"$C6No such NPC."); return; } - c->player.disp.extra_model = npc; - c->player.disp.v2_flags |= 0x02; + c->game_data.player()->disp.extra_model = npc; + c->game_data.player()->disp.v2_flags |= 0x02; } } else if ((tokens[0] == "tech") && (tokens.size() > 2)) { uint8_t level = stoul(tokens[2]) - 1; if (tokens[1] == "all") { for (size_t x = 0; x < 0x14; x++) { - c->player.disp.technique_levels.data()[x] = level; + c->game_data.player()->disp.technique_levels.data()[x] = level; } } else { uint8_t tech_id = technique_for_name(decode_sjis(tokens[1])); @@ -363,7 +363,7 @@ static void command_edit(shared_ptr s, shared_ptr l, send_text_message(c, u"$C6No such technique."); return; } - c->player.disp.technique_levels.data()[tech_id] = level; + c->game_data.player()->disp.technique_levels.data()[tech_id] = level; } } else { send_text_message(c, u"$C6Unknown field."); @@ -376,13 +376,14 @@ static void command_edit(shared_ptr s, shared_ptr l, s->send_lobby_join_notifications(l, c); } -static void command_change_bank(shared_ptr, shared_ptr, +// TODO: implement this +// TODO: make sure the bank name is filesystem-safe +/* static void command_change_bank(shared_ptr, shared_ptr, shared_ptr c, const std::u16string&) { check_version(c, GameVersion::BB); - // TODO: implement this - // TODO: make sure the bank name is filesystem-safe -} + TODO +} */ static void command_convert_char_to_bb(shared_ptr s, shared_ptr l, shared_ptr c, const std::u16string& args) { @@ -419,6 +420,19 @@ static void command_convert_char_to_bb(shared_ptr s, //////////////////////////////////////////////////////////////////////////////// // Administration commands +static string name_for_client(shared_ptr c) { + auto player = c->game_data.player(false); + if (player.get()) { + return encode_sjis(player->disp.name); + } + + if (c->license.get()) { + return string_printf("SN:%" PRIu32, c->license->serial_number); + } + + return "Player"; +} + static void command_silence(shared_ptr s, shared_ptr l, shared_ptr c, const std::u16string& args) { check_privileges(c, Privilege::SILENCE_USER); @@ -436,8 +450,8 @@ static void command_silence(shared_ptr s, shared_ptr l, } target->can_chat = !target->can_chat; - string target_name_sjis = encode_sjis(target->player.disp.name); - send_text_message_printf(l, "$C6%s %ssilenced", target_name_sjis.c_str(), + string target_name = name_for_client(target); + send_text_message_printf(l, "$C6%s %ssilenced", target_name.c_str(), target->can_chat ? "un" : ""); } @@ -459,8 +473,8 @@ static void command_kick(shared_ptr s, shared_ptr l, send_message_box(target, u"$C6You were kicked off by a moderator."); target->should_disconnect = true; - string target_name_sjis = encode_sjis(target->player.disp.name); - send_text_message_printf(l, "$C6%s kicked off", target_name_sjis.c_str()); + string target_name = name_for_client(target); + send_text_message_printf(l, "$C6%s kicked off", target_name.c_str()); } static void command_ban(shared_ptr s, shared_ptr l, @@ -510,8 +524,8 @@ static void command_ban(shared_ptr s, shared_ptr l, s->license_manager->ban_until(target->license->serial_number, now() + usecs); send_message_box(target, u"$C6You were banned by a moderator."); target->should_disconnect = true; - auto encoded_name = encode_sjis(target->player.disp.name); - send_text_message_printf(l, "$C6%s banned", encoded_name.c_str()); + string target_name = name_for_client(target); + send_text_message_printf(l, "$C6%s banned", target_name.c_str()); } //////////////////////////////////////////////////////////////////////////////// @@ -648,11 +662,11 @@ static void command_item(shared_ptr, shared_ptr l, ItemData item_data; memset(&item_data, 0, sizeof(item_data)); - if (data.size() < 12) { - memcpy(&l->next_drop_item.data.item_data1, data.data(), data.size()); + if (data.size() <= 12) { + memcpy(&l->next_drop_item.data.data1, data.data(), data.size()); } else { - memcpy(&l->next_drop_item.data.item_data1, data.data(), 12); - memcpy(&l->next_drop_item.data.item_data2, data.data() + 12, 12 - data.size()); + memcpy(&l->next_drop_item.data.data1, data.data(), 12); + memcpy(&l->next_drop_item.data.data2, data.data() + 12, 12 - data.size()); } send_text_message(c, u"$C6Next drop chosen."); @@ -677,7 +691,7 @@ static const unordered_map chat_commands({ {u"$ax" , {command_ax , u"Usage:\nax "}}, {u"$ban" , {command_ban , u"Usage:\nban "}}, {u"$bbchar" , {command_convert_char_to_bb, u"Usage:\nbbchar <1-4>"}}, - {u"$changebank", {command_change_bank , u"Usage:\nchangebank "}}, + // {u"$bank", {command_bank , u"Usage:\nbank "}}, {u"$cheat" , {command_cheat , u"Usage:\ncheat"}}, {u"$edit" , {command_edit , u"Usage:\nedit "}}, {u"$event" , {command_lobby_event , u"Usage:\nevent "}}, diff --git a/src/Client.cc b/src/Client.cc index f0831d0d..cb615479 100644 --- a/src/Client.cc +++ b/src/Client.cc @@ -58,6 +58,14 @@ Client::Client( memset(&this->next_connection_addr, 0, sizeof(this->next_connection_addr)); } +void Client::set_license(shared_ptr l) { + this->license = l; + this->game_data.serial_number = this->license->serial_number; + if (this->version == GameVersion::BB) { + this->game_data.bb_username = this->license->username; + } +} + ClientConfig Client::export_config() const { ClientConfig cc; cc.magic = CLIENT_CONFIG_MAGIC; diff --git a/src/Client.hh b/src/Client.hh index 50140ce5..54b19b08 100644 --- a/src/Client.hh +++ b/src/Client.hh @@ -100,7 +100,7 @@ struct Client { uint32_t lobby_id; // which lobby is this person in? uint8_t lobby_client_id; // which client number is this person? uint8_t lobby_arrow_color; // lobby arrow color ID - Player player; + ClientGameData game_data; // Miscellaneous (used by chat commands) uint32_t next_exp_value; // next EXP value to give @@ -116,6 +116,8 @@ struct Client { Client(struct bufferevent* bev, GameVersion version, ServerBehavior server_behavior); + void set_license(std::shared_ptr l); + ClientConfig export_config() const; ClientConfigBB export_config_bb() const; void import_config(const ClientConfig& cc); diff --git a/src/Items.cc b/src/Items.cc index a7ad7779..5fef5d7a 100644 --- a/src/Items.cc +++ b/src/Items.cc @@ -142,23 +142,24 @@ using namespace std; //////////////////////////////////////////////////////////////////////////////// void player_use_item(shared_ptr c, size_t item_index) { + auto player = c->game_data.player(); ssize_t equipped_weapon = -1; // ssize_t equipped_armor = -1; // ssize_t equipped_shield = -1; // ssize_t equipped_mag = -1; - for (size_t y = 0; y < c->player.inventory.num_items; y++) { - if (c->player.inventory.items[y].equip_flags & 0x0008) { - if (c->player.inventory.items[y].data.item_data1[0] == 0) { + for (size_t y = 0; y < c->game_data.player()->inventory.num_items; y++) { + if (c->game_data.player()->inventory.items[y].equip_flags & 0x0008) { + if (c->game_data.player()->inventory.items[y].data.data1[0] == 0) { equipped_weapon = y; } - // else if ((c->player.inventory.items[y].data.item_data1[0] == 1) && - // (c->player.inventory.items[y].data.item_data1[1] == 1)) { + // else if ((c->game_data.player()->inventory.items[y].data.data1[0] == 1) && + // (c->game_data.player()->inventory.items[y].data.data1[1] == 1)) { // equipped_armor = y; - // } else if ((c->player.inventory.items[y].data.item_data1[0] == 1) && - // (c->player.inventory.items[y].data.item_data1[1] == 2)) { + // } else if ((c->game_data.player()->inventory.items[y].data.data1[0] == 1) && + // (c->game_data.player()->inventory.items[y].data.data1[1] == 2)) { // equipped_shield = y; - // } else if (c->player.inventory.items[y].data.item_data1[0] == 2) { + // } else if (c->game_data.player()->inventory.items[y].data.data1[0] == 2) { // equipped_mag = y; // } } @@ -166,42 +167,42 @@ void player_use_item(shared_ptr c, size_t item_index) { bool should_delete_item = true; - auto& item = c->player.inventory.items[item_index]; - if (item.data.item_data1w[0] == 0x0203) { // technique disk - c->player.disp.technique_levels.data()[item.data.item_data1[4]] = item.data.item_data1[2]; + auto& item = c->game_data.player()->inventory.items[item_index]; + if (item.data.data1w[0] == 0x0203) { // technique disk + c->game_data.player()->disp.technique_levels.data()[item.data.data1[4]] = item.data.data1[2]; - } else if (item.data.item_data1w[0] == 0x0A03) { // grinder + } else if (item.data.data1w[0] == 0x0A03) { // grinder if (equipped_weapon < 0) { throw invalid_argument("grinder used with no weapon equipped"); } - if (item.data.item_data1[2] > 2) { + if (item.data.data1[2] > 2) { throw invalid_argument("incorrect grinder value"); } - c->player.inventory.items[equipped_weapon].data.item_data1[3] += (item.data.item_data1[2] + 1); + c->game_data.player()->inventory.items[equipped_weapon].data.data1[3] += (item.data.data1[2] + 1); // TODO: we should check for max grind here - } else if (item.data.item_data1w[0] == 0x0B03) { // material - switch (item.data.item_data1[2]) { + } else if (item.data.data1w[0] == 0x0B03) { // material + switch (item.data.data1[2]) { case 0: // Power Material - c->player.disp.stats.atp += 2; + c->game_data.player()->disp.stats.atp += 2; break; case 1: // Mind Material - c->player.disp.stats.mst += 2; + c->game_data.player()->disp.stats.mst += 2; break; case 2: // Evade Material - c->player.disp.stats.evp += 2; + c->game_data.player()->disp.stats.evp += 2; break; case 3: // HP Material - c->player.inventory.hp_materials_used += 2; + c->game_data.player()->inventory.hp_materials_used += 2; break; case 4: // TP Material - c->player.inventory.tp_materials_used += 2; + c->game_data.player()->inventory.tp_materials_used += 2; break; case 5: // Def Material - c->player.disp.stats.dfp += 2; + c->game_data.player()->disp.stats.dfp += 2; break; case 6: // Luck Material - c->player.disp.stats.lck += 2; + c->game_data.player()->disp.stats.lck += 2; break; default: throw invalid_argument("unknown material used"); @@ -209,17 +210,17 @@ void player_use_item(shared_ptr c, size_t item_index) { } else { // default item action is to unwrap the item if it's a present - if ((item.data.item_data1[0] == 2) && (item.data.item_data2[2] & 0x40)) { - item.data.item_data2[2] &= 0xBF; + if ((item.data.data1[0] == 2) && (item.data.data2[2] & 0x40)) { + item.data.data2[2] &= 0xBF; should_delete_item = false; - } else if ((item.data.item_data1[0] != 2) && (item.data.item_data1[4] & 0x40)) { - item.data.item_data1[4] &= 0xBF; + } else if ((item.data.data1[0] != 2) && (item.data.data1[4] & 0x40)) { + item.data.data1[4] &= 0xBF; should_delete_item = false; } } if (should_delete_item) { - c->player.remove_item(item.data.item_id, 1); + c->game_data.player()->remove_item(item.data.id, 1); } } @@ -323,56 +324,56 @@ ItemData CommonItemCreator::create_drop_item(bool is_box, uint8_t episode, int32_t type = this->decide_item_type(is_box); switch (type) { case 0x00: // material - item.item_data1[0] = 0x03; - item.item_data1[1] = 0x0B; - item.item_data1[2] = random_int(0, 6); + item.data1[0] = 0x03; + item.data1[1] = 0x0B; + item.data1[2] = random_int(0, 6); break; case 0x01: // equipment switch (random_int(0, 3)) { case 0x00: // weapon - item.item_data1[1] = random_int(1, 12); // random normal class - item.item_data1[2] = difficulty + random_int(0, 2); // special type - if ((item.item_data1[1] > 0x09) && (item.item_data1[2] > 0x04)) { - item.item_data1[2] = 0x04; // no special classes above 4 + item.data1[1] = random_int(1, 12); // random normal class + item.data1[2] = difficulty + random_int(0, 2); // special type + if ((item.data1[1] > 0x09) && (item.data1[2] > 0x04)) { + item.data1[2] = 0x04; // no special classes above 4 } - item.item_data1[4] = 0x80; // untekked - if (item.item_data1[2] < 0x04) { - item.item_data1[4] |= random_int(0, 40); // give a special + item.data1[4] = 0x80; // untekked + if (item.data1[2] < 0x04) { + item.data1[4] |= random_int(0, 40); // give a special } for (size_t x = 0, y = 0; (x < 5) && (y < 3); x++) { // percentages if (random_int(0, 10) == 1) { // 1/11 chance of getting each type of percentage - item.item_data1[6 + (y * 2)] = x + 1; - item.item_data1[7 + (y * 2)] = random_int(0, 10) * 5; + item.data1[6 + (y * 2)] = x + 1; + item.data1[7 + (y * 2)] = random_int(0, 10) * 5; y++; } } break; case 0x01: // armor - item.item_data1[0] = 0x01; - item.item_data1[1] = 0x01; - item.item_data1[2] = (6 * difficulty) + random_int(0, ((area / 2) + 2) - 1); // standard type based on difficulty and area - if (item.item_data1[2] > 0x17) { - item.item_data1[2] = 0x17; // no standard types above 0x17 + item.data1[0] = 0x01; + item.data1[1] = 0x01; + item.data1[2] = (6 * difficulty) + random_int(0, ((area / 2) + 2) - 1); // standard type based on difficulty and area + if (item.data1[2] > 0x17) { + item.data1[2] = 0x17; // no standard types above 0x17 } if (random_int(0, 10) == 0) { // +/- - item.item_data1[4] = random_int(0, 5); - item.item_data1[6] = random_int(0, 2); + item.data1[4] = random_int(0, 5); + item.data1[6] = random_int(0, 2); } - item.item_data1[5] = random_int(0, 4); // slots + item.data1[5] = random_int(0, 4); // slots break; case 0x02: // shield - item.item_data1[0] = 0x01; - item.item_data1[1] = 0x02; - item.item_data1[2] = (5 * difficulty) + random_int(0, ((area / 2) + 2) - 1); // standard type based on difficulty and area - if (item.item_data1[2] > 0x14) { - item.item_data1[2] = 0x14; // no standard types above 0x14 + item.data1[0] = 0x01; + item.data1[1] = 0x02; + item.data1[2] = (5 * difficulty) + random_int(0, ((area / 2) + 2) - 1); // standard type based on difficulty and area + if (item.data1[2] > 0x14) { + item.data1[2] = 0x14; // no standard types above 0x14 } if (random_int(0, 10) == 0) { // +/- - item.item_data1[4] = random_int(0, 5); - item.item_data1[6] = random_int(0, 5); + item.data1[4] = random_int(0, 5); + item.data1[6] = random_int(0, 5); } break; @@ -382,81 +383,81 @@ ItemData CommonItemCreator::create_drop_item(bool is_box, uint8_t episode, if (type == 0xFF) { throw out_of_range("no item dropped"); // 0xFF -> no item drops } - item.item_data1[0] = 0x01; - item.item_data1[1] = 0x03; - item.item_data1[2] = type; + item.data1[0] = 0x01; + item.data1[1] = 0x03; + item.data1[2] = type; break; } } break; case 0x02: // technique - item.item_data1[0] = 0x03; - item.item_data1[1] = 0x02; - item.item_data1[4] = random_int(0, 18); // tech type - if ((item.item_data1[4] != 14) && (item.item_data1[4] != 17)) { // if not ryuker or reverser, give it a level - if (item.item_data1[4] == 16) { // if not anti, give it a level between 1 and 30 + item.data1[0] = 0x03; + item.data1[1] = 0x02; + item.data1[4] = random_int(0, 18); // tech type + if ((item.data1[4] != 14) && (item.data1[4] != 17)) { // if not ryuker or reverser, give it a level + if (item.data1[4] == 16) { // if not anti, give it a level between 1 and 30 if (area > 3) { - item.item_data1[2] = difficulty + random_int(0, ((area - 1) / 2) - 1); + item.data1[2] = difficulty + random_int(0, ((area - 1) / 2) - 1); } else { - item.item_data1[2] = difficulty; + item.data1[2] = difficulty; } - if (item.item_data1[2] > 6) { - item.item_data1[2] = 6; + if (item.data1[2] > 6) { + item.data1[2] = 6; } } else { - item.item_data1[2] = (5 * difficulty) + random_int(0, ((area * 3) / 2) - 1); // else between 1 and 7 + item.data1[2] = (5 * difficulty) + random_int(0, ((area * 3) / 2) - 1); // else between 1 and 7 } } break; case 0x03: // scape doll - item.item_data1[0] = 0x03; - item.item_data1[1] = 0x09; - item.item_data1[2] = 0x00; + item.data1[0] = 0x03; + item.data1[1] = 0x09; + item.data1[2] = 0x00; break; case 0x04: // grinder - item.item_data1[0] = 0x03; - item.item_data1[1] = 0x0A; - item.item_data1[2] = random_int(0, 2); // mono, di, tri + item.data1[0] = 0x03; + item.data1[1] = 0x0A; + item.data1[2] = random_int(0, 2); // mono, di, tri break; case 0x05: // consumable - item.item_data1[0] = 0x03; - item.item_data1[5] = 0x01; + item.data1[0] = 0x03; + item.data1[5] = 0x01; switch (random_int(0, 2)) { case 0: // antidote / antiparalysis - item.item_data1[1] = 6; - item.item_data1[2] = random_int(0, 1); + item.data1[1] = 6; + item.data1[2] = random_int(0, 1); break; case 1: // telepipe / trap vision - item.item_data1[1] = 7 + random_int(0, 1); + item.data1[1] = 7 + random_int(0, 1); break; case 2: // sol / moon / star atomizer - item.item_data1[1] = 3 + random_int(0, 2); + item.data1[1] = 3 + random_int(0, 2); break; } break; case 0x06: // consumable - item.item_data1[0] = 0x03; - item.item_data1[5] = 0x01; - item.item_data1[1] = random_int(0, 1); // mate or fluid + item.data1[0] = 0x03; + item.data1[5] = 0x01; + item.data1[1] = random_int(0, 1); // mate or fluid if (difficulty == 0) { - item.item_data1[2] = random_int(0, 1); // only mono and di on normal + item.data1[2] = random_int(0, 1); // only mono and di on normal } else if (difficulty == 3) { - item.item_data1[2] = random_int(1, 2); // only di and tri on ultimate + item.data1[2] = random_int(1, 2); // only di and tri on ultimate } else { - item.item_data1[2] = random_int(0, 2); // else, any of the three + item.data1[2] = random_int(0, 2); // else, any of the three } break; case 0x07: // meseta - item.item_data1[0] = 0x04; - item.item_data2d = (90 * difficulty) + (random_int(0, 20) * (area * 2)); // meseta amount + item.data1[0] = 0x04; + item.data2d = (90 * difficulty) + (random_int(0, 20) * (area * 2)); // meseta amount break; default: @@ -477,27 +478,27 @@ ItemData CommonItemCreator::create_shop_item(uint8_t difficulty, ItemData item; memset(&item, 0, sizeof(item)); - item.item_data1[0] = item_type; - while (item.item_data1[0] == 2) { - item.item_data1[0] = rand() % 3; + item.data1[0] = item_type; + while (item.data1[0] == 2) { + item.data1[0] = rand() % 3; } - switch (item.item_data1[0]) { + switch (item.data1[0]) { case 0: { // weapon - item.item_data1[1] = (rand() % 12) + 1; - if (item.item_data1[1] > 9) { - item.item_data1[2] = difficulty; + item.data1[1] = (rand() % 12) + 1; + if (item.data1[1] > 9) { + item.data1[2] = difficulty; } else { - item.item_data1[2] = (rand() & 1) + difficulty; + item.data1[2] = (rand() & 1) + difficulty; } - item.item_data1[3] = rand() % 11; - item.item_data1[4] = rand() % 11; + item.data1[3] = rand() % 11; + item.data1[4] = rand() % 11; size_t num_percentages = 0; for (size_t x = 0; (x < 5) && (num_percentages < 3); x++) { if ((rand() % 4) == 1) { - item.item_data1[(num_percentages * 2) + 6] = x; - item.item_data1[(num_percentages * 2) + 7] = rand() % (max_percentages[difficulty] + 1); + item.data1[(num_percentages * 2) + 6] = x; + item.data1[(num_percentages * 2) + 7] = rand() % (max_percentages[difficulty] + 1); num_percentages++; } } @@ -505,69 +506,69 @@ ItemData CommonItemCreator::create_shop_item(uint8_t difficulty, } case 1: // armor - item.item_data1[1] = 0; - while (item.item_data1[1] == 0) { - item.item_data1[1] = rand() & 3; + item.data1[1] = 0; + while (item.data1[1] == 0) { + item.data1[1] = rand() & 3; } - switch (item.item_data1[1]) { + switch (item.data1[1]) { case 1: - item.item_data1[2] = (rand() % 6) + (difficulty * 6); - item.item_data1[5] = rand() % 5; + item.data1[2] = (rand() % 6) + (difficulty * 6); + item.data1[5] = rand() % 5; break; case 2: - item.item_data2[2] = (rand() % 6) + (difficulty * 5); - *reinterpret_cast(&item.item_data1[6]) = (rand() % 9) - 4; - *reinterpret_cast(&item.item_data1[9]) = (rand() % 9) - 4; + item.data2[2] = (rand() % 6) + (difficulty * 5); + *reinterpret_cast(&item.data1[6]) = (rand() % 9) - 4; + *reinterpret_cast(&item.data1[9]) = (rand() % 9) - 4; break; case 3: - item.item_data2[2] = rand() % 0x3B; - *reinterpret_cast(&item.item_data1[7]) = (rand() % 5) - 4; + item.data2[2] = rand() % 0x3B; + *reinterpret_cast(&item.data1[7]) = (rand() % 5) - 4; break; } break; case 3: // tool - item.item_data1[1] = rand() % 12; - switch (item.item_data1[1]) { + item.data1[1] = rand() % 12; + switch (item.data1[1]) { case 0: case 1: if (difficulty == 0) { - item.item_data1[2] = 0; + item.data1[2] = 0; } else if (difficulty == 1) { - item.item_data1[2] = rand() % 2; + item.data1[2] = rand() % 2; } else if (difficulty == 2) { - item.item_data1[2] = (rand() % 2) + 1; + item.data1[2] = (rand() % 2) + 1; } else if (difficulty == 3) { - item.item_data1[2] = 2; + item.data1[2] = 2; } break; case 6: - item.item_data1[2] = rand() % 2; + item.data1[2] = rand() % 2; break; case 10: - item.item_data1[2] = rand() % 3; + item.data1[2] = rand() % 3; break; case 11: - item.item_data1[2] = rand() % 7; + item.data1[2] = rand() % 7; break; } - switch (item.item_data1[1]) { + switch (item.data1[1]) { case 2: - item.item_data1[4] = rand() % 19; - switch (item.item_data1[4]) { + item.data1[4] = rand() % 19; + switch (item.data1[4]) { case 14: case 17: - item.item_data1[2] = 0; // reverser & ryuker always level 1 + item.data1[2] = 0; // reverser & ryuker always level 1 break; case 16: - item.item_data1[2] = rand() % max_anti_level[difficulty]; + item.data1[2] = rand() % max_anti_level[difficulty]; break; default: - item.item_data1[2] = rand() % max_tech_level[difficulty]; + item.data1[2] = rand() % max_tech_level[difficulty]; } break; case 0: @@ -579,7 +580,7 @@ ItemData CommonItemCreator::create_shop_item(uint8_t difficulty, case 7: case 8: case 16: - item.item_data1[5] = rand() % (max_quantity[difficulty] + 1); + item.data1[5] = rand() % (max_quantity[difficulty] + 1); break; } } diff --git a/src/LevelTable.hh b/src/LevelTable.hh index ab45d136..9097ded3 100644 --- a/src/LevelTable.hh +++ b/src/LevelTable.hh @@ -3,8 +3,21 @@ #include #include +#include -#include "Player.hh" + + +struct PlayerStats { + le_uint16_t atp; + le_uint16_t mst; + le_uint16_t evp; + le_uint16_t hp; + le_uint16_t dfp; + le_uint16_t ata; + le_uint16_t lck; + + PlayerStats() noexcept; +} __attribute__((packed)); // information on a single level for a single class struct LevelStats { @@ -15,7 +28,7 @@ struct LevelStats { uint8_t dfp; // dfp to add on level up uint8_t ata; // ata to add on level up uint8_t unknown[2]; - uint32_t experience; // EXP value of this level + le_uint32_t experience; // EXP value of this level void apply(PlayerStats& ps) const; } __attribute__((packed)); @@ -23,7 +36,7 @@ struct LevelStats { // level table format (PlyLevelTbl.prs) struct LevelTable { PlayerStats base_stats[12]; - uint32_t unknown[12]; + le_uint32_t unknown[12]; LevelStats levels[12][200]; LevelTable(const std::string& filename, bool compressed); diff --git a/src/Lobby.cc b/src/Lobby.cc index 9acab8b8..ade2d6b8 100644 --- a/src/Lobby.cc +++ b/src/Lobby.cc @@ -99,12 +99,12 @@ void Lobby::add_client(shared_ptr c, bool reverse_indexes) { // If the lobby is a game, assign the inventory's item IDs if (this->is_game()) { - auto& inv = c->player.inventory; + auto& inv = c->game_data.player()->inventory; size_t count = max(inv.num_items, 30); for (size_t x = 0; x < count; x++) { - inv.items[x].data.item_id = 0x00010000 + 0x00200000 * c->lobby_client_id + x; + inv.items[x].data.id = 0x00010000 + 0x00200000 * c->lobby_client_id + x; } - c->player.print_inventory(stderr); + c->game_data.player()->print_inventory(stderr); } } @@ -155,7 +155,7 @@ shared_ptr Lobby::find_client(const u16string* identifier, (this->clients[x]->license->serial_number == serial_number)) { return this->clients[x]; } - if (identifier && (this->clients[x]->player.disp.name == *identifier)) { + if (identifier && (this->clients[x]->game_data.player()->disp.name == *identifier)) { return this->clients[x]; } } @@ -181,7 +181,7 @@ uint8_t Lobby::game_event_for_lobby_event(uint8_t lobby_event) { void Lobby::add_item(const PlayerInventoryItem& item, uint8_t area, float x, float z) { - auto& fi = this->item_id_to_floor_item[item.data.item_id]; + auto& fi = this->item_id_to_floor_item[item.data.id]; fi.inv_item = item; fi.area = area; fi.x = x; diff --git a/src/PSOEncryption.cc b/src/PSOEncryption.cc index 367ba0e1..ce8af224 100644 --- a/src/PSOEncryption.cc +++ b/src/PSOEncryption.cc @@ -574,7 +574,6 @@ void PSOBBMultiKeyServerEncryption::ensure_stream_ready() { if (!this->client_crypt->active_key.get()) { throw logic_error("server crypt cannot be initialized because client crypt is not ready"); } - log(INFO, "[PSOBB/MK] Generating server stream"); this->stream = PSOBBEncryption::generate_stream( *this->client_crypt->active_key, this->seed.data(), this->seed.size()); } diff --git a/src/Player.cc b/src/Player.cc index aa705f3f..7d8b66d8 100644 --- a/src/Player.cc +++ b/src/Player.cc @@ -15,10 +15,13 @@ using namespace std; -// originally there was going to be a language-based header, but then I decided against it. -// these strings were already in use for that parser, so I didn't bother changing them. -#define PLAYER_FILE_SIGNATURE "newserv player file format; 10 sections present; sequential;" -#define ACCOUNT_FILE_SIGNATURE "newserv account file format; 7 sections present; sequential;" +// Originally there was going to be a language-based header, but then I decided +// against it. These strings were already in use for that parser, so I didn't +// bother changing them. +static const string PLAYER_FILE_SIGNATURE = + "newserv player file format; 10 sections present; sequential;"; +static const string ACCOUNT_FILE_SIGNATURE = + "newserv account file format; 7 sections present; sequential;"; @@ -52,14 +55,15 @@ PlayerDispDataPCGC::PlayerDispDataPCGC() noexcept void PlayerDispDataPCGC::enforce_pc_limits() { // PC has fewer classes, so we'll substitute some here if (this->char_class == 11) { - this->char_class = 0; // fomar -> humar + this->char_class = 0; // FOmar -> HUmar } else if (this->char_class == 10) { - this->char_class = 1; // ramarl -> hunewearl + this->char_class = 1; // RAmarl -> HUnewearl } else if (this->char_class == 9) { - this->char_class = 5; // hucaseal -> racaseal + this->char_class = 5; // HUcaseal -> RAcaseal } - // if the player is still not a valid class, make them appear as the "ninja" NPC + // If the player is somehow still not a valid class, make them appear as the + // "ninja" NPC if (this->char_class > 8) { this->extra_model = 0; this->v2_flags |= 2; @@ -67,7 +71,6 @@ void PlayerDispDataPCGC::enforce_pc_limits() { this->version = 2; } -// converts PC/GC player data to BB format PlayerDispDataBB PlayerDispDataPCGC::to_bb() const { PlayerDispDataBB bb; bb.stats.atp = this->stats.atp; @@ -134,7 +137,6 @@ PlayerDispDataBB::PlayerDispDataBB() noexcept proportion_x(0), proportion_y(0) { } -// converts BB player data to PC/GC format PlayerDispDataPCGC PlayerDispDataBB::to_pcgc() const { PlayerDispDataPCGC pcgc; pcgc.stats.atp = this->stats.atp; @@ -174,7 +176,6 @@ PlayerDispDataPCGC PlayerDispDataBB::to_pcgc() const { return pcgc; } -// creates a player preview, which can then be sent to a BB client for character select PlayerDispDataBBPreview PlayerDispDataBB::to_preview() const { PlayerDispDataBBPreview pre; pre.level = this->level; @@ -271,7 +272,7 @@ GuildCardBB::GuildCardBB() noexcept void PlayerBank::load(const string& filename) { *this = load_object_file(filename); for (uint32_t x = 0; x < this->num_items; x++) { - this->items[x].data.item_id = 0x0F010000 + x; + this->items[x].data.id = 0x0F010000 + x; } } @@ -281,72 +282,8 @@ void PlayerBank::save(const string& filename) const { -void Player::import(const PSOPlayerDataPC& pc) { - this->inventory = pc.inventory; - this->disp = pc.disp.to_bb(); - // TODO: Add these fields to the existing structure so we can parse them - // this->info_board = pc.info_board; - // this->blocked_senders = pc.blocked_senders; - // this->auto_reply = pc.auto_reply; -} - -void Player::import(const PSOPlayerDataGC& gc) { - this->inventory = gc.inventory; - this->disp = gc.disp.to_bb(); - this->info_board = gc.info_board; - this->blocked_senders = gc.blocked_senders; - if (gc.auto_reply_enabled) { - this->auto_reply = gc.auto_reply; - } else { - this->auto_reply.clear(); - } -} - -void Player::import(const PSOPlayerDataBB& bb) { - // Note: we don't copy the inventory and disp here because we already have - // it (we sent the player data to the client in the first place) - this->info_board = bb.info_board; - this->blocked_senders = bb.blocked_senders; - if (bb.auto_reply_enabled) { - this->auto_reply = bb.auto_reply; - } else { - this->auto_reply.clear(); - } -} - -PlayerBB Player::export_bb_player_data() const { - PlayerBB bb; - bb.inventory = this->inventory; - bb.disp = this->disp; - bb.unknown.clear(); - bb.option_flags = this->option_flags; - bb.quest_data1 = this->quest_data1; - bb.bank = this->bank; - bb.serial_number = this->serial_number; - bb.name = this->disp.name; - bb.team_name = this->team_name; - bb.guild_card_desc = this->guild_card_desc; - bb.reserved1 = 0; - bb.reserved2 = 0; - bb.section_id = this->disp.section_id; - bb.char_class = this->disp.char_class; - bb.unknown3 = 0; - bb.symbol_chats = this->symbol_chats; - bb.shortcuts = this->shortcuts; - bb.auto_reply = this->auto_reply; - bb.info_board = this->info_board; - bb.unknown5.clear(); - bb.challenge_data = this->challenge_data; - bb.tech_menu_config = this->tech_menu_config; - bb.unknown6.clear(); - bb.quest_data2 = this->quest_data2; - bb.key_config = this->key_config; - return bb; -} - //////////////////////////////////////////////////////////////////////////////// -// checksums the guild card file for BB player account data uint32_t compute_guild_card_checksum(const void* vdata, size_t size) { const uint8_t* data = reinterpret_cast(vdata); @@ -364,65 +301,213 @@ uint32_t compute_guild_card_checksum(const void* vdata, size_t size) { return (cs ^ 0xFFFFFFFF); } -void Player::load_account_data(const string& filename) { - SavedAccountBB account = load_object_file(filename); - if (account.signature != ACCOUNT_FILE_SIGNATURE) { - throw runtime_error("account data header is incorrect"); +ClientGameData::~ClientGameData() { + if (!this->bb_username.empty()) { + if (this->account_data.get()) { + this->save_account_data(); + } + if (this->player_data.get()) { + this->save_player_data(); + } } - this->blocked_senders = account.blocked_senders; - this->guild_cards = account.guild_cards; - this->key_config = account.key_config; - this->option_flags = account.option_flags; - this->shortcuts = account.shortcuts; - this->symbol_chats = account.symbol_chats; - this->team_name = account.team_name; } -void Player::save_account_data(const string& filename) const { - SavedAccountBB account; - account.signature = ACCOUNT_FILE_SIGNATURE; - account.blocked_senders = this->blocked_senders; - account.guild_cards = this->guild_cards; - account.key_config = this->key_config; - account.option_flags = this->option_flags; - account.shortcuts = this->shortcuts; - account.symbol_chats = this->symbol_chats; - account.team_name = this->team_name; - save_file(filename, &account, sizeof(account)); -} - -void Player::load_player_data(const string& filename) { - SavedPlayerBB player = load_object_file(filename); - if (player.signature != PLAYER_FILE_SIGNATURE) { - throw runtime_error("account data header is incorrect"); +shared_ptr ClientGameData::account(bool should_load) { + if (!this->account_data.get() && should_load) { + if (this->bb_username.empty()) { + this->account_data.reset(new SavedAccountDataBB()); + } else { + this->load_account_data(); + } } - this->auto_reply = player.auto_reply; - this->bank = player.bank; - this->challenge_data = player.challenge_data; - this->disp = player.disp; - this->guild_card_desc = player.guild_card_desc; - this->info_board = player.info_board; - this->inventory = player.inventory; - this->quest_data1 = player.quest_data1; - this->quest_data2 = player.quest_data2; - this->tech_menu_config = player.tech_menu_config; + return this->account_data; } -void Player::save_player_data(const string& filename) const { - SavedPlayerBB player; - player.signature = PLAYER_FILE_SIGNATURE; - player.preview = this->disp.to_preview(); - player.auto_reply = this->auto_reply; - player.bank = this->bank; - player.challenge_data = this->challenge_data; - player.disp = this->disp; - player.guild_card_desc = this->guild_card_desc; - player.info_board = this->info_board; - player.inventory = this->inventory; - player.quest_data1 = this->quest_data1; - player.quest_data2 = this->quest_data2; - player.tech_menu_config = this->tech_menu_config; - save_file(filename, &player, sizeof(player)); +shared_ptr ClientGameData::player(bool should_load) { + if (!this->player_data.get() && should_load) { + if (this->bb_username.empty()) { + this->player_data.reset(new SavedPlayerDataBB()); + } else { + this->load_player_data(); + } + } + return this->player_data; +} + +shared_ptr ClientGameData::account() const { + if (!this->account_data.get()) { + throw runtime_error("account data is not loaded"); + } + return this->account_data; +} + +shared_ptr ClientGameData::player() const { + if (!this->player_data.get()) { + throw runtime_error("player data is not loaded"); + } + return this->player_data; +} + +string ClientGameData::account_data_filename() const { + if (this->bb_username.empty()) { + throw logic_error("non-BB players do not have account data"); + } + return string_printf("system/players/account_%s.nsa", + this->bb_username.c_str()); +} + +string ClientGameData::player_data_filename() const { + if (this->bb_username.empty()) { + throw logic_error("non-BB players do not have account data"); + } + if (this->bb_player_index == 0) { + throw logic_error("0 is not a valid player index"); + } + return string_printf("system/players/player_%s_%zu.nsc", + this->bb_username.c_str(), this->bb_player_index + 1); +} + +string ClientGameData::player_template_filename(uint8_t char_class) { + return string_printf("system/blueburst/player_class_%hhu.nsc", char_class); +} + +void ClientGameData::create_player( + const PlayerDispDataBBPreview& preview, + shared_ptr level_table) { + shared_ptr data(new SavedPlayerDataBB( + load_object_file(player_template_filename(preview.char_class)))); + if (data->signature != PLAYER_FILE_SIGNATURE) { + throw runtime_error("player data header is incorrect"); + } + + try { + data->disp.apply_preview(preview); + data->disp.stats = level_table->base_stats_for_class(data->disp.char_class); + } catch (const exception& e) { + throw runtime_error(string_printf("template application failed: %s", e.what())); + } + + this->player_data = data; + + this->save_player_data(); +} + +void ClientGameData::load_account_data() { + string filename = this->account_data_filename(); + + shared_ptr data; + try { + data.reset(new SavedAccountDataBB( + load_object_file(filename))); + if (data->signature != ACCOUNT_FILE_SIGNATURE) { + throw runtime_error("account data header is incorrect"); + } + } catch (const exception& e) { + log(INFO, "[BB/Account] No account data for %s; using default", + this->bb_username.c_str()); + data.reset(new SavedAccountDataBB( + load_object_file("system/blueburst/default.nsa"))); + if (data->signature != ACCOUNT_FILE_SIGNATURE) { + throw runtime_error("default account data header is incorrect"); + } + } + + this->account_data = data; + log(INFO, "Loaded account data file %s", filename.c_str()); +} + +void ClientGameData::save_account_data() const { + string filename = this->account_data_filename(); + save_file(filename, this->account_data.get(), sizeof(SavedAccountDataBB)); + log(INFO, "Saved account data file %s", filename.c_str()); +} + +void ClientGameData::load_player_data() { + string filename = this->player_data_filename(); + shared_ptr data(new SavedPlayerDataBB( + load_object_file(filename))); + if (data->signature != PLAYER_FILE_SIGNATURE) { + throw runtime_error("player data header is incorrect"); + } + this->player_data = data; + log(INFO, "Loaded player data file %s", filename.c_str()); +} + +void ClientGameData::save_player_data() const { + string filename = this->player_data_filename(); + save_file(filename, this->player_data.get(), sizeof(SavedPlayerDataBB)); + log(INFO, "Saved player data file %s", filename.c_str()); +} + +void ClientGameData::import_player(const PSOPlayerDataPC& pc) { + auto player = this->player(); + player->inventory = pc.inventory; + player->disp = pc.disp.to_bb(); + // TODO: Add these fields to the command structure so we can parse them + // info_board = pc.info_board; + // blocked_senders = pc.blocked_senders; + // auto_reply = pc.auto_reply; +} + +void ClientGameData::import_player(const PSOPlayerDataGC& gc) { + auto account = this->account(); + auto player = this->player(); + player->inventory = gc.inventory; + player->disp = gc.disp.to_bb(); + player->info_board = gc.info_board; + account->blocked_senders = gc.blocked_senders; + if (gc.auto_reply_enabled) { + player->auto_reply = gc.auto_reply; + } else { + player->auto_reply.clear(); + } +} + +void ClientGameData::import_player(const PSOPlayerDataBB& bb) { + auto account = this->account(); + auto player = this->player(); + // Note: we don't copy the inventory and disp here because we already have + // them (we sent the player data to the client in the first place) + player->info_board = bb.info_board; + account->blocked_senders = bb.blocked_senders; + if (bb.auto_reply_enabled) { + player->auto_reply = bb.auto_reply; + } else { + player->auto_reply.clear(); + } +} + +PlayerBB ClientGameData::export_player_bb() const { + auto account = this->account(); + auto player = this->player(); + + PlayerBB ret; + ret.inventory = player->inventory; + ret.disp = player->disp; + ret.unknown.clear(); + ret.option_flags = account->option_flags; + ret.quest_data1 = player->quest_data1; + ret.bank = player->bank; + ret.serial_number = this->serial_number; + ret.name = player->disp.name; + ret.team_name = account->team_name; + ret.guild_card_desc = player->guild_card_desc; + ret.reserved1 = 0; + ret.reserved2 = 0; + ret.section_id = player->disp.section_id; + ret.char_class = player->disp.char_class; + ret.unknown3 = 0; + ret.symbol_chats = account->symbol_chats; + ret.shortcuts = account->shortcuts; + ret.auto_reply = player->auto_reply; + ret.info_board = player->info_board; + ret.unknown5.clear(); + ret.challenge_data = player->challenge_data; + ret.tech_menu_config = player->tech_menu_config; + ret.unknown6.clear(); + ret.quest_data2 = player->quest_data2; + ret.key_config = account->key_config; + return ret; } @@ -440,62 +525,63 @@ PlayerLobbyDataBB::PlayerLobbyDataBB() noexcept //////////////////////////////////////////////////////////////////////////////// -const uint32_t meseta_identifier = 0x00040000; +constexpr uint32_t MESETA_IDENTIFIER = 0x00040000; + +ItemData::ItemData() { + this->data1d[0] = 0; + this->data1d[1] = 0; + this->data1d[2] = 0; + this->id = 0xFFFFFFFF; + this->data2d = 0; +} uint32_t ItemData::primary_identifier() const { - if (this->item_data1[0] == 0x03 && this->item_data1[1] == 0x02) { + if (this->data1[0] == 0x03 && this->data1[1] == 0x02) { return 0x00030200; // Tech disk (data1[2] is level, so omit it) - } else if (this->item_data1[0] == 0x02) { - return 0x00020000 | (this->item_data1[1] << 8); // Mag + } else if (this->data1[0] == 0x02) { + return 0x00020000 | (this->data1[1] << 8); // Mag } else { - return (this->item_data1[0] << 16) | (this->item_data1[1] << 8) | this->item_data1[2]; + return (this->data1[0] << 16) | (this->data1[1] << 8) | this->data1[2]; } } -PlayerBankItem PlayerInventoryItem::to_bank_item() const { - PlayerBankItem bank_item; - bank_item.data = this->data; +PlayerInventoryItem::PlayerInventoryItem() + : equip_flags(0x0000), tech_flag(0x0000), data() { } - if (combine_item_to_max.count(this->data.primary_identifier())) { - bank_item.amount = this->data.item_data1[5]; - } else { - bank_item.amount = 1; - } - bank_item.show_flags = 1; +PlayerInventoryItem::PlayerInventoryItem(const PlayerBankItem& src) + : equip_flags((this->data.data1[0] > 2) ? 0x0044 : 0x0050), + tech_flag(0x0001), + data(src.data) { } - return bank_item; -} +PlayerBankItem::PlayerBankItem() + : data(), amount(0), show_flags(0) { } -PlayerInventoryItem PlayerBankItem::to_inventory_item() const { - PlayerInventoryItem item; - item.data = this->data; - if (item.data.item_data1[0] > 2) { - item.equip_flags = 0x0044; - } else { - item.equip_flags = 0x0050; - } - item.equip_flags = 0x0001; // TODO: is this a bug? - item.tech_flag = 0x0001; - return item; -} +PlayerBankItem::PlayerBankItem(const PlayerInventoryItem& src) + : data(src.data), + amount(combine_item_to_max.count(this->data.primary_identifier()) ? this->data.data1[5] : 1), + show_flags(1) { } -void Player::add_item(const PlayerInventoryItem& item) { +// TODO: Eliminate duplication between this function and the parallel function +// in PlayerBank +void SavedPlayerDataBB::add_item(const PlayerInventoryItem& item) { uint32_t pid = item.data.primary_identifier(); - // is it meseta? then just add to the meseta total - if (pid == meseta_identifier) { - this->disp.meseta += item.data.item_data2d; + // Annoyingly, meseta is in the disp data, not in the inventory struct. If the + // item is meseta, we have to modify disp instead. + if (pid == MESETA_IDENTIFIER) { + this->disp.meseta += item.data.data2d; if (this->disp.meseta > 999999) { this->disp.meseta = 999999; } return; } - // is it a combine item? + // Handle combinable items try { uint32_t combine_max = combine_item_to_max.at(pid); - // is there already a stack of it in the player's inventory? + // Get the item index if there's already a stack of the same item in the + // player's inventory size_t y; for (y = 0; y < this->inventory.num_items; y++) { if (this->inventory.items[y].data.primary_identifier() == item.data.primary_identifier()) { @@ -503,17 +589,18 @@ void Player::add_item(const PlayerInventoryItem& item) { } } - // if there's already a stack, add to the stack and return + // If we found an existing stack, add it to the total and return if (y < this->inventory.num_items) { - this->inventory.items[y].data.item_data1[5] += item.data.item_data1[5]; - if (this->inventory.items[y].data.item_data1[5] > combine_max) { - this->inventory.items[y].data.item_data1[5] = combine_max; + this->inventory.items[y].data.data1[5] += item.data.data1[5]; + if (this->inventory.items[y].data.data1[5] > combine_max) { + this->inventory.items[y].data.data1[5] = combine_max; } return; } } catch (const out_of_range&) { } - // else, just add the item if there's room + // If we get here, then it's not meseta and not a combine item, so it needs to + // go into an empty inventory slot if (this->inventory.num_items >= 30) { throw runtime_error("inventory is full"); } @@ -521,24 +608,20 @@ void Player::add_item(const PlayerInventoryItem& item) { this->inventory.num_items++; } -// adds an item to a bank void PlayerBank::add_item(const PlayerBankItem& item) { uint32_t pid = item.data.primary_identifier(); - // is it meseta? then just add to the meseta total - if (pid == meseta_identifier) { - this->meseta += item.data.item_data2d; + if (pid == MESETA_IDENTIFIER) { + this->meseta += item.data.data2d; if (this->meseta > 999999) { this->meseta = 999999; } return; } - // is it a combine item? try { uint32_t combine_max = combine_item_to_max.at(pid); - // is there already a stack of it in the player's inventory? size_t y; for (y = 0; y < this->num_items; y++) { if (this->items[y].data.primary_identifier() == item.data.primary_identifier()) { @@ -546,18 +629,16 @@ void PlayerBank::add_item(const PlayerBankItem& item) { } } - // if there's already a stack, add to the stack and return if (y < this->num_items) { - this->items[y].data.item_data1[5] += item.data.item_data1[5]; - if (this->items[y].data.item_data1[5] > combine_max) { - this->items[y].data.item_data1[5] = combine_max; + this->items[y].data.data1[5] += item.data.data1[5]; + if (this->items[y].data.data1[5] > combine_max) { + this->items[y].data.data1[5] = combine_max; } - this->items[y].amount = this->items[y].data.item_data1[5]; + this->items[y].amount = this->items[y].data.data1[5]; return; } } catch (const out_of_range&) { } - // else, just add the item if there's room if (this->num_items >= 200) { throw runtime_error("bank is full"); } @@ -565,38 +646,45 @@ void PlayerBank::add_item(const PlayerBankItem& item) { this->num_items++; } -PlayerInventoryItem Player::remove_item(uint32_t item_id, uint32_t amount) { +// TODO: Eliminate code duplication between this function and the parallel +// function in PlayerBank +PlayerInventoryItem SavedPlayerDataBB::remove_item( + uint32_t item_id, uint32_t amount) { PlayerInventoryItem ret; - // are we removing meseta? then create a meseta item + // If we're removing meseta (signaled by an invalid item ID), then create a + // meseta item. if (item_id == 0xFFFFFFFF) { if (amount > this->disp.meseta) { throw out_of_range("player does not have enough meseta"); } memset(&ret, 0, sizeof(ret)); - ret.data.item_data1[0] = 0x04; - ret.data.item_data2d = amount; + ret.data.data1[0] = 0x04; + ret.data.data2d = amount; this->disp.meseta -= amount; return ret; } - // find this item size_t index = this->inventory.find_item(item_id); auto& inventory_item = this->inventory.items[index]; - // is it a combine item, and are we removing less than we have of it? - // (amount == 0 means remove all of it) - if (amount && (amount < inventory_item.data.item_data1[5]) && + // If the item is a combine item and are we removing less than we have of it, + // 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 && (amount < inventory_item.data.data1[5]) && combine_item_to_max.count(inventory_item.data.primary_identifier())) { ret = inventory_item; - ret.data.item_data1[5] = amount; - ret.data.item_id = 0xFFFFFFFF; - inventory_item.data.item_data1[5] -= amount; + ret.data.data1[5] = amount; + ret.data.id = 0xFFFFFFFF; + inventory_item.data.data1[5] -= amount; return ret; } - // not a combine item, or we're removing the whole stack? then just remove the item + // If we get here, then it's not meseta, and either it's not a combine item or + // we're removing the entire stack. Delete the item from the inventory slot + // and return the deleted item. ret = inventory_item; this->inventory.num_items--; memcpy(&this->inventory.items[index], &this->inventory.items[index + 1], @@ -604,39 +692,33 @@ PlayerInventoryItem Player::remove_item(uint32_t item_id, uint32_t amount) { return ret; } -// removes an item from a bank. works just like RemoveItem for inventories; I won't comment it PlayerBankItem PlayerBank::remove_item(uint32_t item_id, uint32_t amount) { PlayerBankItem ret; - // are we removing meseta? then create a meseta item if (item_id == 0xFFFFFFFF) { if (amount > this->meseta) { throw out_of_range("player does not have enough meseta"); } memset(&ret, 0, sizeof(ret)); - ret.data.item_data1[0] = 0x04; - ret.data.item_data2d = amount; + ret.data.data1[0] = 0x04; + ret.data.data2d = amount; this->meseta -= amount; return ret; } - // find this item size_t index = this->find_item(item_id); auto& bank_item = this->items[index]; - // is it a combine item, and are we removing less than we have of it? - // (amount == 0 means remove all of it) - if (amount && (amount < bank_item.data.item_data1[5]) && + if (amount && (amount < bank_item.data.data1[5]) && combine_item_to_max.count(bank_item.data.primary_identifier())) { ret = bank_item; - ret.data.item_data1[5] = amount; + ret.data.data1[5] = amount; ret.amount = amount; - bank_item.data.item_data1[5] -= amount; + bank_item.data.data1[5] -= amount; bank_item.amount -= amount; return ret; } - // not a combine item, or we're removing the whole stack? then just remove the item ret = bank_item; this->num_items--; memcpy(&this->items[index], &this->items[index + 1], @@ -646,7 +728,7 @@ PlayerBankItem PlayerBank::remove_item(uint32_t item_id, uint32_t amount) { size_t PlayerInventory::find_item(uint32_t item_id) { for (size_t x = 0; x < this->num_items; x++) { - if (this->items[x].data.item_id == item_id) { + if (this->items[x].data.id == item_id) { return x; } } @@ -655,38 +737,20 @@ size_t PlayerInventory::find_item(uint32_t item_id) { size_t PlayerBank::find_item(uint32_t item_id) { for (size_t x = 0; x < this->num_items; x++) { - if (this->items[x].data.item_id == item_id) { + if (this->items[x].data.id == item_id) { return x; } } throw out_of_range("item not present"); } -void Player::print_inventory(FILE* stream) const { +void SavedPlayerDataBB::print_inventory(FILE* stream) const { fprintf(stream, "[PlayerInventory] Meseta: %" PRIu32 "\n", this->disp.meseta.load()); fprintf(stream, "[PlayerInventory] %hhu items\n", this->inventory.num_items); for (size_t x = 0; x < this->inventory.num_items; x++) { const auto& item = this->inventory.items[x]; auto name = name_for_item(item.data, false); - fprintf(stream, "[PlayerInventory] %zu (%08" PRIX32 "): %08" PRIX32 " (%s)\n", - x, item.data.item_id.load(), item.data.primary_identifier(), name.c_str()); + fprintf(stream, "[PlayerInventory] %zu (%08" PRIX32 "): %06" PRIX32 " (%s)\n", + x, item.data.id.load(), item.data.primary_identifier(), name.c_str()); } } - -string filename_for_player_bb(const string& username, uint8_t player_index) { - return string_printf("system/players/player_%s_%hhu.nsc", username.c_str(), - static_cast(player_index + 1)); -} - -string filename_for_bank_bb(const string& username, const std::string& bank_name) { - return string_printf("system/players/bank_%s_%s.nsb", username.c_str(), - bank_name.c_str()); -} - -string filename_for_class_template_bb(uint8_t char_class) { - return string_printf("system/blueburst/player_class_%hhu.nsc", char_class); -} - -string filename_for_account_bb(const string& username) { - return string_printf("system/players/account_%s.nsa", username.c_str()); -} diff --git a/src/Player.hh b/src/Player.hh index bb6dfca4..01ef6e9c 100644 --- a/src/Player.hh +++ b/src/Player.hh @@ -7,51 +7,51 @@ #include #include +#include "LevelTable.hh" #include "Version.hh" #include "Text.hh" -// raw item data -// TODO: use parray for the fields here struct ItemData { union { - uint8_t item_data1[12]; - le_uint16_t item_data1w[6]; - le_uint32_t item_data1d[3]; + uint8_t data1[12]; + le_uint16_t data1w[6]; + le_uint32_t data1d[3]; } __attribute__((packed)); - le_uint32_t item_id; + le_uint32_t id; union { - uint8_t item_data2[4]; - le_uint16_t item_data2w[2]; - le_uint32_t item_data2d; + uint8_t data2[4]; + le_uint16_t data2w[2]; + le_uint32_t data2d; } __attribute__((packed)); + ItemData(); + uint32_t primary_identifier() const; } __attribute__((packed)); struct PlayerBankItem; -// an item in a player's inventory struct PlayerInventoryItem { le_uint16_t equip_flags; le_uint16_t tech_flag; le_uint32_t game_flags; ItemData data; - PlayerBankItem to_bank_item() const; + PlayerInventoryItem(); + PlayerInventoryItem(const PlayerBankItem&); } __attribute__((packed)); -// an item in a player's bank struct PlayerBankItem { ItemData data; le_uint16_t amount; le_uint16_t show_flags; - PlayerInventoryItem to_inventory_item() const; + PlayerBankItem(); + PlayerBankItem(const PlayerInventoryItem&); } __attribute__((packed)); -// a player's inventory (remarkably, the format is the same in all versions of PSO) struct PlayerInventory { uint8_t num_items; uint8_t hp_materials_used; @@ -62,7 +62,6 @@ struct PlayerInventory { size_t find_item(uint32_t item_id); } __attribute__((packed)); -// a player's bank struct PlayerBank { le_uint32_t num_items; le_uint32_t meseta; @@ -81,19 +80,6 @@ struct PlayerBank { -// simple player stats -struct PlayerStats { - le_uint16_t atp; - le_uint16_t mst; - le_uint16_t evp; - le_uint16_t hp; - le_uint16_t dfp; - le_uint16_t ata; - le_uint16_t lck; - - PlayerStats() noexcept; -} __attribute__((packed)); - struct PlayerDispDataBB; // PC/GC player appearance and stats data @@ -310,12 +296,12 @@ struct PlayerLobbyDataBB { -struct PSOPlayerDataPC { // for command 0x61 +struct PSOPlayerDataPC { // For command 61 PlayerInventory inventory; PlayerDispDataPCGC disp; } __attribute__((packed)); -struct PSOPlayerDataGC { // for command 0x61 +struct PSOPlayerDataGC { // For command 61 PlayerInventory inventory; PlayerDispDataPCGC disp; parray unknown; @@ -325,7 +311,7 @@ struct PSOPlayerDataGC { // for command 0x61 char auto_reply[0]; } __attribute__((packed)); -struct PSOPlayerDataBB { // for command 0x61 +struct PSOPlayerDataBB { // For command 61 PlayerInventory inventory; PlayerDispDataBB disp; ptext unused; @@ -335,53 +321,57 @@ struct PSOPlayerDataBB { // for command 0x61 char16_t auto_reply[0]; } __attribute__((packed)); -// complete BB player data format (used in E7 command) -struct PlayerBB { - PlayerInventory inventory; // 0000 // player - PlayerDispDataBB disp; // 034C // player +struct PlayerBB { // Used in 00E7 command + PlayerInventory inventory; // 0000 // player + PlayerDispDataBB disp; // 034C // player parray unknown; // 04DC // - le_uint32_t option_flags; // 04EC // account + le_uint32_t option_flags; // 04EC // account parray quest_data1; // 04F0 // player - PlayerBank bank; // 06F8 // player - le_uint32_t serial_number; // 19C0 // player - ptext name; // 19C4 // player - ptext team_name; // 19C4 // player - ptext guild_card_desc; // 1A14 // player - uint8_t reserved1; // 1AC4 // player - uint8_t reserved2; // 1AC5 // player - uint8_t section_id; // 1AC6 // player - uint8_t char_class; // 1AC7 // player - le_uint32_t unknown3; // 1AC8 // + PlayerBank bank; // 06F8 // player + le_uint32_t serial_number; // 19C0 // player + ptext name; // 19C4 // player + ptext team_name; // 19C4 // player + ptext guild_card_desc; // 1A14 // player + uint8_t reserved1; // 1AC4 // player + uint8_t reserved2; // 1AC5 // player + uint8_t section_id; // 1AC6 // player + uint8_t char_class; // 1AC7 // player + le_uint32_t unknown3; // 1AC8 // parray symbol_chats; // 1ACC // account parray shortcuts; // 1FAC // account - ptext auto_reply; // 29EC // player - ptext info_board; // 2B44 // player + ptext auto_reply; // 29EC // player + ptext info_board; // 2B44 // player parray unknown5; // 2C9C // parray challenge_data; // 2CB8 // player parray tech_menu_config; // 2DF8 // player parray unknown6; // 2E20 // parray quest_data2; // 2E4C // player - KeyAndTeamConfigBB key_config; // 2EA4 // account -} __attribute__((packed)); // total size: 39A0 + KeyAndTeamConfigBB key_config; // 2EA4 // account +} __attribute__((packed)); // total size: 39A0 -struct SavedPlayerBB { // .nsc file format +struct SavedPlayerDataBB { // .nsc file format ptext signature; PlayerDispDataBBPreview preview; ptext auto_reply; PlayerBank bank; parray challenge_data; PlayerDispDataBB disp; - ptext guild_card_desc; + ptext guild_card_desc; ptext info_board; PlayerInventory inventory; parray quest_data1; parray quest_data2; parray tech_menu_config; + + void add_item(const PlayerInventoryItem& item); + PlayerInventoryItem remove_item(uint32_t item_id, uint32_t amount); + + void print_inventory(FILE* stream) const; } __attribute__((packed)); -struct SavedAccountBB { // .nsa file format +struct SavedAccountDataBB { // .nsa file format ptext signature; parray blocked_senders; GuildCardFileBB guild_cards; @@ -392,59 +382,56 @@ struct SavedAccountBB { // .nsa file format ptext team_name; } __attribute__((packed)); -// complete player info stored by the server -struct Player { - le_uint32_t loaded_from_shipgate_time; - ptext auto_reply; // player - PlayerBank bank; // player - ptext bank_name; // not saved - parray blocked_senders; // account - parray challenge_data; // player - PlayerDispDataBB disp; // player - parray ep3_config; // not saved - ptext guild_card_desc; // player - GuildCardFileBB guild_cards; // account - PlayerInventoryItem identify_result; // not saved - ptext info_board; // player - PlayerInventory inventory; // player - KeyAndTeamConfigBB key_config; // account - le_uint32_t option_flags; // account - parray quest_data1; // player - parray quest_data2; // player - le_uint32_t serial_number; // account identifier - std::vector current_shop_contents; // not saved - parray shortcuts; // account - parray symbol_chats; // account - ptext team_name; // account - parray tech_menu_config; // player +class ClientGameData { +private: + std::shared_ptr account_data; + std::shared_ptr player_data; - void load_player_data(const std::string& filename); - void save_player_data(const std::string& filename) const; +public: + uint32_t serial_number; - void load_account_data(const std::string& filename); - void save_account_data(const std::string& filename) const; + // The following fields are not saved, and are only used in certain situations - void import(const PSOPlayerDataPC& pd); - void import(const PSOPlayerDataGC& pd); - void import(const PSOPlayerDataBB& pd); - PlayerBB export_bb_player_data() const; + // Null unless the client is Episode 3 and has sent its config already + std::shared_ptr> ep3_config; - void add_item(const PlayerInventoryItem& item); - PlayerInventoryItem remove_item(uint32_t item_id, uint32_t amount); - size_t find_item(uint32_t item_id); + // These are only used if the client is BB + std::string bb_username; + size_t bb_player_index; + PlayerInventoryItem identify_result; + std::vector shop_contents; - void print_inventory(FILE* stream) const; + ClientGameData() : serial_number(0), bb_player_index(0) { } + ~ClientGameData(); + + std::shared_ptr account(bool should_load = true); + std::shared_ptr player(bool should_load = true); + std::shared_ptr account() const; + std::shared_ptr player() const; + + std::string account_data_filename() const; + std::string player_data_filename() const; + static std::string player_template_filename(uint8_t char_class); + + void create_player( + const PlayerDispDataBBPreview& preview, + std::shared_ptr level_table); + + void load_account_data(); + void save_account_data() const; + void load_player_data(); + void save_player_data() const; + + void import_player(const PSOPlayerDataPC& pd); + void import_player(const PSOPlayerDataGC& pd); + void import_player(const PSOPlayerDataBB& pd); + PlayerBB export_player_bb() const; }; uint32_t compute_guild_card_checksum(const void* data, size_t size); -std::string filename_for_player_bb(const std::string& username, uint8_t player_index); -std::string filename_for_bank_bb(const std::string& username, const std::string& bank_name); -std::string filename_for_class_template_bb(uint8_t char_class); -std::string filename_for_account_bb(const std::string& username); - template diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 11b808d2..dd436124 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -135,37 +135,8 @@ void process_login_complete(shared_ptr s, shared_ptr c) { } else if (c->server_behavior == ServerBehavior::LOBBY_SERVER) { - // if the client is BB, load thair player and account data if (c->version == GameVersion::BB) { - string account_filename = filename_for_account_bb(c->license->username); - try { - c->player.load_account_data(account_filename); - } catch (const exception& e) { - c->player.load_account_data("system/blueburst/default.nsa"); - } - - c->player.bank_name = string_printf("player%d", c->bb_player_index + 1); - - string player_filename = filename_for_player_bb(c->license->username, - c->bb_player_index); - try { - c->player.load_player_data(player_filename); - } catch (const exception&) { - send_message_box(c, u"$C6Your player data cannot be found."); - c->should_disconnect = true; - return; - } - - string bank_filename = filename_for_bank_bb(c->license->username, - c->player.bank_name); - try { - c->player.bank.load(bank_filename); - } catch (const exception&) { - send_message_box(c, u"$C6Your bank data cannot be found."); - c->should_disconnect = true; - return; - } - + // This implicitly loads the client's account and player data send_complete_player_bb(c); } @@ -182,20 +153,13 @@ void process_disconnect(shared_ptr s, shared_ptr c) { s->remove_client_from_lobby(c); } - if (c->version == GameVersion::BB) { - // TODO: Make a timer event for each connected player that saves their data - // periodically, not only when they disconnect - // TODO: Track play time somewhere - // c->player.disp.play_time += ((now() - c->play_time_begin) / 1000000); - string account_filename = filename_for_account_bb(c->license->username); - string player_filename = filename_for_player_bb(c->license->username, - c->bb_player_index); - string bank_filename = filename_for_bank_bb(c->license->username, - c->player.bank_name); - c->player.save_account_data(account_filename); - c->player.save_player_data(player_filename); - c->player.bank.save(bank_filename); - } + // TODO: Make a timer event for each connected player that saves their data + // periodically, not only when they disconnect + // TODO: Track play time somewhere for BB players + // c->game_data.player()->disp.play_time += ((now() - c->play_time_begin) / 1000000); + + // Note: The client's GameData destructor should save their player data + // shortly after this point } @@ -208,8 +172,9 @@ void process_verify_license_gc(shared_ptr s, shared_ptr c, uint32_t serial_number = stoul(cmd.serial_number, nullptr, 16); try { - c->license = s->license_manager->verify_gc(serial_number, cmd.access_key, + auto l = s->license_manager->verify_gc(serial_number, cmd.access_key, cmd.password); + c->set_license(l); } catch (const exception& e) { if (!s->allow_unregistered_users) { u16string message = u"Login failed: " + decode_sjis(e.what()); @@ -220,7 +185,7 @@ void process_verify_license_gc(shared_ptr s, shared_ptr c, auto l = LicenseManager::create_license_gc(serial_number, cmd.access_key, cmd.password, true); s->license_manager->add(l); - c->license = l; + c->set_license(l); } } @@ -236,11 +201,14 @@ void process_login_a_dc_pc_gc(shared_ptr s, shared_ptr c, uint32_t serial_number = stoul(cmd.serial_number, nullptr, 16); try { + shared_ptr l; if (c->version == GameVersion::GC) { - c->license = s->license_manager->verify_gc(serial_number, cmd.access_key); + l = s->license_manager->verify_gc(serial_number, cmd.access_key); } else { - c->license = s->license_manager->verify_pc(serial_number, cmd.access_key); + l = s->license_manager->verify_pc(serial_number, cmd.access_key); } + c->set_license(l); + } catch (const exception& e) { // On GC, the client should have sent a different command containing the // password already, which should have created and added a temporary @@ -263,12 +231,15 @@ void process_login_c_dc_pc_gc(shared_ptr s, shared_ptr c, uint32_t serial_number = stoul(cmd.serial_number, nullptr, 16); try { + shared_ptr l; if (c->version == GameVersion::GC) { - c->license = s->license_manager->verify_gc(serial_number, cmd.access_key, + l = s->license_manager->verify_gc(serial_number, cmd.access_key, cmd.password); } else { - c->license = s->license_manager->verify_pc(serial_number, cmd.access_key); + l = s->license_manager->verify_pc(serial_number, cmd.access_key); } + c->set_license(l); + } catch (const exception& e) { if (!s->allow_unregistered_users) { u16string message = u"Login failed: " + decode_sjis(e.what()); @@ -285,7 +256,7 @@ void process_login_c_dc_pc_gc(shared_ptr s, shared_ptr c, true); } s->license_manager->add(l); - c->license = l; + c->set_license(l); } } @@ -326,13 +297,16 @@ void process_login_d_e_pc_gc(shared_ptr s, shared_ptr c, uint32_t serial_number = stoul(base_cmd->serial_number, nullptr, 16); try { + shared_ptr l; if (c->version == GameVersion::GC) { - c->license = s->license_manager->verify_gc(serial_number, + l = s->license_manager->verify_gc(serial_number, base_cmd->access_key); } else { - c->license = s->license_manager->verify_pc(serial_number, + l = s->license_manager->verify_pc(serial_number, base_cmd->access_key); } + c->set_license(l); + } catch (const exception& e) { // See comment in 9A handler about why we do this even if unregistered users // are allowed on the server @@ -358,7 +332,8 @@ void process_login_bb(shared_ptr s, shared_ptr c, c->flags |= flags_for_version(c->version, 0); try { - c->license = s->license_manager->verify_bb(cmd.username, cmd.password); + auto l = s->license_manager->verify_bb(cmd.username, cmd.password); + c->set_license(l); } catch (const exception& e) { u16string message = u"Login failed: " + decode_sjis(e.what()); send_message_box(c, message.c_str()); @@ -378,29 +353,19 @@ void process_login_bb(shared_ptr s, shared_ptr c, switch (c->bb_game_state) { case ClientStateBB::INITIAL_LOGIN: - // first login? send them to the other port + // On first login, send the client to the data server port send_reconnect(c, s->connect_address_for_client(c), s->name_to_port_config.at("bb-data1")->port); break; - case ClientStateBB::DOWNLOAD_DATA: { - // download data? send them their account data and player previews - string account_filename = filename_for_account_bb(c->license->username); - try { - c->player.load_account_data(account_filename); - } catch (const exception& e) { - c->player.load_account_data("system/blueburst/default.nsa"); - } - break; - } - + case ClientStateBB::DOWNLOAD_DATA: case ClientStateBB::CHOOSE_PLAYER: case ClientStateBB::SAVE_PLAYER: - // just wait; the command handlers will handle it + // Just wait in these cases; the client will request something from us and + // the command handlers will take care of it break; case ClientStateBB::SHIP_SELECT: - // this happens on the login server and later process_login_complete(s, c); break; @@ -805,11 +770,11 @@ void process_menu_selection(shared_ptr s, shared_ptr c, send_message_box(c, u"$C6Incorrect password."); break; } - if (c->player.disp.level < game->min_level) { + if (c->game_data.player()->disp.level < game->min_level) { send_message_box(c, u"$C6Your level is too\nlow to join this\ngame."); break; } - if (c->player.disp.level > game->max_level) { + if (c->game_data.player()->disp.level > game->max_level) { send_message_box(c, u"$C6Your level is too\nhigh to join this\ngame."); break; } @@ -1087,51 +1052,51 @@ void process_player_data(shared_ptr s, shared_ptr c, // Note: we add extra buffer on the end when checking sizes because the // autoreply text is a variable length switch (c->version) { - case GameVersion::PC: - check_size_v(data.size(), sizeof(PSOPlayerDataPC), 0xFFFF); - c->player.import(*reinterpret_cast(data.data())); + case GameVersion::PC: { + const auto& disp = check_size_t(data, + sizeof(PSOPlayerDataPC), 0xFFFF); + c->game_data.import_player(disp); break; - case GameVersion::GC: + } + case GameVersion::GC: { + const PSOPlayerDataGC* disp; if (flag == 4) { // Episode 3 - check_size_v(data.size(), sizeof(PSOPlayerDataGC) + 0x23FC); + disp = &check_size_t(data, + sizeof(PSOPlayerDataGC) + 0x23FC); // TODO: import Episode 3 data somewhere } else { - check_size_v(data.size(), sizeof(PSOPlayerDataGC), - sizeof(PSOPlayerDataGC) + sizeof(char) * c->player.auto_reply.size()); + disp = &check_size_t(data, sizeof(PSOPlayerDataGC), + sizeof(PSOPlayerDataGC) + c->game_data.player()->auto_reply.bytes()); } - c->player.import(*reinterpret_cast(data.data())); + c->game_data.import_player(*disp); break; - case GameVersion::BB: - check_size_v(data.size(), sizeof(PSOPlayerDataBB), - sizeof(PSOPlayerDataBB) + sizeof(char16_t) * c->player.auto_reply.size()); - c->player.import(*reinterpret_cast(data.data())); + } + case GameVersion::BB: { + const auto& disp = check_size_t(data, sizeof(PSOPlayerDataBB), + sizeof(PSOPlayerDataBB) + c->game_data.player()->auto_reply.bytes()); + c->game_data.import_player(disp); break; + } default: throw logic_error("player data command not implemented for version"); } if (command == 0x61 && !c->pending_bb_save_username.empty()) { + string prev_bb_username = c->game_data.bb_username; + size_t prev_bb_player_index = c->game_data.bb_player_index; + + c->game_data.bb_username = c->pending_bb_save_username; + c->game_data.bb_player_index = c->pending_bb_save_player_index; + bool failure = false; try { - string filename = filename_for_player_bb(c->pending_bb_save_username, - c->pending_bb_save_player_index + 1); - c->player.save_player_data(filename); + c->game_data.save_player_data(); } catch (const exception& e) { u16string buffer = u"$C6PSOBB player data could\nnot be saved:\n" + decode_sjis(e.what()); send_text_message(c, buffer.c_str()); failure = true; } - try { - string filename = string_printf("system/players/player_%s_player%d.nsb", - c->pending_bb_save_username.c_str(), c->pending_bb_save_player_index + 1); - c->player.bank.save(filename); - } catch (const exception& e) { - u16string buffer = u"$C6PSOBB bank data could\nnot be saved:\n" + decode_sjis(e.what()); - send_text_message(c, buffer.c_str()); - failure = true; - } - if (!failure) { send_text_message_printf(c, "$C6PSOBB player data saved\nas player %hhu for user\n%s", @@ -1139,6 +1104,9 @@ void process_player_data(shared_ptr s, shared_ptr c, c->pending_bb_save_username.c_str()); } + c->game_data.bb_username = prev_bb_username; + c->game_data.bb_player_index = prev_bb_player_index; + c->pending_bb_save_username.clear(); } @@ -1198,7 +1166,7 @@ void process_chat_generic(shared_ptr s, shared_ptr c, continue; } send_chat_message(l->clients[x], c->license->serial_number, - c->player.disp.name.data(), processed_text.c_str()); + c->game_data.player()->disp.name.data(), processed_text.c_str()); } } } @@ -1241,18 +1209,18 @@ void process_player_preview_request_bb(shared_ptr, shared_ptrshould_disconnect = true; return; } - string filename = filename_for_player_bb(c->license->username, - c->bb_player_index); + + ClientGameData temp_gd; + temp_gd.serial_number = c->license->serial_number; + temp_gd.bb_username = c->license->username; + temp_gd.bb_player_index = c->bb_player_index; try { - // generate a preview - Player p; - p.load_player_data(filename); - auto preview = p.disp.to_preview(); + auto preview = temp_gd.player()->disp.to_preview(); send_player_preview_bb(c, cmd.player_index, &preview); } catch (const exception&) { - // player doesn't exist + // Player doesn't exist send_player_preview_bb(c, cmd.player_index, nullptr); } } @@ -1298,45 +1266,18 @@ void process_create_character_bb(shared_ptr s, shared_ptr c send_message_box(c, u"$C6You are not logged in."); return; } - if (!c->player.disp.name.empty()) { + if (!c->game_data.player()->disp.name.empty()) { send_message_box(c, u"$C6You have already loaded a character."); return; } c->bb_player_index = cmd.player_index; - c->player.bank_name = string_printf("player%" PRIu32, cmd.player_index + 1); - string player_filename = filename_for_player_bb(c->license->username, cmd.player_index); - string bank_filename = filename_for_bank_bb(c->license->username, c->player.bank_name); - string template_filename = filename_for_class_template_bb(cmd.preview.char_class); - - Player p; - try { - p.load_player_data(template_filename); - } catch (const exception& e) { - send_message_box(c, u"$C6New character could not be created.\n\nA server file is missing."); - return; - } try { - p.disp.apply_preview(cmd.preview); - c->player.disp.stats = s->level_table->base_stats_for_class(c->player.disp.char_class); + c->game_data.create_player(cmd.preview, s->level_table); } catch (const exception& e) { - send_message_box(c, u"$C6New character could not be created.\n\nTemplate application failed."); - return; - } - - try { - p.save_player_data(player_filename); - } catch (const exception& e) { - send_message_box(c, u"$C6New character could not be created.\n\nThe disk is full or write-protected."); - return; - } - - try { - p.bank.save(bank_filename); - } catch (const exception& e) { - unlink(player_filename); - send_message_box(c, u"$C6New bank could not be created.\n\nThe disk is full or write-protected."); + string message = string_printf("$C6New character could not be created:\n%s", e.what()); + send_message_box(c, decode_sjis(message)); return; } @@ -1351,31 +1292,31 @@ void process_change_account_data_bb(shared_ptr, shared_ptr switch (command) { case 0x01ED: check_size_v(data.size(), sizeof(cmd->option)); - c->player.option_flags = cmd->option; + c->game_data.account()->option_flags = cmd->option; break; case 0x02ED: check_size_v(data.size(), sizeof(cmd->symbol_chats)); - c->player.symbol_chats = cmd->symbol_chats; + c->game_data.account()->symbol_chats = cmd->symbol_chats; break; case 0x03ED: check_size_v(data.size(), sizeof(cmd->chat_shortcuts)); - c->player.shortcuts = cmd->chat_shortcuts; + c->game_data.account()->shortcuts = cmd->chat_shortcuts; break; case 0x04ED: check_size_v(data.size(), sizeof(cmd->key_config)); - c->player.key_config.key_config = cmd->key_config; + c->game_data.account()->key_config.key_config = cmd->key_config; break; case 0x05ED: check_size_v(data.size(), sizeof(cmd->pad_config)); - c->player.key_config.joystick_config = cmd->pad_config; + c->game_data.account()->key_config.joystick_config = cmd->pad_config; break; case 0x06ED: check_size_v(data.size(), sizeof(cmd->tech_menu)); - c->player.tech_menu_config = cmd->tech_menu; + c->game_data.player()->tech_menu_config = cmd->tech_menu; break; case 0x07ED: check_size_v(data.size(), sizeof(cmd->customize)); - c->player.disp.config = cmd->customize; + c->game_data.player()->disp.config = cmd->customize; break; default: throw invalid_argument("unknown account command"); @@ -1387,9 +1328,18 @@ void process_return_player_data_bb(shared_ptr, shared_ptr c const auto& cmd = check_size_t(data); // We only trust the player's quest data and challenge data. - c->player.challenge_data = cmd.challenge_data; - c->player.quest_data1 = cmd.quest_data1; - c->player.quest_data2 = cmd.quest_data2; + c->game_data.player()->challenge_data = cmd.challenge_data; + c->game_data.player()->quest_data1 = cmd.quest_data1; + c->game_data.player()->quest_data2 = cmd.quest_data2; +} + +void process_update_key_config_bb(shared_ptr, shared_ptr c, + uint16_t, uint32_t, const string& data) { + // Some clients have only a uint32_t at the end for team rewards + check_size_t(data, + sizeof(KeyAndTeamConfigBB) - 4, sizeof(KeyAndTeamConfigBB)); + memcpy(&c->game_data.account()->key_config, data.data(), data.size()); + // TODO: We should send a response here, but I don't know which one! } //////////////////////////////////////////////////////////////////////////////// @@ -1436,20 +1386,21 @@ void process_simple_mail(shared_ptr s, shared_ptr c, // If the sender is blocked, don't forward the mail for (size_t y = 0; y < 30; y++) { - if (target->player.blocked_senders.data()[y] == c->license->serial_number) { + if (target->game_data.account()->blocked_senders.data()[y] == c->license->serial_number) { return; } } // If the target has auto-reply enabled, send the autoreply - if (!target->player.auto_reply.empty()) { + if (!target->game_data.player()->auto_reply.empty()) { send_simple_mail(c, target->license->serial_number, - target->player.disp.name, target->player.auto_reply); + target->game_data.player()->disp.name, + target->game_data.player()->auto_reply); } // Forward the message u16string msg = decode_sjis(cmd.text); - send_simple_mail(target, c->license->serial_number, c->player.disp.name, msg); + send_simple_mail(target, c->license->serial_number, c->game_data.player()->disp.name, msg); } @@ -1466,8 +1417,8 @@ void process_info_board_request(shared_ptr s, shared_ptr c, template void process_write_info_board_t(shared_ptr, shared_ptr c, uint16_t, uint32_t, const string& data) { // D9 - check_size_v(data.size(), 0, c->player.info_board.size() * sizeof(CharT)); - c->player.info_board.assign( + check_size_v(data.size(), 0, c->game_data.player()->info_board.size() * sizeof(CharT)); + c->game_data.player()->info_board.assign( reinterpret_cast(data.data()), data.size() / sizeof(CharT)); } @@ -1475,8 +1426,8 @@ void process_write_info_board_t(shared_ptr, shared_ptr c, template void process_set_auto_reply_t(shared_ptr, shared_ptr c, uint16_t, uint32_t, const string& data) { // C7 - check_size_v(data.size(), 0, c->player.auto_reply.size() * sizeof(CharT)); - c->player.auto_reply.assign( + check_size_v(data.size(), 0, c->game_data.player()->auto_reply.size() * sizeof(CharT)); + c->game_data.player()->auto_reply.assign( reinterpret_cast(data.data()), data.size() / sizeof(CharT)); } @@ -1484,13 +1435,13 @@ void process_set_auto_reply_t(shared_ptr, shared_ptr c, void process_disable_auto_reply(shared_ptr, shared_ptr c, uint16_t, uint32_t, const string& data) { // C8 check_size_v(data.size(), 0); - c->player.auto_reply.clear(); + c->game_data.player()->auto_reply.clear(); } void process_set_blocked_senders_list(shared_ptr, shared_ptr c, uint16_t, uint32_t, const string& data) { // C6 const auto& cmd = check_size_t(data); - c->player.blocked_senders = cmd.blocked_senders; + c->game_data.account()->blocked_senders = cmd.blocked_senders; } @@ -1543,7 +1494,7 @@ shared_ptr create_game_generic(shared_ptr s, uint8_t min_level = ((episode == 0xFF) ? 0 : default_minimum_levels[episode - 1][difficulty]); if (!(c->license->privileges & Privilege::FREE_JOIN_GAMES) && - (min_level > c->player.disp.level)) { + (min_level > c->game_data.player()->disp.level)) { throw invalid_argument("level too low for difficulty"); } @@ -1552,7 +1503,7 @@ shared_ptr create_game_generic(shared_ptr s, game->password = password; game->version = c->version; game->section_id = c->override_section_id >= 0 - ? c->override_section_id : c->player.disp.section_id; + ? c->override_section_id : c->game_data.player()->disp.section_id; game->episode = episode; game->difficulty = difficulty; if (battle) { @@ -1779,12 +1730,14 @@ independently.\r\n\ } message += u"License check "; try { + shared_ptr l; if (c->flags & Client::Flag::BB_PATCH) { - c->license = s->license_manager->verify_bb(cmd.username, cmd.password); + l = s->license_manager->verify_bb(cmd.username, cmd.password); } else { - c->license = s->license_manager->verify_pc( + l = s->license_manager->verify_pc( stoul(cmd.username, nullptr, 16), cmd.password); } + c->set_license(l); message += u"OK"; } catch (const exception& e) { message += u"failed: "; @@ -2156,7 +2109,7 @@ static process_command_t bb_handlers[0x100] = { process_guild_card_data_request_bb, nullptr, nullptr, nullptr, // E0 - process_key_config_request_bb, nullptr, nullptr, process_player_preview_request_bb, + process_key_config_request_bb, nullptr, process_update_key_config_bb, process_player_preview_request_bb, nullptr, process_create_character_bb, nullptr, process_return_player_data_bb, process_client_checksum_bb, nullptr, process_team_command_bb, process_stream_file_request_bb, process_ignored_command, process_change_account_data_bb, nullptr, nullptr, @@ -2224,7 +2177,11 @@ static process_command_t* handlers[6] = { void process_command(shared_ptr s, shared_ptr c, uint16_t command, uint32_t flag, const string& data) { - string encoded_name = remove_language_marker(encode_sjis(c->player.disp.name)); + string encoded_name; + auto player = c->game_data.player(false); + if (player) { + encoded_name = remove_language_marker(encode_sjis(player->disp.name)); + } print_received_command(command, flag, data.data(), data.size(), c->version, encoded_name.c_str()); diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index e1508cb9..6ccf0a8b 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -115,13 +115,13 @@ static void process_subcommand_send_guild_card(shared_ptr, if (c->version == GameVersion::PC) { const auto* cmd = check_size_sc(data); - c->player.guild_card_desc = cmd->desc; + c->game_data.player()->guild_card_desc = cmd->desc; } else if (c->version == GameVersion::GC) { const auto* cmd = check_size_sc(data); - c->player.guild_card_desc = cmd->desc; + c->game_data.player()->guild_card_desc = cmd->desc; } else if (c->version == GameVersion::BB) { const auto* cmd = check_size_sc(data); - c->player.guild_card_desc = cmd->desc; + c->game_data.player()->guild_card_desc = cmd->desc; } send_guild_card(l->clients[flag], c); @@ -256,11 +256,12 @@ static void process_subcommand_player_drop_item(shared_ptr, return; } - l->add_item(c->player.remove_item(cmd->item_id, 0), cmd->area, cmd->x, cmd->z); + l->add_item(c->game_data.player()->remove_item(cmd->item_id, 0), + cmd->area, cmd->x, cmd->z); log(INFO, "[Items/%08" PRIX32 "] Player %hhu dropped item %08" PRIX32 " at %hu:(%g, %g)", l->lobby_id, cmd->client_id, cmd->item_id.load(), cmd->area.load(), cmd->x.load(), cmd->z.load()); - // c->player.print_inventory(stderr); + // c->game_data.player()->print_inventory(stderr); forward_subcommand(l, c, command, flag, data); } @@ -284,11 +285,11 @@ static void process_subcommand_create_inventory_item(shared_ptr, item.tech_flag = 0; item.game_flags = 0; item.data = cmd->item; - c->player.add_item(item); + c->game_data.player()->add_item(item); log(INFO, "[Items/%08" PRIX32 "] Player %hhu created inventory item %08" PRIX32, - l->lobby_id, cmd->client_id, cmd->item.item_id.load()); - // c->player.print_inventory(stderr); + l->lobby_id, cmd->client_id, cmd->item.id.load()); + // c->game_data.player()->print_inventory(stderr); forward_subcommand(l, c, command, flag, data); } @@ -316,8 +317,8 @@ static void process_subcommand_drop_partial_stack(shared_ptr, l->add_item(item, cmd->area, cmd->x, cmd->z); log(INFO, "[Items/%08" PRIX32 "] Player %hhu split stack to create ground item %08" PRIX32 " at %hu:(%g, %g)", - l->lobby_id, cmd->client_id, item.data.item_id.load(), cmd->area.load(), cmd->x.load(), cmd->z.load()); - // c->player.print_inventory(stderr); + l->lobby_id, cmd->client_id, item.data.id.load(), cmd->area.load(), cmd->x.load(), cmd->z.load()); + // c->game_data.player()->print_inventory(stderr); forward_subcommand(l, c, command, flag, data); } @@ -332,12 +333,12 @@ static void process_subcommand_drop_partial_stack_bb(shared_ptr, return; } - auto item = c->player.remove_item(cmd->item_id, cmd->amount); + auto item = c->game_data.player()->remove_item(cmd->item_id, cmd->amount); // 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 id=-1 - if (item.data.item_id == 0xFFFFFFFF) { - item.data.item_id = l->generate_item_id(c->lobby_client_id); + if (item.data.id == 0xFFFFFFFF) { + item.data.id = l->generate_item_id(c->lobby_client_id); } l->add_item(item, cmd->area, cmd->x, cmd->z); @@ -345,7 +346,7 @@ static void process_subcommand_drop_partial_stack_bb(shared_ptr, log(INFO, "[Items/%08" PRIX32 "] Player %hhu split stack %08" PRIX32 " (%" PRIu32 " of them) at %hu:(%g, %g)", l->lobby_id, cmd->client_id, cmd->item_id.load(), cmd->amount.load(), cmd->area.load(), cmd->x.load(), cmd->z.load()); - // c->player.print_inventory(stderr); + // c->game_data.player()->print_inventory(stderr); send_drop_stacked_item(l, item.data, cmd->area, cmd->x, cmd->z); @@ -371,11 +372,11 @@ static void process_subcommand_buy_shop_item(shared_ptr, item.tech_flag = 0; item.game_flags = 0; item.data = cmd->item; - c->player.add_item(item); + c->game_data.player()->add_item(item); log(INFO, "[Items/%08" PRIX32 "] Player %hhu bought item %08" PRIX32 " from shop", - l->lobby_id, cmd->client_id, item.data.item_id.load()); - // c->player.print_inventory(stderr); + l->lobby_id, cmd->client_id, item.data.id.load()); + // c->game_data.player()->print_inventory(stderr); forward_subcommand(l, c, command, flag, data); } @@ -400,8 +401,8 @@ static void process_subcommand_box_or_enemy_item_drop(shared_ptr, l->add_item(item, cmd->area, cmd->x, cmd->z); log(INFO, "[Items/%08" PRIX32 "] Leader created ground item %08" PRIX32 " at %hhu:(%g, %g)", - l->lobby_id, item.data.item_id.load(), cmd->area, cmd->x.load(), cmd->z.load()); - // c->player.print_inventory(stderr); + l->lobby_id, item.data.id.load(), cmd->area, cmd->x.load(), cmd->z.load()); + // c->game_data.player()->print_inventory(stderr); forward_subcommand(l, c, command, flag, data); } @@ -419,11 +420,11 @@ static void process_subcommand_pick_up_item(shared_ptr, // BB clients should never send this; only the server should send this return; } - c->player.add_item(l->remove_item(cmd->item_id)); + c->game_data.player()->add_item(l->remove_item(cmd->item_id)); log(INFO, "[Items/%08" PRIX32 "] Player %hu picked up %08" PRIX32, l->lobby_id, cmd->client_id.load(), cmd->item_id.load()); - // c->player.print_inventory(stderr); + // c->game_data.player()->print_inventory(stderr); forward_subcommand(l, c, command, flag, data); } @@ -439,7 +440,7 @@ static void process_subcommand_pick_up_item_request(shared_ptr, return; } - c->player.add_item(l->remove_item(cmd->item_id)); + c->game_data.player()->add_item(l->remove_item(cmd->item_id)); send_pick_up_item(l, c, cmd->item_id, cmd->area); @@ -459,11 +460,11 @@ static void process_subcommand_equip_unequip_item(shared_ptr, return; } - size_t index = c->player.inventory.find_item(cmd->item_id); + size_t index = c->game_data.player()->inventory.find_item(cmd->item_id); if (cmd->command == 0x25) { - c->player.inventory.items[index].game_flags |= 0x00000008; // equip + c->game_data.player()->inventory.items[index].game_flags |= 0x00000008; // equip } else { - c->player.inventory.items[index].game_flags &= 0xFFFFFFF7; // unequip + c->game_data.player()->inventory.items[index].game_flags &= 0xFFFFFFF7; // unequip } } else { @@ -480,12 +481,12 @@ static void process_subcommand_use_item(shared_ptr, return; } - size_t index = c->player.inventory.find_item(cmd->item_id); + size_t index = c->game_data.player()->inventory.find_item(cmd->item_id); player_use_item(c, index); log(INFO, "[Items/%08" PRIX32 "] Player used item %hhu:%08" PRIX32, l->lobby_id, cmd->client_id, cmd->item_id.load()); - // c->player.print_inventory(stderr); + // c->game_data.player()->print_inventory(stderr); forward_subcommand(l, c, command, flag, data); } @@ -502,9 +503,9 @@ static void process_subcommand_open_shop_bb_or_unknown_ep3(shared_ptrversion == GameVersion::BB) && l->is_game()) { - size_t num_items = (rand() % 4) + 9; - c->player.current_shop_contents.clear(); - while (c->player.current_shop_contents.size() < num_items) { + size_t num_items = 9 + (rand() % 4); + c->game_data.shop_contents.clear(); + while (c->game_data.shop_contents.size() < num_items) { ItemData item_data; if (shop_type == 0) { // tool shop item_data = s->common_item_creator->create_shop_item(l->difficulty, 3); @@ -516,8 +517,8 @@ static void process_subcommand_open_shop_bb_or_unknown_ep3(shared_ptrgenerate_item_id(c->lobby_client_id); - c->player.current_shop_contents.emplace_back(item_data); + item_data.id = l->generate_item_id(c->lobby_client_id); + c->game_data.shop_contents.emplace_back(item_data); } send_shop(c, shop_type); @@ -543,34 +544,34 @@ static void process_subcommand_bank_action_bb(shared_ptr, if (cmd->action == 0) { // deposit if (cmd->item_id == 0xFFFFFFFF) { // meseta - if (cmd->meseta_amount > c->player.disp.meseta) { + if (cmd->meseta_amount > c->game_data.player()->disp.meseta) { return; } - if ((c->player.bank.meseta + cmd->meseta_amount) > 999999) { + if ((c->game_data.player()->bank.meseta + cmd->meseta_amount) > 999999) { return; } - c->player.bank.meseta += cmd->meseta_amount; - c->player.disp.meseta -= cmd->meseta_amount; + c->game_data.player()->bank.meseta += cmd->meseta_amount; + c->game_data.player()->disp.meseta -= cmd->meseta_amount; } else { // item - auto item = c->player.remove_item(cmd->item_id, cmd->item_amount); - c->player.bank.add_item(item.to_bank_item()); + auto item = c->game_data.player()->remove_item(cmd->item_id, cmd->item_amount); + c->game_data.player()->bank.add_item(item); send_destroy_item(l, c, cmd->item_id, cmd->item_amount); } } else if (cmd->action == 1) { // take if (cmd->item_id == 0xFFFFFFFF) { // meseta - if (cmd->meseta_amount > c->player.bank.meseta) { + if (cmd->meseta_amount > c->game_data.player()->bank.meseta) { return; } - if ((c->player.disp.meseta + cmd->meseta_amount) > 999999) { + if ((c->game_data.player()->disp.meseta + cmd->meseta_amount) > 999999) { return; } - c->player.bank.meseta -= cmd->meseta_amount; - c->player.disp.meseta += cmd->meseta_amount; + c->game_data.player()->bank.meseta -= cmd->meseta_amount; + c->game_data.player()->disp.meseta += cmd->meseta_amount; } else { // item - auto bank_item = c->player.bank.remove_item(cmd->item_id, cmd->item_amount); - PlayerInventoryItem item = bank_item.to_inventory_item(); - item.data.item_id = l->generate_item_id(0xFF); - c->player.add_item(item); + auto bank_item = c->game_data.player()->bank.remove_item(cmd->item_id, cmd->item_amount); + PlayerInventoryItem item = bank_item; + item.data.id = l->generate_item_id(0xFF); + c->game_data.player()->add_item(item); send_create_inventory_item(l, c, item.data); } } @@ -589,18 +590,18 @@ static void process_subcommand_sort_inventory_bb(shared_ptr, for (size_t x = 0; x < 30; x++) { if (cmd->item_ids[x] == 0xFFFFFFFF) { - sorted.items[x].data.item_id = 0xFFFFFFFF; + sorted.items[x].data.id = 0xFFFFFFFF; } else { - size_t index = c->player.inventory.find_item(cmd->item_ids[x]); - sorted.items[x] = c->player.inventory.items[index]; + size_t index = c->game_data.player()->inventory.find_item(cmd->item_ids[x]); + sorted.items[x] = c->game_data.player()->inventory.items[index]; } } - sorted.num_items = c->player.inventory.num_items; - sorted.hp_materials_used = c->player.inventory.hp_materials_used; - sorted.tp_materials_used = c->player.inventory.tp_materials_used; - sorted.language = c->player.inventory.language; - c->player.inventory = sorted; + sorted.num_items = c->game_data.player()->inventory.num_items; + sorted.hp_materials_used = c->game_data.player()->inventory.hp_materials_used; + sorted.tp_materials_used = c->game_data.player()->inventory.tp_materials_used; + sorted.language = c->game_data.player()->inventory.language; + c->game_data.player()->inventory = sorted; } } @@ -620,10 +621,11 @@ static void process_subcommand_enemy_drop_item_request(shared_ptr s PlayerInventoryItem item; memset(&item, 0, sizeof(PlayerInventoryItem)); + // TODO: Deduplicate this code with the box drop item request handler bool is_rare = false; - if (l->next_drop_item.data.item_data1d[0]) { + if (l->next_drop_item.data.data1d[0]) { item = l->next_drop_item; - l->next_drop_item.data.item_data1d[0] = 0; + l->next_drop_item.data.data1d[0] = 0; } else { if (l->rare_item_set) { if (cmd->enemy_id <= 0x65) { @@ -632,10 +634,10 @@ static void process_subcommand_enemy_drop_item_request(shared_ptr s } if (is_rare) { - memcpy(&item.data.item_data1d, l->rare_item_set->rares[cmd->enemy_id].item_code, 3); + memcpy(&item.data.data1d, l->rare_item_set->rares[cmd->enemy_id].item_code, 3); //RandPercentages(); - if (item.data.item_data1d[0] == 0) { - item.data.item_data1[4] |= 0x80; // make it untekked if it's a weapon + if (item.data.data1d[0] == 0) { + item.data.data1[4] |= 0x80; // make it unidentified if it's a weapon } } else { try { @@ -647,7 +649,7 @@ static void process_subcommand_enemy_drop_item_request(shared_ptr s } } } - item.data.item_id = l->generate_item_id(0xFF); + item.data.id = l->generate_item_id(0xFF); l->add_item(item, cmd->area, cmd->x, cmd->z); send_drop_item(l, item.data, false, cmd->area, cmd->x, cmd->z, @@ -672,9 +674,9 @@ static void process_subcommand_box_drop_item_request(shared_ptr s, memset(&item, 0, sizeof(PlayerInventoryItem)); bool is_rare = false; - if (l->next_drop_item.data.item_data1d[0]) { + if (l->next_drop_item.data.data1d[0]) { item = l->next_drop_item; - l->next_drop_item.data.item_data1d[0] = 0; + l->next_drop_item.data.data1d[0] = 0; } else { size_t index; if (l->rare_item_set) { @@ -690,10 +692,10 @@ static void process_subcommand_box_drop_item_request(shared_ptr s, } if (is_rare) { - memcpy(item.data.item_data1, l->rare_item_set->box_rares[index].item_code, 3); + memcpy(item.data.data1, l->rare_item_set->box_rares[index].item_code, 3); //RandPercentages(); - if (item.data.item_data1d[0] == 0) { - item.data.item_data1[4] |= 0x80; // make it untekked if it's a weapon + if (item.data.data1d[0] == 0) { + item.data.data1[4] |= 0x80; // make it unidentified if it's a weapon } } else { try { @@ -705,7 +707,7 @@ static void process_subcommand_box_drop_item_request(shared_ptr s, } } } - item.data.item_id = l->generate_item_id(0xFF); + item.data.id = l->generate_item_id(0xFF); l->add_item(item, cmd->area, cmd->x, cmd->z); send_drop_item(l, item.data, false, cmd->area, cmd->x, cmd->z, @@ -812,7 +814,7 @@ static void process_subcommand_enemy_killed(shared_ptr s, if (!other_c) { continue; // no player } - if (other_c->player.disp.level >= 199) { + if (other_c->game_data.player()->disp.level >= 199) { continue; // player is level 200 or higher } @@ -824,21 +826,21 @@ static void process_subcommand_enemy_killed(shared_ptr s, exp = ((enemy.experience * 77) / 100); } - other_c->player.disp.experience += exp; + other_c->game_data.player()->disp.experience += exp; send_give_experience(l, other_c, exp); bool leveled_up = false; do { const auto& level = s->level_table->stats_for_level( - other_c->player.disp.char_class, other_c->player.disp.level + 1); - if (other_c->player.disp.experience >= level.experience) { + other_c->game_data.player()->disp.char_class, other_c->game_data.player()->disp.level + 1); + if (other_c->game_data.player()->disp.experience >= level.experience) { leveled_up = true; - level.apply(other_c->player.disp.stats); - other_c->player.disp.level++; + level.apply(other_c->game_data.player()->disp.stats); + other_c->game_data.player()->disp.level++; } else { break; } - } while (other_c->player.disp.level < 199); + } while (other_c->game_data.player()->disp.level < 199); if (leveled_up) { send_level_up(l, other_c); } @@ -856,10 +858,10 @@ static void process_subcommand_destroy_inventory_item(shared_ptr, if (cmd->client_id != c->lobby_client_id) { return; } - c->player.remove_item(cmd->item_id, cmd->amount); + c->game_data.player()->remove_item(cmd->item_id, cmd->amount); log(INFO, "[Items/%08" PRIX32 "] Inventory item %hhu:%08" PRIX32 " destroyed (%" PRIX32 " of them)", l->lobby_id, cmd->client_id, cmd->item_id.load(), cmd->amount.load()); - // c->player.print_inventory(stderr); + // c->game_data.player()->print_inventory(stderr); forward_subcommand(l, c, command, flag, data); } @@ -873,7 +875,7 @@ static void process_subcommand_destroy_ground_item(shared_ptr, l->remove_item(cmd->item_id); log(INFO, "[Items/%08" PRIX32 "] Ground item %08" PRIX32 " destroyed (%" PRIX32 " of them)", l->lobby_id, cmd->item_id.load(), cmd->amount.load()); - // c->player.print_inventory(stderr); + // c->game_data.player()->print_inventory(stderr); forward_subcommand(l, c, command, flag, data); } @@ -886,14 +888,14 @@ static void process_subcommand_identify_item_bb(shared_ptr, return; } - size_t x = c->player.inventory.find_item(cmd->item_id); - if (c->player.inventory.items[x].data.item_data1[0] != 0) { + size_t x = c->game_data.player()->inventory.find_item(cmd->item_id); + if (c->game_data.player()->inventory.items[x].data.data1[0] != 0) { return; // only weapons can be identified } - c->player.disp.meseta -= 100; - c->player.identify_result = c->player.inventory.items[x]; - c->player.identify_result.data.item_data1[4] &= 0x7F; + c->game_data.player()->disp.meseta -= 100; + c->game_data.identify_result = c->game_data.player()->inventory.items[x]; + c->game_data.identify_result.data.data1[4] &= 0x7F; // TODO: move this into a SendCommands.cc function G_IdentifyResult_BB_6xB9 res; @@ -901,7 +903,7 @@ static void process_subcommand_identify_item_bb(shared_ptr, res.size = sizeof(cmd) / 4; res.client_id = c->lobby_client_id; res.unused = 0; - res.item = c->player.identify_result.data; + res.item = c->game_data.identify_result.data; send_command(l, 0x60, 0x00, res); } else { @@ -923,8 +925,8 @@ static void process_subcommand_identify_item_bb(shared_ptr, // return; // } // -// size_t x = c->player.inventory.find_item(cmd->item_id); -// c->player.inventory.items[x] = c->player.identify_result; +// size_t x = c->game_data.player()->inventory.find_item(cmd->item_id); +// c->game_data.player()->inventory.items[x] = c->game_data.player()->identify_result; // // TODO: what do we send to the other clients? anything? // // } else { diff --git a/src/SendCommands.cc b/src/SendCommands.cc index 91bb54c0..507ac164 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -113,7 +113,11 @@ void send_command(shared_ptr c, uint16_t command, uint32_t flag, if (!c->bev) { return; } - string encoded_name = remove_language_marker(encode_sjis(c->player.disp.name)); + string encoded_name; + auto player = c->game_data.player(false); + if (player) { + encoded_name = remove_language_marker(encode_sjis(player->disp.name)); + } send_command(c->bev, c->version, c->crypt_out.get(), command, flag, data, size, encoded_name.c_str()); } @@ -294,7 +298,7 @@ void send_client_init_bb(shared_ptr c, uint32_t error) { } void send_team_and_key_config_bb(shared_ptr c) { - send_command(c, 0x00E2, 0x00000000, c->player.key_config); + send_command(c, 0x00E2, 0x00000000, c->game_data.account()->key_config); } void send_player_preview_bb(shared_ptr c, uint8_t player_index, @@ -317,8 +321,8 @@ void send_accept_client_checksum_bb(shared_ptr c) { } void send_guild_card_header_bb(shared_ptr c) { - uint32_t checksum = compute_guild_card_checksum(&c->player.guild_cards, - sizeof(GuildCardFileBB)); + uint32_t checksum = compute_guild_card_checksum( + &c->game_data.account()->guild_cards, sizeof(GuildCardFileBB)); S_GuildCardHeader_BB_01DC cmd = {1, 0x00000490, checksum}; send_command(c, 0x01DC, 0x00000000, cmd); } @@ -336,7 +340,8 @@ void send_guild_card_chunk_bb(shared_ptr c, size_t chunk_index) { StringWriter w; w.put_u32l(0); w.put_u32l(chunk_index); - w.write(&c->player.guild_cards + chunk_offset, data_size); + w.write(reinterpret_cast(&c->game_data.account()->guild_cards) + chunk_offset, + data_size); send_command(c, 0x02DC, 0x00000000, w.str()); } @@ -397,7 +402,7 @@ void send_approve_player_choice_bb(shared_ptr c) { } void send_complete_player_bb(shared_ptr c) { - send_command(c, 0x00E7, 0x00000000, c->player.export_bb_player_data()); + send_command(c, 0x00E7, 0x00000000, c->game_data.export_player_bb()); } @@ -528,8 +533,8 @@ void send_info_board_t(shared_ptr c, shared_ptr l) { continue; } auto& e = entries.emplace_back(); - e.name = c->player.disp.name; - e.message = c->player.info_board; + e.name = c->game_data.player()->disp.name; + e.message = c->game_data.player()->info_board; add_color_inplace(e.message); } send_command(c, 0xD8, entries.size(), entries); @@ -582,7 +587,7 @@ void send_card_search_result_t( cmd.location_string = location_string; cmd.menu_id = LOBBY_MENU_ID; cmd.lobby_id = result->lobby_id; - cmd.name = result->player.disp.name; + cmd.name = result->game_data.player()->disp.name; send_command(c, 0x41, 0x00, cmd); } @@ -616,13 +621,13 @@ void send_guild_card_pc_gc(shared_ptr c, shared_ptr source) { cmd.unused = 0x0000; cmd.player_tag = 0x00010000; cmd.guild_card_number = source->license->serial_number; - cmd.name = source->player.disp.name; + cmd.name = source->game_data.player()->disp.name; remove_language_marker_inplace(cmd.name); - cmd.desc = source->player.guild_card_desc; + cmd.desc = source->game_data.player()->guild_card_desc; cmd.reserved1 = 1; cmd.reserved2 = 1; - cmd.section_id = source->player.disp.section_id; - cmd.char_class = source->player.disp.char_class; + cmd.section_id = source->game_data.player()->disp.section_id; + cmd.char_class = source->game_data.player()->disp.char_class; send_command(c, 0x62, c->lobby_client_id, cmd); } @@ -632,13 +637,13 @@ void send_guild_card_bb(shared_ptr c, shared_ptr source) { cmd.size = sizeof(cmd) / 4; cmd.unused = 0x0000; cmd.guild_card_number = source->license->serial_number; - cmd.name = remove_language_marker(source->player.disp.name); - cmd.team_name = remove_language_marker(source->player.team_name); - cmd.desc = source->player.guild_card_desc; + cmd.name = remove_language_marker(source->game_data.player()->disp.name); + cmd.team_name = remove_language_marker(source->game_data.account()->team_name); + cmd.desc = source->game_data.player()->guild_card_desc; cmd.reserved1 = 1; cmd.reserved2 = 1; - cmd.section_id = source->player.disp.section_id; - cmd.char_class = source->player.disp.char_class; + cmd.section_id = source->game_data.player()->disp.section_id; + cmd.char_class = source->game_data.player()->disp.char_class; send_command(c, 0x62, c->lobby_client_id, cmd); } @@ -860,11 +865,11 @@ void send_join_game_t(shared_ptr c, shared_ptr l) { // See comment in send_join_lobby_t about Episode III behavior here cmd.lobby_data[x].ip_address = 0x7F000001; cmd.lobby_data[x].client_id = c->lobby_client_id; - cmd.lobby_data[x].name = l->clients[x]->player.disp.name; + cmd.lobby_data[x].name = l->clients[x]->game_data.player()->disp.name; if (l->flags & Lobby::Flag::EPISODE_3_ONLY) { - cmd.players_ep3[x].inventory = l->clients[x]->player.inventory; + cmd.players_ep3[x].inventory = l->clients[x]->game_data.player()->inventory; cmd.players_ep3[x].disp = convert_player_disp_data( - l->clients[x]->player.disp); + l->clients[x]->game_data.player()->disp); } player_count++; } else { @@ -957,9 +962,9 @@ void send_join_lobby_t(shared_ptr c, shared_ptr l, // to avoid this behavior. e.lobby_data.ip_address = 0x7F000001; e.lobby_data.client_id = lc->lobby_client_id; - e.lobby_data.name = lc->player.disp.name; - e.inventory = lc->player.inventory; - e.disp = convert_player_disp_data(lc->player.disp); + e.lobby_data.name = lc->game_data.player()->disp.name; + e.inventory = lc->game_data.player()->inventory; + e.disp = convert_player_disp_data(lc->game_data.player()->disp); if (c->version == GameVersion::PC) { e.disp.enforce_pc_limits(); } @@ -1173,12 +1178,12 @@ void send_destroy_item(shared_ptr l, shared_ptr c, // sends the player their bank data void send_bank(shared_ptr c) { - vector items(c->player.bank.items, - &c->player.bank.items[c->player.bank.num_items]); + vector items(c->game_data.player()->bank.items, + &c->game_data.player()->bank.items[c->game_data.player()->bank.num_items]); uint32_t checksum = random_object(); G_BankContentsHeader_BB_6xBC cmd = { - 0xBC, 0, 0, 0, checksum, c->player.bank.num_items, c->player.bank.meseta}; + 0xBC, 0, 0, 0, checksum, c->game_data.player()->bank.num_items, c->game_data.player()->bank.meseta}; size_t size = 8 + sizeof(cmd) + items.size() * sizeof(PlayerBankItem); cmd.size = size; @@ -1193,18 +1198,18 @@ void send_shop(shared_ptr c, uint8_t shop_type) { 0x2C, 0x037F, shop_type, - static_cast(c->player.current_shop_contents.size()), + static_cast(c->game_data.shop_contents.size()), 0, {}, }; - size_t count = c->player.current_shop_contents.size(); + 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 < count; x++) { - cmd.entries[x] = c->player.current_shop_contents[x]; + cmd.entries[x] = c->game_data.shop_contents[x]; } send_command(c, 0x6C, 0x00, &cmd, sizeof(cmd) - sizeof(cmd.entries[0]) * (20 - count)); @@ -1212,15 +1217,15 @@ void send_shop(shared_ptr c, uint8_t shop_type) { // notifies players about a level up void send_level_up(shared_ptr l, shared_ptr c) { - PlayerStats stats = c->player.disp.stats; + PlayerStats stats = c->game_data.player()->disp.stats; - for (size_t x = 0; x < c->player.inventory.num_items; x++) { - if ((c->player.inventory.items[x].equip_flags & 0x08) && - (c->player.inventory.items[x].data.item_data1[0] == 0x02)) { - stats.dfp += (c->player.inventory.items[x].data.item_data1w[2] / 100); - stats.atp += (c->player.inventory.items[x].data.item_data1w[3] / 50); - stats.ata += (c->player.inventory.items[x].data.item_data1w[4] / 200); - stats.mst += (c->player.inventory.items[x].data.item_data1w[5] / 50); + for (size_t x = 0; x < c->game_data.player()->inventory.num_items; x++) { + if ((c->game_data.player()->inventory.items[x].equip_flags & 0x08) && + (c->game_data.player()->inventory.items[x].data.data1[0] == 0x02)) { + stats.dfp += (c->game_data.player()->inventory.items[x].data.data1w[2] / 100); + stats.atp += (c->game_data.player()->inventory.items[x].data.data1w[3] / 50); + stats.ata += (c->game_data.player()->inventory.items[x].data.data1w[4] / 200); + stats.mst += (c->game_data.player()->inventory.items[x].data.data1w[5] / 50); } } @@ -1235,7 +1240,7 @@ void send_level_up(shared_ptr l, shared_ptr c) { stats.hp, stats.dfp, stats.ata, - c->player.disp.level}; + c->game_data.player()->disp.level}; send_command(l, 0x60, 0x00, cmd); } diff --git a/src/StaticGameData.cc b/src/StaticGameData.cc index 0615f1cf..5cce8ed2 100644 --- a/src/StaticGameData.cc +++ b/src/StaticGameData.cc @@ -1370,16 +1370,16 @@ uint8_t technique_for_name(const u16string& name) { } string name_for_item(const ItemData& item, bool include_color_codes) { - if (item.item_data1[0] == 0x04) { - return string_printf("%" PRIu32 " Meseta", item.item_data2d.load()); + if (item.data1[0] == 0x04) { + return string_printf("%" PRIu32 " Meseta", item.data2d.load()); } vector ret_tokens; // For weapons, specials appear before the weapon name - if ((item.item_data1[0] == 0x00) && (item.item_data1[4] != 0x00)) { - bool is_present = item.item_data1[4] & 0x40; - uint8_t special_id = item.item_data1[4] & 0x3F; + if ((item.data1[0] == 0x00) && (item.data1[4] != 0x00)) { + bool is_present = item.data1[4] & 0x40; + uint8_t special_id = item.data1[4] & 0x3F; if (is_present) { ret_tokens.emplace_back("Wrapped"); } @@ -1392,7 +1392,7 @@ string name_for_item(const ItemData& item, bool include_color_codes) { } } // Mags can be wrapped as well - if ((item.item_data1[0] == 0x02) && (item.item_data2[1] & 0x40)) { + if ((item.data1[0] == 0x02) && (item.data2[1] & 0x40)) { ret_tokens.emplace_back("Wrapped"); } @@ -1402,10 +1402,10 @@ string name_for_item(const ItemData& item, bool include_color_codes) { ItemNameInfo name_info(nullptr, false, false); uint32_t primary_identifier = item.primary_identifier(); if ((primary_identifier & 0xFFFFFF00) == 0x00030200) { - string technique_name = name_for_technique(item.item_data1[4]); + string technique_name = name_for_technique(item.data1[4]); technique_name[0] = toupper(technique_name[0]); ret_tokens.emplace_back(string_printf( - "Disk:%s Lv.%d", technique_name.c_str(), item.item_data1[2] + 1)); + "Disk:%s Lv.%d", technique_name.c_str(), item.data1[2] + 1)); } else { try { name_info = name_info_for_primary_identifier.at(primary_identifier); @@ -1416,22 +1416,22 @@ string name_for_item(const ItemData& item, bool include_color_codes) { } // For weapons, add the grind and percentages, or S-rank name if applicable - if (item.item_data1[0] == 0x00) { - if (item.item_data1[3] > 0) { - ret_tokens.emplace_back(string_printf("+%hhu", item.item_data1[3])); + if (item.data1[0] == 0x00) { + if (item.data1[3] > 0) { + ret_tokens.emplace_back(string_printf("+%hhu", item.data1[3])); } - if (name_info.is_s_rank && (item.item_data1[6] & 0x18)) { + if (name_info.is_s_rank && (item.data1[6] & 0x18)) { // S-rank (has name instead of percent bonuses) uint8_t char_indexes[8] = { - static_cast((item.item_data1w[3] >> 5) & 0x1F), - static_cast(item.item_data1w[3] & 0x1F), - static_cast((item.item_data1w[4] >> 10) & 0x1F), - static_cast((item.item_data1w[4] >> 5) & 0x1F), - static_cast(item.item_data1w[4] & 0x1F), - static_cast((item.item_data1w[5] >> 10) & 0x1F), - static_cast((item.item_data1w[5] >> 5) & 0x1F), - static_cast(item.item_data1w[5] & 0x1F), + static_cast((item.data1w[3] >> 5) & 0x1F), + static_cast(item.data1w[3] & 0x1F), + static_cast((item.data1w[4] >> 10) & 0x1F), + static_cast((item.data1w[4] >> 5) & 0x1F), + static_cast(item.data1w[4] & 0x1F), + static_cast((item.data1w[5] >> 10) & 0x1F), + static_cast((item.data1w[5] >> 5) & 0x1F), + static_cast(item.data1w[5] & 0x1F), }; const char* translation_table = "\0ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"; @@ -1450,8 +1450,8 @@ string name_for_item(const ItemData& item, bool include_color_codes) { } else { // Not S-rank (extended name bits not set) uint8_t percentages[5] = {0, 0, 0, 0, 0}; for (size_t x = 0; x < 3; x++) { - uint8_t which = item.item_data1[6 + 2 * x]; - uint8_t value = item.item_data1[7 + 2 * x]; + uint8_t which = item.data1[6 + 2 * x]; + uint8_t value = item.data1[7 + 2 * x]; if (which == 0) { continue; } @@ -1466,9 +1466,9 @@ string name_for_item(const ItemData& item, bool include_color_codes) { } // For armors, add the slots, unit modifiers, and/or DEF/EVP bonuses - } else if (item.item_data1[0] == 0x01) { - if (item.item_data1[1] == 0x03) { // Units - uint16_t modifier = (item.item_data1[8] << 8) | item.item_data1[7]; + } else if (item.data1[0] == 0x01) { + if (item.data1[1] == 0x03) { // Units + uint16_t modifier = (item.data1[8] << 8) | item.data1[7]; if (modifier == 0x0001 || modifier == 0x0002) { ret_tokens.back().append("+"); } else if (modifier == 0x0003 || modifier == 0x0004) { @@ -1482,42 +1482,42 @@ string name_for_item(const ItemData& item, bool include_color_codes) { } } else { // Armor/shields - if (item.item_data1[5] > 0) { - if (item.item_data1[5] == 1) { + if (item.data1[5] > 0) { + if (item.data1[5] == 1) { ret_tokens.emplace_back("(1 slot)"); } else { - ret_tokens.emplace_back(string_printf("(%hhu slots)", item.item_data1[5])); + ret_tokens.emplace_back(string_printf("(%hhu slots)", item.data1[5])); } } - if (item.item_data1w[3] != 0) { + if (item.data1w[3] != 0) { ret_tokens.emplace_back(string_printf("+%hdDEF", - static_cast(item.item_data1w[3].load()))); + static_cast(item.data1w[3].load()))); } - if (item.item_data1w[4] != 0) { + if (item.data1w[4] != 0) { ret_tokens.emplace_back(string_printf("+%hdEVP", - static_cast(item.item_data1w[4].load()))); + static_cast(item.data1w[4].load()))); } } // For mags, add tons of info - } else if (item.item_data1[0] == 0x02) { - ret_tokens.emplace_back(string_printf("LV%hhu", item.item_data1[2])); + } else if (item.data1[0] == 0x02) { + ret_tokens.emplace_back(string_printf("LV%hhu", item.data1[2])); ret_tokens.emplace_back(string_printf("%d/%d/%d/%d", - item.item_data1w[2] / 100, item.item_data1w[3] / 100, - item.item_data1w[4] / 100, item.item_data1w[5] / 100)); - ret_tokens.emplace_back(string_printf("%hu%%", item.item_data2[3])); - ret_tokens.emplace_back(string_printf("%huIQ", item.item_data2[2])); + item.data1w[2] / 100, item.data1w[3] / 100, + item.data1w[4] / 100, item.data1w[5] / 100)); + ret_tokens.emplace_back(string_printf("%hu%%", item.data2[3])); + ret_tokens.emplace_back(string_printf("%huIQ", item.data2[2])); - uint8_t flags = item.item_data2[1]; + uint8_t flags = item.data2[1]; if (flags & 7) { static const vector pb_shortnames = { "F", "E", "G", "P", "L", "M&Y", "MG", "GR"}; const char* pb_names[3] = {nullptr, nullptr, nullptr}; - uint8_t center_pb = (flags & 2) ? (item.item_data1[3] & 7) : 0xFF; - uint8_t right_pb = (flags & 1) ? ((item.item_data1[3] >> 3) & 7) : 0xFF; - uint8_t left_pb = (flags & 4) ? ((item.item_data1[3] >> 6) & 3) : 0xFF; + uint8_t center_pb = (flags & 2) ? (item.data1[3] & 7) : 0xFF; + uint8_t right_pb = (flags & 1) ? ((item.data1[3] >> 3) & 7) : 0xFF; + uint8_t left_pb = (flags & 4) ? ((item.data1[3] >> 6) & 3) : 0xFF; if (center_pb != 0xFF) { pb_names[1] = pb_shortnames[center_pb]; } @@ -1584,16 +1584,16 @@ string name_for_item(const ItemData& item, bool include_color_codes) { "costume color", }); try { - ret_tokens.emplace_back(string_printf("(%s)", mag_colors.at(item.item_data2[0]))); + ret_tokens.emplace_back(string_printf("(%s)", mag_colors.at(item.data2[0]))); } catch (const out_of_range&) { - ret_tokens.emplace_back(string_printf("(!CL:%02hhX)", item.item_data2[0])); + ret_tokens.emplace_back(string_printf("(!CL:%02hhX)", item.data2[0])); } } // For tools, add the amount (if applicable) - } else if (item.item_data1[0] == 0x03) { + } else if (item.data1[0] == 0x03) { if (combine_item_to_max.count(primary_identifier)) { - ret_tokens.emplace_back(string_printf("x%hhu", item.item_data1[5])); + ret_tokens.emplace_back(string_printf("x%hhu", item.data1[5])); } }