mirror of
https://gitlab.com/MaddoScientisto/cirnogodot.git
synced 2026-06-18 15:03:47 +00:00
179 lines
7.3 KiB
GDScript
179 lines
7.3 KiB
GDScript
@tool
|
|
@icon("res://addons/func_godot/icons/icon_godot_ranger.svg")
|
|
class_name FuncGodotFGDFile extends Resource
|
|
## [Resource] file used to express a set of [FuncGodotFGDEntity] definitions.
|
|
##
|
|
## Can be exported as an FGD file for use with a Quake or Hammer-based map editor. Used in conjunction with [FuncGodotMapSetting] to generate nodes in a [FuncGodotMap] node.
|
|
##
|
|
## @tutorial(Level Design Book FGD Chapter): https://book.leveldesignbook.com/appendix/resources/formats/fgd
|
|
## @tutorial(Valve Developer Wiki FGD Article): https://developer.valvesoftware.com/wiki/FGD
|
|
|
|
## Supported map editors enum, used in conjunction with [member target_map_editor].
|
|
enum FuncGodotTargetMapEditors {
|
|
OTHER,
|
|
TRENCHBROOM,
|
|
JACK,
|
|
NET_RADIANT_CUSTOM,
|
|
}
|
|
|
|
## Builds and exports the FGD file.
|
|
@export_tool_button("Export FGD") var export_file := export_button
|
|
|
|
func export_button() -> void:
|
|
do_export_file(target_map_editor)
|
|
|
|
func do_export_file(target_editor: FuncGodotTargetMapEditors = FuncGodotTargetMapEditors.TRENCHBROOM, fgd_output_folder: String = "") -> void:
|
|
if fgd_output_folder.is_empty():
|
|
fgd_output_folder = FuncGodotLocalConfig.get_setting(FuncGodotLocalConfig.PROPERTY.FGD_OUTPUT_FOLDER) as String
|
|
if fgd_output_folder.is_empty():
|
|
printerr("Skipping export: No game config folder")
|
|
return
|
|
|
|
if fgd_name == "":
|
|
printerr("Skipping export: Empty FGD name")
|
|
|
|
if not DirAccess.dir_exists_absolute(fgd_output_folder):
|
|
if DirAccess.make_dir_recursive_absolute(fgd_output_folder) != OK:
|
|
printerr("Skipping export: Failed to create directory")
|
|
return
|
|
|
|
var fgd_file = fgd_output_folder.path_join(fgd_name + ".fgd")
|
|
|
|
var file_obj := FileAccess.open(fgd_file, FileAccess.WRITE)
|
|
if not file_obj:
|
|
printerr("Failed to open file for writing: ", fgd_file)
|
|
return
|
|
|
|
print("Exporting FGD to ", fgd_file)
|
|
file_obj.store_string(build_class_text(target_editor))
|
|
file_obj.close()
|
|
|
|
@export_group("Map Editor")
|
|
|
|
## Some map editors do not support the features found in others
|
|
## (ex: TrenchBroom supports the "model" key word while others require "studio",
|
|
## J.A.C.K. uses the "shader" key word while others use "material", etc...).
|
|
## If you get errors in your map editor, try changing this setting and re-exporting.
|
|
## This setting is overridden when the FGD is built via the Game Config resource.
|
|
@export var target_map_editor: FuncGodotTargetMapEditors = FuncGodotTargetMapEditors.TRENCHBROOM
|
|
|
|
# Some map editors do not support the "model" key word and require the "studio" key word instead.
|
|
# If you get errors in your map editor, try changing this setting.
|
|
# This setting is overridden when the FGD is built via the Game Config resource.
|
|
#@export var model_key_word_supported: bool = true
|
|
|
|
@export_group("FGD")
|
|
|
|
## FGD output filename without the extension.
|
|
@export var fgd_name: String = "FuncGodot"
|
|
|
|
## Array of [FuncGodotFGDFile] resources to include in FGD file output. All of the entities included with these FuncGodotFGDFile resources will be prepended to the outputted FGD file.
|
|
@export var base_fgd_files: Array[Resource] = []
|
|
|
|
## Array of resources that inherit from [FuncGodotFGDEntityClass]. This array defines the entities that will be added to the exported FGD file and the nodes that will be generated in a [FuncGodotMap].
|
|
@export var entity_definitions: Array[Resource] = []
|
|
|
|
## Toggles whether [FuncGodotFGDModelPointClass] resources will generate models from their [PackedScene] files.
|
|
@export var generate_model_point_class_models: bool = true
|
|
|
|
func build_class_text(target_editor: FuncGodotTargetMapEditors = FuncGodotTargetMapEditors.TRENCHBROOM) -> String:
|
|
var res : String = ""
|
|
|
|
for base_fgd in base_fgd_files:
|
|
if base_fgd is FuncGodotFGDFile:
|
|
res += base_fgd.build_class_text(target_editor)
|
|
else:
|
|
printerr("Base Fgd Files contains incorrect resource type! Should only be type FuncGodotFGDFile.")
|
|
|
|
var entities = get_fgd_classes()
|
|
for ent in entities:
|
|
if not ent is FuncGodotFGDEntityClass:
|
|
continue
|
|
if ent.func_godot_internal:
|
|
continue
|
|
if ent is FuncGodotFGDModelPointClass:
|
|
ent._model_generation_enabled = generate_model_point_class_models
|
|
|
|
var ent_text = ent.build_def_text(target_editor)
|
|
res += ent_text
|
|
if ent != entities[-1]:
|
|
res += "\n"
|
|
return res
|
|
|
|
## This getter does a little bit of validation. Providing only an array of non-null uniquely-named entity definitions
|
|
func get_fgd_classes() -> Array:
|
|
var res : Array = []
|
|
for cur_ent_def_ind in range(entity_definitions.size()):
|
|
var cur_ent_def = entity_definitions[cur_ent_def_ind]
|
|
if cur_ent_def == null:
|
|
continue
|
|
elif not (cur_ent_def is FuncGodotFGDEntityClass):
|
|
printerr("Bad value in entity definition set at position %s! Not an entity defintion." % cur_ent_def_ind)
|
|
continue
|
|
res.append(cur_ent_def)
|
|
return res
|
|
|
|
func get_entity_definitions() -> Dictionary[String, FuncGodotFGDEntityClass]:
|
|
var res: Dictionary[String, FuncGodotFGDEntityClass] = {}
|
|
|
|
for base_fgd in base_fgd_files:
|
|
var fgd_res = base_fgd.get_entity_definitions()
|
|
for key in fgd_res:
|
|
res[key] = fgd_res[key]
|
|
|
|
for ent in get_fgd_classes():
|
|
# Skip entities without classnames
|
|
if ent.classname.replace(" ","") == "":
|
|
printerr("Skipping " + ent.get_path() + ": Empty classname")
|
|
continue
|
|
|
|
if ent is FuncGodotFGDPointClass or ent is FuncGodotFGDSolidClass:
|
|
var entity_def = ent.duplicate()
|
|
var meta_properties: Dictionary[String, Variant] = {}
|
|
var class_properties: Dictionary[String, Variant] = {}
|
|
var class_property_descriptions: Dictionary[String, Variant] = {}
|
|
|
|
for base_class in _generate_base_class_list(entity_def):
|
|
for meta_property in base_class.meta_properties:
|
|
meta_properties[meta_property] = base_class.meta_properties[meta_property]
|
|
|
|
for class_property in base_class.class_properties:
|
|
class_properties[class_property] = base_class.class_properties[class_property]
|
|
|
|
for class_property_desc in base_class.class_property_descriptions:
|
|
class_property_descriptions[class_property_desc] = base_class.class_property_descriptions[class_property_desc]
|
|
|
|
for meta_property in entity_def.meta_properties:
|
|
meta_properties[meta_property] = entity_def.meta_properties[meta_property]
|
|
|
|
for class_property in entity_def.class_properties:
|
|
class_properties[class_property] = entity_def.class_properties[class_property]
|
|
|
|
for class_property_desc in entity_def.class_property_descriptions:
|
|
class_property_descriptions[class_property_desc] = entity_def.class_property_descriptions[class_property_desc]
|
|
|
|
entity_def.meta_properties = meta_properties
|
|
entity_def.class_properties = class_properties
|
|
entity_def.class_property_descriptions = class_property_descriptions
|
|
|
|
res[ent.classname] = entity_def
|
|
return res
|
|
|
|
func _generate_base_class_list(entity_def : Resource, visited_base_classes = []) -> Array:
|
|
var base_classes : Array = []
|
|
|
|
visited_base_classes.append(entity_def.classname)
|
|
|
|
# End recursive search if no more base_classes
|
|
if len(entity_def.base_classes) == 0:
|
|
return base_classes
|
|
|
|
# Traverse up to the next level of hierarchy, if not already visited
|
|
for base_class in entity_def.base_classes:
|
|
if not base_class.classname in visited_base_classes:
|
|
base_classes.append(base_class)
|
|
base_classes += _generate_base_class_list(base_class, visited_base_classes)
|
|
else:
|
|
printerr(str("Entity '", entity_def.classname,"' contains cycle/duplicate to Entity '", base_class.classname, "'"))
|
|
|
|
return base_classes
|