summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--data/connections.txtpb2
-rw-r--r--data/maps/the_between/connections.txtpb43
-rw-r--r--data/maps/the_between/rooms/B2 Back Room.txtpb17
-rw-r--r--data/maps/the_between/rooms/B2 Front Room.txtpb12
-rw-r--r--data/maps/the_between/rooms/B2 Room.txtpb8
-rw-r--r--data/maps/the_between/rooms/Main Area.txtpb6
-rw-r--r--data/maps/the_between/rooms/Plaza Entrance.txtpb7
-rw-r--r--data/maps/the_colorful/metadata.txtpb2
-rw-r--r--data/maps/the_entry/metadata.txtpb10
-rw-r--r--data/maps/the_extravagant/metadata.txtpb2
-rw-r--r--data/maps/the_gallery/metadata.txtpb8
-rw-r--r--data/maps/the_graveyard/metadata.txtpb5
-rw-r--r--data/maps/the_great/metadata.txtpb7
-rw-r--r--data/maps/the_great/rooms/Back Area.txtpb2
-rw-r--r--data/maps/the_great/rooms/Daedalus Entrance.txtpb2
-rw-r--r--data/maps/the_great/rooms/Main Area.txtpb10
-rw-r--r--data/maps/the_great/rooms/Purple Room.txtpb2
-rw-r--r--data/maps/the_great/rooms/Salmon Room.txtpb2
-rw-r--r--data/maps/the_hinterlands/metadata.txtpb32
-rw-r--r--data/maps/the_impressive/metadata.txtpb5
-rw-r--r--data/maps/the_orb/metadata.txtpb4
-rw-r--r--data/maps/the_orb/rooms/B Room.txtpb1
-rw-r--r--proto/human.proto5
-rw-r--r--tools/util/CMakeLists.txt1
-rw-r--r--tools/util/godot_scene.cpp269
-rw-r--r--tools/util/godot_scene.h67
-rw-r--r--tools/validator/CMakeLists.txt1
-rw-r--r--tools/validator/godot_processor.cpp76
-rw-r--r--tools/validator/godot_processor.h14
-rw-r--r--tools/validator/human_processor.cpp15
-rw-r--r--tools/validator/main.cpp11
-rw-r--r--tools/validator/structs.h1
-rw-r--r--tools/validator/validator.cpp8
33 files changed, 627 insertions, 30 deletions
diff --git a/data/connections.txtpb b/data/connections.txtpb index 5e67500..e5014f7 100644 --- a/data/connections.txtpb +++ b/data/connections.txtpb
@@ -915,7 +915,7 @@ connections {
915 from { 915 from {
916 port { 916 port {
917 map: "the_between" 917 map: "the_between"
918 room: "Main Area" 918 room: "Plaza Entrance"
919 name: "PLAZA" 919 name: "PLAZA"
920 } 920 }
921 } 921 }
diff --git a/data/maps/the_between/connections.txtpb b/data/maps/the_between/connections.txtpb index e2e7dc8..4e2e9a5 100644 --- a/data/maps/the_between/connections.txtpb +++ b/data/maps/the_between/connections.txtpb
@@ -5,6 +5,47 @@ connections {
5} 5}
6connections { 6connections {
7 from_room: "Main Area" 7 from_room: "Main Area"
8 to_room: "B2 Room" 8 to_room: "B2 Front Room"
9 door { name: "B2 Door" } 9 door { name: "B2 Door" }
10} 10}
11connections {
12 from {
13 painting {
14 room: "B2 Front Room"
15 name: "EYES4"
16 }
17 }
18 to {
19 painting {
20 room: "B2 Back Room"
21 name: "EYES3"
22 }
23 }
24 oneway: true
25}
26connections {
27 from {
28 painting {
29 room: "B2 Front Room"
30 name: "EYES"
31 }
32 }
33 to {
34 painting {
35 room: "B2 Back Room"
36 name: "EYES2"
37 }
38 }
39 oneway: true
40}
41connections {
42 from_room: "B2 Back Room"
43 to_room: "B2 Front Room"
44 # via collecting B2
45 oneway: true
46}
47connections {
48 from_room: "Main Area"
49 to_room: "Plaza Entrance"
50 door { name: "Plaza Entrance" }
51}
diff --git a/data/maps/the_between/rooms/B2 Back Room.txtpb b/data/maps/the_between/rooms/B2 Back Room.txtpb new file mode 100644 index 0000000..132ba56 --- /dev/null +++ b/data/maps/the_between/rooms/B2 Back Room.txtpb
@@ -0,0 +1,17 @@
1name: "B2 Back Room"
2display_name: "B2 Room"
3letters {
4 key: "b"
5 level2: true
6 path: "Components/Collectables/collectable"
7}
8paintings {
9 name: "EYES2"
10 path: "Components/Paintings/eyes2"
11 orientation: "east"
12}
13paintings {
14 name: "EYES3"
15 path: "Components/Paintings/eyes3"
16 orientation: "west"
17}
diff --git a/data/maps/the_between/rooms/B2 Front Room.txtpb b/data/maps/the_between/rooms/B2 Front Room.txtpb new file mode 100644 index 0000000..02c09fb --- /dev/null +++ b/data/maps/the_between/rooms/B2 Front Room.txtpb
@@ -0,0 +1,12 @@
1name: "B2 Front Room"
2display_name: "B2 Room"
3paintings {
4 name: "EYES"
5 path: "Components/Paintings/eyes"
6 orientation: "east"
7}
8paintings {
9 name: "EYES4"
10 path: "Components/Paintings/eyes4"
11 orientation: "west"
12}
diff --git a/data/maps/the_between/rooms/B2 Room.txtpb b/data/maps/the_between/rooms/B2 Room.txtpb deleted file mode 100644 index aad5d15..0000000 --- a/data/maps/the_between/rooms/B2 Room.txtpb +++ /dev/null
@@ -1,8 +0,0 @@
1name: "B2 Room"
2display_name: "B2 Room"
3letters {
4 key: "b"
5 level2: true
6 path: "Components/Collectables/collectable"
7}
8# Uhh idk if the paintings and door in here should be randomized.
diff --git a/data/maps/the_between/rooms/Main Area.txtpb b/data/maps/the_between/rooms/Main Area.txtpb index 1e0e291..164493d 100644 --- a/data/maps/the_between/rooms/Main Area.txtpb +++ b/data/maps/the_between/rooms/Main Area.txtpb
@@ -201,9 +201,3 @@ ports {
201 path: "Components/Warps/worldport" 201 path: "Components/Warps/worldport"
202 orientation: "east" 202 orientation: "east"
203} 203}
204ports {
205 name: "PLAZA"
206 path: "Components/Warps/worldport4"
207 orientation: "north"
208 required_door { name: "Plaza Entrance" }
209}
diff --git a/data/maps/the_between/rooms/Plaza Entrance.txtpb b/data/maps/the_between/rooms/Plaza Entrance.txtpb new file mode 100644 index 0000000..b99081a --- /dev/null +++ b/data/maps/the_between/rooms/Plaza Entrance.txtpb
@@ -0,0 +1,7 @@
1name: "Plaza Entrance"
2display_name: "Main Area"
3ports {
4 name: "PLAZA"
5 path: "Components/Warps/worldport4"
6 orientation: "north"
7}
diff --git a/data/maps/the_colorful/metadata.txtpb b/data/maps/the_colorful/metadata.txtpb new file mode 100644 index 0000000..4c475a0 --- /dev/null +++ b/data/maps/the_colorful/metadata.txtpb
@@ -0,0 +1,2 @@
1# This has something to do with the FISH/FISHES proxy.
2excluded_nodes: "Components/panel_fake"
diff --git a/data/maps/the_entry/metadata.txtpb b/data/maps/the_entry/metadata.txtpb new file mode 100644 index 0000000..24d17bf --- /dev/null +++ b/data/maps/the_entry/metadata.txtpb
@@ -0,0 +1,10 @@
1# This is a debug warp to The Ancient and as far as I can tell there is no way
2# to access it.
3excluded_nodes: "Components/Warps/worldport-test"
4# Proxy stuff related to the Ctrl Tutorial.
5excluded_nodes: "Panels/Back Left/backleft_2_proxied_1"
6excluded_nodes: "Panels/Back Left/backleft_2_proxied_2"
7excluded_nodes: "Panels/Back Left/backleft_3_proxied_1"
8excluded_nodes: "Panels/Back Left/backleft_3_proxied_2"
9excluded_nodes: "Panels/Back Left/backleft_4_proxied_1"
10excluded_nodes: "Panels/Back Left/backleft_4_proxied_2"
diff --git a/data/maps/the_extravagant/metadata.txtpb b/data/maps/the_extravagant/metadata.txtpb new file mode 100644 index 0000000..36fc80a --- /dev/null +++ b/data/maps/the_extravagant/metadata.txtpb
@@ -0,0 +1,2 @@
1# This appears to be completely inaccessible.
2excluded_nodes: "Components/Warps/worldport"
diff --git a/data/maps/the_gallery/metadata.txtpb b/data/maps/the_gallery/metadata.txtpb new file mode 100644 index 0000000..c07cb5c --- /dev/null +++ b/data/maps/the_gallery/metadata.txtpb
@@ -0,0 +1,8 @@
1# These are the eyes in the foyer, and aren't normal paintings.
2excluded_nodes: "Components/Paintings/Starting/eye"
3excluded_nodes: "Components/Paintings/Starting/eye2"
4excluded_nodes: "Components/Paintings/Starting/eye3"
5excluded_nodes: "Components/Paintings/Starting/eye4"
6# This is the E that got removed but not deleted. It's above the ceiling and
7# cannot be accessed.
8excluded_nodes: "Components/Paintings/Starting/e"
diff --git a/data/maps/the_graveyard/metadata.txtpb b/data/maps/the_graveyard/metadata.txtpb new file mode 100644 index 0000000..fbac6ab --- /dev/null +++ b/data/maps/the_graveyard/metadata.txtpb
@@ -0,0 +1,5 @@
1# These really shouldn't be shuffled because it would make Black Ending trivial.
2excluded_nodes: "Components/Paintings/grave"
3excluded_nodes: "Components/Paintings/grave2"
4# I'll be real, I have no idea what this is.
5excluded_nodes: "Panels/panel_4"
diff --git a/data/maps/the_great/metadata.txtpb b/data/maps/the_great/metadata.txtpb new file mode 100644 index 0000000..d3b3018 --- /dev/null +++ b/data/maps/the_great/metadata.txtpb
@@ -0,0 +1,7 @@
1# This can't be shuffled because it is tilted.
2excluded_nodes: "Components/Paintings/u"
3# This can't be shuffled because it is on the ground.
4excluded_nodes: "Components/blare"
5# This is the fake HI panel that used to be in the Control Center entrance. It
6# is neither visible nor accessible.
7excluded_nodes: "Panels/General/entry_4"
diff --git a/data/maps/the_great/rooms/Back Area.txtpb b/data/maps/the_great/rooms/Back Area.txtpb index 0a8b168..013b0e2 100644 --- a/data/maps/the_great/rooms/Back Area.txtpb +++ b/data/maps/the_great/rooms/Back Area.txtpb
@@ -134,7 +134,7 @@ ports {
134} 134}
135ports { 135ports {
136 name: "TOWER" 136 name: "TOWER"
137 path: "Components/Warps/worldport10" 137 path: "Meshes/Blocks/Warps/worldport10"
138 orientation: "south" 138 orientation: "south"
139 required_door { name: "Tower Entrance" } 139 required_door { name: "Tower Entrance" }
140 # The reverse warp bypasses the door, so there needs to be two oneway connections. 140 # The reverse warp bypasses the door, so there needs to be two oneway connections.
diff --git a/data/maps/the_great/rooms/Daedalus Entrance.txtpb b/data/maps/the_great/rooms/Daedalus Entrance.txtpb index c7e71f0..f4c159a 100644 --- a/data/maps/the_great/rooms/Daedalus Entrance.txtpb +++ b/data/maps/the_great/rooms/Daedalus Entrance.txtpb
@@ -9,7 +9,7 @@ panels {
9} 9}
10ports { 10ports {
11 name: "DAEDALUS" 11 name: "DAEDALUS"
12 path: "Components/Warps/worldport8" 12 path: "Meshes/Blocks/Warps/worldport8"
13 orientation: "south" 13 orientation: "south"
14 required_door { name: "Daedalus Entrance" } 14 required_door { name: "Daedalus Entrance" }
15 # The reverse warp bypasses the door, so there needs to be two oneway connections. 15 # The reverse warp bypasses the door, so there needs to be two oneway connections.
diff --git a/data/maps/the_great/rooms/Main Area.txtpb b/data/maps/the_great/rooms/Main Area.txtpb index b562909..cf6285a 100644 --- a/data/maps/the_great/rooms/Main Area.txtpb +++ b/data/maps/the_great/rooms/Main Area.txtpb
@@ -121,27 +121,27 @@ panels {
121} 121}
122ports { 122ports {
123 name: "ENTRY" 123 name: "ENTRY"
124 path: "Components/Warps/worldport" 124 path: "Meshes/Blocks/Warps/worldport"
125 orientation: "south" 125 orientation: "south"
126} 126}
127ports { 127ports {
128 name: "KEEN" 128 name: "KEEN"
129 path: "Components/Warps/worldport6" 129 path: "Meshes/Blocks/Warps/worldport6"
130 orientation: "north" 130 orientation: "north"
131} 131}
132ports { 132ports {
133 name: "ORB" 133 name: "ORB"
134 path: "Components/Warps/worldport3" 134 path: "Meshes/Blocks/Warps/worldport3"
135 orientation: "north" 135 orientation: "north"
136} 136}
137ports { 137ports {
138 name: "LINEAR" 138 name: "LINEAR"
139 path: "Components/Warps/worldport15" 139 path: "Meshes/Blocks/Warps/worldport15"
140 orientation: "south" 140 orientation: "south"
141} 141}
142ports { 142ports {
143 name: "DIGITAL" 143 name: "DIGITAL"
144 path: "Components/Warps/worldport4" 144 path: "Meshes/Blocks/Warps/worldport4"
145 orientation: "down" 145 orientation: "down"
146 required_door { name: "Digital Entrance" } 146 required_door { name: "Digital Entrance" }
147} 147}
diff --git a/data/maps/the_great/rooms/Purple Room.txtpb b/data/maps/the_great/rooms/Purple Room.txtpb index 8edc789..ff9bd9a 100644 --- a/data/maps/the_great/rooms/Purple Room.txtpb +++ b/data/maps/the_great/rooms/Purple Room.txtpb
@@ -2,6 +2,6 @@ name: "Purple Room"
2display_name: "Main Area" 2display_name: "Main Area"
3ports { 3ports {
4 name: "DAEDALUS" 4 name: "DAEDALUS"
5 path: "Components/Warps/worldport18" 5 path: "Meshes/Blocks/Warps/worldport18"
6 orientation: "north" 6 orientation: "north"
7} 7}
diff --git a/data/maps/the_great/rooms/Salmon Room.txtpb b/data/maps/the_great/rooms/Salmon Room.txtpb index 9e29860..96efd1a 100644 --- a/data/maps/the_great/rooms/Salmon Room.txtpb +++ b/data/maps/the_great/rooms/Salmon Room.txtpb
@@ -2,6 +2,6 @@ name: "Salmon Room"
2display_name: "Main Area" 2display_name: "Main Area"
3ports { 3ports {
4 name: "BETWEEN" 4 name: "BETWEEN"
5 path: "Components/Warps/worldport11" 5 path: "Meshes/Blocks/Warps/worldport11"
6 orientation: "east" 6 orientation: "east"
7} 7}
diff --git a/data/maps/the_hinterlands/metadata.txtpb b/data/maps/the_hinterlands/metadata.txtpb new file mode 100644 index 0000000..b15ef2e --- /dev/null +++ b/data/maps/the_hinterlands/metadata.txtpb
@@ -0,0 +1,32 @@
1# I'm not currently planning on shuffling anything in here.
2excluded_nodes: "Components/Paintings/C"
3excluded_nodes: "Components/Paintings/E"
4excluded_nodes: "Components/Paintings/F/F_0"
5excluded_nodes: "Components/Paintings/F/F_1"
6excluded_nodes: "Components/Paintings/U"
7excluded_nodes: "Components/Paintings/U_1"
8excluded_nodes: "Components/Paintings/X"
9excluded_nodes: "Components/Paintings/z2_1"
10excluded_nodes: "Components/Paintings/z2_2"
11excluded_nodes: "Components/Warps/worldport3_deco"
12excluded_nodes: "Components/Warps/worldport3_deco2"
13excluded_nodes: "Components/Warps/worldport3_deco3"
14excluded_nodes: "Components/Warps/worldport3_deco4"
15excluded_nodes: "Components/Warps/worldport3_deco5"
16excluded_nodes: "Panels/G/court"
17excluded_nodes: "Panels/J/red"
18excluded_nodes: "Panels/K/no"
19excluded_nodes: "Panels/L/trick"
20excluded_nodes: "Panels/Misc/hi"
21excluded_nodes: "Panels/Misc/hint"
22excluded_nodes: "Panels/O/oh"
23excluded_nodes: "Panels/P/hint"
24excluded_nodes: "Panels/Q/gray"
25excluded_nodes: "Panels/V/grin"
26excluded_nodes: "Panels/Z/art"
27excluded_nodes: "Panels/k2/place"
28excluded_nodes: "Panels/m2/green"
29excluded_nodes: "Panels/o2/orange2"
30excluded_nodes: "Panels/p2/foxes"
31excluded_nodes: "Panels/u2/open"
32excluded_nodes: "Panels/v2/snipe"
diff --git a/data/maps/the_impressive/metadata.txtpb b/data/maps/the_impressive/metadata.txtpb new file mode 100644 index 0000000..3d4e2b5 --- /dev/null +++ b/data/maps/the_impressive/metadata.txtpb
@@ -0,0 +1,5 @@
1# These are apparently little eyes on the Green Eye panel pedestals? I don't
2# think they're ever visible in gameplay.
3excluded_nodes: "Meshes/eye"
4excluded_nodes: "Meshes/eye2"
5excluded_nodes: "Meshes/eye3"
diff --git a/data/maps/the_orb/metadata.txtpb b/data/maps/the_orb/metadata.txtpb new file mode 100644 index 0000000..920422d --- /dev/null +++ b/data/maps/the_orb/metadata.txtpb
@@ -0,0 +1,4 @@
1# These are inaccessible, and were probably just copy pasted from the other
2# rooms.
3excluded_nodes: "Components/Warps/worldport2"
4excluded_nodes: "Components/Warps/worldport3"
diff --git a/data/maps/the_orb/rooms/B Room.txtpb b/data/maps/the_orb/rooms/B Room.txtpb index 08dce6e..833c659 100644 --- a/data/maps/the_orb/rooms/B Room.txtpb +++ b/data/maps/the_orb/rooms/B Room.txtpb
@@ -10,7 +10,6 @@ paintings {
10 # TODO: This is too high up to enter. It's also a hint painting. 10 # TODO: This is too high up to enter. It's also a hint painting.
11 exit_only: true 11 exit_only: true
12} 12}
13# I believe worldport2 and worldport3 are completely inaccessible.
14# TODO: Should these two be independent for shuffling purposes, or always tied 13# TODO: Should these two be independent for shuffling purposes, or always tied
15# to the Main Area's port? 14# to the Main Area's port?
16ports { 15ports {
diff --git a/proto/human.proto b/proto/human.proto index 64231a2..6cec66d 100644 --- a/proto/human.proto +++ b/proto/human.proto
@@ -163,6 +163,11 @@ message HumanRoom {
163 repeated HumanMastery masteries = 8; 163 repeated HumanMastery masteries = 8;
164} 164}
165 165
166message HumanMap {
167 optional string display_name = 1;
168 repeated string excluded_nodes = 2;
169}
170
166message IdMappings { 171message IdMappings {
167 message RoomIds { 172 message RoomIds {
168 map<string, uint64> panels = 1; 173 map<string, uint64> panels = 1;
diff --git a/tools/util/CMakeLists.txt b/tools/util/CMakeLists.txt index f086e10..4d19c3b 100644 --- a/tools/util/CMakeLists.txt +++ b/tools/util/CMakeLists.txt
@@ -1,6 +1,7 @@
1find_package(Protobuf REQUIRED) 1find_package(Protobuf REQUIRED)
2 2
3add_library(util 3add_library(util
4 godot_scene.cpp
4 identifiers.cpp 5 identifiers.cpp
5 naming.cpp 6 naming.cpp
6) 7)
diff --git a/tools/util/godot_scene.cpp b/tools/util/godot_scene.cpp new file mode 100644 index 0000000..272111d --- /dev/null +++ b/tools/util/godot_scene.cpp
@@ -0,0 +1,269 @@
1#include "godot_scene.h"
2
3#include <absl/strings/str_split.h>
4
5#include <fstream>
6#include <sstream>
7#include <variant>
8
9namespace com::fourisland::lingo2_archipelago {
10
11namespace {
12
13class GodotSceneImpl : public GodotScene {
14 public:
15 GodotSceneImpl(std::map<std::string, GodotExtResource> ext_resources,
16 std::unique_ptr<GodotNode> root,
17 std::vector<std::unique_ptr<GodotNode>> descendents)
18 : ext_resources_(std::move(ext_resources)),
19 root_(std::move(root)),
20 descendents_(std::move(descendents)) {}
21
22 virtual const GodotExtResource* GetExtResource(const std::string& id) const {
23 auto it = ext_resources_.find(id);
24 if (it != ext_resources_.end()) {
25 return &it->second;
26 } else {
27 return nullptr;
28 }
29 }
30
31 virtual const GodotNode& GetRoot() const { return *root_; }
32
33 private:
34 std::map<std::string, GodotExtResource> ext_resources_;
35 std::unique_ptr<GodotNode> root_;
36 std::vector<std::unique_ptr<GodotNode>> descendents_;
37};
38
39struct Heading {
40 std::string type;
41
42 std::string id;
43 std::string path;
44 std::string resource_type;
45
46 std::string name;
47 std::string parent;
48 GodotInstanceType instance_type;
49};
50
51Heading ParseTscnHeading(absl::string_view line) {
52 std::string original_line(line);
53 Heading heading;
54
55 if (line[0] != '[') {
56 std::ostringstream errormsg;
57 errormsg << "Heading must start with [." << std::endl
58 << "Bad heading: " << original_line;
59 throw std::invalid_argument(errormsg.str());
60 }
61
62 line.remove_prefix(1);
63 int divider = line.find_first_of(" ]");
64 if (divider == std::string_view::npos) {
65 std::ostringstream errormsg;
66 errormsg << "Malformatted heading: " << line << std::endl
67 << "Original line: " << original_line;
68 throw std::invalid_argument(errormsg.str());
69 }
70
71 heading.type = std::string(line.substr(0, divider));
72 line.remove_prefix(divider + 1);
73
74 while (!line.empty()) {
75 divider = line.find_first_of("=");
76 if (divider == std::string_view::npos) {
77 std::ostringstream errormsg;
78 errormsg << "Malformatted heading: " << line << std::endl
79 << "Original line: " << original_line;
80 throw std::invalid_argument(errormsg.str());
81 }
82
83 std::string key(line.substr(0, divider));
84 line.remove_prefix(divider + 1);
85
86 if (line[0] == '"') {
87 line.remove_prefix(1);
88 divider = line.find_first_of("\"");
89
90 if (divider == std::string_view::npos) {
91 std::ostringstream errormsg;
92 errormsg << "Malformatted heading: " << line << std::endl
93 << "Original line: " << original_line;
94 throw std::invalid_argument(errormsg.str());
95 }
96
97 std::string strval(line.substr(0, divider));
98 line.remove_prefix(divider + 2);
99
100 if (key == "name") {
101 heading.name = strval;
102 } else if (key == "parent") {
103 heading.parent = strval;
104 } else if (key == "path") {
105 heading.path = strval;
106 } else if (key == "type") {
107 heading.resource_type = strval;
108 } else if (key == "id") {
109 heading.id = strval;
110 }
111 } else if (line[0] == 'S' || line[0] == 'E') {
112 GodotInstanceType rrval;
113 char internal = line[0];
114
115 line.remove_prefix(13); // SubResource("
116 divider = line.find_first_of("\"");
117
118 if (divider == std::string_view::npos) {
119 std::ostringstream errormsg;
120 errormsg << "Malformatted heading: " << line << std::endl
121 << "Original line: " << original_line;
122 throw std::invalid_argument(errormsg.str());
123 }
124
125 std::string refid = std::string(line.substr(0, divider));
126 line.remove_prefix(divider + 3);
127
128 GodotInstanceType instance_type;
129 if (internal == 'E') {
130 instance_type = GodotExtResourceRef{.id = refid};
131 } else {
132 // SubResource is not supported right now.
133 }
134
135 if (key == "instance") {
136 heading.instance_type = instance_type;
137 } else {
138 // Other keys aren't supported right now.
139 }
140 } else {
141 divider = line.find_first_of(" ]");
142
143 if (divider == std::string_view::npos) {
144 std::ostringstream errormsg;
145 errormsg << "Malformatted heading: " << line << std::endl
146 << "Original line: " << original_line;
147 throw std::invalid_argument(errormsg.str());
148 }
149
150 int numval = std::atoi(line.substr(0, divider).data());
151 line.remove_prefix(divider + 1);
152
153 // keyvals_[key] = numval;
154 }
155 }
156
157 return heading;
158}
159
160} // namespace
161
162void GodotNode::AddChild(GodotNode& child) {
163 children_[child.GetName()] = &child;
164 child.parent_ = this;
165}
166
167std::string GodotNode::GetPath() const {
168 if (parent_ == nullptr || parent_->GetName() == "") {
169 return name_;
170 } else {
171 return parent_->GetPath() + "/" + name_;
172 }
173}
174
175const GodotNode* GodotNode::GetNode(absl::string_view path) const {
176 std::vector<std::string> names = absl::StrSplit(path, "/");
177
178 auto it = children_.find(names[0]);
179 if (it == children_.end()) {
180 return nullptr;
181 } else {
182 if (names.size() == 1) {
183 return it->second;
184 } else {
185 path.remove_prefix(names[0].size() + 1);
186
187 return it->second->GetNode(path);
188 }
189 }
190}
191
192GodotNode* GodotNode::GetNode(absl::string_view path) {
193 return const_cast<GodotNode*>(
194 const_cast<const GodotNode*>(this)->GetNode(path));
195}
196
197std::unique_ptr<GodotScene> ReadGodotSceneFromFile(const std::string& path) {
198 std::map<std::string, GodotExtResource> ext_resources;
199 auto root = std::make_unique<GodotNode>("", GodotInstanceType{});
200 std::vector<std::unique_ptr<GodotNode>> descendents;
201
202 std::ifstream input(path);
203
204 std::string line;
205 bool section_started = false;
206 Heading cur_heading;
207 std::ostringstream cur_value;
208 bool value_started = false;
209 auto handle_end_of_section = [&]() {
210 section_started = false;
211 value_started = false;
212
213 if (cur_heading.type == "sub_resource") {
214 // sub_resources_[std::get<int>(cur_heading.GetKeyval("id"))] =
215 // {cur_heading, cur_value.str(), ""};
216 } else {
217 // other_.emplace_back(cur_heading, cur_value.str());
218 }
219
220 cur_value = {};
221 };
222 while (std::getline(input, line)) {
223 if (section_started && (line.empty() || line[0] == '[')) {
224 handle_end_of_section();
225 }
226 if (!line.empty() && line[0] == '[') {
227 Heading heading = ParseTscnHeading(line);
228 if (heading.type == "gd_scene") {
229 // file_descriptor_ = heading;
230 } else if (heading.type == "ext_resource") {
231 GodotExtResource ext_resource;
232 ext_resource.path = heading.path;
233 ext_resource.type = heading.resource_type;
234
235 ext_resources[heading.id] = ext_resource;
236 } else if (heading.type == "node") {
237 if (heading.parent != "") {
238 descendents.push_back(
239 std::make_unique<GodotNode>(heading.name, heading.instance_type));
240 GodotNode* child = descendents.back().get();
241
242 if (heading.parent == ".") {
243 root->AddChild(*child);
244 } else {
245 root->GetNode(heading.parent)->AddChild(*child);
246 }
247 }
248 } else {
249 cur_heading = heading;
250 section_started = true;
251 }
252 } else if (!line.empty()) {
253 if (value_started) {
254 cur_value << std::endl;
255 } else {
256 value_started = true;
257 }
258 cur_value << line;
259 }
260 }
261 if (section_started) {
262 handle_end_of_section();
263 }
264
265 return std::make_unique<GodotSceneImpl>(
266 std::move(ext_resources), std::move(root), std::move(descendents));
267}
268
269} // namespace com::fourisland::lingo2_archipelago
diff --git a/tools/util/godot_scene.h b/tools/util/godot_scene.h new file mode 100644 index 0000000..529e38e --- /dev/null +++ b/tools/util/godot_scene.h
@@ -0,0 +1,67 @@
1#ifndef TOOLS_UTIL_TSCN_H_
2#define TOOLS_UTIL_TSCN_H_
3
4#include <absl/strings/string_view.h>
5
6#include <map>
7#include <memory>
8#include <string>
9#include <string_view>
10#include <variant>
11
12namespace com::fourisland::lingo2_archipelago {
13
14struct GodotExtResource {
15 std::string type;
16 std::string path;
17};
18
19struct GodotExtResourceRef {
20 std::string id;
21};
22
23using GodotInstanceType = std::variant<std::monostate, GodotExtResourceRef>;
24
25class GodotNode {
26 public:
27 GodotNode(std::string name, GodotInstanceType instance_type)
28 : name_(std::move(name)), instance_type_(std::move(instance_type)) {}
29
30 const std::string& GetName() const { return name_; }
31
32 const GodotInstanceType& GetInstanceType() const { return instance_type_; }
33
34 const GodotNode* GetParent() const { return parent_; }
35 GodotNode* GetParent() { return parent_; }
36
37 std::string GetPath() const;
38
39 void AddChild(GodotNode& child);
40
41 const GodotNode* GetNode(absl::string_view path) const;
42 GodotNode* GetNode(absl::string_view path);
43
44 const std::map<std::string, GodotNode*> GetChildren() const {
45 return children_;
46 }
47
48 private:
49 std::string name_;
50 GodotInstanceType instance_type_;
51
52 GodotNode* parent_ = nullptr;
53 std::map<std::string, GodotNode*> children_;
54};
55
56class GodotScene {
57 public:
58 virtual const GodotExtResource* GetExtResource(
59 const std::string& id) const = 0;
60 virtual const GodotNode& GetRoot() const = 0;
61};
62
63std::unique_ptr<GodotScene> ReadGodotSceneFromFile(const std::string& path);
64
65} // namespace com::fourisland::lingo2_archipelago
66
67#endif /* TOOLS_UTIL_TSCN_H_ */
diff --git a/tools/validator/CMakeLists.txt b/tools/validator/CMakeLists.txt index 0ad58c2..967b890 100644 --- a/tools/validator/CMakeLists.txt +++ b/tools/validator/CMakeLists.txt
@@ -1,6 +1,7 @@
1find_package(Protobuf REQUIRED) 1find_package(Protobuf REQUIRED)
2 2
3add_executable(validator 3add_executable(validator
4 godot_processor.cpp
4 human_processor.cpp 5 human_processor.cpp
5 main.cpp 6 main.cpp
6 validator.cpp 7 validator.cpp
diff --git a/tools/validator/godot_processor.cpp b/tools/validator/godot_processor.cpp new file mode 100644 index 0000000..f345cff --- /dev/null +++ b/tools/validator/godot_processor.cpp
@@ -0,0 +1,76 @@
1#include "godot_processor.h"
2
3#include <filesystem>
4#include <iostream>
5#include <memory>
6#include <set>
7
8#include "structs.h"
9#include "util/godot_scene.h"
10
11namespace com::fourisland::lingo2_archipelago {
12
13namespace {
14
15static const std::set<std::string> kImportantNodeTypes = {
16 "res://objects/nodes/panel.tscn", "res://objects/nodes/worldport.tscn",
17 "res://objects/nodes/keyHolder.tscn",
18 "res://objects/nodes/collectable.tscn"};
19
20class GodotProcessor {
21 public:
22 GodotProcessor(const std::string& repodir, CollectedInfo& info)
23 : repodir_(repodir), info_(info) {}
24
25 void Run() {
26 for (auto& [map_name, map_info] : info_.maps) {
27 ProcessMap(map_name, map_info);
28 }
29 }
30
31 void ProcessMap(const std::string& map_name, MapInfo& map_info) {
32 std::filesystem::path scene_path = std::filesystem::path(repodir_) /
33 "objects" / "scenes" /
34 (map_name + ".tscn");
35 std::string scene_path_str = scene_path.string();
36 std::cout << "Processing " << scene_path_str << std::endl;
37
38 std::unique_ptr<GodotScene> scene =
39 ReadGodotSceneFromFile(scene_path_str);
40
41 ProcessMapNode(*scene, scene->GetRoot(), map_info);
42 }
43
44 void ProcessMapNode(const GodotScene& scene, const GodotNode& node,
45 MapInfo& map_info) {
46 if (std::holds_alternative<GodotExtResourceRef>(node.GetInstanceType())) {
47 const GodotExtResourceRef& ext_resource_ref =
48 std::get<GodotExtResourceRef>(node.GetInstanceType());
49 const GodotExtResource* ext_resource =
50 scene.GetExtResource(ext_resource_ref.id);
51
52 if (ext_resource != nullptr &&
53 (kImportantNodeTypes.count(ext_resource->path) ||
54 ext_resource->path.starts_with("res://objects/meshes/paintings/"))) {
55 map_info.game_nodes[node.GetPath()].defined = true;
56 }
57 }
58
59 for (const auto& [child_name, child_node] : node.GetChildren()) {
60 ProcessMapNode(scene, *child_node, map_info);
61 }
62 }
63
64 private:
65 std::string repodir_;
66 CollectedInfo& info_;
67};
68
69} // namespace
70
71void ProcessGodotData(const std::string& repodir, CollectedInfo& info) {
72 GodotProcessor godot_processor(repodir, info);
73 godot_processor.Run();
74}
75
76} // namespace com::fourisland::lingo2_archipelago
diff --git a/tools/validator/godot_processor.h b/tools/validator/godot_processor.h new file mode 100644 index 0000000..97bcea6 --- /dev/null +++ b/tools/validator/godot_processor.h
@@ -0,0 +1,14 @@
1#ifndef TOOLS_VALIDATOR_GODOT_PROCESSOR_H_
2#define TOOLS_VALIDATOR_GODOT_PROCESSOR_H_
3
4#include <string>
5
6namespace com::fourisland::lingo2_archipelago {
7
8struct CollectedInfo;
9
10void ProcessGodotData(const std::string& repodir, CollectedInfo& info);
11
12} // namespace com::fourisland::lingo2_archipelago
13
14#endif /* TOOLS_VALIDATOR_GODOT_PROCESSOR_H_ */
diff --git a/tools/validator/human_processor.cpp b/tools/validator/human_processor.cpp index 0846bb8..af40980 100644 --- a/tools/validator/human_processor.cpp +++ b/tools/validator/human_processor.cpp
@@ -55,11 +55,26 @@ class HumanProcessor {
55 void ProcessMap(std::filesystem::path path) { 55 void ProcessMap(std::filesystem::path path) {
56 std::string map_name = path.filename().string(); 56 std::string map_name = path.filename().string();
57 57
58 ProcessMetadataFile(path / "metadata.txtpb", map_name);
58 ProcessConnectionsFile(path / "connections.txtpb", map_name); 59 ProcessConnectionsFile(path / "connections.txtpb", map_name);
59 ProcessDoorsFile(path / "doors.txtpb", map_name); 60 ProcessDoorsFile(path / "doors.txtpb", map_name);
60 ProcessRooms(path / "rooms", map_name); 61 ProcessRooms(path / "rooms", map_name);
61 } 62 }
62 63
64 void ProcessMetadataFile(std::filesystem::path path,
65 const std::string& current_map_name) {
66 if (!std::filesystem::exists(path)) {
67 return;
68 }
69
70 MapInfo& map_info = info_.maps[current_map_name];
71
72 auto metadata = ReadMessageFromFile<HumanMap>(path.string());
73 for (const std::string& path : metadata.excluded_nodes()) {
74 map_info.game_nodes[path].uses++;
75 }
76 }
77
63 void ProcessRooms(std::filesystem::path path, 78 void ProcessRooms(std::filesystem::path path,
64 const std::string& current_map_name) { 79 const std::string& current_map_name) {
65 for (auto const& dir_entry : std::filesystem::directory_iterator(path)) { 80 for (auto const& dir_entry : std::filesystem::directory_iterator(path)) {
diff --git a/tools/validator/main.cpp b/tools/validator/main.cpp index af9842b..1a72e9a 100644 --- a/tools/validator/main.cpp +++ b/tools/validator/main.cpp
@@ -1,3 +1,4 @@
1#include "godot_processor.h"
1#include "human_processor.h" 2#include "human_processor.h"
2#include "structs.h" 3#include "structs.h"
3#include "validator.h" 4#include "validator.h"
@@ -5,10 +6,11 @@
5namespace com::fourisland::lingo2_archipelago { 6namespace com::fourisland::lingo2_archipelago {
6namespace { 7namespace {
7 8
8void Run(const std::string& mapdir) { 9void Run(const std::string& mapdir, const std::string& repodir) {
9 CollectedInfo info; 10 CollectedInfo info;
10 11
11 ProcessHumanData(mapdir, info); 12 ProcessHumanData(mapdir, info);
13 ProcessGodotData(repodir, info);
12 14
13 ValidateCollectedInfo(info); 15 ValidateCollectedInfo(info);
14} 16}
@@ -17,15 +19,16 @@ void Run(const std::string& mapdir) {
17} // namespace com::fourisland::lingo2_archipelago 19} // namespace com::fourisland::lingo2_archipelago
18 20
19int main(int argc, char** argv) { 21int main(int argc, char** argv) {
20 if (argc != 2) { 22 if (argc != 3) {
21 std::cout << "Incorrect argument count." << std::endl; 23 std::cout << "Incorrect argument count." << std::endl;
22 std::cout << "Usage: validator [path to map directory]" << std::endl; 24 std::cout << "Usage: validator [path to map directory] [path to Lingo 2 repository]" << std::endl;
23 return 1; 25 return 1;
24 } 26 }
25 27
26 std::string mapdir = argv[1]; 28 std::string mapdir = argv[1];
29 std::string repodir = argv[2];
27 30
28 com::fourisland::lingo2_archipelago::Run(mapdir); 31 com::fourisland::lingo2_archipelago::Run(mapdir, repodir);
29 32
30 return 0; 33 return 0;
31} 34}
diff --git a/tools/validator/structs.h b/tools/validator/structs.h index 1b61f77..406dc0c 100644 --- a/tools/validator/structs.h +++ b/tools/validator/structs.h
@@ -21,6 +21,7 @@ struct MalformedIdentifiers {
21}; 21};
22 22
23struct GameNodeInfo { 23struct GameNodeInfo {
24 bool defined = false;
24 int uses = 0; 25 int uses = 0;
25}; 26};
26 27
diff --git a/tools/validator/validator.cpp b/tools/validator/validator.cpp index f2ec280..6d01b7c 100644 --- a/tools/validator/validator.cpp +++ b/tools/validator/validator.cpp
@@ -14,6 +14,14 @@ void ValidateMap(const std::string& map_name, const MapInfo& map_info) {
14 if (node_info.uses > 1) { 14 if (node_info.uses > 1) {
15 std::cout << "Map " << map_name << " node " << node_path 15 std::cout << "Map " << map_name << " node " << node_path
16 << " is used in multiple places." << std::endl; 16 << " is used in multiple places." << std::endl;
17 } else if (node_info.uses == 0) {
18 std::cout << "Map " << map_name << " node " << node_path
19 << " is not used." << std::endl;
20 }
21
22 if (!node_info.defined) {
23 std::cout << "Map " << map_name << " node " << node_path
24 << " is not defined in the game file." << std::endl;
17 } 25 }
18 } 26 }
19} 27}