extends SceneTree const DEFAULT_EXTRACTED_DIR := "res://data/extracted" const DEFAULT_MAP := "Azeroth" const DEFAULT_TARGET_UID := 11785 var _failures := 0 func _initialize() -> void: var args := OS.get_cmdline_user_args() var extracted_dir := _arg(args, "--extracted", DEFAULT_EXTRACTED_DIR) var map_name := _arg(args, "--map", DEFAULT_MAP) var target_uid := int(_arg(args, "--uid", str(DEFAULT_TARGET_UID))) var path_contains := _arg(args, "--path-contains", "waterfall").to_lower() print("ADT M2 placement verification started. map=%s uid=%d path_contains=%s" % [ map_name, target_uid, path_contains, ]) var matches := _find_m2_placements(extracted_dir, map_name, target_uid, path_contains) if matches.is_empty(): _fail("No matching ADT M2 placement found. map=%s uid=%d path_contains=%s" % [ map_name, target_uid, path_contains, ]) else: print("ADT M2 placement verification matched %d placement(s)." % matches.size()) for match in matches: print(_format_match(match)) if _failures > 0: quit(1) return quit(0) func _find_m2_placements( extracted_dir: String, map_name: String, target_uid: int, path_contains: String) -> Array: if not ClassDB.class_exists("ADTLoader"): _fail("ADTLoader is not registered. Rebuild the native GDExtension first.") return [] var map_dir := ProjectSettings.globalize_path( extracted_dir.path_join("World/Maps").path_join(map_name) ) var dir := DirAccess.open(map_dir) if dir == null: _fail("Cannot open map directory: %s" % map_dir) return [] var files := _collect_adt_files(dir, map_name) if files.is_empty(): _fail("No ADT files found in %s" % map_dir) return [] var loader = ClassDB.instantiate("ADTLoader") if loader == null: _fail("Cannot instantiate ADTLoader.") return [] var matches := [] for file_name in files: var tile := _tile_from_adt_filename(file_name) var data: Dictionary = loader.call("load_adt", map_dir.path_join(file_name)) var names: PackedStringArray = data.get("m2_names", PackedStringArray()) var placements: Array = data.get("m2_placements", []) for placement_variant in placements: if not (placement_variant is Dictionary): continue var placement: Dictionary = placement_variant var uid := int(placement.get("unique_id", -1)) if target_uid >= 0 and uid != target_uid: continue var name_id := int(placement.get("name_id", -1)) if name_id < 0 or name_id >= names.size(): continue var rel_path := String(names[name_id]).replace("\\", "/") if not path_contains.is_empty() and not rel_path.to_lower().contains(path_contains): continue matches.append({ "tile": tile, "uid": uid, "path": rel_path, "pos": placement.get("pos", Vector3.ZERO), "rot": placement.get("rot", Vector3.ZERO), "scale": float(placement.get("scale", 1.0)), }) return matches func _collect_adt_files(dir: DirAccess, map_name: String) -> Array: var result := [] dir.list_dir_begin() while true: var file_name := dir.get_next() if file_name.is_empty(): break if dir.current_is_dir(): continue if not file_name.to_lower().ends_with(".adt"): continue if not file_name.begins_with("%s_" % map_name): continue result.append(file_name) dir.list_dir_end() result.sort() return result func _tile_from_adt_filename(file_name: String) -> Vector2i: var parts := file_name.get_basename().split("_") if parts.size() < 3: return Vector2i(-1, -1) return Vector2i(int(parts[parts.size() - 2]), int(parts[parts.size() - 1])) func _format_match(match: Dictionary) -> String: var rot: Vector3 = match.get("rot", Vector3.ZERO) var rot_deg := Vector3(rad_to_deg(rot.x), rad_to_deg(rot.y), rad_to_deg(rot.z)) return "ADT_M2_PLACEMENT tile=%s uid=%d path=%s pos=%s rot_deg=%s scale=%.3f" % [ match.get("tile", Vector2i(-1, -1)), int(match.get("uid", -1)), String(match.get("path", "")), match.get("pos", Vector3.ZERO), rot_deg, float(match.get("scale", 1.0)), ] func _arg(args: PackedStringArray, name: String, default_value: String) -> String: var idx := args.find(name) if idx >= 0 and idx + 1 < args.size(): return args[idx + 1] return default_value func _fail(message: String) -> void: _failures += 1 push_error(message)