8const DataCacheManager = preload(
"res://scripts/systems/dataCacheManager/data_cache_manager.gd")
11var data_cache_manager = DataCacheManager.new()
15func load_profiles(directory_path:String =
"res://data/build_profiles") -> Array:
18 var dir = DirAccess.open(directory_path)
22 var file_name = dir.get_next()
23 while file_name !=
"":
24 if not dir.current_is_dir()
and file_name.ends_with(
".json"):
25 var profile = load_profile(
"%s/%s" % [directory_path.trim_suffix(
"/"), file_name])
26 if not profile.is_empty():
27 loaded_ids.append(str(profile.get(
"game_id",
"")))
28 file_name = dir.get_next()
34func load_profile(file_path:String) -> Dictionary:
35 var data:Dictionary = data_cache_manager.load_json(file_path)
36 var profile = normalize_profile(data)
37 if str(profile.get(
"game_id",
"")) !=
"":
38 profiles[str(profile[
"game_id"])] = profile
43func get_profile(game_id:String) -> Dictionary:
44 return profiles.get(game_id, {}).duplicate(true)
48func get_profile_ids() -> Array:
49 var ids = profiles.keys()
55func get_profile_options(include_all_demos:bool = true) -> Array:
57 for id
in get_profile_ids():
58 if not include_all_demos
and str(id) ==
"all_demos":
60 var profile = profiles[id]
61 var metadata = profile.get(
"metadata", {})
if profile.get(
"metadata", {})
is Dictionary
else {}
64 "label": str(profile.get(
"label", id)),
65 "menu_label": str(metadata.get(
"menu_label", profile.get(
"label", id))),
66 "menu_route": str(profile.get(
"menu_route", id)),
67 "menu_order": int(metadata.get(
"menu_order", 999)),
68 "sprite_id": str(metadata.get(
"sprite_id",
"")),
69 "description": str(metadata.get(
"description",
"")),
70 "start_scene": str(profile.get(
"start_scene",
"")),
71 "entry_scene": str(profile.get(
"entry_scene", profile.get(
"start_scene",
""))),
72 "launch_scene": str(profile.get(
"launch_scene", profile.get(
"start_scene",
""))),
73 "output_name": str(profile.get(
"output_name", id)),
74 "platforms": profile.get(
"platforms", {}).keys()
76 rows.sort_custom(func(a, b):
return int(a.get(
"menu_order", 999)) < int(b.get(
"menu_order", 999)))
83func get_sprite_map_options(games_directory_path:String =
"res://data/games") -> Array:
85 var discovered_paths := {}
86 for profile_option
in get_profile_options(false):
87 var game_id := str(profile_option.get(
"id",
""))
88 var sprite_path :=
"%s/%s/sprites.json" % [games_directory_path.trim_suffix(
"/"), game_id]
89 if game_id ==
"" or not FileAccess.file_exists(sprite_path):
93 "label": str(profile_option.get(
"label", game_id.capitalize())),
96 discovered_paths[sprite_path] = true
98 var directory := DirAccess.open(games_directory_path)
100 directory.list_dir_begin()
101 var game_directory := directory.get_next()
102 while game_directory !=
"":
103 if directory.current_is_dir()
and not game_directory.begins_with(
"."):
104 var sprite_path :=
"%s/%s/sprites.json" % [games_directory_path.trim_suffix(
"/"), game_directory]
105 if FileAccess.file_exists(sprite_path)
and not discovered_paths.has(sprite_path):
107 "id": game_directory,
108 "label": game_directory.capitalize().replace(
"_",
" "),
111 discovered_paths[sprite_path] = true
112 game_directory = directory.get_next()
113 directory.list_dir_end()
119func normalize_profile(profile:Dictionary) -> Dictionary:
120 var normalized = profile.duplicate(true)
121 normalized[
"game_id"] = str(normalized.get(
"game_id",
""))
122 normalized[
"label"] = str(normalized.get(
"label", normalized.get(
"game_id",
"")))
123 normalized[
"start_scene"] = str(normalized.get(
"start_scene",
"res://scenes/splash_screen.tscn"))
124 normalized[
"entry_scene"] = str(normalized.get(
"entry_scene", normalized.get(
"start_scene",
"res://scenes/splash_screen.tscn")))
125 normalized[
"launch_scene"] = str(normalized.get(
"launch_scene", normalized.get(
"start_scene",
"res://scenes/start_menu.tscn")))
126 normalized[
"menu_route"] = str(normalized.get(
"menu_route", normalized.get(
"game_id",
"")))
127 normalized[
"output_name"] = str(normalized.get(
"output_name", normalized.get(
"game_id",
"game")))
128 normalized[
"include_roots"] = _string_array(normalized.get(
"include_roots", []))
129 normalized[
"shared_roots"] = _string_array(normalized.get(
"shared_roots", []))
130 normalized[
"exclude_roots"] = _string_array(normalized.get(
"exclude_roots", []))
131 normalized[
"exclude_game_ids"] = _string_array(normalized.get(
"exclude_game_ids", []))
132 normalized[
"platforms"] = normalized.get(
"platforms", {})
if normalized.get(
"platforms", {})
is Dictionary
else {}
133 normalized[
"metadata"] = normalized.get(
"metadata", {})
if normalized.get(
"metadata", {})
is Dictionary
else {}
138func validate_profile(profile:Dictionary) -> Array:
139 var normalized = normalize_profile(profile)
141 for field
in [
"game_id",
"label",
"start_scene",
"output_name"]:
142 if str(normalized.get(field,
"")).strip_edges() ==
"":
143 issues.append(_issue(
"missing_field", field,
"Build profile is missing '%s'." % field))
144 if not FileAccess.file_exists(str(normalized.get(
"start_scene",
""))):
145 issues.append(_issue(
"missing_scene",
"start_scene",
"Start scene does not exist."))
146 if str(normalized.get(
"entry_scene",
"")).strip_edges() !=
"" and not FileAccess.file_exists(str(normalized.get(
"entry_scene",
""))):
147 issues.append(_issue(
"missing_scene",
"entry_scene",
"Entry scene does not exist."))
148 if str(normalized.get(
"launch_scene",
"")).strip_edges() !=
"" and not FileAccess.file_exists(str(normalized.get(
"launch_scene",
""))):
149 issues.append(_issue(
"missing_scene",
"launch_scene",
"Launch scene does not exist."))
150 if normalized.get(
"include_roots", []).is_empty():
151 issues.append(_issue(
"missing_include_roots",
"include_roots",
"At least one game include root is required."))
152 if normalized.get(
"platforms", {}).is_empty():
153 issues.append(_issue(
"missing_platforms",
"platforms",
"At least one platform artifact definition is required."))
154 for root
in get_all_include_roots(normalized):
155 if not _resource_exists(root):
156 issues.append(_issue(
"missing_include_root", root,
"Included resource root does not exist."))
157 for platform_id
in normalized.get(
"platforms", {}).keys():
158 var platform = normalized[
"platforms"][platform_id]
159 if not platform
is Dictionary:
160 issues.append(_issue(
"invalid_platform", str(platform_id),
"Platform entry must be an object."))
162 if str(platform.get(
"preset",
"")).strip_edges() ==
"":
163 issues.append(_issue(
"missing_platform_preset", str(platform_id),
"Platform entry is missing export preset name."))
164 if str(platform.get(
"artifact",
"")).strip_edges() ==
"":
165 issues.append(_issue(
"missing_platform_artifact", str(platform_id),
"Platform entry is missing artifact path."))
170func get_all_include_roots(profile:Dictionary) -> Array:
172 for root
in _string_array(profile.get(
"shared_roots", [])) + _string_array(profile.get(
"include_roots", [])):
173 if root !=
"" and not roots.has(root):
180func get_exclude_roots(profile:Dictionary, all_game_ids:Array = []) -> Array:
181 var normalized = normalize_profile(profile)
182 var excludes := _string_array(normalized.get(
"exclude_roots", []))
183 var ids = _string_array(normalized.get(
"exclude_game_ids", []))
185 for game_id
in _string_array(all_game_ids):
186 if game_id != str(normalized.get(
"game_id",
"")):
189 var root =
"res://data/games/%s" % game_id
190 if not excludes.has(root):
191 excludes.append(root)
196func get_platform_artifacts(profile:Dictionary) -> Dictionary:
197 var normalized = normalize_profile(profile)
198 var output_name = str(normalized.get(
"output_name",
"Game"))
200 for platform_id
in normalized.get(
"platforms", {}).keys():
201 var platform = normalized[
"platforms"][platform_id]
202 if not platform
is Dictionary:
204 var row = platform.duplicate(true)
205 if str(row.get(
"artifact",
"")) ==
"":
206 row[
"artifact"] =
"builds/%s/%s" % [str(platform_id), output_name]
207 artifacts[str(platform_id)] = row
212func get_export_plan(profile:Dictionary, all_game_ids:Array = []) -> Dictionary:
213 var normalized = normalize_profile(profile)
215 "game_id": normalized.get(
"game_id",
""),
216 "label": normalized.get(
"label",
""),
217 "start_scene": normalized.get(
"start_scene",
""),
218 "entry_scene": normalized.get(
"entry_scene",
""),
219 "launch_scene": normalized.get(
"launch_scene",
""),
220 "menu_route": normalized.get(
"menu_route",
""),
221 "include_roots": get_all_include_roots(normalized),
222 "exclude_roots": get_exclude_roots(normalized, all_game_ids),
223 "platforms": get_platform_artifacts(normalized),
224 "metadata": normalized.get(
"metadata", {})
231func audit_single_game_profile(profile:Dictionary, all_game_ids:Array = [], required_shared_roots:Array = [], required_game_roots:Array = []) -> Dictionary:
232 var normalized:Dictionary = normalize_profile(profile)
233 var game_id:String = str(normalized.get(
"game_id",
""))
234 var include_roots:Array = get_all_include_roots(normalized)
235 var exclude_roots:Array = get_exclude_roots(normalized, all_game_ids)
236 var missing_shared_roots:Array = []
237 var missing_game_roots:Array = []
238 var missing_exclude_roots:Array = []
239 var unexpected_game_data_includes:Array = []
240 for root_value
in _string_array(required_shared_roots):
241 if not include_roots.has(str(root_value)):
242 missing_shared_roots.append(str(root_value))
243 for root_value
in _string_array(required_game_roots):
244 if not include_roots.has(str(root_value)):
245 missing_game_roots.append(str(root_value))
246 for other_game_id_value
in _string_array(all_game_ids):
247 var other_game_id:String = str(other_game_id_value)
248 if other_game_id ==
"" or other_game_id == game_id
or other_game_id ==
"all_demos":
250 var other_data_root:String =
"res://data/games/%s" % other_game_id
251 if include_roots.has(other_data_root):
252 unexpected_game_data_includes.append(other_data_root)
253 if not exclude_roots.has(other_data_root):
254 missing_exclude_roots.append(other_data_root)
257 "include_roots": include_roots,
258 "exclude_roots": exclude_roots,
259 "missing_shared_roots": missing_shared_roots,
260 "missing_game_roots": missing_game_roots,
261 "missing_exclude_roots": missing_exclude_roots,
262 "unexpected_game_data_includes": unexpected_game_data_includes,
263 "valid": missing_shared_roots.is_empty()
and missing_game_roots.is_empty()
and missing_exclude_roots.is_empty()
and unexpected_game_data_includes.is_empty()
269func audit_single_game_profiles(profile_ids:Array = [], required_shared_roots_by_game:Dictionary = {}, required_game_roots_by_game:Dictionary = {}) -> Dictionary:
270 var ids:Array = _string_array(profile_ids)
272 ids = get_profile_ids()
273 var all_game_ids:Array = []
275 var profile_id:String = str(id_value)
276 if profile_id !=
"" and profile_id !=
"all_demos":
277 all_game_ids.append(profile_id)
280 for id_value
in all_game_ids:
281 var game_id:String = str(id_value)
282 var profile:Dictionary = get_profile(game_id)
283 if profile.is_empty():
287 "missing_profile": true
291 var shared_roots:Array = _string_array(required_shared_roots_by_game.get(game_id, []))
292 var game_roots:Array = _string_array(required_game_roots_by_game.get(game_id, []))
293 var audit:Dictionary = audit_single_game_profile(profile, all_game_ids, shared_roots, game_roots)
294 audits[game_id] = audit
295 if not bool(audit.get(
"valid", false)):
300 "game_ids": all_game_ids
304func _resource_exists(path:String) -> bool:
307 if FileAccess.file_exists(path):
309 return DirAccess.open(path) != null
312func get_cache_stats() -> Dictionary:
313 return data_cache_manager.get_cache_stats()
316func _string_array(value) -> Array:
318 if not value
is Array:
321 result.append(str(item))
325func _issue(type:String, path:String, message:String) -> Dictionary:
326 return {
"type": type,
"path": path,
"message": message}
bool
Return compact rows suitable for editor pickers, menus, or CI dashboards.