1+ extends SceneTree
2+
3+ func get_files (entities_dir ):
4+ # Define the root directory to search
5+ var root_dir = "res://%s " % entities_dir
6+ var dir = DirAccess .open (root_dir )
7+ var files = []
8+ var folders = []
9+
10+ # Get all files in the directory
11+ dir .list_dir_begin ()
12+ while true :
13+ var file = dir .get_next ()
14+ if file == "" :
15+ break
16+ if dir .current_is_dir ():
17+ folders .append (file )
18+ else :
19+ files .append ("%s /%s " % [entities_dir , file ])
20+ dir .list_dir_end ()
21+
22+ for folder in folders :
23+ # Recursively get all files in the subdirectories
24+ files += get_files ("%s /%s " % [entities_dir , folder ])
25+
26+ return files
27+
28+ func remove_attached_scripts (files ):
29+ var filtered_files = []
30+ var attached_scripts = []
31+
32+ # Find all attached scripts
33+ for file in files :
34+ if file .ends_with (".tscn" ):
35+ var scene = load (file )
36+ var instance = scene .instantiate ()
37+ var script = instance .get_script ()
38+ if script : attached_scripts .append (script .get_path ().replace ("res://" , "" ))
39+ instance .queue_free ()
40+
41+ # Remove attached scripts from the list
42+ for file in files :
43+ if file not in attached_scripts :
44+ filtered_files .append (file )
45+
46+ return filtered_files
47+
48+ func get_entity_properties (files = []):
49+ var properties = ["// Generated by Godot FGD Generator https://github.com/krazyjakee/godot-fgd\n\n " ]
50+
51+ for file in files :
52+ if file .ends_with (".gd" ):
53+ properties .append (create_entity (file , script_loader (file )))
54+ if file .ends_with (".tscn" ):
55+ properties .append (create_entity (file , scene_loader (file )))
56+ else :
57+ continue
58+
59+ return properties
60+
61+ func scene_loader (path ):
62+ var properties = []
63+ var scene = load (path )
64+ var instance = scene .instantiate ()
65+ var script = instance .get_script ()
66+ if script :
67+ properties = script_loader (script .get_path ())
68+ var parsed_path = path_parser (path )
69+ properties = set_property_by_name ("name" , parsed_path [0 ], properties )
70+ instance .queue_free ()
71+ return properties
72+
73+ func script_loader (path ):
74+ var script = load (path )
75+ var properties = filter_properties (script .get_script_property_list ())
76+ var instance = script .new ()
77+ for property in properties :
78+ property .default_value = instance .get (property .name )
79+ instance .queue_free ()
80+ return properties
81+
82+ func filter_properties (properties = []):
83+ # Filter out properties that are not exposed to the editor
84+ var filtered_properties = []
85+ for property in properties :
86+ if property .type > 0 :
87+ filtered_properties .append (property )
88+ return filtered_properties
89+
90+ func type_converter (type : int ):
91+ # Convert the type to a string
92+ var types = {
93+ 1 : "bool" ,
94+ 2 : "integer" ,
95+ 3 : "float" ,
96+ 4 : "string" ,
97+ 20 : "color" ,
98+ 27 : "flags" ,
99+ 28 : "choices"
100+ }
101+ return types [type ] if type in types else "string"
102+
103+ func value_converter (type : int , value : Variant ):
104+ # Convert the value to a string
105+ if type == 4 :
106+ return "\" %s \" " % value
107+ elif type == 1 :
108+ return "1" if value else "0"
109+ elif type == 20 :
110+ return "\" %s %s %s \" " % [value .r , value .g , value .b ]
111+ elif type == 27 :
112+ var fgd_values = []
113+ var count = 1
114+ for key in value :
115+ var v = 1 if value [key ] else 0
116+ fgd_values .append (" %s : \" %s \" : %s \n " % [count , key , v ])
117+ if count > 1 :
118+ count *= count
119+ else :
120+ count += 1
121+
122+ return "[\n %s ]" % "" .join (PackedStringArray (fgd_values ))
123+ elif type == 28 : # Array (but only string array is supported)
124+ var fgd_values = []
125+ for i in value .size ():
126+ var v = value [i ]
127+ fgd_values .append (" %s : \" %s \"\n " % [i , v ])
128+
129+ return "0 = [\n %s ]" % "" .join (PackedStringArray (fgd_values ))
130+ else :
131+ return value
132+
133+ func get_property_value_by_name (name , properties = [], default_value = null ):
134+ # Get the properties by name
135+ for property in properties :
136+ if property .name == name :
137+ return property .default_value
138+ return default_value
139+
140+ func set_property_by_name (name , value , properties = []):
141+ # Set the properties by name
142+ for i in properties .size ():
143+ if properties [i ].name == name :
144+ properties [i ][name ] = value
145+ return properties
146+
147+ func path_parser (raw_path ):
148+ var regex = RegEx .new ()
149+
150+ # Parse the path to get the entity and group name
151+ var entity_name = raw_path .get_file ().split ("." )[0 ]
152+ var entity_location = raw_path .get_base_dir ()
153+
154+ # Remove non-alphanumeric characters
155+ regex .compile ("^entities" )
156+ entity_location = regex .sub (entity_location , "" )
157+ entity_location = entity_location .replace ("/" , "_" )
158+ regex .compile ("[\\ -\\ s]" )
159+ entity_location = regex .sub (entity_location , "_" , true )
160+ regex .compile ("[\\ W]" )
161+ entity_location = regex .sub (entity_location , "" , true )
162+ regex .compile ("^_" )
163+ entity_location = regex .sub (entity_location , "" )
164+ if entity_location :
165+ entity_location += "_"
166+
167+ var entity_label = string_to_title_case (entity_location )
168+
169+ return ["%s%s " % [entity_location , entity_name ], entity_label ]
170+
171+ func string_to_title_case (string : String ) -> String :
172+ var words = string .split ("_" )
173+ var result = ""
174+ for word in words :
175+ result += word .capitalize () + " "
176+ return result .strip_edges (true , true )
177+
178+ func create_entity (path , properties = []):
179+ if properties == null :
180+ return null
181+ # Header
182+ # Example: @PointClass size(-4 -4 -4, 4 4 4) color(255 255 0) model({ "path": ":progs/player.mdl" }) = light : "Light" [
183+ var fgd_size = get_property_value_by_name ("fgd_size" , properties , 4 )
184+ var fgd_color = get_property_value_by_name ("fgd_color" , properties , "(0 255 0)" )
185+ var fgd_model = get_property_value_by_name ("fgd_model" , properties , "" )
186+ var fgd_block = get_property_value_by_name ("fgd_block" , properties , [])
187+
188+ var entity_name_properties = path_parser (path )
189+ var entity_name = entity_name_properties [0 ]
190+ var entity_label = entity_name_properties [1 ]
191+ fgd_model = " model(%s )" % JSON .stringify (fgd_model ) if fgd_model else ""
192+
193+ var entity = "@PointClass size(-%s -%s -%s , %s %s %s ) color%s%s = %s : \" %s \" [\n " % [
194+ fgd_size , fgd_size , fgd_size ,
195+ fgd_size , fgd_size , fgd_size ,
196+ fgd_color , fgd_model ,
197+ entity_name , entity_label
198+ ]
199+
200+ # Body
201+ # Example: energy(float) : "Energy" : 1 : "The light's strengh multiplier"
202+ for property in properties :
203+ if property .name .begins_with ("fgd_" ) or property .name in fgd_block :
204+ continue
205+
206+ if property .type in [27 ]: # Flags
207+ entity += " %s (%s ) = %s " % [
208+ property .name if property .name else "unnamed" ,
209+ type_converter (property .type ) if property .type else 4 ,
210+ value_converter (property .type , property .default_value ) if property .default_value else "" ,
211+ ]
212+ else :
213+ entity += " %s (%s ) : \" %s \" : %s " % [
214+ property .name if property .name else "unnamed" ,
215+ type_converter (property .type ) if property .type else 4 ,
216+ string_to_title_case (property .name ) if property .name else "" ,
217+ value_converter (property .type , property .default_value ) if property .default_value else "" ,
218+ ]
219+
220+ if property .type not in [28 ]: # Array
221+ entity += " : \" %s \" " % property .hint_string if property .hint_string else ""
222+
223+ entity += "\n "
224+
225+ # Footer
226+ entity += "]\n\n "
227+
228+ return entity
229+
230+ func build (entities_dir = "entities" ):
231+ var all_files = get_files (entities_dir ) # Get all .gd and .tscn files in the entity folder
232+ var files = remove_attached_scripts (all_files ) # Remove attached scripts
233+ var properties = get_entity_properties (files ) # Get the properties of each entity
234+
235+ var file = FileAccess .open ("Game.fgd" , FileAccess .WRITE )
236+ for property in properties :
237+ if property :
238+ file .store_string (property )
239+
240+ print ("Game.fgd created successfully!" )
241+
242+ func _init ():
243+ build ()
0 commit comments