add $allrare debug command; closes #739

This commit is contained in:
Martin Michelsen
2026-03-22 21:37:43 -07:00
parent 983753f840
commit 5724fb9a12
8 changed files with 48 additions and 33 deletions
+1
View File
@@ -603,6 +603,7 @@ Some commands only work for clients not in proxy sessions. The chat commands are
* `$qsyncall <reg-num> <value>`: Set a quest register's value for everyone in the game. `<reg-num>` should be either rXX (e.g. r60) or fXX (e.g. f60); if the latter, `<value>` is parsed as a floating-point value instead of as an integer.
* `$swset [floor] <flag-num>` and `$swclear [floor] <flag-num>`: Set or clear a switch flag. If floor is not given, sets or clears the flag on your current floor.
* `$swsetall`: Set all switch flags on your current floor. This unlocks all doors, disables all laser fences, triggers all light/poison switches, etc.
* `$allrare`: Make all enemies and boxes drop their rare items every time.
* `$gc` (non-proxy only): Send your own Guild Card to yourself.
* `$sc <data>`: Send a command to yourself.
* `$scp <data>`: Send a protected command to yourself.
+9
View File
@@ -2227,6 +2227,15 @@ ChatCommandDefinition cc_fastkill(
}
co_return;
});
ChatCommandDefinition cc_allrare(
{"$allrare"},
+[](const Args& a) -> asio::awaitable<void> {
a.check_debug_enabled();
a.c->toggle_flag(Client::Flag::ALL_RARES_ENABLED);
send_text_message_fmt(
a.c, "$C6All-rares {}", a.c->check_flag(Client::Flag::ALL_RARES_ENABLED) ? "enabled" : "disabled");
co_return;
});
ChatCommandDefinition cc_rand(
{"$rand"},
+1
View File
@@ -78,6 +78,7 @@ public:
INFINITE_HP_ENABLED = 0x0000000040000000,
INFINITE_TP_ENABLED = 0x0000000080000000,
FAST_KILLS_ENABLED = 0x0000000100000000,
ALL_RARES_ENABLED = 0x0000100000000000,
DEBUG_ENABLED = 0x0000000200000000,
ITEM_DROP_NOTIFICATIONS_1 = 0x0000000400000000,
ITEM_DROP_NOTIFICATIONS_2 = 0x0000000800000000,
+19 -20
View File
@@ -136,13 +136,13 @@ uint8_t ItemCreator::table_index_for_area(uint8_t area) const {
return data[area];
}
ItemCreator::DropResult ItemCreator::on_box_item_drop(uint8_t area) {
ItemCreator::DropResult ItemCreator::on_box_item_drop(uint8_t area, bool force_rare) {
try {
uint8_t table_index = this->table_index_for_area(area);
this->log.info_f("Box drop checks for area {:02X} (table index {:02X})", area, table_index);
DropResult res;
res.item = this->check_rare_specs_and_create_rare_box_item(area);
res.item = this->check_rare_specs_and_create_rare_box_item(area, force_rare);
if (!res.item.empty()) {
res.is_from_rare_table = true;
} else {
@@ -188,7 +188,7 @@ ItemCreator::DropResult ItemCreator::on_box_item_drop(uint8_t area) {
}
}
ItemCreator::DropResult ItemCreator::on_monster_item_drop(EnemyType enemy_type, uint8_t area) {
ItemCreator::DropResult ItemCreator::on_monster_item_drop(EnemyType enemy_type, uint8_t area, bool force_rare) {
try {
// Note: The original implementation has a bounds check for enemy_type here, because it uses rt_index instead
// if (enemy_type >= NUM_RT_INDEXES_V4) {
@@ -205,16 +205,18 @@ ItemCreator::DropResult ItemCreator::on_monster_item_drop(EnemyType enemy_type,
this->log.info_f("No drop probability is set for this enemy type");
return DropResult();
}
uint8_t drop_sample = this->rand_int(100);
if (drop_sample >= type_drop_prob) {
this->log.info_f("Drop not chosen ({} >= {})", drop_sample, type_drop_prob);
return DropResult();
} else {
this->log.info_f("Drop chosen ({} < {})", drop_sample, type_drop_prob);
if (!force_rare) {
uint8_t drop_sample = this->rand_int(100);
if (drop_sample >= type_drop_prob) {
this->log.info_f("Drop not chosen ({} >= {})", drop_sample, type_drop_prob);
return DropResult();
} else {
this->log.info_f("Drop chosen ({} < {})", drop_sample, type_drop_prob);
}
}
DropResult res;
res.item = this->check_rare_spec_and_create_rare_enemy_item(enemy_type, area);
res.item = this->check_rare_spec_and_create_rare_enemy_item(enemy_type, area, force_rare);
if (!res.item.empty()) {
res.is_from_rare_table = true;
} else {
@@ -284,7 +286,7 @@ ItemCreator::DropResult ItemCreator::on_monster_item_drop(EnemyType enemy_type,
}
}
ItemData ItemCreator::check_rare_specs_and_create_rare_box_item(uint8_t area) {
ItemData ItemCreator::check_rare_specs_and_create_rare_box_item(uint8_t area, bool force_rare) {
ItemData item;
if (!this->are_rare_drops_allowed()) {
return item;
@@ -294,7 +296,7 @@ ItemData ItemCreator::check_rare_specs_and_create_rare_box_item(uint8_t area) {
Episode episode = episode_for_area(area);
auto rare_specs = this->rare_item_set->get_box_specs(this->mode, episode, this->difficulty, this->section_id, table_index);
for (const auto& spec : rare_specs) {
item = this->check_rate_and_create_rare_item(spec, area);
item = this->check_rate_and_create_rare_item(spec, area, force_rare);
if (!item.empty()) {
if (this->log.should_log(phosg::LogLevel::L_INFO)) {
auto hex = spec.data.hex();
@@ -338,7 +340,7 @@ bool ItemCreator::should_allow_meseta_drops() const {
return (this->mode != GameMode::CHALLENGE);
}
ItemData ItemCreator::check_rare_spec_and_create_rare_enemy_item(EnemyType enemy_type, uint8_t area) {
ItemData ItemCreator::check_rare_spec_and_create_rare_enemy_item(EnemyType enemy_type, uint8_t area, bool force_rare) {
// Note: The original implementation has a bounds check for enemy_type here, since it uses rt_index instead.
// if ((enemy_type <= 0) || (enemy_type >= NUM_RT_INDEXES_V4)) return ItemData{};
if (!this->are_rare_drops_allowed()) {
@@ -353,7 +355,7 @@ ItemData ItemCreator::check_rare_spec_and_create_rare_enemy_item(EnemyType enemy
this->mode, episode, this->difficulty, this->section_id, enemy_type);
ItemData item;
for (const auto& spec : rare_specs) {
item = this->check_rate_and_create_rare_item(spec, area);
item = this->check_rate_and_create_rare_item(spec, area, force_rare);
if (!item.empty()) {
if (this->log.should_log(phosg::LogLevel::L_INFO)) {
auto hex = spec.data.hex();
@@ -369,14 +371,11 @@ ItemData ItemCreator::check_rare_spec_and_create_rare_enemy_item(EnemyType enemy
return item;
}
ItemData ItemCreator::check_rate_and_create_rare_item(const RareItemSet::ExpandedDrop& drop, uint8_t area) {
if (drop.probability == 0) {
return ItemData();
}
ItemData ItemCreator::check_rate_and_create_rare_item(
const RareItemSet::ExpandedDrop& drop, uint8_t area, bool force_rare) {
// Note: The original code uses 0xFFFFFFFF as the maximum here. We use 0x100000000 instead, which makes all rare
// items SLIGHTLY more rare.
if (this->rand_int(0x100000000) >= drop.probability) {
if (!force_rare && ((drop.probability == 0) || (this->rand_int(0x100000000) >= drop.probability))) {
return ItemData();
}
+7 -6
View File
@@ -37,10 +37,11 @@ public:
bool is_from_rare_table = false;
};
DropResult on_monster_item_drop(EnemyType enemy_type, uint8_t area);
DropResult on_box_item_drop(uint8_t area);
DropResult on_monster_item_drop(EnemyType enemy_type, uint8_t area, bool force_rare);
DropResult on_box_item_drop(uint8_t area, bool force_rare);
// Note: param3-6 refer to the corresponding fields of the object definition
DropResult on_specialized_box_item_drop(uint8_t area, float param3, uint32_t param4, uint32_t param5, uint32_t param6);
DropResult on_specialized_box_item_drop(
uint8_t area, float param3, uint32_t param4, uint32_t param5, uint32_t param6);
ItemData base_item_for_specialized_box(uint32_t param4, uint32_t param5, uint32_t param6) const;
std::vector<ItemData> generate_armor_shop_contents(Episode episode, size_t player_level);
@@ -120,9 +121,9 @@ private:
bool should_allow_meseta_drops() const;
ItemData check_rare_spec_and_create_rare_enemy_item(EnemyType enemy_type, uint8_t area);
ItemData check_rare_specs_and_create_rare_box_item(uint8_t area);
ItemData check_rate_and_create_rare_item(const RareItemSet::ExpandedDrop& drop, uint8_t area);
ItemData check_rare_spec_and_create_rare_enemy_item(EnemyType enemy_type, uint8_t area, bool force_rare);
ItemData check_rare_specs_and_create_rare_box_item(uint8_t area, bool force_rare);
ItemData check_rate_and_create_rare_item(const RareItemSet::ExpandedDrop& drop, uint8_t area, bool force_rare);
void generate_rare_weapon_bonuses(ItemData& item, Episode episode, uint32_t random_sample);
void deduplicate_weapon_bonuses(ItemData& item) const;
+1
View File
@@ -3484,6 +3484,7 @@ const array<uint32_t, 41> MapFile::RAND_ENEMY_BASE_TYPES = {
0x00DD, /* TObjEneDolmOlm */
0x00DE, /* TObjEneMorfos */
0x00DF, /* TObjEneRecobox */
// This is not a bug; 0x00E0 really does appear twice in this list on the client.
0x00E0, /* TObjEneMe3SinowZoaReal/TObjEneEpsilonBody (depending on area) */
0x00E0, /* TObjEneMe3SinowZoaReal/TObjEneEpsilonBody (depending on area) */
0x00E1, /* TObjEneIllGill */
+4 -2
View File
@@ -960,7 +960,8 @@ static asio::awaitable<HandlerResult> SC_6x60_6xA2(shared_ptr<Client> c, Channel
if (rec.obj_st) {
if (rec.ignore_def) {
c->log.info_f("Creating item from box {:04X} (area {:02X})", cmd.entity_index, cmd.effective_area);
res = c->proxy_session->item_creator->on_box_item_drop(cmd.effective_area);
res = c->proxy_session->item_creator->on_box_item_drop(
cmd.effective_area, c->check_flag(Client::Flag::ALL_RARES_ENABLED));
} else {
c->log.info_f("Creating item from box {:04X} (area {:02X}; specialized with {:g} {:08X} {:08X} {:08X})",
cmd.entity_index, cmd.effective_area,
@@ -970,7 +971,8 @@ static asio::awaitable<HandlerResult> SC_6x60_6xA2(shared_ptr<Client> c, Channel
}
} else {
c->log.info_f("Creating item from enemy {:04X} (area {:02X})", cmd.entity_index, cmd.effective_area);
res = c->proxy_session->item_creator->on_monster_item_drop(rec.effective_enemy_type, cmd.effective_area);
res = c->proxy_session->item_creator->on_monster_item_drop(
rec.effective_enemy_type, cmd.effective_area, c->check_flag(Client::Flag::ALL_RARES_ENABLED));
}
if (res.item.empty()) {
+6 -5
View File
@@ -2964,12 +2964,13 @@ static asio::awaitable<void> on_entity_drop_item_request(shared_ptr<Client> c, S
}
if (rec.should_drop) {
auto generate_item = [&]() -> ItemCreator::DropResult {
auto generate_item_for_client = [&](std::shared_ptr<Client> c) -> ItemCreator::DropResult {
bool force_rare = c->check_flag(Client::Flag::ALL_RARES_ENABLED);
if (rec.obj_st) {
if (rec.ignore_def) {
l->log.info_f("Creating item from box {:04X} => K-{:03X} (area {:02X})",
cmd.entity_index, rec.obj_st->k_id, cmd.effective_area);
return l->item_creator->on_box_item_drop(cmd.effective_area);
return l->item_creator->on_box_item_drop(cmd.effective_area, force_rare);
} else {
l->log.info_f(
"Creating item from box {:04X} => K-{:03X} (area {:02X}; specialized with {:g} {:08X} {:08X} {:08X})",
@@ -2980,7 +2981,7 @@ static asio::awaitable<void> on_entity_drop_item_request(shared_ptr<Client> c, S
} else if (rec.target_ene_st) {
l->log.info_f("Creating item from enemy {:04X} => E-{:03X} (area {:02X})",
cmd.entity_index, rec.target_ene_st->e_id, cmd.effective_area);
return l->item_creator->on_monster_item_drop(rec.effective_enemy_type, cmd.effective_area);
return l->item_creator->on_monster_item_drop(rec.effective_enemy_type, cmd.effective_area, force_rare);
} else {
throw runtime_error("neither object nor enemy were present");
}
@@ -3002,7 +3003,7 @@ static asio::awaitable<void> on_entity_drop_item_request(shared_ptr<Client> c, S
throw logic_error("unhandled simple drop mode");
case ServerDropMode::SERVER_SHARED:
case ServerDropMode::SERVER_DUPLICATE: {
auto res = generate_item();
auto res = generate_item_for_client(c);
if (res.item.empty()) {
l->log.info_f("No item was created");
} else {
@@ -3040,7 +3041,7 @@ static asio::awaitable<void> on_entity_drop_item_request(shared_ptr<Client> c, S
case ServerDropMode::SERVER_PRIVATE: {
for (const auto& lc : l->clients) {
if (lc && (rec.obj_st || (lc->floor == cmd.floor))) {
auto res = generate_item();
auto res = generate_item_for_client(lc);
if (res.item.empty()) {
l->log.info_f("No item was created for {}", lc->channel->name);
} else {