RapidGameFramework
Reusable Godot managers for data-driven small games
Loading...
Searching...
No Matches
adventure_manager.gd
Go to the documentation of this file.
1extends RefCounted
2
3
6
7const DataCacheManager = preload("res://scripts/systems/dataCacheManager/data_cache_manager.gd")
8
9var adventures := {}
10var data_cache_manager = DataCacheManager.new()
11
12
13
14func load_adventure(file_path:String) -> bool:
15 return load_adventure_data(data_cache_manager.load_json(file_path))
16
17
18
19func get_cache_stats() -> Dictionary:
20 return data_cache_manager.get_cache_stats()
21
22
23
24func load_adventure_data(data) -> bool:
25 if not data is Dictionary:
26 return false
27 return register_adventure(data)
28
29
30
31func load_adventures(file_paths:Array) -> int:
32 var loaded := 0
33 for file_path in file_paths:
34 if load_adventure(str(file_path)):
35 loaded += 1
36 return loaded
37
38
39
40func load_adventures_data(adventure_rows:Array) -> int:
41 var loaded := 0
42 for adventure_data in adventure_rows:
43 if load_adventure_data(adventure_data):
44 loaded += 1
45 return loaded
46
47
48
49func register_adventure(data:Dictionary) -> bool:
50 var id = str(data.get("id", ""))
51 if id == "" or not (data.get("levels", []) is Array) or data.get("levels", []).is_empty():
52 return false
53 adventures[id] = data.duplicate(true)
54 return true
55
56
57func has_adventure(id:String) -> bool:
58 return adventures.has(id)
59
60
61func get_adventure(id:String) -> Dictionary:
62 return adventures.get(id, {}).duplicate(true)
63
64
65func list_adventures() -> Array:
66 var rows := []
67 for id in adventures.keys():
68 var adventure = adventures[id]
69 rows.append({
70 "id": id,
71 "name": str(adventure.get("name", id)),
72 "description": str(adventure.get("description", "")),
73 "level_count": adventure.get("levels", []).size(),
74 "estimated_minutes": int(adventure.get("estimated_minutes", 0)),
75 "rewards": adventure.get("rewards", {}).duplicate(true)
76 })
77 rows.sort_custom(func(a, b): return str(a.get("name", "")) < str(b.get("name", "")))
78 return rows
79
80
81func get_level(adventure_id:String, level_index:int) -> Dictionary:
82 var levels = adventures.get(adventure_id, {}).get("levels", [])
83 if level_index < 0 or level_index >= levels.size():
84 return {}
85 return levels[level_index].duplicate(true)
86
87
88
89func validate_adventure(adventure_id:String) -> Array:
90 var issues := []
91 var adventure = adventures.get(adventure_id, {})
92 if adventure.is_empty():
93 return ["Missing adventure: %s" % adventure_id]
94 if str(adventure.get("id", "")) == "":
95 issues.append("Adventure is missing id.")
96 var levels = adventure.get("levels", [])
97 if not (levels is Array) or levels.is_empty():
98 issues.append("Adventure has no levels.")
99 for index in range(levels.size()):
100 var level = levels[index]
101 if not level is Dictionary:
102 issues.append("Level %d is not a dictionary." % index)
103 continue
104 if str(level.get("id", "")) == "":
105 issues.append("Level %d is missing id." % index)
106 if str(level.get("name", "")) == "":
107 issues.append("Level %d is missing name." % index)
108 if not level.has("layout") and not level.has("procedural_template"):
109 issues.append("Level %d needs layout or procedural_template." % index)
110 if not (level.get("objectives", []) is Array) or level.get("objectives", []).is_empty():
111 issues.append("Level %d has no objectives." % index)
112 if not (level.get("exits", []) is Array) or level.get("exits", []).is_empty():
113 issues.append("Level %d has no exits." % index)
114 if not level.has("player_spawn"):
115 issues.append("Level %d has no player_spawn." % index)
116 return issues
117
118
119
120func create_run_state(adventure_id:String) -> Dictionary:
121 if not adventures.has(adventure_id):
122 return {}
123 return {
124 "adventure_id": adventure_id,
125 "level_index": 0,
126 "completed_objectives": {},
127 "completed_levels": [],
128 "collected_rewards": {},
129 "finished": false
130 }
131
132
133func get_current_level(run_state:Dictionary) -> Dictionary:
134 return get_level(str(run_state.get("adventure_id", "")), int(run_state.get("level_index", 0)))
135
136
137func complete_objective(run_state:Dictionary, objective_id:String) -> Dictionary:
138 var next_state = run_state.duplicate(true)
139 var level = get_current_level(next_state)
140 if level.is_empty() or objective_id == "":
141 return next_state
142 var level_id = str(level.get("id", ""))
143 var completed = next_state.get("completed_objectives", {}).duplicate(true)
144 var level_completed = completed.get(level_id, [])
145 if not level_completed.has(objective_id):
146 level_completed.append(objective_id)
147 completed[level_id] = level_completed
148 next_state["completed_objectives"] = completed
149 return next_state
150
151
152func is_current_level_complete(run_state:Dictionary) -> bool:
153 var level = get_current_level(run_state)
154 if level.is_empty():
155 return false
156 var required := []
157 for objective in level.get("objectives", []):
158 if objective is Dictionary and bool(objective.get("required", true)):
159 required.append(str(objective.get("id", "")))
160 var completed = run_state.get("completed_objectives", {}).get(str(level.get("id", "")), [])
161 for objective_id in required:
162 if objective_id != "" and not completed.has(objective_id):
163 return false
164 return true
165
166
167func get_progress_summary(run_state:Dictionary) -> Dictionary:
168 var adventure_id = str(run_state.get("adventure_id", ""))
169 var adventure = get_adventure(adventure_id)
170 var levels = adventure.get("levels", [])
171 var level_index = clamp(int(run_state.get("level_index", 0)), 0, max(0, levels.size() - 1))
172 var level = get_level(adventure_id, level_index)
173 var completed = run_state.get("completed_objectives", {}).get(str(level.get("id", "")), [])
174 var required_total := 0
175 var optional_total := 0
176 var required_done := 0
177 var optional_done := 0
178 for objective in level.get("objectives", []):
179 if not objective is Dictionary:
180 continue
181 var objective_id = str(objective.get("id", ""))
182 if bool(objective.get("required", true)):
183 required_total += 1
184 if completed.has(objective_id):
185 required_done += 1
186 else:
187 optional_total += 1
188 if completed.has(objective_id):
189 optional_done += 1
190 return {
191 "adventure_id": adventure_id,
192 "adventure_name": str(adventure.get("name", adventure_id)),
193 "level_index": level_index,
194 "level_count": levels.size(),
195 "level_id": str(level.get("id", "")),
196 "level_name": str(level.get("name", "")),
197 "required_done": required_done,
198 "required_total": required_total,
199 "optional_done": optional_done,
200 "optional_total": optional_total,
201 "finished": bool(run_state.get("finished", false))
202 }
203
204
205
206func get_pacing_summary(adventure_id:String) -> Dictionary:
207 var adventure = get_adventure(adventure_id)
208 var levels = adventure.get("levels", [])
209 var estimated_minutes = int(adventure.get("estimated_minutes", 0))
210 var required_objectives := 0
211 var optional_objectives := 0
212 var encounter_count := 0
213 var material_count := 0
214 for level in levels:
215 if not level is Dictionary:
216 continue
217 for objective in level.get("objectives", []):
218 if not objective is Dictionary:
219 continue
220 if bool(objective.get("required", true)):
221 required_objectives += 1
222 else:
223 optional_objectives += 1
224 var explicit_encounters = 0
225 if level.get("enemies", []) is Array:
226 explicit_encounters += level.get("enemies", []).size()
227 if level.get("bosses", []) is Array:
228 explicit_encounters += level.get("bosses", []).size()
229 var procedural_budget = int(level.get("procedural_template", {}).get("enemy_budget", 0)) if level.get("procedural_template", {}) is Dictionary else 0
230 encounter_count += max(explicit_encounters, procedural_budget)
231 if level.get("procedural_template", {}) is Dictionary:
232 material_count += int(level.get("procedural_template", {}).get("material_nodes", 0))
233 for object in level.get("objects", []):
234 if object is Dictionary and str(object.get("kind", "")) == "resource":
235 material_count += int(object.get("amount", 1))
236 return {
237 "adventure_id": adventure_id,
238 "estimated_minutes": estimated_minutes,
239 "level_count": levels.size(),
240 "required_objectives": required_objectives,
241 "optional_objectives": optional_objectives,
242 "encounter_count": encounter_count,
243 "material_count": material_count,
244 "mvp_length_ready": estimated_minutes >= 8 and estimated_minutes <= 12,
245 "has_required_objectives": required_objectives > 0,
246 "has_completion_encounter": encounter_count > 0
247 }
248
249
250func advance_level(run_state:Dictionary) -> Dictionary:
251 var next_state = run_state.duplicate(true)
252 if not is_current_level_complete(next_state):
253 return next_state
254 var adventure = get_adventure(str(next_state.get("adventure_id", "")))
255 var current_index = int(next_state.get("level_index", 0))
256 var completed_levels = next_state.get("completed_levels", []).duplicate()
257 var current_level = get_level(str(next_state.get("adventure_id", "")), current_index)
258 var level_id = str(current_level.get("id", ""))
259 if level_id != "" and not completed_levels.has(level_id):
260 completed_levels.append(level_id)
261 next_state["completed_levels"] = completed_levels
262 if current_index + 1 >= adventure.get("levels", []).size():
263 next_state["finished"] = true
264 else:
265 next_state["level_index"] = current_index + 1
266 return next_state
267
268