mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2025-02-20 07:27:19 -05: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"
|
||||
"sync"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2tbl"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
|
||||
|
||||
"github.com/pkg/profile"
|
||||
"golang.org/x/image/colornames"
|
||||
"gopkg.in/alecthomas/kingpin.v2"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data"
|
||||
"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/d2math"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2config"
|
||||
"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
|
||||
func (v *MPQ) ReadFileStream(fileName string) (d2interface.ArchiveDataStream, error) {
|
||||
func (v *MPQ) ReadFileStream(fileName string) (d2interface.DataStream, error) {
|
||||
fileBlockData, err := v.getFileBlockData(fileName)
|
||||
|
||||
if err != nil {
|
||||
|
@ -2,7 +2,7 @@ package d2mpq
|
||||
|
||||
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.
|
||||
type MpqDataStream struct {
|
||||
|
@ -7,6 +7,9 @@ import (
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2datautils"
|
||||
)
|
||||
|
||||
// TextDictionary is a string map
|
||||
type TextDictionary map[string]string
|
||||
|
||||
type textDictionaryHashEntry struct {
|
||||
IsActive bool
|
||||
Index uint16
|
||||
@ -16,7 +19,7 @@ type textDictionaryHashEntry struct {
|
||||
NameLength uint16
|
||||
}
|
||||
|
||||
var lookupTable map[string]string //nolint:gochecknoglobals // currently global by design
|
||||
var lookupTable TextDictionary //nolint:gochecknoglobals // currently global by design
|
||||
|
||||
const (
|
||||
crcByteCount = 2
|
||||
@ -35,9 +38,9 @@ func TranslateString(key string) string {
|
||||
}
|
||||
|
||||
// LoadTextDictionary loads the text dictionary from the given data
|
||||
func LoadTextDictionary(dictionaryData []byte) map[string]string {
|
||||
func LoadTextDictionary(dictionaryData []byte) TextDictionary {
|
||||
if lookupTable == nil {
|
||||
lookupTable = make(map[string]string)
|
||||
lookupTable = make(TextDictionary)
|
||||
}
|
||||
|
||||
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()
|
||||
FileExists(fileName string) bool
|
||||
ReadFile(fileName string) ([]byte, error)
|
||||
ReadFileStream(fileName string) (ArchiveDataStream, error)
|
||||
ReadFileStream(fileName string) (DataStream, error)
|
||||
ReadTextFile(fileName string) (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
|
||||
|
||||
// ArchiveDataStream is an archive data stream
|
||||
type ArchiveDataStream interface {
|
||||
// DataStream is a data stream
|
||||
type DataStream interface {
|
||||
Read(p []byte) (n int, err error)
|
||||
Seek(offset int64, whence int) (int64, 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
|
||||
type Asset interface {
|
||||
fmt.Stringer
|
||||
io.ReadSeeker
|
||||
io.Reader
|
||||
io.Seeker
|
||||
io.Closer
|
||||
Type() types.AssetType
|
||||
Source() Source
|
||||
Path() string
|
||||
Data() ([]byte, error)
|
||||
}
|
||||
|
@ -1,6 +1,11 @@
|
||||
package types
|
||||
|
||||
import "strings"
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2mpq"
|
||||
)
|
||||
|
||||
// SourceType represents the type of the asset source
|
||||
type SourceType int
|
||||
@ -27,3 +32,16 @@ func Ext2SourceType(ext string) SourceType {
|
||||
|
||||
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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/asset"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/asset/types"
|
||||
)
|
||||
|
||||
const (
|
||||
bufLength = 32
|
||||
)
|
||||
|
||||
// static check that Asset implements Asset
|
||||
var _ asset.Asset = &Asset{}
|
||||
|
||||
@ -14,6 +19,7 @@ var _ asset.Asset = &Asset{}
|
||||
type Asset struct {
|
||||
assetType types.AssetType
|
||||
source *Source
|
||||
data []byte
|
||||
path string
|
||||
file *os.File
|
||||
}
|
||||
@ -43,6 +49,44 @@ func (fsa *Asset) Seek(offset int64, whence int) (int64, error) {
|
||||
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
|
||||
func (fsa *Asset) String() string {
|
||||
return fsa.Path()
|
||||
|
@ -1,11 +1,10 @@
|
||||
package d2loader
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2cache"
|
||||
"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/filesystem"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/mpq"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2config"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultCacheBudget = 1024 * 1024 * 512
|
||||
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
|
||||
func NewLoader() *Loader {
|
||||
loader := &Loader{}
|
||||
func NewLoader(config *d2config.Configuration) *Loader {
|
||||
loader := &Loader{
|
||||
config: config,
|
||||
}
|
||||
|
||||
loader.Cache = d2cache.CreateCache(defaultCacheBudget)
|
||||
|
||||
loader.initFromConfig()
|
||||
|
||||
return loader
|
||||
}
|
||||
|
||||
// Loader represents the manager that handles loading and caching assets with the asset Sources
|
||||
// that have been added
|
||||
type Loader struct {
|
||||
config *d2config.Configuration
|
||||
d2interface.Cache
|
||||
*d2util.Logger
|
||||
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
|
||||
// of each asset source root (regardless of the type of asset source)
|
||||
func (l *Loader) Load(subPath string) (asset.Asset, error) {
|
||||
lang := defaultLanguage
|
||||
|
||||
if l.config != nil {
|
||||
lang = l.config.Language
|
||||
}
|
||||
|
||||
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
|
||||
if cached, found := l.Retrieve(subPath); found {
|
||||
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
|
||||
@ -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
|
||||
@ -89,8 +133,7 @@ func (l *Loader) AddSource(path string) (asset.Source, error) {
|
||||
}
|
||||
|
||||
if mode.IsRegular() {
|
||||
ext := filepath.Ext(cleanPath)
|
||||
sourceType = types.Ext2SourceType(ext)
|
||||
sourceType = types.CheckSourceType(cleanPath)
|
||||
}
|
||||
|
||||
switch sourceType {
|
||||
|
@ -17,12 +17,13 @@ const (
|
||||
exclusiveB = "exclusive_b.txt"
|
||||
exclusiveC = "exclusive_c.txt"
|
||||
exclusiveD = "exclusive_d.txt"
|
||||
subdirCommonD = "dir\\common.txt"
|
||||
badSourcePath = "/x/y/z.mpq"
|
||||
badFilePath = "a/bad/file/path.txt"
|
||||
)
|
||||
|
||||
func TestLoader_NewLoader(t *testing.T) {
|
||||
loader := NewLoader()
|
||||
loader := NewLoader(nil)
|
||||
|
||||
if loader.Cache == nil {
|
||||
t.Error("loader should not be nil")
|
||||
@ -30,7 +31,7 @@ func TestLoader_NewLoader(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLoader_AddSource(t *testing.T) {
|
||||
loader := NewLoader()
|
||||
loader := NewLoader(nil)
|
||||
|
||||
sourceA, errA := loader.AddSource(sourcePathA)
|
||||
sourceB, errB := loader.AddSource(sourcePathB)
|
||||
@ -80,7 +81,7 @@ func TestLoader_AddSource(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(sourcePathD)
|
||||
@ -93,6 +94,7 @@ func TestLoader_Load(t *testing.T) {
|
||||
entryB, errB := loader.Load(exclusiveB)
|
||||
entryC, errC := loader.Load(exclusiveC)
|
||||
entryD, errD := loader.Load(exclusiveD)
|
||||
entryDsubdir, errDsubdir := loader.Load(subdirCommonD)
|
||||
|
||||
_, 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")
|
||||
}
|
||||
|
||||
if errDsubdir != nil {
|
||||
t.Error("mpq subdir entry not found")
|
||||
}
|
||||
|
||||
if expectedError == nil {
|
||||
t.Error("expected Error for nonexistant file path")
|
||||
}
|
||||
@ -123,6 +129,7 @@ func TestLoader_Load(t *testing.T) {
|
||||
{entryB, "b"},
|
||||
{entryC, "c"},
|
||||
{entryD, "d"},
|
||||
{entryDsubdir, "d"},
|
||||
}
|
||||
|
||||
for idx := range tests {
|
||||
|
@ -1,6 +1,8 @@
|
||||
package mpq
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
@ -8,12 +10,17 @@ import (
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/asset/types"
|
||||
)
|
||||
|
||||
const (
|
||||
bufLength = 32
|
||||
)
|
||||
|
||||
// static check that Asset implements Asset
|
||||
var _ asset.Asset = &Asset{}
|
||||
|
||||
// Asset represents a file record within an MPQ archive
|
||||
type Asset struct {
|
||||
stream d2interface.ArchiveDataStream
|
||||
stream d2interface.DataStream
|
||||
data []byte
|
||||
path string
|
||||
source *Source
|
||||
}
|
||||
@ -35,7 +42,12 @@ func (a *Asset) Path() string {
|
||||
|
||||
// Read will read asset data into the given buffer
|
||||
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
|
||||
@ -43,7 +55,50 @@ func (a *Asset) Seek(offset int64, whence int) (n int64, err error) {
|
||||
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 {
|
||||
return a.Path()
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
package mpq
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2mpq"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
"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
|
||||
func (v *Source) Open(name string) (a asset.Asset, err error) {
|
||||
name = cleanName(name)
|
||||
stream, err := v.MPQ.ReadFileStream(name)
|
||||
|
||||
if err != nil {
|
||||
@ -41,12 +44,13 @@ func (v *Source) Open(name string) (a asset.Asset, err error) {
|
||||
a = &Asset{
|
||||
source: v,
|
||||
stream: stream,
|
||||
path: name,
|
||||
}
|
||||
|
||||
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 {
|
||||
return v.MPQ.Path()
|
||||
}
|
||||
@ -55,3 +59,13 @@ func (v *Source) Path() string {
|
||||
func (v *Source) String() string {
|
||||
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"
|
||||
|
@ -7,11 +7,9 @@ import (
|
||||
"math"
|
||||
|
||||
"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/d2interface"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math"
|
||||
)
|
||||
|
||||
type playMode int
|
||||
@ -30,7 +28,7 @@ type animationFrame struct {
|
||||
offsetX int
|
||||
offsetY int
|
||||
|
||||
image d2iface.Surface
|
||||
image d2interface.Surface
|
||||
}
|
||||
|
||||
type animationDirection struct {
|
||||
@ -114,7 +112,7 @@ func (a *animation) Advance(elapsed float64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *animation) renderShadow(target d2iface.Surface) error {
|
||||
func (a *animation) renderShadow(target d2interface.Surface) error {
|
||||
direction := a.directions[a.directionIndex]
|
||||
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
|
||||
func (a *animation) Render(target d2iface.Surface) error {
|
||||
func (a *animation) Render(target d2interface.Surface) error {
|
||||
direction := a.directions[a.directionIndex]
|
||||
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
|
||||
func (a *animation) RenderFromOrigin(target d2iface.Surface, shadow bool) error {
|
||||
func (a *animation) RenderFromOrigin(target d2interface.Surface, shadow bool) error {
|
||||
if a.originAtBottom {
|
||||
direction := a.directions[a.directionIndex]
|
||||
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
|
||||
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]
|
||||
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
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"image/color"
|
||||
"log"
|
||||
|
||||
"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/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
|
||||
type AssetManager struct {
|
||||
archiveManager d2interface.ArchiveManager
|
||||
archivedFileManager d2interface.FileManager
|
||||
paletteManager d2interface.PaletteManager
|
||||
paletteTransformManager *paletteTransformManager
|
||||
animationManager d2interface.AnimationManager
|
||||
fontManager d2interface.FontManager
|
||||
renderer d2interface.Renderer
|
||||
loader *d2loader.Loader
|
||||
tables d2interface.Cache
|
||||
animations d2interface.Cache
|
||||
fonts d2interface.Cache
|
||||
palettes d2interface.Cache
|
||||
transforms d2interface.Cache
|
||||
}
|
||||
|
||||
// LoadFileStream streams an MPQ file from a source file path
|
||||
func (am *AssetManager) LoadFileStream(filePath string) (d2interface.ArchiveDataStream, error) {
|
||||
data, err := am.archivedFileManager.LoadFileStream(filePath)
|
||||
// LoadAsset loads an asset
|
||||
func (am *AssetManager) LoadAsset(filePath string) (asset.Asset, error) {
|
||||
data, err := am.loader.Load(filePath)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
func (am *AssetManager) LoadFile(filePath string) ([]byte, error) {
|
||||
data, err := am.archivedFileManager.LoadFile(filePath)
|
||||
fileAsset, err := am.LoadAsset(filePath)
|
||||
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
|
||||
@ -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.
|
||||
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
|
||||
@ -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
|
||||
func (am *AssetManager) LoadAnimationWithEffect(animationPath, palettePath string,
|
||||
drawEffect d2enum.DrawEffect) (d2interface.Animation, error) {
|
||||
return am.animationManager.LoadAnimation(animationPath, palettePath, drawEffect)
|
||||
effect d2enum.DrawEffect) (d2interface.Animation, error) {
|
||||
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
|
||||
@ -68,14 +141,209 @@ func (am *AssetManager) LoadComposite(baseType d2enum.ObjectType, token, palette
|
||||
|
||||
// LoadFont loads a font the resource files
|
||||
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
|
||||
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 {
|
||||
if err := term.BindAction("assetspam", "display verbose asset manager logs", func(verbose bool) {
|
||||
if verbose {
|
||||
@ -84,11 +352,10 @@ func (am *AssetManager) BindTerminalCommands(term d2interface.Terminal) error {
|
||||
term.OutputInfof("asset manager verbose logging disabled")
|
||||
}
|
||||
|
||||
am.archiveManager.GetCache().SetVerbose(verbose)
|
||||
am.archivedFileManager.GetCache().SetVerbose(verbose)
|
||||
am.paletteManager.GetCache().SetVerbose(verbose)
|
||||
am.paletteTransformManager.cache.SetVerbose(verbose)
|
||||
am.animationManager.GetCache().SetVerbose(verbose)
|
||||
am.palettes.SetVerbose(verbose)
|
||||
am.fonts.SetVerbose(verbose)
|
||||
am.transforms.SetVerbose(verbose)
|
||||
am.animations.SetVerbose(verbose)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -99,24 +366,19 @@ func (am *AssetManager) BindTerminalCommands(term d2interface.Terminal) error {
|
||||
return float64(c.GetWeight()) / float64(c.GetBudget()) * percent
|
||||
}
|
||||
|
||||
term.OutputInfof("archive cache: %f", cacheStatistics(am.archiveManager.GetCache()))
|
||||
term.OutputInfof("file cache: %f", cacheStatistics(am.archivedFileManager.GetCache()))
|
||||
term.OutputInfof("palette cache: %f", cacheStatistics(am.paletteManager.GetCache()))
|
||||
term.OutputInfof("palette transform cache: %f", cacheStatistics(am.paletteTransformManager.
|
||||
cache))
|
||||
term.OutputInfof("animation cache: %f", cacheStatistics(am.animationManager.GetCache()))
|
||||
term.OutputInfof("font cache: %f", cacheStatistics(am.fontManager.GetCache()))
|
||||
term.OutputInfof("palette cache: %f", cacheStatistics(am.palettes))
|
||||
term.OutputInfof("palette transform cache: %f", cacheStatistics(am.transforms))
|
||||
term.OutputInfof("animation cache: %f", cacheStatistics(am.animations))
|
||||
term.OutputInfof("font cache: %f", cacheStatistics(am.fonts))
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := term.BindAction("assetclear", "clear asset manager cache", func() {
|
||||
am.archiveManager.ClearCache()
|
||||
am.archivedFileManager.GetCache().Clear()
|
||||
am.paletteManager.ClearCache()
|
||||
am.paletteTransformManager.cache.Clear()
|
||||
am.animationManager.ClearCache()
|
||||
am.fontManager.ClearCache()
|
||||
am.palettes.Clear()
|
||||
am.transforms.Clear()
|
||||
am.animations.Clear()
|
||||
am.fonts.Clear()
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -3,48 +3,26 @@ package d2asset
|
||||
import (
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2cache"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2config"
|
||||
)
|
||||
|
||||
// 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) {
|
||||
|
||||
manager := &AssetManager{}
|
||||
|
||||
manager.archiveManager = &archiveManager{
|
||||
AssetManager: manager,
|
||||
cache: d2cache.CreateCache(archiveBudget),
|
||||
config: d2config.Config,
|
||||
}
|
||||
|
||||
manager.archivedFileManager = &fileManager{
|
||||
manager,
|
||||
d2cache.CreateCache(fileBudget),
|
||||
manager.archiveManager,
|
||||
d2config.Config,
|
||||
}
|
||||
|
||||
manager.paletteManager = &paletteManager{
|
||||
manager,
|
||||
manager := &AssetManager{
|
||||
renderer,
|
||||
d2loader.NewLoader(config),
|
||||
d2cache.CreateCache(animationBudget),
|
||||
d2cache.CreateCache(tableBudget),
|
||||
d2cache.CreateCache(fontBudget),
|
||||
d2cache.CreateCache(paletteBudget),
|
||||
}
|
||||
|
||||
manager.paletteTransformManager = &paletteTransformManager{
|
||||
manager,
|
||||
d2cache.CreateCache(paletteTransformBudget),
|
||||
}
|
||||
|
||||
manager.animationManager = &animationManager{
|
||||
AssetManager: manager,
|
||||
renderer: renderer,
|
||||
cache: d2cache.CreateCache(animationBudget),
|
||||
}
|
||||
|
||||
manager.fontManager = &fontManager{manager, d2cache.CreateCache(fontBudget)}
|
||||
|
||||
if term != nil {
|
||||
return manager, manager.BindTerminalCommands(term)
|
||||
err := manager.BindTerminalCommands(term)
|
||||
return manager, err
|
||||
}
|
||||
|
||||
return manager, nil
|
||||
|
@ -3,22 +3,24 @@ package d2asset
|
||||
import (
|
||||
"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/d2fileformats/d2dc6"
|
||||
"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
|
||||
type DC6Animation struct {
|
||||
animation
|
||||
dc6Path string
|
||||
dc6 *d2dc6.DC6
|
||||
palette d2iface.Palette
|
||||
renderer d2iface.Renderer
|
||||
palette d2interface.Palette
|
||||
renderer d2interface.Renderer
|
||||
}
|
||||
|
||||
// SetDirection decodes and sets the direction
|
||||
@ -56,7 +58,7 @@ func (a *DC6Animation) decodeDirection(directionIndex int) error {
|
||||
}
|
||||
|
||||
indexData := dc6.DecodeFrame(startFrame + i)
|
||||
colorData := ImgIndexToRGBA(indexData, a.palette)
|
||||
colorData := d2util.ImgIndexToRGBA(indexData, a.palette)
|
||||
|
||||
if err := sfc.ReplacePixels(colorData); err != nil {
|
||||
return err
|
||||
@ -76,7 +78,7 @@ func (a *DC6Animation) decodeDirection(directionIndex int) error {
|
||||
}
|
||||
|
||||
// Clone creates a copy of the animation
|
||||
func (a *DC6Animation) Clone() d2iface.Animation {
|
||||
func (a *DC6Animation) Clone() d2interface.Animation {
|
||||
animation := *a
|
||||
return &animation
|
||||
}
|
||||
|
@ -4,26 +4,29 @@ import (
|
||||
"errors"
|
||||
"math"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math"
|
||||
|
||||
"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
|
||||
type DCCAnimation struct {
|
||||
animation
|
||||
*animationManager
|
||||
*AssetManager
|
||||
dccPath string
|
||||
palette d2iface.Palette
|
||||
renderer d2iface.Renderer
|
||||
palette d2interface.Palette
|
||||
renderer d2interface.Renderer
|
||||
}
|
||||
|
||||
// Clone creates a copy of the animation
|
||||
func (a *DCCAnimation) Clone() d2iface.Animation {
|
||||
func (a *DCCAnimation) Clone() d2interface.Animation {
|
||||
animation := *a
|
||||
return &animation
|
||||
}
|
||||
@ -71,7 +74,7 @@ func (a *DCCAnimation) decodeDirection(directionIndex int) error {
|
||||
frameHeight := maxY - minY
|
||||
|
||||
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)
|
||||
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 (
|
||||
"log"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2ds1"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dt1"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
|
||||
)
|
||||
|
||||
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)
|
||||
indexData := make([]byte, tileData[i].Width*tileHeight)
|
||||
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)
|
||||
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)
|
||||
indexData := make([]byte, tileData.Width*int32(tileHeight))
|
||||
d2dt1.DecodeTileGfxData(tileData.Blocks, &indexData, tileYOffset, tileData.Width)
|
||||
pixels := d2asset.ImgIndexToRGBA(indexData, mr.palette)
|
||||
pixels := d2util.ImgIndexToRGBA(indexData, mr.palette)
|
||||
_ = image.ReplacePixels(pixels)
|
||||
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)
|
||||
}
|
||||
|
||||
pixels := d2asset.ImgIndexToRGBA(indexData, mr.palette)
|
||||
pixels := d2util.ImgIndexToRGBA(indexData, mr.palette)
|
||||
|
||||
if err := image.ReplacePixels(pixels); err != nil {
|
||||
log.Panicf(err.Error())
|
||||
|
@ -298,7 +298,7 @@ type SelectHeroClass struct {
|
||||
// CreateSelectHeroClass creates an instance of a SelectHeroClass
|
||||
func CreateSelectHeroClass(
|
||||
navigator Navigator,
|
||||
manager *d2asset.AssetManager,
|
||||
asset *d2asset.AssetManager,
|
||||
renderer d2interface.Renderer,
|
||||
audioProvider d2interface.AudioProvider,
|
||||
ui *d2ui.UIManager,
|
||||
@ -306,6 +306,7 @@ func CreateSelectHeroClass(
|
||||
connectionHost string,
|
||||
) *SelectHeroClass {
|
||||
result := &SelectHeroClass{
|
||||
asset: asset,
|
||||
heroRenderInfo: make(map[d2enum.Hero]*HeroRenderInfo),
|
||||
selectedHero: d2enum.HeroNone,
|
||||
connectionType: connectionType,
|
||||
|
@ -379,7 +379,7 @@ func (g *GameControls) OnMouseButtonDown(event d2interface.MouseEvent) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Load loads the resources required for the GameControls
|
||||
// Load the resources required for the GameControls
|
||||
func (g *GameControls) Load() {
|
||||
animation, _ := g.asset.LoadAnimation(d2resource.GameGlobeOverlap, d2resource.PaletteSky)
|
||||
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() {
|
||||
animation, _ := s.asset.LoadAnimation(d2resource.Frame, d2resource.PaletteSky)
|
||||
s.frame, _ = s.uiManager.NewSprite(animation)
|
||||
|
@ -69,7 +69,7 @@ func (g *Inventory) Close() {
|
||||
g.isOpen = false
|
||||
}
|
||||
|
||||
// Load loads the resources required by the inventory
|
||||
// Load the resources required by the inventory
|
||||
func (g *Inventory) Load() {
|
||||
animation, _ := g.asset.LoadAnimation(d2resource.Frame, d2resource.PaletteSky)
|
||||
g.frame, _ = g.uiManager.NewSprite(animation)
|
||||
|
Loading…
x
Reference in New Issue
Block a user