add option to specify BB rare enemy rates
This commit is contained in:
@@ -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
@@ -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
@@ -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
@@ -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++) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user