summary refs log tree commit diff stats
path: root/tools/util/godot_scene.cpp
diff options
context:
space:
mode:
authorStar Rauchenberger <fefferburbia@gmail.com>2025-08-18 12:56:13 -0400
committerStar Rauchenberger <fefferburbia@gmail.com>2025-08-18 12:56:13 -0400
commit1ac21d4a67ddd211fda841aa6e368bc2cf52a3d6 (patch)
treebdcf651c156c27982e37bddb7cb7e0b09aa90d5a /tools/util/godot_scene.cpp
parent15b8794bbe80be0bcf1f482674455efe002cec2c (diff)
downloadlingo2-archipelago-1ac21d4a67ddd211fda841aa6e368bc2cf52a3d6.tar.gz
lingo2-archipelago-1ac21d4a67ddd211fda841aa6e368bc2cf52a3d6.tar.bz2
lingo2-archipelago-1ac21d4a67ddd211fda841aa6e368bc2cf52a3d6.zip
Validate that nodes in game files are used
You can now also list out nodes that you are explicitly not mapping out. The current state of the repo does produce some warnings when the validator is run and they're either endings, paintings that I'm not sure what to do with yet, and weird proxy stuff I'm not sure how to handle yet.
Diffstat (limited to 'tools/util/godot_scene.cpp')
-rw-r--r--tools/util/godot_scene.cpp269
1 files changed, 269 insertions, 0 deletions
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