abstract supermap construction across entity types

This commit is contained in:
Martin Michelsen
2025-02-10 22:44:13 -08:00
parent a3428d33ae
commit 17fe80cf85
3 changed files with 220 additions and 119 deletions
+168 -102
View File
@@ -1745,8 +1745,12 @@ string MapFile::disassemble() const {
////////////////////////////////////////////////////////////////////////////////
// Super map
string SuperMap::Object::id_str() const {
return phosg::string_printf("KS-%02hhX-%03zX", this->floor, this->super_id);
}
string SuperMap::Object::str() const {
string ret = phosg::string_printf("[Object KS-%02hhX-%03zX", this->floor, this->super_id);
string ret = "[Object " + this->id_str();
for (Version v : ALL_ARPG_SEMANTIC_VERSIONS) {
const auto& def = this->version(v);
if (def.relative_object_index != 0xFFFF) {
@@ -1759,6 +1763,10 @@ string SuperMap::Object::str() const {
return ret;
}
string SuperMap::Enemy::id_str() const {
return phosg::string_printf("ES-%02hhX-%03zX-%03zX", this->floor, this->super_set_id, this->super_id);
}
string SuperMap::Enemy::str() const {
string ret = phosg::string_printf("[Enemy ES-%02hhX-%03zX-%03zX type=%s child_index=%hX alias_enemy_index_delta=%hX is_default_rare_v123=%s is_default_rare_bb=%s",
this->floor,
@@ -1785,8 +1793,12 @@ string SuperMap::Enemy::str() const {
return ret;
}
string SuperMap::Event::id_str() const {
return phosg::string_printf("WS-%02hhX-%03zX", this->floor, this->super_id);
}
string SuperMap::Event::str() const {
string ret = phosg::string_printf("[Event WS-%02hhX-%03zX", this->floor, this->super_id);
string ret = "[Event " + this->id_str();
for (Version v : ALL_ARPG_SEMANTIC_VERSIONS) {
const auto& def = this->version(v);
if (def.relative_event_index != 0xFFFF) {
@@ -1864,6 +1876,10 @@ void SuperMap::link_object_version(std::shared_ptr<Object> obj, Version version,
entities.objects.emplace_back(obj);
// Add to semantic hash index
uint64_t semantic_hash = set_entry->semantic_hash();
this->objects_for_semantic_hash[semantic_hash].emplace_back(obj);
// Add to room/group index
uint64_t k = room_index_key(obj->floor, set_entry->room, set_entry->group);
entities.object_for_floor_room_and_group.emplace(k, obj);
@@ -2380,6 +2396,10 @@ void SuperMap::link_enemy_version_and_children(
entities.enemies.emplace_back(ene);
if (ene->child_index == 0) {
entities.enemy_sets.emplace_back(ene);
// Add to semantic hash index (but only for the root ene)
uint64_t semantic_hash = set_entry->semantic_hash();
this->enemy_sets_for_semantic_hash[semantic_hash].emplace_back(ene);
}
// Add to room/group index
@@ -2470,6 +2490,11 @@ void SuperMap::link_event_version(
entities.events.emplace_back(ev);
// Add to semantic hash index
uint64_t semantic_hash = entry->semantic_hash();
this->events_for_semantic_hash[semantic_hash].emplace_back(ev);
// Add to room index
uint64_t k = room_index_key(ev->floor, entry->room, entry->wave_number);
entities.event_for_floor_room_and_wave_number.emplace(k, ev);
k = (static_cast<uint64_t>(ev->floor) << 32) | entry->event_id;
@@ -2616,6 +2641,35 @@ vector<EditAction> compute_edit_path(
return reverse_path;
}
template <typename EntityT>
vector<shared_ptr<EntityT>> compute_prev_entities(
const vector<shared_ptr<EntityT>>& existing_prev_entities,
size_t prev_entities_offset,
const vector<EditAction>& edit_path) {
vector<shared_ptr<EntityT>> ret;
for (auto action : edit_path) {
switch (action) {
case EditAction::ADD:
// This object doesn't match any object from the previous version
ret.emplace_back(nullptr);
break;
case EditAction::DELETE:
// There is an object in the previous version that doesn't match any in this version; skip it
prev_entities_offset++;
break;
case EditAction::EDIT: {
// The current object in this_sf matches the current object in prev_sf; link them together
ret.emplace_back(existing_prev_entities.at(prev_entities_offset));
prev_entities_offset++;
break;
}
default:
throw logic_error("invalid edit path action");
}
}
return ret;
}
static double object_set_add_cost(const MapFile::ObjectSetEntry&) {
return 100.0;
}
@@ -2710,6 +2764,37 @@ void SuperMap::add_map_file(Version this_v, shared_ptr<const MapFile> this_map_f
}
for (uint8_t floor = 0; floor < 0x12; floor++) {
auto link_or_add_entities = [this_v, floor]<typename EntityT, typename EntryT>(
const EntryT* prev_sets,
size_t prev_set_count,
const EntryT* this_sets,
size_t this_set_count,
double (*add_cost)(const EntryT&),
double (*delete_cost)(const EntryT&),
double (*edit_cost)(const EntryT&, const EntryT& current),
const vector<EntityT>& prev_entities,
size_t prev_entities_start_index,
auto&& link_existing,
auto&& add_new) {
auto edit_path = compute_edit_path(
prev_sets, prev_set_count, this_sets, this_set_count, add_cost, delete_cost, edit_cost);
auto used_prev_entities = compute_prev_entities(prev_entities, prev_entities_start_index, edit_path);
if (used_prev_entities.size() != this_set_count) {
throw std::logic_error("incorrect previous entity list length");
}
// TODO; // Use semantic hash index to fill in the gaps
for (size_t z = 0; z < this_set_count; z++) {
auto& prev_ent = used_prev_entities[z];
if (prev_ent) {
link_existing(prev_ent, this_v, this_sets + z);
} else {
add_new(this_v, floor, this_sets + z);
}
}
};
this_entities.object_floor_start_indexes[floor] = this_entities.objects.size();
this_entities.enemy_floor_start_indexes[floor] = this_entities.enemies.size();
this_entities.enemy_set_floor_start_indexes[floor] = this_entities.enemy_sets.size();
@@ -2726,42 +2811,20 @@ void SuperMap::add_map_file(Version this_v, shared_ptr<const MapFile> this_map_f
} else if (this_sf.object_sets) {
const auto& prev_sf = prev_map_file->floor(floor);
auto edit_path = compute_edit_path(
auto& prev_entities = this->version(prev_v);
link_or_add_entities(
prev_sf.object_sets,
prev_sf.object_set_count,
this_sf.object_sets,
this_sf.object_set_count,
object_set_add_cost,
object_set_delete_cost,
object_set_edit_cost);
auto& prev_entities = this->version(prev_v);
size_t prev_entities_offset = prev_entities.object_floor_start_indexes.at(floor);
size_t this_sf_offset = 0;
for (auto action : edit_path) {
switch (action) {
case EditAction::ADD:
// This object doesn't match any object from the previous version; create a new object
this->add_object(this_v, floor, this_sf.object_sets + this_sf_offset);
this_sf_offset++;
break;
case EditAction::DELETE:
// There is an object in the previous version that doesn't match any in this version; skip it
prev_entities_offset++;
break;
case EditAction::EDIT: {
// The current object in this_sf matches the current object in prev_sf; link them together
auto prev_obj = prev_entities.objects.at(prev_entities_offset);
this->link_object_version(prev_obj, this_v, this_sf.object_sets + this_sf_offset);
prev_entities_offset++;
this_sf_offset++;
break;
}
default:
throw logic_error("invalid edit path action");
}
}
object_set_edit_cost,
prev_entities.objects,
prev_entities.object_floor_start_indexes.at(floor),
bind(&SuperMap::link_object_version, this, placeholders::_1, placeholders::_2, placeholders::_3),
bind(&SuperMap::add_object, this, placeholders::_1, placeholders::_2, placeholders::_3));
}
if (!prev_map_file || !prev_map_file->floor(floor).enemy_sets) {
@@ -2770,42 +2833,20 @@ void SuperMap::add_map_file(Version this_v, shared_ptr<const MapFile> this_map_f
}
} else if (this_sf.enemy_sets) {
const auto& prev_sf = prev_map_file->floor(floor);
auto edit_path = compute_edit_path(
auto& prev_entities = this->version(prev_v);
link_or_add_entities(
prev_sf.enemy_sets,
prev_sf.enemy_set_count,
this_sf.enemy_sets,
this_sf.enemy_set_count,
enemy_set_add_cost,
enemy_set_delete_cost,
enemy_set_edit_cost);
auto& prev_entities = this->version(prev_v);
size_t prev_entities_offset = prev_entities.enemy_set_floor_start_indexes.at(floor);
size_t this_sf_offset = 0;
for (auto action : edit_path) {
switch (action) {
case EditAction::ADD:
// This object doesn't match any object from the previous version; create a new object
this->add_enemy_and_children(this_v, floor, this_sf.enemy_sets + this_sf_offset);
this_sf_offset++;
break;
case EditAction::DELETE:
// There is an object in the previous version that doesn't match any in this version; skip it
prev_entities_offset++;
break;
case EditAction::EDIT: {
// The current object in this_sf matches the current object in prev_sf; link them together
auto prev_ene = prev_entities.enemy_sets.at(prev_entities_offset);
this->link_enemy_version_and_children(prev_ene, this_v, this_sf.enemy_sets + this_sf_offset);
prev_entities_offset++;
this_sf_offset++;
break;
}
default:
throw logic_error("invalid edit path action");
}
}
enemy_set_edit_cost,
prev_entities.enemy_sets,
prev_entities.enemy_set_floor_start_indexes.at(floor),
bind(&SuperMap::link_enemy_version_and_children, this, placeholders::_1, placeholders::_2, placeholders::_3),
bind(&SuperMap::add_enemy_and_children, this, placeholders::_1, placeholders::_2, placeholders::_3));
}
if (!prev_map_file || !prev_map_file->floor(floor).events1) {
@@ -2814,52 +2855,20 @@ void SuperMap::add_map_file(Version this_v, shared_ptr<const MapFile> this_map_f
}
} else if (this_sf.events1) {
const auto& prev_sf = prev_map_file->floor(floor);
auto edit_path = compute_edit_path(
auto& prev_entities = this->version(prev_v);
link_or_add_entities(
prev_sf.events1,
prev_sf.event_count,
this_sf.events1,
this_sf.event_count,
event_add_cost,
event_delete_cost,
event_edit_cost);
auto& prev_entities = this->version(prev_v);
size_t prev_entities_offset = prev_entities.event_floor_start_indexes.at(floor);
size_t this_sf_offset = 0;
for (auto action : edit_path) {
switch (action) {
case EditAction::ADD:
// This object doesn't match any object from the previous version; try to look it up in the semantic hash index
this->add_event(
this_v,
floor,
this_sf.events1 + this_sf_offset,
this_sf.event_action_stream,
this_sf.event_action_stream_bytes);
this_sf_offset++;
break;
case EditAction::DELETE:
// There is an object in the previous version that doesn't match any in this version; skip it
prev_entities_offset++;
break;
case EditAction::EDIT: {
// The current object in this_sf matches the current object in prev_sf; link them together
auto prev_ev = prev_entities.events.at(prev_entities_offset);
this->link_event_version(
prev_ev,
this_v,
this_sf.events1 + this_sf_offset,
this_sf.event_action_stream,
this_sf.event_action_stream_bytes);
prev_entities_offset++;
this_sf_offset++;
break;
}
default:
throw logic_error("invalid edit path action");
}
}
event_edit_cost,
prev_entities.events,
prev_entities.event_floor_start_indexes.at(floor),
bind(&SuperMap::link_event_version, this, placeholders::_1, placeholders::_2, placeholders::_3, this_sf.event_action_stream, this_sf.event_action_stream_bytes),
bind(&SuperMap::add_event, this, placeholders::_1, placeholders::_2, placeholders::_3, this_sf.event_action_stream, this_sf.event_action_stream_bytes));
}
}
}
@@ -2970,6 +2979,63 @@ vector<shared_ptr<const SuperMap::Event>> SuperMap::events_for_floor_room_wave(
return ret;
}
SuperMap::EfficiencyStats& SuperMap::EfficiencyStats::operator+=(const EfficiencyStats& other) {
this->filled_object_slots += other.filled_object_slots;
this->total_object_slots += other.total_object_slots;
this->filled_enemy_set_slots += other.filled_enemy_set_slots;
this->total_enemy_set_slots += other.total_enemy_set_slots;
this->filled_event_slots += other.filled_event_slots;
this->total_event_slots += other.total_event_slots;
return *this;
}
std::string SuperMap::EfficiencyStats::str() const {
return phosg::string_printf(
"EfficiencyStats[K = %zu/%zu (%lg%%), E = %zu/%zu (%lg%%), W = %zu/%zu (%g%%)]",
this->filled_object_slots, this->total_object_slots,
static_cast<double>(this->filled_object_slots * 100) / static_cast<double>(this->total_object_slots),
this->filled_enemy_set_slots, this->total_enemy_set_slots,
static_cast<double>(this->filled_enemy_set_slots * 100) / static_cast<double>(this->total_enemy_set_slots),
this->filled_event_slots, this->total_event_slots,
static_cast<double>(this->filled_event_slots * 100) / static_cast<double>(this->total_event_slots));
}
SuperMap::EfficiencyStats SuperMap::efficiency() const {
EfficiencyStats ret;
for (const auto& obj : this->objects) {
for (Version v : ALL_ARPG_SEMANTIC_VERSIONS) {
const auto& obj_ver = obj->version(v);
if (obj_ver.relative_object_index != 0xFFFF) {
ret.filled_object_slots++;
}
}
}
ret.total_object_slots = this->objects.size() * ALL_ARPG_SEMANTIC_VERSIONS.size();
for (const auto& ene : this->enemy_sets) {
for (Version v : ALL_ARPG_SEMANTIC_VERSIONS) {
const auto& ene_ver = ene->version(v);
if (ene_ver.relative_enemy_index != 0xFFFF) {
ret.filled_enemy_set_slots++;
}
}
}
ret.total_enemy_set_slots = this->enemy_sets.size() * ALL_ARPG_SEMANTIC_VERSIONS.size();
for (const auto& ev : this->events) {
for (Version v : ALL_ARPG_SEMANTIC_VERSIONS) {
const auto& ev_ver = ev->version(v);
if (ev_ver.relative_event_index != 0xFFFF) {
ret.filled_event_slots++;
}
}
}
ret.total_event_slots = this->events.size() * ALL_ARPG_SEMANTIC_VERSIONS.size();
return ret;
}
void SuperMap::verify() const {
for (size_t super_id = 0; super_id < this->objects.size(); super_id++) {
if (this->objects[super_id]->super_id != super_id) {