]> Untitled Git - wolf-seeking-sheep.git/blob - addons/dialogic/Core/DialogicResourceUtil.gd
Updated export config options
[wolf-seeking-sheep.git] / addons / dialogic / Core / DialogicResourceUtil.gd
1 @tool
2 class_name DialogicResourceUtil
3
4 static var label_cache := {}
5 static var event_cache: Array[DialogicEvent] = []
6
7 static var special_resources := {}
8
9
10 static func update() -> void:
11         update_directory('.dch')
12         update_directory('.dtl')
13         update_label_cache()
14
15
16 #region RESOURCE DIRECTORIES
17 ################################################################################
18
19 static func get_directory(extension:String) -> Dictionary:
20         extension = extension.trim_prefix('.')
21         if Engine.has_meta(extension+'_directory'):
22                 return Engine.get_meta(extension+'_directory', {})
23
24         var directory: Dictionary = ProjectSettings.get_setting("dialogic/directories/"+extension+'_directory', {})
25         Engine.set_meta(extension+'_directory', directory)
26         return directory
27
28
29 static func set_directory(extension:String, directory:Dictionary) -> void:
30         extension = extension.trim_prefix('.')
31         if Engine.is_editor_hint():
32                 ProjectSettings.set_setting("dialogic/directories/"+extension+'_directory', directory)
33                 ProjectSettings.save()
34         Engine.set_meta(extension+'_directory', directory)
35
36
37 static func update_directory(extension:String) -> void:
38         var directory := get_directory(extension)
39
40         for resource in list_resources_of_type(extension):
41                 if not resource in directory.values():
42                         directory = add_resource_to_directory(resource, directory)
43
44         var keys_to_remove := []
45         for key in directory:
46                 if not ResourceLoader.exists(directory[key]):
47                         keys_to_remove.append(key)
48         for key in keys_to_remove:
49                 directory.erase(key)
50
51         set_directory(extension, directory)
52
53
54 static func add_resource_to_directory(file_path:String, directory:Dictionary) -> Dictionary:
55         var suggested_name := file_path.get_file().trim_suffix("."+file_path.get_extension())
56         while suggested_name in directory:
57                 suggested_name = file_path.trim_suffix("/"+suggested_name+"."+file_path.get_extension()).get_file().path_join(suggested_name)
58         directory[suggested_name] = file_path
59         return directory
60
61
62 ## Returns the unique identifier for the given resource path.
63 ## Returns an empty string if no identifier was found.
64 static func get_unique_identifier(file_path:String) -> String:
65         var identifier: String = get_directory(file_path.get_extension()).find_key(file_path)
66         if typeof(identifier) == TYPE_STRING:
67                 return identifier
68         return ""
69
70
71 ## Returns the resource associated with the given unique identifier.
72 ## The expected extension is needed to use the right directory.
73 static func get_resource_from_identifier(identifier:String, extension:String) -> Resource:
74         var path: String = get_directory(extension).get(identifier, '')
75         if ResourceLoader.exists(path):
76                 return load(path)
77         return null
78
79
80 static func change_unique_identifier(file_path:String, new_identifier:String) -> void:
81         var directory := get_directory(file_path.get_extension())
82         var key: String = directory.find_key(file_path)
83         while key != null:
84                 if key == new_identifier:
85                         break
86                 directory.erase(key)
87                 directory[new_identifier] = file_path
88                 key = directory.find_key(file_path)
89         set_directory(file_path.get_extension(), directory)
90
91
92 static func change_resource_path(old_path:String, new_path:String) -> void:
93         var directory := get_directory(new_path.get_extension())
94         var key: String = directory.find_key(old_path)
95         while key != null:
96                 directory[key] = new_path
97                 key = directory.find_key(old_path)
98         set_directory(new_path.get_extension(), directory)
99
100
101 static func remove_resource(file_path:String) -> void:
102         var directory := get_directory(file_path.get_extension())
103         var key: String = directory.find_key(file_path)
104         while key != null:
105                 directory.erase(key)
106                 key = directory.find_key(file_path)
107         set_directory(file_path.get_extension(), directory)
108
109
110 static func is_identifier_unused(extension:String, identifier:String) -> bool:
111         return not identifier in get_directory(extension)
112
113 #endregion
114
115 #region LABEL CACHE
116 ################################################################################
117 # The label cache is only for the editor so we don't have to scan all timelines
118 # whenever we want to suggest labels. This has no use in game and is not always perfect.
119
120 static func get_label_cache() -> Dictionary:
121         if not label_cache.is_empty():
122                 return label_cache
123
124         label_cache = DialogicUtil.get_editor_setting('label_ref', {})
125         return label_cache
126
127
128 static func set_label_cache(cache:Dictionary) -> void:
129         label_cache = cache
130
131
132 static func update_label_cache() -> void:
133         var cache := get_label_cache()
134         var timelines := get_timeline_directory().values()
135         for timeline in cache:
136                 if !timeline in timelines:
137                         cache.erase(timeline)
138         set_label_cache(cache)
139
140 #endregion
141
142 #region EVENT CACHE
143 ################################################################################
144
145 ##  Dialogic keeps a list that has each event once. This allows retrieval of that list.
146 static func get_event_cache() -> Array:
147         if not event_cache.is_empty():
148                 return event_cache
149
150         event_cache = update_event_cache()
151         return event_cache
152
153
154 static func update_event_cache() -> Array:
155         event_cache = []
156         for indexer in DialogicUtil.get_indexers():
157                 # build event cache
158                 for event in indexer._get_events():
159                         if not ResourceLoader.exists(event):
160                                 continue
161                         if not 'event_end_branch.gd' in event and not 'event_text.gd' in event:
162                                 event_cache.append(load(event).new())
163
164         # Events are checked in order while testing them. EndBranch needs to be first, Text needs to be last
165         event_cache.push_front(DialogicEndBranchEvent.new())
166         event_cache.push_back(DialogicTextEvent.new())
167
168         return event_cache
169
170 #endregion
171
172 #region SPECIAL RESOURCES
173 ################################################################################
174
175 static func update_special_resources() -> void:
176         special_resources.clear()
177         for indexer in DialogicUtil.get_indexers():
178                 var additions := indexer._get_special_resources()
179                 for resource_type in additions:
180                         if not resource_type in special_resources:
181                                 special_resources[resource_type] = {}
182                         special_resources[resource_type].merge(additions[resource_type])
183
184
185 static func list_special_resources(type:String, filter := {}) -> Dictionary:
186         if special_resources.is_empty():
187                 update_special_resources()
188         if type in special_resources:
189                 if filter.is_empty():
190                         return special_resources[type]
191                 else:
192                         var results := {}
193                         for i in special_resources[type]:
194                                 if match_resource_filter(special_resources[type][i], filter):
195                                         results[i] = special_resources[type][i]
196                         return results
197         return {}
198
199
200 static func match_resource_filter(dict:Dictionary, filter:Dictionary) -> bool:
201         for i in filter:
202                 if not i in dict:
203                         return false
204                 if typeof(filter[i]) == TYPE_ARRAY:
205                         if not dict[i] in filter[i]:
206                                 return false
207                 else:
208                         if not dict[i] == filter[i]:
209                                 return false
210         return true
211
212
213 static func guess_special_resource(type: String, string: String, default := {}, filter := {}, ignores:PackedStringArray=[]) -> Dictionary:
214         if string.is_empty():
215                 return default
216
217         if special_resources.is_empty():
218                 update_special_resources()
219         var resources := list_special_resources(type, filter)
220         if resources.is_empty():
221                 printerr("[Dialogic] No ", type, "s found, but attempted to use one.")
222                 return default
223
224         if string.begins_with('res://'):
225                 for i in resources.values():
226                         if i.path == string:
227                                 return i
228                 printerr("[Dialogic] Unable to find ", type, " at path '", string, "'.")
229                 return default
230
231         string = string.to_lower()
232
233         if string in resources:
234                 return resources[string]
235
236         if not ignores.is_empty():
237                 var regex := RegEx.create_from_string(r" ?\b(" + "|".join(ignores) + r")\b")
238                 for name in resources:
239                         if regex.sub(name, "") == regex.sub(string, ""):
240                                 return resources[name]
241
242         ## As a last effort check against the unfiltered list
243         if string in special_resources[type]:
244                 push_warning("[Dialogic] Using ", type, " '", string,"' when not supposed to.")
245                 return special_resources[type][string]
246
247         printerr("[Dialogic] Unable to identify ", type, " based on string '", string, "'.")
248         return default
249
250 #endregion
251
252 #region HELPERS
253 ################################################################################
254
255 static func get_character_directory() -> Dictionary:
256         return get_directory('dch')
257
258
259 static func get_timeline_directory() -> Dictionary:
260         return get_directory('dtl')
261
262
263 static func get_timeline_resource(timeline_identifier:String) -> DialogicTimeline:
264         return get_resource_from_identifier(timeline_identifier, 'dtl')
265
266
267 static func get_character_resource(character_identifier:String) -> DialogicCharacter:
268         return get_resource_from_identifier(character_identifier, 'dch')
269
270
271 static func list_resources_of_type(extension:String) -> Array:
272         var all_resources := scan_folder('res://', extension)
273         return all_resources
274
275
276 static func scan_folder(path:String, extension:String) -> Array:
277         var list: Array = []
278         if DirAccess.dir_exists_absolute(path):
279                 var dir := DirAccess.open(path)
280                 dir.list_dir_begin()
281                 var file_name := dir.get_next()
282                 while file_name != "":
283                         if dir.current_is_dir() and not file_name.begins_with("."):
284                                 list += scan_folder(path.path_join(file_name), extension)
285                         else:
286                                 if file_name.ends_with(extension):
287                                         list.append(path.path_join(file_name))
288                         file_name = dir.get_next()
289         return list
290
291 #endregion