mod_loader_utils.gd 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. class_name ModLoaderUtils
  2. extends Node
  3. const LOG_NAME := "ModLoader:ModLoaderUtils"
  4. ## This is a dummy func. It is exclusively used to show notes in the code that
  5. ## stay visible after decompiling a PCK, as is primarily intended to assist new
  6. ## modders in understanding and troubleshooting issues
  7. static func _code_note(_msg:String):
  8. pass
  9. ## Returns an empty [String] if the key does not exist or is not type of [String]
  10. static func get_string_from_dict(dict: Dictionary, key: String) -> String:
  11. if not dict.has(key):
  12. return ""
  13. if not dict[key] is String:
  14. return ""
  15. return dict[key]
  16. ## Returns an empty [Array] if the key does not exist or is not type of [Array]
  17. static func get_array_from_dict(dict: Dictionary, key: String) -> Array:
  18. if not dict.has(key):
  19. return []
  20. if not dict[key] is Array:
  21. return []
  22. return dict[key]
  23. ## Returns an empty [Dictionary] if the key does not exist or is not type of [Dictionary]
  24. static func get_dict_from_dict(dict: Dictionary, key: String) -> Dictionary:
  25. if not dict.has(key):
  26. return {}
  27. if not dict[key] is Dictionary:
  28. return {}
  29. return dict[key]
  30. ## Works like [method Dictionary.has_all],
  31. ## but allows for more specific errors if a field is missing
  32. static func dict_has_fields(dict: Dictionary, required_fields: Array[String]) -> bool:
  33. var missing_fields := get_missing_dict_fields(dict, required_fields)
  34. if missing_fields.size() > 0:
  35. ModLoaderLog.fatal("Dictionary is missing required fields: %s" % str(missing_fields), LOG_NAME)
  36. return false
  37. return true
  38. static func get_missing_dict_fields(dict: Dictionary, required_fields: Array[String]) -> Array[String]:
  39. var missing_fields := required_fields.duplicate()
  40. for key in dict.keys():
  41. if(required_fields.has(key)):
  42. missing_fields.erase(key)
  43. return missing_fields
  44. ## Register an array of classes to the global scope, since Godot only does that in the editor.
  45. static func register_global_classes_from_array(new_global_classes: Array) -> void:
  46. var registered_classes: Array = ProjectSettings.get_setting("_global_script_classes")
  47. var registered_class_icons: Dictionary = ProjectSettings.get_setting("_global_script_class_icons")
  48. for new_class in new_global_classes:
  49. if not _is_valid_global_class_dict(new_class):
  50. continue
  51. for old_class in registered_classes:
  52. if old_class.get_class() == new_class.get_class():
  53. if OS.has_feature("editor"):
  54. ModLoaderLog.info('Class "%s" to be registered as global was already registered by the editor. Skipping.' % new_class.get_class(), LOG_NAME)
  55. else:
  56. ModLoaderLog.info('Class "%s" to be registered as global already exists. Skipping.' % new_class.get_class(), LOG_NAME)
  57. continue
  58. registered_classes.append(new_class)
  59. registered_class_icons[new_class.get_class()] = "" # empty icon, does not matter
  60. ProjectSettings.set_setting("_global_script_classes", registered_classes)
  61. ProjectSettings.set_setting("_global_script_class_icons", registered_class_icons)
  62. ## Checks if all required fields are in the given [Dictionary]
  63. ## Format: [code]{ "base": "ParentClass", "class": "ClassName", "language": "GDScript", "path": "res://path/class_name.gd" }[/code]
  64. static func _is_valid_global_class_dict(global_class_dict: Dictionary) -> bool:
  65. var required_fields := ["base", "class", "language", "path"]
  66. if not global_class_dict.has_all(required_fields):
  67. ModLoaderLog.fatal("Global class to be registered is missing one of %s" % required_fields, LOG_NAME)
  68. return false
  69. if not _ModLoaderFile.file_exists(global_class_dict.path):
  70. ModLoaderLog.fatal('Class "%s" to be registered as global could not be found at given path "%s"' %
  71. [global_class_dict.get_class, global_class_dict.path], LOG_NAME)
  72. return false
  73. return true