mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2024-06-13 03:00:42 +00:00
improve AssetManager implementation (#728)
* improve AssetManager implementation Notable changes are: * removed the individual managers inside of d2asset, only one asset manager * AssetManager now has caches for the types of files it loads * created a type for TextDictionary (the txt file structs) * fixed a file path bug in d2loader Source * fixed a asset stream bug in d2loader Asset * d2loader.Loader now needs a d2config.Config on creation (for resolving locale files) * updated the mpq file in d2asset test data, added test case for "sub-directory" * added a Data method to d2asset.Asset. The data is cached on first full read. * renamed ArchiveDataStream to DataStream in d2interface * moved palette utility func out of d2asset and into d2util * bugfix for MacOS mpq loader issue * minor lint fixes * removed obsolete interfaces from d2interface * lint fixes, added data caching to filesystem asset * adding comment for mpq asset close * adding comment for mpq asset close
This commit is contained in:
parent
854fce3b14
commit
7f6ae1b785
|
@ -17,18 +17,17 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2tbl"
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
|
|
||||||
|
|
||||||
"github.com/pkg/profile"
|
"github.com/pkg/profile"
|
||||||
"golang.org/x/image/colornames"
|
"golang.org/x/image/colornames"
|
||||||
"gopkg.in/alecthomas/kingpin.v2"
|
"gopkg.in/alecthomas/kingpin.v2"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
|
||||||
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2tbl"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||||
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2config"
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2config"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
|
||||||
|
|
|
@ -308,7 +308,7 @@ func (v *MPQ) ReadFile(fileName string) ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadFileStream reads the mpq file data and returns a stream
|
// ReadFileStream reads the mpq file data and returns a stream
|
||||||
func (v *MPQ) ReadFileStream(fileName string) (d2interface.ArchiveDataStream, error) {
|
func (v *MPQ) ReadFileStream(fileName string) (d2interface.DataStream, error) {
|
||||||
fileBlockData, err := v.getFileBlockData(fileName)
|
fileBlockData, err := v.getFileBlockData(fileName)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -2,7 +2,7 @@ package d2mpq
|
||||||
|
|
||||||
import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||||
|
|
||||||
var _ d2interface.ArchiveDataStream = &MpqDataStream{} // Static check to confirm struct conforms to interface
|
var _ d2interface.DataStream = &MpqDataStream{} // Static check to confirm struct conforms to interface
|
||||||
|
|
||||||
// MpqDataStream represents a stream for MPQ data.
|
// MpqDataStream represents a stream for MPQ data.
|
||||||
type MpqDataStream struct {
|
type MpqDataStream struct {
|
||||||
|
|
|
@ -7,6 +7,9 @@ import (
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2datautils"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2datautils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TextDictionary is a string map
|
||||||
|
type TextDictionary map[string]string
|
||||||
|
|
||||||
type textDictionaryHashEntry struct {
|
type textDictionaryHashEntry struct {
|
||||||
IsActive bool
|
IsActive bool
|
||||||
Index uint16
|
Index uint16
|
||||||
|
@ -16,7 +19,7 @@ type textDictionaryHashEntry struct {
|
||||||
NameLength uint16
|
NameLength uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
var lookupTable map[string]string //nolint:gochecknoglobals // currently global by design
|
var lookupTable TextDictionary //nolint:gochecknoglobals // currently global by design
|
||||||
|
|
||||||
const (
|
const (
|
||||||
crcByteCount = 2
|
crcByteCount = 2
|
||||||
|
@ -35,9 +38,9 @@ func TranslateString(key string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadTextDictionary loads the text dictionary from the given data
|
// LoadTextDictionary loads the text dictionary from the given data
|
||||||
func LoadTextDictionary(dictionaryData []byte) map[string]string {
|
func LoadTextDictionary(dictionaryData []byte) TextDictionary {
|
||||||
if lookupTable == nil {
|
if lookupTable == nil {
|
||||||
lookupTable = make(map[string]string)
|
lookupTable = make(TextDictionary)
|
||||||
}
|
}
|
||||||
|
|
||||||
br := d2datautils.CreateStreamReader(dictionaryData)
|
br := d2datautils.CreateStreamReader(dictionaryData)
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
package d2interface
|
|
||||||
|
|
||||||
import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
|
||||||
|
|
||||||
// AnimationManager loads animations
|
|
||||||
type AnimationManager interface {
|
|
||||||
Cacher
|
|
||||||
LoadAnimation(animationPath, palettePath string, drawEffect d2enum.DrawEffect) (Animation, error)
|
|
||||||
}
|
|
|
@ -11,7 +11,7 @@ type Archive interface {
|
||||||
Close()
|
Close()
|
||||||
FileExists(fileName string) bool
|
FileExists(fileName string) bool
|
||||||
ReadFile(fileName string) ([]byte, error)
|
ReadFile(fileName string) ([]byte, error)
|
||||||
ReadFileStream(fileName string) (ArchiveDataStream, error)
|
ReadFileStream(fileName string) (DataStream, error)
|
||||||
ReadTextFile(fileName string) (string, error)
|
ReadTextFile(fileName string) (string, error)
|
||||||
GetFileList() ([]string, error)
|
GetFileList() ([]string, error)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
package d2interface
|
|
||||||
|
|
||||||
// ArchiveManager manages loading files from archives
|
|
||||||
type ArchiveManager interface {
|
|
||||||
Cacher
|
|
||||||
LoadArchiveForFile(filePath string) (Archive, error)
|
|
||||||
FileExistsInArchive(filePath string) (bool, error)
|
|
||||||
LoadArchive(archivePath string) (Archive, error)
|
|
||||||
CacheArchiveEntries() error
|
|
||||||
}
|
|
|
@ -1,7 +1,7 @@
|
||||||
package d2interface
|
package d2interface
|
||||||
|
|
||||||
// ArchiveDataStream is an archive data stream
|
// DataStream is a data stream
|
||||||
type ArchiveDataStream interface {
|
type DataStream interface {
|
||||||
Read(p []byte) (n int, err error)
|
Read(p []byte) (n int, err error)
|
||||||
Seek(offset int64, whence int) (int64, error)
|
Seek(offset int64, whence int) (int64, error)
|
||||||
Close() error
|
Close() error
|
|
@ -1,10 +0,0 @@
|
||||||
package d2interface
|
|
||||||
|
|
||||||
// FileManager manages file access to the archives being managed
|
|
||||||
// by the ArchiveManager
|
|
||||||
type FileManager interface {
|
|
||||||
Cacher
|
|
||||||
LoadFileStream(filePath string) (ArchiveDataStream, error)
|
|
||||||
LoadFile(filePath string) ([]byte, error)
|
|
||||||
FileExists(filePath string) (bool, error)
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
package d2interface
|
|
||||||
|
|
||||||
// FontManager manages fonts that are in archives being
|
|
||||||
// managed by the ArchiveManager
|
|
||||||
type FontManager interface {
|
|
||||||
Cacher
|
|
||||||
LoadFont(tablePath, spritePath, palettePath string) (Font, error)
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
package d2interface
|
|
||||||
|
|
||||||
// PaletteManager is responsible for loading palettes
|
|
||||||
type PaletteManager interface {
|
|
||||||
Cacher
|
|
||||||
LoadPalette(palettePath string) (Palette, error)
|
|
||||||
}
|
|
|
@ -11,8 +11,11 @@ import (
|
||||||
// asset source), and it can read data and seek within the data
|
// asset source), and it can read data and seek within the data
|
||||||
type Asset interface {
|
type Asset interface {
|
||||||
fmt.Stringer
|
fmt.Stringer
|
||||||
io.ReadSeeker
|
io.Reader
|
||||||
|
io.Seeker
|
||||||
|
io.Closer
|
||||||
Type() types.AssetType
|
Type() types.AssetType
|
||||||
Source() Source
|
Source() Source
|
||||||
Path() string
|
Path() string
|
||||||
|
Data() ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
package types
|
package types
|
||||||
|
|
||||||
import "strings"
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2mpq"
|
||||||
|
)
|
||||||
|
|
||||||
// SourceType represents the type of the asset source
|
// SourceType represents the type of the asset source
|
||||||
type SourceType int
|
type SourceType int
|
||||||
|
@ -27,3 +32,16 @@ func Ext2SourceType(ext string) SourceType {
|
||||||
|
|
||||||
return AssetSourceUnknown
|
return AssetSourceUnknown
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CheckSourceType attempts to determine the source type of the source
|
||||||
|
func CheckSourceType(path string) SourceType {
|
||||||
|
// on MacOS, the MPQ's from blizzard don't have file extensions
|
||||||
|
// so we just attempt to init the file as an mpq
|
||||||
|
if _, err := d2mpq.Load(path); err == nil {
|
||||||
|
return AssetSourceMPQ
|
||||||
|
}
|
||||||
|
|
||||||
|
ext := filepath.Ext(path)
|
||||||
|
|
||||||
|
return Ext2SourceType(ext)
|
||||||
|
}
|
||||||
|
|
|
@ -1,12 +1,17 @@
|
||||||
package filesystem
|
package filesystem
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/asset"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/asset"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/asset/types"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/asset/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
bufLength = 32
|
||||||
|
)
|
||||||
|
|
||||||
// static check that Asset implements Asset
|
// static check that Asset implements Asset
|
||||||
var _ asset.Asset = &Asset{}
|
var _ asset.Asset = &Asset{}
|
||||||
|
|
||||||
|
@ -14,6 +19,7 @@ var _ asset.Asset = &Asset{}
|
||||||
type Asset struct {
|
type Asset struct {
|
||||||
assetType types.AssetType
|
assetType types.AssetType
|
||||||
source *Source
|
source *Source
|
||||||
|
data []byte
|
||||||
path string
|
path string
|
||||||
file *os.File
|
file *os.File
|
||||||
}
|
}
|
||||||
|
@ -43,6 +49,44 @@ func (fsa *Asset) Seek(offset int64, whence int) (int64, error) {
|
||||||
return fsa.file.Seek(offset, whence)
|
return fsa.file.Seek(offset, whence)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close closes the file
|
||||||
|
func (fsa *Asset) Close() error {
|
||||||
|
return fsa.file.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data returns the raw file data as a slice of bytes
|
||||||
|
func (fsa *Asset) Data() ([]byte, error) {
|
||||||
|
if fsa.file == nil {
|
||||||
|
return nil, fmt.Errorf("asset has no file: %s", fsa.Path())
|
||||||
|
}
|
||||||
|
|
||||||
|
if fsa.data != nil {
|
||||||
|
return fsa.data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, seekErr := fsa.file.Seek(0, 0)
|
||||||
|
if seekErr != nil {
|
||||||
|
return nil, seekErr
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := make([]byte, bufLength)
|
||||||
|
data := make([]byte, 0)
|
||||||
|
|
||||||
|
for {
|
||||||
|
numBytesRead, readErr := fsa.Read(buf)
|
||||||
|
|
||||||
|
data = append(data, buf[:numBytesRead]...)
|
||||||
|
|
||||||
|
if readErr != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fsa.data = data
|
||||||
|
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
// String returns the path
|
// String returns the path
|
||||||
func (fsa *Asset) String() string {
|
func (fsa *Asset) String() string {
|
||||||
return fsa.Path()
|
return fsa.Path()
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
package d2loader
|
package d2loader
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2cache"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2cache"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||||
|
@ -13,40 +12,85 @@ import (
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/asset/types"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/asset/types"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/filesystem"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/filesystem"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/mpq"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/mpq"
|
||||||
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
|
||||||
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2config"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultCacheBudget = 1024 * 1024 * 512
|
defaultCacheBudget = 1024 * 1024 * 512
|
||||||
defaultCacheEntryWeight = 1
|
defaultCacheEntryWeight = 1
|
||||||
errFileNotFound = "file not found"
|
errFmtFileNotFound = "file not found: %s"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultLanguage = "ENG"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
fontToken = d2resource.LanguageFontToken
|
||||||
|
tableToken = d2resource.LanguageTableToken
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewLoader creates a new loader
|
// NewLoader creates a new loader
|
||||||
func NewLoader() *Loader {
|
func NewLoader(config *d2config.Configuration) *Loader {
|
||||||
loader := &Loader{}
|
loader := &Loader{
|
||||||
|
config: config,
|
||||||
|
}
|
||||||
|
|
||||||
loader.Cache = d2cache.CreateCache(defaultCacheBudget)
|
loader.Cache = d2cache.CreateCache(defaultCacheBudget)
|
||||||
|
|
||||||
|
loader.initFromConfig()
|
||||||
|
|
||||||
return loader
|
return loader
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loader represents the manager that handles loading and caching assets with the asset Sources
|
// Loader represents the manager that handles loading and caching assets with the asset Sources
|
||||||
// that have been added
|
// that have been added
|
||||||
type Loader struct {
|
type Loader struct {
|
||||||
|
config *d2config.Configuration
|
||||||
d2interface.Cache
|
d2interface.Cache
|
||||||
*d2util.Logger
|
*d2util.Logger
|
||||||
Sources []asset.Source
|
Sources []asset.Source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *Loader) initFromConfig() {
|
||||||
|
if l.config == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, mpqName := range l.config.MpqLoadOrder {
|
||||||
|
cleanDir := filepath.Clean(l.config.MpqPath)
|
||||||
|
srcPath := filepath.Join(cleanDir, mpqName)
|
||||||
|
|
||||||
|
_, err := l.AddSource(srcPath)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Load attempts to load an asset with the given sub-path. The sub-path is relative to the root
|
// Load attempts to load an asset with the given sub-path. The sub-path is relative to the root
|
||||||
// of each asset source root (regardless of the type of asset source)
|
// of each asset source root (regardless of the type of asset source)
|
||||||
func (l *Loader) Load(subPath string) (asset.Asset, error) {
|
func (l *Loader) Load(subPath string) (asset.Asset, error) {
|
||||||
|
lang := defaultLanguage
|
||||||
|
|
||||||
|
if l.config != nil {
|
||||||
|
lang = l.config.Language
|
||||||
|
}
|
||||||
|
|
||||||
subPath = filepath.Clean(subPath)
|
subPath = filepath.Clean(subPath)
|
||||||
|
subPath = strings.ReplaceAll(subPath, fontToken, "latin")
|
||||||
|
subPath = strings.ReplaceAll(subPath, tableToken, lang)
|
||||||
|
|
||||||
// first, we check the cache for an existing entry
|
// first, we check the cache for an existing entry
|
||||||
if cached, found := l.Retrieve(subPath); found {
|
if cached, found := l.Retrieve(subPath); found {
|
||||||
l.Debug(fmt.Sprintf("file `%s` exists in loader cache", subPath))
|
l.Debug(fmt.Sprintf("file `%s` exists in loader cache", subPath))
|
||||||
return cached.(asset.Asset), nil
|
|
||||||
|
a := cached.(asset.Asset)
|
||||||
|
_, err := a.Seek(0, 0)
|
||||||
|
|
||||||
|
return a, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// if it isn't in the cache, we check if each source can open the file
|
// if it isn't in the cache, we check if each source can open the file
|
||||||
|
@ -60,7 +104,7 @@ func (l *Loader) Load(subPath string) (asset.Asset, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, errors.New(errFileNotFound)
|
return nil, fmt.Errorf(errFmtFileNotFound, subPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddSource adds an asset source with the given path. The path will either resolve to a directory
|
// AddSource adds an asset source with the given path. The path will either resolve to a directory
|
||||||
|
@ -89,8 +133,7 @@ func (l *Loader) AddSource(path string) (asset.Source, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if mode.IsRegular() {
|
if mode.IsRegular() {
|
||||||
ext := filepath.Ext(cleanPath)
|
sourceType = types.CheckSourceType(cleanPath)
|
||||||
sourceType = types.Ext2SourceType(ext)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch sourceType {
|
switch sourceType {
|
||||||
|
|
|
@ -17,12 +17,13 @@ const (
|
||||||
exclusiveB = "exclusive_b.txt"
|
exclusiveB = "exclusive_b.txt"
|
||||||
exclusiveC = "exclusive_c.txt"
|
exclusiveC = "exclusive_c.txt"
|
||||||
exclusiveD = "exclusive_d.txt"
|
exclusiveD = "exclusive_d.txt"
|
||||||
|
subdirCommonD = "dir\\common.txt"
|
||||||
badSourcePath = "/x/y/z.mpq"
|
badSourcePath = "/x/y/z.mpq"
|
||||||
badFilePath = "a/bad/file/path.txt"
|
badFilePath = "a/bad/file/path.txt"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLoader_NewLoader(t *testing.T) {
|
func TestLoader_NewLoader(t *testing.T) {
|
||||||
loader := NewLoader()
|
loader := NewLoader(nil)
|
||||||
|
|
||||||
if loader.Cache == nil {
|
if loader.Cache == nil {
|
||||||
t.Error("loader should not be nil")
|
t.Error("loader should not be nil")
|
||||||
|
@ -30,7 +31,7 @@ func TestLoader_NewLoader(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoader_AddSource(t *testing.T) {
|
func TestLoader_AddSource(t *testing.T) {
|
||||||
loader := NewLoader()
|
loader := NewLoader(nil)
|
||||||
|
|
||||||
sourceA, errA := loader.AddSource(sourcePathA)
|
sourceA, errA := loader.AddSource(sourcePathA)
|
||||||
sourceB, errB := loader.AddSource(sourcePathB)
|
sourceB, errB := loader.AddSource(sourcePathB)
|
||||||
|
@ -80,7 +81,7 @@ func TestLoader_AddSource(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoader_Load(t *testing.T) {
|
func TestLoader_Load(t *testing.T) {
|
||||||
loader := NewLoader()
|
loader := NewLoader(nil)
|
||||||
|
|
||||||
_, _ = loader.AddSource(sourcePathB) // we expect files common to any source to come from here
|
_, _ = loader.AddSource(sourcePathB) // we expect files common to any source to come from here
|
||||||
_, _ = loader.AddSource(sourcePathD)
|
_, _ = loader.AddSource(sourcePathD)
|
||||||
|
@ -93,6 +94,7 @@ func TestLoader_Load(t *testing.T) {
|
||||||
entryB, errB := loader.Load(exclusiveB)
|
entryB, errB := loader.Load(exclusiveB)
|
||||||
entryC, errC := loader.Load(exclusiveC)
|
entryC, errC := loader.Load(exclusiveC)
|
||||||
entryD, errD := loader.Load(exclusiveD)
|
entryD, errD := loader.Load(exclusiveD)
|
||||||
|
entryDsubdir, errDsubdir := loader.Load(subdirCommonD)
|
||||||
|
|
||||||
_, expectedError := loader.Load(badFilePath) // we expect an Error for this bad file path
|
_, expectedError := loader.Load(badFilePath) // we expect an Error for this bad file path
|
||||||
|
|
||||||
|
@ -106,6 +108,10 @@ func TestLoader_Load(t *testing.T) {
|
||||||
t.Error("files exclusive to each source don't exist")
|
t.Error("files exclusive to each source don't exist")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if errDsubdir != nil {
|
||||||
|
t.Error("mpq subdir entry not found")
|
||||||
|
}
|
||||||
|
|
||||||
if expectedError == nil {
|
if expectedError == nil {
|
||||||
t.Error("expected Error for nonexistant file path")
|
t.Error("expected Error for nonexistant file path")
|
||||||
}
|
}
|
||||||
|
@ -123,6 +129,7 @@ func TestLoader_Load(t *testing.T) {
|
||||||
{entryB, "b"},
|
{entryB, "b"},
|
||||||
{entryC, "c"},
|
{entryC, "c"},
|
||||||
{entryD, "d"},
|
{entryD, "d"},
|
||||||
|
{entryDsubdir, "d"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for idx := range tests {
|
for idx := range tests {
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package mpq
|
package mpq
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||||
|
@ -8,12 +10,17 @@ import (
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/asset/types"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/asset/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
bufLength = 32
|
||||||
|
)
|
||||||
|
|
||||||
// static check that Asset implements Asset
|
// static check that Asset implements Asset
|
||||||
var _ asset.Asset = &Asset{}
|
var _ asset.Asset = &Asset{}
|
||||||
|
|
||||||
// Asset represents a file record within an MPQ archive
|
// Asset represents a file record within an MPQ archive
|
||||||
type Asset struct {
|
type Asset struct {
|
||||||
stream d2interface.ArchiveDataStream
|
stream d2interface.DataStream
|
||||||
|
data []byte
|
||||||
path string
|
path string
|
||||||
source *Source
|
source *Source
|
||||||
}
|
}
|
||||||
|
@ -35,7 +42,12 @@ func (a *Asset) Path() string {
|
||||||
|
|
||||||
// Read will read asset data into the given buffer
|
// Read will read asset data into the given buffer
|
||||||
func (a *Asset) Read(buf []byte) (n int, err error) {
|
func (a *Asset) Read(buf []byte) (n int, err error) {
|
||||||
return a.stream.Read(buf)
|
totalRead, err := a.stream.Read(buf)
|
||||||
|
if totalRead == 0 {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalRead, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Seek will seek the read position for the next read operation
|
// Seek will seek the read position for the next read operation
|
||||||
|
@ -43,7 +55,50 @@ func (a *Asset) Seek(offset int64, whence int) (n int64, err error) {
|
||||||
return a.stream.Seek(offset, whence)
|
return a.stream.Seek(offset, whence)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Path returns the path
|
// Close will seek the read position for the next read operation
|
||||||
|
func (a *Asset) Close() (err error) {
|
||||||
|
// Calling a.stream.Close() will set the stream to nil, we dont want to do that.
|
||||||
|
// Because this asset gets cached, it may get retrieved again and used, in which
|
||||||
|
// case we will want the stream ready. So, instead of closing, we just seek back to the start.
|
||||||
|
// The garbage collector should get around to it if it ever gets ejected from the cache.
|
||||||
|
_, err = a.Seek(0, 0)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data returns the raw file data as a slice of bytes
|
||||||
|
func (a *Asset) Data() ([]byte, error) {
|
||||||
|
if a.stream == nil {
|
||||||
|
return nil, fmt.Errorf("asset has no file: %s", a.Path())
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.data != nil {
|
||||||
|
return a.data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, seekErr := a.Seek(0, 0)
|
||||||
|
if seekErr != nil {
|
||||||
|
return nil, seekErr
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := make([]byte, bufLength)
|
||||||
|
data := make([]byte, 0)
|
||||||
|
|
||||||
|
for {
|
||||||
|
numBytesRead, readErr := a.Read(buf)
|
||||||
|
|
||||||
|
data = append(data, buf[:numBytesRead]...)
|
||||||
|
|
||||||
|
if readErr != nil || numBytesRead == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a.data = data
|
||||||
|
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the path
|
||||||
func (a *Asset) String() string {
|
func (a *Asset) String() string {
|
||||||
return a.Path()
|
return a.Path()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package mpq
|
package mpq
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2mpq"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2mpq"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/asset"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/asset"
|
||||||
|
@ -32,6 +34,7 @@ func (v *Source) Type() types.SourceType {
|
||||||
|
|
||||||
// Open attempts to open a file within the MPQ archive
|
// Open attempts to open a file within the MPQ archive
|
||||||
func (v *Source) Open(name string) (a asset.Asset, err error) {
|
func (v *Source) Open(name string) (a asset.Asset, err error) {
|
||||||
|
name = cleanName(name)
|
||||||
stream, err := v.MPQ.ReadFileStream(name)
|
stream, err := v.MPQ.ReadFileStream(name)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -41,12 +44,13 @@ func (v *Source) Open(name string) (a asset.Asset, err error) {
|
||||||
a = &Asset{
|
a = &Asset{
|
||||||
source: v,
|
source: v,
|
||||||
stream: stream,
|
stream: stream,
|
||||||
|
path: name,
|
||||||
}
|
}
|
||||||
|
|
||||||
return a, nil
|
return a, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns the path of the MPQ on the host filesystem
|
// Path returns the path of the MPQ on the host filesystem
|
||||||
func (v *Source) Path() string {
|
func (v *Source) Path() string {
|
||||||
return v.MPQ.Path()
|
return v.MPQ.Path()
|
||||||
}
|
}
|
||||||
|
@ -55,3 +59,13 @@ func (v *Source) Path() string {
|
||||||
func (v *Source) String() string {
|
func (v *Source) String() string {
|
||||||
return v.Path()
|
return v.Path()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func cleanName(name string) string {
|
||||||
|
name = strings.ReplaceAll(name, "/", "\\")
|
||||||
|
|
||||||
|
if string(name[0]) == "\\" {
|
||||||
|
name = name[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
BIN
d2common/d2loader/testdata/D.mpq
vendored
BIN
d2common/d2loader/testdata/D.mpq
vendored
Binary file not shown.
|
@ -1,4 +1,4 @@
|
||||||
package d2asset
|
package d2util
|
||||||
|
|
||||||
import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||||
|
|
|
@ -7,11 +7,9 @@ import (
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math"
|
|
||||||
|
|
||||||
d2iface "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dcc"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dcc"
|
||||||
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||||
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math"
|
||||||
)
|
)
|
||||||
|
|
||||||
type playMode int
|
type playMode int
|
||||||
|
@ -30,7 +28,7 @@ type animationFrame struct {
|
||||||
offsetX int
|
offsetX int
|
||||||
offsetY int
|
offsetY int
|
||||||
|
|
||||||
image d2iface.Surface
|
image d2interface.Surface
|
||||||
}
|
}
|
||||||
|
|
||||||
type animationDirection struct {
|
type animationDirection struct {
|
||||||
|
@ -114,7 +112,7 @@ func (a *animation) Advance(elapsed float64) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *animation) renderShadow(target d2iface.Surface) error {
|
func (a *animation) renderShadow(target d2interface.Surface) error {
|
||||||
direction := a.directions[a.directionIndex]
|
direction := a.directions[a.directionIndex]
|
||||||
frame := direction.frames[a.frameIndex]
|
frame := direction.frames[a.frameIndex]
|
||||||
|
|
||||||
|
@ -133,7 +131,7 @@ func (a *animation) renderShadow(target d2iface.Surface) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render renders the animation to the given surface
|
// Render renders the animation to the given surface
|
||||||
func (a *animation) Render(target d2iface.Surface) error {
|
func (a *animation) Render(target d2interface.Surface) error {
|
||||||
direction := a.directions[a.directionIndex]
|
direction := a.directions[a.directionIndex]
|
||||||
frame := direction.frames[a.frameIndex]
|
frame := direction.frames[a.frameIndex]
|
||||||
|
|
||||||
|
@ -150,7 +148,7 @@ func (a *animation) Render(target d2iface.Surface) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenderFromOrigin renders the animation from the animation origin
|
// RenderFromOrigin renders the animation from the animation origin
|
||||||
func (a *animation) RenderFromOrigin(target d2iface.Surface, shadow bool) error {
|
func (a *animation) RenderFromOrigin(target d2interface.Surface, shadow bool) error {
|
||||||
if a.originAtBottom {
|
if a.originAtBottom {
|
||||||
direction := a.directions[a.directionIndex]
|
direction := a.directions[a.directionIndex]
|
||||||
frame := direction.frames[a.frameIndex]
|
frame := direction.frames[a.frameIndex]
|
||||||
|
@ -173,7 +171,7 @@ func (a *animation) RenderFromOrigin(target d2iface.Surface, shadow bool) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenderSection renders the section of the animation frame enclosed by bounds
|
// RenderSection renders the section of the animation frame enclosed by bounds
|
||||||
func (a *animation) RenderSection(sfc d2iface.Surface, bound image.Rectangle) error {
|
func (a *animation) RenderSection(sfc d2interface.Surface, bound image.Rectangle) error {
|
||||||
direction := a.directions[a.directionIndex]
|
direction := a.directions[a.directionIndex]
|
||||||
frame := direction.frames[a.frameIndex]
|
frame := direction.frames[a.frameIndex]
|
||||||
|
|
||||||
|
|
|
@ -1,159 +0,0 @@
|
||||||
package d2asset
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dc6"
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dcc"
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
animationBudget = 64
|
|
||||||
)
|
|
||||||
|
|
||||||
// Static checks to confirm struct conforms to interface
|
|
||||||
var _ d2interface.AnimationManager = &animationManager{}
|
|
||||||
var _ d2interface.Cacher = &animationManager{}
|
|
||||||
|
|
||||||
type animationManager struct {
|
|
||||||
*AssetManager
|
|
||||||
cache d2interface.Cache
|
|
||||||
renderer d2interface.Renderer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (am *animationManager) ClearCache() {
|
|
||||||
am.cache.Clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (am *animationManager) GetCache() d2interface.Cache {
|
|
||||||
return am.cache
|
|
||||||
}
|
|
||||||
|
|
||||||
func (am *animationManager) LoadAnimation(
|
|
||||||
animationPath, palettePath string,
|
|
||||||
effect d2enum.DrawEffect) (d2interface.Animation, error) {
|
|
||||||
cachePath := fmt.Sprintf("%s;%s;%d", animationPath, palettePath, effect)
|
|
||||||
if animation, found := am.cache.Retrieve(cachePath); found {
|
|
||||||
return animation.(d2interface.Animation).Clone(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var animation d2interface.Animation
|
|
||||||
|
|
||||||
ext := strings.ToLower(filepath.Ext(animationPath))
|
|
||||||
switch ext {
|
|
||||||
case ".dc6":
|
|
||||||
palette, err := am.LoadPalette(palettePath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
animation, err = am.CreateDC6Animation(animationPath, palette, d2enum.DrawEffectNone)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
case ".dcc":
|
|
||||||
palette, err := am.LoadPalette(palettePath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
animation, err = am.CreateDCCAnimation(animationPath, palette, effect)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unknown animation format: %s", ext)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := am.cache.Insert(cachePath, animation.Clone(), 1); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return animation, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateDC6Animation creates an Animation from d2dc6.DC6 and d2dat.DATPalette
|
|
||||||
func (am *animationManager) CreateDC6Animation(dc6Path string,
|
|
||||||
palette d2interface.Palette, effect d2enum.DrawEffect) (d2interface.Animation, error) {
|
|
||||||
dc6, err := am.loadDC6(dc6Path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
anim := DC6Animation{
|
|
||||||
animation: animation{
|
|
||||||
directions: make([]animationDirection, dc6.Directions),
|
|
||||||
playLength: defaultPlayLength,
|
|
||||||
playLoop: true,
|
|
||||||
originAtBottom: true,
|
|
||||||
effect: effect,
|
|
||||||
},
|
|
||||||
dc6Path: dc6Path,
|
|
||||||
dc6: dc6,
|
|
||||||
palette: palette,
|
|
||||||
renderer: am.renderer,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = anim.SetDirection(0)
|
|
||||||
|
|
||||||
return &anim, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateDCCAnimation creates an animation from d2dcc.DCC and d2dat.DATPalette
|
|
||||||
func (am *animationManager) CreateDCCAnimation(dccPath string,
|
|
||||||
palette d2interface.Palette,
|
|
||||||
effect d2enum.DrawEffect) (d2interface.Animation, error) {
|
|
||||||
dcc, err := am.loadDCC(dccPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
anim := animation{
|
|
||||||
playLength: defaultPlayLength,
|
|
||||||
playLoop: true,
|
|
||||||
directions: make([]animationDirection, dcc.NumberOfDirections),
|
|
||||||
effect: effect,
|
|
||||||
}
|
|
||||||
|
|
||||||
DCC := DCCAnimation{
|
|
||||||
animation: anim,
|
|
||||||
animationManager: am,
|
|
||||||
dccPath: dccPath,
|
|
||||||
palette: palette,
|
|
||||||
renderer: am.renderer,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = DCC.SetDirection(0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &DCC, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (am *animationManager) loadDC6(path string) (*d2dc6.DC6, error) {
|
|
||||||
dc6Data, err := am.LoadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
dc6, err := d2dc6.Load(dc6Data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return dc6, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (am *animationManager) loadDCC(path string) (*d2dcc.DCC, error) {
|
|
||||||
dccData, err := am.LoadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return d2dcc.Load(dccData)
|
|
||||||
}
|
|
|
@ -1,119 +0,0 @@
|
||||||
package d2asset
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"path"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2mpq"
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2config"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Static checks to confirm struct conforms to interface
|
|
||||||
var _ d2interface.ArchiveManager = &archiveManager{}
|
|
||||||
var _ d2interface.Cacher = &archiveManager{}
|
|
||||||
|
|
||||||
type archiveManager struct {
|
|
||||||
*AssetManager
|
|
||||||
cache d2interface.Cache
|
|
||||||
config *d2config.Configuration
|
|
||||||
archives []d2interface.Archive
|
|
||||||
mutex sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
archiveBudget = 1024 * 1024 * 512
|
|
||||||
)
|
|
||||||
|
|
||||||
// LoadArchiveForFile loads the archive for the given (in-archive) file path
|
|
||||||
func (am *archiveManager) LoadArchiveForFile(filePath string) (d2interface.Archive, error) {
|
|
||||||
am.mutex.Lock()
|
|
||||||
defer am.mutex.Unlock()
|
|
||||||
|
|
||||||
if err := am.CacheArchiveEntries(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, archive := range am.archives {
|
|
||||||
if archive.Contains(filePath) {
|
|
||||||
result, ok := am.LoadArchive(archive.Path())
|
|
||||||
if ok == nil {
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.New("file not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
// FileExistsInArchive checks if a file exists in an archive
|
|
||||||
func (am *archiveManager) FileExistsInArchive(filePath string) (bool, error) {
|
|
||||||
am.mutex.Lock()
|
|
||||||
defer am.mutex.Unlock()
|
|
||||||
|
|
||||||
if err := am.CacheArchiveEntries(); err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, archiveEntry := range am.archives {
|
|
||||||
if archiveEntry.Contains(filePath) {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadArchive loads and caches an archive
|
|
||||||
func (am *archiveManager) LoadArchive(archivePath string) (d2interface.Archive, error) {
|
|
||||||
if archive, found := am.cache.Retrieve(archivePath); found {
|
|
||||||
return archive.(d2interface.Archive), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
archive, err := d2mpq.Load(archivePath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := am.cache.Insert(archivePath, archive, int(archive.Size())); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return archive, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CacheArchiveEntries updates the archive entries
|
|
||||||
func (am *archiveManager) CacheArchiveEntries() error {
|
|
||||||
if len(am.archives) == len(am.config.MpqLoadOrder) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
am.archives = nil
|
|
||||||
|
|
||||||
for _, archiveName := range am.config.MpqLoadOrder {
|
|
||||||
archivePath := path.Join(am.config.MpqPath, archiveName)
|
|
||||||
|
|
||||||
archive, err := am.LoadArchive(archivePath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
am.archives = append(
|
|
||||||
am.archives,
|
|
||||||
archive,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClearCache clears the archive manager cache
|
|
||||||
func (am *archiveManager) ClearCache() {
|
|
||||||
am.cache.Clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCache returns the archive manager cache
|
|
||||||
func (am *archiveManager) GetCache() d2interface.Cache {
|
|
||||||
return am.cache
|
|
||||||
}
|
|
|
@ -1,95 +0,0 @@
|
||||||
package d2asset
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2config"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
fileBudget = 1024 * 1024 * 32
|
|
||||||
)
|
|
||||||
|
|
||||||
// Static checks to confirm struct conforms to interface
|
|
||||||
var _ d2interface.FileManager = &fileManager{}
|
|
||||||
var _ d2interface.Cacher = &fileManager{}
|
|
||||||
|
|
||||||
type fileManager struct {
|
|
||||||
*AssetManager
|
|
||||||
cache d2interface.Cache
|
|
||||||
archiveManager d2interface.ArchiveManager
|
|
||||||
config *d2config.Configuration
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadFileStream loads a file as a stream automatically from an archive
|
|
||||||
func (fm *fileManager) LoadFileStream(filePath string) (d2interface.ArchiveDataStream, error) {
|
|
||||||
filePath = fm.fixupFilePath(filePath)
|
|
||||||
|
|
||||||
archive, err := fm.archiveManager.LoadArchiveForFile(filePath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return archive.ReadFileStream(filePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadFile loads a file automatically from a managed archive
|
|
||||||
func (fm *fileManager) LoadFile(filePath string) ([]byte, error) {
|
|
||||||
filePath = fm.fixupFilePath(filePath)
|
|
||||||
if value, found := fm.cache.Retrieve(filePath); found {
|
|
||||||
return value.([]byte), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
archive, err := fm.archiveManager.LoadArchiveForFile(filePath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := archive.ReadFile(filePath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := fm.cache.Insert(filePath, data, len(data)); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FileExists checks if a file exists in an archive
|
|
||||||
func (fm *fileManager) FileExists(filePath string) (bool, error) {
|
|
||||||
filePath = fm.fixupFilePath(filePath)
|
|
||||||
return fm.archiveManager.FileExistsInArchive(filePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fm *fileManager) ClearCache() {
|
|
||||||
fm.cache.Clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fm *fileManager) GetCache() d2interface.Cache {
|
|
||||||
return fm.cache
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fm *fileManager) fixupFilePath(filePath string) string {
|
|
||||||
filePath = fm.removeLocaleTokens(filePath)
|
|
||||||
filePath = strings.ToLower(filePath)
|
|
||||||
filePath = strings.ReplaceAll(filePath, `/`, "\\")
|
|
||||||
filePath = strings.TrimPrefix(filePath, "\\")
|
|
||||||
|
|
||||||
return filePath
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fm *fileManager) removeLocaleTokens(filePath string) string {
|
|
||||||
tableToken := d2resource.LanguageTableToken
|
|
||||||
fontToken := d2resource.LanguageFontToken
|
|
||||||
|
|
||||||
filePath = strings.ReplaceAll(filePath, tableToken, fm.config.Language)
|
|
||||||
|
|
||||||
// fixme: not all languages==latin
|
|
||||||
filePath = strings.ReplaceAll(filePath, fontToken, "latin")
|
|
||||||
|
|
||||||
return filePath
|
|
||||||
}
|
|
|
@ -1,25 +1,49 @@
|
||||||
package d2asset
|
package d2asset
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"image/color"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||||
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dat"
|
||||||
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dc6"
|
||||||
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dcc"
|
||||||
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2pl2"
|
||||||
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2tbl"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||||
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader"
|
||||||
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/asset"
|
||||||
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/asset/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultCacheEntryWeight = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
animationBudget = 1024 * 1024 * 128
|
||||||
|
fontBudget = 128
|
||||||
|
tableBudget = 64
|
||||||
|
paletteBudget = 64
|
||||||
|
paletteTransformBudget = 64
|
||||||
)
|
)
|
||||||
|
|
||||||
// AssetManager loads files and game objects
|
// AssetManager loads files and game objects
|
||||||
type AssetManager struct {
|
type AssetManager struct {
|
||||||
archiveManager d2interface.ArchiveManager
|
renderer d2interface.Renderer
|
||||||
archivedFileManager d2interface.FileManager
|
loader *d2loader.Loader
|
||||||
paletteManager d2interface.PaletteManager
|
tables d2interface.Cache
|
||||||
paletteTransformManager *paletteTransformManager
|
animations d2interface.Cache
|
||||||
animationManager d2interface.AnimationManager
|
fonts d2interface.Cache
|
||||||
fontManager d2interface.FontManager
|
palettes d2interface.Cache
|
||||||
|
transforms d2interface.Cache
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadFileStream streams an MPQ file from a source file path
|
// LoadAsset loads an asset
|
||||||
func (am *AssetManager) LoadFileStream(filePath string) (d2interface.ArchiveDataStream, error) {
|
func (am *AssetManager) LoadAsset(filePath string) (asset.Asset, error) {
|
||||||
data, err := am.archivedFileManager.LoadFileStream(filePath)
|
data, err := am.loader.Load(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("error loading file stream %s (%v)", filePath, err.Error())
|
log.Printf("error loading file stream %s (%v)", filePath, err.Error())
|
||||||
}
|
}
|
||||||
|
@ -27,11 +51,21 @@ func (am *AssetManager) LoadFileStream(filePath string) (d2interface.ArchiveData
|
||||||
return data, err
|
return data, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadFileStream streams an MPQ file from a source file path
|
||||||
|
func (am *AssetManager) LoadFileStream(filePath string) (d2interface.DataStream, error) {
|
||||||
|
return am.LoadAsset(filePath)
|
||||||
|
}
|
||||||
|
|
||||||
// LoadFile loads an entire file from a source file path as a []byte
|
// LoadFile loads an entire file from a source file path as a []byte
|
||||||
func (am *AssetManager) LoadFile(filePath string) ([]byte, error) {
|
func (am *AssetManager) LoadFile(filePath string) ([]byte, error) {
|
||||||
data, err := am.archivedFileManager.LoadFile(filePath)
|
fileAsset, err := am.LoadAsset(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("error loading file %s (%v)", filePath, err.Error())
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := fileAsset.Data()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return data, err
|
return data, err
|
||||||
|
@ -39,7 +73,11 @@ func (am *AssetManager) LoadFile(filePath string) ([]byte, error) {
|
||||||
|
|
||||||
// FileExists checks if a file exists on the underlying file system at the given file path.
|
// FileExists checks if a file exists on the underlying file system at the given file path.
|
||||||
func (am *AssetManager) FileExists(filePath string) (bool, error) {
|
func (am *AssetManager) FileExists(filePath string) (bool, error) {
|
||||||
return am.archivedFileManager.FileExists(filePath)
|
if loadedAsset, err := am.loader.Load(filePath); err != nil || loadedAsset == nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadAnimation loads an animation by its resource path and its palette path
|
// LoadAnimation loads an animation by its resource path and its palette path
|
||||||
|
@ -49,8 +87,43 @@ func (am *AssetManager) LoadAnimation(animationPath, palettePath string) (d2inte
|
||||||
|
|
||||||
// LoadAnimationWithEffect loads an animation by its resource path and its palette path with a given transparency value
|
// LoadAnimationWithEffect loads an animation by its resource path and its palette path with a given transparency value
|
||||||
func (am *AssetManager) LoadAnimationWithEffect(animationPath, palettePath string,
|
func (am *AssetManager) LoadAnimationWithEffect(animationPath, palettePath string,
|
||||||
drawEffect d2enum.DrawEffect) (d2interface.Animation, error) {
|
effect d2enum.DrawEffect) (d2interface.Animation, error) {
|
||||||
return am.animationManager.LoadAnimation(animationPath, palettePath, drawEffect)
|
cachePath := fmt.Sprintf("%s;%s;%d", animationPath, palettePath, effect)
|
||||||
|
|
||||||
|
if animation, found := am.animations.Retrieve(cachePath); found {
|
||||||
|
return animation.(d2interface.Animation).Clone(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
animAsset, err := am.LoadAsset(animationPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
palette, err := am.LoadPalette(palettePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var animation d2interface.Animation
|
||||||
|
|
||||||
|
switch animAsset.Type() {
|
||||||
|
case types.AssetTypeDC6:
|
||||||
|
animation, err = am.createDC6Animation(animationPath, palette, effect)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case types.AssetTypeDCC:
|
||||||
|
animation, err = am.createDCCAnimation(animationPath, palette, effect)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unknown animation format for file: %s", animAsset.Path())
|
||||||
|
}
|
||||||
|
|
||||||
|
err = am.animations.Insert(cachePath, animation, defaultCacheEntryWeight)
|
||||||
|
|
||||||
|
return animation, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadComposite creates a composite object from a ObjectLookupRecord and palettePath describing it
|
// LoadComposite creates a composite object from a ObjectLookupRecord and palettePath describing it
|
||||||
|
@ -68,14 +141,209 @@ func (am *AssetManager) LoadComposite(baseType d2enum.ObjectType, token, palette
|
||||||
|
|
||||||
// LoadFont loads a font the resource files
|
// LoadFont loads a font the resource files
|
||||||
func (am *AssetManager) LoadFont(tablePath, spritePath, palettePath string) (d2interface.Font, error) {
|
func (am *AssetManager) LoadFont(tablePath, spritePath, palettePath string) (d2interface.Font, error) {
|
||||||
return am.fontManager.LoadFont(tablePath, spritePath, palettePath)
|
cachePath := fmt.Sprintf("%s;%s;%s", tablePath, spritePath, palettePath)
|
||||||
|
|
||||||
|
if cached, found := am.fonts.Retrieve(cachePath); found {
|
||||||
|
return cached.(d2interface.Font), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
sheet, err := am.LoadAnimation(spritePath, palettePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tableData, err := am.LoadFile(tablePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if string(tableData[:5]) != "Woo!\x01" {
|
||||||
|
return nil, fmt.Errorf("invalid font table format: %s", tablePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, maxCharHeight := sheet.GetFrameBounds()
|
||||||
|
|
||||||
|
glyphs := make(map[rune]fontGlyph)
|
||||||
|
|
||||||
|
for i := 12; i < len(tableData); i += 14 {
|
||||||
|
code := rune(binary.LittleEndian.Uint16(tableData[i : i+2]))
|
||||||
|
|
||||||
|
var glyph fontGlyph
|
||||||
|
glyph.frame = int(binary.LittleEndian.Uint16(tableData[i+8 : i+10]))
|
||||||
|
glyph.width = int(tableData[i+3])
|
||||||
|
glyph.height = maxCharHeight
|
||||||
|
|
||||||
|
glyphs[code] = glyph
|
||||||
|
}
|
||||||
|
|
||||||
|
font := &Font{
|
||||||
|
sheet: sheet,
|
||||||
|
glyphs: glyphs,
|
||||||
|
color: color.White,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = am.fonts.Insert(cachePath, font, defaultCacheEntryWeight)
|
||||||
|
|
||||||
|
return font, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadPalette loads a palette from a given palette path
|
// LoadPalette loads a palette from a given palette path
|
||||||
func (am *AssetManager) LoadPalette(palettePath string) (d2interface.Palette, error) {
|
func (am *AssetManager) LoadPalette(palettePath string) (d2interface.Palette, error) {
|
||||||
return am.paletteManager.LoadPalette(palettePath)
|
if cached, found := am.palettes.Retrieve(palettePath); found {
|
||||||
|
return cached.(d2interface.Palette), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
paletteAsset, err := am.LoadAsset(palettePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if paletteAsset.Type() != types.AssetTypePalette {
|
||||||
|
return nil, fmt.Errorf("not an instance of a palette: %s", palettePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := am.LoadFile(palettePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
palette, err := d2dat.Load(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = am.palettes.Insert(palettePath, palette, defaultCacheEntryWeight)
|
||||||
|
|
||||||
|
return palette, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadStringTable loads a string table from the given path
|
||||||
|
func (am *AssetManager) LoadStringTable(tablePath string) (d2tbl.TextDictionary, error) {
|
||||||
|
if cached, found := am.tables.Retrieve(tablePath); found {
|
||||||
|
return cached.(d2tbl.TextDictionary), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := am.LoadFile(tablePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
table := d2tbl.LoadTextDictionary(data)
|
||||||
|
if table != nil {
|
||||||
|
return nil, fmt.Errorf("table not found: %s", tablePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = am.tables.Insert(tablePath, table, defaultCacheEntryWeight)
|
||||||
|
|
||||||
|
return table, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadPaletteTransform loads a palette transform file
|
||||||
|
func (am *AssetManager) LoadPaletteTransform(path string) (*d2pl2.PL2, error) {
|
||||||
|
if pl2, found := am.transforms.Retrieve(path); found {
|
||||||
|
return pl2.(*d2pl2.PL2), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := am.LoadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pl2, err := d2pl2.Load(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := am.transforms.Insert(path, pl2, 1); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return pl2, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// createDC6Animation creates an Animation from d2dc6.DC6 and d2dat.DATPalette
|
||||||
|
func (am *AssetManager) createDC6Animation(dc6Path string,
|
||||||
|
palette d2interface.Palette, effect d2enum.DrawEffect) (d2interface.Animation, error) {
|
||||||
|
dc6, err := am.loadDC6(dc6Path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
anim := DC6Animation{
|
||||||
|
animation: animation{
|
||||||
|
directions: make([]animationDirection, dc6.Directions),
|
||||||
|
playLength: defaultPlayLength,
|
||||||
|
playLoop: true,
|
||||||
|
originAtBottom: true,
|
||||||
|
effect: effect,
|
||||||
|
},
|
||||||
|
dc6Path: dc6Path,
|
||||||
|
dc6: dc6,
|
||||||
|
palette: palette,
|
||||||
|
renderer: am.renderer,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = anim.SetDirection(0)
|
||||||
|
|
||||||
|
return &anim, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// createDCCAnimation creates an animation from d2dcc.DCC and d2dat.DATPalette
|
||||||
|
func (am *AssetManager) createDCCAnimation(dccPath string,
|
||||||
|
palette d2interface.Palette,
|
||||||
|
effect d2enum.DrawEffect) (d2interface.Animation, error) {
|
||||||
|
dcc, err := am.loadDCC(dccPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
anim := animation{
|
||||||
|
playLength: defaultPlayLength,
|
||||||
|
playLoop: true,
|
||||||
|
directions: make([]animationDirection, dcc.NumberOfDirections),
|
||||||
|
effect: effect,
|
||||||
|
}
|
||||||
|
|
||||||
|
DCC := DCCAnimation{
|
||||||
|
animation: anim,
|
||||||
|
AssetManager: am,
|
||||||
|
dccPath: dccPath,
|
||||||
|
palette: palette,
|
||||||
|
renderer: am.renderer,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = DCC.SetDirection(0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &DCC, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (am *AssetManager) loadDC6(path string) (*d2dc6.DC6, error) {
|
||||||
|
dc6Data, err := am.LoadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dc6, err := d2dc6.Load(dc6Data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return dc6, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (am *AssetManager) loadDCC(path string) (*d2dcc.DCC, error) {
|
||||||
|
dccData, err := am.LoadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return d2dcc.Load(dccData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindTerminalCommands binds the in-game terminal comands for the asset manager.
|
||||||
func (am *AssetManager) BindTerminalCommands(term d2interface.Terminal) error {
|
func (am *AssetManager) BindTerminalCommands(term d2interface.Terminal) error {
|
||||||
if err := term.BindAction("assetspam", "display verbose asset manager logs", func(verbose bool) {
|
if err := term.BindAction("assetspam", "display verbose asset manager logs", func(verbose bool) {
|
||||||
if verbose {
|
if verbose {
|
||||||
|
@ -84,11 +352,10 @@ func (am *AssetManager) BindTerminalCommands(term d2interface.Terminal) error {
|
||||||
term.OutputInfof("asset manager verbose logging disabled")
|
term.OutputInfof("asset manager verbose logging disabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
am.archiveManager.GetCache().SetVerbose(verbose)
|
am.palettes.SetVerbose(verbose)
|
||||||
am.archivedFileManager.GetCache().SetVerbose(verbose)
|
am.fonts.SetVerbose(verbose)
|
||||||
am.paletteManager.GetCache().SetVerbose(verbose)
|
am.transforms.SetVerbose(verbose)
|
||||||
am.paletteTransformManager.cache.SetVerbose(verbose)
|
am.animations.SetVerbose(verbose)
|
||||||
am.animationManager.GetCache().SetVerbose(verbose)
|
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -99,24 +366,19 @@ func (am *AssetManager) BindTerminalCommands(term d2interface.Terminal) error {
|
||||||
return float64(c.GetWeight()) / float64(c.GetBudget()) * percent
|
return float64(c.GetWeight()) / float64(c.GetBudget()) * percent
|
||||||
}
|
}
|
||||||
|
|
||||||
term.OutputInfof("archive cache: %f", cacheStatistics(am.archiveManager.GetCache()))
|
term.OutputInfof("palette cache: %f", cacheStatistics(am.palettes))
|
||||||
term.OutputInfof("file cache: %f", cacheStatistics(am.archivedFileManager.GetCache()))
|
term.OutputInfof("palette transform cache: %f", cacheStatistics(am.transforms))
|
||||||
term.OutputInfof("palette cache: %f", cacheStatistics(am.paletteManager.GetCache()))
|
term.OutputInfof("animation cache: %f", cacheStatistics(am.animations))
|
||||||
term.OutputInfof("palette transform cache: %f", cacheStatistics(am.paletteTransformManager.
|
term.OutputInfof("font cache: %f", cacheStatistics(am.fonts))
|
||||||
cache))
|
|
||||||
term.OutputInfof("animation cache: %f", cacheStatistics(am.animationManager.GetCache()))
|
|
||||||
term.OutputInfof("font cache: %f", cacheStatistics(am.fontManager.GetCache()))
|
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := term.BindAction("assetclear", "clear asset manager cache", func() {
|
if err := term.BindAction("assetclear", "clear asset manager cache", func() {
|
||||||
am.archiveManager.ClearCache()
|
am.palettes.Clear()
|
||||||
am.archivedFileManager.GetCache().Clear()
|
am.transforms.Clear()
|
||||||
am.paletteManager.ClearCache()
|
am.animations.Clear()
|
||||||
am.paletteTransformManager.cache.Clear()
|
am.fonts.Clear()
|
||||||
am.animationManager.ClearCache()
|
|
||||||
am.fontManager.ClearCache()
|
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,48 +3,26 @@ package d2asset
|
||||||
import (
|
import (
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2cache"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2cache"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||||
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2config"
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2config"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewAssetManager creates and assigns all necessary dependencies for the AssetManager top-level functions to work correctly
|
// NewAssetManager creates and assigns all necessary dependencies for the AssetManager top-level functions to work correctly
|
||||||
func NewAssetManager(renderer d2interface.Renderer,
|
func NewAssetManager(renderer d2interface.Renderer, config *d2config.Configuration,
|
||||||
term d2interface.Terminal) (*AssetManager, error) {
|
term d2interface.Terminal) (*AssetManager, error) {
|
||||||
|
manager := &AssetManager{
|
||||||
manager := &AssetManager{}
|
renderer,
|
||||||
|
d2loader.NewLoader(config),
|
||||||
manager.archiveManager = &archiveManager{
|
d2cache.CreateCache(animationBudget),
|
||||||
AssetManager: manager,
|
d2cache.CreateCache(tableBudget),
|
||||||
cache: d2cache.CreateCache(archiveBudget),
|
d2cache.CreateCache(fontBudget),
|
||||||
config: d2config.Config,
|
|
||||||
}
|
|
||||||
|
|
||||||
manager.archivedFileManager = &fileManager{
|
|
||||||
manager,
|
|
||||||
d2cache.CreateCache(fileBudget),
|
|
||||||
manager.archiveManager,
|
|
||||||
d2config.Config,
|
|
||||||
}
|
|
||||||
|
|
||||||
manager.paletteManager = &paletteManager{
|
|
||||||
manager,
|
|
||||||
d2cache.CreateCache(paletteBudget),
|
d2cache.CreateCache(paletteBudget),
|
||||||
}
|
|
||||||
|
|
||||||
manager.paletteTransformManager = &paletteTransformManager{
|
|
||||||
manager,
|
|
||||||
d2cache.CreateCache(paletteTransformBudget),
|
d2cache.CreateCache(paletteTransformBudget),
|
||||||
}
|
}
|
||||||
|
|
||||||
manager.animationManager = &animationManager{
|
|
||||||
AssetManager: manager,
|
|
||||||
renderer: renderer,
|
|
||||||
cache: d2cache.CreateCache(animationBudget),
|
|
||||||
}
|
|
||||||
|
|
||||||
manager.fontManager = &fontManager{manager, d2cache.CreateCache(fontBudget)}
|
|
||||||
|
|
||||||
if term != nil {
|
if term != nil {
|
||||||
return manager, manager.BindTerminalCommands(term)
|
err := manager.BindTerminalCommands(term)
|
||||||
|
return manager, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return manager, nil
|
return manager, nil
|
||||||
|
|
|
@ -3,22 +3,24 @@ package d2asset
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dc6"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||||
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dc6"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dcc"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dcc"
|
||||||
d2iface "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ d2iface.Animation = &DC6Animation{} // Static check to confirm struct conforms to interface
|
var _ d2interface.Animation = &DC6Animation{} // Static check to confirm struct conforms to
|
||||||
|
// interface
|
||||||
|
|
||||||
// DC6Animation is an animation made from a DC6 file
|
// DC6Animation is an animation made from a DC6 file
|
||||||
type DC6Animation struct {
|
type DC6Animation struct {
|
||||||
animation
|
animation
|
||||||
dc6Path string
|
dc6Path string
|
||||||
dc6 *d2dc6.DC6
|
dc6 *d2dc6.DC6
|
||||||
palette d2iface.Palette
|
palette d2interface.Palette
|
||||||
renderer d2iface.Renderer
|
renderer d2interface.Renderer
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDirection decodes and sets the direction
|
// SetDirection decodes and sets the direction
|
||||||
|
@ -56,7 +58,7 @@ func (a *DC6Animation) decodeDirection(directionIndex int) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
indexData := dc6.DecodeFrame(startFrame + i)
|
indexData := dc6.DecodeFrame(startFrame + i)
|
||||||
colorData := ImgIndexToRGBA(indexData, a.palette)
|
colorData := d2util.ImgIndexToRGBA(indexData, a.palette)
|
||||||
|
|
||||||
if err := sfc.ReplacePixels(colorData); err != nil {
|
if err := sfc.ReplacePixels(colorData); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -76,7 +78,7 @@ func (a *DC6Animation) decodeDirection(directionIndex int) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clone creates a copy of the animation
|
// Clone creates a copy of the animation
|
||||||
func (a *DC6Animation) Clone() d2iface.Animation {
|
func (a *DC6Animation) Clone() d2interface.Animation {
|
||||||
animation := *a
|
animation := *a
|
||||||
return &animation
|
return &animation
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,26 +4,29 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dcc"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dcc"
|
||||||
d2iface "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ d2iface.Animation = &DCCAnimation{} // Static check to confirm struct conforms to interface
|
var _ d2interface.Animation = &DCCAnimation{} // Static check to confirm struct conforms to
|
||||||
|
// interface
|
||||||
|
|
||||||
// DCCAnimation represents an animation decoded from DCC
|
// DCCAnimation represents an animation decoded from DCC
|
||||||
type DCCAnimation struct {
|
type DCCAnimation struct {
|
||||||
animation
|
animation
|
||||||
*animationManager
|
*AssetManager
|
||||||
dccPath string
|
dccPath string
|
||||||
palette d2iface.Palette
|
palette d2interface.Palette
|
||||||
renderer d2iface.Renderer
|
renderer d2interface.Renderer
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clone creates a copy of the animation
|
// Clone creates a copy of the animation
|
||||||
func (a *DCCAnimation) Clone() d2iface.Animation {
|
func (a *DCCAnimation) Clone() d2interface.Animation {
|
||||||
animation := *a
|
animation := *a
|
||||||
return &animation
|
return &animation
|
||||||
}
|
}
|
||||||
|
@ -71,7 +74,7 @@ func (a *DCCAnimation) decodeDirection(directionIndex int) error {
|
||||||
frameHeight := maxY - minY
|
frameHeight := maxY - minY
|
||||||
|
|
||||||
for _, dccFrame := range direction.Frames {
|
for _, dccFrame := range direction.Frames {
|
||||||
pixels := ImgIndexToRGBA(dccFrame.PixelData, a.palette)
|
pixels := d2util.ImgIndexToRGBA(dccFrame.PixelData, a.palette)
|
||||||
|
|
||||||
sfc, err := a.renderer.NewSurface(frameWidth, frameHeight, d2enum.FilterNearest)
|
sfc, err := a.renderer.NewSurface(frameWidth, frameHeight, d2enum.FilterNearest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -1,87 +0,0 @@
|
||||||
package d2asset
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"image/color"
|
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
fontBudget = 64
|
|
||||||
)
|
|
||||||
|
|
||||||
// Static checks to confirm struct conforms to interface
|
|
||||||
var _ d2interface.FontManager = &fontManager{}
|
|
||||||
var _ d2interface.Cacher = &fontManager{}
|
|
||||||
|
|
||||||
type fontManager struct {
|
|
||||||
*AssetManager
|
|
||||||
cache d2interface.Cache
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadFont loads a font from the archives managed by the ArchiveManager
|
|
||||||
func (fm *fontManager) LoadFont(tablePath, spritePath, palettePath string) (d2interface.Font,
|
|
||||||
error) {
|
|
||||||
cachePath := fmt.Sprintf("%s;%s;%s", tablePath, spritePath, palettePath)
|
|
||||||
if font, found := fm.cache.Retrieve(cachePath); found {
|
|
||||||
return font.(d2interface.Font), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
sheet, err := fm.LoadAnimation(spritePath, palettePath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := fm.LoadFile(tablePath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if string(data[:5]) != "Woo!\x01" {
|
|
||||||
return nil, errors.New("invalid font table format")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, maxCharHeight := sheet.GetFrameBounds()
|
|
||||||
|
|
||||||
glyphs := make(map[rune]fontGlyph)
|
|
||||||
|
|
||||||
for i := 12; i < len(data); i += 14 {
|
|
||||||
code := rune(binary.LittleEndian.Uint16(data[i : i+2]))
|
|
||||||
|
|
||||||
var glyph fontGlyph
|
|
||||||
glyph.frame = int(binary.LittleEndian.Uint16(data[i+8 : i+10]))
|
|
||||||
glyph.width = int(data[i+3])
|
|
||||||
glyph.height = maxCharHeight
|
|
||||||
|
|
||||||
glyphs[code] = glyph
|
|
||||||
}
|
|
||||||
|
|
||||||
font := &Font{
|
|
||||||
sheet: sheet,
|
|
||||||
glyphs: glyphs,
|
|
||||||
color: color.White,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := fm.cache.Insert(cachePath, font, 1); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return font, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClearCache clears the font cache
|
|
||||||
func (fm *fontManager) ClearCache() {
|
|
||||||
fm.cache.Clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCache returns the font managers cache
|
|
||||||
func (fm *fontManager) GetCache() d2interface.Cache {
|
|
||||||
return fm.cache
|
|
||||||
}
|
|
|
@ -1,52 +0,0 @@
|
||||||
package d2asset
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dat"
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Static checks to confirm struct conforms to interface
|
|
||||||
var _ d2interface.PaletteManager = &paletteManager{}
|
|
||||||
var _ d2interface.Cacher = &paletteManager{}
|
|
||||||
|
|
||||||
type paletteManager struct {
|
|
||||||
*AssetManager
|
|
||||||
cache d2interface.Cache
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
paletteBudget = 64
|
|
||||||
)
|
|
||||||
|
|
||||||
// LoadPalette loads a palette from archives managed by the ArchiveManager
|
|
||||||
func (pm *paletteManager) LoadPalette(palettePath string) (d2interface.Palette, error) {
|
|
||||||
if palette, found := pm.cache.Retrieve(palettePath); found {
|
|
||||||
return palette.(d2interface.Palette), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
paletteData, err := pm.LoadFile(palettePath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
palette, err := d2dat.Load(paletteData)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := pm.cache.Insert(palettePath, palette, 1); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return palette, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClearCache clears the palette cache
|
|
||||||
func (pm *paletteManager) ClearCache() {
|
|
||||||
pm.cache.Clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCache returns the palette managers cache
|
|
||||||
func (pm *paletteManager) GetCache() d2interface.Cache {
|
|
||||||
return pm.cache
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
package d2asset
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2pl2"
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
|
||||||
)
|
|
||||||
|
|
||||||
type paletteTransformManager struct {
|
|
||||||
*AssetManager
|
|
||||||
cache d2interface.Cache
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
paletteTransformBudget = 64
|
|
||||||
)
|
|
||||||
|
|
||||||
func (pm *paletteTransformManager) loadPaletteTransform(path string) (*d2pl2.PL2, error) {
|
|
||||||
if pl2, found := pm.cache.Retrieve(path); found {
|
|
||||||
return pl2.(*d2pl2.PL2), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := pm.LoadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pl2, err := d2pl2.Load(data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := pm.cache.Insert(path, pl2, 1); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return pl2, nil
|
|
||||||
}
|
|
|
@ -3,11 +3,12 @@ package d2maprenderer
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2ds1"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2ds1"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dt1"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dt1"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (mr *MapRenderer) generateTileCache() {
|
func (mr *MapRenderer) generateTileCache() {
|
||||||
|
@ -85,7 +86,7 @@ func (mr *MapRenderer) generateFloorCache(tile *d2ds1.FloorShadowRecord) {
|
||||||
image, _ := mr.renderer.NewSurface(int(tileData[i].Width), int(tileHeight), d2enum.FilterNearest)
|
image, _ := mr.renderer.NewSurface(int(tileData[i].Width), int(tileHeight), d2enum.FilterNearest)
|
||||||
indexData := make([]byte, tileData[i].Width*tileHeight)
|
indexData := make([]byte, tileData[i].Width*tileHeight)
|
||||||
d2dt1.DecodeTileGfxData(tileData[i].Blocks, &indexData, tileYOffset, tileData[i].Width)
|
d2dt1.DecodeTileGfxData(tileData[i].Blocks, &indexData, tileYOffset, tileData[i].Width)
|
||||||
pixels := d2asset.ImgIndexToRGBA(indexData, mr.palette)
|
pixels := d2util.ImgIndexToRGBA(indexData, mr.palette)
|
||||||
|
|
||||||
_ = image.ReplacePixels(pixels)
|
_ = image.ReplacePixels(pixels)
|
||||||
mr.setImageCacheRecord(tile.Style, tile.Sequence, 0, tileIndex, image)
|
mr.setImageCacheRecord(tile.Style, tile.Sequence, 0, tileIndex, image)
|
||||||
|
@ -127,7 +128,7 @@ func (mr *MapRenderer) generateShadowCache(tile *d2ds1.FloorShadowRecord) {
|
||||||
image, _ := mr.renderer.NewSurface(int(tileData.Width), tileHeight, d2enum.FilterNearest)
|
image, _ := mr.renderer.NewSurface(int(tileData.Width), tileHeight, d2enum.FilterNearest)
|
||||||
indexData := make([]byte, tileData.Width*int32(tileHeight))
|
indexData := make([]byte, tileData.Width*int32(tileHeight))
|
||||||
d2dt1.DecodeTileGfxData(tileData.Blocks, &indexData, tileYOffset, tileData.Width)
|
d2dt1.DecodeTileGfxData(tileData.Blocks, &indexData, tileYOffset, tileData.Width)
|
||||||
pixels := d2asset.ImgIndexToRGBA(indexData, mr.palette)
|
pixels := d2util.ImgIndexToRGBA(indexData, mr.palette)
|
||||||
_ = image.ReplacePixels(pixels)
|
_ = image.ReplacePixels(pixels)
|
||||||
mr.setImageCacheRecord(tile.Style, tile.Sequence, 13, tile.RandomIndex, image)
|
mr.setImageCacheRecord(tile.Style, tile.Sequence, 13, tile.RandomIndex, image)
|
||||||
}
|
}
|
||||||
|
@ -192,7 +193,7 @@ func (mr *MapRenderer) generateWallCache(tile *d2ds1.WallRecord) {
|
||||||
d2dt1.DecodeTileGfxData(newTileData.Blocks, &indexData, tileYOffset, 160)
|
d2dt1.DecodeTileGfxData(newTileData.Blocks, &indexData, tileYOffset, 160)
|
||||||
}
|
}
|
||||||
|
|
||||||
pixels := d2asset.ImgIndexToRGBA(indexData, mr.palette)
|
pixels := d2util.ImgIndexToRGBA(indexData, mr.palette)
|
||||||
|
|
||||||
if err := image.ReplacePixels(pixels); err != nil {
|
if err := image.ReplacePixels(pixels); err != nil {
|
||||||
log.Panicf(err.Error())
|
log.Panicf(err.Error())
|
||||||
|
|
|
@ -298,7 +298,7 @@ type SelectHeroClass struct {
|
||||||
// CreateSelectHeroClass creates an instance of a SelectHeroClass
|
// CreateSelectHeroClass creates an instance of a SelectHeroClass
|
||||||
func CreateSelectHeroClass(
|
func CreateSelectHeroClass(
|
||||||
navigator Navigator,
|
navigator Navigator,
|
||||||
manager *d2asset.AssetManager,
|
asset *d2asset.AssetManager,
|
||||||
renderer d2interface.Renderer,
|
renderer d2interface.Renderer,
|
||||||
audioProvider d2interface.AudioProvider,
|
audioProvider d2interface.AudioProvider,
|
||||||
ui *d2ui.UIManager,
|
ui *d2ui.UIManager,
|
||||||
|
@ -306,6 +306,7 @@ func CreateSelectHeroClass(
|
||||||
connectionHost string,
|
connectionHost string,
|
||||||
) *SelectHeroClass {
|
) *SelectHeroClass {
|
||||||
result := &SelectHeroClass{
|
result := &SelectHeroClass{
|
||||||
|
asset: asset,
|
||||||
heroRenderInfo: make(map[d2enum.Hero]*HeroRenderInfo),
|
heroRenderInfo: make(map[d2enum.Hero]*HeroRenderInfo),
|
||||||
selectedHero: d2enum.HeroNone,
|
selectedHero: d2enum.HeroNone,
|
||||||
connectionType: connectionType,
|
connectionType: connectionType,
|
||||||
|
|
|
@ -379,7 +379,7 @@ func (g *GameControls) OnMouseButtonDown(event d2interface.MouseEvent) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load loads the resources required for the GameControls
|
// Load the resources required for the GameControls
|
||||||
func (g *GameControls) Load() {
|
func (g *GameControls) Load() {
|
||||||
animation, _ := g.asset.LoadAnimation(d2resource.GameGlobeOverlap, d2resource.PaletteSky)
|
animation, _ := g.asset.LoadAnimation(d2resource.GameGlobeOverlap, d2resource.PaletteSky)
|
||||||
g.globeSprite, _ = g.uiManager.NewSprite(animation)
|
g.globeSprite, _ = g.uiManager.NewSprite(animation)
|
||||||
|
|
|
@ -76,7 +76,7 @@ func NewHeroStatsPanel(asset *d2asset.AssetManager, ui *d2ui.UIManager, heroName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load loads the data for the hero status panel
|
// Load the data for the hero status panel
|
||||||
func (s *HeroStatsPanel) Load() {
|
func (s *HeroStatsPanel) Load() {
|
||||||
animation, _ := s.asset.LoadAnimation(d2resource.Frame, d2resource.PaletteSky)
|
animation, _ := s.asset.LoadAnimation(d2resource.Frame, d2resource.PaletteSky)
|
||||||
s.frame, _ = s.uiManager.NewSprite(animation)
|
s.frame, _ = s.uiManager.NewSprite(animation)
|
||||||
|
|
|
@ -69,7 +69,7 @@ func (g *Inventory) Close() {
|
||||||
g.isOpen = false
|
g.isOpen = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load loads the resources required by the inventory
|
// Load the resources required by the inventory
|
||||||
func (g *Inventory) Load() {
|
func (g *Inventory) Load() {
|
||||||
animation, _ := g.asset.LoadAnimation(d2resource.Frame, d2resource.PaletteSky)
|
animation, _ := g.asset.LoadAnimation(d2resource.Frame, d2resource.PaletteSky)
|
||||||
g.frame, _ = g.uiManager.NewSprite(animation)
|
g.frame, _ = g.uiManager.NewSprite(animation)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user