diff options
author | Star Rauchenberger <fefferburbia@gmail.com> | 2025-08-18 12:56:13 -0400 |
---|---|---|
committer | Star Rauchenberger <fefferburbia@gmail.com> | 2025-08-18 12:56:13 -0400 |
commit | 1ac21d4a67ddd211fda841aa6e368bc2cf52a3d6 (patch) | |
tree | bdcf651c156c27982e37bddb7cb7e0b09aa90d5a /tools/util/godot_scene.cpp | |
parent | 15b8794bbe80be0bcf1f482674455efe002cec2c (diff) | |
download | lingo2-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.cpp | 269 |
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 | |||
9 | namespace com::fourisland::lingo2_archipelago { | ||
10 | |||
11 | namespace { | ||
12 | |||
13 | class 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 | |||
39 | struct 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 | |||
51 | Heading 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 | |||
162 | void GodotNode::AddChild(GodotNode& child) { | ||
163 | children_[child.GetName()] = &child; | ||
164 | child.parent_ = this; | ||
165 | } | ||
166 | |||
167 | std::string GodotNode::GetPath() const { | ||
168 | if (parent_ == nullptr || parent_->GetName() == "") { | ||
169 | return name_; | ||
170 | } else { | ||
171 | return parent_->GetPath() + "/" + name_; | ||
172 | } | ||
173 | } | ||
174 | |||
175 | const 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 | |||
192 | GodotNode* GodotNode::GetNode(absl::string_view path) { | ||
193 | return const_cast<GodotNode*>( | ||
194 | const_cast<const GodotNode*>(this)->GetNode(path)); | ||
195 | } | ||
196 | |||
197 | std::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 | ||