summary refs log tree commit diff stats
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-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
10 files changed, 459 insertions, 4 deletions
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}