From c88f4184eaa48efbdc69e76eb3c8a4a206434ad3 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Sat, 16 Aug 2025 14:52:24 -0400 Subject: Started writing a data validator Currently, it can check whether identifiers point to non-existent objects, or whether multiple objects share the same identifier. It can also determine whether an identifier is underspecified (e.g. a door doesn't specify a room, or a global connection doesn't specify a map). --- CMakeLists.txt | 1 + data/connections.txtpb | 20 +- data/ids.txtpb | 20 ++ data/maps/four_rooms/doors.txtpb | 8 +- data/maps/four_rooms/rooms/Examples Room.txtpb | 8 +- data/maps/the_butterfly/doors.txtpb | 2 +- data/maps/the_double_sided/connections.txtpb | 2 +- data/maps/the_great/rooms/Main Area.txtpb | 7 + tools/util/CMakeLists.txt | 5 + tools/util/identifiers.cpp | 102 ++++++ tools/util/identifiers.h | 85 +++++ tools/util/naming.cpp | 4 + tools/util/naming.h | 4 + tools/validator/CMakeLists.txt | 11 + tools/validator/human_processor.cpp | 425 +++++++++++++++++++++++++ tools/validator/human_processor.h | 14 + tools/validator/main.cpp | 31 ++ tools/validator/structs.h | 97 ++++++ tools/validator/validator.cpp | 250 +++++++++++++++ tools/validator/validator.h | 12 + 20 files changed, 1080 insertions(+), 28 deletions(-) create mode 100644 tools/util/identifiers.cpp create mode 100644 tools/util/identifiers.h create mode 100644 tools/validator/CMakeLists.txt create mode 100644 tools/validator/human_processor.cpp create mode 100644 tools/validator/human_processor.h create mode 100644 tools/validator/main.cpp create mode 100644 tools/validator/structs.h create mode 100644 tools/validator/validator.cpp create mode 100644 tools/validator/validator.h diff --git a/CMakeLists.txt b/CMakeLists.txt index f15a970..e76d0db 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,3 +6,4 @@ add_subdirectory(proto) add_subdirectory(tools/util) add_subdirectory(tools/assign_ids) add_subdirectory(tools/datapacker) +add_subdirectory(tools/validator) diff --git a/data/connections.txtpb b/data/connections.txtpb index 3cfbe6a..9d72467 100644 --- a/data/connections.txtpb +++ b/data/connections.txtpb @@ -1,4 +1,5 @@ # TODO +# the_entry/Starting Room/HI/thewords # the_entry/Lime Room/DAEDALUS # the_entry/Lime Room/REVITALIZED # the_entry/Shop Entrance/SHOP @@ -36,23 +37,6 @@ # the_hive/Main Area/DAED3 # the_impressive/Green Eye/PLAZA # the_liberated/Painting Room/PYRAMID -connections { - from { - panel { - map: "the_entry" - room: "Starting Room" - name: "HI" - answer: "thewords" - } - } - to { - room { - map: "the_words" - name: "Entrance" - } - } - oneway: true -} connections { from { port { @@ -171,7 +155,7 @@ connections { from { painting { map: "the_bearer" - room: "Butterfly" + room: "Butterfly Room" name: "BUTTERFLY" } } diff --git a/data/ids.txtpb b/data/ids.txtpb index 7d1d9d0..49e035a 100644 --- a/data/ids.txtpb +++ b/data/ids.txtpb @@ -36,10 +36,26 @@ maps { key: "EMOTION" value: 265 } + panels { + key: "EMOTION (1)" + value: 738 + } + panels { + key: "EMOTION (2)" + value: 740 + } panels { key: "SIZE" value: 264 } + panels { + key: "SIZE (1)" + value: 737 + } + panels { + key: "SIZE (2)" + value: 739 + } panels { key: "SONNET" value: 267 @@ -2739,6 +2755,10 @@ maps { key: "COLOR" value: 518 } + panels { + key: "CURT" + value: 736 + } panels { key: "DEW" value: 505 diff --git a/data/maps/four_rooms/doors.txtpb b/data/maps/four_rooms/doors.txtpb index 46d0abe..f0bf060 100644 --- a/data/maps/four_rooms/doors.txtpb +++ b/data/maps/four_rooms/doors.txtpb @@ -16,10 +16,10 @@ doors { name: "Examples Door" type: STANDARD receivers: "Components/Doors/entry_2" - panels { room: "Examples Room" name: "SIZE" } - panels { room: "Examples Room" name: "EMOTION" } - panels { room: "Examples Room" name: "SIZE" } - panels { room: "Examples Room" name: "EMOTION" } + panels { room: "Examples Room" name: "SIZE (1)" } + panels { room: "Examples Room" name: "EMOTION (1)" } + panels { room: "Examples Room" name: "SIZE (2)" } + panels { room: "Examples Room" name: "EMOTION (2)" } panels { room: "Examples Room" name: "SUPERLATIVE" } panels { room: "Examples Room" name: "SONNET" } panels { room: "Examples Room" name: "URN" } diff --git a/data/maps/four_rooms/rooms/Examples Room.txtpb b/data/maps/four_rooms/rooms/Examples Room.txtpb index ffbe74d..bec3a23 100644 --- a/data/maps/four_rooms/rooms/Examples Room.txtpb +++ b/data/maps/four_rooms/rooms/Examples Room.txtpb @@ -1,28 +1,28 @@ name: "Examples Room" display_name: "Examples Room" panels { - name: "SIZE" + name: "SIZE (1)" path: "Panels/Room 2 Examples/entry_1" clue: "size" answer: "tiny" symbols: "example" } panels { - name: "EMOTION" + name: "EMOTION (1)" path: "Panels/Room 2 Examples/entry_2" clue: "emotion" answer: "love" symbols: "example" } panels { - name: "SIZE" + name: "SIZE (2)" path: "Panels/Room 2 Examples/entry_3" clue: "size" answer: "huge" symbols: "example" } panels { - name: "EMOTION" + name: "EMOTION (2)" path: "Panels/Room 2 Examples/entry_4" clue: "emotion" answer: "fear" diff --git a/data/maps/the_butterfly/doors.txtpb b/data/maps/the_butterfly/doors.txtpb index 1994d2f..987c269 100644 --- a/data/maps/the_butterfly/doors.txtpb +++ b/data/maps/the_butterfly/doors.txtpb @@ -4,7 +4,7 @@ doors { type: EVENT panels { room: "Main Area" name: "SPECIES" } panels { room: "Main Area" name: "PERSONALITY" } - panels { room: "Main Area" name: "CONSTITUION" } + panels { room: "Main Area" name: "CONSTITUTION" } panels { room: "Main Area" name: "GAME" } panels { room: "Main Area" name: "SCIENCE" } panels { room: "Main Area" name: "SCHOOL" } diff --git a/data/maps/the_double_sided/connections.txtpb b/data/maps/the_double_sided/connections.txtpb index c21049d..3c404e2 100644 --- a/data/maps/the_double_sided/connections.txtpb +++ b/data/maps/the_double_sided/connections.txtpb @@ -94,7 +94,7 @@ connections { door { name: "Obverse Black/Pink Door" } } connections { - from_room: "Flipped Yellow Area" + from_room: "Flipped Yellow Front Area" to_room: "Flipped Black Area" door { name: "Flipped Yellow/Black Door" } } diff --git a/data/maps/the_great/rooms/Main Area.txtpb b/data/maps/the_great/rooms/Main Area.txtpb index a70b011..b014b16 100644 --- a/data/maps/the_great/rooms/Main Area.txtpb +++ b/data/maps/the_great/rooms/Main Area.txtpb @@ -119,6 +119,13 @@ panels { symbols: "sun" symbols: "zero" } +panels { + name: "CURT" + path: "Panels/Maze/entry_1" + clue: "curt" + answer: "court" + symbols: "sparkles" +} ports { name: "ENTRY" path: "Components/Warps/worldport" diff --git a/tools/util/CMakeLists.txt b/tools/util/CMakeLists.txt index 8eb8d3b..f086e10 100644 --- a/tools/util/CMakeLists.txt +++ b/tools/util/CMakeLists.txt @@ -1,5 +1,10 @@ +find_package(Protobuf REQUIRED) + add_library(util + identifiers.cpp naming.cpp ) set_property(TARGET util PROPERTY CXX_STANDARD 20) set_property(TARGET util PROPERTY CXX_STANDARD_REQUIRED ON) +target_include_directories(util PUBLIC ${CMAKE_BINARY_DIR}) +target_link_libraries(util PUBLIC protos protobuf::libprotobuf) diff --git a/tools/util/identifiers.cpp b/tools/util/identifiers.cpp new file mode 100644 index 0000000..5b51c57 --- /dev/null +++ b/tools/util/identifiers.cpp @@ -0,0 +1,102 @@ +#include "identifiers.h" + +#include + +#include "proto/human.pb.h" + +namespace com::fourisland::lingo2_archipelago { + +std::optional GetCompleteRoomIdentifier( + RoomIdentifier identifier, std::optional map_name) { + if (!identifier.has_map()) { + if (!map_name) { + return std::nullopt; + } + identifier.set_map(*map_name); + } + return identifier; +} + +std::optional GetCompleteDoorIdentifier( + DoorIdentifier identifier, std::optional map_name) { + if (!identifier.has_map()) { + if (!map_name) { + return std::nullopt; + } + identifier.set_map(*map_name); + } + return identifier; +} + +std::optional GetCompletePortIdentifier( + PortIdentifier identifier, std::optional map_name, + std::optional room_name) { + if (!identifier.has_map()) { + if (!map_name) { + return std::nullopt; + } + identifier.set_map(*map_name); + } + if (!identifier.has_room()) { + if (!room_name) { + return std::nullopt; + } + identifier.set_room(*room_name); + } + return identifier; +} + +std::optional GetCompletePaintingIdentifier( + PaintingIdentifier identifier, std::optional map_name, + std::optional room_name) { + if (!identifier.has_map()) { + if (!map_name) { + return std::nullopt; + } + identifier.set_map(*map_name); + } + if (!identifier.has_room()) { + if (!room_name) { + return std::nullopt; + } + identifier.set_room(*room_name); + } + return identifier; +} + +std::optional GetCompletePanelIdentifierWithoutAnswer( + PanelIdentifier identifier, std::optional map_name, + std::optional room_name) { + if (!identifier.has_map()) { + if (!map_name) { + return std::nullopt; + } + identifier.set_map(*map_name); + } + if (!identifier.has_room()) { + if (!room_name) { + return std::nullopt; + } + identifier.set_room(*room_name); + } + identifier.clear_answer(); + return identifier; +} + +std::optional GetCompleteKeyholderIdentifierWithoutKey( + KeyholderIdentifier identifier, const std::string& map_name, + std::optional room_name) { + if (!identifier.has_map()) { + identifier.set_map(map_name); + } + if (!identifier.has_room()) { + if (!room_name) { + return std::nullopt; + } + identifier.set_room(*room_name); + } + identifier.clear_key(); + return identifier; +} + +} // namespace com::fourisland::lingo2_archipelago diff --git a/tools/util/identifiers.h b/tools/util/identifiers.h new file mode 100644 index 0000000..341dee1 --- /dev/null +++ b/tools/util/identifiers.h @@ -0,0 +1,85 @@ +#ifndef TOOLS_UTIL_IDENTIFIERS_H_ +#define TOOLS_UTIL_IDENTIFIERS_H_ + +#include +#include +#include + +#include "proto/human.pb.h" + +namespace com::fourisland::lingo2_archipelago { + +class RoomIdentifierLess { + public: + bool operator()(const RoomIdentifier& lhs, const RoomIdentifier& rhs) const { + return std::tie(lhs.map(), lhs.name()) < std::tie(rhs.map(), rhs.name()); + } +}; + +class DoorIdentifierLess { + public: + bool operator()(const DoorIdentifier& lhs, const DoorIdentifier& rhs) const { + return std::tie(lhs.map(), lhs.name()) < std::tie(rhs.map(), rhs.name()); + } +}; + +class PortIdentifierLess { + public: + bool operator()(const PortIdentifier& lhs, const PortIdentifier& rhs) const { + return std::tie(lhs.map(), lhs.room(), lhs.name()) < + std::tie(rhs.map(), rhs.room(), rhs.name()); + } +}; + +class PaintingIdentifierLess { + public: + bool operator()(const PaintingIdentifier& lhs, + const PaintingIdentifier& rhs) const { + return std::tie(lhs.map(), lhs.room(), lhs.name()) < + std::tie(rhs.map(), rhs.room(), rhs.name()); + } +}; + +class PanelIdentifierLess { + public: + bool operator()(const PanelIdentifier& lhs, + const PanelIdentifier& rhs) const { + return std::tie(lhs.map(), lhs.room(), lhs.name(), lhs.answer()) < + std::tie(rhs.map(), rhs.room(), rhs.name(), rhs.answer()); + } +}; + +class KeyholderIdentifierLess { + public: + bool operator()(const KeyholderIdentifier& lhs, + const KeyholderIdentifier& rhs) const { + return std::tie(lhs.map(), lhs.room(), lhs.name(), lhs.key()) < + std::tie(rhs.map(), rhs.room(), rhs.name(), rhs.key()); + } +}; + +std::optional GetCompleteRoomIdentifier( + RoomIdentifier identifier, std::optional map_name); + +std::optional GetCompleteDoorIdentifier( + DoorIdentifier identifier, std::optional map_name); + +std::optional GetCompletePortIdentifier( + PortIdentifier identifier, std::optional map_name, + std::optional room_name); + +std::optional GetCompletePaintingIdentifier( + PaintingIdentifier identifier, std::optional map_name, + std::optional room_name); + +std::optional GetCompletePanelIdentifierWithoutAnswer( + PanelIdentifier identifier, std::optional map_name, + std::optional room_name); + +std::optional GetCompleteKeyholderIdentifierWithoutKey( + KeyholderIdentifier identifier, const std::string& map_name, + std::optional room_name); + +} // namespace com::fourisland::lingo2_archipelago + +#endif /* TOOLS_UTIL_IDENTIFIERS_H_ */ diff --git a/tools/util/naming.cpp b/tools/util/naming.cpp index 0ae99f6..8229c6d 100644 --- a/tools/util/naming.cpp +++ b/tools/util/naming.cpp @@ -2,6 +2,8 @@ #include +namespace com::fourisland::lingo2_archipelago { + std::string GetLetterName(std::string key, bool level2) { std::ostringstream lettername_s; lettername_s << key; @@ -9,3 +11,5 @@ std::string GetLetterName(std::string key, bool level2) { return lettername_s.str(); } + +} // namespace com::fourisland::lingo2_archipelago diff --git a/tools/util/naming.h b/tools/util/naming.h index 9a68851..85e2db0 100644 --- a/tools/util/naming.h +++ b/tools/util/naming.h @@ -3,6 +3,10 @@ #include +namespace com::fourisland::lingo2_archipelago { + std::string GetLetterName(std::string key, bool level2); +} // namespace com::fourisland::lingo2_archipelago + #endif /* TOOLS_UTIL_NAMING_H_ */ diff --git a/tools/validator/CMakeLists.txt b/tools/validator/CMakeLists.txt new file mode 100644 index 0000000..0ad58c2 --- /dev/null +++ b/tools/validator/CMakeLists.txt @@ -0,0 +1,11 @@ +find_package(Protobuf REQUIRED) + +add_executable(validator + human_processor.cpp + main.cpp + validator.cpp +) +set_property(TARGET validator PROPERTY CXX_STANDARD 20) +set_property(TARGET validator PROPERTY CXX_STANDARD_REQUIRED ON) +target_include_directories(validator PUBLIC ${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR}/tools) +target_link_libraries(validator PUBLIC protos util protobuf::libprotobuf) diff --git a/tools/validator/human_processor.cpp b/tools/validator/human_processor.cpp new file mode 100644 index 0000000..5382443 --- /dev/null +++ b/tools/validator/human_processor.cpp @@ -0,0 +1,425 @@ +#include "human_processor.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "structs.h" + +namespace com::fourisland::lingo2_archipelago { +namespace { + +template +T ReadMessageFromFile(const std::string& path) { + std::cout << "Processing " << path << std::endl; + + std::ifstream file(path); + std::stringstream buffer; + buffer << file.rdbuf(); + + T message; + google::protobuf::TextFormat::ParseFromString(buffer.str(), &message); + + return message; +} + +class HumanProcessor { + public: + HumanProcessor(const std::string& mapdir, CollectedInfo& info) + : mapdir_(mapdir), info_(info) {} + + void Run() { + std::filesystem::path datadir_path = mapdir_; + + ProcessConnectionsFile(datadir_path / "connections.txtpb", std::nullopt); + ProcessMaps(datadir_path); + ProcessIdsFile(datadir_path / "ids.txtpb"); + } + + private: + void ProcessMaps(std::filesystem::path path) { + std::filesystem::path maps_dir = path / "maps"; + for (auto const& dir_entry : + std::filesystem::directory_iterator(maps_dir)) { + ProcessMap(dir_entry.path()); + } + } + + void ProcessMap(std::filesystem::path path) { + std::string map_name = path.filename(); + + ProcessConnectionsFile(path / "connections.txtpb", map_name); + ProcessDoorsFile(path / "doors.txtpb", map_name); + ProcessRooms(path / "rooms", map_name); + } + + void ProcessRooms(std::filesystem::path path, + const std::string& current_map_name) { + for (auto const& dir_entry : std::filesystem::directory_iterator(path)) { + auto room = ReadMessageFromFile(dir_entry.path().string()); + ProcessRoom(room, current_map_name); + } + } + + void ProcessRoom(const HumanRoom& h_room, + const std::string& current_map_name) { + RoomIdentifier room_identifier; + room_identifier.set_map(current_map_name); + room_identifier.set_name(h_room.name()); + + RoomInfo& room_info = info_.rooms[room_identifier]; + room_info.definitions.push_back(h_room); + + for (const HumanPanel& h_panel : h_room.panels()) { + ProcessPanel(h_panel, current_map_name, h_room.name()); + } + + for (const HumanPainting& h_painting : h_room.paintings()) { + ProcessPainting(h_painting, current_map_name, h_room.name()); + } + + for (const HumanPort& h_port : h_room.ports()) { + ProcessPort(h_port, current_map_name, h_room.name()); + } + + for (const HumanLetter& h_letter : h_room.letters()) { + ProcessLetter(h_letter, current_map_name, h_room.name()); + } + + for (const HumanMastery& h_mastery : h_room.masteries()) { + ProcessMastery(h_mastery, current_map_name, h_room.name()); + } + + for (const HumanKeyholder& h_keyholder : h_room.keyholders()) { + ProcessKeyholder(h_keyholder, current_map_name, h_room.name()); + } + } + + void ProcessPanel(const HumanPanel& h_panel, + const std::string& current_map_name, + const std::string& current_room_name) { + PanelIdentifier panel_identifier; + panel_identifier.set_map(current_map_name); + panel_identifier.set_room(current_room_name); + panel_identifier.set_name(h_panel.name()); + + PanelInfo& panel_info = info_.panels[panel_identifier]; + panel_info.definitions.push_back(h_panel); + panel_info.proxies[h_panel.answer()].definitions.push_back(Proxy()); + + for (const Proxy& h_proxy : h_panel.proxies()) { + ProxyInfo& proxy_info = panel_info.proxies[h_proxy.answer()]; + proxy_info.definitions.push_back(h_proxy); + } + + if (h_panel.has_required_door()) { + DoorIdentifier required_door_identifier = + *GetCompleteDoorIdentifier(h_panel.required_door(), current_map_name); + DoorInfo& required_door_info = info_.doors[required_door_identifier]; + required_door_info.panels_referenced_by.push_back(panel_identifier); + } + + if (h_panel.has_required_room()) { + RoomIdentifier required_room_identifier = + *GetCompleteRoomIdentifier(h_panel.required_room(), current_map_name); + RoomInfo& required_room_info = info_.rooms[required_room_identifier]; + required_room_info.panels_referenced_by.push_back(panel_identifier); + } + } + + void ProcessPainting(const HumanPainting& h_painting, + const std::string& current_map_name, + const std::string& current_room_name) { + PaintingIdentifier painting_identifier; + painting_identifier.set_map(current_map_name); + painting_identifier.set_room(current_room_name); + painting_identifier.set_name(h_painting.name()); + + PaintingInfo& painting_info = info_.paintings[painting_identifier]; + painting_info.definitions.push_back(h_painting); + + if (h_painting.has_required_door()) { + DoorIdentifier required_door_identifier = *GetCompleteDoorIdentifier( + h_painting.required_door(), current_map_name); + DoorInfo& required_door_info = info_.doors[required_door_identifier]; + required_door_info.paintings_referenced_by.push_back(painting_identifier); + } + } + + void ProcessPort(const HumanPort& h_port, const std::string& current_map_name, + const std::string& current_room_name) { + PortIdentifier port_identifier; + port_identifier.set_map(current_map_name); + port_identifier.set_room(current_room_name); + port_identifier.set_name(h_port.name()); + + PortInfo& port_info = info_.ports[port_identifier]; + port_info.definitions.push_back(h_port); + + if (h_port.has_required_door()) { + DoorIdentifier required_door_identifier = + *GetCompleteDoorIdentifier(h_port.required_door(), current_map_name); + DoorInfo& required_door_info = info_.doors[required_door_identifier]; + required_door_info.ports_referenced_by.push_back(port_identifier); + } + } + + void ProcessLetter(const HumanLetter& h_letter, + const std::string& current_map_name, + const std::string& current_room_name) { + LetterIdentifier letter_identifier = + std::make_tuple(h_letter.key()[0], h_letter.level2()); + LetterInfo& letter_info = info_.letters[letter_identifier]; + + RoomIdentifier room_identifier; + room_identifier.set_map(current_map_name); + room_identifier.set_name(current_room_name); + letter_info.defined_in.push_back(room_identifier); + } + + void ProcessMastery(const HumanMastery& h_mastery, + const std::string& current_map_name, + const std::string& current_room_name) { + // Nothing really to validate about masteries yet. + } + + void ProcessKeyholder(const HumanKeyholder& h_keyholder, + const std::string& current_map_name, + const std::string& current_room_name) { + KeyholderIdentifier keyholder_identifier; + keyholder_identifier.set_map(current_map_name); + keyholder_identifier.set_room(current_room_name); + keyholder_identifier.set_name(h_keyholder.name()); + + KeyholderInfo& keyholder_info = info_.keyholders[keyholder_identifier]; + keyholder_info.definitions.push_back(h_keyholder); + } + + void ProcessDoorsFile(std::filesystem::path path, + const std::string& current_map_name) { + if (!std::filesystem::exists(path)) { + return; + } + + auto doors = ReadMessageFromFile(path.string()); + + for (const HumanDoor& door : doors.doors()) { + ProcessDoor(door, current_map_name); + } + } + + void ProcessDoor(const HumanDoor& h_door, + const std::string& current_map_name) { + DoorIdentifier door_identifier; + door_identifier.set_map(current_map_name); + door_identifier.set_name(h_door.name()); + + DoorInfo& door_info = info_.doors[door_identifier]; + door_info.definitions.push_back(h_door); + + if (h_door.has_location_room()) { + RoomIdentifier location_room_identifier; + location_room_identifier.set_map(current_map_name); + location_room_identifier.set_name(h_door.location_room()); + info_.rooms[location_room_identifier].doors_referenced_by.push_back( + door_identifier); + } + + for (const PaintingIdentifier& pi : h_door.move_paintings()) { + auto complete_painting_identifier = + GetCompletePaintingIdentifier(pi, current_map_name, std::nullopt); + if (complete_painting_identifier) { + PaintingInfo& move_painting_info = + info_.paintings[*complete_painting_identifier]; + move_painting_info.doors_referenced_by.push_back(door_identifier); + } else { + door_info.malformed_identifiers.paintings.push_back(pi); + } + } + + for (const PanelIdentifier& pi : h_door.panels()) { + auto complete_panel_identifier = GetCompletePanelIdentifierWithoutAnswer( + pi, current_map_name, std::nullopt); + if (complete_panel_identifier) { + PanelInfo& panel_info = info_.panels[*complete_panel_identifier]; + panel_info.doors_referenced_by.push_back(door_identifier); + + if (pi.has_answer()) { + panel_info.proxies[pi.answer()].doors_referenced_by.push_back( + door_identifier); + } + } else { + door_info.malformed_identifiers.panels.push_back(pi); + } + } + + for (const KeyholderIdentifier& ki : h_door.keyholders()) { + auto complete_keyholder_identifier = + GetCompleteKeyholderIdentifierWithoutKey(ki, current_map_name, + std::nullopt); + if (complete_keyholder_identifier) { + KeyholderInfo& keyholder_info = + info_.keyholders[*complete_keyholder_identifier]; + keyholder_info.doors_referenced_by.push_back(door_identifier); + } else { + door_info.malformed_identifiers.keyholders.push_back(ki); + } + } + + for (const RoomIdentifier& ri : h_door.rooms()) { + RoomIdentifier complete_room_identifier = + *GetCompleteRoomIdentifier(ri, current_map_name); + RoomInfo& room_info = info_.rooms[complete_room_identifier]; + room_info.doors_referenced_by.push_back(door_identifier); + } + + for (const DoorIdentifier& di : h_door.doors()) { + DoorIdentifier complete_door_identifier = + *GetCompleteDoorIdentifier(di, current_map_name); + DoorInfo& other_door_info = info_.doors[complete_door_identifier]; + other_door_info.doors_referenced_by.push_back(door_identifier); + } + } + + void ProcessConnectionsFile(std::filesystem::path path, + std::optional current_map_name) { + if (!std::filesystem::exists(path)) { + return; + } + + auto connections = ReadMessageFromFile(path.string()); + + for (const HumanConnection& connection : connections.connections()) { + ProcessConnection(connection, current_map_name); + } + } + + void ProcessConnection(const HumanConnection& human_connection, + const std::optional& current_map_name) { + if (human_connection.has_from_room()) { + if (current_map_name) { + RoomIdentifier room_identifier; + room_identifier.set_map(*current_map_name); + room_identifier.set_name(human_connection.from_room()); + + RoomInfo& room_info = info_.rooms[room_identifier]; + room_info.connections_referenced_by.push_back(human_connection); + } else { + // Not sure where else to store this right now. + std::cout << "A global connection used from_room." << std::endl; + } + } else if (human_connection.has_from()) { + ProcessSingleConnection(human_connection, human_connection.from(), + current_map_name); + } + + if (human_connection.has_to_room()) { + if (current_map_name) { + RoomIdentifier room_identifier; + room_identifier.set_map(*current_map_name); + room_identifier.set_name(human_connection.to_room()); + + RoomInfo& room_info = info_.rooms[room_identifier]; + room_info.connections_referenced_by.push_back(human_connection); + } else { + // Not sure where else to store this right now. + std::cout << "A global connection used to_room." << std::endl; + } + } else if (human_connection.has_to()) { + ProcessSingleConnection(human_connection, human_connection.to(), + current_map_name); + } + + if (human_connection.has_door()) { + auto door_identifier = + GetCompleteDoorIdentifier(human_connection.door(), current_map_name); + if (door_identifier) { + DoorInfo& door_info = info_.doors[*door_identifier]; + door_info.connections_referenced_by.push_back(human_connection); + } else { + // Not sure where else to store this right now. + std::cout + << "A connection used the following malformed door identifier: " + << human_connection.door().ShortDebugString() << std::endl; + } + } + } + + void ProcessSingleConnection( + const HumanConnection& human_connection, + const HumanConnection::Endpoint& endpoint, + const std::optional& current_map_name) { + if (endpoint.has_room()) { + auto room_identifier = + GetCompleteRoomIdentifier(endpoint.room(), current_map_name); + if (room_identifier) { + RoomInfo& room_info = info_.rooms[*room_identifier]; + room_info.connections_referenced_by.push_back(human_connection); + } else { + // Not sure where else to store this right now. + std::cout + << "A connection used the following malformed room identifier: " + << endpoint.room().ShortDebugString() << std::endl; + } + } else if (endpoint.has_painting()) { + auto painting_identifier = GetCompletePaintingIdentifier( + endpoint.painting(), current_map_name, std::nullopt); + if (painting_identifier) { + PaintingInfo& painting_info = info_.paintings[*painting_identifier]; + painting_info.connections_referenced_by.push_back(human_connection); + } else { + // Not sure where else to store this right now. + std::cout + << "A connection used the following malformed painting identifier: " + << endpoint.painting().ShortDebugString() << std::endl; + } + } else if (endpoint.has_port()) { + auto port_identifier = GetCompletePortIdentifier( + endpoint.port(), current_map_name, std::nullopt); + if (port_identifier) { + PortInfo& port_info = info_.ports[*port_identifier]; + port_info.connections_referenced_by.push_back(human_connection); + } else { + // Not sure where else to store this right now. + std::cout + << "A connection used the following malformed port identifier: " + << endpoint.port().ShortDebugString() << std::endl; + } + } else if (endpoint.has_panel()) { + auto panel_identifier = GetCompletePanelIdentifierWithoutAnswer( + endpoint.panel(), current_map_name, std::nullopt); + if (panel_identifier) { + PanelInfo& panel_info = info_.panels[*panel_identifier]; + panel_info.connections_referenced_by.push_back(human_connection); + + if (endpoint.panel().has_answer()) { + panel_info.proxies[endpoint.panel().answer()] + .connections_referenced_by.push_back(human_connection); + } + } + } + } + + void ProcessIdsFile(std::filesystem::path path) { + // Ignore this for now. + } + + std::string mapdir_; + CollectedInfo& info_; +}; + +} // namespace + +void ProcessHumanData(const std::string& mapdir, CollectedInfo& info) { + HumanProcessor human_processor(mapdir, info); + human_processor.Run(); +} + +} // namespace com::fourisland::lingo2_archipelago diff --git a/tools/validator/human_processor.h b/tools/validator/human_processor.h new file mode 100644 index 0000000..52f174f --- /dev/null +++ b/tools/validator/human_processor.h @@ -0,0 +1,14 @@ +#ifndef TOOLS_VALIDATOR_HUMAN_PROCESSOR_H_ +#define TOOLS_VALIDATOR_HUMAN_PROCESSOR_H_ + +#include + +namespace com::fourisland::lingo2_archipelago { + +struct CollectedInfo; + +void ProcessHumanData(const std::string& mapdir, CollectedInfo& info); + +} // namespace com::fourisland::lingo2_archipelago + +#endif /* TOOLS_VALIDATOR_HUMAN_PROCESSOR_H_ */ diff --git a/tools/validator/main.cpp b/tools/validator/main.cpp new file mode 100644 index 0000000..af9842b --- /dev/null +++ b/tools/validator/main.cpp @@ -0,0 +1,31 @@ +#include "human_processor.h" +#include "structs.h" +#include "validator.h" + +namespace com::fourisland::lingo2_archipelago { +namespace { + +void Run(const std::string& mapdir) { + CollectedInfo info; + + ProcessHumanData(mapdir, info); + + ValidateCollectedInfo(info); +} + +} // namespace +} // namespace com::fourisland::lingo2_archipelago + +int main(int argc, char** argv) { + if (argc != 2) { + std::cout << "Incorrect argument count." << std::endl; + std::cout << "Usage: validator [path to map directory]" << std::endl; + return 1; + } + + std::string mapdir = argv[1]; + + com::fourisland::lingo2_archipelago::Run(mapdir); + + return 0; +} diff --git a/tools/validator/structs.h b/tools/validator/structs.h new file mode 100644 index 0000000..c3427f4 --- /dev/null +++ b/tools/validator/structs.h @@ -0,0 +1,97 @@ +#ifndef TOOLS_VALIDATOR_STRUCTS_H_ +#define TOOLS_VALIDATOR_STRUCTS_H_ + +#include +#include +#include + +#include "proto/human.pb.h" +#include "util/identifiers.h" + +namespace com::fourisland::lingo2_archipelago { + +struct MalformedIdentifiers { + std::vector paintings; + std::vector panels; + std::vector keyholders; + + bool HasAny() const { + return !paintings.empty() || !panels.empty() || !keyholders.empty(); + } +}; + +struct RoomInfo { + std::vector definitions; + + std::vector doors_referenced_by; + std::vector panels_referenced_by; + std::vector connections_referenced_by; +}; + +struct DoorInfo { + std::vector definitions; + + std::vector connections_referenced_by; + std::vector doors_referenced_by; + std::vector panels_referenced_by; + std::vector paintings_referenced_by; + std::vector ports_referenced_by; + + MalformedIdentifiers malformed_identifiers; +}; + +struct PortInfo { + std::vector definitions; + + std::vector connections_referenced_by; +}; + +struct PaintingInfo { + std::vector definitions; + + std::vector connections_referenced_by; + std::vector doors_referenced_by; +}; + +struct ProxyInfo { + std::vector definitions; + + std::vector connections_referenced_by; + std::vector doors_referenced_by; +}; + +struct PanelInfo { + std::vector definitions; + + std::vector connections_referenced_by; + std::vector doors_referenced_by; + + std::map proxies; +}; + +struct KeyholderInfo { + std::vector definitions; + + std::vector doors_referenced_by; +}; + +using LetterIdentifier = std::tuple; + +struct LetterInfo { + std::vector defined_in; +}; + +struct CollectedInfo { + std::map rooms; + std::map doors; + std::map ports; + std::map paintings; + std::map panels; + std::map + keyholders; + std::map letters; +}; + +} // namespace com::fourisland::lingo2_archipelago + +#endif /* TOOLS_VALIDATOR_STRUCTS_H_ */ diff --git a/tools/validator/validator.cpp b/tools/validator/validator.cpp new file mode 100644 index 0000000..3381ed2 --- /dev/null +++ b/tools/validator/validator.cpp @@ -0,0 +1,250 @@ +#include "validator.h" + +#include + +#include "proto/human.pb.h" +#include "structs.h" +#include "util/identifiers.h" + +namespace com::fourisland::lingo2_archipelago { +namespace { + +void ValidateRoom(const RoomIdentifier& room_identifier, + const RoomInfo& room_info) { + if (room_info.definitions.empty()) { + std::cout << "Room " << room_identifier.ShortDebugString() + << " has no definition, but was referenced:" << std::endl; + + for (const DoorIdentifier& door_identifier : + room_info.doors_referenced_by) { + std::cout << " DOOR " << door_identifier.ShortDebugString() + << std::endl; + } + + for (const PanelIdentifier& panel_identifier : + room_info.panels_referenced_by) { + std::cout << " PANEL " << panel_identifier.ShortDebugString() + << std::endl; + } + + for (const HumanConnection& connection : + room_info.connections_referenced_by) { + std::cout << " CONNECTION " << connection.ShortDebugString() + << std::endl; + } + } else if (room_info.definitions.size() > 1) { + std::cout << "Room " << room_identifier.ShortDebugString() + << " was defined multiple times." << std::endl; + } +} + +void ValidateDoor(const DoorIdentifier& door_identifier, + const DoorInfo& door_info) { + if (door_info.definitions.empty()) { + std::cout << "Door " << door_identifier.ShortDebugString() + << " has no definition, but was referenced:" << std::endl; + + for (const DoorIdentifier& other_door_identifier : + door_info.doors_referenced_by) { + std::cout << " DOOR " << other_door_identifier.ShortDebugString() + << std::endl; + } + + for (const PanelIdentifier& panel_identifier : + door_info.panels_referenced_by) { + std::cout << " PANEL " << panel_identifier.ShortDebugString() + << std::endl; + } + + for (const PaintingIdentifier& painting_identifier : + door_info.paintings_referenced_by) { + std::cout << " PAINTING " << painting_identifier.ShortDebugString() + << std::endl; + } + + for (const PortIdentifier& port_identifier : + door_info.ports_referenced_by) { + std::cout << " PORT " << port_identifier.ShortDebugString() + << std::endl; + } + + for (const HumanConnection& connection : + door_info.connections_referenced_by) { + std::cout << " CONNECTION " << connection.ShortDebugString() + << std::endl; + } + } else if (door_info.definitions.size() > 1) { + std::cout << "Door " << door_identifier.ShortDebugString() + << " was defined multiple times." << std::endl; + } + + if (door_info.malformed_identifiers.HasAny()) { + std::cout << "Door " << door_identifier.ShortDebugString() + << " has malformed identifiers:" << std::endl; + + for (const PaintingIdentifier& painting_identifier : + door_info.malformed_identifiers.paintings) { + std::cout << " PAINTING " << painting_identifier.ShortDebugString() + << std::endl; + } + + for (const PanelIdentifier& panel_identifier : + door_info.malformed_identifiers.panels) { + std::cout << " PANEL " << panel_identifier.ShortDebugString() + << std::endl; + } + + for (const KeyholderIdentifier& keyholder_identifier : + door_info.malformed_identifiers.keyholders) { + std::cout << " KEYHOLDER " << keyholder_identifier.ShortDebugString() + << std::endl; + } + } +} + +void ValidatePort(const PortIdentifier& port_identifier, + const PortInfo& port_info) { + if (port_info.definitions.empty()) { + std::cout << "Port " << port_identifier.ShortDebugString() + << " has no definition, but was referenced:" << std::endl; + + for (const HumanConnection& connection : + port_info.connections_referenced_by) { + std::cout << " CONNECTION " << connection.ShortDebugString() + << std::endl; + } + } else if (port_info.definitions.size() > 1) { + std::cout << "Port " << port_identifier.ShortDebugString() + << " was defined multiple times." << std::endl; + } +} + +void ValidatePainting(const PaintingIdentifier& painting_identifier, + const PaintingInfo& painting_info) { + if (painting_info.definitions.empty()) { + std::cout << "Painting " << painting_identifier.ShortDebugString() + << " has no definition, but was referenced:" << std::endl; + + for (const DoorIdentifier& door_identifier : + painting_info.doors_referenced_by) { + std::cout << " DOOR " << door_identifier.ShortDebugString() + << std::endl; + } + + for (const HumanConnection& connection : + painting_info.connections_referenced_by) { + std::cout << " CONNECTION " << connection.ShortDebugString() + << std::endl; + } + } else if (painting_info.definitions.size() > 1) { + std::cout << "Painting " << painting_identifier.ShortDebugString() + << " was defined multiple times." << std::endl; + } +} + +void ValidatePanel(const PanelIdentifier& panel_identifier, + const PanelInfo& panel_info) { + if (panel_info.definitions.empty()) { + std::cout << "Panel " << panel_identifier.ShortDebugString() + << " has no definition, but was referenced:" << std::endl; + + for (const DoorIdentifier& door_identifier : + panel_info.doors_referenced_by) { + std::cout << " DOOR " << door_identifier.ShortDebugString() + << std::endl; + } + + for (const HumanConnection& connection : + panel_info.connections_referenced_by) { + std::cout << " CONNECTION " << connection.ShortDebugString() + << std::endl; + } + } else if (panel_info.definitions.size() > 1) { + std::cout << "Panel " << panel_identifier.ShortDebugString() + << " was defined multiple times." << std::endl; + } + + for (const auto& [answer, proxy_info] : panel_info.proxies) { + if (proxy_info.definitions.empty()) { + std::cout << "Panel " << panel_identifier.ShortDebugString() + << " with answer \"" << answer + << "\" has no definition, but was referenced:" << std::endl; + + for (const DoorIdentifier& door_identifier : + proxy_info.doors_referenced_by) { + std::cout << " DOOR " << door_identifier.ShortDebugString() + << std::endl; + } + + for (const HumanConnection& connection : + proxy_info.connections_referenced_by) { + std::cout << " CONNECTION " << connection.ShortDebugString() + << std::endl; + } + } else if (proxy_info.definitions.size() > 1) { + std::cout << "Panel " << panel_identifier.ShortDebugString() + << " with answer \"" << answer + << "\" was defined multiple times." << std::endl; + } + } +} + +void ValidateKeyholder(const KeyholderIdentifier& keyholder_identifier, + const KeyholderInfo& keyholder_info) { + if (keyholder_info.definitions.empty()) { + std::cout << "Keyholder " << keyholder_identifier.ShortDebugString() + << " has no definition, but was referenced:" << std::endl; + + for (const DoorIdentifier& door_identifier : + keyholder_info.doors_referenced_by) { + std::cout << " DOOR " << door_identifier.ShortDebugString() + << std::endl; + } + } else if (keyholder_info.definitions.size() > 1) { + std::cout << "Keyholder " << keyholder_identifier.ShortDebugString() + << " was defined multiple times." << std::endl; + } +} + +void ValidateLetter(const LetterIdentifier& letter_identifier, + const LetterInfo& letter_info) { + std::string letter_name = std::string(1, std::get<0>(letter_identifier)) + + (std::get<1>(letter_identifier) ? "2" : "1"); + + if (letter_info.defined_in.size() > 1) { + std::cout << "Letter " << letter_name + << " was defined in multiple places:" << std::endl; + + for (const RoomIdentifier& room_identifier : letter_info.defined_in) { + std::cout << " " << room_identifier.ShortDebugString() << std::endl; + } + } +} + +} // namespace + +void ValidateCollectedInfo(const CollectedInfo& info) { + for (const auto& [room_identifier, room_info] : info.rooms) { + ValidateRoom(room_identifier, room_info); + } + for (const auto& [door_identifier, door_info] : info.doors) { + ValidateDoor(door_identifier, door_info); + } + for (const auto& [port_identifier, port_info] : info.ports) { + ValidatePort(port_identifier, port_info); + } + for (const auto& [painting_identifier, painting_info] : info.paintings) { + ValidatePainting(painting_identifier, painting_info); + } + for (const auto& [panel_identifier, panel_info] : info.panels) { + ValidatePanel(panel_identifier, panel_info); + } + for (const auto& [keyholder_identifier, keyholder_info] : info.keyholders) { + ValidateKeyholder(keyholder_identifier, keyholder_info); + } + for (const auto& [letter_identifier, letter_info] : info.letters) { + ValidateLetter(letter_identifier, letter_info); + } +} + +} // namespace com::fourisland::lingo2_archipelago diff --git a/tools/validator/validator.h b/tools/validator/validator.h new file mode 100644 index 0000000..b710429 --- /dev/null +++ b/tools/validator/validator.h @@ -0,0 +1,12 @@ +#ifndef TOOLS_VALIDATOR_VALIDATOR_H_ +#define TOOLS_VALIDATOR_VALIDATOR_H + +namespace com::fourisland::lingo2_archipelago { + +struct CollectedInfo; + +void ValidateCollectedInfo(const CollectedInfo& info); + +} // namespace com::fourisland::lingo2_archipelago + +#endif /* TOOLS_VALIDATOR_VALIDATOR_H */ -- cgit 1.4.1