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.
|
* `$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.
|
* `$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.
|
* `$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.
|
* `$gc` (non-proxy only): Send your own Guild Card to yourself.
|
||||||
* `$sc <data>`: Send a command to yourself.
|
* `$sc <data>`: Send a command to yourself.
|
||||||
* `$scp <data>`: Send a protected command to yourself.
|
* `$scp <data>`: Send a protected command to yourself.
|
||||||
|
|||||||
@@ -2227,6 +2227,15 @@ ChatCommandDefinition cc_fastkill(
|
|||||||
}
|
}
|
||||||
co_return;
|
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(
|
ChatCommandDefinition cc_rand(
|
||||||
{"$rand"},
|
{"$rand"},
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ public:
|
|||||||
INFINITE_HP_ENABLED = 0x0000000040000000,
|
INFINITE_HP_ENABLED = 0x0000000040000000,
|
||||||
INFINITE_TP_ENABLED = 0x0000000080000000,
|
INFINITE_TP_ENABLED = 0x0000000080000000,
|
||||||
FAST_KILLS_ENABLED = 0x0000000100000000,
|
FAST_KILLS_ENABLED = 0x0000000100000000,
|
||||||
|
ALL_RARES_ENABLED = 0x0000100000000000,
|
||||||
DEBUG_ENABLED = 0x0000000200000000,
|
DEBUG_ENABLED = 0x0000000200000000,
|
||||||
ITEM_DROP_NOTIFICATIONS_1 = 0x0000000400000000,
|
ITEM_DROP_NOTIFICATIONS_1 = 0x0000000400000000,
|
||||||
ITEM_DROP_NOTIFICATIONS_2 = 0x0000000800000000,
|
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];
|
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 {
|
try {
|
||||||
uint8_t table_index = this->table_index_for_area(area);
|
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);
|
this->log.info_f("Box drop checks for area {:02X} (table index {:02X})", area, table_index);
|
||||||
|
|
||||||
DropResult res;
|
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()) {
|
if (!res.item.empty()) {
|
||||||
res.is_from_rare_table = true;
|
res.is_from_rare_table = true;
|
||||||
} else {
|
} 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 {
|
try {
|
||||||
// Note: The original implementation has a bounds check for enemy_type here, because it uses rt_index instead
|
// 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) {
|
// 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");
|
this->log.info_f("No drop probability is set for this enemy type");
|
||||||
return DropResult();
|
return DropResult();
|
||||||
}
|
}
|
||||||
|
if (!force_rare) {
|
||||||
uint8_t drop_sample = this->rand_int(100);
|
uint8_t drop_sample = this->rand_int(100);
|
||||||
if (drop_sample >= type_drop_prob) {
|
if (drop_sample >= type_drop_prob) {
|
||||||
this->log.info_f("Drop not chosen ({} >= {})", 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 {
|
} else {
|
||||||
this->log.info_f("Drop chosen ({} < {})", drop_sample, type_drop_prob);
|
this->log.info_f("Drop chosen ({} < {})", drop_sample, type_drop_prob);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DropResult res;
|
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()) {
|
if (!res.item.empty()) {
|
||||||
res.is_from_rare_table = true;
|
res.is_from_rare_table = true;
|
||||||
} else {
|
} 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;
|
ItemData item;
|
||||||
if (!this->are_rare_drops_allowed()) {
|
if (!this->are_rare_drops_allowed()) {
|
||||||
return item;
|
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);
|
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);
|
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) {
|
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 (!item.empty()) {
|
||||||
if (this->log.should_log(phosg::LogLevel::L_INFO)) {
|
if (this->log.should_log(phosg::LogLevel::L_INFO)) {
|
||||||
auto hex = spec.data.hex();
|
auto hex = spec.data.hex();
|
||||||
@@ -338,7 +340,7 @@ bool ItemCreator::should_allow_meseta_drops() const {
|
|||||||
return (this->mode != GameMode::CHALLENGE);
|
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.
|
// 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 ((enemy_type <= 0) || (enemy_type >= NUM_RT_INDEXES_V4)) return ItemData{};
|
||||||
if (!this->are_rare_drops_allowed()) {
|
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);
|
this->mode, episode, this->difficulty, this->section_id, enemy_type);
|
||||||
ItemData item;
|
ItemData item;
|
||||||
for (const auto& spec : rare_specs) {
|
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 (!item.empty()) {
|
||||||
if (this->log.should_log(phosg::LogLevel::L_INFO)) {
|
if (this->log.should_log(phosg::LogLevel::L_INFO)) {
|
||||||
auto hex = spec.data.hex();
|
auto hex = spec.data.hex();
|
||||||
@@ -369,14 +371,11 @@ ItemData ItemCreator::check_rare_spec_and_create_rare_enemy_item(EnemyType enemy
|
|||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemData ItemCreator::check_rate_and_create_rare_item(const RareItemSet::ExpandedDrop& drop, uint8_t area) {
|
ItemData ItemCreator::check_rate_and_create_rare_item(
|
||||||
if (drop.probability == 0) {
|
const RareItemSet::ExpandedDrop& drop, uint8_t area, bool force_rare) {
|
||||||
return ItemData();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: The original code uses 0xFFFFFFFF as the maximum here. We use 0x100000000 instead, which makes all rare
|
// Note: The original code uses 0xFFFFFFFF as the maximum here. We use 0x100000000 instead, which makes all rare
|
||||||
// items SLIGHTLY more 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();
|
return ItemData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+7
-6
@@ -37,10 +37,11 @@ public:
|
|||||||
bool is_from_rare_table = false;
|
bool is_from_rare_table = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
DropResult on_monster_item_drop(EnemyType enemy_type, 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);
|
DropResult on_box_item_drop(uint8_t area, bool force_rare);
|
||||||
// Note: param3-6 refer to the corresponding fields of the object definition
|
// 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;
|
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);
|
std::vector<ItemData> generate_armor_shop_contents(Episode episode, size_t player_level);
|
||||||
@@ -120,9 +121,9 @@ private:
|
|||||||
|
|
||||||
bool should_allow_meseta_drops() const;
|
bool should_allow_meseta_drops() const;
|
||||||
|
|
||||||
ItemData check_rare_spec_and_create_rare_enemy_item(EnemyType enemy_type, 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);
|
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);
|
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 generate_rare_weapon_bonuses(ItemData& item, Episode episode, uint32_t random_sample);
|
||||||
void deduplicate_weapon_bonuses(ItemData& item) const;
|
void deduplicate_weapon_bonuses(ItemData& item) const;
|
||||||
|
|||||||
@@ -3484,6 +3484,7 @@ const array<uint32_t, 41> MapFile::RAND_ENEMY_BASE_TYPES = {
|
|||||||
0x00DD, /* TObjEneDolmOlm */
|
0x00DD, /* TObjEneDolmOlm */
|
||||||
0x00DE, /* TObjEneMorfos */
|
0x00DE, /* TObjEneMorfos */
|
||||||
0x00DF, /* TObjEneRecobox */
|
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) */
|
||||||
0x00E0, /* TObjEneMe3SinowZoaReal/TObjEneEpsilonBody (depending on area) */
|
0x00E0, /* TObjEneMe3SinowZoaReal/TObjEneEpsilonBody (depending on area) */
|
||||||
0x00E1, /* TObjEneIllGill */
|
0x00E1, /* TObjEneIllGill */
|
||||||
|
|||||||
@@ -960,7 +960,8 @@ static asio::awaitable<HandlerResult> SC_6x60_6xA2(shared_ptr<Client> c, Channel
|
|||||||
if (rec.obj_st) {
|
if (rec.obj_st) {
|
||||||
if (rec.ignore_def) {
|
if (rec.ignore_def) {
|
||||||
c->log.info_f("Creating item from box {:04X} (area {:02X})", cmd.entity_index, cmd.effective_area);
|
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 {
|
} else {
|
||||||
c->log.info_f("Creating item from box {:04X} (area {:02X}; specialized with {:g} {:08X} {:08X} {:08X})",
|
c->log.info_f("Creating item from box {:04X} (area {:02X}; specialized with {:g} {:08X} {:08X} {:08X})",
|
||||||
cmd.entity_index, cmd.effective_area,
|
cmd.entity_index, cmd.effective_area,
|
||||||
@@ -970,7 +971,8 @@ static asio::awaitable<HandlerResult> SC_6x60_6xA2(shared_ptr<Client> c, Channel
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
c->log.info_f("Creating item from enemy {:04X} (area {:02X})", cmd.entity_index, cmd.effective_area);
|
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()) {
|
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) {
|
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.obj_st) {
|
||||||
if (rec.ignore_def) {
|
if (rec.ignore_def) {
|
||||||
l->log.info_f("Creating item from box {:04X} => K-{:03X} (area {:02X})",
|
l->log.info_f("Creating item from box {:04X} => K-{:03X} (area {:02X})",
|
||||||
cmd.entity_index, rec.obj_st->k_id, cmd.effective_area);
|
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 {
|
} else {
|
||||||
l->log.info_f(
|
l->log.info_f(
|
||||||
"Creating item from box {:04X} => K-{:03X} (area {:02X}; specialized with {:g} {:08X} {:08X} {:08X})",
|
"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) {
|
} else if (rec.target_ene_st) {
|
||||||
l->log.info_f("Creating item from enemy {:04X} => E-{:03X} (area {:02X})",
|
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);
|
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 {
|
} else {
|
||||||
throw runtime_error("neither object nor enemy were present");
|
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");
|
throw logic_error("unhandled simple drop mode");
|
||||||
case ServerDropMode::SERVER_SHARED:
|
case ServerDropMode::SERVER_SHARED:
|
||||||
case ServerDropMode::SERVER_DUPLICATE: {
|
case ServerDropMode::SERVER_DUPLICATE: {
|
||||||
auto res = generate_item();
|
auto res = generate_item_for_client(c);
|
||||||
if (res.item.empty()) {
|
if (res.item.empty()) {
|
||||||
l->log.info_f("No item was created");
|
l->log.info_f("No item was created");
|
||||||
} else {
|
} else {
|
||||||
@@ -3040,7 +3041,7 @@ static asio::awaitable<void> on_entity_drop_item_request(shared_ptr<Client> c, S
|
|||||||
case ServerDropMode::SERVER_PRIVATE: {
|
case ServerDropMode::SERVER_PRIVATE: {
|
||||||
for (const auto& lc : l->clients) {
|
for (const auto& lc : l->clients) {
|
||||||
if (lc && (rec.obj_st || (lc->floor == cmd.floor))) {
|
if (lc && (rec.obj_st || (lc->floor == cmd.floor))) {
|
||||||
auto res = generate_item();
|
auto res = generate_item_for_client(lc);
|
||||||
if (res.item.empty()) {
|
if (res.item.empty()) {
|
||||||
l->log.info_f("No item was created for {}", lc->channel->name);
|
l->log.info_f("No item was created for {}", lc->channel->name);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user