about summary refs log tree commit diff stats
path: root/tools/datapacker/main.cpp
blob: 6bbb46139c20d0d92f73095b15e1c60de385ac1b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
#ifndef TOOLS_DATAPACKER_CONTAINER_H_
#define TOOLS_DATAPACKER_CONTAINER_H_

#include <cstdint>
#include <map>
#include <optional>
#include <string>

#include "proto/data.pb.h"

namespace com::fourisland::lingo2_archipelago {

class Container {
 public:
  uint64_t FindOrAddMap(std::string map_name);

  uint64_t FindOrAddRoom(std::optional<std::string> map_name,
                         std::string room_name,
                         std::optional<std::string> map_fallback);

  uint64_t FindOrAddPainting(std::optional<std::string> map_name,
                             std::optional<std::string> room_name,
                             std::string painting_name,
                             std::optional<std::string> map_fallback,
                             std::optional<std::string> room_fallback);

  uint64_t FindOrAddPort(std::optional<std::string> map_name,
                         std::optional<std::string> room_name,
                         std::string port_name,
                         std::optional<std::string> map_fallback,
                         std::optional<std::string> room_fallback);

  uint64_t FindOrAddPanel(std::optional<std::string> map_name,
                          std::optional<std::string> room_name,
                          std::string panel_name,
                          std::optional<std::string> map_fallback,
                          std::optional<std::string> room_fallback);

  uint64_t FindOrAddDoor(std::optional<std::string> map_name,
                         std::string door_name,
                         std::optional<std::string> map_fallback);

  void AddConnection(const Connection& connection);

  AllObjects& all_objects() { return all_objects_; }

 private:
  AllObjects all_objects_;

  std::map<std::string, uint64_t> map_id_by_name_;
  std::map<std::string, std::map<std::string, uint64_t>>
      room_id_by_map_room_names_;
  std::map<std::string, std::map<std::string, std::map<std::string, uint64_t>>>
      painting_id_by_map_room_painting_names_;
  std::map<std::string, std::map<std::string, std::map<std::string, uint64_t>>>
      port_id_by_map_room_port_names_;
  std::map<std::string, std::map<std::string, std::map<std::string, uint64_t>>>
      panel_id_by_map_room_panel_names_;
  std::map<std::string, std::map<std::string, uint64_t>>
      door_id_by_map_door_names_;
};

}  // namespace com::fourisland::lingo2_archipelago

#endif /* TOOLS_DATAPACKER_CONTAINER_H_ */
/a> 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699
#include <google/protobuf/message.h>
#include <google/protobuf/text_format.h>

#include <algorithm>
#include <cstdint>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <map>
#include <optional>
#include <sstream>
#include <string>

#include "container.h"
#include "proto/data.pb.h"
#include "proto/human.pb.h"
#include "util/ids_yaml_format.h"

namespace com::fourisland::lingo2_archipelago {
namespace {

template <typename T>
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 DataPacker {
 public:
  DataPacker(const std::string& mapdir, const std::string& outputpath)
      : mapdir_(mapdir), outputpath_(outputpath) {}

  void Run() {
    std::filesystem::path datadir_path = mapdir_;

    ProcessConnectionsFile(datadir_path / "connections.txtpb", std::nullopt);
    ProcessMaps(datadir_path);
    ProcessProgressivesFile(datadir_path / "progressives.txtpb");
    ProcessDoorGroupsFile(datadir_path / "door_groups.txtpb");
    ProcessGlobalMetadataFile(datadir_path / "metadata.txtpb");
    ProcessIdsFile(datadir_path / "ids.yaml");

    {
      std::ofstream outputfile(outputpath_);
      container_.all_objects().SerializeToOstream(&outputfile);
    }

    /*std::string output;
    google::protobuf::TextFormat::PrintToString(container_.all_objects(),
                                                &output);
    std::cout << output << std::endl;*/
  }

 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().string();

    ProcessMapMetadataFile(path / "metadata.txtpb", map_name);
    ProcessConnectionsFile(path / "connections.txtpb", map_name);
    ProcessDoorsFile(path / "doors.txtpb", map_name);
    ProcessRooms(path / "rooms", map_name);
  }

  void ProcessMapMetadataFile(std::filesystem::path path,
                              const std::string& map_name) {
    if (!std::filesystem::exists(path)) {
      return;
    }

    auto metadata = ReadMessageFromFile<HumanMap>(path.string());

    uint64_t map_id = container_.FindOrAddMap(map_name);
    Map& map = *container_.all_objects().mutable_maps(map_id);

    if (metadata.has_display_name()) {
      map.set_display_name(metadata.display_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<HumanRoom>(dir_entry.path().string());
      ProcessRoom(room, current_map_name);
    }
  }

  void ProcessRoom(const HumanRoom& h_room,
                   const std::string& current_map_name) {
    uint64_t room_id =
        container_.FindOrAddRoom(current_map_name, h_room.name(), std::nullopt);
    Room& room = *container_.all_objects().mutable_rooms(room_id);

    // room.set_display_name(h_room.display_name());

    if (h_room.has_panel_display_name()) {
      room.set_panel_display_name(h_room.panel_display_name());
    }

    for (const HumanPanel& h_panel : h_room.panels()) {
      room.add_panels(ProcessPanel(h_panel, current_map_name, room.name()));
    }

    for (const HumanPainting& h_painting : h_room.paintings()) {
      room.add_paintings(
          ProcessPainting(h_painting, current_map_name, room.name()));
    }

    for (const HumanPort& h_port : h_room.ports()) {
      room.add_ports(ProcessPort(h_port, current_map_name, room.name()));
    }

    for (const HumanLetter& h_letter : h_room.letters()) {
      room.add_letters(ProcessLetter(h_letter, current_map_name, room.name()));
    }

    for (const HumanMastery& h_mastery : h_room.masteries()) {
      room.add_masteries(
          ProcessMastery(h_mastery, current_map_name, room.name()));
    }

    for (const HumanKeyholder& h_keyholder : h_room.keyholders()) {
      room.add_keyholders(
          ProcessKeyholder(h_keyholder, current_map_name, room.name()));
    }

    for (const HumanEnding& h_ending : h_room.endings()) {
      room.add_endings(ProcessEnding(h_ending, current_map_name, room.name()));
    }
  }

  uint64_t ProcessPanel(const HumanPanel& h_panel,
                        const std::string& current_map_name,
                        const std::string& current_room_name) {
    uint64_t panel_id =
        container_.FindOrAddPanel(current_map_name, current_room_name,
                                  h_panel.name(), std::nullopt, std::nullopt);
    PanelData& panel = *container_.all_objects().mutable_panels(panel_id);

    panel.set_path(h_panel.path());
    panel.set_clue(h_panel.clue());
    panel.set_answer(h_panel.answer());

    std::copy(
        h_panel.symbols().begin(), h_panel.symbols().end(),
        google::protobuf::RepeatedFieldBackInserter(panel.mutable_symbols()));
    std::copy(
        h_panel.proxies().begin(), h_panel.proxies().end(),
        google::protobuf::RepeatedFieldBackInserter(panel.mutable_proxies()));

    if (h_panel.has_required_door()) {
      std::optional<std::string> map_name =
          h_panel.required_door().has_map()
              ? std::optional<std::string>(h_panel.required_door().map())
              : std::nullopt;
      panel.set_required_door(container_.FindOrAddDoor(
          map_name, h_panel.required_door().name(), current_map_name));
    }

    if (h_panel.has_required_room()) {
      std::optional<std::string> map_name =
          h_panel.required_room().has_map()
              ? std::optional<std::string>(h_panel.required_room().map())
              : std::nullopt;
      panel.set_required_room(container_.FindOrAddRoom(
          map_name, h_panel.required_room().name(), current_map_name));
    }

    if (h_panel.has_display_name()) {
      panel.set_display_name(h_panel.display_name());
    }

    return panel_id;
  }

  uint64_t ProcessPainting(const HumanPainting& h_painting,
                           const std::string& current_map_name,
                           const std::string& current_room_name) {
    uint64_t painting_id = container_.FindOrAddPainting(
        current_map_name, current_room_name, h_painting.name(), std::nullopt,
        std::nullopt);
    PaintingData& painting =
        *container_.all_objects().mutable_paintings(painting_id);

    painting.set_path(h_painting.path());
    painting.set_display_name(h_painting.display_name());
    painting.set_orientation(h_painting.orientation());

    // Setting this explicitly because the Godot protobuf doesn't support
    // custom defaults.
    painting.set_gravity(h_painting.gravity());

    if (h_painting.has_move()) {
      painting.set_move(h_painting.move());
    }

    if (h_painting.has_enter_only()) {
      painting.set_enter_only(h_painting.enter_only());
    }

    if (h_painting.has_exit_only()) {
      painting.set_exit_only(h_painting.exit_only());
    }

    if (h_painting.has_required_door()) {
      std::optional<std::string> map_name =
          h_painting.required_door().has_map()
              ? std::optional<std::string>(h_painting.required_door().map())
              : std::nullopt;
      painting.set_required_door(container_.FindOrAddDoor(
          map_name, h_painting.required_door().name(), current_map_name));
    }

    return painting_id;
  }

  uint64_t ProcessPort(const HumanPort& h_port,
                       const std::string& current_map_name,
                       const std::string& current_room_name) {
    uint64_t port_id =
        container_.FindOrAddPort(current_map_name, current_room_name,
                                 h_port.name(), std::nullopt, std::nullopt);
    Port& port = *container_.all_objects().mutable_ports(port_id);

    port.set_path(h_port.path());
    port.set_orientation(h_port.orientation());

    // Setting this explicitly because the Godot protobuf doesn't support
    // custom defaults.
    port.set_gravity(h_port.gravity());

    if (h_port.has_required_door()) {
      std::optional<std::string> map_name =
          h_port.required_door().has_map()
              ? std::optional<std::string>(h_port.required_door().map())
              : std::nullopt;
      port.set_required_door(container_.FindOrAddDoor(
          map_name, h_port.required_door().name(), current_map_name));
    }

    return port_id;
  }

  uint64_t ProcessLetter(const HumanLetter& h_letter,
                         const std::string& current_map_name,
                         const std::string& current_room_name) {
    uint64_t letter_id =
        container_.FindOrAddLetter(h_letter.key(), h_letter.level2());
    Letter& letter = *container_.all_objects().mutable_letters(letter_id);

    letter.set_room_id(container_.FindOrAddRoom(
        current_map_name, current_room_name, std::nullopt));
    letter.set_path(h_letter.path());

    return letter_id;
  }

  uint64_t ProcessMastery(const HumanMastery& h_mastery,
                          const std::string& current_map_name,
                          const std::string& current_room_name) {
    uint64_t mastery_id = container_.FindOrAddMastery(
        current_map_name, current_room_name, h_mastery.name(), std::nullopt,
        std::nullopt);
    Mastery& mastery = *container_.all_objects().mutable_masteries(mastery_id);

    mastery.set_path(h_mastery.path());

    return mastery_id;
  }

  uint64_t ProcessKeyholder(const HumanKeyholder& h_keyholder,
                            const std::string& current_map_name,
                            const std::string& current_room_name) {
    uint64_t keyholder_id = container_.FindOrAddKeyholder(
        current_map_name, current_room_name, h_keyholder.name(), std::nullopt,
        std::nullopt);
    KeyholderData& keyholder =
        *container_.all_objects().mutable_keyholders(keyholder_id);

    keyholder.set_path(h_keyholder.path());

    if (h_keyholder.has_key()) {
      keyholder.set_key(h_keyholder.key());
    }

    return keyholder_id;
  }

  uint64_t ProcessEnding(const HumanEnding& h_ending,
                         const std::string& current_map_name,
                         const std::string& current_room_name) {
    uint64_t ending_id = container_.FindOrAddEnding(h_ending.name());
    Ending& ending = *container_.all_objects().mutable_endings(ending_id);

    ending.set_room_id(container_.FindOrAddRoom(
        current_map_name, current_room_name, std::nullopt));
    ending.set_path(h_ending.path());

    return ending_id;
  }

  void ProcessDoorsFile(std::filesystem::path path,
                        const std::string& current_map_name) {
    if (!std::filesystem::exists(path)) {
      return;
    }

    auto doors = ReadMessageFromFile<HumanDoors>(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) {
    uint64_t door_id =
        container_.FindOrAddDoor(current_map_name, h_door.name(), std::nullopt);
    Door& door = *container_.all_objects().mutable_doors(door_id);

    if (h_door.has_location_room()) {
      door.set_room_id(container_.FindOrAddRoom(
          current_map_name, h_door.location_room(), std::nullopt));

      Room& room = *container_.all_objects().mutable_rooms(door.room_id());
      room.add_doors(door_id);
    }

    std::copy(
        h_door.receivers().begin(), h_door.receivers().end(),
        google::protobuf::RepeatedFieldBackInserter(door.mutable_receivers()));

    for (const PaintingIdentifier& pi : h_door.move_paintings()) {
      std::optional<std::string> map_name =
          pi.has_map() ? std::optional<std::string>(pi.map()) : std::nullopt;
      door.add_move_paintings(container_.FindOrAddPainting(
          map_name, pi.room(), pi.name(), current_map_name, std::nullopt));
    }

    for (const PanelIdentifier& pi : h_door.panels()) {
      ProxyIdentifier* proxy = door.add_panels();

      std::optional<std::string> map_name =
          pi.has_map() ? std::optional<std::string>(pi.map()) : std::nullopt;
      proxy->set_panel(container_.FindOrAddPanel(
          map_name, pi.room(), pi.name(), current_map_name, std::nullopt));

      if (pi.has_answer()) {
        proxy->set_answer(pi.answer());
      }
    }

    for (const KeyholderIdentifier& ki : h_door.keyholders()) {
      KeyholderAnswer* answer = door.add_keyholders();

      std::optional<std::string> map_name =
          ki.has_map() ? std::optional<std::string>(ki.map()) : std::nullopt;
      answer->set_keyholder(container_.FindOrAddKeyholder(
          map_name, ki.room(), ki.name(), current_map_name, std::nullopt));

      if (ki.has_key()) {
        answer->set_key(ki.key());
      }
    }

    for (const RoomIdentifier& ri : h_door.rooms()) {
      std::optional<std::string> map_name =
          ri.has_map() ? std::optional<std::string>(ri.map()) : std::nullopt;
      door.add_rooms(
          container_.FindOrAddRoom(map_name, ri.name(), current_map_name));
    }

    for (const DoorIdentifier& di : h_door.doors()) {
      std::optional<std::string> map_name =
          di.has_map() ? std::optional<std::string>(di.map()) : std::nullopt;
      door.add_doors(
          container_.FindOrAddDoor(map_name, di.name(), current_map_name));
    }

    for (const std::string& ending_name : h_door.endings()) {
      door.add_endings(container_.FindOrAddEnding(ending_name));
    }

    if (h_door.has_control_center_color()) {
      door.set_control_center_color(h_door.control_center_color());
    }

    if (h_door.has_complete_at()) {
      door.set_complete_at(h_door.complete_at());
    }

    door.set_type(h_door.type());

    if (h_door.has_location_name()) {
      door.set_location_name(h_door.location_name());
    }

    if (h_door.has_double_letters()) {
      door.set_double_letters(h_door.double_letters());
    }
  }

  void ProcessConnectionsFile(std::filesystem::path path,
                              std::optional<std::string> current_map_name) {
    if (!std::filesystem::exists(path)) {
      return;
    }

    auto connections = ReadMessageFromFile<HumanConnections>(path.string());

    for (const HumanConnection& connection : connections.connections()) {
      ProcessConnection(connection, current_map_name);
    }
  }

  void ProcessConnection(const HumanConnection& human_connection,
                         const std::optional<std::string>& current_map_name) {
    Connection f_connection;

    if (human_connection.has_from_room()) {
      f_connection.set_from_room(container_.FindOrAddRoom(
          std::nullopt, human_connection.from_room(), current_map_name));
    } else if (human_connection.has_from()) {
      ProcessSingleConnection(human_connection.from(), current_map_name,
                              f_connection);
    }

    Connection r_connection;
    r_connection.set_to_room(f_connection.from_room());

    if (human_connection.has_to_room()) {
      r_connection.set_from_room(container_.FindOrAddRoom(
          std::nullopt, human_connection.to_room(), current_map_name));
    } else if (human_connection.has_to()) {
      ProcessSingleConnection(human_connection.to(), current_map_name,
                              r_connection);
    }

    f_connection.set_to_room(r_connection.from_room());

    if (human_connection.has_door()) {
      std::optional<std::string> map_name =
          human_connection.door().has_map()
              ? std::optional<std::string>(human_connection.door().map())
              : std::nullopt;
      uint64_t door_id = container_.FindOrAddDoor(
          map_name, human_connection.door().name(), current_map_name);
      f_connection.set_required_door(door_id);
      r_connection.set_required_door(door_id);
    }

    if (human_connection.has_roof_access()) {
      f_connection.set_roof_access(human_connection.roof_access());
      r_connection.set_roof_access(human_connection.roof_access());
    }

    container_.AddConnection(f_connection);
    if (!human_connection.oneway()) {
      container_.AddConnection(r_connection);
    }
  }

  void ProcessSingleConnection(
      const HumanConnection::Endpoint& endpoint,
      const std::optional<std::string>& current_map_name,
      Connection& connection) {
    if (endpoint.has_room()) {
      std::optional<std::string> map_name =
          endpoint.room().has_map()
              ? std::optional<std::string>(endpoint.room().map())
              : std::nullopt;
      connection.set_from_room(container_.FindOrAddRoom(
          map_name, endpoint.room().name(), current_map_name));
    } else if (endpoint.has_painting()) {
      std::optional<std::string> map_name =
          endpoint.painting().has_map()
              ? std::optional<std::string>(endpoint.painting().map())
              : std::nullopt;

      std::string room_name;
      if (!endpoint.painting().has_room()) {
        std::cout << "Missing room name for painting "
                  << endpoint.painting().name() << std::endl;
        room_name = "default";
      } else {
        room_name = endpoint.painting().room();
      }

      connection.set_from_room(
          container_.FindOrAddRoom(map_name, room_name, current_map_name));
      connection.set_painting(container_.FindOrAddPainting(
          map_name, room_name, endpoint.painting().name(), current_map_name,
          std::nullopt));
    } else if (endpoint.has_port()) {
      std::optional<std::string> map_name =
          endpoint.port().has_map()
              ? std::optional<std::string>(endpoint.port().map())
              : std::nullopt;

      std::string room_name;
      if (!endpoint.port().has_room()) {
        std::cout << "Missing room name for port " << endpoint.port().name()
                  << std::endl;
        room_name = "default";
      } else {
        room_name = endpoint.port().room();
      }

      connection.set_from_room(
          container_.FindOrAddRoom(map_name, room_name, current_map_name));
      connection.set_port(
          container_.FindOrAddPort(map_name, room_name, endpoint.port().name(),
                                   current_map_name, std::nullopt));
    } else if (endpoint.has_panel()) {
      std::optional<std::string> map_name =
          endpoint.panel().has_map()
              ? std::optional<std::string>(endpoint.panel().map())
              : std::nullopt;

      std::string room_name;
      if (!endpoint.panel().has_room()) {
        std::cout << "Missing room name for panel " << endpoint.panel().name()
                  << std::endl;
        room_name = "default";
      } else {
        room_name = endpoint.panel().room();
      }

      connection.set_from_room(
          container_.FindOrAddRoom(map_name, room_name, current_map_name));
      connection.mutable_panel()->set_panel(container_.FindOrAddPanel(
          map_name, room_name, endpoint.panel().name(), current_map_name,
          std::nullopt));
      if (endpoint.panel().has_answer()) {
        connection.mutable_panel()->set_answer(endpoint.panel().answer());
      }
    }
  }

  void ProcessProgressivesFile(std::filesystem::path path) {
    if (!std::filesystem::exists(path)) {
      return;
    }

    auto h_progs = ReadMessageFromFile<HumanProgressives>(path.string());

    for (const HumanProgressive& h_prog : h_progs.progressives()) {
      ProcessProgressive(h_prog);
    }
  }

  void ProcessProgressive(const HumanProgressive& h_prog) {
    uint64_t prog_id = container_.FindOrAddProgressive(h_prog.name());
    Progressive& prog = *container_.all_objects().mutable_progressives(prog_id);

    for (const DoorIdentifier& di : h_prog.doors()) {
      uint64_t door_id =
          container_.FindOrAddDoor(di.map(), di.name(), std::nullopt);
      prog.add_doors(door_id);
    }
  }

  void ProcessDoorGroupsFile(std::filesystem::path path) {
    if (!std::filesystem::exists(path)) {
      return;
    }

    auto h_groups = ReadMessageFromFile<HumanDoorGroups>(path.string());

    for (const HumanDoorGroup& h_group : h_groups.door_groups()) {
      ProcessDoorGroup(h_group);
    }
  }

  void ProcessDoorGroup(const HumanDoorGroup& h_group) {
    uint64_t group_id = container_.FindOrAddDoorGroup(h_group.name());
    DoorGroup& group = *container_.all_objects().mutable_door_groups(group_id);

    group.set_type(h_group.type());

    for (const DoorIdentifier& di : h_group.doors()) {
      uint64_t door_id =
          container_.FindOrAddDoor(di.map(), di.name(), std::nullopt);
      group.add_doors(door_id);
    }
  }

  void ProcessGlobalMetadataFile(std::filesystem::path path) {
    if (!std::filesystem::exists(path)) {
      return;
    }

    auto h_metadata = ReadMessageFromFile<HumanGlobalMetadata>(path.string());
    container_.all_objects().set_version(h_metadata.version());
  }

  void ProcessIdsFile(std::filesystem::path path) {
    auto ids = ReadIdsFromYaml(path.string());

    for (const auto& [map_name, map] : ids.maps()) {
      for (const auto& [door_name, ap_id] : map.doors()) {
        uint64_t door_id =
            container_.FindOrAddDoor(map_name, door_name, std::nullopt);
        container_.all_objects().mutable_doors(door_id)->set_ap_id(ap_id);
      }

      for (const auto& [room_name, room] : map.rooms()) {
        for (const auto& [panel_name, ap_id] : room.panels()) {
          uint64_t panel_id = container_.FindOrAddPanel(
              map_name, room_name, panel_name, std::nullopt, std::nullopt);
          container_.all_objects().mutable_panels(panel_id)->set_ap_id(ap_id);
        }

        for (const auto& [mastery_name, ap_id] : room.masteries()) {
          uint64_t mastery_id = container_.FindOrAddMastery(
              map_name, room_name, mastery_name, std::nullopt, std::nullopt);
          container_.all_objects()
              .mutable_masteries(mastery_id)
              ->set_ap_id(ap_id);
        }

        for (const auto& [keyholder_name, ap_id] : room.keyholders()) {
          uint64_t keyholder_id = container_.FindOrAddKeyholder(
              map_name, room_name, keyholder_name, std::nullopt, std::nullopt);
          container_.all_objects()
              .mutable_keyholders(keyholder_id)
              ->set_ap_id(ap_id);
        }
      }
    }

    auto& specials = *container_.all_objects().mutable_special_ids();
    for (const auto& [tag, id] : ids.special()) {
      specials[tag] = id;
    }

    for (const auto& [letter_name, ap_id] : ids.letters()) {
      uint64_t letter_id = container_.FindLetterByName(letter_name);
      container_.all_objects().mutable_letters(letter_id)->set_ap_id(ap_id);
    }

    for (const auto& [ending_name, ap_id] : ids.endings()) {
      uint64_t ending_id = container_.FindOrAddEnding(ending_name);
      container_.all_objects().mutable_endings(ending_id)->set_ap_id(ap_id);
    }

    for (const auto& [prog_name, ap_id] : ids.progressives()) {
      uint64_t prog_id = container_.FindOrAddProgressive(prog_name);
      container_.all_objects().mutable_progressives(prog_id)->set_ap_id(ap_id);
    }

    for (const auto& [group_name, ap_id] : ids.door_groups()) {
      uint64_t group_id = container_.FindOrAddDoorGroup(group_name);
      container_.all_objects().mutable_door_groups(group_id)->set_ap_id(ap_id);
    }
  }

  std::string mapdir_;
  std::string outputpath_;

  Container container_;
};

}  // namespace
}  // namespace com::fourisland::lingo2_archipelago

int main(int argc, char** argv) {
  if (argc != 3) {
    std::cout << "Incorrect argument count." << std::endl;
    std::cout << "Usage: datapacker [path to map directory] [output file]"
              << std::endl;
    return 1;
  }

  std::string mapdir = argv[1];
  std::string outputpath = argv[2];

  com::fourisland::lingo2_archipelago::DataPacker data_packer(mapdir,
                                                              outputpath);
  data_packer.Run();

  return 0;
}