2020-10-10 22:49:17 -04:00
|
|
|
package d2systems
|
|
|
|
|
|
|
|
import (
|
2020-11-14 12:52:07 -05:00
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
|
2020-10-13 04:19:38 -04:00
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
|
|
|
|
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2cache"
|
|
|
|
|
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
|
|
|
|
2020-10-12 17:35:11 -04:00
|
|
|
"github.com/gravestench/akara"
|
2020-10-10 22:49:17 -04:00
|
|
|
|
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2components"
|
|
|
|
)
|
|
|
|
|
2020-10-13 04:19:38 -04:00
|
|
|
const (
|
|
|
|
languageTokenFont = "{LANG_FONT}"
|
|
|
|
languageTokenStringTable = "{LANG}"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
fileHandleCacheBudget = 1024
|
|
|
|
fileHandleCacheEntryWeight = 1
|
|
|
|
)
|
|
|
|
|
2020-11-14 12:52:07 -05:00
|
|
|
const (
|
|
|
|
logPrefixFileHandleResolver = "File Handle Resolver"
|
|
|
|
)
|
|
|
|
|
2020-10-10 22:49:17 -04:00
|
|
|
func NewFileHandleResolver() *FileHandleResolutionSystem {
|
|
|
|
// this filter is for entities that have a file path and file type but no file handle.
|
2020-10-12 17:35:11 -04:00
|
|
|
filesToSource := akara.NewFilter().
|
|
|
|
Require(d2components.FilePath).
|
|
|
|
Require(d2components.FileType).
|
|
|
|
Forbid(d2components.FileHandle).
|
|
|
|
Forbid(d2components.FileSource).
|
|
|
|
Build()
|
|
|
|
|
|
|
|
sourcesToUse := akara.NewFilter().
|
|
|
|
RequireOne(d2components.FileSource).
|
2020-10-10 22:49:17 -04:00
|
|
|
Build()
|
|
|
|
|
2020-11-14 12:52:07 -05:00
|
|
|
fhr := &FileHandleResolutionSystem{
|
2020-11-19 17:17:01 -05:00
|
|
|
BaseSubscriberSystem: akara.NewBaseSubscriberSystem(filesToSource, sourcesToUse),
|
2020-10-13 04:19:38 -04:00
|
|
|
cache: d2cache.CreateCache(fileHandleCacheBudget).(*d2cache.Cache),
|
2020-11-14 12:52:07 -05:00
|
|
|
Logger: d2util.NewLogger(),
|
2020-10-10 22:49:17 -04:00
|
|
|
}
|
2020-11-14 12:52:07 -05:00
|
|
|
|
|
|
|
fhr.SetPrefix(logPrefixFileHandleResolver)
|
|
|
|
|
|
|
|
return fhr
|
2020-10-10 22:49:17 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
type FileHandleResolutionSystem struct {
|
2020-11-19 17:17:01 -05:00
|
|
|
*akara.BaseSubscriberSystem
|
2020-11-14 12:52:07 -05:00
|
|
|
*d2util.Logger
|
2020-10-13 04:19:38 -04:00
|
|
|
cache *d2cache.Cache
|
2020-10-12 17:35:11 -04:00
|
|
|
filesToLoad *akara.Subscription
|
|
|
|
sourcesToUse *akara.Subscription
|
|
|
|
filePaths *d2components.FilePathMap
|
|
|
|
fileTypes *d2components.FileTypeMap
|
|
|
|
fileSources *d2components.FileSourceMap
|
|
|
|
fileHandles *d2components.FileHandleMap
|
2020-10-10 22:49:17 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Init initializes the system with the given world
|
2020-10-12 17:35:11 -04:00
|
|
|
func (m *FileHandleResolutionSystem) Init(world *akara.World) {
|
2020-11-14 12:52:07 -05:00
|
|
|
m.Info("initializing ...")
|
|
|
|
|
2020-10-12 17:35:11 -04:00
|
|
|
m.filesToLoad = m.Subscriptions[0]
|
|
|
|
m.sourcesToUse = m.Subscriptions[1]
|
|
|
|
|
2020-10-10 22:49:17 -04:00
|
|
|
// 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
|
2020-11-18 21:33:22 -05:00
|
|
|
func (m *FileHandleResolutionSystem) Update() {
|
2020-10-12 17:35:11 -04:00
|
|
|
filesToLoad := m.filesToLoad.GetEntities()
|
|
|
|
sourcesToUse := m.sourcesToUse.GetEntities()
|
|
|
|
|
|
|
|
for _, fileID := range filesToLoad {
|
|
|
|
for _, sourceID := range sourcesToUse {
|
|
|
|
if m.loadFileWithSource(fileID, sourceID) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2020-10-10 22:49:17 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-12 17:35:11 -04:00
|
|
|
// 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)
|
2020-10-10 22:49:17 -04:00
|
|
|
if !found {
|
2020-10-12 17:35:11 -04:00
|
|
|
return false
|
2020-10-10 22:49:17 -04:00
|
|
|
}
|
|
|
|
|
2020-10-13 04:19:38 -04:00
|
|
|
ft, found := m.fileTypes.GetFileType(fileID)
|
|
|
|
if !found {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2020-10-12 17:35:11 -04:00
|
|
|
source, found := m.fileSources.GetFileSource(sourceID)
|
|
|
|
if !found {
|
|
|
|
return false
|
|
|
|
}
|
2020-10-10 22:49:17 -04:00
|
|
|
|
2020-10-13 04:19:38 -04:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2020-10-12 17:35:11 -04:00
|
|
|
data, err := source.Open(fp)
|
|
|
|
if err != nil {
|
2020-10-13 04:19:38 -04:00
|
|
|
// 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
|
|
|
|
}
|
2020-10-10 22:49:17 -04:00
|
|
|
}
|
2020-10-12 17:35:11 -04:00
|
|
|
|
2020-11-14 12:52:07 -05:00
|
|
|
m.Infof("resolved `%s` with source `%s`", fp.Path, sourceFp.Path)
|
2020-10-13 04:19:38 -04:00
|
|
|
|
|
|
|
component := m.fileHandles.AddFileHandle(fileID)
|
|
|
|
component.Data = data
|
|
|
|
|
|
|
|
m.cache.Insert(cacheKey, data, assetCacheEntryWeight)
|
2020-10-12 17:35:11 -04:00
|
|
|
|
|
|
|
return true
|
2020-10-10 22:49:17 -04:00
|
|
|
}
|
2020-10-13 04:19:38 -04:00
|
|
|
|
|
|
|
func (m *FileHandleResolutionSystem) makeCacheKey(path, source string) string {
|
|
|
|
const sep = "->"
|
|
|
|
return strings.Join([]string{source, path}, sep)
|
|
|
|
}
|