Merge upstream changes from 2026-05-31

This commit is contained in:
2026-05-31 11:23:38 -04:00
148 changed files with 8048 additions and 8319 deletions
+65 -67
View File
@@ -6,8 +6,6 @@
#include "EnemyType.hh"
#include "Loggers.hh"
using namespace std;
// The favored weapon type table is hardcoded in the game client. The table is:
// Viridia shots
// Greennill rifles
@@ -19,23 +17,23 @@ using namespace std;
// Oran daggers
// Yellowboze (none)
// Whitill slicers
static const array<uint8_t, 10> favored_weapon_by_section_id = {
static const std::array<uint8_t, 10> favored_weapon_by_section_id = {
0x09, 0x07, 0x02, 0x04, 0x08, 0x0A, 0xFF, 0x03, 0xFF, 0x05};
ItemCreator::ItemCreator(
shared_ptr<const CommonItemSet> common_item_set,
shared_ptr<const RareItemSet> rare_item_set,
shared_ptr<const ArmorRandomSet> armor_random_set,
shared_ptr<const ToolRandomSet> tool_random_set,
shared_ptr<const WeaponRandomSet> weapon_random_set,
shared_ptr<const TekkerAdjustmentSet> tekker_adjustment_set,
shared_ptr<const ItemParameterTable> item_parameter_table,
std::shared_ptr<const CommonItemSet> common_item_set,
std::shared_ptr<const RareItemSet> rare_item_set,
std::shared_ptr<const ArmorRandomSet> armor_random_set,
std::shared_ptr<const ToolRandomSet> tool_random_set,
std::shared_ptr<const WeaponRandomSet> weapon_random_set,
std::shared_ptr<const TekkerAdjustmentSet> tekker_adjustment_set,
std::shared_ptr<const ItemParameterTable> item_parameter_table,
std::shared_ptr<const ItemData::StackLimits> stack_limits,
GameMode mode,
Difficulty difficulty,
uint8_t section_id,
std::shared_ptr<RandomGenerator> rand_crypt,
shared_ptr<const BattleRules> restrictions)
std::shared_ptr<const BattleRules> restrictions)
: log(std::format("[ItemCreator:{}/{}/{}/{}] ", phosg::name_for_enum(stack_limits->version), abbreviation_for_mode(mode), abbreviation_for_difficulty(difficulty), section_id), lobby_log.min_level),
logic_version(stack_limits->version),
is_legacy_replay(false),
@@ -175,7 +173,7 @@ ItemCreator::DropResult ItemCreator::on_box_item_drop(uint8_t area, bool force_r
case 6: // Nothing
break;
default:
throw logic_error("this should be impossible");
throw std::logic_error("this should be impossible");
}
if (item_class < 6) {
this->generate_common_item_variances(res.item, area);
@@ -183,7 +181,7 @@ ItemCreator::DropResult ItemCreator::on_box_item_drop(uint8_t area, bool force_r
}
return res;
} catch (const exception& e) {
} catch (const std::exception& e) {
this->log.error_f("Exception in item creation: {}", e.what());
return DropResult();
}
@@ -233,13 +231,13 @@ ItemCreator::DropResult ItemCreator::on_monster_item_drop(EnemyType enemy_type,
case 2:
try {
item_class = pt->enemy_type_item_classes.at(enemy_type);
} catch (const out_of_range&) {
} catch (const std::out_of_range&) {
this->log.info_f("Item class is not set for this enemy type");
item_class = 0xFF;
}
break;
default:
throw logic_error("invalid item class determinant");
throw std::logic_error("invalid item class determinant");
}
this->log.info_f(
@@ -265,7 +263,7 @@ ItemCreator::DropResult ItemCreator::on_monster_item_drop(EnemyType enemy_type,
res.item.data1[0] = 0x04;
try {
res.item.data2d = this->choose_meseta_amount(pt->enemy_type_meseta_ranges.at(enemy_type)) & 0xFFFF;
} catch (const out_of_range&) {
} catch (const std::out_of_range&) {
this->log.info_f("Meseta range is not set for this enemy type");
return DropResult();
}
@@ -281,7 +279,7 @@ ItemCreator::DropResult ItemCreator::on_monster_item_drop(EnemyType enemy_type,
return res;
} catch (const exception& e) {
} catch (const std::exception& e) {
this->log.error_f("Exception in item creation: {}", e.what());
return DropResult();
}
@@ -408,7 +406,7 @@ ItemData ItemCreator::create_rare_item(const ItemData& drop_item, uint8_t area)
case 4:
break;
default:
throw logic_error("invalid item class");
throw std::logic_error("invalid item class");
}
this->set_item_kill_count_if_unsealable(item);
}
@@ -424,7 +422,7 @@ void ItemCreator::generate_rare_weapon_bonuses(ItemData& item, Episode episode,
auto pt = this->pt(episode);
if (!pt->has_rare_bonus_value_prob_table) {
throw logic_error("generate_rare_weapon_bonuses called for common item table without rare bonus value probability table");
throw std::logic_error("generate_rare_weapon_bonuses called for common item table without rare bonus value probability table");
}
for (size_t z = 0; z < 6; z += 2) {
@@ -557,7 +555,7 @@ void ItemCreator::clear_item_if_restricted(ItemData& item) const {
item.clear();
break;
default:
throw logic_error("invalid weapon and armor mode");
throw std::logic_error("invalid weapon and armor mode");
}
break;
case 2:
@@ -588,7 +586,7 @@ void ItemCreator::clear_item_if_restricted(ItemData& item) const {
}
break;
default:
throw logic_error("invalid tech disk mode");
throw std::logic_error("invalid tech disk mode");
}
} else if ((item.data1[1] == 9) && this->restrictions->forbid_scape_dolls) {
this->log.info_f("Restricted: scape dolls not allowed");
@@ -602,7 +600,7 @@ void ItemCreator::clear_item_if_restricted(ItemData& item) const {
}
break;
default:
throw logic_error("invalid item");
throw std::logic_error("invalid item");
}
}
}
@@ -642,7 +640,7 @@ void ItemCreator::generate_common_item_variances(ItemData& item, uint8_t area) {
// Note: The original code does the following here:
// item.clear();
// item.data1[0] = 0x05;
throw logic_error("invalid item class");
throw std::logic_error("invalid item class");
}
this->clear_item_if_restricted(item);
@@ -706,7 +704,7 @@ void ItemCreator::generate_common_tool_variances(ItemData& item, uint8_t area) {
item.data1[0] = 0x03;
item.data1[1] = data.first;
item.data1[2] = data.second;
} catch (const out_of_range&) {
} catch (const std::out_of_range&) {
this->log.info_f("Tool class is missing; skipping item generation");
return;
}
@@ -795,7 +793,7 @@ void ItemCreator::generate_common_weapon_variances(ItemData& item, uint8_t area)
void ItemCreator::generate_common_weapon_grind(ItemData& item, uint8_t area, uint8_t offset_within_subtype_range) {
if (item.data1[0] == 0) {
uint8_t offset = clamp<uint8_t>(offset_within_subtype_range, 0, 3);
uint8_t offset = std::clamp<uint8_t>(offset_within_subtype_range, 0, 3);
item.data1[3] = this->get_rand_from_weighted_tables_2d_vertical(this->pt(area)->grind_prob_table, offset);
this->log.info_f("Generated grind {:02X} from offset within subtype range {:02X}", item.data1[3], offset_within_subtype_range);
}
@@ -860,7 +858,7 @@ void ItemCreator::generate_unit_stars_tables() {
case Version::BB_PATCH:
case Version::GC_EP3_NTE:
case Version::GC_EP3:
throw logic_error("ItemCreator cannot be created for Episode 3 games");
throw std::logic_error("ItemCreator cannot be created for Episode 3 games");
case Version::DC_NTE:
star_base_index = 0x124;
num_units = 0x43;
@@ -890,7 +888,7 @@ void ItemCreator::generate_unit_stars_tables() {
num_units = 0x64;
break;
default:
throw logic_error("invalid game version");
throw std::logic_error("invalid game version");
}
for (auto& vec : this->unit_results_by_star_count) {
@@ -944,7 +942,7 @@ IntT ItemCreator::get_rand_from_weighted_tables(const IntT* tables, size_t offse
rand_max += tables[x * stride + offset];
}
if (rand_max == 0) {
throw runtime_error("weighted table is empty");
throw std::runtime_error("weighted table is empty");
}
uint32_t x = this->rand_int(rand_max);
@@ -955,7 +953,7 @@ IntT ItemCreator::get_rand_from_weighted_tables(const IntT* tables, size_t offse
}
x -= table_value;
}
throw logic_error("selector was not less than rand_max");
throw std::logic_error("selector was not less than rand_max");
}
template <typename IntT, size_t X>
@@ -968,8 +966,8 @@ IntT ItemCreator::get_rand_from_weighted_tables_2d_vertical(const parray<parray<
return ItemCreator::get_rand_from_weighted_tables<IntT>(tables[0].data(), offset, Y, X);
}
vector<ItemData> ItemCreator::generate_armor_shop_contents(Episode episode, size_t player_level) {
vector<ItemData> shop;
std::vector<ItemData> ItemCreator::generate_armor_shop_contents(Episode episode, size_t player_level) {
std::vector<ItemData> shop;
this->generate_armor_shop_armors(shop, episode, player_level);
this->generate_armor_shop_shields(shop, player_level);
this->generate_armor_shop_units(shop, player_level);
@@ -992,7 +990,7 @@ size_t ItemCreator::get_table_index_for_armor_shop(
}
bool ItemCreator::shop_does_not_contain_duplicate_armor(
const vector<ItemData>& shop, const ItemData& item) {
const std::vector<ItemData>& shop, const ItemData& item) {
for (const auto& shop_item : shop) {
if ((shop_item.data1[0] == item.data1[0]) &&
(shop_item.data1[1] == item.data1[1]) &&
@@ -1005,7 +1003,7 @@ bool ItemCreator::shop_does_not_contain_duplicate_armor(
}
bool ItemCreator::shop_does_not_contain_duplicate_tech_disk(
const vector<ItemData>& shop, const ItemData& item) {
const std::vector<ItemData>& shop, const ItemData& item) {
for (const auto& shop_item : shop) {
if ((shop_item.data1[0] == item.data1[0]) &&
(shop_item.data1[1] == item.data1[1]) &&
@@ -1018,7 +1016,7 @@ bool ItemCreator::shop_does_not_contain_duplicate_tech_disk(
}
bool ItemCreator::shop_does_not_contain_duplicate_or_too_many_similar_weapons(
const vector<ItemData>& shop, const ItemData& item) {
const std::vector<ItemData>& shop, const ItemData& item) {
size_t similar_items = 0;
for (const auto& shop_item : shop) {
// Disallow exact matches
@@ -1037,7 +1035,7 @@ bool ItemCreator::shop_does_not_contain_duplicate_or_too_many_similar_weapons(
}
bool ItemCreator::shop_does_not_contain_duplicate_item_by_data1_0_1_2(
const vector<ItemData>& shop, const ItemData& item) {
const std::vector<ItemData>& shop, const ItemData& item) {
for (const auto& shop_item : shop) {
if ((shop_item.data1[0] == item.data1[0]) &&
(shop_item.data1[1] == item.data1[1]) &&
@@ -1048,7 +1046,7 @@ bool ItemCreator::shop_does_not_contain_duplicate_item_by_data1_0_1_2(
return true;
}
void ItemCreator::generate_armor_shop_armors(vector<ItemData>& shop, Episode episode, size_t player_level) {
void ItemCreator::generate_armor_shop_armors(std::vector<ItemData>& shop, Episode episode, size_t player_level) {
size_t num_items;
if (player_level < 11) {
num_items = 4;
@@ -1092,7 +1090,7 @@ void ItemCreator::generate_armor_shop_armors(vector<ItemData>& shop, Episode epi
}
}
void ItemCreator::generate_armor_shop_shields(vector<ItemData>& shop, size_t player_level) {
void ItemCreator::generate_armor_shop_shields(std::vector<ItemData>& shop, size_t player_level) {
size_t num_items;
if (player_level < 11) {
num_items = 4;
@@ -1135,7 +1133,7 @@ void ItemCreator::generate_armor_shop_shields(vector<ItemData>& shop, size_t pla
}
}
void ItemCreator::generate_armor_shop_units(vector<ItemData>& shop, size_t player_level) {
void ItemCreator::generate_armor_shop_units(std::vector<ItemData>& shop, size_t player_level) {
size_t num_items;
if (player_level < 11) {
return; // num_items = 0
@@ -1169,8 +1167,8 @@ void ItemCreator::generate_armor_shop_units(vector<ItemData>& shop, size_t playe
}
}
vector<ItemData> ItemCreator::generate_tool_shop_contents(size_t player_level) {
vector<ItemData> shop;
std::vector<ItemData> ItemCreator::generate_tool_shop_contents(size_t player_level) {
std::vector<ItemData> shop;
this->generate_common_tool_shop_recovery_items(shop, player_level);
this->generate_rare_tool_shop_recovery_items(shop, player_level);
this->generate_tool_shop_tech_disks(shop, player_level);
@@ -1192,11 +1190,11 @@ size_t ItemCreator::get_table_index_for_tool_shop(size_t player_level) {
}
}
static const vector<pair<uint8_t, uint8_t>> tool_item_defs{
static const std::vector<std::pair<uint8_t, uint8_t>> tool_item_defs{
{0x00, 0x00}, {0x00, 0x01}, {0x00, 0x02}, {0x01, 0x00}, {0x01, 0x01}, {0x01, 0x02}, {0x06, 0x00}, {0x06, 0x01},
{0x03, 0x00}, {0x04, 0x00}, {0x05, 0x00}, {0x07, 0x00}, {0x08, 0x00}, {0x09, 0x00}, {0x0A, 0x00}, {0xFF, 0xFF}};
void ItemCreator::generate_common_tool_shop_recovery_items(vector<ItemData>& shop, size_t player_level) {
void ItemCreator::generate_common_tool_shop_recovery_items(std::vector<ItemData>& shop, size_t player_level) {
size_t table_index;
if (player_level < 11) {
table_index = 0;
@@ -1226,7 +1224,7 @@ void ItemCreator::generate_common_tool_shop_recovery_items(vector<ItemData>& sho
}
}
void ItemCreator::generate_rare_tool_shop_recovery_items(vector<ItemData>& shop, size_t player_level) {
void ItemCreator::generate_rare_tool_shop_recovery_items(std::vector<ItemData>& shop, size_t player_level) {
if (player_level < 11) {
return;
}
@@ -1264,7 +1262,7 @@ void ItemCreator::generate_rare_tool_shop_recovery_items(vector<ItemData>& shop,
}
}
void ItemCreator::generate_tool_shop_tech_disks(vector<ItemData>& shop, size_t player_level) {
void ItemCreator::generate_tool_shop_tech_disks(std::vector<ItemData>& shop, size_t player_level) {
size_t num_items;
if (player_level < 11) {
num_items = 4;
@@ -1286,7 +1284,7 @@ void ItemCreator::generate_tool_shop_tech_disks(vector<ItemData>& shop, size_t p
}
pt.shuffle(this->rand_crypt);
static const array<uint8_t, 0x13> tech_num_map = {
static const std::array<uint8_t, 0x13> tech_num_map = {
0x00, 0x03, 0x06, 0x0F, 0x10, 0x0D, 0x0A, 0x0B, 0x0C, 0x01, 0x04, 0x07,
0x0E, 0x11, 0x02, 0x05, 0x08, 0x09, 0x12};
@@ -1309,7 +1307,7 @@ void ItemCreator::choose_tech_disk_level_for_tool_shop(ItemData& item, size_t pl
size_t table_index = this->get_table_index_for_tool_shop(player_level);
auto table = this->tool_random_set->get_tech_disk_level_table(table_index);
if (tech_num_index >= table.second) {
throw runtime_error("technique number out of range");
throw std::runtime_error("technique number out of range");
}
const auto& e = table.first[tech_num_index];
@@ -1318,23 +1316,23 @@ void ItemCreator::choose_tech_disk_level_for_tool_shop(ItemData& item, size_t pl
item.data1[2] = 0;
break;
case ToolRandomSet::TechDiskLevelEntry::Mode::PLAYER_LEVEL_DIVISOR:
item.data1[2] = clamp<ssize_t>(
(min<size_t>(player_level, 99) / e.player_level_divisor_or_min_level) - 1, 0, 14);
item.data1[2] = std::clamp<ssize_t>(
(std::min<size_t>(player_level, 99) / e.player_level_divisor_or_min_level) - 1, 0, 14);
break;
case ToolRandomSet::TechDiskLevelEntry::Mode::RANDOM_IN_RANGE: {
// Note: This logic does not give a uniform distribution - if the minimumlevel is not zero (level 1), then the
// minimum level is more likely than all the other levels. This behavior matches the client's logic, though it's
// unclear if this nonuniformity was intentional.
int16_t min_level = max<int16_t>(e.player_level_divisor_or_min_level - 1, 0);
item.data1[2] = clamp<int16_t>(this->rand_int(e.max_level), min_level, 14);
int16_t min_level = std::max<int16_t>(e.player_level_divisor_or_min_level - 1, 0);
item.data1[2] = std::clamp<int16_t>(this->rand_int(e.max_level), min_level, 14);
break;
}
default:
throw logic_error("invalid tech disk level mode");
throw std::logic_error("invalid tech disk level mode");
}
}
vector<ItemData> ItemCreator::generate_weapon_shop_contents(size_t player_level) {
std::vector<ItemData> ItemCreator::generate_weapon_shop_contents(size_t player_level) {
size_t num_items;
if (player_level < 11) {
num_items = 10;
@@ -1385,13 +1383,13 @@ vector<ItemData> ItemCreator::generate_weapon_shop_contents(size_t player_level)
}
pt.shuffle(this->rand_crypt);
vector<ItemData> shop;
std::vector<ItemData> shop;
while (shop.size() < num_items) {
ItemData item;
uint8_t which = pt.pop();
if (which == 0x39) {
static const vector<pair<uint8_t, uint8_t>> defs{
static const std::vector<std::pair<uint8_t, uint8_t>> defs{
{0x28, 0x00}, {0x2A, 0x00}, {0x2B, 0x00}, {0x35, 0x00}, {0x52, 0x00}, {0x48, 0x00}, {0x64, 0x00},
{0x59, 0x00}, {0x8A, 0x00}, {0x99, 0x00}};
const auto& def = defs.at(this->section_id);
@@ -1400,7 +1398,7 @@ vector<ItemData> ItemCreator::generate_weapon_shop_contents(size_t player_level)
item.data1[2] = def.second;
} else if (which == 0x3A) {
static const vector<pair<uint8_t, uint8_t>> defs{
static const std::vector<std::pair<uint8_t, uint8_t>> defs{
{0x99, 0x00}, {0x64, 0x00}, {0x8A, 0x00}, {0x28, 0x00}, {0x59, 0x00}, {0x2B, 0x00}, {0x52, 0x00},
{0x2A, 0x00}, {0x48, 0x00}, {0x35, 0x00}};
const auto& def = defs.at(this->section_id);
@@ -1409,7 +1407,7 @@ vector<ItemData> ItemCreator::generate_weapon_shop_contents(size_t player_level)
item.data1[2] = def.second;
} else {
static const vector<pair<uint8_t, uint8_t>> defs({
static const std::vector<std::pair<uint8_t, uint8_t>> defs({
/* 00 */ {0x01, 0x00},
/* 01 */ {0x01, 0x01},
/* 02 */ {0x01, 0x02},
@@ -1528,7 +1526,7 @@ void ItemCreator::generate_weapon_shop_item_grind(ItemData& item, size_t player_
: this->weapon_random_set->get_standard_grind_range(table_index);
const auto& weapon_def = this->item_parameter_table->get_weapon(item.data1[1], item.data1[2]);
item.data1[3] = clamp<uint8_t>(this->rand_int(range->max + 1), range->min, weapon_def.max_grind);
item.data1[3] = std::clamp<uint8_t>(this->rand_int(range->max + 1), range->min, weapon_def.max_grind);
}
void ItemCreator::generate_weapon_shop_item_special(ItemData& item, size_t player_level) {
@@ -1574,11 +1572,11 @@ void ItemCreator::generate_weapon_shop_item_special(ItemData& item, size_t playe
item.data1[4] = this->choose_weapon_special(1);
break;
default:
throw runtime_error("invalid special mode");
throw std::runtime_error("invalid special mode");
}
}
static const array<int8_t, 20> bonus_values = {
static const std::array<int8_t, 20> bonus_values = {
-50, -45, -40, -35, -30, -25, -20, -15, -10, -5, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50};
void ItemCreator::generate_weapon_shop_item_bonus1(ItemData& item, size_t player_level) {
@@ -1619,7 +1617,7 @@ void ItemCreator::generate_weapon_shop_item_bonus1(ItemData& item, size_t player
item.data1[7] = 0;
} else {
const auto* range = this->weapon_random_set->get_bonus_range(0, table_index);
item.data1[7] = bonus_values.at(max<size_t>(this->rand_int(range->max + 1), range->min));
item.data1[7] = bonus_values.at(std::max<size_t>(this->rand_int(range->max + 1), range->min));
}
}
@@ -1663,7 +1661,7 @@ void ItemCreator::generate_weapon_shop_item_bonus2(ItemData& item, size_t player
item.data1[9] = 0;
} else {
const auto* range = this->weapon_random_set->get_bonus_range(1, table_index);
item.data1[9] = bonus_values.at(max<size_t>(this->rand_int(range->max + 1), range->min));
item.data1[9] = bonus_values.at(std::max<size_t>(this->rand_int(range->max + 1), range->min));
}
}
@@ -1715,7 +1713,7 @@ ItemData ItemCreator::base_item_for_specialized_box(uint32_t param4, uint32_t pa
item.data2d = ((param5 >> 0x10) & 0xFFFF) * 10;
break;
default:
throw runtime_error("invalid item class");
throw std::runtime_error("invalid item class");
}
return item;
@@ -1723,10 +1721,10 @@ ItemData ItemCreator::base_item_for_specialized_box(uint32_t param4, uint32_t pa
ssize_t ItemCreator::apply_tekker_deltas(ItemData& item, uint8_t section_id) {
if (item.data1[0] != 0) {
throw runtime_error("tekker deltas can only be applied to weapons");
throw std::runtime_error("tekker deltas can only be applied to weapons");
}
static const array<int8_t, 11> delta_table = {-10, -5, -3, -2, -1, 0, 1, 2, 3, 5, 10};
static const std::array<int8_t, 11> delta_table = {-10, -5, -3, -2, -1, 0, 1, 2, 3, 5, 10};
bool favored = item.data1[1] == favored_weapon_by_section_id[section_id];
ssize_t luck = 0;
@@ -1759,7 +1757,7 @@ ssize_t ItemCreator::apply_tekker_deltas(ItemData& item, uint8_t section_id) {
this->log.info_f("(Special) Delta canceled because it would change special category");
}
}
} catch (const out_of_range&) {
} catch (const std::out_of_range&) {
// Invalid special number passed to get_special; just ignore it
}
luck += this->tekker_adjustment_set->get_luck_for_special_upgrade(delta_index);
@@ -1774,7 +1772,7 @@ ssize_t ItemCreator::apply_tekker_deltas(ItemData& item, uint8_t section_id) {
int8_t delta = delta_table.at(delta_index);
this->log.info_f("(Grind) Delta index {}, delta {}", delta_index, delta);
int16_t new_grind = static_cast<int16_t>(item.data1[3]) + static_cast<int16_t>(delta);
item.data1[3] = clamp<int16_t>(new_grind, 0, weapon_def.max_grind);
item.data1[3] = std::clamp<int16_t>(new_grind, 0, weapon_def.max_grind);
luck += this->tekker_adjustment_set->get_luck_for_grind_delta(delta_index);
this->log.info_f("(Grind) Luck is now {}", luck);
} else {
@@ -1793,7 +1791,7 @@ ssize_t ItemCreator::apply_tekker_deltas(ItemData& item, uint8_t section_id) {
// to check here if each bonus is actually present.
for (size_t z = 6; z <= 10; z += 2) {
if (item.data1[z] >= 1 && item.data1[z] <= 5) {
item.data1[z + 1] = min<int8_t>(item.data1[z + 1] + delta, 100);
item.data1[z + 1] = std::min<int8_t>(item.data1[z + 1] + delta, 100);
}
}
luck += this->tekker_adjustment_set->get_luck_for_bonus_delta(delta_index);