rewrite map data model

This commit is contained in:
Martin Michelsen
2024-12-30 09:20:42 -08:00
parent 69f7bb3db9
commit 72ac20e574
95 changed files with 7596 additions and 5125 deletions
+70 -218
View File
@@ -27,12 +27,18 @@ shared_ptr<Lobby::FloorItem> Lobby::FloorItemManager::find(uint32_t item_id) con
return this->items.at(item_id);
}
void Lobby::FloorItemManager::add(const ItemData& item, float x, float z, uint16_t flags) {
void Lobby::FloorItemManager::add(
const ItemData& item,
const VectorXZF& pos,
shared_ptr<const MapState::ObjectState> from_obj,
shared_ptr<const MapState::EnemyState> from_ene,
uint16_t flags) {
auto fi = make_shared<FloorItem>();
fi->data = item;
fi->x = x;
fi->z = z;
fi->pos = pos;
fi->drop_number = this->next_drop_number++;
fi->from_obj = from_obj;
fi->from_ene = from_ene;
fi->flags = flags;
this->add(fi);
}
@@ -52,7 +58,7 @@ void Lobby::FloorItemManager::add(shared_ptr<Lobby::FloorItem> fi) {
}
}
this->log.info("Added floor item %08" PRIX32 " at %g, %g with drop number %" PRIu64 " with flags %03hX",
fi->data.id.load(), fi->x, fi->z, fi->drop_number, fi->flags);
fi->data.id.load(), fi->pos.x.load(), fi->pos.z.load(), fi->drop_number, fi->flags);
}
std::shared_ptr<Lobby::FloorItem> Lobby::FloorItemManager::remove(uint32_t item_id, uint8_t client_id) {
@@ -71,7 +77,7 @@ std::shared_ptr<Lobby::FloorItem> Lobby::FloorItemManager::remove(uint32_t item_
}
this->items.erase(item_it);
this->log.info("Removed floor item %08" PRIX32 " at %g, %g with drop number %" PRIu64 " with flags %03hX",
fi->data.id.load(), fi->x, fi->z, fi->drop_number, fi->flags);
fi->data.id.load(), fi->pos.x.load(), fi->pos.z.load(), fi->drop_number, fi->flags);
return fi;
}
@@ -142,7 +148,6 @@ Lobby::Lobby(shared_ptr<ServerState> s, uint32_t id, bool is_game)
min_level(0),
max_level(0xFFFFFFFF),
next_game_item_id(0xCC000000),
base_version(Version::GC_V3),
allowed_versions(0x0000),
override_section_id(0xFF),
episode(Episode::NONE),
@@ -196,24 +201,22 @@ shared_ptr<Lobby::ChallengeParameters> Lobby::require_challenge_params() const {
return this->challenge_params;
}
void Lobby::set_drop_mode(DropMode new_mode) {
this->drop_mode = new_mode;
bool should_have_item_creator = (this->base_version == Version::BB_V4) ||
((new_mode != DropMode::DISABLED) && (new_mode != DropMode::CLIENT));
if (should_have_item_creator && !this->item_creator) {
this->create_item_creator();
} else if (!should_have_item_creator && this->item_creator) {
void Lobby::create_item_creator(Version logic_version) {
if (!this->is_game() || this->episode == Episode::EP3) {
this->item_creator.reset();
return;
}
}
void Lobby::create_item_creator() {
auto s = this->require_server_state();
if (logic_version == Version::UNKNOWN) {
auto leader_c = this->clients[this->leader_id];
logic_version = leader_c ? leader_c->version() : Version::BB_V4;
}
shared_ptr<const RareItemSet> rare_item_set;
shared_ptr<const CommonItemSet> common_item_set;
switch (this->base_version) {
switch (logic_version) {
case Version::PC_PATCH:
case Version::BB_PATCH:
case Version::GC_EP3_NTE:
@@ -245,6 +248,7 @@ void Lobby::create_item_creator() {
default:
throw logic_error("invalid lobby base version");
}
this->item_creator = make_shared<ItemCreator>(
common_item_set,
rare_item_set,
@@ -252,8 +256,8 @@ void Lobby::create_item_creator() {
s->tool_random_set,
s->weapon_random_sets.at(this->difficulty),
s->tekker_adjustment_set,
s->item_parameter_table(this->base_version),
s->item_stack_limits(this->base_version),
s->item_parameter_table(logic_version),
s->item_stack_limits(logic_version),
this->episode,
(this->mode == GameMode::SOLO) ? GameMode::NORMAL : this->mode,
this->difficulty,
@@ -262,21 +266,6 @@ void Lobby::create_item_creator() {
this->quest ? this->quest->battle_rules : nullptr);
}
void Lobby::change_section_id() {
if (this->item_creator) {
uint8_t new_section_id = this->effective_section_id();
if (this->item_creator->get_section_id() != new_section_id) {
this->log.info("Changing section ID to %s", name_for_section_id(new_section_id));
this->item_creator->set_section_id(new_section_id);
for (const auto& c : this->clients) {
if (c && c->config.check_flag(Client::Flag::DEBUG_ENABLED)) {
send_text_message_printf(c, "$C5Section ID changed\nto %s (%hhu)", name_for_section_id(new_section_id), new_section_id);
}
}
}
}
}
uint8_t Lobby::effective_section_id() const {
if (this->override_section_id != 0xFF) {
return this->override_section_id;
@@ -291,199 +280,55 @@ uint8_t Lobby::effective_section_id() const {
return 0;
}
shared_ptr<Map> Lobby::load_maps(
Version version,
Episode episode,
uint8_t difficulty,
uint8_t event,
uint32_t lobby_id,
shared_ptr<const Map::RareEnemyRates> rare_rates,
uint32_t random_seed,
shared_ptr<PSOLFGEncryption> opt_rand_crypt,
shared_ptr<const string> quest_dat_contents_decompressed) {
auto map = make_shared<Map>(version, lobby_id, random_seed, opt_rand_crypt);
map->add_entities_from_quest_data(episode, difficulty, event, quest_dat_contents_decompressed, rare_rates);
return map;
}
shared_ptr<Map> Lobby::load_maps(
Version version,
Episode episode,
GameMode mode,
uint8_t difficulty,
uint8_t event,
uint32_t lobby_id,
shared_ptr<const SetDataTableBase> sdt,
function<shared_ptr<const string>(Version, const string&)> get_file_data,
shared_ptr<const Map::RareEnemyRates> rare_rates,
uint32_t random_seed,
shared_ptr<PSOLFGEncryption> opt_rand_crypt,
const parray<le_uint32_t, 0x20>& variations,
const phosg::PrefixedLogger* log) {
auto enemy_filenames = sdt->map_filenames_for_variations(variations, episode, mode, SetDataTable::FilenameType::ENEMIES);
auto object_filenames = sdt->map_filenames_for_variations(variations, episode, mode, SetDataTable::FilenameType::OBJECTS);
auto event_filenames = sdt->map_filenames_for_variations(variations, episode, mode, SetDataTable::FilenameType::EVENTS);
return Lobby::load_maps(
enemy_filenames,
object_filenames,
event_filenames,
version,
episode,
mode,
difficulty,
event,
lobby_id,
get_file_data,
rare_rates,
random_seed,
opt_rand_crypt,
log);
}
shared_ptr<Map> Lobby::load_maps(
const vector<string>& enemy_filenames,
const vector<string>& object_filenames,
const vector<string>& event_filenames,
Version version,
Episode episode,
GameMode mode,
uint8_t difficulty,
uint8_t event,
uint32_t lobby_id,
function<shared_ptr<const string>(Version, const string&)> get_file_data,
shared_ptr<const Map::RareEnemyRates> rare_rates,
uint32_t rare_seed,
shared_ptr<PSOLFGEncryption> opt_rand_crypt,
const phosg::PrefixedLogger* log) {
auto map = make_shared<Map>(version, lobby_id, rare_seed, opt_rand_crypt);
// Don't load free-roam maps in Challenge mode, since players can't go to
// Ragol without a quest loaded
if (mode == GameMode::CHALLENGE) {
return map;
}
for (size_t floor = 0; floor < 0x12; floor++) {
const auto& floor_enemy_filename = enemy_filenames.at(floor);
if (!floor_enemy_filename.empty()) {
auto map_data = get_file_data(version, floor_enemy_filename);
if (map_data) {
map->add_enemies_from_map_data(
episode,
difficulty,
event,
floor,
map_data->data(),
map_data->size(),
rare_rates);
if (log) {
log->info("Loaded enemies map %s for floor %02zX", floor_enemy_filename.c_str(), floor);
}
} else if (log) {
log->info("Enemies map %s for floor %02zX cannot be used; skipping", floor_enemy_filename.c_str(), floor);
}
} else if (log) {
log->info("No enemies to load for floor %02zX", floor);
}
const auto& floor_object_filename = object_filenames.at(floor);
if (!floor_object_filename.empty()) {
auto map_data = get_file_data(version, floor_object_filename);
if (map_data) {
map->add_objects_from_map_data(floor, map_data);
if (log) {
log->info("Loaded objects map %s for floor %02zX", floor_object_filename.c_str(), floor);
}
} else if (log) {
log->info("Objects map %s for floor %02zX cannot be used; skipping", floor_object_filename.c_str(), floor);
}
} else if (log) {
log->info("No objects to load for floor %02zX", floor);
}
const auto& floor_event_filename = event_filenames.at(floor);
if (!floor_event_filename.empty()) {
auto map_data = get_file_data(version, floor_event_filename);
if (map_data) {
map->add_events_from_map_data(floor, map_data->data(), map_data->size());
if (log) {
log->info("Loaded events map %s for floor %02zX", floor_event_filename.c_str(), floor);
}
} else if (log) {
log->info("Events map %s for floor %02zX cannot be used; skipping", floor_event_filename.c_str(), floor);
}
} else if (log) {
log->info("No events to load for floor %02zX", floor);
uint16_t Lobby::quest_version_flags() const {
uint16_t ret = 0;
for (auto lc : this->clients) {
if (lc) {
ret |= (1 << static_cast<size_t>(lc->version()));
}
}
return map;
return ret;
}
void Lobby::load_maps() {
auto rare_rates = ((this->base_version == Version::BB_V4) && this->rare_enemy_rates)
? this->rare_enemy_rates
: Map::DEFAULT_RARE_ENEMIES;
auto rare_rates = this->rare_enemy_rates ? this->rare_enemy_rates : MapState::DEFAULT_RARE_ENEMIES;
if (this->quest) {
auto leader_c = this->clients.at(this->leader_id);
if (!leader_c) {
throw logic_error("lobby leader is missing");
}
auto vq = this->quest->version(this->base_version, leader_c->language());
if (!vq->dat_contents_decompressed) {
throw runtime_error("quest does not have DAT data");
}
this->map = this->load_maps(
this->base_version,
this->episode,
if (this->episode == Episode::EP3) {
this->map_state = make_shared<MapState>();
} else if (this->quest) {
this->map_state = make_shared<MapState>(
this->lobby_id,
this->difficulty,
this->event,
this->lobby_id,
rare_rates,
this->random_seed,
this->rare_enemy_rates,
this->opt_rand_crypt,
vq->dat_contents_decompressed);
} else if (this->mode != GameMode::CHALLENGE) {
auto s = this->require_server_state();
this->map = this->load_maps(
this->base_version,
this->episode,
this->mode,
this->difficulty,
this->event,
this->lobby_id,
s->set_data_table(this->base_version, this->episode, this->mode, this->difficulty),
bind(&ServerState::load_map_file, s.get(), placeholders::_1, placeholders::_2),
rare_rates,
this->random_seed,
this->opt_rand_crypt,
this->variations,
&this->log);
this->quest->get_supermap(this->random_seed));
} else {
this->map = make_shared<Map>(this->base_version, this->lobby_id, this->random_seed, this->opt_rand_crypt);
auto s = this->require_server_state();
this->map_state = make_shared<MapState>(
this->lobby_id,
this->difficulty,
this->event,
this->random_seed,
this->rare_enemy_rates,
this->opt_rand_crypt,
s->supermaps_for_variations(this->episode, this->mode, this->difficulty, this->variations));
}
this->log.info("Generated objects list (%zu entries):", this->map->objects.size());
for (size_t z = 0; z < this->map->objects.size(); z++) {
string o_str = this->map->objects[z].str();
this->log.info("(K-%zX) %s", z, o_str.c_str());
if (this->check_flag(Lobby::Flag::DEBUG)) {
this->log.info("Generated map state:");
this->map_state->print(stderr);
}
this->log.info("Generated enemies list (%zu entries):", this->map->enemies.size());
for (size_t z = 0; z < this->map->enemies.size(); z++) {
string e_str = this->map->enemies[z].str();
this->log.info("(E-%zX) %s", z, e_str.c_str());
}
[[nodiscard]] bool Lobby::is_ep3_nte() const {
for (const auto& lc : this->clients) {
if (lc && (lc->version() != Version::GC_EP3_NTE)) {
return false;
}
}
this->log.info("Generated events list (%zu entries):", this->map->events.size());
for (size_t z = 0; z < this->map->events.size(); z++) {
string e_str = this->map->events[z].str();
this->log.info("%s", e_str.c_str());
}
this->log.info("Loaded maps contain %zu object entries and %zu enemy entries overall (%zu as rares)",
this->map->objects.size(), this->map->enemies.size(), this->map->rare_enemy_indexes.size());
return true;
}
void Lobby::create_ep3_server() {
@@ -494,7 +339,8 @@ void Lobby::create_ep3_server() {
this->log.info("Recreating Episode 3 server state");
}
auto tourn = this->tournament_match ? this->tournament_match->tournament.lock() : nullptr;
bool is_nte = this->base_version == Version::GC_EP3_NTE;
bool is_nte = this->is_ep3_nte();
Episode3::Server::Options options = {
.card_index = is_nte ? s->ep3_card_index_trial : s->ep3_card_index,
.map_index = s->ep3_map_index,
@@ -504,7 +350,7 @@ void Lobby::create_ep3_server() {
.tournament = tourn,
.trap_card_ids = s->ep3_trap_card_ids,
};
if (this->base_version == Version::GC_EP3_NTE) {
if (is_nte) {
options.behavior_flags |= Episode3::BehaviorFlag::IS_TRIAL_EDITION;
} else {
options.behavior_flags &= (~Episode3::BehaviorFlag::IS_TRIAL_EDITION);
@@ -520,7 +366,7 @@ void Lobby::reassign_leader_on_client_departure(size_t leaving_client_index) {
}
if (this->clients[x]) {
this->leader_id = x;
this->change_section_id();
this->create_item_creator();
return;
}
}
@@ -611,7 +457,7 @@ void Lobby::add_client(shared_ptr<Client> c, ssize_t required_client_id) {
}
if (leader_index >= this->max_clients) {
this->leader_id = c->lobby_client_id;
this->change_section_id();
this->create_item_creator();
}
// If this is a lobby or no one was here before this, reassign all the floor
@@ -850,9 +696,15 @@ shared_ptr<Lobby::FloorItem> Lobby::find_item(uint8_t floor, uint32_t item_id) c
return this->floor_item_managers.at(floor).find(item_id);
}
void Lobby::add_item(uint8_t floor, const ItemData& data, float x, float z, uint16_t flags) {
void Lobby::add_item(
uint8_t floor,
const ItemData& data,
const VectorXZF& pos,
std::shared_ptr<const MapState::ObjectState> from_obj,
std::shared_ptr<const MapState::EnemyState> from_ene,
uint16_t flags) {
auto& m = this->floor_item_managers.at(floor);
m.add(data, x, z, flags);
m.add(data, pos, from_obj, from_ene, flags);
this->evict_items_from_floor(floor);
}