fix tech disk stacking on 11/2000

This commit is contained in:
Martin Michelsen
2024-01-04 10:39:18 -08:00
parent 70207896e3
commit 612b5d28ba
11 changed files with 81 additions and 79 deletions
+4 -4
View File
@@ -987,7 +987,7 @@ bool ItemCreator::shop_does_not_contain_duplicate_or_too_many_similar_weapons(
return true;
}
bool ItemCreator::shop_does_not_contain_duplicate_item_by_primary_identifier(
bool ItemCreator::shop_does_not_contain_duplicate_item_by_data1_0_1_2(
const vector<ItemData>& shop, const ItemData& item) {
for (const auto& shop_item : shop) {
if ((shop_item.data1[0] == item.data1[0]) &&
@@ -1080,7 +1080,7 @@ void ItemCreator::generate_armor_shop_shields(vector<ItemData>& shop, size_t pla
}
}
if (this->shop_does_not_contain_duplicate_item_by_primary_identifier(shop, item)) {
if (this->shop_does_not_contain_duplicate_item_by_data1_0_1_2(shop, item)) {
shop.emplace_back(std::move(item));
items_generated++;
}
@@ -1114,7 +1114,7 @@ void ItemCreator::generate_armor_shop_units(vector<ItemData>& shop, size_t playe
item.data1[0] = 1;
item.data1[1] = 3;
item.data1[2] = pt.pop();
if (this->shop_does_not_contain_duplicate_item_by_primary_identifier(shop, item)) {
if (this->shop_does_not_contain_duplicate_item_by_data1_0_1_2(shop, item)) {
shop.emplace_back(std::move(item));
items_generated++;
}
@@ -1225,7 +1225,7 @@ void ItemCreator::generate_rare_tool_shop_recovery_items(
item.data1[0] = 3;
item.data1[1] = tool_item_defs[type].first;
item.data1[2] = tool_item_defs[type].second;
if (this->shop_does_not_contain_duplicate_item_by_primary_identifier(shop, item)) {
if (this->shop_does_not_contain_duplicate_item_by_data1_0_1_2(shop, item)) {
shop.emplace_back(std::move(item));
items_generated++;
}
+1 -1
View File
@@ -134,7 +134,7 @@ private:
const std::vector<ItemData>& shop, const ItemData& item);
static bool shop_does_not_contain_duplicate_or_too_many_similar_weapons(
const std::vector<ItemData>& shop, const ItemData& item);
static bool shop_does_not_contain_duplicate_item_by_primary_identifier(
static bool shop_does_not_contain_duplicate_item_by_data1_0_1_2(
const std::vector<ItemData>& shop, const ItemData& item);
void generate_armor_shop_armors(
std::vector<ItemData>& shop, size_t player_level);
+26 -9
View File
@@ -71,16 +71,17 @@ uint32_t ItemData::primary_identifier() const {
// The game treats any item starting with 04 as Meseta, and ignores the rest
// of data1 (the value is in data2)
if (this->data1[0] == 0x04) {
return 0x040000;
return 0x04000000;
}
if (this->data1[0] == 0x03 && this->data1[1] == 0x02) {
return 0x030200; // Tech disk (data1[2] is level, so omit it)
// Tech disk (tech ID is data1[4], not [2])
return 0x03020000 | (this->data1[4] << 8) | this->data1[2];
} else if (this->data1[0] == 0x02) {
return 0x020000 | (this->data1[1] << 8); // Mag
return 0x02000000 | (this->data1[1] << 16); // Mag
} else if (this->is_s_rank_weapon()) {
return (this->data1[0] << 16) | (this->data1[1] << 8);
return (this->data1[0] << 24) | (this->data1[1] << 16);
} else {
return (this->data1[0] << 16) | (this->data1[1] << 8) | this->data1[2];
return (this->data1[0] << 24) | (this->data1[1] << 16) | (this->data1[2] << 8);
}
}
@@ -164,10 +165,9 @@ void ItemData::enforce_min_stack_size(Version version) {
}
bool ItemData::is_common_consumable(uint32_t primary_identifier) {
if (primary_identifier == 0x030200) {
return false;
}
return (primary_identifier >= 0x030000) && (primary_identifier < 0x030A00);
return (primary_identifier >= 0x03000000) &&
(primary_identifier < 0x030A0000) &&
((primary_identifier & 0xFFFF0000) != 0x03020000);
}
bool ItemData::is_common_consumable() const {
@@ -667,6 +667,23 @@ ItemData ItemData::from_data(const string& data) {
return ret;
}
ItemData ItemData::from_primary_identifier(Version version, uint32_t primary_identifier) {
ItemData ret;
if (primary_identifier > 0x04000000) {
throw runtime_error("invalid item class");
}
ret.data1[0] = (primary_identifier >> 24) & 0xFF;
ret.data1[1] = (primary_identifier >> 16) & 0xFF;
if ((primary_identifier & 0xFFFF0000) == 0x03020000) {
ret.data1[4] = (primary_identifier >> 8) & 0xFF;
ret.data1[2] = primary_identifier & 0xFF;
} else {
ret.data1[2] = (primary_identifier >> 8) & 0xFF;
}
ret.set_tool_item_amount(version, 1);
return ret;
}
string ItemData::hex() const {
return string_printf("%02hhX%02hhX%02hhX%02hhX %02hhX%02hhX%02hhX%02hhX %02hhX%02hhX%02hhX%02hhX (%08" PRIX32 ") %02hhX%02hhX%02hhX%02hhX",
this->data1[0], this->data1[1], this->data1[2], this->data1[3],
+1 -2
View File
@@ -6,8 +6,6 @@
#include "Text.hh"
#include "Version.hh"
constexpr uint32_t MESETA_IDENTIFIER = 0x040000;
class ItemParameterTable;
enum class EquipSlot {
@@ -126,6 +124,7 @@ struct ItemData { // 0x14 bytes
void clear();
static ItemData from_data(const std::string& data);
static ItemData from_primary_identifier(Version version, uint32_t primary_identifier);
std::string hex() const;
uint32_t primary_identifier() const;
+12 -27
View File
@@ -4,22 +4,6 @@
using namespace std;
// class ItemNameIndex {
// public:
// ItemNameIndex(std::shared_ptr<const ItemParameterTable> pmt, const std::vector<std::string>& name_coll);
// std::string describe_item(const ItemData& item, bool include_color_escapes = false) const;
// ItemData parse_item_description(const std::string& description) const;
// private:
// ItemData parse_item_description_phase(const std::string& description, bool skip_special) const;
// std::shared_ptr<const ItemParameterTable> item_parameter_table;
// struct ItemMetadata {
// uint32_t primary_identifier;
// std::string name;
// };
// std::unordered_map<uint32_t, std::shared_ptr<ItemMetadata>> primary_identifier_indexes;
// std::map<std::string, std::shared_ptr<ItemMetadata>> name_indexes;
// };
ItemNameIndex::ItemNameIndex(
Version version,
std::shared_ptr<const ItemParameterTable> item_parameter_table,
@@ -55,7 +39,9 @@ ItemNameIndex::ItemNameIndex(
};
auto find_items_2d = [&](uint64_t data1) {
for (size_t x = 0; x < 0x100; x++) {
if (find_items_1d(data1 | (static_cast<uint64_t>(x) << 48), 2) == 0) {
size_t effective_data1 = data1 | (static_cast<uint64_t>(x) << 48);
size_t data2_position = (effective_data1 == 0x0302000000000000) ? 4 : 2;
if (find_items_1d(effective_data1, data2_position) == 0) {
break;
}
}
@@ -182,17 +168,15 @@ std::string ItemNameIndex::describe_item(
ret_tokens.emplace_back("Wrapped");
}
// Add the item name. Technique disks are special because the level is part of
// the primary identifier, so we manually generate the name instead of looking
// it up.
// Add the item name
uint32_t primary_identifier = item.primary_identifier();
if ((primary_identifier & 0xFFFFFF00) == 0x00030200) {
if ((primary_identifier & 0xFFFF0000) == 0x03020000) {
string technique_name;
try {
technique_name = tech_id_to_name.at(item.data1[4]);
technique_name[0] = toupper(technique_name[0]);
} catch (const out_of_range&) {
technique_name = string_printf("!TECH:%02hhX", item.data1[4]);
technique_name = string_printf("!TD:%02hhX", item.data1[4]);
}
// Hide the level for Reverser and Ryuker, unless the level isn't 1
if ((item.data1[2] == 0) && ((item.data1[4] == 0x0E) || (item.data1[4] == 0x11))) {
@@ -204,9 +188,8 @@ std::string ItemNameIndex::describe_item(
try {
auto meta = this->primary_identifier_index.at(primary_identifier);
ret_tokens.emplace_back(meta->name);
} catch (const out_of_range&) {
ret_tokens.emplace_back(string_printf("!ID:%06" PRIX32, primary_identifier));
ret_tokens.emplace_back(string_printf("!ID:%08" PRIX32, primary_identifier));
}
}
@@ -492,10 +475,12 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
desc = desc.substr(1);
}
// Tech disks should have already been handled above, so we don't need to
// special-case 0302xxxx identifiers here.
uint32_t primary_identifier = name_it->second->primary_identifier;
ret.data1[0] = (primary_identifier >> 16) & 0xFF;
ret.data1[1] = (primary_identifier >> 8) & 0xFF;
ret.data1[2] = primary_identifier & 0xFF;
ret.data1[0] = (primary_identifier >> 24) & 0xFF;
ret.data1[1] = (primary_identifier >> 16) & 0xFF;
ret.data1[2] = (primary_identifier >> 8) & 0xFF;
if (ret.data1[0] == 0x00) {
// Weapons: add special, grind and percentages (or name, if S-rank)
+16 -16
View File
@@ -17,12 +17,12 @@ void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<PSOLFGE
auto player = c->character();
auto& item = player->inventory.items[item_index];
uint32_t item_identifier = item.data.primary_identifier();
uint32_t primary_identifier = item.data.primary_identifier();
if (item.data.is_common_consumable()) { // Monomate, etc.
// Nothing to do (it should be deleted)
} else if (item_identifier == 0x030200) { // Technique disk
} else if ((primary_identifier & 0xFFFF0000) == 0x03020000) { // Technique disk
auto item_parameter_table = s->item_parameter_table(c->version());
uint8_t max_level = item_parameter_table->get_max_tech_level(player->disp.visual.char_class, item.data.data1[4]);
if (item.data.data1[2] > max_level) {
@@ -30,7 +30,7 @@ void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<PSOLFGE
}
player->set_technique_level(item.data.data1[4], item.data.data1[2]);
} else if ((item_identifier & 0xFFFF00) == 0x030A00) { // Grinder
} else if ((primary_identifier & 0xFFFF00) == 0x030A0000) { // Grinder
if (item.data.data1[2] > 2) {
throw runtime_error("incorrect grinder value");
}
@@ -51,7 +51,7 @@ void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<PSOLFGE
}
weapon.data.data1[3] += (item.data.data1[2] + 1);
} else if ((item_identifier & 0xFFFF00) == 0x030B00) { // Material
} else if ((primary_identifier & 0xFFFF0000) == 0x030B0000) { // Material
auto p = c->character();
using Type = PSOBBCharacterFile::MaterialType;
@@ -104,7 +104,7 @@ void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<PSOLFGE
p->set_material_usage(type, p->get_material_usage(type) + 1);
}
} else if ((item_identifier & 0xFFFF00) == 0x030F00) { // AddSlot
} else if ((primary_identifier & 0xFFFF0000) == 0x030F0000) { // AddSlot
auto& armor = player->inventory.items[player->inventory.find_equipped_item(EquipSlot::ARMOR)];
if (armor.data.data1[5] >= 4) {
throw runtime_error("armor already at maximum slot count");
@@ -116,57 +116,57 @@ void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<PSOLFGE
item.data.unwrap(c->version());
should_delete_item = false;
} else if (item_identifier == 0x003300) {
} else if (primary_identifier == 0x00330000) {
// Unseal Sealed J-Sword => Tsumikiri J-Sword
item.data.data1[1] = 0x32;
should_delete_item = false;
} else if (item_identifier == 0x00AB00) {
} else if (primary_identifier == 0x00AB0000) {
// Unseal Lame d'Argent => Excalibur
item.data.data1[1] = 0xAC;
should_delete_item = false;
} else if (item_identifier == 0x01034D) {
} else if (primary_identifier == 0x01034D00) {
// Unseal Limiter => Adept
item.data.data1[2] = 0x4E;
should_delete_item = false;
} else if (item_identifier == 0x01034F) {
} else if (primary_identifier == 0x01034F00) {
// Unseal Swordsman Lore => Proof of Sword-Saint
item.data.data1[2] = 0x50;
should_delete_item = false;
} else if (item_identifier == 0x030C00) {
} else if (primary_identifier == 0x030C0000) {
// Cell of MAG 502
auto& mag = player->inventory.items[player->inventory.find_equipped_item(EquipSlot::MAG)];
mag.data.data1[1] = (player->disp.visual.section_id & 1) ? 0x1D : 0x21;
} else if (item_identifier == 0x030C01) {
} else if (primary_identifier == 0x030C0100) {
// Cell of MAG 213
auto& mag = player->inventory.items[player->inventory.find_equipped_item(EquipSlot::MAG)];
mag.data.data1[1] = (player->disp.visual.section_id & 1) ? 0x27 : 0x22;
} else if (item_identifier == 0x030C02) {
} else if (primary_identifier == 0x030C0200) {
// Parts of RoboChao
auto& mag = player->inventory.items[player->inventory.find_equipped_item(EquipSlot::MAG)];
mag.data.data1[1] = 0x28;
} else if (item_identifier == 0x030C03) {
} else if (primary_identifier == 0x030C0300) {
// Heart of Opa Opa
auto& mag = player->inventory.items[player->inventory.find_equipped_item(EquipSlot::MAG)];
mag.data.data1[1] = 0x29;
} else if (item_identifier == 0x030C04) {
} else if (primary_identifier == 0x030C0400) {
// Heart of Pian
auto& mag = player->inventory.items[player->inventory.find_equipped_item(EquipSlot::MAG)];
mag.data.data1[1] = 0x2A;
} else if (item_identifier == 0x030C05) {
} else if (primary_identifier == 0x030C0500) {
// Heart of Chao
auto& mag = player->inventory.items[player->inventory.find_equipped_item(EquipSlot::MAG)];
mag.data.data1[1] = 0x2B;
} else if ((item_identifier & 0xFFFF00) == 0x031500) {
} else if ((primary_identifier & 0xFFFF0000) == 0x03150000) {
// Christmas Present, etc. - use unwrap_table + probabilities therein
auto item_parameter_table = s->item_parameter_table(c->version());
auto table = item_parameter_table->get_event_items(item.data.data1[2]);
+3 -2
View File
@@ -1472,11 +1472,12 @@ Action a_name_all_items(
fputc('\n', stderr);
for (uint32_t primary_identifier : all_primary_identifiers) {
fprintf(stderr, "%06" PRIX32 ":", primary_identifier);
fprintf(stderr, "%08" PRIX32 ":", primary_identifier);
for (size_t v_s = 0; v_s < NUM_VERSIONS; v_s++) {
const auto& index = s.item_name_indexes.at(v_s);
if (index) {
ItemData item(static_cast<uint64_t>(primary_identifier) << 40);
Version version = static_cast<Version>(v_s);
ItemData item = ItemData::from_primary_identifier(version, primary_identifier);
string name = index->describe_item(item);
fprintf(stderr, " %30s", name.c_str());
}
+3 -3
View File
@@ -451,9 +451,9 @@ PlayerRecordsBB_Challenge::operator PlayerRecordsV3_Challenge<false>() const {
}
void PlayerBank::add_item(const ItemData& item, Version version) {
uint32_t pid = item.primary_identifier();
uint32_t primary_identifier = item.primary_identifier();
if (pid == MESETA_IDENTIFIER) {
if (primary_identifier == 0x04000000) {
this->meseta += item.data2d;
if (this->meseta > 999999) {
this->meseta = 999999;
@@ -465,7 +465,7 @@ void PlayerBank::add_item(const ItemData& item, Version version) {
if (combine_max > 1) {
size_t y;
for (y = 0; y < this->num_items; y++) {
if (this->items[y].data.primary_identifier() == item.primary_identifier()) {
if (this->items[y].data.primary_identifier() == primary_identifier) {
break;
}
}
+6 -6
View File
@@ -446,13 +446,13 @@ std::string RareItemSet::serialize_json(shared_ptr<const ItemNameIndex> name_ind
}
for (const auto& spec : this->get_enemy_specs(GameMode::NORMAL, episode, difficulty, section_id, rt_index)) {
uint32_t primary_identifier = (spec.item_code[0] << 16) | (spec.item_code[1] << 8) | spec.item_code[2];
if (primary_identifier == 0) {
uint32_t data1_0_1_2 = (spec.item_code[0] << 16) | (spec.item_code[1] << 8) | spec.item_code[2];
if (data1_0_1_2 == 0) {
continue;
}
auto frac = reduce_fraction<uint64_t>(spec.probability, 0x100000000);
auto spec_json = JSON::list({string_printf("%" PRIu64 "/%" PRIu64, frac.first, frac.second), primary_identifier});
auto spec_json = JSON::list({string_printf("%" PRIu64 "/%" PRIu64, frac.first, frac.second), data1_0_1_2});
if (name_index) {
ItemData data;
data.data1[0] = spec.item_code[0];
@@ -473,13 +473,13 @@ std::string RareItemSet::serialize_json(shared_ptr<const ItemNameIndex> name_ind
auto area_list = JSON::list();
for (const auto& spec : this->get_box_specs(GameMode::NORMAL, episode, difficulty, section_id, area)) {
uint32_t primary_identifier = (spec.item_code[0] << 16) | (spec.item_code[1] << 8) | spec.item_code[2];
if (primary_identifier == 0) {
uint32_t data1_0_1_2 = (spec.item_code[0] << 16) | (spec.item_code[1] << 8) | spec.item_code[2];
if (data1_0_1_2 == 0) {
continue;
}
auto frac = reduce_fraction<uint64_t>(spec.probability, 0x100000000);
area_list.emplace_back(JSON::list({string_printf("%" PRIu64 "/%" PRIu64, frac.first, frac.second), std::move(primary_identifier)}));
area_list.emplace_back(JSON::list({string_printf("%" PRIu64 "/%" PRIu64, frac.first, frac.second), data1_0_1_2}));
if (name_index) {
ItemData data;
data.data1[0] = spec.item_code[0];
+6 -6
View File
@@ -3118,7 +3118,7 @@ static void on_photon_drop_exchange_for_item_bb(shared_ptr<Client> c, uint8_t, u
try {
auto p = c->character();
size_t found_index = p->inventory.find_item_by_primary_identifier(0x031000);
size_t found_index = p->inventory.find_item_by_primary_identifier(0x03100000);
auto found_item = p->remove_item(p->inventory.items[found_index].data.id, 0, c->version());
send_destroy_item_to_lobby(c, found_item.id, found_item.stack_size(c->version()));
@@ -3150,7 +3150,7 @@ static void on_photon_drop_exchange_for_s_rank_special_bb(shared_ptr<Client> c,
static const array<uint8_t, 0x10> costs({60, 60, 20, 20, 30, 30, 30, 50, 40, 50, 40, 40, 50, 40, 40, 40});
uint8_t cost = costs.at(cmd.special_type);
size_t payment_item_index = p->inventory.find_item_by_primary_identifier(0x031000);
size_t payment_item_index = p->inventory.find_item_by_primary_identifier(0x03100000);
// Ensure weapon exists before removing PDs, so inventory state will be
// consistent in case of error
p->inventory.find_item(cmd.item_id);
@@ -3186,7 +3186,7 @@ static void on_secret_lottery_ticket_exchange_bb(shared_ptr<Client> c, uint8_t,
auto p = c->character();
ssize_t slt_index = -1;
try {
slt_index = p->inventory.find_item_by_primary_identifier(0x031003); // Secret Lottery Ticket
slt_index = p->inventory.find_item_by_primary_identifier(0x03100300); // Secret Lottery Ticket
} catch (const out_of_range&) {
}
@@ -3234,7 +3234,7 @@ static void on_photon_crystal_exchange_bb(shared_ptr<Client> c, uint8_t, uint8_t
if (l->is_game() && (l->base_version == Version::BB_V4) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) {
check_size_t<G_ExchangePhotonCrystals_BB_6xDF>(data, size);
auto p = c->character();
size_t index = p->inventory.find_item_by_primary_identifier(0x031002);
size_t index = p->inventory.find_item_by_primary_identifier(0x03100200);
auto item = p->remove_item(p->inventory.items[index].data.id, 1, c->version());
send_destroy_item_to_lobby(c, item.id, 1);
}
@@ -3284,7 +3284,7 @@ static void on_quest_F95F_result_bb(shared_ptr<Client> c, uint8_t, uint8_t, void
throw runtime_error("invalid result index");
}
size_t index = p->inventory.find_item_by_primary_identifier(0x031004); // Photon Ticket
size_t index = p->inventory.find_item_by_primary_identifier(0x03100400); // Photon Ticket
auto ticket_item = p->remove_item(p->inventory.items[index].data.id, result.first, c->version());
// TODO: Shouldn't we send a 6x29 here? Check if this causes desync in an
// actual game
@@ -3417,7 +3417,7 @@ static void on_upgrade_weapon_attribute_bb(shared_ptr<Client> c, uint8_t, uint8_
size_t item_index = p->inventory.find_item(cmd.item_id);
auto& item = p->inventory.items[item_index].data;
uint32_t payment_primary_identifier = cmd.payment_type ? 0x031001 : 0x031000;
uint32_t payment_primary_identifier = cmd.payment_type ? 0x03100100 : 0x03100000;
size_t payment_index = p->inventory.find_item_by_primary_identifier(payment_primary_identifier);
auto& payment_item = p->inventory.items[payment_index].data;
if (payment_item.stack_size(c->version()) < cmd.payment_count) {
+3 -3
View File
@@ -397,11 +397,11 @@ PSOBBCharacterFile::SymbolChatEntry PSOBBCharacterFile::DefaultSymbolChatEntry::
// TODO: Eliminate duplication between this function and the parallel function
// in PlayerBank
void PSOBBCharacterFile::add_item(const ItemData& item, Version version) {
uint32_t pid = item.primary_identifier();
uint32_t primary_identifier = item.primary_identifier();
// 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) {
if (primary_identifier == 0x04000000) {
this->add_meseta(item.data2d);
return;
}
@@ -413,7 +413,7 @@ void PSOBBCharacterFile::add_item(const ItemData& item, Version version) {
// player's inventory
size_t y;
for (y = 0; y < this->inventory.num_items; y++) {
if (this->inventory.items[y].data.primary_identifier() == item.primary_identifier()) {
if (this->inventory.items[y].data.primary_identifier() == primary_identifier) {
break;
}
}