add $allrare debug command; closes #739
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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"},
|
||||
|
||||
@@ -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,
|
||||
|
||||
+13
-14
@@ -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,6 +205,7 @@ 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();
|
||||
}
|
||||
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);
|
||||
@@ -212,9 +213,10 @@ ItemCreator::DropResult ItemCreator::on_monster_item_drop(EnemyType enemy_type,
|
||||
} 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
@@ -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;
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user