fix mericarol type logic; closes #607

This commit is contained in:
Martin Michelsen
2025-02-08 17:27:47 -08:00
parent b451c82943
commit 048b8ba09c
5 changed files with 112 additions and 42 deletions
+8
View File
@@ -158,6 +158,8 @@ const char* phosg::name_for_enum<EnemyType>(EnemyType type) {
return "LOVE_RAPPY";
case EnemyType::MERICAROL:
return "MERICAROL";
case EnemyType::MERICARAND:
return "MERICARAND";
case EnemyType::MERICUS:
return "MERICUS";
case EnemyType::MERIKLE:
@@ -348,6 +350,7 @@ EnemyType phosg::enum_for_name<EnemyType>(const char* name) {
{"KONDRIEU", EnemyType::KONDRIEU},
{"LA_DIMENIAN", EnemyType::LA_DIMENIAN},
{"LOVE_RAPPY", EnemyType::LOVE_RAPPY},
{"MERICARAND", EnemyType::MERICARAND},
{"MERICAROL", EnemyType::MERICAROL},
{"MERICUS", EnemyType::MERICUS},
{"MERIKLE", EnemyType::MERIKLE},
@@ -500,6 +503,7 @@ bool enemy_type_valid_for_episode(Episode episode, EnemyType enemy_type) {
case EnemyType::LA_DIMENIAN:
case EnemyType::LOVE_RAPPY:
case EnemyType::MERICAROL:
case EnemyType::MERICARAND:
case EnemyType::MERICUS:
case EnemyType::MERIKLE:
case EnemyType::MERILLIA:
@@ -744,6 +748,7 @@ uint8_t battle_param_index_for_enemy_type(Episode episode, EnemyType enemy_type)
case EnemyType::MIGIUM:
return 0x33;
case EnemyType::MERICAROL:
case EnemyType::MERICARAND:
return 0x3A;
case EnemyType::UL_GIBBON:
return 0x3B;
@@ -979,6 +984,7 @@ uint8_t rare_table_index_for_enemy_type(EnemyType enemy_type) {
case EnemyType::LOVE_RAPPY:
return 0x33;
case EnemyType::MERICAROL:
case EnemyType::MERICARAND:
return 0x38;
case EnemyType::MERICUS:
return 0x3A;
@@ -1116,6 +1122,8 @@ bool enemy_type_is_rare(EnemyType type) {
(type == EnemyType::AL_RAPPY) ||
(type == EnemyType::NAR_LILY) ||
(type == EnemyType::POUILLY_SLIME) ||
(type == EnemyType::MERICUS) ||
(type == EnemyType::MERIKLE) ||
(type == EnemyType::MERISSA_AA) ||
(type == EnemyType::PAZUZU_ALT) ||
(type == EnemyType::DORPHON_ECLAIR) ||
+1
View File
@@ -80,6 +80,7 @@ enum class EnemyType {
KONDRIEU,
LA_DIMENIAN,
LOVE_RAPPY,
MERICARAND,
MERICAROL,
MERICUS,
MERIKLE,
+63 -28
View File
@@ -2066,12 +2066,9 @@ shared_ptr<SuperMap::Enemy> SuperMap::add_enemy_and_children(
break;
}
case 0x0064: { // TObjEneSlime
// TODO: It appears BB doesn't have a way to force slimes to be rare via
// constructor args. Is this true?
// Unlike all other versions, BB doesn't have a way to force slimes to be
// rare via constructor args
bool is_rare_v123 = (set_entry->uparam2 & 1);
if ((set_entry->num_children != 0) && (set_entry->num_children != 4)) {
this->log.warning("POFUILLY_SLIME has an unusual num_children (0x%hX)", set_entry->num_children.load());
}
default_num_children = -1; // Skip adding children later (because we do it here)
size_t num_children = set_entry->num_children ? set_entry->num_children.load() : 4;
for (size_t z = 0; z < num_children + 1; z++) {
@@ -2224,15 +2221,24 @@ shared_ptr<SuperMap::Enemy> SuperMap::add_enemy_and_children(
default_num_children = 4;
break;
case 0x00D5: // TObjEneMerillLia
add((set_entry->uparam1 & 0x01) ? EnemyType::MERILTAS : EnemyType::MERILLIA);
add((set_entry->uparam1 & 1) ? EnemyType::MERILTAS : EnemyType::MERILLIA);
break;
case 0x00D6: // TObjEneBm9Mericarol
if (set_entry->uparam1 == 0) {
add(EnemyType::MERICAROL);
} else {
add(((set_entry->uparam1 % 3) == 2) ? EnemyType::MERICUS : EnemyType::MERIKLE);
case 0x00D6: { // TObjEneBm9Mericarol
switch (set_entry->uparam1) {
case 0:
add(EnemyType::MERICAROL);
break;
case 1:
add(EnemyType::MERIKLE);
break;
case 2:
add(EnemyType::MERICUS);
break;
default:
add(EnemyType::MERICARAND);
}
break;
}
case 0x00D7: // TObjEneBm5GibonU
add((set_entry->uparam1 & 0x01) ? EnemyType::ZOL_GIBBON : EnemyType::UL_GIBBON);
break;
@@ -3218,29 +3224,31 @@ void SuperMap::print(FILE* stream) const {
////////////////////////////////////////////////////////////////////////////////
// Map state
MapState::RareEnemyRates::RareEnemyRates(uint32_t enemy_rate, uint32_t boss_rate)
MapState::RareEnemyRates::RareEnemyRates(uint32_t enemy_rate, uint32_t mericarand_rate, uint32_t boss_rate)
: hildeblue(enemy_rate),
rappy(enemy_rate),
nar_lily(enemy_rate),
pouilly_slime(enemy_rate),
mericarand(mericarand_rate),
merissa_aa(enemy_rate),
pazuzu(enemy_rate),
dorphon_eclair(enemy_rate),
kondrieu(boss_rate) {}
MapState::RareEnemyRates::RareEnemyRates(const phosg::JSON& json)
: hildeblue(json.get_int("Hildeblue")),
rappy(json.get_int("Rappy")),
nar_lily(json.get_int("NarLily")),
pouilly_slime(json.get_int("PouillySlime")),
merissa_aa(json.get_int("MerissaAA")),
pazuzu(json.get_int("Pazuzu")),
dorphon_eclair(json.get_int("DorphonEclair")),
kondrieu(json.get_int("Kondrieu")) {}
: hildeblue(json.get_int("Hildeblue", DEFAULT_RARE_ENEMY_RATE_V3)),
rappy(json.get_int("Rappy", DEFAULT_RARE_ENEMY_RATE_V3)),
nar_lily(json.get_int("NarLily", DEFAULT_RARE_ENEMY_RATE_V3)),
pouilly_slime(json.get_int("PouillySlime", DEFAULT_RARE_ENEMY_RATE_V3)),
mericarand(json.get_int("Mericarand", DEFAULT_MERICARAND_RATE_V3)),
merissa_aa(json.get_int("MerissaAA", DEFAULT_RARE_ENEMY_RATE_V3)),
pazuzu(json.get_int("Pazuzu", DEFAULT_RARE_ENEMY_RATE_V3)),
dorphon_eclair(json.get_int("DorphonEclair", DEFAULT_RARE_ENEMY_RATE_V3)),
kondrieu(json.get_int("Kondrieu", DEFAULT_RARE_BOSS_RATE_V4)) {}
string MapState::RareEnemyRates::str() const {
return phosg::string_printf("RareEnemyRates(hildeblue=%08" PRIX32 ", rappy=%08" PRIX32 ", nar_lily=%08" PRIX32 ", pouilly_slime=%08" PRIX32 ", merissa_aa=%08" PRIX32 ", pazuzu=%08" PRIX32 ", dorphon_eclair=%08" PRIX32 ", kondrieu=%08" PRIX32 ")",
this->hildeblue, this->rappy, this->nar_lily, this->pouilly_slime,
return phosg::string_printf("RareEnemyRates(hildeblue=%08" PRIX32 ", rappy=%08" PRIX32 ", nar_lily=%08" PRIX32 ", pouilly_slime=%08" PRIX32 ", mericarand=%08" PRIX32 ", merissa_aa=%08" PRIX32 ", pazuzu=%08" PRIX32 ", dorphon_eclair=%08" PRIX32 ", kondrieu=%08" PRIX32 ")",
this->hildeblue, this->rappy, this->nar_lily, this->pouilly_slime, this->mericarand,
this->merissa_aa, this->pazuzu, this->dorphon_eclair, this->kondrieu);
}
@@ -3250,6 +3258,7 @@ phosg::JSON MapState::RareEnemyRates::json() const {
{"Rappy", this->rappy},
{"NarLily", this->nar_lily},
{"PouillySlime", this->pouilly_slime},
{"Mericarand", this->mericarand},
{"MerissaAA", this->merissa_aa},
{"Pazuzu", this->pazuzu},
{"DorphonEclair", this->dorphon_eclair},
@@ -3269,6 +3278,8 @@ uint32_t MapState::RareEnemyRates::for_enemy_type(EnemyType type) const {
return this->nar_lily;
case EnemyType::POFUILLY_SLIME:
return this->pouilly_slime;
case EnemyType::MERICARAND:
return this->mericarand;
case EnemyType::MERISSA_A:
return this->merissa_aa;
case EnemyType::ZU:
@@ -3284,8 +3295,12 @@ uint32_t MapState::RareEnemyRates::for_enemy_type(EnemyType type) const {
}
}
const shared_ptr<const MapState::RareEnemyRates> MapState::NO_RARE_ENEMIES = make_shared<MapState::RareEnemyRates>(0, 0);
const shared_ptr<const MapState::RareEnemyRates> MapState::DEFAULT_RARE_ENEMIES = make_shared<MapState::RareEnemyRates>(0x0083126E, 0x1999999A);
const shared_ptr<const MapState::RareEnemyRates> MapState::NO_RARE_ENEMIES = make_shared<MapState::RareEnemyRates>(
0, 0, 0);
const shared_ptr<const MapState::RareEnemyRates> MapState::DEFAULT_RARE_ENEMIES = make_shared<MapState::RareEnemyRates>(
MapState::RareEnemyRates::DEFAULT_RARE_ENEMY_RATE_V3,
MapState::RareEnemyRates::DEFAULT_MERICARAND_RATE_V3,
MapState::RareEnemyRates::DEFAULT_RARE_BOSS_RATE_V4);
uint32_t MapState::EnemyState::convert_game_flags(uint32_t game_flags, bool to_v3) {
// The format of game_flags was changed significantly between v2 and v3, and
@@ -3522,7 +3537,7 @@ void MapState::index_super_map(const FloorConfig& fc, shared_ptr<PSOLFGEncryptio
}
auto rare_type = rare_type_for_enemy_type(type, fc.super_map->get_episode(), this->event, ene->floor);
if (rare_type != type) {
if ((type == EnemyType::MERICARAND) || (rare_type != type)) {
unordered_map<uint32_t, float> det_cache;
uint32_t bb_rare_rate = this->bb_rare_rates->for_enemy_type(type);
for (Version v : ALL_ARPG_SEMANTIC_VERSIONS) {
@@ -3556,15 +3571,35 @@ void MapState::index_super_map(const FloorConfig& fc, shared_ptr<PSOLFGEncryptio
det_cache.emplace(seed, det);
}
// On v1 and v2 (and GC NTE), the rare rate is 0.1% instead of 0.2%.
if (det < (is_v1_or_v2(v) ? 0.001f : 0.002f)) {
ene_st->set_rare(v);
if (type == EnemyType::MERICARAND) {
// On v3, Mericarols that have uparam1 > 2 are randomized to be
// Mericus, Merikle, or Mericarol, but the former two are not
// considered rare. (We use rare_flags anyway to distinguish them
// from Mericarol.)
if (det > 0.9) { // Merikle
ene_st->set_rare(v);
ene_st->set_mericarand_variant_flag(v);
} else if (det > 0.8) { // Mericus
ene_st->set_rare(v);
} else {
// Mericarol (no flags to set)
}
} else {
// On v1 and v2 (and GC NTE), the rare rate is 0.1% instead of 0.2%.
if (det < (is_v1_or_v2(v) ? 0.001f : 0.002f)) {
ene_st->set_rare(v);
}
}
} else if ((bb_rare_rate > 0) &&
(this->bb_rare_enemy_indexes.size() < 0x10) &&
(random_from_optional_crypt(opt_rand_crypt) < bb_rare_rate)) {
this->bb_rare_enemy_indexes.emplace_back(enemy_index);
ene_st->set_rare(v);
if ((type == EnemyType::MERICARAND) && (enemy_index & 1)) {
ene_st->set_mericarand_variant_flag(v);
}
}
}
}
+26 -6
View File
@@ -628,16 +628,22 @@ protected:
class MapState {
public:
struct RareEnemyRates {
static constexpr uint32_t DEFAULT_RARE_ENEMY_RATE_V1_V2 = 0x00418937; // 1/1000
static constexpr uint32_t DEFAULT_RARE_ENEMY_RATE_V3 = 0x0083126E; // 1/500
static constexpr uint32_t DEFAULT_MERICARAND_RATE_V3 = 0x33333333; // 1/5
static constexpr uint32_t DEFAULT_RARE_BOSS_RATE_V4 = 0x1999999A; // 1/10
uint32_t hildeblue; // HILDEBEAR -> HILDEBLUE
uint32_t rappy; // RAG_RAPPY -> {AL_RAPPY or seasonal rappies}; SAND_RAPPY -> DEL_RAPPY
uint32_t nar_lily; // POISON_LILY -> NAR_LILY
uint32_t pouilly_slime; // POFUILLY_SLIME -> POUILLY_SLIME
uint32_t mericarand; // MERICARAND -> MERIKLE or MERICUS (only for those with subtype > 2)
uint32_t merissa_aa; // MERISSA_A -> MERISSA_AA
uint32_t pazuzu; // ZU -> PAZUZU (and _ALT variants)
uint32_t dorphon_eclair; // DORPHON -> DORPHON_ECLAIR
uint32_t kondrieu; // {SAINT_MILLION, SHAMBERTIN} -> KONDRIEU
RareEnemyRates(uint32_t enemy_rate, uint32_t boss_rate);
RareEnemyRates(uint32_t enemy_rate, uint32_t mericarand_rate, uint32_t boss_rate);
explicit RareEnemyRates(const phosg::JSON& json);
uint32_t for_enemy_type(EnemyType type) const;
@@ -687,9 +693,10 @@ public:
};
size_t e_id = 0;
size_t set_id = 0;
uint32_t game_flags = 0; // From 6x0A
uint16_t total_damage = 0;
uint16_t rare_flags = 0;
uint32_t game_flags = 0; // From 6x0A
uint16_t mericarand_variant_flags = 0;
uint16_t set_flags = 0; // Only used if super_ene->child_index == 0
uint16_t server_flags = 0;
@@ -721,16 +728,29 @@ public:
}
inline bool is_rare(Version version) const {
return (((rare_flags >> static_cast<size_t>(version)) & 1) ||
return (((this->rare_flags >> static_cast<size_t>(version)) & 1) ||
((version == Version::BB_V4) ? this->super_ene->is_default_rare_bb : this->super_ene->is_default_rare_v123));
}
inline void set_rare(Version version) {
this->rare_flags |= (1 << static_cast<size_t>(version));
}
inline void set_mericarand_variant_flag(Version version) {
this->mericarand_variant_flags |= (1 << static_cast<size_t>(version));
}
inline EnemyType type(Version version, Episode episode, uint8_t event) const {
return this->is_rare(version)
? rare_type_for_enemy_type(this->super_ene->type, episode, event, this->super_ene->floor)
: this->super_ene->type;
if (this->super_ene->type == EnemyType::MERICARAND) {
if (this->is_rare(version)) {
return ((this->mericarand_variant_flags >> static_cast<size_t>(version)) & 1)
? EnemyType::MERIKLE
: EnemyType::MERICUS;
} else {
return EnemyType::MERICAROL;
}
} else {
return this->is_rare(version)
? rare_type_for_enemy_type(this->super_ene->type, episode, event, this->super_ene->floor)
: this->super_ene->type;
}
}
inline bool ever_hit_by_client_id(uint8_t client_id) const {
return this->server_flags & (Flag::ALL_HITS_MASK_FIRST << client_id);