101 lines
3.3 KiB
Go
101 lines
3.3 KiB
Go
// 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()
|
||
}
|