add option to specify BB rare enemy rates

This commit is contained in:
Martin Michelsen
2023-12-03 21:44:18 -08:00
parent 8e1edbc34e
commit f605a21c1a
8 changed files with 119 additions and 44 deletions
+1
View File
@@ -63,6 +63,7 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
float z;
uint8_t floor;
};
std::shared_ptr<const Map::RareEnemyRates> rare_enemy_rates;
std::shared_ptr<Map> map;
std::array<uint32_t, 12> next_item_id;
uint32_t next_game_item_id;
+47 -34
View File
@@ -14,6 +14,39 @@ using namespace std;
static constexpr float UINT32_MAX_AS_FLOAT = 4294967296.0f;
Map::RareEnemyRates::RareEnemyRates(uint32_t enemy_rate, uint32_t boss_rate)
: hildeblue(enemy_rate),
rappy(enemy_rate),
nar_lily(enemy_rate),
pouilly_slime(enemy_rate),
merissa_aa(enemy_rate),
pazuzu(enemy_rate),
dorphon_eclair(enemy_rate),
kondrieu(boss_rate) {}
Map::RareEnemyRates::RareEnemyRates(const 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")) {}
JSON Map::RareEnemyRates::json() const {
return JSON::dict({
{"Hildeblue", this->hildeblue},
{"Rappy", this->rappy},
{"NarLily", this->nar_lily},
{"PouillySlime", this->pouilly_slime},
{"MerissaAA", this->merissa_aa},
{"Pazuzu", this->pazuzu},
{"DorphonEclair", this->dorphon_eclair},
{"Kondrieu", this->kondrieu},
});
}
string Map::ObjectEntry::str() const {
return string_printf("[ObjectEntry type=%04hX flags=%04hX index=%04hX a2=%04hX entity_id=%04hX group=%04hX section=%04hX a3=%04hX x=%g y=%g z=%g x_angle=%08" PRIX32 " y_angle=%08" PRIX32 " z_angle=%08" PRIX32 " params=[%g %g %g %08" PRIX32 " %08" PRIX32 " %08" PRIX32 "] unused=%08" PRIX32 "]",
this->base_type.load(),
@@ -145,7 +178,7 @@ void Map::add_enemy(
uint8_t floor,
size_t index,
const EnemyEntry& e,
const RareEnemyRates& rare_rates) {
std::shared_ptr<const RareEnemyRates> rare_rates) {
auto add = [&](EnemyType type) -> void {
this->enemies.emplace_back(index, floor, type);
};
@@ -218,12 +251,12 @@ void Map::add_enemy(
break;
case 0x0040: // TObjEneMoja
add(this->check_and_log_rare_enemy(e.uparam1 & 0x01, rare_rates.hildeblue)
add(this->check_and_log_rare_enemy(e.uparam1 & 0x01, rare_rates->hildeblue)
? EnemyType::HILDEBLUE
: EnemyType::HILDEBEAR);
break;
case 0x0041: { // TObjEneLappy
bool is_rare = this->check_and_log_rare_enemy(e.uparam1 & 0x01, rare_rates.rappy);
bool is_rare = this->check_and_log_rare_enemy(e.uparam1 & 0x01, rare_rates->rappy);
switch (episode) {
case Episode::EP1:
add(is_rare ? EnemyType::AL_RAPPY : EnemyType::RAG_RAPPY);
@@ -278,7 +311,7 @@ void Map::add_enemy(
if ((episode == Episode::EP2) && (e.floor > 0x0F)) {
add(EnemyType::DEL_LILY);
} else {
add(this->check_and_log_rare_enemy(e.uparam1 & 0x01, rare_rates.nar_lily)
add(this->check_and_log_rare_enemy(e.uparam1 & 0x01, rare_rates->nar_lily)
? EnemyType::NAR_LILY
: EnemyType::POISON_LILY);
}
@@ -292,7 +325,7 @@ void Map::add_enemy(
break;
}
case 0x0064: // TObjEneSlime
add(this->check_and_log_rare_enemy(e.uparam1 & 0x01, rare_rates.pouilly_slime)
add(this->check_and_log_rare_enemy(e.uparam1 & 0x01, rare_rates->pouilly_slime)
? EnemyType::POFUILLY_SLIME
: EnemyType::POUILLY_SLIME);
default_num_children = 4;
@@ -503,7 +536,7 @@ void Map::add_enemy(
}
break;
case 0x0112:
add(this->check_and_log_rare_enemy(e.uparam1 & 0x01, rare_rates.merissa_aa)
add(this->check_and_log_rare_enemy(e.uparam1 & 0x01, rare_rates->merissa_aa)
? EnemyType::MERISSA_AA
: EnemyType::MERISSA_A);
break;
@@ -511,7 +544,7 @@ void Map::add_enemy(
add(EnemyType::GIRTABLULU);
break;
case 0x0114: {
bool is_rare = this->check_and_log_rare_enemy(e.uparam1 & 0x01, rare_rates.pazuzu);
bool is_rare = this->check_and_log_rare_enemy(e.uparam1 & 0x01, rare_rates->pazuzu);
if (e.floor > 0x05) {
add(is_rare ? EnemyType::PAZUZU_ALT : EnemyType::ZU_ALT);
} else {
@@ -527,7 +560,7 @@ void Map::add_enemy(
}
break;
case 0x0116:
add(this->check_and_log_rare_enemy(e.uparam1 & 0x01, rare_rates.dorphon_eclair)
add(this->check_and_log_rare_enemy(e.uparam1 & 0x01, rare_rates->dorphon_eclair)
? EnemyType::DORPHON_ECLAIR
: EnemyType::DORPHON);
break;
@@ -537,7 +570,7 @@ void Map::add_enemy(
break;
}
case 0x0119: {
bool is_rare = this->check_and_log_rare_enemy((e.fparam2 != 0.0f), rare_rates.kondrieu);
bool is_rare = this->check_and_log_rare_enemy((e.fparam2 != 0.0f), rare_rates->kondrieu);
if (is_rare) {
add(EnemyType::KONDRIEU);
} else {
@@ -584,7 +617,7 @@ void Map::add_enemies_from_map_data(
uint8_t floor,
const void* data,
size_t size,
const RareEnemyRates& rare_rates) {
std::shared_ptr<const RareEnemyRates> rare_rates) {
size_t entry_count = size / sizeof(EnemyEntry);
if (size != entry_count * sizeof(EnemyEntry)) {
throw runtime_error("data size is not a multiple of entry size");
@@ -678,7 +711,7 @@ void Map::add_random_enemies_from_map_data(
StringReader locations_segment_r,
StringReader definitions_segment_r,
uint32_t rare_seed,
const RareEnemyRates& rare_rates) {
std::shared_ptr<const RareEnemyRates> rare_rates) {
static const array<uint32_t, 41> rand_enemy_base_types = {
0x44, 0x43, 0x41, 0x42, 0x40, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x80,
@@ -889,7 +922,7 @@ void Map::add_enemies_and_objects_from_quest_data(
const void* data,
size_t size,
uint32_t rare_seed,
const RareEnemyRates& rare_rates) {
std::shared_ptr<const RareEnemyRates> rare_rates) {
auto all_floor_sections = this->collect_quest_map_data_sections(data, size);
StringReader r(data, size);
@@ -1311,25 +1344,5 @@ vector<string> map_filenames_for_variation(
return ret;
}
const Map::RareEnemyRates Map::NO_RARE_ENEMIES = {
.hildeblue = 0x00000000,
.rappy = 0x00000000,
.nar_lily = 0x00000000,
.pouilly_slime = 0x00000000,
.merissa_aa = 0x00000000,
.pazuzu = 0x00000000,
.dorphon_eclair = 0x00000000,
.kondrieu = 0x00000000,
};
const Map::RareEnemyRates Map::DEFAULT_RARE_ENEMIES = {
// All 1/512 except Kondrieu, which is 1/10
.hildeblue = 0x00800000,
.rappy = 0x00800000,
.nar_lily = 0x00800000,
.pouilly_slime = 0x00800000,
.merissa_aa = 0x00800000,
.pazuzu = 0x00800000,
.dorphon_eclair = 0x00800000,
.kondrieu = 0x1999999A,
};
const shared_ptr<const Map::RareEnemyRates> Map::NO_RARE_ENEMIES = make_shared<Map::RareEnemyRates>(0, 0);
const shared_ptr<const Map::RareEnemyRates> Map::DEFAULT_RARE_ENEMIES = make_shared<Map::RareEnemyRates>(0x00800000, 0x1999999A);
+12 -7
View File
@@ -4,6 +4,7 @@
#include <memory>
#include <phosg/Encoding.hh>
#include <phosg/JSON.hh>
#include <random>
#include <string>
#include <vector>
@@ -192,10 +193,15 @@ struct Map {
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);
explicit RareEnemyRates(const JSON& json);
JSON json() const;
};
static const RareEnemyRates NO_RARE_ENEMIES;
static const RareEnemyRates DEFAULT_RARE_ENEMIES;
static const std::shared_ptr<const RareEnemyRates> NO_RARE_ENEMIES;
static const std::shared_ptr<const RareEnemyRates> DEFAULT_RARE_ENEMIES;
struct Object {
// TODO: Add more fields in here if we ever care about them. Currently we
@@ -249,7 +255,7 @@ struct Map {
uint8_t floor,
size_t index,
const EnemyEntry& e,
const RareEnemyRates& rare_rates = Map::DEFAULT_RARE_ENEMIES);
std::shared_ptr<const RareEnemyRates> rare_rates = DEFAULT_RARE_ENEMIES);
void add_enemies_from_map_data(
Episode episode,
uint8_t difficulty,
@@ -257,7 +263,7 @@ struct Map {
uint8_t floor,
const void* data,
size_t size,
const RareEnemyRates& rare_rates = Map::DEFAULT_RARE_ENEMIES);
std::shared_ptr<const RareEnemyRates> rare_rates = DEFAULT_RARE_ENEMIES);
void add_random_enemies_from_map_data(
Episode episode,
uint8_t difficulty,
@@ -267,7 +273,7 @@ struct Map {
StringReader random_enemy_locations_r,
StringReader random_enemy_definitions_r,
uint32_t rare_seed,
const RareEnemyRates& rare_rates = Map::DEFAULT_RARE_ENEMIES);
std::shared_ptr<const RareEnemyRates> rare_rates = DEFAULT_RARE_ENEMIES);
struct DATSectionsForFloor {
uint32_t objects = 0xFFFFFFFF;
@@ -285,7 +291,7 @@ struct Map {
const void* data,
size_t size,
uint32_t rare_seed,
const RareEnemyRates& rare_rates = Map::DEFAULT_RARE_ENEMIES);
std::shared_ptr<const RareEnemyRates> rare_rates = Map::DEFAULT_RARE_ENEMIES);
static std::string disassemble_quest_data(const void* data, size_t size);
};
@@ -329,4 +335,3 @@ void generate_variations_dc_nte(
std::shared_ptr<PSOLFGEncryption> random);
std::vector<std::string> map_filenames_for_variation(
Episode episode, bool is_solo, uint8_t floor, uint32_t var1, uint32_t var2, bool is_enemies);
void load_map_files();
+14 -2
View File
@@ -1863,7 +1863,7 @@ static void on_quest_loaded(shared_ptr<Lobby> l) {
dat_contents.data(),
dat_contents.size(),
l->random_seed,
(l->mode == GameMode::CHALLENGE) ? Map::NO_RARE_ENEMIES : Map::DEFAULT_RARE_ENEMIES);
l->rare_enemy_rates ? l->rare_enemy_rates : Map::NO_RARE_ENEMIES);
l->item_creator->clear_destroyed_entities();
l->log.info("Replaced objects list with quest layout (%zu entries)", l->map->objects.size());
@@ -3932,6 +3932,12 @@ shared_ptr<Lobby> create_game_generic(
generate_variations(game->variations, game->random_crypt, game->episode, is_solo);
}
if (game->mode == GameMode::CHALLENGE) {
game->rare_enemy_rates = s->rare_enemy_rates_challenge;
} else {
game->rare_enemy_rates = s->rare_enemy_rates.at(game->difficulty);
}
if (game->base_version == Version::BB_V4) {
game->map = make_shared<Map>();
for (size_t floor = 0; floor < 0x10; floor++) {
@@ -3954,7 +3960,13 @@ shared_ptr<Lobby> create_game_generic(
auto map_data = s->load_bb_file(filename, "", "map/" + filename);
size_t start_offset = game->map->enemies.size();
game->map->add_enemies_from_map_data(
game->episode, game->difficulty, game->event, floor, map_data->data(), map_data->size());
game->episode,
game->difficulty,
game->event,
floor,
map_data->data(),
map_data->size(),
game->rare_enemy_rates);
size_t entries_loaded = game->map->enemies.size() - start_offset;
c->log.info("[Map/%zu:e] Loaded %s (%zu entries)", floor, filename.c_str(), entries_loaded);
for (size_t z = start_offset; z < game->map->enemies.size(); z++) {
+1 -1
View File
@@ -2440,7 +2440,7 @@ static void on_battle_restart_bb(shared_ptr<Client> c, uint8_t, uint8_t, const v
dat_contents.data(),
dat_contents.size(),
l->random_seed,
(l->mode == GameMode::CHALLENGE) ? Map::NO_RARE_ENEMIES : Map::DEFAULT_RARE_ENEMIES);
l->rare_enemy_rates ? l->rare_enemy_rates : Map::NO_RARE_ENEMIES);
}
}
+17
View File
@@ -913,6 +913,23 @@ void ServerState::parse_config(const JSON& json, bool is_reload) {
this->team_reward_defs_json = std::move(json.at("TeamRewards"));
} catch (const out_of_range&) {
}
for (size_t z = 0; z < 4; z++) {
shared_ptr<const Map::RareEnemyRates> prev = Map::DEFAULT_RARE_ENEMIES;
try {
string key = "RareEnemyRates-";
key += token_name_for_difficulty(z);
this->rare_enemy_rates[z] = make_shared<Map::RareEnemyRates>(json.at(key));
prev = this->rare_enemy_rates[z];
} catch (const out_of_range&) {
this->rare_enemy_rates[z] = prev;
}
}
try {
this->rare_enemy_rates_challenge = make_shared<Map::RareEnemyRates>(json.at("RareEnemyRates-Challenge"));
} catch (const out_of_range&) {
this->rare_enemy_rates_challenge = Map::DEFAULT_RARE_ENEMIES;
}
}
void ServerState::load_bb_private_keys() {
+2
View File
@@ -114,6 +114,8 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
std::shared_ptr<const MagEvolutionTable> mag_evolution_table;
std::shared_ptr<const ItemNameIndex> item_name_index;
std::shared_ptr<const WordSelectTable> word_select_table;
std::array<std::shared_ptr<const Map::RareEnemyRates>, 4> rare_enemy_rates;
std::shared_ptr<const Map::RareEnemyRates> rare_enemy_rates_challenge;
// Indexed as [type][difficulty][random_choice]
std::vector<std::vector<std::vector<ItemData>>> quest_F95E_results;
+25
View File
@@ -724,6 +724,31 @@
"ItemDropMode": "OnByDefault",
"UseServerItemTables": "OffByDefault",
// Rare enemy rates for BB games. The default rates specified here match the
// original rates on the official servers. There is a hard limit of 16 rare
// enemies per room or quest, which you may run into if you increase these
// rates significantly.
// If no rates are specified for a difficulty, the previous difficulty's rates
// will be used. (In the default configuration, only Normal is specified, so
// all difficulties use the same rates.)
"RareEnemyRates-Normal": {
// These are probabilities out of 0xFFFFFFFF - so 0 means that rare enemy
// will never appear, and 0xFFFFFFFF means it will always appear (until 16
// rare enemies have been assigned).
"Hildeblue": 0x00800000,
"Rappy": 0x00800000,
"NarLily": 0x00800000,
"PouillySlime": 0x00800000,
"MerissaAA": 0x00800000,
"Pazuzu": 0x00800000,
"DorphonEclair": 0x00800000,
"Kondrieu": 0x1999999A,
},
// "RareEnemyRates-Hard": {...},
// "RareEnemyRates-VeryHard": {...},
// "RareEnemyRates-Ultimate": {...},
// "RareEnemyRates-Challenge": {...},
// Whether to enable certain exception handling. Disabling this causes
// newserv to abort when any client causes an exception, which is generally
// only useful for debugging newserv itself. This setting should usually be