mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2025-02-10 02:26:29 -05:00
186 lines
4.8 KiB
Go
186 lines
4.8 KiB
Go
package d2systems
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2cache"
|
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
|
|
|
"github.com/gravestench/akara"
|
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2components"
|
|
)
|
|
|
|
const (
|
|
languageTokenFont = "{LANG_FONT}"
|
|
languageTokenStringTable = "{LANG}"
|
|
)
|
|
|
|
const (
|
|
fileHandleCacheBudget = 1024
|
|
fileHandleCacheEntryWeight = 1
|
|
)
|
|
|
|
func NewFileHandleResolver() *FileHandleResolutionSystem {
|
|
// this filter is for entities that have a file path and file type but no file handle.
|
|
filesToSource := akara.NewFilter().
|
|
Require(d2components.FilePath).
|
|
Require(d2components.FileType).
|
|
Forbid(d2components.FileHandle).
|
|
Forbid(d2components.FileSource).
|
|
Build()
|
|
|
|
sourcesToUse := akara.NewFilter().
|
|
RequireOne(d2components.FileSource).
|
|
Build()
|
|
|
|
configsToUse := akara.NewFilter().
|
|
Require(d2components.GameConfig).
|
|
Build()
|
|
|
|
return &FileHandleResolutionSystem{
|
|
SubscriberSystem: akara.NewSubscriberSystem(filesToSource, sourcesToUse, configsToUse),
|
|
cache: d2cache.CreateCache(fileHandleCacheBudget).(*d2cache.Cache),
|
|
}
|
|
}
|
|
|
|
type FileHandleResolutionSystem struct {
|
|
*akara.SubscriberSystem
|
|
cache *d2cache.Cache
|
|
filesToLoad *akara.Subscription
|
|
sourcesToUse *akara.Subscription
|
|
filePaths *d2components.FilePathMap
|
|
fileTypes *d2components.FileTypeMap
|
|
fileSources *d2components.FileSourceMap
|
|
fileHandles *d2components.FileHandleMap
|
|
}
|
|
|
|
// Init initializes the system with the given world
|
|
func (m *FileHandleResolutionSystem) Init(world *akara.World) {
|
|
m.World = world
|
|
|
|
for subIdx := range m.Subscriptions {
|
|
m.Subscriptions[subIdx] = m.AddSubscription(m.Subscriptions[subIdx].Filter)
|
|
}
|
|
|
|
if world == nil {
|
|
m.SetActive(false)
|
|
return
|
|
}
|
|
|
|
m.filesToLoad = m.Subscriptions[0]
|
|
m.sourcesToUse = m.Subscriptions[1]
|
|
|
|
testBS := akara.NewBitSet(int(d2components.FileSourceCID), 1)
|
|
truth := m.sourcesToUse.Filter.Allow(testBS)
|
|
_ = truth
|
|
|
|
// try to inject the components we require, then cast the returned
|
|
// abstract ComponentMap back to the concrete implementation
|
|
m.filePaths = m.InjectMap(d2components.FilePath).(*d2components.FilePathMap)
|
|
m.fileTypes = m.InjectMap(d2components.FileType).(*d2components.FileTypeMap)
|
|
m.fileHandles = m.InjectMap(d2components.FileHandle).(*d2components.FileHandleMap)
|
|
m.fileSources = m.InjectMap(d2components.FileSource).(*d2components.FileSourceMap)
|
|
}
|
|
|
|
// Process processes all of the Entities
|
|
func (m *FileHandleResolutionSystem) Process() {
|
|
filesToLoad := m.filesToLoad.GetEntities()
|
|
sourcesToUse := m.sourcesToUse.GetEntities()
|
|
|
|
for _, fileID := range filesToLoad {
|
|
for _, sourceID := range sourcesToUse {
|
|
if m.loadFileWithSource(fileID, sourceID) {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// try to load a file with a source, returns true if loaded
|
|
func (m *FileHandleResolutionSystem) loadFileWithSource(fileID, sourceID akara.EID) bool {
|
|
fp, found := m.filePaths.GetFilePath(fileID)
|
|
if !found {
|
|
return false
|
|
}
|
|
|
|
ft, found := m.fileTypes.GetFileType(fileID)
|
|
if !found {
|
|
return false
|
|
}
|
|
|
|
source, found := m.fileSources.GetFileSource(sourceID)
|
|
if !found {
|
|
return false
|
|
}
|
|
|
|
sourceFp, found := m.filePaths.GetFilePath(sourceID)
|
|
if !found {
|
|
return false
|
|
}
|
|
|
|
// replace the locale tokens if present
|
|
if strings.Contains(fp.Path, languageTokenFont) {
|
|
fp.Path = strings.ReplaceAll(fp.Path, languageTokenFont, "latin")
|
|
} else if strings.Contains(fp.Path, languageTokenStringTable) {
|
|
fp.Path = strings.ReplaceAll(fp.Path, languageTokenStringTable, "ENG")
|
|
}
|
|
|
|
cacheKey := m.makeCacheKey(fp.Path, sourceFp.Path)
|
|
if entry, found := m.cache.Retrieve(cacheKey); found {
|
|
component := m.fileHandles.AddFileHandle(fileID)
|
|
component.Data = entry.(d2interface.DataStream)
|
|
|
|
return true
|
|
}
|
|
|
|
data, err := source.Open(fp)
|
|
if err != nil {
|
|
// HACK: sound environment stuff doesnt specify the path, just the filename
|
|
// so we gotta check this edge case
|
|
if ft.Type != d2enum.FileTypeWAV {
|
|
return false
|
|
}
|
|
|
|
if !strings.Contains(fp.Path, "sfx") {
|
|
return false
|
|
}
|
|
|
|
tryPath := strings.ReplaceAll(fp.Path, "sfx", "music")
|
|
tmpComponent := &d2components.FilePathComponent{Path: tryPath}
|
|
|
|
cacheKey := m.makeCacheKey(tryPath, sourceFp.Path)
|
|
if entry, found := m.cache.Retrieve(cacheKey); found {
|
|
component := m.fileHandles.AddFileHandle(fileID)
|
|
component.Data = entry.(d2interface.DataStream)
|
|
fp.Path = tryPath
|
|
|
|
return true
|
|
} else {
|
|
data, err = source.Open(tmpComponent)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
fp.Path = tryPath
|
|
}
|
|
}
|
|
|
|
//fmt.Printf("%s -> %s\n", sourceFp.Path, fp.Path)
|
|
|
|
component := m.fileHandles.AddFileHandle(fileID)
|
|
component.Data = data
|
|
|
|
m.cache.Insert(cacheKey, data, assetCacheEntryWeight)
|
|
|
|
return true
|
|
}
|
|
|
|
func (m *FileHandleResolutionSystem) makeCacheKey(path, source string) string {
|
|
const sep = "->"
|
|
return strings.Join([]string{source, path}, sep)
|
|
}
|