новая структура проекта
This commit is contained in:
Binary file not shown.
@@ -1,161 +0,0 @@
|
||||
extends SceneTree
|
||||
|
||||
const ADT_BUILDER_SCRIPT := preload("res://addons/mpq_extractor/loaders/adt_builder.gd")
|
||||
const BAKED_TILE_SCRIPT := preload("res://resources/baked_adt_tile.gd")
|
||||
|
||||
var _builder
|
||||
var _loader
|
||||
var _image_cache: Dictionary = {}
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
var args := OS.get_cmdline_user_args()
|
||||
var map_name := _get_arg_value(args, "--map", "Azeroth")
|
||||
var extracted_dir := _normalize_res_path(_get_arg_value(args, "--extracted", "res://extracted"))
|
||||
var output_dir := _normalize_res_path(_get_arg_value(args, "--output", "res://cache/baked_terrain_v2"))
|
||||
var legacy_texture_size = _get_optional_int_arg(args, "--texture-size")
|
||||
var full_texture_size := maxi(
|
||||
256,
|
||||
_get_arg_value(
|
||||
args,
|
||||
"--full-texture-size",
|
||||
str(legacy_texture_size if legacy_texture_size != null else 2048)
|
||||
).to_int()
|
||||
)
|
||||
var coarse_texture_size := maxi(
|
||||
128,
|
||||
_get_arg_value(
|
||||
args,
|
||||
"--coarse-texture-size",
|
||||
str(legacy_texture_size if legacy_texture_size != null else 512)
|
||||
).to_int()
|
||||
)
|
||||
var tile_x: Variant = _get_optional_int_arg(args, "--tile-x")
|
||||
var tile_y: Variant = _get_optional_int_arg(args, "--tile-y")
|
||||
var force := args.has("--force")
|
||||
|
||||
_builder = ADT_BUILDER_SCRIPT.new()
|
||||
if not ClassDB.class_exists("ADTLoader"):
|
||||
push_error("ADTLoader not found. Rebuild GDExtension first.")
|
||||
quit(1)
|
||||
return
|
||||
|
||||
_loader = ClassDB.instantiate("ADTLoader")
|
||||
if _loader == null:
|
||||
push_error("Failed to instantiate ADTLoader.")
|
||||
quit(1)
|
||||
return
|
||||
|
||||
var map_dir := extracted_dir.path_join("World/Maps/%s" % map_name)
|
||||
var dir := DirAccess.open(map_dir)
|
||||
if dir == null:
|
||||
push_error("Cannot open map directory: %s" % map_dir)
|
||||
quit(1)
|
||||
return
|
||||
|
||||
DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(output_dir.path_join(map_name)))
|
||||
|
||||
var baked := 0
|
||||
var skipped := 0
|
||||
var failed := 0
|
||||
var started_ms := Time.get_ticks_msec()
|
||||
|
||||
for file_name in dir.get_files():
|
||||
if not file_name.ends_with(".adt"):
|
||||
continue
|
||||
|
||||
var stem := file_name.trim_suffix(".adt")
|
||||
var parts := stem.split("_")
|
||||
if parts.size() != 3 or parts[0] != map_name:
|
||||
continue
|
||||
if not parts[1].is_valid_int() or not parts[2].is_valid_int():
|
||||
continue
|
||||
|
||||
var tx := parts[1].to_int()
|
||||
var ty := parts[2].to_int()
|
||||
if tile_x != null and tx != tile_x:
|
||||
continue
|
||||
if tile_y != null and ty != tile_y:
|
||||
continue
|
||||
var source_res_path := map_dir.path_join(file_name)
|
||||
var source_abs_path := ProjectSettings.globalize_path(source_res_path)
|
||||
var output_res_path := output_dir.path_join(map_name).path_join("%s_%d_%d.res" % [map_name, tx, ty])
|
||||
var output_abs_path := ProjectSettings.globalize_path(output_res_path)
|
||||
|
||||
if not force and FileAccess.file_exists(output_abs_path):
|
||||
skipped += 1
|
||||
continue
|
||||
|
||||
var data: Dictionary = _loader.call("load_adt", source_abs_path)
|
||||
if data.is_empty() or not data.has("chunks"):
|
||||
push_warning("Bake failed to parse ADT: %s" % source_res_path)
|
||||
failed += 1
|
||||
continue
|
||||
|
||||
var full_payload: Dictionary = _builder.build_baked_tile_render_payload(
|
||||
data,
|
||||
_image_cache,
|
||||
ProjectSettings.globalize_path(extracted_dir),
|
||||
0,
|
||||
full_texture_size)
|
||||
var coarse_payload: Dictionary = _builder.build_baked_tile_render_payload(
|
||||
data,
|
||||
_image_cache,
|
||||
ProjectSettings.globalize_path(extracted_dir),
|
||||
3,
|
||||
coarse_texture_size)
|
||||
|
||||
if full_payload.is_empty():
|
||||
push_warning("Bake produced empty full mesh: %s" % source_res_path)
|
||||
failed += 1
|
||||
continue
|
||||
|
||||
var baked_tile: Resource = BAKED_TILE_SCRIPT.new()
|
||||
baked_tile.set("format_version", BAKED_TILE_SCRIPT.FORMAT_VERSION)
|
||||
baked_tile.set("map_name", map_name)
|
||||
baked_tile.set("tile_x", tx)
|
||||
baked_tile.set("tile_y", ty)
|
||||
baked_tile.set("tile_origin", _builder.get_tile_origin_for_data(data))
|
||||
baked_tile.set("full_texture_size", full_texture_size)
|
||||
baked_tile.set("coarse_texture_size", coarse_texture_size)
|
||||
baked_tile.set("wmo_names", data.get("wmo_names", PackedStringArray()))
|
||||
baked_tile.set("wmo_placements", data.get("wmo_placements", []))
|
||||
baked_tile.set("m2_names", data.get("m2_names", PackedStringArray()))
|
||||
baked_tile.set("m2_placements", data.get("m2_placements", []))
|
||||
baked_tile.set("full_mesh", full_payload.get("mesh", null))
|
||||
baked_tile.set("coarse_mesh", coarse_payload.get("mesh", null))
|
||||
|
||||
var save_err := ResourceSaver.save(baked_tile, output_res_path)
|
||||
if save_err != OK:
|
||||
push_warning("Failed to save baked tile: %s (err=%d)" % [output_res_path, save_err])
|
||||
failed += 1
|
||||
continue
|
||||
|
||||
baked += 1
|
||||
if baked % 25 == 0:
|
||||
print("Baked %d tiles..." % baked)
|
||||
|
||||
var elapsed_ms := Time.get_ticks_msec() - started_ms
|
||||
print("Bake finished. baked=%d skipped=%d failed=%d elapsed=%.2fs" % [
|
||||
baked, skipped, failed, float(elapsed_ms) / 1000.0])
|
||||
quit(0 if failed == 0 else 2)
|
||||
|
||||
|
||||
func _get_arg_value(args: PackedStringArray, name: String, default_value: String) -> String:
|
||||
var index := args.find(name)
|
||||
if index >= 0 and index + 1 < args.size():
|
||||
return args[index + 1]
|
||||
return default_value
|
||||
|
||||
|
||||
func _get_optional_int_arg(args: PackedStringArray, name: String):
|
||||
var index := args.find(name)
|
||||
if index >= 0 and index + 1 < args.size() and args[index + 1].is_valid_int():
|
||||
return args[index + 1].to_int()
|
||||
return null
|
||||
|
||||
|
||||
func _normalize_res_path(path: String) -> String:
|
||||
if path.begins_with("res://"):
|
||||
return path
|
||||
return "res://%s" % path.trim_prefix("./").trim_prefix("/")
|
||||
@@ -1 +0,0 @@
|
||||
uid://dyamnnp08iy2e
|
||||
@@ -1,283 +0,0 @@
|
||||
## Bakes terrain, M2 doodads and WMO objects in one pass.
|
||||
##
|
||||
## Usage:
|
||||
## godot --headless --path <project> --script res://tools/bake_all.gd -- \
|
||||
## --map Azeroth --force
|
||||
##
|
||||
## Optional args:
|
||||
## --extracted res://extracted
|
||||
## --terrain-output res://cache/baked_terrain_v2
|
||||
## --m2-output res://cache/m2_glb
|
||||
## --wmo-output res://cache/wmo_tscn
|
||||
## --full-texture-size 2048
|
||||
## --coarse-texture-size 512
|
||||
## --skip-terrain --skip-m2 --skip-wmo
|
||||
extends SceneTree
|
||||
|
||||
const ADT_BUILDER_SCRIPT := preload("res://addons/mpq_extractor/loaders/adt_builder.gd")
|
||||
const BAKED_TILE_SCRIPT := preload("res://resources/baked_adt_tile.gd")
|
||||
const WMO_BUILDER_SCRIPT := preload("res://addons/mpq_extractor/loaders/wmo_builder.gd")
|
||||
const M2_BUILDER_SCRIPT := preload("res://addons/mpq_extractor/loaders/m2_builder.gd")
|
||||
|
||||
var _image_cache: Dictionary = {}
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
var args := OS.get_cmdline_user_args()
|
||||
|
||||
if not ClassDB.class_exists("ADTLoader"):
|
||||
push_error("ADTLoader not found. Rebuild GDExtension first.")
|
||||
quit(1); return
|
||||
if not ClassDB.class_exists("WMOLoader"):
|
||||
push_error("WMOLoader not found. Rebuild GDExtension first.")
|
||||
quit(1); return
|
||||
if not ClassDB.class_exists("M2Loader"):
|
||||
push_error("M2Loader not found. Rebuild GDExtension first.")
|
||||
quit(1); return
|
||||
|
||||
var map_name := _arg(args, "--map", "Azeroth")
|
||||
var extracted := _res(_arg(args, "--extracted", "res://extracted"))
|
||||
var terrain_out := _res(_arg(args, "--terrain-output","res://cache/baked_terrain_v2"))
|
||||
var m2_out := _res(_arg(args, "--m2-output", "res://cache/m2_glb"))
|
||||
var wmo_out := _res(_arg(args, "--wmo-output", "res://cache/wmo_tscn"))
|
||||
var full_tex_size := maxi(256, _arg(args, "--full-texture-size", "2048").to_int())
|
||||
var coarse_size := maxi(128, _arg(args, "--coarse-texture-size", "512").to_int())
|
||||
var force := args.has("--force")
|
||||
var skip_terrain := args.has("--skip-terrain")
|
||||
var skip_m2 := args.has("--skip-m2")
|
||||
var skip_wmo := args.has("--skip-wmo")
|
||||
|
||||
var map_dir := extracted.path_join("World/Maps/%s" % map_name)
|
||||
var dir := DirAccess.open(map_dir)
|
||||
if dir == null:
|
||||
push_error("Cannot open map directory: %s" % map_dir)
|
||||
quit(1); return
|
||||
|
||||
var total_start := Time.get_ticks_msec()
|
||||
|
||||
# ── Phase 1: Terrain ─────────────────────────────────────────────────────
|
||||
if not skip_terrain:
|
||||
print("\n=== Phase 1/3: Terrain ===")
|
||||
_bake_terrain(map_name, map_dir, extracted, terrain_out,
|
||||
full_tex_size, coarse_size, force)
|
||||
else:
|
||||
print("\n=== Phase 1/3: Terrain [SKIPPED] ===")
|
||||
|
||||
# ── Phase 2: M2 ──────────────────────────────────────────────────────────
|
||||
if not skip_m2:
|
||||
print("\n=== Phase 2/3: M2 Doodads ===")
|
||||
var unique_m2 := _collect_m2(map_name, map_dir)
|
||||
_bake_m2(unique_m2, extracted, m2_out, force)
|
||||
else:
|
||||
print("\n=== Phase 2/3: M2 Doodads [SKIPPED] ===")
|
||||
|
||||
# ── Phase 3: WMO ─────────────────────────────────────────────────────────
|
||||
if not skip_wmo:
|
||||
print("\n=== Phase 3/3: WMO Objects ===")
|
||||
var unique_wmo := _collect_wmo(map_name, map_dir)
|
||||
_bake_wmo(unique_wmo, extracted, wmo_out, force)
|
||||
else:
|
||||
print("\n=== Phase 3/3: WMO Objects [SKIPPED] ===")
|
||||
|
||||
var elapsed := float(Time.get_ticks_msec() - total_start) / 1000.0
|
||||
print("\nAll done in %.1fs." % elapsed)
|
||||
quit(0)
|
||||
|
||||
|
||||
# ── Terrain ───────────────────────────────────────────────────────────────────
|
||||
|
||||
func _bake_terrain(map_name: String, map_dir: String, extracted: String,
|
||||
output_dir: String, full_tex: int, coarse_tex: int, force: bool) -> void:
|
||||
DirAccess.make_dir_recursive_absolute(
|
||||
ProjectSettings.globalize_path(output_dir.path_join(map_name)))
|
||||
|
||||
var builder = ADT_BUILDER_SCRIPT.new()
|
||||
var loader = ClassDB.instantiate("ADTLoader")
|
||||
var dir = DirAccess.open(map_dir)
|
||||
var baked := 0; var skipped := 0; var failed := 0
|
||||
|
||||
for fname in dir.get_files():
|
||||
if not fname.ends_with(".adt"): continue
|
||||
var parts := fname.trim_suffix(".adt").split("_")
|
||||
if parts.size() != 3 or parts[0] != map_name: continue
|
||||
if not parts[1].is_valid_int() or not parts[2].is_valid_int(): continue
|
||||
var tx := parts[1].to_int()
|
||||
var ty := parts[2].to_int()
|
||||
var out_path := output_dir.path_join(map_name).path_join(
|
||||
"%s_%d_%d.res" % [map_name, tx, ty])
|
||||
if not force and FileAccess.file_exists(ProjectSettings.globalize_path(out_path)):
|
||||
skipped += 1; continue
|
||||
|
||||
var data: Dictionary = loader.call("load_adt",
|
||||
ProjectSettings.globalize_path(map_dir.path_join(fname)))
|
||||
if data.is_empty() or not data.has("chunks"):
|
||||
failed += 1; continue
|
||||
|
||||
var full_payload := builder.build_baked_tile_render_payload(
|
||||
data, _image_cache, ProjectSettings.globalize_path(extracted), 0, full_tex)
|
||||
var coarse_payload := builder.build_baked_tile_render_payload(
|
||||
data, _image_cache, ProjectSettings.globalize_path(extracted), 3, coarse_tex)
|
||||
if full_payload.is_empty():
|
||||
failed += 1; continue
|
||||
|
||||
var tile: Resource = BAKED_TILE_SCRIPT.new()
|
||||
tile.set("format_version", BAKED_TILE_SCRIPT.FORMAT_VERSION)
|
||||
tile.set("map_name", map_name)
|
||||
tile.set("tile_x", tx)
|
||||
tile.set("tile_y", ty)
|
||||
tile.set("tile_origin", builder.get_tile_origin_for_data(data))
|
||||
tile.set("full_texture_size", full_tex)
|
||||
tile.set("coarse_texture_size", coarse_tex)
|
||||
tile.set("wmo_names", data.get("wmo_names", PackedStringArray()))
|
||||
tile.set("wmo_placements", data.get("wmo_placements", []))
|
||||
tile.set("m2_names", data.get("m2_names", PackedStringArray()))
|
||||
tile.set("m2_placements", data.get("m2_placements", []))
|
||||
tile.set("full_mesh", full_payload.get("mesh", null))
|
||||
tile.set("coarse_mesh", coarse_payload.get("mesh", null))
|
||||
|
||||
if ResourceSaver.save(tile, out_path) != OK:
|
||||
failed += 1; continue
|
||||
baked += 1
|
||||
if baked % 25 == 0:
|
||||
print(" terrain: baked=%d skipped=%d failed=%d" % [baked, skipped, failed])
|
||||
|
||||
print(" terrain done. baked=%d skipped=%d failed=%d" % [baked, skipped, failed])
|
||||
|
||||
|
||||
# ── M2 ────────────────────────────────────────────────────────────────────────
|
||||
|
||||
func _collect_m2(map_name: String, map_dir: String) -> Dictionary:
|
||||
var loader = ClassDB.instantiate("ADTLoader")
|
||||
var dir = DirAccess.open(map_dir)
|
||||
var unique: Dictionary = {}
|
||||
for fname in dir.get_files():
|
||||
if not fname.ends_with(".adt"): continue
|
||||
var parts := fname.trim_suffix(".adt").split("_")
|
||||
if parts.size() != 3 or parts[0] != map_name: continue
|
||||
var data: Dictionary = loader.call("load_adt",
|
||||
ProjectSettings.globalize_path(map_dir.path_join(fname)))
|
||||
for rel in data.get("m2_names", PackedStringArray()):
|
||||
var norm := str(rel).replace("\\", "/").to_lower()
|
||||
if norm.ends_with(".mdx") or norm.ends_with(".mdl"):
|
||||
norm = norm.get_basename() + ".m2"
|
||||
if not norm.is_empty():
|
||||
unique[norm] = true
|
||||
print(" found %d unique M2 models." % unique.size())
|
||||
return unique
|
||||
|
||||
|
||||
func _bake_m2(unique: Dictionary, extracted: String, output_dir: String, force: bool) -> void:
|
||||
DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(output_dir))
|
||||
var loader = ClassDB.instantiate("M2Loader")
|
||||
var baked := 0; var skipped := 0; var failed := 0
|
||||
var total := unique.size(); var i := 0
|
||||
|
||||
for rel_path in unique.keys():
|
||||
i += 1
|
||||
var out_path := output_dir.path_join(rel_path.get_basename() + ".tscn")
|
||||
if not force and ResourceLoader.exists(out_path):
|
||||
skipped += 1; continue
|
||||
|
||||
var abs_m2 := ProjectSettings.globalize_path(extracted.path_join(rel_path))
|
||||
if not FileAccess.file_exists(abs_m2):
|
||||
failed += 1; continue
|
||||
|
||||
var data: Dictionary = loader.call("load_m2", abs_m2)
|
||||
if data.is_empty() or data.get("vertices", PackedVector3Array()).is_empty():
|
||||
failed += 1; continue
|
||||
|
||||
var node: Node3D = M2_BUILDER_SCRIPT.build(data, extracted)
|
||||
if node == null:
|
||||
failed += 1; continue
|
||||
|
||||
var scene := PackedScene.new()
|
||||
if scene.pack(node) != OK:
|
||||
node.free(); failed += 1; continue
|
||||
node.free()
|
||||
|
||||
DirAccess.make_dir_recursive_absolute(
|
||||
ProjectSettings.globalize_path(out_path.get_base_dir()))
|
||||
if ResourceSaver.save(scene, out_path) != OK:
|
||||
failed += 1; continue
|
||||
|
||||
baked += 1
|
||||
if i % 50 == 0 or i == total:
|
||||
print(" m2 [%d/%d] baked=%d skipped=%d failed=%d" % [i, total, baked, skipped, failed])
|
||||
|
||||
print(" m2 done. baked=%d skipped=%d failed=%d" % [baked, skipped, failed])
|
||||
|
||||
|
||||
# ── WMO ───────────────────────────────────────────────────────────────────────
|
||||
|
||||
func _collect_wmo(map_name: String, map_dir: String) -> Dictionary:
|
||||
var loader = ClassDB.instantiate("ADTLoader")
|
||||
var dir = DirAccess.open(map_dir)
|
||||
var unique: Dictionary = {}
|
||||
for fname in dir.get_files():
|
||||
if not fname.ends_with(".adt"): continue
|
||||
var parts := fname.trim_suffix(".adt").split("_")
|
||||
if parts.size() != 3 or parts[0] != map_name: continue
|
||||
var data: Dictionary = loader.call("load_adt",
|
||||
ProjectSettings.globalize_path(map_dir.path_join(fname)))
|
||||
for rel in data.get("wmo_names", PackedStringArray()):
|
||||
var norm := str(rel).replace("\\", "/").to_lower()
|
||||
if not norm.is_empty():
|
||||
unique[norm] = true
|
||||
print(" found %d unique WMO models." % unique.size())
|
||||
return unique
|
||||
|
||||
|
||||
func _bake_wmo(unique: Dictionary, extracted: String, output_dir: String, force: bool) -> void:
|
||||
DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(output_dir))
|
||||
var loader = ClassDB.instantiate("WMOLoader")
|
||||
var baked := 0; var skipped := 0; var failed := 0
|
||||
var total := unique.size(); var i := 0
|
||||
|
||||
for rel_path in unique.keys():
|
||||
i += 1
|
||||
var out_path := output_dir.path_join(rel_path.get_basename() + ".tscn")
|
||||
if not force and ResourceLoader.exists(out_path):
|
||||
skipped += 1; continue
|
||||
|
||||
var abs_wmo := ProjectSettings.globalize_path(extracted.path_join(rel_path))
|
||||
if not FileAccess.file_exists(abs_wmo):
|
||||
failed += 1; continue
|
||||
|
||||
var data: Dictionary = loader.call("load_wmo", abs_wmo)
|
||||
if data.is_empty():
|
||||
failed += 1; continue
|
||||
|
||||
var node: Node3D = WMO_BUILDER_SCRIPT.build(data, extracted)
|
||||
if node == null:
|
||||
failed += 1; continue
|
||||
|
||||
var scene := PackedScene.new()
|
||||
if scene.pack(node) != OK:
|
||||
node.free(); failed += 1; continue
|
||||
node.free()
|
||||
|
||||
DirAccess.make_dir_recursive_absolute(
|
||||
ProjectSettings.globalize_path(out_path.get_base_dir()))
|
||||
if ResourceSaver.save(scene, out_path) != OK:
|
||||
failed += 1; continue
|
||||
|
||||
baked += 1
|
||||
if i % 20 == 0 or i == total:
|
||||
print(" wmo [%d/%d] baked=%d skipped=%d failed=%d" % [i, total, baked, skipped, failed])
|
||||
|
||||
print(" wmo done. baked=%d skipped=%d failed=%d" % [baked, skipped, failed])
|
||||
|
||||
|
||||
# ── Helpers ───────────────────────────────────────────────────────────────────
|
||||
|
||||
func _arg(args: PackedStringArray, name: String, default: String) -> String:
|
||||
var idx := args.find(name)
|
||||
if idx >= 0 and idx + 1 < args.size():
|
||||
return args[idx + 1]
|
||||
return default
|
||||
|
||||
|
||||
func _res(path: String) -> String:
|
||||
if path.begins_with("res://") or path.begins_with("user://"):
|
||||
return path
|
||||
return "res://" + path.trim_prefix("./").trim_prefix("/")
|
||||
@@ -1 +0,0 @@
|
||||
uid://bij5obimkjcud
|
||||
@@ -1,128 +0,0 @@
|
||||
## Pre-bakes M2 doodad models into PackedScene (.tscn) files for fast runtime loading.
|
||||
##
|
||||
## Usage:
|
||||
## godot --headless --path <project> --script res://tools/bake_m2_cache.gd -- \
|
||||
## --map Azeroth --extracted res://extracted --output res://cache/m2_glb --force
|
||||
extends SceneTree
|
||||
|
||||
const M2_BUILDER_SCRIPT := preload("res://addons/mpq_extractor/loaders/m2_builder.gd")
|
||||
|
||||
func _initialize() -> void:
|
||||
var args := OS.get_cmdline_user_args()
|
||||
var map_name := _arg(args, "--map", "Azeroth")
|
||||
var extracted := _res(_arg(args, "--extracted", "res://extracted"))
|
||||
var output_dir := _res(_arg(args, "--output", "res://cache/m2_glb"))
|
||||
var force := args.has("--force")
|
||||
|
||||
if not ClassDB.class_exists("ADTLoader") or not ClassDB.class_exists("M2Loader"):
|
||||
push_error("GDExtension not loaded. Rebuild first.")
|
||||
quit(1)
|
||||
return
|
||||
|
||||
DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(output_dir))
|
||||
|
||||
# Collect unique M2 paths from all ADT tiles
|
||||
var unique: Dictionary = {}
|
||||
var adt_loader = ClassDB.instantiate("ADTLoader")
|
||||
var map_dir := extracted.path_join("World/Maps/%s" % map_name)
|
||||
var dir := DirAccess.open(map_dir)
|
||||
if dir == null:
|
||||
push_error("Cannot open map dir: %s" % map_dir)
|
||||
quit(1)
|
||||
return
|
||||
|
||||
print("Scanning ADT tiles...")
|
||||
var scanned := 0
|
||||
for fname in dir.get_files():
|
||||
if not fname.ends_with(".adt"):
|
||||
continue
|
||||
var parts := fname.trim_suffix(".adt").split("_")
|
||||
if parts.size() != 3 or parts[0] != map_name:
|
||||
continue
|
||||
var data: Dictionary = adt_loader.call("load_adt",
|
||||
ProjectSettings.globalize_path(map_dir.path_join(fname)))
|
||||
if data.is_empty():
|
||||
continue
|
||||
for rel in data.get("m2_names", PackedStringArray()):
|
||||
var norm := str(rel).replace("\\", "/").to_lower()
|
||||
if norm.ends_with(".mdx") or norm.ends_with(".mdl"):
|
||||
norm = norm.get_basename() + ".m2"
|
||||
if not norm.is_empty():
|
||||
unique[norm] = true
|
||||
scanned += 1
|
||||
|
||||
print("Found %d unique M2 models from %d tiles." % [unique.size(), scanned])
|
||||
|
||||
var m2_loader = ClassDB.instantiate("M2Loader")
|
||||
var baked := 0
|
||||
var skipped := 0
|
||||
var failed := 0
|
||||
var total := unique.size()
|
||||
var i := 0
|
||||
|
||||
for rel_path in unique.keys():
|
||||
i += 1
|
||||
var stem: String = rel_path.get_basename()
|
||||
var out_path := output_dir.path_join(stem + ".tscn")
|
||||
|
||||
if not force and ResourceLoader.exists(out_path):
|
||||
skipped += 1
|
||||
continue
|
||||
|
||||
var abs_m2 := ProjectSettings.globalize_path(extracted.path_join(rel_path))
|
||||
if not FileAccess.file_exists(abs_m2):
|
||||
failed += 1
|
||||
continue
|
||||
|
||||
var data: Dictionary = m2_loader.call("load_m2", abs_m2)
|
||||
if data.is_empty() or data.get("vertices", PackedVector3Array()).is_empty():
|
||||
failed += 1
|
||||
continue
|
||||
|
||||
var node: Node3D = M2_BUILDER_SCRIPT.build(data, extracted)
|
||||
if node == null:
|
||||
failed += 1
|
||||
continue
|
||||
|
||||
_set_owner_recursive(node, node)
|
||||
var scene := PackedScene.new()
|
||||
var err := scene.pack(node)
|
||||
node.free()
|
||||
if err != OK:
|
||||
push_warning("pack failed for %s: %d" % [rel_path, err])
|
||||
failed += 1
|
||||
continue
|
||||
|
||||
DirAccess.make_dir_recursive_absolute(
|
||||
ProjectSettings.globalize_path(out_path.get_base_dir()))
|
||||
err = ResourceSaver.save(scene, out_path)
|
||||
if err != OK:
|
||||
push_warning("save failed for %s: %d" % [out_path, err])
|
||||
failed += 1
|
||||
continue
|
||||
|
||||
baked += 1
|
||||
if i % 50 == 0 or i == total:
|
||||
print("[%d/%d] baked=%d skipped=%d failed=%d" % [i, total, baked, skipped, failed])
|
||||
|
||||
print("Done. baked=%d skipped=%d failed=%d" % [baked, skipped, failed])
|
||||
quit(0)
|
||||
|
||||
|
||||
func _arg(args: PackedStringArray, name: String, default: String) -> String:
|
||||
var idx := args.find(name)
|
||||
if idx >= 0 and idx + 1 < args.size():
|
||||
return args[idx + 1]
|
||||
return default
|
||||
|
||||
|
||||
func _res(path: String) -> String:
|
||||
if path.begins_with("res://") or path.begins_with("user://"):
|
||||
return path
|
||||
return "res://" + path.trim_prefix("./").trim_prefix("/")
|
||||
|
||||
|
||||
func _set_owner_recursive(node: Node, owner_root: Node) -> void:
|
||||
for child in node.get_children():
|
||||
child.owner = owner_root
|
||||
_set_owner_recursive(child, owner_root)
|
||||
@@ -1 +0,0 @@
|
||||
uid://djtm87b82im5y
|
||||
@@ -1,126 +0,0 @@
|
||||
## Pre-bakes WMO world objects into PackedScene (.tscn) files for fast runtime loading.
|
||||
##
|
||||
## Usage:
|
||||
## godot --headless --path <project> --script res://tools/bake_wmo_cache.gd -- \
|
||||
## --map Azeroth --extracted res://extracted --output res://cache/wmo_tscn --force
|
||||
extends SceneTree
|
||||
|
||||
const WMO_BUILDER_SCRIPT := preload("res://addons/mpq_extractor/loaders/wmo_builder.gd")
|
||||
|
||||
func _initialize() -> void:
|
||||
var args := OS.get_cmdline_user_args()
|
||||
var map_name := _arg(args, "--map", "Azeroth")
|
||||
var extracted := _res(_arg(args, "--extracted", "res://extracted"))
|
||||
var output_dir := _res(_arg(args, "--output", "res://cache/wmo_tscn"))
|
||||
var force := args.has("--force")
|
||||
|
||||
if not ClassDB.class_exists("ADTLoader") or not ClassDB.class_exists("WMOLoader"):
|
||||
push_error("GDExtension not loaded. Rebuild first.")
|
||||
quit(1)
|
||||
return
|
||||
|
||||
DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(output_dir))
|
||||
|
||||
# Collect unique WMO paths from all ADT tiles
|
||||
var unique: Dictionary = {}
|
||||
var adt_loader = ClassDB.instantiate("ADTLoader")
|
||||
var map_dir := extracted.path_join("World/Maps/%s" % map_name)
|
||||
var dir := DirAccess.open(map_dir)
|
||||
if dir == null:
|
||||
push_error("Cannot open map dir: %s" % map_dir)
|
||||
quit(1)
|
||||
return
|
||||
|
||||
print("Scanning ADT tiles...")
|
||||
var scanned := 0
|
||||
for fname in dir.get_files():
|
||||
if not fname.ends_with(".adt"):
|
||||
continue
|
||||
var parts := fname.trim_suffix(".adt").split("_")
|
||||
if parts.size() != 3 or parts[0] != map_name:
|
||||
continue
|
||||
var data: Dictionary = adt_loader.call("load_adt",
|
||||
ProjectSettings.globalize_path(map_dir.path_join(fname)))
|
||||
if data.is_empty():
|
||||
continue
|
||||
for rel in data.get("wmo_names", PackedStringArray()):
|
||||
var norm := str(rel).replace("\\", "/").to_lower()
|
||||
if not norm.is_empty():
|
||||
unique[norm] = true
|
||||
scanned += 1
|
||||
|
||||
print("Found %d unique WMO models from %d tiles." % [unique.size(), scanned])
|
||||
|
||||
var wmo_loader = ClassDB.instantiate("WMOLoader")
|
||||
var baked := 0
|
||||
var skipped := 0
|
||||
var failed := 0
|
||||
var total := unique.size()
|
||||
var i := 0
|
||||
|
||||
for rel_path in unique.keys():
|
||||
i += 1
|
||||
var stem: String = rel_path.get_basename()
|
||||
var out_path := output_dir.path_join(stem + ".tscn")
|
||||
|
||||
if not force and ResourceLoader.exists(out_path):
|
||||
skipped += 1
|
||||
continue
|
||||
|
||||
var abs_wmo := ProjectSettings.globalize_path(extracted.path_join(rel_path))
|
||||
if not FileAccess.file_exists(abs_wmo):
|
||||
failed += 1
|
||||
continue
|
||||
|
||||
var data: Dictionary = wmo_loader.call("load_wmo", abs_wmo)
|
||||
if data.is_empty():
|
||||
failed += 1
|
||||
continue
|
||||
|
||||
var node: Node3D = WMO_BUILDER_SCRIPT.build(data, extracted)
|
||||
if node == null:
|
||||
failed += 1
|
||||
continue
|
||||
|
||||
_set_owner_recursive(node, node)
|
||||
var scene := PackedScene.new()
|
||||
var err := scene.pack(node)
|
||||
node.free()
|
||||
if err != OK:
|
||||
push_warning("pack failed for %s: %d" % [rel_path, err])
|
||||
failed += 1
|
||||
continue
|
||||
|
||||
DirAccess.make_dir_recursive_absolute(
|
||||
ProjectSettings.globalize_path(out_path.get_base_dir()))
|
||||
err = ResourceSaver.save(scene, out_path)
|
||||
if err != OK:
|
||||
push_warning("save failed for %s: %d" % [out_path, err])
|
||||
failed += 1
|
||||
continue
|
||||
|
||||
baked += 1
|
||||
if i % 20 == 0 or i == total:
|
||||
print("[%d/%d] baked=%d skipped=%d failed=%d" % [i, total, baked, skipped, failed])
|
||||
|
||||
print("Done. baked=%d skipped=%d failed=%d" % [baked, skipped, failed])
|
||||
quit(0)
|
||||
|
||||
|
||||
func _arg(args: PackedStringArray, name: String, default: String) -> String:
|
||||
var idx := args.find(name)
|
||||
if idx >= 0 and idx + 1 < args.size():
|
||||
return args[idx + 1]
|
||||
return default
|
||||
|
||||
|
||||
func _res(path: String) -> String:
|
||||
if path.begins_with("res://") or path.begins_with("user://"):
|
||||
return path
|
||||
return "res://" + path.trim_prefix("./").trim_prefix("/")
|
||||
|
||||
|
||||
func _set_owner_recursive(node: Node, owner_root: Node) -> void:
|
||||
for child in node.get_children():
|
||||
child.owner = owner_root
|
||||
_set_owner_recursive(child, owner_root)
|
||||
@@ -1 +0,0 @@
|
||||
uid://beyuk31ywdwua
|
||||
@@ -1,94 +0,0 @@
|
||||
extends SceneTree
|
||||
|
||||
func _initialize() -> void:
|
||||
var args := OS.get_cmdline_user_args()
|
||||
var map_name := _get_arg_value(args, "--map", "Azeroth")
|
||||
var extracted_dir := _normalize_res_path(_get_arg_value(args, "--extracted", "res://extracted"))
|
||||
var output_path := _normalize_output_path(_get_arg_value(args, "--output", "res://cache/m2_glb/%s_m2_list.txt" % map_name))
|
||||
|
||||
if not ClassDB.class_exists("ADTLoader"):
|
||||
push_error("ADTLoader not found. Rebuild GDExtension first.")
|
||||
quit(1)
|
||||
return
|
||||
|
||||
var loader = ClassDB.instantiate("ADTLoader")
|
||||
if loader == null:
|
||||
push_error("Failed to instantiate ADTLoader.")
|
||||
quit(1)
|
||||
return
|
||||
|
||||
var map_dir := extracted_dir.path_join("World/Maps/%s" % map_name)
|
||||
var dir := DirAccess.open(map_dir)
|
||||
if dir == null:
|
||||
push_error("Cannot open map directory: %s" % map_dir)
|
||||
quit(1)
|
||||
return
|
||||
|
||||
var unique_models := {}
|
||||
var scanned_tiles := 0
|
||||
|
||||
for file_name in dir.get_files():
|
||||
if not file_name.ends_with(".adt"):
|
||||
continue
|
||||
|
||||
var stem := file_name.trim_suffix(".adt")
|
||||
var parts := stem.split("_")
|
||||
if parts.size() != 3 or parts[0] != map_name:
|
||||
continue
|
||||
|
||||
var source_abs_path := ProjectSettings.globalize_path(map_dir.path_join(file_name))
|
||||
var data: Dictionary = loader.call("load_adt", source_abs_path)
|
||||
if data.is_empty():
|
||||
continue
|
||||
|
||||
var names: PackedStringArray = data.get("m2_names", PackedStringArray())
|
||||
for rel_path in names:
|
||||
var normalized := str(rel_path).replace("\\", "/")
|
||||
if not normalized.is_empty():
|
||||
unique_models[normalized.to_lower()] = normalized
|
||||
|
||||
scanned_tiles += 1
|
||||
if scanned_tiles % 50 == 0:
|
||||
print("Scanned %d tiles..." % scanned_tiles)
|
||||
|
||||
var models: PackedStringArray = PackedStringArray(unique_models.values())
|
||||
models.sort()
|
||||
|
||||
var output_abs_path := ProjectSettings.globalize_path(output_path)
|
||||
DirAccess.make_dir_recursive_absolute(output_abs_path.get_base_dir())
|
||||
|
||||
var file := FileAccess.open(output_path, FileAccess.WRITE)
|
||||
if file == null:
|
||||
push_error("Failed to open output file: %s" % output_path)
|
||||
quit(2)
|
||||
return
|
||||
|
||||
for rel_path in models:
|
||||
file.store_line(rel_path)
|
||||
file.close()
|
||||
|
||||
print("Collected %d unique M2 models from %d tiles -> %s" % [
|
||||
models.size(),
|
||||
scanned_tiles,
|
||||
output_path
|
||||
])
|
||||
quit(0)
|
||||
|
||||
|
||||
func _get_arg_value(args: PackedStringArray, name: String, default_value: String) -> String:
|
||||
var index := args.find(name)
|
||||
if index >= 0 and index + 1 < args.size():
|
||||
return args[index + 1]
|
||||
return default_value
|
||||
|
||||
|
||||
func _normalize_res_path(path: String) -> String:
|
||||
if path.begins_with("res://"):
|
||||
return path
|
||||
return "res://%s" % path.trim_prefix("./").trim_prefix("/")
|
||||
|
||||
|
||||
func _normalize_output_path(path: String) -> String:
|
||||
if path.begins_with("res://") or path.begins_with("user://"):
|
||||
return path
|
||||
return "res://%s" % path.trim_prefix("./").trim_prefix("/")
|
||||
@@ -1 +0,0 @@
|
||||
uid://d2ef25y28ysf3
|
||||
-1578
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user