use mag evolution table for fixed-type cell evolution; fixes #608

This commit is contained in:
Martin Michelsen
2025-02-13 21:59:00 -08:00
parent 5ed2503491
commit 9ed01ede2d
5 changed files with 77 additions and 53 deletions
+6 -3
View File
@@ -1286,14 +1286,17 @@ size_t ItemParameterTable::price_for_item(const ItemData& item) const {
throw logic_error("this should be impossible");
}
MagEvolutionTable::MagEvolutionTable(shared_ptr<const string> data)
MagEvolutionTable::MagEvolutionTable(shared_ptr<const string> data, size_t num_mags)
: data(data),
num_mags(num_mags),
r(*data) {
size_t offset_table_offset = this->r.pget_u32l(this->data->size() - 0x10);
this->offsets = &r.pget<TableOffsets>(offset_table_offset);
}
uint8_t MagEvolutionTable::get_evolution_number(uint8_t data1_1) const {
const auto& table = this->r.pget<EvolutionNumberTable>(this->offsets->evolution_number);
return table.values[data1_1];
if (data1_1 >= this->num_mags) {
throw runtime_error("invalid mag number");
}
return this->r.pget_u8(this->offsets->evolution_number + data1_1);
}
+12 -12
View File
@@ -628,27 +628,27 @@ protected:
class MagEvolutionTable {
public:
// TODO: V1 format is different! Offsets are 0438 0440 0498 0520 054C
struct TableOffsets {
// num_mags = 0x53 in BB, 0x43 in V3
/* 00 / 0400 */ le_uint32_t unknown_a1; // -> [offset -> (0xC-byte struct)[num_mags], offset -> (same as first offset)]
/* 04 / 0408 */ le_uint32_t unknown_a2; // -> (2-byte struct, or single word)[num_mags]
/* 08 / 04AE */ le_uint32_t unknown_a3; // -> (0xA8 bytes; possibly (8-byte struct)[0x15])
/* 0C / 0556 */ le_uint32_t unknown_a4; // -> (uint8_t)[num_mags]
/* 10 / 05AC */ le_uint32_t unknown_a5; // -> (float)[0x48]
/* 14 / 06CC */ le_uint32_t evolution_number; // -> (uint8_t)[num_mags]
// num_mags = 0x3A in v2 and GC NTE, 0x43 in V3, 0x53 in BB
// TODO: GC NTE uses the v2 format but is big-endian
/* -- / V2 / V3 / BB */
/* 00 / 05BC / 0340 / 0400 */ le_uint32_t unknown_a1; // -> [offset -> (uint8_t[0xC])[num_mags], offset -> (same as first offset on v3; different on v2 (TODO))]
/* 04 / 0594 / 0348 / 0408 */ le_uint32_t unknown_a2; // -> (uint8_t[2])[num_mags]
/* 08 / 0608 / 03CE / 04AE */ le_uint32_t unknown_a3; // -> (8-byte struct)[0x15]
/* 0C / 06B0 / 0476 / 0556 */ le_uint32_t unknown_a4; // -> (uint8_t)[num_mags]
/* 10 / 06EC / 04BC / 05AC */ le_uint32_t unknown_a5; // -> (float)[0x48] on v3+, (float)[0x24] on v2
/* 14 / 077C / 05DC / 06CC */ le_uint32_t evolution_number; // -> (uint8_t)[num_mags]
} __packed_ws__(TableOffsets, 0x18);
struct EvolutionNumberTable {
parray<uint8_t, 0x53> values;
} __packed_ws__(EvolutionNumberTable, 0x53);
MagEvolutionTable(std::shared_ptr<const std::string> data);
MagEvolutionTable(std::shared_ptr<const std::string> data, size_t num_mags);
~MagEvolutionTable() = default;
uint8_t get_evolution_number(uint8_t data1_1) const;
protected:
std::shared_ptr<const std::string> data;
size_t num_mags;
phosg::StringReader r;
const TableOffsets* offsets;
};
+30 -32
View File
@@ -135,35 +135,32 @@ void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<PSOLFGE
item.flags &= (~8); // Unequip it
should_delete_item = false;
} else if (primary_identifier == 0x030C0000) {
// Cell of MAG 502
} else if ((primary_identifier & 0xFFFF0000) == 0x030C0000) { // Non-combo mag cells
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 (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 (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 (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 (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 (primary_identifier == 0x030C0500) {
// Heart of Chao
auto& mag = player->inventory.items[player->inventory.find_equipped_item(EquipSlot::MAG)];
mag.data.data1[1] = 0x2B;
if (s->mag_evolution_table(c->version())->get_evolution_number(mag.data.data1[1]) < 4) {
switch (item.data.data1[2]) {
case 0x00: // Cell of MAG 502
mag.data.data1[1] = (player->disp.visual.section_id & 1) ? 0x1D : 0x21;
break;
case 0x01: // Cell of MAG 213
mag.data.data1[1] = (player->disp.visual.section_id & 1) ? 0x27 : 0x22;
break;
case 0x02: // Parts of RoboChao
mag.data.data1[1] = 0x28;
break;
case 0x03: // Heart of Opa Opa
mag.data.data1[1] = 0x29;
break;
case 0x04: // Heart of Pian
mag.data.data1[1] = 0x2A;
break;
case 0x05: // Heart of Chao
mag.data.data1[1] = 0x2B;
break;
default:
throw runtime_error("invalid mag cell used");
}
}
} else if ((primary_identifier & 0xFFFF0000) == 0x03150000) {
// Christmas Present, etc. - use unwrap_table + probabilities therein
@@ -176,6 +173,10 @@ void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<PSOLFGE
if (sum == 0) {
throw runtime_error("no unwrap results available for event");
}
// TODO: It seems that on non-BB, clients don't synchronize this at all, so
// they could end up thinking the unwrapped item is something completely
// different. (They don't even use a fixed random seed, like for rares;
// they just call rand().) How does this actually work on console PSO?
size_t det = random_from_optional_crypt(opt_rand_crypt) % sum;
for (size_t z = 0; z < table.second; z++) {
const auto& entry = table.first[z];
@@ -189,9 +190,6 @@ void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<PSOLFGE
item.data.data1.clear_after(3);
should_delete_item = false;
// TODO: It seems that on non-BB, clients don't synchronize this at all
// so they could end up thinking the unwrapped item is something
// completely different. How does this actually work on console PSO?
auto l = c->require_lobby();
for (const auto& lc : l->clients) {
if (lc && (lc->version() == Version::BB_V4)) {
@@ -506,7 +504,7 @@ void player_feed_mag(std::shared_ptr<Client> c, size_t mag_item_index, size_t fe
player->inventory.items[mag_item_index].data,
player->inventory.items[fed_item_index].data,
s->item_parameter_table(c->version()),
s->mag_evolution_table,
s->mag_evolution_table(c->version()),
player->disp.visual.char_class,
player->disp.visual.section_id,
!is_v1_or_v2(c->version()));
+25 -5
View File
@@ -451,6 +451,16 @@ shared_ptr<const ItemParameterTable> ServerState::item_parameter_table_for_encod
return this->item_parameter_table(is_v1(version) ? Version::PC_V2 : version);
}
shared_ptr<const MagEvolutionTable> ServerState::mag_evolution_table(Version version) const {
if (is_v1_or_v2(version)) {
return this->mag_evolution_table_v1_v2;
} else if (!is_v4(version)) {
return this->mag_evolution_table_v3;
} else {
return this->mag_evolution_table_v4;
}
}
shared_ptr<const ItemData::StackLimits> ServerState::item_stack_limits(Version version) const {
auto ret = this->item_stack_limits_tables.at(static_cast<size_t>(version));
if (ret == nullptr) {
@@ -2060,15 +2070,25 @@ void ServerState::load_item_definitions(bool from_non_event_thread) {
}
// TODO: We should probably load the tables for other versions too.
config_log.info("Loading mag evolution table");
auto mag_data = make_shared<string>(prs_decompress(phosg::load_file("system/item-tables/ItemMagEdit-bb-v4.prs")));
auto new_mag_evolution_table = make_shared<MagEvolutionTable>(mag_data);
config_log.info("Loading v1/v2 mag evolution table");
auto mag_data_v1_v2 = make_shared<string>(prs_decompress(phosg::load_file("system/item-tables/ItemMagEdit-dc-v2.prs")));
auto new_table_v1_v2 = make_shared<MagEvolutionTable>(mag_data_v1_v2, 0x3A);
config_log.info("Loading v3 mag evolution table");
auto mag_data_v3 = make_shared<string>(prs_decompress(phosg::load_file("system/item-tables/ItemMagEdit-xb-v3.prs")));
auto new_table_v3 = make_shared<MagEvolutionTable>(mag_data_v3, 0x43);
config_log.info("Loading v4 mag evolution table");
auto mag_data_v4 = make_shared<string>(prs_decompress(phosg::load_file("system/item-tables/ItemMagEdit-bb-v4.prs")));
auto new_table_v4 = make_shared<MagEvolutionTable>(mag_data_v4, 0x53);
auto set = [s = this->shared_from_this(),
new_item_parameter_tables = std::move(new_item_parameter_tables),
new_mag_evolution_table = std::move(new_mag_evolution_table)]() {
new_table_v1_v2 = std::move(new_table_v1_v2),
new_table_v3 = std::move(new_table_v3),
new_table_v4 = std::move(new_table_v4)]() {
s->item_parameter_tables = std::move(new_item_parameter_tables);
s->mag_evolution_table = std::move(new_mag_evolution_table);
s->mag_evolution_table_v1_v2 = std::move(new_table_v1_v2);
s->mag_evolution_table_v3 = std::move(new_table_v3);
s->mag_evolution_table_v4 = std::move(new_table_v4);
};
this->forward_or_call(from_non_event_thread, std::move(set));
}
+4 -1
View File
@@ -201,7 +201,9 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
std::shared_ptr<const TekkerAdjustmentSet> tekker_adjustment_set;
std::array<std::shared_ptr<const ItemParameterTable>, NUM_VERSIONS> item_parameter_tables;
std::array<std::shared_ptr<const ItemData::StackLimits>, NUM_VERSIONS> item_stack_limits_tables;
std::shared_ptr<const MagEvolutionTable> mag_evolution_table;
std::shared_ptr<const MagEvolutionTable> mag_evolution_table_v1_v2;
std::shared_ptr<const MagEvolutionTable> mag_evolution_table_v3;
std::shared_ptr<const MagEvolutionTable> mag_evolution_table_v4;
std::shared_ptr<const TextIndex> text_index;
std::array<std::shared_ptr<const ItemNameIndex>, NUM_VERSIONS> item_name_indexes;
std::shared_ptr<const WordSelectTable> word_select_table;
@@ -344,6 +346,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
std::shared_ptr<const LevelTable> level_table(Version version) const;
std::shared_ptr<const ItemParameterTable> item_parameter_table(Version version) const;
std::shared_ptr<const ItemParameterTable> item_parameter_table_for_encode(Version version) const;
std::shared_ptr<const MagEvolutionTable> mag_evolution_table(Version version) const;
std::shared_ptr<const ItemData::StackLimits> item_stack_limits(Version version) const;
std::shared_ptr<const ItemNameIndex> item_name_index_opt(Version version) const; // Returns null if missing
std::shared_ptr<const ItemNameIndex> item_name_index(Version version) const; // Throws if missing