Files
mtgodot-poc/project/ui/ui_assets.gd
T
2026-09-27 19:44:41 -07:00

255 lines
7.9 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# UiAssets (P1) —— 解析 uiscript 里的图片路径(`.sub` / `.tga` / `.png` / `.jpg` / `.dds`)。
#
# 路径形如 `d:/ymir work/ui/public/middle_button_01.sub`。解析:
# 1) 优先从 40250 Client/pack(Metin2Pack)取(保持 d:/ 盘符或规范相对路径)
# 2) 支持 pack 内 .sub 子图切片(AtlasTexture),支持 version 1.0 / 2.0 及公用 Public.dds / IntroEmpire.dds 母图寻址
# 3) 若 pack 找不到且指定了 assets_root,回退到散文件目录
# 4) .dds 走 Metin2World.load_dds(C++ dxt 解码)运行时解成 Image。
extends RefCounted
static var _cache := {}
static func clear_cache() -> void:
_cache.clear()
# DDS 运行时解码:走 Metin2World.load_dds(C++ 里的 dxt.cpp,DXT1/3/5 + BGRA8)。
# Godot 无原生运行时 DDS 解码,UI 里的 select.dds / 职业名图都得走这条路。
static func load_dds_image(path: String) -> Image:
if path == "" or not FileAccess.file_exists(path):
return null
if not ClassDB.class_exists("Metin2World"):
return null
var helper: Object = ClassDB.instantiate("Metin2World")
var img = helper.call("load_dds", path)
helper.free()
return img if img is Image else null
static func load_tex(assets_root: String, vpath: String) -> Texture2D:
if vpath == "":
return null
var key := (assets_root if assets_root != "" else "pack") + "|" + vpath
if _cache.has(key):
return _cache[key]
var tex: Texture2D = _load_pack_any(vpath)
if tex == null and assets_root != "":
tex = _load_uncached(assets_root, vpath)
_cache[key] = tex
return tex
# 只从 40250 Client/pack(Metin2Pack)取,不看 assets/ 散文件:移植的客户端从包里读模型,
# 贴图也必须是同一份(assets/ 里同名的 fork 贴图 UV 布局可能不同)。
static func load_pack_tex(vpath: String) -> Texture2D:
if vpath == "":
return null
var key := "pack|" + vpath
if _cache.has(key):
return _cache[key]
var tex: Texture2D = _load_pack_any(vpath)
_cache[key] = tex
return tex
static func _strip_drive(p: String) -> String:
var s := p.replace("\\", "/")
if s.length() >= 2 and s[1] == ":":
s = s.substr(2)
return s.lstrip("/")
static func _find_pack_path(vpath: String) -> String:
if not ClassDB.class_exists("Metin2Pack") or not Metin2Pack.is_ready():
return ""
var norm := vpath.replace("\\", "/").strip_edges()
var stripped := _strip_drive(norm)
var candidates: Array[String] = [
norm,
stripped,
"d:/" + stripped,
norm.to_lower(),
stripped.to_lower(),
("d:/" + stripped).to_lower(),
]
for c in candidates:
if Metin2Pack.exists(c):
return c
return ""
static func _load_pack_any(vpath: String) -> Texture2D:
var pack_path := _find_pack_path(vpath)
if pack_path == "":
return null
var ext := pack_path.get_extension().to_lower()
if ext == "sub":
return _load_pack_sub(pack_path)
return _load_pack_image(pack_path)
static func _load_pack_sub(pack_path: String) -> Texture2D:
if not ClassDB.class_exists("Metin2Pack") or not Metin2Pack.is_ready():
return null
if not Metin2Pack.exists(pack_path):
return null
var bytes: PackedByteArray = Metin2Pack.get_bytes(pack_path)
if bytes.is_empty():
return null
var txt := bytes.get_string_from_ascii()
var image_name := ""
var version := ""
var l := 0
var t := 0
var r := -1
var b := -1
for line in txt.split("\n"):
var parts := line.strip_edges().split(" ", false)
if parts.size() < 2:
continue
match parts[0].to_lower():
"version": version = parts[1].strip_edges().trim_prefix('"').trim_suffix('"')
"image": image_name = parts[1].strip_edges().trim_prefix('"').trim_suffix('"')
"left": l = int(parts[1])
"top": t = int(parts[1])
"right": r = int(parts[1])
"bottom": b = int(parts[1])
if image_name == "":
return null
var sub_dir := pack_path.get_base_dir()
var candidates: Array[String] = []
if version == "2.0":
candidates.append(sub_dir.path_join(image_name))
candidates.append("d:/ymir work/ui/".path_join(image_name))
else:
candidates.append("d:/ymir work/ui/".path_join(image_name))
candidates.append(sub_dir.path_join(image_name))
var search_dir := sub_dir
for _i in 4:
var parent := search_dir.get_base_dir()
if parent == search_dir or parent.is_empty():
break
candidates.append(parent.path_join(image_name))
search_dir = parent
var base_tex: Texture2D = null
for cand in candidates:
base_tex = _load_pack_any(cand)
if base_tex != null:
break
if base_tex == null:
return null
if r <= l or b <= t:
return base_tex
var at := AtlasTexture.new()
at.atlas = base_tex
at.region = Rect2(l, t, r - l, b - t)
return at
static func _resolve(assets_root: String, rel: String) -> String:
var direct := assets_root.path_join(rel)
if FileAccess.file_exists(direct):
return direct
# 散包:<assets>/<sub>/<rel>
var da := DirAccess.open(assets_root)
if da:
for sub in da.get_directories():
var cand := assets_root.path_join(sub).path_join(rel)
if FileAccess.file_exists(cand):
return cand
return ""
static func _load_uncached(assets_root: String, vpath: String) -> Texture2D:
var rel := _strip_drive(vpath)
var real := _resolve(assets_root, rel)
if real == "":
for ext: String in [".tga", ".png", ".jpg"]:
real = _resolve(assets_root, rel.get_basename() + ext)
if real != "":
break
if real == "":
return _load_pack_any(vpath)
if real.get_extension().to_lower() == "sub":
return _load_sub(real)
return _load_image_file(real)
static func _load_pack_image(rel: String) -> Texture2D:
if not ClassDB.class_exists("Metin2Pack") or not Metin2Pack.is_ready():
return null
if not Metin2Pack.exists(rel):
return null
var ext := rel.get_extension().to_lower()
var image: Image
if ext == "dds":
if not ClassDB.class_exists("Metin2World"):
return null
var helper: Object = ClassDB.instantiate("Metin2World")
image = helper.call("load_dds", "pack://" + rel)
helper.free()
else:
var bytes: PackedByteArray = Metin2Pack.get_bytes(rel)
image = Image.new()
var err := ERR_FILE_UNRECOGNIZED
match ext:
"tga": err = image.load_tga_from_buffer(bytes)
"png": err = image.load_png_from_buffer(bytes)
"jpg", "jpeg": err = image.load_jpg_from_buffer(bytes)
"bmp": err = image.load_bmp_from_buffer(bytes)
if err != OK:
return null
return ImageTexture.create_from_image(image) if image != null and not image.is_empty() else null
static func _load_image_file(path: String) -> Texture2D:
if not FileAccess.file_exists(path):
return null
var ext := path.get_extension().to_lower()
if ext == "dds":
var di := load_dds_image(path)
return ImageTexture.create_from_image(di) if di != null else null
var img := Image.new()
if img.load(path) != OK:
return null
return ImageTexture.create_from_image(img)
static func _load_sub(path: String) -> Texture2D:
var txt := FileAccess.get_file_as_string(path)
var image_name := ""
var l := 0
var t := 0
var r := -1
var b := -1
for line in txt.split("\n"):
var parts := line.strip_edges().split(" ", false)
if parts.size() < 2:
continue
match parts[0]:
"image": image_name = parts[1].strip_edges().trim_prefix('"').trim_suffix('"')
"left": l = int(parts[1])
"top": t = int(parts[1])
"right": r = int(parts[1])
"bottom": b = int(parts[1])
if image_name == "":
return null
var img_path := ""
var search_dir := path.get_base_dir()
for _i in 6:
for candidate in [image_name, image_name.get_basename() + ".tga",
image_name.get_basename() + ".png", image_name.get_basename() + ".dds"]:
var candidate_path := search_dir.path_join(candidate)
if FileAccess.file_exists(candidate_path):
img_path = candidate_path
break
if img_path != "":
break
var parent_dir := search_dir.get_base_dir()
if parent_dir == search_dir:
break
search_dir = parent_dir
if not FileAccess.file_exists(img_path):
return null
var base := _load_image_file(img_path)
if base == null:
return null
if r <= l or b <= t:
return base
var at := AtlasTexture.new()
at.atlas = base
var region_top := t
at.region = Rect2(l, region_top, r - l, b - t)
return at