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)))
81func normalize_profile(profile:Dictionary) -> Dictionary:
82 var normalized = profile.duplicate(true)
83 normalized[
"game_id"] = str(normalized.get(
"game_id",
""))
84 normalized[
"label"] = str(normalized.get(
"label", normalized.get(
"game_id",
"")))
85 normalized[
"start_scene"] = str(normalized.get(
"start_scene",
"res://scenes/splash_screen.tscn"))
86 normalized[
"entry_scene"] = str(normalized.get(
"entry_scene", normalized.get(
"start_scene",
"res://scenes/splash_screen.tscn")))
87 normalized[
"launch_scene"] = str(normalized.get(
"launch_scene", normalized.get(
"start_scene",
"res://scenes/start_menu.tscn")))
88 normalized[
"menu_route"] = str(normalized.get(
"menu_route", normalized.get(
"game_id",
"")))
89 normalized[
"output_name"] = str(normalized.get(
"output_name", normalized.get(
"game_id",
"game")))
90 normalized[
"include_roots"] = _string_array(normalized.get(
"include_roots", []))
91 normalized[
"shared_roots"] = _string_array(normalized.get(
"shared_roots", []))
92 normalized[
"exclude_roots"] = _string_array(normalized.get(
"exclude_roots", []))
93 normalized[
"exclude_game_ids"] = _string_array(normalized.get(
"exclude_game_ids", []))
94 normalized[
"platforms"] = normalized.get(
"platforms", {})
if normalized.get(
"platforms", {})
is Dictionary
else {}
95 normalized[
"metadata"] = normalized.get(
"metadata", {})
if normalized.get(
"metadata", {})
is Dictionary
else {}
100func validate_profile(profile:Dictionary) -> Array:
101 var normalized = normalize_profile(profile)
103 for field
in [
"game_id",
"label",
"start_scene",
"output_name"]:
104 if str(normalized.get(field,
"")).strip_edges() ==
"":
105 issues.append(_issue(
"missing_field", field,
"Build profile is missing '%s'." % field))
106 if not FileAccess.file_exists(str(normalized.get(
"start_scene",
""))):
107 issues.append(_issue(
"missing_scene",
"start_scene",
"Start scene does not exist."))
108 if str(normalized.get(
"entry_scene",
"")).strip_edges() !=
"" and not FileAccess.file_exists(str(normalized.get(
"entry_scene",
""))):
109 issues.append(_issue(
"missing_scene",
"entry_scene",
"Entry scene does not exist."))
110 if str(normalized.get(
"launch_scene",
"")).strip_edges() !=
"" and not FileAccess.file_exists(str(normalized.get(
"launch_scene",
""))):
111 issues.append(_issue(
"missing_scene",
"launch_scene",
"Launch scene does not exist."))
112 if normalized.get(
"include_roots", []).is_empty():
113 issues.append(_issue(
"missing_include_roots",
"include_roots",
"At least one game include root is required."))
114 if normalized.get(
"platforms", {}).is_empty():
115 issues.append(_issue(
"missing_platforms",
"platforms",
"At least one platform artifact definition is required."))
116 for root
in get_all_include_roots(normalized):
117 if not _resource_exists(root):
118 issues.append(_issue(
"missing_include_root", root,
"Included resource root does not exist."))
119 for platform_id
in normalized.get(
"platforms", {}).keys():
120 var platform = normalized[
"platforms"][platform_id]
121 if not platform
is Dictionary:
122 issues.append(_issue(
"invalid_platform", str(platform_id),
"Platform entry must be an object."))
124 if str(platform.get(
"preset",
"")).strip_edges() ==
"":
125 issues.append(_issue(
"missing_platform_preset", str(platform_id),
"Platform entry is missing export preset name."))
126 if str(platform.get(
"artifact",
"")).strip_edges() ==
"":
127 issues.append(_issue(
"missing_platform_artifact", str(platform_id),
"Platform entry is missing artifact path."))
132func get_all_include_roots(profile:Dictionary) -> Array:
134 for root
in _string_array(profile.get(
"shared_roots", [])) + _string_array(profile.get(
"include_roots", [])):
135 if root !=
"" and not roots.has(root):
142func get_exclude_roots(profile:Dictionary, all_game_ids:Array = []) -> Array:
143 var normalized = normalize_profile(profile)
144 var excludes := _string_array(normalized.get(
"exclude_roots", []))
145 var ids = _string_array(normalized.get(
"exclude_game_ids", []))
147 for game_id
in _string_array(all_game_ids):
148 if game_id != str(normalized.get(
"game_id",
"")):
151 var root =
"res://data/games/%s" % game_id
152 if not excludes.has(root):
153 excludes.append(root)
158func get_platform_artifacts(profile:Dictionary) -> Dictionary:
159 var normalized = normalize_profile(profile)
160 var output_name = str(normalized.get(
"output_name",
"Game"))
162 for platform_id
in normalized.get(
"platforms", {}).keys():
163 var platform = normalized[
"platforms"][platform_id]
164 if not platform
is Dictionary:
166 var row = platform.duplicate(true)
167 if str(row.get(
"artifact",
"")) ==
"":
168 row[
"artifact"] =
"builds/%s/%s" % [str(platform_id), output_name]
169 artifacts[str(platform_id)] = row
174func get_export_plan(profile:Dictionary, all_game_ids:Array = []) -> Dictionary:
175 var normalized = normalize_profile(profile)
177 "game_id": normalized.get(
"game_id",
""),
178 "label": normalized.get(
"label",
""),
179 "start_scene": normalized.get(
"start_scene",
""),
180 "entry_scene": normalized.get(
"entry_scene",
""),
181 "launch_scene": normalized.get(
"launch_scene",
""),
182 "menu_route": normalized.get(
"menu_route",
""),
183 "include_roots": get_all_include_roots(normalized),
184 "exclude_roots": get_exclude_roots(normalized, all_game_ids),
185 "platforms": get_platform_artifacts(normalized),
186 "metadata": normalized.get(
"metadata", {})
193func audit_single_game_profile(profile:Dictionary, all_game_ids:Array = [], required_shared_roots:Array = [], required_game_roots:Array = []) -> Dictionary:
194 var normalized:Dictionary = normalize_profile(profile)
195 var game_id:String = str(normalized.get(
"game_id",
""))
196 var include_roots:Array = get_all_include_roots(normalized)
197 var exclude_roots:Array = get_exclude_roots(normalized, all_game_ids)
198 var missing_shared_roots:Array = []
199 var missing_game_roots:Array = []
200 var missing_exclude_roots:Array = []
201 var unexpected_game_data_includes:Array = []
202 for root_value
in _string_array(required_shared_roots):
203 if not include_roots.has(str(root_value)):
204 missing_shared_roots.append(str(root_value))
205 for root_value
in _string_array(required_game_roots):
206 if not include_roots.has(str(root_value)):
207 missing_game_roots.append(str(root_value))
208 for other_game_id_value
in _string_array(all_game_ids):
209 var other_game_id:String = str(other_game_id_value)
210 if other_game_id ==
"" or other_game_id == game_id
or other_game_id ==
"all_demos":
212 var other_data_root:String =
"res://data/games/%s" % other_game_id
213 if include_roots.has(other_data_root):
214 unexpected_game_data_includes.append(other_data_root)
215 if not exclude_roots.has(other_data_root):
216 missing_exclude_roots.append(other_data_root)
219 "include_roots": include_roots,
220 "exclude_roots": exclude_roots,
221 "missing_shared_roots": missing_shared_roots,
222 "missing_game_roots": missing_game_roots,
223 "missing_exclude_roots": missing_exclude_roots,
224 "unexpected_game_data_includes": unexpected_game_data_includes,
225 "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()
231func audit_single_game_profiles(profile_ids:Array = [], required_shared_roots_by_game:Dictionary = {}, required_game_roots_by_game:Dictionary = {}) -> Dictionary:
232 var ids:Array = _string_array(profile_ids)
234 ids = get_profile_ids()
235 var all_game_ids:Array = []
237 var profile_id:String = str(id_value)
238 if profile_id !=
"" and profile_id !=
"all_demos":
239 all_game_ids.append(profile_id)
242 for id_value
in all_game_ids:
243 var game_id:String = str(id_value)
244 var profile:Dictionary = get_profile(game_id)
245 if profile.is_empty():
249 "missing_profile": true
253 var shared_roots:Array = _string_array(required_shared_roots_by_game.get(game_id, []))
254 var game_roots:Array = _string_array(required_game_roots_by_game.get(game_id, []))
255 var audit:Dictionary = audit_single_game_profile(profile, all_game_ids, shared_roots, game_roots)
256 audits[game_id] = audit
257 if not bool(audit.get(
"valid", false)):
262 "game_ids": all_game_ids
266func _resource_exists(path:String) -> bool:
269 if FileAccess.file_exists(path):
271 return DirAccess.open(path) != null
274func get_cache_stats() -> Dictionary:
275 return data_cache_manager.get_cache_stats()
278func _string_array(value) -> Array:
280 if not value
is Array:
283 result.append(str(item))
287func _issue(type:String, path:String, message:String) -> Dictionary:
288 return {
"type": type,
"path": path,
"message": message}
bool
Return compact rows suitable for editor pickers, menus, or CI dashboards.