RapidGameFramework
Reusable Godot managers for data-driven small games
Loading...
Searching...
No Matches
menu_manager.gd
Go to the documentation of this file.
1extends RefCounted
2
3
12
13const DataCacheManager = preload("res://scripts/systems/dataCacheManager/data_cache_manager.gd")
14
15var config := {}
16var current_screen_id := ""
17var context := {}
18var screen_stack := []
19var data_cache_manager = DataCacheManager.new()
20
21
22
23func load_menu(file_path:String) -> void:
24 var data = data_cache_manager.load_json(file_path)
25 config = data if data is Dictionary else {}
26 current_screen_id = str(config.get("start_screen", ""))
27 screen_stack.clear()
28
29
30
31func get_cache_stats() -> Dictionary:
32 return data_cache_manager.get_cache_stats()
33
34
35
36func set_context(values:Dictionary) -> void:
37 context = values.duplicate(true)
38
39
40
41func update_context(values:Dictionary) -> void:
42 for key in values.keys():
43 context[key] = values[key]
44
45
46
47func show_screen(screen_id:String, add_to_stack:bool = true) -> bool:
48 if not get_screens().has(screen_id):
49 return false
50 if add_to_stack and current_screen_id != "":
51 screen_stack.append(current_screen_id)
52 current_screen_id = screen_id
53 return true
54
55
56
57func go_back() -> bool:
58 if screen_stack.is_empty():
59 return false
60 current_screen_id = str(screen_stack.pop_back())
61 return true
62
63
64
65func get_current_screen() -> Dictionary:
66 return get_screen(current_screen_id)
67
68
69
70func get_screen(screen_id:String) -> Dictionary:
71 return get_screens().get(screen_id, {})
72
73
74
75func get_screens() -> Dictionary:
76 return config.get("screens", {})
77
78
79
80func get_current_screen_id() -> String:
81 return current_screen_id
82
83
84
85func get_render_items(screen_id:String = "") -> Array:
86 var screen = get_current_screen() if screen_id == "" else get_screen(screen_id)
87 var items = []
88 for raw_item in screen.get("items", []):
89 if not raw_item is Dictionary:
90 continue
91 for expanded_item in expand_item(raw_item):
92 if not is_item_visible(expanded_item):
93 continue
94 expanded_item["disabled"] = is_item_disabled(expanded_item)
95 expanded_item["display_label"] = format_item_label(expanded_item)
96 items.append(expanded_item)
97 return items
98
99
100
101func expand_item(item:Dictionary) -> Array:
102 var template_id = str(item.get("template", ""))
103 if template_id == "":
104 return [item.duplicate(true)]
105 if template_id == "save_slots":
106 return _expand_save_slots(item)
107 if template_id == "game_profiles":
108 return _expand_game_profiles(item)
109 if template_id == "platformer_levels":
110 return _expand_platformer_levels(item)
111 var templates = config.get("templates", {})
112 if templates.has(template_id) and templates[template_id] is Array:
113 var expanded = []
114 for template_item in templates[template_id]:
115 if template_item is Dictionary:
116 expanded.append(_merge_template_item(template_item, item))
117 return expanded
118 return []
119
120
121
122func is_item_visible(item:Dictionary) -> bool:
123 if not item.has("visible_when"):
124 return true
125 return _evaluate_condition(item.get("visible_when", {}))
126
127
128
129func is_item_disabled(item:Dictionary) -> bool:
130 if not item.has("disabled_when"):
131 return false
132 return _evaluate_condition(item.get("disabled_when", {}))
133
134
135
136func format_item_label(item:Dictionary) -> String:
137 var label = str(item.get("label", "Menu Item"))
138 var formatter = str(item.get("metadata_formatter", ""))
139 if formatter == "":
140 return label
141 var data = item.get("metadata", context.get(formatter, {}))
142 if data is Dictionary and not data.is_empty():
143 return label + "\n" + _format_metadata(data)
144 return label
145
146
147
148func get_confirmation(item:Dictionary) -> Dictionary:
149 var confirm = item.get("confirm", {})
150 return confirm if confirm is Dictionary else {}
151
152
153
154func get_item_sprite_id(item:Dictionary) -> String:
155 var explicit = str(item.get("sprite_id", "")).strip_edges()
156 if explicit != "":
157 return explicit
158 var icons = config.get("icons", {})
159 if not icons is Dictionary:
160 return ""
161 var label_icons = icons.get("labels", {})
162 if label_icons is Dictionary:
163 var label_key = str(item.get("label", item.get("display_label", ""))).strip_edges().to_lower()
164 if label_icons.has(label_key):
165 return str(label_icons[label_key])
166 var action = str(item.get("action", ""))
167 var action_icons = icons.get("actions", {})
168 if action_icons is Dictionary and action_icons.has(action):
169 return str(action_icons[action])
170 if action == "screen":
171 var target = str(item.get("target", ""))
172 var target_icons = icons.get("targets", {})
173 if target_icons is Dictionary and target_icons.has(target):
174 return str(target_icons[target])
175 return str(icons.get("fallback", ""))
176
177
178func _format_metadata(data:Dictionary) -> String:
179 if data.has("status"):
180 return str(data["status"])
181 if data.has("money") or data.has("cards") or data.has("wins") or data.has("losses"):
182 var updated = str(data.get("updated_label", ""))
183 var lines = [
184 "$%d | %d cards" % [int(data.get("money", 0)), int(data.get("cards", 0))],
185 "%dW / %dL" % [int(data.get("wins", 0)), int(data.get("losses", 0))]
186 ]
187 if updated != "":
188 lines.append("Updated %s" % updated)
189 return "\n".join(lines)
190 var parts = []
191 for key in data.keys():
192 if str(key) in ["empty", "updated_at", "slot"]:
193 continue
194 parts.append("%s: %s" % [str(key).capitalize(), str(data[key])])
195 return " | ".join(parts)
196
197
198func _evaluate_condition(rule) -> bool:
199 if rule is Array:
200 for child_rule in rule:
201 if _evaluate_condition(child_rule):
202 return true
203 return false
204 if not rule is Dictionary:
205 return false
206 if rule.has("all"):
207 for child_rule in rule["all"]:
208 if not _evaluate_condition(child_rule):
209 return false
210 return true
211 if rule.has("any"):
212 for child_rule in rule["any"]:
213 if _evaluate_condition(child_rule):
214 return true
215 return false
216 if rule.has("not"):
217 return not _evaluate_condition(rule["not"])
218 var key = str(rule.get("key", ""))
219 var value = _get_context_value(key)
220 if rule.has("equals"):
221 return value == rule["equals"]
222 if rule.has("not_equals"):
223 return value != rule["not_equals"]
224 if rule.has("greater_than"):
225 return float(value) > float(rule["greater_than"])
226 if rule.has("less_than"):
227 return float(value) < float(rule["less_than"])
228 if rule.has("at_least"):
229 return float(value) >= float(rule["at_least"])
230 if rule.has("at_most"):
231 return float(value) <= float(rule["at_most"])
232 if rule.has("missing"):
233 return bool(rule["missing"]) and (value == null or value == false)
234 if rule.has("present"):
235 return bool(rule["present"]) and value != null and value != false
236 return false
237
238
239func _get_context_value(path:String):
240 if path == "":
241 return null
242 var parts = path.split(".")
243 var value = context
244 for part in parts:
245 if value is Dictionary and value.has(part):
246 value = value[part]
247 else:
248 return null
249 return value
250
251
252func _expand_save_slots(item:Dictionary) -> Array:
253 var slots = context.get(str(item.get("source", "save_slots")), [])
254 var expanded = []
255 for slot in slots:
256 if not slot is Dictionary:
257 continue
258 var slot_item = item.duplicate(true)
259 slot_item.erase("template")
260 slot_item["label"] = _format_template(str(item.get("label_template", "Slot {slot}")), slot)
261 slot_item["metadata"] = slot
262 slot_item["action"] = str(item.get("slot_action", item.get("action", "save_slot")))
263 slot_item["slot"] = int(slot.get("slot", 0))
264 slot_item["empty"] = bool(slot.get("empty", false))
265 if bool(slot.get("empty", false)):
266 slot_item["display_state"] = "empty"
267 expanded.append(slot_item)
268 return expanded
269
270
271func _expand_game_profiles(item:Dictionary) -> Array:
272 var profiles = context.get(str(item.get("source", "game_profiles")), [])
273 var expanded = []
274 for profile in profiles:
275 if not profile is Dictionary:
276 continue
277 var profile_item = item.duplicate(true)
278 profile_item.erase("template")
279 profile_item["label"] = str(profile.get("menu_label", profile.get("label", profile.get("id", "Game"))))
280 profile_item["action"] = str(profile.get("action", item.get("profile_action", "screen")))
281 profile_item["target"] = str(profile.get("target", profile.get("menu_route", profile.get("id", ""))))
282 profile_item["game_id"] = str(profile.get("id", profile.get("game_id", "")))
283 profile_item["metadata"] = profile.duplicate(true)
284 if profile.has("sprite_id"):
285 profile_item["sprite_id"] = str(profile["sprite_id"])
286 if profile.has("description"):
287 profile_item["description"] = str(profile["description"])
288 expanded.append(profile_item)
289 return expanded
290
291
292func _expand_platformer_levels(item:Dictionary) -> Array:
293 var levels = context.get(str(item.get("source", "platformer_levels")), [])
294 var expanded := []
295 for level in levels:
296 if not level is Dictionary:
297 continue
298 var level_item = item.duplicate(true)
299 level_item.erase("template")
300 level_item["label"] = str(level.get("display_label", level.get("name", level.get("id", "Level"))))
301 level_item["action"] = str(item.get("level_action", "platformer_select_level"))
302 level_item["level_id"] = str(level.get("id", ""))
303 level_item["metadata"] = level.duplicate(true)
304 if bool(level.get("locked", false)):
305 level_item["disabled_when"] = {"key": "platformer_level_locks.%s" % str(level.get("id", "")), "equals": true}
306 level_item["sprite_id"] = "lock" if bool(level.get("locked", false)) else ("check" if bool(level.get("cleared", false)) else "play")
307 expanded.append(level_item)
308 return expanded
309
310
311func _format_template(template:String, values:Dictionary) -> String:
312 var formatted = template
313 for key in values.keys():
314 formatted = formatted.replace("{%s}" % str(key), str(values[key]))
315 return formatted
316
317
318func _merge_template_item(template_item:Dictionary, source_item:Dictionary) -> Dictionary:
319 var merged = template_item.duplicate(true)
320 for key in source_item.keys():
321 if key != "template":
322 merged[key] = source_item[key]
323 return merged
bool
Set current screen id when it exists.