Files
ngzz-mc/internal/assets/assets.go

101 lines
3.3 KiB
Go
Raw Normal View History

// Package assets 内容包加载与资源管理。
//
// 职责(设计.md §1.3 内容包统一格式):
// - 扫描 mods/ 与 resourcepacks/,识别「原生 pack.mcmeta 素材包」与「扩展 manifest.json 内容包」;
// - 按优先级链(功能 Mod 资源 > 素材包 > 光影包 > 默认嵌入)解析资源;
// - 提供纹理、模型、方块状态、图集、语言、字体的统一访问。
//
// 引用约定:资源键使用原版命名(如 "block/stone"、"models/block/stone"),
// 不含命名空间前缀与扩展名。
package assets
import (
"fmt"
"os"
"path/filepath"
"sort"
)
// Manager 资源管理器:持有按优先级排序的内容包列表与嵌入资源根目录。
type Manager struct {
packs []*Pack // 已排序内容包(高优先级在前)
embeddedRoot string // 嵌入/默认资源根(如 assets/,命名空间 minecraft)
}
// New 创建空的资源管理器。
func New() *Manager { return &Manager{} }
// Load 扫描全部包目录并按优先级排序;目录不存在时视为无包(默认回退策略)。
func (m *Manager) Load(embeddedRoot string, packDirs ...string) error {
m.embeddedRoot = embeddedRoot
var packs []*Pack
for _, dir := range packDirs {
ps, err := Discover(dir)
if err != nil {
return fmt.Errorf("assets.Manager.Load %s: %w", dir, err)
}
packs = append(packs, ps...)
}
// 排序(确定性):Mod(0) < 素材包(1) < 光影包(2);同类型 priority 大者优先,再按 ID 字典序。
sort.SliceStable(packs, func(i, j int) bool {
a, b := packs[i], packs[j]
if a.Type != b.Type {
return a.Type < b.Type
}
if a.Priority() != b.Priority() {
return a.Priority() > b.Priority()
}
return a.ID < b.ID
})
m.packs = packs
return nil
}
// Packs 返回已加载内容包(只读视图,供日志/调试)。
func (m *Manager) Packs() []*Pack { return m.packs }
// namespaces 返回包的资源命名空间候选(原版包固定 minecraft)。
func (p *Pack) namespaces() []string {
if p.Manifest != nil && p.Manifest.ResourceNamespace != "" {
return []string{p.Manifest.ResourceNamespace, "minecraft"}
}
return []string{"minecraft"}
}
// find 按优先级链查找 kind 类别下的资源 key(扩展名 ext),返回首个存在的文件路径。
// kind 取值:textures / models / blockstates / lang / font / atlases / shaders。
func (m *Manager) find(kind, key, ext string) (string, bool) {
for _, p := range m.packs {
for _, ns := range p.namespaces() {
path := filepath.Join(p.Root, "assets", ns, kind, key+ext)
if fileExists(path) {
return path, true
}
}
}
// 默认嵌入资源(assets/ 根,命名空间 minecraft)
if m.embeddedRoot != "" {
path := filepath.Join(m.embeddedRoot, kind, key+ext)
if fileExists(path) {
return path, true
}
}
return "", false
}
// ResolveFile 通用资源解析入口。
func (m *Manager) ResolveFile(kind, key, ext string) (string, bool) {
return m.find(kind, key, ext)
}
// ResolveTexture 解析纹理键(如 "block/stone")到 PNG 文件路径。
func (m *Manager) ResolveTexture(key string) (string, bool) {
return m.find("textures", key, ".png")
}
// fileExists 判断路径是否为已存在的普通文件。
func fileExists(path string) bool {
st, err := os.Stat(path)
return err == nil && !st.IsDir()
}